Files
FWS-ISO/docs/windows-spoke-plan.md
T
nocode fa3b953d99 docs(windows): add comprehensive dual-boot windows installation plan
Add detailed technical specification for offline Windows 11 deployment via FWS spoke installer. Covers:

- Architecture overview: automatic offline WIM application without Windows Setup
- Security considerations: unified Secure Boot signing chain and protection against wrong-disk erasure
- Five-phase implementation roadmap: package dependencies, boot fixes, deployer tool, kickstart integration, validation
- Spoke amendments: safe defaults, disk occupation warnings, explicit confirmation for non-empty disks
- Safe partitioning with closed-fail guards against resolver failures
- Offline BCD construction via hivex with WinPE fallback
- Integration with FWS hibernation and gameboot-windows components
- Honest documentation of single irreducible manual action: firmware Setup Mode for Secure Boot activation
2026-07-08 23:58:11 +02:00

28 KiB
Raw Blame History

PLAN DE BUILD — Spoke installateur FWS « Dual-boot Windows gaming » (option A, OFFLINE-APPLY)

État vérifié dans le dépôt au moment de ce plan : le spoke fws_windows.py + fws_windows.glade existent déjà (à amender, pas à créer). wimlib, hivex, sbctl, sbsigntools, nvidia*, binutils sont absents des deux packages.x86_64 (présents : efibootmgr, ntfs-3g, gptfdisk, dosfstools, parted, e2fsprogs). fws-windows-deploy, fws-bcd-write, le template unattend.offline.xml.tmpl et le repli fws-windows-bcdfix n'existent pas encore. gameboot.conf, fws-gameboot-bootfix(.service), le hook de re-signature, fws-secureboot-setup, fws_recap.py existent. Le %pre (l.99106) est disque-agnostique (juste clearpart firmware-conditionnel) ; le %post a 3 blocs : nochroot early (114157), chroot (165613), nochroot final d'exfil de log (624628).


1) Résumé & principe

L'utilisateur, pendant l'install FWS, coche « Dual-boot Windows gaming », choisit un disque à dédier, une taille NTFS et fournit son ISO Windows 11. FWS mène ensuite tout de façon automatique : carve des partitions, dépôt de Windows 11 par application offline de install.wim (jamais Windows Setup), rendu bootable depuis Linux, injection du composant gameboot-windows + answer file, provisioning FWS (swap d'hibernation dédié, resume=, NVIDIA S4, chaîne Secure Boot). Une seule action manuelle irréductible subsiste : la visite firmware pour passer en Setup Mode et activer Secure Boot (non automatisable depuis l'OS) — à assumer honnêtement dans la doc et l'UI (voir §8).

Le spoke est un clone du pattern fws_desktop.py : .py + .glade déposés dans l'overlay airootfs, découverts au runtime par collect() d'Anaconda (aucun rebuild du paquet anaconda). Son seul canal vers l'installation est le fichier /tmp/fws-windows (clé=valeur), relu par le kickstart. Il n'exécute aucune opération disque. Tout le travail dépendant des choix GUI vit en %post --nochroot (le %pre tourne avant le GUI → ne voit pas /tmp/fws-windows : contrainte dure, source unique de vérité = %post).

Répartition des 3 NVMe = garde-fou anti-mauvais-disque : (a) disque FWS = celui sélectionné dans « Destination », partitionné nativement par blivet ; (b) disque Windows = disk= du spoke, jamais donné à Anaconda, carvé 100 % par nos soins ; (c) tout le reste = jamais nommé, donc physiquement intouchable.


2) Approche de déploiement Windows retenue (justifiée)

RETENU : (A) OFFLINE-APPLY, 100 % depuis le live FWS, modèle « apply image » de WDS/MDT. On n'allume jamais Windows Setup.

Pipeline : mount -o loop,ro de l'ISO → wimlib-imagex apply de install.wim/.esd directement sur le bloc device NTFS (backend libntfs-3g : descripteurs de sécurité, ADS, hardlinks, reparse préservés) → copie des ressources bootmgr + construction du magasin BCD offline → dépôt du unattend.xml offline + du dossier gameboot-windows par simples cp → entrée UEFI. Le 1er boot enchaîne specialize + oobeSystem unattended sur l'image généralisée.

