Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/nix/module-eval/core-toggle.nix
atlas bd79b58e78 log the agent bridge's forwarded flows in the hive_egress table
A forward chain in the hive-owned nft table logs each new flow from the agent bridge to the uplink (v4 and v6) and counts every packet. It never blocks. The mesh interface is skipped. Logging is rate limited per source address; a flow that limit cannot log gets one line from a shared limit with its own prefix. services.hyperhive.network.egress.forward selects accept, log (default) or drop; drop is refused at evaluation until the interception it needs exists.
2026-11-12 09:20:51 +01:00

2492 lines
114 KiB
Nix

# `checks.module-eval-core-toggle` — see ./lib.nix for the shared
# rationale (why this suite exists, naming convention, "evaluates
# not executes").
{
pkgs,
lib,
self,
nixosSystem,
shared,
}:
let
inherit
(import ./lib.nix {
inherit
pkgs
lib
self
nixosSystem
;
})
hive
hiveWith
hiveWithController
runGroup
forceServiceConfigs
baoNames
baoStream
bridgePorts
bridgePortsOrNone
swarmServiceEnables
otelSettings
;
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
# store path. Both sides lose their string context here.
hasStorePath =
needle: hay:
lib.hasInfix (builtins.unsafeDiscardStringContext needle) (builtins.unsafeDiscardStringContext hay);
# The same stub with the central toggle (`deploy.hive-controller.enable`)
# off. Paired with `bare` below to pin the defaults that do not read it:
# each is asserted to hold the SAME literal in both, so a future edit
# that quietly introduces the dependency — or that changes what the
# default renders for a hive with the toggle on — fails here. Reading an
# option off this fixture forces that option only, not the config, so the
# toggle being off costs nothing. Also the "installs the modules and turns
# nothing on" host the swarm-service absences below read: none of the
# per-service deployment toggles derives from the hive being on, so it
# renders the same absences `bare` does.
centralToggleOff = shared.bare;
inherit (shared) forgeHere;
# A host that turns `route_localnet` on, for the assertion in
# ../host-modules/hive-egress.nix: the kernel then routes a bridge packet
# for 127.0.0.0/8 instead of dropping it, so the config is refused.
routeLocalnetOn = hiveWith [
{ boot.kernel.sysctl."net.ipv4.conf.all.route_localnet" = 1; }
] { network.enable = true; };
# The two settings of `egress.forward` the ordinary host (`log`) does not
# render, for ../host-modules/hive-egress.nix: `accept` leaves the table
# without a forward chain, `drop` is refused by an assertion.
egressForwardAccept = hive {
network.enable = true;
network.egress.forward = "accept";
};
egressForwardDrop = hive {
network.enable = true;
network.egress.forward = "drop";
};
# Every hive defaults the agents' queue address to the queue's name, so the
# only hive without one is a hive told to have none. Also the host still
# setting the four removed host options; no case on its queue reads them.
noAgentQueue = hiveWithController {
deploy.hive-controller.queue.agentNatsUrl = null;
c0re.operatorPronouns = "they/them";
c0re.contextWindowTokens.sonnet = 900000;
otel.metricIntervalMs = 10000;
otel.debug = true;
};
# The controller behind a gateway on a non-default TLS port, with the
# firewall opened, a private IPv4 and IPv6 address, and one vhost kept off the
# public address. Its collector URL and its listeners are read off it.
controllerAltPort = hive {
deploy.swarm-controller.enable = true;
gateway.httpsPort = 8443;
gateway.openFirewall = true;
gateway.privateListenAddresses = [
"10.100.0.1"
"fd00:100::1"
];
gateway.vhosts."internal.t.local".public = false;
};
# A private address that is the wildcard, beside a vhost that asks to be
# private: the listen set is well-formed, the privacy it promises is not.
privateWildcard = hive {
gateway.privateListenAddresses = [ "0.0.0.0" ];
gateway.vhosts."internal.t.local".public = false;
};
# Only its `networking.enableIPv6` default is read.
noIPv6 = hiveWith [ { networking.enableIPv6 = false; } ] { };
gatewayListen = import ../host-modules/hive-gateway/listen.nix { inherit lib; };
# nginx fills defaults into each `listen` entry; compare the fields the
# gateway sets.
listenFields = map (l: {
inherit (l) addr port;
ssl = l.ssl or false;
});
httpAt = addr: port: {
inherit addr port;
ssl = false;
};
httpsAt = addr: port: {
inherit addr port;
ssl = true;
};
# A container the operator runs that hyperhive doesn't own, next to one it
# does (the classifier's).
withForeignContainer = hiveWith [ { containers.operator-box.config = { }; } ] {
deploy.bao.enable = true;
deploy.review-classifier.enable = true;
};
# On the store's host, which mints the runner's store identity.
withCi = hive {
deploy.bao.enable = true;
deploy.ci-runner.enable = true;
};
# On the store's host, which mints the classifier's store identity.
withClassifier = hive {
deploy.bao.enable = true;
deploy.review-classifier.enable = true;
};
# The same classifier on a host that runs a hive: its metrics path is the
# same as without one.
withClassifierOnHive = hiveWithController {
deploy.bao.enable = true;
deploy.review-classifier.enable = true;
};
# The host fetches that restart their consumer when the store's value
# changes (../host-modules/lib/refresh-consumer.nix), each on a fixture
# that renders it.
storeRefreshed = [
{
unit = "swarm-bao-grafana-oidc";
machine = allLocal;
}
{
unit = "swarm-bao-otel-oidc";
machine = allLocal;
}
{
unit = "swarm-bao-matrix-oidc";
machine = allLocal;
}
{
unit = "swarm-bao-forge-oidc";
machine = allLocal;
}
{
unit = "swarm-bao-hive-otel-oidc";
machine = allLocal;
}
{
unit = "swarm-bao-forwarder-oidc";
machine = allLocal;
}
{
unit = "swarm-bao-ci-runner";
machine = withCi;
}
];
remainsAfterExit = u: u.serviceConfig.RemainAfterExit or false;
cases = [
{
# The control is the default on a host that defines the ids below:
# it gets past every arm, so each refusal is the arm's own.
name = "the userns data owner is refused where it collides with a host id or a reserved range";
ok =
let
problems =
owner:
import ../host-modules/lib/userns-data-owner.nix { inherit lib; } {
inherit owner;
uids = [
0
60001
];
gids = [
100
60002
];
};
in
problems 60000 == [ ]
&&
map (o: builtins.length (problems o)) [
1000
29999
61184
65534
60001
60002
] == [
1
1
1
1
1
1
];
}
{
# Read back from the rendered unit and assertion list: a misspelt
# variable or a dropped assertion fails here.
name = "hive-priv carries the userns data owner, no agent list, and the owner assertion passes";
ok =
let
e = allLocal.systemd.services.hive-priv.environment;
in
!(e ? HIVE_PRIV_USERNS_AGENTS)
&& e.HIVE_PRIV_USERNS_DATA_OWNER == "60000"
&& lib.any (
a: lib.hasInfix "hive-controller.userns.dataOwner" a.message && a.assertion
) allLocal.assertions;
}
{
# The domain is the stub's swarm domain, which both fixtures share.
name = "the forge's publicUrl default does not consult the central toggle";
ok =
bare.services.hyperhive.swarm.forge.publicUrl == "https://forge.t.local"
&& centralToggleOff.services.hyperhive.swarm.forge.publicUrl == "https://forge.t.local";
}
{
# The controller's token mint follows where the forge runs
# (./forge-placement.nix), never the central toggle: a forge host with
# the toggle off still mints and publishes it.
name = "the forge's controller-token mint does not consult the central toggle";
ok = forgeHere.containers.hive-forge.config.systemd.services ? forgejo-swarm-controller-account;
}
{
name = "a hive that does not host the swarm's shared services runs none of them";
ok =
let
es = swarmServiceEnables bare;
in
# The hosting switch's host is the control that the lookup finds what
# is there.
lib.length (lib.attrNames es) == 8
&& !lib.any lib.id (lib.attrValues es)
&& lib.all lib.id (lib.attrValues (swarmServiceEnables shared.servicesHere));
}
{
name = "the all-local mode turns the swarm controller on";
ok = allLocal.services.hyperhive.deploy.swarm-controller.enable;
}
{
# `gateway.httpsPort` is a listen setting: the nginx TLS listener and the
# opened firewall port follow it, while a service URL built on the same
# host stays `https://<domain>`. The listener and firewall arms are the
# control that the port took effect on this fixture, so the URL arm
# cannot pass on a fixture where it was never set.
name = "a non-default gateway TLS port moves the listener and firewall, not the service URLs";
ok =
let
otel = controllerAltPort.services.hyperhive.swarm.otel;
listen = controllerAltPort.services.nginx.virtualHosts."_".listen;
tcp = controllerAltPort.networking.firewall.allowedTCPPorts;
url =
controllerAltPort.systemd.services.swarm-controller.environment.OTEL_EXPORTER_OTLP_ENDPOINT or null;
in
lib.any (l: l.port == 8443 && (l.ssl or false)) listen
&& !(lib.any (l: l.port == 443) listen)
&& lib.elem 8443 tcp
&& !(lib.elem 443 tcp)
&& url == "https://${otel.domain}/${otel.producerName}";
}
{
# ../host-modules/glue-controller-env.nix: the controller always exports to
# the swarm's collector. `controllerAlone` runs no collector of its own
# (`allLocal` does, as the presence control); `centralToggleOff` has no
# controller, so no unit to set it on.
name = "the controller always exports to the swarm's collector";
ok =
let
env = m: m.systemd.services.swarm-controller.environment;
otel = allLocal.services.hyperhive.swarm.otel;
clientId = allLocal.services.hyperhive.swarm.controller.queueClientId;
# ../host-modules/glue-controller-oidc-client.nix: the collector's
# `oidc/swarm` reads only a signed access token.
client = lib.findFirst (
c: c.id == clientId
) null allLocal.services.hyperhive.swarm.authelia.oidc.clients;
in
(env allLocal).OTEL_EXPORTER_OTLP_ENDPOINT or null == "https://${otel.domain}/${otel.producerName}"
&& (env allLocal).SWARM_CONTROLLER_OTEL_AUDIENCE or null == clientId
&& client != null
&& lib.elem clientId client.audience
&& client.accessTokenSignedResponseAlg == "RS256"
&& allLocal.services.hyperhive.deploy.swarm-otel.enable
&& !shared.controllerAlone.services.hyperhive.deploy.swarm-otel.enable
&&
(env shared.controllerAlone).OTEL_EXPORTER_OTLP_ENDPOINT or null
== "https://${otel.domain}/${otel.producerName}"
&& (env shared.controllerAlone).SWARM_CONTROLLER_OTEL_AUDIENCE or null == clientId
&& !(centralToggleOff.systemd.services ? swarm-controller);
}
{
# Same file: `swarmctl` is told where the configured authelia is only
# where authelia runs here, and where the bao bootstrap token and policy
# go only where the store runs here. `controllerAlone` runs neither, so
# it is the control that an entry is absent when its service is.
name = "swarmctl is given the authelia and bao-bootstrap coordinates only where those run here";
ok =
let
env = m: m.services.hyperhive.deploy.swarm-controller.swarmctlEnvironment;
d = allLocal.services.hyperhive.deploy.authelia;
b = allLocal.services.hyperhive.deploy.bao;
in
env allLocal == {
SWARMCTL_AUTHELIA_BIN = "${d.package}/bin/authelia";
SWARMCTL_AUTHELIA_USERS_FILE = d.hostUsersFile;
SWARMCTL_BAO_BOOTSTRAP_TOKEN_FILE = b.bootstrapTokenFile;
SWARMCTL_BAO_BOOTSTRAP_POLICY = "${allLocal.environment.etc."hyperhive/bao-bootstrap-policy.hcl".source
}";
}
&& env shared.controllerAlone == { };
}
{
# No host process hands nix a forge URL any more, so none carries a git
# credential helper for the forge. The hive-c0re arm is the control: its
# `git` and its forge URL are found by the same lookups, so the absences
# are real.
name = "no host unit carries the forge credential helper, and hive-priv has no forge URL";
ok =
let
pathNames =
unit:
map (p: if builtins.isAttrs p then p.name or "" else "") allLocal.systemd.services.${unit}.path;
env = unit: allLocal.systemd.services.${unit}.environment;
in
lib.any (lib.hasPrefix "git-2") (pathNames "hive-c0re")
&& (env "hive-c0re") ? HIVE_FORGE_URL
&& lib.all (unit: !(builtins.elem "git-credential-hive-forge" (pathNames unit))) [
"hive-c0re"
"hive-priv"
"swarm-controller"
]
&& !((env "hive-priv") ? HIVE_PRIV_FORGE_URL);
}
{
# `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. 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
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.
# The control: the same environment carries the agents' queue address.
name = "hive-c0re forwards no shared agent queue credential";
ok =
let
e = allLocal.systemd.services.hive-c0re.environment;
in
!(e ? HIVE_C0RE_AGENT_QUEUE_CREDENTIAL_DIR)
&& !(e ? HIVE_AGENT_OIDC_TOKEN_ENDPOINT)
&& e ? HIVE_AGENT_NATS_URL;
}
{
# Inside an agent container loopback is the agent itself, so the agents'
# address must never be one. By name it is the same string the hive
# dials, which ./nats-tls.nix pins for every client.
name = "the agents' queue address is the queue's name, never loopback";
ok =
let
e = allLocal.systemd.services.hive-c0re.environment;
in
e.HIVE_AGENT_NATS_URL == "tls://nats.t.local:4222"
&& !(lib.hasInfix "127.0.0.1" e.HIVE_AGENT_NATS_URL)
&& e.HIVE_AGENT_NATS_URL == e.HIVE_C0RE_NATS_URL;
}
{
# Agents are handed no address at all: their module defaults to a
# literal name, the same on every hive and in every swarm, which this
# hive's resolver answers with the bridge IP its collector binds.
# hive-c0re itself keeps the address. `HYPERHIVE_OTEL_ENDPOINT` is gone,
# and the bridge address next to it is the control that the lookup on
# the same attrset finds what is there.
name = "the agents' collector address is a fixed name this hive resolves to its bridge";
ok =
let
e = allLocal.systemd.services.hive-c0re.environment;
bridge = allLocal.services.hyperhive.network.bridgeIp;
in
!(e ? HYPERHIVE_OTEL_ENDPOINT)
&& e.OTEL_EXPORTER_OTLP_ENDPOINT == "http://${bridge}:4318"
&& allLocal.services.hyperhive.otel.collector.host == "hive-collector.hive"
&& lib.elem "/hive-collector.hive/${bridge}" allLocal.services.dnsmasq.settings.address;
}
{
# An `address=` rule alone sends the other record types upstream
# (dnsmasq 2.86), so each of its domains is also `local=`, and no other
# domain is: `local=` on a domain with public records would hide them.
name = "the resolver keeps queries for every domain it answers itself, and only those, off the upstream";
ok =
let
s = allLocal.services.dnsmasq.settings;
domain = rule: builtins.elemAt (lib.splitString "/" rule) 1;
answered = map domain s.address;
in
lib.elem "hive-collector.hive" answered
&& map domain (s.local or [ ]) == answered
&& lib.all (rule: lib.hasSuffix "/" rule) (s.local or [ ]);
}
{
# The absence arm, and what makes the agents' queue address cases above able
# to fail.
name = "a hive with no agent queue address forwards none";
ok = !(noAgentQueue.systemd.services.hive-c0re.environment ? HIVE_AGENT_NATS_URL);
}
{
# hive-priv refuses a value outside the facts' character set at container
# start, so the option refuses an IPv6 literal or a list at evaluation.
# Only the option's own value is forced, not the rest of either host. The
# default (`allLocal`) is the control that the lookup accepts an address.
name = "the agents' queue address option takes one scheme://host:port and nothing else";
ok =
let
accepts =
host: (builtins.tryEval host.services.hyperhive.deploy.hive-controller.queue.agentNatsUrl).success;
in
accepts allLocal
&& !(accepts (hiveWithController {
deploy.hive-controller.queue.agentNatsUrl = "tls://[fd00::1]:4222";
}))
&& !(accepts (hiveWithController {
deploy.hive-controller.queue.agentNatsUrl = "tls://a.t.local:4222,tls://b.t.local:4222";
}));
}
{
# Control: `bare` sets none of them, and its serve.json carries the
# dashboard port, so the key lookup reads a real document.
name = "a definition of a removed host option evaluates and warns, and the c0re ones reach no serve.json";
ok =
let
warned =
m: option:
lib.any (w: lib.hasInfix "services.hyperhive.${option} is removed and ignored" w) m.warnings;
options = [
"c0re.operatorPronouns"
"c0re.contextWindowTokens"
"otel.metricIntervalMs"
"otel.debug"
];
hasKey = m: key: lib.hasInfix "\"${key}\":" m.environment.etc."hyperhive/serve.json".text;
noRemovedKeys =
m:
hasKey m "dashboard_port" && !(hasKey m "operator_pronouns") && !(hasKey m "context_window_tokens");
in
lib.all (warned noAgentQueue) options
&& !(lib.any (warned bare) options)
&& noRemovedKeys noAgentQueue
&& noRemovedKeys bare;
}
{
# Where the store is and where the agent is told it is, one agreement
# spanning two modules. Asserted against the hive's own `BAO_ADDR`
# rather than a literal, because an agent pointed at a different
# spelling of the same store presents a certificate to a listener whose
# name it cannot verify.
name = "an agent is told the same store address its hive uses";
ok =
let
e = allLocal.systemd.services.hive-c0re.environment;
in
e.HIVE_AGENT_BAO_ADDR == e.BAO_ADDR;
}
{
name = "an agent is told the mount its swarm root is read from, only where it is told the store";
ok =
allLocal.systemd.services.hive-c0re.environment.HIVE_AGENT_BAO_SERVICES_PKI_MOUNT == "pki"
&& !(bare.systemd.services.hive-c0re.environment ? HIVE_AGENT_BAO_SERVICES_PKI_MOUNT);
}
{
# A hive with no certificate of its own can collect no agent's identity,
# so forwarding an address would name a store nothing in the container
# can reach. The same gate the `BAO_*` pair beside it sits behind.
name = "a hive with no store identity forwards no store address to its agents";
ok = !(bare.systemd.services.hive-c0re.environment ? HIVE_AGENT_BAO_ADDR);
}
{
# The gateway's per-name issuer choice. If this ever collapses to a
# constant, every swarm-service vhost serves a certificate its CA
# is name-constrained out of — which evaluates cleanly and fails in
# a browser.
name = "a swarm service name gets the swarm-services leaf and the default server does not";
ok =
let
l = allLocal.services.hyperhive.gateway.lib;
in
(l.tlsFor "t.local").sslCertificate != (l.tlsFor "_").sslCertificate;
}
{
# A leaf is a key that can SERVE every name in it, so the names it
# asks for are that key's blast radius. The forge's host fronts forge
# and nothing else; the last conjunct is the control, since
# `auth.t.local` is in the swarm's set and an unnarrowed request would
# carry it here too.
name = "a gateway's services leaf asks for only the swarm names that host fronts";
ok =
let
s = forgeHere.services.hyperhive.swarm.serviceDomains;
u = forgeHere.systemd.services.swarm-services-cert.script;
in
lib.hasInfix "alt_names=forge.t.local" u
&& !(lib.hasInfix "auth.t.local" u)
&& lib.elem "auth.t.local" s;
}
{
# The store's issuing role is built from this list on the store's host,
# so it cannot depend on which services the evaluating host runs. The
# last conjunct is the control: grafana runs on neither `bare` nor the
# forge host, and is still listed.
name = "the swarm's service names are the same on hosts running different services";
ok =
let
s = h: h.services.hyperhive.swarm.serviceDomains;
in
s forgeHere == s bare && s allLocal == s bare && lib.elem "grafana.t.local" (s bare);
}
{
# The narrowing's floor: no names left means no request, rather than a
# request for an empty SAN set that the store would refuse. Its own
# vhosts are the hive leaf's, which this host still signs.
name = "a host fronting none of the swarm's service names requests no services leaf";
ok =
bare.services.hyperhive.deploy.hive-controller.tls.localServiceDomains == [ ]
&& bare.services.hyperhive.swarm.serviceDomains != [ ]
&& lib.hasInfix "want_svc=0" bare.systemd.services.swarm-services-cert.script;
}
{
# 🩸 A deploy left browsers on an old entry document that named
# `/static/main-<oldhash>.js`; with no cache rule the browser kept that
# document for a heuristic stretch of days, and the missing script was
# answered with the entry document (200, text/html), so the page was blank
# with only a MIME-type error to say why. The hashed bundle is immutable
# and a missing file is a 404; every fixed name (the entry document, the
# worker and the manifest, the theme's `colors.css`) is revalidated. The
# cache rule has no `always`, so a 404 is never marked cacheable.
name = "the swarm UI's static locations cache by name, 404 a missing file and carry the policy";
ok =
let
l = allLocal.services.nginx.virtualHosts."t.local".locations;
has = key: needle: lib.hasInfix needle l.${key}.extraConfig;
gated = key: has key "auth_request /__hive_authelia;";
armed =
key:
has key "add_header Content-Security-Policy \"default-src 'self'"
&& has key "add_header X-Frame-Options";
rootFiles = "~ ^/[^/]+\\.[A-Za-z0-9]+$";
in
has "^~ /static/" "add_header Cache-Control \"public, max-age=31536000, immutable\";"
&& has "^~ /static/" "try_files $uri =404;"
&& has "/" "add_header Cache-Control \"no-cache\";"
&& has "/" "try_files $uri /index.html;"
&& has "= /static/colors.css" "add_header Cache-Control \"no-cache\";"
&& !(has "= /static/colors.css" "immutable")
&& has rootFiles "add_header Cache-Control \"no-cache\";"
&& has rootFiles "try_files $uri =404;"
&& lib.all (k: gated k && armed k) [
"/"
"^~ /static/"
"= /static/colors.css"
rootFiles
]
&& !(has "^~ /static/" "Cache-Control \"public, max-age=31536000, immutable\" always");
}
{
# nixos asserts when a vhost declares both, so this is also a
# statement that the `removeAttrs` upstream of it still happens.
name = "the swarm UI vhost forces TLS instead of merely adding it";
ok =
let
v = allLocal.services.nginx.virtualHosts."t.local";
in
v.forceSSL && !(v.addSSL or false);
}
{
# `swarmctl --swarm` reads a 401 as "log in again"; a 302 would hand it
# the login page instead. The swarm UI's own `fetch` and `EventSource`
# need the same 401, because a redirect to another origin makes both fail
# with an error that says nothing about the session; only a request that
# asks for `text/html` gets the login. The `/` clause is the control: the
# page route still redirects, so a pass means the two routes differ.
# Each answer carries the gateway's security headers, which a location
# (or `if`) with an `add_header` of its own would otherwise drop.
name = "the controller API answers a refused bearer caller or fetch 401 and a page request the login";
ok =
let
l = allLocal.services.nginx.virtualHosts."t.local".locations;
api = l."/api/".extraConfig;
refused = l."@swarm_api_unauthorized".extraConfig;
sec = allLocal.services.hyperhive.gateway.lib.securityHeaders;
in
lib.hasInfix "error_page 401 = @swarm_api_unauthorized;" api
&& !(lib.hasInfix "=302" api)
&& lib.hasInfix ''proxy_set_header Authorization "";'' api
&& lib.hasInfix "if ($http_authorization) {\n ${sec}\n add_header WWW-Authenticate 'Bearer realm=\"swarm\"' always;\n return 401;\n}" refused
&& lib.hasInfix "if ($http_accept ~* \"text/html\") {\n return 302 https://auth.t.local/?rd=$target_url;\n}" refused
&& lib.hasPrefix sec refused
&& lib.hasInfix "}\nadd_header WWW-Authenticate 'Bearer realm=\"swarm\"' always;\nreturn 401;" refused
&& lib.hasInfix "error_page 401 =302 https://auth.t.local/" l."/".extraConfig;
}
{
# authelia 4.39.20 refuses a bearer-scoped public client missing any of
# these at startup, after a green deploy; and a public client must not
# be handed a secret.
name = "the swarmctl login client is public, bearer-scoped to the controller API, and gets no secret";
ok =
let
s = allLocal.containers.swarm-authelia.config.systemd.services.authelia-swarm-secrets.script;
entry = lib.last (lib.splitString "client_id: %s\\n' swarmctl" s);
client = lib.head (lib.splitString "client_id: %s" entry);
in
lib.all (line: lib.hasInfix line client) [
"public: true"
"token_endpoint_auth_method: none"
"require_pkce: true"
"pkce_challenge_method: S256"
"require_pushed_authorization_requests: true"
"consent_mode: explicit"
"response_modes: [form_post]"
"grant_types: [authorization_code, refresh_token]"
"scopes: [offline_access, authelia.bearer.authz]"
"audience: [%s]\\n' https://t.local/api/"
"' - %s\\n' http://127.0.0.1/callback"
]
&& !(lib.hasInfix "client_secret" client)
&& !(lib.hasInfix "mint swarmctl" s)
&& !(lib.hasInfix "mint_client_secret swarmctl" s)
&& (
lib.hasInfix "mint swarm-authelia-bridge" s
|| lib.hasInfix "mint_client_secret swarm-authelia-bridge" s
);
}
{
# swarm-controller checks no caller itself: this location's authelia
# subrequest is the operator gate for every route under `/api/`. The
# webhook location is the control, the one path to the same socket that
# skips the gate, so the first clause reads a string that can lack it.
name = "the swarm UI's API location asks authelia before reaching the controller";
ok =
let
l = allLocal.services.nginx.virtualHosts."t.local".locations;
socket = allLocal.services.hyperhive.deploy.swarm-controller.socketPath;
in
lib.hasInfix "auth_request /__hive_authelia;" l."/api/".extraConfig
&& lib.hasInfix socket l."/api/".proxyPass
&& lib.hasInfix socket l."/webhook/forge/".proxyPass
&& !(lib.hasInfix "auth_request" l."/webhook/forge/".extraConfig);
}
{
# Only nginx (through the clients group) and root reach the controller
# socket: the runtime dir is 0750 with that group set. The last two
# clauses are the presence controls — the group exists and is not the
# daemon's own, so a misspelled name cannot satisfy the rest.
name = "the controller socket's directory is 0750 and group-owned by the group nginx joins";
ok =
let
sc = allLocal.systemd.services.swarm-controller.serviceConfig;
group = "swarm-controller-clients";
in
sc.RuntimeDirectoryMode == "0750"
&& lib.hasPrefix "+" sc.ExecStartPost
&& lib.hasInfix "chgrp ${group} /run/swarm-controller" sc.ExecStartPost
&& lib.elem group allLocal.systemd.services.nginx.serviceConfig.SupplementaryGroups
&& allLocal.users.groups ? ${group}
&& sc.Group != group;
}
{
# nginx serves a path from its longest matching prefix location, so that
# one must be the gated `/api/`, and no exact location may take a route
# out from under it.
name = "the claude login routes reach the controller only through a location that asks authelia";
ok =
let
l = allLocal.services.nginx.virtualHosts."t.local".locations;
route = "/api/agents/a/claude-login/code";
covering = lib.filter (
name: !(lib.hasPrefix "~" name) && !(lib.hasPrefix "= " name) && lib.hasPrefix (prefix name) route
) (lib.attrNames l);
prefix = lib.removePrefix "^~ ";
longest = lib.foldl' (
a: b: if lib.stringLength (prefix b) > lib.stringLength (prefix a) then b else a
) "" covering;
in
longest == "/api/"
&& lib.hasInfix "auth_request /__hive_authelia;" l.${longest}.extraConfig
&& !(lib.any (name: lib.hasPrefix "= /api/agents" name) (lib.attrNames l));
}
{
# `auth_request` does not inherit across sibling locations, so each
# gated one is read on its own. `/health/` is the control: the one
# c0re route that must stay open, so a pass means the block is
# per-location rather than everywhere.
name = "the dashboard answers on its own name behind authelia, and the hive domain redirects there";
ok =
let
vh = allLocal.services.nginx.virtualHosts;
l = vh."h1.hives.t.local".locations;
gated = loc: lib.hasInfix "auth_request /__hive_authelia;" loc.extraConfig;
in
allLocal.services.hyperhive.deploy.hive-controller.dashboardDomain == "h1.hives.t.local"
&& vh."h1.hives.t.local".forceSSL
&& lib.all (p: gated l.${p}) [
"/"
"/api/"
"/api/docs/"
]
&& lib.hasInfix "error_page 401 =302 https://auth.t.local/?rd=$target_url;" l."/".extraConfig
&& !(lib.hasInfix "auth_request" (l."/health/".extraConfig or ""))
&& l."= /__hive_authelia".proxyPass == "https://auth.t.local/api/authz/auth-request"
&& lib.hasInfix "agents.conf" vh."h1.hives.t.local".extraConfig
&&
lib.hasInfix "return 301 https://h1.hives.t.local$request_uri;"
vh."h1.t.local".locations."/".extraConfig
&& !(vh."h1.t.local".locations ? "/api/")
&& lib.elem "h1.hives.t.local" allLocal.services.hyperhive.gateway.localNames
&& allLocal.systemd.services.hive-c0re.environment.HYPERHIVE_DASHBOARD_DOMAIN == "h1.hives.t.local";
}
{
# The gateway's catch-all and the hive's two vhosts (../host-modules/
# hive-c0re/vhosts.nix) are declared under `gateway.vhosts` like a
# service's, so the gateway's listen set and issuer choice reach them
# by the same path. The catch-all keeps its bare `return 444;` (no
# headers: it sends no response) and the dashboard keeps its exact
# location set.
name = "the catch-all and the hive's vhosts render with the gateway's listen set, issuer choice, headers and locations";
ok =
let
gw = allLocal.services.hyperhive.gateway;
vh = allLocal.services.nginx.virtualHosts;
listenOf = map (l: {
inherit (l) addr port;
ssl = l.ssl or false;
});
sameListen = v: listenOf v.listen == listenOf gw.lib.listen;
sameCert = name: v: v.sslCertificate == (gw.lib.tlsFor name).sslCertificate;
catchAll = vh."_";
dash = vh."h1.hives.t.local";
hive = vh."h1.t.local";
in
sameListen catchAll
&& sameCert "_" catchAll
&& catchAll.addSSL
&& catchAll.default
&& catchAll.extraConfig == "return 444;\n"
&& sameListen dash
&& sameCert "h1.hives.t.local" dash
&& dash.forceSSL
&& !(dash.addSSL or false)
&& dash.extraConfig == gw.lib.securityHeaders + "include /var/lib/hive-gateway/conf/agents.conf;\n"
&&
lib.attrNames dash.locations == [
"/"
"/agent/"
"/api/"
"/api/docs/"
"/health/"
"= /__hive_agent_not_found"
"= /__hive_agent_unreachable"
"= /__hive_authelia"
"= /api/docs"
"= /api/whoami"
]
# The profile menu's name comes from authelia's user info, on this
# origin so the session cookie reaches it.
&& lib.hasSuffix "/api/user/info" dash.locations."= /api/whoami".proxyPass
&& sameListen hive
&& sameCert "h1.t.local" hive
&& hive.addSSL
&& hive.extraConfig == gw.lib.securityHeaders
&& hive.locations ? "/";
}
{
# hive-priv creates the agents root as root on a host with no agents yet,
# and hive-c0re (hive-core) then cannot make `agents/<name>`. The rule
# names no mode, so a host that has the dir keeps its mode. The second
# arm needs the first: a host with the controller off has no such rule.
name = "the agents root is created hive-core-owned with the mode left to the default";
ok =
lib.elem "d /var/lib/hyperhive/agents - hive-core hive-core -" allLocal.systemd.tmpfiles.rules
&& !(lib.any (lib.hasInfix "/var/lib/hyperhive/agents") centralToggleOff.systemd.tmpfiles.rules);
}
{
# The file held bcrypt hashes at 0644; a host carrying one from before
# gets it deleted, and nothing names it for nginx.
name = "the gateway reads no htpasswd, and tmpfiles removes a leftover one";
ok =
lib.elem "r /var/lib/hive-gateway/conf/gateway.htpasswd" allLocal.systemd.tmpfiles.rules
&& !(lib.any (
v:
lib.any (t: lib.hasInfix "auth_basic" t) (
[ (v.extraConfig or "") ] ++ map (loc: loc.extraConfig or "") (lib.attrValues (v.locations or { }))
)
) (lib.attrValues allLocal.services.nginx.virtualHosts));
}
{
name = "the swarm UI's hive roster carries each hive's dashboard name";
ok =
builtins.fromJSON allLocal.systemd.services.swarm-controller.environment.SWARM_CONTROLLER_HIVES == [
{
name = "h1";
domain = "h1.t.local";
dashboard = "h1.hives.t.local";
}
];
}
{
# The hive domain answers discovery only for a server name pinned to it,
# which `bare` does not do. Its vhost exists (the redirect is there), so
# the absence arms cannot pass on a misspelled name.
name = "an unpinned hive serves no matrix discovery on its domain";
ok =
let
l = bare.services.nginx.virtualHosts."h1.t.local".locations;
in
l ? "/" && !(l ? "= /.well-known/matrix/client") && !(l ? "= /.well-known/matrix/server");
}
{
# main got eval-borked twice by this exact class of bug (once on the
# unit's `Restart` key, once on `RestartSec`) — a nixpkgs bump to
# `gitea-actions-runner.nix` adds a plain `serviceConfig.*`
# definition that collides with one of ours, and nix refuses to
# merge two plain definitions at *host* eval. No other check
# instantiates a host with `containers.swarm-ci-runner` actually enabled, so
# the collision only surfaces on operator deploy, not in CI.
name = "the CI container's unit definitions merge without a priority collision";
ok = forceServiceConfigs withCi.containers.swarm-ci-runner.config;
}
{
# It spends money on an operator's key, so no host runs it by default.
name = "the review classifier's container exists only where its host enables it";
ok =
!(bare.containers ? swarm-review-classifier)
&& !(centralToggleOff.containers ? swarm-review-classifier)
&& withClassifier.containers ? swarm-review-classifier;
}
{
# The daemon holds its store leaf, handed in by nspawn without a copy on
# disk, and nothing else from the host; a key is read per run and
# reaches only the model child's environment. Reading the claude path
# also evaluates the container's unfree allowance.
name = "the review classifier loads only its store leaf and carries no API or provider key in its unit";
ok =
let
svc =
withClassifier.containers.swarm-review-classifier.config.systemd.services.swarm-review-classifier;
env = svc.environment;
in
lib.elem "review-classifier-bao-key" svc.serviceConfig.LoadCredential
&&
env.BAO_CLIENT_KEY == "/run/credentials/swarm-review-classifier.service/review-classifier-bao-key"
&& env.SWARM_REVIEW_CLASSIFIER_OIDC_CLIENT_ID == "swarm-review-classifier"
&& lib.hasSuffix "/bin/claude" env.SWARM_REVIEW_CLASSIFIER_CLAUDE
&& lib.hasSuffix "/bin/opencode" env.SWARM_REVIEW_CLASSIFIER_OPENCODE
&& !(env ? ANTHROPIC_API_KEY)
&& !(env ? ACP_PROVIDER_API_KEY)
&& lib.elem "--load-credential=review-classifier-bao-key:/var/lib/swarm-bao-pki/review-classifier-key.pem" (
withClassifier.containers.swarm-review-classifier.extraFlags
)
# No host unit copies the leaf into the container's root.
&& !(lib.any (u: lib.hasInfix "nixos-containers/swarm-review-classifier" (u.script or "")) (
lib.attrValues withClassifier.systemd.services
));
}
{
# The collector the classifier's metrics reach is the container's own
# forwarder, which holds the service's client, so the path is the same
# with or without a hive on the host. The logs pipeline is the control
# that the lookup finds what is there.
name = "the review classifier pushes its metrics to the swarm collector through its own forwarder, with or without a hive";
ok =
let
check =
h:
let
c = h.containers.swarm-review-classifier.config;
s = c.services.opentelemetry-collector.settings;
otel = h.services.hyperhive.swarm.otel;
in
c.systemd.services.swarm-review-classifier.environment.OTEL_EXPORTER_OTLP_ENDPOINT
== "http://127.0.0.1:4318"
&& s.receivers.otlp.protocols.http.endpoint == "127.0.0.1:4318"
&& s.service.pipelines.metrics.receivers == [ "otlp" ]
&& s.service.pipelines.metrics.exporters == [ "otlphttp" ]
&& s.exporters.otlphttp.endpoint == "https://${otel.domain}/swarm-review-classifier"
&& s.exporters.otlphttp.auth.authenticator == "oauth2client/swarm"
&& s.service.pipelines.logs.receivers == [ "journald" ];
in
check withClassifier && check withClassifierOnHive;
}
{
# The runner serves its Prometheus endpoint on loopback in its own
# netns, and the container's forwarder scrapes exactly that address into
# a pipeline that leaves through the swarm exporter. The classifier's
# forwarder, which sets no extra targets, is the control: no extra
# receiver, no extra pipeline.
name = "the CI runner's built-in metrics are scraped on loopback by its forwarder";
ok =
let
cc = withCi.containers.swarm-ci-runner.config;
s = cc.services.opentelemetry-collector.settings;
c =
withClassifier.containers.swarm-review-classifier.config.services.opentelemetry-collector.settings;
runner = cc.services.gitea-actions-runner.instances.swarm.settings;
in
runner.metrics.enabled == true
&& runner.metrics.addr == "127.0.0.1:9101"
&&
s.receivers."prometheus/extra".config.scrape_configs == [
{
job_name = "gitea-runner";
static_configs = [ { targets = [ "127.0.0.1:9101" ]; } ];
}
]
&& s.service.pipelines."metrics/extra".receivers == [ "prometheus/extra" ]
&& s.service.pipelines."metrics/extra".exporters == [ "otlphttp" ]
&& s.exporters ? otlphttp
&& !(c.receivers ? "prometheus/extra")
&& !(c.service.pipelines ? "metrics/extra");
}
{
# Only the classifier asks the forwarder for a metrics receiver; the
# others carry a journal. The classifier is the control.
name = "a service forwarder without an OTLP port has no metrics pipeline";
ok =
let
s = withCi.containers.swarm-ci-runner.config.services.opentelemetry-collector.settings;
c =
withClassifier.containers.swarm-review-classifier.config.services.opentelemetry-collector.settings;
in
c.service.pipelines ? metrics && !(s.service.pipelines ? metrics) && !(s.receivers ? otlp);
}
{
# The hive tier is the labelling point for a hive's telemetry, so it runs
# where a hive does and nowhere else. A services host with no hive keeps
# its swarm collector and loses only the host-level one.
name = "the hive collector runs on a hive host and not on a swarm-services host without one";
ok =
let
bridge = bare.services.hyperhive.network.bridgeIp;
in
bare.services.hyperhive.otel.collector.enable
&& bare.services.opentelemetry-collector.enable
&&
bare.services.opentelemetry-collector.settings.receivers.otlp.protocols.http.endpoint
== "${bridge}:4318"
&& shared.swarmServicesWithController.services.opentelemetry-collector.enable
&& !shared.servicesHere.services.hyperhive.otel.collector.enable
&& !shared.servicesHere.services.opentelemetry-collector.enable
&& lib.elem "hive-collector.hive" bare.services.hyperhive.gateway.localNames
&& !(lib.elem "hive-collector.hive" shared.servicesHere.services.hyperhive.gateway.localNames);
}
{
# See the same case in ./builder.nix.
name = "the review classifier's container runs the leaf-minting unit before every start";
ok =
let
c = withClassifier.systemd.services."container@swarm-review-classifier";
in
lib.elem "swarm-bao-pki.service" c.wants && lib.elem "swarm-bao-pki.service" c.after;
}
{
# The collector reaches these routes through the gateway now, so each
# store needs an ingest location of its own. Without one the write rides
# the `/` catch-all: unauthenticated on the metrics store, and into a
# browser redirect on the log store.
name = "each store's vhost has an authenticated ingest location";
ok =
let
v = allLocal.services.nginx.virtualHosts;
m = v."metrics.t.local".locations."= /opentelemetry/api/v1/push" or null;
l = v."logs.t.local".locations."= /insert/opentelemetry/v1/logs" or null;
in
m != null
&& l != null
&& lib.hasInfix "auth_request" m.extraConfig
&& lib.hasInfix "auth_request" l.extraConfig;
}
{
# The metrics vhost is default-deny; the collector's push only lands
# because its `metrics_endpoint` path is the vhost's one gated exact
# location. The two literals live in different modules, so renaming
# either alone breaks ingest while everything still evaluates.
name = "the collector's metrics push path is a gated location on the metrics vhost";
ok =
let
endpoint = (otelSettings allLocal).exporters."otlphttp/victoriametrics".metrics_endpoint;
# "https://host/path" -> "/path"
path = "/" + lib.concatStringsSep "/" (lib.drop 3 (lib.splitString "/" endpoint));
loc = allLocal.services.nginx.virtualHosts."metrics.t.local".locations."= ${path}" or null;
in
loc != null && lib.hasInfix "auth_request" loc.extraConfig;
}
{
# The arm that actually protects something. A pusher handed
# `error_page 401 =302` FOLLOWS it and POSTs its batch at a login page,
# which answers 200 — ingest reporting healthy while storing nothing.
# The third clause is the positive control: the log store's browser
# location really does redirect, so this says the machine routes differ
# rather than that the string is absent from the whole file.
name = "the ingest locations answer 401 instead of redirecting a pusher";
ok =
let
v = allLocal.services.nginx.virtualHosts;
m = v."metrics.t.local".locations."= /opentelemetry/api/v1/push".extraConfig;
l = v."logs.t.local".locations."= /insert/opentelemetry/v1/logs".extraConfig;
browser = v."logs.t.local".locations."/".extraConfig;
in
!(lib.hasInfix "error_page" m)
&& !(lib.hasInfix "error_page" l)
&& lib.hasInfix "error_page" browser;
}
{
# The store authenticates nothing itself, so a location that forwards to
# it without `auth_request` is open to anything that can reach the
# domain. The query location is the control: it shows the filter finds
# store-forwarding locations at all.
name = "no location on the metrics vhost forwards to the store without auth_request";
ok =
let
locs = allLocal.services.nginx.virtualHosts."metrics.t.local".locations;
toStore = lib.filterAttrs (
_: l: l.proxyPass != null && lib.hasPrefix "http://unix:/run/swarm-victoriametrics/" l.proxyPass
) locs;
open = lib.filterAttrs (_: l: !(lib.hasInfix "auth_request " l.extraConfig)) toStore;
in
toStore ? "= /api/v1/query"
&& open == { }
&& locs."/".proxyPass == null
&& locs."/".return == "403";
}
{
# The read API's check, link by link: every read location names the
# switch with no login redirect (a machine reader would get login HTML
# as a 200), the switch rewrites to the map's pick, an agent bearer
# reaches the agents' responder (listed before the catch-all bearer arm,
# since a map takes the first matching regex), any other bearer reaches
# Grafana's, each socket bound by its unit, and anything else goes to
# authelia. vmui is a browser's, so it redirects to the login.
name = "the metrics read API sends an agent bearer to swarm-metrics-agent-auth, another bearer to swarm-metrics-auth and a session to authelia";
ok =
let
l = allLocal.services.nginx.virtualHosts."metrics.t.local".locations;
http = allLocal.services.nginx.appendHttpConfig;
unit = allLocal.systemd.services.swarm-metrics-auth;
agentUnit = allLocal.systemd.services.swarm-metrics-agent-auth;
agentArm = "\"~^Bearer swarm-agent\\.\" /__metrics_agent_auth;";
bearerArm = "\"~^Bearer \" /__metrics_grafana_auth;";
v = allLocal.services.hyperhive.deploy.victoriametrics;
logs = allLocal.services.hyperhive.deploy.victorialogs;
reads = lib.filterAttrs (n: _: lib.hasPrefix "= /api/v1/" n || lib.hasPrefix "^~ /api/v1/" n) l;
in
reads ? "= /api/v1/query_range"
&& lib.all (
r:
lib.hasInfix "auth_request /__metrics_read_auth;" r.extraConfig
&& !(lib.hasInfix "error_page" r.extraConfig)
) (lib.attrValues reads)
&& lib.hasInfix "map \$http_authorization \$swarm_metrics_read_auth_target" http
&& lib.hasInfix agentArm http
&& lib.hasInfix bearerArm http
&& !(lib.hasInfix bearerArm (lib.head (lib.splitString agentArm http)))
&& lib.hasInfix "default /__metrics_authelia;" http
&&
lib.hasInfix "rewrite ^ \$swarm_metrics_read_auth_target last;"
l."= /__metrics_read_auth".extraConfig
&& lib.hasInfix "internal;" l."= /__metrics_grafana_auth".extraConfig
&& l."= /__metrics_grafana_auth".proxyPass == "http://unix:/run/swarm-metrics-auth/auth.sock:/"
&& lib.hasInfix "--socket /run/swarm-metrics-auth/auth.sock" unit.serviceConfig.ExecStart
&& lib.hasInfix "internal;" l."= /__metrics_agent_auth".extraConfig
&& l."= /__metrics_agent_auth".proxyPass == "http://unix:/run/swarm-metrics-agent-auth/auth.sock:/"
&& lib.hasInfix "--socket /run/swarm-metrics-agent-auth/auth.sock" agentUnit.serviceConfig.ExecStart
&& hasStorePath "${v.agentAuthPackage}/bin/swarm-logs-auth " agentUnit.serviceConfig.ExecStart
&& !(hasStorePath "${logs.authPackage}" agentUnit.serviceConfig.ExecStart)
&& hasStorePath "${v.authPackage}/bin/swarm-metrics-auth " unit.serviceConfig.ExecStart
&& lib.hasInfix "--store-cert-role swarm-metrics-auth" agentUnit.serviceConfig.ExecStart
&& lib.hasInfix "--surface metrics" agentUnit.serviceConfig.ExecStart
&& lib.hasInfix "--verdict-ttl ${toString v.agentAuthVerdictTtl}" agentUnit.serviceConfig.ExecStart
&& agentUnit.serviceConfig.LoadCredential == unit.serviceConfig.LoadCredential
&& lib.hasInfix "limit_req_zone $binary_remote_addr zone=swarm_metrics_agent_auth:1m rate=${v.agentAuthRate};" http
&&
lib.hasInfix "limit_req zone=swarm_metrics_agent_auth burst=${toString v.agentAuthBurst} nodelay;"
l."= /__metrics_agent_auth".extraConfig
&& !(lib.hasInfix "limit_req" l."= /__metrics_grafana_auth".extraConfig)
&& lib.hasInfix "auth_request /__metrics_authelia;" l."^~ /vmui/".extraConfig
&& lib.hasInfix "error_page 401 =302" l."^~ /vmui/".extraConfig
&& l."= /__metrics_authelia".proxyPass == "https://auth.t.local/api/authz/auth-request";
}
{
# Grafana's host creates the bearer and its datasource sends it; the
# store's responder reads it. Three modules spell the path, and the
# store's grants name it twice: if any one drifts, Grafana sends a value
# the responder never reads, or one of them is refused by the store.
name = "Grafana's datasource sends the bearer swarm-metrics-auth checks, at one store path";
ok =
let
leaf = "swarm/services/${allLocal.services.hyperhive.swarm.grafana.oidc.clientId}/metrics/bearer";
ds =
lib.findFirst (d: d.uid == "swarm-victoriametrics") null
allLocal.containers.swarm-grafana.config.services.grafana.provision.datasources.settings.datasources;
bearerUnit = allLocal.systemd.services.swarm-bao-grafana-metrics-bearer;
s = allLocal.systemd.services;
in
ds.jsonData.httpHeaderName1 == "Authorization"
&& ds.secureJsonData.httpHeaderValue1 == "$__file{/run/swarm-grafana-secrets/metrics-bearer}"
&& lib.hasInfix "bao kv put -cas=0 secret/${leaf} value=-" bearerUnit.script
&& lib.hasInfix "/run/swarm-grafana-secrets/metrics-bearer \"Bearer $bearer\"" bearerUnit.script
&& lib.hasInfix "--store-path ${leaf}" s.swarm-metrics-auth.serviceConfig.ExecStart
&& lib.hasInfix "path \"secret/data/${leaf}\" {\n capabilities = [\"read\"]\n}" s.swarm-bao-metrics-auth-policy.script
&& lib.hasInfix "path \"secret/data/${leaf}\" {\n capabilities = [\"create\", \"read\"]\n}" s.swarm-bao-grafana-oidc-policy.script;
}
{
# The log store's second responder checks the very bearer the metrics
# datasource sends, at the same store path: a different file would be a
# bearer the responder never reads.
name = "Grafana's logs datasource sends the bearer the log store's responder checks, at one store path";
ok =
let
datasources =
allLocal.containers.swarm-grafana.config.services.grafana.provision.datasources.settings.datasources;
metrics = lib.findFirst (d: d.uid == "swarm-victoriametrics") null datasources;
logs = lib.findFirst (d: d.uid == "swarm-victorialogs") null datasources;
leaf = "swarm/services/${allLocal.services.hyperhive.swarm.grafana.oidc.clientId}/metrics/bearer";
in
logs.secureJsonData.httpHeaderValue1 == metrics.secureJsonData.httpHeaderValue1
&& lib.hasInfix "--store-path ${leaf}" allLocal.systemd.services.swarm-logs-grafana-auth.serviceConfig.ExecStart;
}
{
# The responder reads the store as itself, with the leaf the store's
# host mints for it.
name = "swarm-metrics-auth presents its own store leaf";
ok =
let
v = allLocal.services.hyperhive.deploy.victoriametrics;
lc = allLocal.systemd.services.swarm-metrics-auth.serviceConfig.LoadCredential;
in
lib.hasSuffix "/metrics-auth.pem" v.authBaoClientCertFile
&& lib.hasSuffix "/metrics-auth-key.pem" v.authBaoClientKeyFile
&& builtins.elem "bao-client.pem:${v.authBaoClientCertFile}" lc
&& builtins.elem "bao-client-key.pem:${v.authBaoClientKeyFile}" lc
&& lib.hasInfix "/metrics-auth.pem ]" allLocal.systemd.services.swarm-bao-pki.script;
}
{
# The read counterpart to the ingest location: an agent queries the log
# store with a bearer token, and the `/` catch-all is the browser's
# route. Riding it would mean inheriting the login redirect the next
# case is about, so the route has to exist separately to be gated
# separately.
name = "the log store's vhost has an authenticated machine query location";
ok =
let
q = allLocal.services.nginx.virtualHosts."logs.t.local".locations."^~ /select/logsql/" or null;
in
q != null && lib.hasInfix "auth_request" q.extraConfig;
}
{
# Same trap as the ingest case, on the read side, where it is worse: a
# redirected pusher at least stores nothing visibly, while a redirected
# *reader* is handed a 200 carrying login HTML and records a query that
# succeeded and matched no logs. The browser clause is the positive
# control — that location really does redirect to the login host — so a
# pass means these two routes differ rather than that the strings are
# absent from the whole vhost.
name = "the machine query location answers 401 instead of redirecting to a login page";
ok =
let
v = allLocal.services.nginx.virtualHosts;
q = v."logs.t.local".locations."^~ /select/logsql/".extraConfig;
browser = v."logs.t.local".locations."/".extraConfig;
in
!(lib.hasInfix "error_page" q)
&& !(lib.hasInfix "auth.t.local" q)
&& lib.hasInfix "error_page" browser
&& lib.hasInfix "auth.t.local" browser;
}
{
# Read access is deliberately unscoped: an authenticated caller reads
# the whole swarm's logs until a permission system exists. Pinned so a
# scoping parameter arriving later is a visible diff here rather than a
# quiet change of rule — and pinned on `proxyPass` too, because
# VictoriaLogs takes its filters as request parameters, which ride an
# upstream URI as easily as a directive. The first clause is the
# control: it proves the location resolved and that `hasInfix` finds
# what is genuinely in this string, so the absences below mean absent
# rather than unreadable.
name = "the machine query location forwards the caller's query unmodified";
ok =
let
q = allLocal.services.nginx.virtualHosts."logs.t.local".locations."^~ /select/logsql/";
in
lib.hasInfix "auth_request" q.extraConfig
&& !(lib.hasInfix "extra_filters" q.extraConfig)
&& !(lib.hasInfix "extra_stream_filters" q.extraConfig)
&& !(lib.hasInfix "$args" q.extraConfig)
&& !(lib.hasInfix "?" q.proxyPass);
}
{
# `auth_request` takes a fixed URI, so the split between the agents'
# responder and authelia is a map plus a rewrite in an internal
# location. Every link of that chain is pinned: the map keys on the
# agent-token prefix and falls back to authelia, the query location
# names the switch, the switch rewrites to the map's pick, and the
# agent target reaches the responder's socket, which the unit binds.
name = "the machine query location sends agent bearers to swarm-logs-auth and the rest to authelia";
ok =
let
l = allLocal.services.nginx.virtualHosts."logs.t.local".locations;
http = allLocal.services.nginx.appendHttpConfig;
unit = allLocal.systemd.services.swarm-logs-auth;
grafanaUnit = allLocal.systemd.services.swarm-logs-grafana-auth;
logs = allLocal.services.hyperhive.deploy.victorialogs;
metrics = allLocal.services.hyperhive.deploy.victoriametrics;
in
lib.hasInfix "map \$http_authorization \$swarm_logs_auth_target" http
&& lib.hasInfix "\"~^Bearer swarm-agent\\.\" /__swarm_logs_agent_auth;" http
&& lib.hasInfix "\"~^Bearer [0-9a-f]{64}$\" /__swarm_logs_grafana_auth;" http
&& lib.hasInfix "default /__hive_authelia;" http
&& lib.hasInfix "auth_request /__swarm_logs_auth;" l."^~ /select/logsql/".extraConfig
&& lib.hasInfix "rewrite ^ \$swarm_logs_auth_target last;" l."= /__swarm_logs_auth".extraConfig
&& lib.hasInfix "internal;" l."= /__swarm_logs_agent_auth".extraConfig
&& l."= /__swarm_logs_agent_auth".proxyPass == "http://unix:/run/swarm-logs-auth/auth.sock:/"
&& lib.hasInfix "--socket /run/swarm-logs-auth/auth.sock" unit.serviceConfig.ExecStart
&& lib.hasInfix "--surface logs" unit.serviceConfig.ExecStart
&& lib.hasInfix "internal;" l."= /__swarm_logs_grafana_auth".extraConfig
&&
l."= /__swarm_logs_grafana_auth".proxyPass == "http://unix:/run/swarm-logs-grafana-auth/auth.sock:/"
&& lib.hasInfix "--socket /run/swarm-logs-grafana-auth/auth.sock" grafanaUnit.serviceConfig.ExecStart
&& hasStorePath "${logs.grafanaAuthPackage}/bin/swarm-metrics-auth " grafanaUnit.serviceConfig.ExecStart
&& !(hasStorePath "${metrics.authPackage}" grafanaUnit.serviceConfig.ExecStart)
&& hasStorePath "${logs.authPackage}/bin/swarm-logs-auth " unit.serviceConfig.ExecStart
&& lib.hasInfix "--store-cert-role swarm-logs-auth" grafanaUnit.serviceConfig.ExecStart
&& l ? "= /__hive_authelia";
}
{
# A `DynamicUser` unit's preserved runtime directory lives under
# `/run/private/` (0700 root:root), which nginx cannot traverse, so a
# socket there answers every proxied request with a 502. Each `unix:`
# upstream under `/run/<dir>/` is matched to the units creating `<dir>`.
# The first two clauses are the control: the match finds both responders.
name = "no unix socket nginx proxies to sits in a DynamicUser's preserved runtime directory";
ok =
let
prefix = "http://unix:/run/";
upstreamDirs = lib.unique (
map (p: lib.head (lib.splitString "/" (lib.removePrefix prefix p))) (
lib.filter (p: p != null && lib.hasPrefix prefix p) (
lib.concatMap (vh: lib.mapAttrsToList (_: loc: loc.proxyPass) vh.locations) (
lib.attrValues allLocal.services.nginx.virtualHosts
)
)
)
);
runtimeDirs =
sc:
let
r = sc.RuntimeDirectory or [ ];
in
if lib.isList r then r else lib.splitString " " r;
owners = lib.filterAttrs (
_: u: lib.any (d: lib.elem d upstreamDirs) (runtimeDirs u.serviceConfig)
) allLocal.systemd.services;
hidden =
sc:
lib.elem (sc.DynamicUser or false) [
true
"yes"
]
&& !(lib.elem (sc.RuntimeDirectoryPreserve or "no") [
false
"no"
]);
in
owners ? swarm-logs-auth
&& owners ? swarm-logs-grafana-auth
&& owners ? swarm-metrics-auth
&& owners ? swarm-metrics-agent-auth
&& !(lib.any (u: hidden u.serviceConfig) (lib.attrValues owners));
}
{
# Every agent bearer the responder has not recently granted costs a
# store login and read, so its location carries the per-address limit.
# The responder gets the grant TTL from the option. The query and
# authelia locations carry no limit of their own.
name = "agent bearer checks are rate-limited per address and granted for a TTL";
ok =
let
l = allLocal.services.nginx.virtualHosts."logs.t.local".locations;
http = allLocal.services.nginx.appendHttpConfig;
unit = allLocal.systemd.services.swarm-logs-auth;
v = allLocal.services.hyperhive.deploy.victorialogs;
in
lib.hasInfix "limit_req_zone $binary_remote_addr zone=swarm_logs_agent_auth:1m rate=${v.authRate};" http
&&
lib.hasInfix "limit_req zone=swarm_logs_agent_auth burst=${toString v.authBurst} nodelay;"
l."= /__swarm_logs_agent_auth".extraConfig
&& lib.hasInfix "limit_req_status 403;" l."= /__swarm_logs_agent_auth".extraConfig
&& !(lib.hasInfix "limit_req" l."^~ /select/logsql/".extraConfig)
&& !(lib.hasInfix "limit_req" l."= /__hive_authelia".extraConfig)
&& lib.hasInfix "--verdict-ttl 60" unit.serviceConfig.ExecStart;
}
{
# The responder reads the store as itself, with the leaf the store's
# host mints for it.
name = "swarm-logs-auth presents its own store leaf";
ok =
let
v = allLocal.services.hyperhive.deploy.victorialogs;
lc = allLocal.systemd.services.swarm-logs-auth.serviceConfig.LoadCredential;
in
lib.hasSuffix "/logs-auth.pem" v.authBaoClientCertFile
&& lib.hasSuffix "/logs-auth-key.pem" v.authBaoClientKeyFile
&& builtins.elem "bao-client.pem:${v.authBaoClientCertFile}" lc
&& builtins.elem "bao-client-key.pem:${v.authBaoClientKeyFile}" lc;
}
{
# The absence arm for the case above, and the option's own rule — a
# service declares its entry under its own `enable` — made checkable.
# Without it, moving the assignment outside the collector's `mkIf`
# passes every arm above while handing a collector-less hive a scrape
# target for a port nothing binds.
name = "a hive with no collector declares no self-scrape target";
ok =
centralToggleOff.services.hyperhive.otel.scrapeTargets == { }
&& bare.services.hyperhive.otel.scrapeTargets ? collector;
}
{
# Absence arm, and the one that matters: claiming a name this host does
# not serve points every local reader at the wrong machine.
name = "a hive that does not run the store claims no name for it";
ok = !(builtins.elem "bao.t.local" (baoNames bare));
}
{
# ⚠️ The absence arm that matters. `services.nginx.streamConfig` is a
# host-wide option, so a block rendered outside the store's own `mkIf`
# gives every hive in the swarm a listener — on the port the store
# answers on, in front of no store at all.
name = "a hive that does not run the store renders no stream passthrough";
ok = baoStream bare == "";
}
{
# Absence arm for the case above — a hive with no store has no reason to
# open the store's port, and opening it would point agents at a host that
# answers nothing.
name = "a hive that does not run the store opens no bridge port for it";
ok = !(builtins.elem 8200 (bridgePorts bare));
}
{
# The four blocks below used to be gated on the hive being enabled AND
# their own condition. The second half was always the load-bearing one —
# none of these conditions is derived from the hive toggle — so these
# arms pin what the conjunct was doing: nothing. Each is written against
# a host with the hive OFF as well as one with it on, because the way a
# dropped conjunct fails is by making something unconditional, and that
# shows up as a service appearing where nothing asked for it.
name = "no bridge port is opened for an exposeHostPorts nobody set";
ok =
!(builtins.elem 5432 (bridgePortsOrNone bare))
&& !(builtins.elem 5432 (bridgePortsOrNone centralToggleOff));
}
{
# `deploy.swarm-controller.enable`, which defaults false and is
# deliberately not derived from the hive toggle — a swarm has one
# controller, so the host that runs it says so itself.
#
# Probed by what the daemon needs in order to run, not by
# `? swarm-controller`: ./host-modules/glue-hive-tls-controllers.nix defines an
# environment key on that unit name, which leaves the attr
# present-but-inert (no `ExecStart`, empty `wantedBy`) on every hive
# that has a CA — see the comment there. The daemon's system user has no
# second definer, so its absence is the unambiguous half.
name = "the swarm controller does not run unless this host is told to run it";
ok =
let
inert =
machine:
!(machine.users.users ? swarm-controller)
&& !(
(machine.systemd.services.swarm-controller or { serviceConfig = { }; }).serviceConfig ? ExecStart
);
in
inert bare && inert centralToggleOff;
}
{
# `deploy.swarm-otel.enable`, same shape: the swarm's collector is one
# host's job, and the container is the whole of what it renders.
name = "the swarm collector container is absent unless this host is told to run it";
ok = !(bare.containers ? swarm-otel) && !(centralToggleOff.containers ? swarm-otel);
}
{
# The swarm UI follows `deploy.swarm-controller.enable` — a sibling
# deployment decision, not the hive toggle. `t.local` is the fixtures'
# swarm domain, which is the apex the UI claims; the arm below is what
# proves this vhost renders at all.
name = "the swarm UI vhost is absent unless this host runs the swarm controller";
ok =
!(bare.services.nginx.virtualHosts ? "t.local")
&& !(centralToggleOff.services.nginx.virtualHosts ? "t.local");
}
{
# The three infrastructure toggles are off by default and asserted by
# whoever needs them. With nothing on the host needing them, none of
# the three renders — which is also the control for the arm below.
name = "the gateway, resolver and bridge are absent where nothing on the host needs them";
ok =
!centralToggleOff.services.hyperhive.gateway.enable
&& !centralToggleOff.services.hyperhive.gateway.dns.enable
&& !centralToggleOff.services.hyperhive.network.enable
&& !(centralToggleOff.services.nginx.enable or false)
&& !(centralToggleOff.services.dnsmasq.enable or false)
&& !(centralToggleOff.networking.bridges ? hive-br0);
}
{
# hive-c0re asserts all three, and it follows the central toggle — so
# an ordinary hive keeps getting them with no opt-in, which is what
# this change must not break.
name = "an ordinary hive runs the gateway, resolver and bridge because its coordinator needs them";
ok =
bare.services.hyperhive.gateway.enable
&& bare.services.hyperhive.gateway.dns.enable
&& bare.services.hyperhive.network.enable
&& bare.services.nginx.enable
&& bare.services.dnsmasq.enable
&& bare.networking.bridges ? hive-br0;
}
{
# The bridge→loopback drop is a unit of its own that loads its own nft
# table, so it does not depend on which firewall backend the host runs.
# `bare` is the presence arm; the firewall option the old rule used
# stays empty, so the rule has one home.
name = "the bridge to loopback drop is its own unit where the bridge is up and absent elsewhere";
ok =
let
unit = bare.systemd.services.hive-egress or null;
in
unit != null
&& unit.serviceConfig.Type == "oneshot"
&& lib.hasInfix "hive-egress.nft" unit.serviceConfig.ExecStart
&& lib.hasInfix "delete table inet hive_egress" unit.serviceConfig.ExecStop
&& bare.networking.firewall.extraInputRules == ""
&& !(centralToggleOff.systemd.services ? hive-egress);
}
{
# The forward chain is in the ordinary host's table and logs the bridge's
# forwarded flows without a verdict that blocks. The ruleset is read as
# text; the last rule of the chain is the accept that keeps the log from
# changing any outcome.
name = "the default egress table logs the bridge's forwarded flows and blocks none";
ok =
let
text = bare.services.hyperhive.network.egress.ruleset;
chain = lib.last (lib.splitString "chain forward" text);
rules = lib.filter (l: l != "") (map lib.trim (lib.splitString "\n" chain));
idx = pat: lib.lists.findFirstIndex (lib.hasInfix pat) 1000 rules;
in
lib.hasInfix "chain forward" text
&& lib.hasInfix "type filter hook forward priority filter - 10; policy accept;" chain
&& lib.hasInfix ''iifname "hive-br0" oifname != "hive-br0" ct state new update @log_v4 { ip saddr limit rate'' chain
&& lib.hasInfix ''iifname "hive-br0" oifname != "hive-br0" ct state new update @log_v6 { ip6 saddr limit rate'' chain
&& lib.hasInfix ''log prefix "hive-egress-would-drop "'' chain
&& lib.hasInfix ''iifname "hive-br0" oifname "wg-hive" accept'' chain
# The per-source rules accept after logging, and the unmetered rule
# comes after both: a flow gets one line, and one the per-source rules
# cannot log still gets the unmetered one.
&& lib.all (l: lib.hasSuffix " accept" l) (lib.filter (lib.hasInfix "update @log_") rules)
&& lib.hasInfix ''iifname "hive-br0" oifname != "hive-br0" ct state new limit rate 5/second burst 50 packets log prefix "hive-egress-would-drop-unmetered "'' chain
&& idx "update @log_v4" < idx "update @log_v6"
&& idx "update @log_v6" < idx "would-drop-unmetered"
&& lib.last (lib.filter (l: l != "}") rules) == ''iifname "hive-br0" oifname != "hive-br0" accept''
&& !(lib.any (l: lib.hasSuffix " drop" l || lib.hasSuffix " reject" l) rules)
&& lib.hasInfix "chain input" text;
}
{
# `accept` renders the table without the forward chain and without the
# log prefix; the input chain is the presence arm that the text it
# reads is the table.
name = "egress.forward accept leaves the table without a forward chain";
ok =
let
text = egressForwardAccept.services.hyperhive.network.egress.ruleset;
in
lib.hasInfix "chain input" text
&& !(lib.hasInfix "chain forward" text)
&& !(lib.hasInfix "hive-egress-would-drop" text)
&& !(lib.any (a: !a.assertion) egressForwardAccept.assertions);
}
{
# `drop` is refused by an assertion that names the option and says it is
# not available; `bare` and the `accept` host, which set `log` and
# `accept`, are the controls that the lookup finds nothing for them.
name = "egress.forward drop is refused as not available yet";
ok =
let
refused =
m:
lib.any (
a:
!a.assertion
&& lib.hasInfix "services.hyperhive.network.egress.forward" a.message
&& lib.hasInfix "not available yet" a.message
) m.assertions;
in
refused egressForwardDrop && !(refused bare) && !(refused egressForwardAccept);
}
{
# The host that turns `route_localnet` on is refused by an assertion
# naming it; the ordinary host, which leaves it alone, is not.
name = "route_localnet on the sysctl is refused on a host with the bridge up";
ok =
let
refused = m: lib.any (a: !a.assertion && lib.hasInfix "route_localnet" a.message) m.assertions;
in
refused routeLocalnetOn && !(refused bare);
}
{
# Refused where a hive runs and nowhere else. A name is an identifier
# only for a hive; the hive host is the control that the lookup finds the
# refusal when there is one.
name = "a missing hiveName is refused on a hive and not on a store, homeserver or services host";
ok =
let
refused =
extra:
lib.any (
a:
!a.assertion
&& lib.hasInfix "services.hyperhive.deploy.hive-controller.hiveName" a.message
&& lib.hasInfix "to be set" a.message
) (hive (lib.recursiveUpdate { deploy.hive-controller.hiveName = null; } extra)).assertions;
in
refused { deploy.hive-controller.enable = true; }
&& !(refused { deploy.bao.enable = true; })
&& !(refused { deploy.matrix.enable = true; })
&& !(refused { deploy.allSwarmServices = true; });
}
{
# The directory entry is asserted for a hive and only for a hive. The
# hive host with a name the directory lacks is the control.
name = "a hive whose name has no swarm.hives entry is refused, and a host without a hive is not asked";
ok =
let
noEntry =
extra:
lib.any (a: !a.assertion && lib.hasInfix "swarm.hives has no entry for this hive" a.message)
(hive (lib.recursiveUpdate { deploy.hive-controller.hiveName = "other"; } extra)).assertions;
in
noEntry { deploy.hive-controller.enable = true; }
&& !(noEntry { deploy.bao.enable = true; })
&& !(noEntry { deploy.allSwarmServices = true; });
}
{
# Nothing of the hive's identity is minted for a host that runs no hive:
# no reader leaf named after a hive, and no reader identity at all, so
# the glue that locates the PKI falls back to the store's own directory.
# The hive host with the same store is the control.
name = "a store host without a hive mints no hive reader leaf and still wires the controller and publisher";
ok =
let
storeNoHive = hive {
deploy.hive-controller.hiveName = null;
deploy.bao.enable = true;
deploy.swarm-controller.enable = true;
deploy.swarm-secret-publisher.enable = true;
};
storeWithHive = hiveWithController {
deploy.bao.enable = true;
deploy.swarm-controller.enable = true;
deploy.swarm-secret-publisher.enable = true;
};
pki = m: m.systemd.services.swarm-bao-pki.script;
d = m: m.services.hyperhive.deploy;
in
(d storeNoHive).bao.clientCertFile == null
&& (d storeNoHive).bao.clientKeyFile == null
&& !(lib.hasInfix "/var/lib/swarm-bao-pki/client.pem ]" (pki storeNoHive))
&& (d storeNoHive).swarm-controller.baoClientCertFile == "/var/lib/swarm-bao-pki/controller.pem"
&&
(d storeNoHive).swarm-secret-publisher.baoClientCertFile
== "/var/lib/swarm-bao-pki/secret-publisher.pem"
&& (d storeWithHive).bao.clientCertFile == "/var/lib/swarm-bao-pki/client.pem"
&& lib.hasInfix "/var/lib/swarm-bao-pki/client.pem ]" (pki storeWithHive)
&& (d storeWithHive).swarm-controller.baoClientCertFile == "/var/lib/swarm-bao-pki/controller.pem";
}
{
# An operator's own reader leaf names the PKI directory, exactly as it did
# when the glue always derived it from that path.
name = "a controller and publisher with an operator-placed reader leaf and no store follow its directory";
ok =
let
m = hive {
deploy.swarm-controller.enable = true;
deploy.swarm-secret-publisher.enable = true;
deploy.bao.clientCertFile = "/etc/pki/elsewhere/client.pem";
deploy.bao.clientKeyFile = "/etc/pki/elsewhere/client-key.pem";
};
d = m.services.hyperhive.deploy;
in
!d.bao.enable
&& d.swarm-controller.baoClientCertFile == "/etc/pki/elsewhere/controller.pem"
&& d.swarm-secret-publisher.baoClientCertFile == "/etc/pki/elsewhere/secret-publisher.pem";
}
{
# The old spelling forwards with the module system's one warning and
# produces the same hive. The new-path host is the control that the
# warning is not always present.
name = "services.hyperhive.hiveName still sets the hive's name, with a rename warning";
ok =
let
old = hiveWithController {
hiveName = "legacy";
swarm.hives.legacy = { };
};
warned =
m:
lib.any (
w: lib.hasInfix "services.hyperhive.hiveName" w && lib.hasInfix "has been renamed" w
) m.warnings;
in
old.services.hyperhive.deploy.hive-controller.hiveName == "legacy"
&& old.systemd.services.hive-c0re.environment.HYPERHIVE_HIVE_NAME == "legacy"
&& warned old
&& !(warned bare);
}
{
# Every c0re option that moved under `deploy.hive-controller` still
# takes its old spelling, with one rename warning each, and lands at
# the new path. The packages are distinct stand-ins so a crossed
# forward shows. `bare` is the control that the warning is not always
# present, and the new path is what the rendered values below read.
name = "services.hyperhive.c0re.* options that moved still set deploy.hive-controller.*, with rename warnings";
ok =
let
old = hiveWithController {
c0re.package = pkgs.hello;
c0re.frontend = pkgs.coreutils;
c0re.assets = pkgs.gnused;
c0re.xdgIcons = pkgs.gnugrep;
c0re.hyperhiveRev = "abc123";
c0re.agentCpuQuota = "300%";
c0re.agentMemoryMax = "8G";
c0re.agentCpuWeight = 50;
c0re.agentIoWeight = null;
c0re.buildSlots = 3;
c0re.adminUsers = [ "alice" ];
c0re.hyperhiveFlake = "/nix/store/legacy-flake";
};
c = old.services.hyperhive.deploy.hive-controller;
warned =
m: name:
lib.any (
w: lib.hasInfix "services.hyperhive.c0re.${name}" w && lib.hasInfix "has been renamed" w
) m.warnings;
serve = builtins.fromJSON (
builtins.unsafeDiscardStringContext old.environment.etc."hyperhive/serve.json".text
);
path = p: builtins.unsafeDiscardStringContext "${p}";
in
path c.package == path pkgs.hello
&& path c.frontend == path pkgs.coreutils
&& path c.assets == path pkgs.gnused
&& path c.xdgIcons == path pkgs.gnugrep
&& path c.servedFrontend == path pkgs.coreutils
&& c.hyperhiveRev == "abc123"
&& c.agentCpuQuota == "300%"
&& c.agentMemoryMax == "8G"
&& c.agentCpuWeight == 50
&& c.agentIoWeight == null
&& c.buildSlots == 3
&& c.adminUsers == [ "alice" ]
&& old.services.hyperhive.deploy.hyperhiveFlake == "/nix/store/legacy-flake"
&& serve.hyperhive_flake == "/nix/store/legacy-flake"
&& lib.all (warned old) [
"package"
"frontend"
"assets"
"xdgIcons"
"hyperhiveRev"
"agentCpuQuota"
"agentMemoryMax"
"agentCpuWeight"
"agentIoWeight"
"buildSlots"
"adminUsers"
"hyperhiveFlake"
]
&& !(warned bare "package")
&& !(warned bare "adminUsers")
&& !(warned bare "hyperhiveFlake")
&& serve.agent_memory_max == "8G"
&& serve.agent_cpu_quota == "300%"
&& serve.build_slots == 3
&& old.users.groups.hive-admin.members == [ "alice" ];
}
{
# Under either namespace, each removed pre-fetch option fails evaluation naming
# itself and the swarm cache. The controls are a host that sets none, and
# a host that sets a moved sibling option (`buildSlots`), which warns
# but does not fail.
name = "a definition of a removed agent-base pre-fetch option fails evaluation, naming the option and the swarm cache";
ok =
let
refused =
m: path:
lib.any (
a:
!a.assertion
&& lib.hasInfix "services.hyperhive.${path}" a.message
&& lib.hasInfix "swarm cache" a.message
) m.assertions;
setting = lib.listToAttrs (
lib.concatMap
(
ns:
lib.mapAttrsToList
(name: value: {
name = "${ns}.${name}";
value = hiveWithController (lib.setAttrByPath (lib.splitString "." "${ns}.${name}") value);
})
{
preBuildAgentTemplates = true;
agentBaseToplevel = pkgs.findutils;
}
)
[
"c0re"
"deploy.hive-controller"
]
);
sibling = hiveWithController { c0re.buildSlots = 3; };
in
lib.length (lib.attrNames setting) == 4
&& lib.all (path: refused setting.${path} path) (lib.attrNames setting)
&& !(lib.any (path: refused bare path) (lib.attrNames setting))
&& !(lib.any (path: refused sibling path) (lib.attrNames setting));
}
{
# The price table fed only the hive dashboard's stats page, which is gone.
# A definition under either namespace fails evaluation naming the option;
# a host that sets none, or a moved sibling, does not.
name = "a definition of the removed modelPrices option fails evaluation, naming the option";
ok =
let
refused =
m: path:
lib.any (
a: !a.assertion && lib.hasInfix "services.hyperhive.${path}.modelPrices" a.message
) m.assertions;
prices = {
sonnet = {
input = 3.0;
};
};
in
refused (hiveWithController { c0re.modelPrices = prices; }) "c0re"
&& refused (hiveWithController {
deploy.hive-controller.modelPrices = prices;
}) "deploy.hive-controller"
&& !(refused bare "c0re")
&& !(refused bare "deploy.hive-controller")
&& !(refused (hiveWithController { c0re.buildSlots = 3; }) "c0re");
}
{
# What the moved options render with their defaults: the flake source, the agent limits
# and build slots in serve.json, and the sudoless-hivectl group's
# members. The same values the options held at their old paths.
name = "the flake source, agent limits, build slots and admin group render their defaults unchanged";
ok =
let
serve = builtins.fromJSON (
builtins.unsafeDiscardStringContext allLocal.environment.etc."hyperhive/serve.json".text
);
in
serve.hyperhive_flake
== builtins.unsafeDiscardStringContext "${(import ../sources.nix { inherit lib; })
.hyperhiveFlakeSource
}"
&& allLocal.services.hyperhive.deploy.hyperhiveFlake == serve.hyperhive_flake
&& serve.agent_memory_max == "4G"
&& serve.agent_cpu_quota == "200%"
&& serve.agent_cpu_weight == 80
&& serve.agent_io_weight == 80
&& serve.build_slots == 1
&& allLocal.users.groups.hive-admin.members == [ ];
}
{
# Each old spelling lands on the new path, with its own rename warning,
# and its value reaches the unit or vhost that reads it. Ports are chosen
# so three of them collide on purpose: the bao collector's telemetry
# port equals the NATS monitor port and the forge forwarder's, and the
# NATS forwarder's equals the log store forwarder's. Each of the three
# colocation assertions must read the moved options to see it.
name = "the per-service settings moved to deploy.* still apply from their old paths, with a rename warning each";
ok =
let
marker = name: pkgs.runCommand "auth-marker-${name}" { } "mkdir $out";
old = hiveWithController {
deploy.singleHostSwarm = true;
deploy.victorialogs.authPackage = marker "logs";
deploy.victorialogs.grafanaAuthPackage = marker "logs-grafana";
deploy.victoriametrics.authPackage = marker "metrics";
deploy.victoriametrics.agentAuthPackage = marker "metrics-agent";
c0re.nixpkgsFlake = "path:/legacy/nixpkgs";
swarm = {
controller.queue.natsUrl = "tls://queue.legacy.example:4333";
controller.queue.tokenEndpoint = "https://auth.legacy.example/api/oidc/token";
bao.otel.telemetryPort = 8898;
nats.monitorPort = 8898;
nats.metricsPort = 17777;
nats.otel.telemetryPort = 8897;
otel.telemetryPort = 18889;
victorialogs.port = 19428;
forge.httpPort = 13000;
matrix.allowEncryption = true;
controller.links = [
{
label = "Legacy";
icon = "L";
url = "https://legacy.example/";
}
];
matrix.adminSync.ignoreUsers = [ "@legacy:t.local" ];
otel.scrapeTargets.legacy = "127.0.0.1:9777";
};
};
d = old.services.hyperhive.deploy;
sw = old.services.hyperhive.swarm;
warned =
path:
lib.any (
w: lib.hasInfix "services.hyperhive.${path}" w && lib.hasInfix "has been renamed" w
) old.warnings;
locs = domain: old.services.nginx.virtualHosts.${domain}.locations;
refused = m: part: lib.any (a: !a.assertion && lib.hasInfix part a.message) m.assertions;
in
d.swarm-controller.queue.natsUrl == "tls://queue.legacy.example:4333"
&& d.swarm-controller.queue.tokenEndpoint == "https://auth.legacy.example/api/oidc/token"
&&
old.systemd.services.swarm-controller.environment.SWARM_CONTROLLER_NATS_URL
== "tls://queue.legacy.example:4333"
&&
old.systemd.services.swarm-controller.environment.SWARM_CONTROLLER_OIDC_TOKEN_ENDPOINT
== "https://auth.legacy.example/api/oidc/token"
&& d.bao.otel.telemetryPort == 8898
&& d.nats.monitorPort == 8898
&& d.nats.metricsPort == 17777
&& d.nats.otel.telemetryPort == 8897
&& d.swarm-otel.telemetryPort == 18889
&& d.victorialogs.port == 19428
&& d.forgejo.httpPort == 13000
&& d.matrix.allowEncryption
&& d.builder.nixpkgsFlake == "path:/legacy/nixpkgs"
&& lib.any (l: l.label == "Legacy" && l.url == "https://legacy.example/") d.swarm-controller.links
&& lib.any (l: l.label == "Legacy") (
builtins.fromJSON old.systemd.services.swarm-controller.environment.SWARM_CONTROLLER_LINKS
)
&& d.swarm-controller.matrixAdminSync.ignoreUsers == [ "@legacy:t.local" ]
&&
old.systemd.services.swarm-controller.environment.SWARM_CONTROLLER_MATRIX_ADMIN_SYNC_IGNORE_USERS
== "@legacy:t.local"
&& d.swarm-otel.scrapeTargets.legacy == "127.0.0.1:9777"
&&
(lib.findFirst (j: j.job_name == "legacy") null (
(otelSettings old).receivers.prometheus.config.scrape_configs
)).static_configs == [ { targets = [ "127.0.0.1:9777" ]; } ]
&&
old.containers.swarm-builder.config.systemd.services.swarm-builder.environment.SWARM_BUILDER_NIXPKGS_FLAKE
== "path:/legacy/nixpkgs"
&& (locs sw.nats.domain)."= /metrics".proxyPass == "http://127.0.0.1:17777/metrics"
&& (locs sw.victorialogs.domain)."= /metrics".proxyPass == "http://127.0.0.1:19428/metrics"
&& (locs sw.forge.domain)."/".proxyPass == "http://127.0.0.1:13000/"
&& old.containers.hive-matrix.config.services.matrix-tuwunel.settings.global.allow_encryption
&& lib.all warned [
"swarm.controller.queue.natsUrl"
"swarm.controller.queue.tokenEndpoint"
"swarm.bao.otel.telemetryPort"
"swarm.nats.monitorPort"
"swarm.nats.metricsPort"
"swarm.nats.otel.telemetryPort"
"swarm.otel.telemetryPort"
"swarm.victorialogs.port"
"swarm.forge.httpPort"
"swarm.matrix.allowEncryption"
"c0re.nixpkgsFlake"
"swarm.controller.links"
"swarm.matrix.adminSync.ignoreUsers"
"swarm.otel.scrapeTargets"
]
&& !(lib.any (w: lib.hasInfix "swarm.controller.queue" w) allLocal.warnings)
&& refused old "deploy.nats.otel.telemetryPort"
&& refused old "The forge's journal forwarder's telemetry port"
&& refused old "The log store's journal forwarder's telemetry port"
&& !(refused allLocal "deploy.nats.otel.telemetryPort")
&& !(refused allLocal "The forge's journal forwarder's telemetry port")
&& !(refused allLocal "The log store's journal forwarder's telemetry port");
}
{
# The default render of the moved options, pinned so moving them changes
# none of it: the ports in the vhosts and the collector config, the
# controller's queue coordinates, the homeserver's encryption switch.
name = "the per-service defaults reach their units and vhosts unchanged";
ok =
let
h = allLocal.services.hyperhive;
locs = domain: allLocal.services.nginx.virtualHosts.${domain}.locations;
env = allLocal.systemd.services.swarm-controller.environment;
in
allLocal.services.hyperhive.deploy.builder.nixpkgsFlake == "path:${pkgs.path}"
&& env.SWARM_CONTROLLER_NATS_URL == "tls://${h.swarm.nats.domain}:${toString h.swarm.nats.port}"
&& env.SWARM_CONTROLLER_OIDC_TOKEN_ENDPOINT == "${h.swarm.authelia.url}/api/oidc/token"
&& h.deploy.bao.otel.telemetryPort == 8890
&& h.deploy.nats.monitorPort == 8222
&& h.deploy.nats.otel.telemetryPort == 8891
&& h.deploy.swarm-otel.telemetryPort == 8889
&& (locs h.swarm.nats.domain)."= /metrics".proxyPass == "http://127.0.0.1:7777/metrics"
&& (locs h.swarm.victorialogs.domain)."= /metrics".proxyPass == "http://127.0.0.1:9428/metrics"
&& (locs h.swarm.forge.domain)."/".proxyPass == "http://127.0.0.1:3000/"
&& !allLocal.containers.hive-matrix.config.services.matrix-tuwunel.settings.global.allow_encryption
&& env.SWARM_CONTROLLER_MATRIX_ADMIN_SYNC_IGNORE_USERS == ""
&& lib.any (l: l.label == "Forge" && l.url == "https://${h.swarm.forge.domain}/") (
builtins.fromJSON env.SWARM_CONTROLLER_LINKS
)
&&
(lib.findFirst (j: j.job_name == "collector") null (
(otelSettings allLocal).receivers.prometheus.config.scrape_configs
)).static_configs == [ { targets = [ "127.0.0.1:8889" ]; } ];
}
{
# Each old spelling lands on the new path, with its own rename warning;
# the squid.conf it renders equals the one the new spelling renders, and
# differs from the default's (the control that the values reach it).
name = "services.hyperhive.squid.* settings still apply, with a rename warning each";
ok =
let
old = hiveWithController {
squid = {
allowedDomains = [ "legacy.example" ];
baseAllowedDomains = [ "base.example" ];
usersFile = "/var/lib/legacy-squid/users";
selfcheck.unrestrictedCanary = "legacy-u.example";
selfcheck.canary = "legacy-c.example";
};
};
new = hiveWithController {
deploy.hive-controller.squid = {
allowedDomains = [ "legacy.example" ];
baseAllowedDomains = [ "base.example" ];
usersFile = "/var/lib/legacy-squid/users";
selfcheck.unrestrictedCanary = "legacy-u.example";
selfcheck.canary = "legacy-c.example";
};
};
sq = old.services.hyperhive.deploy.hive-controller.squid;
warned =
m: path:
lib.any (
w: lib.hasInfix "services.hyperhive.squid.${path}" w && lib.hasInfix "has been renamed" w
) m.warnings;
in
sq.allowedDomains == [ "legacy.example" ]
&& sq.baseAllowedDomains == [ "base.example" ]
&& sq.usersFile == "/var/lib/legacy-squid/users"
&& sq.allowedFile == "/var/lib/legacy-squid/allowed"
&& sq.selfcheck.unrestrictedCanary == "legacy-u.example"
&& sq.selfcheck.canary == "legacy-c.example"
&& lib.all (warned old) [
"allowedDomains"
"baseAllowedDomains"
"usersFile"
"selfcheck.unrestrictedCanary"
"selfcheck.canary"
]
&& !(warned bare "allowedDomains")
&& old.services.squid.configText == new.services.squid.configText
&& lib.hasInfix "legacy-c.example" old.services.squid.configText
&& !(lib.hasInfix "legacy-c.example" bare.services.squid.configText);
}
{
# The default render, pinned so moving the options changes none of it:
# the listen address squid binds and hive-c0re hands out, and the users
# file hive-c0re writes, on a hive holding a store identity.
#
# Also the order hive-c0re's `log`-mode rules rely on: the include of
# `agent-allow.conf` follows every safety deny and `deny !authed`, and
# precedes `deny !allowed_dst`, so a log rule in it cannot let through
# what those refuse. The `would_deny` log line sits beside the unchanged
# request line.
name = "squid's default listen address and users file reach squid.conf and hive-c0re unchanged, and the include sits after the safety denies";
ok =
let
withIdentity = hiveWithController {
deploy.bao.enable = true;
deploy.authelia.enable = true;
};
e = withIdentity.systemd.services.hive-c0re.environment;
conf = withIdentity.services.squid.configText;
lines = map lib.strings.trim (lib.splitString "\n" conf);
at = line: lib.lists.findFirstIndex (l: l == line) null lines;
include = at "include /var/lib/hive-squid/agent-allow.conf";
before = [
"http_access deny !Safe_ports"
"http_access deny CONNECT !SSL_ports"
"http_access deny ip_v4_host"
"http_access deny ip_v6_host"
"http_access deny to_localhost"
"http_access deny to_linklocal"
"http_access deny to_bridge"
"http_access deny to_private"
"http_access deny !authed"
];
in
e.HIVE_SQUID_PROXY == "${withIdentity.services.hyperhive.network.bridgeIp}:3128"
&& e.HIVE_SQUID_USERS_FILE == "/var/lib/hive-squid/users"
&& lib.hasInfix "http_port ${e.HIVE_SQUID_PROXY}" conf
&& lib.hasInfix "basic_ncsa_auth /var/lib/hive-squid/users" conf
&& lib.hasInfix "acl allowed_dst dstdomain cache.nixos.org" conf
# Presence controls: the include and the policy deny are found at all.
&& include != null
&& at "http_access deny !allowed_dst" != null
&& include < at "http_access deny !allowed_dst"
&& lib.all (rule: at rule != null && at rule < include) before
# The request line is unchanged; the would_deny line is a second one.
&& at "access_log syslog:daemon.info squid !manager" != null
&& at "acl would_denied note would_deny 1" != null
&& at "access_log syslog:daemon.info logformat=would_deny would_denied" != null
&& lib.hasInfix "logformat would_deny would_deny user=%[un " conf;
}
{
# A hive's rendered identity, pinned so moving the option changes none of
# it: the coordinator's name and client, the dashboard name, the CN of the
# reader leaf the store host signs, and what the hive collector presents.
name = "a hive's name reaches hive-c0re, the reader leaf, the dashboard and the hive collector unchanged";
ok =
let
e = allLocal.systemd.services.hive-c0re.environment;
script = allLocal.systemd.services.swarm-bao-pki.script;
lines = lib.splitString "\n" script;
at = lib.lists.findFirstIndex (lib.hasSuffix "swarm-bao-pki client \\") null lines;
nextLine = builtins.head (builtins.match " *(.*)" (builtins.elemAt lines (at + 1)));
in
e.HYPERHIVE_HIVE_NAME == "h1"
&& e.HIVE_C0RE_OIDC_CLIENT_ID == "hive-h1"
&& e.HYPERHIVE_DASHBOARD_DOMAIN == "h1.hives.t.local"
&& allLocal.services.hyperhive.deploy.hive-controller.dashboardDomain == "h1.hives.t.local"
&& at != null
&& nextLine == ''h1 "" clientAuth'';
}
{
# The control is a host that defines no such option; the removal message
# must not appear for it.
name = "a definition of the removed services.hyperhive.ruthless option fails evaluation, pointing at the swarm dashboard";
ok =
let
refused =
m:
lib.any (
a:
!a.assertion
&& lib.hasInfix "services.hyperhive.ruthless" a.message
&& lib.hasInfix "swarm dashboard" a.message
) m.assertions;
in
refused (hive {
ruthless = true;
})
&& !(refused allLocal);
}
{
# One host defining all five removed paths. Each message names its own
# path (`services.hyperhive.swarm.snapshotStore.port` does not contain
# `services.hyperhive.snapshotStore.port`), so each lookup finds only
# its option. The control is a host that defines none.
name = "a definition of a removed snapshot store option fails evaluation, naming the option and swarm-archive";
ok =
let
paths = [
"swarm.snapshotStore.address"
"swarm.snapshotStore.port"
"snapshotStore.enable"
"snapshotStore.path"
"snapshotStore.port"
];
refused =
m: path:
lib.any (
a:
!a.assertion
&& lib.hasInfix "services.hyperhive.${path}" a.message
&& lib.hasInfix "snapshot store was removed" a.message
&& lib.hasInfix "swarm-archive" a.message
) m.assertions;
old = hive {
swarm.snapshotStore = {
address = "10.100.0.1";
port = 51821;
};
snapshotStore = {
enable = true;
path = "/var/lib/hyperhive-snapshots";
port = 51821;
};
};
in
lib.all (path: refused old path) paths && !(lib.any (path: refused bare path) paths);
}
{
# Refused wherever any hyperhive service runs, the hive or a swarm
# service alone. The host enabling nothing is the control that it is
# gated at all, and has to pass every assertion: it is what an
# operator gets from importing the module.
name = "a missing swarm.domain is refused on a hive and on a swarm-service host, and a host enabling nothing evaluates";
ok =
let
noDomain = extra: hiveWithController ({ swarm.domain = null; } // extra);
refusedSwarmDomain =
extra:
lib.any (
a: !a.assertion && lib.hasInfix "services.hyperhive.swarm.domain to be set" a.message
) (noDomain extra).assertions;
in
refusedSwarmDomain { }
&& refusedSwarmDomain {
deploy.hive-controller.enable = false;
deploy.nats.enable = true;
}
&& lib.all (a: a.assertion) (noDomain { deploy.hive-controller.enable = false; }).assertions;
}
{
# Control: a store fetch with no timer still renders `RemainAfterExit`,
# so the cases below read a real value rather than a missing one.
name = "Grafana's admin-password fetch stays active after it exits and has no timer";
ok =
allLocal.systemd.services ? swarm-bao-grafana-admin
&& remainsAfterExit allLocal.systemd.services.swarm-bao-grafana-admin
&& !(allLocal.systemd.timers ? swarm-bao-grafana-admin);
}
{
# A timer whose unit does not exist fails to load, so each timer is
# gated exactly as its fetch is.
name = "a host running none of the store-refreshed fetches renders none of their timers";
ok = lib.all (
{ unit, ... }: !(bare.systemd.services ? ${unit}) && !(bare.systemd.timers ? ${unit})
) storeRefreshed;
}
{
# `or false` so a dropped key fails this case by name instead of
# aborting eval. Only the rendered unit is pinned; eval cannot show
# that hive-priv still works under the directives.
name = "hive-priv locks personality and denies real-time scheduling, kernel logs and module loading";
ok =
let
sc = allLocal.systemd.services.hive-priv.serviceConfig;
in
(sc.LockPersonality or false)
&& (sc.RestrictRealtime or false)
&& (sc.ProtectKernelLogs or false)
&& (sc.ProtectKernelModules or false);
}
{
# Hyperhive's own declarative containers each run the root check from
# their own unit, warn-only. Every container in these fixtures is one of
# hyperhive's, so the names are read off the fixture: a container added
# without the check breaks the list, which is the point. The unit's own
# pre-start script is not a `check-rootfs` entry, so the count is the check
# alone.
name = "every hyperhive container runs the root check warn-only from its own unit, and no hyperhive container is left out";
ok =
let
checks =
m: name:
let
pre = m.systemd.services."container@${name}".serviceConfig.ExecStartPre or [ ];
in
builtins.filter (lib.hasInfix "check-rootfs") (
map (p: builtins.unsafeDiscardStringContext (toString p)) pre
);
carries =
m: name:
checks m name == [
"-/run/hive-rootfs-check check-rootfs --warn-only ${name}"
];
names = lib.attrNames allLocal.containers;
in
names == [
"hive-forge"
"hive-matrix"
"swarm-attic"
"swarm-authelia"
"swarm-bao"
"swarm-builder"
"swarm-claude-login"
"swarm-grafana"
"swarm-nats"
"swarm-notify-router"
"swarm-otel"
"swarm-review-classifier"
"swarm-victorialogs"
"swarm-victoriametrics"
]
&& lib.all (carries allLocal) names
&& withCi.containers ? swarm-ci-runner
&& lib.all (carries withCi) (lib.attrNames withCi.containers)
# The line holds no store path, so a new hyperhive build leaves the
# unit text alone. Presence control: the lookup does find the line,
# and a line with a store path is caught by the same test.
&& lib.all (n: !(lib.hasInfix "/nix/store" (lib.head (checks allLocal n)))) names
&& lib.hasInfix "/nix/store" "-/nix/store/x-hyperhive/bin/hive-priv check-rootfs --warn-only x"
# The stable path is a link to the current package's hive-priv.
&& lib.elem "L+ /run/hive-rootfs-check - - - - ${allLocal.services.hyperhive.deploy.hive-controller.package}/bin/hive-priv" allLocal.systemd.tmpfiles.rules
# Control: a name with no unit finds nothing.
&& checks allLocal "swarm-nope" == [ ];
}
{
# Advisory means the start goes on whatever the check does, a crash or a
# missing binary included: systemd ignores the failure of a command
# prefixed `-`. The agents' strict line is the opposite and has no
# prefix; hive-priv renders it at runtime, and its own test
# (`the_rootfs_check_leads_the_service_section_…`) pins the bare
# `ExecStartPre=` there.
name = "every hyperhive container's root check is ignored on failure, not left to fail the start";
ok =
let
hivePrivLines =
m: name:
builtins.filter (lib.hasInfix "check-rootfs") (
map (p: builtins.unsafeDiscardStringContext (toString p)) (
m.systemd.services."container@${name}".serviceConfig.ExecStartPre or [ ]
)
);
allLines = m: lib.concatMap (hivePrivLines m) (lib.attrNames m.containers);
ignored = lib.hasPrefix "-/";
in
allLines allLocal != [ ]
&& lib.length (allLines allLocal) == lib.length (lib.attrNames allLocal.containers)
&& lib.all ignored (allLines allLocal)
&& lib.all ignored (allLines withCi)
# Control: the test discriminates, a line without the prefix is not ignored.
&& !(ignored "/run/hive-rootfs-check check-rootfs --warn-only x");
}
{
# The operator's other containers are not hyperhive's to touch: a
# `containers.<name>` it declares gets no check, while the classifier
# beside it does. The positive arm is what makes the absence mean
# something: the same lookup finds the classifier's.
name = "a container hyperhive does not declare gets no root check";
ok =
let
checks =
name:
builtins.filter (lib.hasInfix "check-rootfs") (
map (p: builtins.unsafeDiscardStringContext (toString p)) (
withForeignContainer.systemd.services."container@${name}".serviceConfig.ExecStartPre or [ ]
)
);
in
lib.attrNames withForeignContainer.containers == [
"operator-box"
"swarm-bao"
"swarm-review-classifier"
]
&& lib.length (checks "swarm-review-classifier") == 1
&& lib.length (checks "swarm-bao") == 1
&& checks "operator-box" == [ ];
}
{
# Presence controls: the IPv6 arm is on in this fixture, and the private
# list is empty, so what is absent below is absent by configuration.
name = "a gateway with no private addresses listens on both ports of 0.0.0.0 and [::] and nowhere else";
ok =
let
gw = allLocal.services.hyperhive.gateway;
l = allLocal.services.nginx.virtualHosts."_".listen;
in
gw.listenIPv6
&& gw.privateListenAddresses == [ ]
&&
listenFields l == [
(httpAt "0.0.0.0" 80)
(httpsAt "0.0.0.0" 443)
(httpAt "[::]" 80)
(httpsAt "[::]" 443)
]
&& listenFields gw.lib.listen == listenFields l
&& gw.lib.privateListen == [ ];
}
{
# `listenIPv6` is the escape for a host whose kernel cannot open an IPv6
# socket, and follows `networking.enableIPv6` unless set.
name = "the IPv6 listener can be turned off and follows networking.enableIPv6";
ok =
let
off = {
port = 80;
httpsPort = 443;
listenIPv6 = false;
privateListenAddresses = [ ];
};
in
listenFields (gatewayListen.public off) == [
(httpAt "0.0.0.0" 80)
(httpsAt "0.0.0.0" 443)
]
&& !noIPv6.services.hyperhive.gateway.listenIPv6
&& allLocal.services.hyperhive.gateway.listenIPv6;
}
{
# An IPv6 literal is bracketed whichever way the operator wrote it, and
# listed once: nginx refuses a duplicate `listen`. The wildcard test is
# paired with addresses that are not wildcards.
name = "private addresses render IPv6 in brackets, once, and the wildcards are recognised";
ok =
let
cfg = {
port = 80;
httpsPort = 443;
listenIPv6 = true;
privateListenAddresses = [
"10.100.0.1"
"fd00:100::1"
"[fd00:100::1]"
];
};
in
listenFields (gatewayListen.private cfg) == [
(httpAt "10.100.0.1" 80)
(httpsAt "10.100.0.1" 443)
(httpAt "[fd00:100::1]" 80)
(httpsAt "[fd00:100::1]" 443)
]
&& lib.all gatewayListen.isWildcard [
"0.0.0.0"
"::"
"[::]"
"*"
]
&& !(lib.any gatewayListen.isWildcard cfg.privateListenAddresses);
}
{
# The private vhost binds the private addresses only; the catch-all and
# every vhost that stays public bind the wildcards AND the private
# addresses, because nginx gives a private address a server set of its
# own. The catch-all arm is the presence control for the wildcard check.
name = "a vhost with public = false listens on the private addresses only, the catch-all on those and the wildcards";
ok =
let
vh = controllerAltPort.services.nginx.virtualHosts;
private = [
(httpAt "10.100.0.1" 80)
(httpsAt "10.100.0.1" 8443)
(httpAt "[fd00:100::1]" 80)
(httpsAt "[fd00:100::1]" 8443)
];
in
listenFields vh."internal.t.local".listen == private
&& !(lib.any (l: l.addr == "0.0.0.0" || l.addr == "[::]") vh."internal.t.local".listen)
&&
listenFields vh."_".listen == [
(httpAt "0.0.0.0" 80)
(httpsAt "0.0.0.0" 8443)
(httpAt "[::]" 80)
(httpsAt "[::]" 8443)
]
++ private
&& vh."_".default;
}
{
# Both arms: the wildcard refusal fires on the wildcard, and the
# "needs an address" refusal stays quiet there because the list is not
# empty.
name = "a wildcard private address is refused";
ok =
let
fired = lib.filter (a: !a.assertion) privateWildcard.assertions;
mentions = s: lib.any (a: lib.hasInfix s a.message) fired;
in
mentions "privateListenAddresses holds"
&& !(mentions "while services.hyperhive.gateway.privateListenAddresses is empty");
}
]
# Starting a unit that stays `active (exited)` is a no-op, so a timer aimed
# at one fires and fetches nothing.
++ map (
{ unit, machine }:
{
name = "${unit} re-runs on a ten-minute timer and does not stay active after it exits";
ok =
let
s = machine.systemd.services;
t = machine.systemd.timers;
in
s ? ${unit}
&& t ? ${unit}
&& !(remainsAfterExit s.${unit})
&& lib.elem "timers.target" t.${unit}.wantedBy
&& t.${unit}.timerConfig.OnUnitInactiveSec == "10min"
&& (t.${unit}.timerConfig.Unit or "${unit}.service") == "${unit}.service";
}
) storeRefreshed;
in
runGroup "core-toggle" cases