cloudaxe-opencode/docs/cact-server-playbook.md

415 lines
13 KiB
Markdown
Raw Permalink Normal View 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
```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`