hivectl: collapse infra allowlist + restart/control ops onto SIBLING_CONTAINERS

Per review: RESTARTABLE_INFRA_CONTAINERS and the new CONTROLLABLE_INFRA_CONTAINERS
were near-identical subsets of SIBLING_CONTAINERS. Drop both and validate infra
lifecycle ops against SIBLING_CONTAINERS directly (all four infra containers;
hive-c0re is never in it, so it can't stop itself). This also makes hive-matrix
restartable, including via an infra_admin agent's restart tool.

Collapse the two priv ops too: RestartInfraContainer is gone; ControlInfraContainer
{ action } is the single op (restart = action: Restart). priv_client's
restart_infra_container is now a thin wrapper over control_infra_container.
This commit is contained in:
atlas 2026-06-19 00:50:00 +02:00 committed by mara
commit 0df9e40940
4 changed files with 46 additions and 109 deletions

View file

@ -21,9 +21,9 @@ use std::path::{Path, PathBuf};
use anyhow::{Context as _, Result, bail};
use hive_sh4re::priv_proto::{
AGENT_PREFIX, AGENT_STATE_ROOT, BindMount, CONTROLLABLE_INFRA_CONTAINERS, InfraAction,
JournalQuery, MANAGER_NAME, META_DIR, NetworkIsolation, PRIV_SOCK, PrivEvent, PrivRequest,
PrivResponse, PrivStream, PrivStreamLine, RESTARTABLE_INFRA_CONTAINERS, SIBLING_CONTAINERS,
AGENT_PREFIX, AGENT_STATE_ROOT, BindMount, InfraAction, JournalQuery, MANAGER_NAME, META_DIR,
NetworkIsolation, PRIV_SOCK, PrivEvent, PrivRequest, PrivResponse, PrivStream, PrivStreamLine,
SIBLING_CONTAINERS,
};
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::unix::OwnedWriteHalf;
@ -265,10 +265,6 @@ async fn exec(req: PrivRequest, writer: &mut OwnedWriteHalf) -> Result<(String,
restart_matrix_daemon(agent_name).await
}
PrivRequest::RestartInfraContainer { ref container } => {
restart_infra_container(container).await
}
PrivRequest::ControlInfraContainer {
ref container,
action,
@ -399,43 +395,15 @@ async fn restart_matrix_daemon(agent_name: &str) -> Result<(String, String)> {
))
}
/// `RestartInfraContainer` — restart a hive infrastructure container on
/// the host via `systemctl restart container@<container>.service`. The
/// `container` is validated against `RESTARTABLE_INFRA_CONTAINERS` here,
/// root-side, so this is the authoritative allowlist even though
/// hive-c0re also gates on the caller's `infra_admin` capability.
async fn restart_infra_container(container: &str) -> Result<(String, String)> {
if !RESTARTABLE_INFRA_CONTAINERS.contains(&container) {
bail!("container {container:?} is not a restartable hive infra container");
}
let unit = format!("container@{container}.service");
let out = Command::new("systemctl")
.args(["restart", &unit])
.output()
.await
.with_context(|| format!("systemctl restart {unit}"))?;
if !out.status.success() {
bail!(
"systemctl restart {unit} exited {}: {}",
out.status,
String::from_utf8_lossy(&out.stderr).trim()
);
}
tracing::info!(target: "infra-restart", "restarted {unit}");
Ok((
String::from_utf8_lossy(&out.stdout).into_owned(),
String::from_utf8_lossy(&out.stderr).into_owned(),
))
}
/// `ControlInfraContainer` — start/stop/restart a hive infrastructure
/// container via `systemctl <verb> container@<container>.service`. The
/// `container` is validated against `CONTROLLABLE_INFRA_CONTAINERS` here,
/// root-side; this is the authoritative allowlist (hive-c0re itself can
/// never appear in it, so a hive-wide stop can't sever the daemon socket
/// the request arrived on).
/// `container` is validated against `SIBLING_CONTAINERS` here, root-side;
/// this is the authoritative allowlist (hive-c0re is never in it, so a
/// stop can't sever the daemon socket the request arrived on). Serves both
/// the hive-wide `hivectl stop`/`start` flow and an `infra_admin` agent's
/// `restart` (action = Restart).
async fn control_infra_container(container: &str, action: InfraAction) -> Result<(String, String)> {
if !CONTROLLABLE_INFRA_CONTAINERS.contains(&container) {
if !SIBLING_CONTAINERS.contains(&container) {
bail!("container {container:?} is not a controllable hive infra container");
}
let verb = action.systemctl_verb();