Skip to content

Commit

Permalink
feat: Implement graceful shutdown (#81)
Browse files Browse the repository at this point in the history
  • Loading branch information
kroese authored Jan 23, 2024
1 parent 8474b91 commit 08f040a
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 23 deletions.
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ services:
- 3389:3389/tcp
- 3389:3389/udp
stop_grace_period: 2m
restart: unless-stopped
restart: on-failure
6 changes: 3 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@ services:
- 3389:3389/tcp
- 3389:3389/udp
stop_grace_period: 2m
restart: unless-stopped
restart: on-failure
```
Via `docker run`

```bash
docker run -it --rm -p 8006:8006 --device=/dev/kvm --cap-add NET_ADMIN dockurr/windows
docker run -it --rm -p 8006:8006 --device=/dev/kvm --cap-add NET_ADMIN --stop-timeout 120 dockurr/windows
```

## FAQ
Expand Down Expand Up @@ -152,7 +152,7 @@ docker run -it --rm -p 8006:8006 --device=/dev/kvm --cap-add NET_ADMIN dockurr/w
VERSION: "https://example.com/win.iso"
```

Alternatively, you can also place a file called `custom.iso` in an empty `/storage` folder to skip the download.
Alternatively, you can also rename a local file to `custom.iso` and place it in an empty `/storage` folder to skip the download.

* ### How do I pass-through a disk?

Expand Down
16 changes: 12 additions & 4 deletions src/entry.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,31 @@
set -Eeuo pipefail

APP="Windows"
export BOOT_MODE=windows
BOOT_MODE="windows"
SUPPORT="https://github.com/dockur/windows"

cd /run

. reset.sh # Initialize system
. install.sh # Get bootdisk
. install.sh # Run installation
. disk.sh # Initialize disks
. display.sh # Initialize graphics
. network.sh # Initialize network
. boot.sh # Configure boot
. proc.sh # Initialize processor
. power.sh # Configure shutdown
. config.sh # Configure arguments

trap - ERR

info "Booting $APP using $VERS..."
[[ "$DEBUG" == [Yy1]* ]] && echo "Arguments: $ARGS" && echo

[[ "$DEBUG" == [Yy1]* ]] && set -x
exec qemu-system-x86_64 ${ARGS:+ $ARGS}
{ qemu-system-x86_64 ${ARGS:+ $ARGS} >"$QEMU_OUT" 2>"$QEMU_LOG"; rc=$?; } || :
(( rc != 0 )) && error "$(<"$QEMU_LOG")" && exit 15

terminal
tail -fn +0 "$QEMU_LOG" 2>/dev/null &
cat "$QEMU_TERM" 2>/dev/null & wait $! || :

sleep 1 && finish 0
17 changes: 2 additions & 15 deletions src/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,6 @@ set -Eeuo pipefail
[[ "${VERSION,,}" == "win16" ]] && VERSION="win2016-eval"
[[ "${VERSION,,}" == "win2016" ]] && VERSION="win2016-eval"

if [[ "${VERSION,,}" == "tiny10" ]]; then
VERSION="https://archive.org/download/tiny-10-23-h2/tiny10%20x64%2023h2.iso"
fi

if [[ "${VERSION,,}" == "tiny11" ]]; then
VERSION="https://archive.org/download/tiny-11-core-x-64-beta-1/tiny11%20core%20x64%20beta%201.iso"
fi

CUSTOM="custom.iso"

[ ! -f "$STORAGE/$CUSTOM" ] && CUSTOM="Custom.iso"
Expand Down Expand Up @@ -91,14 +83,9 @@ else
fi

html "$MSG"
TMP="$STORAGE/tmp"

if [ -z "$MANUAL" ]; then

MANUAL="N"
[[ "${BASE,,}" == "tiny10"* ]] && MANUAL="Y"

fi
TMP="$STORAGE/tmp"
[ -z "$MANUAL" ] && MANUAL="N"

if [ -f "$STORAGE/$BASE" ]; then

Expand Down
151 changes: 151 additions & 0 deletions src/power.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#!/usr/bin/env bash
set -Eeuo pipefail

# Configure QEMU for graceful shutdown

This comment has been minimized.

Copy link
@brankko

brankko Jan 23, 2024

Thanks for this. In a combination with going to hibernation on shutdown signal in Win guest machine, this should work like a charm.


QEMU_TERM=""
QEMU_PORT=7100
QEMU_TIMEOUT=110
QEMU_PID="/run/shm/qemu.pid"
QEMU_LOG="/run/shm/qemu.log"
QEMU_OUT="/run/shm/qemu.out"
QEMU_END="/run/shm/qemu.end"

rm -f /run/shm/qemu.*
touch "$QEMU_LOG"

_trap() {
func="$1" ; shift
for sig ; do
trap "$func $sig" "$sig"
done
}

finish() {

local pid
local reason=$1

if [ -f "$QEMU_PID" ]; then

pid=$(<"$QEMU_PID")
echo && error "Forcefully terminating Windows, reason: $reason..."
{ kill -15 "$pid" || true; } 2>/dev/null

while isAlive "$pid"; do
sleep 1
# Workaround for zombie pid
[ ! -f "$QEMU_PID" ] && break
done
fi

pid="/var/run/tpm.pid"
[ -f "$pid" ] && pKill "$(<"$pid")"

closeNetwork

sleep 1
echo && echo "❯ Shutdown completed!"

exit "$reason"
}

terminal() {

local dev=""

if [ -f "$QEMU_OUT" ]; then

local msg
msg=$(<"$QEMU_OUT")

if [ -n "$msg" ]; then

if [[ "${msg,,}" != "char"* || "$msg" != *"serial0)" ]]; then
echo "$msg"
fi

dev="${msg#*/dev/p}"
dev="/dev/p${dev%% *}"

fi
fi

if [ ! -c "$dev" ]; then
dev=$(echo 'info chardev' | nc -q 1 -w 1 localhost "$QEMU_PORT" | tr -d '\000')
dev="${dev#*serial0}"
dev="${dev#*pty:}"
dev="${dev%%$'\n'*}"
dev="${dev%%$'\r'*}"
fi

if [ ! -c "$dev" ]; then
error "Device '$dev' not found!"
finish 34 && return 34
fi

QEMU_TERM="$dev"
return 0
}

_graceful_shutdown() {

local code=$?

set +e

if [ -f "$QEMU_END" ]; then
echo && info "Received $1 while already shutting down..."
return
fi

touch "$QEMU_END"
echo && info "Received $1, sending ACPI shutdown signal..."

if [ ! -f "$QEMU_PID" ]; then
echo && error "QEMU PID file does not exist?"
finish "$code" && return "$code"
fi

local pid=""
pid=$(<"$QEMU_PID")

if ! isAlive "$pid"; then
echo && error "QEMU process does not exist?"
finish "$code" && return "$code"
fi

# Send ACPI shutdown signal
echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_PORT}" > /dev/null

local cnt=0
while [ "$cnt" -lt "$QEMU_TIMEOUT" ]; do

sleep 1
cnt=$((cnt+1))

! isAlive "$pid" && break
# Workaround for zombie pid
[ ! -f "$QEMU_PID" ] && break

info "Waiting for Windows to shutdown... ($cnt/$QEMU_TIMEOUT)"

# Send ACPI shutdown signal
echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_PORT}" > /dev/null

done

if [ "$cnt" -ge "$QEMU_TIMEOUT" ]; then
echo && error "Shutdown timeout reached, aborting..."
fi

finish "$code" && return "$code"
}

SERIAL="pty"
MONITOR="telnet:localhost:$QEMU_PORT,server,nowait,nodelay"
MONITOR="$MONITOR -daemonize -D $QEMU_LOG -pidfile $QEMU_PID"

_trap _graceful_shutdown SIGTERM SIGHUP SIGINT SIGABRT SIGQUIT

return 0

0 comments on commit 08f040a

Please sign in to comment.