Add an operator runbook for standing up the CloudAxe Coder Team on a new Linux server. Co-authored-by: Cursor <cursoragent@cursor.com>
13 KiB
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:
- Confirm access scope (sudo / root).
- Name the environment:
dev/staging/production. - Note existing listeners on
4096(ss -tlnp | grep 4096). - Decide whether port
4096is exposed raw or only via Nginx TLS on443. - 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
- Validate binary:
opencode --version - Validate env file permissions and that
OPENCODE_SERVER_PASSWORDis set daemon-reload→restart(orstarton first install)- Run health probes (section 7)
- Connect desktop
Rollback
sudo systemctl stop opencode-cact.service- Restore previous binary into
/home/opencode/bin/opencode - Restore config/auth from the timestamped snapshot (section 8)
sudo systemctl start opencode-cact.service- 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:
- Bind OpenCode to
127.0.0.1:4096. - Proxy
https://cact.example.com→http://127.0.0.1:4096with WebSocket upgrade headers if clients use SSE/WS. - Obtain certificates (Let’s Encrypt / ACME).
- Point the desktop at
https://cact.example.comwith 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:
/api/health/global/health- 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
- Open Settings → Servers (or Add Server).
- URL:
http://SERVER_IP:4096or your HTTPS proxy URL. - Username / password: same as
OPENCODE_SERVER_*. - Save when health shows online.
- Start a session with agent
cact-orchestrator-tech-lead. - Confirm a trivial prompt returns (proves provider auth + model routing).
8. Upgrade
-
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" -
Pull / rebuild / replace the binary at the pinned ref.
-
sudo systemctl restart opencode-cact.service -
Re-run section 7 probes and a desktop session.
-
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 --versionrecordedAGENTS.mdand allcact-*.mdinstalled under~/.config/opencode/cact-documentation-agentmodel switched off OpenRouter (or agent unused)- Provider logins present;
auth.jsonmode600 /etc/opencode/server.envmode640, password setopencode-cact.serviceenabled and active- Firewall / security group allowlists
4096(or only443via Nginx) curlhealth +/api/locationsucceed with basic auth- Desktop Add Server shows online
- Session as
cact-orchestrator-tech-leadsucceeds - 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