From 8908b50b3458fac1c4a036b288062c9e6c28c5eb Mon Sep 17 00:00:00 2001 From: Mikhail Mlhcnn Date: Fri, 5 Jun 2026 21:03:03 +0300 Subject: [PATCH] Add Windows To Go support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deploy a runnable Windows onto the USB drive (not installer media): GPT layout with a FAT32 ESP + NTFS Windows partition, the selected WIM edition is expanded with wimlib and the UEFI bootloader is taken from the deployed image. - New windows_togo_logic.py generating the deploy script (iso/dev passed as $1/$2, mktemp -d mount points — same hardening as the other scripts). - "Windows To Go" checkbox on the ISO page, shown only for Windows; it forces GPT/UEFI and hides the partition-scheme selector. - Summary and worker honour the new mode; fully localized (EN/RU). - Ship windows_togo_logic.py in PKGBUILD. Co-Authored-By: Claude Opus 4.8 --- PKGBUILD | 5 +- main.py | 34 ++++++++++++-- windows_togo_logic.py | 107 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 windows_togo_logic.py diff --git a/PKGBUILD b/PKGBUILD index 5fb52ee..f6c53db 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -7,14 +7,15 @@ url="https://github.com/Advnirr/lufux" license=('GPL3') depends=('python-gobject' 'gtk4' 'libadwaita' 'wimlib' 'rsync' 'parted' 'polkit' 'libarchive') makedepends=('git') -source=("main.py" "windows_logic.py" "universal_logic.py" "deps_logic.py" "lufux.desktop" "lufux.svg") -sha256sums=('SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP') +source=("main.py" "windows_logic.py" "windows_togo_logic.py" "universal_logic.py" "deps_logic.py" "lufux.desktop" "lufux.svg") +sha256sums=('SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP') package() { install -d "${pkgdir}/usr/share/lufux" install -Dm755 "${srcdir}/main.py" "${pkgdir}/usr/share/lufux/main.py" install -Dm644 "${srcdir}/windows_logic.py" "${pkgdir}/usr/share/lufux/windows_logic.py" + install -Dm644 "${srcdir}/windows_togo_logic.py" "${pkgdir}/usr/share/lufux/windows_togo_logic.py" install -Dm644 "${srcdir}/universal_logic.py" "${pkgdir}/usr/share/lufux/universal_logic.py" install -Dm644 "${srcdir}/deps_logic.py" "${pkgdir}/usr/share/lufux/deps_logic.py" diff --git a/main.py b/main.py index d34a40a..caa2035 100644 --- a/main.py +++ b/main.py @@ -43,6 +43,7 @@ def save_config(cfg): # importing logic from windows_logic import get_windows_script +from windows_togo_logic import get_windows_togo_script from universal_logic import get_linux_script from deps_logic import check_dependencies, get_distro_info, get_install_cmd @@ -78,6 +79,8 @@ def get_locale_dict(): "partition_scheme": "Схема разделов (для Windows):", "scheme_gpt": "GPT (UEFI / FAT32)", "scheme_mbr": "MBR (Legacy BIOS / NTFS)", + "wtg_mode": "Windows To Go (запускаемая Windows на USB)", + "wtg_summary": "Windows To Go (GPT / UEFI)", "summary_drive": "Целевой накопитель", "summary_iso": "Выбранный образ", "summary_os": "Определенная система", @@ -135,6 +138,8 @@ def get_locale_dict(): "partition_scheme": "Partition Scheme (for Windows):", "scheme_gpt": "GPT (UEFI / FAT32)", "scheme_mbr": "MBR (Legacy BIOS / NTFS)", + "wtg_mode": "Windows To Go (bootable Windows on USB)", + "wtg_summary": "Windows To Go (GPT / UEFI)", "summary_drive": "Target Drive", "summary_iso": "Selected ISO", "summary_os": "Detected OS", @@ -300,18 +305,33 @@ def setup_page_iso(self): box.append(self.scheme_label) self.scheme_dropdown = Gtk.DropDown.new_from_strings([T["scheme_gpt"], T["scheme_mbr"]]) - self.scheme_dropdown.set_visible(False) + self.scheme_dropdown.set_visible(False) self.scheme_dropdown.set_size_request(280, -1) box.append(self.scheme_dropdown) + # Windows To Go: deploy a runnable Windows instead of installer media. + # Forces GPT/UEFI, so the scheme selector is hidden while it is on. + self.wtg_check = Gtk.CheckButton(label=T["wtg_mode"]) + self.wtg_check.set_visible(False) + self.wtg_check.connect("toggled", self.on_wtg_toggled) + box.append(self.wtg_check) + self.os_dropdown.connect("notify::selected", self.on_os_changed) return page def on_os_changed(self, dropdown, param): is_windows = dropdown.get_selected() == 0 - self.scheme_label.set_visible(is_windows) - self.scheme_dropdown.set_visible(is_windows) + self.wtg_check.set_visible(is_windows) + wtg = is_windows and self.wtg_check.get_active() + self.scheme_label.set_visible(is_windows and not wtg) + self.scheme_dropdown.set_visible(is_windows and not wtg) + + def on_wtg_toggled(self, check): + # Windows To Go is always GPT/UEFI, so hide the scheme picker + wtg = check.get_active() + self.scheme_label.set_visible(not wtg) + self.scheme_dropdown.set_visible(not wtg) def setup_page_summary(self): page = Adw.StatusPage(title=T["step_summary"], icon_name="emblem-system-symbolic") @@ -419,7 +439,9 @@ def update_summary_data(self): os_text = T["os_win"] if os_idx == 0 else T["os_lin"] if os_idx == 1 else T["os_other"] self.sum_os.set_subtitle(os_text) - if os_idx == 0: + if os_idx == 0 and self.wtg_check.get_active(): + self.sum_scheme.set_subtitle(T["wtg_summary"]) + elif os_idx == 0: scheme_idx = self.scheme_dropdown.get_selected() scheme_text = T["scheme_gpt"] if scheme_idx == 0 else T["scheme_mbr"] self.sum_scheme.set_subtitle(scheme_text) @@ -718,7 +740,9 @@ def worker_thread(self, iso, dev): scheme_idx = self.scheme_dropdown.get_selected() scheme = "gpt" if scheme_idx == 0 else "mbr" - if os_idx == 0: + if os_idx == 0 and self.wtg_check.get_active(): + script = get_windows_togo_script() + elif os_idx == 0: script = get_windows_script(scheme) else: script = get_linux_script() diff --git a/windows_togo_logic.py b/windows_togo_logic.py new file mode 100644 index 0000000..cf7a0ee --- /dev/null +++ b/windows_togo_logic.py @@ -0,0 +1,107 @@ +import os + +# Locals +def get_locale_dict(): + lang = os.environ.get('LANG', '') + if lang.startswith('ru'): + return { +# russian locals + "prep": "Подготовка накопителя...", + "part": "Создание разделов (ESP + Windows)...", + "no_image": "В образе не найден install.wim/install.esd", + "apply": "Развёртывание Windows на накопитель (это надолго)...", + "boot": "Установка загрузчика UEFI...", + "sync": "Синхронизация ввода-вывода (sync)..." + } + return { +# english locals + "prep": "Preparing drive...", + "part": "Creating partitions (ESP + Windows)...", + "no_image": "install.wim/install.esd not found in the image", + "apply": "Deploying Windows to the drive (this takes a while)...", + "boot": "Installing the UEFI bootloader...", + "sync": "Syncing I/O (sync)..." + } + +def get_windows_togo_script(img_index=1): + # iso/dev come in as $1/$2; img_index is the WIM image to deploy. + # A real, bootable Windows is applied to the drive (not an installer): + # GPT with a FAT32 ESP + NTFS Windows partition, the WIM is expanded with + # wimlib and the UEFI bootloader is taken from the deployed image. + T = get_locale_dict() + + # img_index is an int chosen in Python, never user free-text + idx = int(img_index) + + script = f"""#!/bin/bash +set -e + +ISO_PATH="$1" +DEV_PATH="$2" +IMG_INDEX="{idx}" + +ISO_MNT=$(mktemp -d /tmp/lufux_iso.XXXXXX) +WIN_MNT=$(mktemp -d /tmp/lufux_win.XXXXXX) +EFI_MNT=$(mktemp -d /tmp/lufux_efi.XXXXXX) + +cleanup() {{ + umount "$ISO_MNT" "$WIN_MNT" "$EFI_MNT" 2>/dev/null || true + rmdir "$ISO_MNT" "$WIN_MNT" "$EFI_MNT" 2>/dev/null || true +}} +trap cleanup EXIT + +echo "STATUS: {T['prep']}" +umount "$DEV_PATH"* 2>/dev/null || true +wipefs -a "$DEV_PATH" + +echo "STATUS: {T['part']}" +# GPT: partition 1 = FAT32 ESP, partition 2 = NTFS Windows +parted -s "$DEV_PATH" mklabel gpt +parted -s "$DEV_PATH" mkpart ESP fat32 1MiB 1025MiB +parted -s "$DEV_PATH" set 1 esp on +parted -s "$DEV_PATH" mkpart Windows ntfs 1025MiB 100% +sleep 2 + +mkfs.vfat -F 32 -n "ESP" "${{DEV_PATH}}1" +mkfs.ntfs -f -L "Windows" "${{DEV_PATH}}2" + +mount -o loop,ro "$ISO_PATH" "$ISO_MNT" +mount "${{DEV_PATH}}2" "$WIN_MNT" + +# locate the Windows image inside the ISO +TF="" +[ -f "$ISO_MNT/sources/install.wim" ] && TF="$ISO_MNT/sources/install.wim" +[ -f "$ISO_MNT/sources/install.esd" ] && TF="$ISO_MNT/sources/install.esd" +if [ -z "$TF" ]; then + echo "STATUS: {T['no_image']}" + exit 1 +fi + +echo "STATUS: {T['apply']}" +# expand the chosen edition straight onto the NTFS partition +wimlib-imagex apply "$TF" "$IMG_INDEX" "$WIN_MNT" 2>&1 + +echo "STATUS: {T['boot']}" +mount "${{DEV_PATH}}1" "$EFI_MNT" +mkdir -p "$EFI_MNT/EFI/Microsoft/Boot" "$EFI_MNT/EFI/Boot" + +# UEFI bootloader from the deployed Windows +if [ -f "$WIN_MNT/Windows/Boot/EFI/bootmgfw.efi" ]; then + cp "$WIN_MNT/Windows/Boot/EFI/bootmgfw.efi" "$EFI_MNT/EFI/Boot/bootx64.efi" + cp "$WIN_MNT/Windows/Boot/EFI/bootmgfw.efi" "$EFI_MNT/EFI/Microsoft/Boot/bootmgfw.efi" +fi +# boot resources (fonts, locale, etc.) +if [ -d "$WIN_MNT/Windows/Boot/EFI" ]; then + cp -r "$WIN_MNT/Windows/Boot/EFI/." "$EFI_MNT/EFI/Microsoft/Boot/" 2>/dev/null || true +fi +# BCD store seeded from the OS image template +if [ -f "$WIN_MNT/Windows/System32/config/BCD-Template" ]; then + cp "$WIN_MNT/Windows/System32/config/BCD-Template" "$EFI_MNT/EFI/Microsoft/Boot/BCD" +fi + +echo "STATUS: {T['sync']}" +sync + +echo "STATUS: DONE" +""" + return script