#!/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