refactor(#1970): replace githubAccount option with hyperhive.github.enable gate (default true, github.com + x-access-token)
This commit is contained in:
parent
3e5aff39eb
commit
af9b46e80a
1 changed files with 44 additions and 90 deletions
|
|
@ -17,12 +17,12 @@ let
|
||||||
# from `userName` to keep them coupled.
|
# from `userName` to keep them coupled.
|
||||||
userName = config.hyperhive.user.name;
|
userName = config.hyperhive.user.name;
|
||||||
homeDir = "/home/${userName}";
|
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
|
# credential helper, both sourcing the PAT from HIVE_GITHUB_TOKEN_FILE at
|
||||||
# invocation time so a dashboard-pasted token takes effect with no rebuild.
|
# invocation time so a dashboard-pasted token takes effect with no rebuild.
|
||||||
# The scripts are static (they read env at runtime) — the account option
|
# The scripts are static (they read env at runtime) — the enable flag only
|
||||||
# only gates whether they're installed + the env is set, so the token
|
# gates whether they're installed + the env is set, so the token value never
|
||||||
# value never enters the nix store.
|
# enters the nix store. github.com only; git auths as `x-access-token` + PAT.
|
||||||
ghWrapper = pkgs.writeShellScriptBin "gh" ''
|
ghWrapper = pkgs.writeShellScriptBin "gh" ''
|
||||||
if [ -n "''${HIVE_GITHUB_TOKEN_FILE:-}" ] && [ -r "''${HIVE_GITHUB_TOKEN_FILE}" ]; then
|
if [ -n "''${HIVE_GITHUB_TOKEN_FILE:-}" ] && [ -r "''${HIVE_GITHUB_TOKEN_FILE}" ]; then
|
||||||
GH_TOKEN="$(cat "''${HIVE_GITHUB_TOKEN_FILE}")"
|
GH_TOKEN="$(cat "''${HIVE_GITHUB_TOKEN_FILE}")"
|
||||||
|
|
@ -34,7 +34,9 @@ let
|
||||||
# git credential-helper protocol: only the `get` action needs an answer.
|
# git credential-helper protocol: only the `get` action needs an answer.
|
||||||
[ "''${1:-}" = "get" ] || exit 0
|
[ "''${1:-}" = "get" ] || exit 0
|
||||||
if [ -n "''${HIVE_GITHUB_TOKEN_FILE:-}" ] && [ -r "''${HIVE_GITHUB_TOKEN_FILE}" ]; then
|
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}")"
|
printf 'password=%s\n' "$(cat "''${HIVE_GITHUB_TOKEN_FILE}")"
|
||||||
fi
|
fi
|
||||||
'';
|
'';
|
||||||
|
|
@ -645,73 +647,28 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
options.hyperhive.githubAccount = lib.mkOption {
|
options.hyperhive.github.enable = lib.mkOption {
|
||||||
type = lib.types.nullOr (
|
type = lib.types.bool;
|
||||||
lib.types.submodule {
|
default = true;
|
||||||
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";
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
description = ''
|
description = ''
|
||||||
Give the agent a managed GitHub account: a `gh` CLI wrapper and a
|
Install the GitHub integration in this agent: a `gh` CLI wrapper and a
|
||||||
git credential helper, both authenticated from an operator-supplied
|
git credential helper for `https://github.com`, both authenticated from
|
||||||
PAT, so the agent can run `gh` API calls and `git push` to GitHub as
|
an operator-supplied personal access token (PAT). The PAT is written to
|
||||||
the configured login without any manual `gh auth login` dance.
|
`<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,
|
github.com only. git authenticates as `x-access-token` + the PAT (GitHub
|
||||||
no credential helper, no env. When set, the token is supplied out of
|
ignores the username for PAT auth); `gh` derives its identity from the
|
||||||
band (dashboard credentials tab / `hivectl`) into `tokenFile`; nix
|
token. Keep the PAT minimally scoped: the agent has passwordless sudo, so
|
||||||
only carries the login + host, never the secret.
|
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
|
On by default. Host-driven: set `services.hyperhive.github.enable = false`
|
||||||
multi-account): the GitHub workflow is "this agent is this one bot".
|
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.
|
# regardless of which profile files are sourced.
|
||||||
NIX_REMOTE = "daemon";
|
NIX_REMOTE = "daemon";
|
||||||
}
|
}
|
||||||
// lib.optionalAttrs (config.hyperhive.githubAccount != null) {
|
// lib.optionalAttrs config.hyperhive.github.enable {
|
||||||
# GitHub account: metadata + token-file PATH only, never the secret.
|
# GitHub integration: point the gh wrapper + git credential helper at the
|
||||||
# The `gh` wrapper + git credential helper read the PAT from the file
|
# agent's PAT file (written by the credentials tab / `hivectl github
|
||||||
# at invocation time (see hyperhive.githubAccount).
|
# set-token`). Only the path — never the secret (see hyperhive.github.enable).
|
||||||
HIVE_GITHUB_USER = config.hyperhive.githubAccount.username;
|
HIVE_GITHUB_TOKEN_FILE = "/agents/${userName}/state/github-token";
|
||||||
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.autoCompact) {
|
// lib.optionalAttrs (!config.hyperhive.autoCompact) {
|
||||||
# Zero watermark disables proactive compaction; the reactive path
|
# Zero watermark disables proactive compaction; the reactive path
|
||||||
|
|
@ -1705,25 +1659,25 @@ in
|
||||||
# (view, pr, issue, comment, assign, close, labels, branches, etc.)
|
# (view, pr, issue, comment, assign, close, labels, branches, etc.)
|
||||||
(pkgs.callPackage ../packages/hive-forge-tools.nix { })
|
(pkgs.callPackage ../packages/hive-forge-tools.nix { })
|
||||||
]
|
]
|
||||||
++ lib.optionals (config.hyperhive.githubAccount != null) [
|
++ lib.optionals config.hyperhive.github.enable [
|
||||||
# gh wrapper + git credential helper for hyperhive.githubAccount.
|
# gh wrapper + git credential helper for hyperhive.github.enable.
|
||||||
# (No bare pkgs.gh here — the wrapper *is* `gh` and hardcodes the real
|
# (No bare pkgs.gh here — the wrapper *is* `gh` and hardcodes the real
|
||||||
# binary path, so it can't be shadowed.)
|
# binary path, so it can't be shadowed.)
|
||||||
ghWrapper
|
ghWrapper
|
||||||
gitCredHelper
|
gitCredHelper
|
||||||
];
|
];
|
||||||
|
|
||||||
# Wire the GitHub credential helper for `git push` over HTTPS to the
|
# Wire the GitHub credential helper for `git push` over HTTPS. Host-scoped
|
||||||
# configured host. Host-scoped (github.com or a GHE host), so it never
|
# to `https://github.com`, so it never touches the forge (localhost:3000)
|
||||||
# touches the forge (localhost:3000) or any other remote. Gated on the
|
# or any other remote. Gated on hyperhive.github.enable; the helper reads
|
||||||
# account; the helper reads the PAT from HIVE_GITHUB_TOKEN_FILE at
|
# the PAT from HIVE_GITHUB_TOKEN_FILE at invocation and auths as
|
||||||
# invocation (see hyperhive.githubAccount). System /etc/gitconfig merges
|
# `x-access-token` + the PAT. System /etc/gitconfig merges under the agent's
|
||||||
# under the agent's ~/.gitconfig (safe.directory), so this is additive.
|
# ~/.gitconfig (safe.directory), so this is additive.
|
||||||
environment.etc = lib.optionalAttrs (config.hyperhive.githubAccount != null) {
|
environment.etc = lib.optionalAttrs config.hyperhive.github.enable {
|
||||||
"gitconfig".text = ''
|
"gitconfig".text = ''
|
||||||
[credential "https://${config.hyperhive.githubAccount.host}"]
|
[credential "https://github.com"]
|
||||||
helper = hive-github
|
helper = hive-github
|
||||||
username = ${config.hyperhive.githubAccount.username}
|
username = x-access-token
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue