diff --git a/CHANGELOGS/2026-10-03-cact-deploy-playbook.md b/CHANGELOGS/2026-10-03-cact-deploy-playbook.md new file mode 100644 index 0000000000..41bd754215 --- /dev/null +++ b/CHANGELOGS/2026-10-03-cact-deploy-playbook.md @@ -0,0 +1,24 @@ +# 2026-10-03 — CACT server deploy playbook + +## Summary + +Added an operator playbook for deploying the CloudAxe Coder Team (CACT) on a new Linux server: headless OpenCode on port 4096, the `cact-*` agent pack, provider auth, systemd, firewall/TLS notes, health checks, desktop connection, and upgrade/rollback. + +## Changes + +### Added + +- [`docs/cact-server-playbook.md`](../docs/cact-server-playbook.md) — repeatable runbook with placeholders only (no live secrets). Covers: + - Target shape (`opencode serve`, basic auth, XDG paths) + - Host bootstrap (packages, service user, fork install) + - CACT agent inventory and provider mapping + - OpenRouter callout for `cact-documentation-agent` + - Secrets (`server.env`, `auth.json`) + - systemd unit and rollback + - Network hardening and optional Nginx TLS + - Verification via `/global/health`, `/api/location`, and CloudAxe Desktop + - Upgrade snapshot procedure and ops checklist + +## Scope + +Documentation only. No runtime, agent-definition, or config code changes in this change set. diff --git a/docs/cact-server-playbook.md b/docs/cact-server-playbook.md new file mode 100644 index 0000000000..944369c11b --- /dev/null +++ b/docs/cact-server-playbook.md @@ -0,0 +1,414 @@ +# CACT server deploy playbook + +Repeatable runbook for standing up the **CloudAxe Coder Team (CACT)** on a new Linux server: headless OpenCode, the `cact-*` agent pack, provider credentials, systemd supervision, and a CloudAxe desktop connection check. + +Use placeholders only. Do not commit live IPs, passwords, or API keys. + +## What you are deploying + +| Piece | Role | +| --- | --- | +| OpenCode headless server | `opencode serve` HTTP API on port `4096` | +| CACT agents | `~/.config/opencode/agents/cact-*.md` | +| Global rules | `~/.config/opencode/AGENTS.md` | +| Provider auth | `~/.local/share/opencode/auth.json` (never in git) | +| Clients | CloudAxe / OpenCode Desktop (Settings → Add Server), or `curl` / SDK | + +`cact-` means **CloudAxe Coder Team**. The normal entry agent is `cact-orchestrator-tech-lead`. + +Paths follow XDG defaults (`xdg-basedir`): + +| Kind | Default path (Linux) | +| --- | --- | +| Config | `~/.config/opencode` | +| Data (auth, logs) | `~/.local/share/opencode` | +| State | `~/.local/state/opencode` | +| Cache | `~/.cache/opencode` | + +Override the config directory with `OPENCODE_CONFIG_DIR` only when you intentionally isolate installs. + +## Assumptions + +- Target OS: Ubuntu 22.04/24.04 or Debian 12 (amd64 or arm64). +- You have SSH access and can use `sudo`. +- You will use a **dedicated service user** (example: `opencode`). +- The CloudAxe desktop expects the **same OpenCode build** as this fork (v2 API). Do not deploy an unrelated older package and assume compatibility. +- Provider policy: **direct vendor APIs** (Moonshot, DeepSeek, Z.ai). Avoid OpenRouter for CACT agents. + +## Pre-flight + +Before changing anything on the host: + +1. Confirm access scope (sudo / root). +2. Name the environment: `dev` / `staging` / `production`. +3. Note existing listeners on `4096` (`ss -tlnp | grep 4096`). +4. Decide whether port `4096` is exposed raw or only via Nginx TLS on `443`. +5. Prepare a rollback posture: previous binary path, config tarball location, and how to stop the unit. + +Destructive classification for a first install: **risky** (new systemd unit, firewall rules, public listener). Treat password/key rotation and unit replacement on a live host as **destructive** unless a verified backup and rollback path exist. + +--- + +## 1. Target shape + +```text +Internet / operator LAN + │ + ▼ + [optional Nginx TLS :443] + │ + ▼ + opencode serve + --hostname 0.0.0.0 # or 127.0.0.1 behind Nginx + --port 4096 + + OPENCODE_SERVER_PASSWORD + + OPENCODE_SERVER_USERNAME (optional; default opencode) + │ + ▼ + ~/.config/opencode/agents/cact-*.md + ~/.local/share/opencode/auth.json +``` + +CLI flags (see `packages/opencode/src/cli/cmd/serve.ts` and `packages/opencode/src/cli/network.ts`): + +| Flag | Purpose | Typical CACT value | +| --- | --- | --- | +| `--port` | Listen port | `4096` | +| `--hostname` | Bind address | `0.0.0.0` (direct) or `127.0.0.1` (behind proxy) | +| `--cors` | Extra browser origins | Desktop / app origins as needed | +| `--mdns` | LAN discovery | **off** on a VPS | + +If `OPENCODE_SERVER_PASSWORD` is unset, the server starts **unsecured** and prints a warning. Always set it for any network-reachable host. + +--- + +## 2. Host bootstrap + +Run as a sudo-capable operator. + +### 2.1 Packages + +```bash +sudo apt-get update +sudo apt-get install -y git curl ca-certificates ripgrep build-essential unzip +``` + +Install Bun (required to build/run from this repo): + +```bash +curl -fsSL https://bun.sh/install | bash +# ensure ~/.bun/bin is on PATH for the service user as well +``` + +### 2.2 Service user + +```bash +sudo useradd --system --create-home --shell /bin/bash opencode +sudo -u opencode -H bash -lc 'mkdir -p ~/.config/opencode/agents ~/.local/share/opencode' +``` + +### 2.3 Clone and install this fork + +Replace `YOUR_FORK_URL` and pin a known-good ref (tag or commit SHA). + +```bash +sudo -u opencode -H bash <<'EOF' +set -euo pipefail +cd ~ +git clone YOUR_FORK_URL opencode +cd opencode +git checkout YOUR_PINNED_REF +bun install +cd packages/opencode +bun run build # or the package's documented production build script +EOF +``` + +Install a durable binary on `PATH` for the service user (example: copy the built CLI into `~/bin`): + +```bash +sudo -u opencode -H bash <<'EOF' +set -euo pipefail +mkdir -p "$HOME/bin" +# Adjust source path to match the build output for your platform +cp -f "$HOME/opencode/packages/opencode/dist/opencode-linux-"*/bin/opencode "$HOME/bin/opencode" +chmod 755 "$HOME/bin/opencode" +"$HOME/bin/opencode" --version +EOF +``` + +Record the version string in your ops notes. The CloudAxe desktop must speak the same API generation as this server. + +Alternative: install a prebuilt musl binary that matches this fork’s release artifacts (see `packages/opencode/Dockerfile`), then still deploy CACT config as below. + +--- + +## 3. Deploy the CACT agent pack + +Copy from a trusted workstation or private backup into the service user’s config. Source of truth on the operator machine is typically: + +```text +~/.config/opencode/AGENTS.md +~/.config/opencode/agents/cact-*.md +``` + +On the server: + +```bash +# From an operator machine (example) +scp AGENTS.md opencode@SERVER:~/.config/opencode/ +scp cact-*.md opencode@SERVER:~/.config/opencode/agents/ +``` + +Expected agents: + +| Agent file | Model (frontmatter) | Provider | +| --- | --- | --- | +| `cact-orchestrator-tech-lead.md` | `moonshotai/kimi-k3` | Moonshot | +| `cact-frontend-agent.md` | `moonshotai/kimi-k3` | Moonshot | +| `cact-backend-agent.md` | `deepseek/deepseek-v4-pro` | DeepSeek | +| `cact-database-agent.md` | `deepseek/deepseek-v4-pro` | DeepSeek | +| `cact-devops-server-engineer.md` | `deepseek/deepseek-v4-pro` | DeepSeek | +| `cact-security-agent.md` | `deepseek/deepseek-v4-pro` | DeepSeek | +| `cact-qa-agent.md` | `deepseek/deepseek-v4-pro` | DeepSeek | +| `cact-gatekeeper-agent.md` | `deepseek/deepseek-v4-pro` | DeepSeek | +| `cact-requirements-agent.md` | `zai/glm-5.2` | Z.ai | +| `cact-requirements-agent-glm-divergent.md` | `zai/glm-5.2` | Z.ai | +| `cact-research-agent.md` | `zai/glm-5.2` | Z.ai | +| `cact-research-agent-glm-divergent.md` | `zai/glm-5.2` | Z.ai | +| `cact-requirements-agent-deepseek.md` | `deepseek/deepseek-v4-pro` | DeepSeek | +| `cact-research-agent-deepseek.md` | `deepseek/deepseek-v4-pro` | DeepSeek | +| `cact-documentation-agent.md` | `openrouter/moonshotai/kimi-k2.6` | **Needs fix** | + +**OpenRouter callout:** `cact-documentation-agent` still references OpenRouter. Before relying on that agent, change its frontmatter `model` to a direct Moonshot (or other approved direct) model ID and verify with `opencode models --refresh` on the server. + +Optional global config (`~/.config/opencode/opencode.jsonc`) may enable MCP servers. Keep secrets out of that file. + +Do **not** copy `auth.json`, `.env`, or systemd env files into the git repository. + +--- + +## 4. Secrets and provider auth + +### 4.1 Server basic auth env file + +```bash +sudo install -d -m 0750 -o root -g opencode /etc/opencode +sudo tee /etc/opencode/server.env >/dev/null <<'EOF' +OPENCODE_SERVER_USERNAME=opencode +OPENCODE_SERVER_PASSWORD=REPLACE_WITH_LONG_RANDOM_SECRET +# Optional: pin HOME for the service if the unit does not set User= +# HOME=/home/opencode +EOF +sudo chmod 0640 /etc/opencode/server.env +sudo chown root:opencode /etc/opencode/server.env +``` + +Generate a strong password (`openssl rand -base64 32`). Store it in your password manager; do not paste it into tickets or git. + +### 4.2 Provider credentials + +As the service user, log in to each vendor API you need: + +```bash +sudo -u opencode -H bash -lc 'export PATH="$HOME/bin:$HOME/.bun/bin:$PATH"; opencode auth login' +``` + +Credentials land in `~/.local/share/opencode/auth.json` under that user’s home. Restrict permissions: + +```bash +sudo -u opencode -H bash -lc 'chmod 600 ~/.local/share/opencode/auth.json' +``` + +Minimum providers for the current CACT pack: Moonshot, DeepSeek, Z.ai. Refresh model lists after login: + +```bash +sudo -u opencode -H bash -lc 'opencode models --refresh' +``` + +--- + +## 5. systemd unit + +Create `/etc/systemd/system/opencode-cact.service`: + +```ini +[Unit] +Description=OpenCode CACT headless server +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=opencode +Group=opencode +WorkingDirectory=/home/opencode +EnvironmentFile=/etc/opencode/server.env +Environment=HOME=/home/opencode +Environment=PATH=/home/opencode/bin:/home/opencode/.bun/bin:/usr/local/bin:/usr/bin +ExecStart=/home/opencode/bin/opencode serve --hostname 0.0.0.0 --port 4096 +Restart=on-failure +RestartSec=5 +# Harden lightly; expand after validating tool/workspace needs +NoNewPrivileges=true +PrivateTmp=true + +[Install] +WantedBy=multi-user.target +``` + +If you terminate TLS at Nginx, bind OpenCode to loopback instead: + +```ini +ExecStart=/home/opencode/bin/opencode serve --hostname 127.0.0.1 --port 4096 +``` + +Enable and start: + +```bash +sudo systemctl daemon-reload +sudo systemctl enable --now opencode-cact.service +sudo systemctl status opencode-cact.service --no-pager +sudo journalctl -u opencode-cact.service -n 50 --no-pager +``` + +### Rollout order + +1. Validate binary: `opencode --version` +2. Validate env file permissions and that `OPENCODE_SERVER_PASSWORD` is set +3. `daemon-reload` → `restart` (or `start` on first install) +4. Run health probes (section 7) +5. Connect desktop + +### Rollback + +1. `sudo systemctl stop opencode-cact.service` +2. Restore previous binary into `/home/opencode/bin/opencode` +3. Restore config/auth from the timestamped snapshot (section 8) +4. `sudo systemctl start opencode-cact.service` +5. Re-run health probes + +--- + +## 6. Network and hardening + +### 6.1 Firewall + +Allow `4096/tcp` only from operator addresses (example with UFW): + +```bash +sudo ufw allow OpenSSH +sudo ufw allow from OPERATOR_CIDR to any port 4096 proto tcp +sudo ufw enable +sudo ufw status verbose +``` + +Cloud security groups / nftables should match the same allowlist. + +### 6.2 Optional Nginx TLS + +When the host must not expose raw HTTP: + +1. Bind OpenCode to `127.0.0.1:4096`. +2. Proxy `https://cact.example.com` → `http://127.0.0.1:4096` with WebSocket upgrade headers if clients use SSE/WS. +3. Obtain certificates (Let’s Encrypt / ACME). +4. Point the desktop at `https://cact.example.com` with the same basic-auth username/password. + +Keep mDNS disabled on public VPS hosts (`--mdns` unset / false). + +### 6.3 Workspace directories + +Clients send a project directory via request headers. Ensure the service user can read/write the repos you intend to work in (for example under `/home/opencode/workspaces`). Do not run the server as root. + +--- + +## 7. Prove it + +Replace placeholders. Username defaults to `opencode` unless you set `OPENCODE_SERVER_USERNAME`. + +### 7.1 Authenticated HTTP + +```bash +export CACT_URL='http://SERVER_IP:4096' +export CACT_USER='opencode' +export CACT_PASS='YOUR_SERVER_PASSWORD' + +# Global health (many builds) +curl -fsS -u "$CACT_USER:$CACT_PASS" "$CACT_URL/global/health" + +# v2 location probe (desktop also uses this when health routes are odd) +curl -fsS -u "$CACT_USER:$CACT_PASS" "$CACT_URL/api/location" + +# OpenAPI explorer (optional) +# open "$CACT_URL/doc" +``` + +Desktop health checking (`packages/app/src/utils/server-health.ts`) tries, in order: + +1. `/api/health` +2. `/global/health` +3. Successful `/api/location` (treat as healthy when health routes fail for a non-network reason) + +A 404 on one health path with a working `/api/location` can still show **online** in CloudAxe Desktop. + +### 7.2 Agent inventory on the host + +```bash +sudo -u opencode -H bash -lc 'opencode agent' # or the install's list-agents command +# Confirm cact-orchestrator-tech-lead and other cact-* names appear +ls -1 /home/opencode/.config/opencode/agents/cact-*.md +``` + +### 7.3 CloudAxe Desktop + +1. Open **Settings → Servers** (or Add Server). +2. URL: `http://SERVER_IP:4096` or your HTTPS proxy URL. +3. Username / password: same as `OPENCODE_SERVER_*`. +4. Save when health shows online. +5. Start a session with agent **`cact-orchestrator-tech-lead`**. +6. Confirm a trivial prompt returns (proves provider auth + model routing). + +--- + +## 8. Upgrade + +1. Snapshot: + + ```bash + TS=$(date +%Y%m%d-%H%M%S) + sudo -u opencode -H bash -lc "tar -C \$HOME -czf \$HOME/cact-backup-\$TS.tgz .config/opencode .local/share/opencode" + sudo cp -a /home/opencode/bin/opencode "/home/opencode/bin/opencode.prev-$TS" + ``` + +2. Pull / rebuild / replace the binary at the pinned ref. +3. `sudo systemctl restart opencode-cact.service` +4. Re-run section 7 probes and a desktop session. +5. Keep the previous binary and tarball until the new build is confirmed. + +--- + +## 9. Ops checklist (copy into the ticket) + +- [ ] Service user created; OpenCode binary on PATH for that user +- [ ] `opencode --version` recorded +- [ ] `AGENTS.md` and all `cact-*.md` installed under `~/.config/opencode/` +- [ ] `cact-documentation-agent` model switched off OpenRouter (or agent unused) +- [ ] Provider logins present; `auth.json` mode `600` +- [ ] `/etc/opencode/server.env` mode `640`, password set +- [ ] `opencode-cact.service` enabled and active +- [ ] Firewall / security group allowlists `4096` (or only `443` via Nginx) +- [ ] `curl` health + `/api/location` succeed with basic auth +- [ ] Desktop Add Server shows online +- [ ] Session as `cact-orchestrator-tech-lead` succeeds +- [ ] Backup tarball + previous binary path documented for rollback + +--- + +## References in this repo + +- Serve command: `packages/opencode/src/cli/cmd/serve.ts` +- Network flags: `packages/opencode/src/cli/network.ts` +- Global path layout: `packages/core/src/global.ts` +- Desktop health probes: `packages/app/src/utils/server-health.ts` +- Upstream-style server docs: `packages/web/src/content/docs/server.mdx` +- Container packaging of the CLI binary: `packages/opencode/Dockerfile`