- plan-cc-ci-hetzner-migration.md: 3-phase plan — (1) provision the Hetzner cpx32 cc-ci fully + green !testme readiness gate, (2) repoint the loops + dashboard + *.ci at it (one ssh-config + DNS change), (3) stop the b1 cc-nix-test (cold standby). Parallel bring-up, reversible cutover, b1 freed. - plan-cc-ci-hetzner-terraform.md: cpx31 is retired → default to cpx32 (current dedicated-vCPU 8GB). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.9 KiB
Plan — migrate the cc-ci SERVER from b1 Incus to Hetzner (full cutover)
Status: PROPOSED. Move the cc-ci CI server (cc-nix-test) off the slow b1 host onto a fast
Hetzner cpx32 (8 GB, dedicated vCPU, NVMe), repoint the Builder/Adversary loops + everything at it,
then stop the old VM. This file: /srv/cc-ci/cc-ci-plan/plan-cc-ci-hetzner-migration.md.
Owner: assistant (provisioning + cutover mechanics) + orchestrator (coordination); operator for the
secret/DNS gates. Supersedes the narrower plan-cc-ci-hetzner-terraform.md (that is Phase 1's
deliverable; this plan wraps it with the cutover + decommission).
0. Context (why, and what's where)
- Two VMs run on b1 (a 2015 Intel i5-6400T low-power CPU + a spinning HDD — measured: CPU
pressure ~55%, root disk
ROTA=1):- cc-ci server
cc-nix-test(tailnet100.90.116.4, 8 GB) — where the loops deploy recipes + run the harness (the heavy CI work). This is what we migrate. - orchestrator VM
cc-ci-orchestrator(tailnet100.116.55.106, 2 GB) — where the loops + orchestrator + assistant run (claude sessions). Stays for now.
- cc-ci server
- b1 is overloaded running both on a slow CPU + HDD — "everything is getting slow."
- The win (see the perf analysis): Hetzner cpx32 = modern dedicated vCPU + NVMe vs a 2015 low-power CPU + HDD → I/O-bound deploys (the ghost/discourse near-timeouts) likely 3–10× faster, CPU work ~2–3×. Moving the heavy server off b1 also relieves b1, so the orchestrator VM (still there) speeds up too.
1. Phase 1 — provision the Hetzner cc-ci, FULLY ready
The plan-cc-ci-hetzner-terraform.md deliverable, taken all the way to a converged, green server
(not just "terraform applies"):
terraform/in the cc-ci repo (cpx32, ubuntu-24.04, pinned hcloud provider + nixos-infect).apply→ nixos-infect → bare NixOS on Hetzner.- Add the
cc-ci-hetznerflake host (nixos-infect's DO/Hetzner hardware + the sharednix/modules/*). - Full convergence (the D8 flow): clone cc-ci
--recursive+ place the bootstrap age key at/var/lib/sops-nix/key.txt(operator) +nixos-rebuild switch --flake .#cc-ci-hetzner→ traefik / drone / bridge / dashboard / backupbot / swarm all up, 0 failed units. - DNS/cert: point
ci.commoninternet.net+*.ciA record at the Hetzner public IP (the server has one — can drop the b1 TLS-passthrough gateway). Keep the sops wildcard cert for v1 (or ACME — §decision). - Readiness gate (before any cutover): ssh works; the dashboard +
*.ci.commoninternet.netare reachable; a full!testmeruns GREEN on the Hetzner server (drive one recipe end-to-end via the harness). Keep the b1 cc-ci running untouched in parallel during all of Phase 1. - Operator inputs for Phase 1:
HCLOUD_TOKEN(have),TS_AUTH_KEY(have), the bootstrap age key (needed for convergence), and the DNS change. Note: the token may be invalidated after the KEEPER server is applied — the server runs without it; only futureterraformneeds a (new) token.
2. Phase 2 — cut everything over to the Hetzner server
Once Phase 1 is green, switch all consumers from the b1 cc-nix-test to the Hetzner server:
- Loop access: update the
Host cc-cientry in the loops' ssh config (on the orchestrator VM, used by builder/adversary/orchestrator/assistant) —HostNamefrom100.90.116.4→ the Hetzner server's tailnet IP / MagicDNS. (ssh cc-ciis the single indirection the loops use, so this one change repoints all of them. The Hetzner box joins the SAME tailnet viaTS_AUTH_KEY, so it's a direct peer like today.) - CI flow: the
!testme→ bridge → Drone → harness path + the dashboard now run on the Hetzner server (they're part of the converged config there). The recipe mirrors stay on Gitea (unaffected). - State carry-over (minimal — mostly stateless): recipes redeploy from the mirrors; warm canonicals re-seed on the first green cold runs; the harness lives in the cc-ci repo. Drone build history + dashboard state start fresh on the new server (acceptable; migrate only if wanted).
- Verify cutover: a full loop cycle works against Hetzner — Builder deploys + claims a gate, the Adversary cold-verifies green on the Hetzner server; phase-2 recipe work continues, now fast. Watch a ghost/discourse deploy to confirm the timeouts are gone.
3. Phase 3 — stop the old cc-ci VM (free b1)
- Once everything is confirmed serving green on Hetzner, stop
cc-nix-teston b1 (IncusPUT .../state {"action":"stop"}). Keep it as a cold standby for a few days (don't delete) for rollback, then retire. - b1 now runs only the small orchestrator VM → it gets b1's full (modest) resources → the loops' runtime is less starved too. "Everything faster from here on out."
- Rollback (until the old VM is deleted): if Hetzner has a problem, revert the
Host cc-cissh entry to100.90.116.4and start the b1 VM again.
4. Sequencing & gates (don't break the running CI)
- Strictly parallel bring-up: Phase 1 stands Hetzner up alongside the live b1 cc-ci; no
consumer is repointed until the Hetzner
!testmeis green (Phase 1 readiness gate). - The cutover (Phase 2) is a single ssh-config repoint + DNS — fast and reversible.
- Phase 3 (stop b1) only after Phase 2 is verified.
- The loops keep working on b1 throughout Phase 1 (no disruption); the brief cutover window is the only moment they switch servers.
5. Open decisions (log in DECISIONS.md)
- DNS/cert: point
*.ciat the Hetzner public IP + drop the gateway; sops cert (v1) vs ACME. - Drone/dashboard history: fresh on Hetzner (default) vs migrate the volumes.
- Orchestrator VM: leave on b1 (freed) for now; a later, separate plan could also move the loops' runtime to Hetzner and fully retire b1 — out of scope here (the runtime needn't be fast).
- Token lifecycle: invalidate
HCLOUD_TOKENafter the keeper apply, or keep a (rotated) one for ongoingterraformmanagement of the server.
6. Definition of Done
- Hetzner cpx32 cc-ci fully converged (0 failed units) + a green
!testmeon it. - Loops + dashboard +
*.ci.commoninternet.netall served from Hetzner; a full Builder→Adversary cycle verified green there; deploy/convergence visibly faster (ghost/discourse no longer near-timeout). - Old b1
cc-nix-teststopped (cold standby, not deleted). terraform/committed to the cc-ci repo (via PR); no secrets/state in git;docs/install.mdupdated for the Hetzner host. Adversary-verifiable: from-scratch reproducibility holds on Hetzner.
7. Guardrails
- Parallel bring-up; never repoint consumers until Hetzner is green; keep b1 as cold standby.
- No secrets in git (token, TS key, age key, tfstate). Pin everything. x86 only (cpx32/cx32).
- Real Nix provisioning (the flake) + real abra; don't weaken anything to make the new server "pass."