# Recipe Report static site (report.ci.commoninternet.net): a public nginx serving the weekly # "Recipe Report" HTML pages written to /var/lib/cc-ci-reports by the /recipe-report skill. No app, # no secrets — just static files behind traefik + the wildcard TLS (same pattern as dashboard.nix, # but a plain nginx:alpine since there's nothing to render server-side). Content is updated by writing # files into /var/lib/cc-ci-reports; nginx serves them live (no redeploy needed). # # It ALSO serves a same-origin realtime PR-status proxy at /pr//: the report's STATUS # column fetches it client-side to show each PR's live state (open vs. ✓). Same-origin means no # dependency on the Gitea CORS allow-list; the recipe mirrors are public so no token is needed. The # proxy is pinned to recipe-maintainers + a safe recipe-name charset and is read-only (GET/HEAD). { pkgs, ... }: let reportsDir = "/var/lib/cc-ci-reports"; # Custom nginx server: static report files + the /pr// → Gitea-API proxy. Replaces the # stock /etc/nginx/conf.d/default.conf (which the image's nginx.conf includes inside http{}). nginxConf = pkgs.writeText "cc-ci-reports-default.conf" '' server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; # Realtime PR-status proxy for the Recipe Report STATUS column. # GET /pr// -> the PUBLIC Gitea PR JSON ({state, merged, ...}). Same-origin from # the browser's view, so no CORS dependency; unauthenticated, since the recipe mirrors are # public. The repo owner is hard-pinned to recipe-maintainers and the recipe name to a # slashless charset, so the proxied path can only ever address recipe-maintainers//pulls # (it cannot be coerced to another org or path). Only safe read methods are allowed. location ~ ^/pr/([a-z0-9._-]+)/([0-9]+)$ { limit_except GET HEAD { deny all; } resolver 127.0.0.11 ipv6=off valid=30s; # docker embedded DNS (forwards external names) proxy_ssl_server_name on; proxy_set_header Host git.autonomic.zone; proxy_set_header Accept "application/json"; proxy_pass https://git.autonomic.zone/api/v1/repos/recipe-maintainers/$1/pulls/$2; proxy_intercept_errors off; proxy_connect_timeout 5s; proxy_read_timeout 10s; add_header Cache-Control "no-store" always; # always fetch live state, never cache in the browser } location / { try_files $uri $uri/ =404; } } ''; stack = pkgs.writeText "cc-ci-reports-stack.yml" '' version: "3.8" services: app: image: nginx:alpine volumes: - type: bind source: ${reportsDir} target: /usr/share/nginx/html read_only: true - type: bind source: ${nginxConf} target: /etc/nginx/conf.d/default.conf read_only: true networks: - proxy deploy: replicas: 1 restart_policy: condition: any labels: - "traefik.enable=true" - "traefik.http.services.ccci-reports.loadbalancer.server.port=80" - "traefik.http.routers.ccci-reports.rule=Host(`report.ci.commoninternet.net`)" - "traefik.http.routers.ccci-reports.entrypoints=web-secure" - "traefik.http.routers.ccci-reports.tls=true" networks: proxy: external: true ''; reconcile = pkgs.writeShellApplication { name = "cc-ci-reconcile-reports"; runtimeInputs = with pkgs; [ docker coreutils ]; text = '' mkdir -p ${reportsDir} # Seed a placeholder index so the site serves something before the first report is generated. if [ ! -f ${reportsDir}/index.html ]; then cat > ${reportsDir}/index.html <<'HTML' The Recipe Report

🌻 The Recipe Report

No reports yet — the first one is generated after the weekly recipe-upgrade run.

HTML fi docker stack deploy --detach=true -c ${stack} ccci-reports ''; }; in { systemd.services.deploy-reports = { description = "Reconcile the cc-ci Recipe Report static site (report.ci.commoninternet.net)"; # Ordering-only: chain after the dashboard (proxy→…→dashboard→reports) to avoid concurrent # docker-init races on a fresh host. after = [ "deploy-dashboard.service" "deploy-proxy.service" "swarm-init.service" "docker.service" "network-online.target" ]; requires = [ "swarm-init.service" "docker.service" ]; wants = [ "network-online.target" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; ExecStart = "${reconcile}/bin/cc-ci-reconcile-reports"; }; }; }