From 493d2e6c379f8f600f63ab503d575e1647d1c284 Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 11 Nov 2026 18:16:54 +0100 Subject: [PATCH] ci-runner: warn when the host has no nix store gc --- docs/4-implementation/scheduler/ci.md | 6 ++++-- docs/examples/semi-spread/swarm-heavyduty.nix | 12 +++++++++++ nix/host-modules/swarm-ci-runner.nix | 11 ++++++++++ nix/module-eval/forge-placement.nix | 21 ++++++++++++++++++- 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/docs/4-implementation/scheduler/ci.md b/docs/4-implementation/scheduler/ci.md index 796a2403f..b36c167b1 100644 --- a/docs/4-implementation/scheduler/ci.md +++ b/docs/4-implementation/scheduler/ci.md @@ -314,8 +314,10 @@ CI day can fill the disk until every job fails fast with `ENOSPC`. Store GC is a **host-level** concern, so it belongs in the host's own NixOS configuration, not in the hyperhive service modules — a single service should -not reach out and change the host's global nix-daemon options. Add the -following to your host config: +not reach out and change the host's global nix-daemon options. The module +only warns at `nixos-rebuild` time when the host sets neither +`nix.gc.automatic` nor `nix.settings.min-free`. Add the following to your +host config: ```nix { diff --git a/docs/examples/semi-spread/swarm-heavyduty.nix b/docs/examples/semi-spread/swarm-heavyduty.nix index 9dda80eea..183babcd5 100644 --- a/docs/examples/semi-spread/swarm-heavyduty.nix +++ b/docs/examples/semi-spread/swarm-heavyduty.nix @@ -32,6 +32,18 @@ in ]; }; + # CI builds land in this host's store; the runner module warns without a + # collector. See docs/4-implementation/scheduler/ci.md, "Host store + # maintenance", for the daily timer and the disk-pressure settings. + nix.gc = { + automatic = true; + dates = "daily"; + randomizedDelaySec = "45min"; + options = "--delete-older-than 1d"; + }; + nix.settings.min-free = 20 * 1024 * 1024 * 1024; + nix.settings.max-free = 50 * 1024 * 1024 * 1024; + # The cache is served through the gateway, over the VPN only. networking.firewall.interfaces.wg0.allowedTCPPorts = [ hyperhive.gateway.httpsPort ]; } diff --git a/nix/host-modules/swarm-ci-runner.nix b/nix/host-modules/swarm-ci-runner.nix index 1f2e217e9..0e125d865 100644 --- a/nix/host-modules/swarm-ci-runner.nix +++ b/nix/host-modules/swarm-ci-runner.nix @@ -236,6 +236,17 @@ in } ]; + # The runner builds through the host's nix daemon, so its outputs fill the + # host's /nix/store. The host's nix settings are not this module's to set; + # it only tells the operator when neither collector is configured. + warnings = lib.optional (!config.nix.gc.automatic && (config.nix.settings.min-free or 0) == 0) '' + services.hyperhive.deploy.ci-runner.enable is on, but this host sets + neither nix.gc.automatic nor nix.settings.min-free. CI builds fill + /nix/store until the disk is full and every job fails with ENOSPC. + Add the GC settings from "Host store maintenance" in + docs/4-implementation/scheduler/ci.md to the host's NixOS config. + ''; + # The container hangs off the bridge, takes its address from the # gateway's DHCP and resolves through its dnsmasq. The gateway also runs # `hive-tls-ca`, which writes the trust bundle bound in below. diff --git a/nix/module-eval/forge-placement.nix b/nix/module-eval/forge-placement.nix index 6473953fc..fdd4fbb32 100644 --- a/nix/module-eval/forge-placement.nix +++ b/nix/module-eval/forge-placement.nix @@ -24,6 +24,7 @@ let ; }) hive + hiveWith runGroup selfScrapes ; @@ -56,7 +57,7 @@ let # The runner alone: no hive, no forge, no store, its store identity placed # by hand. `nix` is a label without a scheme. - ciAlone = hive { + ciAloneOpts = { deploy.hive-controller.enable = false; deploy.ci-runner.enable = true; deploy.ci-runner.labels = [ @@ -66,6 +67,13 @@ let deploy.bao.ciRunnerClientCertFile = "/etc/pki/bao-ci-runner.pem"; deploy.bao.ciRunnerClientKeyFile = "/etc/pki/bao-ci-runner-key.pem"; }; + ciAlone = hive ciAloneOpts; + # `ciAlone` with the host's store collection set, one way each. + ciGcTimer = hiveWith [ { nix.gc.automatic = true; } ] ciAloneOpts; + ciMinFree = hiveWith [ { nix.settings.min-free = 20 * 1024 * 1024 * 1024; } ] ciAloneOpts; + ciOffNoGc = hive { deploy.hive-controller.enable = false; }; + ciGcWarned = + m: lib.any (lib.hasInfix "neither nix.gc.automatic nor nix.settings.min-free") m.warnings; ciNoIdentity = hive { deploy.hive-controller.enable = false; deploy.ci-runner.enable = true; @@ -211,6 +219,17 @@ let in !(lib.any (n: lib.elem n (u.after ++ u.wants ++ u.requires)) storeUnits); } + { + # The module only warns: the host's nix settings stay the host's. Each + # collector alone, and the runner being off, silence the warning. + name = "the CI runner warns when the host has neither nix.gc.automatic nor nix.settings.min-free"; + ok = + ciGcWarned ciAlone + && !(ciGcWarned ciGcTimer) + && !(ciGcWarned ciMinFree) + && !(ciGcWarned ciOffNoGc) + && !ciAlone.nix.gc.automatic; + } { # Without the identity the token never arrives; the control is the # placed pair above.