The orchestrator's flake now builds the machine it shares with the cc-ci CI
server: `nixosConfigurations.cc-ci` composes cc-ci's nixosModules.cc-ci-server
(new flake input, nixpkgs + sops-nix follow ours), this repo's orchestrator
module (nix/modules/cc-ci.nix, exported as cc-ci-orchestrator, `cc-ci` kept
as an alias for notplants-nix) and the new nix/modules/orchestrator-host.nix
— the host contract those units always assumed (loops user, claude/opencode
CLIs, opencode web server + tailnet-only UI on 8443 since traefik owns
80/443, nix-ld, tool set, `ssh cc-ci` → loopback).
nix/hosts/cc-ci/{hardware,networking}.nix are PROVISIONAL copies of the old
server's layout so the flake evaluates; they get replaced by the
nixos-infect output of 195.201.88.249.
README.md is the deploy guide (Hetzner Debian → nixos-infect → this flake →
staging → data restore → cutover). archive/ holds the retired Incus/Hetzner
orchestrator host configs, the old terraform and the migration plans;
references updated. cc-ci-plan/plan-cc-ci-combined-host.md is the working
plan for the move.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FqkQq3CDmFWcQ7u1LzoyRz
7.6 KiB
Plan — one Hetzner host for cc-ci (CI server) + cc-ci-orchestrator
Status: IN PROGRESS (started 2026-09-07). Operator request: move the cc-ci CI server AND the
cc-ci orchestrator onto one new Hetzner server (195.201.88.249, 8 GB, 150 GB, Debian 13 image),
cleanly split off from the shared notplants-orchestrator box, which keeps everything else
(lichen, project-orchestrator, notplants agents). Nix config ownership: cc-ci's config in
recipe-maintainers/cc-ci, the orchestrator's in recipe-maintainers/cc-ci-orchestrator, and the
orchestrator flake includes cc-ci's module so one nixos-rebuild produces the combined host.
Last step (separate, after everything works on the current names): move both to autonomic.zone
subdomains.
Facts (2026-09-07)
| old cc-ci server | old orchestrator host (stays, becomes notplants-only) | new combined host | |
|---|---|---|---|
| public IP | 91.98.47.73 (fsn1, Hetzner 134485294) | 168.119.126.100 (nbg1, Hetzner 134487234) | 195.201.88.249 |
| tailnet | cc-ci 100.95.31.88 |
cc-ci-orchestrator-1 100.84.190.30 |
cc-ci (new node) |
| RAM / disk | 8 GB / 150 GB (83 GB used, 59 GB docker) | 4 GB + 4 GB swap / 75 GB + 250 GB /mnt/data |
8 GB / 150 GB, one disk |
| built by | cc-ci flake #cc-ci (nixpkgs 26.05 rev 531670d) |
notplants-nix flake #notplants-orchestrator (26.05 channel), importing cc-ci-orchestrator's nixosModules.cc-ci |
cc-ci-orchestrator flake #cc-ci importing cc-ci's nixosModules.cc-ci-server |
| DNS | ci., *.ci., ns-acme.commoninternet.net → 91.98.47.73 (Gandi, direct, no gateway) |
oc.commoninternet.net → 100.84.190.30 |
operator repoints at cutover |
Data on the old cc-ci server that must move: /var/lib/cc-ci-reports (published reports),
/var/lib/cc-ci-runs (dashboard artifacts, 1.7 G), /var/lib/ci-warm (1.4 G), /var/lib/acme
(LE cert valid to 2026-11-29 + acme-dns account json that the _acme-challenge CNAME points at),
/var/lib/acme-dns (the authoritative zone DB), /var/lib/ci-certs, /root/.abra (app env files),
/etc/cc-ci (deployed checkout the Sunday sweep runs from), Drone's drone_ci_commoninternet_net_data
volume (Gitea OAuth grant + repo activation + build history). Warm recipe volumes are caches and get
rebuilt on first use / the Sunday sweep. Docker swarm secrets/configs cannot be copied; the reconcile
oneshots recreate them from sops.
Out-of-band secrets the new host needs (never in git): /var/lib/sops-nix/key.txt (= the master age
key, /srv/cc-ci/.sops/master-age.txt here — the new host's SSH host key is not a sops recipient),
/etc/ts-auth-key, /srv/cc-ci/.testenv, /srv/cc-ci/upgrader.env, /srv/cc-ci/.sops/master-age.txt,
~loops/.ssh/{cc-ci-root,autonomic-bot-gitea,tangled}-ed25519, /etc/nginx/oc-* (self-signed cert +
htpasswd for the opencode UI), claude/opencode/codex auth under ~loops.
Design
cc-ci repo (feat/nixos-module-export):
nixosModules.cc-ci-server=nix/modules/default.nix: imports all service modules + the host-generic cc-ci settings that used to sit in the host file (UTC, docker/swarm firewall 80/443,environment.systemPackages = ccciRuntimeTools, allowUnfree). No hardware, no networking, no tailscale, no root keys, no stateVersion — the host supplies those.- New options under
cc-ci.*:publicIPv4(acme-dns listen + thens-acmeA record),sopsFile(absolute path to the decrypted-at-activationsecrets.yaml, default the submodule path so#cc-cikeeps working),repoPath(/etc/cc-ci, used by nightly-sweep). nixosConfigurations.cc-ci(old host) keeps building unchanged via the same module.
cc-ci-orchestrator repo (feat/combined-cc-ci-host):
- flake input
cc-ci(https, public) withnixpkgs/sops-nixfollowsso one nixpkgs + one sops-nix. nixosModules.cc-ci-orchestrator(the existingnix/modules/cc-ci.nix, kept exported asnixosModules.cc-citoo so notplants-nix keeps evaluating until it drops the input) — the loops, orchestrator session and the weekly/hourly timers.nix/modules/orchestrator-host.nix: the host contract the module assumes —loopsuser + sudo, nix-ld, claude/opencode/codex installers,opencode-web, the tailnet-only nginxoc.vhost (on the tailscale IP, port 8443, because traefik owns 80/443), tool packages, PATH.nixosConfigurations.cc-ci=nix/hosts/cc-ci/{configuration,hardware,networking}.niximporting both modules./srvis a plain directory (no/mnt/data), 8 GB swapfile, root keys, tailscale--hostname=cc-ci, firewall 22 (+ what cc-ci-server opens: 80, 443, 53).loops' ssh configHost cc-ci→127.0.0.1so everyssh cc-ci …in skills/scripts keeps working.archive/: the retired Incus/Hetzner-orchestrator host config, old terraform, historical plans.README.md: provisioning (Hetzner Debian → nixos-infect → NixOS), secrets staging, the onenixos-rebuild, data restore, cutover, verification — written so a person or an LLM can redo it.
notplants-nix (chore/drop-cc-ci, after cutover): remove the cc-ci input, module import, the
four cc-ci units' mount gating, loopsSshConfig, opencode-web + the oc. vhost (unless something
notplants-side uses it), tailscale hostname → notplants-orchestrator.
Steps
- nixos-infect the new box (
NIX_CHANNEL=nixos-26.05 PROVIDER=hetzner); capturehardware-configuration.nix+networking.nix. - cc-ci: module export + options; verify
#cc-cistill evaluates; PR. - cc-ci-orchestrator: input + host + modules + archive/ + README + terraform refresh; verify
#cc-cievaluates; PR. - Stage secrets + clones on the new host;
nixos-rebuild test→ verify →switch. Immediately after: scale the newccci-bridge_appto 0 and mask the two cc-ci timers so the new host does not double-process!testmeor run a second weekly upgrade before cutover. - Copy data (rsync over tailscale): reports, runs, ci-warm, acme, acme-dns, ci-certs, /root/.abra, /etc/cc-ci; Drone volume with Drone scaled to 0 during the copy.
- Pre-cutover verification on the new IP (
curl --resolve, port 53, dashboard, reports, drone, one directcc-ci-runon custom-html-tiny). - Operator: Gandi A records
ci,*.ci,ns-acme→ 195.201.88.249. Then: old bridge + drone + timers off, new bridge up, one real!testmeend-to-end, a!testme-driven report page. - Move the orchestrator: stop cc-ci units here, final rsync of
/srv/cc-ci-orch+ agent state, enable on the new host, operator reconnects there; notplants-nix PR removing cc-ci. - Old cc-ci server: cold standby ~1 week, then operator deletes it and the stale tailnet node.
- Domain move to
autonomic.zone— separate plan, after 1–9 are proven.
Log
- 2026-09-07 19:40 UTC — recon done, plan written, ssh to the new box verified as root with
notplants-orchestrator-ed25519. - 2026-09-07 20:05 UTC — nixos-infect started on 195.201.88.249 (rev 40f62a6, nixos-26.05,
PROVIDER=hetznercloud). Two false starts: the Debian 13 image has /tmp on tmpfs, so
nixos-infect's temp swapfile fails
swapon: Invalid argument; fixed withNO_SWAP=true. Build ran, box rebooted ~20:11 UTC and has not answered ping/ssh since (>25 min) — needs the Hetzner console (no API token for that project on this host). - 2026-09-07 20:40 UTC — cc-ci branch
feat/nixos-module-export(9b99f81) pushed: the standalone#cc-cidrv is byte-identical before/after. Orchestrator branchfeat/combined-cc-ci-host:#cc-cievaluates (gcnwq4fy…-nixos-system-cc-ci-26.05.20260803.531670d.drv) with PROVISIONAL hardware/networking copied from the old CI server — to be replaced by the infect output.