58 lines
2.3 KiB
Bash
Executable File
58 lines
2.3 KiB
Bash
Executable File
#!/bin/bash
|
|
# =====================================================================
|
|
# Presses the virtual power button, from the unit's ExecStop=.
|
|
#
|
|
# WHY THIS EXISTS
|
|
#
|
|
# Stopping the unit used to SIGTERM qemu, which from inside the machine
|
|
# is indistinguishable from pulling the plug out of the wall. For a
|
|
# session that is fine - the overlay is thrown away either side of it -
|
|
# but a MAINTENANCE boot is the operator editing the base image that
|
|
# every future session runs from, and yanking power on that is how a
|
|
# frozen image acquires a dirty filesystem, or a half-written registry.
|
|
#
|
|
# ExecStop= runs while the main process is still alive, so this sends
|
|
# `system_powerdown` over QMP and returns. systemd then waits for qemu
|
|
# to exit by itself, up to TimeoutStopSec - and if the guest ignores the
|
|
# button, or has already hung, the SIGTERM still happens afterwards. So
|
|
# this is a courtesy with a deadline, not a new way to get stuck.
|
|
#
|
|
# Deliberately never fails: a stop path that can error is a stop path
|
|
# that leaves the unit in `failed` and the next start blocked.
|
|
# =====================================================================
|
|
set -u
|
|
|
|
SESSION="${1:-${OWUIVM_SESSION:-}}"
|
|
[[ "$SESSION" =~ ^[0-9a-f]{32}$ ]] || exit 0
|
|
SOCK="/run/owuivm/$SESSION/qmp.sock"
|
|
[ -S "$SOCK" ] || exit 0
|
|
|
|
/usr/bin/python3 - "$SOCK" <<'PY' 2>/dev/null || true
|
|
import json, socket, sys, time
|
|
path = sys.argv[1]
|
|
try:
|
|
s = socket.socket(socket.AF_UNIX); s.settimeout(5); s.connect(path)
|
|
f = s.makefile("rwb")
|
|
f.readline() # greeting
|
|
f.write(b'{"execute":"qmp_capabilities"}\n'); f.flush(); f.readline()
|
|
f.write(b'{"execute":"system_powerdown"}\n'); f.flush(); f.readline()
|
|
# Give the guest a moment to notice the button before systemd starts
|
|
# counting; an OS that has begun shutting down will not be helped by
|
|
# being asked twice.
|
|
deadline = time.monotonic() + 90
|
|
while time.monotonic() < deadline:
|
|
f.write(b'{"execute":"query-status"}\n'); f.flush()
|
|
line = f.readline()
|
|
if not line:
|
|
break # qemu has gone: done
|
|
try:
|
|
if not (json.loads(line).get("return") or {}).get("running", True):
|
|
break
|
|
except ValueError:
|
|
pass
|
|
time.sleep(1)
|
|
except Exception:
|
|
pass
|
|
PY
|
|
exit 0
|