From dce2bd0686ec14b3b2cf6c28d4532d04d3b83692 Mon Sep 17 00:00:00 2001 From: damocles Date: Tue, 2 Jun 2026 12:59:49 +0200 Subject: [PATCH] feat(#343): route container restart through rebuild queue --- docs/coordinator.md | 4 ++-- hive-c0re/src/dashboard.rs | 18 +++++++++--------- hive-c0re/src/manager_server.rs | 20 ++++++++++---------- hive-c0re/src/rebuild_queue.rs | 14 ++++++++++++++ 4 files changed, 35 insertions(+), 21 deletions(-) diff --git a/docs/coordinator.md b/docs/coordinator.md index 10e865a9..e1173709 100644 --- a/docs/coordinator.md +++ b/docs/coordinator.md @@ -38,9 +38,9 @@ somewhere." | `MetaUpdate` | `nix flake update` on the meta flake. The worker runs the lock bump itself, then enqueues a cascade of `Rebuild` entries with `parent_id` set to the meta-update's id. | | `Spawn` | First-deploy of a new agent (approval-driven). Same serialisation as `Rebuild` from the operator's POV. | | `Destroy` | For future use (`destroy --purge` does real I/O). Variant exists so the wire shape doesn't change later; not currently routed through the queue. | +| `Restart` | Stop + start a container without touching config (~5-10s). Routed through the queue so it serialises against in-flight rebuilds for the same agent — prevents a restart racing a rebuild mid-flight. Sources: dashboard ↺ button, manager `restart` MCP tool. | -**Intentionally not queued** (sub-second ops; adding them adds dashboard noise -without serving the "one at a time" goal): `start`, `stop`, `restart`, `kill`. +**Intentionally not queued** (sub-second ops): `start`, `stop`, `kill`. ### Dedup diff --git a/hive-c0re/src/dashboard.rs b/hive-c0re/src/dashboard.rs index 53ff1173..0218229f 100644 --- a/hive-c0re/src/dashboard.rs +++ b/hive-c0re/src/dashboard.rs @@ -2720,15 +2720,15 @@ async fn post_restart(State(state): State, AxumPath(name): AxumPath, AxumPath(name): AxumPath) -> Response { diff --git a/hive-c0re/src/manager_server.rs b/hive-c0re/src/manager_server.rs index 176d01a2..4ac9695d 100644 --- a/hive-c0re/src/manager_server.rs +++ b/hive-c0re/src/manager_server.rs @@ -159,21 +159,21 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc) -> ManagerResp } } ManagerRequest::Restart { name } => { - tracing::info!(%name, "manager: restart"); + tracing::info!(%name, "manager: enqueue restart"); if name == crate::lifecycle::MANAGER_NAME { return ManagerResponse::Err { message: "refusing to restart the manager from itself".into(), }; } - match lifecycle::restart(name).await { - Ok(()) => { - coord.kick_agent(name, "container restarted"); - ManagerResponse::Ok - } - Err(e) => ManagerResponse::Err { - message: format!("{e:#}"), - }, - } + coord.rebuild_queue.enqueue( + crate::rebuild_queue::QueueKind::Restart, + name.to_owned(), + crate::rebuild_queue::QueueSource::Manual, + "manager `restart` tool".to_owned(), + None, + ); + coord.emit_rebuild_queue_snapshot(); + ManagerResponse::Ok } ManagerRequest::Update { name } => { tracing::info!(%name, "manager: enqueue update"); diff --git a/hive-c0re/src/rebuild_queue.rs b/hive-c0re/src/rebuild_queue.rs index fe8d090f..1e7e78fa 100644 --- a/hive-c0re/src/rebuild_queue.rs +++ b/hive-c0re/src/rebuild_queue.rs @@ -32,6 +32,10 @@ pub enum QueueKind { /// after the lock bump; children run as independent queue entries /// grouped under this parent's `id`. `agent` = `"hyperhive"`. StartupSweep, + /// Stop + start a container without touching config. Fast op (~5-10s). + /// Queued so it serialises against in-flight rebuilds for the same + /// agent — prevents a restart racing a rebuild mid-flight. + Restart, } impl QueueKind { @@ -42,6 +46,7 @@ impl QueueKind { QueueKind::Spawn => "spawn", QueueKind::Destroy => "destroy", QueueKind::StartupSweep => "startup_sweep", + QueueKind::Restart => "restart", } } } @@ -591,6 +596,15 @@ async fn dispatch( // `finish` clears the step label; no explicit clear needed here. Ok(()) } + (QueueKind::Restart, _) => { + let name = &entry.agent; + let _guard = coord.transient_guard(name, crate::coordinator::TransientKind::Restarting); + coord.set_queue_step(Some(entry.id), "nixos-container restart"); + crate::lifecycle::restart(name).await?; + coord.kick_agent(name, "container restarted"); + coord.rescan_containers_and_emit().await; + Ok(()) + } } }