1
0
Files
win11-builder/isobuilder.sh
T
stkn e577b48c6c WIP
Signed-off-by: Stefan Knoblich <stkn@bitplumber.de>
2026-06-04 17:56:16 +02:00

271 lines
7.7 KiB
Bash

#!/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 $?