fix(#702): give hive-priv a PATH; route forge/matrix list through priv

hive-priv had no PATH, so the nixos-container it runs for every container
op (incl. lifecycle::list) failed ENOENT. build_all swallows that into an
empty list — the 'no managed containers' symptom. Give the helper the
minimal set nixos-container shells out to (nixos-container, nix, util-linux,
e2fsprogs) on top of the systemd/coreutils/findutils already in the unit
PATH, rather than the whole system profile.

Also route forge/matrix is_present() through priv_client::list_containers
instead of spawning nixos-container directly from unprivileged hive-c0re.
This commit is contained in:
müde 2026-06-03 00:05:17 +02:00
commit e7b6896e4c
3 changed files with 23 additions and 17 deletions

View file

@ -70,17 +70,14 @@ fn token_path(name: &str) -> PathBuf {
}
/// Probe whether `hive-forge` exists as a nixos-container. Cheap —
/// `nixos-container list` is just a directory scan in /etc.
/// `nixos-container list` is just a directory scan in /etc. Routed
/// through hive-priv: `nixos-container` needs root, and hive-c0re runs
/// unprivileged (privsep).
pub async fn is_present() -> bool {
let Ok(out) = Command::new("nixos-container").arg("list").output().await else {
let Ok(stdout) = crate::priv_client::list_containers().await else {
return false;
};
if !out.status.success() {
return false;
}
String::from_utf8_lossy(&out.stdout)
.lines()
.any(|l| l.trim() == FORGE_CONTAINER)
stdout.lines().any(|l| l.trim() == FORGE_CONTAINER)
}
/// Run `forgejo admin <args>` inside the hive-forge container as the

View file

@ -11,7 +11,6 @@ use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use reqwest::StatusCode;
use tokio::process::Command;
use crate::coordinator::Coordinator;
@ -55,17 +54,13 @@ fn password_path(name: &str) -> PathBuf {
/// Probe whether `hive-matrix` exists as a nixos-container. Cheap —
/// `nixos-container list` is just a directory scan in /etc. Same shape
/// as `forge::is_present`.
/// as `forge::is_present` — routed through hive-priv since
/// `nixos-container` needs root and hive-c0re runs unprivileged.
pub async fn is_present() -> bool {
let Ok(out) = Command::new("nixos-container").arg("list").output().await else {
let Ok(stdout) = crate::priv_client::list_containers().await else {
return false;
};
if !out.status.success() {
return false;
}
String::from_utf8_lossy(&out.stdout)
.lines()
.any(|l| l.trim() == MATRIX_CONTAINER)
stdout.lines().any(|l| l.trim() == MATRIX_CONTAINER)
}
/// Read `n` cryptographic-quality bytes from `/dev/urandom` and return

View file

@ -507,6 +507,20 @@ in
# entry point; systemd starts this service on first connect.
after = [ "hive-priv.socket" ];
requires = [ "hive-priv.socket" ];
# `nixos-container` is a perl script that shells out by bare name to
# nix / nix-env / nix-instantiate (create + update), machinectl +
# systemctl (start/stop), and find / rm / umount / chattr (destroy);
# only nsenter + su are hardcoded. Give the helper exactly those —
# not the whole system profile — on top of the systemd/coreutils/
# findutils already in the default unit PATH. Without `nixos-container`
# on PATH every container op fails ENOENT, which `build_all` silently
# swallows into an empty list ("no managed containers").
path = [
pkgs.nixos-container
pkgs.nix # nix, nix-env, nix-instantiate — create + update
pkgs.util-linux # umount (nsenter is hardcoded in the script)
pkgs.e2fsprogs # chattr
];
serviceConfig = {
ExecStart = "${cfg.package}/bin/hive-priv";
Type = "simple";