Pourquoi pas (B) BOOT-INTO-SETUP : lancer Setup depuis le disque sans clé USB exige de toute façon d'écrire un BCD depuis Linux (même point dur), plus le gate de compatibilité TPM/SB (bypass LabConfig), le repartitionnement piloté par Setup (risque pour les 3 NVMe), Windows.old, et des reboots entrelacés avec l'install FWS. (B) n'est « béni » qu'en variante clé-USB-bootée-à-la-main → viole « plus rien à la main ».

Avantages décisifs de (A) : la partition cible est adressée côté Linux par /dev/disk/by-partlabel/WINDOWS — pas de DiskID/DiskConfiguration/WillWipeDisk dans le XML → la classe de panne « effacer le mauvais NVMe » n'existe pas. Le gate TPM2/SB ne se déclenche jamais (il n'existe que dans Setup). L'answer file est un unattend OFFLINE sans passe windowsPE (Setup ne tourne pas). Sur 25H2, \Windows\Panther\unattend.xml + vrais fichiers sur le NTFS est plus fiable que l'autounattend média (SetupPrep.exe, injections Rufus/Ventoy qui « sautent » specialize/oobe).

Point dur unique et honnête = le BCD offline (format registre propriétaire, bcdboot est Windows-only). Co-conçu avec un repli WinPE-bcdboot automatique qui reste mains-libres (§6, §9).


3) Roadmap par phases (fichiers + commandes)

Phase 0 — Paquets (BLOQUANT, prérequis de tout le reste)

Ajouter aux deux configs/{releng,baseline}/packages.x86_64 :

  • Live/déploiement : wimlib (apply WIM/ESD), hivex (BCD + anti-BitLocker hors ligne).
  • Secure Boot : sbctl, sbsigntools, binutils (objdump du hook SBAT), mokutil.
  • Hibernation : nvidia, nvidia-utils (hooks nvidia-suspend/hibernate/resume).

Déjà présents et suffisants : ntfs-3g (fournit mkntfs/ntfs-3g), gptfdisk (sgdisk), dosfstools (mkfs.fat), efibootmgr, parted, e2fsprogs. Sans wimlib+hivex, le %post offline est impossible ou retombe sur pacman réseau pendant l'install (fragile).

Phase 1 — Corriger la chaîne Secure Boot (BLOQUANT, sinon FWS non bootable sous SB)

Défaut vérifié : install_grub (kickstart l.212218) pose GRUB en --bootloader-id=FWS (+ --removable\EFI\FWS\grubx64.efi et \EFI\BOOT\BOOTX64.EFI), mais fws-secureboot-setup (l.3745) réinstalle un GRUB séparé en --bootloader-id=GRUB (\EFI\GRUB\grubx64.efi) et ne signe que ce chemin + les vmlinuz. Résultat : activer SB → firmware charge \EFI\FWS\grubx64.efi non signé → refus ; le repli \EFI\BOOT\BOOTX64.EFI est aussi non signémachine sans OS bootable.

