Watch
0
0
Fork
You've already forked hyperhive
0

read the running rev and the hyperhive flake from /etc files, not the unit

This commit is contained in:
atlas 2026-11-06 04:44:27 +01:00 • committed by mara
commit aed7c8451a
4 changed files with 416 additions and 85 deletions

View file

@ -258,6 +258,14 @@ in
};
systemd.services.${forwarderUnit}.serviceConfig.LoadCredential = lib.attrNames storeFiles;
# The hyperhive source every build is made on. The store path changes
# with any edit outside `scripts/` and root-level `*.md`, so it is a
# file the daemon re-reads for each build, not part of the unit: a
# changed unit restarts the daemon and kills the build in progress.
environment.etc."hyperhive/swarm-builder.json".text = builtins.toJSON {
hyperhive_flake = hyperhiveFlake;
};
systemd.services.${unit} = {
description = "build agent configs for swarm-controller";
wantedBy = [ "multi-user.target" ];
@ -281,7 +289,6 @@ in
SWARM_BUILDER_NIX = "${nix}/bin/nix";
SWARM_BUILDER_CHECK_FLAKE = "${../builder/check-flake}";
SWARM_BUILDER_SYSTEM = pkgs.stdenv.hostPlatform.system;
SWARM_BUILDER_HYPERHIVE_FLAKE = hyperhiveFlake;
SWARM_BUILDER_NIXPKGS_FLAKE = nixpkgsFlake;
SWARM_BUILDER_FORGE_URL = "https://${forgeCfg.domain}";
SWARM_BUILDER_CONFIG_ORG = "agent-configs";

View file

@ -286,12 +286,18 @@ let
# helper answers only `https` for the forge.
git_base = "https://${forgeCfg.domain}";
github_disabled = !swarmCfg.github.enable;
# The rev this controller runs, which `internal/meta` pins hyperhive
# to once it descends from the committed pin; null on a dirty tree.
hyperhive_rev = config.services.hyperhive.deploy.hive-controller.hyperhiveRev;
};
};
# The rev this controller runs, which `internal/meta` pins hyperhive to once
# it descends from the committed pin; null on a dirty tree. It is a file the
# daemon re-reads on every meta update, not part of the unit: the rev differs
# on every commit, and a changed unit restarts the controller and drops its
# job graph.
runningRevFile = builtins.toJSON {
hyperhive_rev = deployCfg.hive-controller.hyperhiveRev;
};
# The swarm's own display name, for `GET /api/swarm` (swarm-ui's chrome).
# Gated on the option resolving, not defaulted to an empty string: an
# operator who never named the swarm gets `name: null` from the
@ -692,6 +698,9 @@ in
serviceConfig.SupplementaryGroups = [ clientsGroup ];
};
# Read by `meta_flake::running_rev`.
environment.etc."hyperhive/swarm-controller.json".text = runningRevFile;
# Installed host-wide, not into the daemon's unit: `swarmctl` is run
# by a human on this box and acts as root, so the daemon's sandbox is
# exactly what it must not inherit.

View file

