The lab went from one Windows guest to five and there was no way to see what they were all doing without issuing a QEMU screendump per guest by hand. - tools/vmwatch.py: always-on HTTP service serving an auto-refreshing wall of live guest screens. Runs on spicy as vmwatch.service; browse it at http://192.168.3.201:8140/. Click a tile for that guest full size. Guests are discovered from /etc/pve/qemu-server by matching sots-re, so clones appear and vanish on their own. A stopped, paused or unreachable guest gets a labelled placeholder tile carrying the monitor's own error, never a broken image or a 500. Python 3 stdlib only. - tools/vmwatch-install.sh: install/update/uninstall the unit on the host. - tools/vmshot.py: one-shot contact sheet, and --one <id> for a full-size grab. Pulls frames from the vmwatch service when it is up (0.5s) and falls back to ssh + qm monitor when it is not (4s). - guides/lab-screen-wall.md: how to use both, and why. Capture goes over each guest's QMP socket rather than forking qm: qm is a Perl program, and one fork per guest per tick cost ~90% of a host core and a 728 MB cgroup peak. Direct QMP is 0.33 CPU-seconds per 88s and 23 MB RSS. QEMU 11 here dumps PNG natively; the fallback PPM encoder was verified pixel-identical to QEMU's own on a real framebuffer. Read-only throughout: screendump does not perturb the guest (method-rule 19), so reading VM 140's screen is not an experiment and does not take its lock.
60 lines
2 KiB
Bash
Executable file
60 lines
2 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Install / update the vmwatch fishtank service on the Proxmox host.
|
|
#
|
|
# What this puts on spicy (and nothing else -- no packages, no pip):
|
|
# /opt/vmwatch/vmwatch.py the service (Python 3 stdlib only)
|
|
# /etc/systemd/system/vmwatch.service
|
|
#
|
|
# Usage: tools/vmwatch-install.sh [host] (default host: spicy)
|
|
# tools/vmwatch-install.sh spicy --uninstall
|
|
set -euo pipefail
|
|
|
|
HOST="${1:-spicy}"
|
|
SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/vmwatch.py"
|
|
PORT="${VMWATCH_PORT:-8140}"
|
|
INTERVAL="${VMWATCH_INTERVAL:-5}"
|
|
|
|
if [[ "${2:-}" == "--uninstall" ]]; then
|
|
ssh "$HOST" 'systemctl disable --now vmwatch.service 2>/dev/null || true
|
|
rm -f /etc/systemd/system/vmwatch.service
|
|
rm -rf /opt/vmwatch
|
|
systemctl daemon-reload
|
|
echo "vmwatch removed from $(hostname)"'
|
|
exit 0
|
|
fi
|
|
|
|
echo "installing vmwatch on $HOST (port $PORT, interval ${INTERVAL}s)"
|
|
ssh "$HOST" "mkdir -p /opt/vmwatch"
|
|
scp -q "$SRC" "$HOST:/opt/vmwatch/vmwatch.py"
|
|
ssh "$HOST" "chmod 0755 /opt/vmwatch/vmwatch.py"
|
|
|
|
ssh "$HOST" "cat > /etc/systemd/system/vmwatch.service" <<UNIT
|
|
[Unit]
|
|
Description=vmwatch - live screen wall for the SOTS lab VMs
|
|
Documentation=file:///opt/vmwatch/vmwatch.py
|
|
After=network-online.target pve-guests.service
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
ExecStart=/usr/bin/python3 /opt/vmwatch/vmwatch.py --port ${PORT} --interval ${INTERVAL}
|
|
Restart=always
|
|
RestartSec=3
|
|
# read-only instrument: it only runs \`qm list\` and \`qm monitor ... screendump\`
|
|
NoNewPrivileges=yes
|
|
ProtectHome=yes
|
|
PrivateTmp=no
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
UNIT
|
|
|
|
# `enable --now` will not restart an already-running unit, so restart explicitly
|
|
ssh "$HOST" "systemctl daemon-reload \
|
|
&& systemctl enable vmwatch.service >/dev/null \
|
|
&& systemctl restart vmwatch.service && sleep 2 && systemctl is-active vmwatch.service"
|
|
IP=$(ssh "$HOST" "hostname -I | tr ' ' '\n' | grep -E '^192\.168\.' | head -1")
|
|
echo
|
|
echo "http://${IP}:${PORT}/"
|