1
0

install.sh 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. #!/usr/bin/env bash
  2. set -Eeuo pipefail
  3. : "${MANUAL:=""}"
  4. : "${VERSION:="win11x64"}"
  5. if [[ "${VERSION}" == \"*\" || "${VERSION}" == \'*\' ]]; then
  6. VERSION="${VERSION:1:-1}"
  7. fi
  8. [[ "${VERSION,,}" == "11" ]] && VERSION="win11x64"
  9. [[ "${VERSION,,}" == "win11" ]] && VERSION="win11x64"
  10. [[ "${VERSION,,}" == "10" ]] && VERSION="win10x64"
  11. [[ "${VERSION,,}" == "win10" ]] && VERSION="win10x64"
  12. [[ "${VERSION,,}" == "8" ]] && VERSION="win81x64"
  13. [[ "${VERSION,,}" == "81" ]] && VERSION="win81x64"
  14. [[ "${VERSION,,}" == "8.1" ]] && VERSION="win81x64"
  15. [[ "${VERSION,,}" == "win81" ]] && VERSION="win81x64"
  16. [[ "${VERSION,,}" == "win8" ]] && VERSION="win81x64"
  17. [[ "${VERSION,,}" == "22" ]] && VERSION="win2022-eval"
  18. [[ "${VERSION,,}" == "2022" ]] && VERSION="win2022-eval"
  19. [[ "${VERSION,,}" == "win22" ]] && VERSION="win2022-eval"
  20. [[ "${VERSION,,}" == "win2022" ]] && VERSION="win2022-eval"
  21. [[ "${VERSION,,}" == "19" ]] && VERSION="win2019-eval"
  22. [[ "${VERSION,,}" == "2019" ]] && VERSION="win2019-eval"
  23. [[ "${VERSION,,}" == "win19" ]] && VERSION="win2019-eval"
  24. [[ "${VERSION,,}" == "win2019" ]] && VERSION="win2019-eval"
  25. [[ "${VERSION,,}" == "16" ]] && VERSION="win2016-eval"
  26. [[ "${VERSION,,}" == "2016" ]] && VERSION="win2016-eval"
  27. [[ "${VERSION,,}" == "win16" ]] && VERSION="win2016-eval"
  28. [[ "${VERSION,,}" == "win2016" ]] && VERSION="win2016-eval"
  29. CUSTOM="custom.iso"
  30. [ ! -f "$STORAGE/$CUSTOM" ] && CUSTOM="Custom.iso"
  31. [ ! -f "$STORAGE/$CUSTOM" ] && CUSTOM="custom.ISO"
  32. [ ! -f "$STORAGE/$CUSTOM" ] && CUSTOM="CUSTOM.ISO"
  33. [ ! -f "$STORAGE/$CUSTOM" ] && CUSTOM="custom.img"
  34. [ ! -f "$STORAGE/$CUSTOM" ] && CUSTOM="Custom.img"
  35. [ ! -f "$STORAGE/$CUSTOM" ] && CUSTOM="custom.IMG"
  36. [ ! -f "$STORAGE/$CUSTOM" ] && CUSTOM="CUSTOM.IMG"
  37. TMP="$STORAGE/tmp"
  38. DIR="$TMP/unpack"
  39. FB="falling back to manual installation!"
  40. ETFS="boot/etfsboot.com"
  41. EFISYS="efi/microsoft/boot/efisys_noprompt.bin"
  42. replaceXML() {
  43. local dir="$1"
  44. local asset="$2"
  45. local path="$dir/autounattend.xml"
  46. [ -f "$path" ] && cp "$asset" "$path"
  47. path="$dir/Autounattend.xml"
  48. [ -f "$path" ] && cp "$asset" "$path"
  49. path="$dir/AutoUnattend.xml"
  50. [ -f "$path" ] && cp "$asset" "$path"
  51. path="$dir/autounattend.XML"
  52. [ -f "$path" ] && cp "$asset" "$path"
  53. path="$dir/Autounattend.XML"
  54. [ -f "$path" ] && cp "$asset" "$path"
  55. path="$dir/AutoUnattend.XML"
  56. [ -f "$path" ] && cp "$asset" "$path"
  57. path="$dir/AUTOUNATTEND.xml"
  58. [ -f "$path" ] && cp "$asset" "$path"
  59. path="$dir/AUTOUNATTEND.XML"
  60. [ -f "$path" ] && cp "$asset" "$path"
  61. return 0
  62. }
  63. hasDisk() {
  64. [ -b "${DEVICE:-}" ] && return 0
  65. if [ -f "$STORAGE/data.img" ] || [ -f "$STORAGE/data.qcow2" ]; then
  66. return 0
  67. fi
  68. return 1
  69. }
  70. skipInstall() {
  71. if hasDisk && [ -f "$STORAGE/windows.boot" ]; then
  72. return 0
  73. fi
  74. return 1
  75. }
  76. finishInstall() {
  77. local iso="$1"
  78. # Mark ISO as prepared via magic byte
  79. printf '\x16' | dd of="$iso" bs=1 seek=0 count=1 conv=notrunc status=none
  80. rm -f "$STORAGE/windows.boot"
  81. cp /run/version "$STORAGE/windows.ver"
  82. rm -rf "$TMP"
  83. return 0
  84. }
  85. startInstall() {
  86. local magic
  87. local msg="Windows is being started, please wait..."
  88. if [ -f "$STORAGE/$CUSTOM" ]; then
  89. EXTERNAL="Y"
  90. BASE="$CUSTOM"
  91. else
  92. CUSTOM=""
  93. if [[ "${VERSION,,}" == "http"* ]]; then
  94. EXTERNAL="Y"
  95. else
  96. EXTERNAL="N"
  97. fi
  98. if [[ "$EXTERNAL" != [Yy1]* ]]; then
  99. BASE="$VERSION.iso"
  100. if ! skipInstall && [ ! -f "$STORAGE/$BASE" ]; then
  101. msg="Windows is being downloaded, please wait..."
  102. fi
  103. else
  104. BASE=$(basename "${VERSION%%\?*}")
  105. : "${BASE//+/ }"; printf -v BASE '%b' "${_//%/\\x}"
  106. BASE=$(echo "$BASE" | sed -e 's/[^A-Za-z0-9._-]/_/g')
  107. if ! skipInstall && [ ! -f "$STORAGE/$BASE" ]; then
  108. msg="Image '$BASE' is being downloaded, please wait..."
  109. fi
  110. fi
  111. [[ "${BASE,,}" == "custom."* ]] && BASE="windows.iso"
  112. fi
  113. html "$msg"
  114. [ -z "$MANUAL" ] && MANUAL="N"
  115. if [ -f "$STORAGE/$BASE" ]; then
  116. # Check if the ISO was already processed by our script
  117. magic=$(dd if="$STORAGE/$BASE" seek=0 bs=1 count=1 status=none | tr -d '\000')
  118. magic="$(printf '%s' "$magic" | od -A n -t x1 -v | tr -d ' \n')"
  119. if [[ "$magic" == "16" ]]; then
  120. if hasDisk || [[ "$MANUAL" = [Yy1]* ]]; then
  121. return 1
  122. fi
  123. fi
  124. EXTERNAL="Y"
  125. CUSTOM="$BASE"
  126. else
  127. if skipInstall; then
  128. BASE=""
  129. return 1
  130. fi
  131. fi
  132. mkdir -p "$TMP"
  133. if [ ! -f "$STORAGE/$CUSTOM" ]; then
  134. CUSTOM=""
  135. ISO="$TMP/$BASE"
  136. else
  137. ISO="$STORAGE/$CUSTOM"
  138. fi
  139. rm -f "$TMP/$BASE"
  140. return 0
  141. }
  142. downloadImage() {
  143. local iso="$1"
  144. local url="$2"
  145. local progress
  146. rm -f "$iso"
  147. if [[ "$EXTERNAL" != [Yy1]* ]]; then
  148. cd "$TMP"
  149. /run/mido.sh "$url"
  150. cd /run
  151. [ ! -f "$iso" ] && error "Failed to download $url" && exit 61
  152. return 0
  153. fi
  154. info "Downloading $BASE as boot image..."
  155. # Check if running with interactive TTY or redirected to docker log
  156. if [ -t 1 ]; then
  157. progress="--progress=bar:noscroll"
  158. else
  159. progress="--progress=dot:giga"
  160. fi
  161. { wget "$url" -O "$iso" -q --no-check-certificate --show-progress "$progress"; rc=$?; } || :
  162. (( rc != 0 )) && error "Failed to download $url, reason: $rc" && exit 60
  163. [ ! -f "$iso" ] && error "Failed to download $url" && exit 61
  164. return 0
  165. }
  166. extractImage() {
  167. local iso="$1"
  168. local dir="$2"
  169. local size size_gb space space_gb
  170. local msg="Extracting downloaded ISO image..."
  171. [ -n "$CUSTOM" ] && msg="Extracting local ISO image..."
  172. info "$msg" && html "$msg"
  173. size=$(stat -c%s "$iso")
  174. size_gb=$(( (size + 1073741823)/1073741824 ))
  175. space=$(df --output=avail -B 1 "$TMP" | tail -n 1)
  176. space_gb=$(( (space + 1073741823)/1073741824 ))
  177. if ((size<10000000)); then
  178. error "Invalid ISO file: Size is smaller than 10 MB" && exit 62
  179. fi
  180. if (( size > space )); then
  181. error "Not enough free space in $STORAGE, have $space_gb GB available but need at least $size_gb GB." && exit 63
  182. fi
  183. rm -rf "$dir"
  184. 7z x "$iso" -o"$dir" > /dev/null
  185. if [ ! -f "$dir/$ETFS" ] || [ ! -f "$dir/$EFISYS" ]; then
  186. if [ ! -f "$dir/$ETFS" ]; then
  187. warn "failed to locate file 'etfsboot.com' in ISO image, $FB"
  188. else
  189. warn "failed to locate file 'efisys_noprompt.bin' in ISO image, $FB"
  190. fi
  191. return 1
  192. fi
  193. [ -z "$CUSTOM" ] && rm -f "$iso"
  194. return 0
  195. }
  196. findVersion() {
  197. local name="$1"
  198. local detected=""
  199. [[ "${name,,}" == *"windows 11"* ]] && detected="win11x64"
  200. [[ "${name,,}" == *"windows 10"* ]] && detected="win10x64"
  201. [[ "${name,,}" == *"windows 8"* ]] && detected="win81x64"
  202. [[ "${name,,}" == *"server 2022"* ]] && detected="win2022-eval"
  203. [[ "${name,,}" == *"server 2019"* ]] && detected="win2019-eval"
  204. [[ "${name,,}" == *"server 2016"* ]] && detected="win2016-eval"
  205. echo "$detected"
  206. return 0
  207. }
  208. selectXML() {
  209. local dir="$1"
  210. local tag result name name2 detected
  211. XML=""
  212. [[ "$MANUAL" == [Yy1]* ]] && return 0
  213. if [[ "$EXTERNAL" != [Yy1]* ]] && [ -z "$CUSTOM" ]; then
  214. XML="$VERSION.xml"
  215. [ -f "/run/assets/$XML" ] && return 0
  216. fi
  217. info "Detecting Windows version from ISO image..."
  218. local loc="$dir/sources/install.wim"
  219. [ ! -f "$loc" ] && loc="$dir/sources/install.esd"
  220. if [ ! -f "$loc" ]; then
  221. warn "failed to locate 'install.wim' or 'install.esd' in ISO image, $FB"
  222. return 0
  223. fi
  224. tag="DISPLAYNAME"
  225. result=$(wimlib-imagex info -xml "$loc" | tr -d '\000')
  226. name=$(sed -n "/$tag/{s/.*<$tag>\(.*\)<\/$tag>.*/\1/;p}" <<< "$result")
  227. detected=$(findVersion "$name")
  228. if [ -z "$detected" ]; then
  229. tag="PRODUCTNAME"
  230. name2=$(sed -n "/$tag/{s/.*<$tag>\(.*\)<\/$tag>.*/\1/;p}" <<< "$result")
  231. [ -z "$name" ] && name="$name2"
  232. detected=$(findVersion "$name2")
  233. fi
  234. if [ -n "$detected" ]; then
  235. if [ -f "/run/assets/$detected.xml" ]; then
  236. XML="$detected.xml"
  237. echo "Detected image of type '$detected', which supports automatic installation."
  238. else
  239. warn "detected image of type '$detected', but no matching XML file exists, $FB."
  240. fi
  241. else
  242. if [ -z "$name" ]; then
  243. warn "failed to detect Windows version from image, $FB"
  244. else
  245. if [[ "${name,,}" == "windows 7" ]]; then
  246. warn "detected Windows 7 image, $FB"
  247. else
  248. warn "failed to detect Windows version from string '$name', $FB"
  249. fi
  250. fi
  251. fi
  252. return 0
  253. }
  254. updateImage() {
  255. local dir="$1"
  256. local asset="$2"
  257. local index result
  258. [ ! -f "$asset" ] && return 0
  259. replaceXML "$dir" "$asset"
  260. local loc="$dir/sources/boot.wim"
  261. [ ! -f "$loc" ] && loc="$dir/sources/boot.esd"
  262. if [ ! -f "$loc" ]; then
  263. warn "failed to locate 'boot.wim' or 'boot.esd' in ISO image, $FB"
  264. return 0
  265. fi
  266. info "Adding XML file for automatic installation..."
  267. index="1"
  268. result=$(wimlib-imagex info -xml "$loc" | tr -d '\000')
  269. if [[ "${result^^}" == *"<IMAGE INDEX=\"2\">"* ]]; then
  270. index="2"
  271. fi
  272. wimlib-imagex update "$loc" "$index" --command "add $asset /autounattend.xml" > /dev/null
  273. return 0
  274. }
  275. buildImage() {
  276. local dir="$1"
  277. local cat="BOOT.CAT"
  278. local label="${BASE%.*}"
  279. local size size_gb space space_gb
  280. label="${label::30}"
  281. local out="$TMP/$label.tmp"
  282. rm -f "$out"
  283. local msg="Generating updated ISO image..."
  284. info "$msg" && html "$msg"
  285. size=$(du -h -b --max-depth=0 "$dir" | cut -f1)
  286. size_gb=$(( (size + 1073741823)/1073741824 ))
  287. space=$(df --output=avail -B 1 "$TMP" | tail -n 1)
  288. space_gb=$(( (space + 1073741823)/1073741824 ))
  289. if (( size > space )); then
  290. error "Not enough free space in $STORAGE, have $space_gb GB available but need at least $size_gb GB." && exit 63
  291. fi
  292. genisoimage -b "$ETFS" -no-emul-boot -c "$cat" -iso-level 4 -J -l -D -N -joliet-long -relaxed-filenames -quiet -V "$label" -udf \
  293. -boot-info-table -eltorito-alt-boot -eltorito-boot "$EFISYS" -no-emul-boot -o "$out" -allow-limited-size "$dir"
  294. [ -n "$CUSTOM" ] && rm -f "$STORAGE/$CUSTOM"
  295. if [ -f "$STORAGE/$BASE" ]; then
  296. error "File $STORAGE/$BASE does already exist ?!" && exit 64
  297. fi
  298. mv "$out" "$STORAGE/$BASE"
  299. return 0
  300. }
  301. ######################################
  302. if ! startInstall; then
  303. rm -rf "$TMP"
  304. return 0
  305. fi
  306. if [ ! -f "$ISO" ]; then
  307. downloadImage "$ISO" "$VERSION"
  308. fi
  309. if ! extractImage "$ISO" "$DIR"; then
  310. if [[ "$ISO" != "$STORAGE/$BASE" ]]; then
  311. mv -f "$ISO" "$STORAGE/$BASE"
  312. fi
  313. finishInstall "$STORAGE/$BASE"
  314. return 0
  315. fi
  316. selectXML "$DIR"
  317. updateImage "$DIR" "/run/assets/$XML"
  318. buildImage "$DIR"
  319. finishInstall "$STORAGE/$BASE"
  320. html "Successfully prepared image for installation..."
  321. return 0