From 09cb10af73234ea61d061028de65b833fd5e8124 Mon Sep 17 00:00:00 2001 From: Stefan Knoblich Date: Wed, 3 Jun 2026 22:13:08 +0200 Subject: [PATCH] WIP Signed-off-by: Stefan Knoblich --- win11-builder-generic.sh | 334 ++++++++++++++++++++++++++++----------- 1 file changed, 246 insertions(+), 88 deletions(-) diff --git a/win11-builder-generic.sh b/win11-builder-generic.sh index 7bcb89e..9f9b994 100755 --- a/win11-builder-generic.sh +++ b/win11-builder-generic.sh @@ -201,86 +201,6 @@ function fileext_from_qemu_image_type { esac } -function qemu_prepare_disk { - local QEMU_DISK_FILE="${1}" - local QEMU_DISK_SIZE="${2:-32G}" - - # Remove leftover temp files - rm -f "${QEMU_DISK_FILE}.tmp" 2>/dev/null - - if [[ ! -f "${QEMU_DISK_FILE}" || "${FORCE_RESET}" -eq 1 ]]; then - echo "Creating QEMU disk image..." >&2 - qemu-img create -f qcow2 "${QEMU_DISK_FILE}" "${QEMU_DISK_SIZE}" >/dev/null || return $? - elif [[ -f "${QEMU_DISK_FILE}" ]]; then - local QEMU_COW_FILE="${QEMU_DISK_FILE}.cow" - local QEMU_COW_SIZE="$(qemu_image_size "${QEMU_DISK_FILE}")" || return $? - - echo "Creating QEMU CoW disk image for '$(basename "${QEMU_DISK_FILE}")' ..." >&2 - qemu-img create -f qcow2 -B qcow2 -b "${QEMU_DISK_FILE}" "${QEMU_COW_FILE}" "${QEMU_COW_SIZE}" >/dev/null || return $? - fi - - # - QEMU_DYN_OPTIONS+=( - "-drive if=none,format=qcow2,file=${QEMU_COW_FILE:-${QEMU_DISK_FILE}},discard=unmap,id=hd0" - "-device virtio-blk-pci,drive=hd0,bootindex=1" - ) -} - -function qemu_prepare_cdrom_amd64 { - local QEMU_BOOTINDEX=2 # First non-HD drive bootindex - - # Only assign cdrom drives for build command (TODO: clean this up) - [[ "${COMMAND}" == "build" ]] || return 0; - - [[ -n "${UNATTEND_ISO}" ]] && { - QEMU_DYN_OPTIONS+=( - "-drive if=none,file=${UNATTEND_ISO},media=cdrom,id=cd2" - "-device ide-cd,drive=cd2,bootindex=$(( QEMU_BOOTINDEX++ )),bus=ahci.2" - ) - } - - [[ -n "${INSTALL_ISO}" ]] && { - QEMU_DYN_OPTIONS+=( - "-drive if=none,file=${INSTALL_ISO},media=cdrom,id=cd0" - "-device ide-cd,drive=cd0,bootindex=$(( QEMU_BOOTINDEX++ )),bus=ahci.0" - ) - } - - [[ -n "${VIRTIO_ISO}" ]] && { - QEMU_DYN_OPTIONS+=( - "-drive if=none,file=${VIRTIO_ISO},media=cdrom,id=cd1" - "-device ide-cd,drive=cd1,bootindex=$(( QEMU_BOOTINDEX++ )),bus=ahci.1" - ) - } -} - -function qemu_prepare_cdrom_arm64 { - local QEMU_BOOTINDEX=2 # First non-HD drive bootindex - - # Only assign cdrom drives for build command (TODO: clean this up) - [[ "${COMMAND}" == "build" ]] || return 0; - - [[ -n "${UNATTEND_ISO}" ]] && { - QEMU_DYN_OPTIONS+=( - "-drive if=none,file=${UNATTEND_ISO},media=cdrom,id=cd2" - "-device usb-storage,drive=cd2,bootindex=$(( QEMU_BOOTINDEX++ ))" - ) - } - - [[ -n "${INSTALL_ISO}" ]] && { - QEMU_DYN_OPTIONS+=( - "-drive if=none,file=${INSTALL_ISO},media=cdrom,id=cd0" - "-device usb-storage,drive=cd0,bootindex=$(( QEMU_BOOTINDEX++ ))" - ) - } - - [[ -n "${VIRTIO_ISO}" ]] && { - QEMU_DYN_OPTIONS+=( - "-drive if=none,file=${VIRTIO_ISO},media=cdrom,id=cd1" - "-device usb-storage,drive=cd1,bootindex=$(( QEMU_BOOTINDEX++ ))" - ) - } -} function expand_size { local value="${1}" @@ -302,25 +222,263 @@ function expand_size { esac } -function resize_image { +function do_resize_image { local img_file="${1}" + local img_type="${2}" + local img_cur_size="${3}" + local img_req_size="${4}" - local img_cur_size="$(qemu_image_size "${1}")" - local img_req_size="$(expand_size "${2:-${img_cur_size}}")" - [[ "${img_cur_size}" -eq "${img_req_size}" ]] && return 0; - [[ "${img_cur_size}" -lt "${img_req_size}" ]] || return 1; + [[ "${img_cur_size}" -eq "${img_req_size}" ]] && return 0 + [[ "${img_cur_size}" -gt "${img_req_size}" ]] && { + echo "Shrinking ${img_type} images is not supported" >&2 + return 1 + } - local img_type="$(qemu_image_type_from_fileext "${img_file}")" case "${img_type}" in - raw) + "raw") echo "Resizing raw image '$(basename "${img_file}")'" >&2 truncate --size="${img_req_size}" "${img_file}" || return $? ;; + "qcow2") + echo "Resizing qcow2 image '$(basename "${img_file}")'" >&2 + qemu-img resize -f qcow2 "${img_file}" "${img_req_size}" || return $? ;; *) - echo "Qcow2 image resizing is currently not implemented" >&2 + echo "Resizing ${img_type} images is currently not supported" >&2 return 1 ;; esac } +function resize_image { + local img_file="${1}" + local img_type="$(qemu_image_type_from_fileext "${img_file}")" + local img_cur_size="$(qemu_image_size "${1}")" + local img_req_size="$(expand_size "${2:-${img_cur_size}}")" + + do_resize_image "${img_file}" "${img_type}" "${img_cur_size}" "${img_req_size}" +} + + +# Drivers for the WinPE early installation environment +declare -r VIRTIO_WINPE_DRIVERS=( + "NetKVM" + "vioscsi" + "viostor" + "viogpudo" +) + +function has { + local val="${1}"; shift + for x in $@; do + [[ "${x}" == "${val}" ]] && return 0 + done + return 1 +} + +# +# +# +function qemu_fill_unattend_fs { + local destdir="${1}" + [[ -d "${destdir}" ]] || { + echo "ERROR: Virtual VFAT target directory '${destdir}' does not exist" >&2 + return 1 + } + + local unattend_xml="${2}" + [[ -f "${unattend_xml}" ]] || { + echo "ERROR: Unattended XML installation file '${unattend_xml}' does not exist" >&2 + return 1 + } + + # Copy autounattend.xml + sed -e "s|processorArchitecture=\"[^\"]\+\"|processorArchitecture=\"${GUEST_ARCH}\"|g" \ + < "${unattend_xml}" > "${destdir}/autounattend.xml" || return $? + + # + local virtio_arch="${GUEST_ARCH@L}" + case "${GUEST_ARCH@L}" in + "arm64") + virtio_arch="${GUEST_ARCH@U}" ;; + "amd64") + virtio_arch="${GUEST_ARCH@L}" ;; + *) + echo "ERROR: " >&2 + return 1 ;; + esac + + # Copy virtio drivers + local virtio_iso="${3}" + [[ -f "${virtio_iso}" ]] || { + echo "ERROR: VirtIO ISO image '${virtio_iso}' does not exist" >&2 + return 1 + } + + local virtio_drivers_list="$(7z l -slt "${virtio_iso}" | sed -ne "/^Path = [^/]\+\/w11\/${virtio_arch}\$/{ s|^Path = \([^/]\+\)/.*|\1|; p }")" + [[ -n "${virtio_drivers_list}" ]] || { + echo "ERROR: No drivers found in VirtIO ISO image '${virtio_iso}'" >&2 + return 1 + } + + echo "Copying virtio drivers '${virtio_drivers_list//$'\n'/,}' into unattend VFS..." >&2 + mkdir -p "${destdir}/drivers" "${destdir}/\$WinPEDriver\$" || return $? + for x in ${virtio_drivers_list}; do + mkdir -p "${destdir}/drivers/${x}" || return $? + 7z e -y -bd -bb0 -o"${destdir}/drivers/${x}" -- "${virtio_iso}" "${x}/w11/${virtio_arch}/*" >/dev/null || return $? + has "${x}" "${VIRTIO_WINPE_DRIVERS[@]}" && { + echo "Copying ${x} into \$WinPEDriver\$..." >&2 + find "${destdir}/drivers/${x}/" -maxdepth 1 -type f -exec cp '{}' "${destdir}/\$WinPEDriver\$/" \; || return $? + } + done +} + +function qemu_generate_unattend_iso { + local destfile="${1}" + + local destdir="${BASEDIR}/unattend.tmp" + mkdir -p "${destdir}" || return $? + + qemu_fill_unattend_fs "${destdir}" "${2}" "${3}" || return $? + + if type -p "mkisofs" &>/dev/null; then + mkisofs \ + -iso-level 3 -J -l -D -N -U -joliet-long -relaxed-filenames -rational-rock \ + -V "win11-builder-unattend" -o "${destfile}" -quiet \ + "${destdir}" || return $? + elif type -p "genisoimage" &>/dev/null; then + genisoimage \ + -iso-level 3 -J -l -D -N -U -joliet-long -allow-limited-size -relaxed-filenames -rock \ + -V "win11-builder-unattend" -o "${destfile}" -quiet \ + "${destdir}" || return $? + elif type -p "xorriso" &>/dev/null; then + echo "ERROR: xorriso support is pending" >&2 + return 1 + else + echo "ERROR: Failed to find one of mkisofs | genisofs | xorriso" >&2 + return 1 + fi + + # cleanup + find "${destdir}" -delete || return $? +} + +function qemu_prepare_disk { + local QEMU_DISK_FILE="${1}" + local QEMU_DISK_SIZE="${2:-32G}" + + # Remove leftover temp files + rm -f "${QEMU_DISK_FILE}.tmp" 2>/dev/null + + if [[ ! -f "${QEMU_DISK_FILE}" || "${FORCE_RESET}" -eq 1 ]]; then + echo "Creating QEMU disk image..." >&2 + qemu-img create -f qcow2 "${QEMU_DISK_FILE}" "${QEMU_DISK_SIZE}" >/dev/null || return $? + elif [[ -f "${QEMU_DISK_FILE}" && "${COMMAND}" != "build" ]]; then + local QEMU_COW_FILE="${QEMU_DISK_FILE}.cow" + local QEMU_COW_SIZE="$(qemu_image_size "${QEMU_DISK_FILE}")" || return $? + + echo "Creating QEMU CoW disk image for '$(basename "${QEMU_DISK_FILE}")' ..." >&2 + qemu-img create -f qcow2 -B qcow2 -b "${QEMU_DISK_FILE}" "${QEMU_COW_FILE}" "${QEMU_COW_SIZE}" >/dev/null || return $? + fi + + QEMU_DYN_OPTIONS+=( + "-drive if=none,format=qcow2,file=${QEMU_COW_FILE:-${QEMU_DISK_FILE}},discard=unmap,id=hd0,index=0" + "-device virtio-blk-pci,drive=hd0,bootindex=1" + ) +} + +function qemu_prepare_cdrom_amd64 { + local QEMU_BOOTINDEX=2 # First non-HD drive bootindex + local QEMU_DRIVEINDEX=1 # First non-HD drive index + + # Only assign cdrom drives for build command (TODO: clean this up) + [[ "${COMMAND}" == "build" ]] || return 0; + + if [[ -n "${UNATTEND_CONFIG}" ]]; then + local unattend_iso="${BASEDIR}/unattend.iso" + qemu_generate_unattend_iso "${unattend_iso}" "${UNATTEND_CONFIG}" "${VIRTIO_ISO}" || return $? + + QEMU_DYN_OPTIONS+=( + "-drive if=none,file=${unattend_iso},media=cdrom,id=cd2,index=$(( QEMU_DRIVEINDEX++ ))" + "-device ide-cd,drive=cd2,bootindex=$(( QEMU_BOOTINDEX++ )),bus=ahci.2" + ) + + #local unattend_tmp="${BASEDIR}/unattend" + + #mkdir -p "${unattend_tmp}" || return $? + #qemu_fill_unattend_fs "${unattend_tmp}" "${UNATTEND_CONFIG}" "${VIRTIO_ISO}" || return $? + + #QEMU_DYN_OPTIONS+=( + # "-drive if=none,file=fat:rw:${unattend_tmp},format=raw,id=cd2,index=$(( QEMU_DRIVEINDEX++ ))" + # "-device usb-storage,drive=cd2,bootindex=$(( QEMU_BOOTINDEX++ )),removable=on" + #) + elif [[ -n "${UNATTEND_ISO}" ]]; then + QEMU_DYN_OPTIONS+=( + "-drive if=none,file=${UNATTEND_ISO},media=cdrom,id=cd2,index=$(( QEMU_DRIVEINDEX++ ))" + "-device ide-cd,drive=cd2,bootindex=$(( QEMU_BOOTINDEX++ )),bus=ahci.2" + ) + fi + + [[ -n "${INSTALL_ISO}" ]] && { + QEMU_DYN_OPTIONS+=( + "-drive if=none,file=${INSTALL_ISO},media=cdrom,id=cd0,index=$(( QEMU_DRIVEINDEX++ ))" + "-device ide-cd,drive=cd0,bootindex=$(( QEMU_BOOTINDEX++ )),bus=ahci.0" + ) + } + + [[ -n "${VIRTIO_ISO}" ]] && { + QEMU_DYN_OPTIONS+=( + "-drive if=none,file=${VIRTIO_ISO},media=cdrom,id=cd1,index=$(( QEMU_DRIVEINDEX++ ))" + "-device ide-cd,drive=cd1,bootindex=$(( QEMU_BOOTINDEX++ )),bus=ahci.1" + ) + } +} + +function qemu_prepare_cdrom_arm64 { + local QEMU_BOOTINDEX=2 # First non-HD drive bootindex + local QEMU_DRIVEINDEX=1 # First non-HD drive index + + # Only assign cdrom drives for build command (TODO: clean this up) + [[ "${COMMAND}" == "build" ]] || return 0; + + if [[ -n "${UNATTEND_CONFIG}" ]]; then + local unattend_iso="${BASEDIR}/unattend.iso" + qemu_generate_unattend_iso "${unattend_iso}" "${UNATTEND_CONFIG}" "${VIRTIO_ISO}" || return $? + + QEMU_DYN_OPTIONS+=( + "-drive if=none,file=${unattend_iso},media=cdrom,id=cd2,index=$(( QEMU_DRIVEINDEX++ ))" + "-device usb-storage,drive=cd2,bootindex=$(( QEMU_BOOTINDEX++ ))" + ) + + # Using Qemu's VVFAT support to create an unattend drive (NOK: blocks C: / Disk #0) + #local unattend_tmp="${BASEDIR}/unattend" + #mkdir -p "${unattend_tmp}" || return $? + #qemu_fill_unattend_fs "${unattend_tmp}" "${UNATTEND_CONFIG}" "${VIRTIO_ISO}" || return $? + + #QEMU_DYN_OPTIONS+=( + # "-drive if=none,file=fat:${unattend_tmp},media=disk,format=raw,id=cd2,index=$(( QEMU_DRIVEINDEX++ ))" + # "-device usb-storage,drive=cd2,bootindex=$(( QEMU_BOOTINDEX++ )),removable=on" + #) + elif [[ -n "${UNATTEND_ISO}" ]]; then + QEMU_DYN_OPTIONS+=( + "-drive if=none,file=${UNATTEND_ISO},media=cdrom,id=cd2,index=$(( QEMU_DRIVEINDEX++ ))" + "-device usb-storage,drive=cd2,bootindex=$(( QEMU_BOOTINDEX++ ))" + ) + fi + + [[ -n "${INSTALL_ISO}" ]] && { + QEMU_DYN_OPTIONS+=( + "-drive if=none,file=${INSTALL_ISO},media=cdrom,id=cd0,index=$(( QEMU_DRIVEINDEX++ ))" + "-device usb-storage,drive=cd0,bootindex=$(( QEMU_BOOTINDEX++ ))" + ) + } + + [[ -n "${VIRTIO_ISO}" ]] && { + QEMU_DYN_OPTIONS+=( + "-drive if=none,file=${VIRTIO_ISO},media=cdrom,id=cd1,index=$(( QEMU_DRIVEINDEX++ ))" + "-device usb-storage,drive=cd1,bootindex=$(( QEMU_BOOTINDEX++ ))" + ) + } +} + + function qemu_prepare_efi_code_and_vars { local SRC_EFI_CODE_IMG="${1}" # Required: EFI code image path local SRC_EFI_VARS_IMG="${2}" # Optional: EFI vars image path