cloudaxe-opencode/docs/cact-server-playbook.md
axcho 93d18f26b5
Some checks failed
deploy / deploy (push) Has been cancelled
generate / generate (push) Has been cancelled
nix-eval / nix-eval (push) Has been cancelled
publish / version (push) Has been cancelled
test / unit (linux) (push) Has been cancelled
test / unit (windows) (push) Has been cancelled
test / e2e (linux) (push) Has been cancelled
test / e2e (windows) (push) Has been cancelled
typecheck / typecheck (push) Has been cancelled
publish / build-cli (push) Has been cancelled
publish / sign-cli-windows (push) Has been cancelled
publish / build-electron (map[bun_install_flags:--os=darwin --cpu=arm64 host:macos-26 platform_flag:--mac --arm64 target:aarch64-apple-darwin]) (push) Has been cancelled
publish / build-electron (map[bun_install_flags:--os=darwin --cpu=x64 host:macos-26-intel platform_flag:--mac --x64 target:x86_64-apple-darwin]) (push) Has been cancelled
publish / build-electron (map[host:blacksmith-4vcpu-ubuntu-2404 platform_flag:--linux target:x86_64-unknown-linux-gnu]) (push) Has been cancelled
publish / build-electron (map[host:blacksmith-4vcpu-ubuntu-2404-arm platform_flag:--linux --arm64 target:aarch64-unknown-linux-gnu]) (push) Has been cancelled
publish / build-electron (map[host:blacksmith-4vcpu-windows-2025 platform_flag:--win target:x86_64-pc-windows-msvc]) (push) Has been cancelled
publish / build-electron (map[host:windows-2025 platform_flag:--win --arm64 target:aarch64-pc-windows-msvc]) (push) Has been cancelled
publish / publish (push) Has been cancelled
docs: add CACT server deploy playbook
Add an operator runbook for standing up the CloudAxe Coder Team on a new Linux server.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-04 09:34:09 -04:00

414 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`