# The hive's shared swarm-queue client credential, imported into the harness # unit's credentials directory for `swarm-logs` (./logs.nix) to authenticate # with. The harness itself connects to the queue with the agent's own # credential (./queue-identity.nix) and reads neither file. # # The credential is two files, the secret and the client id beside it, 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 the reader 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 agent user cannot open. { lib, config, ... }: let cfg = config.services.hyperhive.agent.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"; # Where systemd materialises this unit's credentials. `%d` only expands # *inside* a unit, so `swarm-logs` (./logs.nix), which is not one, needs the # literal; deriving it from the unit name here is what keeps the two from # disagreeing about which unit's credentials they mean. credentialsDir = "/run/credentials/hive-agent.service"; in { options.services.hyperhive.agent.queue = { natsUrl = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; example = "tls://nats.example.com: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 names the queue rather than a loopback address — inside this container `127.0.0.1` is the agent itself. `null` means this hive has not been given the queue's address for its agents: the harness unit imports no credential, and the harness fails at boot, since it cannot run without a queue connection. 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, where `swarm-logs` mints a `client_credentials` token for the client named in the delivered credential. The harness unit imports that credential only when this and {option}`services.hyperhive.agent.queue.natsUrl` are both set. ''; }; # One declaration, two readers. The credential ids are this module's — # `hive_c0re::lifecycle::host_config` and the `LoadCredential=` below are # the pair that has to agree on them — and a second consumer that spelled # them again would be a second source of truth for a string whose # mismatch is a file that is simply not there. clientIdFile = lib.mkOption { type = lib.types.str; readOnly = true; default = "${credentialsDir}/${clientIdCredential}"; description = '' Path the agent's OIDC client id is delivered at, for a consumer outside the harness unit. Read-only: it is a fact about where the credential lands, not a knob — see {option}`services.hyperhive.agent.logs.queryUrl` for the consumer this exists for. ''; }; clientSecretFile = lib.mkOption { type = lib.types.str; readOnly = true; default = "${credentialsDir}/${secretCredential}"; description = '' Path the agent's OIDC client secret is delivered at. Read-only for the same reason as {option}`services.hyperhive.agent.queue.clientIdFile`. 🩸 A PATH and never a value. The file is `0400` to the agent user and is read at the moment of a token request; nothing in this tree puts its contents in an environment variable, where `/proc//environ` would publish them to every process in the container. ''; }; }; 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. serviceConfig.LoadCredential = [ secretCredential clientIdCredential ]; }; }; }