121 lines
4.7 KiB
Nix
121 lines
4.7 KiB
Nix
# 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";
|
|
};
|
|
}
|