Files
cc-ci/nix/modules/nightly-sweep.nix
autonomic-bot 2c61f2fadf
All checks were successful
continuous-integration/drone/push Build is passing
fix(canon): sweep runs with host PATH = Drone-runner env parity (DEFECT-3 git-lfs etc.)
The real timer fire redded gitea at the custom tier (git: 'lfs' is not a git command) — the
nightly-sweep writeShellApplication had a clean nix-only PATH, while Drone's recipe-CI runner runs
with PATH=/run/current-system/sw/bin:/run/wrappers/bin (where git-lfs + all host tooling live). My
manual sweeps used a login PATH that masked this. Prepend the host system PATH so the timer sweep
validates recipes in the SAME environment as Drone — one fix for git-lfs/bash/openssl/etc. parity.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 13:00:18 +00:00

69 lines
3.9 KiB
Nix

# Phase 2w / WC6 + phase canon — WEEKLY full-cold canonical sweep. A systemd TIMER fires weekly and
# runs `runner/nightly_sweep.py`: faithfully mirror-sync each recipe to upstream, then a SERIAL
# full-cold run across enrolled (WARM_CANONICAL) recipes — but only for those with a NEW RELEASE TAG
# newer than their canonical (canon §2.D), cold-testing that tagged version and promoting its
# canonical on green (canon §2.A tagged-promote). Serial = MAX_TESTS honored (one at a time); skips
# itself if a test is already in flight.
#
# canon M1.4 (hollow-sweep fix): the sweep runs FROM the deployed checkout at $CCCI_REPO=/etc/cc-ci
# (the nixos flake source, kept current by the deploy `git pull` + nixos-rebuild), NOT from a
# nix-store copy of runner/. The store copy carried no tests/, so enrolled_recipes() resolved
# TESTS_DIR to a nonexistent dir → [] → the timer was a no-op ("hollow sweep"). Running from
# /etc/cc-ci (which has runner/ AND tests/) is the same checkout run_recipe_ci already executes from,
# and lets sweep-logic changes ship via a checkout pull without a store rebuild.
{ pkgs, ... }:
let
# The sweep drives run_recipe_ci.py (pytest/playwright) — needs the full harness env like cc-ci-run.
pyEnv = pkgs.python3.withPackages (ps: with ps; [ pytest playwright ]);
sweep = pkgs.writeShellApplication {
name = "cc-ci-nightly-sweep";
# util-linux provides `script` (abra's PTY wrapper for backup/restore TTY ops) — same as cc-ci-run.
# bash: the sweep's mirror_sync shells out to `bash scripts/recipe-mirror-sync.sh`; writeShellApplication
# sets a clean PATH limited to runtimeInputs, so bash must be listed (a real timer fire caught its
# absence — manual ssh runs had bash on PATH and masked it).
runtimeInputs = with pkgs; [ bash abra docker git curl jq gnused gnugrep gnutar coreutils util-linux procps ];
text = ''
export HOME=/root
export PLAYWRIGHT_BROWSERS_PATH=${pkgs.playwright-driver.browsers}
export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
# ENV PARITY with the Drone recipe-CI runner (canon DEFECT-3): the recipes + their tests shell
# out to host tooling (git-lfs for gitea, openssl, etc.). Drone's exec runner runs them with
# PATH=/run/current-system/sw/bin:/run/wrappers/bin; writeShellApplication otherwise gives a
# clean nix-only PATH, so the timer sweep silently lacked tools the recipes assume (a real fire
# caught git-lfs + bash gaps that manual ssh runs, with a login PATH, masked). Prepend the host
# system PATH so the sweep validates recipes in the SAME environment Drone does.
export PATH="/run/current-system/sw/bin:/run/wrappers/bin:$PATH"
# canon M1.4: read enrollment + run the harness from the deployed checkout (has tests/).
export CCCI_REPO=/etc/cc-ci
cd "$CCCI_REPO"
exec ${pyEnv}/bin/python3 "$CCCI_REPO/runner/nightly_sweep.py"
'';
};
in
{
systemd.services.nightly-sweep = {
description = "Weekly canonical sweep: mirror-sync + new-release-tag-gated full-cold runs that promote canonicals";
after = [ "deploy-proxy.service" "warm-keycloak.service" "docker.service" ];
environment.HOME = "/root";
serviceConfig = {
Type = "oneshot";
# A full sweep across several recipes (each a cold deploy/test/teardown) is long; bound it.
TimeoutStartSec = "21600"; # 6h ceiling
ExecStart = "${sweep}/bin/cc-ci-nightly-sweep";
};
};
systemd.timers.nightly-sweep = {
description = "Weekly trigger for the full-cold canonical sweep (canon §2.F)";
wantedBy = [ "timers.target" ];
timerConfig = {
# canon §2.F: WEEKLY (operator preference) — a full-cold sweep over ~21 recipes is heavy;
# weekly is the chosen cadence. Sunday 03:00 UTC = a low-traffic slot (exact day/time not
# critical). Persistent catches up a missed run after downtime.
OnCalendar = "Sun *-*-* 03:00:00";
Persistent = true;
RandomizedDelaySec = "600";
};
};
}