From 415d530bafeaf2675502bc3c95ce7dd24c854972 Mon Sep 17 00:00:00 2001 From: autonomic-bot Date: Mon, 7 Sep 2026 21:31:13 +0000 Subject: [PATCH] cc-ci host: keys generated on the host, nothing copied from another machine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Operator: no ssh keys or non-essential secrets from the old orchestrator box on the cc-ci host. `ssh cc-ci` uses cc-ci-local-ed25519 (generated on the host, pub in nix/hosts/cc-ci/ssh-keys); pushes to Gitea use autonomic-bot-cc-ci-ed25519 (generated on the host, registered on the bot account); root reuses that file for the cc-ci-secrets submodule. README §4: the /secrets/files inventory shrinks to exactly what cc-ci needs, and §4b now says to make the host a sops recipient and take the master key away. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01FqkQq3CDmFWcQ7u1LzoyRz --- README.md | 22 +++++++++++++--------- nix/hosts/cc-ci/ssh-keys | 1 + nix/modules/orchestrator-host.nix | 7 ++++--- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index a4924cf..15e2568 100644 --- a/README.md +++ b/README.md @@ -187,10 +187,15 @@ install -d -m700 /var/lib/sops-nix install -m600 /var/lib/sops-nix/key.txt ``` -`/etc/cc-ci/secrets/secrets.yaml` is encrypted to the master key and the *old* host's SSH host -key. That is enough to deploy. Afterwards (optional, tidier) add the new host as a recipient: -`ssh-to-age < /etc/ssh/ssh_host_ed25519_key.pub`, add it to `secrets/.sops.yaml` in cc-ci-secrets, -`sops updatekeys secrets.yaml`, push, `git -C /etc/cc-ci submodule update --remote`. +`/etc/cc-ci/secrets/secrets.yaml` is encrypted to the master key and the *previous* hosts' SSH +host keys. The master key is enough for the FIRST deploy. Then make the new host a recipient so +the master key can leave the box again (it belongs off-box, with the operator): +`ssh-to-age < /etc/ssh/ssh_host_ed25519_key.pub` → add it to `.sops.yaml` in cc-ci-secrets AND in +cc-ci, `sops updatekeys secrets.yaml` (needs the master key, so do it where that lives), push, +bump the submodule in cc-ci, `git -C /etc/cc-ci pull --recurse-submodules` on the host, then +replace `/var/lib/sops-nix/key.txt` with the host-derived identity +(`ssh-to-age -private-key -i /etc/ssh/ssh_host_ed25519_key`), rebuild, and delete the master key +from the host. ### 4c. The orchestrator's workspace (as the `loops` user — it exists after the first rebuild, so run §5 once first if this is a fresh host, then come back) @@ -213,13 +218,12 @@ secrets do not get spread around. `/secrets/README.txt` lists each file and its | runtime path → `/secrets/files/…` | what | source | |---|---|---| -| `/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/.testenv` → `cc-ci.testenv` | `GITEA_PASSWORD` (autonomic-bot: PR/API calls), `DOCKERHUB_USERNAME/TOKEN` (harness image pulls). Nothing else: no tailscale key, no third-party model API keys (opencode's own auth is `opencode-auth.json`; add `TINFOIL_API_KEY` back only if `LOOP_MODEL` moves to a tinfoil model). | 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/.ssh/cc-ci-local-ed25519` (+`.pub`) → same names | `ssh cc-ci` as root — to loopback on this host. **Generated on the host**, its pub in `nix/hosts/cc-ci/ssh-keys` | `ssh-keygen -t ed25519 -C cc-ci-loops-to-root@cc-ci` as loops | +| `~loops/.ssh/autonomic-bot-cc-ci-ed25519` (+`.pub`) → same names | pushes recipe branches / PRs as `autonomic-bot`; root's ssh config points at the same file to clone the private cc-ci-secrets submodule. **Generated on the host** and registered on the bot's Gitea account (`POST /api/v1/user/keys` with the bot password) | `ssh-keygen -t ed25519 -C autonomic-bot@cc-ci-host` as loops | | `~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:` line via `mkpasswd -m bcrypt` | +| `/etc/nginx/oc-htpasswd` (root:nginx; the bcrypt line only — the plaintext stays with the operator, not on the host) | 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:` 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`); it is not overwritten if present. diff --git a/nix/hosts/cc-ci/ssh-keys b/nix/hosts/cc-ci/ssh-keys index b686565..c16066e 100644 --- a/nix/hosts/cc-ci/ssh-keys +++ b/nix/hosts/cc-ci/ssh-keys @@ -8,3 +8,4 @@ ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHOcLo0YBa0UYi7i/l8K/Y/7cF2OclmDqSTlAsHM0dOS ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMniNzAzuI527bfk/EipqFILFayUCwYXDoZ3R7+QgYq6 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOk8NaeBdPbS2gfUvbny8h0AkZlVjGYHzx4QPXSJ38gd claude@claude-vm ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAcyTGb/wVgdhg5oBCZZvBaR1RuUQRY/3WHnOQpNDCsp claude-cc-ci-sandbox@20260526 +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKmGDZC6wrOQNJAW5PPDpxgEXXrcsnIU4b3QJLtq05RQ cc-ci-loops-to-root@cc-ci diff --git a/nix/modules/orchestrator-host.nix b/nix/modules/orchestrator-host.nix index 49c98ca..e2adca3 100644 --- a/nix/modules/orchestrator-host.nix +++ b/nix/modules/orchestrator-host.nix @@ -23,7 +23,8 @@ in example = "100.95.31.88"; description = '' Where `ssh cc-ci` (used by every skill and script that drives the CI server) connects to, - as root with ~loops/.ssh/cc-ci-root-ed25519. On the combined host the CI server IS this + as root with ~loops/.ssh/cc-ci-local-ed25519 (a key generated ON the host — nothing + copied from another machine). On the combined host the CI server IS this machine, so the default is loopback; a standalone orchestrator points it at the CI server's tailnet address. ''; @@ -112,7 +113,7 @@ in Host cc-ci HostName ${cfg.ciSshHost} User root - IdentityFile /home/loops/.ssh/cc-ci-root-ed25519 + IdentityFile /home/loops/.ssh/cc-ci-local-ed25519 IdentitiesOnly yes StrictHostKeyChecking accept-new ServerAliveInterval 30 @@ -121,7 +122,7 @@ Host git.autonomic.zone HostName git.autonomic.zone Port 2222 User git - IdentityFile /home/loops/.ssh/autonomic-bot-gitea-ed25519 + IdentityFile /home/loops/.ssh/autonomic-bot-cc-ci-ed25519 IdentitiesOnly yes SSHCFG chmod 600 /home/loops/.ssh/config