The v0.8 milestone: installable on any Debian-based server via docker (Gitea registry image, honest sandbox degrade), deb (amd64+arm64, advisory postinst), and arch-aware binary — with release consistency enforced by pre-commit hooks + Gitea Actions (3-runner fleet on CT 113, machine-made releases with the full 8-asset matrix + registry image), the CLI carrying the full suite (guide, channel-aware doctor), and the admin runbook (deploy/README.md, evidence-backed recipes). ---ci--- phase: 6 milestone: v0.8 status: complete requirements: covered: [REQ-8-001, REQ-8-002, REQ-8-003, REQ-8-004, REQ-8-005] partial: [] ---/ci---
CI runner fleet (CT 113) — bring-up runbook
The reproducible definition of the Gitea Actions runner fleet on CT 113
(gitea-runner LXC, 10.0.0.113). This directory is the source of truth
(G-207): CT 113's /srv/nc-ci/ is a copy of these files — never hand-edited
box memory. deploy/README.md (the admin runbook) references these files.
Contents
| File | Purpose |
|---|---|
docker-compose.yaml |
Three sibling-mode runner services (nc-runner-node / nc-runner-python / nc-runner-release), image docker.io/gitea/runner:3, restart: always, host /var/run/docker.sock |
config-runner-{node,python,release}.yaml |
Per-runner CONFIG_FILE: runner.capacity: 1, cache server (enabled, host 10.0.0.113, port 8088 published 8081/8082/8083 per runner), health_check.min_free_disk_space_mb: 4096 |
nc-ci-prune.cron |
Daily host cache-hygiene cron (docker system prune -af --filter until=168h + docker volume prune -f) |
Label map (R-3 hybrid fleet)
| Label | Job image | Carried by |
|---|---|---|
node |
docker://node:24-bookworm |
nc-runner-node |
ubuntu-latest |
docker://docker.gitea.com/runner-images:ubuntu-latest |
nc-runner-node |
python |
docker://python:3.11-bookworm |
nc-runner-python |
release |
docker://node:24-bookworm |
nc-runner-release |
Fleet concurrency = 3 (capacity 1 per runner). Jobs whose runs-on matches
no runner label still run (on the runner's default image) — always pin one of
the four labels above in workflows.
Bring-up (fresh CT or reproduction)
-
Copy this directory to the CT:
scp -r deploy/ci/ root@10.0.0.113:/srv/nc-ci/ -
Verify the runner config schema on the deployed 3.x minor (R-risk-9 —
generate-configis the authority; adapt the threeconfig-runner-*.yamlkeys if renamed):docker run --rm docker.io/gitea/runner:3 gitea-runner generate-config > /tmp/gen-config.yaml -
Register and start — the registration token is passed via the shell environment ONLY (never committed, never in a file on disk; it is founder-provided from
/org/coreci/settings/actions/runners):cd /srv/nc-ci export REGISTRATION_TOKEN='<founder-provided org registration token>' docker compose up -d unset REGISTRATION_TOKENEach runner container registers itself on first start (
.runnerfile lands in its owndata-runner-*/volume — never shared, two runners on one file refuse to start via advisory lock), then execsgitea-runner daemon. -
Install the prune cron (root crontab):
crontab /srv/nc-ci/nc-ci-prune.cron
Bring-up variables
| Variable | Value | Where |
|---|---|---|
REGISTRATION_TOKEN |
founder-provided, session env only | compose interpolation (${REGISTRATION_TOKEN}) |
CT IP (cache.host) |
10.0.0.113 |
config-runner-*.yaml — if the CT IP changes, update all three |
Verification (post bring-up)
docker compose ps— three containersUp..runnerfiles exist:/srv/nc-ci/data-runner-{node,python,release}/.runner.docker logs nc-runner-node 2>&1 | tail— daemon connected, labels declared.- Org runners page:
/org/coreci/settings/actions/runners— all three visible + idle. - Cache ports reachable from a job container:
10.0.0.113:8081/8082/8083.
Secrets chain (G-205 outcome)
REGISTRY_TOKEN(Gitea Actions repo secret, consumed by P2's registry push): the self-serve PUT (PUT /api/v1/repos/coreci/nextcraft/actions/secrets/REGISTRY_TOKEN) SUCCEEDED on 2026-09-27 (HTTP 201). The value is the existingcoreci-botPAT — probe-verified it carrieswrite:package+write:repositoryscope (the registry token-flow JWT lists both), so it satisfies the G-205 requirement (package-write). No founder escalation needed for P1. If the registry push in P2 hits 401/403 anyway, the G-205 escalation stands: founder mints a PAT withwrite:package+write:repositoryand either PUTs it or hands it to the orchestrator. The secret value is never committed, echoed, or logged.GITEA_TOKEN(repo secret, release-asset ownership per D-064): not yet set — when set by the founder it also wins over the automatic job token inrelease.yaml(lowest-level-wins, R-13). The automatic job token's releases scope covers P1's asset-attach steps.
Notes
- Reboot persistence:
restart: alwayson all three services — the docker daemon (systemd) brings the compose stack back on CT reboot. Registration lives in the per-runner volumes; no re-registration needed. - Re-registration (new token) requires removing the old runner entries
in the Gitea org UI and deleting
data-runner-*/.runnerbefore restart. - Accepted risk: sibling mode shares the host docker daemon with job containers (org-scoped private repos only — see PLAN R-21 note; the CT hosts nothing else).
- Token lifecycle: after the fleet is green, the founder resets the
registration token in the org UI. The token's job is done once the three
.runnerfiles exist. - Root access: first contact uses the founder-provided root password ONCE
to install the repo's ed25519 deploy key into
/root/.ssh/authorized_keys; the founder then rotates the root password (G-206 — Wave 1-1 completion condition). Note: on this CT template, sshd runsPermitRootLogin without-password(password auth policy-blocked for root) — the deploy key is the access path, and the initial key install must happen founder-side via the Proxmox console/pct exec(see Wave 1-1 report).