Files
dockur-windows/src/power.sh
T
2026-07-07 02:32:00 +02:00

341 lines
6.1 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
set -Eeuo pipefail
: "${SHUTDOWN:="Y"}" # Graceful ACPI shutdown
: "${TIMEOUT:="115"}" # QEMU termination timeout
# Configure QEMU for graceful shutdown
QEMU_PTY="$QEMU_DIR/qemu.pty"
QEMU_END="$QEMU_DIR/qemu.end"
_trap() {
local func="$1"; shift
local sig
TRAP_PID=$BASHPID
for sig; do
trap "$func $sig" "$sig"
done
return 0
}
app() {
echo "$APP" && return 0
}
signalCode() {
local sig="$1"
case "$sig" in
SIGHUP) echo 129 ;;
SIGINT) echo 130 ;;
SIGQUIT) echo 131 ;;
SIGABRT) echo 134 ;;
SIGTERM) echo 143 ;;
*) echo 0 ;;
esac
return 0
}
displayReason() {
local reason="$1"
case "$reason" in
129 ) echo "SIGHUP" ;;
130 ) echo "SIGINT" ;;
131 ) echo "SIGQUIT" ;;
134 ) echo "SIGABRT" ;;
143 ) echo "SIGTERM" ;;
* ) echo "$reason" ;;
esac
return 0
}
readQemuPid() {
local file="$1"
local __var="$2"
local pid=""
[ -s "$file" ] || return 1
read -r pid <"$file" || return 1
[ -n "$pid" ] || return 1
printf -v "$__var" '%s' "$pid"
return 0
}
bootFailed() {
local fail=""
if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then
grep -Fq "No bootable device." "$QEMU_PTY" && fail="y"
grep -Fq "BOOTMGR is missing" "$QEMU_PTY" && fail="y"
fi
[ -n "$fail" ]
}
boot() {
[ -f "$QEMU_END" ] && return 0
if [ -s "$QEMU_PTY" ]; then
if [ "$(stat -c%s "$QEMU_PTY")" -gt 7 ]; then
if ! bootFailed; then
info "$(app) started successfully, visit http://127.0.0.1:8006/ to view the screen..."
return 0
fi
fi
fi
error "Timeout while waiting for QEMU to boot the machine, aborting..."
sKill "$QEMU_PID"
return 0
}
legacyBootReady() {
local last
local bios="Booting from Hard"
last=$(grep "^Booting.*" "$QEMU_PTY" | tail -1)
[[ "${last,,}" != "${bios,,}"* ]] && return 1
grep -Fq "No bootable device." "$QEMU_PTY" && return 1
grep -Fq "BOOTMGR is missing" "$QEMU_PTY" && return 1
return 0
}
ready() {
[ -f "$STORAGE/windows.boot" ] && return 0
[ ! -s "$QEMU_PTY" ] && return 1
if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then
legacyBootReady && return 0
return 1
fi
local line="\"Windows Boot Manager\""
grep -Fq "$line" "$QEMU_PTY" && return 0
return 1
}
forceKillQemu() {
local reason="$1"
local pid=""
local display
if readQemuPid "$QEMU_PID" pid; then
if isAlive "$pid"; then
display=$(displayReason "$reason")
error "Forcefully terminating $(app), reason: $display..."
{ disown "$pid" || :; kill -9 -- "$pid" || :; } 2>/dev/null
fi
fi
return 0
}
markWindowsBooted() {
local file="$STORAGE/windows.boot"
if [ -f "$file" ] || [ ! -f "$BOOT" ]; then
return 0
fi
# Remove CD-ROM ISO after install
if ready; then
touch "$file"
! setOwner "$file" && error "Failed to set the owner for \"$file\" !"
if ! disabled "$REMOVE"; then
rm -f "$BOOT" 2>/dev/null || true
fi
fi
return 0
}
cleanupHelpers() {
local pids=( "${SMB_PID:-}" "${NMB_PID:-}" "${DDN_PID:-}" "${TPM_PID:-}" "${WSD_PID:-}" \
"${WEB_PID:-}" "${PASST_PID:-}" "${DNSMASQ_PID:-}" "${BALLOONING_PID:-}" )
mKill "${pids[@]}"
closeNetwork
return 0
}
finish() {
local reason=$1
touch "$QEMU_END"
forceKillQemu "$reason"
if [ ! -f "$STORAGE/windows.boot" ]; then
markWindowsBooted
fi
cleanupHelpers
if ! waitPidFile "$QEMU_PID" 10; then
warn "Timed out while waiting for $(app) to exit!"
fi
(( reason != 1 )) && echo && echo " Shutdown completed!"
exit "$reason"
}
normalizeTimeout() {
local min
term_grace=3 # seconds before loop ends to send SIGTERM
cleanup_grace=3 # seconds reserved after the loop for cleanup
TIMEOUT=$(strip "$TIMEOUT")
if [[ ! "$TIMEOUT" =~ ^[0-9]+$ ]]; then
TIMEOUT=115
fi
if (( TIMEOUT >= 30 )); then
term_grace=5
cleanup_grace=5
elif (( TIMEOUT >= 15 )); then
term_grace=4
cleanup_grace=4
fi
min=$((term_grace + cleanup_grace + 1))
(( TIMEOUT < min )) && (( TIMEOUT = min ))
wait_until=$((TIMEOUT - cleanup_grace))
sigterm_at=$((wait_until - term_grace))
return 0
}
sendAcpiShutdown() {
# Send ACPI shutdown signal
if [ -S "$QEMU_DIR/monitor.sock" ]; then
nc -q 1 -w 1 -U "$QEMU_DIR/monitor.sock" &> /dev/null <<<'system_powerdown' || :
fi
return 0
}
abortDuringSetup() {
local code="$1"
info "Cannot send ACPI signal during $(app) setup, aborting..."
sKill "$QEMU_PID"
if ! waitPidFile "$QEMU_PID" 5; then
warn "Timed out while waiting for $(app) to exit!"
fi
finish "$code"
}
waitForShutdown() {
local cnt=0
local name="$1"
local pid="$2"
local slp
while (( cnt <= wait_until )); do
sleep 1 &
slp=$!
# Stop waiting if the process has exited
! isAlive "$pid" && break
# Workaround for stale/zombie QEMU pid file
[ ! -s "$QEMU_PID" ] && break
if (( cnt == sigterm_at )); then
info "${name^} is still running, sending SIGTERM... ($cnt/$wait_until)"
kill -15 -- "$pid" 2>/dev/null || :
elif (( cnt > 0 )); then
info "Waiting for $name to shut down... ($cnt/$wait_until)"
fi
sendAcpiShutdown
wait "$slp"
(( cnt++ ))
done
return 0
}
graceful_shutdown() {
local sig="$1"
local pid=""
local code=0
local name
local term_grace cleanup_grace
local sigterm_at=0 wait_until=0
[[ $BASHPID != "$TRAP_PID" ]] && return
code=$(signalCode "$sig")
if [ -f "$QEMU_END" ]; then
echo && info "Received $1 signal while already shutting down..."
return
fi
set +e
touch "$QEMU_END"
echo && info "Received $1 signal, sending ACPI shutdown signal..."
if ! readQemuPid "$QEMU_PID" pid; then
warn "QEMU PID file ($QEMU_PID) does not exist?"
finish "$code"
fi
if ! isAlive "$pid"; then
warn "QEMU process with PID $pid does not exist?"
finish "$code"
fi
if ! ready; then
abortDuringSetup "$code"
fi
name="$(app)"
normalizeTimeout
waitForShutdown "$name" "$pid"
finish "$code"
}
! enabled "$SHUTDOWN" && return 0
[ -n "${QEMU_TIMEOUT:-}" ] && TIMEOUT="$QEMU_TIMEOUT"
_trap graceful_shutdown SIGTERM SIGHUP SIGABRT SIGQUIT
return 0