Fix (unifier l'identité bootloader) : aligner fws-secureboot-setup sur --bootloader-id=FWS et signer les trois chemins réellement utilisés :

sbctl sign -s /boot/efi/EFI/FWS/grubx64.efi
sbctl sign -s /boot/efi/EFI/BOOT/BOOTX64.EFI      # repli removable (le firmware peut le charger)
for k in /boot/vmlinuz-linux*; do sbctl sign -s "$k"; done
sbctl verify        # DOIT être clean sur les 3 chemins AVANT d'instruire l'activation SB

Et faire pointer fws-gameboot-bootfix sur l'entrée NVRAM signée. Ne jamais instruire l'utilisateur d'activer SB tant que sbctl verify n'est pas clean.

Phase 2 — Amender le spoke (BLOQUANT : perte de données par défaut)

fws_windows.py refresh() l.147 fait set_active(0) → cible par défaut = 1er disque lsblk = nvme0n1, un disque de jeux plein. Un utilisateur qui coche, parcourt l'ISO et clique Suivant sans toucher au combo fait sgdisk --zap-all sur un disque plein. Corriger (voir §4).

Phase 3 — Créer le déployeur fws-windows-deploy (+ helpers)

Nouveaux fichiers :

  • configs/releng/airootfs/usr/local/bin/fws-windows-deploy — orchestrateur bash (garde-fous → carve → apply → BCD → registre → answer file → injection → UEFI → vérif → handoff). Exécutable. Rejouable hors installateur.
  • configs/releng/airootfs/usr/local/lib/fws/fws-bcd-write — helper BCD offline via hivex (voie primaire, à auditer).
  • configs/releng/airootfs/usr/local/bin/fws-windows-bcdfixrepli WinPE-bcdboot (chemin par défaut recommandé pour la 1re validation matériel).
  • configs/releng/airootfs/usr/local/share/fws/gameboot-windows/unattend.offline.xml.tmpl — template tokenisé (§6).

Phase 4 — Câbler le kickstart

interactive-defaults.ks :

  1. %pre : inchangé (juste un commentaire fort : « aucune op disque dépendante du GUI ici — voir %post »).
  2. nochroot early (~l.138) : propager [ -f /tmp/fws-windows ] && cp -f /tmp/fws-windows "$SYSROOT/tmp/fws-windows".
  3. %post chroot (avant l'unmount, après install_grub/fws-secureboot-setup) : bloc gaté . /tmp/fws-windows; [ "${enabled:-0}" = 1 ] → hibernation (hook resume, resume=UUID=, NVIDIA), enable fws-gameboot-bootfix.service.
  4. Nouveau %post --nochroot inséré entre la fin du chroot (l.613) et le nochroot final d'exfil (l.624) : appel non bloquant /usr/local/bin/fws-windows-deploy --state /tmp/fws-windows --sysroot "$SYSROOT" 2>&1 | tee /tmp/fws-windows-deploy.log.

Phase 5 — Récap & validation

fws_recap.py : ligne « Dual-boot Windows : GiO — CE DISQUE SERA EFFACÉ » + contrôle croisé (si disk=SelectedDisks → avertissement rouge). Puis ./build.sh, boot ISO, vérifier l'écran sur le hub Résumé, puis spike end-to-end sur une vraie ISO Win11 25H2.


4) Le spoke Anaconda (py + glade) — amendements

Le squelette existant est correct (NormalSpoke, category=SystemCategory, builderObjects=['adj_size','fwsWindowsWindow'], cycle initialize/refresh/apply, écrit /tmp/fws-windows). Trois corrections load-bearing :

(a) Supprimer la présélection dangereuse. refresh() : retirer le elif self._disks: self._combo.set_active(0). Combo sans choix par défaut (id vide) → l'utilisateur DOIT choisir explicitement. completed (déjà : exige disk + ISO lisible) refuse alors tant que rien n'est choisi.

(b) Enrichir l'affichage disque + garde-fous UI. Dans _list_disks(), afficher pour chaque disque : modèle, taille, et un indicateur d'occupation (lsblk -no FSUSE%,FSTYPE,MOUNTPOINT ou présence de partitions). Marquer visuellement « ⚠ contient des données » un disque non vide. Ajouter dans _validate() : si le disque choisi n'est pas vide, exiger une case « effacer quand même » (nouveau chk_confirm_erase, propagé en confirm_erase=1) avant de considérer completed. Le message reste « ⚠ TOUT le disque %s sera EFFACÉ » avec le modèle exact.

(c) Contrat /tmp/fws-windows figé (relu par le kickstart) :

enabled=1|0
disk=/dev/nvme1n1        # disque ENTIÈREMENT dédié à Windows (mode DEDICATE)
size_gib=200             # taille de la NTFS
iso=/run/media/.../Win11_25H2.iso
edition=Windows 11 Pro   # optionnel ; défaut Pro (deferral Windows Update)
confirm_erase=1          # exigé si le disque n'est pas vide

Glade (fws_windows.glade, existant) : AnacondaSpokeWindow 'fwsWindowsWindow' + GtkAdjustment 'adj_size' (lower 100, value 200, step 10). chk_enable (maître) grise box_options (combo_disk, spin_size, entry_iso+btn_iso, iso_warn, chk_confirm_erase), note_platform masqué affiché si /sys/firmware/efi absent. GtkFileChooserNative filtre *.iso démarrant sur /run/media ; le champ libre entry_iso reste le secours si le chooser natif est bancal en X minimal.


5) Partitionnement non destructif

