diff --git a/CLAUDE.md b/CLAUDE.md index aab72fdc..37322c3a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -113,8 +113,6 @@ read them à la carte. - **"How does the matrix-tuwunel container work? What about fluffychat-web and per-agent matrix accounts?"** → [`docs/matrix.md`](docs/matrix.md). -- **"How do I give an agent a GitHub account (`gh` + `git push`)? - How is the PAT injected?"** → [`docs/github.md`](docs/github.md). - **"How does DNS resolution work in agent containers? What's the bridge network for?"** → [`docs/network.md`](docs/network.md). - **"How do I connect two hives into a swarm? How do I declare peer diff --git a/README.md b/README.md index 0a9e8e7d..b83d55a4 100644 --- a/README.md +++ b/README.md @@ -118,18 +118,6 @@ The hive-internal account is always named `main` (synthesized from `hyperhive.ma For more details see [`docs/matrix.md`](docs/matrix.md). -### GitHub account - -Every agent gets a managed GitHub identity — a `gh` CLI wrapper and `git push` over HTTPS — on by default (`hyperhive.github.enable`), inert until a PAT is provisioned. There is nothing per-agent to declare: paste an operator-supplied personal access token into the agent's dashboard **credentials** tab (or `hivectl github set-token --token-stdin`) and it works. The `gh` wrapper + git credential helper read the token live (git auths as `x-access-token` + the PAT; github.com only), so a rotated PAT takes effect with no rebuild. - -Turn the integration off for the whole hive with the host option: - -```nix -services.hyperhive.github.enable = false; -``` - -The PAT value is never in nix — only the enable flag. For more details see [`docs/github.md`](docs/github.md). - ## Operator CLI `hivectl` is the operator-facing host CLI for ad-hoc administration that diff --git a/docs/github.md b/docs/github.md deleted file mode 100644 index 8665f8fb..00000000 --- a/docs/github.md +++ /dev/null @@ -1,79 +0,0 @@ -# GitHub accounts - -Give an agent a managed GitHub identity — a `gh` CLI and `git push` over -HTTPS, both authenticated by an operator-supplied personal access token -(PAT) — so it can run GitHub API calls and push commits without any manual -`gh auth login`. - -Provisioning is UI-driven, mirroring the dashboard side of the -[matrix account](matrix.md) flow: paste a PAT into the agent's credentials -tab and it works. No per-agent nix declaration, no rebuild — the token is -injected into the agent's state dir out of band. - -## Enabling - -The integration is **on by default** for every agent (`hyperhive.github.enable -= true`), inert until a PAT is provisioned. There is nothing per-agent to -declare — an agent gains GitHub simply by having a PAT written to its token -file. - -To turn it off for the whole hive, set the host option: - -```nix -services.hyperhive.github.enable = false; -``` - -hive-c0re's meta-flake renderer then injects `hyperhive.github.enable = false` -into every agent, so no agent ships the `gh` wrapper or credential helper. -(`hyperhive.github.enable` also exists per-agent for completeness, but the -hive-wide host switch is the intended control.) - -github.com only. The token **value** never touches nix — it is written to -`/github-token` separately (see [Provisioning](#provisioning)). - -## How the agent uses it - -When enabled, the container gets: - -- **A `gh` wrapper** on `PATH` (shadowing the raw `gh`) that exports - `GH_TOKEN` from the token file at invocation, then execs real `gh`. So - `gh pr create`, `gh api …`, etc. just work — `gh` derives the identity - from the token. -- **A git credential helper** (`git-credential-hive-github`), wired via a - host-scoped `/etc/gitconfig` entry for `https://github.com`, so - `git push https://github.com//` authenticates as - `x-access-token` + the PAT (GitHub ignores the username for PAT auth). - Host-scoped, so it never touches the forge (`localhost:3000`) or any - other remote. -- **Env**: `HIVE_GITHUB_TOKEN_FILE` (the token path — never the secret). - -Both the wrapper and the credential helper read the token from the file -**at invocation time**, so a PAT written (or rotated) mid-session takes -effect immediately — no container rebuild or restart. Until the file -exists, `gh` / `git push` simply fail unauthenticated. - -## Provisioning - -The PAT is operator-supplied. The primary path is the **dashboard -credentials tab** (github sub-tab): paste the PAT for an agent and submit -(`POST /api/github-account`). There is also a CLI path for -recovery/scripting: - -```sh -hivectl github set-token --token-stdin # paste the PAT on stdin (preferred) -hivectl github set-token --token # inline (visible in shell history) -``` - -Either path has hive-c0re delegate the write to hive-priv, which stores the -file `0600` owned by the agent (so the container can read it) — the same -credential-injection path as forge/matrix tokens. See -[hivectl → GitHub](tools/hivectl.md#github). - -## Security - -- Use a **dedicated bot account**, never a human's. -- Mint a **minimally-scoped PAT** — only the repos/scopes the agent's - workflow needs. Agents have passwordless sudo, so a compromised or - hallucinating agent can act as the account within the token's scopes; - scope is the real blast-radius limiter, and the container boundary is - the enforcement. See [security.md](security.md). diff --git a/docs/tools/hivectl-cli.md b/docs/tools/hivectl-cli.md index 6c5b5d34..4e685019 100644 --- a/docs/tools/hivectl-cli.md +++ b/docs/tools/hivectl-cli.md @@ -13,8 +13,6 @@ This document contains the help content for the `hivectl` command-line program. * [`hivectl matrix promote-user`↴](#hivectl-matrix-promote-user) * [`hivectl matrix reset-password`↴](#hivectl-matrix-reset-password) * [`hivectl matrix invite`↴](#hivectl-matrix-invite) -* [`hivectl github`↴](#hivectl-github) -* [`hivectl github set-token`↴](#hivectl-github-set-token) * [`hivectl gateway`↴](#hivectl-gateway) * [`hivectl gateway create-user`↴](#hivectl-gateway-create-user) * [`hivectl gateway delete-user`↴](#hivectl-gateway-delete-user) @@ -51,7 +49,6 @@ Sibling to the `hive-c0re` daemon binary. Covers host-side admin operations that * `forge` — Forgejo user provisioning. Manual entry point to the same idempotent flow c0re runs automatically at boot (`forge::ensure_all`) — useful for recovery, ad-hoc reprovisioning, or single-agent fixes without bouncing the daemon * `matrix` — matrix-tuwunel user provisioning. Manual entry point to the same idempotent flow c0re runs automatically at boot (`matrix::ensure_all`) — useful when the boot-time sweep skipped an agent (e.g. matrix container wasn't up yet) or to re-register after wiping a token file -* `github` — GitHub account provisioning: write an operator-supplied personal access token (PAT) into an agent's `github-token` state file so its `gh` wrapper + git credential helper can authenticate. Unlike forge/matrix there is no account creation — the operator supplies a PAT for an existing GitHub account. A CLI alternative to the dashboard credentials tab; the integration is on by default (`hyperhive.github.enable`), so no per-agent config is needed * `gateway` — Gateway htpasswd user management. Add, remove, or list users in an htpasswd file used by the gateway's HTTP Basic auth (`services.hyperhive.gateway.auth`). Credentials are stored as `BCrypt` hashes — no extra service or PAM required * `agents` — Agent container management. Requires the hive-c0re daemon to be running (connects to the host admin socket) * `wg` — WireGuard inter-hive mesh setup helpers (`services.hyperhive.swarm`) @@ -197,35 +194,6 @@ Invite a matrix user to the hive Space (default) or a specific room. Uses the hi -## `hivectl github` - -GitHub account provisioning: write an operator-supplied personal access token (PAT) into an agent's `github-token` state file so its `gh` wrapper + git credential helper can authenticate. Unlike forge/matrix there is no account creation — the operator supplies a PAT for an existing GitHub account. A CLI alternative to the dashboard credentials tab; the integration is on by default (`hyperhive.github.enable`), so no per-agent config is needed - -**Usage:** `hivectl github ` - -###### **Subcommands:** - -* `set-token` — Write a GitHub PAT into ``'s state dir (`github-token`, 0600, agent-owned) via hive-priv. The GitHub integration is on by default (`hyperhive.github.enable`), so the `gh` wrapper + git credential helper pick the token up with no per-agent config. The token is read live at invocation, so no rebuild/restart is needed. Prefer `--token-stdin`: an inline `--token` is visible in shell history + process listings - - - -## `hivectl github set-token` - -Write a GitHub PAT into ``'s state dir (`github-token`, 0600, agent-owned) via hive-priv. The GitHub integration is on by default (`hyperhive.github.enable`), so the `gh` wrapper + git credential helper pick the token up with no per-agent config. The token is read live at invocation, so no rebuild/restart is needed. Prefer `--token-stdin`: an inline `--token` is visible in shell history + process listings - -**Usage:** `hivectl github set-token [OPTIONS] ` - -###### **Arguments:** - -* `` — Logical agent name (the container/agent name) - -###### **Options:** - -* `--token ` — The PAT value inline. Mutually exclusive with `--token-stdin` -* `--token-stdin` — Read the PAT from stdin (trailing newline stripped). Mutually exclusive with `--token` - - - ## `hivectl gateway` Gateway htpasswd user management. Add, remove, or list users in an htpasswd file used by the gateway's HTTP Basic auth (`services.hyperhive.gateway.auth`). Credentials are stored as `BCrypt` hashes — no extra service or PAM required diff --git a/docs/tools/hivectl.md b/docs/tools/hivectl.md index 04c208cc..6af3b932 100644 --- a/docs/tools/hivectl.md +++ b/docs/tools/hivectl.md @@ -75,26 +75,6 @@ hivectl matrix invite @mara:server --room '#hive-chat:server' # ...or to a spec invite power (it owns the hive Space, so that case always works). Idempotent — already-member / already-invited is a no-op. -## GitHub - -Write an operator-supplied GitHub personal access token (PAT) into an -agent's token file so its `gh` wrapper + git credential helper can act as -the bot account. Unlike forge/matrix there is no account creation — the PAT -is for an existing GitHub account. A CLI alternative to the dashboard -credentials tab; the [GitHub integration](../github.md) is on by default -(`hyperhive.github.enable`), so no per-agent config is needed. - -```bash -hivectl github set-token damocles --token-stdin # paste the PAT on stdin (preferred) -hivectl github set-token damocles --token # inline (visible in shell history) -``` - -- `set-token`: writes `/github-token` (`0600`, agent-owned) via - hive-priv — the same credential-injection path as forge/matrix tokens. - The `gh` wrapper / git credential helper read it live, so a freshly-set - or rotated PAT takes effect with no rebuild or restart. Refuses an empty - token. See [github.md](../github.md) for the full flow + security notes. - ## Gateway Manage users in the gateway's HTTP Basic auth htpasswd file diff --git a/hive-c0re/src/bin/hivectl.rs b/hive-c0re/src/bin/hivectl.rs index fa0a0540..eef8d729 100644 --- a/hive-c0re/src/bin/hivectl.rs +++ b/hive-c0re/src/bin/hivectl.rs @@ -70,17 +70,6 @@ enum Cmd { #[command(subcommand)] cmd: MatrixCmd, }, - /// GitHub account provisioning: write an operator-supplied personal - /// access token (PAT) into an agent's `github-token` state file so its - /// `gh` wrapper + git credential helper can authenticate. Unlike - /// forge/matrix there is no account creation — the operator supplies a - /// PAT for an existing GitHub account. A CLI alternative to the dashboard - /// credentials tab; the integration is on by default - /// (`hyperhive.github.enable`), so no per-agent config is needed. - Github { - #[command(subcommand)] - cmd: GithubCmd, - }, /// Gateway htpasswd user management. Add, remove, or list users in /// an htpasswd file used by the gateway's HTTP Basic auth /// (`services.hyperhive.gateway.auth`). Credentials are stored as @@ -432,28 +421,6 @@ enum MatrixCmd { }, } -#[derive(Subcommand)] -enum GithubCmd { - /// Write a GitHub PAT into ``'s state dir (`github-token`, 0600, - /// agent-owned) via hive-priv. The GitHub integration is on by default - /// (`hyperhive.github.enable`), so the `gh` wrapper + git credential - /// helper pick the token up with no per-agent config. The token is read - /// live at invocation, so no rebuild/restart is needed. Prefer - /// `--token-stdin`: an inline `--token` is visible in shell history + - /// process listings. - SetToken { - /// Logical agent name (the container/agent name). - agent: String, - /// The PAT value inline. Mutually exclusive with `--token-stdin`. - #[arg(long)] - token: Option, - /// Read the PAT from stdin (trailing newline stripped). Mutually - /// exclusive with `--token`. - #[arg(long, conflicts_with = "token")] - token_stdin: bool, - }, -} - // Default htpasswd file path — the host-side location of the gateway's // credential store, pre-created by a tmpfiles rule when // `services.hyperhive.gateway.auth.enable = true`. Literal lives in @@ -651,13 +618,6 @@ async fn main() -> Result<()> { MatrixCmd::ResetPassword { name } => matrix_reset_password(&name).await, MatrixCmd::Invite { user, room } => matrix_invite(&user, room.as_deref()).await, }, - Cmd::Github { cmd } => match cmd { - GithubCmd::SetToken { - agent, - token, - token_stdin, - } => github_set_token(&agent, token, token_stdin).await, - }, Cmd::Gateway { cmd } => match cmd { GatewayCmd::CreateUser { file, @@ -1164,34 +1124,6 @@ fn choom(name: &str, resume_session: Option<&str>) -> Result<()> { Err(anyhow::anyhow!("exec machinectl: {err}")) } -/// `hivectl github set-token `: write an operator-supplied GitHub PAT -/// into the agent's `github-token` state file (0600, agent-owned) via -/// hive-priv, so the agent's `gh` wrapper + git credential helper can -/// authenticate. Read live at invocation, so no rebuild/restart is needed. -async fn github_set_token(agent: &str, token: Option, token_stdin: bool) -> Result<()> { - let token = match (token, token_stdin) { - (Some(t), _) => t, - (None, true) => { - let mut s = String::new(); - std::io::Read::read_to_string(&mut std::io::stdin(), &mut s)?; - s.trim_end_matches(['\n', '\r']).to_owned() - } - (None, false) => bail!( - "provide the PAT via --token or --token-stdin (stdin preferred — \ - an inline token is visible in shell history + process listings)" - ), - }; - if token.is_empty() { - bail!("refusing to write an empty GitHub token for agent '{agent}'"); - } - hive_c0re::priv_client::write_agent_github_token(agent, &token).await?; - println!( - "wrote github-token for agent '{agent}' \ - (read live by the gh wrapper / git credential helper — no rebuild needed)" - ); - Ok(()) -} - async fn forge_create_user(name: &str, password: Option<&str>, password_stdin: bool) -> Result<()> { if !hive_c0re::forge::is_present().await { bail!( diff --git a/hive-c0re/src/dashboard/matrix_accounts.rs b/hive-c0re/src/dashboard/matrix_accounts.rs index 4f5f5c52..6d0e5cfa 100644 --- a/hive-c0re/src/dashboard/matrix_accounts.rs +++ b/hive-c0re/src/dashboard/matrix_accounts.rs @@ -262,73 +262,6 @@ pub(super) async fn post_matrix_account_login(Form(f): Form) -> axum::Json(MatrixLoginResult { ok: true, user_id }).into_response() } -/// Form body for `POST /api/github-account` (urlencoded, the dashboard's -/// mutation convention). Writes the operator-supplied PAT to the agent's -/// `github-token` file. The GitHub counterpart of the matrix login form, but -/// far simpler: no account creation, no homeserver, no login modes — the -/// operator pastes a PAT for an existing account. -#[derive(Deserialize)] -pub(super) struct GithubAccountForm { - agent: String, - token: String, -} - -#[derive(Serialize)] -struct GithubAccountResult { - ok: bool, -} - -/// Provision (or refresh) an agent's GitHub PAT from the dashboard -/// credentials tab. Validates the agent name, then writes the PAT to -/// `/github-token` (`0600`, agent-owned) via hive-priv. No account -/// creation and no daemon to kick — the agent's `gh` wrapper / git credential -/// helper read the file live, so the new token takes effect immediately. -/// Operator-authenticated (dashboard). Never echoes the token back — only -/// `{ ok: true }`. -pub(super) async fn post_github_account(Form(f): Form) -> Response { - let agent = f.agent.trim(); - let token = f.token.trim(); - if !is_plain_ident(agent) { - return error_response(&format!("github-account: invalid agent {agent:?}")); - } - if token.is_empty() { - return error_response("github-account: token is required"); - } - if let Err(e) = crate::priv_client::write_agent_github_token(agent, token).await { - return error_response(&format!("github-account: write token failed: {e:#}")); - } - tracing::info!(%agent, "github-account: provisioned github PAT"); - axum::Json(GithubAccountResult { ok: true }).into_response() -} - -#[derive(Deserialize)] -pub(super) struct GithubAccountQuery { - agent: String, -} - -#[derive(Serialize)] -struct GithubAccountStatus { - /// A `github-token` file exists in the agent's state dir (a PAT has been - /// provisioned). A static PAT has no live/heartbeat concept, so this is - /// the only status the credentials tab needs. - present: bool, -} - -/// `GET /api/github-account?agent=` — whether the agent has a GitHub -/// PAT provisioned (its `github-token` file exists). Lets the credentials tab -/// show "token stored" vs "not set" instead of a black-hole paste field. -/// Never returns the token itself. -pub(super) async fn get_github_account(Query(q): Query) -> Response { - let agent = q.agent.trim(); - if !is_plain_ident(agent) { - return error_response(&format!("github-account: invalid agent {agent:?}")); - } - let present = Coordinator::agent_notes_dir(agent) - .join("github-token") - .exists(); - axum::Json(GithubAccountStatus { present }).into_response() -} - /// POST `m.login.password` to `/_matrix/client/v3/login`. /// Returns `(access_token, user_id)`. async fn matrix_password_login( diff --git a/hive-c0re/src/dashboard/mod.rs b/hive-c0re/src/dashboard/mod.rs index 60facd1f..18abd4e5 100644 --- a/hive-c0re/src/dashboard/mod.rs +++ b/hive-c0re/src/dashboard/mod.rs @@ -188,10 +188,6 @@ pub async fn serve(port: u16, coord: Arc) -> Result<()> { "/api/matrix-account-login", post(matrix_accounts::post_matrix_account_login), ) - .route( - "/api/github-account", - post(matrix_accounts::post_github_account).get(matrix_accounts::get_github_account), - ) .route( "/api/cancel-reminder/{id}", post(reminders::post_cancel_reminder), diff --git a/hive-c0re/src/meta.rs b/hive-c0re/src/meta.rs index 6ae393f2..a7d833f3 100644 --- a/hive-c0re/src/meta.rs +++ b/hive-c0re/src/meta.rs @@ -1041,14 +1041,6 @@ where out.push_str(" hyperhive.otel.debug = true;\n"); } } - // GitHub integration is on by default in every agent - // (`hyperhive.github.enable`); the host turns it off hive-wide via - // `services.hyperhive.github.enable = false`, surfaced here as the - // `HYPERHIVE_GITHUB_DISABLED` env on hive-c0re's unit. Only the OFF - // override is propagated — the enabled default needs no per-agent line. - if std::env::var_os("HYPERHIVE_GITHUB_DISABLED").is_some() { - out.push_str(" hyperhive.github.enable = false;\n"); - } out.push_str( r#" # The harness service inside the container runs as a # non-root unix user named after the agent (`damocles`, @@ -1753,44 +1745,4 @@ mod tests { "no otel lines when disabled:\n{off}" ); } - - #[test] - fn render_flake_injects_github_disable_only_when_signalled() { - // services.hyperhive.github.enable = false -> HYPERHIVE_GITHUB_DISABLED - // on hive-c0re's unit -> `hyperhive.github.enable = false` injected into - // every agent. On by default, so nothing is emitted unless disabled. - // - // SAFETY: single-threaded mutation of an env var no other test asserts - // on; restored before returning. - let render = || { - render_flake( - "github:example/hyperhive", - "path:/nix/store/bbbb-hyperhive-docs-source", - "path:/nix/store/aaaa-nixpkgs-source", - 8000, - "she/her", - &std::collections::HashMap::new(), - &[sample_spec("alice", false, 9001)], - ) - }; - unsafe { - std::env::remove_var("HYPERHIVE_GITHUB_DISABLED"); - } - let on_default = render(); - unsafe { - std::env::set_var("HYPERHIVE_GITHUB_DISABLED", "1"); - } - let disabled = render(); - unsafe { - std::env::remove_var("HYPERHIVE_GITHUB_DISABLED"); - } - assert!( - !on_default.contains("hyperhive.github.enable"), - "github.enable must not be emitted by default (agents keep the true default):\n{on_default}" - ); - assert!( - disabled.contains("hyperhive.github.enable = false;"), - "github.enable = false must be injected when the host disables it:\n{disabled}" - ); - } } diff --git a/hive-c0re/src/priv_client.rs b/hive-c0re/src/priv_client.rs index 9b69ee68..c8337f21 100644 --- a/hive-c0re/src/priv_client.rs +++ b/hive-c0re/src/priv_client.rs @@ -281,25 +281,6 @@ pub async fn write_agent_matrix_token( .await?) } -/// Write a GitHub personal access token (PAT) for `agent_name` via hive-priv -/// (running as root). Writes `/github-token` 0600, chowned to the agent -/// user so the `gh` wrapper / git credential helper can read it from inside the -/// container. Single account per agent — no account suffix. The token value is -/// operator-supplied (for the agent's GitHub integration, `hyperhive.github.enable`). -/// -/// # Errors -/// -/// Returns an error if the hive-priv call fails — the socket is unreachable, -/// `agent_name` is rejected by the root-side validation, or the file -/// write/chown fails. -pub async fn write_agent_github_token(agent_name: &str, token: &str) -> Result<()> { - ok(call(&PrivRequest::WriteAgentGithubToken { - agent_name: agent_name.to_owned(), - token: token.to_owned(), - }) - .await?) -} - /// Restart `hive-matrix-daemon.service` inside an agent container via /// `systemctl --machine=h- restart hive-matrix-daemon.service`. /// Non-fatal: callers should handle errors gracefully — if the container is diff --git a/hive-priv/src/main.rs b/hive-priv/src/main.rs index 2977f8e9..93293183 100644 --- a/hive-priv/src/main.rs +++ b/hive-priv/src/main.rs @@ -289,14 +289,6 @@ async fn exec(req: PrivRequest, writer: &mut OwnedWriteHalf) -> Result<(String, Ok(res) } - PrivRequest::WriteAgentGithubToken { - ref agent_name, - ref token, - } => { - validate_agent_name(agent_name)?; - write_agent_state_file(agent_name, "github-token", &format!("{token}\n")) - } - PrivRequest::RestartMatrixDaemon { ref agent_name } => { restart_matrix_daemon(agent_name).await } diff --git a/hive-sh4re/src/priv_proto.rs b/hive-sh4re/src/priv_proto.rs index bb4361e4..89fef768 100644 --- a/hive-sh4re/src/priv_proto.rs +++ b/hive-sh4re/src/priv_proto.rs @@ -411,22 +411,6 @@ pub enum PrivRequest { homeserver: Option, }, - /// Write `github-token` into `AGENT_STATE_ROOT//state/github-token`. - /// - /// The operator-supplied GitHub personal access token (PAT) for the - /// agent's GitHub integration (`hyperhive.github.enable`). Same write - /// semantics as - /// `WriteAgentForgeToken` — validates `agent_name`, creates the state dir - /// if absent, writes the file 0600, and chowns it to the agent so the - /// `gh` wrapper / git credential helper can read it. No account suffix - /// (single GitHub account per agent). - WriteAgentGithubToken { - /// Logical agent name (validated by `validate_agent_name`). - agent_name: String, - /// PAT value. hive-priv appends a trailing newline before writing. - token: String, - }, - /// Restart `hive-matrix-daemon.service` inside an agent container via /// `systemctl --machine=h- restart hive-matrix-daemon.service`. /// Used by hive-c0re to kick the daemon after a successful token write diff --git a/nix/modules/hive-c0re.nix b/nix/modules/hive-c0re.nix index 0bdaaa5c..184c63a8 100644 --- a/nix/modules/hive-c0re.nix +++ b/nix/modules/hive-c0re.nix @@ -215,22 +215,6 @@ in ''; }; - options.services.hyperhive.github.enable = lib.mkOption { - type = lib.types.bool; - default = true; - example = false; - description = '' - Hive-wide switch for the per-agent GitHub integration (the `gh` CLI - wrapper + git credential helper, per `hyperhive.github.enable`). On by - default: every agent gets the integration, inert until a PAT is - provisioned via the dashboard credentials tab or `hivectl github - set-token`. Set `false` to turn it off for the whole hive --- the - meta-flake renderer (`hive-c0re/src/meta.rs`) then injects - `hyperhive.github.enable = false` into every agent. Exposed to hive-c0re - as `HYPERHIVE_GITHUB_DISABLED` (set only when the integration is off). - ''; - }; - # Hive-wide OTEL stats export. Set ONCE here at host level; the # meta-flake renderer (`hive-c0re/src/meta.rs::otel_config`) reads the # HYPERHIVE_OTEL_* env exported below off hive-c0re's unit and injects @@ -972,12 +956,6 @@ in // lib.optionalAttrs (config.services.hyperhive.swarmName != null) { HYPERHIVE_SWARM_NAME = config.services.hyperhive.swarmName; } - // lib.optionalAttrs (!config.services.hyperhive.github.enable) { - # GitHub integration is on by default; only signal the OFF override to - # meta.rs, which then injects `hyperhive.github.enable = false` into - # every agent. See services.hyperhive.github.enable. - HYPERHIVE_GITHUB_DISABLED = "1"; - } // lib.optionalAttrs config.services.hyperhive.otel.enable ( # Hive-wide OTEL config -> read by meta.rs::otel_config and # injected as build-time `hyperhive.otel.*` into every agent. diff --git a/nix/templates/harness-base.nix b/nix/templates/harness-base.nix index 13eefd3b..39199562 100644 --- a/nix/templates/harness-base.nix +++ b/nix/templates/harness-base.nix @@ -17,29 +17,6 @@ let # from `userName` to keep them coupled. userName = config.hyperhive.user.name; homeDir = "/home/${userName}"; - # 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 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}")" - 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 - # 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 - ''; # Hive-wide OpenTelemetry config (host-driven; baked in per-agent by # meta.rs `otel_config`). otelCfg = config.hyperhive.otel; @@ -647,31 +624,6 @@ in ''; }; - options.hyperhive.github.enable = lib.mkOption { - type = lib.types.bool; - default = true; - description = '' - 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. - - 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. - - 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). - ''; - }; - options.hyperhive.frontend.dist = lib.mkOption { type = lib.types.package; default = pkgs.hyperhive-frontend; @@ -1536,12 +1488,6 @@ in # regardless of which profile files are sourced. NIX_REMOTE = "daemon"; } - // 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 # (compact-on-overflow) still fires when the session is truly full. @@ -1636,53 +1582,27 @@ 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 : 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.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. 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. - # Nested-path binding + mkIf (matching the other `environment.etc."…"` - # entries above) — a whole-set `environment.etc = {…}` here would collide - # with them at the nix level ("attribute already defined"). - environment.etc."gitconfig" = lib.mkIf config.hyperhive.github.enable { - text = '' - [credential "https://github.com"] - helper = hive-github - username = x-access-token - ''; - }; + 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 : CLI wrapping common Forgejo REST API operations + # (view, pr, issue, comment, assign, close, labels, branches, etc.) + (pkgs.callPackage ../packages/hive-forge-tools.nix { }) + ]; # One-shot: tea config.yml from the seeded forge token. Shape # contract (always exit 0, no set -e, skip-silently, re-runnable):