hive: stop controlling the swarm's forge and matrix containers
The forge and matrix are swarm services; per mara's ruling on #5475 (shape a) a hive no longer starts, stops or restarts them. - hive-priv-sock: InfraContainer keeps only Gateway; InfraTarget goes, service_unit maps the gateway to nginx.service directly. A ControlInfraContainer naming Forge/Matrix fails to decode. - hive-host-sock: LifecycleScope drops forge/matrix and refuses unknown fields, so an older hivectl's --forge is an error rather than an all-false scope meaning everything. - hive-c0re: scoped_infra selects only the gateway. - hivectl: --forge/--matrix removed from stop/start/restart. - docs: services.md says how the forge and matrix are managed; security.md, coordinator.md and hivectl-cli.md follow. - hive-host-sock, hivectl/power.rs: Stop and RestartScoped doc comments name the gateway (host nginx.service), not infra containers.
This commit is contained in:
parent
be92bc0835
commit
d4f3d1ef8e
12 changed files with 177 additions and 203 deletions
|
|
@ -1172,16 +1172,13 @@ async fn daemon_reload() -> Result<(String, String)> {
|
|||
|
||||
/// `ControlInfraContainer` — start/stop a hive infrastructure service via
|
||||
/// `systemctl <verb> <unit>`. The [`InfraContainer`] enum is the allowlist:
|
||||
/// serde already rejected any unknown / unsafe name (hive-c0re has no
|
||||
/// variant, so a stop can't sever the daemon socket) at deserialisation, so
|
||||
/// no root-side `.contains()` check is needed here. Serves the hive-wide
|
||||
/// `hivectl stop`/`start` flow and the dashboard's operator-only infra
|
||||
/// panel. No agent-facing path exists.
|
||||
/// serde already rejected any unknown / unsafe name (hive-c0re, the forge
|
||||
/// and the matrix have no variant) at deserialisation, so no root-side
|
||||
/// `.contains()` check is needed here. Serves the hive-wide
|
||||
/// `hivectl stop`/`start`/`restart` flow. No agent-facing path exists.
|
||||
///
|
||||
/// ⚠️ The unit is derived from the variant, never sent by the caller —
|
||||
/// which is what keeps this from being a general `systemctl` pass-through.
|
||||
/// It is not always `container@<name>.service`: the gateway resolves to the
|
||||
/// host's `nginx.service`.
|
||||
async fn control_infra_container(
|
||||
container: InfraContainer,
|
||||
action: InfraAction,
|
||||
|
|
@ -1189,7 +1186,7 @@ async fn control_infra_container(
|
|||
let verb = action.systemctl_verb();
|
||||
let unit = container.service_unit();
|
||||
let out = Command::new("systemctl")
|
||||
.args([verb, &unit])
|
||||
.args([verb, unit])
|
||||
.output()
|
||||
.await
|
||||
.with_context(|| format!("systemctl {verb} {unit}"))?;
|
||||
|
|
@ -2772,10 +2769,10 @@ async fn sync_gateway_nginx() -> Result<(String, String)> {
|
|||
///
|
||||
/// **This prefix is what confines the lifecycle verbs to agent containers** —
|
||||
/// not their name validation, which only checks characters. A caller cannot
|
||||
/// name `hive-forge` and reach it: it becomes `h-hive-forge`. Infra containers
|
||||
/// are reached through [`PrivRequest::ControlInfraContainer`] and the
|
||||
/// [`InfraContainer`] enum instead, which is why no lifecycle verb here takes
|
||||
/// a sibling name.
|
||||
/// name `hive-forge` and reach it: it becomes `h-hive-forge`. No lifecycle
|
||||
/// verb takes a sibling name: the forge and matrix are swarm services a hive
|
||||
/// does not start or stop, and the gateway is reached through
|
||||
/// [`PrivRequest::ControlInfraContainer`] and the [`InfraContainer`] enum.
|
||||
fn container_system_name(name: &str) -> String {
|
||||
format!("{AGENT_PREFIX}{name}")
|
||||
}
|
||||
|
|
@ -3163,13 +3160,13 @@ fn ensure_socket_dir_in(root: &Path, name: &str) -> Result<()> {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
BindMount, BoundedRun, Command, HiveFacts, OwnedFd, PAUSED_MARKER_FILE, PrivRequest,
|
||||
StagedFile, agent_disk_paths, check_fd_agreement, contains_secret_shaped_run,
|
||||
describe_forge_admin, du_command, du_total, ensure_plain_filename, ensure_socket_dir_in,
|
||||
exec_measure_agent_disk, git_overlay_flags, limits_dropin_body, open_dir, open_export_dest,
|
||||
parse_du_total, partial_name, publish_file, redact_secret_line, remove_marker_in,
|
||||
render_hive_facts, run_bounded, single_output_path, toplevel_attr,
|
||||
validate_credential_name, validate_snapshot_name, write_agent_dir_file,
|
||||
BindMount, BoundedRun, Command, HiveFacts, InfraAction, InfraContainer, OwnedFd,
|
||||
PAUSED_MARKER_FILE, PrivRequest, StagedFile, agent_disk_paths, check_fd_agreement,
|
||||
contains_secret_shaped_run, describe_forge_admin, du_command, du_total,
|
||||
ensure_plain_filename, ensure_socket_dir_in, exec_measure_agent_disk, git_overlay_flags,
|
||||
limits_dropin_body, open_dir, open_export_dest, parse_du_total, partial_name, publish_file,
|
||||
redact_secret_line, remove_marker_in, render_hive_facts, run_bounded, single_output_path,
|
||||
toplevel_attr, validate_credential_name, validate_snapshot_name, write_agent_dir_file,
|
||||
write_bridge_dns_marker_in,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
|
|
@ -4044,6 +4041,27 @@ mod tests {
|
|||
assert!(serde_json::from_str::<PrivRequest>(r#"{"op":"sync_agent_tmpfilez"}"#).is_err());
|
||||
}
|
||||
|
||||
/// A start/stop naming the forge or matrix container fails to decode, so
|
||||
/// `run` answers it with a parse error before anything reaches
|
||||
/// `systemctl`. Control: the same request naming the gateway decodes.
|
||||
#[test]
|
||||
fn infra_control_refuses_swarm_services() {
|
||||
let req = |c: &str| {
|
||||
format!(r#"{{"op":"control_infra_container","container":"{c}","action":"stop"}}"#)
|
||||
};
|
||||
for c in ["Forge", "Matrix"] {
|
||||
let err = serde_json::from_str::<PrivRequest>(&req(c)).unwrap_err();
|
||||
assert!(err.to_string().contains("unknown variant"), "{c}: {err}");
|
||||
}
|
||||
assert!(matches!(
|
||||
serde_json::from_str::<PrivRequest>(&req("Gateway")),
|
||||
Ok(PrivRequest::ControlInfraContainer {
|
||||
container: InfraContainer::Gateway,
|
||||
action: InfraAction::Stop,
|
||||
})
|
||||
));
|
||||
}
|
||||
|
||||
/// The pause marker round-trips through the same root-only path the
|
||||
/// credential writes use, and BOTH directions are idempotent — the
|
||||
/// dashboard toggle and `hivectl pause|resume` fire blind, without
|
||||
|
|
|
|||
Loading…
Reference in a new issue