Prechádzať zdrojové kódy

feat: Implement graceful shutdown (#81)

Kroese 1 rok pred
rodič
commit
08f040a819
5 zmenil súbory, kde vykonal 169 pridanie a 23 odobranie
  1. 1 1
      docker-compose.yml
  2. 3 3
      readme.md
  3. 12 4
      src/entry.sh
  4. 2 15
      src/install.sh
  5. 151 0
      src/power.sh

+ 1 - 1
docker-compose.yml

@@ -12,4 +12,4 @@ services:
       - 3389:3389/tcp
       - 3389:3389/udp
     stop_grace_period: 2m
-    restart: unless-stopped
+    restart: on-failure

+ 3 - 3
readme.md

@@ -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
src/entry.sh

@@ -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
src/install.sh

@@ -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
 

+ 151 - 0
src/power.sh

@@ -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