power.sh 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. #!/usr/bin/env bash
  2. set -Eeuo pipefail
  3. # Configure QEMU for graceful shutdown
  4. QEMU_TERM=""
  5. QEMU_PORT=7100
  6. QEMU_TIMEOUT=110
  7. QEMU_DIR="/run/shm"
  8. QEMU_PID="$QEMU_DIR/qemu.pid"
  9. QEMU_PTY="$QEMU_DIR/qemu.pty"
  10. QEMU_LOG="$QEMU_DIR/qemu.log"
  11. QEMU_OUT="$QEMU_DIR/qemu.out"
  12. QEMU_END="$QEMU_DIR/qemu.end"
  13. rm -f "$QEMU_DIR/qemu.*"
  14. touch "$QEMU_LOG"
  15. _trap() {
  16. func="$1" ; shift
  17. for sig ; do
  18. trap "$func $sig" "$sig"
  19. done
  20. }
  21. boot() {
  22. [ -f "$QEMU_END" ] && return 0
  23. if [ -s "$QEMU_PTY" ]; then
  24. if [ "$(stat -c%s "$QEMU_PTY")" -gt 7 ]; then
  25. local fail=""
  26. if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then
  27. grep -Fq "No bootable device." "$QEMU_PTY" && fail="y"
  28. grep -Fq "BOOTMGR is missing" "$QEMU_PTY" && fail="y"
  29. fi
  30. if [ -z "$fail" ]; then
  31. info "Windows started succesfully, visit http://127.0.0.1:8006/ to view the screen..."
  32. return 0
  33. fi
  34. fi
  35. fi
  36. error "Timeout while waiting for QEMU to boot the machine!"
  37. local pid
  38. pid=$(<"$QEMU_PID")
  39. { kill -15 "$pid" || true; } 2>/dev/null
  40. return 0
  41. }
  42. ready() {
  43. [ -f "$STORAGE/windows.boot" ] && return 0
  44. [ ! -s "$QEMU_PTY" ] && return 1
  45. if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then
  46. local last
  47. local bios="Booting from Hard"
  48. last=$(grep "^Booting.*" "$QEMU_PTY" | tail -1)
  49. [[ "${last,,}" != "${bios,,}"* ]] && return 1
  50. grep -Fq "No bootable device." "$QEMU_PTY" && return 1
  51. grep -Fq "BOOTMGR is missing" "$QEMU_PTY" && return 1
  52. return 0
  53. fi
  54. local line="\"Windows Boot Manager\""
  55. grep -Fq "$line" "$QEMU_PTY" && return 0
  56. return 1
  57. }
  58. finish() {
  59. local pid
  60. local reason=$1
  61. touch "$QEMU_END"
  62. if [ -s "$QEMU_PID" ]; then
  63. pid=$(<"$QEMU_PID")
  64. error "Forcefully terminating Windows, reason: $reason..."
  65. { kill -15 "$pid" || true; } 2>/dev/null
  66. while isAlive "$pid"; do
  67. sleep 1
  68. # Workaround for zombie pid
  69. [ ! -s "$QEMU_PID" ] && break
  70. done
  71. fi
  72. if [ ! -f "$STORAGE/windows.boot" ] && [ -f "$BOOT" ]; then
  73. # Remove CD-ROM ISO after install
  74. if ready; then
  75. touch "$STORAGE/windows.boot"
  76. if [[ "$REMOVE" != [Nn]* ]]; then
  77. rm -f "$BOOT" 2>/dev/null || true
  78. fi
  79. fi
  80. fi
  81. pid="/var/run/tpm.pid"
  82. [ -s "$pid" ] && pKill "$(<"$pid")"
  83. pid="/var/run/wsdd.pid"
  84. [ -s "$pid" ] && pKill "$(<"$pid")"
  85. fKill "smbd"
  86. closeNetwork
  87. sleep 0.5
  88. echo "❯ Shutdown completed!"
  89. exit "$reason"
  90. }
  91. terminal() {
  92. local dev=""
  93. if [ -s "$QEMU_OUT" ]; then
  94. local msg
  95. msg=$(<"$QEMU_OUT")
  96. if [ -n "$msg" ]; then
  97. if [[ "${msg,,}" != "char"* || "$msg" != *"serial0)" ]]; then
  98. echo "$msg"
  99. fi
  100. dev="${msg#*/dev/p}"
  101. dev="/dev/p${dev%% *}"
  102. fi
  103. fi
  104. if [ ! -c "$dev" ]; then
  105. dev=$(echo 'info chardev' | nc -q 1 -w 1 localhost "$QEMU_PORT" | tr -d '\000')
  106. dev="${dev#*serial0}"
  107. dev="${dev#*pty:}"
  108. dev="${dev%%$'\n'*}"
  109. dev="${dev%%$'\r'*}"
  110. fi
  111. if [ ! -c "$dev" ]; then
  112. error "Device '$dev' not found!"
  113. finish 34 && return 34
  114. fi
  115. QEMU_TERM="$dev"
  116. return 0
  117. }
  118. _graceful_shutdown() {
  119. local code=$?
  120. set +e
  121. if [ -f "$QEMU_END" ]; then
  122. info "Received $1 while already shutting down..."
  123. return
  124. fi
  125. touch "$QEMU_END"
  126. info "Received $1, sending ACPI shutdown signal..."
  127. if [ ! -s "$QEMU_PID" ]; then
  128. error "QEMU PID file does not exist?"
  129. finish "$code" && return "$code"
  130. fi
  131. local pid=""
  132. pid=$(<"$QEMU_PID")
  133. if ! isAlive "$pid"; then
  134. error "QEMU process does not exist?"
  135. finish "$code" && return "$code"
  136. fi
  137. if ! ready; then
  138. info "Cannot send ACPI signal during Windows setup, aborting..."
  139. finish "$code" && return "$code"
  140. fi
  141. # Send ACPI shutdown signal
  142. echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_PORT}" > /dev/null
  143. local cnt=0
  144. while [ "$cnt" -lt "$QEMU_TIMEOUT" ]; do
  145. sleep 1
  146. cnt=$((cnt+1))
  147. ! isAlive "$pid" && break
  148. # Workaround for zombie pid
  149. [ ! -s "$QEMU_PID" ] && break
  150. info "Waiting for Windows to shutdown... ($cnt/$QEMU_TIMEOUT)"
  151. # Send ACPI shutdown signal
  152. echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_PORT}" > /dev/null
  153. done
  154. if [ "$cnt" -ge "$QEMU_TIMEOUT" ]; then
  155. error "Shutdown timeout reached, aborting..."
  156. fi
  157. finish "$code" && return "$code"
  158. }
  159. SERIAL="pty"
  160. MONITOR="telnet:localhost:$QEMU_PORT,server,nowait,nodelay"
  161. MONITOR+=" -daemonize -D $QEMU_LOG -pidfile $QEMU_PID"
  162. _trap _graceful_shutdown SIGTERM SIGHUP SIGINT SIGABRT SIGQUIT
  163. return 0