aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/modules/vm
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-25 10:15:41 +0200
committerDanilo M. <danix@danix.xyz>2026-09-25 10:15:41 +0200
commitd0a946798700cc0dea6d338296f1cffa413d2541 (patch)
treead6a5fe269604ea75d392998e8c5dde3811ddcb5 /desktop/modules/vm
parent696f3ad4f65a6516549544af13a9646f57ae523e (diff)
downloadquickshell-d0a946798700cc0dea6d338296f1cffa413d2541.tar.gz
quickshell-d0a946798700cc0dea6d338296f1cffa413d2541.zip
feat(vm): notify when a started VM accepts ssh
libvirt reports "running" as soon as QEMU is up, long before the guest has booted, so the panel had no way to say when a VM is actually usable. After a successful start, a detached script polls the VM's sshd and sends a notification once it accepts connections, or a warning after five minutes. Host and port come from `ssh -G <vm>`: a Host alias named after the VM may put sshd on a non-default port (buildsystem listens on 2290, and a probe of port 22 never succeeded). Without an alias `ssh -G` echoes the name back, and the guest agent's IPv4 address on port 22 is used. The probe is a plain `nc -z`, so no authentication or key is involved. Detached rather than a Process so several VMs can wait at once and the wait survives a shell reload. Only starts issued from the panel are watched; a VM started from virt-manager or the CLI is not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'desktop/modules/vm')
-rw-r--r--desktop/modules/vm/Virsh.qml28
1 files changed, 28 insertions, 0 deletions
diff --git a/desktop/modules/vm/Virsh.qml b/desktop/modules/vm/Virsh.qml
index 14bd368..6a15f97 100644
--- a/desktop/modules/vm/Virsh.qml
+++ b/desktop/modules/vm/Virsh.qml
@@ -306,6 +306,32 @@ Singleton {
actProc.running = true;
}
+ // After a start, notify once sshd accepts connections. "Running" in
+ // libvirt means QEMU is up, not the guest, so the port is the real
+ // signal. Host and port come from `ssh -G <vm>`, because a Host alias
+ // named after the VM may put sshd on a non-default port (buildsystem
+ // listens on 2290, and probing 22 waited forever); without an alias
+ // `ssh -G` echoes the name back, and the guest agent's address with
+ // port 22 is used instead. Detached so several VMs can wait at once;
+ // a VM that never answers gets a notice after five minutes.
+ readonly property string _waitSsh: `
+ vm=$1; end=$(( $(date +%s) + 300 ))
+ set -- $(ssh -G "$vm" 2>/dev/null | awk '/^hostname /{h=$2} /^port /{p=$2} END{print h, p}')
+ host=$1; port=\${2:-22}
+ while [ "$(date +%s)" -lt "$end" ]; do
+ if [ "$host" = "$vm" ]; then
+ ip=$(virsh domifaddr "$vm" --source agent 2>/dev/null |
+ awk '$3 == "ipv4" && $4 !~ /^127\\./ { sub("/.*", "", $4); print $4; exit }')
+ else
+ ip=$host
+ fi
+ [ -n "$ip" ] && nc -z -w 2 "$ip" "$port" 2>/dev/null &&
+ exec notify-send --app-name=vm-manager --icon=computer "$vm is ready" "ssh $ip:$port"
+ sleep 2
+ done
+ notify-send --app-name=vm-manager --icon=dialog-warning "$vm not reachable" "no ssh after 5 minutes"
+ `
+
Process {
id: actProc
property string vm: ""
@@ -315,6 +341,8 @@ Singleton {
if (code !== 0)
root.actionFailed(actProc.vm, actProc.action,
actErr.text.trim() || `exited ${code}`);
+ else if (actProc.action === "start")
+ Quickshell.execDetached(["sh", "-c", root._waitSsh, "sh", actProc.vm]);
root.refreshList();
if (root.snapshots[actProc.vm]) root.loadSnapshots(actProc.vm);
}