diff --git a/build-windows-install-disk.sh b/build-windows-install-disk.sh index deb48e6..6cbdee5 100755 --- a/build-windows-install-disk.sh +++ b/build-windows-install-disk.sh @@ -46,8 +46,8 @@ die() { exit 1 } -# The build has five steps; step prints a numbered heading for each of them. -total_steps=5 +# The build has four steps; step prints a numbered heading for each of them. +total_steps=4 step_number=0 step() { step_number=$((step_number + 1)) @@ -60,6 +60,18 @@ note() { printf ' %s\n' "$*" >&2 } +# show logs a command line before it runs; run logs it and then runs it. Use show +# alone where the output of the command is captured. +show() { + local line + printf -v line '%q ' "$@" + note "\$ ${line% }" +} +run() { + show "$@" + "$@" +} + os="$(uname -s)" case "$os" in Darwin|Linux) ;; @@ -105,7 +117,11 @@ mkdir "$mount_dir" note "work directory: $work_dir" mount_method="" loop_device="" +# Set while the output image is being written, so that a failed or cancelled +# run does not leave a half-built image behind. +partial_output="" cleanup() { + [[ -z "$partial_output" ]] || rm -f "$partial_output" case "$mount_method" in hdiutil) hdiutil detach -quiet "$mount_dir" >/dev/null 2>&1 || true ;; udisks) @@ -127,6 +143,7 @@ mount_with_udisks() { return 1 fi local setup + show udisksctl loop-setup -r -f "$iso" if ! setup="$(udisksctl loop-setup -r -f "$iso" 2>&1)"; then note "udisksctl loop-setup failed: $setup" return 1 @@ -142,8 +159,10 @@ mount_with_udisks() { # A desktop session may have mounted the loop device already, which makes # udisksctl mount fail with AlreadyMounted; use that mount point then. local mounted point + show lsblk -no MOUNTPOINT "$loop_device" point="$(lsblk -no MOUNTPOINT "$loop_device" 2>/dev/null | head -n 1)" if [[ -z "$point" ]]; then + show udisksctl mount -b "$loop_device" -t udf -o ro if mounted="$(udisksctl mount -b "$loop_device" -t udf -o ro 2>&1)"; then # Some udisks versions end the message with a period, others do not. point="$(sed -n 's/^Mounted .* at \(.*\)$/\1/p' <<<"$mounted")" @@ -157,7 +176,7 @@ mount_with_udisks() { fi if [[ ! -d "$point" ]]; then note "no usable mount point for $loop_device (parsed mount point: '${point}')" - udisksctl loop-delete -b "$loop_device" >/dev/null 2>&1 || true + run udisksctl loop-delete -b "$loop_device" >/dev/null 2>&1 || true mount_method="" return 1 fi @@ -169,7 +188,7 @@ step "Reading the ISO $iso (it is UDF, so it has to be mounted)" note "ISO size: $(stat -L "${size_args[@]}" "$iso") bytes" if [[ "$os" == Darwin ]]; then note "mounting with hdiutil at $mount_dir" - hdiutil attach -readonly -nobrowse -mountpoint "$mount_dir" "$iso" >/dev/null + run hdiutil attach -readonly -nobrowse -mountpoint "$mount_dir" "$iso" >/dev/null mount_method=hdiutil else note "mounting with udisksctl" @@ -187,7 +206,7 @@ note "install.wim: $(stat "${size_args[@]}" "$install_wim") bytes" step "Splitting install.wim into install.swm parts (FAT32 files must stay below 4 GiB)" split_dir="$work_dir/split" mkdir "$split_dir" -wimlib-imagex split "$install_wim" "$split_dir/install.swm" 3800 >/dev/null +run wimlib-imagex split "$install_wim" "$split_dir/install.swm" 3800 >/dev/null for part in "$split_dir"/install*.swm; do note "$(basename "$part"): $(stat "${size_args[@]}" "$part") bytes" done @@ -204,12 +223,17 @@ image_mib=$(( (content_bytes + content_bytes / 20) / mib + 64 )) step "Creating a $image_mib MiB image with a GPT and one FAT32 partition" note "content to copy: $content_bytes bytes (without install.wim)" -image="$work_dir/disk.img" -truncate -s "${image_mib}m" "$image" -sgdisk -o -n 1:2048:0 -t 1:0700 -c 1:WINPE "$image" >/dev/null +image="$output" +run mkdir -p "$(dirname -- "$output")" +# --force replaces an existing image; truncate alone would keep its old content. +run rm -f "$image" +partial_output="$image" +run truncate -s "${image_mib}m" "$image" +run sgdisk -o -n 1:2048:0 -t 1:0700 -c 1:WINPE "$image" >/dev/null +show sgdisk -i 1 "$image" sectors=$(sgdisk -i 1 "$image" | awk '/^Partition size:/ { print $3 }') [[ -n "$sectors" ]] || die "could not read the partition size" -mkfs.fat -F 32 -n WINPE --offset 2048 "$image" $((sectors / 2)) >/dev/null +run mkfs.fat -F 32 -n WINPE --offset 2048 "$image" $((sectors / 2)) >/dev/null # mtools addresses the partition by its byte offset inside the image. mtools_image="$image@@$((2048 * 512))" @@ -222,6 +246,7 @@ copy_dir() { # copy_dir SOURCE_DIR TARGET_DIR_IN_IMAGE files+=("$entry") done < <(find "$source" -maxdepth 1 -type f ! -path "$install_wim" -print0) if ((${#files[@]} > 0)); then + note "\$ mcopy -Q -m -i $mtools_image <${#files[@]} files from ${source#"$mount_dir"} > ::$target/" mcopy -Q -m -i "$mtools_image" "${files[@]}" "::$target/" fi } @@ -232,14 +257,12 @@ dir_count=0 while IFS= read -r -d '' dir; do relative="${dir#"$mount_dir"/}" dir_count=$((dir_count + 1)) - mmd -i "$mtools_image" "::$relative" + run mmd -i "$mtools_image" "::$relative" copy_dir "$dir" "$relative" done < <(find "$mount_dir" -mindepth 1 -type d -print0 | sort -z) note "copied $dir_count directories" note "copying the install*.swm parts to sources/" -mcopy -Q -m -i "$mtools_image" "$split_dir"/install*.swm "::sources/" +run mcopy -Q -m -i "$mtools_image" "$split_dir"/install*.swm "::sources/" -step "Moving the image to $output" -mkdir -p "$(dirname -- "$output")" -mv -f "$image" "$output" +partial_output="" printf 'Built Windows 11 installation disk: %s\n' "$output"