# 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 # `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: # # - 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 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. # # hive-c0re keeps its own copies under `/run/hyperhive/attic/` for the probe, # which only reports. { pkgs, lib, config, ... }: let hyperhiveCfg = config.services.hyperhive; hiveController = hyperhiveCfg.deploy.hive-controller; tls = hiveController.tls; baoCfg = hyperhiveCfg.swarm.bao; baoDeploy = hyperhiveCfg.deploy.bao; atticCfg = hyperhiveCfg.swarm.attic; # Without a cache to name, a hive name to read under or an identity to log # in with there is nothing to fetch, and hive-priv has no cache to download # from. enabled = hiveController.enable && atticCfg.domain != null && atticCfg.cache != null && hiveController.hiveName != null && tls.baoHiveIdentityCertFile != null && tls.baoHiveIdentityKeyFile != null; dir = "/run/hyperhive-priv/attic"; confDir = "/run/hyperhive-nix"; confFile = "${confDir}/cache.conf"; clientNetrc = "${confDir}/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. storeContainer = lib.optional baoDeploy.enable "container@${baoCfg.machine}.service"; storeRetry = import ./lib/store-retry.nix { }; storeRefreshTimer = import ./lib/store-refresh-timer.nix { }; in { config = lib.mkIf enabled { systemd.tmpfiles.rules = [ "d /run/hyperhive-priv 0700 root root - -" "d ${confDir} 0755 root root - -" ]; # `!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" ]; wants = [ "network-online.target" ]; after = [ "network-online.target" "systemd-tmpfiles-setup.service" ] ++ storeContainer; path = [ baoDeploy.package pkgs.coreutils pkgs.gnugrep pkgs.systemd ]; environment = { BAO_ADDR = "https://${baoCfg.domain}:${toString baoCfg.port}"; BAO_CLIENT_CERT = tls.baoHiveIdentityCertFile; BAO_CLIENT_KEY = tls.baoHiveIdentityKeyFile; } // lib.optionalAttrs (baoDeploy.serverCaFile != null) { BAO_CACERT = baoDeploy.serverCaFile; }; # ./lib/store-retry.nix: the login fails for as long as the store is # sealed or down. inherit (storeRetry) startLimitBurst startLimitIntervalSec; serviceConfig = storeRetry.serviceConfig // { Type = "oneshot"; UMask = "0077"; SyslogIdentifier = "hive-priv-cache-facts"; TimeoutStartSec = "5min"; }; 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; }; }; systemd.timers.hive-priv-cache-facts = storeRefreshTimer "re-read the swarm cache's facts for hive-priv"; }; }