cc-ci host: the orchestrator is an opencode agent; no Claude on the box; /secrets convention

Operator 2026-09-07 (evening): Claude sessions stay on notplants-orchestrator;
on the cc-ci host the orchestrator is an opencode agent (agents.toml:
opencode/glm-5.2) steered from https://oc.ci.commoninternet.net, next to the
upgrader/report sessions. claude-install and CLAUDE_BIN are gone from the
modules; launch-upgrader/report print the real UI URL; README "Operating the
orchestrator" rewritten and a "Weekly upgrade run" section added.

Secrets: only cc-ci's, under /secrets/files with runtime paths symlinked
(README §4 table), nothing from other projects — tangled key and the
tailscale line dropped from the host.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FqkQq3CDmFWcQ7u1LzoyRz
This commit is contained in:
2026-09-07 21:27:23 +00:00
co-authored by Claude Fable 5.1
parent 01caa90e76
commit a72a816b48
7 changed files with 66 additions and 70 deletions
+37 -26
View File
@@ -9,7 +9,7 @@ module from the [cc-ci](https://git.autonomic.zone/recipe-maintainers/cc-ci) rep
| | where |
|---|---|
| Orchestrator loops, timers (weekly upgrader, hourly supervisor) | `nix/modules/cc-ci.nix``nixosModules.cc-ci-orchestrator` |
| The host contract those need (loops user, claude/opencode CLIs, opencode web UI) | `nix/modules/orchestrator-host.nix``nixosModules.orchestrator-host` |
| The host contract those need (loops user, opencode CLI, opencode web UI) | `nix/modules/orchestrator-host.nix``nixosModules.orchestrator-host` |
| The CI server (swarm, traefik, drone, runner, `!testme` bridge, dashboard, reports, acme-dns) | cc-ci repo `nix/modules/``nixosModules.cc-ci-server` (flake input `cc-ci`) |
| The machine: hardware, networking, root keys, firewall + fail2ban | `nix/hosts/cc-ci/``nixosConfigurations.cc-ci` |
| Plans, launch tooling, loop prompts, journal | `cc-ci-plan/` (see `AGENTS.md` for roles) |
@@ -205,22 +205,24 @@ git clone https://git.autonomic.zone/recipe-maintainers/cc-ci.git cc-ci-adv #
mkdir -p .cc-ci-logs .sops
```
Then the files below (`install -m600 -o loops -g users`):
Secrets live in **`/secrets/files/`** (owned by loops, mode 700 on the directory), the same
convention as the notplants-orchestrator box, with the runtime paths symlinked to them so one
`ls /secrets/files` is the complete inventory. **Only cc-ci's secrets go there** — nothing from
other projects on the box they were copied from (no tailscale, lichen, tangled, b1, borg, …):
secrets do not get spread around. `/secrets/README.txt` lists each file and its purpose.
| file | what | source |
| runtime path → `/secrets/files/…` | what | source |
|---|---|---|
| `/srv/cc-ci/.testenv` | `TS_AUTH_KEY`, `GITEA_PASSWORD` (autonomic-bot), `DOCKERHUB_USERNAME/TOKEN`, model API keys | old host `/secrets/files/cc-ci.testenv`; fresh: create each credential |
| `/srv/cc-ci/upgrader.env` | `LOOP_TIER`, `LOOP_MODEL`, `REPORT_MODEL` for the weekly run (no secrets) | old host, or copy the example in `AGENTS.md` |
| `/srv/cc-ci/.sops/master-age.txt` | the same master age key as 4b (skills that re-key secrets use it) | old host |
| `~loops/.ssh/cc-ci-root-ed25519` (+`.pub`) | `ssh cc-ci` as root — to loopback on this host | old host; fresh: `ssh-keygen -t ed25519` and add the pub to `nix/hosts/cc-ci/ssh-keys` |
| `~loops/.ssh/autonomic-bot-gitea-ed25519` (+`.pub`) | pushes recipe branches / PRs as `autonomic-bot` | old host; fresh: new key added to the bot's Gitea account |
| `~loops/.ssh/tangled-ed25519` | optional, tangled.org mirrors | old host |
| `~loops/.claude/` | Claude Code auth + settings + the orchestrator session history | old host (`rsync -a`); fresh: `claude auth login` as loops (device code, interactive) |
| `~loops/.local/share/opencode/auth.json`, `~loops/.config/opencode/` | opencode provider auth (the weekly upgrader runs on opencode) | old host; fresh: `opencode auth login` |
| `/etc/nginx/oc-htpasswd` | basic auth for the opencode UI (`https://oc.ci.commoninternet.net`, via traefik); **nginx refuses to start without it**, and its config check runs as the `nginx` user, so `root:nginx 0640` (the `nginx` group exists after the first rebuild — fix ownership then and `systemctl restart nginx`) | old host (`/secrets/files/oc-basic-auth.txt` has the plaintext), or a new `oc:<bcrypt>` line via `mkpasswd -m bcrypt` |
| `/srv/cc-ci/.testenv` `cc-ci.testenv` | `GITEA_PASSWORD` (autonomic-bot), `DOCKERHUB_USERNAME/TOKEN`, model API keys used by opencode providers. No `TS_AUTH_KEY` (no tailscale here). | old host's copy minus the tailscale line; fresh: create each credential |
| `/srv/cc-ci/upgrader.env` (not a secret, lives in the checkout) | `LOOP_TIER`, `LOOP_MODEL`, `REPORT_MODEL` for the weekly run | old host, or copy the example in `AGENTS.md` |
| `/srv/cc-ci/.sops/master-age.txt` → `master-age.txt` | the same master age key as 4b (skills that re-key secrets use it); root's `/var/lib/sops-nix/key.txt` is a root-only copy of it | old host |
| `~loops/.ssh/cc-ci-root-ed25519` (+`.pub`) → same names | `ssh cc-ci` as root — to loopback on this host | old host; fresh: `ssh-keygen -t ed25519` and add the pub to `nix/hosts/cc-ci/ssh-keys` |
| `~loops/.ssh/autonomic-bot-gitea-ed25519` (+`.pub`) → same names | pushes recipe branches / PRs as `autonomic-bot`; root's `/root/.ssh/` copy clones the private cc-ci-secrets submodule | old host; fresh: new key added to the bot's Gitea account |
| `~loops/.local/share/opencode/auth.json` → `opencode-auth.json`; `~loops/.config/opencode/opencode.jsonc` (config, not secret) | opencode provider auth + config — the orchestrator AND the weekly upgrader are opencode agents; there is no Claude on this host | old host; fresh: `opencode auth login` as loops |
| `/etc/nginx/oc-htpasswd` (root:nginx copy; plaintext in `/secrets/files/oc-basic-auth.txt`) | basic auth for the opencode UI (`https://oc.ci.commoninternet.net`, via traefik); **nginx refuses to start without it**, and its config check runs as the `nginx` user, so `root:nginx 0640` (the `nginx` group exists after the first rebuild — fix ownership then and `systemctl restart nginx`) | old host (`/secrets/files/oc-basic-auth.txt` has the plaintext), or a new `oc:<bcrypt>` line via `mkpasswd -m bcrypt` |
`~loops/.ssh/config` is written by the activation script on first rebuild (`Host cc-ci` →
`127.0.0.1`, `git.autonomic.zone`, `tangled.org`); it is not overwritten if present.
`127.0.0.1`, `git.autonomic.zone`); it is not overwritten if present.
## 5. Build and activate
@@ -340,29 +342,38 @@ stopped on the new host while its directory is copied:
---
# Operating the orchestrator session
# Operating the orchestrator
The steering session is a long-lived interactive Claude Code session under tmux with
`--remote-control`, so it can be watched and steered from **claude.ai/code** (or the mobile app).
`cc-ci-orchestrator.service` relaunches it on boot via `cc-ci-plan/agents.py up orchestrator`
(backend + model in `cc-ci-plan/agents.toml`).
The cc-ci orchestrator on this host is an **opencode agent** (`cc-ci-plan/agents.toml`:
backend `opencode`, model `opencode/glm-5.2`), launched on boot by `cc-ci-orchestrator.service`
via `cc-ci-plan/agents.py up orchestrator` into tmux session `cc-ci-orchestrator`, attached to
the shared opencode web server. Steer it from the web UI, **https://oc.ci.commoninternet.net**
(basic auth, user `oc`), where the weekly upgrader's and report's sessions show up too.
There is no Claude Code on this host: Claude sessions run on the notplants-orchestrator box and
operate the CI from there over ssh (`Host cc-ci` in that box's loops ssh config → this host).
```bash
# attach on the host
sudo -iu loops tmux attach -t cc-ci-orchestrator
# or resume the conversation by hand in a fresh tmux pane
claude --resume autonomous-orchestrator --remote-control "autonomous-orchestrator" --dangerously-skip-permissions
# already inside a live session and just want the web surface? /remote-control
# on the host
sudo -iu loops tmux attach -t cc-ci-orchestrator # the agent's terminal
systemctl restart cc-ci-orchestrator # relaunch it (fresh opencode session)
sudo -iu loops tmux ls # cc-ci-orchestrator, cc-ci-upgrader (+watchdog) during the weekly run
```
`--resume <name|id>` selects the *conversation* to restore; the `--remote-control "<name>"` value is
only the web display label. Don't pass `--fork-session` unless you mean to branch.
## Weekly upgrade run
`cc-ci-upgrade-all.timer` (Thu 22:00 America/New_York) starts `launch-upgrader.py`, which runs
`/upgrade-all` as an opencode agent (`upgrader.env`: `LOOP_MODEL`/`REPORT_MODEL`) in tmux
`cc-ci-upgrader` with a usage-limit watchdog; the hourly `cc-ci-upgrade-supervisor.timer` drives a
stalled run to completion. Start one by hand with `systemctl start cc-ci-upgrade-all`; follow
`/srv/cc-ci/.cc-ci-logs/cc-ci-upgrader.log`; it ends with `UPGRADE RUN COMPLETE`, a summary in
`.cc-ci-logs/upgrades/upgrade-all-<date>.md`, and a report published to
`https://report.ci.commoninternet.net/week-<date>.html`.
## Kick off / supervise the loops
```bash
cd /srv/cc-ci/cc-ci-plan
./launch.sh start # Builder + Adversary loops (interactive --remote-control in tmux) + watchdog
./launch.sh start # Builder + Adversary loops + watchdog (all phases are DONE; this just confirms)
./launch.sh status # session + DONE state
./launch.sh logs builder|adversary|watchdog
./launch.sh stop