Timing (dur) : le carve ne peut pas vivre en %pre (tourne avant le GUI) ni dans les directives de partitionnement. Il vit dans fws-windows-deploy (%post --nochroot, live, cible sous /mnt/sysroot). Anaconda ne partitionne que le disque FWS (flux clearpart --all sur le seul disque « Destination »).

Garde-fous AVANT le premier sgdisk — fail-CLOSED (le défaut critique : un resolver muet renvoie « » et "" != /dev/nvmeXn1 est toujours vrai → garde qui s'ouvre). Règle : abort si un resolver renvoie vide ou non-bloc-device, et comparer par identifiant stable quand possible.

fws_disk_of(){ src=$(findmnt -no SOURCE "$1" 2>/dev/null) || return 1
  [ -n "$src" ] || return 1
  lsblk -spno NAME,TYPE "$src" 2>/dev/null | awk '$2=="disk"{print $1; exit}'; }

WIN_DISK="$disk"
[ -b "$WIN_DISK" ]                         || die "cible invalide"           # (a)
[ -d /mnt/sysroot ] && findmnt -no SOURCE /mnt/sysroot >/dev/null || die "sysroot non monté"
for probe in /mnt/sysroot /mnt/sysroot/boot/efi /run/archiso/bootmnt; do
  d=$(fws_disk_of "$probe") || die "resolver muet ($probe) → abort"        # fail-closed
  [ "$WIN_DISK" != "$d" ]   || die "$WIN_DISK == disque protégé ($probe)"  # (b)(c)(d)
done
ISO_DISK=$(fws_disk_of "$(df --output=target "$iso" | tail -1)") && \
  [ "$WIN_DISK" != "$ISO_DISK" ] || die "l'ISO vit sur la cible"           # (e)
DGIB=$(( $(lsblk -dbno SIZE "$WIN_DISK") / 1073741824 ))
[ "$size_gib" -ge 100 ] && [ "$size_gib" -le $((DGIB-100)) ] || die "taille hors bornes"  # (f)
# (g) occupation : aucune partition de $WIN_DISK montée/swap/VG/MD active

Neutraliser l'auto-activation blivet (sinon corruption) : pour chaque partition de $WIN_DISK, umount + swapoff, puis vgchange -an / mdadm --stop de tout VG/MD assis dessus ; revérifier via findmnt/swapon --show/lsblk qu'aucune n'est occupée. En mode DEDICATE, si quelque chose reste occupé de façon inattendue → abort (plus sûr que forcer).

Carve (mode DEDICATE, défaut promis par le spoke) — swap d'hibernation calculé dynamiquement (RAM+VRAM+marge, plancher 96 GiO) :

MEM=$(awk '/^MemTotal/{print int($2/1024)}' /proc/meminfo)
VRAM=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits 2>/dev/null | awk '{s+=$1}END{print s+0}')
SWAP_GIB=$(( (MEM+VRAM+4096+1023)/1024 )); [ "$SWAP_GIB" -lt 96 ] && SWAP_GIB=96
sgdisk --zap-all "$WIN_DISK"
sgdisk -n1:0:+1GiB      -t1:ef00 -c1:WINESP  "$WIN_DISK"
sgdisk -n2:0:+16MiB     -t2:0c01 -c2:MSR     "$WIN_DISK"   # optionnel (décision ouverte)
sgdisk -n3:0:+${size_gib}GiB -t3:0700 -c3:WINDOWS "$WIN_DISK"
sgdisk -n4:0:+${SWAP_GIB}GiB -t4:8200 -c4:FWSSWAP "$WIN_DISK"
partprobe "$WIN_DISK"; udevadm settle
mkfs.fat -F32 -n WINESP /dev/disk/by-partlabel/WINESP
mkswap  -L FWSSWAP /dev/disk/by-partlabel/FWSSWAP

