systemd: container/remote restart, permanent-error backoff, recursive section via loader (step 6)
This commit is contained in:
parent
5676b1ac62
commit
dfa3840d97
6 changed files with 118 additions and 56 deletions
|
|
@ -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,17 +606,22 @@ 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;
|
||||
|
||||
// Local + system or user scope: native zbus, supports user manager too.
|
||||
if host.is_empty() && machine.is_empty() {
|
||||
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;
|
||||
}
|
||||
let conn = match scope.as_str() {
|
||||
"user" => Connection::session().await,
|
||||
_ => Connection::system().await,
|
||||
|
|
@ -622,5 +634,42 @@ impl qobject::SystemdService {
|
|||
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");
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(target: "nova_plugin", unit = %name, error = %e, "restart_unit busctl spawn failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@ Column {
|
|||
SystemdMachineSection {
|
||||
width: _row.width
|
||||
accentColor: root.accentColor
|
||||
machineName: _row.modelData.isLocal ? "" : _row.modelData.name
|
||||
hostTarget: _row.modelData.isLocal ? "" : _row.modelData.name
|
||||
machineName: ""
|
||||
title: _row.modelData.name
|
||||
marker: _row.modelData.marker ?? ""
|
||||
systemState: _row.modelData.systemState ?? "unknown"
|
||||
|
|
|
|||
|
|
@ -10,15 +10,17 @@ import NovaStats as NS
|
|||
Column {
|
||||
id: root
|
||||
|
||||
required property color accentColor
|
||||
required property string machineName
|
||||
required property string title
|
||||
required property string marker
|
||||
required property string systemState
|
||||
required property int runningCount
|
||||
required property int totalCount
|
||||
required property var failedUnits
|
||||
required property var runningUnits
|
||||
property color accentColor
|
||||
// SSH target string (for remote hosts/their containers); "" for local-side entries.
|
||||
property string hostTarget: ""
|
||||
property string machineName: ""
|
||||
property string title: ""
|
||||
property string marker: ""
|
||||
property string systemState: "unknown"
|
||||
property int runningCount: 0
|
||||
property int totalCount: 0
|
||||
property var failedUnits: []
|
||||
property var runningUnits: []
|
||||
property string errorKind: ""
|
||||
property string errorReason: ""
|
||||
property int lastSeen: 0
|
||||
|
|
@ -169,6 +171,7 @@ Column {
|
|||
description: modelData.description ?? ""
|
||||
subState: modelData.subState ?? ""
|
||||
scope: modelData.scope ?? "system"
|
||||
hostTarget: root.hostTarget
|
||||
machineName: root.machineName
|
||||
accentColor: root.accentColor
|
||||
}
|
||||
|
|
@ -229,40 +232,47 @@ Column {
|
|||
description: modelData.description ?? ""
|
||||
subState: modelData.subState ?? ""
|
||||
scope: modelData.scope ?? "system"
|
||||
hostTarget: root.hostTarget
|
||||
machineName: root.machineName
|
||||
accentColor: root.accentColor
|
||||
}
|
||||
}
|
||||
|
||||
// Nested containers running on this machine. Indented to convey hierarchy.
|
||||
// QML disallows direct recursive component use, so we load the same .qml
|
||||
// by source path through a Loader to break the static graph.
|
||||
Repeater {
|
||||
model: root.containers ?? []
|
||||
delegate: Item {
|
||||
id: _childWrap
|
||||
required property var modelData
|
||||
width: root.width
|
||||
height: _child.height + 4
|
||||
height: _childLoader.height + 4
|
||||
|
||||
SystemdMachineSection {
|
||||
id: _child
|
||||
Loader {
|
||||
id: _childLoader
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 16
|
||||
width: parent.width - 16
|
||||
accentColor: root.accentColor
|
||||
machineName: _childWrap.modelData.name
|
||||
title: _childWrap.modelData.name
|
||||
marker: _childWrap.modelData.marker ?? ""
|
||||
systemState: _childWrap.modelData.systemState ?? "unknown"
|
||||
runningCount: _childWrap.modelData.runningCount ?? 0
|
||||
totalCount: _childWrap.modelData.totalCount ?? 0
|
||||
failedUnits: _childWrap.modelData.failedUnits ?? []
|
||||
runningUnits: _childWrap.modelData.runningUnits ?? []
|
||||
errorKind: _childWrap.modelData.errorKind ?? ""
|
||||
errorReason: _childWrap.modelData.errorReason ?? ""
|
||||
lastSeen: _childWrap.modelData.lastSeen ?? 0
|
||||
containers: _childWrap.modelData.containers ?? []
|
||||
depth: root.depth + 1
|
||||
onContentResized: root.contentResized()
|
||||
source: "SystemdMachineSection.qml"
|
||||
onLoaded: {
|
||||
item.accentColor = root.accentColor;
|
||||
item.hostTarget = root.hostTarget;
|
||||
item.machineName = _childWrap.modelData.name;
|
||||
item.title = _childWrap.modelData.name;
|
||||
item.marker = _childWrap.modelData.marker ?? "";
|
||||
item.systemState = _childWrap.modelData.systemState ?? "unknown";
|
||||
item.runningCount = _childWrap.modelData.runningCount ?? 0;
|
||||
item.totalCount = _childWrap.modelData.totalCount ?? 0;
|
||||
item.failedUnits = _childWrap.modelData.failedUnits ?? [];
|
||||
item.runningUnits = _childWrap.modelData.runningUnits ?? [];
|
||||
item.errorKind = _childWrap.modelData.errorKind ?? "";
|
||||
item.errorReason = _childWrap.modelData.errorReason ?? "";
|
||||
item.lastSeen = _childWrap.modelData.lastSeen ?? 0;
|
||||
item.containers = _childWrap.modelData.containers ?? [];
|
||||
item.depth = root.depth + 1;
|
||||
item.contentResized.connect(root.contentResized);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ Item {
|
|||
required property string description
|
||||
required property string subState
|
||||
required property string scope
|
||||
required property string hostTarget
|
||||
required property string machineName
|
||||
required property color accentColor
|
||||
|
||||
|
|
@ -96,7 +97,7 @@ Item {
|
|||
}
|
||||
TapHandler {
|
||||
enabled: _rowHover.hovered
|
||||
onTapped: S.SystemdService.restartUnit(root.unitName, root.scope, root.machineName)
|
||||
onTapped: S.SystemdService.restartUnit(root.unitName, root.scope, root.hostTarget, root.machineName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ QtObject {
|
|||
}
|
||||
}
|
||||
|
||||
function restartUnit(name, scope, machine) {
|
||||
NS.SystemdService.restartUnit(name, scope ?? "system", machine ?? "");
|
||||
function restartUnit(name, scope, host, machine) {
|
||||
NS.SystemdService.restartUnit(name, scope ?? "system", host ?? "", machine ?? "");
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ shell/applets/PowerApplet.qml: Type "QColor" of property "base0D" not found. Thi
|
|||
shell/applets/PowerApplet.qml: Type "QColor" of property "base0E" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type]
|
||||
shell/applets/PowerApplet.qml: Unqualified access [unqualified]
|
||||
shell/applets/Separator.qml: Type "QColor" of property "base03" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type]
|
||||
shell/applets/SystemdMachineSection.qml: Member "contentResized" not found on type "QObject" [missing-property]
|
||||
shell/applets/SystemdMachineSection.qml: Type "QColor" of property "base00" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type]
|
||||
shell/applets/SystemdMachineSection.qml: Type "QColor" of property "base02" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type]
|
||||
shell/applets/SystemdMachineSection.qml: Type "QColor" of property "base03" not found. This is likely due to a missing dependency entry or a type not being exposed declaratively. [unresolved-type]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue