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

feat: Remove ISO after installation (#83)

parent 08f040a8
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ info "Booting $APP using $VERS..."

terminal
tail -fn +0 "$QEMU_LOG" 2>/dev/null &
cat "$QEMU_TERM" 2>/dev/null & wait $! || :
cat "$QEMU_TERM" 2> /dev/null | tee "$QEMU_PTY" &
wait $! || :

sleep 1 && finish 0
+292 −215
Original line number Diff line number Diff line
@@ -4,6 +4,10 @@ set -Eeuo pipefail
: "${MANUAL:=""}"
: "${VERSION:="win11x64"}"

if [[ "${VERSION}" == \"*\" || "${VERSION}" == \'*\' ]]; then
  VERSION="${VERSION:1:-1}"
fi

[[ "${VERSION,,}" == "11" ]] && VERSION="win11x64"
[[ "${VERSION,,}" == "win11" ]] && VERSION="win11x64"

@@ -41,7 +45,75 @@ CUSTOM="custom.iso"
[ ! -f "$STORAGE/$CUSTOM" ] && CUSTOM="custom.IMG"
[ ! -f "$STORAGE/$CUSTOM" ] && CUSTOM="CUSTOM.IMG"

MSG="Windows is being started, please wait..."
TMP="$STORAGE/tmp"
DIR="$TMP/unpack"
FB="falling back to manual installation!"
ETFS="boot/etfsboot.com"
EFISYS="efi/microsoft/boot/efisys_noprompt.bin"

replaceXML() {

  local dir="$1"
  local asset="$2"

  local path="$dir/autounattend.xml"
  [ -f "$path" ] && mv -f "$asset" "$path"
  path="$dir/Autounattend.xml"
  [ -f "$path" ] && mv -f "$asset" "$path"
  path="$dir/AutoUnattend.xml"
  [ -f "$path" ] && mv -f "$asset" "$path"
  path="$dir/autounattend.XML"
  [ -f "$path" ] && mv -f "$asset" "$path"
  path="$dir/Autounattend.XML"
  [ -f "$path" ] && mv -f "$asset" "$path"
  path="$dir/AutoUnattend.XML"
  [ -f "$path" ] && mv -f "$asset" "$path"
  path="$dir/AUTOUNATTEND.xml"
  [ -f "$path" ] && mv -f "$asset" "$path"
  path="$dir/AUTOUNATTEND.XML"
  [ -f "$path" ] && mv -f "$asset" "$path"

  return 0
}

hasDisk() {

  [ -b "${DEVICE:-}" ] && return 0

  if [ -f "$STORAGE/data.img" ] || [ -f "$STORAGE/data.qcow2" ]; then
    return 0
  fi

  return 1
}

skipInstall() {

  if hasDisk && [ -f "$STORAGE/windows.boot" ]; then
    return 0
  fi

  return 1
}

finishInstall() {

  local iso="$1"

  # Mark ISO as prepared via magic byte
  printf '\x16' | dd of="$iso" bs=1 seek=0 count=1 conv=notrunc status=none

  rm -f "$STORAGE/windows.ver"
  rm -f "$STORAGE/windows.boot"
  cp /run/version "$STORAGE/windows.ver"

  rm -rf "$TMP"
  return 0
}

startInstall() {

  local msg="Windows is being started, please wait..."

  if [ -f "$STORAGE/$CUSTOM" ]; then

@@ -62,8 +134,8 @@ else

      BASE="$VERSION.iso"

    if [ ! -f "$STORAGE/$BASE" ]; then
      MSG="Windows is being downloaded, please wait..."
      if ! skipInstall && [ ! -f "$STORAGE/$BASE" ]; then
        msg="Windows is being downloaded, please wait..."
      fi

    else
@@ -72,8 +144,8 @@ else
      : "${BASE//+/ }"; printf -v BASE '%b' "${_//%/\\x}"
      BASE=$(echo "$BASE" | sed -e 's/[^A-Za-z0-9._-]/_/g')

    if [ ! -f "$STORAGE/$BASE" ]; then
      MSG="Image '$BASE' is being downloaded, please wait..."
      if ! skipInstall && [ ! -f "$STORAGE/$BASE" ]; then
        msg="Image '$BASE' is being downloaded, please wait..."
      fi

    fi
@@ -82,34 +154,20 @@ else

  fi

html "$MSG"
  html "$msg"

TMP="$STORAGE/tmp"
  [ -z "$MANUAL" ] && MANUAL="N"

  if [ -f "$STORAGE/$BASE" ]; then

    # Check if the ISO was already processed by our script
  MAGIC=$(dd if="$STORAGE/$BASE" seek=0 bs=1 count=1 status=none | tr -d '\000')
  MAGIC="$(printf '%s' "$MAGIC" | od -A n -t x1 -v | tr -d ' \n')"
    local magic=$(dd if="$STORAGE/$BASE" seek=0 bs=1 count=1 status=none | tr -d '\000')
    magic="$(printf '%s' "$magic" | od -A n -t x1 -v | tr -d ' \n')"

  if [[ "$MAGIC" == "16" ]]; then
    if [[ "$magic" == "16" ]]; then

    if [[ "$MANUAL" = [Yy1]* ]]; then
      rm -rf "$TMP"
      return 0
    fi

    FOUND="N"
    if [ -f "$STORAGE/data.img" ] || [ -f "$STORAGE/data.qcow2" ]; then
      FOUND="Y"
    else
      [ -b "${DEVICE:-}" ] && FOUND="Y"
    fi

    if [[ "$FOUND" == "Y" ]]; then
      rm -rf "$TMP"
      return 0
      if hasDisk || [[ "$MANUAL" = [Yy1]* ]]; then
        return 1
      fi

    fi
@@ -117,6 +175,13 @@ if [ -f "$STORAGE/$BASE" ]; then
    EXTERNAL="Y"
    CUSTOM="$BASE"

  else

    if skipInstall; then
      BASE=""
      return 1
    fi

  fi

  mkdir -p "$TMP"
@@ -129,230 +194,216 @@ else
  fi

  rm -f "$TMP/$BASE"
  return 0
}

if [ ! -f "$ISO" ]; then
downloadImage() {

  local iso="$1"
  local url="$2"
  local progress
  rm -f "$iso"

  if [[ "$EXTERNAL" != [Yy1]* ]]; then

    cd "$TMP"
    /run/mido.sh "$VERSION"
    /run/mido.sh "$url"
    cd /run

  else
    [ ! -f "$iso" ] && error "Failed to download $url" && exit 61
    return 0

  fi

  info "Downloading $BASE as boot image..."

  # Check if running with interactive TTY or redirected to docker log
  if [ -t 1 ]; then
      PROGRESS="--progress=bar:noscroll"
    progress="--progress=bar:noscroll"
  else
      PROGRESS="--progress=dot:giga"
    progress="--progress=dot:giga"
  fi

    { wget "$VERSION" -O "$ISO" -q --no-check-certificate --show-progress "$PROGRESS"; rc=$?; } || :
  { wget "$url" -O "$iso" -q --no-check-certificate --show-progress "$progress"; rc=$?; } || :

    (( rc != 0 )) && error "Failed to download $VERSION, reason: $rc" && exit 60

  fi
  (( rc != 0 )) && error "Failed to download $url, reason: $rc" && exit 60
  [ ! -f "$iso" ] && error "Failed to download $url" && exit 61

  [ ! -f "$ISO" ] && error "Failed to download $VERSION" && exit 61
  return 0
}

fi
extractImage() {

SIZE=$(stat -c%s "$ISO")
SIZE_GB=$(( (SIZE + 1073741823)/1073741824 ))
  local iso="$1"
  local dir="$2"

if ((SIZE<10000000)); then
  error "Invalid ISO file: Size is smaller than 10 MB" && exit 62
fi
  local msg="Extracting downloaded ISO image..."
  [ -n "$CUSTOM" ] && msg="Extracting local ISO image..."
  info "$msg" && html "$msg"

SPACE=$(df --output=avail -B 1 "$TMP" | tail -n 1)
SPACE_GB=$(( (SPACE + 1073741823)/1073741824 ))
  local size=$(stat -c%s "$iso")
  local size_gb=$(( (size + 1073741823)/1073741824 ))
  local space=$(df --output=avail -B 1 "$TMP" | tail -n 1)
  local space_gb=$(( (space + 1073741823)/1073741824 ))

if (( SIZE > SPACE )); then
  error "Not enough free space in $STORAGE, have $SPACE_GB GB available but need at least $SIZE_GB GB." && exit 63
  if ((size<10000000)); then
    error "Invalid ISO file: Size is smaller than 10 MB" && exit 62
  fi

if [ -n "$CUSTOM" ]; then
  MSG="Extracting local ISO image..."
else
  MSG="Extracting downloaded ISO image..."
  if (( size > space )); then
    error "Not enough free space in $STORAGE, have $space_gb GB available but need at least $size_gb GB." && exit 63
  fi

info "$MSG" && html "$MSG"

DIR="$TMP/unpack"
rm -rf "$DIR"
  rm -rf "$dir"
  7z x "$iso" -o"$dir" > /dev/null

7z x "$ISO" -o"$DIR" > /dev/null
  if [ ! -f "$dir/$ETFS" ] || [ ! -f "$dir/$EFISYS" ]; then

FB="falling back to manual installation!"
ETFS="boot/etfsboot.com"
EFISYS="efi/microsoft/boot/efisys_noprompt.bin"

if [ ! -f "$DIR/$ETFS" ] || [ ! -f "$DIR/$EFISYS" ]; then

  if [ ! -f "$DIR/$ETFS" ]; then
    if [ ! -f "$dir/$ETFS" ]; then
      warn "failed to locate file 'etfsboot.com' in ISO image, $FB"
    else
      warn "failed to locate file 'efisys_noprompt.bin' in ISO image, $FB"
    fi

  # Mark ISO as prepared via magic byte
  printf '\x16' | dd of="$ISO" bs=1 seek=0 count=1 conv=notrunc status=none
    return 1
  fi

  [[ "$ISO" != "$STORAGE/$BASE" ]] && mv -f "$ISO" "$STORAGE/$BASE"
  [ -z "$CUSTOM" ] && rm -f "$iso"

  rm -f "$STORAGE/windows.ver"
  cp /run/version "$STORAGE/windows.ver"

  rm -rf "$TMP"
  return 0
fi
}

[ -z "$CUSTOM" ] && rm -f "$ISO"
findVersion() {

XML=""
  local name="$1"
  local detected=""

if [[ "$MANUAL" != [Yy1]* ]]; then
  [[ "${name,,}" == *"windows 11"* ]] && detected="win11x64"
  [[ "${name,,}" == *"windows 10"* ]] && detected="win10x64"
  [[ "${name,,}" == *"windows 8"* ]] && detected="win81x64"
  [[ "${name,,}" == *"server 2022"* ]] && detected="win2022-eval"
  [[ "${name,,}" == *"server 2019"* ]] && detected="win2019-eval"
  [[ "${name,,}" == *"server 2016"* ]] && detected="win2016-eval"

  if [[ "$EXTERNAL" != [Yy1]* ]]; then
    [ -z "$CUSTOM" ] && XML="$VERSION.xml"
  fi
  echo "$detected"
  return 0
}

  if [ ! -f "/run/assets/$XML" ]; then
selectXML() {

    MSG="Detecting Windows version from ISO image..."
    info "$MSG" && html "$MSG"
  local dir="$1"

    LOC="$DIR/sources/install.wim"
    [ ! -f "$LOC" ] && LOC="$DIR/sources/install.esd"
  XML=""
  [[ "$MANUAL" == [Yy1]* ]] && return 0

    if [ -f "$LOC" ]; then
  if [[ "$EXTERNAL" != [Yy1]* ]] && [ -z "$CUSTOM" ]; then
    XML="$VERSION.xml"
    [ -f "/run/assets/$XML" ] && return 0
  fi

  info "Detecting Windows version from ISO image..."

      DETECTED=""
      TAG="DISPLAYNAME"
      RESULT=$(wimlib-imagex info -xml "$LOC" | tr -d '\000')
      NAME=$(sed -n "/$TAG/{s/.*<$TAG>\(.*\)<\/$TAG>.*/\1/;p}" <<< "$RESULT")
  local loc="$dir/sources/install.wim"
  [ ! -f "$loc" ] && loc="$dir/sources/install.esd"

      [[ "${NAME,,}" == *"windows 11"* ]] && DETECTED="win11x64"
      [[ "${NAME,,}" == *"windows 10"* ]] && DETECTED="win10x64"
      [[ "${NAME,,}" == *"windows 8"* ]] && DETECTED="win81x64"
      [[ "${NAME,,}" == *"server 2022"* ]] && DETECTED="win2022-eval"
      [[ "${NAME,,}" == *"server 2019"* ]] && DETECTED="win2019-eval"
      [[ "${NAME,,}" == *"server 2016"* ]] && DETECTED="win2016-eval"
  if [ ! -f "$loc" ]; then
    warn "failed to locate 'install.wim' or 'install.esd' in ISO image, $FB"
    return 0
  fi

      if [ -z "$DETECTED" ]; then
  local tag="DISPLAYNAME"
  local result=$(wimlib-imagex info -xml "$loc" | tr -d '\000')
  local name=$(sed -n "/$tag/{s/.*<$tag>\(.*\)<\/$tag>.*/\1/;p}" <<< "$result")
  local detected=$(findVersion "$name")

        TAG="PRODUCTNAME"
        NAME2=$(sed -n "/$TAG/{s/.*<$TAG>\(.*\)<\/$TAG>.*/\1/;p}" <<< "$RESULT")
        [ -z "$NAME" ] && NAME="$NAME2"
  if [ -z "$detected" ]; then

        [[ "${NAME2,,}" == *"windows 11"* ]] && DETECTED="win11x64"
        [[ "${NAME2,,}" == *"windows 10"* ]] && DETECTED="win10x64"
        [[ "${NAME2,,}" == *"windows 8"* ]] && DETECTED="win81x64"
        [[ "${NAME2,,}" == *"server 2022"* ]] && DETECTED="win2022-eval"
        [[ "${NAME2,,}" == *"server 2019"* ]] && DETECTED="win2019-eval"
        [[ "${NAME2,,}" == *"server 2016"* ]] && DETECTED="win2016-eval"
    tag="PRODUCTNAME"
    local name2=$(sed -n "/$tag/{s/.*<$tag>\(.*\)<\/$tag>.*/\1/;p}" <<< "$result")
    [ -z "$name" ] && name="$name2"
    detected=$(findVersion "$name2")

  fi

      if [ -n "$DETECTED" ]; then

        XML="$DETECTED.xml"
  if [ -n "$detected" ]; then

        if [ -f "/run/assets/$XML" ]; then
          echo "Detected image of type '$DETECTED', which supports automatic installation."
    if [ -f "/run/assets/$detected.xml" ]; then
      XML="$detected.xml"
      echo "Detected image of type '$detected', which supports automatic installation."
    else
          XML=""
          warn "detected image of type '$DETECTED', but no matching XML file exists, $FB."
      warn "detected image of type '$detected', but no matching XML file exists, $FB."
    fi

  else
        if [ -z "$NAME" ]; then

    if [ -z "$name" ]; then
      warn "failed to detect Windows version from image, $FB"
    else
          if [[ "${NAME,,}" == "windows 7" ]]; then
      if [[ "${name,,}" == "windows 7" ]]; then
        warn "detected Windows 7 image, $FB"
      else
            warn "failed to detect Windows version from string '$NAME', $FB"
          fi
        fi
      fi
    else
      warn "failed to locate 'install.wim' or 'install.esd' in ISO image, $FB"
        warn "failed to detect Windows version from string '$name', $FB"
      fi
    fi

  fi

ASSET="/run/assets/$XML"
  return 0
}

if [ -f "$ASSET" ]; then
updateImage() {

  LOC="$DIR/autounattend.xml"
  [ -f "$LOC" ] && mv -f "$ASSET" "$LOC"
  LOC="$DIR/Autounattend.xml"
  [ -f "$LOC" ] && mv -f "$ASSET" "$LOC"
  LOC="$DIR/AutoUnattend.xml"
  [ -f "$LOC" ] && mv -f "$ASSET" "$LOC"
  LOC="$DIR/autounattend.XML"
  [ -f "$LOC" ] && mv -f "$ASSET" "$LOC"
  LOC="$DIR/Autounattend.XML"
  [ -f "$LOC" ] && mv -f "$ASSET" "$LOC"
  LOC="$DIR/AutoUnattend.XML"
  [ -f "$LOC" ] && mv -f "$ASSET" "$LOC"
  LOC="$DIR/AUTOUNATTEND.xml"
  [ -f "$LOC" ] && mv -f "$ASSET" "$LOC"
  LOC="$DIR/AUTOUNATTEND.XML"
  [ -f "$LOC" ] && mv -f "$ASSET" "$LOC"
  local dir="$1"
  local asset="$2"

  LOC="$DIR/sources/boot.wim"
  [ ! -f "$LOC" ] && LOC="$DIR/sources/boot.esd"
  [ ! -f "$asset" ] && return 0
  replaceXML "$dir" "$asset"

  if [ -f "$LOC" ]; then
  local loc="$dir/sources/boot.wim"
  [ ! -f "$loc" ] && loc="$dir/sources/boot.esd"

    MSG="Adding XML file for automatic installation..."
    info "$MSG" && html "$MSG"
  if [ ! -f "$loc" ]; then
    warn "failed to locate 'boot.wim' or 'boot.esd' in ISO image, $FB"
    return 0
  fi

    RESULT=$(wimlib-imagex info -xml "$LOC" | tr -d '\000')
  info "Adding XML file for automatic installation..."

    if [[ "${RESULT^^}" == *"<IMAGE INDEX=\"2\">"* ]]; then
      INDEX="2"
    else
      INDEX="1"
  local index="1"
  local result=$(wimlib-imagex info -xml "$loc" | tr -d '\000')

  if [[ "${result^^}" == *"<IMAGE INDEX=\"2\">"* ]]; then
    index="2"
  fi

    wimlib-imagex update "$LOC" "$INDEX" --command "add $ASSET /autounattend.xml" > /dev/null
  wimlib-imagex update "$loc" "$index" --command "add $asset /autounattend.xml" > /dev/null

  else
  return 0
}

    ASSET=""
    warn "failed to locate 'boot.wim' or 'boot.esd' in ISO image, $FB"
buildImage() {

  fi
fi
  local dir="$1"
  local cat="BOOT.CAT"
  local label="${BASE%.*}"
  label="${label::30}"
  local out="$TMP/$label.tmp"
  rm -f "$out"

CAT="BOOT.CAT"
LABEL="${BASE%.*}"
LABEL="${LABEL::30}"
OUT="$TMP/$LABEL.tmp"
rm -f "$OUT"
  local msg="Generating updated ISO image..."
  info "$msg" && html "$msg"

SPACE=$(df --output=avail -B 1 "$TMP" | tail -n 1)
SPACE_GB=$(( (SPACE + 1073741823)/1073741824 ))
  local size=$(du -h -b --max-depth=0 "$dir" | cut -f1)
  local size_gb=$(( (size + 1073741823)/1073741824 ))
  local space=$(df --output=avail -B 1 "$TMP" | tail -n 1)
  local space_gb=$(( (space + 1073741823)/1073741824 ))

if (( SIZE > SPACE )); then
  error "Not enough free space in $STORAGE, have $SPACE_GB GB available but need at least $SIZE_GB GB." && exit 63
  if (( size > space )); then
    error "Not enough free space in $STORAGE, have $space_gb GB available but need at least $size_gb GB." && exit 63
  fi

MSG="Generating the ISO image..."
info "$MSG" && html "$MSG"

genisoimage -b "$ETFS" -no-emul-boot -c "$CAT" -iso-level 4 -J -l -D -N -joliet-long -relaxed-filenames -quiet -V "$LABEL" -udf \
                       -boot-info-table -eltorito-alt-boot -eltorito-boot "$EFISYS" -no-emul-boot -o "$OUT" -allow-limited-size "$DIR"

# Mark ISO as prepared via magic byte
printf '\x16' | dd of="$OUT" bs=1 seek=0 count=1 conv=notrunc status=none
  genisoimage -b "$ETFS" -no-emul-boot -c "$cat" -iso-level 4 -J -l -D -N -joliet-long -relaxed-filenames -quiet -V "$label" -udf \
                         -boot-info-table -eltorito-alt-boot -eltorito-boot "$EFISYS" -no-emul-boot -o "$out" -allow-limited-size "$dir"

  [ -n "$CUSTOM" ] && rm -f "$STORAGE/$CUSTOM"

@@ -360,13 +411,39 @@ if [ -f "$STORAGE/$BASE" ]; then
    error "File $STORAGE/$BASE does already exist ?!" && exit 64
  fi

mv "$OUT" "$STORAGE/$BASE"
  mv "$out" "$STORAGE/$BASE"
  return 0
}

rm -f "$STORAGE/windows.ver"
cp /run/version "$STORAGE/windows.ver"
######################################

if ! startInstall; then
  rm -rf "$TMP"
  return 0
fi

html "Successfully prepared image for installation..."
if [ ! -f "$ISO" ]; then
  downloadImage "$ISO" "$VERSION"
fi

if ! extractImage "$ISO" "$DIR"; then

  if [[ "$ISO" != "$STORAGE/$BASE" ]]; then
    mv -f "$ISO" "$STORAGE/$BASE"
  fi

  finishInstall "$STORAGE/$BASE"
  return 0

fi

selectXML "$DIR"

updateImage "$DIR" "/run/assets/$XML"

buildImage "$DIR"

finishInstall "$STORAGE/$BASE"

html "Successfully prepared image for installation..."
return 0
+21 −8
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@ QEMU_TERM=""
QEMU_PORT=7100
QEMU_TIMEOUT=110
QEMU_PID="/run/shm/qemu.pid"
QEMU_PTY="/run/shm/qemu.pty"
QEMU_LOG="/run/shm/qemu.log"
QEMU_OUT="/run/shm/qemu.out"
QEMU_END="/run/shm/qemu.end"
@@ -29,7 +30,7 @@ finish() {
  if [ -f "$QEMU_PID" ]; then

    pid=$(<"$QEMU_PID")
    echo && error "Forcefully terminating Windows, reason: $reason..."
    error "Forcefully terminating Windows, reason: $reason..."
    { kill -15 "$pid" || true; } 2>/dev/null

    while isAlive "$pid"; do
@@ -44,8 +45,8 @@ finish() {

  closeNetwork

  sleep 1
  echo && echo "❯ Shutdown completed!"
  sleep 0.5
  echo "❯ Shutdown completed!"

  exit "$reason"
}
@@ -95,15 +96,15 @@ _graceful_shutdown() {
  set +e

  if [ -f "$QEMU_END" ]; then
    echo && info "Received $1 while already shutting down..."
    info "Received $1 while already shutting down..."
    return
  fi

  touch "$QEMU_END"
  echo && info "Received $1, sending ACPI shutdown signal..."
  info "Received $1, sending ACPI shutdown signal..."

  if [ ! -f "$QEMU_PID" ]; then
    echo && error "QEMU PID file does not exist?"
    error "QEMU PID file does not exist?"
    finish "$code" && return "$code"
  fi

@@ -111,8 +112,20 @@ _graceful_shutdown() {
  pid=$(<"$QEMU_PID")

  if ! isAlive "$pid"; then
    echo && error "QEMU process does not exist?"
    error "QEMU process does not exist?"
    finish "$code" && return "$code"
  fi

  if [ ! f "$STORAGE/windows.boot" ] && [ -f "$QEMU_PTY" ]; then
    if ! grep -Fq "Windows Boot Manager" "$QEMU_PTY"; then
      info "Cannot send ACPI signal during Windows setup, aborting..."
      finish "$code" && return "$code"
    else
      if [ -f "$STORAGE/$BASE" ]; then
        rm -f "$STORAGE/$BASE"
        touch "$STORAGE/windows.boot"
      fi
    fi
  fi

  # Send ACPI shutdown signal
@@ -136,7 +149,7 @@ _graceful_shutdown() {
  done

  if [ "$cnt" -ge "$QEMU_TIMEOUT" ]; then
    echo && error "Shutdown timeout reached, aborting..."
    error "Shutdown timeout reached, aborting..."
  fi

  finish "$code" && return "$code"