Décision swap : partition dédiée 8200 sur le disque Windows (le seul qu'on pilote de bout en bout depuis le kickstart → seul endroit déterministe pour un swap de 96 GiO ; partition dédiée ⇒ pas de resume_offset fragile ; Windows ignore le type 8200). Contrepartie documentée : retirer/réordonner ce disque perd le resume FWS (filet BootOrder[0]=FWS).

Adressage NTFS : par PARTLABEL — mais asserter que by-partlabel/WINDOWS ne résout qu'UN device, sur $WIN_DISK (sinon un WINDOWS préexistant sur un autre NVMe piège l'apply). Envisager un label unique FWSWIN si collision possible.

Mode SHRINK (conserver les données du disque cible) = chemin risqué optionnel (e2fsck -fyresize2fs -P pour vérifier le libérable → resize2fs <taille>parted resizepart table-only, jamais l'ancien resizeresize2fs re-remplit → carve de la queue). Exige un champ spoke shrink=1+shrink_part, ext4 seulement, abort propre si FS réellement plein. À traiter en enhancement, pas en voie de prod.


6) autounattend OFFLINE + injection first-boot

Nature : unattend OFFLINE, \Windows\Panther\unattend.xmlPAS de passe windowsPE, PAS de DiskConfiguration (Setup ne tourne jamais). Seuls specialize + oobeSystem s'exécutent au 1er boot. Taille et partition ne sont PAS dans le XML (gérées par le carve + l'adressage PARTLABEL).

