refactor(1b): RL5 — consolidate Nix code under nix/ (modules->nix/modules, hosts->nix/hosts)
flake.nix/flake.lock STAY at root so the build ref #cc-ci is unchanged; only flake's internal configuration.nix path updated. Root-relative refs inside moved modules re-based ../X -> ../../X (secrets/bridge/dashboard); configuration.nix's ../../modules imports unchanged (both dirs under nix/). Living docs (README, architecture/install/secrets/enroll) + .drone.yml comment updated to nix/...; append-only history logs left as-is. DECISIONS.md records RL5 + the deferred-coordinated RL6. Verified on cc-ci: nixos-rebuild build 'path:#cc-ci' -> toplevel 8i3jcad9 (BYTE-IDENTICAL to the pre-move build — store derivations are content-addressed on file contents, module .nix not in the runtime closure); scripts/lint.sh -> lint: PASS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
# Results dashboard (§4.5, D7): the YunoHost-CI-like overview at ci.commoninternet.net. Reads the
|
||||
# Drone API (read-only) and renders latest-run-per-recipe + SVG badges. Packaged as a Nix-built OCI
|
||||
# image and run as a swarm service on `proxy`, routed by traefik at Host(ci.commoninternet.net) — the
|
||||
# comment-bridge's Host && PathPrefix(`/hook`) rule is longer, so /hook still wins (priority by rule
|
||||
# length). Deployed by an idempotent-reconcile oneshot (same pattern as bridge/drone).
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
dashApp = pkgs.runCommand "cc-ci-dashboard-app" { } ''
|
||||
mkdir -p $out/app
|
||||
cp ${../../dashboard/dashboard.py} $out/app/dashboard.py
|
||||
'';
|
||||
|
||||
# Content-derived tag: changes whenever dashboard.py changes, so `docker stack deploy` actually
|
||||
# rolls the service to the new image (a fixed `:latest` tag + unchanged stack spec does NOT roll —
|
||||
# swarm sees no change). Reproducible + self-healing.
|
||||
imageTag = builtins.substring 0 12 (builtins.hashString "sha256"
|
||||
(builtins.readFile ../../dashboard/dashboard.py));
|
||||
|
||||
image = pkgs.dockerTools.buildLayeredImage {
|
||||
name = "cc-ci-dashboard";
|
||||
tag = imageTag;
|
||||
contents = [ pkgs.python3 pkgs.cacert dashApp ];
|
||||
config = {
|
||||
Cmd = [ "${pkgs.python3}/bin/python3" "/app/dashboard.py" ];
|
||||
Env = [ "SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" ];
|
||||
ExposedPorts = { "8080/tcp" = { }; };
|
||||
};
|
||||
};
|
||||
|
||||
stack = pkgs.writeText "cc-ci-dashboard-stack.yml" ''
|
||||
version: "3.8"
|
||||
services:
|
||||
app:
|
||||
image: cc-ci-dashboard:${imageTag}
|
||||
environment:
|
||||
- DRONE_URL=https://drone.ci.commoninternet.net
|
||||
- CI_REPO=recipe-maintainers/cc-ci
|
||||
- DASH_LISTEN=0.0.0.0:8080
|
||||
- DRONE_TOKEN_FILE=/run/secrets/drone_token
|
||||
secrets:
|
||||
- drone_token
|
||||
networks:
|
||||
- proxy
|
||||
deploy:
|
||||
replicas: 1
|
||||
restart_policy:
|
||||
condition: any
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.services.ccci-dashboard.loadbalancer.server.port=8080"
|
||||
- "traefik.http.routers.ccci-dashboard.rule=Host(`ci.commoninternet.net`)"
|
||||
- "traefik.http.routers.ccci-dashboard.entrypoints=web-secure"
|
||||
- "traefik.http.routers.ccci-dashboard.tls=true"
|
||||
networks:
|
||||
proxy:
|
||||
external: true
|
||||
secrets:
|
||||
drone_token:
|
||||
external: true
|
||||
name: cc_ci_dashboard_drone_token_v1
|
||||
'';
|
||||
|
||||
reconcile = pkgs.writeShellApplication {
|
||||
name = "cc-ci-reconcile-dashboard";
|
||||
runtimeInputs = with pkgs; [ docker coreutils ];
|
||||
text = ''
|
||||
if [ ! -r /run/secrets/bridge_drone_token ]; then
|
||||
echo "FATAL: /run/secrets/bridge_drone_token missing (rebuild ordering?)" >&2
|
||||
exit 1
|
||||
fi
|
||||
docker load -i ${image}
|
||||
# Dashboard reads the Drone API read-only; reuse the same Drone token value as the bridge.
|
||||
docker secret inspect cc_ci_dashboard_drone_token_v1 >/dev/null 2>&1 \
|
||||
|| docker secret create cc_ci_dashboard_drone_token_v1 /run/secrets/bridge_drone_token >/dev/null
|
||||
docker stack deploy --detach=true -c ${stack} ccci-dashboard
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
systemd.services.deploy-dashboard = {
|
||||
description = "Reconcile the cc-ci results dashboard (overview + badges) swarm service";
|
||||
# Serialized after deploy-bridge (chain proxy→drone→bridge→dashboard→backupbot) to avoid the
|
||||
# concurrent abra-init race on a fresh host (see bridge.nix). Ordering-only.
|
||||
after = [ "deploy-bridge.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-dashboard";
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user