# This agent's GitHub personal access token, fetched from the swarm secret # store by the agent itself, into the file ./github.nix's readers use. # # An operator links the token in the swarm UI; `swarm-controller` stores it at # `swarm/agents//github-token` (`swarm_secret_client::github`). The # agent's own read grant covers that path, so this unit reads it and writes # `/github-token`, which the `gh` wrapper, the git credential helper and # `hive-github-notify` read. # # It never deletes. A `github-token` already in place stays when the store has # none or cannot be read. The file is replaced by rename, and only when its # bytes changed. { pkgs, lib, config, ... }: let cfg = config.services.hyperhive.agent.bao; agentName = config.services.hyperhive.agent.user.name; stateDir = "/agents/${agentName}/state"; # The same three ids ./bao.nix and ./forge-accounts.nix load. certCredential = "hive-agent-bao-cert"; keyCredential = "hive-agent-bao-key"; serverCaCredential = "hive-agent-bao-server-ca"; unitName = "hive-agent-github-token"; # The nix half of `swarm_secret_client::github::account_path` plus # `path::MOUNT`. tokenPath = "secret/swarm/agents/${agentName}/github-token"; runtimeDir = unitName; # The store's whole answer, token included: kept in the unit's own `0700` # directory, never in the state dir. rawFile = "/run/${runtimeDir}/account.json"; errFile = "/run/${runtimeDir}/bao.err"; tokenFile = "${stateDir}/github-token"; stagedFile = "${stateDir}/.github-token.new"; configured = cfg.addr != null && config.services.hyperhive.agent.github.enable; storeRetry = import ../host-modules/lib/store-retry.nix { }; in { config = lib.mkIf configured { systemd.services.${unitName} = { description = "fetch this agent's GitHub token from the secret store"; after = [ "network.target" "hive-agent-bao-identity.service" ]; # The poller reads the token once at start. before = [ "hive-github-notify.service" ]; wantedBy = [ "multi-user.target" ]; path = [ pkgs.openbao pkgs.coreutils pkgs.diffutils pkgs.jq ]; # ../host-modules/lib/store-retry.nix. inherit (storeRetry) startLimitBurst startLimitIntervalSec; serviceConfig = storeRetry.serviceConfig // { Type = "oneshot"; # Not `RemainAfterExit`, so the timer below can start it again. RemainAfterExit = false; TimeoutStartSec = 30; User = agentName; Group = agentName; RuntimeDirectory = runtimeDir; RuntimeDirectoryMode = "0700"; # `0600`, the mode `github-token` has. UMask = "0077"; LoadCredential = [ certCredential keyCredential serverCaCredential ]; }; environment = { BAO_ADDR = cfg.addr; BAO_CLIENT_CERT = "%d/${certCredential}"; BAO_CLIENT_KEY = "%d/${keyCredential}"; }; script = '' set -euo pipefail # No identity delivered: ./bao.nix's check reports that. for id in ${lib.escapeShellArg certCredential} ${lib.escapeShellArg keyCredential}; do if [ ! -s "$CREDENTIALS_DIRECTORY/$id" ]; then echo "this agent has no store identity, so it cannot fetch its GitHub token." >&2 exit 0 fi done if [ -s "$CREDENTIALS_DIRECTORY/${serverCaCredential}" ]; then export BAO_CACERT="$CREDENTIALS_DIRECTORY/${serverCaCredential}" fi err=${lib.escapeShellArg errFile} raw=${lib.escapeShellArg rawFile} staged=${lib.escapeShellArg stagedFile} token=${lib.escapeShellArg tokenFile} trap 'rm -f "$err" "$raw" "$staged"' EXIT if ! BAO_TOKEN="$(bao login -method=cert -token-only 2>"$err")"; then echo "the swarm secret store at $BAO_ADDR did not accept this agent's certificate login:" >&2 if [ -s "$err" ]; then cat "$err" >&2; fi exit 1 fi export BAO_TOKEN # No token linked and a store that cannot answer look alike here, and # either way the file in place, if any, is kept. if ! bao kv get -format=json ${lib.escapeShellArg tokenPath} >"$raw" 2>"$err"; then echo "no GitHub token read from ${tokenPath}; github-token left as it is:" >&2 if [ -s "$err" ]; then cat "$err" >&2; fi exit 0 fi # ⚠️ The token goes from the store's answer straight into a file; it is # never in a variable or an argument. A malformed object exits 0: # failing the unit would only restart it into the same answer. rm -f "$staged" if ! jq -er '.data.data.value | strings' "$raw" >"$staged"; then echo "${tokenPath} holds no string value; github-token left as it is." >&2 exit 0 fi if cmp -s "$staged" "$token"; then echo "this agent's GitHub token at ${tokenPath} is unchanged." exit 0 fi mv -f "$staged" "$token" echo "fetched this agent's GitHub token from ${tokenPath}." ''; }; # The same cadence as ./forge-accounts.nix. systemd.timers.${unitName} = { description = "re-fetch this agent's GitHub token from the secret store"; wantedBy = [ "timers.target" ]; timerConfig = { OnUnitInactiveSec = "2min"; RandomizedDelaySec = "20s"; }; }; }; }