diff --git a/nix/templates/harness-base.nix b/nix/templates/harness-base.nix index 4825cb5e..19489046 100644 --- a/nix/templates/harness-base.nix +++ b/nix/templates/harness-base.nix @@ -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..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 + `/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 ''; };