Génération (pattern sed déjà utilisé pour hyprland.conf.tmpl) — template tokenisé unattend.offline.xml.tmpl (__USER__ __DISPLAY__ __HOSTNAME__ __LOCALE__ __INPUT__ __FIRSTBOOT__), substitué par sed (évite tout piège d'échappement des \ Windows et & XML). Contenu clé :

  • specialize : ComputerName, TimeZone, et ceinture reg add … OOBE /v BypassNRO /d 1 + reg add … BitLocker /v PreventDeviceEncryption /d 1 (doublons du reg offline hivex).
  • oobeSystem : International-Core fr-CH (InputLocale 100C:0000100C), <OOBE> avec <HideOnlineAccountScreens>true</HideOnlineAccountScreens> (clé anti compte-MS 25H2 ; bypassnro retiré ~03/2025 → ne pas s'y fier), ProtectYourPC=3 ; <UserAccounts><LocalAccount Group=Administrators Name=gaming> sans <Password> (contrat auto-login ; jamais Administrator que 25H2 confond avec le compte intégré) ; <AutoLogon LogonCount=5> (survit au reboot Vanguard le temps qu'Install-FwsGameboot pose l'auto-login permanent Winlogon) ; <FirstLogonCommands>
    powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\ProgramData\FWS\deploy\FirstBoot-FwsGameboot.ps1"
    

Injection (le cœur) — vrais fichiers sur le NTFS (évite la méthode « scripts embarqués » cassée par SetupPrep.exe en 25H2) :

install -Dm644 /tmp/unattend.rendered /mnt/win/Windows/Panther/unattend.xml
mkdir -p /mnt/win/ProgramData/FWS/deploy
cp -r /usr/local/share/fws/gameboot-windows/* /mnt/win/ProgramData/FWS/deploy/   # source = rootfs LIVE
sync

FirstBoot-FwsGameboot.ps1 utilise $PSScriptRootdoit vivre sous C:\ProgramData\FWS\deploy\ (satisfait ci-dessus). Compte admin ⇒ FirstLogonCommands s'exécute élevé, #Requires -RunAsAdministrator satisfait sans UAC.

Anti-BitLocker HORS LIGNE (primaire, crucial) : le flip SB OFF→ON change PCR7 → écran de récupération si Device Encryption s'active, clé partie vers un compte Microsoft inexistant (compte local) ⇒ verrouillage définitif. Écrire dans le ControlSet pointé par SYSTEM\Select\Current (= 001 sur image fraîche, jamais CurrentControlSet) :

SEL=$(hivexget /mnt/win/Windows/System32/config/SYSTEM /Select Current)  # → 1
hivexregedit --merge --prefix 'HKEY_LOCAL_MACHINE\SYSTEM' \
  /mnt/win/Windows/System32/config/SYSTEM <<< \
  "[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet00$SEL\Control\BitLocker]
\"PreventDeviceEncryption\"=dword:00000001"
hivexget /mnt/win/.../SYSTEM /ControlSet00$SEL/Control/BitLocker PreventDeviceEncryption  # relire == 1

Défense en profondeur : les reg add specialize ci-dessus et un manage-bde -off / vérification « Device Encryption OFF » au 1er boot conditionnant le go.

BCD + bootabilité (point dur) :

  • 4a — ressources : cp -rn /mnt/win/Windows/Boot/EFI/. /mnt/esp/EFI/Microsoft/Boot/ (+ Fonts/, Resources/) ; bootmgfw.efi reste signé MS → boote sous SB (clés MS conservées par enroll-keys --microsoft).
  • 4b — voie primaire fws-bcd-write : cp .../config/BCD-Template /mnt/esp/EFI/Microsoft/Boot/BCD puis patch hivex de {bootmgr} (device→WINESP, default, displayorder) et de l'OS loader (device/osdevice→partition WINDOWS via blob GPT PARTUUID+DISK_GUID, path=\Windows\system32\winload.efi, systemroot=\Windows). Le blob binaire « device » est la surface à auditer.
  • 4c — repli automatique fws-windows-bcdfix (recommandé pour la 1re validation) : stager la WinPE de l'ISO sur la WINESP + un autounattend.xml racine à passe windowsPE uniquement faisant bcdboot W:\Windows /s S: /f UEFI puis wpeutil reboot, W:/S: épinglés par diskpart (label), aucune DiskConfiguration. Coût : 1 reboot, 100 % automatique. Basculer sur 4c si la vérif du BCD rejette la voie 4b.

Entrée UEFI (transactionnel)ne créer qu'après vérif complète et placer explicitement en DERNIER dans BootOrder (ne jamais laisser le tout 1er boot post-install partir sur Windows) :

efibootmgr -c -d "$WIN_DISK" -p 1 -L 'Windows Boot Manager' -l '\EFI\Microsoft\Boot\bootmgfw.efi'
# relire le Boot####, réasserter BootOrder avec FWS en tête, Windows en queue

Back-fill : sed -i 's/^FWS_WINDOWS_BOOTNUM=.*/FWS_WINDOWS_BOOTNUM="'$WBN'"/' $SYSROOT/etc/fws/gameboot.conf.

Vérification post-déploiement (avant de déclarer succès) : ntoskrnl.exe + winload.efi présents ; bootmgfw.efi + BCD présents et relus non vides ; PreventDeviceEncryption==1 ; entrée UEFI relue. Échec → repli 4c ou echo DEPLOY_FAILED > $SYSROOT/var/lib/fws/windows-deploy.status sans laisser d'entrée Windows piégeuse. FWS reste toujours seul bootable.


7) Ordre global de bout en bout

  1. %pre : clearpart firmware-conditionnel sur le seul disque « Destination » (disque-agnostique, inchangé).
  2. Commandes de partitionnement : blivet partitionne uniquement le disque FWS (ESP + ext4 + swap éventuel). Le disque Windows n'est jamais référencé.
  3. Copie payload LiveOS (racine FWS montée sous /mnt/sysroot).
  4. nochroot early : propage /tmp/fws-windows (+ /tmp/fws-desktop, hostname, noyau) vers la cible.
  5. %post chroot : purge résidus live → mkinitcpio (hook resume après block, avant filesystems, busybox) → resume=UUID=$SWAP_UUID hibernate.compressor=lz4 + GRUB_DISABLE_OS_PROBER=true dans /etc/default/grub → NVIDIA (NVreg_PreserveVideoMemoryAllocations=1, NVreg_TemporaryFilePath=/var/tmp, enable nvidia-suspend/hibernate/resume) → install_grub (--bootloader-id=FWS) → fws-secureboot-setup (signe FWS + BOOTX64 + vmlinuz, sbctl verify clean)systemctl enable fws-gameboot-bootfix.servicegameboot.conf.
  6. Nouveau %post --nochroot (fws-windows-deploy) : garde-fous fail-closed → neutralisation blivet → carve → mkntfs -Q -f -L WINDOWSwimlib-imagex apply par PARTLABEL → 4a/4b(/4c) BCD → anti-BitLocker offline → unattend.xml + injection gameboot-windows → entrée UEFI en queue + back-fill FWS_WINDOWS_BOOTNUM → vérif → sync; umount.
  7. nochroot final : exfil de log.
  8. Hors installateur (manuel, irréductible) : visite firmware → Setup Mode + activer Secure Boot (sbctl verify déjà clean) — écran/rappel d'instructions au 1er boot FWS (motd/fws-hello) avec les commandes exactes.
  9. 1er fws-gameboot to-windows (déclenché par l'utilisateur ; preflight refuse si SB≠enabled → l'ordre « SB ON avant 1er Windows » s'auto-impose, PCR7 stable). Idéalement un boot de PROVISIONING unique (installer les jeux, pas les lancer, retour auto à FWS) distinct des boots de jeu (voir §8).
  10. Régime établi : fws-gameboot-bootfix réasserte BootOrder[0]=FWS à chaque boot FWS.

Contrainte d'ordre unique : SB doit être ON avant le 1er boot Windows (l'apply offline ne « mesure » rien → SB peut être activé après l'apply mais avant ce 1er boot).


8) Risques & mitigations (critique intégrée)

Bloquants

  • Perte de données par défaut (spoke set_active(0)) → §4(a) : combo sans présélection, indicateur d'occupation, case « effacer quand même » + modèle exact, completed refuse tant que rien de choisi.
  • Garde-fous fail-open (resolver muet → « » ≠ devnode toujours vrai) → §5 : fail-closed, abort sur valeur vide/non-bloc, asserter /mnt/sysroot monté, comparer par identifiant stable.
  • Chaîne SB incohérente → FWS non bootable (\EFI\FWS+BOOTX64 non signés) → Phase 1 : unifier --bootloader-id=FWS, signer les 3 chemins, sbctl verify clean avant d'instruire l'activation.
  • Contradiction carve %pre vs GUI → source unique = %post --nochroot ; commentaire fort interdisant toute op disque dépendante du GUI en %pre.

Majeurs

  • BCD Linux non validé (0xc000000e) → voie hivex pas primaire tant que non validée sur 25H2 réel ; repli WinPE-bcdboot par défaut (W:/S: épinglés par label) ; vérif post-déploiement obligatoire, transactionnel.
  • Lockout BitLocker → écriture ControlSet001 vérifiée par relecture + doublons specialize + manage-bde -off au 1er boot ; ne jamais activer BitLocker ; conditionner le 1er boot à « Device Encryption OFF ».
  • 1er boot post-install part sur Windows → entrée UEFI créée en dernier de BootOrder, NVRAM relue avant umount, pas de dépendance au seul bootfix.
  • Orchestration 1er boot ratée (OOBE + download Riot + FWS-Play-OnLogon qui boucle sur un jeu pas installé, jamais de retour FWS) → boot de provisioning séparé (installer, pas lancer, retour auto FWS) ; fws-play tolérant à « jeu pas encore installé ».
  • Promesse « 100 % automatique » fausse (activation SB = visite firmware manuelle) → corriger doc/UI ; écran guidé avec commandes/étapes firmware exactes ; détecter Setup Mode.
  • Variance ISO (Home/N/localisée, pas de clé, tiny11) → énumérer les éditions dans le spoke (edition=), résoudre par nom via wimlib-imagex info et échouer proprement si absent ; Pro par défaut (deferral Windows Update = mitigation brick dbx/SBAT) ; gérer « pas de clé » ; refuser les ISO non standard avec message actionnable.

Mineurs

  • PARTLABEL WINDOWS ambigu (autre install antérieure) → asserter résolution unique sur $WIN_DISK ou label unique FWSWIN.
  • Transactionnel incomplet (--zap-all avant apply) → tout valider avant le 1er sgdisk (ISO montée, édition résolue, espace re-vérifié) ; entrée UEFI seulement après vérif.
  • WinRE non installé par l'apply seul → Windows boote sans, à documenter (gaming minimal).
  • mkntfs -Q parfois mal digéré par 25H2 → valider le NTFS produit, repli full format sans -Q.
  • ISO retirée pendant le %post → statut DEPLOY_DEFERRED + relance depuis le système installé ; documenter « laisser la clé branchée ».
  • efivars réordonnées par le firmware → filet BootOrder[0]=FWS + chemin de secours \EFI\BOOT\BOOTX64.EFI (signé, cf. Phase 1) ; sémantique BootNext à valider sur matériel réel.
  • Resume GPU RTX 5090 (Blackwell) non validé → tester session chargée avant de se fier au bouton « Jouer » ; fallback reboot déjà câblé.
  • Couplage version python3.14 / SystemCategory / anaconda 45.8 → re-vérifier à chaque bump (comme l'en-tête fws_recap.py).

9) Décisions ouvertes

  1. BCD primaire : hivex fws-bcd-write vs WinPE-bcdboot par défaut. Recommandation : WinPE-bcdboot comme chemin par défaut pour la 1re validation matériel ; hivex promu primaire seulement après succès reproductible sur 25H2 réel.
  2. Partition MSR (0c01, 16 MiB) : recommandée pour un GPT Windows propre, non requise pour booter. Trancher (carvée par prudence dans le plan) après validation.
  3. Emplacement du swap : sur le disque Windows (retenu, déterministe depuis le kickstart) vs sur le disque FWS (plus propre conceptuellement, mais exige de capter le disque FWS dans le spoke). Rebasculer si le spoke est étendu.
  4. Mode SHRINK : livrer ou non le chemin non destructif (conserver les données du disque cible). Enhancement fragile — hors v1.
  5. edition= dans le spoke : énumérer les éditions à l'écran (choix explicite) vs Pro par défaut silencieux. Recommandation : énumérer si l'ISO le permet.
  6. ProductKey / activation : suppose un droit numérique de l'utilisateur ; définir le comportement si l'édition ciblée n'est pas activable.
  7. Label NTFS : WINDOWS vs FWSWIN unique (anti-collision multi-NVMe).
  8. Boot de provisioning vs boot de jeu : formaliser un premier passage Windows « installer et revenir » distinct des lancements de jeu (impacte FirstBoot-FwsGameboot.ps1 et fws-play).

Fichiers à créer : usr/local/bin/fws-windows-deploy, usr/local/lib/fws/fws-bcd-write, usr/local/bin/fws-windows-bcdfix, usr/local/share/fws/gameboot-windows/unattend.offline.xml.tmpl (tous sous configs/releng/airootfs/). Fichiers à modifier : configs/{releng,baseline}/packages.x86_64 (+wimlib, hivex, sbctl, sbsigntools, binutils, mokutil, nvidia, nvidia-utils) ; configs/releng/airootfs/usr/share/anaconda/interactive-defaults.ks (propagation + bloc chroot hibernation/SB + nouveau %post --nochroot) ; configs/releng/airootfs/usr/local/bin/fws-secureboot-setup (unifier --bootloader-id=FWS, signer les 3 chemins) ; .../spokes/fws_windows.py + .glade (anti-présélection + confirm_erase) ; .../spokes/fws_recap.py (ligne + contrôle croisé). Réutilisés tels quels : gameboot.conf, fws-gameboot(-bootfix)(.service), 95-fws-secureboot-sign.hook, gameboot-windows/* (FirstBoot/Install-FwsGameboot/Install-Games/games.json/installers.json), docs/hibernate-swap-dualboot.md (référence de conception §4/§6/§7).