From 52a96f657d50736a54b86be62ae647c16236dda4 Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 11 May 2026 22:29:39 +0200 Subject: [PATCH] docs(ci): document DooD runner architecture and nsenter pattern Replace the stale generic runner provisioning docs with an accurate description of the actual two-container setup on the Hetzner VPS. Document the nsenter pattern for running host-level commands (systemctl) from containerised CI steps, and the Caddyfile symlink contract that the reload step depends on. Co-Authored-By: Claude Sonnet 4.6 --- docs/infrastructure/ci-gitea.md | 45 ++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/docs/infrastructure/ci-gitea.md b/docs/infrastructure/ci-gitea.md index 3f96583e..f58bfde4 100644 --- a/docs/infrastructure/ci-gitea.md +++ b/docs/infrastructure/ci-gitea.md @@ -4,16 +4,49 @@ This document covers the Gitea Actions CI workflow for Familienarchiv, including --- -## Self-Hosted Runner Provisioning +## Runner Architecture -Gitea Actions requires self-hosted runners. GitHub Actions provides `ubuntu-latest` for free; on Gitea you run the runner yourself. +Familienarchiv uses **two runners** on the same Hetzner VPS: -```bash -# On the VPS โ€” register a Gitea Actions runner -docker run -d --name gitea-runner --restart unless-stopped -v /var/run/docker.sock:/var/run/docker.sock -v gitea-runner-data:/data -e GITEA_INSTANCE_URL=https://gitea.example.com -e GITEA_RUNNER_REGISTRATION_TOKEN= -e GITEA_RUNNER_NAME=vps-runner-1 -e GITEA_RUNNER_LABELS=ubuntu-latest:docker://node:20-bullseye gitea/act_runner:latest +| Runner | Purpose | Config | +|---|---|---| +| `gitea` (Docker container) | Hosts Gitea itself | `infra/gitea/docker-compose.yml` | +| `gitea-runner` (Docker container) | Runs all CI and deploy jobs | `infra/gitea/docker-compose.yml` + `/root/docker/gitea/runner-config.yaml` | + +Both containers live in the `gitea_gitea` Docker network on the VPS. The runner connects to Gitea via the LAN IP so job containers (which don't share the `gitea_gitea` network) can also reach it. + +### Docker-out-of-Docker (DooD) + +The `gitea-runner` container mounts the host Docker socket (`/var/run/docker.sock`). When a workflow job runs, act_runner spawns a **sibling container** for each job. That job container also gets the Docker socket mounted (via `valid_volumes` in `runner-config.yaml`), enabling `docker compose` calls in workflow steps. + +### Running host-level commands from CI (nsenter pattern) + +Job containers are unprivileged and do not share the host's PID/mount/network namespaces. Commands like `systemctl` that target the host daemon are therefore unavailable by default. When a workflow step needs to manage a host service (e.g. `systemctl reload caddy`), it uses the Docker socket to spin up a **privileged sibling container** in the host PID namespace: + +```yaml +- name: Reload Caddy + run: | + docker run --rm --privileged --pid=host \ + ubuntu:22.04 \ + nsenter -t 1 -m -u -n -p -i -- /bin/systemctl reload caddy ``` -The runner label `ubuntu-latest` maps to the Docker image it uses -- this is how `runs-on: ubuntu-latest` in the workflow YAML continues to work unchanged. +`nsenter -t 1 -m -u -n -p -i` enters the init process's mount, UTS, IPC, network, PID, and cgroup namespaces, giving `systemctl` a view of the real host systemd. No sudoers entry is required โ€” the Docker socket already grants root-equivalent host access. + +### Caddyfile symlink contract + +The deploy workflows reload Caddy to pick up committed Caddyfile changes. This relies on a symlink that must exist on the VPS: + +``` +/etc/caddy/Caddyfile โ†’ /opt/familienarchiv/infra/caddy/Caddyfile +``` + +Created once during server bootstrap (see `docs/DEPLOYMENT.md ยง3.1`). Verify with: + +```bash +ls -la /etc/caddy/Caddyfile +# Expected: lrwxrwxrwx ... /etc/caddy/Caddyfile -> /opt/familienarchiv/infra/caddy/Caddyfile +``` ---