Unverified Commit 08f040a8 authored by Kroese's avatar Kroese Committed by GitHub
Browse files

feat: Implement graceful shutdown (#81)

parent 8474b917
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -12,4 +12,4 @@ services:
      - 3389:3389/tcp
      - 3389:3389/udp
    stop_grace_period: 2m
    restart: unless-stopped
    restart: on-failure
+3 −3
Original line number Diff line number Diff line
@@ -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
@@ -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?

+12 −4
Original line number Diff line number Diff line
@@ -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
+2 −15
Original line number Diff line number Diff line
@@ -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"
@@ -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

src/power.sh

0 → 100644
+151 −0
Original line number Diff line number Diff line
#!/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