commit c21cce51b9786689d97cb75ca02fdf370a7a17f7 Author: autonomic-bot Date: Tue May 26 21:07:31 2026 +0100 chore: bootstrap cc-ci loop state Co-Authored-By: Claude Opus 4.7 (1M context) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b114f2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# local secrets / env — never commit +.testenv +*.key +*.pem +!docs/**/*.pem +# python +__pycache__/ +*.pyc +.venv/ +.pytest_cache/ +# run-scoped ephemeral state (per §4.4 sidecars) +runs/ +# nix +result +result-* diff --git a/BACKLOG.md b/BACKLOG.md new file mode 100644 index 0000000..f65d2e5 --- /dev/null +++ b/BACKLOG.md @@ -0,0 +1,62 @@ +# BACKLOG — cc-ci + +Two single-writer sections (§6.1): Builder edits only `## Build backlog`; Adversary edits only +`## Adversary findings`. Closing an item = checking the box in your own section. + +## Build backlog + +### M0 — Foundations +- [ ] Author flake.nix (NixOS host cc-ci) + hosts/cc-ci/{configuration,hardware}.nix from baseline +- [ ] Deploy mechanism decision + first rebuild from repo (DECISIONS.md) +- [ ] sops-nix wiring: host age key, secrets/secrets.yaml, decrypt a test secret on host +- [ ] Gate: M0 — `ssh cc-ci 'systemctl is-system-running'` healthy after rebuild from repo + +### M1 — Swarm + abra target +- [ ] Docker + single-node swarm via Nix +- [ ] Traefik (file provider → /var/lib/ci-certs/live/) + per-run wildcard router +- [ ] abra installed; deploy + tear down a trivial recipe by hand over HTTPS +- [ ] Gate: M1 — recipe reachable over HTTPS at *.ci.commoninternet.net, torn down clean + +### M2 — Drone online +- [ ] Drone server + exec runner via Nix; Gitea OAuth app +- [ ] hello-world .drone.yml runs green; logs in Drone UI +- [ ] Gate: M2 — push to cc-ci triggers visible green build + +### M3 — Comment bridge +- [ ] comment-bridge service: HMAC verify, !testme exact match, collaborator check, Drone API call +- [ ] PR comment posting with run link +- [ ] Gate: M3 — live demo on scratch PR; auth enforced + +### M4 — Harness + install stage +- [ ] run_recipe_ci.py + conftest; install stage for recipe #1 + Playwright assertion; teardown +- [ ] Gate: M4 — green install run, no orphaned app/volume + +### M5 — Upgrade + backup/restore stages +- [ ] Add upgrade + backup/restore stages for recipe #1 +- [ ] Gate: M5 — upgrade preserves data; backup→mutate→restore returns original + +### M6 — Recipe-local tests + second recipe +- [ ] Discover/run recipe-repo tests/; enroll DB-backed recipe #2 +- [ ] Gate: M6 — both green; recipe-local tests merged + +### M6.5 — Breadth ramp (recipes 3→6) +- [ ] Enroll recipes 3–6 covering remaining D10 categories, no harness surgery +- [ ] Gate: M6.5 — recipes 3–6 three-stage green + +### M7 — Secrets hardening (D6) +- [ ] Full sops model, rotation doc, log redaction + leak test +- [ ] Gate: M7 — secret-grep finds nothing + +### M8 — Dashboard (D7) +- [ ] Overview page + badges + PR-comment outcome reflection +- [ ] Gate: M8 — overview matches reality; outcomes mirrored + +### M9 — Reproducibility + docs (D8/D9) +- [ ] docs/install.md from-scratch rebuild; all docs complete +- [ ] Gate: M9 — Adversary rebuilds from docs on throwaway host + +### M10 — Proof (D10) +- [ ] All six recipes green via real !testme PRs; flip STATUS to DONE + +## Adversary findings + diff --git a/DECISIONS.md b/DECISIONS.md new file mode 100644 index 0000000..eddcb64 --- /dev/null +++ b/DECISIONS.md @@ -0,0 +1,33 @@ +# DECISIONS — cc-ci Builder + +Architecture decisions and dead-ends. One line of rationale each. (§0, §8) + +## Settled + +- **Wildcard TLS:** operator pre-issues wildcard cert at `/var/lib/ci-certs/live/`; Traefik file + provider serves it; **no ACME** for commoninternet.net. (Plan §4.0/§8 — fixed.) +- **Repo:** `git.autonomic.zone/recipe-maintainers/cc-ci`, private. Bot is org admin. (Bootstrap.) +- **Git credentials:** helper script in repo-local git config sources `/srv/cc-ci/.testenv` at call + time — no secret values stored in `.git/config` or commits. + +## Open (defaults from §8, to confirm as reality lands) + +- **Deploy mechanism:** TBD in M0. Leaning `nixos-rebuild switch --flake` run *on cc-ci itself* + (repo cloned on host) rather than `--target-host`/deploy-rs from the sandbox, to avoid copying + large Nix closures over the userspace-tailscaled SOCKS proxy. Atomic-rollback is preserved by + Nix generations. Will record final choice + rationale when M0 lands. +- **Webhook scope:** default per-repo via enroll script. +- **Drone runner type:** default exec (must drive host abra). +- **Secret tool:** default sops-nix. +- **D10 recipe set:** lock six early. Candidates favouring already-mirrored: custom-html (simple), + cryptpad (stateful no-DB), keycloak (SSO/DB), matrix-synapse (DB+media), lasuite-docs (multi+S3), + bluesky-pds (TLS-passthrough) — covers all five categories. Confirm during M4–M6.5. + +## Risks + +- **Disk:** cc-ci has only ~3.8 GiB free on an 8.9 GiB root. Multiple recipe images + volumes may + exhaust it during M6.5 breadth. Mitigation: aggressive teardown + image prune; if insufficient, + request operator grow the VM disk (Incus, recreatable per the incus skill). Not yet blocking. + +## Dead-ends +- (none yet) diff --git a/JOURNAL.md b/JOURNAL.md new file mode 100644 index 0000000..83410c7 --- /dev/null +++ b/JOURNAL.md @@ -0,0 +1,24 @@ +# JOURNAL — cc-ci Builder (append-only) + +## 2026-05-26 — Bootstrap (§1) + +**Access verification (all pass):** +- `ssh cc-ci 'hostname && whoami && nixos-version'` → `nixos` / `root` / `24.11.719113.50ab793786d9 (Vicuna)` +- `curl https://git.autonomic.zone/api/v1/version` → `{"version":"1.24.2"}` +- Gitea bot auth (`curl -u $GITEA_USERNAME:$GITEA_PASSWORD .../api/v1/user`) → `login: autonomic-bot`, id 64 +- `getent hosts probe-$RANDOM.ci.commoninternet.net` → `143.244.213.108` (the gateway IP, as expected — TLS passthrough) +- Cert present: `ls /var/lib/ci-certs/live/` → `fullchain.pem` (2909 b), `privkey.pem` (227 b, mode 640) +- recipe-maintainers org exists (private); `recipe-maintainers/cc-ci` → 404 (created below) +- Mirrored recipes already present: bluesky-pds, lasuite-docs, custom-html, custom-html-tiny, n8n, + keycloak, lasuite-meet, matrix-synapse, cryptpad + +**Baseline (docs/baseline.md):** fresh NixOS 24.11 Incus VM, 2 vCPU, 3.5 GiB RAM, 8.9 GiB disk +(3.8 GiB free). No docker/swarm/abra. Channel-based `/etc/nixos/configuration.nix` (no flake). + +**Actions:** +- Created repo `recipe-maintainers/cc-ci` (private) via Gitea API. +- `git init` in /srv/cc-ci/cc-ci; credential helper reads creds from /srv/cc-ci/.testenv (no + secrets stored in git config). +- Seeded skeleton layout (§3) + loop-state files + docs/baseline.md. + +**Next:** commit + push bootstrap, then M0 (flake + base config + sops test secret). diff --git a/README.md b/README.md new file mode 100644 index 0000000..82fc764 --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# cc-ci — Co-op Cloud recipe CI server + +Comment **`!testme`** on a PR in an enrolled Co-op Cloud recipe repo and cc-ci deploys the recipe +at that commit onto a real single-node Docker Swarm, runs install / upgrade / backup-restore tests +(Python + Playwright) end-to-end, and reports a live, tail-able run with pass/fail back to the PR. + +This repo declares the **entire server** as a NixOS flake and holds the test harness, the +per-recipe test trees, and the docs to enroll a recipe or rebuild the box from scratch. + +> Status: under active autonomous construction. See `STATUS.md` for the live phase and +> `plan.md`-driven milestones in `BACKLOG.md`. Definition of Done is D1–D10 (see the build plan). + +## Layout + +``` +flake.nix NixOS host(s) + devshell +hosts/cc-ci/ the cc-ci machine config +modules/ drone, comment-bridge, swarm, dashboard, secrets (Nix modules) +secrets/ sops-encrypted infra secrets +bridge/ !testme webhook listener source +runner/ run_recipe_ci.py + shared pytest harness +dashboard/ results overview generator +tests// per-recipe install/upgrade/backup tests + playwright/ +docs/ install, enroll-recipe, secrets, architecture, runbook, baseline +``` + +## Docs + +- `docs/install.md` — rebuild the server from scratch (D8) +- `docs/enroll-recipe.md` — add a recipe under CI (D5) +- `docs/secrets.md` — secret model + rotation (D6) +- `docs/architecture.md`, `docs/runbook.md` — design + debugging failed runs +- `docs/baseline.md` — bootstrap snapshot / rollback reference + +## Loop state (autonomous build) + +`STATUS.md` (phase/blockers), `BACKLOG.md` (work + adversary findings), `REVIEW.md` (independent +verification), `JOURNAL.md` (build log), `DECISIONS.md` (architecture choices). See the build plan +for the two-loop Builder/Adversary protocol. diff --git a/REVIEW.md b/REVIEW.md new file mode 100644 index 0000000..3cc973b --- /dev/null +++ b/REVIEW.md @@ -0,0 +1,7 @@ +# REVIEW — cc-ci Adversary (append-only) + +This file is owned by the **Adversary** loop (§6.1). The Builder seeds this stub at bootstrap and +does not edit it afterward. Adversary appends milestone/D-item verdicts (`: PASS @` + +evidence, or `FAIL` + a finding in `BACKLOG.md ## Adversary findings`), and may write `## VETO`. + + diff --git a/STATUS.md b/STATUS.md new file mode 100644 index 0000000..7ab8747 --- /dev/null +++ b/STATUS.md @@ -0,0 +1,17 @@ +# STATUS — cc-ci Builder + +**Phase:** M0 — Foundations +**In-flight:** Bootstrap complete; starting M0 (flake + base config + sops test secret). +**Last updated:** 2026-05-26 (bootstrap) + +## Gates +- (none claimed yet) + +## Blocked +- (none) + +## Notes +- cc-ci baseline: Incus VM, 2 vCPU, 3.5 GiB RAM, **3.8 GiB free disk** — tight for multi-recipe + docker deploys; watch disk pressure, may need operator to grow the VM disk before M6.5 breadth. +- Server config is currently channel-based `/etc/nixos/configuration.nix` (no flake). M0 converts + to a flake checked out from this repo on the host. diff --git a/bridge/.gitkeep b/bridge/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/dashboard/.gitkeep b/dashboard/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/baseline.md b/docs/baseline.md new file mode 100644 index 0000000..a09da51 --- /dev/null +++ b/docs/baseline.md @@ -0,0 +1,48 @@ +# Baseline — cc-ci starting environment (rollback reference) + +Captured at bootstrap, 2026-05-26, before any Builder changes. This is the state to roll back to. + +## Host + +- Hostname: `nixos` (Tailscale node `cc-nix-test`, tailnet IP **100.90.116.4**, tailnet + `taila4a0bf.ts.net`). +- OS: **NixOS 24.11** `24.11.719113.50ab793786d9 (Vicuna)`. +- Virtualisation: **Incus VM** (imports `virtualisation/incus-virtual-machine.nix`), incus agent on. +- Resources: **2 vCPU, 3.5 GiB RAM, 8.9 GiB root disk (4.7 GiB used / 3.8 GiB free)**. +- Access: SSH as **root** (PermitRootLogin yes), reached from sandbox via userspace-tailscaled + SOCKS proxy `127.0.0.1:1055` → `ssh cc-ci`. + +## Installed / present + +- Config: **channel-based**, no flake. `/etc/nixos/`: + - `configuration.nix` — incus VM module, cloud-init, tailscale (auth-key file), openssh, + base pkgs (curl git jq openssh), firewall (trust tailscale0, allow tcp/22), DHCP, + nameservers 1.1.1.1/8.8.8.8, `nix.settings.experimental-features = [nix-command flakes]`, + `system.stateVersion = "24.11"`. + - `incus-base.nix` — tailscale auth-key + hostname from `/etc/ts-hostname`. + - `setup.sh` — original provisioning script (channel add + `nixos-rebuild boot` + sysrq reboot). +- **No** docker, **no** swarm, **no** abra installed. +- Tailscale up and authenticated (state persists; reconnects without key). + +## Provided infra inputs (operator-owned, do not improvise — §4.4 class A1) + +- Wildcard TLS cert at **`/var/lib/ci-certs/live/{fullchain.pem,privkey.pem}`** + (`*.ci.commoninternet.net` + `ci.commoninternet.net`, LE 90-day, next renewal ~2026-08-24). + Agent serves it via Traefik file provider; **never** runs ACME for this domain. +- DNS: wildcard `*.ci.commoninternet.net` (+ bare `ci.commoninternet.net`) → **gateway** + `143.244.213.108` (Gandi-hosted public zone). Gateway TLS-passthroughs the whole wildcard to + cc-ci by SNI; TLS terminates on cc-ci's Traefik. Per-run subdomains need no DNS/gateway/cert work. +- Gitea bot `autonomic-bot` (id 64), admin on private org `recipe-maintainers`. +- Tailscale auth key (reusable) — in `/srv/cc-ci/.testenv`. + +## Recipes already mirrored to recipe-maintainers (at bootstrap) + +`bluesky-pds`, `cryptpad`, `custom-html`, `custom-html-tiny`, `keycloak`, `lasuite-docs`, +`lasuite-meet`, `matrix-synapse`, `n8n`. Others (hedgedoc, authentik, immich, lasuite-drive) are +pulled from upstream git.coopcloud.tech and mirrored via the recipe mirror+PR flow (§4.1) as needed. + +## Rollback + +The original config is preserved above and in the host's Nix generations +(`nixos-rebuild --rollback` / boot menu). To fully revert, restore `/etc/nixos/*` to the channel +config above and `nixos-rebuild switch`. diff --git a/hosts/cc-ci/.gitkeep b/hosts/cc-ci/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/runner/harness/.gitkeep b/runner/harness/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/secrets/.gitkeep b/secrets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/_template/.gitkeep b/tests/_template/.gitkeep new file mode 100644 index 0000000..e69de29