refactor(hive-c0re): split socket_server into submodules
mod.rs keeps dispatch + messaging/guards; schedules, reminders, config approvals, and lifecycle handlers move to their own files
This commit is contained in:
parent
380c6ad47f
commit
9e7af3b6bf
8 changed files with 2269 additions and 2195 deletions
201
hive-c0re/src/socket_server/lifecycle_handlers.rs
Normal file
201
hive-c0re/src/socket_server/lifecycle_handlers.rs
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
//! Container-lifecycle request handlers (`Start` / `Restart` / `Kill` /
|
||||
//! `Update` / `ListDescendants`), including the capability-gated
|
||||
//! infra-container restart path. All are topology-guarded via
|
||||
//! `super::require_descendant`.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use hive_sh4re::AgentResponse;
|
||||
|
||||
use super::require_descendant;
|
||||
use crate::coordinator::Coordinator;
|
||||
|
||||
/// `Start` — start a container, kicking its next turn. The caller must be an
|
||||
/// ancestor of `name` in the topology (the root covers every agent).
|
||||
pub(super) fn handle_start(coord: &Arc<Coordinator>, agent: &str, name: &str) -> AgentResponse {
|
||||
if let Some(err) = require_descendant(agent, name, "start") {
|
||||
return err;
|
||||
}
|
||||
tracing::info!(%agent, %name, "start container");
|
||||
// Persist `wanted = Up` and submit the Start DAG; the submit layer
|
||||
// upgrades a stale-rev start to a full rebuild so the container
|
||||
// runs current nix derivations before it starts.
|
||||
crate::job_queue::submit::start(
|
||||
coord,
|
||||
name,
|
||||
crate::job_queue::Source::Manual,
|
||||
format!("agent `{agent}` start tool"),
|
||||
);
|
||||
AgentResponse::Ok
|
||||
}
|
||||
|
||||
/// `Restart` — enqueue a restart for a container. The caller must be an
|
||||
/// ancestor of `name` in the topology. The infra-container branch is
|
||||
/// orthogonal: it is gated on the `infra_admin` capability and audited, so it
|
||||
/// stays ahead of the topology guard.
|
||||
pub(super) async fn handle_restart(
|
||||
coord: &Arc<Coordinator>,
|
||||
agent: &str,
|
||||
name: &str,
|
||||
) -> AgentResponse {
|
||||
// Infra-container restart: an agent holding the `infra_admin`
|
||||
// capability can restart a hive infrastructure container (hive-ci /
|
||||
// hive-gateway / hive-forge / hive-matrix) by passing its name to the
|
||||
// same restart tool. The `InfraContainer` enum parse both recognises
|
||||
// these (never agent children, so disjoint from the child path below)
|
||||
// and yields the typed value the restart path needs.
|
||||
if let Ok(container) = name.parse::<hive_sh4re::priv_proto::InfraContainer>() {
|
||||
return handle_restart_infra(coord, agent, container).await;
|
||||
}
|
||||
if let Some(err) = require_descendant(agent, name, "restart") {
|
||||
return err;
|
||||
}
|
||||
tracing::info!(%agent, %name, "submit restart");
|
||||
crate::job_queue::submit::restart(
|
||||
coord,
|
||||
name,
|
||||
crate::job_queue::Source::Manual,
|
||||
format!("agent `{agent}` restart tool"),
|
||||
);
|
||||
AgentResponse::Ok
|
||||
}
|
||||
|
||||
/// Restart a hive infrastructure container on behalf of an agent that
|
||||
/// holds the `infra_admin` capability. The `container` is already a valid
|
||||
/// [`InfraContainer`] (the caller parsed it); this gates on the capability
|
||||
/// and routes the systemctl restart through hive-priv. Direct, not
|
||||
/// approval-gated.
|
||||
async fn handle_restart_infra(
|
||||
coord: &Arc<Coordinator>,
|
||||
agent: &str,
|
||||
container: hive_sh4re::priv_proto::InfraContainer,
|
||||
) -> AgentResponse {
|
||||
let name = container.unit_name();
|
||||
// Record the attempt in the operator-visible privileged-action audit
|
||||
// trail, then emit a live `AuditEntryAdded` so the dashboard audit view
|
||||
// appends it off `/dashboard/stream`. Best-effort: `record` returns the
|
||||
// canonical row (or `None` on a sqlite blip), and we stream exactly that
|
||||
// row so the stored + streamed views can't drift. `action` is stable so
|
||||
// the dashboard can group/filter.
|
||||
let audit = |outcome: crate::audit_log::AuditOutcome, detail: Option<&str>| {
|
||||
if let Some(entry) = coord
|
||||
.audit_log
|
||||
.record(agent, "restart_infra", name, outcome, detail)
|
||||
{
|
||||
coord.emit_audit_entry(entry);
|
||||
}
|
||||
};
|
||||
if !crate::capabilities::has_cap(agent, hive_sh4re::Capability::InfraAdmin) {
|
||||
tracing::warn!(%agent, %name, "agent: infra restart denied (no infra_admin capability)");
|
||||
audit(
|
||||
crate::audit_log::AuditOutcome::Err,
|
||||
Some("denied: missing infra_admin capability"),
|
||||
);
|
||||
return AgentResponse::Err {
|
||||
message: format!(
|
||||
"restarting infra container `{name}` requires the `infra_admin` capability"
|
||||
),
|
||||
};
|
||||
}
|
||||
tracing::info!(%agent, %name, "agent: restart infra container");
|
||||
match crate::priv_client::restart_infra_container(container).await {
|
||||
Ok(()) => {
|
||||
audit(crate::audit_log::AuditOutcome::Ok, None);
|
||||
AgentResponse::Ok
|
||||
}
|
||||
Err(e) => {
|
||||
let msg = format!("{e:#}");
|
||||
audit(crate::audit_log::AuditOutcome::Err, Some(&msg));
|
||||
AgentResponse::Err { message: msg }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `Kill` — kill a container, unregister it, notify the manager. The caller
|
||||
/// must be an ancestor of `name` in the topology.
|
||||
pub(super) async fn handle_kill(
|
||||
coord: &Arc<Coordinator>,
|
||||
agent: &str,
|
||||
name: &str,
|
||||
) -> AgentResponse {
|
||||
if let Some(err) = require_descendant(agent, name, "kill") {
|
||||
return err;
|
||||
}
|
||||
tracing::info!(%agent, %name, "kill container");
|
||||
// Persist the intent even if the kill fails — otherwise the next
|
||||
// reconcile would restart the container.
|
||||
if let Err(e) = coord.power.set(name, crate::power::Wanted::Offline) {
|
||||
tracing::warn!(%name, error = ?e, "agent_power: set wanted=offline failed");
|
||||
}
|
||||
let result: anyhow::Result<()> = async {
|
||||
crate::lifecycle::kill(name).await?;
|
||||
coord.unregister_agent(name);
|
||||
Ok(())
|
||||
}
|
||||
.await;
|
||||
match result {
|
||||
Ok(()) => {
|
||||
coord.notify_manager(&hive_sh4re::HelperEvent::Killed {
|
||||
agent: name.to_owned(),
|
||||
});
|
||||
AgentResponse::Ok
|
||||
}
|
||||
Err(e) => AgentResponse::Err {
|
||||
message: format!("{e:#}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// `Update` — enqueue a rebuild for a container. The caller must be an
|
||||
/// ancestor of `name` in the topology.
|
||||
pub(super) fn handle_update(coord: &Arc<Coordinator>, agent: &str, name: &str) -> AgentResponse {
|
||||
if let Some(err) = require_descendant(agent, name, "rebuild") {
|
||||
return err;
|
||||
}
|
||||
tracing::info!(%agent, %name, "submit rebuild");
|
||||
crate::job_queue::submit::rebuild(
|
||||
coord,
|
||||
name,
|
||||
crate::job_queue::Source::Manual,
|
||||
format!("agent `{agent}` update tool"),
|
||||
);
|
||||
AgentResponse::Ok
|
||||
}
|
||||
|
||||
/// `ListDescendants` — every topological descendant of `agent` with
|
||||
/// its running/stopped state, parents before children.
|
||||
pub(super) async fn handle_list_descendants(agent: &str) -> AgentResponse {
|
||||
tracing::debug!(%agent, "agent: list descendants");
|
||||
// All containers known to nixos-container (running only).
|
||||
let running_set: std::collections::HashSet<String> = match crate::lifecycle::list().await {
|
||||
Ok(names) => names
|
||||
.into_iter()
|
||||
.filter_map(|c| {
|
||||
c.strip_prefix(crate::lifecycle::AGENT_PREFIX)
|
||||
.map(str::to_owned)
|
||||
})
|
||||
.collect(),
|
||||
Err(e) => {
|
||||
return AgentResponse::Err {
|
||||
message: format!("list containers failed: {e:#}"),
|
||||
};
|
||||
}
|
||||
};
|
||||
// Walk the full topology and collect every descendant.
|
||||
let topo = crate::topology::read();
|
||||
let mut names: Vec<String> = topo
|
||||
.keys()
|
||||
.filter(|name| crate::topology::is_descendant_of(name, agent))
|
||||
.cloned()
|
||||
.collect();
|
||||
// Parents before children, then alpha within each tier.
|
||||
crate::auto_update::topology_sort(&mut names, &topo);
|
||||
let containers = names
|
||||
.into_iter()
|
||||
.map(|name| {
|
||||
let running = running_set.contains(&name);
|
||||
hive_sh4re::ContainerInfo { name, running }
|
||||
})
|
||||
.collect();
|
||||
AgentResponse::Containers { containers }
|
||||
}
|
||||
Loading…
Reference in a new issue