From 26f2c1f59b0433930388f214367a91c1a261b97a Mon Sep 17 00:00:00 2001 From: damocles Date: Wed, 3 Jun 2026 10:45:48 +0200 Subject: [PATCH] feat(#1107): hivectl agents restart / restart-all commands --- hive-c0re/src/bin/hivectl.rs | 87 +++++++++++++++++++++++++++++++++--- hive-c0re/src/server.rs | 26 +++++++++++ hive-sh4re/src/lib.rs | 8 ++++ 3 files changed, 116 insertions(+), 5 deletions(-) diff --git a/hive-c0re/src/bin/hivectl.rs b/hive-c0re/src/bin/hivectl.rs index e2fd156d..fee6c191 100644 --- a/hive-c0re/src/bin/hivectl.rs +++ b/hive-c0re/src/bin/hivectl.rs @@ -3,11 +3,10 @@ //! Sibling binary to the `hive-c0re` daemon. Where `hive-c0re`'s //! subcommands focus on the broker / approval / topology surface //! (`spawn`, `kill`, `rebuild`, `approve` …), `hivectl` covers -//! host-side administration that doesn't need the daemon running — -//! starting with manual user provisioning on the bundled forge + -//! matrix containers when c0re's automatic boot-time sweep is -//! inappropriate (recovery, debugging, single-shot reprovisioning, -//! verifying the registration token path). +//! host-side administration — both manual provisioning operations +//! that work without the daemon running (forge, matrix, gateway) AND +//! daemon-assisted agent management (agents restart/restart-all) that +//! goes through the host admin socket. //! //! Verbs read configuration off the same on-disk paths c0re uses //! (`/var/lib/hyperhive/forge-core-token`, @@ -64,6 +63,12 @@ enum Cmd { #[command(subcommand)] cmd: GatewayCmd, }, + /// Agent container management. Requires the hive-c0re daemon to be + /// running (connects to the host admin socket). + Agents { + #[command(subcommand)] + cmd: AgentsCmd, + }, } #[derive(Subcommand)] @@ -196,6 +201,32 @@ enum GatewayCmd { }, } +/// Default host admin socket path (same as `hive-c0re`'s default). +const DEFAULT_HOST_SOCKET: &str = "/run/hive/host.sock"; + +#[derive(Subcommand)] +enum AgentsCmd { + /// Stop and start a single agent container without rebuilding config. + /// Useful for "kick the container" when the process is stuck or the + /// container needs a clean restart without changing the NixOS config. + Restart { + /// Agent name (e.g. `damocles`, `ruth`). + name: String, + /// Path to the hive-c0re host admin socket. + #[arg(long, default_value = DEFAULT_HOST_SOCKET)] + socket: PathBuf, + }, + /// Stop and restart ALL managed agent containers in sequence. + /// Iterates the live container list and restarts each one. Any per-agent + /// failure is reported at the end rather than stopping mid-run, so all + /// containers get a restart attempt. + RestartAll { + /// Path to the hive-c0re host admin socket. + #[arg(long, default_value = DEFAULT_HOST_SOCKET)] + socket: PathBuf, + }, +} + #[tokio::main] async fn main() -> Result<()> { tracing_subscriber::fmt() @@ -230,6 +261,10 @@ async fn main() -> Result<()> { GatewayCmd::DeleteUser { file, username } => gateway_delete_user(&file, &username), GatewayCmd::ListUsers { file } => gateway_list_users(&file), }, + Cmd::Agents { cmd } => match cmd { + AgentsCmd::Restart { name, socket } => agents_restart(&socket, &name).await, + AgentsCmd::RestartAll { socket } => agents_restart_all(&socket).await, + }, } } @@ -461,6 +496,48 @@ fn gateway_list_users(file: &Path) -> Result<()> { Ok(()) } +// --------------------------------------------------------------------------- +// Agent management helpers (require daemon via host admin socket) +// --------------------------------------------------------------------------- + +async fn agents_restart(socket: &Path, name: &str) -> Result<()> { + let resp = hive_c0re::client::request(socket, hive_sh4re::HostRequest::Restart { + name: name.to_owned(), + }) + .await + .with_context(|| format!("connect to daemon socket {}", socket.display()))?; + if resp.ok { + println!("restarted: {name}"); + Ok(()) + } else { + bail!( + "restart {name}: {}", + resp.error.as_deref().unwrap_or("unknown error") + ) + } +} + +async fn agents_restart_all(socket: &Path) -> Result<()> { + let resp = hive_c0re::client::request(socket, hive_sh4re::HostRequest::RestartAll) + .await + .with_context(|| format!("connect to daemon socket {}", socket.display()))?; + let agents = resp.agents.as_deref().unwrap_or(&[]); + if agents.is_empty() { + println!("restart-all: no managed containers found"); + } else { + for a in agents { + println!("restarted: {a}"); + } + } + if !resp.ok { + bail!( + "restart-all: {}", + resp.error.as_deref().unwrap_or("unknown error") + ); + } + Ok(()) +} + /// Reject usernames containing `:` (field separator) or control chars /// that would corrupt the htpasswd file format. fn validate_htpasswd_username(username: &str) -> Result<()> { diff --git a/hive-c0re/src/server.rs b/hive-c0re/src/server.rs index 0987ef48..2bd75c5a 100644 --- a/hive-c0re/src/server.rs +++ b/hive-c0re/src/server.rs @@ -140,6 +140,32 @@ async fn dispatch(req: &HostRequest, coord: Arc) -> HostResponse { }); HostResponse::success() } + HostRequest::Restart { name } => { + tracing::info!(%name, "restart"); + lifecycle::restart(name).await?; + HostResponse::success() + } + HostRequest::RestartAll => { + tracing::info!("restart-all"); + let agents = lifecycle::list().await?; + let mut errors: Vec = Vec::new(); + for agent in &agents { + if let Err(e) = lifecycle::restart(agent).await { + tracing::warn!(%agent, error = ?e, "restart-all: failed to restart agent"); + errors.push(format!("{agent}: {e:#}")); + } + } + if errors.is_empty() { + HostResponse::list(agents) + } else { + HostResponse { + ok: false, + error: Some(errors.join("; ")), + agents: Some(agents), + approvals: None, + } + } + } HostRequest::Destroy { name, purge } => { actions::destroy(&coord, name, *purge).await?; HostResponse::success() diff --git a/hive-sh4re/src/lib.rs b/hive-sh4re/src/lib.rs index 66dd820d..6f861623 100644 --- a/hive-sh4re/src/lib.rs +++ b/hive-sh4re/src/lib.rs @@ -31,6 +31,14 @@ pub enum HostRequest { #[serde(default)] purge: bool, }, + /// Stop and start a managed container without rebuilding config. + /// For "kick the container" operations that don't touch the flake or + /// nspawn flags. Mirrors `lifecycle::restart` (kill + start). + Restart { name: String }, + /// Stop and restart all managed containers in sequence. Convenience + /// wrapper for `hivectl agents restart-all`; iterates the live + /// container list and restarts each one. + RestartAll, /// Apply pending config to a managed container. Rebuild { name: String }, /// List managed containers.