#!/usr/bin/env /bin/bash declare -r BASEDIR="`pwd`" declare -r mode="full" # one of 'full'|'unattend' declare -r ISO_BASEDIR="/mnt/machines/iso" declare -r CONFIG_DIR="${BASEDIR}/configs" declare -r UNATTEND_XML="${2:-"${CONFIG_DIR}/autounattend-builder-amd64-20260527.xml"}" #declare -r WINDOWS_ISO="${ISO_BASEDIR}/nano11-25H2-Pro-Eng-Arm64-20260513.iso" #declare -r WINDOWS_ISO="${ISO_BASEDIR}/tiny11-25H2-Pro-Eng-Arm64-20260514.iso" #declare -r WINDOWS_ISO="${ISO_BASEDIR}/nano11-25H2-English-Pro-2026-03-15.iso" #declare -r WINDOWS_ISO="${ISO_BASEDIR}/Tiny11-25H2-English-Pro-2026-05-20.iso" #declare -r WINDOWS_ISO="${ISO_BASEDIR}/Tiny11Core-25H2-English-Pro-2026-05-20.iso" #declare -r WINDOWS_ISO="${ISO_BASEDIR}/nano11-25H2-English-Pro-2026-05-20.iso" #declare -r WINDOWS_ISO="${ISO_BASEDIR}/nano11-base-25H2-Pro-Eng-amd64-20260526.iso" declare -r WINDOWS_ISO="${ISO_BASEDIR}/nano11-base-25H2-Pro-Eng-arm64-20260526.iso" declare -r VIRTIO_ISO="${ISO_BASEDIR}/virtio-win-0.1.285.iso" #declare -r VIRTIO_ISO="${BASEDIR}/virtio-win-0.1.285.iso" ###################################################################################### # # function detect_iso_arch { local iso_file="${1}" local fallback_arch="${2}" case "${iso_file@L}" in *-amd64-*|*-x64-*) echo "amd64" ;; *-arm64-*|*-aarch64-*) echo "arm64" ;; *) # Unknown use host arch as fallback echo "NOTICE: Cannot detect architecture from ISO name, using fallback" >&2 echo "${fallback_arch}" ;; esac } function detect_host_arch { case "$(uname -m | tr '[:upper:]' '[:lower:]')" in "x86_64") echo "amd64" ;; "aarch64") echo "arm64" ;; *) return 1 ;; esac } ###################################################################################### # Detect host and guest architecture declare -r HOST_ARCH="$(detect_host_arch)" [[ -n "${HOST_ARCH}" ]] || { echo "Failed to detect host arch" >&2; exit 1; } declare -r TARGET_ARCH="$(detect_iso_arch "${WINDOWS_ISO}")" [[ -n "${TARGET_ARCH}" ]] || { echo "Failed to detect target arch" >&2; exit 1; } case "${mode}" in "full") echo "Generating full ${TARGET_ARCH} installation ISO image..." >&2 declare -r DEFAULT_OUTPUT_FILE="install-${TARGET_ARCH}-$(date +%Y%m%d-%H%M00).iso" ;; "unattend") echo "Generating unattended ${TARGET_ARCH} ISO image..." >&2 declare -r DEFAULT_OUTPUT_FILE="unattend-${TARGET_ARCH}-$(date +%Y%m%d-%H%M00).iso" ;; *) echo "Invalid mode '${mode}' expected either 'full'|'unattend'" >&2 exit 1 ;; esac # # Output file # declare -r OUTPUT_FILE="${1:-${DEFAULT_OUTPUT_FILE}}" # # Validate input XML # xmllint -noout "${UNATTEND_XML}" || { echo "$(basename "${UNATTEND_XML}") contains invalid XML, aborting" >&2 exit 1 } mkdir -p "${BASEDIR}/build" || exit $? mkdir -p "${BASEDIR}/output" || exit $? # Convert architecture for virtio installation media case "${TARGET_ARCH}" in "arm64") VIRTIO_ARCH="${TARGET_ARCH@U}" ;; # Virtio ISO uses "ARM64" *) VIRTIO_ARCH="${TARGET_ARCH}" ;; # Virtio ISO uses "amd64" esac # # Generate builder script # cat -> "${BASEDIR}/build/genimage-${TARGET_ARCH}.sh" <<-EOF #!/usr/bin/env /bin/ash # # Required packages # echo "Installing required packages..." >&2 apk update || exit \$? apk add xorriso 7zip || exit \$? # # Prepare directories / mountpoints # echo "Preparing build directory and mountpoints..." >&2 mkdir -p /tmp/base || exit \$? mkdir -p /tmp/virtio || exit \$? mkdir -p /tmp/build || exit \$? # # Mounting (if possible) / unpacking of ISO images # if [[ "x${mode}" == "xfull" ]]; then echo "Attempting to mount windows ISO image..." >&2 mount -t udf -o loop,ro /input/windows.iso /tmp/base/ 2>/dev/null || { echo "Unpacking windows ISO image (fallback)..." >&2 7z x -bb0 -o/tmp/base/ /input/windows.iso || exit \$? } else echo "Building unattend ISO image, skipping windows mount" >&2 fi echo "Attempting to mount virtio-win ISO image..." >&2 mount -o loop,ro /input/virtio.iso /tmp/virtio/ 2>/dev/null || { echo "Unpacking virtio-win ISO image (fallback)..." >&2 7z x -bb0 -o/tmp/virtio/ /input/virtio.iso || exit \$? } # # Autounattend XML configuration file (replaces any existing one) # echo "Copying autounattend.xml..." >&2 sed -e 's|processorArchitecture="[^"]\+"|processorArchitecture="${TARGET_ARCH}"|' \ < /input/autounattend.xml \ > /tmp/build/autounattend.xml || exit \$? # # Required drivers for installation (into "$WinPEDriver$") # echo "Creating \\\$WinPEDriver\\\$..." >&2 mkdir -p "/tmp/build/\\\$WinPEDriver\\\$" || exit \$? for x in NetKVM viostor vioscsi; do cp -vfR /tmp/virtio/\${x}/w11/${VIRTIO_ARCH} "/tmp/build/\\\$WinPEDriver\\\$/\${x}" || exit \$? done # # OEM files (drivers, packages, etc.) # echo "Copying OEM files..." >&2 mkdir -p \ "/tmp/build/\\\$OEM\\\$/\\\$\\\$" \ "/tmp/build/\\\$OEM\\\$/\\\$1" \ || exit \$? # Viofs files for use in the final OS (virtiofsd) mkdir -p "/tmp/build/\\\$OEM\\\$/\\\$1/drivers" || exit \$? for x in /tmp/virtio/viofs/w11/${VIRTIO_ARCH}; do drvname="\$(echo "\${x}" | cut -d'/' -f4)" || exit \$? cp -vfR "\${x}" "/tmp/build/\\\$OEM\\\$/\\\$1/drivers/\${drvname}" || exit \$? done # # Virtio drivers for auto-installation # echo "Copying virtio drivers for automatic installation..." >&2 mkdir -p "/tmp/build/drivers" || exit \$? for x in /tmp/virtio/*/w11/${VIRTIO_ARCH}; do drvname="\$(echo "\${x}" | cut -d'/' -f4)" || exit \$? cp -vfR "\${x}" "/tmp/build/drivers/\${drvname}" || exit \$? done # # ISO image generation # if [[ "x${mode}" == "xfull" ]]; then echo "Generating ISO boot image..." >&2 mkisofs \\ -quiet \\ -b boot/etfsboot.com \\ -no-emul-boot \\ -boot-load-size 8 \\ -iso-level 3 -J -l -D -N -U -joliet-long -relaxed-filenames -rational-rock \\ -eltorito-alt-boot -e efi/microsoft/boot/efisys_noprompt.bin -no-emul-boot \\ -m "/tmp/base/autounattend.xml" \\ -V "Nano11-Builder" \\ -o "/output/$(basename "${OUTPUT_FILE}")" \\ "/tmp/base/" "/tmp/build/" || exit \$? else echo "Generating ISO unattend image..." >&2 mkisofs \\ -quiet \\ -iso-level 3 -J -l -D -N -U -joliet-long -relaxed-filenames -rational-rock \\ -V "Nano11-Builder-Unattend" \\ -o "/output/$(basename "${OUTPUT_FILE}")" \\ "/tmp/build/" || exit \$? fi # genisoimage \\ # -quiet \\ # -b boot/etfsboot.com \\ # -no-emul-boot \\ # -boot-load-seg 1984 \\ # -boot-load-size 8 \\ # -iso-level 3 -J -l -D -N -U -joliet-long -allow-limited-size -relaxed-filenames -rock \\ # -eltorito-alt-boot -e efi/microsoft/boot/efisys_noprompt.bin \\ # -m "/tmp/base/autounattend.xml" \\ # -V "Nano11-Builder" \\ # -o "/build/output.iso" \\ # "/tmp/base/" "/tmp/build/" || exit \$? # # # echo "Cleaning up..." >&2 umount /tmp/base/ 2>/dev/null umount /tmp/virtio/ 2>/dev/null EOF chmod 0755 "${BASEDIR}/build/genimage-${TARGET_ARCH}.sh" || exit $? declare -r CONTAINER_IMAGE="alpine:3.23" declare -r CONTAINER_ARGS=( "-it --rm --name isobuilder-${TARGET_ARCH}" "-v ${VIRTIO_ISO}:/input/virtio.iso:ro,Z" "-v ${WINDOWS_ISO}:/input/windows.iso:ro,Z" "-v ${UNATTEND_XML}:/input/autounattend.xml:ro,Z" "-v ${BASEDIR}/build:/build:ro,Z" "-v ${BASEDIR}/output:/output:rw,Z" "--privileged --network host" ) # # Run generation script in alpine 3.23 container # if type -p docker &>/dev/null; then echo "Running isobuilder in docker container" >&2 docker run ${CONTAINER_ARGS[@]} ${CONTAINER_IMAGE} \ /build/genimage-${TARGET_ARCH}.sh || exit $? elif type -p podman &>/dev/null; then echo "Running isobuilder in podman container" >&2 podman run ${CONTAINER_ARGS[@]} ${CONTAINER_IMAGE} \ /build/genimage-${TARGET_ARCH}.sh || exit $? else echo "Failed to start build container: no runtime available" >&2 exit 1 fi (cd "${BASEDIR}/output" && ls -sh "$(basename "${OUTPUT_FILE}")") || exit $?