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
This commit is contained in:
@@ -0,0 +1,252 @@
|
|||||||
|
# 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.99‑106) est disque-agnostique (juste `clearpart` firmware-conditionnel) ; le `%post` a 3 blocs : nochroot early (114‑157), chroot (165‑613), nochroot final d'exfil de log (624‑628).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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.212‑218) pose GRUB en `--bootloader-id=FWS` (+ `--removable` → `\EFI\FWS\grubx64.efi` **et** `\EFI\BOOT\BOOTX64.EFI`), mais `fws-secureboot-setup` (l.37‑45) 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-bcdfix` — **repli 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 : <disque> <taille> 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.
|
||||||
|
```bash
|
||||||
|
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) :
|
||||||
|
```bash
|
||||||
|
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 -fy` → `resize2fs -P` pour vérifier le libérable → `resize2fs <taille>` → `parted resizepart` **table-only, jamais l'ancien `resize`** → `resize2fs` 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.xml` — **PAS** 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) :
|
||||||
|
```bash
|
||||||
|
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 `$PSScriptRoot` → **doit** 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`**) :
|
||||||
|
```bash
|
||||||
|
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) :
|
||||||
|
```bash
|
||||||
|
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.service` → `gameboot.conf`.
|
||||||
|
6. **Nouveau `%post --nochroot`** (`fws-windows-deploy`) : garde-fous fail-closed → neutralisation blivet → carve → `mkntfs -Q -f -L WINDOWS` → `wimlib-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).
|
||||||
Reference in New Issue
Block a user