@ -127,7 +127,7 @@ let
builderCheck =
agentModule:
let
src = env.SWARM_BUILDER_HYPERHIVE_FLAKE;
src = withBuilder.services.hyperhive.deploy.hyperhiveFlake;
hyperhive = (import "${src}/flake.nix").outputs (
self.inputs
// {
@ -262,10 +262,15 @@ let
}
{
# The check builds on the sources this host was deployed with, so a green check means
# the same hyperhive and nixpkgs base.
# the same hyperhive and nixpkgs base. The hyperhive source is a file the
# daemon re-reads, not part of its unit.
name = "the builder builds on the hyperhive and nixpkgs this host was deployed with";
ok =
env.SWARM_BUILDER_HYPERHIVE_FLAKE == withBuilder.services.hyperhive.deploy.hyperhiveFlake
(builtins.fromJSON (
builtins.unsafeDiscardStringContext inner.environment.etc."hyperhive/swarm-builder.json".text
)).hyperhive_flake
== builtins.unsafeDiscardStringContext withBuilder.services.hyperhive.deploy.hyperhiveFlake
&& !(env ? SWARM_BUILDER_HYPERHIVE_FLAKE)
&& env.SWARM_BUILDER_NIXPKGS_FLAKE == withBuilder.services.hyperhive.deploy.builder.nixpkgsFlake
&& lib.hasSuffix "-check-flake" env.SWARM_BUILDER_CHECK_FLAKE
&& env.SWARM_BUILDER_SYSTEM == pkgs.stdenv.hostPlatform.system

View file

@ -33,6 +33,14 @@ let
inherit (shared) allLocal;
# `allLocal` on another hyperhive commit and source, which differ from its
# own in nothing else.
allLocalMoved = hiveWithController {
deploy.singleHostSwarm = true;
deploy.hyperhiveFlake = "/nix/store/00000000000000000000000000000000-hyperhive-moved";
deploy.hive-controller.hyperhiveRev = lib.fixedWidthString 40 "b" "";
};
bare = shared.bareWithController;
# `lib.hasInfix` matches by regex, which refuses a needle that refers to a
@ -308,18 +316,68 @@ let
&& !((env "hive-priv") ? HIVE_PRIV_FORGE_URL);
}
{
# `hyperhive_rev` defaults to none on the controller, so a settings JSON
# `hyperhive_rev` defaults to none on the controller, so a rev file
# without the key is accepted and the pin would never move: the key has
# to be there, carrying the option it is wired from.
name = "the controller hands its running rev to the meta flake settings";
# to be there, carrying the option it is wired from. The settings JSON
# must not carry it: the daemon refuses a key it does not know, and a
# rev in the unit restarts the controller on every commit.
name = "the controller hands its running rev to the meta flake through its rev file";
ok =
let
revFile = builtins.fromJSON (
builtins.unsafeDiscardStringContext allLocal.environment.etc."hyperhive/swarm-controller.json".text
);
settings = builtins.fromJSON allLocal.systemd.services.swarm-controller.environment.SWARM_CONTROLLER_META_FLAKE;
in
settings ? hyperhive_rev
&& settings.hyperhive_rev == allLocal.services.hyperhive.deploy.hive-controller.hyperhiveRev
revFile ? hyperhive_rev
&& revFile.hyperhive_rev == allLocal.services.hyperhive.deploy.hive-controller.hyperhiveRev
&& !(settings ? hyperhive_rev)
&& settings ? git_base;
}
{
# A deploy that changes only the commit and the hyperhive source must
# leave both long-running units as they are, or systemd restarts them:
# the controller drops its job graph and the builder its build. The
# moved host differs in exactly those two options, and its files carry
# them, which is the control that the comparison reads what changed.
name = "a new hyperhive commit and source leave the controller's and the builder's units unchanged";
ok =
let
unit = m: m.systemd.services.swarm-controller;
builderUnit = m: m.containers.swarm-builder.config.systemd.services.swarm-builder;
same =
f:
let
a = f allLocal;
b = f allLocalMoved;
in
a.environment == b.environment
&& a.serviceConfig == b.serviceConfig
&& map toString a.path == map toString b.path;
etcText = m: name: builtins.unsafeDiscardStringContext m.environment.etc.${name}.text;
builderEtc = m: m.containers.swarm-builder.config;
movedRev = allLocalMoved.services.hyperhive.deploy.hive-controller.hyperhiveRev;
movedFlake = allLocalMoved.services.hyperhive.deploy.hyperhiveFlake;
in
same unit
&& same builderUnit
&& movedRev != allLocal.services.hyperhive.deploy.hive-controller.hyperhiveRev
&& movedFlake != allLocal.services.hyperhive.deploy.hyperhiveFlake
&&
(builtins.fromJSON (etcText allLocalMoved "hyperhive/swarm-controller.json")).hyperhive_rev
== movedRev
&&
etcText allLocal "hyperhive/swarm-controller.json"
!= etcText allLocalMoved "hyperhive/swarm-controller.json"
&&
(builtins.fromJSON (etcText (builderEtc allLocalMoved) "hyperhive/swarm-builder.json"))
.hyperhive_flake == builtins.unsafeDiscardStringContext movedFlake
&&
etcText (builderEtc allLocal) "hyperhive/swarm-builder.json"
!= etcText (builderEtc allLocalMoved) "hyperhive/swarm-builder.json"
&& !((builderUnit allLocal).environment ? SWARM_BUILDER_HYPERHIVE_FLAKE)
&& (builderUnit allLocal).environment ? SWARM_BUILDER_NIXPKGS_FLAKE;
}
{
# An agent connects to the queue with its own secret, so the hive hands
# its agents no shared credential and no token endpoint to mint one at.