refactor(#1970): replace githubAccount option with hyperhive.github.enable gate (default true, github.com + x-access-token)

This commit is contained in:
damocles 2026-07-11 11:29:52 +02:00 committed by mara
commit af9b46e80a

View file

@ -17,12 +17,12 @@ let
# from `userName` to keep them coupled.
userName = config.hyperhive.user.name;
homeDir = "/home/${userName}";
# GitHub account (hyperhive.githubAccount): a `gh` wrapper + a git
# GitHub integration (hyperhive.github.enable): a `gh` wrapper + a git
# credential helper, both sourcing the PAT from HIVE_GITHUB_TOKEN_FILE at
# invocation time so a dashboard-pasted token takes effect with no rebuild.
# The scripts are static (they read env at runtime) — the account option
# only gates whether they're installed + the env is set, so the token
# value never enters the nix store.
# The scripts are static (they read env at runtime) — the enable flag only
# gates whether they're installed + the env is set, so the token value never
# enters the nix store. github.com only; git auths as `x-access-token` + PAT.
ghWrapper = pkgs.writeShellScriptBin "gh" ''
if [ -n "''${HIVE_GITHUB_TOKEN_FILE:-}" ] && [ -r "''${HIVE_GITHUB_TOKEN_FILE}" ]; then
GH_TOKEN="$(cat "''${HIVE_GITHUB_TOKEN_FILE}")"
@ -34,7 +34,9 @@ let
# git credential-helper protocol: only the `get` action needs an answer.
[ "''${1:-}" = "get" ] || exit 0
if [ -n "''${HIVE_GITHUB_TOKEN_FILE:-}" ] && [ -r "''${HIVE_GITHUB_TOKEN_FILE}" ]; then
printf 'username=%s\n' "''${HIVE_GITHUB_USER:-x-access-token}"
# GitHub ignores the username for PAT auth — `x-access-token` is the
# conventional placeholder; the PAT is the password.
printf 'username=x-access-token\n'
printf 'password=%s\n' "$(cat "''${HIVE_GITHUB_TOKEN_FILE}")"
fi
'';
@ -645,73 +647,28 @@ in
'';
};
options.hyperhive.githubAccount = lib.mkOption {
type = lib.types.nullOr (
lib.types.submodule {
options = {
username = lib.mkOption {
type = lib.types.str;
example = "the-sword-above";
description = ''
GitHub login the agent acts as. Used as the username for
`git push` over HTTPS and shown to the agent via the
`HIVE_GITHUB_USER` environment variable. Should be a
dedicated bot account, never a human's.
'';
};
tokenFile = lib.mkOption {
type = lib.types.str;
example = "/agents/damocles/state/github-token";
description = ''
Path to the file holding this account's personal access
token (PAT). The token *value* is never in nix --- the
provisioner (dashboard credentials tab, or `hivectl`)
writes an operator-supplied PAT here (0600, agent-owned),
the same contract as `matrixAccounts.<name>.tokenFile`.
The `gh` wrapper and the git credential helper read the
token from this path at invocation time, so a PAT pasted
mid-session takes effect with no rebuild. `gh` / `git push`
simply fail unauthenticated until the file exists.
Keep the PAT minimally scoped (only the repos/scopes the
agent's workflow needs): the agent has passwordless sudo,
so a compromised agent can act as the account within the
token's scopes --- scope is the real blast-radius limiter.
'';
};
host = lib.mkOption {
type = lib.types.str;
default = "github.com";
example = "github.example.com";
description = ''
GitHub host. Defaults to `github.com`; set it for a GitHub
Enterprise instance. Drives both the `gh` API host
(`GH_HOST`) and the git credential helper's URL match.
'';
};
};
}
);
default = null;
example = lib.literalExpression ''
{
username = "the-sword-above";
tokenFile = "/agents/damocles/state/github-token";
}
'';
options.hyperhive.github.enable = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Give the agent a managed GitHub account: a `gh` CLI wrapper and a
git credential helper, both authenticated from an operator-supplied
PAT, so the agent can run `gh` API calls and `git push` to GitHub as
the configured login without any manual `gh auth login` dance.
Install the GitHub integration in this agent: a `gh` CLI wrapper and a
git credential helper for `https://github.com`, both authenticated from
an operator-supplied personal access token (PAT). The PAT is written to
`<state>/github-token` out of band --- the dashboard credentials tab or
`hivectl github set-token` --- so giving an agent GitHub is a runtime
paste, no per-agent config or rebuild. The wrappers read the token file
at invocation, so a freshly-pasted PAT takes effect immediately; until
one exists, `gh` / `git push` just fail unauthenticated.
`null` (the default) leaves GitHub off entirely --- no `gh` wrapper,
no credential helper, no env. When set, the token is supplied out of
band (dashboard credentials tab / `hivectl`) into `tokenFile`; nix
only carries the login + host, never the secret.
github.com only. git authenticates as `x-access-token` + the PAT (GitHub
ignores the username for PAT auth); `gh` derives its identity from the
token. Keep the PAT minimally scoped: the agent has passwordless sudo, so
a compromised agent can act within the token's scopes --- scope is the
real blast-radius limiter.
Single account per agent by design (unlike `matrixAccounts`, which is
multi-account): the GitHub workflow is "this agent is this one bot".
On by default. Host-driven: set `services.hyperhive.github.enable = false`
to turn the integration off hive-wide (meta.rs propagates the override
into every agent).
'';
};
@ -1579,14 +1536,11 @@ in
# regardless of which profile files are sourced.
NIX_REMOTE = "daemon";
}
// lib.optionalAttrs (config.hyperhive.githubAccount != null) {
# GitHub account: metadata + token-file PATH only, never the secret.
# The `gh` wrapper + git credential helper read the PAT from the file
# at invocation time (see hyperhive.githubAccount).
HIVE_GITHUB_USER = config.hyperhive.githubAccount.username;
HIVE_GITHUB_HOST = config.hyperhive.githubAccount.host;
HIVE_GITHUB_TOKEN_FILE = config.hyperhive.githubAccount.tokenFile;
GH_HOST = config.hyperhive.githubAccount.host;
// lib.optionalAttrs config.hyperhive.github.enable {
# GitHub integration: point the gh wrapper + git credential helper at the
# agent's PAT file (written by the credentials tab / `hivectl github
# set-token`). Only the path — never the secret (see hyperhive.github.enable).
HIVE_GITHUB_TOKEN_FILE = "/agents/${userName}/state/github-token";
}
// lib.optionalAttrs (!config.hyperhive.autoCompact) {
# Zero watermark disables proactive compaction; the reactive path
@ -1705,25 +1659,25 @@ in
# (view, pr, issue, comment, assign, close, labels, branches, etc.)
(pkgs.callPackage ../packages/hive-forge-tools.nix { })
]
++ lib.optionals (config.hyperhive.githubAccount != null) [
# gh wrapper + git credential helper for hyperhive.githubAccount.
++ lib.optionals config.hyperhive.github.enable [
# gh wrapper + git credential helper for hyperhive.github.enable.
# (No bare pkgs.gh here — the wrapper *is* `gh` and hardcodes the real
# binary path, so it can't be shadowed.)
ghWrapper
gitCredHelper
];
# Wire the GitHub credential helper for `git push` over HTTPS to the
# configured host. Host-scoped (github.com or a GHE host), so it never
# touches the forge (localhost:3000) or any other remote. Gated on the
# account; the helper reads the PAT from HIVE_GITHUB_TOKEN_FILE at
# invocation (see hyperhive.githubAccount). System /etc/gitconfig merges
# under the agent's ~/.gitconfig (safe.directory), so this is additive.
environment.etc = lib.optionalAttrs (config.hyperhive.githubAccount != null) {
# Wire the GitHub credential helper for `git push` over HTTPS. Host-scoped
# to `https://github.com`, so it never touches the forge (localhost:3000)
# or any other remote. Gated on hyperhive.github.enable; the helper reads
# the PAT from HIVE_GITHUB_TOKEN_FILE at invocation and auths as
# `x-access-token` + the PAT. System /etc/gitconfig merges under the agent's
# ~/.gitconfig (safe.directory), so this is additive.
environment.etc = lib.optionalAttrs config.hyperhive.github.enable {
"gitconfig".text = ''
[credential "https://${config.hyperhive.githubAccount.host}"]
[credential "https://github.com"]
helper = hive-github
username = ${config.hyperhive.githubAccount.username}
username = x-access-token
'';
};