diff --git a/runner/.gitignore b/runner/.gitignore index f25c230..581407f 100644 --- a/runner/.gitignore +++ b/runner/.gitignore @@ -6,3 +6,6 @@ result # committed; the public key is regenerated per deployment too # (`nix-store --generate-binary-cache-key`). cache/ + +# Caddy state: ACME account + issued certs + on-disk config tree. +caddy/ diff --git a/runner/Caddyfile b/runner/Caddyfile new file mode 100644 index 0000000..a56caeb --- /dev/null +++ b/runner/Caddyfile @@ -0,0 +1,43 @@ +# Caddy config for the cargoxx binary cache. +# +# The router does PAT (port forwarding) so the *external* world reaches +# us at the standard 80/443 but the *internal* ports are different. +# `http_port` and `https_port` below must match the internal ports the +# router forwards to. Override via runner/.env: +# +# CADDY_HTTP_PORT=8080 +# CADDY_HTTPS_PORT=8443 +# +# (Those env vars are picked up by compose.yml to publish the ports +# AND injected into this Caddyfile via the {$VAR:default} substitution +# below — Caddy expands env vars natively.) + +{ + # Internal ports — must equal whatever the router forwards 80/443 to. + http_port {$CADDY_HTTP_PORT:8080} + https_port {$CADDY_HTTPS_PORT:8443} + # ACME's HTTP-01 challenge probe still arrives at host:80 → router + # → :8080; Caddy answers it on the internal port. Auto cert works + # as long as the PAT maps 80 → CADDY_HTTP_PORT and 443 → CADDY_HTTPS_PORT. + email vorontsov@amadey.xyz +} + +cache.cargoxx.amadey.xyz { + root * /srv/cache + file_server + + # narinfo / nar are immutable per content hash → cache aggressively. + @cache_immutable path *.narinfo *.nar.xz *.nar + header @cache_immutable Cache-Control "public, immutable, max-age=31536000" + + # Substituter probe; short cache so new entries land quickly. + @cache_info path /nix-cache-info + header @cache_info Cache-Control "public, max-age=300" + + log { + output file /data/access.log { + roll_size 50MiB + roll_keep 5 + } + } +} diff --git a/runner/README.md b/runner/README.md index 0710431..f2d2361 100644 --- a/runner/README.md +++ b/runner/README.md @@ -61,20 +61,28 @@ Self-hosted Gitea Actions runner that validates package PRs. The `cache/` directory is gitignored. Both keys live alongside `compose.yml`; the named volume binds use `${PWD}/cache/...`. -5. **(optional) Front the store with Caddy** so substituters can read it. - A ready-to-edit `Caddyfile.example` ships in this directory — copy - into `/etc/caddy/Caddyfile` (or `import` it) and reload: +5. **Pick the Caddy ports.** `compose.yml` runs Caddy alongside the + runner to HTTPS-front the cache. Because the router does PAT, the + *internal* ports Caddy listens on must equal whatever 80/443 are + forwarded to. Add to `.env`: - ```sh - sudo install -m644 Caddyfile.example /etc/caddy/conf.d/cargoxx-cache - sudo systemctl reload caddy + ```env + CADDY_HTTP_PORT=8080 + CADDY_HTTPS_PORT=8443 ``` - Caddy auto-provisions a Let's Encrypt cert. Consumers later need - `substituters = https://cache.cargoxx.` and - `trusted-public-keys = ` in their nix config - — those go into the cargoxx wrapper (`cargoxx`'s own `flake.nix`), - so any user installing the bundled cargoxx picks them up. + Both compose.yml and the Caddyfile pick those up. The Caddyfile + already targets `cache.cargoxx.amadey.xyz` and the e-mail + `vorontsov@amadey.xyz`; edit if you're deploying somewhere else. + + ACME provisioning works as long as the router forwards 80 → + CADDY_HTTP_PORT and 443 → CADDY_HTTPS_PORT, so Let's Encrypt's + HTTP-01 challenge reaches Caddy. + + Consumers' substituter config (`substituters = https://cache.`, + `trusted-public-keys = `) is baked into cargoxx's own + wrapper (`cargoxx/flake.nix:cargoxxNixConfig`), so any installed + `cargoxx` binary picks them up — no per-user setup needed. 6. **Start the runner**: diff --git a/runner/compose.yml b/runner/compose.yml index bd3c810..1aba4b9 100644 --- a/runner/compose.yml +++ b/runner/compose.yml @@ -1,9 +1,11 @@ -# Runs the act_runner that listens to Gitea and spawns one job -# container per workflow run. The job image (cargoxx-runner-job:latest) -# is built reproducibly from runner/flake.nix — run `nix run .#load-image` -# in this directory to load it into the host's Docker daemon before -# starting the runner. -version: "3.8" +# Runs two services on the host: +# - act_runner — polls Gitea, spawns one job container per workflow +# run via the host docker socket. Job image built reproducibly from +# runner/flake.nix (`nix run .#load-image`). +# - caddy — HTTPS-fronts the binary cache (./cache/store) so +# consumers' substituter config can read it. Custom ports because +# the router does PAT (port-forwarding 80→CADDY_HTTP_PORT, +# 443→CADDY_HTTPS_PORT). Set those in .env. services: runner: image: docker.io/gitea/act_runner:nightly @@ -20,12 +22,26 @@ services: - /var/run/docker.sock:/var/run/docker.sock # Binary cache — `validate-pr.yml`'s push step writes `$out` NAR # archives here. Named volumes (defined below) make the same - # storage reachable from both this runner container AND every - # job container act_runner spawns. nginx (on the host) serves - # ./cache/store over HTTPS for consumers' substituter config. + # storage reachable from this runner container AND every job + # container act_runner spawns AND the caddy frontend below. - cargoxx-cache-store:/srv/cargoxx-cache/store - cargoxx-cache-keys:/srv/cargoxx-cache/keys:ro + caddy: + image: docker.io/caddy:2 + restart: unless-stopped + ports: + - "${CADDY_HTTP_PORT:-8080}:${CADDY_HTTP_PORT:-8080}" + - "${CADDY_HTTPS_PORT:-8443}:${CADDY_HTTPS_PORT:-8443}" + environment: + CADDY_HTTP_PORT: "${CADDY_HTTP_PORT:-8080}" + CADDY_HTTPS_PORT: "${CADDY_HTTPS_PORT:-8443}" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - cargoxx-cache-store:/srv/cache:ro + - caddy-data:/data + - caddy-config:/config + volumes: cargoxx-cache-store: # Explicit name disables compose's project-prefix so spawned job @@ -44,3 +60,19 @@ volumes: type: none o: bind device: "${PWD}/cache/keys" + caddy-data: + # Caddy's own state: ACME account, issued certificates, OCSP + # staples. Persist so we don't re-issue certs every restart. + name: caddy-data + driver: local + driver_opts: + type: none + o: bind + device: "${PWD}/caddy/data" + caddy-config: + name: caddy-config + driver: local + driver_opts: + type: none + o: bind + device: "${PWD}/caddy/config"