diff --git a/hive-c0re/src/bin/hivectl.rs b/hive-c0re/src/bin/hivectl.rs index 8cd2ec63..eb70eaa5 100644 --- a/hive-c0re/src/bin/hivectl.rs +++ b/hive-c0re/src/bin/hivectl.rs @@ -696,7 +696,7 @@ async fn main() -> Result<()> { agent, token, token_stdin, - } => github_set_token(&agent, token, token_stdin).await, + } => github_set_token(&socket, &agent, token, token_stdin).await, }, Cmd::Gateway { cmd } => match cmd { GatewayCmd::CreateUser { @@ -1234,7 +1234,15 @@ fn choom(name: &str, resume_session: Option<&str>) -> Result<()> { /// 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<()> { +async fn github_set_token( + socket: &Path, + agent: &str, + token: Option, + token_stdin: bool, +) -> Result<()> { + // Resolve + validate the token client-side (inline flag or stdin read); + // the daemon never touches this process's stdin. Persistence happens + // daemon-side via the privileged helper. let token = match (token, token_stdin) { (Some(t), _) => t, (None, true) => { @@ -1250,12 +1258,15 @@ async fn github_set_token(agent: &str, token: Option, token_stdin: bool) 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(()) + daemon_request( + socket, + hive_host_sock::HostRequest::SetAgentGithubToken { + agent: agent.to_owned(), + token, + }, + "github", + ) + .await } async fn forge_create_user( @@ -1269,26 +1280,31 @@ async fn forge_create_user( // agent-vs-operator branch, and token persistence now live in the // daemon handler. let password = resolve_password(password, password_stdin)?; - forge_request( + daemon_request( socket, hive_host_sock::HostRequest::ForgeCreateUser { name: name.to_owned(), password, }, + "forge", ) .await } -/// Send a forge provisioning request to the daemon and print its result -/// lines. Mirrors [`matrix_request`] — the daemon owns the provisioning -/// logic; hivectl just relays the outcome. -async fn forge_request(socket: &Path, req: hive_host_sock::HostRequest) -> Result<()> { +/// Send a provisioning request to the daemon and print its result lines. +/// The daemon owns the provisioning logic; hivectl just relays the outcome, +/// prefixing any error with `label` (e.g. `forge` / `github`). +async fn daemon_request( + socket: &Path, + req: hive_host_sock::HostRequest, + label: &str, +) -> Result<()> { let resp = hive_c0re::client::request(socket, req) .await .with_context(|| format!("connect to daemon socket {}", socket.display()))?; if !resp.ok { bail!( - "forge: {}", + "{label}: {}", resp.error.as_deref().unwrap_or("unknown error") ); } diff --git a/hive-c0re/src/server.rs b/hive-c0re/src/server.rs index baafde31..8a184039 100644 --- a/hive-c0re/src/server.rs +++ b/hive-c0re/src/server.rs @@ -196,6 +196,9 @@ async fn dispatch(req: &HostRequest, coord: Arc) -> HostResponse { HostRequest::ForgeCreateUser { name, password } => { handle_forge_create_user(name, password.as_deref()).await? } + HostRequest::SetAgentGithubToken { agent, token } => { + handle_set_agent_github_token(agent, token).await? + } }) } .await; @@ -395,6 +398,16 @@ async fn handle_forge_create_user(name: &str, password: Option<&str>) -> Result< Ok(HostResponse::messages(out)) } +async fn handle_set_agent_github_token(agent: &str, token: &str) -> Result { + crate::priv_client::write_agent_github_token(agent, token) + .await + .with_context(|| format!("write github-token for agent {agent}"))?; + Ok(HostResponse::messages(vec![format!( + "wrote github-token for agent '{agent}' \ + (read live by the gh wrapper / git credential helper — no rebuild needed)" + )])) +} + async fn handle_matrix_sync_admin() -> Result { require_matrix_present().await?; let register_token = diff --git a/hive-host-sock/src/lib.rs b/hive-host-sock/src/lib.rs index a375505f..1f55db5e 100644 --- a/hive-host-sock/src/lib.rs +++ b/hive-host-sock/src/lib.rs @@ -158,6 +158,12 @@ pub enum HostRequest { #[serde(default)] password: Option, }, + /// Write (or overwrite) an agent's GitHub PAT under its state dir, via + /// the privileged helper. Daemon-side equivalent of `hivectl github + /// set-token`. `token` is resolved + non-empty-validated client-side + /// (inline flag or stdin); the daemon just persists it. Read live by + /// the agent's `gh` wrapper / git credential helper — no rebuild needed. + SetAgentGithubToken { agent: String, token: String }, } /// Selects which container classes a hive-wide [`HostRequest::Stop`] /