# Swarm-queue coordinates for this agent's harness. # # Three of the four the harness needs are addresses (this file's two options # plus the secret's path); the fourth, the client id, arrives as a file beside # the secret so a reader never spells `hive--agent` a second time. # # ⚠️ The credential arrives as a systemd credential and NOT as a bind mount, # and the mode is why: the host file is `root:0600` and this unit runs as the # unprivileged agent user. nspawn's `--load-credential` (written by # `hive_c0re::lifecycle::host_config`) is read by the container manager as # root and re-exposed under this unit's own `User=`; a bind would deliver a # file the harness cannot open. { lib, config, ... }: let cfg = config.hyperhive.queue; configured = cfg.natsUrl != null && cfg.tokenEndpoint != null; # The two ids `hive_c0re::lifecycle::host_config` forwards under. Neither # side can discover the other's spelling, so a rename is a rename there too. secretCredential = "hive-queue-agent-secret"; clientIdCredential = "hive-queue-agent-client-id"; in { options.hyperhive.queue = { natsUrl = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; example = "nats://10.42.0.1:4222"; description = '' Where the swarm queue listens, as this container reaches it. Set by the generated meta flake from the host's {option}`services.hyperhive.deploy.hive-controller.queue.agentNatsUrl`, which is the bridge address rather than a loopback one — inside this container `127.0.0.1` is the agent itself. `null` means this hive has no queue, and the harness then declares no credential and logs that it has none. It is deliberately not defaulted to anything: a guessed address builds fine and talks to the wrong machine. ''; }; tokenEndpoint = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; example = "https://auth.example.com/api/oidc/token"; description = '' The swarm IdP's OAuth2 token endpoint. The harness mints a `client_credentials` token there and presents it to the queue, which authenticates it as the client named in the delivered credential. Set together with {option}`hyperhive.queue.natsUrl` or not at all — the harness treats a half-set pair as a deployment bug rather than as "no queue". ''; }; }; config = lib.mkIf configured { systemd.services.hive-agent = { # Bare ids, no paths: this is the terse `LoadCredential=` form that # inherits a credential the service *manager* received, which is what # the container manager passed in. `man systemd.exec` also makes that # form non-fatal when the credential is absent, which is exactly the # behaviour a hive whose publisher has not run yet needs — the unit # starts, finds no id, and says so. serviceConfig.LoadCredential = [ secretCredential clientIdCredential ]; environment = { # `%d` is `$CREDENTIALS_DIRECTORY`, per-unit and owned by `User=`. # Same shape hive-c0re's own queue client is handed its secret in # (`nix/host-modules/hive-c0re/environment.nix`) — the harness reads # a path and never a value. HIVE_AGENT_OIDC_CLIENT_SECRET_FILE = "%d/${secretCredential}"; # The id is public (it is sent to the token endpoint on every # connection) but still arrives as a path, because it arrives *with* # the secret. `QueueConfig::from_env` wants it as a value, so the # harness reads this file itself — see `hive-agent`'s `swarm_queue`. HIVE_AGENT_OIDC_CLIENT_ID_FILE = "%d/${clientIdCredential}"; }; # No `HIVE_AGENT_OIDC_CA_FILE`. The hive's own client needs one because # the host does not trust the swarm's CA, but an agent does: the meta # flake embeds the hive CA and the swarm root it is issued under into # this container's `security.pki.certificateFiles` at build time, and # reqwest's rustls backend verifies against the system store. A path # here would need the bundle delivered as a third credential to say # nothing new. }; }; }