Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement graceful shutdown #81

Merged
merged 10 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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

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