Stage the Windows ISO files with rsync before copying them into the image
Copy the ISO without install.wim to a staging directory, split install.wim into it, and fill the image with one recursive mcopy instead of a mmd and mcopy per directory. rsync --chmod keeps the staged directories writable so that cleanup can remove them. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
dd29fd92a7
commit
d9707fd57b
1 file changed
+28
-35
@@ -15,7 +15,7 @@
|
||||
#
|
||||
# Dependencies
|
||||
# macOS (Homebrew) wimlib dosfstools gptfdisk mtools
|
||||
# Debian wimtools dosfstools gdisk mtools udisks2
|
||||
# Debian wimtools dosfstools gdisk mtools rsync udisks2
|
||||
#
|
||||
# Usage: build-windows-install-disk.sh [--force] ISO [OUTPUT.img]
|
||||
# OUTPUT defaults to the ISO path with ".img" in place of ".iso".
|
||||
@@ -46,8 +46,8 @@ die() {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# The build has four steps; step prints a numbered heading for each of them.
|
||||
total_steps=4
|
||||
# The build has five steps; step prints a numbered heading for each of them.
|
||||
total_steps=5
|
||||
step_number=0
|
||||
step() {
|
||||
step_number=$((step_number + 1))
|
||||
@@ -88,7 +88,7 @@ for dir in ${brew_prefix:+"$brew_prefix/sbin"} /opt/homebrew/sbin /usr/local/sbi
|
||||
[[ -d "$dir" ]] && PATH="$PATH:$dir"
|
||||
done
|
||||
|
||||
tools=(wimlib-imagex mkfs.fat sgdisk mcopy mmd)
|
||||
tools=(wimlib-imagex mkfs.fat sgdisk mcopy rsync)
|
||||
[[ "$os" == Darwin ]] && tools+=(hdiutil)
|
||||
missing=()
|
||||
for tool in "${tools[@]}"; do
|
||||
@@ -98,7 +98,7 @@ if ((${#missing[@]} > 0)); then
|
||||
if [[ "$os" == Darwin ]]; then
|
||||
hint="brew install wimlib dosfstools gptfdisk mtools"
|
||||
else
|
||||
hint="apt install wimtools dosfstools gdisk mtools"
|
||||
hint="apt install wimtools dosfstools gdisk mtools rsync"
|
||||
fi
|
||||
die "missing tools: ${missing[*]} ($hint)"
|
||||
fi
|
||||
@@ -128,6 +128,8 @@ cleanup() {
|
||||
udisksctl unmount -b "$loop_device" >/dev/null 2>&1 || true
|
||||
udisksctl loop-delete -b "$loop_device" >/dev/null 2>&1 || true ;;
|
||||
esac
|
||||
# Staged directories may be read-only when the run was cancelled early.
|
||||
chmod -R u+rwX "$work_dir" >/dev/null 2>&1 || true
|
||||
rm -rf "$work_dir"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
@@ -202,20 +204,27 @@ install_wim="$mount_dir/sources/install.wim"
|
||||
[[ -d "$mount_dir/efi/boot" ]] || die "efi/boot was not found in the ISO; is it a UEFI Windows ISO?"
|
||||
note "install.wim: $(stat "${size_args[@]}" "$install_wim") bytes"
|
||||
|
||||
# The staging directory holds everything that goes into the image: the ISO files
|
||||
# without install.wim, and the install.swm parts in sources/.
|
||||
stage_dir="$work_dir/stage"
|
||||
mkdir "$stage_dir"
|
||||
step "Staging the ISO files without install.wim"
|
||||
# The UDF mount is read-only, so its directories are too; --chmod keeps the copies
|
||||
# writable, otherwise cleanup cannot remove them.
|
||||
run rsync -rt --chmod=u+rwX --exclude=/sources/install.wim "$mount_dir/" "$stage_dir/"
|
||||
run mkdir -p "$stage_dir/sources"
|
||||
|
||||
# Parts must stay below the 4 GiB FAT32 file limit; wimlib takes MiB.
|
||||
step "Splitting install.wim into install.swm parts (FAT32 files must stay below 4 GiB)"
|
||||
split_dir="$work_dir/split"
|
||||
mkdir "$split_dir"
|
||||
run wimlib-imagex split "$install_wim" "$split_dir/install.swm" 3800 >/dev/null
|
||||
for part in "$split_dir"/install*.swm; do
|
||||
run wimlib-imagex split "$install_wim" "$stage_dir/sources/install.swm" 3800 >/dev/null
|
||||
for part in "$stage_dir"/sources/install*.swm; do
|
||||
note "$(basename "$part"): $(stat "${size_args[@]}" "$part") bytes"
|
||||
done
|
||||
|
||||
# Size the image: everything but install.wim, the split parts, and 5% plus 64 MiB
|
||||
# of room for FAT32 metadata and partition alignment.
|
||||
# Size the image: the staged files, and 5% plus 64 MiB of room for FAT32
|
||||
# metadata and partition alignment.
|
||||
content_bytes=$(
|
||||
{ find "$mount_dir" -type f ! -path "$install_wim" -exec stat "${size_args[@]}" {} +
|
||||
find "$split_dir" -type f -exec stat "${size_args[@]}" {} +; } |
|
||||
find "$stage_dir" -type f -exec stat "${size_args[@]}" {} + |
|
||||
awk '{ total += $1 } END { printf "%d", total }'
|
||||
)
|
||||
mib=$((1024 * 1024))
|
||||
@@ -240,29 +249,13 @@ mtools_image="$image@@$((2048 * 512))"
|
||||
note "partition: $sectors sectors, formatted as FAT32 (label WINPE)"
|
||||
export MTOOLS_SKIP_CHECK=1
|
||||
|
||||
copy_dir() { # copy_dir SOURCE_DIR TARGET_DIR_IN_IMAGE
|
||||
local source="$1" target="$2" files=() entry
|
||||
while IFS= read -r -d '' entry; do
|
||||
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
|
||||
}
|
||||
|
||||
step "Copying the installation files into the image"
|
||||
copy_dir "$mount_dir" ""
|
||||
dir_count=0
|
||||
while IFS= read -r -d '' dir; do
|
||||
relative="${dir#"$mount_dir"/}"
|
||||
dir_count=$((dir_count + 1))
|
||||
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/"
|
||||
run mcopy -Q -m -i "$mtools_image" "$split_dir"/install*.swm "::sources/"
|
||||
entries=()
|
||||
while IFS= read -r -d '' entry; do
|
||||
entries+=("$entry")
|
||||
done < <(find "$stage_dir" -mindepth 1 -maxdepth 1 -print0)
|
||||
note "\$ mcopy -s -Q -m -i $mtools_image <${#entries[@]} entries from the staging directory> ::/"
|
||||
mcopy -s -Q -m -i "$mtools_image" "${entries[@]}" "::/"
|
||||
|
||||
partial_output=""
|
||||
printf 'Built Windows 11 installation disk: %s\n' "$output"
|
||||
Reference in new issue
Block a user