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

13 KiB
Raw Permalink Blame History

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

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

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):

curl -fsSL https://bun.sh/install | bash
# ensure ~/.bun/bin is on PATH for the service user as well

2.2 Service user

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).

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):

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:

~/.config/opencode/AGENTS.md
~/.config/opencode/agents/cact-*.md

On the server:

# 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

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:

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:

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:

sudo -u opencode -H bash -lc 'opencode models --refresh'

5. systemd unit

Create /etc/systemd/system/opencode-cact.service:

[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:

ExecStart=/home/opencode/bin/opencode serve --hostname 127.0.0.1 --port 4096

Enable and start:

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):

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

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

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:

    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