# hive-ci: Forgejo Actions Runner The `hive-ci` module runs a Forgejo Actions runner in a `hive-ci` nixos-container, executing CI jobs from `.forgejo/workflows/ci.yml` (e.g., `nix flake check` on every PR). ## Operator bootstrap Set `services.hyperhive.ci.enable = true` in the host NixOS config. That's it — no manual token provisioning. **Requirements:** - `services.hyperhive.forge.enable = true` must also be set (the runner registers against hive-forge). - Optional: tune `services.hyperhive.ci.name` (runner name in forge admin panel), `concurrency` (parallel job capacity), `labels` (workflow targeting). ## Container design - **Shared host netns**: container reaches hive-forge at `http://127.0.0.1:` (same as hive-gateway). - **Non-ephemeral**: runner credentials persist across restarts (written to container's stateDir on first registration, reused thereafter). - **Sandbox fallback**: nspawn containers can't create user-namespaces, so nix's sandboxing would always fail. Module sets `nix.settings.sandbox-fallback = true` in the container — nix builds run unsandboxed (safe because the container is already isolated). See `docs/gotchas.md`. ## Auto-registration flow `hive-ci-register.service` is a oneshot that runs on **every boot** before `gitea-runner-hive.service`. It handles both first-run registration and stale-credential detection. ### Every boot 1. Read the core admin token from `/run/hive-ci/core-token` (bind-mounted from `/var/lib/hyperhive/forge-core-token`). 2. If `.runner` exists: validate the runner ID against `GET /api/v1/admin/runners/{id}` using the core token: - **200**: runner still registered — write dummy `TOKEN=placeholder` and exit; runner reuses `.runner` credentials. - **404**: runner was deleted from forge (e.g. after a wipe) — delete `.runner`, proceed to re-registration below. - **000** (forge unreachable): keep existing `.runner`; the runner itself will surface the connectivity error. - **other non-200**: treat as stale, delete `.runner` and re-register. - **malformed `.runner`** (no `id` field): delete and re-register. 3. If `.runner` is absent (first boot or purged above): fetch a fresh registration token from `GET /api/v1/admin/runners/registration-token`. Retries for 30s in case forge is still starting. Writes `TOKEN=` to `/run/hive-ci/runner-token`. 4. `gitea-actions-runner` reads the token, registers itself, and persists credentials to `.runner`. On subsequent boots step 2 validates these credentials and fast-paths past registration. ## CI workflow The single CI job is defined in `.forgejo/workflows/ci.yml`: ```yaml name: CI on: pull_request: branches: ["**"] jobs: check: name: nix flake check runs-on: [hive-ci] steps: - uses: actions/checkout@v3 - name: check run: nix flake check ``` This runs on every PR, executing all flake checks (treefmt, rustfmt, cargo test, cargo clippy, module evaluation). No `--no-build`: the checks' derivations are the canonical source of truth. ## Security: unsandboxed builds and trusted contributors **hive-ci should only run CI for trusted contributors.** The security boundary is weaker than it looks: ### What unsandboxed builds mean nspawn containers cannot create user-namespaces, so `nix.settings.sandbox-fallback = true` is set in the container. This means every `nix build` (and `nix flake check`) runs **without a build sandbox** — the build process has full access to the container filesystem, network, and any bind-mounts during the build phase. A malicious `default.nix` or build script in a PR can therefore: - **Read the core admin token** at `/run/hive-ci/core-token` (bind-mounted into the container for runner auto-registration). This token has `read:admin + write:admin` scopes on hive-forge — enough to read any repo, enumerate users, and issue forge admin API calls. - **Make arbitrary network requests** to any address reachable from the container. The container shares host netns, so `http://127.0.0.1:` is reachable. - **Write to the container filesystem**, including corrupting the runner's state dir or `.runner` credentials. Note: nix *evaluation* (`nix flake check` with `--no-build`) is safer — evaluation is sandboxed at the nix level. Full `nix flake check` (which builds derivations) is not. ### Mitigation For a hive used by a single operator or a small trusted team, the risk is low — all contributors are already trusted with forge access anyway. For repos with external contributors or fork PRs: - Use Forgejo's **fork PR approval workflow** (`repository.settings` → "Require approval for fork PRs from first-time contributors") to gate CI until a maintainer approves the first PR. - Or restrict the CI workflow trigger to push events on branches (not `pull_request` from forks) — forks can't push to upstream branches. - As a structural fix, move `core-token` out of the container bind-mount tree and use a scoped registration-only token. That work is tracked separately. The current design is appropriate for a trusted-team hive where all contributors have implicit forge access. ## References - `nix/modules/hive-ci.nix`: runner configuration, auto-registration script, container setup. - `.forgejo/workflows/ci.yml`: workflow definition. - `docs/gotchas.md`: nix sandboxing limitations in containers.