systemd: container/remote restart, permanent-error backoff, recursive section via loader (step 6)

This commit is contained in:
Damocles 2026-05-07 21:01:23 +02:00
parent 5676b1ac62
commit dfa3840d97
6 changed files with 118 additions and 56 deletions

View file

@ -60,7 +60,13 @@ pub mod qobject {
#[qinvokable]
#[cxx_name = "restartUnit"]
fn restart_unit(self: Pin<&mut Self>, name: QString, scope: QString, machine: QString);
fn restart_unit(
self: Pin<&mut Self>,
name: QString,
scope: QString,
host: QString,
machine: QString,
);
}
impl cxx_qt::Initialize for SystemdService {}
@ -527,13 +533,14 @@ impl qobject::SystemdService {
let applet_open = self.as_ref().rust().applet_open;
if applet_open {
for target in &want_targets {
let prev_last_seen = self
.as_ref()
.rust()
.remote_cache
.get(target)
.map(|m| m.last_seen)
.unwrap_or(0);
let prev = self.as_ref().rust().remote_cache.get(target).cloned();
let prev_last_seen = prev.as_ref().map(|m| m.last_seen).unwrap_or(0);
// Backoff: don't retry hosts with a permanent error until
// either the config changes (cache cleared) or the user
// restarts the shell. Reuse the cached entry verbatim.
if prev.as_ref().is_some_and(|m| m.error_kind == "permanent") {
continue;
}
let entry = match fetch_via_busctl(target, &["--host", target], false, "") {
Ok(mut m) => {
// Enumerate + fetch remote nspawn containers via the
@ -599,28 +606,70 @@ impl qobject::SystemdService {
}
}
fn restart_unit(self: Pin<&mut Self>, name: QString, scope: QString, machine: QString) {
fn restart_unit(
self: Pin<&mut Self>,
name: QString,
scope: QString,
host: QString,
machine: QString,
) {
let name = name.to_string();
let scope = scope.to_string();
let host = host.to_string();
let machine = machine.to_string();
let _ = self;
rt().block_on(async move {
// Local-only restart for now. Container/remote restart is TODO.
if !machine.is_empty() {
tracing::warn!(target: "nova_plugin", machine = %machine, "container/remote restart not yet implemented");
return;
// Local + system or user scope: native zbus, supports user manager too.
if host.is_empty() && machine.is_empty() {
rt().block_on(async move {
let conn = match scope.as_str() {
"user" => Connection::session().await,
_ => Connection::system().await,
};
let Ok(conn) = conn else { return };
let Ok(mgr) = SystemdManagerProxy::new(&conn).await else {
return;
};
if let Err(e) = mgr.restart_unit(&name, "replace").await {
tracing::warn!(target: "nova_plugin", unit = %name, error = %e, "restart_unit failed");
}
});
return;
}
// Container or remote: spawn busctl with the matching prefix flags.
// (busctl doesn't have a concept of `--user` over remote; we restart
// system units only for non-local hosts.)
let mut prefix: Vec<String> = Vec::new();
if !host.is_empty() {
prefix.push("--host".into());
prefix.push(host.clone());
}
if !machine.is_empty() {
prefix.push("--machine".into());
prefix.push(machine.clone());
}
let mut args: Vec<&str> = prefix.iter().map(String::as_str).collect();
args.extend([
"call",
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
"RestartUnit",
"ss",
name.as_str(),
"replace",
]);
let out = std::process::Command::new("busctl").args(&args).output();
match out {
Ok(o) if o.status.success() => {}
Ok(o) => {
let stderr = String::from_utf8_lossy(&o.stderr);
tracing::warn!(target: "nova_plugin", unit = %name, host = %host, machine = %machine, error = %stderr, "restart_unit busctl failed");
}
let conn = match scope.as_str() {
"user" => Connection::session().await,
_ => Connection::system().await,
};
let Ok(conn) = conn else { return };
let Ok(mgr) = SystemdManagerProxy::new(&conn).await else {
return;
};
if let Err(e) = mgr.restart_unit(&name, "replace").await {
tracing::warn!(target: "nova_plugin", unit = %name, error = %e, "restart_unit failed");
Err(e) => {
tracing::warn!(target: "nova_plugin", unit = %name, error = %e, "restart_unit busctl spawn failed");
}
});
}
}
}