dashboard: add hive infra containers to the logs UI agent selector

Extends GET /api/journal/{name} to also accept the four hive infra
container names (hive-ci, hive-forge, hive-gateway, hive-matrix —
hive_priv_sock::InfraContainer is the allowlist), reusing the same
journalctl -M / hive-priv delegation path already used for agent
containers. Infra containers don't run the per-agent hive daemons, so
the unit filter is skipped for them — always the full machine journal.

Frontend: the AGENT tab's agent selector now lists infra containers
in a separate optgroup (sourced from /api/state's existing
infra_containers field), and disables the unit-filter select when one
is chosen.
This commit is contained in:
iris 2026-07-18 16:28:31 +02:00 committed by mara
commit 66c715842f
2 changed files with 66 additions and 10 deletions

View file

@ -1,10 +1,13 @@
//! Journal-read endpoints for the dashboard.
//!
//! `GET /api/journal/{name}` reads a managed container's journal via the
//! root helper (`journalctl -M`, delegated to hive-priv since hive-c0re is
//! unprivileged). `GET /api/journal-host` reads host-side journald, both
//! gated by an allow-list of known units so arbitrary unit names can't be
//! probed. Operator-only by virtue of the dashboard binding host-only.
//! `GET /api/journal/{name}` reads a managed agent container's journal, OR
//! one of the four hive infra containers (`hive-ci`, `hive-forge`,
//! `hive-gateway`, `hive-matrix` — [`hive_priv_sock::InfraContainer`] is the
//! allowlist), via the root helper (`journalctl -M`, delegated to hive-priv
//! since hive-c0re is unprivileged). `GET /api/journal-host` reads
//! host-side journald, both gated by an allow-list of known units so
//! arbitrary unit names can't be probed. Operator-only by virtue of the
//! dashboard binding host-only.
use axum::{
extract::Path as AxumPath,
@ -33,10 +36,23 @@ pub(super) struct JournalQuery {
/// Operator-only by virtue of the dashboard being host-bound. hive-c0re
/// runs unprivileged (privsep), so the `-M` read — which enters the
/// container namespace and needs root — is delegated to hive-priv.
///
/// `name` is either a managed agent name (`iris`, optionally already
/// carrying the `h-` prefix) or one of the four infra container names
/// (`hive-ci` / `hive-forge` / `hive-gateway` / `hive-matrix` — see
/// [`hive_priv_sock::InfraContainer`]). Infra containers don't run the
/// per-agent hive daemons, so `unit` is ignored for them — always the
/// full machine journal.
pub(super) async fn get_journal(
AxumPath(name): AxumPath<String>,
axum::extract::Query(q): axum::extract::Query<JournalQuery>,
) -> Result<Response, ProblemDetails> {
let lines = q.lines.unwrap_or(500).min(5000);
if let Ok(infra) = name.parse::<hive_priv_sock::InfraContainer>() {
return read_journal_response(infra.unit_name(), None, lines).await;
}
// Defense-in-depth format check so weird chars never reach the
// shellout below — the `lifecycle::list()` existence check would
// catch them anyway, but rejecting at the boundary keeps the
@ -54,7 +70,6 @@ pub(super) async fn get_journal(
return Err(ProblemDetails::from_status_code(StatusCode::NOT_FOUND)
.with_detail(format!("journal: no managed container {prefixed:?}")));
}
let lines = q.lines.unwrap_or(500).min(5000);
let unit = match q.unit.as_deref().filter(|s| !s.is_empty()) {
Some(u) => {
// accept any of the per-container hive daemons [.service] —
@ -78,8 +93,21 @@ pub(super) async fn get_journal(
}
None => None,
};
read_journal_response(&prefixed, unit, lines).await
}
/// Shared `journalctl -M <machine> [-u <unit>]` shellout + response
/// formatting for [`get_journal`], factored out so the infra-container
/// branch (no `unit` filtering) and the agent-container branch (allow-listed
/// `unit` filtering) don't duplicate the priv-client call + stdout/stderr
/// combining.
async fn read_journal_response(
machine: &str,
unit: Option<String>,
lines: u32,
) -> Result<Response, ProblemDetails> {
match crate::priv_client::read_container_journal(
&prefixed,
machine,
hive_priv_sock::JournalQuery {
lines,
boot: true,