From e639f59113853372d35425f9a8057ce67ce9b44a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tuomas=20R=C3=A4s=C3=A4nen?= Date: Fri, 10 Jan 2025 21:34:44 +0200 Subject: [PATCH 01/15] New script: puavo-get-disk-of-filepath It prints out the disk devpath of the given file (or dir, which is file too). --- parts/ltsp/puavo-install/Makefile | 1 + .../puavo-install/puavo-get-disk-of-filepath | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100755 parts/ltsp/puavo-install/puavo-get-disk-of-filepath diff --git a/parts/ltsp/puavo-install/Makefile b/parts/ltsp/puavo-install/Makefile index 0d1bfb639f..b9d8531433 100644 --- a/parts/ltsp/puavo-install/Makefile +++ b/parts/ltsp/puavo-install/Makefile @@ -36,6 +36,7 @@ install : installdirs puavo-client-daemon \ puavo-disk-clone \ puavo-disk-erase \ + puavo-get-disk-of-filepath \ puavo-install \ puavo-install-and-update-ltspimages \ puavo-install-grub \ diff --git a/parts/ltsp/puavo-install/puavo-get-disk-of-filepath b/parts/ltsp/puavo-install/puavo-get-disk-of-filepath new file mode 100755 index 0000000000..2e2f8bc732 --- /dev/null +++ b/parts/ltsp/puavo-install/puavo-get-disk-of-filepath @@ -0,0 +1,35 @@ +#!/bin/bash + +set -eu +set -o pipefail + +if [ $# -ne 1 ]; then + echo "ERROR: invalid number of args ($#), expected 1" >&2 + echo "Usage: $0 FILEPATH" >&2 + exit 1 +fi + +filepath=$1 +shift + +majmin=$(stat --format '%Hd:%Ld' "${filepath}") || { + echo 'ERROR: failed to find filesystem' >&2 + exit 1 +} + +if [ -z "${majmin}" ]; then + echo 'ERROR: failed to find filesystem (empty stat output)' >&2 + exit 1 +fi + +disk_devpath=$(lsblk -p -J -s | jq --arg majmin "${majmin}" -r '.. | select(."maj:min"? == $majmin) | .. | select(.type? == "disk").name') || { + echo "ERROR: failed to find disk (lsblk or jq failed)'" >&2 + exit 1 +} + +if [ -z "${disk_devpath}" ]; then + echo 'ERROR: failed to find disk (empty jq output)' >&2 + exit 1 +fi + +echo -n "${disk_devpath}" From d9c3508d878fb8b5c52cafc11868341ccf3c688c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tuomas=20R=C3=A4s=C3=A4nen?= Date: Fri, 6 Dec 2024 11:11:13 +0200 Subject: [PATCH 02/15] puavo-reset-windows: add install-boot-dir mode New usage: puavo-reset-windows --mode install-boot-dir [--force] WIN_C_PARTITION_DEVPATH BOOT_DIR This command installs Windows boot manager from a boot WIM image to to BOOT_DIR. It requires correctly Windows C Partition at WIN_C_PARTITION_DEVPATH. Boot WIM image is selected based installed Windows. --- debs/puavo-os/debian/control | 1 + parts/ltsp/puavo-install/puavo-reset-windows | 226 ++++++++++++++++--- 2 files changed, 193 insertions(+), 34 deletions(-) diff --git a/debs/puavo-os/debian/control b/debs/puavo-os/debian/control index 443432d326..c86514de20 100644 --- a/debs/puavo-os/debian/control +++ b/debs/puavo-os/debian/control @@ -321,6 +321,7 @@ Depends: ${misc:Depends}, ruby-highline, secure-delete, sudo, + util-linux, wget Description: Bits and pieces needed inside the LTSP chroot image. Bits and pieces needed inside the LTSP chroot image. diff --git a/parts/ltsp/puavo-install/puavo-reset-windows b/parts/ltsp/puavo-install/puavo-reset-windows index 3e8d32cf02..56ec51ea05 100755 --- a/parts/ltsp/puavo-install/puavo-reset-windows +++ b/parts/ltsp/puavo-install/puavo-reset-windows @@ -5,22 +5,30 @@ set -o pipefail usage() { cat < Date: Wed, 30 Oct 2024 20:13:17 +0200 Subject: [PATCH 03/15] puavo-reset-windows: add install-c-partition mode New usage: puavo-reset-windows --mode install-c-partition [--force] WIN_C_PARTITION_DEVPATH This causes puavo-reset-windows to install a fresh Win11 Pro from a WIM image to NTFS partition at WIN_C_PARTITION_DEVPATH. --- parts/ltsp/puavo-install/puavo-reset-windows | 65 +++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/parts/ltsp/puavo-install/puavo-reset-windows b/parts/ltsp/puavo-install/puavo-reset-windows index 56ec51ea05..5b52082349 100755 --- a/parts/ltsp/puavo-install/puavo-reset-windows +++ b/parts/ltsp/puavo-install/puavo-reset-windows @@ -6,6 +6,7 @@ set -o pipefail usage() { cat < Date: Sat, 11 Jan 2025 16:15:35 +0200 Subject: [PATCH 04/15] puavo-reset-windows: add install-re-partition mode New usage: puavo-reset-windows --mode install-re-partition [--force] WIN_C_PARTITION_DEVPATH WIN_RE_PARTITION_DEVPATH This causes puavo-reset-windows to install Windows Recovery Environment (certain files from the given WIN C partition) to the partition at WIN_RE_PARTITION_DEVPATH. --- parts/ltsp/puavo-install/puavo-reset-windows | 131 ++++++++++++++++--- 1 file changed, 113 insertions(+), 18 deletions(-) diff --git a/parts/ltsp/puavo-install/puavo-reset-windows b/parts/ltsp/puavo-install/puavo-reset-windows index 5b52082349..0200cb8fa8 100755 --- a/parts/ltsp/puavo-install/puavo-reset-windows +++ b/parts/ltsp/puavo-install/puavo-reset-windows @@ -7,6 +7,7 @@ usage() { cat < Date: Sat, 16 Nov 2024 11:18:42 +0200 Subject: [PATCH 05/15] puavo-manage-efi: [refactor] rename g_umount_on_exit => g_esp_mount_path --- parts/ltsp/puavo-install/puavo-manage-efi | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/parts/ltsp/puavo-install/puavo-manage-efi b/parts/ltsp/puavo-install/puavo-manage-efi index 13fcc59ac7..8fcd04dcd7 100755 --- a/parts/ltsp/puavo-install/puavo-manage-efi +++ b/parts/ltsp/puavo-install/puavo-manage-efi @@ -6,7 +6,7 @@ set -o pipefail g_exitval=1 g_no_efi_is_ok=false g_cmd= -g_umount_on_exit= +g_esp_mount_path= PID_FILE=/run/puavo-manage-efi.pid # Locks this file to ensure only one puavo-manage-efi is running log() { @@ -17,8 +17,8 @@ on_exit() { set +e - if [ -n "${g_umount_on_exit}" ]; then - umount "${g_umount_on_exit}" || log err "failed to umount '${g_umount_on_exit}'" + if [ -n "${g_esp_mount_path}" ]; then + umount "${g_esp_mount_path}" || log err "failed to umount '${g_esp_mount_path}'" fi rm -f "${PID_FILE}" @@ -252,7 +252,7 @@ trap on_exit EXIT echo "$$" >"${PID_FILE}" -g_umount_on_exit=$(esp_mount) +g_esp_mount_path=$(esp_mount) case "${g_cmd}" in enable-windows) From 8272f0270948411b265dec7b1d0785b2f04033e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tuomas=20R=C3=A4s=C3=A4nen?= Date: Fri, 6 Dec 2024 11:11:55 +0200 Subject: [PATCH 06/15] puavo-manage-efi: add support for installing Windows boot manager Installation of Windows boot manager is not simple. 1. Ensure ESP has Windows boot manager and all its files (copied from Boot.wim, by puavo-reset-windows). 2. Generate Boot Configuration Data (BCD) with GPT correct partition UUIDs. 3. Create EFI Boot entry --- parts/ltsp/puavo-install/puavo-manage-efi | 228 +++++++++++++++++++--- 1 file changed, 197 insertions(+), 31 deletions(-) diff --git a/parts/ltsp/puavo-install/puavo-manage-efi b/parts/ltsp/puavo-install/puavo-manage-efi index 8fcd04dcd7..efce59e3b3 100755 --- a/parts/ltsp/puavo-install/puavo-manage-efi +++ b/parts/ltsp/puavo-install/puavo-manage-efi @@ -7,7 +7,11 @@ g_exitval=1 g_no_efi_is_ok=false g_cmd= g_esp_mount_path= +g_esp_devpath= +g_win_c_partition_devpath= +g_win_disk= PID_FILE=/run/puavo-manage-efi.pid # Locks this file to ensure only one puavo-manage-efi is running +ESP_WINDOWS_DIR_SUFFIX='/EFI/Microsoft' log() { logger -t puavo-manage-efi -p "user.${1}" "$2" || true @@ -41,16 +45,107 @@ get_windows_boot_entries_to_change() { ' } +get_esp_devpaths() +{ + lsblk -n -l -o PATH,PARTTYPE -- "$@" | awk '$2 == "c12a7328-f81f-11d2-ba4b-00a0c93ec93b" {print $1}' +} + +get_one_esp_devpath() +{ + local esp_devpath esp_devpath_count line + + esp_devpath_count=0 + + while read -r line; do + if [ -z "${line}" ]; then + continue + fi + esp_devpath="${line}" + log notice "Found ESP at '${esp_devpath}'" + esp_devpath_count=$((esp_devpath_count + 1)) + done < <(get_esp_devpaths "$@") || { + log err 'failed to find ESP' + return 1 + } + + if [ ${esp_devpath_count} -eq 0 ]; then + log err 'failed to find ESP' + return 1 + elif [ ${esp_devpath_count} -gt 1 ]; then + log err 'found multiple ESPs' + return 1 + fi + + echo -n "${esp_devpath}" +} + +get_partnum() +{ + local devpath partnum + + devpath="$1" + shift + + partnum=$(stat --format %Lr "${devpath}") || { + log err 'failed to get the partition number' + return 1 + } + + if [ "$partnum" -lt 1 ]; then + log err "file '${devpath}' is not a block device" + return 1 + fi + + echo -n "$partnum" +} + +get_disk() +{ + local part_devpath disk_devpath + + part_devpath=$1 + shift + + disk_devpath=$(lsblk -p -J -s "${part_devpath}" | jq -r '.blockdevices[0].children[0] | select(.type == "disk").name') || { + log err "failed to find disk for '${part_devpath}'" + return 1 + } + + if [ -z "${disk_devpath}" ]; then + log err 'failed to find disk (jq empty output)' + return 1 + fi + + echo -n "${disk_devpath}" +} + +efibootmgr_windows_create() { + local esp_partnum + + log notice 'creating a Windows boot entry' + + esp_partnum=$(get_partnum "${g_esp_devpath}") || return 1 + + efibootmgr -q -c -L 'Windows Boot Manager' -l '\EFI\Microsoft\Boot\bootmgfw.efi' -d "${g_win_disk}" -p "${esp_partnum}" || { + log err 'failed to create a Windows boot entry' + return 1 + } + + log notice 'created a Windows boot entry' +} + efibootmgr_windows() { local boot_entry efibootmgr_flag enable_or_disable_msg efibootmgr_flag=$1 + shift case "$efibootmgr_flag" in -A) enable_or_disable_msg='disabling' ;; -a) enable_or_disable_msg='enabling' ;; *) return 1;; esac + for boot_entry in $(get_windows_boot_entries_to_change "$efibootmgr_flag"); do log notice "${enable_or_disable_msg} Windows boot entry ${boot_entry}" efibootmgr -q "$efibootmgr_flag" -b "$boot_entry" || return 1 @@ -60,51 +155,47 @@ efibootmgr_windows() { } esp_mount() { - local esp_devpath esp_boot_efi_mounts + local esp_devpath esp_or_boot_efi_mount - esp_devpath=$(lsblk -n -l -o PATH,PARTTYPE | awk '$2 == "c12a7328-f81f-11d2-ba4b-00a0c93ec93b" {print $1}') || return 1 - if [ -z "${esp_devpath}" ]; then - log err 'EFI system partition not found' - return 1 - fi - - esp_boot_efi_mounts=$(awk "-vdev=${esp_devpath}" '$1 == dev && $2 == "/boot/efi" {print $2}' /proc/mounts) || return 1 + esp_devpath="$1" + shift - # Umount if already mounted to /boot/efi to ensure we control mount options. - if [ -n "${esp_boot_efi_mounts}" ]; then - log warning '/boot/efi was already mounted, umounting it' - umount /boot/efi || return 1 - fi + # Umount if already mounted to ensure we control mount options. + while IFS= read -r -d $'\0' esp_or_boot_efi_mount; do + log warning "'${esp_or_boot_efi_mount}' was already mounted, umounting it" + umount "${esp_or_boot_efi_mount}" || return 1 + done < <(awk "-vdev=${esp_devpath}" '$1 == dev && $2 == "/boot/efi" { printf "%s" $2 }' /proc/mounts) || return 1 mkdir -p /boot/efi || return 1 mount -onodev,nosuid "${esp_devpath}" /boot/efi || return 1 - echo /boot/efi + echo -n /boot/efi } esp_break_windows() { - local defused_windows_file tmp_defused_windows_file + local defused_windows_file esp_windows_dir tmp_defused_windows_file - defused_windows_file='/boot/efi/EFI/Puavo-defused-Windows.tar.gz' + esp_windows_dir="${g_esp_mount_path}${ESP_WINDOWS_DIR_SUFFIX}" + defused_windows_file="${g_esp_mount_path}/EFI/Puavo-defused-Windows.tar.gz" tmp_defused_windows_file="${defused_windows_file}.tmp" rm -f "$tmp_defused_windows_file" if [ -e "$defused_windows_file" ]; then # nothing to do - if [ -d '/boot/efi/EFI/Microsoft' ]; then - log info "cleaning up /boot/efi/EFI/Microsoft that should not exist" - rm -rf '/boot/efi/EFI/Microsoft' || true + if [ -d "${esp_windows_dir}" ]; then + log info "cleaning up ${esp_windows_dir} that should not exist" + rm -rf "${esp_windows_dir}" || true fi return 0 fi - if [ ! -d '/boot/efi/EFI/Microsoft' ]; then + if [ ! -d "${esp_windows_dir}" ]; then # not doing/logging anything, perhaps we do not have Windows installed # and that should be normal return 0 fi log info 'defusing Windows...' - tar -C '/boot/efi/EFI' -z -c -f "$tmp_defused_windows_file" Microsoft || { + tar -C "${g_esp_mount_path}/EFI" -z -c -f "$tmp_defused_windows_file" Microsoft || { log err 'failed to defuse Windows (tar error)' rm -f "$tmp_defused_windows_file" || true return 1 @@ -114,8 +205,8 @@ esp_break_windows() { log err 'failed to defuse Windows (mv error)' return 1 fi - if ! rm -rf '/boot/efi/EFI/Microsoft'; then - log warning "error cleaning up /boot/efi/EFI/Microsoft" + if ! rm -rf "${esp_windows_dir}"; then + log warning "error cleaning up ${esp_windows_dir}" return 1 fi @@ -124,10 +215,57 @@ esp_break_windows() { return 0 } +esp_install_windows() { + local defused_windows_file esp_windows_dir tmp_windows_dir + + esp_windows_dir="${g_esp_mount_path}${ESP_WINDOWS_DIR_SUFFIX}" + defused_windows_file="${g_esp_mount_path}/EFI/Puavo-defused-Windows.tar.gz" + tmp_windows_dir="${esp_windows_dir}.tmp" + rm -rf "${tmp_windows_dir}" + + if [ -e "${defused_windows_file}" ]; then + log err 'ESP already contains a defused Windows' + return 1 + fi + + if [ -e "${esp_windows_dir}" ]; then + log err 'ESP already contains Windows' + return 1 + fi + + log info 'installing Windows bootloader...' + mkdir -p "${tmp_windows_dir}" || { + log err 'failed to install Windows bootloader (mkdir)' + return 1 + } + puavo-reset-windows --mode install-boot-dir "${g_win_c_partition_devpath}" "${tmp_windows_dir}" || { + log err 'failed to install Windows bootloader (puavo-reset-windows)' + rm -rf "${tmp_windows_dir}" || true + return 1 + } + mv -n -T "${tmp_windows_dir}" "${esp_windows_dir}" || { + log err 'failed to install Windows bootloader (mv)' + rm -rf "${tmp_windows_dir}" || true + return 1 + } + if [ -d "${tmp_windows_dir}" ]; then + # mv failed because the source dir still exists + log err 'failed to install Windows bootloader (mv)' + rm -rf "${esp_windows_dir}" || true + rm -rf "${tmp_windows_dir}" || true + return 1 + fi + + sync || true + + log info 'Windows bootloader installed' +} + esp_fix_windows() { local defused_windows_file tmp_defused_windows_file - defused_windows_file='/boot/efi/EFI/Puavo-defused-Windows.tar.gz' + esp_windows_dir="${g_esp_mount_path}${ESP_WINDOWS_DIR_SUFFIX}" + defused_windows_file="${g_esp_mount_path}/EFI/Puavo-defused-Windows.tar.gz" tmp_defused_windows_file="${defused_windows_file}.tmp" rm -f "$tmp_defused_windows_file" @@ -137,9 +275,9 @@ esp_fix_windows() { fi log info 'repriming Windows...' - tar -C '/boot/efi/EFI' -z -x -f "$defused_windows_file" || { + tar -C "${g_esp_mount_path}/EFI" -z -x -f "$defused_windows_file" || { log err 'error repriming Windows (tar error)' - rm -rf '/boot/efi/EFI/Microsoft' || true + rm -rf "${esp_windows_dir}" || true return 1 } sync || true @@ -162,12 +300,19 @@ efi_enable_windows() { efibootmgr_windows -a } +efi_install_windows() { + esp_install_windows || return 1 + efibootmgr_windows_create +} + has_efi() { grep -q '^efivarfs ' /proc/mounts } usage() { - echo "Usage: $0 [OPTIONS] COMMAND" + echo "Usage: $0 [OPTIONS] disable-windows" + echo " $0 [OPTIONS] enabled-windows" + echo " $0 [OPTIONS] install-windows WIN_C_PARTITION_DEVPATH" echo " $0 --help" } @@ -195,6 +340,7 @@ while [ $# -gt 0 ]; do echo "Commands:" echo " disable-windows disable Windows" echo " enable-windows enable Windows" + echo " install-windows install Windows boot manager to ESP, lookup Windows version from WIN_C_PARTITION_DEVPATH" echo echo "Options:" echo " --no-efi-is-ok exit with status 0 if the system does not use EFI" @@ -220,13 +366,25 @@ while [ $# -gt 0 ]; do esac done -if [ $# -ne 1 ]; then - usage_error "invalid number of arguments ($#), expected 1" +if [ $# -lt 1 ]; then + usage_error 'COMMAND is missing' fi g_cmd=$1 shift +if [ "${g_cmd}" = 'install-windows' ]; then + if [ $# -lt 1 ]; then + usage_error 'WIN_C_PARTITION_DEVPATH is missing' + fi + g_win_c_partition_devpath=$1 + shift +fi + +if [ $# -ne 0 ]; then + usage_error 'too many arguments' +fi + if ! has_efi; then if ${g_no_efi_is_ok}; then log warning "this system does not have EFI" @@ -252,15 +410,23 @@ trap on_exit EXIT echo "$$" >"${PID_FILE}" -g_esp_mount_path=$(esp_mount) - case "${g_cmd}" in enable-windows) + g_esp_devpath=$(get_one_esp_devpath) + g_esp_mount_path=$(esp_mount "${g_esp_devpath}") efi_enable_windows ;; disable-windows) + g_esp_devpath=$(get_one_esp_devpath) + g_esp_mount_path=$(esp_mount "${g_esp_devpath}") efi_disable_windows ;; + install-windows) + g_win_disk=$(get_disk "${g_win_c_partition_devpath}") + g_esp_devpath=$(get_one_esp_devpath "${g_win_disk}") + g_esp_mount_path=$(esp_mount "${g_esp_devpath}") + efi_install_windows + ;; *) usage_error "invalid command '${g_cmd}'" ;; From b01bd4d2bbbf39116bbc86199bb632006711abd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tuomas=20R=C3=A4s=C3=A4nen?= Date: Fri, 10 Jan 2025 21:36:36 +0200 Subject: [PATCH 07/15] puavo-manage-efi: require excplicit disk devpath as arg Previously, puavo-manage-efi expected to find just one ESP, but it's certainly possible that there are multiple ESPs on different disks etc. Require the disk of the Windows installation explicitly as an argument. setup_grub_environment script assumes the Windows installation disk is the same which contains /images. Which is quite reasonable assumption: we only support single-disk installations for dual-boot hosts. --- .../puavo-conf/scripts/setup_grub_environment | 11 +++++- parts/ltsp/puavo-install/puavo-manage-efi | 38 ++++++++++++------- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/parts/core/puavo-conf/scripts/setup_grub_environment b/parts/core/puavo-conf/scripts/setup_grub_environment index 4462ccdd05..bc2971523e 100755 --- a/parts/core/puavo-conf/scripts/setup_grub_environment +++ b/parts/core/puavo-conf/scripts/setup_grub_environment @@ -74,12 +74,19 @@ setup_timeout() { } setup_windows_enabled() { + local grubdisk + + grubdisk=$(puavo-get-disk-of-filepath /images) || { + echo 'ERROR: failed to get disk of images' >&2 + return 1 + } + if [ "$(puavo-conf puavo.grub.windows.enabled)" = 'true' ]; then - puavo-manage-efi --no-efi-is-ok enable-windows || return 1 + puavo-manage-efi --no-efi-is-ok enable-windows "${grubdisk}" || return 1 grubedit set "puavo_grub_windows_enabled=true" || return 1 else grubedit unset puavo_grub_windows_enabled || return 1 - puavo-manage-efi --no-efi-is-ok disable-windows || return 1 + puavo-manage-efi --no-efi-is-ok disable-windows "${grubdisk}" || return 1 fi return 0 diff --git a/parts/ltsp/puavo-install/puavo-manage-efi b/parts/ltsp/puavo-install/puavo-manage-efi index efce59e3b3..f78b94f9ea 100755 --- a/parts/ltsp/puavo-install/puavo-manage-efi +++ b/parts/ltsp/puavo-install/puavo-manage-efi @@ -310,8 +310,8 @@ has_efi() { } usage() { - echo "Usage: $0 [OPTIONS] disable-windows" - echo " $0 [OPTIONS] enabled-windows" + echo "Usage: $0 [OPTIONS] disable-windows WIN_DISK_DEVPATH" + echo " $0 [OPTIONS] enabled-windows WIN_DISK_DEVPATH" echo " $0 [OPTIONS] install-windows WIN_C_PARTITION_DEVPATH" echo " $0 --help" } @@ -338,8 +338,8 @@ while [ $# -gt 0 ]; do echo "Manage EFI boot. Modifies EFI boot order and ESP." echo echo "Commands:" - echo " disable-windows disable Windows" - echo " enable-windows enable Windows" + echo " disable-windows disable Windows installed on WIN_DISK_DEVPATH" + echo " enable-windows enable Windows installed on WIN_DISK_DEVPATH" echo " install-windows install Windows boot manager to ESP, lookup Windows version from WIN_C_PARTITION_DEVPATH" echo echo "Options:" @@ -373,13 +373,25 @@ fi g_cmd=$1 shift -if [ "${g_cmd}" = 'install-windows' ]; then - if [ $# -lt 1 ]; then - usage_error 'WIN_C_PARTITION_DEVPATH is missing' - fi - g_win_c_partition_devpath=$1 - shift -fi +case "${g_cmd}" in + 'install-windows') + if [ $# -ne 1 ]; then + usage_error 'WIN_C_PARTITION_DEVPATH is missing' + fi + g_win_c_partition_devpath=$1 + shift + ;; + 'enable-windows'|'disable-windows') + if [ $# -ne 1 ]; then + usage_error 'WIN_DISK_DEVPATH is missing' + fi + g_win_disk=$1 + shift + ;; + *) + usage_error "invalid command '${g_cmd}'" + ;; +esac if [ $# -ne 0 ]; then usage_error 'too many arguments' @@ -412,12 +424,12 @@ echo "$$" >"${PID_FILE}" case "${g_cmd}" in enable-windows) - g_esp_devpath=$(get_one_esp_devpath) + g_esp_devpath=$(get_one_esp_devpath "${g_win_disk}") g_esp_mount_path=$(esp_mount "${g_esp_devpath}") efi_enable_windows ;; disable-windows) - g_esp_devpath=$(get_one_esp_devpath) + g_esp_devpath=$(get_one_esp_devpath "${g_win_disk}") g_esp_mount_path=$(esp_mount "${g_esp_devpath}") efi_disable_windows ;; From 02773441d2419460144c01e1a28ccacc58ad2d45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tuomas=20R=C3=A4s=C3=A4nen?= Date: Wed, 11 Sep 2024 20:16:45 +0300 Subject: [PATCH 08/15] puavo-setup-filesystems: add support for Windows partitioning This commit adds new command line and prompt options for optionally partitioning the disk (and creating filesystems) for Windows. - Preseeding is supported. - Only UEFI/GPT partitioning scheme is supported. - Default Windows partition sizes are minimum (hard-minimum is obtained from Windows docs) - Windows partition sizes are validated - The default is to NOT create Windows partitions and filesystems. --- parts/ltsp/puavo-install/puavo-install | 1 + .../puavo-install/puavo-make-install-disk | 4 +- .../puavo-install/puavo-setup-filesystems | 189 ++++++++++++++++-- 3 files changed, 173 insertions(+), 21 deletions(-) diff --git a/parts/ltsp/puavo-install/puavo-install b/parts/ltsp/puavo-install/puavo-install index 6915d841c6..ebccb4348c 100755 --- a/parts/ltsp/puavo-install/puavo-install +++ b/parts/ltsp/puavo-install/puavo-install @@ -138,6 +138,7 @@ ask_preseed() { ask-imageoverlay-size force-imageoverlay-size ask-install-without-raid force-install-without-raid ask-partition force-partition + ask-setup-windows force-setup-windows ask-unpartitioned-space force-unpartitioned-space ask-wipe-partition force-wipe-partition ask-write-partitions force-write-partitions diff --git a/parts/ltsp/puavo-install/puavo-make-install-disk b/parts/ltsp/puavo-install/puavo-make-install-disk index bcc0587544..52e5fcc0e5 100755 --- a/parts/ltsp/puavo-install/puavo-make-install-disk +++ b/parts/ltsp/puavo-install/puavo-make-install-disk @@ -133,13 +133,15 @@ if [ -n "$target_image" ]; then puavo-setup-filesystems --force-disk-device "${loopdev#/dev/}" \ --force-partition whole \ + --force-setup-windows no \ --force-wipe-partition no \ --force-write-partitions yes \ --hosttype diskinstaller \ --loopback-only \ --partition-label "$partition_label" else - puavo-setup-filesystems --hosttype diskinstaller \ + puavo-setup-filesystems --force-setup-windows no \ + --hosttype diskinstaller \ --partition-label "$partition_label" fi diff --git a/parts/ltsp/puavo-install/puavo-setup-filesystems b/parts/ltsp/puavo-install/puavo-setup-filesystems index 021c1ca436..e0c1c423bb 100755 --- a/parts/ltsp/puavo-install/puavo-setup-filesystems +++ b/parts/ltsp/puavo-install/puavo-setup-filesystems @@ -7,6 +7,16 @@ require 'getoptlong' require 'highline/import' require 'open3' +GPT_PARTTYPE_LVM = 'e6d6d379-f507-44c2-a23c-238f2a3df928' +GPT_PARTTYPE_EFI = 'c12a7328-f81f-11d2-ba4b-00a0c93ec93b' + +GPT_PARTLABEL_PUAVO_INSTALLED_WINDOWS_C = 'Puavo_Installed_Windows_C' + +# Why 20G: https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/configure-uefigpt-based-hard-drive-partitions?view=windows-11#windows-partition +DUALBOOT_LAPTOP_WINDOWS_MIN_SIZE_G = 20 +DUALBOOT_LAPTOP_PUAVOOS_MIN_SIZE_G = 70 # ~60G + 10G home +DUALBOOT_LAPTOP_DISK_MIN_SIZE_G = DUALBOOT_LAPTOP_WINDOWS_MIN_SIZE_G + DUALBOOT_LAPTOP_PUAVOOS_MIN_SIZE_G + # XXX this should be elsewhere, other scripts might find this useful module PuavoFacts Puavodir = '/etc/puavo' @@ -288,17 +298,68 @@ module DiskHandler "r\nh\n1 2 3\nn\n\ny\n\nn\n\nn\nw\ny\n") end - def self.get_partition(device, partitioning_scheme, numstart) - special_disk_regexp = %r{\A((loop|md|mmcblk)[0-9]+|nvme[0-9]+n[0-9]+)\z} - part_prefix = device.match(special_disk_regexp) ? 'p' : '' - puavo_partnum = %w(UEFI/GPT UEFI/MBR+GPT).include?(partitioning_scheme) \ - ? (numstart + 2) \ - : numstart - [ device, part_prefix, puavo_partnum ].join + def self.get_partitions(device) + IO.popen([ 'lsblk', '-l', '-n', '-oTYPE,NAME', "/dev/#{ device }" ]).readlines.map do |line| + line.strip.split(' ') + end.select do |lineparts| + lineparts.length > 1 && lineparts[0].downcase == 'part' + end.map do |lineparts| + lineparts[1..].join(' ') + end + end + + def self.get_partition_by_partnum(device, partnum) + partitions = get_partitions(device) + + raise "Device '#{ device }' does not have any partitions" if partitions.empty? + raise "Partition number '#{ partnum }' not found on '#{ device }'" if partitions.length < partnum + + partitions[partnum - 1] + end + + def self.wait_partition_by_partnum(device, partnum, try_count:, try_interval: 1) + try_count.times do + warn "Looking for partition '#{ partnum }' on device '#{ device }'..." + return get_partition_by_partnum(device, partnum) rescue sleep(try_interval) + end + raise "Partition '#{ partnum }' not found on device '#{ device }'" + end + + def self.get_gpt_partitions_by_parttype(device, parttype) + IO.popen([ 'lsblk', '-l', '-n', '-oPARTTYPE,NAME', "/dev/#{ device }" ]).readlines.map do |line| + line.strip.split(' ') + end.select do |lineparts| + lineparts.length > 1 && lineparts[0].downcase == parttype.downcase + end.map do |lineparts| + lineparts[1..].join(' ') + end + end + + def self.get_one_gpt_partition_by_parttype(device, parttype) + partitions = get_gpt_partitions_by_parttype(device, parttype) + + raise "GPT partition of type '#{ parttype }' not found on '#{ device }'" if partitions.empty? + raise "Multiple GPT partitions of type '#{ parttype }' found on '#{ device }'" if partitions.length > 1 + + partitions[0] + end + + def self.wait_one_gpt_partition_by_parttype(device, parttype, try_count:, try_interval: 1) + try_count.times do + warn "Looking for GPT partition of type '#{ parttype }' on device '#{ device }'..." + return get_one_gpt_partition_by_parttype(device, parttype) rescue sleep(try_interval) + end + raise "GPT partition of type '#{ parttype }' not found on device '#{ device }'" end def self.get_puavo_partition(device, partitioning_scheme) - get_partition(device, partitioning_scheme, 1) + return get_partition_by_partnum(device, 1) if partitioning_scheme == 'BIOS/MBR' + + # It should not be possible to have multiple LVM partitions, + # because at this stage, we have already created a new partition + # table. If partition is not found in 30secs, then it's most + # probably a progamming error; check gdisk/fdisk inputs. + return wait_one_gpt_partition_by_parttype(device, GPT_PARTTYPE_LVM, try_count: 30) end def self.do_fs_setup(conf, vgname, partitioning_scheme) @@ -307,8 +368,6 @@ module DiskHandler fs_setup_phases = [] if fs_conf['partition'] == 'whole' then fs_setup_phases += [ :wipe_device ] if conf['wipe'] - fs_conf['partition'] = get_puavo_partition(fs_conf['device'], - partitioning_scheme) fs_setup_phases += [ :create_partitions, :puavo_filesystems ] else fs_setup_phases += [ :wipe_partition ] if conf['wipe'] @@ -333,10 +392,18 @@ module DiskHandler end def self.create_partitions(fs_conf, partitioning_scheme) + gpt_format = "2\no\ny\nn\n1\n\n+1M\nef02\nn\n2\n\n+512M\nef00\nn\n3\n\n\n8e00\nw\ny\n" + + unless fs_conf['windows_size'].nil? then + raise 'Windows can be setup only in UEFI/GPT partitioning scheme!' unless partitioning_scheme == 'UEFI/GPT' + gpt_format = "2\no\ny\nn\n1\n\n+1M\nef02\nn\n2\n\n+512M\nef00\nn\n3\n\n+16M\n0c01\nn\n4\n\n+#{ fs_conf['windows_size'] }\n0700\nc\n4\n#{ GPT_PARTLABEL_PUAVO_INSTALLED_WINDOWS_C }\nn\n5\n\n+768M\n2700\nn\n6\n\n\n8e00\nw\ny\n" + end + setup_partition(fs_conf['device'], partitioning_scheme, "n\np\n1\n#{ fs_conf['first_sector'] }\n\nt\n8e\na\np\nw\n", - "2\no\ny\nn\n1\n\n+1M\nef02\nn\n2\n\n+512M\nef00\nn\n3\n\n\n8e00\nw\ny\n", + gpt_format, "r\nh\n1 2 3\nn\n\ny\n\nn\n\nn\nw\ny\n") + fs_conf['partition'] = get_puavo_partition(fs_conf['device'], partitioning_scheme) end def self.non_swap_filesystems(hosttype) @@ -345,7 +412,7 @@ module DiskHandler end def self.create_efi_filesystem(device) - efi_partition = get_partition(device, 'UEFI/GPT', 0) + efi_partition = wait_one_gpt_partition_by_parttype(device, GPT_PARTTYPE_EFI, try_count: 30) run('mkfs.fat', '-F32', "/dev/#{ efi_partition }") end @@ -440,6 +507,15 @@ module QueryDiskInfo return devices_by_size[biggest_common_disksize].sort end + def self.get_blkdev_size_g(device) + IO.popen([ 'lsblk', '-d', '-b', '-l', '-n', '-oSIZE', "/dev/#{ device }" ]) do |io| + output = io.read + io.close + raise "lsblk failed" unless $?.success? + output.to_i / 1024 ** 3 + end + end + def self.ask_device() choosable_disk_devices = [] @@ -502,12 +578,62 @@ module QueryDiskInfo UI::ask_with_default(prompt, 'no', UI::WritePartitionsPrompt) == 'yes' end + def self.ask_windows_setup(hosttype, device, partition, partitioning_scheme) + 30.times do # Invalid preseeds shall not cause infinite loops. + setup_windows_answer = UI::ask_with_default("\nSetup Windows partitions? (yes/no)", 'no', UI::SetupWindowsPrompt) + case setup_windows_answer + when 'yes' + unless partition == 'whole' then + UI::colormsg('Windows partitions can be setup only when partitioning the whole device.', HighLine::RED) + next + end + + unless partitioning_scheme == 'UEFI/GPT' then + UI::colormsg('Windows partitions can be setup only in UEFI/GPT partitioning scheme.', HighLine::RED) + next + end + + unless hosttype == 'laptop' then + UI::colormsg('Windows partitions can be setup only in laptops.', HighLine::RED) + next + end + + disk_size_g = get_blkdev_size_g(device) + unless disk_size_g >= DUALBOOT_LAPTOP_DISK_MIN_SIZE_G then + UI::colormsg("Windows partitions can be setup only on #{ DUALBOOT_LAPTOP_DISK_MIN_SIZE_G }G or larger disks.", HighLine::RED) + next + end + + windows_max_size_g = disk_size_g - DUALBOOT_LAPTOP_PUAVOOS_MIN_SIZE_G + windows_size_tuple = UI::ask_size_with_default("Size of Windows Local Disk (C:) partition:", + "#{ [40, windows_max_size_g].min }G", UI::WindowsSizePrompt) + + windows_size_g = Size::to_bytes(windows_size_tuple) / 1024 ** 3 + unless windows_size_g >= DUALBOOT_LAPTOP_WINDOWS_MIN_SIZE_G then + UI::colormsg("Minimum Windows Local Disk (C:) partition size is #{ DUALBOOT_LAPTOP_WINDOWS_MIN_SIZE_G }G.", HighLine::RED) + next + end + + unless windows_size_g <= windows_max_size_g then + UI::colormsg("Maximum Windows Local Disk (C:) partition size on this disk (#{ disk_size_g }G) is #{ windows_max_size_g }G.", HighLine::RED) + next + end + + return windows_size_tuple.join() + when 'no' + return nil + end + end + raise 'Windows setup prompts exceeded.' + end + def self.ask_device_and_partition_with_confirmation(hosttype, raid_setup_done, partitioning_scheme) - do_it = false device = nil - partition = nil + do_it = false first_sector = nil + partition = nil + windows_size = nil wipe = false until do_it do @@ -538,6 +664,8 @@ module QueryDiskInfo end end + windows_size = QueryDiskInfo::ask_windows_setup(hosttype, device, partition, partitioning_scheme) + msg = "\nIt is possible to wipe disk/partition before installing,\n" \ + "but this can take a rather long time.\n" \ + "Should we wipe the disk/partition before installing? (yes/no)" @@ -557,11 +685,12 @@ module QueryDiskInfo end { - 'device' => device, - 'first_sector' => first_sector, - 'hosttype' => hosttype, - 'partition' => partition, - 'wipe' => wipe, + 'device' => device, + 'first_sector' => first_sector, + 'hosttype' => hosttype, + 'partition' => partition, + 'windows_size' => windows_size, + 'wipe' => wipe, } end @@ -688,10 +817,12 @@ module UI ImageoverlaySizePrompt, InstallWithoutRaidPrompt, PartitionPrompt, + SetupWindowsPrompt, UnpartitionedSpacePrompt, + WindowsSizePrompt, WipePartitionPrompt, WritePartitionsPrompt, - = *(1..7) + = *(1..9) @ask_forced = {} @defaults = {} @@ -718,14 +849,18 @@ puavo-setup-filesystems [OPTIONS] --ask-imageoverlay-size ask with default for imageoverlay size --ask-install-without-raid ask with default for install without raid --ask-partition ask with default for partition + --ask-setup-windows ask with default for setup windows --ask-unpartitioned-space ask with default for unpartitioned space --ask-wipe-partition ask with default for wipe partition + --ask-windows-size ask with default for windows size --ask-write-partitions ask with default for write partitions --force-disk-device force value for disk device --force-imageoverlay-size force value for imageoverlay size --force-install-without-raid force value for install without raid --force-partition force value for partition + --force-setup-windows force value for setup windows --force-unpartitioned-space force value for unpartitioned space + --force-windows-size force value for windows size --force-wipe-partition force value for wipe partition --force-write-partitions force value for write partitions @@ -744,14 +879,18 @@ EOF [ '--ask-imageoverlay-size' , GetoptLong::REQUIRED_ARGUMENT ], [ '--ask-install-without-raid' , GetoptLong::REQUIRED_ARGUMENT ], [ '--ask-partition' , GetoptLong::REQUIRED_ARGUMENT ], + [ '--ask-setup-windows' , GetoptLong::REQUIRED_ARGUMENT ], [ '--ask-unpartitioned-space' , GetoptLong::REQUIRED_ARGUMENT ], + [ '--ask-windows-size' , GetoptLong::REQUIRED_ARGUMENT ], [ '--ask-wipe-partition' , GetoptLong::REQUIRED_ARGUMENT ], [ '--ask-write-partitions' , GetoptLong::REQUIRED_ARGUMENT ], [ '--force-disk-device' , GetoptLong::REQUIRED_ARGUMENT ], [ '--force-imageoverlay-size' , GetoptLong::REQUIRED_ARGUMENT ], [ '--force-install-without-raid', GetoptLong::REQUIRED_ARGUMENT ], [ '--force-partition' , GetoptLong::REQUIRED_ARGUMENT ], + [ '--force-setup-windows' , GetoptLong::REQUIRED_ARGUMENT ], [ '--force-unpartitioned-space' , GetoptLong::REQUIRED_ARGUMENT ], + [ '--force-windows-size' , GetoptLong::REQUIRED_ARGUMENT ], [ '--force-wipe-partition' , GetoptLong::REQUIRED_ARGUMENT ], [ '--force-write-partitions' , GetoptLong::REQUIRED_ARGUMENT ], [ '--hosttype' , GetoptLong::REQUIRED_ARGUMENT ], @@ -772,8 +911,12 @@ EOF @defaults[InstallWithoutRaidPrompt] = arg when '--ask-partition' @defaults[PartitionPrompt] = arg + when '--ask-setup-windows' + @defaults[SetupWindowsPrompt] = arg when '--ask-unpartitioned-space' @defaults[UnpartitionedSpacePrompt] = arg + when '--ask-windows-size' + @defaults[WindowsSizePrompt] = arg when '--ask-wipe-partition' @defaults[WipePartitionPrompt] = arg when '--ask-write-partitions' @@ -790,9 +933,15 @@ EOF when '--force-partition' @defaults[PartitionPrompt] = arg @force_defaults[PartitionPrompt] = true + when '--force-setup-windows' + @defaults[SetupWindowsPrompt] = arg + @force_defaults[SetupWindowsPrompt] = true when '--force-unpartitioned-space' @defaults[UnpartitionedSpacePrompt] = arg @force_defaults[UnpartitionedSpacePrompt] = true + when '--force-windows-size' + @defaults[WindowsSizePrompt] = arg + @force_defaults[WindowsSizePrompt] = true when '--force-wipe-partition' @defaults[WipePartitionPrompt] = arg @force_defaults[WipePartitionPrompt] = true From 435a7d2e0b190fdb193f5a7d064536af12b97a21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tuomas=20R=C3=A4s=C3=A4nen?= Date: Sun, 20 Oct 2024 20:47:03 +0300 Subject: [PATCH 09/15] puavo-install: add create-bcd library script It creates device-specific BCD from a template BCD by scanning partition UUIDs filling them in to the template. The base is https://github.com/U1F984/create_bcd 1ecfabdcde499db173595134670e2e746222d0a9 We have made some improvements on top of it: - added _get_uuids() function - added command line argument parsing Requires python3-hivex. This commit also includes a "template" minimal BCD, which is used by create-bcd to create a device-specific read BCD. --- debs/puavo-os/debian/control | 1 + parts/ltsp/puavo-install/Makefile | 4 + parts/ltsp/puavo-install/lib/create-bcd | 722 +++++++++++++++++++++++ parts/ltsp/puavo-install/lib/minimal-bcd | Bin 0 -> 8192 bytes 4 files changed, 727 insertions(+) create mode 100755 parts/ltsp/puavo-install/lib/create-bcd create mode 100644 parts/ltsp/puavo-install/lib/minimal-bcd diff --git a/debs/puavo-os/debian/control b/debs/puavo-os/debian/control index c86514de20..3de0381bec 100644 --- a/debs/puavo-os/debian/control +++ b/debs/puavo-os/debian/control @@ -315,6 +315,7 @@ Depends: ${misc:Depends}, pv, python3, python3-dbus, + python3-hivex, python3-notify2, rdiff, ruby-dbus, diff --git a/parts/ltsp/puavo-install/Makefile b/parts/ltsp/puavo-install/Makefile index b9d8531433..8b8afa46fe 100644 --- a/parts/ltsp/puavo-install/Makefile +++ b/parts/ltsp/puavo-install/Makefile @@ -52,7 +52,11 @@ install : installdirs $(INSTALL_DATA) -t $(DESTDIR)$(datarootdir)/dbus-1/system-services \ dbus-1/org.puavo.client.Daemon.service + $(INSTALL_DATA) -t $(DESTDIR)$(libdir)/puavo-ltsp-install \ + lib/minimal-bcd + $(INSTALL_PROGRAM) -t $(DESTDIR)$(libdir)/puavo-ltsp-install \ + lib/create-bcd \ lib/is-update-available \ lib/ls-old-images-and-rdiffs \ lib/puavo-image-preinst \ diff --git a/parts/ltsp/puavo-install/lib/create-bcd b/parts/ltsp/puavo-install/lib/create-bcd new file mode 100755 index 0000000000..96e8cb38f8 --- /dev/null +++ b/parts/ltsp/puavo-install/lib/create-bcd @@ -0,0 +1,722 @@ +#!/usr/bin/env python3 + +# Based on Leo Tietz's create_bcd: +# https://github.com/U1F984/create_bcd (1ecfabdcde499db173595134670e2e746222d0a9) +# Original work is licensed under GPLv3 + +# Changes to the original: +# - added _get_uuids() function +# - added command line argument parsing + +# Standard library imports +import argparse +import json +import struct +import subprocess +import uuid +from os import path + +# Third-party imports +import hivex +from hivex.hive_types import * + +LOCALE = r"en-US" + +CONST_DESC = "Description" +CONST_ELEMENTS = "Elements" +CONST_ELEMENT = "Element" + +OBJECT_TYPE_APPLICATION = 0x1000_0000 +OBJECT_TYPE_INHERIT = 0x2000_0000 +OBJECT_TYPE_DEVICE = 0x3000_0000 + +OBJECT_APPLICATION_FIRMWARE = 0x0010_0000 +OBJECT_APPLICATION_WIN_BOOT = 0x0020_0000 +OBJECT_APPLICATION_LEGACY_LOADER = 0x0030_0000 +OBJECT_APPLICATION_REAL_MODE = 0x0040_0000 + +OBJECT_INHERITABLE_BY_ANY = 0x0010_0000 +OBJECT_INHERITABLE_BY_APPLICATION = 0x0020_0000 +OBJECT_INHERITABLE_BY_DEVICE = 0x0030_0000 + + +def create_object_type(object_type, object_type_flags, object_id, test=None): + val = object_type + object_type_flags + object_id + if test: + assert val == test, f"create_object_type failed, was {val}, should be {test}" + return val + + +ELEMENT_CLASS_LIBRARY = 0x1000_0000 +ELEMENT_CLASS_APPLICATION = 0x2000_0000 +ELEMENT_CLASS_DEVICE = 0x3000_0000 +ELEMENT_CLASS_TEMPLATE = 0x4000_0000 + +ELEMENT_FORMAT_DEVICE = 0x0100_0000 +ELEMENT_FORMAT_STRING = 0x0200_0000 +ELEMENT_FORMAT_GUID = 0x0300_0000 +ELEMENT_FORMAT_GUID_LIST = 0x0400_0000 +ELEMENT_FORMAT_INTEGER = 0x0500_0000 +ELEMENT_FORMAT_BOOLEAN = 0x0600_0000 +ELEMENT_FORMAT_INTEGER_LIST = 0x0700_0000 + + +def create_element_type(element_class, element_format, element_id, test=None): + val = element_class + element_format + element_id + val_str = hex(val)[2:].zfill(8) + if test: + assert ( + val_str == test + ), f"create_element_type failed, was {val_str}, should be {test}" + return val_str + + +# Global element types +BCDE_LIBRARY_TYPE_INHERIT = create_element_type( + ELEMENT_CLASS_LIBRARY, ELEMENT_FORMAT_GUID_LIST, 0x6, "14000006" +) +BCDE_LIBRARY_TYPE_APPLICATION_DEVICE = create_element_type( + ELEMENT_CLASS_LIBRARY, ELEMENT_FORMAT_DEVICE, 0x1, "11000001" +) +BCDE_LIBRARY_TYPE_APPLICATION_PATH = create_element_type( + ELEMENT_CLASS_LIBRARY, ELEMENT_FORMAT_STRING, 0x2, "12000002" +) +BCDE_LIBRARY_TYPE_DESCRIPTION = create_element_type( + ELEMENT_CLASS_LIBRARY, ELEMENT_FORMAT_STRING, 0x4, "12000004" +) +BCDE_LIBRARY_TYPE_PREFERRED_LOCALE = create_element_type( + ELEMENT_CLASS_LIBRARY, ELEMENT_FORMAT_STRING, 0x5, "12000005" +) + +# EMS Settings +GUID_EMS_SETTINGS_GROUP = "{0ce4991b-e6b3-4b16-b23c-5e0d9250e5d9}" +OBJECT_TYPE_EMS_SETTINGS = create_object_type( + OBJECT_TYPE_INHERIT, OBJECT_INHERITABLE_BY_ANY, 0x0, 0x20100000 +) +BCDE_LIBRARY_TYPE_EMS_ENABLED = create_element_type( + ELEMENT_CLASS_LIBRARY, ELEMENT_FORMAT_BOOLEAN, 0x20, "16000020" +) + +GUID_RESUME_LOADER_SETTINGS_GROUP = "{1afa9c49-16ab-4a5c-901b-212802da9460}" +OBJECT_TYPE_RESUME_LOADER_SETTINGS = create_object_type( + OBJECT_TYPE_INHERIT, OBJECT_INHERITABLE_BY_APPLICATION, 0x4, 0x20200004 +) +# Debugger settings +GUID_DEBUGGER_SETTINGS_GROUP = "{4636856e-540f-4170-a130-a84776f4c654}" +OBJECT_TYPE_DEBUGGER_SETTINGS = create_object_type( + OBJECT_TYPE_INHERIT, OBJECT_INHERITABLE_BY_ANY, 0x0, 0x20100000 +) +BCDE_LIBRARY_TYPE_DEBUGGER_TYPE = create_element_type( + ELEMENT_CLASS_LIBRARY, ELEMENT_FORMAT_INTEGER, 0x11, "15000011" +) + +# Bad memory settings +GUID_BAD_MEMORY_GROUP = "{5189b25c-5558-4bf2-bca4-289b11bd29e2}" +OBJECT_TYPE_BAD_MEMORY = create_object_type( + OBJECT_TYPE_INHERIT, OBJECT_INHERITABLE_BY_ANY, 0x0, 0x20100000 +) + +# Boot loader settings +GUID_BOOT_LOADER_SETTINGS_GROUP = "{6efb52bf-1766-41db-a6b3-0ee5eff72bd7}" +OBJECT_TYPE_BOOT_LOADER_SETTINGS = create_object_type( + OBJECT_TYPE_INHERIT, OBJECT_INHERITABLE_BY_APPLICATION, 0x3, 0x20200003 +) + +# Global settings +GUID_GLOBAL_SETTINGS_GROUP = "{7ea2e1ac-2e61-4728-aaa3-896d9d0a9f0e}" +OBJECT_TYPE_GLOBAL_SETTINGS = create_object_type( + OBJECT_TYPE_INHERIT, OBJECT_INHERITABLE_BY_ANY, 0x0, 0x20100000 +) + +# Hypervisor settings +GUID_HYPERVISOR_SETTINGS_GROUP = "{7ff607e0-4395-11db-b0de-0800200c9a66}" +OBJECT_TYPE_HYPERVISOR_SETTINGS = create_object_type( + OBJECT_TYPE_INHERIT, OBJECT_INHERITABLE_BY_APPLICATION, 0x3, 0x20200003 +) +BCDE_OSLOADER_TYPE_HYPERVISOR_DEBUGGER_TYPE = create_element_type( + ELEMENT_CLASS_APPLICATION, ELEMENT_FORMAT_INTEGER, 0xF3, "250000f3" +) +BCDE_OSLOADER_TYPE_HYPERVISOR_DEBUGGER_PORT_NUMBER = create_element_type( + ELEMENT_CLASS_APPLICATION, ELEMENT_FORMAT_INTEGER, 0xF4, "250000f4" +) +BCDE_OSLOADER_TYPE_HYPERVISOR_DEBUGGER_BAUDRATE = create_element_type( + ELEMENT_CLASS_APPLICATION, ELEMENT_FORMAT_INTEGER, 0xF5, "250000f5" +) + +# Windows BootMgr Settings +GUID_WINDOWS_BOOTMGR = "{9dea862c-5cdd-4e70-acc1-f32b344d4795}" +OBJECT_TYPE_WINDOWS_BOOTMGR = create_object_type( + OBJECT_TYPE_APPLICATION, OBJECT_APPLICATION_FIRMWARE, 0x2, 0x10100002 +) +BCDE_BOOTMGR_TYPE_DEFAULT_OBJECT = create_element_type( + ELEMENT_CLASS_APPLICATION, ELEMENT_FORMAT_GUID, 0x3, "23000003" +) +BCDE_BOOTMGR_TYPE_RESUME_OBJECT = create_element_type( + ELEMENT_CLASS_APPLICATION, ELEMENT_FORMAT_GUID, 0x6, "23000006" +) +BCDE_BOOTMGR_TYPE_DISPLAY_ORDER = create_element_type( + ELEMENT_CLASS_APPLICATION, ELEMENT_FORMAT_GUID_LIST, 0x1, "24000001" +) +BCDE_BOOTMGR_TYPE_TOOLS_DISPLAY_ORDER = create_element_type( + ELEMENT_CLASS_APPLICATION, ELEMENT_FORMAT_GUID_LIST, 0x10, "24000010" +) +BCDE_BOOTMGR_TYPE_TIMEOUT = create_element_type( + ELEMENT_CLASS_APPLICATION, ELEMENT_FORMAT_INTEGER, 0x4, "25000004" +) + +# Firmware BootMgr Settings +GUID_FIRMWARE_BOOTMGR = "{a5a30fa2-3d06-4e9f-b5f4-a01df9d1fcba}" +OBJECT_TYPE_FIRMWARE_BOOTMGR = create_object_type( + OBJECT_TYPE_APPLICATION, OBJECT_APPLICATION_FIRMWARE, 0x1, 0x10100001 +) + +# Windows MemTest +GUID_WINDOWS_MEMORY_TESTER = "{b2721d73-1db4-4c62-bf78-c548a880142d}" +OBJECT_TYPE_WINDOWS_MEMORY_TESTER = create_object_type( + OBJECT_TYPE_APPLICATION, OBJECT_APPLICATION_WIN_BOOT, 0x5, 0x10200005 +) +BCDE_LIBRARY_TYPE_ALLOW_BAD_MEMORY_ACCESS = create_element_type( + ELEMENT_CLASS_LIBRARY, ELEMENT_FORMAT_BOOLEAN, 0xB, "1600000b" +) + +# Windows resume +OBJECT_TYPE_WINDOWS_RESUME = create_object_type( + OBJECT_TYPE_APPLICATION, OBJECT_APPLICATION_WIN_BOOT, 0x4, 0x10200004 +) +BCDE_LIBRARY_TYPE_ISOLATED_EXECUTION_CONTEXT = create_element_type( + ELEMENT_CLASS_LIBRARY, ELEMENT_FORMAT_BOOLEAN, 0x60, "16000060" +) +BCDE_LIBRARY_TYPE_ALLOWED_IN_MEMORY_SETTINGS = create_element_type( + ELEMENT_CLASS_LIBRARY, ELEMENT_FORMAT_INTEGER_LIST, 0x77, "17000077" +) +BCDE_RESUME_LOADER_TYPE_HIBERFILE_PATH = create_element_type( + ELEMENT_CLASS_APPLICATION, ELEMENT_FORMAT_STRING, 0x2, "22000002" +) +BCDE_RESUME_LOADER_TYPE_BOOT_MENU_POLICY = create_element_type( + ELEMENT_CLASS_APPLICATION, ELEMENT_FORMAT_INTEGER, 0x8, "25000008" +) + +# Windows loader +OBJECT_TYPE_WINDOWS_LOADER = create_object_type( + OBJECT_TYPE_APPLICATION, OBJECT_APPLICATION_WIN_BOOT, 0x3, 0x10200003 +) +BCDE_OSLOADER_TYPE_OS_DEVICE = create_element_type( + ELEMENT_CLASS_APPLICATION, ELEMENT_FORMAT_DEVICE, 0x1, "21000001" +) +BCDE_OSLOADER_TYPE_SYSTEM_ROOT = create_element_type( + ELEMENT_CLASS_APPLICATION, ELEMENT_FORMAT_STRING, 0x2, "22000002" +) +BCDE_OSLOADER_TYPE_ASSOCIATED_RESUME_OBJECT = create_element_type( + ELEMENT_CLASS_APPLICATION, ELEMENT_FORMAT_GUID, 0x3, "23000003" +) +BCDE_OSLOADER_TYPE_NX_POLICY = create_element_type( + ELEMENT_CLASS_APPLICATION, ELEMENT_FORMAT_INTEGER, 0x20, "25000020" +) +BCDE_OSLOADER_TYPE_BOOT_MENU_POLICY = create_element_type( + ELEMENT_CLASS_APPLICATION, ELEMENT_FORMAT_INTEGER, 0xC2, "250000c2" +) + + +def format_uuid(uuid_val): + string = str(uuid_val) + return r"{" + string + r"}" + + +def pack_uint64(i): + return struct.pack("GX2hQK^f#I$8z!6klV zZkBOSq>%j3f2p7AQ}1-EFJmItKl-i@yZbxcrgQb#to|gY2sp8g->voSo2wn-#@;jX geSpe1t7IY|0wN#+A|L`HAOa#F0wN#+BJei?pOc0_k^lez literal 0 HcmV?d00001 From 35d76d8084f5a408bbe0bb53d6ec20b22d949716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tuomas=20R=C3=A4s=C3=A4nen?= Date: Wed, 9 Oct 2024 20:00:15 +0300 Subject: [PATCH 10/15] puavo-install: install Windows if there's a Windows filesystem puavo-setup-filesystems creates Windows C Local partitions with a specific well-known name. If puavo-install finds one, then it's a signal that Windows should be installed. --- parts/ltsp/puavo-install/puavo-install | 36 ++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/parts/ltsp/puavo-install/puavo-install b/parts/ltsp/puavo-install/puavo-install index ebccb4348c..a29c17e30b 100755 --- a/parts/ltsp/puavo-install/puavo-install +++ b/parts/ltsp/puavo-install/puavo-install @@ -4,6 +4,8 @@ set -eu deviceid_url="" chosen_preseed="" +GPT_PARTLABEL_PUAVO_INSTALLED_WINDOWS_C='Puavo_Installed_Windows_C' + ask_install_target_hosttype() { puavo_install_target_hosttype=laptop while true; do @@ -205,6 +207,8 @@ do_preinstall() { puavo-setup-filesystems --hosttype "$puavo_install_target_hosttype" \ ${puavo_preseed_setup_filesystems_args[@]} + maybe_install_windows + # Grub needs to be installed before images, because grub # configuration is updated during image installation by # puavo-image-preinst. @@ -277,6 +281,37 @@ get_preinstalled_hosttype() { esac } +# Install Windows if Windows partitions created by us (puavo-setup-filesystems) exist. +maybe_install_windows() { + local line win_c_partition_devpath win_c_partition_devpath_count + + win_c_partition_devpath_count=0 + + while read -r line; do + if [ -z "${line}" ]; then + continue + fi + win_c_partition_devpath="${line}" + win_c_partition_devpath_count=$((win_c_partition_devpath_count + 1)) + done < <(lsblk -J -oPARTLABEL,PATH | jq -r --arg partlabel "${GPT_PARTLABEL_PUAVO_INSTALLED_WINDOWS_C}" '.blockdevices[] | select(.partlabel == $partlabel).path') || { + echo "Failed to check if there are ${GPT_PARTLABEL_PUAVO_INSTALLED_WINDOWS_C} partitions" >&2 + return 1 + } + + if [ ${win_c_partition_devpath_count} -eq 0 ]; then + echo "INFO: Not installing Windows, ${GPT_PARTLABEL_PUAVO_INSTALLED_WINDOWS_C} partitions not found." >&2 + return 0 + elif [ ${win_c_partition_devpath_count} -gt 1 ]; then + echo "ERROR: Not installing Windows, multiple (${win_c_partition_devpath_count}) ${GPT_PARTLABEL_PUAVO_INSTALLED_WINDOWS_C} partitions found!" >&2 + return 1 + fi + + puavo-reset-windows --mode install-c-partition --force "${win_c_partition_devpath}" || return 1 + puavo-manage-efi install-windows "${win_c_partition_devpath}" || return 1 + + return 0 +} + install_grub() { echo -n 'Doing grub installation: ' puavo-install-grub "$@" @@ -364,6 +399,7 @@ install_localbootdevice() { fi else puavo-setup-filesystems ${puavo_preseed_setup_filesystems_args[@]} + maybe_install_windows fi setup_state "$puavo_hosttype" From 5c12b67985703e36a629d7fadf403f25044a2184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tuomas=20R=C3=A4s=C3=A4nen?= Date: Sat, 11 Jan 2025 16:15:43 +0200 Subject: [PATCH 11/15] puavo-install: install Windows Recovery Environment when installing Windows Windows without Recovery Environment is frankenversion, like one admin which I highly appreciate, once said. So, let's keep our basement monster-free zone. --- parts/ltsp/puavo-install/puavo-install | 33 +++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/parts/ltsp/puavo-install/puavo-install b/parts/ltsp/puavo-install/puavo-install index a29c17e30b..1434110274 100755 --- a/parts/ltsp/puavo-install/puavo-install +++ b/parts/ltsp/puavo-install/puavo-install @@ -5,6 +5,7 @@ deviceid_url="" chosen_preseed="" GPT_PARTLABEL_PUAVO_INSTALLED_WINDOWS_C='Puavo_Installed_Windows_C' +GPT_PARTTYPE_WINDOWS_RE='de94bba4-06d1-4d40-a16a-bfd50179d6ac' ask_install_target_hosttype() { puavo_install_target_hosttype=laptop @@ -283,9 +284,10 @@ get_preinstalled_hosttype() { # Install Windows if Windows partitions created by us (puavo-setup-filesystems) exist. maybe_install_windows() { - local line win_c_partition_devpath win_c_partition_devpath_count + local line ptuuid win_c_partition_devpath win_c_partition_devpath_count win_re_partition_devpath win_re_partition_devpath_count win_c_partition_devpath_count=0 + win_re_partition_devpath_count=0 while read -r line; do if [ -z "${line}" ]; then @@ -302,11 +304,40 @@ maybe_install_windows() { echo "INFO: Not installing Windows, ${GPT_PARTLABEL_PUAVO_INSTALLED_WINDOWS_C} partitions not found." >&2 return 0 elif [ ${win_c_partition_devpath_count} -gt 1 ]; then + # This is very unexpected case: it means the partition table + # contains multiple Windows RE partitions AND partitioning is made + # by puavo-setup-filesystem. This is certainly a bug in + # puavo-setup-filesystem! echo "ERROR: Not installing Windows, multiple (${win_c_partition_devpath_count}) ${GPT_PARTLABEL_PUAVO_INSTALLED_WINDOWS_C} partitions found!" >&2 return 1 fi + ptuuid=$(lsblk -n -oPTUUID "${win_c_partition_devpath}") || { + echo "ERROR: Failed to get PTUUID of '${win_c_partition_devpath}'" >&2 + return 1 + } + + while read -r line; do + if [ -z "${line}" ]; then + continue + fi + win_re_partition_devpath="${line}" + win_re_partition_devpath_count=$((win_re_partition_devpath_count + 1)) + done < <(lsblk -J -oPTUUID,PARTTYPE,PATH | jq -r --arg ptuuid "${ptuuid}" --arg parttype "${GPT_PARTTYPE_WINDOWS_RE}" '.blockdevices[] | select(.parttype == $parttype and .ptuuid == $ptuuid).path') || { + echo "Failed to check if there are Windows RE partitions" >&2 + return 1 + } + + if [ ${win_re_partition_devpath_count} -eq 0 ]; then + echo "ERROR: Not installing Windows, because Windows RE partition is not found. It should exist though, because the Windows C partition '${win_c_partition_devpath}' is Puavo partitioned." >&2 + return 1 + elif [ ${win_re_partition_devpath_count} -gt 1 ]; then + echo "ERROR: Not installing Windows, because multiple (${win_re_partition_devpath_count}) Windows RE partitions were found! I'm not able to make an informed guess which one to actually use." >&2 + return 1 + fi + puavo-reset-windows --mode install-c-partition --force "${win_c_partition_devpath}" || return 1 + puavo-reset-windows --mode install-re-partition --force "${win_c_partition_devpath}" "${win_re_partition_devpath}" || return 1 puavo-manage-efi install-windows "${win_c_partition_devpath}" || return 1 return 0 From 2b953a1ebfdb2457d8ccd0d58514600ba934fa72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tuomas=20R=C3=A4s=C3=A4nen?= Date: Fri, 10 Jan 2025 21:35:27 +0200 Subject: [PATCH 12/15] puavo-install: improve logging --- parts/ltsp/puavo-install/puavo-install | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/parts/ltsp/puavo-install/puavo-install b/parts/ltsp/puavo-install/puavo-install index 1434110274..8da5a2fa60 100755 --- a/parts/ltsp/puavo-install/puavo-install +++ b/parts/ltsp/puavo-install/puavo-install @@ -217,7 +217,10 @@ do_preinstall() { # Setup Grub environment for preinstalled so that Windows can be booted # even when we have Puavo OS only preinstalled. - /etc/puavo-conf/scripts/setup_grub_environment + /etc/puavo-conf/scripts/setup_grub_environment || { + echo 'ERROR: failed to setup grub environment' >&2 + return 1 + } install_image "$puavo_install_hosttype" --hosttype preinstalled @@ -344,9 +347,10 @@ maybe_install_windows() { } install_grub() { - echo -n 'Doing grub installation: ' + echo 'Starting grub installation...' puavo-install-grub "$@" sync + echo 'Finished grub installation.' } install_image() { From 16b5a4862e9235a5a08d58d3f86af8d12f982b08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tuomas=20R=C3=A4s=C3=A4nen?= Date: Fri, 10 Jan 2025 22:55:38 +0200 Subject: [PATCH 13/15] [testing]: hard-coded wim.json path for locally modified wim.json --- parts/ltsp/puavo-install/puavo-reset-windows | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/parts/ltsp/puavo-install/puavo-reset-windows b/parts/ltsp/puavo-install/puavo-reset-windows index 0200cb8fa8..f397f8c92d 100755 --- a/parts/ltsp/puavo-install/puavo-reset-windows +++ b/parts/ltsp/puavo-install/puavo-reset-windows @@ -41,7 +41,8 @@ g_win_c_partition_devpath= # can be passed as arg, but if not given g_win_re_partition_devpath= G_RESET_STATE_DIR=/state/windows G_WORK_DIR=/home/.puavo-reset-windows # Where WIM files will be downloaded, mounted etc. -G_WIM_CATALOGUE_PATH="${G_WORK_DIR}/wim.json" +#G_WIM_CATALOGUE_PATH="${G_WORK_DIR}/wim.json" +G_WIM_CATALOGUE_PATH='/tmp/wim.json' G_PID_FILE=/run/puavo-reset-windows.pid # Locks this file to ensure only one puavo-reset-windows is running on_exit() @@ -524,6 +525,9 @@ get_wim_catalogue() wim_tmppath="${G_WIM_CATALOGUE_PATH}.tmp" for url in $wim_image_source_urls; do + printf "%s/images" "${url%/*}" || return 1 + return 0 + if ! wget_with_certauth -q \ --output-document "$wim_gpg_tmppath" "$url"; then echo "error fetching $url" >&2 From a05e0ab98744bf3d8cbf16cd5db3e335ab65b96f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tuomas=20R=C3=A4s=C3=A4nen?= Date: Thu, 16 Jan 2025 21:36:04 +0200 Subject: [PATCH 14/15] puavo-manage-efi: create Windows boot entry only if one does not already exist Duplicate boot entries are pollution. Pollution is bad. --- parts/ltsp/puavo-install/puavo-manage-efi | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/parts/ltsp/puavo-install/puavo-manage-efi b/parts/ltsp/puavo-install/puavo-manage-efi index f78b94f9ea..f5638fde94 100755 --- a/parts/ltsp/puavo-install/puavo-manage-efi +++ b/parts/ltsp/puavo-install/puavo-manage-efi @@ -120,12 +120,27 @@ get_disk() } efibootmgr_windows_create() { - local esp_partnum + local esp_partnum esp_partuuid log notice 'creating a Windows boot entry' esp_partnum=$(get_partnum "${g_esp_devpath}") || return 1 + esp_partuuid=$(lsblk -n -oPARTUUID "${g_esp_devpath}") || { + log err "failed to query GPT partuuid of ESP '${g_esp_devpath}' (lsblk failed)" + return 1 + } + + if ! [[ "${esp_partuuid}" =~ ^[A-Fa-f0-9]{8}(-[A-Fa-f0-9]{4}){3}-[A-Fa-f0-9]{12}$ ]]; then + log err "failed to query GPT partuuid of ESP '${g_esp_devpath}' (lsblk printed invalid UUID: '${esp_partuuid}')" + return 1 + fi + + if efibootmgr -v | sed -r -n 's|^Boot[0-9]+[* ] [^\t]+\tHD\([0-9]+,GPT,([^,]+),[^)]+\)/File\(\\EFI\\Microsoft\\Boot\\bootmgfw\.efi\).*$|\1|p' | grep -i -q -x "${esp_partuuid}"; then + log notice "not creating a Windows boot entry because at least one already exists for Windows Boot Manager in ESP '${g_esp_devpath}'" + return 0 + fi + efibootmgr -q -c -L 'Windows Boot Manager' -l '\EFI\Microsoft\Boot\bootmgfw.efi' -d "${g_win_disk}" -p "${esp_partnum}" || { log err 'failed to create a Windows boot entry' return 1 From 9fcc08f36f147f5565fd0637e32f5b4617fcdeba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tuomas=20R=C3=A4s=C3=A4nen?= Date: Thu, 16 Jan 2025 21:43:54 +0200 Subject: [PATCH 15/15] puavo-manage-efi: improve logging --- parts/ltsp/puavo-install/puavo-manage-efi | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/parts/ltsp/puavo-install/puavo-manage-efi b/parts/ltsp/puavo-install/puavo-manage-efi index f5638fde94..7f13068710 100755 --- a/parts/ltsp/puavo-install/puavo-manage-efi +++ b/parts/ltsp/puavo-install/puavo-manage-efi @@ -87,7 +87,7 @@ get_partnum() shift partnum=$(stat --format %Lr "${devpath}") || { - log err 'failed to get the partition number' + log err "failed to get the partition number of '${devpath}'" return 1 } @@ -122,7 +122,7 @@ get_disk() efibootmgr_windows_create() { local esp_partnum esp_partuuid - log notice 'creating a Windows boot entry' + log notice "creating EFI boot entry for Windows Boot Manager in ESP '${g_esp_devpath}'" esp_partnum=$(get_partnum "${g_esp_devpath}") || return 1 @@ -137,16 +137,16 @@ efibootmgr_windows_create() { fi if efibootmgr -v | sed -r -n 's|^Boot[0-9]+[* ] [^\t]+\tHD\([0-9]+,GPT,([^,]+),[^)]+\)/File\(\\EFI\\Microsoft\\Boot\\bootmgfw\.efi\).*$|\1|p' | grep -i -q -x "${esp_partuuid}"; then - log notice "not creating a Windows boot entry because at least one already exists for Windows Boot Manager in ESP '${g_esp_devpath}'" + log notice "not creating EFI boot entry for Windows Boot Manager in ESP '${g_esp_devpath}', because at least one already exists" return 0 fi efibootmgr -q -c -L 'Windows Boot Manager' -l '\EFI\Microsoft\Boot\bootmgfw.efi' -d "${g_win_disk}" -p "${esp_partnum}" || { - log err 'failed to create a Windows boot entry' + log err "failed to create EFI boot entry for Windows Boot Manager in ESP '${g_esp_devpath}'" return 1 } - log notice 'created a Windows boot entry' + log notice "created EFI boot entry for Windows Boot Manager in ESP '${g_esp_devpath}'" } efibootmgr_windows() { @@ -183,6 +183,7 @@ esp_mount() { mkdir -p /boot/efi || return 1 mount -onodev,nosuid "${esp_devpath}" /boot/efi || return 1 + log info "mounted ESP '${esp_devpath}' to /boot/efi" echo -n /boot/efi } @@ -209,7 +210,7 @@ esp_break_windows() { return 0 fi - log info 'defusing Windows...' + log info "defusing Windows in ESP '${g_esp_devpath}' ..." tar -C "${g_esp_mount_path}/EFI" -z -c -f "$tmp_defused_windows_file" Microsoft || { log err 'failed to defuse Windows (tar error)' rm -f "$tmp_defused_windows_file" || true @@ -289,7 +290,7 @@ esp_fix_windows() { return 0 fi - log info 'repriming Windows...' + log info "repriming Windows in ESP '${g_esp_devpath}' ..." tar -C "${g_esp_mount_path}/EFI" -z -x -f "$defused_windows_file" || { log err 'error repriming Windows (tar error)' rm -rf "${esp_windows_dir}" || true