# 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 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. Neither decision may come from a file hive-c0re writes: # # - 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 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. { 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"; 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. 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}"; 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 netrcFile hostNetrc ; url = cacheUrl; }; }; systemd.timers.hive-priv-cache-facts = storeRefreshTimer "re-read the swarm cache's facts for hive-priv"; }; }