wip(#1970): gh wrapper + git credential helper + gated env/gitconfig for githubAccount

This commit is contained in:
damocles 2026-07-11 10:37:18 +02:00 committed by mara
commit 5fb4b9f4b0

View file

@ -17,6 +17,27 @@ let
# from `userName` to keep them coupled.
userName = config.hyperhive.user.name;
homeDir = "/home/${userName}";
# GitHub account (hyperhive.githubAccount): 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.
ghWrapper = pkgs.writeShellScriptBin "gh" ''
if [ -n "''${HIVE_GITHUB_TOKEN_FILE:-}" ] && [ -r "''${HIVE_GITHUB_TOKEN_FILE}" ]; then
GH_TOKEN="$(cat "''${HIVE_GITHUB_TOKEN_FILE}")"
export GH_TOKEN
fi
exec ${pkgs.gh}/bin/gh "$@"
'';
gitCredHelper = pkgs.writeShellScriptBin "git-credential-hive-github" ''
# 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}"
printf 'password=%s\n' "$(cat "''${HIVE_GITHUB_TOKEN_FILE}")"
fi
'';
# Hive-wide OpenTelemetry config (host-driven; baked in per-agent by
# meta.rs `otel_config`).
otelCfg = config.hyperhive.otel;
@ -1558,6 +1579,15 @@ 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.autoCompact) {
# Zero watermark disables proactive compaction; the reactive path
# (compact-on-overflow) still fires when the session is truly full.
@ -1652,27 +1682,50 @@ in
# we have to allow it inside the container's config as well.
nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (pkgs.lib.getName pkg) [ "claude-code" ];
environment.systemPackages = with pkgs; [
hyperhive
claude-code
bashInteractive
coreutils-full
# procps for pkill — used by the web UI's /api/cancel to SIGINT the
# in-flight claude turn.
procps
# tea: gitea/forgejo CLI client. Configured at boot by the
# tea-login oneshot below if /state/forge-token is present, so
# claude can `tea repos create`, `tea pulls create`, etc.
tea
# jq: JSON processing in shell — useful for parsing API responses,
# forge REST calls, sqlite output, etc.
jq
# curl: HTTP client for forge REST API and other web requests.
curl
# hive-forge <verb>: CLI wrapping common Forgejo REST API operations
# (view, pr, issue, comment, assign, close, labels, branches, etc.)
(pkgs.callPackage ../packages/hive-forge-tools.nix { })
];
environment.systemPackages =
with pkgs;
[
hyperhive
claude-code
bashInteractive
coreutils-full
# procps for pkill — used by the web UI's /api/cancel to SIGINT the
# in-flight claude turn.
procps
# tea: gitea/forgejo CLI client. Configured at boot by the
# tea-login oneshot below if /state/forge-token is present, so
# claude can `tea repos create`, `tea pulls create`, etc.
tea
# jq: JSON processing in shell — useful for parsing API responses,
# forge REST calls, sqlite output, etc.
jq
# curl: HTTP client for forge REST API and other web requests.
curl
# hive-forge <verb>: CLI wrapping common Forgejo REST API operations
# (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.
# (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) {
"gitconfig".text = ''
[credential "https://${config.hyperhive.githubAccount.host}"]
helper = hive-github
username = ${config.hyperhive.githubAccount.username}
'';
};
# One-shot: tea config.yml from the seeded forge token. Shape
# contract (always exit 0, no set -e, skip-silently, re-runnable):