feat(hivectl): queue-routed lifecycle verbs with wait + DAG progress

every agent lifecycle verb on the admin socket (rebuild / restart /
restart-all / kill / stop / start) now submits job-queue DAGs and
returns their ids; hivectl polls the new HostRequest::QueueDag and
prints a live node-chain progress line per DAG (fan-out children
included), exiting non-zero on failure — --no-wait opts out. DagView
and the queue wire enums move to hive_sh4re::jobs (wire types live in
the shared crate); the last fused rebuild path (lifecycle::rebuild)
is gone. tracker: #2166
This commit is contained in:
müde 2026-07-06 22:30:49 +02:00
commit b489454dc2
9 changed files with 641 additions and 443 deletions

View file

@ -501,59 +501,14 @@ pub async fn destroy(name: &str) -> Result<()> {
Ok(())
}
/// Rebuild `name`'s container: sync the meta flake, optionally re-lock
/// the agent's input, then re-apply + restart via `nixos-container`.
///
/// When `relock` is `true` the agent's meta input is bumped to whatever
/// `applied/<n>/main` points at before the build. Pass `false` for
/// meta-update cascade rebuilds, where re-locking would revert the bump
/// the cascade just committed (see the inline note below).
///
/// # Errors
///
/// Propagates errors from meta-flake sync / lock-update and the
/// `nixos-container` apply + restart shellouts.
///
/// Returns `true` when `defer_start` suppressed the start-after-update —
/// the caller owns bringing the container back up (see
/// [`rebuild_no_meta`]).
pub async fn rebuild(
name: &str,
hive: &HiveEnv,
paths: &AgentPaths,
relock: bool,
defer_start: bool,
on_step: &(dyn Fn(&str) + Send + Sync),
on_build_log_id: &(dyn Fn(i64) + Send + Sync),
) -> Result<bool> {
// Sync the meta flake (idempotent — no-op when the rendered
// flake matches disk) so a manual rebuild from the dashboard
// can also recover from a divergent meta repo (e.g. an agent
// got added directly via `nixos-container create` outside
// hive-c0re).
let agents = agents_for_meta(None).await?;
crate::meta::sync_agents(hive, &agents).await?;
// Then bump just this agent's input — picks up whatever
// `applied/<n>/main` currently points at (deployed/<latest>).
// Commits the lock if it changed.
//
// `relock = false` skips this: a meta-update cascade has *just* set
// the meta lock deliberately, and `lock_update_for_rebuild` re-runs
// `nix flake update agent-<name>`, which re-resolves the agent's
// transitive inputs back to the agent's own flake.lock — reverting
// the input the meta-update just bumped. Cascade rebuilds therefore
// build against the freshly-set on-disk lock as-is.
if relock {
crate::meta::lock_update_for_rebuild(name).await?;
}
rebuild_no_meta(name, hive, paths, defer_start, on_step, on_build_log_id).await
}
/// Container-level rebuild without touching the meta repo. Callers
/// that own the meta side themselves (`actions::run_apply_commit`
/// drives meta through the two-phase prepare/finalize/abort flow)
/// use this directly. Public `rebuild` wraps it with idempotent meta
/// sync + lock-bump-and-commit.
/// Container-level rebuild without touching the meta repo. The one
/// remaining fused stop/update/start pipeline: the approval deploy
/// (`actions::deploy_applied_target`) drives meta through the
/// two-phase prepare/finalize/abort flow itself and needs the inline
/// start to verify the agent comes back up before finalizing. Every
/// other rebuild is a job-queue DAG (`Prebuild → StopForUpdate → Swap
/// → Reconcile`) whose `Prebuild` executor owns the meta sync +
/// relock this path's deleted `rebuild` wrapper used to do.
///
/// `on_step` is called at each phase boundary with a short human-readable
/// label so callers can surface progress (e.g. update the rebuild-queue