Watch
0
0
Fork
You've already forked hyperhive
0

pull swarm closures through the nix daemon: substituters in the fragment, merged root-only netrc

This commit is contained in:
atlas 2026-11-08 10:29:46 +01:00 • committed by mara
commit ef68baa00f

View file

@ -1,30 +1,33 @@
# The binary-cache facts the host's nix daemon, hive-priv (root) and hive-c0re
# use, kept where only root can change them.
#
# hive-c0re pulls an agent's closure from the swarm cache with its own
# `nix copy --from`: the client does the HTTP fetch with the pull token, and
# hands the NAR to the host's nix daemon, which stores it only if a key in its
# hive-c0re pulls an agent's closure with `nix-store --realise` through the
# host's nix daemon, which substitutes each path from the swarm cache or from
# its other substituters (cache.nixos.org) and stores it only if a key in its
# `trusted-public-keys` signed it. hive-priv applies a closure only if the
# swarm's key signed it. What decides that must not come from a file the
# unprivileged hive-c0re writes. So:
# swarm's key signed it. Neither decision may come from a file hive-c0re writes:
#
# - the cache's address is this host's configuration
# ({option}`services.hyperhive.swarm.attic.domain` and `cache`), handed to
# hive-c0re in its unit's environment;
# - the cache's address is the host's
# {option}`services.hyperhive.swarm.attic.domain` and `cache`;
# - the cache's public key and the pull token are read from the store by a root
# oneshot, `hive-priv-cache-facts`, under this hive's own store identity. The
# key and the token land in `/run/hyperhive-priv/attic/` (root, `0700`; files
# `0600`). The key also lands in a nix.conf fragment,
# `/run/hyperhive-nix/cache.conf`, as `extra-trusted-public-keys`, which the
# host's nix.conf includes (`!include`, so a host whose unit has not run yet
# still starts). The fragment names no substituter and no netrc, so a client
# of the daemon that is not hive-c0re (an agent container on `nixBuilds =
# host`) cannot fetch from the cache or send the token;
# - the token also lands in `/run/hyperhive-nix/netrc`, `root:hive-core 0640`:
# the one place hive-core, and nothing else unprivileged, can read it.
# key lands in `/run/hyperhive-priv/attic/public-key` (directory `0700`, file
# `0600`).
# The cache's address, the key and a netrc also land in a nix.conf fragment,
# `/run/hyperhive-nix/cache.conf`, as `extra-substituters`,
# `extra-trusted-public-keys` and `netrc-file`, which the host's nix.conf
# includes (`!include`, so a host whose unit has not run yet still starts).
# Every client of the daemon (hive-c0re, agent containers on `nixBuilds =
# host`) can have a swarm closure substituted; closures hold no secret;
# - that netrc, `/run/hyperhive-nix/netrc` (`root:root 0600`), is the pull
# token's entry plus the entries of the host's own netrc, which
# `netrc-file` would otherwise replace. The host's netrc is
# `nix.settings.netrc-file` when the host sets it, else nix's default
# `/etc/nix/netrc`. The unit merges again on every run, so an edit reaches the
# daemon within the timer's ten minutes. A netrc named only in
# `nix.extraOptions` is not seen.
#
# hive-c0re keeps its own copies under `/run/hyperhive/attic/` for the probe,
# which only reports.
# hive-c0re keeps its own copies under `/run/hyperhive/attic/` for the probe.
{
pkgs,
lib,
@ -53,7 +56,10 @@ let
dir = "/run/hyperhive-priv/attic";
confDir = "/run/hyperhive-nix";
confFile = "${confDir}/cache.conf";
clientNetrc = "${confDir}/netrc";
cacheUrl = "https://${atticCfg.domain}/${atticCfg.cache}";
netrcFile = "${confDir}/netrc";
# `netrc-file` is not additive: the fragment's setting replaces the host's.
hostNetrc = toString (config.nix.settings.netrc-file or "/etc/nix/netrc");
# Where the store runs on this host, the first login waits for its container.
# No `wants`: a pull would start the container mid-switch on the old closure.
@ -72,10 +78,6 @@ in
# `!include` tolerates a fragment that does not exist yet.
nix.extraOptions = "!include ${confFile}";
# The address hive-c0re names to `nix copy --from`; no request chooses it.
systemd.services.hive-c0re.environment.HIVE_C0RE_CACHE_URL =
"https://${atticCfg.domain}/${atticCfg.cache}";
systemd.services.hive-priv-cache-facts = {
description = "Read the swarm cache's public key and pull token for the host's nix daemon and hive-priv";
wantedBy = [ "multi-user.target" ];
@ -111,8 +113,13 @@ in
script = import ./lib/attic-root-facts.nix { inherit lib; } {
hive = hiveController.hiveName;
host = atticCfg.domain;
inherit dir confFile clientNetrc;
clientGroup = config.users.users.hive-core.group;
inherit
dir
confFile
netrcFile
hostNetrc
;
url = cacheUrl;
};
};