Compare commits
199 Commits
Anaconda
...
Hypervisor
| Author | SHA1 | Date | |
|---|---|---|---|
|
e313fc4dde
|
|||
|
384a45f70e
|
|||
|
89e1321ab7
|
|||
|
e2dd1abc18
|
|||
|
d050af8279
|
|||
|
64763c3707
|
|||
|
2e2108d2b8
|
|||
|
d4e479cc15
|
|||
|
d592b24858
|
|||
|
9836925a37
|
|||
|
47d59e51fe
|
|||
|
6a1ce74169
|
|||
|
fa3b953d99
|
|||
|
d47686efd9
|
|||
|
fe9b30b745
|
|||
|
7a4f36c916
|
|||
|
e3a0b988a5
|
|||
|
05a44af161
|
|||
|
4bc9ccc1f7
|
|||
|
d1fa5143e8
|
|||
|
39baaa67e6
|
|||
|
976e24876d
|
|||
|
ce6e9533c2
|
|||
|
4d15dc2632
|
|||
|
e811cce88d
|
|||
|
0dc5329d90
|
|||
|
fd21293725
|
|||
|
e69c071969
|
|||
|
3b47ef30e7
|
|||
|
e5bc082049
|
|||
|
8d1b6bf23b
|
|||
|
3f6fb08af6
|
|||
|
c90e48f14f
|
|||
|
226af87379
|
|||
|
8e3bd2b53c
|
|||
|
af7ed4e469
|
|||
|
a1be0e6d49
|
|||
|
afe884b9e4
|
|||
|
2bee8ed359
|
|||
|
e6c0224db9
|
|||
|
057898524a
|
|||
|
11a531003e
|
|||
|
9b80effc77
|
|||
|
810a98fc34
|
|||
|
96da52f0e1
|
|||
|
beec38a899
|
|||
|
3cb4132727
|
|||
|
367233eae5
|
|||
|
3d91ef3ac5
|
|||
|
bc84731290
|
|||
|
e5b36d6c07
|
|||
|
585c4691a1
|
|||
|
732082144a
|
|||
|
ae8140ccff
|
|||
|
f3ffcb62b8
|
|||
|
7730ac7b00
|
|||
|
bee59a2fb3
|
|||
|
00d34dfc49
|
|||
|
ac810f2ee3
|
|||
|
5592c57642
|
|||
|
8f6b8e7a95
|
|||
|
1826dbfead
|
|||
|
b7bf497cd6
|
|||
|
ae70926ed1
|
|||
|
aac3c8b59e
|
|||
|
260547f6b6
|
|||
|
730cfd697c
|
|||
|
292e3071c6
|
|||
|
8a90ef4794
|
|||
|
9c7f9b46c5
|
|||
|
bf8b29c975
|
|||
|
9c9891d98e
|
|||
|
de05361b28
|
|||
|
9e3a02d96d
|
|||
|
881f771ed1
|
|||
|
bc08f59e74
|
|||
|
e35b352a2a
|
|||
|
44b476e1d0
|
|||
|
f3feff4dc2
|
|||
|
d00bcd4144
|
|||
|
34bf7909ad
|
|||
|
50773635e8
|
|||
|
a75964e391
|
|||
|
7a44c4c291
|
|||
|
e25935fa75
|
|||
|
40d49cb9e2
|
|||
|
3a437a8bfe
|
|||
|
8e2fc2ec6a
|
|||
|
c7221247f2
|
|||
|
57f86ef5f7
|
|||
|
a9ba4be111
|
|||
|
6f5fc95e82
|
|||
|
680ee39e94
|
|||
|
ec442193f5
|
|||
|
a0fc2c1019
|
|||
|
9e62cedff0
|
|||
|
bfd43ff41a
|
|||
|
6fb48bd3a8
|
|||
|
61fa81dd24
|
|||
|
45ff906097
|
|||
|
2daac4a455
|
|||
|
d65f08cfad
|
|||
|
7268e7e23d
|
|||
|
d4bef2fe91
|
|||
|
a1255eb5c6
|
|||
|
cbdb386ba0
|
|||
|
4df4b2bff3
|
|||
|
73cf60e876
|
|||
|
07ff62af55
|
|||
|
22a95badcf
|
|||
|
a1adefe890
|
|||
|
1f465bf647
|
|||
|
ba2dc4637e
|
|||
|
ccdbdf4adf
|
|||
|
1c74b59459
|
|||
|
1cb48ce4e7
|
|||
|
17f9c79983
|
|||
|
3cdfdcfaeb
|
|||
|
9e59be79c3
|
|||
|
080a77c5e3
|
|||
|
7e40a565cc
|
|||
|
338f6b87ee
|
|||
|
4f4afec6b2
|
|||
|
640aa15af8
|
|||
|
73f73ff17e
|
|||
|
6c8e7c0f78
|
|||
|
5762f59d95
|
|||
|
affba77349
|
|||
|
87977ce8d7
|
|||
|
b5f00d8dd2
|
|||
|
349cc28d7d
|
|||
|
fc1bc218a4
|
|||
|
d399fc5fa6
|
|||
|
4d36e86f51
|
|||
|
a0f269cf90
|
|||
|
7831b537d7
|
|||
|
2c5f08330a
|
|||
|
a63021cde7
|
|||
|
b408c84a8c
|
|||
|
2a3824a991
|
|||
|
06121343e7
|
|||
|
3c12d9514c
|
|||
|
5f108c607b
|
|||
|
95eb6c15e0
|
|||
|
4a598c854a
|
|||
|
9f46b9bb1b
|
|||
|
14d138cae4
|
|||
|
8e2e74a227
|
|||
|
d0e431329d
|
|||
|
75113980f0
|
|||
|
4d123b6f5d
|
|||
|
a09278b63f
|
|||
|
dd71664b9e
|
|||
|
2aaf1cbfd5
|
|||
|
e20523470f
|
|||
|
c56db3de04
|
|||
|
81dfac3762
|
|||
|
43ecf7f54f
|
|||
|
96e5721ad8
|
|||
|
cef3b291d1
|
|||
|
6e59e1d4c4
|
|||
|
3a5efffc61
|
|||
|
53942aece0
|
|||
|
e98a8320fb
|
|||
|
038700e22d
|
|||
|
4857a9fda9
|
|||
|
289a70c10c
|
|||
|
8224a24d02
|
|||
|
0ab90857e5
|
|||
|
d6212deaa9
|
|||
|
754b6e8802
|
|||
|
053998d1c0
|
|||
|
218b0707a4
|
|||
|
7c11f53520
|
|||
|
be0844b50b
|
|||
|
24370461ea
|
|||
|
5d6d1c6db8
|
|||
|
23df7656e5
|
|||
|
a28a64310a
|
|||
|
7aa1276e2c
|
|||
|
9df1f444bc
|
|||
|
7fe54d0791
|
|||
|
4249282052
|
|||
|
5e66f99816
|
|||
|
35f77ba33d
|
|||
|
d8e67fe861
|
|||
|
68850f0ed2
|
|||
|
aa9cb13648
|
|||
|
8b7af60239
|
|||
|
ee0082baa1
|
|||
|
0b0fcd8ce4
|
|||
|
99006b9ede
|
|||
|
ff98a1e186
|
|||
|
7daca57714
|
|||
|
d409406fde
|
|||
|
0de970239f
|
|||
|
7d6bba42b2
|
|||
|
1184ba17d9
|
|||
|
d25ab9b4da
|
+42
-2
@@ -2,15 +2,41 @@
|
|||||||
Changelog
|
Changelog
|
||||||
#########
|
#########
|
||||||
|
|
||||||
[XX] - YYYY-MM-DD
|
[0.6.0] - 2026-07-07
|
||||||
=================
|
====================
|
||||||
|
|
||||||
Added
|
Added
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
- **Installateur Anaconda 45 porté sur Arch** (payload LiveOS) : page « Récapitulatif avant installation » avec bouton
|
||||||
|
Précédent, choix du bureau intégré (GNOME, KDE, XFCE, Hyprland, i3, CLI) avec compatibilité VR affichée, splash de
|
||||||
|
préparation alimenté en direct par les logs (diagnostic automatique en cas de blocage), garde-fou RAM (< 2 Go),
|
||||||
|
mécanisme de retry réseau pour l'installation du bureau (``fws-desktop-retry``).
|
||||||
|
- **Outillage FWS** : ``fws-hello`` (accueil 1er démarrage), ``fws-update`` + indicateur de mises à jour,
|
||||||
|
``fws-keys`` (raccourcis réels), ``fws-powermenu``, ``fws-about``, ``fws-vr-setup`` (SteamVR/ALVR/Monado),
|
||||||
|
``fws-proton-ge``, ``fws-setup-hardware`` (drivers GPU/VM/audio/Bluetooth), ``fws-dnd``.
|
||||||
|
- **Récupération système** (« point de restauration ») : application GTK ``fws-recovery-gui`` + moteur ``fws-recovery``
|
||||||
|
(GRUB, initramfs, pacman, chroot), accessible depuis le live (entrée de boot), le menu GRUB du système installé
|
||||||
|
(``fws-recovery.target``) et son menu d'applications (élévation polkit).
|
||||||
|
- **Swap zram** sur le live et le système installé (``zram-generator``).
|
||||||
|
- **firewalld** activé par défaut sur le système installé.
|
||||||
|
- Bureaux Hyprland et i3 à parité visuelle (waybar/polybar, wofi/rofi, Catppuccin Mocha), thème SDDM « fws »,
|
||||||
|
bannière GDM.
|
||||||
|
- **Choix du nom de machine à l'installation** : champ « Nom de machine » dans la page Utilisateur d'anaconda,
|
||||||
|
appliqué au système installé (``/etc/hostname``, repli ``fws``).
|
||||||
|
- **Script de build interactif** (``build-offi.sh``) : version (``major.minor.build``) et variante (``cli``, ``gnome``,
|
||||||
|
``kde``…) demandées sur l'hôte avant d'entrer dans le conteneur, ISO nommée
|
||||||
|
``fws-<version>-<variante>-<AAAA.MM.JJ>.iso``, logo FWS dans fastfetch.
|
||||||
|
|
||||||
Changed
|
Changed
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- **Branding FWS complet** : l'installateur s'annonce « FWS 0.6 » (variables ``ANACONDA_PRODUCT*``), splash FWS au menu
|
||||||
|
de boot BIOS (l'image Arch d'archiso est remplacée), ``/etc/issue`` sans version de noyau, filtres d'affichage
|
||||||
|
Fedora/Arch sur le splash et le journal de récupération.
|
||||||
|
- Toutes les entrées de boot plafonnent les workers udev bloqués (``udev.event_timeout=45``) ; le menu BIOS démarre
|
||||||
|
explicitement l'entrée FWS (``DEFAULT fws``).
|
||||||
|
- ``systemd-timesyncd`` explicitement activé sur le système installé pour éviter la dérive d'horloge (TLS, pacman).
|
||||||
- Use xz compression for the baseline profile's bootstrap tarball so that it has more differences when compared to the
|
- Use xz compression for the baseline profile's bootstrap tarball so that it has more differences when compared to the
|
||||||
releng profile.
|
releng profile.
|
||||||
- Match network interfaces by their type, instead of name, in systemd-networkd configuration.
|
- Match network interfaces by their type, instead of name, in systemd-networkd configuration.
|
||||||
@@ -21,12 +47,26 @@ Deprecated
|
|||||||
Fixed
|
Fixed
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
- Watchdog du splash qui affichait son diagnostic PAR-DESSUS l'installateur (il se désarme désormais dès que la GUI
|
||||||
|
d'anaconda est à l'écran, et mesure l'inactivité en itérations — insensible aux sauts d'horloge des VM).
|
||||||
|
- Gel de l'installateur sur machines à faible RAM (1 Go) : zram + avertissement préalable.
|
||||||
|
- Profils ``baseline``/``releng`` synchronisés (la fusion baseline-écrase-releng avait fait perdre ``firewalld``,
|
||||||
|
le cap udev et les entrées de boot FWS d'ISO précédentes).
|
||||||
|
- DNS du système installé (NetworkManager + systemd-resolved), hook ``lvm2`` dans l'initramfs, GRUB de secours
|
||||||
|
``--removable`` en UEFI.
|
||||||
|
- Disposition clavier dérivée de la locale complète et non de la seule langue : ``fr_CH`` donne désormais ``ch(fr)``
|
||||||
|
au lieu de ``fr``, appliqué à la console (``vconsole.conf``) et à X11 (via langtable) ; anaconda écrivait par
|
||||||
|
ailleurs un ``/etc/hostname`` vide sur le système installé.
|
||||||
|
- DNS dans le conteneur de build : ``podman run --network=host`` pour passer par la pile réseau de l'hôte
|
||||||
|
(corrige les timeouts avec firewalld/NAT).
|
||||||
- Use ``systemd-run`` to invoke ``curl`` with a transient service that depends on ``network-online.target`` since no
|
- Use ``systemd-run`` to invoke ``curl`` with a transient service that depends on ``network-online.target`` since no
|
||||||
services are currently pulling in ``network_online.target``.
|
services are currently pulling in ``network_online.target``.
|
||||||
|
|
||||||
Removed
|
Removed
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- ``configs/baseline/syslinux/syslinux-linux.cfg`` (label ``arch`` stock, orphelin et sans cap udev).
|
||||||
|
|
||||||
[87] - 2025-10-29
|
[87] - 2025-10-29
|
||||||
=================
|
=================
|
||||||
|
|
||||||
|
|||||||
+7
-3
@@ -85,8 +85,12 @@ projet (« inbound = outbound », `GPL-3.0-or-later`), avec attestation d'origin
|
|||||||
plus « Arch Linux ») ; `motd` ne renvoie plus vers `archlinux.org`
|
plus « Arch Linux ») ; `motd` ne renvoie plus vers `archlinux.org`
|
||||||
- [ ] **Dépôt rendu publiquement accessible** (sinon l'offre écrite n'est pas
|
- [ ] **Dépôt rendu publiquement accessible** (sinon l'offre écrite n'est pas
|
||||||
opposable) — action hors-code
|
opposable) — action hors-code
|
||||||
- [ ] Branding Red Hat/Fedora retiré de l'UI Anaconda ; installateur marqué
|
- [x] Branding Red Hat/Fedora retiré de l'UI Anaconda : nom de produit **FWS**
|
||||||
« modifié » — à faire pendant la migration (config `product.d`)
|
via les variables `ANACONDA_PRODUCTNAME`/`ANACONDA_PRODUCTVERSION`
|
||||||
|
(exportées par le `.xinitrc` du live, lues depuis l'os-release FWS —
|
||||||
|
l'en-tête affiche « FWS 0.6 INSTALLATION ») ; visuel **Arch** du menu de
|
||||||
|
boot BIOS remplacé par un splash FWS ; le caractère « version modifiée
|
||||||
|
d'Anaconda » reste documenté ici et dans TRADEMARKS.md
|
||||||
- [ ] Décision tranchée sur `python-iso639` (AGPL-3.0-only) : garder ou remplacer
|
- [ ] Décision tranchée sur `python-iso639` (AGPL-3.0-only) : garder ou remplacer
|
||||||
- [ ] Revue par un conseil FOSS (recommandé avant diffusion commerciale)
|
- [ ] Revue par un conseil FOSS (recommandé avant diffusion commerciale)
|
||||||
|
|
||||||
@@ -96,4 +100,4 @@ projet (« inbound = outbound », `GPL-3.0-or-later`), avec attestation d'origin
|
|||||||
> dépôt** (construits depuis l'AUR) : à corriger à la source AUR, pas ici.
|
> dépôt** (construits depuis l'AUR) : à corriger à la source AUR, pas ici.
|
||||||
|
|
||||||
> **Ceci n'est pas un avis juridique.** Base de conformité de bonne foi au
|
> **Ceci n'est pas un avis juridique.** Base de conformité de bonne foi au
|
||||||
> 2026-06-17.
|
> 2026-07-07.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# FWS
|
# FWS
|
||||||
|
|
||||||
**FWS** est une distribution Linux live/installable basée sur **Arch Linux**, livrée sous forme d'ISO bootable BIOS/UEFI. L'installateur graphique est **en cours de migration de Calamares vers Anaconda** (Calamares retiré au commit 05ae9c3, intégration d'Anaconda en cours — voir le chantier dédié).
|
**FWS** est une distribution Linux live/installable basée sur **Arch Linux**, pensée pour **remplacer Windows** : bureau prêt à l'emploi, jeux (Steam/Proton-GE), réalité virtuelle (SteamVR), compatibilité applicative (winboat/Wine) et outillage de maintenance intégré. L'installateur graphique est **Anaconda** (celui de Fedora/RHEL), **porté sur Arch** : payload LiveOS (copie du live sur le disque), spokes FWS (choix du bureau, récapitulatif avant installation), finalisation pacman/GRUB/mkinitcpio en `%post`.
|
||||||
|
|
||||||
Le projet est conçu pour être **construit depuis Windows via WSL** (les scripts détectent Git Bash et basculent automatiquement dans WSL), mais fonctionne aussi directement depuis une **Arch Linux native** ou depuis **toute distribution non-Arch (Fedora, Ubuntu…) via une bascule automatique dans un conteneur Arch podman**.
|
Le projet est conçu pour être **construit depuis Windows via WSL** (les scripts détectent Git Bash et basculent automatiquement dans WSL), mais fonctionne aussi directement depuis une **Arch Linux native** ou depuis **toute distribution non-Arch (Fedora, Ubuntu…) via une bascule automatique dans un conteneur Arch podman**.
|
||||||
|
|
||||||
@@ -9,14 +9,40 @@ Le projet est conçu pour être **construit depuis Windows via WSL** (les script
|
|||||||
## Aperçu
|
## Aperçu
|
||||||
|
|
||||||
- **Base** : Arch Linux (rolling release)
|
- **Base** : Arch Linux (rolling release)
|
||||||
- **Installateur** : en migration Calamares → Anaconda (Calamares retiré, Anaconda en préparation ; payload `liveimg` pour installer FWS et non Fedora)
|
- **Installateur** : **Anaconda 45 porté sur Arch** — payload LiveOS (installe FWS, pas Fedora), page « Récapitulatif avant installation » avec bouton Précédent, choix du bureau intégré, **brandé FWS** (en-tête « FWS 0.6 INSTALLATION », via les variables `ANACONDA_PRODUCT*` exportées par le `.xinitrc`)
|
||||||
- **Affichage live** : Xorg + Openbox (session graphique conservée pour l'installateur)
|
- **Affichage live** : Xorg + Openbox + splash de préparation qui montre l'activité réelle d'anaconda (dernier log actif + chrono ; si rien ne bouge avant l'apparition de l'installateur, il le dit puis affiche un diagnostic complet — le watchdog se désarme dès que la GUI est à l'écran)
|
||||||
- **Bureaux installables** : XFCE, KDE Plasma, GNOME, Hyprland (choix à l'installation)
|
- **Mémoire** : **4 Go recommandés** pour installer (2 Go minimum — swap **zram** intégré au live ET au système installé ; sous 2 Go le live avertit, à 1 Go sans zram l'installateur graphique gèle)
|
||||||
- **Boot** : BIOS (syslinux) + UEFI (systemd-boot)
|
- **Bureaux installables** : GNOME, KDE Plasma, XFCE, **Hyprland** (Wayland — non compatible VR), **i3** (X11 — compatible VR/SteamVR), ou ligne de commande seule
|
||||||
|
- **Boot** : UEFI (systemd-boot) + BIOS (syslinux, splash **FWS**, `DEFAULT fws`) — entrées *normale*, *safe graphics (nomodeset)*, *Réparer FWS (recovery)* et *speech*
|
||||||
|
- **Récupération** : accessible depuis le live (entrée de boot), depuis le **menu GRUB du système installé** (« Réparer FWS ») et depuis son **menu d'applications** (« Récupération FWS », élévation polkit)
|
||||||
- **Compression** : squashfs (xz)
|
- **Compression** : squashfs (xz)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Ce qu'il y a dans FWS
|
||||||
|
|
||||||
|
### À l'installation
|
||||||
|
- **Choix du bureau** dans Anaconda, avec compatibilité VR affichée (i3 = SteamVR ✔ ; Hyprland = ✘)
|
||||||
|
- **Page récapitulative** avant toute écriture disque : config choisie (langue, clavier, fuseau, comptes, partitionnement, bureau, nom de machine) + matériel détecté (GPU, disques, virtualisation, réseau)
|
||||||
|
- **Automatique en fin d'install** : drivers GPU (NVIDIA/AMD/Intel), outils invité VM (VMware/VirtualBox/QEMU/Hyper-V), PipeWire + pavucontrol, Bluetooth (BlueZ + blueman), luminosité (brightnessctl), **pare-feu firewalld activé**, Steam + Wine + Monado + MangoHud + gamemode, clavier/locale, hostname, sudo pour l'utilisateur créé, branding FWS
|
||||||
|
|
||||||
|
### Sur le système installé (outillage `fws-*`)
|
||||||
|
| Commande | Rôle | Accès |
|
||||||
|
|---|---|---|
|
||||||
|
| `fws-hello` | Bienvenue au 1er démarrage (mise à jour, codecs, Proton-GE, VR, raccourcis) | auto au 1er login |
|
||||||
|
| `fws-update` | Mise à jour complète (pacman + cache + orphelins + flatpak) | bouton de la barre |
|
||||||
|
| `fws-keys` | Panneau des **raccourcis clavier réels** (lit la config Hyprland/i3) | `Super+K`, bouton ⌨ |
|
||||||
|
| `fws-powermenu` | Verrouiller / déconnexion / suspendre / redémarrer / éteindre | `Super+X`, bouton |
|
||||||
|
| `fws-about` | « À propos » : le projet FWS + infos système | menu d'applications |
|
||||||
|
| `fws-vr-setup` | Drivers/runtimes VR (Vive/Index → SteamVR natif ; Quest → ALVR ; Monado) | `Super+Shift+V` (i3), menu |
|
||||||
|
| `fws-proton-ge` | Installe/actualise Proton-GE pour Steam (vérif sha512) | fws-hello, terminal |
|
||||||
|
| `fws-recovery` | **Application GTK de réparation** : GRUB, initramfs, pacman, terminal chroot (moteur bash + repli console) | menu « Récupération FWS » de l'OS installé, entrée GRUB « Réparer FWS », entrée de boot du live |
|
||||||
|
|
||||||
|
### Hyprland & i3 : parité visuelle
|
||||||
|
Même identité Catppuccin Mocha (nuances de bleu) sur les deux : barre flottante arrondie (**waybar** / **polybar**) avec date+heure, indicateur de mises à jour, volume/réseau/CPU/RAM/luminosité/Bluetooth cliquables ; menus assortis (**wofi** / **rofi**) ; mêmes raccourcis (déclarés avec descriptions → `Super+K`) ; **Dolphin** en gestionnaire de fichiers ; thème **SDDM « fws »** maison (pur QtQuick, accent adapté au bureau ; GNOME garde GDM avec bannière FWS).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Environnement requis — INSTALLATION ULTRA PRÉCISE
|
## Environnement requis — INSTALLATION ULTRA PRÉCISE
|
||||||
|
|
||||||
### Côté Windows (machine hôte)
|
### Côté Windows (machine hôte)
|
||||||
@@ -146,9 +172,9 @@ Détails utiles :
|
|||||||
Depuis **Git Bash sur Windows** (à la racine du repo) :
|
Depuis **Git Bash sur Windows** (à la racine du repo) :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Étape 1 : compilation des paquets AUR (winboat) + deps locales
|
# Étape 1 : compilation des paquets AUR (winboat) + PKGBUILD locaux
|
||||||
|
# (anaconda porté sur Arch + ses dépendances python)
|
||||||
# → crée local-repo/ avec les .pkg.tar.zst
|
# → crée local-repo/ avec les .pkg.tar.zst
|
||||||
# (Calamares retiré ; l'installateur Anaconda est un chantier en cours)
|
|
||||||
./setup-aur.sh
|
./setup-aur.sh
|
||||||
|
|
||||||
# Étape 2 : build de l'ISO via mkarchiso
|
# Étape 2 : build de l'ISO via mkarchiso
|
||||||
@@ -170,11 +196,11 @@ L'ISO finale est générée dans `./out/FWS-YYYY.MM.DD-x86_64.iso`.
|
|||||||
4. **`pacman -Syu`** complet (obligatoire — sinon `makepkg` plante sur les "conflicting files" lors des splits de paquets type `gcc-libs`).
|
4. **`pacman -Syu`** complet (obligatoire — sinon `makepkg` plante sur les "conflicting files" lors des splits de paquets type `gcc-libs`).
|
||||||
5. **Install des outils de build** : `base-devel git`.
|
5. **Install des outils de build** : `base-devel git`.
|
||||||
6. **Création d'un utilisateur `fwsbuild`** non-root avec `sudo` NOPASSWD (makepkg refuse de tourner en root).
|
6. **Création d'un utilisateur `fwsbuild`** non-root avec `sudo` NOPASSWD (makepkg refuse de tourner en root).
|
||||||
7. **Clone + build AUR** : `winboat-bin` via `makepkg -s --noconfirm`, plus les PKGBUILD locaux de `pkgbuilds/` (deps Anaconda). Calamares n'est plus compilé.
|
7. **Clone + build AUR** : `winboat-bin` via `makepkg -s --noconfirm`, plus les PKGBUILD locaux de `pkgbuilds/` (anaconda, anaconda-widgets, pykickstart, blivet, python-meh patché…). Le build est **incrémental** : un paquet déjà présent dans `local-repo/` à la bonne version est sauté.
|
||||||
8. **Création du repo local** : `repo-add local-repo/fws-local.db.tar.gz`.
|
8. **Création du repo local** : `repo-add local-repo/fws-local.db.tar.gz`.
|
||||||
9. **Cleanup** : suppression de l'utilisateur `fwsbuild`.
|
9. **Cleanup** : suppression de l'utilisateur `fwsbuild`.
|
||||||
|
|
||||||
**Durée** : quelques minutes (winboat-bin + deps locales ; Calamares et ses ~12 dépendances Qt6 ne sont plus compilés).
|
**Durée** : quelques minutes en incrémental ; le premier build complet (anaconda + deps) est plus long.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -220,11 +246,12 @@ L'ISO finale est générée dans `./out/FWS-YYYY.MM.DD-x86_64.iso`.
|
|||||||
|
|
||||||
## Séquence de boot de l'ISO
|
## Séquence de boot de l'ISO
|
||||||
|
|
||||||
1. Démarrage de l'ISO FWS (BIOS ou UEFI)
|
1. Menu de boot : **installation** (défaut), **safe graphics** (`nomodeset`), **Réparer FWS** (`fws.recovery=1` → app GTK `fws-recovery-gui` via openbox, repli console `fws-recovery`), **speech**. Toutes les entrées plafonnent les workers udev bloqués (`udev.event_timeout=45`)
|
||||||
2. `systemd` → autologin root sur `tty1`
|
2. `systemd` → autologin root sur `tty1`
|
||||||
3. `.zprofile` → `startx`
|
3. `.zprofile` → mode recovery si demandé, sinon `startx`
|
||||||
4. `.xinitrc` → Openbox (session graphique du live)
|
4. `.xinitrc` → Openbox + **garde-fou RAM** (avertissement sous 2 Go) + **splash « Préparation de l'installateur »** (label alimenté en direct par les logs anaconda ; à 4 min sans activité → diagnostic auto `/tmp/fws-stall.txt` ; le tout se désarme dès que la GUI d'anaconda apparaît), bind mount `/run/rootfsbase` (source du payload LiveOS), patch du hub Anaconda, **exports `ANACONDA_PRODUCT*`** (branding FWS), puis `dbus-run-session liveinst`
|
||||||
5. **[Chantier C]** lancement de l'installateur Anaconda — pas encore câblé (Calamares retiré au commit 05ae9c3)
|
5. **Anaconda** : accueil → hub (langue, clavier, disque, utilisateur, **bureau FWS**) → « Commencer l'installation » → **page Récapitulatif** (bouton Précédent / Lancer l'installation) → installation → écran de réussite → extinction
|
||||||
|
6. En cas d'échec : écran de debug avec tous les logs (liveinst, %post chrooté, anaconda)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -233,8 +260,18 @@ L'ISO finale est générée dans `./out/FWS-YYYY.MM.DD-x86_64.iso`.
|
|||||||
- **Paquets du live** : `configs/releng/packages.x86_64`
|
- **Paquets du live** : `configs/releng/packages.x86_64`
|
||||||
- **Paquets baseline** : `configs/baseline/packages.x86_64`
|
- **Paquets baseline** : `configs/baseline/packages.x86_64`
|
||||||
- **Overlay rootfs** : `configs/releng/airootfs/` ou `configs/baseline/airootfs/`
|
- **Overlay rootfs** : `configs/releng/airootfs/` ou `configs/baseline/airootfs/`
|
||||||
- **Kickstart de l'installateur** : `configs/releng/airootfs/usr/share/anaconda/interactive-defaults.ks` (payload liveimg + %post)
|
- **Kickstart de l'installateur** : `configs/releng/airootfs/usr/share/anaconda/interactive-defaults.ks` (payload LiveOS via `--liveinst` + tout le `%post` : bureaux, drivers, pare-feu, branding)
|
||||||
- **Branding ISO** : `configs/releng/profiledef.sh` (`iso_name`, `iso_label`, `iso_publisher`)
|
- **Spokes Anaconda FWS** : `configs/baseline/airootfs/usr/lib/python3.14/site-packages/pyanaconda/ui/gui/spokes/` (`fws_desktop.py`, `fws_recap.py`) + glades dans `usr/share/anaconda/ui/spokes/`
|
||||||
|
- **Configs bureaux & thèmes** : `configs/releng/airootfs/usr/local/share/fws/` (hyprland, i3, waybar, polybar, wofi, rofi, picom) et `usr/share/sddm/themes/fws/`
|
||||||
|
- **Outils fws-\*** : `configs/releng/airootfs/usr/local/bin/`
|
||||||
|
- **Récupération sur l'OS installé** : entrée GRUB générée par `configs/releng/airootfs/etc/grub.d/42_fws-recovery` → `fws-recovery.target` + `fws-recovery-boot.service` (`etc/systemd/system/`) ; lanceur `usr/local/share/applications/fws-recovery.desktop` + politique polkit `usr/share/polkit-1/actions/org.fws.recovery.policy`
|
||||||
|
- **Swap zram** (live + installé) : `configs/releng/airootfs/etc/systemd/zram-generator.conf` (paquet `zram-generator`)
|
||||||
|
- **Branding ISO** : `configs/releng/profiledef.sh` (`iso_name`, `iso_label`, `iso_publisher`) ; **visuel du menu BIOS** : `configs/releng/syslinux/splash.png` (PNG 8-bit) ; **nom/version affichés par l'installateur** : exports `ANACONDA_PRODUCTNAME`/`ANACONDA_PRODUCTVERSION` dans le `.xinitrc` (lus depuis l'os-release du live)
|
||||||
|
|
||||||
|
> ⚠ **Règle d'or de la fusion** : au build, `configs/baseline/*` est copié **par-dessus** `configs/releng/*` — tout fichier homonyme de baseline **écrase** celui de releng. Ça concerne notamment `profiledef.sh`, `packages.x86_64`, `pacman.conf`, `efiboot/loader/entries/01-archiso-linux.conf` et `syslinux/syslinux.cfg` : **toute modification d'un de ces fichiers doit être reportée dans les deux profils** (des divergences ont déjà fait perdre silencieusement `firewalld` et le cap `udev.event_timeout` d'une ISO ; un `syslinux-linux.cfg` stock orphelin côté baseline définissait aussi un label `arch` sans le cap — supprimé, et le menu BIOS pointe désormais explicitement sur `DEFAULT fws`). Vérifier avec :
|
||||||
|
> ```bash
|
||||||
|
> comm -12 <(cd configs/baseline && find . -type f | sort) <(cd configs/releng && find . -type f | sort)
|
||||||
|
> ```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -247,7 +284,20 @@ L'ISO finale est générée dans `./out/FWS-YYYY.MM.DD-x86_64.iso`.
|
|||||||
| `pacman: signature unknown trust` | Keyring périmé → `pacman -Sy --noconfirm archlinux-keyring` puis relancer. |
|
| `pacman: signature unknown trust` | Keyring périmé → `pacman -Sy --noconfirm archlinux-keyring` puis relancer. |
|
||||||
| `makepkg: conflicting files` | `pacman -Syu` incomplet avant le build. Relancer `setup-aur.sh`. |
|
| `makepkg: conflicting files` | `pacman -Syu` incomplet avant le build. Relancer `setup-aur.sh`. |
|
||||||
| `mkarchiso` plante sur `[fws-local]` | Le repo local n'a pas été généré. Relancer `setup-aur.sh`. |
|
| `mkarchiso` plante sur `[fws-local]` | Le repo local n'a pas été généré. Relancer `setup-aur.sh`. |
|
||||||
| Écran noir après login Hyprland | Tu testes dans VirtualBox → pas d'accélération GPU. Utiliser QEMU/KVM avec `virtio-gpu` ou bare-metal. |
|
| Écran bleu 1-2 min au boot du live | Normal en VM : un worker udev traîne (plafonné à 45 s) — le splash « Préparation de l'installateur » l'indique. Attendre. |
|
||||||
|
| L'installateur semble figé / fenêtre blanche | Rendu logiciel en VM : la fenêtre Anaconda se peint lentement. Patienter quelques secondes. |
|
||||||
|
| Session Hyprland/i3 : fenêtres au contenu noir en VM | Compositing sur GPU virtuel — picom est auto-désactivé en VM (config FWS) ; si tu l'as réactivé, `pkill picom`. |
|
||||||
|
| Hyprland crash au lancement en VM | Géré : le wrapper `fws-hyprland` force le rendu logiciel (`kms_swrast`/llvmpipe) sous hyperviseur. Sur vrai GPU, rendu matériel normal. |
|
||||||
|
| Steam plante à l'install de SteamVR | En VM : `steam -cef-disable-gpu -cef-disable-gpu-compositing` (et la VR ne peut PAS marcher en VM — GPU/USB inaccessibles). |
|
||||||
|
| Icône Bluetooth absente / blueman en erreur | Pas d'adaptateur Bluetooth (cas VM) : le module de barre se cache et l'applet ne se lance pas — comportement voulu. |
|
||||||
|
| Le système installé ne boote plus | Menu GRUB → « **Réparer FWS (récupération du système)** » (boote l'initramfs fallback + l'app de réparation). Si GRUB lui-même est mort : booter l'ISO → entrée « **Réparer FWS (recovery)** ». |
|
||||||
|
| Réparer depuis le système installé (il boote encore) | Menu d'applications → « **Récupération FWS** » (mot de passe admin demandé une fois — polkit). |
|
||||||
|
| Réinstallation par-dessus une install existante | Le kickstart nettoie tout (`zerombr` + `clearpart --all`), mais en cas de comportement étrange au 2ᵉ passage, effacer/recréer le disque virtuel. |
|
||||||
|
| Boot BIOS | Fonctionne (résolu) : syslinux est sous `/boot/syslinux` de l'ISO (pas `/syslinux`), El-Torito BIOS + UEFI présents ; install BIOS complète validée en VM. Le menu BIOS démarre l'entrée FWS par défaut (`DEFAULT fws`). |
|
||||||
|
| Splash « Préparation de l'installateur » figé | **RAM insuffisante** (reproduit : gel à 1 Go, OK à 2 Go, aucun swap sur le live avant zram). En VM : allouer **4 Go** (le wizard VMware « Other 64-bit » n'en met que 1 !). Le live avertit désormais sous 2 Go, embarque un swap zram, et le splash affiche l'activité réelle d'anaconda (+ diagnostic auto si les logs ne bougent plus). |
|
||||||
|
| Dialogue « préparation bloquée : diagnostic » PAR-DESSUS l'installateur | Bug corrigé (ISO ≤ 2026-07-07 01:04) : le watchdog du splash se déclenchait alors qu'anaconda était à l'écran (logs silencieux = normal quand il attend l'utilisateur). Il se désarme désormais dès l'apparition de la GUI ; ferme le dialogue et continue, ou rebuild. |
|
||||||
|
| « Possibly missing firmware for module … » pendant une réparation Recovery | Avertissements `mkinitcpio` **normaux** : firmware de matériel exotique (SCSI/fibre channel…) absent de `linux-firmware`. Sans effet, sauf si tu possèdes ce matériel précis. Le journal Recovery l'explique désormais. |
|
||||||
|
| Recovery : « tout est grisé » | Corrigé : seul le sélecteur était désactivé (une seule installation détectée) mais toute l'app semblait morte ; il reste actif désormais. Les boutons d'action, eux, ont toujours été fonctionnels. |
|
||||||
| Retours à la ligne CRLF | `build.sh` lance `dos2unix` automatiquement, mais si tu modifies pendant le build, relancer. |
|
| Retours à la ligne CRLF | `build.sh` lance `dos2unix` automatiquement, mais si tu modifies pendant le build, relancer. |
|
||||||
|
|
||||||
### Logs utiles
|
### Logs utiles
|
||||||
@@ -256,11 +306,16 @@ L'ISO finale est générée dans `./out/FWS-YYYY.MM.DD-x86_64.iso`.
|
|||||||
# Pendant le build
|
# Pendant le build
|
||||||
tail -f /tmp/fws-build/work/iso/arch/pkglist.x86_64.txt
|
tail -f /tmp/fws-build/work/iso/arch/pkglist.x86_64.txt
|
||||||
|
|
||||||
# Dans l'ISO (autologin tty1)
|
# Dans le LIVE (Ctrl+Alt+F2, login root) — pendant/après l'installation
|
||||||
journalctl -b
|
journalctl -b # système
|
||||||
|
cat /tmp/fws-liveinst.log # sortie complète de liveinst/anaconda
|
||||||
|
cat /tmp/anaconda.log # log principal anaconda
|
||||||
|
cat /tmp/fws-post-chroot.log # %post chrooté = grub-install/mkinitcpio
|
||||||
|
# En cas d'échec, l'écran de debug du live affiche déjà tout ça (zenity).
|
||||||
|
|
||||||
# Hyprland post-install
|
# Sur le système installé
|
||||||
cat ~/.local/share/hyprland/hyprland.log
|
cat /var/log/fws-post.log # trace complète du %post d'installation
|
||||||
|
journalctl -b # premier démarrage
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
+26
-4
@@ -47,12 +47,24 @@ Le système live héritait de l'`os-release` du paquet `filesystem` d'Arch et
|
|||||||
donc sans conflit, et est retiré du système final par
|
donc sans conflit, et est retiré du système final par
|
||||||
`zzzz99-remove-custom-hooks-from-airootfs.hook`.
|
`zzzz99-remove-custom-hooks-from-airootfs.hook`.
|
||||||
- **`motd`** : ne référence plus `archlinux.org` ; pointe vers le dépôt FWS.
|
- **`motd`** : ne référence plus `archlinux.org` ; pointe vers le dépôt FWS.
|
||||||
|
- **Menu de boot BIOS (2026-07-07)** : l'image de fond `syslinux/splash.png`
|
||||||
|
héritée d'archiso affichait le **logo Arch Linux plein écran** — remplacée
|
||||||
|
par un visuel FWS original (générique, sans logo tiers).
|
||||||
|
- **Bannière de login (`/etc/issue`)** : `\S (\l)` — affiche le `PRETTY_NAME`
|
||||||
|
FWS, sans la version du noyau (dont le suffixe `-archN`).
|
||||||
|
- **Textes utilisateur** : `fws-about` dit « distribution Linux » ; le splash
|
||||||
|
de préparation et le journal de l'app Récupération remplacent à l'affichage
|
||||||
|
les mentions « Arch Linux »/« Fedora » issues des logs d'outils tiers (les
|
||||||
|
identifiants techniques réels — `archlinux-keyring`, miroirs — restent
|
||||||
|
intacts : usage nominatif fonctionnel).
|
||||||
|
|
||||||
> Note technique : `ID=fws` + `ID_LIKE=arch` préserve la détection « famille
|
> Note technique : `ID=fws` + `ID_LIKE=arch` préserve la détection « famille
|
||||||
> Arch » des outils tiers (et satisfait les checks `^ID(_LIKE)?=.*arch` de
|
> Arch » des outils tiers (et satisfait les checks `^ID(_LIKE)?=.*arch` de
|
||||||
> `build.sh`/`setup-aur.sh`, qui portent de toute façon sur l'hôte de build).
|
> `build.sh`/`setup-aur.sh`, qui portent de toute façon sur l'hôte de build).
|
||||||
> Si la migration Anaconda révèle un composant testant `ID=arch` en dur, basculer
|
> Si la migration Anaconda révèle un composant testant `ID=arch` en dur, basculer
|
||||||
> transitoirement le hook sur `ID=arch` tout en gardant `NAME`/`PRETTY_NAME` FWS.
|
> transitoirement le hook sur `ID=arch` tout en gardant `NAME`/`PRETTY_NAME` FWS.
|
||||||
|
> Le suffixe `-archN` de `uname -r` vient du binaire du noyau Arch : usage
|
||||||
|
> nominatif inévitable sans recompiler le kernel.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -60,7 +72,7 @@ Le système live héritait de l'`os-release` du paquet `filesystem` d'Arch et
|
|||||||
|
|
||||||
> **Red Hat®** et **Fedora®** sont des marques déposées de **Red Hat, Inc.**
|
> **Red Hat®** et **Fedora®** sont des marques déposées de **Red Hat, Inc.**
|
||||||
> L'installateur **Anaconda** est développé par Red Hat. FWS distribue une
|
> L'installateur **Anaconda** est développé par Red Hat. FWS distribue une
|
||||||
> **version MODIFIÉE** d'Anaconda (portée sur Arch, payload `liveimg`), **non
|
> **version MODIFIÉE** d'Anaconda (portée sur Arch, payload LiveOS), **non
|
||||||
> officielle**, **non fournie ni approuvée** par Red Hat ou le projet Fedora.
|
> officielle**, **non fournie ni approuvée** par Red Hat ou le projet Fedora.
|
||||||
> *« This product includes a modified version of Anaconda. Red Hat and Fedora
|
> *« This product includes a modified version of Anaconda. Red Hat and Fedora
|
||||||
> are trademarks of Red Hat, Inc. This is an unofficial build, not provided or
|
> are trademarks of Red Hat, Inc. This is an unofficial build, not provided or
|
||||||
@@ -74,9 +86,19 @@ Engagements FWS :
|
|||||||
passer une version modifiée pour officielle) ;
|
passer une version modifiée pour officielle) ;
|
||||||
- **recommandation béton :** retirer tout **logo / nom de produit Red Hat ou
|
- **recommandation béton :** retirer tout **logo / nom de produit Red Hat ou
|
||||||
Fedora** de l'interface de l'installateur si présent, et y afficher
|
Fedora** de l'interface de l'installateur si présent, et y afficher
|
||||||
« Installateur FWS (basé sur Anaconda) ». La configuration produit d'Anaconda
|
« Installateur FWS (basé sur Anaconda) ».
|
||||||
se règle via `product.d` / les données de branding — à auditer pendant la
|
|
||||||
migration.
|
### ✅ Remédiation appliquée (2026-07-07 — l'installateur s'annonce « FWS »)
|
||||||
|
|
||||||
|
Sans `/.buildstamp`, Anaconda s'annonçait « ANACONDA BLUESKY INSTALLATION » et
|
||||||
|
ses logs parlent de Fedora. `pyanaconda/core/product.py` donne priorité aux
|
||||||
|
variables d'environnement : le `.xinitrc` du live exporte
|
||||||
|
`ANACONDA_PRODUCTNAME`/`ANACONDA_PRODUCTVERSION` (depuis l'os-release FWS) et
|
||||||
|
`ANACONDA_ISFINAL=true` → l'en-tête et l'accueil affichent **« FWS 0.6 »**. Le
|
||||||
|
splash de préparation filtre en outre les mentions Fedora des lignes de log
|
||||||
|
affichées. Les mentions profondes dans les **logs techniques** (fichiers
|
||||||
|
`/tmp/*.log`, écrans de debug) sont conservées volontairement : identification
|
||||||
|
loyale du composant amont (version modifiée d'Anaconda), utile au diagnostic.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+116
-30
@@ -2,10 +2,13 @@
|
|||||||
# ============================================================
|
# ============================================================
|
||||||
# FWS — Build OFFICIEL versionné de l'ISO (archiso)
|
# FWS — Build OFFICIEL versionné de l'ISO (archiso)
|
||||||
#
|
#
|
||||||
# Identique à build.sh dans sa structure, mais :
|
# IDENTIQUE à build.sh (même bascule conteneur/WSL, --network=host, fusion
|
||||||
# - lit la version dans le fichier VERSION (format Major.Minor.Build)
|
# baseline, MÊME injection local-repo → donc même paquet anaconda avec le champ
|
||||||
# - nomme l'ISO FWS-vMAJOR-MINOR-BUILD-DATE-x86_64.iso
|
# Hostname), avec EN PLUS uniquement le RENOMMAGE :
|
||||||
# - range l'ISO dans out/vMAJOR/vMAJOR.MINOR/
|
# - demande la version (major.minor.build) + le nom de variante, le fichier
|
||||||
|
# VERSION servant de défaut,
|
||||||
|
# - nomme l'ISO fws-<version>-<nom>.iso (ex. fws-1.5.0-cli.iso),
|
||||||
|
# - range l'ISO dans out/vMAJOR/vMAJOR.MINOR/.
|
||||||
#
|
#
|
||||||
# Fonctionne sur :
|
# Fonctionne sur :
|
||||||
# - Windows 10 / 11 (Git Bash → bascule auto dans WSL Arch)
|
# - Windows 10 / 11 (Git Bash → bascule auto dans WSL Arch)
|
||||||
@@ -121,6 +124,37 @@ if [ -z "$FWS_IN_CONTAINER" ] && ! host_is_arch; then
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# La version + le nom sont DEMANDÉS sur l'hôte (avant le conteneur) et passés
|
||||||
|
# via -e, pour que le prompt s'affiche au bon endroit (terminal de l'hôte).
|
||||||
|
# (Définis FWS_ISO_VERSION / FWS_ISO_TAG ; lus plus bas, prompt sauté si déjà
|
||||||
|
# fournis — c.-à-d. dans le conteneur.)
|
||||||
|
ask_iso_meta_host() {
|
||||||
|
[ -n "$FWS_ISO_VERSION" ] && [ -n "$FWS_ISO_TAG" ] && return 0
|
||||||
|
local def=""
|
||||||
|
if [ -f "$(dirname "${BASH_SOURCE[0]}")/VERSION" ]; then
|
||||||
|
def="$(tr -d ' \t\r\n' < "$(dirname "${BASH_SOURCE[0]}")/VERSION")"
|
||||||
|
[[ "$def" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || def=""
|
||||||
|
fi
|
||||||
|
while :; do
|
||||||
|
if [ -n "$def" ]; then
|
||||||
|
read -rp "==> Version (major.minor.build) [Entrée = $def] : " FWS_ISO_VERSION || exit 1
|
||||||
|
[ -n "$FWS_ISO_VERSION" ] || FWS_ISO_VERSION="$def"
|
||||||
|
else
|
||||||
|
read -rp "==> Version (major.minor.build, ex. 1.5.0) : " FWS_ISO_VERSION || exit 1
|
||||||
|
fi
|
||||||
|
[[ "$FWS_ISO_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] && break
|
||||||
|
echo -e "\e[31m Format attendu : major.minor.build (ex. 1.5.0)\e[0m" >&2
|
||||||
|
done
|
||||||
|
while :; do
|
||||||
|
read -rp "==> Nom de la variante (ex. cli, gnome, kde) : " FWS_ISO_TAG || exit 1
|
||||||
|
FWS_ISO_TAG="$(printf '%s' "$FWS_ISO_TAG" | tr '[:upper:]' '[:lower:]' | tr -cd 'a-z0-9-')"
|
||||||
|
[ -n "$FWS_ISO_TAG" ] && break
|
||||||
|
echo -e "\e[31m Nom requis (lettres, chiffres, tirets)\e[0m" >&2
|
||||||
|
done
|
||||||
|
export FWS_ISO_VERSION FWS_ISO_TAG
|
||||||
|
}
|
||||||
|
ask_iso_meta_host
|
||||||
|
|
||||||
# Rootful : préfixe sudo si on n'est pas déjà root.
|
# Rootful : préfixe sudo si on n'est pas déjà root.
|
||||||
SUDO=(); [ "$EUID" -eq 0 ] || SUDO=(sudo)
|
SUDO=(); [ "$EUID" -eq 0 ] || SUDO=(sudo)
|
||||||
# -it seulement si on est dans un terminal (sinon podman casse en CI).
|
# -it seulement si on est dans un terminal (sinon podman casse en CI).
|
||||||
@@ -138,8 +172,14 @@ if [ -z "$FWS_IN_CONTAINER" ] && ! host_is_arch; then
|
|||||||
"${SUDO[@]}" podman pull "$IMAGE"
|
"${SUDO[@]}" podman pull "$IMAGE"
|
||||||
|
|
||||||
echo "==> Relance dans le conteneur ($SELF)..."
|
echo "==> Relance dans le conteneur ($SELF)..."
|
||||||
"${SUDO[@]}" podman run --rm "${TTY[@]}" --privileged \
|
# --network=host : le réseau bridge de podman ne route plus vers l'extérieur
|
||||||
|
# sur ce wifi (firewalld/NAT cassé après changement de réseau → « core.db :
|
||||||
|
# Resolving timed out », même avec --dns). En partageant la pile réseau de
|
||||||
|
# l'hôte (qui, lui, a internet + DNS via 127.0.0.53/systemd-resolved joignable
|
||||||
|
# dans son propre netns), le conteneur récupère une connectivité identique.
|
||||||
|
"${SUDO[@]}" podman run --rm "${TTY[@]}" --network=host --privileged \
|
||||||
-e FWS_IN_CONTAINER=1 \
|
-e FWS_IN_CONTAINER=1 \
|
||||||
|
-e FWS_ISO_VERSION -e FWS_ISO_TAG \
|
||||||
-v "$REPO_DIR":"$REPO_DIR" \
|
-v "$REPO_DIR":"$REPO_DIR" \
|
||||||
-v fws-pacman-cache:/var/cache/pacman/pkg \
|
-v fws-pacman-cache:/var/cache/pacman/pkg \
|
||||||
-w "$REPO_DIR" \
|
-w "$REPO_DIR" \
|
||||||
@@ -168,7 +208,8 @@ command -v pacman >/dev/null \
|
|||||||
# $0 est cité pour gérer les chemins à espaces (ex. "1. FWS").
|
# $0 est cité pour gérer les chemins à espaces (ex. "1. FWS").
|
||||||
if [ "$EUID" -ne 0 ]; then
|
if [ "$EUID" -ne 0 ]; then
|
||||||
if command -v sudo >/dev/null 2>&1; then
|
if command -v sudo >/dev/null 2>&1; then
|
||||||
exec sudo bash "$0" "$@"
|
# -E : conserve FWS_ISO_VERSION / FWS_ISO_TAG (demandés avant) à travers sudo.
|
||||||
|
exec sudo -E bash "$0" "$@"
|
||||||
else
|
else
|
||||||
exec su -c "bash \"$0\"" root
|
exec su -c "bash \"$0\"" root
|
||||||
fi
|
fi
|
||||||
@@ -188,29 +229,48 @@ WORK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||||||
echo "==> Dossier de travail : $WORK_DIR"
|
echo "==> Dossier de travail : $WORK_DIR"
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Lecture et validation de la version (fichier VERSION)
|
# Version (major.minor.build) + nom de la variante.
|
||||||
|
# Déjà demandés sur l'hôte (FWS_ISO_VERSION/FWS_ISO_TAG passés via -e) ; sinon
|
||||||
|
# (exécution native Arch / WSL directe) on les demande ici. Le fichier VERSION
|
||||||
|
# sert de défaut (Entrée = le reprendre). Produit fws-<version>-<nom>.iso.
|
||||||
# ============================================================
|
# ============================================================
|
||||||
VERSION_FILE="$WORK_DIR/VERSION"
|
VERSION="$FWS_ISO_VERSION"
|
||||||
[ -f "$VERSION_FILE" ] \
|
ISO_TAG="$FWS_ISO_TAG"
|
||||||
|| { echo -e "\e[31mFichier VERSION introuvable : $VERSION_FILE\e[0m"; exit 1; }
|
VERSION_DEFAULT=""
|
||||||
|
if [ -f "$WORK_DIR/VERSION" ]; then
|
||||||
# Trim espaces / tabulations / CR / LF
|
VERSION_DEFAULT="$(tr -d ' \t\r\n' < "$WORK_DIR/VERSION")"
|
||||||
VERSION="$(tr -d ' \t\r\n' < "$VERSION_FILE")"
|
[[ "$VERSION_DEFAULT" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || VERSION_DEFAULT=""
|
||||||
|
|
||||||
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
||||||
echo -e "\e[31mVERSION invalide : '$VERSION' (attendu Major.Minor.Build, ex. 1.2.3)\e[0m"
|
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
while [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; do
|
||||||
|
if [ -n "$VERSION_DEFAULT" ]; then
|
||||||
|
read -rp "==> Version (major.minor.build) [Entrée = $VERSION_DEFAULT] : " VERSION \
|
||||||
|
|| { echo -e "\e[31mEntrée requise — lance le script en interactif.\e[0m" >&2; exit 1; }
|
||||||
|
[ -n "$VERSION" ] || VERSION="$VERSION_DEFAULT"
|
||||||
|
else
|
||||||
|
read -rp "==> Version (major.minor.build, ex. 1.5.0) : " VERSION \
|
||||||
|
|| { echo -e "\e[31mEntrée requise — lance le script en interactif.\e[0m" >&2; exit 1; }
|
||||||
|
fi
|
||||||
|
[[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] \
|
||||||
|
|| echo -e "\e[31m Format attendu : major.minor.build (ex. 1.5.0)\e[0m" >&2
|
||||||
|
done
|
||||||
|
while [ -z "$ISO_TAG" ]; do
|
||||||
|
read -rp "==> Nom de la variante (ex. cli, gnome, kde) : " ISO_TAG \
|
||||||
|
|| { echo -e "\e[31mEntrée requise — lance le script en interactif.\e[0m" >&2; exit 1; }
|
||||||
|
ISO_TAG="$(printf '%s' "$ISO_TAG" | tr '[:upper:]' '[:lower:]' | tr -cd 'a-z0-9-')"
|
||||||
|
[ -n "$ISO_TAG" ] || echo -e "\e[31m Nom requis (lettres, chiffres, tirets)\e[0m" >&2
|
||||||
|
done
|
||||||
|
|
||||||
IFS='.' read -r MAJOR MINOR BUILD <<< "$VERSION"
|
IFS='.' read -r MAJOR MINOR BUILD <<< "$VERSION"
|
||||||
BUILD_DATE="$(date +%Y.%m.%d)"
|
BUILD_DATE="$(date +%Y.%m.%d)"
|
||||||
ISO_VERSION="v${MAJOR}-${MINOR}-${BUILD}-${BUILD_DATE}"
|
# iso_version interne (passé à mkarchiso) ; l'ISO finale est renommée plus bas.
|
||||||
|
ISO_VERSION="${VERSION}-${ISO_TAG}"
|
||||||
OUT_DIR="$WORK_DIR/out/v${MAJOR}/v${MAJOR}.${MINOR}"
|
OUT_DIR="$WORK_DIR/out/v${MAJOR}/v${MAJOR}.${MINOR}"
|
||||||
ISO_FILE="$OUT_DIR/FWS-${ISO_VERSION}-x86_64.iso"
|
# Date ajoutée À LA FIN du nom, avant .iso → fws-<version>-<nom>-<AAAA.MM.JJ>.iso
|
||||||
|
ISO_FILE="$OUT_DIR/fws-${VERSION}-${ISO_TAG}-${BUILD_DATE}.iso"
|
||||||
|
|
||||||
echo "==> Version officielle : ${MAJOR}.${MINOR}.${BUILD}"
|
echo "==> Version : ${VERSION} · variante : ${ISO_TAG} · date : ${BUILD_DATE}"
|
||||||
echo "==> Dossier de sortie : $OUT_DIR"
|
echo "==> ISO finale : fws-${VERSION}-${ISO_TAG}-${BUILD_DATE}.iso"
|
||||||
echo "==> ISO finale : FWS-${ISO_VERSION}-x86_64.iso"
|
echo "==> Dossier : $OUT_DIR"
|
||||||
|
|
||||||
# pacman ≥ 7 sandboxe les ops FS via landlock, non supporté par le kernel
|
# pacman ≥ 7 sandboxe les ops FS via landlock, non supporté par le kernel
|
||||||
# WSL ni (selon le noyau) par le conteneur → désactivation. Idempotent,
|
# WSL ni (selon le noyau) par le conteneur → désactivation. Idempotent,
|
||||||
@@ -249,8 +309,8 @@ if [ -d "$WORK_DIR/configs/baseline" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Injection de la version dans profiledef.sh (copie temporaire) ──
|
# ── Injection de la version dans profiledef.sh (copie temporaire) ──
|
||||||
# mkarchiso nomme l'ISO ${iso_name}-${iso_version}-${arch}.iso.
|
# mkarchiso nomme l'ISO ${iso_name}-${iso_version}-${arch}.iso ; on force
|
||||||
# On force iso_version pour obtenir vMAJOR-MINOR-BUILD-DATE.
|
# iso_version pour obtenir le nom voulu (l'ISO est renommée fws-… plus bas).
|
||||||
if [ -f /tmp/fws-build/releng/profiledef.sh ]; then
|
if [ -f /tmp/fws-build/releng/profiledef.sh ]; then
|
||||||
sed -i "s|^iso_version=.*|iso_version=\"${ISO_VERSION}\"|" \
|
sed -i "s|^iso_version=.*|iso_version=\"${ISO_VERSION}\"|" \
|
||||||
/tmp/fws-build/releng/profiledef.sh
|
/tmp/fws-build/releng/profiledef.sh
|
||||||
@@ -293,13 +353,39 @@ echo "==> Correction des retours à la ligne Windows (dos2unix)..."
|
|||||||
pacman -S --needed --noconfirm dos2unix
|
pacman -S --needed --noconfirm dos2unix
|
||||||
find /tmp/fws-build/releng -type f -exec dos2unix {} + 2>/dev/null
|
find /tmp/fws-build/releng -type f -exec dos2unix {} + 2>/dev/null
|
||||||
|
|
||||||
|
# ── Pré-vol : résolution de TOUS les paquets du profil ──
|
||||||
|
# « error: target not found » ne doit plus surgir en plein mkarchiso : on
|
||||||
|
# résout la liste complète (packages.x86_64 fusionné) contre le pacman.conf
|
||||||
|
# du profil (fws-local injecté inclus) dans une base temporaire. S'il manque
|
||||||
|
# un paquet, on échoue ICI avec la liste + le geste correctif.
|
||||||
|
echo "==> Pré-vol : résolution des paquets du profil..."
|
||||||
|
PREFLIGHT_DB="$(mktemp -d)"
|
||||||
|
pacman -Sy --config /tmp/fws-build/releng/pacman.conf --dbpath "$PREFLIGHT_DB" >/dev/null
|
||||||
|
mapfile -t PROFILE_PKGS < <(sed -e 's/#.*//' -e 's/[[:space:]]//g' -e '/^$/d' \
|
||||||
|
/tmp/fws-build/releng/packages.x86_64)
|
||||||
|
# -Sp n'installe rien (affiche les URLs) ; LC_ALL=C fige le motif d'erreur.
|
||||||
|
# stderr → capture, stdout → poubelle ; le pipeline sed sort toujours 0.
|
||||||
|
MISSING="$(LC_ALL=C pacman -Sp --config /tmp/fws-build/releng/pacman.conf \
|
||||||
|
--dbpath "$PREFLIGHT_DB" "${PROFILE_PKGS[@]}" 2>&1 >/dev/null \
|
||||||
|
| sed -n 's/^error: target not found:[[:space:]]*//p')"
|
||||||
|
rm -rf "$PREFLIGHT_DB"
|
||||||
|
if [ -n "$MISSING" ]; then
|
||||||
|
echo -e "\e[31mPaquets introuvables dans les dépôts du profil :\e[0m" >&2
|
||||||
|
printf ' \e[31m%s\e[0m\n' $MISSING >&2
|
||||||
|
echo -e "\e[33mDépôt fws-local absent/incomplet ? Lance ./setup-aur.sh (construit" >&2
|
||||||
|
echo -e "anaconda, winboat-bin, fws-dnd, …) puis relance ce script.\e[0m" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "==> Pré-vol OK : ${#PROFILE_PKGS[@]} paquets résolus."
|
||||||
|
|
||||||
echo "==> Lancement de mkarchiso..."
|
echo "==> Lancement de mkarchiso..."
|
||||||
mkdir -p "$OUT_DIR"
|
mkdir -p "$OUT_DIR"
|
||||||
# mkarchiso supprime l'ISO cible avant de la réécrire : si elle est VERROUILLÉE
|
# mkarchiso fait un « rm » interne de l'ISO cible avant de la (ré)écrire ; si
|
||||||
# par Windows (VM ouverte dessus, ISO montée), ce rm casse tout le build à la
|
# une ISO du même nom est VERROUILLÉE par Windows (VM encore ouverte dessus,
|
||||||
# fin. Parade : staging neuf puis déplacement ; si la cible (cette même
|
# ISO montée, antivirus), ce rm échoue et tout le build casse à la toute fin.
|
||||||
# version) est verrouillée, on bascule sur un nom horodaté. On ne touche
|
# Parade : mkarchiso écrit dans un dossier de staging neuf, puis on déplace
|
||||||
# jamais aux ISO des autres versions.
|
# l'ISO. Si la cible (cette même version) est verrouillée, on bascule sur un
|
||||||
|
# nom horodaté → on ne touche jamais aux ISO des autres versions.
|
||||||
STAGE="$OUT_DIR/.staging-$$"
|
STAGE="$OUT_DIR/.staging-$$"
|
||||||
rm -rf "$STAGE"; mkdir -p "$STAGE"
|
rm -rf "$STAGE"; mkdir -p "$STAGE"
|
||||||
mkarchiso -v -w /tmp/fws-build/work -o "$STAGE" /tmp/fws-build/releng
|
mkarchiso -v -w /tmp/fws-build/work -o "$STAGE" /tmp/fws-build/releng
|
||||||
@@ -319,4 +405,4 @@ echo "==> Nettoyage..."
|
|||||||
rm -rf /tmp/fws-build/work
|
rm -rf /tmp/fws-build/work
|
||||||
|
|
||||||
echo "==> Build officiel terminé ! ISO : $FINAL"
|
echo "==> Build officiel terminé ! ISO : $FINAL"
|
||||||
echo -e "\e[32mScript de build officiel (v${MAJOR}.${MINOR}.${BUILD}) terminé avec succès.\e[0m"
|
echo -e "\e[32mScript de build officiel (fws-${VERSION}-${ISO_TAG}-${BUILD_DATE}) terminé avec succès.\e[0m"
|
||||||
|
|||||||
@@ -133,7 +133,12 @@ if [ -z "$FWS_IN_CONTAINER" ] && ! host_is_arch; then
|
|||||||
"${SUDO[@]}" podman pull "$IMAGE"
|
"${SUDO[@]}" podman pull "$IMAGE"
|
||||||
|
|
||||||
echo "==> Relance dans le conteneur ($SELF)..."
|
echo "==> Relance dans le conteneur ($SELF)..."
|
||||||
"${SUDO[@]}" podman run --rm "${TTY[@]}" --privileged \
|
# --network=host : le réseau bridge de podman ne route plus vers l'extérieur
|
||||||
|
# sur ce wifi (firewalld/NAT cassé après changement de réseau → « core.db :
|
||||||
|
# Resolving timed out », même avec --dns). En partageant la pile réseau de
|
||||||
|
# l'hôte (qui, lui, a internet + DNS via 127.0.0.53/systemd-resolved joignable
|
||||||
|
# dans son propre netns), le conteneur récupère une connectivité identique.
|
||||||
|
"${SUDO[@]}" podman run --rm "${TTY[@]}" --network=host --privileged \
|
||||||
-e FWS_IN_CONTAINER=1 \
|
-e FWS_IN_CONTAINER=1 \
|
||||||
-v "$REPO_DIR":"$REPO_DIR" \
|
-v "$REPO_DIR":"$REPO_DIR" \
|
||||||
-v fws-pacman-cache:/var/cache/pacman/pkg \
|
-v fws-pacman-cache:/var/cache/pacman/pkg \
|
||||||
|
|||||||
@@ -20,10 +20,146 @@ _OB_PID=$!
|
|||||||
# boucle (symptôme « fond bleu qui clignote/reboote »). On NE rend jamais la main
|
# boucle (symptôme « fond bleu qui clignote/reboote »). On NE rend jamais la main
|
||||||
# à startx : on reste sur Openbox pour lire les logs / rebooter proprement.
|
# à startx : on reste sur Openbox pour lire les logs / rebooter proprement.
|
||||||
if command -v liveinst >/dev/null 2>&1; then
|
if command -v liveinst >/dev/null 2>&1; then
|
||||||
|
# Payload LiveOS : avec --liveinst (passé par liveinst), Anaconda IGNORE le
|
||||||
|
# payload du kickstart (anaconda.py teste opts.liveinst AVANT le type du
|
||||||
|
# payload DBus — un « liveimg » dans le ks serait du code mort) et détecte
|
||||||
|
# l'image live sur des chemins Fedora/dracut : /dev/mapper/live-base,
|
||||||
|
# /dev/mapper/live-osimg-min ou /run/rootfsbase — tous ABSENTS d'archiso →
|
||||||
|
# « SourceSetupError: No Live OS image found! » = crash au démarrage
|
||||||
|
# (reproduit hors VM dans un chroot de l'airootfs). On expose donc le
|
||||||
|
# squashfs archiso (monté ro sur /run/archiso/airootfs) là où Anaconda le
|
||||||
|
# cherche : findmnt y résout le loop device → montage → rsync vers la cible.
|
||||||
|
if [ -d /run/archiso/airootfs ] && [ ! -e /run/rootfsbase ]; then
|
||||||
|
mkdir -p /run/rootfsbase
|
||||||
|
mount --bind /run/archiso/airootfs /run/rootfsbase || rmdir /run/rootfsbase
|
||||||
|
fi
|
||||||
|
|
||||||
|
# GARDE-FOU RAM : le live n'a que le zram comme marge — en dessous de
|
||||||
|
# ~2 Go, Anaconda (GUI Python + squashfs) peut geler la machine en plein
|
||||||
|
# « Préparation de l'installateur ». On prévient AVANT plutôt que de
|
||||||
|
# laisser l'utilisateur croire à un bug.
|
||||||
|
_MEMKB=$(awk '/^MemTotal:/{print $2}' /proc/meminfo 2>/dev/null)
|
||||||
|
if [ -n "$_MEMKB" ] && [ "$_MEMKB" -lt 2000000 ] && command -v zenity >/dev/null 2>&1; then
|
||||||
|
zenity --warning --width=460 --title="FWS — mémoire insuffisante ?" \
|
||||||
|
--text="<b>Cette machine n'a que $(( _MEMKB / 1024 )) Mo de RAM.</b>\n\nL'installateur graphique peut geler en dessous de 2 Go.\nEn VM : augmente la mémoire (4 Go recommandés) puis redémarre.\n\nOK pour tenter quand même." 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ÉCRAN D'ATTENTE immédiat : le démarrage d'anaconda peut prendre 2 à
|
||||||
|
# 4 minutes (observé en VM VMware : un worker udev se bloque au boot et
|
||||||
|
# n'est tué par udevd qu'après 45 s — udev.event_timeout). Sans feedback,
|
||||||
|
# l'utilisateur croit à une panne alors que TOUT va bien. Le splash montre
|
||||||
|
# EN DIRECT ce que fait anaconda : un nourrisseur écrit sur stdin de
|
||||||
|
# zenity --progress (« # texte » = mise à jour du libellé) le log anaconda
|
||||||
|
# le plus récemment modifié + son dernier message + un chrono. Si les logs
|
||||||
|
# ne bougent plus (vrai blocage), le libellé le DIT (au lieu d'une barre
|
||||||
|
# qui pulse pour rien) et, au-delà de 4 min d'inactivité, on collecte
|
||||||
|
# journal+dmesg dans /tmp/fws-stall.txt et on l'affiche : l'écran devient
|
||||||
|
# lui-même le rapport de bug. Le nourrisseur meurt par SIGPIPE quand le
|
||||||
|
# splash est tué à la sortie de liveinst.
|
||||||
|
_SPLASH_PID=""
|
||||||
|
if command -v zenity >/dev/null 2>&1; then
|
||||||
|
(
|
||||||
|
_el=0; _quiet=0; _prevstamp=""
|
||||||
|
while :; do
|
||||||
|
sleep 3
|
||||||
|
# GUI d'anaconda à l'écran (marqueur ui.gui) → le splash n'a
|
||||||
|
# plus de raison d'être et le watchdog DOIT se désarmer : des
|
||||||
|
# logs silencieux pendant que l'utilisateur remplit les écrans
|
||||||
|
# sont NORMAUX (bug vécu : le rapport « préparation bloquée »
|
||||||
|
# s'affichait PAR-DESSUS l'installateur au bout de 4 min).
|
||||||
|
if grep -q 'ui\.gui' /tmp/anaconda.log 2>/dev/null; then
|
||||||
|
pkill -f 'zenity --progress' 2>/dev/null
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
# Compteurs en itérations (pas d'heure murale : l'horloge du
|
||||||
|
# guest SAUTE en VM — resynchro RTC — et gonflerait les âges,
|
||||||
|
# vu en vrai : « 242 s d'inactivité » 90 s après le boot).
|
||||||
|
_el=$(( _el + 3 ))
|
||||||
|
_last=$(ls -t /tmp/storage.log /tmp/anaconda.log /tmp/program.log /tmp/dbus.log 2>/dev/null | head -n1)
|
||||||
|
if [ -z "$_last" ]; then
|
||||||
|
printf '# Démarrage d'"'"'Anaconda… (%d min %02d s)\n' $(( _el / 60 )) $(( _el % 60 )) || exit 0
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
_stamp=$(stat -c %Y "$_last" 2>/dev/null)
|
||||||
|
if [ "$_stamp" = "$_prevstamp" ]; then
|
||||||
|
_quiet=$(( _quiet + 3 ))
|
||||||
|
else
|
||||||
|
_quiet=0; _prevstamp="$_stamp"
|
||||||
|
fi
|
||||||
|
# Rebranding d'affichage : anaconda (installateur Fedora) parle
|
||||||
|
# de Fedora/Arch dans ses logs — on ne montre pas ça à l'écran.
|
||||||
|
_msg=$(tail -n1 "$_last" 2>/dev/null | tr -d '\r' \
|
||||||
|
| sed -E 's/Fedora( Linux)?/FWS/g; s/Arch Linux/FWS/g; s/\<Arch\>/FWS/g' \
|
||||||
|
| cut -c1-90)
|
||||||
|
if [ "$_quiet" -ge 240 ] && [ ! -f /tmp/fws-stall.txt ]; then
|
||||||
|
{
|
||||||
|
echo "=== FWS : préparation bloquée (aucune activité log depuis ${_quiet}s) ==="
|
||||||
|
echo "--- dernier log actif : $_last"; tail -n 25 "$_last" 2>/dev/null
|
||||||
|
echo "--- udev (workers bloqués ?)"; udevadm settle --timeout=0 2>&1
|
||||||
|
echo "--- journal (fin)"; journalctl -b --no-pager 2>/dev/null | tail -n 60
|
||||||
|
echo "--- dmesg (fin)"; dmesg 2>/dev/null | tail -n 40
|
||||||
|
} > /tmp/fws-stall.txt 2>&1
|
||||||
|
zenity --text-info --title="FWS — préparation bloquée : diagnostic" \
|
||||||
|
--filename=/tmp/fws-stall.txt --width=1000 --height=650 2>/dev/null &
|
||||||
|
fi
|
||||||
|
if [ "$_quiet" -ge 60 ]; then
|
||||||
|
printf '# ⚠ Aucune activité depuis %d s (%s) — dernier message : %s\n' "$_quiet" "$(basename "$_last")" "$_msg" || exit 0
|
||||||
|
else
|
||||||
|
printf '# Préparation (%d min %02d s) — %s : %s\n' $(( _el / 60 )) $(( _el % 60 )) "$(basename "$_last")" "$_msg" || exit 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
) | zenity --progress --pulsate --no-cancel \
|
||||||
|
--title="FWS" \
|
||||||
|
--text="<b>Préparation de l'installateur FWS…</b>\n\nAnalyse du matériel et des disques en cours.\nEncore une à deux minutes, c'est normal." \
|
||||||
|
--width=560 2>/dev/null &
|
||||||
|
_SPLASH_PID=$!
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Hub Résumé SANS le libellé « We won't touch your disks… » : notre glade
|
||||||
|
# modifié NE PEUT PAS être livré directement à son chemin final —
|
||||||
|
# mkarchiso pose l'overlay airootfs AVANT pacstrap, et pacman refuse
|
||||||
|
# alors d'installer anaconda par-dessus (« exists in filesystem »). On le
|
||||||
|
# livre donc sous /usr/local/share/fws et on ÉCRASE ici, au boot du live
|
||||||
|
# (l'overlay du live est inscriptible), le fichier du paquet.
|
||||||
|
if [ -f /usr/local/share/fws/anaconda-summary-hub.glade ]; then
|
||||||
|
cp -f /usr/local/share/fws/anaconda-summary-hub.glade \
|
||||||
|
/usr/share/anaconda/ui/hubs/summary.glade 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# BRANDING : sans /.buildstamp, anaconda s'appelle « anaconda bluesky »
|
||||||
|
# (défauts de pyanaconda/core/product.py) et ses logs parlent de Fedora.
|
||||||
|
# Les variables d'environnement ANACONDA_* ont priorité → l'en-tête
|
||||||
|
# devient « FWS 1.0 INSTALLATION » et le log « anaconda … for FWS started ».
|
||||||
|
# (Les mentions Fedora PROFONDES dans les logs — GeoIP, profils — restent :
|
||||||
|
# ce sont des chaînes internes de l'installateur, sans effet sur l'install.)
|
||||||
|
export ANACONDA_PRODUCTNAME="$(. /etc/os-release 2>/dev/null; echo "${NAME:-FWS}")"
|
||||||
|
export ANACONDA_PRODUCTVERSION="$(. /etc/os-release 2>/dev/null; echo "${VERSION_ID:-0.6}")"
|
||||||
|
export ANACONDA_ISFINAL=true
|
||||||
|
|
||||||
LIVEINST_LOG=/tmp/fws-liveinst.log
|
LIVEINST_LOG=/tmp/fws-liveinst.log
|
||||||
liveinst >"$LIVEINST_LOG" 2>&1
|
# Bus D-Bus de SESSION : au démarrage (--liveinst), inhibit_screensaver()
|
||||||
|
# de pyanaconda se connecte au bus de session. S'il n'y en a AUCUN (notre
|
||||||
|
# startx root minimal n'en garantit pas), Gio lève une GLib.GError que
|
||||||
|
# pyanaconda n'attrape PAS (il n'attrape que DBusError) → crash immédiat
|
||||||
|
# (2e crash reproduit hors VM). dbus-run-session fournit un bus de session
|
||||||
|
# pour toute la durée de l'installateur ; avec un bus présent, l'absence du
|
||||||
|
# service ScreenSaver est attrapée (simple warning dans anaconda.log).
|
||||||
|
if command -v dbus-run-session >/dev/null 2>&1 && [ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ]; then
|
||||||
|
dbus-run-session -- liveinst >"$LIVEINST_LOG" 2>&1
|
||||||
|
else
|
||||||
|
liveinst >"$LIVEINST_LOG" 2>&1
|
||||||
|
fi
|
||||||
rc=$?
|
rc=$?
|
||||||
|
|
||||||
|
# liveinst terminé (install finie ou échec) : le splash n'a plus de raison
|
||||||
|
# d'être — kill de zenity ; le nourrisseur meurt de SIGPIPE à son
|
||||||
|
# prochain printf. Idem pour un éventuel dialogue de diagnostic de stall.
|
||||||
|
if [ -n "$_SPLASH_PID" ]; then
|
||||||
|
pkill -P "$_SPLASH_PID" 2>/dev/null
|
||||||
|
kill "$_SPLASH_PID" 2>/dev/null
|
||||||
|
fi
|
||||||
|
pkill -f "fws-stall.txt" 2>/dev/null
|
||||||
|
|
||||||
# ATTENTION : liveinst NE propage PAS le code de sortie d'anaconda — il
|
# ATTENTION : liveinst NE propage PAS le code de sortie d'anaconda — il
|
||||||
# enchaîne son propre nettoyage et sort souvent 0 même quand anaconda a
|
# enchaîne son propre nettoyage et sort souvent 0 même quand anaconda a
|
||||||
# planté. On ne se fie donc PAS à $rc : en phase debug on regroupe TOUS les
|
# planté. On ne se fie donc PAS à $rc : en phase debug on regroupe TOUS les
|
||||||
@@ -66,6 +202,9 @@ if command -v liveinst >/dev/null 2>&1; then
|
|||||||
echo
|
echo
|
||||||
echo "########## %post --nochroot (/tmp/fws-post-nochroot.log) ##########"
|
echo "########## %post --nochroot (/tmp/fws-post-nochroot.log) ##########"
|
||||||
cat /tmp/fws-post-nochroot.log 2>/dev/null || echo "(absent)"
|
cat /tmp/fws-post-nochroot.log 2>/dev/null || echo "(absent)"
|
||||||
|
echo
|
||||||
|
echo "########## Récapitulatif (page GUI fws_recap, avant install) ##########"
|
||||||
|
cat /tmp/fws-hw-summary.txt 2>/dev/null || echo "(absent — page récap pas atteinte)"
|
||||||
for f in /tmp/anaconda.log /tmp/syslog /tmp/program.log \
|
for f in /tmp/anaconda.log /tmp/syslog /tmp/program.log \
|
||||||
/tmp/storage.log /tmp/dbus.log /tmp/packaging.log /tmp/X.log; do
|
/tmp/storage.log /tmp/dbus.log /tmp/packaging.log /tmp/X.log; do
|
||||||
echo
|
echo
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
# Démarrage automatique de Xorg uniquement dans le live FWS.
|
# Démarrage automatique dans le live FWS uniquement.
|
||||||
# Sur le système installé, /run/archiso n'existe pas, donc rien ne se lance.
|
# Sur le système installé, /run/archiso n'existe pas, donc rien ne se lance.
|
||||||
|
|
||||||
|
# Mode RÉPARATION (entrée de boot « Réparer FWS », paramètre fws.recovery=1) :
|
||||||
|
# on démarre X + l'application GTK (fws-recovery-session lance openbox puis
|
||||||
|
# fws-recovery-gui, qui pilote le moteur fws-recovery). Si X échoue à
|
||||||
|
# démarrer, startx rend la main → repli sur l'assistant console fws-recovery.
|
||||||
|
# (Sur le SYSTÈME INSTALLÉ, l'équivalent est fws-recovery-boot.service via
|
||||||
|
# l'entrée GRUB « Réparer FWS » — pas ce fichier.)
|
||||||
|
if [[ -d /run/archiso && "$(tty)" == "/dev/tty1" ]] \
|
||||||
|
&& grep -qw 'fws.recovery=1' /proc/cmdline 2>/dev/null; then
|
||||||
|
startx /usr/local/bin/fws-recovery-session -- vt1
|
||||||
|
exec fws-recovery
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ -d /run/archiso && "$(tty)" == "/dev/tty1" && -z "$DISPLAY" ]]; then
|
if [[ -d /run/archiso && "$(tty)" == "/dev/tty1" && -z "$DISPLAY" ]]; then
|
||||||
exec startx -- vt1
|
exec startx -- vt1
|
||||||
fi
|
fi
|
||||||
|
|||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+106
@@ -0,0 +1,106 @@
|
|||||||
|
# FWS — Spoke « Desktop Environment » : nouvel onglet de choix du bureau.
|
||||||
|
#
|
||||||
|
# POURQUOI ICI (airootfs, pas dans le paquet anaconda) : Anaconda découvre les
|
||||||
|
# spokes au RUNTIME via collect()/collect_spokes (os.listdir du dossier des
|
||||||
|
# spokes puis import de chaque .py). Déposer ce fichier dans le dossier des
|
||||||
|
# spokes via l'overlay airootfs suffit donc à le faire apparaître — SANS rebuild
|
||||||
|
# du paquet anaconda (juste ./build.sh). Le glade associé est dans
|
||||||
|
# /usr/share/anaconda/ui/spokes/fws_desktop.glade (aussi via l'airootfs).
|
||||||
|
#
|
||||||
|
# ⚠ Le chemin /usr/lib/python3.14/... est couplé à la version de Python d'Arch
|
||||||
|
# (3.14 au moment du build d'anaconda). À mettre à jour si Python change de
|
||||||
|
# version majeure (en même temps que le rebuild d'anaconda de toute façon).
|
||||||
|
#
|
||||||
|
# FONCTION : afficher 5 choix (CLI/GNOME/KDE/Hyprland/XFCE) et écrire la clé
|
||||||
|
# choisie dans /tmp/fws-desktop. Le %post du kickstart lit ce fichier, fait
|
||||||
|
# « pacman -S » du bureau + d'un navigateur (Firefox), et active le display
|
||||||
|
# manager. « cli » (défaut) = aucun bureau, rien à installer.
|
||||||
|
|
||||||
|
from pyanaconda.core.i18n import CN_, _
|
||||||
|
from pyanaconda.ui.categories.software import SoftwareCategory
|
||||||
|
from pyanaconda.ui.gui.spokes import NormalSpoke
|
||||||
|
|
||||||
|
__all__ = ["FWSDesktopSpoke"]
|
||||||
|
|
||||||
|
# clé interne (écrite dans /tmp/fws-desktop) → libellé affiché dans le statut.
|
||||||
|
_CHOICES = [
|
||||||
|
("cli", _("Command line only (no desktop)")),
|
||||||
|
("gnome", "GNOME"),
|
||||||
|
("kde", "KDE Plasma"),
|
||||||
|
("hyprland", "Hyprland — Wayland (non compatible VR)"),
|
||||||
|
("i3", "i3 — X11 (compatible VR : SteamVR)"),
|
||||||
|
("xfce", "XFCE"),
|
||||||
|
]
|
||||||
|
_DESKTOP_FILE = "/tmp/fws-desktop"
|
||||||
|
|
||||||
|
|
||||||
|
class FWSDesktopSpoke(NormalSpoke):
|
||||||
|
"""Choix de l'environnement de bureau FWS (écrit /tmp/fws-desktop)."""
|
||||||
|
|
||||||
|
builderObjects = ["fwsDesktopWindow"]
|
||||||
|
mainWidgetName = "fwsDesktopWindow"
|
||||||
|
uiFile = "spokes/fws_desktop.glade"
|
||||||
|
category = SoftwareCategory
|
||||||
|
icon = "preferences-desktop-display-symbolic"
|
||||||
|
title = CN_("GUI|Spoke", "_Desktop Environment")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_screen_id():
|
||||||
|
"""Identifiant unique de cet écran."""
|
||||||
|
return "fws-desktop-selection"
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
NormalSpoke.__init__(self, *args)
|
||||||
|
self._choice = "cli"
|
||||||
|
self._radios = {}
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
NormalSpoke.initialize(self)
|
||||||
|
for key, _label in _CHOICES:
|
||||||
|
self._radios[key] = self.builder.get_object("radio_" + key)
|
||||||
|
# Reprend un éventuel choix déjà écrit (revisite du spoke).
|
||||||
|
try:
|
||||||
|
with open(_DESKTOP_FILE) as fh:
|
||||||
|
val = fh.read().strip()
|
||||||
|
if val in self._radios:
|
||||||
|
self._choice = val
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def refresh(self):
|
||||||
|
# Coche le bouton correspondant au choix courant.
|
||||||
|
rb = self._radios.get(self._choice) or self._radios.get("cli")
|
||||||
|
if rb is not None:
|
||||||
|
rb.set_active(True)
|
||||||
|
|
||||||
|
def apply(self):
|
||||||
|
# Relit le bouton actif et persiste le choix pour le %post du kickstart.
|
||||||
|
for key, rb in self._radios.items():
|
||||||
|
if rb is not None and rb.get_active():
|
||||||
|
self._choice = key
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
with open(_DESKTOP_FILE, "w") as fh:
|
||||||
|
fh.write(self._choice)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status(self):
|
||||||
|
for key, label in _CHOICES:
|
||||||
|
if key == self._choice:
|
||||||
|
return label
|
||||||
|
return _("Command line only (no desktop)")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def completed(self):
|
||||||
|
# « cli » est un défaut valide → le spoke n'est jamais bloquant.
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mandatory(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ready(self):
|
||||||
|
return True
|
||||||
+388
@@ -0,0 +1,388 @@
|
|||||||
|
# FWS — Page « Récapitulatif » : écran À PART ENTIÈRE avant l'installation.
|
||||||
|
#
|
||||||
|
# POURQUOI ICI (airootfs, pas dans le paquet anaconda) : même mécanisme que la
|
||||||
|
# spoke « Desktop Environment » (fws_desktop.py) — Anaconda découvre les spokes
|
||||||
|
# au RUNTIME (collect) dans ce dossier → l'overlay airootfs suffit, AUCUN
|
||||||
|
# rebuild du paquet anaconda. Le glade associé est dans
|
||||||
|
# /usr/share/anaconda/ui/spokes/fws_recap.glade (aussi via l'airootfs).
|
||||||
|
#
|
||||||
|
# PLACEMENT DANS LE FLUX : standalone spoke « postForHub = SummaryHub » avec
|
||||||
|
# priority = -10. L'ordre des écrans est pre(SummaryHub) → SummaryHub →
|
||||||
|
# post(SummaryHub) trié par priority croissante (UserInterface.
|
||||||
|
# _orderActionClasses). ProgressSpoke — l'écran qui DÉMARRE l'installation
|
||||||
|
# dans son refresh() — est aussi post(SummaryHub), priority 0 par défaut →
|
||||||
|
# cette page s'intercale ENTRE les deux : l'utilisateur clique « Commencer
|
||||||
|
# l'installation » sur le hub, voit CE récapitulatif (le disque n'a encore
|
||||||
|
# subi AUCUNE écriture), et l'installation ne démarre qu'à son clic
|
||||||
|
# « Lancer l'installation ». Le bouton « Précédent » ramène au hub pour
|
||||||
|
# modifier les choix (cf. _on_back_clicked : la file d'actions du GUI est
|
||||||
|
# consommée à chaque écran, on y réinsère le hub).
|
||||||
|
#
|
||||||
|
# CONTENU : configuration choisie par l'utilisateur (via les modules D-Bus
|
||||||
|
# d'Anaconda : langue, clavier, fuseau, comptes, partitionnement + les
|
||||||
|
# fichiers /tmp des spokes FWS : bureau, hostname) ET matériel détecté
|
||||||
|
# (virtualisation, GPU, disques, réseau) — mêmes sources que ce que le
|
||||||
|
# %post du kickstart et fws-setup-hardware appliqueront.
|
||||||
|
#
|
||||||
|
# ⚠ « completed » doit rester False : _instantiateAction SAUTE les standalone
|
||||||
|
# spokes complétées (mécanisme prévu pour les installs kickstart).
|
||||||
|
# ⚠ Chemin couplé à la version Python d'Arch (3.14) — comme fws_desktop.py —
|
||||||
|
# et le bouton « Précédent » s'appuie sur les internes du GUI d'anaconda
|
||||||
|
# 45.8 (_actions/_currentAction) : à re-vérifier à chaque bump d'anaconda.
|
||||||
|
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
import gi
|
||||||
|
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
from gi.repository import Gtk
|
||||||
|
|
||||||
|
from pyanaconda.anaconda_loggers import get_module_logger
|
||||||
|
from pyanaconda.core import util
|
||||||
|
from pyanaconda.modules.common.constants.objects import DISK_SELECTION
|
||||||
|
from pyanaconda.modules.common.constants.services import (
|
||||||
|
LOCALIZATION,
|
||||||
|
STORAGE,
|
||||||
|
TIMEZONE,
|
||||||
|
USERS,
|
||||||
|
)
|
||||||
|
from pyanaconda.modules.common.structures.user import UserData
|
||||||
|
from pyanaconda.ui.gui import GraphicalUserInterface
|
||||||
|
from pyanaconda.ui.gui.hubs.summary import SummaryHub
|
||||||
|
from pyanaconda.ui.gui.spokes import StandaloneSpoke
|
||||||
|
|
||||||
|
log = get_module_logger(__name__)
|
||||||
|
|
||||||
|
__all__ = ["FWSRecapSpoke"]
|
||||||
|
|
||||||
|
# Doit rester aligné sur le %post du kickstart (étape « Bureau choisi ») et
|
||||||
|
# sur les libellés de la spoke fws_desktop.
|
||||||
|
_DESKTOP_LABELS = {
|
||||||
|
"cli": "ligne de commande uniquement (aucun bureau)",
|
||||||
|
"gnome": "GNOME (+ Firefox, GDM)",
|
||||||
|
"kde": "KDE Plasma (+ Firefox, SDDM)",
|
||||||
|
"hyprland": "Hyprland — Wayland, non compatible VR (+ Firefox, SDDM)",
|
||||||
|
"i3": "i3 — X11, compatible VR/SteamVR (+ Firefox, SDDM)",
|
||||||
|
"xfce": "XFCE (+ Firefox, SDDM)",
|
||||||
|
}
|
||||||
|
|
||||||
|
_PARTITIONING_LABELS = {
|
||||||
|
"AUTOMATIC": "automatique (recommandé)",
|
||||||
|
"INTERACTIVE": "personnalisé (assistant)",
|
||||||
|
"CUSTOM": "personnalisé",
|
||||||
|
"BLIVET": "avancé (blivet-gui)",
|
||||||
|
"MANUAL": "points de montage manuels",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _safe(getter, default):
|
||||||
|
"""Valeur d'un getter D-Bus/système, ou défaut : le récap ne doit JAMAIS
|
||||||
|
faire tomber l'installateur, quel que soit le module indisponible (d'où
|
||||||
|
le except large : dasbus lève des types variés selon la panne)."""
|
||||||
|
try:
|
||||||
|
return getter()
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def _capture(*argv):
|
||||||
|
"""stdout d'une commande, chaîne vide si absente ou en échec."""
|
||||||
|
try:
|
||||||
|
return util.execWithCapture(argv[0], list(argv[1:])) or ""
|
||||||
|
except (OSError, RuntimeError):
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def _read_flag(path):
|
||||||
|
try:
|
||||||
|
with open(path) as fh:
|
||||||
|
return fh.read().strip()
|
||||||
|
except OSError:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def _config_lines():
|
||||||
|
"""Section « configuration choisie » : interroge les modules D-Bus
|
||||||
|
d'Anaconda (remplis par les spokes) + les fichiers /tmp des spokes FWS."""
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
lang = _safe(lambda: LOCALIZATION.get_proxy().Language, "")
|
||||||
|
if lang:
|
||||||
|
lines.append(f" • Langue : {lang}")
|
||||||
|
|
||||||
|
layouts = _safe(lambda: list(LOCALIZATION.get_proxy().XLayouts), [])
|
||||||
|
if layouts:
|
||||||
|
lines.append(f" • Clavier (X11) : {', '.join(layouts)}")
|
||||||
|
|
||||||
|
tz = _safe(lambda: TIMEZONE.get_proxy().Timezone, "")
|
||||||
|
if tz:
|
||||||
|
ntp = _safe(lambda: TIMEZONE.get_proxy().NTPEnabled, None)
|
||||||
|
suffix = {True: " — synchronisation NTP activée",
|
||||||
|
False: " — synchronisation NTP désactivée"}.get(ntp, "")
|
||||||
|
lines.append(f" • Fuseau horaire : {tz}{suffix}")
|
||||||
|
|
||||||
|
users = _safe(
|
||||||
|
lambda: UserData.from_structure_list(USERS.get_proxy().Users), [])
|
||||||
|
if users:
|
||||||
|
names = ", ".join(u.name for u in users if u.name)
|
||||||
|
lines.append(f" • Utilisateur(s) : {names} (sudo via wheel)")
|
||||||
|
else:
|
||||||
|
lines.append(" • Utilisateur(s) : AUCUN — pensez-y avant de lancer !")
|
||||||
|
|
||||||
|
root_locked = _safe(lambda: USERS.get_proxy().IsRootAccountLocked, None)
|
||||||
|
root_pw = _safe(lambda: USERS.get_proxy().IsRootPasswordSet, None)
|
||||||
|
if root_locked:
|
||||||
|
root_txt = "verrouillé (recommandé)"
|
||||||
|
elif root_pw:
|
||||||
|
root_txt = "actif, mot de passe défini"
|
||||||
|
elif root_pw is not None:
|
||||||
|
# Même règle que l'étape « Sécurité » du %post : un root resté sans
|
||||||
|
# mot de passe sera verrouillé en fin d'installation.
|
||||||
|
root_txt = "sans mot de passe → sera VERROUILLÉ en fin d'install"
|
||||||
|
else:
|
||||||
|
root_txt = ""
|
||||||
|
if root_txt:
|
||||||
|
lines.append(f" • Compte root : {root_txt}")
|
||||||
|
|
||||||
|
# Hostname : écrit par le spoke Utilisateur (patch user.py du paquet
|
||||||
|
# anaconda). Même nettoyage / même défaut que le %post --nochroot.
|
||||||
|
hn = "".join(c for c in _read_flag("/tmp/fws-hostname")
|
||||||
|
if c.isalnum() or c in "._-") or "fws"
|
||||||
|
lines.append(f" • Nom de machine : {hn}")
|
||||||
|
|
||||||
|
# Bureau : écrit par fws_desktop.apply() (sortie de la spoke). Absent =
|
||||||
|
# spoke jamais visitée = « cli », exactement le défaut du %post.
|
||||||
|
desk = _read_flag("/tmp/fws-desktop") or "cli"
|
||||||
|
lines.append(f" • Bureau : {_DESKTOP_LABELS.get(desk, desk)}")
|
||||||
|
|
||||||
|
method = _safe(
|
||||||
|
lambda: STORAGE.get_proxy(
|
||||||
|
STORAGE.get_proxy().AppliedPartitioning).PartitioningMethod, "")
|
||||||
|
if method:
|
||||||
|
lines.append(" • Partitionnement : "
|
||||||
|
f"{_PARTITIONING_LABELS.get(method, method)}")
|
||||||
|
|
||||||
|
return lines, desk, method
|
||||||
|
|
||||||
|
|
||||||
|
def _windows_dualboot_lines(selected):
|
||||||
|
"""Section dual-boot Windows : lit /tmp/fws-windows (parse LITTÉRAL, jamais
|
||||||
|
sourcé), montre le disque qui SERA EFFACÉ et ALERTE en cas de conflit avec la
|
||||||
|
cible d'installation FWS (même disque choisi pour les deux)."""
|
||||||
|
state = {}
|
||||||
|
try:
|
||||||
|
with open("/tmp/fws-windows") as fh:
|
||||||
|
for line in fh:
|
||||||
|
if "=" in line and not line.lstrip().startswith("#"):
|
||||||
|
key, _, val = line.partition("=")
|
||||||
|
state[key.strip()] = val.strip()
|
||||||
|
except OSError:
|
||||||
|
return []
|
||||||
|
if state.get("enabled") != "1":
|
||||||
|
return [" • Dual-boot Windows : désactivé (FWS uniquement)"]
|
||||||
|
disk = state.get("disk", "?")
|
||||||
|
size = state.get("size_gib", "?")
|
||||||
|
iso = state.get("iso", "")
|
||||||
|
iso_name = iso.rsplit("/", 1)[-1] if iso else "(aucune ISO fournie)"
|
||||||
|
lines = [
|
||||||
|
f" • Dual-boot Windows : ACTIVÉ — {disk} ({size} GiO NTFS)",
|
||||||
|
f" ISO : {iso_name}",
|
||||||
|
f" ⚠ TOUT le disque {disk} sera EFFACÉ et dédié à Windows.",
|
||||||
|
]
|
||||||
|
if state.get("intel_vmd") == "1":
|
||||||
|
lines.append(" • Support Intel VMD/RST : activé (pilote injecté au boot Windows)")
|
||||||
|
base = disk.rsplit("/", 1)[-1]
|
||||||
|
if base and base in selected:
|
||||||
|
lines.append(
|
||||||
|
f" ⛔ CONFLIT : {disk} est AUSSI une cible d'installation FWS —"
|
||||||
|
" choisissez des disques DIFFÉRENTS (sinon l'un écrase l'autre).")
|
||||||
|
return lines
|
||||||
|
|
||||||
|
|
||||||
|
def _build_summary():
|
||||||
|
config_lines, desk, method = _config_lines()
|
||||||
|
config_text = "\n".join(config_lines) \
|
||||||
|
or " (configuration non disponible)"
|
||||||
|
|
||||||
|
# Disques ciblés par l'installation (spoke « Destination »).
|
||||||
|
selected = set(_safe(
|
||||||
|
lambda: list(STORAGE.get_proxy(DISK_SELECTION).SelectedDisks), []))
|
||||||
|
disks = []
|
||||||
|
for line in _capture("lsblk", "-dno", "NAME,SIZE,MODEL,TYPE").splitlines():
|
||||||
|
fields = line.split()
|
||||||
|
if fields and fields[-1] == "disk":
|
||||||
|
mark = " ← CIBLE de l'installation" if fields[0] in selected else ""
|
||||||
|
disks.append(" • " + " ".join(fields[:-1]) + mark)
|
||||||
|
disk_text = "\n".join(disks) or " (aucun disque détecté)"
|
||||||
|
|
||||||
|
if method == "AUTOMATIC":
|
||||||
|
disk_warn = (
|
||||||
|
" ⚠ Rien n'a encore été écrit. Le partitionnement automatique\n"
|
||||||
|
" EFFACERA ENTIÈREMENT le(s) disque(s) cible(s) au lancement.")
|
||||||
|
else:
|
||||||
|
disk_warn = (
|
||||||
|
" ⚠ Rien n'a encore été écrit. Au lancement, ce sont vos choix\n"
|
||||||
|
" de partitionnement faits dans l'installateur qui s'appliqueront.")
|
||||||
|
|
||||||
|
virt = (_capture("systemd-detect-virt").strip().splitlines() or ["none"])[0]
|
||||||
|
if virt in ("", "none"):
|
||||||
|
virt = "aucune (machine physique)"
|
||||||
|
|
||||||
|
gpus = []
|
||||||
|
for line in _capture("lspci").splitlines():
|
||||||
|
low = line.lower()
|
||||||
|
if ("vga" in low or "3d" in low or "display" in low) and ":" in line:
|
||||||
|
gpus.append(" • " + line.split(":", 2)[-1].strip())
|
||||||
|
gpu_text = "\n".join(gpus) or " (aucune carte graphique détectée)"
|
||||||
|
|
||||||
|
try:
|
||||||
|
net_ok = util.execWithRedirect("ip", ["route", "get", "1.1.1.1"]) == 0
|
||||||
|
except (OSError, RuntimeError):
|
||||||
|
net_ok = False
|
||||||
|
if net_ok:
|
||||||
|
net = "disponible ✔"
|
||||||
|
else:
|
||||||
|
net = ("INDISPONIBLE ✖ — bureau, drivers et stack FWS ne\n"
|
||||||
|
" s'installeront PAS (rattrapable au 1er boot)")
|
||||||
|
|
||||||
|
extras = ("Steam, Wine, VR (Monado/OpenXR), winboat-bin"
|
||||||
|
if desk != "cli" else "rien de plus (choix CLI)")
|
||||||
|
|
||||||
|
win_text = "\n".join(_windows_dualboot_lines(selected)) \
|
||||||
|
or " • Dual-boot Windows : désactivé (FWS uniquement)"
|
||||||
|
|
||||||
|
return f"""\
|
||||||
|
══════════════════════════════════════════════════════════
|
||||||
|
FWS — RÉCAPITULATIF AVANT INSTALLATION
|
||||||
|
══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
CONFIGURATION CHOISIE
|
||||||
|
{config_text}
|
||||||
|
|
||||||
|
DISQUES
|
||||||
|
{disk_warn}
|
||||||
|
{disk_text}
|
||||||
|
|
||||||
|
DUAL-BOOT WINDOWS (gaming — Valorant/LoL)
|
||||||
|
{win_text}
|
||||||
|
|
||||||
|
MATÉRIEL DÉTECTÉ
|
||||||
|
• Virtualisation : {virt}
|
||||||
|
• Réseau : {net}
|
||||||
|
Cartes graphiques (drivers installés automatiquement en fin d'install) :
|
||||||
|
{gpu_text}
|
||||||
|
|
||||||
|
DÉROULEMENT DE L'INSTALLATION
|
||||||
|
1. Partitionnement, puis copie du système live FWS (payload LiveOS)
|
||||||
|
2. Noyau + initramfs régénérés (mkinitcpio), GRUB installé
|
||||||
|
3. Bureau choisi + navigateur installés (réseau requis)
|
||||||
|
4. Matériel : drivers GPU, outils VM, son, Bluetooth, Podman
|
||||||
|
+ si bureau choisi : {extras}
|
||||||
|
|
||||||
|
« Précédent » pour revenir modifier vos choix —
|
||||||
|
« Lancer l'installation » pour démarrer réellement l'installation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class FWSRecapSpoke(StandaloneSpoke):
|
||||||
|
"""Récapitulatif des choix, affiché entre le hub et l'installation."""
|
||||||
|
|
||||||
|
builderObjects = ["fwsRecapWindow"]
|
||||||
|
mainWidgetName = "fwsRecapWindow"
|
||||||
|
uiFile = "spokes/fws_recap.glade"
|
||||||
|
|
||||||
|
postForHub = SummaryHub
|
||||||
|
priority = -10
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_screen_id():
|
||||||
|
"""Identifiant unique de cet écran."""
|
||||||
|
return "fws-recap"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def completed(self):
|
||||||
|
# False, sinon _instantiateAction saute l'écran (cf. en-tête).
|
||||||
|
return False
|
||||||
|
|
||||||
|
def apply(self):
|
||||||
|
# Rien à persister : cette page ne fait que montrer l'état.
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
StandaloneSpoke.initialize(self)
|
||||||
|
# Le bouton « continuer » de CETTE page est le vrai déclencheur :
|
||||||
|
# l'écran suivant (ProgressSpoke) démarre la file d'installation.
|
||||||
|
self.window.get_continue_button().set_label("_Lancer l'installation")
|
||||||
|
|
||||||
|
# Capture pour « Précédent » : initialize() est appelé DEPUIS
|
||||||
|
# GraphicalUserInterface._on_continue_clicked (clic « Commencer
|
||||||
|
# l'installation » du hub), AVANT la bascule de _currentAction → à
|
||||||
|
# cet instant ui._currentAction est ENCORE l'instance du hub Résumé,
|
||||||
|
# et le « self » de l'appelant est l'UI. Aucune API publique ne les
|
||||||
|
# expose ; on remonte la pile d'appels (déterministe — pas de scan
|
||||||
|
# mémoire, pas de variable globale).
|
||||||
|
self._gui = None
|
||||||
|
self._hub = None
|
||||||
|
frame = inspect.currentframe()
|
||||||
|
try:
|
||||||
|
frame = frame.f_back
|
||||||
|
while frame is not None:
|
||||||
|
candidate = frame.f_locals.get("self")
|
||||||
|
if isinstance(candidate, GraphicalUserInterface):
|
||||||
|
self._gui = candidate
|
||||||
|
current = getattr(candidate, "_currentAction", None)
|
||||||
|
if isinstance(current, SummaryHub):
|
||||||
|
self._hub = current
|
||||||
|
break
|
||||||
|
frame = frame.f_back
|
||||||
|
finally:
|
||||||
|
del frame
|
||||||
|
|
||||||
|
# Bouton « Précédent », placé avec les boutons de navigation
|
||||||
|
# (à côté de « Quitter » — leur parent est la GtkButtonBox du bas).
|
||||||
|
quit_button = self.window.get_quit_button()
|
||||||
|
button_box = quit_button.get_parent()
|
||||||
|
self._back_button = Gtk.Button.new_with_mnemonic("_Précédent")
|
||||||
|
self._back_button.connect("clicked", self._on_back_clicked)
|
||||||
|
button_box.add(self._back_button)
|
||||||
|
button_box.reorder_child(self._back_button, 0)
|
||||||
|
if self._gui is None or self._hub is None:
|
||||||
|
# Références introuvables (flux inattendu) : bouton grisé plutôt
|
||||||
|
# qu'une navigation cassée — l'install reste possible.
|
||||||
|
log.warning("FWS recap: hub/UI introuvables, « Précédent » désactivé.")
|
||||||
|
self._back_button.set_sensitive(False)
|
||||||
|
self._back_button.show()
|
||||||
|
|
||||||
|
def _on_back_clicked(self, _button):
|
||||||
|
ui = self._gui
|
||||||
|
hub = self._hub
|
||||||
|
if ui is None or hub is None:
|
||||||
|
return
|
||||||
|
log.info("FWS recap: retour au hub Résumé pour modification.")
|
||||||
|
# La file d'actions du GUI est CONSOMMÉE (pop(0)) à chaque écran :
|
||||||
|
# ici _actions = [FWSRecapSpoke, ProgressSpoke] et le hub en est déjà
|
||||||
|
# sorti. On le réinsère en tête pour que le prochain « Commencer
|
||||||
|
# l'installation » ré-enchaîne hub → NOUVELLE page récap (résumé
|
||||||
|
# recalculé avec les choix modifiés) → installation.
|
||||||
|
if not ui._actions or ui._actions[0] is not SummaryHub:
|
||||||
|
ui._actions.insert(0, SummaryHub)
|
||||||
|
# _on_continue_clicked a désactivé le « continuer » du hub en le
|
||||||
|
# quittant : on le réarme, sinon « Commencer l'installation » est mort.
|
||||||
|
hub.window.set_may_continue(True)
|
||||||
|
# Même séquence que la transition officielle (_on_continue_clicked).
|
||||||
|
self.exited.emit(self)
|
||||||
|
hub.refresh()
|
||||||
|
ui.mainWindow.setCurrentAction(hub)
|
||||||
|
hub.entered.emit(hub)
|
||||||
|
ui._currentAction = hub
|
||||||
|
|
||||||
|
def refresh(self):
|
||||||
|
StandaloneSpoke.refresh(self)
|
||||||
|
text = _build_summary()
|
||||||
|
self.builder.get_object("recapView").get_buffer().set_text(text)
|
||||||
|
# Copie pour le harnais de debug du .xinitrc (fws-debug.txt).
|
||||||
|
try:
|
||||||
|
with open("/tmp/fws-hw-summary.txt", "w") as fh:
|
||||||
|
fh.write(text)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
+343
@@ -0,0 +1,343 @@
|
|||||||
|
# FWS — Spoke « Dual-boot Windows gaming » : case + disque + taille + ISO.
|
||||||
|
#
|
||||||
|
# POURQUOI ICI (airootfs, pas dans le paquet anaconda) : identique à
|
||||||
|
# fws_desktop.py / fws_recap.py — Anaconda découvre les spokes au RUNTIME via
|
||||||
|
# collect() (os.listdir du dossier des spokes puis import). Déposer ce .py dans
|
||||||
|
# le dossier des spokes via l'overlay airootfs suffit à le faire apparaître SANS
|
||||||
|
# rebuild du paquet anaconda (juste ./build.sh). Le glade associé est dans
|
||||||
|
# /usr/share/anaconda/ui/spokes/fws_windows.glade (aussi via l'airootfs).
|
||||||
|
#
|
||||||
|
# CATÉGORIE = SystemCategory (pas SoftwareCategory) : c'est du disque/boot, il
|
||||||
|
# vit sur le hub Résumé à côté de « Destination de l'installation », pas dans
|
||||||
|
# « Logiciel ».
|
||||||
|
#
|
||||||
|
# CE QU'IL FAIT : coche « Dual-boot Windows gaming », choix du disque à dédier à
|
||||||
|
# Windows, taille NTFS (défaut 200 GiO, min 100, max = taille du disque), chemin
|
||||||
|
# de l'ISO Windows 11 fournie par l'utilisateur. Il n'exécute AUCUNE opération
|
||||||
|
# disque : son seul canal vers l'installation est /tmp/fws-windows, relu par le
|
||||||
|
# kickstart (%post --nochroot : carve + wimlib apply + injection gameboot ;
|
||||||
|
# %post chroot : swap/resume + Secure Boot). Exactement le modèle fws_desktop.
|
||||||
|
#
|
||||||
|
# ⚠ Chemin /usr/lib/python3.14/... couplé à la version de Python d'Arch (3.14 au
|
||||||
|
# build d'anaconda) — à mettre à jour au bump majeur de Python (= rebuild
|
||||||
|
# anaconda de toute façon). SystemCategory / internes GUI (anaconda 45.8) à
|
||||||
|
# re-vérifier à chaque bump, comme l'avertit l'en-tête de fws_recap.py.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import gi
|
||||||
|
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
from gi.repository import Gtk
|
||||||
|
|
||||||
|
from pyanaconda.anaconda_loggers import get_module_logger
|
||||||
|
from pyanaconda.core import util
|
||||||
|
from pyanaconda.core.i18n import CN_, _
|
||||||
|
from pyanaconda.ui.categories.system import SystemCategory
|
||||||
|
from pyanaconda.ui.gui.spokes import NormalSpoke
|
||||||
|
|
||||||
|
log = get_module_logger(__name__)
|
||||||
|
|
||||||
|
__all__ = ["FWSWindowsSpoke"]
|
||||||
|
|
||||||
|
# Contrat de fichier relu par le kickstart (clé=valeur, une par ligne).
|
||||||
|
_STATE_FILE = "/tmp/fws-windows"
|
||||||
|
|
||||||
|
_DEFAULT_SIZE_GIB = 200
|
||||||
|
_MIN_SIZE_GIB = 100
|
||||||
|
# Marge à laisser sur le disque en fin de partition NTFS (ESP Windows + MSR).
|
||||||
|
_TAIL_RESERVE_GIB = 3
|
||||||
|
|
||||||
|
|
||||||
|
def _capture(cmd, args):
|
||||||
|
try:
|
||||||
|
return util.execWithCapture(cmd, args) or ""
|
||||||
|
except (OSError, RuntimeError):
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def _list_disks():
|
||||||
|
"""[(devnode, libellé, taille_gib)] pour chaque disque physique (lsblk)."""
|
||||||
|
disks = []
|
||||||
|
for line in _capture("lsblk", ["-dpno", "NAME,SIZE,MODEL,TYPE"]).splitlines():
|
||||||
|
parts = line.split()
|
||||||
|
# NAME SIZE [MODEL les mots du milieu] TYPE ; TYPE = dernier champ.
|
||||||
|
if len(parts) < 2 or parts[-1] != "disk":
|
||||||
|
continue
|
||||||
|
name = parts[0]
|
||||||
|
size = parts[1]
|
||||||
|
model = " ".join(parts[2:-1]) or "?"
|
||||||
|
label = f"{name} — {size} {model}"
|
||||||
|
disks.append((name, label, _disk_size_gib(name)))
|
||||||
|
return disks
|
||||||
|
|
||||||
|
|
||||||
|
def _disk_size_gib(devnode):
|
||||||
|
"""Taille du disque en GiO entiers (0 si inconnue)."""
|
||||||
|
raw = _capture("lsblk", ["-dbno", "SIZE", devnode]).strip().split("\n")[0]
|
||||||
|
try:
|
||||||
|
return int(raw) // (1024 ** 3)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def _disk_nonempty(devnode):
|
||||||
|
"""True si le disque porte des partitions (donc des données à écraser)."""
|
||||||
|
return "part" in _capture("lsblk", ["-rno", "TYPE", devnode]).split()
|
||||||
|
|
||||||
|
|
||||||
|
def _hib_reserve_gib():
|
||||||
|
"""Réserve de queue = swap d'hibernation (RAM+VRAM+marge) + ESP/MSR, ALIGNÉE
|
||||||
|
sur fws-windows-deploy (qui exige size_gib <= disque − swap − 4). Sans ça, le
|
||||||
|
GUI proposerait une taille NTFS que le déployeur refuserait (ou qui déborde)."""
|
||||||
|
mem = 0
|
||||||
|
try:
|
||||||
|
with open("/proc/meminfo") as fh:
|
||||||
|
for line in fh:
|
||||||
|
if line.startswith("MemTotal"):
|
||||||
|
mem = int(line.split()[1]) // (1024 * 1024)
|
||||||
|
break
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
vram = 0
|
||||||
|
for tok in _capture(
|
||||||
|
"nvidia-smi",
|
||||||
|
["--query-gpu=memory.total", "--format=csv,noheader,nounits"]).split():
|
||||||
|
if tok.isdigit():
|
||||||
|
vram += int(tok) // 1024
|
||||||
|
swap = max(96, mem + vram + 4)
|
||||||
|
return swap + 6 # + ESP (1 GiO) + MSR + marge
|
||||||
|
|
||||||
|
|
||||||
|
def _intel_vmd_present():
|
||||||
|
"""True si un contrôleur Intel VMD (Volume Management Device) est présent →
|
||||||
|
le Windows déployé aura besoin du pilote iaStorVD pour voir/booter les NVMe.
|
||||||
|
Sur AMD ou machine sans VMD : False (case décochée, no-op)."""
|
||||||
|
return "volume management device" in _capture("lspci", ["-nn"]).lower()
|
||||||
|
|
||||||
|
|
||||||
|
class FWSWindowsSpoke(NormalSpoke):
|
||||||
|
"""Dual-boot Windows gaming : écrit /tmp/fws-windows (enabled/disk/size/iso)."""
|
||||||
|
|
||||||
|
builderObjects = ["adj_size", "fwsWindowsWindow"]
|
||||||
|
mainWidgetName = "fwsWindowsWindow"
|
||||||
|
uiFile = "spokes/fws_windows.glade"
|
||||||
|
category = SystemCategory
|
||||||
|
icon = "applications-games-symbolic"
|
||||||
|
title = CN_("GUI|Spoke", "_Dual-boot Windows")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_screen_id():
|
||||||
|
return "fws-windows-dualboot"
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
NormalSpoke.__init__(self, *args)
|
||||||
|
# État courant (persisté dans /tmp/fws-windows par apply()).
|
||||||
|
self._enabled = False
|
||||||
|
self._disk = ""
|
||||||
|
self._size = _DEFAULT_SIZE_GIB
|
||||||
|
self._iso = ""
|
||||||
|
self._uefi = os.path.exists("/sys/firmware/efi")
|
||||||
|
self._disks = []
|
||||||
|
# Réserve de queue alignée sur le swap d'hibernation du déployeur [M7].
|
||||||
|
self._reserve = _hib_reserve_gib()
|
||||||
|
# Support Intel VMD/RST : auto-coché si un contrôleur VMD est détecté.
|
||||||
|
self._vmd = _intel_vmd_present()
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ cycle
|
||||||
|
def initialize(self):
|
||||||
|
NormalSpoke.initialize(self)
|
||||||
|
self._chk = self.builder.get_object("chk_enable")
|
||||||
|
self._box = self.builder.get_object("box_options")
|
||||||
|
self._combo = self.builder.get_object("combo_disk")
|
||||||
|
self._adj = self.builder.get_object("adj_size")
|
||||||
|
self._spin = self.builder.get_object("spin_size")
|
||||||
|
self._entry = self.builder.get_object("entry_iso")
|
||||||
|
self._btn = self.builder.get_object("btn_iso")
|
||||||
|
self._warn = self.builder.get_object("iso_warn")
|
||||||
|
self._note = self.builder.get_object("note_platform")
|
||||||
|
self._vmd_chk = self.builder.get_object("chk_vmd")
|
||||||
|
|
||||||
|
# Peuple le combo des disques (une fois).
|
||||||
|
self._disks = _list_disks()
|
||||||
|
for name, label, _gib in self._disks:
|
||||||
|
self._combo.append(name, label)
|
||||||
|
|
||||||
|
# Reprend un choix déjà écrit (revisite du spoke).
|
||||||
|
self._load_state()
|
||||||
|
|
||||||
|
# Câblage des signaux (pas de handler dans le .glade, comme fws_recap).
|
||||||
|
self._chk.connect("toggled", self._on_toggle)
|
||||||
|
self._combo.connect("changed", self._on_disk_changed)
|
||||||
|
self._entry.connect("changed", self._on_iso_changed)
|
||||||
|
self._btn.connect("clicked", self._on_browse)
|
||||||
|
|
||||||
|
# Sur BIOS (non-UEFI), le dual-boot est impossible : on grise tout.
|
||||||
|
if not self._uefi:
|
||||||
|
self._enabled = False
|
||||||
|
self._chk.set_sensitive(False)
|
||||||
|
self._note.set_visible(True)
|
||||||
|
|
||||||
|
def refresh(self):
|
||||||
|
# Reflète l'état courant dans les widgets.
|
||||||
|
self._chk.set_active(self._enabled)
|
||||||
|
if self._disk:
|
||||||
|
self._combo.set_active_id(self._disk)
|
||||||
|
# AUCUNE présélection par défaut : sans choix explicite de l'utilisateur,
|
||||||
|
# `completed` refuse (il exige `disk`) → jamais d'effacement « par
|
||||||
|
# accident » d'un disque en cliquant simplement Suivant. (Bug corrigé :
|
||||||
|
# set_active(0) ciblait le 1er disque lsblk — souvent un disque de jeux.)
|
||||||
|
self._update_size_bounds()
|
||||||
|
self._spin.set_value(self._size)
|
||||||
|
self._entry.set_text(self._iso)
|
||||||
|
self._box.set_sensitive(self._enabled and self._uefi)
|
||||||
|
self._vmd_chk.set_active(self._vmd)
|
||||||
|
self._vmd_chk.set_sensitive(self._enabled and self._uefi)
|
||||||
|
self._validate()
|
||||||
|
|
||||||
|
def apply(self):
|
||||||
|
# Relit les widgets et persiste pour le kickstart.
|
||||||
|
self._enabled = self._chk.get_active() and self._uefi
|
||||||
|
self._disk = self._combo.get_active_id() or ""
|
||||||
|
self._size = int(self._spin.get_value())
|
||||||
|
self._iso = self._entry.get_text().strip()
|
||||||
|
self._vmd = self._vmd_chk.get_active()
|
||||||
|
self._save_state()
|
||||||
|
|
||||||
|
# --------------------------------------------------------------- handlers
|
||||||
|
def _on_toggle(self, _btn):
|
||||||
|
active = self._chk.get_active()
|
||||||
|
self._box.set_sensitive(active)
|
||||||
|
self._vmd_chk.set_sensitive(active)
|
||||||
|
self._validate()
|
||||||
|
|
||||||
|
def _on_disk_changed(self, _combo):
|
||||||
|
self._update_size_bounds()
|
||||||
|
self._validate()
|
||||||
|
|
||||||
|
def _on_iso_changed(self, _entry):
|
||||||
|
self._validate()
|
||||||
|
|
||||||
|
def _on_browse(self, _btn):
|
||||||
|
dialog = Gtk.FileChooserNative.new(
|
||||||
|
_("Select the Windows 11 ISO"), self.window,
|
||||||
|
Gtk.FileChooserAction.OPEN, _("_Select"), _("_Cancel"))
|
||||||
|
flt = Gtk.FileFilter()
|
||||||
|
flt.set_name(_("ISO images"))
|
||||||
|
flt.add_pattern("*.iso")
|
||||||
|
flt.add_pattern("*.ISO")
|
||||||
|
dialog.add_filter(flt)
|
||||||
|
# Démarrer sur les médias amovibles montés (clé USB avec l'ISO).
|
||||||
|
for start in ("/run/media", "/mnt", os.path.expanduser("~")):
|
||||||
|
if os.path.isdir(start):
|
||||||
|
dialog.set_current_folder(start)
|
||||||
|
break
|
||||||
|
if dialog.run() == Gtk.ResponseType.ACCEPT:
|
||||||
|
path = dialog.get_filename()
|
||||||
|
if path:
|
||||||
|
self._entry.set_text(path)
|
||||||
|
dialog.destroy()
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------- helpers
|
||||||
|
def _update_size_bounds(self):
|
||||||
|
"""Borne haute du spin = taille du disque choisi − réserve ESP/MSR."""
|
||||||
|
name = self._combo.get_active_id()
|
||||||
|
gib = 0
|
||||||
|
for dn, _label, dgib in self._disks:
|
||||||
|
if dn == name:
|
||||||
|
gib = dgib
|
||||||
|
break
|
||||||
|
upper = max(_MIN_SIZE_GIB, gib - self._reserve) if gib else 2000
|
||||||
|
self._adj.set_lower(_MIN_SIZE_GIB)
|
||||||
|
self._adj.set_upper(upper)
|
||||||
|
if self._spin.get_value() > upper:
|
||||||
|
self._spin.set_value(upper)
|
||||||
|
|
||||||
|
def _validate(self):
|
||||||
|
"""Message inline (jamais bloquant) : reflète la validité des choix."""
|
||||||
|
if not self._chk.get_active():
|
||||||
|
self._warn.set_text("")
|
||||||
|
return
|
||||||
|
iso = self._entry.get_text().strip()
|
||||||
|
msgs = []
|
||||||
|
if not self._combo.get_active_id():
|
||||||
|
msgs.append(_("Select the disk to dedicate to Windows."))
|
||||||
|
if not iso:
|
||||||
|
msgs.append(_("Provide the path to your Windows 11 ISO."))
|
||||||
|
elif not os.path.isfile(iso):
|
||||||
|
msgs.append(_("ISO not found: %s") % iso)
|
||||||
|
if msgs:
|
||||||
|
self._warn.set_text("⚠ " + " ".join(msgs))
|
||||||
|
else:
|
||||||
|
disk = self._combo.get_active_id()
|
||||||
|
extra = _(" — ⚠ THIS DISK CONTAINS DATA") if _disk_nonempty(disk) else ""
|
||||||
|
self._warn.set_text(
|
||||||
|
(_("⚠ The whole disk %s will be ERASED and dedicated to Windows.")
|
||||||
|
% disk) + extra)
|
||||||
|
|
||||||
|
def _load_state(self):
|
||||||
|
try:
|
||||||
|
with open(_STATE_FILE) as fh:
|
||||||
|
data = fh.read()
|
||||||
|
except OSError:
|
||||||
|
return
|
||||||
|
for line in data.splitlines():
|
||||||
|
if "=" not in line:
|
||||||
|
continue
|
||||||
|
key, _, val = line.partition("=")
|
||||||
|
key = key.strip()
|
||||||
|
val = val.strip()
|
||||||
|
if key == "enabled":
|
||||||
|
self._enabled = val == "1"
|
||||||
|
elif key == "disk":
|
||||||
|
self._disk = val
|
||||||
|
elif key == "size_gib" and val.isdigit():
|
||||||
|
self._size = int(val)
|
||||||
|
elif key == "iso":
|
||||||
|
self._iso = val
|
||||||
|
elif key == "intel_vmd":
|
||||||
|
self._vmd = val == "1"
|
||||||
|
|
||||||
|
def _save_state(self):
|
||||||
|
lines = [
|
||||||
|
"# Écrit par le spoke FWS « Dual-boot Windows ». Relu par le kickstart.",
|
||||||
|
"enabled=%d" % (1 if self._enabled else 0),
|
||||||
|
"disk=%s" % self._disk,
|
||||||
|
"size_gib=%d" % self._size,
|
||||||
|
"iso=%s" % self._iso,
|
||||||
|
"intel_vmd=%d" % (1 if self._vmd else 0),
|
||||||
|
]
|
||||||
|
try:
|
||||||
|
with open(_STATE_FILE, "w") as fh:
|
||||||
|
fh.write("\n".join(lines) + "\n")
|
||||||
|
except OSError as exc:
|
||||||
|
log.warning("FWS windows: écriture %s impossible : %s",
|
||||||
|
_STATE_FILE, exc)
|
||||||
|
|
||||||
|
# -------------------------------------------------------------- propriétés
|
||||||
|
@property
|
||||||
|
def status(self):
|
||||||
|
if not self._uefi:
|
||||||
|
return _("Unavailable (requires UEFI)")
|
||||||
|
if not self._enabled:
|
||||||
|
return _("Disabled (FWS only)")
|
||||||
|
iso = os.path.basename(self._iso) if self._iso else _("no ISO")
|
||||||
|
return _("Windows on %(disk)s — %(size)d GiB — %(iso)s") % {
|
||||||
|
"disk": self._disk or "?", "size": self._size, "iso": iso}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def completed(self):
|
||||||
|
# Désactivé = choix valide → jamais bloquant. Activé = exige disque +
|
||||||
|
# ISO lisible (sinon l'écran passe « orange » : l'utilisateur voit qu'il
|
||||||
|
# manque quelque chose, mais l'install FWS reste possible).
|
||||||
|
if not self._enabled:
|
||||||
|
return True
|
||||||
|
return bool(self._disk) and bool(self._iso) and os.path.isfile(self._iso)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mandatory(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ready(self):
|
||||||
|
return True
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Generated with glade 3.18.3 -->
|
||||||
|
<interface>
|
||||||
|
<!-- FWS : copie de summary.glade d'anaconda 45.8 SANS le libellé
|
||||||
|
« We won't touch your disks… » du bas du hub. Livré ICI (chemin neutre)
|
||||||
|
car mkarchiso pose l'airootfs AVANT pacstrap : au chemin du paquet,
|
||||||
|
pacman refuserait d'installer anaconda (« exists in filesystem »).
|
||||||
|
C'est le .xinitrc du live qui le copie sur
|
||||||
|
/usr/share/anaconda/ui/hubs/summary.glade au boot. -->
|
||||||
|
<requires lib="gtk+" version="3.0"/>
|
||||||
|
<requires lib="AnacondaWidgets" version="3.0"/>
|
||||||
|
<object class="AnacondaHubWindow" id="summaryWindow">
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="window_name" translatable="yes">INSTALLATION SUMMARY</property>
|
||||||
|
<property name="quit_button">quitButton</property>
|
||||||
|
<property name="continue_button">continueButton</property>
|
||||||
|
<child internal-child="main_box">
|
||||||
|
<object class="GtkBox" id="AnacondaHubWindow-main_box1">
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child internal-child="nav_box">
|
||||||
|
<object class="GtkEventBox" id="AnacondaHubWindow-nav_box1">
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child internal-child="nav_area">
|
||||||
|
<object class="GtkGrid" id="AnacondaHubWindow-nav_area1">
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child internal-child="alignment">
|
||||||
|
<object class="GtkAlignment" id="AnacondaHubWindow-alignment1">
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="yalign">0</property>
|
||||||
|
<property name="left_padding">12</property>
|
||||||
|
<property name="right_padding">6</property>
|
||||||
|
<child internal-child="action_area">
|
||||||
|
<object class="GtkBox" id="summaryWindow-actionArea">
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="spacing">6</property>
|
||||||
|
<child internal-child="scrolled_window">
|
||||||
|
<object class="GtkScrolledWindow" id="summaryWindow-scroll">
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="is_focus">True</property>
|
||||||
|
<property name="hscrollbar_policy">never</property>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButtonBox" id="buttonbox1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="margin_left">6</property>
|
||||||
|
<property name="margin_right">6</property>
|
||||||
|
<property name="spacing">12</property>
|
||||||
|
<property name="layout_style">end</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="quitButton">
|
||||||
|
<property name="label" translatable="yes" context="GUI|Summary">_Quit</property>
|
||||||
|
<property name="use_action_appearance">False</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="continueButton">
|
||||||
|
<property name="label" translatable="yes" context="GUI|Summary">_Begin Installation</property>
|
||||||
|
<property name="use_action_appearance">False</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="sensitive">False</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<style>
|
||||||
|
<class name="suggested-action"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="pack_type">end</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child internal-child="accessible">
|
||||||
|
<object class="AtkObject" id="summaryWindow-atkobject">
|
||||||
|
<property name="AtkObject::accessible-name" translatable="yes">INSTALLATION SUMMARY</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</interface>
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- FWS — onglet « Desktop Environment » (choix du bureau à installer). -->
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk+" version="3.6"/>
|
||||||
|
<requires lib="AnacondaWidgets" version="1.0"/>
|
||||||
|
<object class="AnacondaSpokeWindow" id="fwsDesktopWindow">
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="vexpand">True</property>
|
||||||
|
<property name="window-name" translatable="yes">DESKTOP ENVIRONMENT</property>
|
||||||
|
<signal name="button-clicked" handler="on_back_clicked" swapped="no"/>
|
||||||
|
<child internal-child="main_box">
|
||||||
|
<object class="GtkBox" id="AnacondaSpokeWindow-main_box1">
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="spacing">6</property>
|
||||||
|
<child internal-child="nav_box">
|
||||||
|
<object class="GtkEventBox" id="AnacondaSpokeWindow-nav_box1">
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<child internal-child="nav_area">
|
||||||
|
<object class="GtkGrid" id="AnacondaSpokeWindow-nav_area1">
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="margin-left">6</property>
|
||||||
|
<property name="margin-right">6</property>
|
||||||
|
<property name="margin-top">6</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child internal-child="alignment">
|
||||||
|
<object class="GtkAlignment" id="AnacondaSpokeWindow-alignment1">
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="margin-top">12</property>
|
||||||
|
<property name="yalign">0</property>
|
||||||
|
<property name="xscale">0</property>
|
||||||
|
<property name="yscale">0</property>
|
||||||
|
<property name="bottom-padding">48</property>
|
||||||
|
<property name="left-padding">24</property>
|
||||||
|
<property name="right-padding">24</property>
|
||||||
|
<child internal-child="action_area">
|
||||||
|
<object class="GtkBox" id="AnacondaSpokeWindow-action_area1">
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="spacing">8</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="fws_desktop_desc">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="margin-bottom">12</property>
|
||||||
|
<property name="wrap">True</property>
|
||||||
|
<property name="max-width-chars">60</property>
|
||||||
|
<property name="label" translatable="yes">Choose the desktop environment to install. A web browser (Firefox) is installed automatically with any desktop.</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkRadioButton" id="radio_cli">
|
||||||
|
<property name="label" translatable="yes">Command line only (no desktop)</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">False</property>
|
||||||
|
<property name="active">True</property>
|
||||||
|
<property name="draw-indicator">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkRadioButton" id="radio_gnome">
|
||||||
|
<property name="label" translatable="yes">GNOME</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">False</property>
|
||||||
|
<property name="draw-indicator">True</property>
|
||||||
|
<property name="group">radio_cli</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkRadioButton" id="radio_kde">
|
||||||
|
<property name="label" translatable="yes">KDE Plasma</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">False</property>
|
||||||
|
<property name="draw-indicator">True</property>
|
||||||
|
<property name="group">radio_cli</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkRadioButton" id="radio_hyprland">
|
||||||
|
<property name="label" translatable="yes">Hyprland — Wayland (NOT VR compatible)</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">False</property>
|
||||||
|
<property name="draw-indicator">True</property>
|
||||||
|
<property name="group">radio_cli</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">4</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkRadioButton" id="radio_i3">
|
||||||
|
<property name="label" translatable="yes">i3 — X11 (VR compatible: SteamVR, HTC Vive, Valve Index…)</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">False</property>
|
||||||
|
<property name="draw-indicator">True</property>
|
||||||
|
<property name="group">radio_cli</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">5</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkRadioButton" id="radio_xfce">
|
||||||
|
<property name="label" translatable="yes">XFCE</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">False</property>
|
||||||
|
<property name="draw-indicator">True</property>
|
||||||
|
<property name="group">radio_cli</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">6</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child internal-child="accessible">
|
||||||
|
<object class="AtkObject" id="fwsDesktopWindow-atkobject">
|
||||||
|
<property name="AtkObject::accessible-name" translatable="yes">DESKTOP ENVIRONMENT</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</interface>
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- FWS — page « Récapitulatif avant installation » (cf. fws_recap.py).
|
||||||
|
Squelette calqué sur installation_progress.glade : AnacondaStandaloneWindow
|
||||||
|
avec ses enfants internes (nav_box/nav_area, alignment/action_area). -->
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk+" version="3.0"/>
|
||||||
|
<requires lib="AnacondaWidgets" version="3.0"/>
|
||||||
|
<object class="AnacondaStandaloneWindow" id="fwsRecapWindow">
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="window_name" translatable="yes">RÉCAPITULATIF AVANT INSTALLATION</property>
|
||||||
|
<child internal-child="main_box">
|
||||||
|
<object class="GtkBox" id="AnacondaStandaloneWindow-main_box1">
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child internal-child="nav_box">
|
||||||
|
<object class="GtkEventBox" id="AnacondaStandaloneWindow-nav_box1">
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child internal-child="nav_area">
|
||||||
|
<object class="GtkGrid" id="AnacondaStandaloneWindow-nav_area1">
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child internal-child="alignment">
|
||||||
|
<object class="GtkAlignment" id="AnacondaStandaloneWindow-alignment1">
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="yalign">0</property>
|
||||||
|
<property name="left_padding">12</property>
|
||||||
|
<property name="right_padding">6</property>
|
||||||
|
<child internal-child="action_area">
|
||||||
|
<object class="GtkBox" id="fwsRecapWindow-actionArea">
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="spacing">6</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkScrolledWindow" id="recapScroll">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="shadow_type">in</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkTextView" id="recapView">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="editable">False</property>
|
||||||
|
<property name="cursor_visible">False</property>
|
||||||
|
<property name="monospace">True</property>
|
||||||
|
<property name="left_margin">12</property>
|
||||||
|
<property name="right_margin">12</property>
|
||||||
|
<property name="top_margin">12</property>
|
||||||
|
<property name="bottom_margin">12</property>
|
||||||
|
<property name="wrap_mode">word-char</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</interface>
|
||||||
@@ -0,0 +1,290 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- FWS — onglet « Dual-boot Windows gaming » (case + disque + taille + ISO).
|
||||||
|
Calqué sur fws_desktop.glade (AnacondaSpokeWindow + ses enfants internes
|
||||||
|
nav_box/nav_area, alignment/action_area). Catégorie SYSTÈME côté .py :
|
||||||
|
l'écran vit à côté de « Destination de l'installation », pas dans « Logiciel ».
|
||||||
|
Les widgets sont pilotés/peuplés au runtime par fws_windows.py. -->
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk+" version="3.6"/>
|
||||||
|
<requires lib="AnacondaWidgets" version="1.0"/>
|
||||||
|
<!-- Bornes de la taille NTFS ; upper réévalué en Python selon le disque choisi. -->
|
||||||
|
<object class="GtkAdjustment" id="adj_size">
|
||||||
|
<property name="lower">100</property>
|
||||||
|
<property name="upper">2000</property>
|
||||||
|
<property name="value">200</property>
|
||||||
|
<property name="step-increment">10</property>
|
||||||
|
<property name="page-increment">50</property>
|
||||||
|
</object>
|
||||||
|
<object class="AnacondaSpokeWindow" id="fwsWindowsWindow">
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="vexpand">True</property>
|
||||||
|
<property name="window-name" translatable="yes">DUAL-BOOT WINDOWS</property>
|
||||||
|
<signal name="button-clicked" handler="on_back_clicked" swapped="no"/>
|
||||||
|
<child internal-child="main_box">
|
||||||
|
<object class="GtkBox" id="AnacondaSpokeWindow-main_box1">
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="spacing">6</property>
|
||||||
|
<child internal-child="nav_box">
|
||||||
|
<object class="GtkEventBox" id="AnacondaSpokeWindow-nav_box1">
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<child internal-child="nav_area">
|
||||||
|
<object class="GtkGrid" id="AnacondaSpokeWindow-nav_area1">
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="margin-left">6</property>
|
||||||
|
<property name="margin-right">6</property>
|
||||||
|
<property name="margin-top">6</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child internal-child="alignment">
|
||||||
|
<object class="GtkAlignment" id="AnacondaSpokeWindow-alignment1">
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="margin-top">12</property>
|
||||||
|
<property name="yalign">0</property>
|
||||||
|
<property name="xscale">0</property>
|
||||||
|
<property name="yscale">0</property>
|
||||||
|
<property name="bottom-padding">48</property>
|
||||||
|
<property name="left-padding">24</property>
|
||||||
|
<property name="right-padding">24</property>
|
||||||
|
<child internal-child="action_area">
|
||||||
|
<object class="GtkBox" id="AnacondaSpokeWindow-action_area1">
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="spacing">8</property>
|
||||||
|
|
||||||
|
<!-- Description -->
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="fws_windows_desc">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="margin-bottom">6</property>
|
||||||
|
<property name="wrap">True</property>
|
||||||
|
<property name="max-width-chars">64</property>
|
||||||
|
<property name="label" translatable="yes">Install a bare-metal Windows 11 alongside FWS to play kernel-anticheat games (Valorant, League of Legends) that refuse virtualization. FWS deploys Windows automatically from the ISO you provide. Requires UEFI + TPM 2.0.</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
<!-- Case maître : active/grise le bloc d'options ci-dessous -->
|
||||||
|
<child>
|
||||||
|
<object class="GtkCheckButton" id="chk_enable">
|
||||||
|
<property name="label" translatable="yes">Enable Windows gaming dual-boot</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">False</property>
|
||||||
|
<property name="draw-indicator">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
<!-- Bloc d'options (grisé tant que la case est décochée) -->
|
||||||
|
<child>
|
||||||
|
<object class="GtkGrid" id="box_options">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="sensitive">False</property>
|
||||||
|
<property name="margin-left">24</property>
|
||||||
|
<property name="margin-top">4</property>
|
||||||
|
<property name="row-spacing">8</property>
|
||||||
|
<property name="column-spacing">12</property>
|
||||||
|
|
||||||
|
<!-- Ligne 0 : disque -->
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="label_disk">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="label" translatable="yes">Disk dedicated to Windows:</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left-attach">0</property>
|
||||||
|
<property name="top-attach">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkComboBoxText" id="combo_disk">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left-attach">1</property>
|
||||||
|
<property name="top-attach">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
<!-- Ligne 1 : taille NTFS -->
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="label_size">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="label" translatable="yes">Windows partition size (GiB):</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left-attach">0</property>
|
||||||
|
<property name="top-attach">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSpinButton" id="spin_size">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="adjustment">adj_size</property>
|
||||||
|
<property name="numeric">True</property>
|
||||||
|
<property name="value">200</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left-attach">1</property>
|
||||||
|
<property name="top-attach">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
<!-- Ligne 2 : ISO (champ libre + bouton Parcourir) -->
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="label_iso">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="label" translatable="yes">Windows 11 ISO image:</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left-attach">0</property>
|
||||||
|
<property name="top-attach">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="box_iso">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="orientation">horizontal</property>
|
||||||
|
<property name="spacing">6</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkEntry" id="entry_iso">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="placeholder-text" translatable="yes">/run/media/…/Win11_25H2.iso</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="btn_iso">
|
||||||
|
<property name="label" translatable="yes">Browse…</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left-attach">1</property>
|
||||||
|
<property name="top-attach">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
<!-- Ligne 3 : avertissement de validation (rempli en Python) -->
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="iso_warn">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="wrap">True</property>
|
||||||
|
<property name="max-width-chars">64</property>
|
||||||
|
<property name="label"></property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left-attach">0</property>
|
||||||
|
<property name="top-attach">3</property>
|
||||||
|
<property name="width">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
<!-- Support Intel VMD/RST : auto-coché si un contrôleur Intel VMD
|
||||||
|
est détecté. Injecte le pilote iaStorVD dans le Windows
|
||||||
|
déployé pour qu'il boote sur les machines Intel (BIOS en mode
|
||||||
|
VMD/RST). Aucun effet sur AMD. -->
|
||||||
|
<child>
|
||||||
|
<object class="GtkCheckButton" id="chk_vmd">
|
||||||
|
<property name="label" translatable="yes">Intel VMD/RST support (auto-detected — Intel machines only)</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="sensitive">False</property>
|
||||||
|
<property name="margin-left">24</property>
|
||||||
|
<property name="margin-top">4</property>
|
||||||
|
<property name="draw-indicator">True</property>
|
||||||
|
<property name="tooltip-text" translatable="yes">Injects the Intel VMD/RST driver into the deployed Windows so it boots on Intel systems with VMD enabled in the BIOS. Auto-detected; has no effect on AMD.</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
<!-- Note plate-forme (affichée par le .py si BIOS/non-UEFI) -->
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="note_platform">
|
||||||
|
<property name="visible">False</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="margin-top">8</property>
|
||||||
|
<property name="wrap">True</property>
|
||||||
|
<property name="max-width-chars">64</property>
|
||||||
|
<property name="label" translatable="yes">Dual-boot requires UEFI firmware (this machine booted in legacy BIOS mode). The option is disabled.</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">4</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child internal-child="accessible">
|
||||||
|
<object class="AtkObject" id="fwsWindowsWindow-atkobject">
|
||||||
|
<property name="AtkObject::accessible-name" translatable="yes">DUAL-BOOT WINDOWS</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</interface>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
title Arch Linux (%ARCH%, UEFI)
|
title FWS install medium (%ARCH%, UEFI)
|
||||||
linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux
|
sort-key 01
|
||||||
initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
|
linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux
|
||||||
options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID%
|
initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
|
||||||
|
options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% udev.event_timeout=45
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ zsh
|
|||||||
grml-zsh-config
|
grml-zsh-config
|
||||||
bash
|
bash
|
||||||
zenity
|
zenity
|
||||||
|
zram-generator
|
||||||
nano
|
nano
|
||||||
vim
|
vim
|
||||||
less
|
less
|
||||||
@@ -79,6 +80,7 @@ pacman-contrib
|
|||||||
dmidecode
|
dmidecode
|
||||||
usbutils
|
usbutils
|
||||||
pciutils
|
pciutils
|
||||||
|
firewalld
|
||||||
fastfetch
|
fastfetch
|
||||||
htop
|
htop
|
||||||
|
|
||||||
@@ -103,6 +105,8 @@ open-vm-tools
|
|||||||
|
|
||||||
# --- FWS (fws-local) ---
|
# --- FWS (fws-local) ---
|
||||||
winboat-bin
|
winboat-bin
|
||||||
|
# Gestionnaire D&D 5e (dés, personnages, grimoire, sessions, combat).
|
||||||
|
fws-dnd
|
||||||
|
|
||||||
# --- Installateur Anaconda (fws-local) ---
|
# --- Installateur Anaconda (fws-local) ---
|
||||||
# 'anaconda' tire automatiquement anaconda-widgets + toutes ses dépendances
|
# 'anaconda' tire automatiquement anaconda-widgets + toutes ses dépendances
|
||||||
@@ -117,3 +121,13 @@ python-crypt_r
|
|||||||
# Namespace GI 'OSTree-1.0' : pyanaconda/rescue.py importe le payload rpm_ostree
|
# Namespace GI 'OSTree-1.0' : pyanaconda/rescue.py importe le payload rpm_ostree
|
||||||
# (inconditionnellement, même si FWS utilise liveimg) → fourni par 'ostree'.
|
# (inconditionnellement, même si FWS utilise liveimg) → fourni par 'ostree'.
|
||||||
ostree
|
ostree
|
||||||
|
|
||||||
|
# --- Gameboot : dual-boot Windows gaming (déploiement offline + Secure Boot) ---
|
||||||
|
# Outils universels utilisés par fws-windows-deploy (%post, hors ligne) et
|
||||||
|
# fws-secureboot-setup. NB : PAS de 'nvidia' ici (conditionnel via fws-setup-hardware).
|
||||||
|
wimlib
|
||||||
|
hivex
|
||||||
|
sbctl
|
||||||
|
sbsigntools
|
||||||
|
mokutil
|
||||||
|
binutils
|
||||||
|
|||||||
@@ -1,8 +1,3 @@
|
|||||||
[fws-local]
|
|
||||||
SigLevel = Optional TrustAll
|
|
||||||
Server = file:///mnt/c/Users/Murasaki/Documents/FWS/fws/local-repo
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# /etc/pacman.conf
|
# /etc/pacman.conf
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -26,4 +26,19 @@ file_permissions=(
|
|||||||
["/usr/local/bin/choose-mirror"]="0:0:755"
|
["/usr/local/bin/choose-mirror"]="0:0:755"
|
||||||
["/usr/local/bin/Installation_guide"]="0:0:755"
|
["/usr/local/bin/Installation_guide"]="0:0:755"
|
||||||
["/usr/local/bin/livecd-sound"]="0:0:755"
|
["/usr/local/bin/livecd-sound"]="0:0:755"
|
||||||
|
["/usr/bin/load_policy"]="0:0:755"
|
||||||
|
["/usr/bin/setstatus"]="0:0:755"
|
||||||
|
["/usr/local/bin/fws-setup-hardware"]="0:0:755"
|
||||||
|
["/usr/local/bin/fws-keys"]="0:0:755"
|
||||||
|
["/usr/local/bin/fws-vr-setup"]="0:0:755"
|
||||||
|
["/usr/local/bin/fws-hello"]="0:0:755"
|
||||||
|
["/usr/local/bin/fws-update"]="0:0:755"
|
||||||
|
["/usr/local/bin/fws-updates-check"]="0:0:755"
|
||||||
|
["/usr/local/bin/fws-proton-ge"]="0:0:755"
|
||||||
|
["/usr/local/bin/fws-recovery"]="0:0:755"
|
||||||
|
["/usr/local/bin/fws-recovery-session"]="0:0:755"
|
||||||
|
["/usr/local/bin/fws-recovery-gui"]="0:0:755"
|
||||||
|
["/usr/local/bin/fws-powermenu"]="0:0:755"
|
||||||
|
["/usr/local/bin/fws-about"]="0:0:755"
|
||||||
|
["/etc/grub.d/42_fws-recovery"]="0:0:755"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
LABEL arch
|
|
||||||
MENU LABEL Arch Linux (%ARCH%, BIOS)
|
|
||||||
LINUX /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux
|
|
||||||
INITRD /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
|
|
||||||
APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID%
|
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
SERIAL 0 115200
|
DEFAULT select
|
||||||
UI menu.c32
|
|
||||||
MENU TITLE Arch Linux
|
|
||||||
MENU CLEAR
|
|
||||||
|
|
||||||
DEFAULT arch
|
LABEL select
|
||||||
TIMEOUT 30
|
COM32 whichsys.c32
|
||||||
|
APPEND -pxe- pxe -sys- sys -iso- sys
|
||||||
|
|
||||||
INCLUDE syslinux-linux.cfg
|
LABEL pxe
|
||||||
|
CONFIG archiso_pxe.cfg
|
||||||
|
|
||||||
|
LABEL sys
|
||||||
|
CONFIG archiso_sys.cfg
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# ============================================================
|
||||||
|
# Configuration de l'orchestrateur FWS « gameboot » (dual-boot hibernate-swap)
|
||||||
|
# Lu par /usr/local/bin/fws-gameboot. Voir docs/hibernate-swap-dualboot.md.
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
# Entrée de boot Windows (numéro Boot#### hex, ex. "0003").
|
||||||
|
# Vide = auto-détection de l'entrée « Windows Boot Manager » via efibootmgr.
|
||||||
|
FWS_WINDOWS_BOOTNUM=""
|
||||||
|
|
||||||
|
# Comportement si le préflight d'hibernation ÉCHOUE (ou si l'hibernation
|
||||||
|
# elle-même échoue) AVANT d'être entré dans Windows :
|
||||||
|
# reboot = on bascule quand même vers Windows par un REBOOT SIMPLE. La session
|
||||||
|
# FWS n'est pas préservée, mais on entre dans le jeu (c'est le "filet"
|
||||||
|
# demandé : ne jamais laisser l'utilisateur bloqué).
|
||||||
|
# abort = on annule, on efface BootNext, on RESTE sur FWS avec une erreur
|
||||||
|
# claire (choix prudent : jamais de bascule surprise).
|
||||||
|
FWS_HIBERNATE_FALLBACK="reboot"
|
||||||
|
|
||||||
|
# Marge de sécurité (Mo) ajoutée à (RAM + VRAM en usage) pour vérifier que
|
||||||
|
# l'image d'hibernation tiendra dans le swap. La VRAM des GPU récents (ex. 32 Go
|
||||||
|
# sur une RTX 5090) est recopiée dans l'image → le swap doit couvrir RAM+VRAM.
|
||||||
|
FWS_SWAP_MARGIN_MB=2048
|
||||||
|
|
||||||
|
# Jeu lancé par défaut si « fws-gameboot to-windows » est appelé sans --game.
|
||||||
|
# L'identifiant doit exister dans games.json côté Windows (valorant, lol, …).
|
||||||
|
FWS_DEFAULT_GAME="valorant"
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# ============================================================================
|
||||||
|
# FWS — entrée « Réparer FWS » dans le menu GRUB du SYSTÈME INSTALLÉ
|
||||||
|
# (l'équivalent de l'environnement de récupération de Windows).
|
||||||
|
#
|
||||||
|
# Ce script est exécuté par grub-mkconfig (à l'installation via le %post du
|
||||||
|
# kickstart, puis à chaque fws-recovery « Réinstaller GRUB »). Il démarre le
|
||||||
|
# MÊME système sur fws-recovery.target (+ marqueur fws.recovery=1) :
|
||||||
|
# fws-recovery-boot.service prend alors tty1 et lance l'application de
|
||||||
|
# réparation (GUI via startx, repli console).
|
||||||
|
#
|
||||||
|
# Sur le live, ce fichier est inerte (grub-mkconfig n'y tourne jamais).
|
||||||
|
# ============================================================================
|
||||||
|
set -e
|
||||||
|
. /usr/share/grub/grub-mkconfig_lib
|
||||||
|
|
||||||
|
kernel=/boot/vmlinuz-linux
|
||||||
|
# L'initramfs FALLBACK (sans autodetect) : c'est l'entrée de secours, elle
|
||||||
|
# doit démarrer même si le matériel a changé depuis le dernier mkinitcpio.
|
||||||
|
initrd=/boot/initramfs-linux-fallback.img
|
||||||
|
[ -f "$initrd" ] || initrd=/boot/initramfs-linux.img
|
||||||
|
[ -f "$kernel" ] && [ -f "$initrd" ] || exit 0
|
||||||
|
|
||||||
|
# Même logique racine que 10_linux : UUID sauf refus explicite.
|
||||||
|
if [ -n "${GRUB_DEVICE_UUID:-}" ] && [ "x${GRUB_DISABLE_LINUX_UUID:-}" != xtrue ]; then
|
||||||
|
rootarg="root=UUID=$GRUB_DEVICE_UUID"
|
||||||
|
else
|
||||||
|
rootarg="root=$GRUB_DEVICE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rel_kernel="$(make_system_path_relative_to_its_root "$kernel")"
|
||||||
|
rel_initrd="$(make_system_path_relative_to_its_root "$initrd")"
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
menuentry 'Réparer FWS (récupération du système)' --class recovery {
|
||||||
|
$(prepare_grub_to_access_device "${GRUB_DEVICE_BOOT}" | grub_add_tab)
|
||||||
|
linux $rel_kernel $rootarg rw ${GRUB_CMDLINE_LINUX:-} systemd.unit=fws-recovery.target fws.recovery=1
|
||||||
|
initrd $rel_initrd
|
||||||
|
}
|
||||||
|
EOF
|
||||||
@@ -1 +1 @@
|
|||||||
archiso
|
fws
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
\S (\l)
|
||||||
|
|
||||||
@@ -5,5 +5,14 @@
|
|||||||
# On utilise donc un autre nom.
|
# On utilise donc un autre nom.
|
||||||
_fastfetch_rc=$?
|
_fastfetch_rc=$?
|
||||||
if [ "${_fastfetch_rc}" -eq 0 ]; then
|
if [ "${_fastfetch_rc}" -eq 0 ]; then
|
||||||
fastfetch
|
# Logo FWS maison (4 carrés + Tux) au lieu du logo Arch détecté par défaut.
|
||||||
|
fastfetch \
|
||||||
|
--logo-type file \
|
||||||
|
--logo /usr/local/share/fws/logo.txt \
|
||||||
|
--logo-color-1 '38;2;30;160;100' \
|
||||||
|
--logo-color-2 '38;2;45;125;210' \
|
||||||
|
--logo-color-3 '38;2;240;180;40' \
|
||||||
|
--logo-color-4 '38;2;25;85;145' \
|
||||||
|
--logo-color-5 '38;2;28;28;34' \
|
||||||
|
--logo-color-6 '38;2;245;165;35'
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
/usr/lib/systemd/system/systemd-timesyncd.service
|
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
/usr/lib/systemd/system/systemd-timesyncd.service
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=FWS — réasserte BootOrder=FWS et le marqueur de retour Windows (gameboot)
|
||||||
|
Documentation=file:///usr/share/doc/fws/hibernate-swap-dualboot.md
|
||||||
|
# Jamais sur le live, uniquement sur un système installé UEFI.
|
||||||
|
ConditionPathExists=!/run/archiso
|
||||||
|
ConditionPathExists=/sys/firmware/efi
|
||||||
|
After=local-fs.target
|
||||||
|
# Ne pas retarder le boot : purement best-effort.
|
||||||
|
DefaultDependencies=no
|
||||||
|
Before=multi-user.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/local/bin/fws-gameboot-bootfix
|
||||||
|
# Ne jamais faire échouer le boot si efivars indisponible.
|
||||||
|
SuccessExitStatus=0 1
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=FWS - Reparation du systeme (session de secours sur tty1)
|
||||||
|
# Demarre UNIQUEMENT via fws-recovery.target (entree GRUB « Reparer FWS » du
|
||||||
|
# systeme installe : systemd.unit=fws-recovery.target). PAS de [Install] :
|
||||||
|
# enrole dans aucun boot normal, donc son Conflicts=getty@tty1 ne peut jamais
|
||||||
|
# casser le login tty1 d'un boot ordinaire.
|
||||||
|
# Sur le live, c'est /root/.zprofile (autologin tty1) qui gere la reparation.
|
||||||
|
ConditionKernelCommandLine=fws.recovery=1
|
||||||
|
ConditionPathExists=!/run/archiso
|
||||||
|
Conflicts=getty@tty1.service display-manager.service
|
||||||
|
After=systemd-user-sessions.service getty@tty1.service display-manager.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=idle
|
||||||
|
# GUI via startx (openbox + fws-recovery-gui) ; si X ne demarre pas (pilote
|
||||||
|
# video casse — un cas de reparation typique), repli sur l'assistant CONSOLE.
|
||||||
|
ExecStart=/bin/sh -c '/usr/bin/startx /usr/local/bin/fws-recovery-session -- vt1 || exec /usr/local/bin/fws-recovery'
|
||||||
|
StandardInput=tty
|
||||||
|
StandardOutput=tty
|
||||||
|
StandardError=tty
|
||||||
|
TTYPath=/dev/tty1
|
||||||
|
TTYReset=yes
|
||||||
|
TTYVHangup=yes
|
||||||
|
Restart=no
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=FWS - Cible de reparation du systeme
|
||||||
|
# Demandee par l'entree GRUB « Reparer FWS » du systeme installe
|
||||||
|
# (kernel : systemd.unit=fws-recovery.target fws.recovery=1).
|
||||||
|
# Un multi-user complet (reseau pour pacman -Syu) + la session de reparation ;
|
||||||
|
# le Conflicts du service libere tty1 (son start est REQUIS ici, il gagne
|
||||||
|
# contre le Wants de getty@tty1).
|
||||||
|
Requires=multi-user.target fws-recovery-boot.service
|
||||||
|
After=multi-user.target fws-recovery-boot.service
|
||||||
|
AllowIsolate=yes
|
||||||
-1
@@ -1 +0,0 @@
|
|||||||
/usr/lib/systemd/system/systemd-time-wait-sync.service
|
|
||||||
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
/usr/lib/systemd/system/systemd-time-wait-sync.service
|
||||||
-1
@@ -1 +0,0 @@
|
|||||||
/usr/lib/systemd/system/systemd-timesyncd.service
|
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
/usr/lib/systemd/system/systemd-timesyncd.service
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# FWS — swap compressé en RAM (zram), activé automatiquement par
|
||||||
|
# zram-generator (paquet dans packages.x86_64, aucun service à activer).
|
||||||
|
#
|
||||||
|
# Le LIVE n'a AUCUN swap : sur une machine/VM à 2 Go, Anaconda + le rootfs
|
||||||
|
# squashfs saturent la mémoire → livelock, écran figé pendant « Préparation
|
||||||
|
# de l'installateur ». Le zram (compression ~3:1 sur les tas Python) donne
|
||||||
|
# la marge nécessaire. Copié tel quel sur le SYSTÈME INSTALLÉ par le payload
|
||||||
|
# LiveOS → FWS garde ce filet de sécurité après installation (esprit
|
||||||
|
# « remplaçant de Windows » sur des machines modestes).
|
||||||
|
[zram0]
|
||||||
|
zram-size = min(ram, 4096)
|
||||||
|
compression-algorithm = zstd
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=Bienvenue sur FWS
|
||||||
|
Comment=Fenêtre de bienvenue au premier démarrage
|
||||||
|
Exec=fws-hello --autostart
|
||||||
|
Icon=help-about
|
||||||
|
Terminal=false
|
||||||
|
Categories=System;
|
||||||
|
X-GNOME-Autostart-enabled=true
|
||||||
+185
@@ -0,0 +1,185 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# FWS — fenêtre « À propos » : deux onglets, « Le projet FWS » (ce qu'est FWS,
|
||||||
|
# version, liens) et « Système » (matériel + logiciel détectés). Remplace le
|
||||||
|
# « About Xfce » de libxfce4ui par une page de marque FWS. GTK3 (python-gobject
|
||||||
|
# installé avec le bureau).
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import gi
|
||||||
|
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
gi.require_version("Gdk", "3.0") # sinon gi charge Gdk 4.0 (conflit avec Gtk3)
|
||||||
|
from gi.repository import Gdk, GLib, Gtk # noqa: E402
|
||||||
|
|
||||||
|
GLib.set_prgname("fws-about")
|
||||||
|
|
||||||
|
|
||||||
|
def _osrelease():
|
||||||
|
data = {}
|
||||||
|
for path in ("/etc/os-release", "/usr/lib/os-release"):
|
||||||
|
try:
|
||||||
|
with open(path) as fh:
|
||||||
|
for line in fh:
|
||||||
|
m = re.match(r'^([A-Z_]+)=(.*)$', line.strip())
|
||||||
|
if m:
|
||||||
|
data[m.group(1)] = m.group(2).strip('"')
|
||||||
|
break
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def _cmd(*argv):
|
||||||
|
try:
|
||||||
|
return subprocess.check_output(argv, text=True,
|
||||||
|
stderr=subprocess.DEVNULL).strip()
|
||||||
|
except (OSError, subprocess.CalledProcessError):
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def _cpu():
|
||||||
|
try:
|
||||||
|
with open("/proc/cpuinfo") as fh:
|
||||||
|
for line in fh:
|
||||||
|
if line.startswith("model name"):
|
||||||
|
return line.split(":", 1)[1].strip()
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
return "inconnu"
|
||||||
|
|
||||||
|
|
||||||
|
def _mem():
|
||||||
|
try:
|
||||||
|
with open("/proc/meminfo") as fh:
|
||||||
|
for line in fh:
|
||||||
|
if line.startswith("MemTotal"):
|
||||||
|
kib = int(line.split()[1])
|
||||||
|
return f"{kib / 1048576:.1f} Gio"
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
return "inconnu"
|
||||||
|
|
||||||
|
|
||||||
|
def _gpus():
|
||||||
|
out = []
|
||||||
|
for line in _cmd("lspci").splitlines():
|
||||||
|
low = line.lower()
|
||||||
|
if "vga" in low or "3d" in low or "display" in low:
|
||||||
|
out.append(line.split(":", 2)[-1].strip())
|
||||||
|
return out or ["inconnu"]
|
||||||
|
|
||||||
|
|
||||||
|
def _desktop():
|
||||||
|
if os.environ.get("HYPRLAND_INSTANCE_SIGNATURE"):
|
||||||
|
return "Hyprland"
|
||||||
|
env = os.environ.get("XDG_CURRENT_DESKTOP") \
|
||||||
|
or os.environ.get("DESKTOP_SESSION") or ""
|
||||||
|
return env or "inconnu"
|
||||||
|
|
||||||
|
|
||||||
|
def _session_type():
|
||||||
|
t = os.environ.get("XDG_SESSION_TYPE", "")
|
||||||
|
return {"wayland": "Wayland", "x11": "X11"}.get(t, t or "inconnu")
|
||||||
|
|
||||||
|
|
||||||
|
def _disks():
|
||||||
|
out = []
|
||||||
|
for line in _cmd("lsblk", "-dno", "NAME,SIZE,MODEL,TYPE").splitlines():
|
||||||
|
f = line.split()
|
||||||
|
if f and f[-1] == "disk":
|
||||||
|
out.append(" ".join(f[:-1]))
|
||||||
|
return out or ["inconnu"]
|
||||||
|
|
||||||
|
|
||||||
|
def _grid(rows):
|
||||||
|
grid = Gtk.Grid(column_spacing=18, row_spacing=8, margin=18)
|
||||||
|
for i, (key, val) in enumerate(rows):
|
||||||
|
k = Gtk.Label(xalign=1)
|
||||||
|
k.set_markup(f"<b>{GLib.markup_escape_text(key)}</b>")
|
||||||
|
k.get_style_context().add_class("dim-label")
|
||||||
|
v = Gtk.Label(label=val, xalign=0, selectable=True)
|
||||||
|
v.set_line_wrap(True)
|
||||||
|
grid.attach(k, 0, i, 1, 1)
|
||||||
|
grid.attach(v, 1, i, 1, 1)
|
||||||
|
return grid
|
||||||
|
|
||||||
|
|
||||||
|
def _project_tab(osr):
|
||||||
|
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10, margin=20)
|
||||||
|
title = Gtk.Label()
|
||||||
|
name = osr.get("PRETTY_NAME") or osr.get("NAME") or "FWS Linux"
|
||||||
|
title.set_markup(f"<span size='xx-large' weight='bold'>{name}</span>")
|
||||||
|
box.pack_start(title, False, False, 0)
|
||||||
|
|
||||||
|
tag = Gtk.Label(label="Une distribution Linux pensée pour remplacer "
|
||||||
|
"Windows : bureau prêt à l'emploi, jeux, VR et "
|
||||||
|
"compatibilité applicative.")
|
||||||
|
tag.set_line_wrap(True)
|
||||||
|
tag.set_max_width_chars(52)
|
||||||
|
box.pack_start(tag, False, False, 0)
|
||||||
|
|
||||||
|
feats = Gtk.Label()
|
||||||
|
feats.set_markup(
|
||||||
|
"<b>Inclus :</b> installateur graphique · plusieurs bureaux "
|
||||||
|
"(GNOME, KDE, Hyprland, i3, XFCE) · Steam & Proton-GE · "
|
||||||
|
"réalité virtuelle (SteamVR) · winboat (applications Windows) · "
|
||||||
|
"pare-feu · réparation système intégrée.")
|
||||||
|
feats.set_line_wrap(True)
|
||||||
|
feats.set_max_width_chars(52)
|
||||||
|
feats.set_xalign(0)
|
||||||
|
box.pack_start(feats, False, False, 6)
|
||||||
|
|
||||||
|
home = osr.get("HOME_URL") or "https://github.com/You-re-like-Windows-a-bitch/fws"
|
||||||
|
link = Gtk.LinkButton.new_with_label(home, "Site du projet")
|
||||||
|
box.pack_start(link, False, False, 0)
|
||||||
|
return box
|
||||||
|
|
||||||
|
|
||||||
|
def _system_tab(osr):
|
||||||
|
rows = [
|
||||||
|
("Système", osr.get("PRETTY_NAME", "FWS Linux")),
|
||||||
|
("Build", osr.get("BUILD_ID", "rolling")),
|
||||||
|
("Noyau", _cmd("uname", "-r") or "inconnu"),
|
||||||
|
("Bureau", _desktop()),
|
||||||
|
("Affichage", _session_type()),
|
||||||
|
("Nom de machine", _cmd("hostname") or os.uname().nodename),
|
||||||
|
("Processeur", _cpu()),
|
||||||
|
("Mémoire", _mem()),
|
||||||
|
("Carte(s) graphique(s)", "\n".join(_gpus())),
|
||||||
|
("Disque(s)", "\n".join(_disks())),
|
||||||
|
]
|
||||||
|
scroll = Gtk.ScrolledWindow()
|
||||||
|
scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
|
||||||
|
scroll.add(_grid(rows))
|
||||||
|
return scroll
|
||||||
|
|
||||||
|
|
||||||
|
class AboutWindow(Gtk.Window):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(title="À propos de FWS")
|
||||||
|
self.set_default_size(520, 460)
|
||||||
|
self.set_position(Gtk.WindowPosition.CENTER)
|
||||||
|
self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
|
||||||
|
|
||||||
|
osr = _osrelease()
|
||||||
|
notebook = Gtk.Notebook()
|
||||||
|
notebook.append_page(_project_tab(osr), Gtk.Label(label="Le projet FWS"))
|
||||||
|
notebook.append_page(_system_tab(osr), Gtk.Label(label="Système"))
|
||||||
|
self.add(notebook)
|
||||||
|
self.connect("destroy", Gtk.main_quit)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if not Gtk.init_check()[0]:
|
||||||
|
return 1
|
||||||
|
win = AboutWindow()
|
||||||
|
win.show_all()
|
||||||
|
Gtk.main()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================
|
||||||
|
# fws-gameboot — orchestrateur dual-boot « hibernate-swap » (FWS ⇄ Windows)
|
||||||
|
#
|
||||||
|
# But : jouer à un jeu à anticheat kernel (Valorant/Vanguard) qui REFUSE la
|
||||||
|
# virtualisation, sans quitter durablement FWS. On hiberne FWS (RAM → swap), on
|
||||||
|
# arme un boot UEFI one-shot (BootNext) vers un Windows 11 BARE-METAL, et on
|
||||||
|
# redémarre. Windows tourne sur le vrai matériel (AUCUN hyperviseur) → Vanguard
|
||||||
|
# content. Au retour de Windows, le noyau reprend la session FWS via resume=.
|
||||||
|
#
|
||||||
|
# Ce N'EST PAS une VM et il n'y a AUCUN hyperviseur : c'est un dual-boot
|
||||||
|
# orchestré. Le « seamless » vient de l'hibernation (session restaurée), pas
|
||||||
|
# d'une exécution simultanée (impossible sans hyperviseur = ban Vanguard).
|
||||||
|
#
|
||||||
|
# Filet de sûreté PERMANENT : BootOrder[0] = FWS (posé par fws-gameboot-bootfix
|
||||||
|
# à chaque démarrage). BootNext ne sert QUE pour les transitions one-shot ; tout
|
||||||
|
# crash/reboot non géré retombe sur FWS.
|
||||||
|
#
|
||||||
|
# ⚠ Exige une VALIDATION MATÉRIELLE réelle (hibernation + resume GPU NVIDIA,
|
||||||
|
# sémantique BootNext du firmware). Voir docs/hibernate-swap-dualboot.md §7.
|
||||||
|
# ============================================================
|
||||||
|
set -u
|
||||||
|
|
||||||
|
CONF=/etc/fws/gameboot.conf
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
[ -r "$CONF" ] && . "$CONF"
|
||||||
|
: "${FWS_WINDOWS_BOOTNUM:=}"
|
||||||
|
: "${FWS_HIBERNATE_FALLBACK:=reboot}"
|
||||||
|
: "${FWS_SWAP_MARGIN_MB:=2048}"
|
||||||
|
: "${FWS_DEFAULT_GAME:=valorant}"
|
||||||
|
|
||||||
|
log() { printf '\e[36m[gameboot]\e[0m %s\n' "$*" >&2; }
|
||||||
|
warn() { printf '\e[33m[gameboot] %s\e[0m\n' "$*" >&2; }
|
||||||
|
die() { printf '\e[31m[gameboot] ERREUR : %s\e[0m\n' "$*" >&2; exit 1; }
|
||||||
|
|
||||||
|
# Notifie l'utilisateur graphique (best-effort ; on tourne en root via pkexec).
|
||||||
|
notify_user() {
|
||||||
|
command -v notify-send >/dev/null 2>&1 || return 0
|
||||||
|
local u uid
|
||||||
|
while IFS=: read -r u _ uid _; do
|
||||||
|
[ "$uid" -ge 1000 ] && [ "$uid" -le 60000 ] || continue
|
||||||
|
[ -S "/run/user/$uid/bus" ] || continue
|
||||||
|
sudo -u "$u" DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$uid/bus" \
|
||||||
|
notify-send -a FWS -u critical "FWS Gaming" "$1" 2>/dev/null || true
|
||||||
|
done < /etc/passwd
|
||||||
|
}
|
||||||
|
|
||||||
|
need_root() { [ "$(id -u)" -eq 0 ] || die "root requis — lance : pkexec fws-gameboot ${1:-}"; }
|
||||||
|
|
||||||
|
# --- Entrée UEFI « Windows Boot Manager » (numéro Boot####, HEX MAJUSCULE) ---
|
||||||
|
find_windows_bootnum() {
|
||||||
|
local n
|
||||||
|
if [ -n "$FWS_WINDOWS_BOOTNUM" ]; then n="$FWS_WINDOWS_BOOTNUM"; else
|
||||||
|
n="$(efibootmgr 2>/dev/null | sed -n 's/^Boot\([0-9A-Fa-f]\{4\}\)\*\? *Windows Boot Manager.*/\1/p' | head -1)"
|
||||||
|
fi
|
||||||
|
# Normaliser en MAJUSCULE : efibootmgr affiche « BootNext: 000A » en majuscule ;
|
||||||
|
# une valeur config en minuscule ferait échouer la relecture (m6).
|
||||||
|
printf '%s' "${n^^}"
|
||||||
|
}
|
||||||
|
# --- Notre propre entrée (FWS ou, à défaut, GRUB) ---
|
||||||
|
find_fws_bootnum() {
|
||||||
|
efibootmgr 2>/dev/null | sed -n 's/^Boot\([0-9A-Fa-f]\{4\}\)\*\? *\(FWS\|GRUB\).*/\1/p' | head -1
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- VRAM en usage (Mo), 0 si pas de NVIDIA — recopiée dans l'image S4 ---
|
||||||
|
vram_used_mb() {
|
||||||
|
command -v nvidia-smi >/dev/null 2>&1 || { echo 0; return; }
|
||||||
|
nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits 2>/dev/null \
|
||||||
|
| awk '{s+=$1} END{print s+0}'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Renvoie 0 si tout est OK pour hiberner. Décrit chaque échec sur stderr.
|
||||||
|
preflight() {
|
||||||
|
local mem_kb swap_kb vram_mb need_mb have_mb
|
||||||
|
grep -q disk /sys/power/state 2>/dev/null || { warn "hibernation indisponible (/sys/power/state sans « disk »)"; return 1; }
|
||||||
|
grep -q 'resume=' /proc/cmdline || { warn "resume= absent de la cmdline noyau (hibernation non reprenable)"; return 1; }
|
||||||
|
swap_kb=$(awk '/^SwapTotal:/{print $2}' /proc/meminfo)
|
||||||
|
[ "${swap_kb:-0}" -gt 0 ] || { warn "aucun swap actif (rien où écrire l'image)"; return 1; }
|
||||||
|
mem_kb=$(awk '/^MemTotal:/{print $2}' /proc/meminfo)
|
||||||
|
vram_mb=$(vram_used_mb)
|
||||||
|
need_mb=$(( mem_kb/1024 + vram_mb + FWS_SWAP_MARGIN_MB ))
|
||||||
|
have_mb=$(( swap_kb/1024 ))
|
||||||
|
if [ "$have_mb" -lt "$need_mb" ]; then
|
||||||
|
warn "swap trop petit : ${have_mb} Mo < ${need_mb} Mo requis (RAM $((mem_kb/1024)) + VRAM ${vram_mb} + marge ${FWS_SWAP_MARGIN_MB})"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Résout le point de montage de l'ESP (FAT). Vide si aucun.
|
||||||
|
find_esp() {
|
||||||
|
findmnt -rno TARGET /boot/efi 2>/dev/null \
|
||||||
|
|| findmnt -rno TARGET /efi 2>/dev/null \
|
||||||
|
|| findmnt -rno TARGET /boot 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_doctor() {
|
||||||
|
local sb f w
|
||||||
|
sb=$(bootctl status 2>/dev/null | sed -n 's/^ *Secure Boot: *//p' | head -1)
|
||||||
|
f="$(find_fws_bootnum)"; w="$(find_windows_bootnum)"
|
||||||
|
echo "== fws-gameboot doctor =="
|
||||||
|
echo "Entrée FWS : ${f:-?}"
|
||||||
|
echo "Entrée Windows : ${w:-INTROUVABLE}"
|
||||||
|
echo "RAM : $(( $(awk '/^MemTotal:/{print $2}' /proc/meminfo)/1024 )) Mo"
|
||||||
|
echo "Swap : $(( $(awk '/^SwapTotal:/{print $2}' /proc/meminfo)/1024 )) Mo"
|
||||||
|
echo "VRAM en usage : $(vram_used_mb) Mo"
|
||||||
|
echo "resume= cmdline : $(grep -o 'resume=[^ ]*' /proc/cmdline || echo 'ABSENT')"
|
||||||
|
echo "Secure Boot : ${sb:-inconnu}"
|
||||||
|
echo -n "Préflight : "
|
||||||
|
if preflight 2>/dev/null; then printf '\e[32mOK\e[0m\n'; else printf '\e[31mÉCHEC\e[0m\n'; preflight; fi
|
||||||
|
case "$sb" in
|
||||||
|
enabled) ;;
|
||||||
|
*) warn "Secure Boot inactif → Vanguard refusera Windows (VAN9003). Configure d'abord : sudo fws-secureboot-setup" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_to_windows() {
|
||||||
|
need_root "to-windows"
|
||||||
|
local win esp tok bootnext_set="" rc m game="${FWS_DEFAULT_GAME:-valorant}" game_req=""
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--game) game="${2:-}"; game_req=1; shift 2 ;;
|
||||||
|
--game=*) game="${1#--game=}"; game_req=1; shift ;;
|
||||||
|
*) die "argument inconnu : $1 (usage : to-windows [--game <id>])" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
# Charte d'identifiant alignée sur les clés de games.json : [a-z0-9_-]
|
||||||
|
# (l'underscore doit passer, sinon divergence FWS↔Windows).
|
||||||
|
game="$(printf '%s' "$game" | tr '[:upper:]' '[:lower:]' | tr -cd 'a-z0-9_-')"
|
||||||
|
[ -n "$game_req" ] && [ -z "$game" ] && warn "identifiant --game invalide après nettoyage — jeu par défaut appliqué."
|
||||||
|
win="$(find_windows_bootnum)"
|
||||||
|
if [ -z "$win" ]; then
|
||||||
|
notify_user "Windows introuvable — installe d'abord le Windows gaming (entrée de boot UEFI absente)."
|
||||||
|
die "entrée « Windows Boot Manager » introuvable (Windows installé + entrée UEFI présente ?)"
|
||||||
|
fi
|
||||||
|
esp="$(find_esp)"
|
||||||
|
|
||||||
|
# Jeton « quel jeu lancer », lu ET CONSOMMÉ par fws-play côté Windows.
|
||||||
|
# Écriture ATOMIQUE (.tmp puis mv) sur l'ESP FAT uniquement. En cas d'échec,
|
||||||
|
# on PURGE tout jeton périmé (jamais de rejeu d'un ancien choix) → Windows
|
||||||
|
# lancera le jeu par défaut plutôt qu'un mauvais jeu.
|
||||||
|
if [ -n "$esp" ] && [ "$(findmnt -no FSTYPE "$esp" 2>/dev/null)" = vfat ]; then
|
||||||
|
if [ -n "$game" ]; then
|
||||||
|
mkdir -p "$esp/EFI/FWS" 2>/dev/null || true
|
||||||
|
tok="$esp/EFI/FWS/launch.json"
|
||||||
|
if printf '{"game":"%s"}\n' "$game" > "$tok.tmp" 2>/dev/null && mv -f "$tok.tmp" "$tok" 2>/dev/null; then
|
||||||
|
log "Jeu demandé : $game"
|
||||||
|
else
|
||||||
|
rm -f "$tok" "$tok.tmp" 2>/dev/null || true
|
||||||
|
warn "écriture du jeton échouée → jeton purgé (Windows lancera le jeu par défaut)."
|
||||||
|
notify_user "Choix du jeu non transmis — Windows lancera le jeu par défaut."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
elif [ -n "$game_req" ]; then
|
||||||
|
warn "ESP FAT introuvable → le choix du jeu ne peut pas être transmis à Windows."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Filet : ne JAMAIS laisser BootNext armé si on ne bascule pas réellement
|
||||||
|
# (sinon départ surprise vers Windows au prochain reboot manuel).
|
||||||
|
cleanup() { [ -n "$bootnext_set" ] && { efibootmgr -N >/dev/null 2>&1 || true; log "BootNext annulé (nettoyage)"; }; }
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
# Démonter toute NTFS montée : jamais rw persistante à travers le cycle.
|
||||||
|
# `findmnt -lno` (liste, PAS -r/raw) → chemins NON échappés (espaces OK) ; on
|
||||||
|
# se limite aux vrais types NTFS (pas fuseblk générique).
|
||||||
|
while IFS= read -r m; do
|
||||||
|
[ -n "$m" ] && umount "$m" 2>/dev/null || true
|
||||||
|
done < <(findmnt -lno TARGET -t ntfs,ntfs3 2>/dev/null)
|
||||||
|
|
||||||
|
if ! preflight; then
|
||||||
|
case "$FWS_HIBERNATE_FALLBACK" in
|
||||||
|
reboot)
|
||||||
|
warn "Préflight hibernation KO → bascule en REBOOT SIMPLE (session FWS NON préservée)."
|
||||||
|
notify_user "Hibernation indisponible — bascule vers Windows par reboot simple (session non sauvegardée)."
|
||||||
|
efibootmgr -n "$win" >/dev/null || die "écriture de BootNext échouée"
|
||||||
|
bootnext_set=1 # ARMÉ : si la relecture échoue, le trap nettoie (M2)
|
||||||
|
efibootmgr 2>/dev/null | grep -qi "BootNext: $win" || die "BootNext non confirmé par le firmware"
|
||||||
|
trap - EXIT # bascule assumée
|
||||||
|
log "Reboot simple vers Windows (Boot$win)…"
|
||||||
|
exec systemctl reboot
|
||||||
|
;;
|
||||||
|
*) die "Préflight hibernation KO et fallback='abort' → on reste sur FWS." ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Bascule NOMINALE : BootNext=Windows puis hibernation ---
|
||||||
|
efibootmgr -n "$win" >/dev/null || die "écriture de BootNext échouée"
|
||||||
|
bootnext_set=1
|
||||||
|
# RELECTURE obligatoire : certains firmwares ignorent/écrasent BootNext.
|
||||||
|
efibootmgr 2>/dev/null | grep -qi "BootNext: $win" \
|
||||||
|
|| die "BootNext non pris en compte par le firmware (entrée Boot$win) — carte non compatible ?"
|
||||||
|
log "BootNext armé vers Windows (Boot$win). Hibernation…"
|
||||||
|
notify_user "Lancement de Windows pour le jeu — FWS s'endort, ta session sera restaurée au retour."
|
||||||
|
|
||||||
|
# M3 : l'ESP (FAT, potentiellement partagée avec Windows) ne doit pas rester
|
||||||
|
# montée rw à travers le cycle — un cache FAT périmé réécrit au resume
|
||||||
|
# corromprait les bootloaders (seul chemin réaliste vers un vrai brick). On
|
||||||
|
# la remonte en lecture seule le temps de l'hibernation, rw au retour.
|
||||||
|
[ -n "$esp" ] && { mount -o remount,ro "$esp" 2>/dev/null || true; }
|
||||||
|
|
||||||
|
# NB : on n'enveloppe PAS « systemctl hibernate » dans un timeout. Un
|
||||||
|
# hibernate RÉUSSI ne « rend la main » qu'au RESUME (après la session
|
||||||
|
# Windows, potentiellement > 1 h) : un timeout tuerait les hibernations
|
||||||
|
# réussies. Une hibernation qui HANG (driver refusant S4) laisse la machine
|
||||||
|
# allumée et n'est PAS mitigeable en userspace (le process est gelé) ; c'est
|
||||||
|
# un risque à valider sur matériel réel (docs §7 #6). Le préflight a déjà
|
||||||
|
# éliminé les causes courantes (swap absent/petit, resume= manquant).
|
||||||
|
if systemctl hibernate; then
|
||||||
|
# On revient ici au RESUME. Bascule réussie : le firmware a normalement
|
||||||
|
# consommé BootNext au boot de Windows — MAIS un override manuel (menu
|
||||||
|
# F12) peut le laisser armé. On le purge donc INCONDITIONNELLEMENT (M1) :
|
||||||
|
# no-op s'il est déjà consommé, rétablit le filet BootOrder=FWS sinon.
|
||||||
|
[ -n "$esp" ] && { mount -o remount,rw "$esp" 2>/dev/null || true; }
|
||||||
|
efibootmgr -N >/dev/null 2>&1 || true
|
||||||
|
bootnext_set=""
|
||||||
|
trap - EXIT
|
||||||
|
log "Session FWS restaurée. Bon retour."
|
||||||
|
else
|
||||||
|
rc=$?
|
||||||
|
[ -n "$esp" ] && { mount -o remount,rw "$esp" 2>/dev/null || true; }
|
||||||
|
warn "Hibernation échouée (rc=$rc)."
|
||||||
|
if [ "$FWS_HIBERNATE_FALLBACK" = reboot ]; then
|
||||||
|
notify_user "Hibernation impossible — bascule vers Windows par reboot simple (session non sauvegardée)."
|
||||||
|
if efibootmgr -n "$win" >/dev/null 2>&1 && efibootmgr 2>/dev/null | grep -qi "BootNext: $win"; then
|
||||||
|
bootnext_set="" # on assume la bascule, pas de nettoyage
|
||||||
|
trap - EXIT
|
||||||
|
log "Reboot simple vers Windows…"
|
||||||
|
exec systemctl reboot
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
die "Bascule annulée — on reste sur FWS (BootNext nettoyé)."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
fws-gameboot — dual-boot hibernate-swap (FWS ⇄ Windows bare-metal, anticheat kernel)
|
||||||
|
|
||||||
|
fws-gameboot doctor Vérifie l'environnement (préflight, boot)
|
||||||
|
fws-gameboot to-windows [--game <id>] Hiberne FWS, bascule + lance <id> (root/pkexec)
|
||||||
|
fws-gameboot status Alias de « doctor »
|
||||||
|
|
||||||
|
Le jeu par défaut (FWS_DEFAULT_GAME) est « valorant ». Les identifiants connus
|
||||||
|
sont définis dans games.json côté Windows (valorant, lol, …).
|
||||||
|
|
||||||
|
Config : $CONF
|
||||||
|
Détails et risques : docs/hibernate-swap-dualboot.md
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
to-windows) shift; cmd_to_windows "$@" ;;
|
||||||
|
doctor|status) cmd_doctor ;;
|
||||||
|
''|-h|--help|help) usage ;;
|
||||||
|
*) die "sous-commande inconnue : $1 (voir --help)" ;;
|
||||||
|
esac
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================
|
||||||
|
# fws-gameboot-bootfix — exécuté à CHAQUE démarrage de FWS (oneshot systemd).
|
||||||
|
#
|
||||||
|
# Réasserte BootOrder[0] = FWS. Filet de sûreté PERMANENT : après un Windows
|
||||||
|
# Update qui remet « Windows Boot Manager » en tête de BootOrder, ou après tout
|
||||||
|
# crash/reboot non géré, un boot SANS BootNext retombe toujours sur FWS (jamais
|
||||||
|
# coincé côté Windows ; l'image d'hibernation reste accessible).
|
||||||
|
#
|
||||||
|
# Inerte sur le live (le service porte ConditionPathExists=!/run/archiso).
|
||||||
|
# ============================================================
|
||||||
|
set -u
|
||||||
|
[ -d /sys/firmware/efi ] || exit 0
|
||||||
|
command -v efibootmgr >/dev/null 2>&1 || exit 0
|
||||||
|
|
||||||
|
fws="$(efibootmgr 2>/dev/null | sed -n 's/^Boot\([0-9A-Fa-f]\{4\}\)\*\? *\(FWS\|GRUB\).*/\1/p' | head -1)"
|
||||||
|
[ -n "$fws" ] || exit 0
|
||||||
|
|
||||||
|
# Nettoyer une entrée « FWS WinPE » résiduelle du déploiement Windows (repli
|
||||||
|
# bcdboot, one-shot déjà consommé) : sinon elle traîne dans le menu de boot.
|
||||||
|
for pe in $(efibootmgr 2>/dev/null | sed -n 's/^Boot\([0-9A-Fa-f]\{4\}\)\*\? *FWS WinPE.*/\1/p'); do
|
||||||
|
efibootmgr -b "$pe" -B >/dev/null 2>&1 || true
|
||||||
|
done
|
||||||
|
|
||||||
|
# BootOrder[0] = FWS (sans dupliquer, en préservant l'ordre existant derrière).
|
||||||
|
order="$(efibootmgr 2>/dev/null | sed -n 's/^BootOrder: //p' | tr -d '[:space:]')"
|
||||||
|
if [ -n "$order" ] && [ "${order%%,*}" != "$fws" ]; then
|
||||||
|
rest="$(printf '%s' "$order" | tr ',' '\n' | grep -vx "$fws" | paste -sd, -)"
|
||||||
|
efibootmgr -o "${fws}${rest:+,$rest}" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
+158
@@ -0,0 +1,158 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# FWS — fenêtre de bienvenue au premier démarrage (pendant post-install de la
|
||||||
|
# page récap de l'installateur). Boutons : mise à jour, codecs, VR, raccourcis,
|
||||||
|
# Proton-GE. GTK3 : présent sur TOUS les bureaux FWS (Firefox le tire) ;
|
||||||
|
# python-gobject est installé avec le bureau par le kickstart.
|
||||||
|
#
|
||||||
|
# Lancement : /etc/xdg/autostart (KDE/GNOME/XFCE) et exec des configs
|
||||||
|
# Hyprland/i3, toujours avec --autostart : on se retire sans bruit si
|
||||||
|
# l'utilisateur a coché « ne plus afficher » (~/.config/fws/hello-done).
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import gi
|
||||||
|
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
gi.require_version("Gdk", "3.0") # sinon gi charge Gdk 4.0 (conflit avec Gtk3)
|
||||||
|
from gi.repository import Gdk, GLib, Gtk # noqa: E402
|
||||||
|
|
||||||
|
# WM_CLASS déterministe : les règles i3/Hyprland « fenêtre flottante »
|
||||||
|
# ciblent cette classe.
|
||||||
|
GLib.set_prgname("fws-hello")
|
||||||
|
|
||||||
|
_FLAG = os.path.expanduser("~/.config/fws/hello-done")
|
||||||
|
|
||||||
|
_CODECS = ("gst-plugins-base gst-plugins-good gst-plugins-bad "
|
||||||
|
"gst-plugins-ugly gst-libav ffmpeg mpv")
|
||||||
|
|
||||||
|
# (libellé, description, commande, en_terminal)
|
||||||
|
_ACTIONS = [
|
||||||
|
("Mettre à jour le système",
|
||||||
|
"pacman -Syu + nettoyage du cache",
|
||||||
|
"fws-update", True),
|
||||||
|
("Installer les codecs multimédia",
|
||||||
|
"lecture vidéo/audio (GStreamer, ffmpeg, mpv)",
|
||||||
|
f"sudo pacman -S --needed {_CODECS}", True),
|
||||||
|
("Proton-GE pour Steam",
|
||||||
|
"meilleure compatibilité des jeux Windows",
|
||||||
|
"fws-proton-ge", True),
|
||||||
|
("Configurer la VR",
|
||||||
|
"drivers casque : HTC Vive, Index, Quest…",
|
||||||
|
"fws-vr-setup", True),
|
||||||
|
]
|
||||||
|
|
||||||
|
_TERMINALS = ["kitty", "konsole", "gnome-terminal", "xfce4-terminal", "xterm"]
|
||||||
|
|
||||||
|
|
||||||
|
def _terminal():
|
||||||
|
for term in _TERMINALS:
|
||||||
|
if shutil.which(term):
|
||||||
|
return term
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _run(command, in_terminal):
|
||||||
|
if in_terminal:
|
||||||
|
term = _terminal()
|
||||||
|
if term is None:
|
||||||
|
return
|
||||||
|
if term == "gnome-terminal":
|
||||||
|
argv = [term, "--", "sh", "-c", command]
|
||||||
|
else:
|
||||||
|
argv = [term, "-e", "sh", "-c", command]
|
||||||
|
else:
|
||||||
|
argv = ["sh", "-c", command]
|
||||||
|
subprocess.Popen(argv) # noqa: S603 — commandes internes FWS
|
||||||
|
|
||||||
|
|
||||||
|
class HelloWindow(Gtk.Window):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(title="Bienvenue sur FWS")
|
||||||
|
self.set_default_size(460, -1)
|
||||||
|
self.set_position(Gtk.WindowPosition.CENTER)
|
||||||
|
self.set_border_width(18)
|
||||||
|
# DIALOGUE : les tuileurs (i3/Hyprland) laissent les dialogues en
|
||||||
|
# flottant → la fenêtre garde sa taille naturelle au lieu d'être
|
||||||
|
# étirée sur tout l'espace de travail (layout illisible sinon).
|
||||||
|
self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
|
||||||
|
self.set_resizable(False)
|
||||||
|
|
||||||
|
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
|
||||||
|
self.add(box)
|
||||||
|
|
||||||
|
title = Gtk.Label()
|
||||||
|
title.set_markup("<span size='x-large' weight='bold'>Bienvenue sur FWS !</span>")
|
||||||
|
box.pack_start(title, False, False, 0)
|
||||||
|
|
||||||
|
subtitle = Gtk.Label(
|
||||||
|
label="Ton système est prêt. Quelques réglages utiles pour bien démarrer :")
|
||||||
|
subtitle.set_line_wrap(True)
|
||||||
|
subtitle.set_xalign(0)
|
||||||
|
box.pack_start(subtitle, False, False, 0)
|
||||||
|
|
||||||
|
actions = list(_ACTIONS)
|
||||||
|
# Raccourcis clavier : uniquement sur les sessions qui ont fws-keys
|
||||||
|
# (Hyprland/i3) — les autres bureaux ont leurs propres réglages.
|
||||||
|
session = os.environ.get("XDG_CURRENT_DESKTOP", "").lower()
|
||||||
|
if os.environ.get("HYPRLAND_INSTANCE_SIGNATURE") or "i3" in session:
|
||||||
|
actions.append(("Voir les raccourcis clavier",
|
||||||
|
"aussi disponible à tout moment : Super+K",
|
||||||
|
"fws-keys", False))
|
||||||
|
|
||||||
|
for label, desc, command, in_term in actions:
|
||||||
|
button = Gtk.Button()
|
||||||
|
inner = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2)
|
||||||
|
lab = Gtk.Label()
|
||||||
|
lab.set_markup(f"<b>{label}</b>")
|
||||||
|
lab.set_xalign(0)
|
||||||
|
sub = Gtk.Label(label=desc)
|
||||||
|
sub.set_xalign(0)
|
||||||
|
sub.get_style_context().add_class("dim-label")
|
||||||
|
inner.pack_start(lab, False, False, 0)
|
||||||
|
inner.pack_start(sub, False, False, 0)
|
||||||
|
button.add(inner)
|
||||||
|
button.connect("clicked",
|
||||||
|
lambda _b, c=command, t=in_term: _run(c, t))
|
||||||
|
box.pack_start(button, False, False, 0)
|
||||||
|
|
||||||
|
self._again = Gtk.CheckButton(label="Afficher cette fenêtre au prochain démarrage")
|
||||||
|
self._again.set_active(False)
|
||||||
|
box.pack_start(self._again, False, False, 6)
|
||||||
|
|
||||||
|
close = Gtk.Button(label="Fermer")
|
||||||
|
close.connect("clicked", lambda _b: self.close())
|
||||||
|
box.pack_start(close, False, False, 0)
|
||||||
|
|
||||||
|
self.connect("destroy", self._on_destroy)
|
||||||
|
|
||||||
|
def _on_destroy(self, _win):
|
||||||
|
if not self._again.get_active():
|
||||||
|
os.makedirs(os.path.dirname(_FLAG), exist_ok=True)
|
||||||
|
with open(_FLAG, "w") as fh:
|
||||||
|
fh.write("ok\n")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
os.unlink(_FLAG)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
Gtk.main_quit()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if "--autostart" in sys.argv and os.path.exists(_FLAG):
|
||||||
|
return 0
|
||||||
|
# Pas d'affichage joignable (lancement hors session graphique) : on se
|
||||||
|
# retire sans traceback plutôt que de crasher.
|
||||||
|
if not Gtk.init_check()[0]:
|
||||||
|
return 1
|
||||||
|
win = HelloWindow()
|
||||||
|
win.show_all()
|
||||||
|
Gtk.main()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
+129
@@ -0,0 +1,129 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# FWS — affiche les raccourcis clavier RÉELS de la session (bouton ⌨ de la
|
||||||
|
# barre ou Super+K). Universel :
|
||||||
|
# • Hyprland : « hyprctl binds -j » = les binds effectivement chargés
|
||||||
|
# (descriptions des bindd) — affichage wofi (Wayland).
|
||||||
|
# • i3 : parse ~/.config/i3/config (bindsym, variables résolues)
|
||||||
|
# — affichage rofi (X11).
|
||||||
|
# Aucune dépendance hors python3 + wofi/rofi (déjà installés avec le bureau).
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
_WOFI_STYLE = "/usr/local/share/fws/wofi-keys.css"
|
||||||
|
|
||||||
|
# Bits de modmask Hyprland (masques X11) dans l'ordre d'affichage voulu.
|
||||||
|
_MODS = [(64, "SUPER"), (4, "CTRL"), (8, "ALT"), (1, "SHIFT")]
|
||||||
|
|
||||||
|
_KEY_LABELS = {
|
||||||
|
"mouse:272": "clic gauche",
|
||||||
|
"mouse:273": "clic droit",
|
||||||
|
"mouse:274": "clic molette",
|
||||||
|
"mouse_down": "molette bas",
|
||||||
|
"mouse_up": "molette haut",
|
||||||
|
"button4": "molette haut",
|
||||||
|
"button5": "molette bas",
|
||||||
|
"return": "Entrée",
|
||||||
|
"space": "Espace",
|
||||||
|
"escape": "Échap",
|
||||||
|
"tab": "Tab",
|
||||||
|
"left": "←",
|
||||||
|
"right": "→",
|
||||||
|
"up": "↑",
|
||||||
|
"down": "↓",
|
||||||
|
"print": "Impr écran",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _label(key):
|
||||||
|
return _KEY_LABELS.get(key.lower(), key)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Hyprland : binds réels via hyprctl ------------------------------------
|
||||||
|
def hyprland_lines():
|
||||||
|
raw = subprocess.check_output(["hyprctl", "binds", "-j"], text=True)
|
||||||
|
lines, seen = [], set()
|
||||||
|
for bind in json.loads(raw):
|
||||||
|
parts = [name for bit, name in _MODS if bind.get("modmask", 0) & bit]
|
||||||
|
key = bind.get("key") or (f"code {bind['keycode']}" if bind.get("keycode") else "")
|
||||||
|
parts.append(_label(key))
|
||||||
|
combo = " + ".join(p for p in parts if p)
|
||||||
|
if not combo or combo in seen:
|
||||||
|
continue
|
||||||
|
seen.add(combo)
|
||||||
|
desc = (bind.get("description") or "").strip() \
|
||||||
|
or f"{bind.get('dispatcher', '?')} {(bind.get('arg') or '').strip()}".strip()
|
||||||
|
lines.append(f"{combo:<24} {desc}")
|
||||||
|
return lines
|
||||||
|
|
||||||
|
|
||||||
|
# --- i3 : parse de la config (variables résolues) ----------------------------
|
||||||
|
def i3_lines():
|
||||||
|
path = os.path.expanduser("~/.config/i3/config")
|
||||||
|
variables = {}
|
||||||
|
lines, seen = [], set()
|
||||||
|
with open(path, encoding="utf-8", errors="replace") as fh:
|
||||||
|
content = fh.readlines()
|
||||||
|
for line in content:
|
||||||
|
m = re.match(r"\s*set\s+(\$\S+)\s+(.+)$", line)
|
||||||
|
if m:
|
||||||
|
variables[m.group(1)] = m.group(2).strip()
|
||||||
|
def resolve(text):
|
||||||
|
# $var les plus longs d'abord ($filemanager avant $file…).
|
||||||
|
for var in sorted(variables, key=len, reverse=True):
|
||||||
|
text = text.replace(var, variables[var])
|
||||||
|
return text
|
||||||
|
for line in content:
|
||||||
|
m = re.match(r"\s*bindsym\s+(?:--\S+\s+)*(\S+)\s+(.+)$", line)
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
combo_raw, action = m.groups()
|
||||||
|
combo_raw = resolve(combo_raw).replace("Mod4", "Super").replace("Mod1", "Alt")
|
||||||
|
combo = " + ".join(_label(k) for k in combo_raw.split("+"))
|
||||||
|
if combo in seen:
|
||||||
|
continue
|
||||||
|
seen.add(combo)
|
||||||
|
action = resolve(action)
|
||||||
|
action = re.sub(r"^exec\s+(--no-startup-id\s+)?", "", action).strip().strip('"')
|
||||||
|
lines.append(f"{combo:<24} {action}")
|
||||||
|
return lines
|
||||||
|
|
||||||
|
|
||||||
|
def show(lines):
|
||||||
|
if not lines:
|
||||||
|
lines = ["(aucun raccourci trouvé)"]
|
||||||
|
text = "\n".join(lines)
|
||||||
|
if os.environ.get("HYPRLAND_INSTANCE_SIGNATURE"):
|
||||||
|
cmd = ["wofi", "--dmenu", "--prompt", "Raccourcis clavier — taper pour filtrer",
|
||||||
|
"--width", "820", "--height", "600", "--insensitive",
|
||||||
|
"--cache-file", "/dev/null"]
|
||||||
|
if os.path.exists(_WOFI_STYLE):
|
||||||
|
cmd += ["--style", _WOFI_STYLE]
|
||||||
|
else:
|
||||||
|
# X11/i3 : rofi en dmenu, style assorti (mono pour l'alignement).
|
||||||
|
cmd = ["rofi", "-dmenu", "-i", "-p", "Raccourcis clavier",
|
||||||
|
"-no-custom",
|
||||||
|
"-theme-str", 'window { width: 880px; } listview { lines: 18; }',
|
||||||
|
"-theme-str", '* { font: "Noto Sans Mono 11"; }']
|
||||||
|
subprocess.run(cmd, input=text, text=True, check=False)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
if os.environ.get("HYPRLAND_INSTANCE_SIGNATURE"):
|
||||||
|
lines = hyprland_lines()
|
||||||
|
else:
|
||||||
|
lines = i3_lines()
|
||||||
|
except Exception as exc: # noqa: BLE001 — best effort, jamais de crash muet
|
||||||
|
subprocess.run(["notify-send", "Raccourcis FWS", f"Erreur : {exc}"],
|
||||||
|
check=False)
|
||||||
|
return 1
|
||||||
|
show(lines)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# FWS — menu d'alimentation (verrouiller / déconnexion / suspendre /
|
||||||
|
# redémarrer / éteindre). Bouton de la barre ou Super+X. Universel :
|
||||||
|
# • Hyprland (Wayland) → wofi, verrouillage hyprlock, sortie hyprctl.
|
||||||
|
# • i3 (X11) → rofi, verrouillage i3lock, sortie i3-msg exit.
|
||||||
|
# Aucune dépendance hors python3 + wofi/rofi (déjà là avec le bureau).
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
_WOFI_STYLE = "/usr/local/share/fws/wofi-style.css"
|
||||||
|
_HYPR = bool(os.environ.get("HYPRLAND_INSTANCE_SIGNATURE"))
|
||||||
|
|
||||||
|
# (libellé affiché, commande shell). L'icône Nerd Font aide au repérage.
|
||||||
|
if _HYPR:
|
||||||
|
_lock = "hyprlock"
|
||||||
|
_logout = "hyprctl dispatch exit"
|
||||||
|
else:
|
||||||
|
_lock = "i3lock -c 1e1e2e"
|
||||||
|
_logout = "i3-msg exit"
|
||||||
|
|
||||||
|
_ITEMS = [
|
||||||
|
(" Verrouiller", _lock),
|
||||||
|
(" Déconnexion", _logout),
|
||||||
|
(" Suspendre", "systemctl suspend"),
|
||||||
|
(" Redémarrer", "systemctl reboot"),
|
||||||
|
(" Éteindre", "systemctl poweroff"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _menu(entries):
|
||||||
|
text = "\n".join(entries)
|
||||||
|
if _HYPR:
|
||||||
|
cmd = ["wofi", "--dmenu", "--prompt", "Alimentation",
|
||||||
|
"--width", "300", "--height", "260", "--cache-file", "/dev/null"]
|
||||||
|
if os.path.exists(_WOFI_STYLE):
|
||||||
|
cmd += ["--style", _WOFI_STYLE]
|
||||||
|
else:
|
||||||
|
cmd = ["rofi", "-dmenu", "-i", "-p", "Alimentation", "-no-custom",
|
||||||
|
"-theme-str", "window { width: 300px; } listview { lines: 5; }"]
|
||||||
|
out = subprocess.run(cmd, input=text, text=True,
|
||||||
|
capture_output=True, check=False)
|
||||||
|
return out.stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
choice = _menu([label for label, _ in _ITEMS])
|
||||||
|
if not choice:
|
||||||
|
return 0
|
||||||
|
for label, command in _ITEMS:
|
||||||
|
if label == choice:
|
||||||
|
subprocess.Popen(["sh", "-c", command]) # noqa: S603,S607
|
||||||
|
break
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# FWS — installe/actualise Proton-GE (GloriousEggroll) pour Steam :
|
||||||
|
# meilleure compatibilité des jeux Windows que le Proton officiel.
|
||||||
|
# S'exécute en UTILISATEUR (installe dans ~/.steam/root/compatibilitytools.d).
|
||||||
|
set -u
|
||||||
|
say() { printf '\n\033[1;34m==> %s\033[0m\n' "$*"; }
|
||||||
|
fail() { printf '\033[1;31m ✗ %s\033[0m\n' "$*"; read -rp "Entrée pour fermer. " _; exit 1; }
|
||||||
|
|
||||||
|
[ "$(id -u)" -eq 0 ] && fail "à lancer en utilisateur normal (pas root) : l'outil s'installe dans ton Steam."
|
||||||
|
|
||||||
|
API="https://api.github.com/repos/GloriousEggroll/proton-ge-custom/releases/latest"
|
||||||
|
DEST="$HOME/.steam/root/compatibilitytools.d"
|
||||||
|
|
||||||
|
say "Recherche de la dernière version de Proton-GE…"
|
||||||
|
JSON="$(curl -fsSL "$API")" || fail "GitHub inaccessible (réseau ?)"
|
||||||
|
URL="$(printf '%s' "$JSON" | grep -o '"browser_download_url": *"[^"]*\.tar\.gz"' | cut -d'"' -f4 | head -1)"
|
||||||
|
SUM_URL="$(printf '%s' "$JSON" | grep -o '"browser_download_url": *"[^"]*\.sha512sum"' | cut -d'"' -f4 | head -1)"
|
||||||
|
[ -n "$URL" ] || fail "archive introuvable dans la release GitHub"
|
||||||
|
NAME="$(basename "$URL" .tar.gz)"
|
||||||
|
|
||||||
|
if [ -d "$DEST/$NAME" ]; then
|
||||||
|
say "$NAME est déjà installé ✔"
|
||||||
|
read -rp "Entrée pour fermer. " _; exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
TMP="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$TMP"' EXIT
|
||||||
|
say "Téléchargement de $NAME (~450 Mo)…"
|
||||||
|
curl -fL --progress-bar -o "$TMP/$NAME.tar.gz" "$URL" || fail "téléchargement échoué"
|
||||||
|
|
||||||
|
if [ -n "$SUM_URL" ]; then
|
||||||
|
say "Vérification de l'intégrité (sha512)…"
|
||||||
|
curl -fsSL -o "$TMP/sum" "$SUM_URL" \
|
||||||
|
&& (cd "$TMP" && sed "s|[^ ]*\.tar\.gz|$NAME.tar.gz|" sum | sha512sum -c -) \
|
||||||
|
|| fail "somme de contrôle invalide — archive corrompue ?"
|
||||||
|
fi
|
||||||
|
|
||||||
|
say "Installation dans $DEST…"
|
||||||
|
mkdir -p "$DEST"
|
||||||
|
tar -xzf "$TMP/$NAME.tar.gz" -C "$DEST" || fail "extraction échouée"
|
||||||
|
|
||||||
|
say "$NAME installé ✔"
|
||||||
|
echo " Dans Steam : Paramètres → Compatibilité → activer, puis choisir"
|
||||||
|
echo " « $NAME » (ou par jeu : Propriétés → Compatibilité)."
|
||||||
|
echo " Redémarre Steam pour qu'il apparaisse."
|
||||||
|
read -rp "Entrée pour fermer. " _
|
||||||
+220
@@ -0,0 +1,220 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================================
|
||||||
|
# FWS — moteur de réparation d'une installation existante depuis le live
|
||||||
|
# (« point de restauration à la Windows »).
|
||||||
|
#
|
||||||
|
# L'interface GRAPHIQUE est fws-recovery-gui (GTK3 python — le live embarque
|
||||||
|
# python-gobject/gtk3 via Anaconda) ; elle pilote CE script en sous-commandes.
|
||||||
|
# Sans argument : assistant CONSOLE (repli sans X, ou usage à la main).
|
||||||
|
#
|
||||||
|
# Sous-commandes (pour la GUI / scripts) :
|
||||||
|
# scan → une ligne « périphérique|nom » par install FWS
|
||||||
|
# repair <action> <dev> → monte, répare, démonte ; sortie en continu
|
||||||
|
# action ∈ grub|initramfs|pacman|pacman-update|all
|
||||||
|
# shell <dev> → terminal DANS le système (xterm si DISPLAY)
|
||||||
|
#
|
||||||
|
# arch-chroot (arch-install-scripts, présent sur le live) gère les binds.
|
||||||
|
#
|
||||||
|
# Fonctionne depuis le LIVE (autre disque) ET depuis le SYSTÈME INSTALLÉ
|
||||||
|
# lui-même (menu d'applications / entrée GRUB « Réparer FWS ») : si la cible
|
||||||
|
# est la racine en cours d'exécution, on opère directement, sans mount/chroot.
|
||||||
|
# ============================================================================
|
||||||
|
set -u
|
||||||
|
MNT=/mnt/fws-recovery
|
||||||
|
ROOTDEV=""
|
||||||
|
DO_UPDATE=""
|
||||||
|
SELF="" # cible = système courant
|
||||||
|
ROOTSRC="$(findmnt -no SOURCE / 2>/dev/null)" # sur le live : overlay → jamais un lsblk
|
||||||
|
|
||||||
|
say() { printf '\n==> %s\n' "$*"; }
|
||||||
|
warn() { printf ' ⚠ %s\n' "$*"; }
|
||||||
|
|
||||||
|
[ "$(id -u)" -eq 0 ] || { echo "root requis"; exit 1; }
|
||||||
|
|
||||||
|
cleanup() { [ -n "$MNT" ] && [ "$MNT" != "/" ] && umount -R "$MNT" 2>/dev/null; }
|
||||||
|
|
||||||
|
# Exécute DANS la cible : chroot si elle est montée, directement si c'est nous.
|
||||||
|
in_target() {
|
||||||
|
if [ -n "$SELF" ]; then "$@"; else arch-chroot "$MNT" "$@"; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Moteur ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
scan_installs() {
|
||||||
|
vgscan --mknodes >/dev/null 2>&1
|
||||||
|
vgchange -ay >/dev/null 2>&1
|
||||||
|
udevadm settle --timeout=10 >/dev/null 2>&1
|
||||||
|
# Le système en cours d'exécution d'abord (app lancée depuis l'OS installé).
|
||||||
|
if [ ! -d /run/archiso ] && grep -qiE '^(ID|NAME)=.*fws' /etc/os-release 2>/dev/null; then
|
||||||
|
printf '%s|%s (ce système)\n' "$ROOTSRC" \
|
||||||
|
"$(. /etc/os-release 2>/dev/null; echo "${PRETTY_NAME:-FWS}")"
|
||||||
|
fi
|
||||||
|
mkdir -p "$MNT"
|
||||||
|
local dev name
|
||||||
|
while read -r dev; do
|
||||||
|
[ -b "$dev" ] || continue
|
||||||
|
[ "$dev" = "$ROOTSRC" ] && continue # déjà listé ci-dessus
|
||||||
|
mount -o ro "$dev" "$MNT" 2>/dev/null || continue
|
||||||
|
if [ -f "$MNT/etc/os-release" ] && [ -d "$MNT/boot" ] \
|
||||||
|
&& grep -qiE '^(ID|NAME)=.*fws' "$MNT/etc/os-release" 2>/dev/null; then
|
||||||
|
name="$(. "$MNT/etc/os-release" 2>/dev/null; echo "${PRETTY_NAME:-FWS}")"
|
||||||
|
printf '%s|%s\n' "$dev" "$name"
|
||||||
|
fi
|
||||||
|
umount "$MNT" 2>/dev/null
|
||||||
|
done < <(lsblk -rpno NAME,TYPE | awk '$2=="part"||$2=="lvm"{print $1}')
|
||||||
|
}
|
||||||
|
|
||||||
|
# Monte la cible + /boot + ESP d'après son fstab.
|
||||||
|
# Cas particulier : la cible EST la racine en cours d'exécution (app lancée
|
||||||
|
# depuis l'OS installé, ou entrée GRUB « Réparer FWS ») → tout est déjà là,
|
||||||
|
# on opère en place (MNT="" : les chemins "$MNT/…" pointent sur la vraie racine).
|
||||||
|
mount_target() {
|
||||||
|
ROOTDEV="$1"
|
||||||
|
if [ "$ROOTDEV" = "$ROOTSRC" ] && [ ! -d /run/archiso ]; then
|
||||||
|
SELF=1; MNT=""
|
||||||
|
echo " cible = système en cours d'exécution (pas de montage)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
mkdir -p "$MNT"
|
||||||
|
mount "$ROOTDEV" "$MNT" || return 1
|
||||||
|
local mp src
|
||||||
|
for mp in /boot /boot/efi; do
|
||||||
|
src="$(awk -v mp="$mp" '$2==mp && $1!~/^#/{print $1; exit}' "$MNT/etc/fstab" 2>/dev/null)"
|
||||||
|
[ -n "$src" ] || continue
|
||||||
|
mount "$(findfs "$src" 2>/dev/null || echo "$src")" "$MNT$mp" 2>/dev/null \
|
||||||
|
&& echo " $mp monté" || warn "$mp non monté (on continue)"
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fix_grub() {
|
||||||
|
say "Réinstallation de GRUB…"
|
||||||
|
if [ -d /sys/firmware/efi ]; then
|
||||||
|
in_target grub-install --target=x86_64-efi \
|
||||||
|
--efi-directory=/boot/efi --bootloader-id=FWS --recheck || true
|
||||||
|
in_target grub-install --target=x86_64-efi \
|
||||||
|
--efi-directory=/boot/efi --bootloader-id=FWS --removable --recheck
|
||||||
|
else
|
||||||
|
local disk
|
||||||
|
disk="$(lsblk -spno NAME,TYPE "$ROOTDEV" 2>/dev/null \
|
||||||
|
| awk '$2=="disk"{print $1; exit}' | grep -oE '/dev/[a-z0-9]+')"
|
||||||
|
[ -n "$disk" ] || { warn "disque BIOS introuvable"; return 1; }
|
||||||
|
in_target grub-install --target=i386-pc --recheck "$disk"
|
||||||
|
fi
|
||||||
|
in_target grub-mkconfig -o /boot/grub/grub.cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
fix_initramfs() {
|
||||||
|
say "Régénération de l'initramfs (mkinitcpio -P)…"
|
||||||
|
echo " NB : les avertissements « Possibly missing firmware for module … » sont"
|
||||||
|
echo " NORMAUX sur Arch : firmware de matériel exotique (cartes SCSI/fibre…)"
|
||||||
|
echo " non fourni par linux-firmware. Sans effet, sauf si tu AS ce matériel."
|
||||||
|
in_target mkinitcpio -P
|
||||||
|
}
|
||||||
|
|
||||||
|
fix_pacman() {
|
||||||
|
say "Réparation de pacman…"
|
||||||
|
rm -f "$MNT/var/lib/pacman/db.lck" && echo " verrou db.lck retiré (s'il existait)"
|
||||||
|
in_target pacman-key --init
|
||||||
|
in_target pacman-key --populate archlinux
|
||||||
|
if [ -n "$DO_UPDATE" ]; then
|
||||||
|
say "Mise à jour complète (pacman -Syu)…"
|
||||||
|
[ -z "$SELF" ] && cp -f /etc/resolv.conf "$MNT/etc/resolv.conf" 2>/dev/null
|
||||||
|
in_target pacman -Syu --noconfirm
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
fix_all() { fix_initramfs && fix_grub; }
|
||||||
|
|
||||||
|
# ── Sous-commandes (pilotées par fws-recovery-gui) ───────────────────────────
|
||||||
|
case "${1:-}" in
|
||||||
|
scan)
|
||||||
|
scan_installs
|
||||||
|
exit 0 ;;
|
||||||
|
repair)
|
||||||
|
action="${2:?action manquante}"; dev="${3:?périphérique manquant}"
|
||||||
|
trap cleanup EXIT
|
||||||
|
say "Montage de $dev…"
|
||||||
|
mount_target "$dev" || { warn "montage impossible"; exit 1; }
|
||||||
|
rc=0
|
||||||
|
case "$action" in
|
||||||
|
grub) fix_grub || rc=$? ;;
|
||||||
|
initramfs) fix_initramfs || rc=$? ;;
|
||||||
|
pacman) fix_pacman || rc=$? ;;
|
||||||
|
pacman-update) DO_UPDATE=1; fix_pacman || rc=$? ;;
|
||||||
|
all) fix_all || rc=$? ;;
|
||||||
|
*) warn "action inconnue : $action"; rc=2 ;;
|
||||||
|
esac
|
||||||
|
cleanup; trap - EXIT
|
||||||
|
[ "$rc" -eq 0 ] && say "Terminé ✔" || say "Échec (code $rc)"
|
||||||
|
exit "$rc" ;;
|
||||||
|
shell)
|
||||||
|
dev="${2:?périphérique manquant}"
|
||||||
|
trap cleanup EXIT
|
||||||
|
mount_target "$dev" || { warn "montage impossible"; exit 1; }
|
||||||
|
if [ -z "$SELF" ] && [ -n "${DISPLAY:-}" ] && command -v xterm >/dev/null 2>&1; then
|
||||||
|
xterm -title "FWS — terminal dans le système (chroot)" \
|
||||||
|
-e arch-chroot "$MNT" /bin/bash
|
||||||
|
else
|
||||||
|
in_target /bin/bash
|
||||||
|
fi
|
||||||
|
cleanup; trap - EXIT
|
||||||
|
exit 0 ;;
|
||||||
|
""|tui) : ;; # assistant console ci-dessous
|
||||||
|
*) echo "usage : fws-recovery [scan|repair <action> <dev>|shell <dev>]"; exit 2 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# ── Assistant CONSOLE (repli sans X / usage manuel) ──────────────────────────
|
||||||
|
trap cleanup EXIT
|
||||||
|
say "Recherche des installations FWS…"
|
||||||
|
mapfile -t CANDIDATES < <(scan_installs)
|
||||||
|
|
||||||
|
if [ "${#CANDIDATES[@]}" -eq 0 ]; then
|
||||||
|
warn "Aucune installation FWS trouvée. Disques visibles :"
|
||||||
|
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS
|
||||||
|
read -rp "Entrée pour ouvrir un shell (exit pour redémarrer). " _
|
||||||
|
bash; exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo; i=1
|
||||||
|
for c in "${CANDIDATES[@]}"; do echo " $i) ${c%%|*} — ${c##*|}"; i=$((i+1)); done
|
||||||
|
read -rp "Installation à réparer [1] : " pick; pick="${pick:-1}"
|
||||||
|
dev="${CANDIDATES[$((pick-1))]%%|*}"
|
||||||
|
[ -n "$dev" ] || exit 1
|
||||||
|
say "Montage de $dev…"
|
||||||
|
mount_target "$dev" || { warn "montage impossible"; exit 1; }
|
||||||
|
|
||||||
|
while :; do
|
||||||
|
cat <<'MENU'
|
||||||
|
|
||||||
|
╔════════════════════════════════════════════════════════╗
|
||||||
|
║ FWS — Réparation du système ║
|
||||||
|
╠════════════════════════════════════════════════════════╣
|
||||||
|
║ 1) Réinstaller GRUB (le système ne démarre plus) ║
|
||||||
|
║ 2) Régénérer l'initramfs (mkinitcpio -P) ║
|
||||||
|
║ 3) Réparer pacman (verrou, clés, mise à jour) ║
|
||||||
|
║ 4) Tout réparer (initramfs + GRUB) ║
|
||||||
|
║ 5) Ouvrir un shell DANS le système (chroot) ║
|
||||||
|
║ q) Quitter et redémarrer ║
|
||||||
|
╚════════════════════════════════════════════════════════╝
|
||||||
|
MENU
|
||||||
|
read -rp " Choix : " choice
|
||||||
|
case "$choice" in
|
||||||
|
1) fix_grub ;;
|
||||||
|
2) fix_initramfs ;;
|
||||||
|
3) DO_UPDATE=""
|
||||||
|
read -rp " Mise à jour complète aussi (réseau) ? [o/N] " _u
|
||||||
|
case "$_u" in o|O|oui) DO_UPDATE=1 ;; esac
|
||||||
|
fix_pacman ;;
|
||||||
|
4) fix_all ;;
|
||||||
|
5) say "Shell chroot — « exit » pour revenir au menu."
|
||||||
|
in_target /bin/bash ;;
|
||||||
|
q|Q) break ;;
|
||||||
|
*) warn "choix inconnu" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
cleanup; trap - EXIT
|
||||||
|
say "Réparation terminée."
|
||||||
|
read -rp "Redémarrer maintenant ? [O/n] " _r
|
||||||
|
case "$_r" in n|N) bash ;; *) systemctl reboot ;; esac
|
||||||
+269
@@ -0,0 +1,269 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# FWS — application GRAPHIQUE de réparation du système (mode « Réparer FWS »
|
||||||
|
# du live). GTK3 : le live embarque python-gobject/gtk3 via Anaconda.
|
||||||
|
#
|
||||||
|
# Pilote le MOTEUR fws-recovery en sous-commandes :
|
||||||
|
# fws-recovery scan → détecte les installations FWS
|
||||||
|
# fws-recovery repair <action> <dev> → répare (sortie streamée ici)
|
||||||
|
# fws-recovery shell <dev> → terminal chroot (xterm)
|
||||||
|
#
|
||||||
|
# Deux contextes de lancement :
|
||||||
|
# • mode « Réparer FWS » (live OU entrée GRUB de l'OS installé) : session
|
||||||
|
# root via fws-recovery-session → le moteur est appelé directement ;
|
||||||
|
# • menu d'applications de l'OS installé : lancée en UTILISATEUR → chaque
|
||||||
|
# appel du moteur passe par pkexec (politique org.fws.recovery,
|
||||||
|
# auth_admin_keep : un seul mot de passe pour toute la session).
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import gi
|
||||||
|
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
from gi.repository import GLib, Gtk, Pango # noqa: E402
|
||||||
|
|
||||||
|
GLib.set_prgname("fws-recovery-gui")
|
||||||
|
|
||||||
|
_ENGINE = "/usr/local/bin/fws-recovery"
|
||||||
|
|
||||||
|
|
||||||
|
def _engine_cmd(args):
|
||||||
|
"""Commande moteur, élevée via pkexec si on n'est pas root."""
|
||||||
|
cmd = [_ENGINE] + args
|
||||||
|
if os.geteuid() != 0:
|
||||||
|
cmd = ["pkexec"] + cmd
|
||||||
|
return cmd
|
||||||
|
|
||||||
|
_ACTIONS = [
|
||||||
|
("grub", "Réinstaller GRUB",
|
||||||
|
"le système ne démarre plus / « Operating System not found »"),
|
||||||
|
("initramfs", "Régénérer l'initramfs",
|
||||||
|
"kernel panic au démarrage, module manquant (mkinitcpio -P)"),
|
||||||
|
("pacman", "Réparer pacman",
|
||||||
|
"verrou bloqué, clés de signature cassées"),
|
||||||
|
("pacman-update", "Réparer pacman + mise à jour complète",
|
||||||
|
"idem, puis pacman -Syu (réseau requis)"),
|
||||||
|
("all", "Tout réparer",
|
||||||
|
"initramfs puis GRUB — le réflexe « ça ne boote plus »"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class RecoveryWindow(Gtk.Window):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(title="FWS — Réparation du système")
|
||||||
|
self.set_icon_name("fws-recovery")
|
||||||
|
self.set_default_size(720, 560)
|
||||||
|
self.set_position(Gtk.WindowPosition.CENTER)
|
||||||
|
self.set_border_width(18)
|
||||||
|
self.connect("destroy", Gtk.main_quit)
|
||||||
|
|
||||||
|
self._proc = None
|
||||||
|
self._installs = [] # [(dev, nom), …]
|
||||||
|
self._action_buttons = []
|
||||||
|
|
||||||
|
root = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
|
||||||
|
self.add(root)
|
||||||
|
|
||||||
|
title = Gtk.Label()
|
||||||
|
title.set_markup("<span size='x-large' weight='bold'>Réparation du système FWS</span>")
|
||||||
|
root.pack_start(title, False, False, 0)
|
||||||
|
|
||||||
|
self._status = Gtk.Label(label="Recherche des installations FWS sur les disques…")
|
||||||
|
self._status.set_xalign(0)
|
||||||
|
root.pack_start(self._status, False, False, 0)
|
||||||
|
|
||||||
|
# Sélecteur d'installation (rempli après le scan).
|
||||||
|
picker_row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
|
||||||
|
picker_row.pack_start(Gtk.Label(label="Système à réparer :"), False, False, 0)
|
||||||
|
self._picker = Gtk.ComboBoxText()
|
||||||
|
picker_row.pack_start(self._picker, True, True, 0)
|
||||||
|
root.pack_start(picker_row, False, False, 0)
|
||||||
|
|
||||||
|
# Boutons d'action (style fws-hello : titre + description).
|
||||||
|
for key, label, desc in _ACTIONS:
|
||||||
|
button = Gtk.Button()
|
||||||
|
inner = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2)
|
||||||
|
lab = Gtk.Label()
|
||||||
|
lab.set_markup(f"<b>{label}</b>")
|
||||||
|
lab.set_xalign(0)
|
||||||
|
sub = Gtk.Label(label=desc)
|
||||||
|
sub.set_xalign(0)
|
||||||
|
sub.get_style_context().add_class("dim-label")
|
||||||
|
inner.pack_start(lab, False, False, 0)
|
||||||
|
inner.pack_start(sub, False, False, 0)
|
||||||
|
button.add(inner)
|
||||||
|
button.connect("clicked", self._on_action, key)
|
||||||
|
root.pack_start(button, False, False, 0)
|
||||||
|
self._action_buttons.append(button)
|
||||||
|
|
||||||
|
shell_btn = Gtk.Button(label="Ouvrir un terminal dans le système (chroot)")
|
||||||
|
shell_btn.connect("clicked", self._on_shell)
|
||||||
|
root.pack_start(shell_btn, False, False, 0)
|
||||||
|
self._action_buttons.append(shell_btn)
|
||||||
|
|
||||||
|
# Journal (sortie du moteur, en continu).
|
||||||
|
frame = Gtk.Frame(label="Journal")
|
||||||
|
scroll = Gtk.ScrolledWindow()
|
||||||
|
scroll.set_min_content_height(150)
|
||||||
|
self._log = Gtk.TextView()
|
||||||
|
self._log.set_editable(False)
|
||||||
|
self._log.set_cursor_visible(False)
|
||||||
|
self._log.set_monospace(True)
|
||||||
|
self._log.modify_font(Pango.FontDescription("Monospace 9"))
|
||||||
|
scroll.add(self._log)
|
||||||
|
frame.add(scroll)
|
||||||
|
root.pack_start(frame, True, True, 0)
|
||||||
|
|
||||||
|
# Pied : redémarrer / éteindre.
|
||||||
|
footer = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
|
||||||
|
reboot = Gtk.Button(label="Redémarrer")
|
||||||
|
reboot.connect("clicked", lambda _b: self._power("reboot"))
|
||||||
|
poweroff = Gtk.Button(label="Éteindre")
|
||||||
|
poweroff.connect("clicked", lambda _b: self._power("poweroff"))
|
||||||
|
footer.pack_end(reboot, False, False, 0)
|
||||||
|
footer.pack_end(poweroff, False, False, 0)
|
||||||
|
root.pack_start(footer, False, False, 0)
|
||||||
|
|
||||||
|
self._set_busy(True)
|
||||||
|
GLib.idle_add(self._start_scan)
|
||||||
|
|
||||||
|
# ── Utilitaires ─────────────────────────────────────────────────────────
|
||||||
|
def _append(self, text):
|
||||||
|
# Rebranding d'affichage : les outils sous-jacents parlent
|
||||||
|
# d'« Arch Linux » — pas le journal FWS. (Les identifiants techniques
|
||||||
|
# comme archlinux-keyring restent tels quels : ce sont de vrais noms
|
||||||
|
# de fichiers/paquets, les masquer fausserait le diagnostic.)
|
||||||
|
text = text.replace("Arch Linux", "FWS").replace("Fedora", "FWS")
|
||||||
|
buf = self._log.get_buffer()
|
||||||
|
buf.insert(buf.get_end_iter(), text)
|
||||||
|
mark = buf.create_mark(None, buf.get_end_iter(), False)
|
||||||
|
self._log.scroll_mark_onscreen(mark)
|
||||||
|
buf.delete_mark(mark)
|
||||||
|
|
||||||
|
def _set_busy(self, busy):
|
||||||
|
for b in self._action_buttons:
|
||||||
|
b.set_sensitive(not busy and bool(self._installs))
|
||||||
|
# Sensible même avec UNE seule install : un combo grisé donne
|
||||||
|
# l'impression que TOUTE l'app est désactivée (retour utilisateur).
|
||||||
|
self._picker.set_sensitive(not busy and bool(self._installs))
|
||||||
|
|
||||||
|
def _selected_dev(self):
|
||||||
|
idx = self._picker.get_active()
|
||||||
|
if 0 <= idx < len(self._installs):
|
||||||
|
return self._installs[idx][0]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _power(self, what):
|
||||||
|
subprocess.run(["systemctl", what], check=False)
|
||||||
|
|
||||||
|
# ── Scan des installations ──────────────────────────────────────────────
|
||||||
|
def _start_scan(self):
|
||||||
|
self._run_engine(["scan"], self._scan_done, quiet=True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _scan_done(self, rc, output):
|
||||||
|
self._installs = []
|
||||||
|
for line in output.splitlines():
|
||||||
|
if "|" in line:
|
||||||
|
dev, _, name = line.partition("|")
|
||||||
|
self._installs.append((dev.strip(), name.strip()))
|
||||||
|
self._picker.append_text(f"{name.strip()} — {dev.strip()}")
|
||||||
|
if self._installs:
|
||||||
|
self._picker.set_active(0)
|
||||||
|
n = len(self._installs)
|
||||||
|
self._status.set_markup(
|
||||||
|
f"<b>{n}</b> installation{'s' if n > 1 else ''} FWS détectée{'s' if n > 1 else ''}."
|
||||||
|
" Choisis une réparation :")
|
||||||
|
elif rc in (126, 127):
|
||||||
|
# pkexec : authentification annulée ou refusée.
|
||||||
|
self._status.set_markup(
|
||||||
|
"<b>Authentification annulée.</b> Ferme puis relance"
|
||||||
|
" l'application pour réessayer.")
|
||||||
|
else:
|
||||||
|
self._status.set_markup(
|
||||||
|
"<b>Aucune installation FWS détectée.</b> Vérifie les disques"
|
||||||
|
" (terminal ci-dessous) ou redémarre.")
|
||||||
|
self._append("Aucune installation FWS trouvée.\n\n"
|
||||||
|
+ subprocess.run(["lsblk", "-o", "NAME,SIZE,TYPE,FSTYPE"],
|
||||||
|
capture_output=True, text=True,
|
||||||
|
check=False).stdout)
|
||||||
|
self._set_busy(False)
|
||||||
|
# Le terminal reste utile même sans installation détectée (diagnostic).
|
||||||
|
self._action_buttons[-1].set_sensitive(True)
|
||||||
|
|
||||||
|
# ── Exécution du moteur avec sortie en continu ──────────────────────────
|
||||||
|
def _run_engine(self, args, done_cb, quiet=False):
|
||||||
|
self._set_busy(True)
|
||||||
|
self._proc = subprocess.Popen(
|
||||||
|
_engine_cmd(args), stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT, text=True, bufsize=1)
|
||||||
|
chunks = []
|
||||||
|
|
||||||
|
def on_io(source, _cond):
|
||||||
|
line = source.readline()
|
||||||
|
if line:
|
||||||
|
chunks.append(line)
|
||||||
|
if not quiet:
|
||||||
|
self._append(line)
|
||||||
|
return True
|
||||||
|
rc = self._proc.wait()
|
||||||
|
done_cb(rc, "".join(chunks))
|
||||||
|
return False
|
||||||
|
|
||||||
|
GLib.io_add_watch(self._proc.stdout,
|
||||||
|
GLib.IO_IN | GLib.IO_HUP, on_io)
|
||||||
|
|
||||||
|
def _on_action(self, _button, key):
|
||||||
|
dev = self._selected_dev()
|
||||||
|
if not dev:
|
||||||
|
return
|
||||||
|
label = next(lab for k, lab, _d in _ACTIONS if k == key)
|
||||||
|
self._status.set_markup(f"<b>{label}</b> sur {dev} — en cours…")
|
||||||
|
self._append(f"\n───── {label} ({dev}) ─────\n")
|
||||||
|
self._run_engine(["repair", key, dev], self._action_done)
|
||||||
|
|
||||||
|
def _action_done(self, rc, _output):
|
||||||
|
if rc == 0:
|
||||||
|
self._status.set_markup("<b>Terminé ✔</b> — autre réparation, ou redémarre.")
|
||||||
|
elif rc in (126, 127):
|
||||||
|
self._status.set_markup(
|
||||||
|
"<b>Authentification annulée</b> — rien n'a été modifié.")
|
||||||
|
else:
|
||||||
|
self._status.set_markup(
|
||||||
|
f"<b>Échec (code {rc})</b> — détail dans le journal ci-dessous.")
|
||||||
|
self._set_busy(False)
|
||||||
|
|
||||||
|
def _on_shell(self, _button):
|
||||||
|
dev = self._selected_dev()
|
||||||
|
if dev and os.geteuid() == 0:
|
||||||
|
subprocess.Popen([_ENGINE, "shell", dev])
|
||||||
|
elif dev:
|
||||||
|
# pkexec purge DISPLAY → le moteur ouvre le chroot DANS cet xterm
|
||||||
|
# (lancé en utilisateur, donc visible sur X11 comme Wayland).
|
||||||
|
subprocess.Popen(["xterm", "-title",
|
||||||
|
"FWS — terminal dans le système (chroot)",
|
||||||
|
"-e", "pkexec", _ENGINE, "shell", dev])
|
||||||
|
elif shutil.which("xterm"):
|
||||||
|
subprocess.Popen(["xterm", "-title", "FWS — terminal"])
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if not Gtk.init_check()[0]:
|
||||||
|
# Pas d'affichage : le .zprofile retombera sur l'assistant console.
|
||||||
|
return 1
|
||||||
|
if os.geteuid() != 0 and not shutil.which("pkexec"):
|
||||||
|
dlg = Gtk.MessageDialog(message_type=Gtk.MessageType.ERROR,
|
||||||
|
buttons=Gtk.ButtonsType.CLOSE,
|
||||||
|
text="pkexec introuvable : lance fws-recovery-gui en root.")
|
||||||
|
dlg.run()
|
||||||
|
return 1
|
||||||
|
win = RecoveryWindow()
|
||||||
|
win.show_all()
|
||||||
|
Gtk.main()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# FWS — session X du mode réparation. Lancé par startx (cf. /root/.zprofile,
|
||||||
|
# entrée de boot « Réparer FWS »). Openbox minimal + l'application GTK
|
||||||
|
# fws-recovery-gui. Si la GUI meurt, openbox reste pour un xterm de secours.
|
||||||
|
xsetroot -solid "#1a1a2e" 2>/dev/null
|
||||||
|
openbox &
|
||||||
|
fws-recovery-gui
|
||||||
|
exec openbox
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================
|
||||||
|
# fws-secureboot-setup — prépare FWS à booter SIGNÉ sous Secure Boot,
|
||||||
|
# SANS activer le lockdown noyau (→ hibernation préservée).
|
||||||
|
#
|
||||||
|
# POURQUOI : Vanguard exige Secure Boot (réglage firmware GLOBAL) → FWS doit
|
||||||
|
# lui aussi booter sous SB, sinon le firmware refuse de le lancer. Fait vérifié :
|
||||||
|
# sur noyau mainline Arch, activer Secure Boot n'arme PAS le lockdown (le patch
|
||||||
|
# Fedora/Ubuntu qui lie les deux a été refusé upstream). Signature (firmware) et
|
||||||
|
# lockdown (LSM runtime) sont ORTHOGONAUX → on peut signer ET hiberner.
|
||||||
|
#
|
||||||
|
# VOIE RETENUE : sbctl (clés « db » custom) en CONSERVANT les clés Microsoft
|
||||||
|
# (--microsoft), pour que Windows et les OpROM continuent de booter.
|
||||||
|
#
|
||||||
|
# ⚠ ORDRE IMPÉRATIF : tout SIGNER avant d'activer Secure Boot au firmware.
|
||||||
|
# Détails, alternative shim+MOK et vérifications : docs §4.
|
||||||
|
# ============================================================
|
||||||
|
set -u
|
||||||
|
[ "$(id -u)" -eq 0 ] || { echo "root requis (sudo fws-secureboot-setup)"; exit 1; }
|
||||||
|
[ -d /sys/firmware/efi ] || { echo "Système non-UEFI : Secure Boot sans objet."; exit 1; }
|
||||||
|
|
||||||
|
log() { printf '\e[36m==>\e[0m %s\n' "$*"; }
|
||||||
|
warn() { printf '\e[33m[!] %s\e[0m\n' "$*"; }
|
||||||
|
die() { printf '\e[31m[ERREUR] %s\e[0m\n' "$*"; exit 1; }
|
||||||
|
|
||||||
|
command -v sbctl >/dev/null 2>&1 || { log "Installation de sbctl…"; pacman -S --noconfirm --needed sbctl || die "sbctl introuvable (réseau ?)"; }
|
||||||
|
command -v objdump >/dev/null 2>&1 || pacman -S --noconfirm --needed binutils || true
|
||||||
|
|
||||||
|
# Localiser l'ESP et grubx64.efi. IDENTITÉ = FWS, cohérente avec le kickstart
|
||||||
|
# install_grub (--bootloader-id=FWS + --removable → \EFI\FWS\ ET \EFI\BOOT\).
|
||||||
|
# Signer un \EFI\GRUB\ séparé ne servirait à RIEN : le firmware charge \EFI\FWS\
|
||||||
|
# (ou le secours \EFI\BOOT\BOOTX64.EFI) → ce sont EUX qu'il faut signer.
|
||||||
|
ESP_DIR=/boot/efi; [ -d "$ESP_DIR/EFI" ] || ESP_DIR=/boot
|
||||||
|
GRUB_EFI="$ESP_DIR/EFI/FWS/grubx64.efi"
|
||||||
|
[ -f "$GRUB_EFI" ] || GRUB_EFI="$(find "$ESP_DIR/EFI" -iname 'grubx64.efi' 2>/dev/null | head -1)"
|
||||||
|
[ -n "$GRUB_EFI" ] && [ -f "$GRUB_EFI" ] || die "grubx64.efi introuvable sous $ESP_DIR/EFI"
|
||||||
|
BOOTX64="$ESP_DIR/EFI/BOOT/BOOTX64.EFI" # chemin de secours removable (à signer aussi)
|
||||||
|
|
||||||
|
# 1) GRUB doit embarquer SBAT (+ shim_lock), sinon shim refusera de le charger.
|
||||||
|
# Un grub-install « nu » produit un binaire sans .sbat → on réinstalle EN FWS
|
||||||
|
# (et on régénère le secours removable \EFI\BOOT\BOOTX64.EFI avec le même SBAT).
|
||||||
|
if ! command -v objdump >/dev/null 2>&1; then
|
||||||
|
warn "objdump (binutils) absent → SBAT non vérifiable, réinstallation de GRUB SAUTÉE (on suppose GRUB déjà correct)."
|
||||||
|
elif ! objdump -h "$GRUB_EFI" 2>/dev/null | grep -q '\.sbat'; then
|
||||||
|
[ -f /usr/share/grub/sbat.csv ] || die "sbat.csv introuvable — impossible d'ajouter SBAT"
|
||||||
|
warn "grubx64.efi sans section .sbat → réinstallation de GRUB (--bootloader-id=FWS + --sbat)."
|
||||||
|
GRUB_MODULES='normal search part_gpt part_msdos fat ext2 configfile linux echo test true loadenv all_video efi_gop tpm'
|
||||||
|
grub-install --target=x86_64-efi --efi-directory="$ESP_DIR" --bootloader-id=FWS \
|
||||||
|
--sbat /usr/share/grub/sbat.csv --modules="$GRUB_MODULES" \
|
||||||
|
|| die "grub-install (FWS) a échoué"
|
||||||
|
grub-install --target=x86_64-efi --efi-directory="$ESP_DIR" --bootloader-id=FWS --removable \
|
||||||
|
--sbat /usr/share/grub/sbat.csv --modules="$GRUB_MODULES" \
|
||||||
|
|| warn "grub-install --removable (secours) a échoué"
|
||||||
|
GRUB_EFI="$ESP_DIR/EFI/FWS/grubx64.efi"
|
||||||
|
objdump -h "$GRUB_EFI" 2>/dev/null | grep -q '\.sbat' || die "SBAT toujours absent après réinstallation"
|
||||||
|
grub-mkconfig -o /boot/grub/grub.cfg 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2) Clés sbctl (idempotent).
|
||||||
|
if ! sbctl status 2>/dev/null | grep -qi 'setup mode.*✓\|setup mode.*enabled\|Setup Mode.*Enabled'; then
|
||||||
|
warn "Firmware pas en Setup Mode : « enroll-keys » échouera. Mets le firmware en Setup Mode (efface les clés d'usine), puis relance. On continue quand même la génération/signature."
|
||||||
|
fi
|
||||||
|
[ -d /var/lib/sbctl/keys ] || sbctl create-keys || die "sbctl create-keys a échoué"
|
||||||
|
|
||||||
|
# 3) Enrôler NOS clés + CONSERVER Microsoft (sinon Windows/OpROM cassés).
|
||||||
|
log "Enrôlement des clés (avec Microsoft)…"
|
||||||
|
sbctl enroll-keys --microsoft \
|
||||||
|
|| warn "enroll-keys a échoué (firmware pas en Setup Mode ?) — à refaire une fois en Setup Mode."
|
||||||
|
|
||||||
|
# 4) Signer les TROIS chemins réellement chargés par le firmware + les noyaux :
|
||||||
|
# \EFI\FWS\grubx64.efi, le secours \EFI\BOOT\BOOTX64.EFI, et chaque vmlinuz.
|
||||||
|
log "Signature de GRUB (FWS + secours removable) et des noyaux…"
|
||||||
|
sbctl sign -s "$GRUB_EFI" || die "signature de $GRUB_EFI échouée"
|
||||||
|
[ -f "$BOOTX64" ] && { sbctl sign -s "$BOOTX64" || warn "signature de $BOOTX64 échouée"; }
|
||||||
|
for k in /boot/vmlinuz-linux /boot/vmlinuz-linux-lts /boot/vmlinuz-linux-zen /boot/vmlinuz-linux-hardened; do
|
||||||
|
[ -f "$k" ] && { sbctl sign -s "$k" || warn "signature de $k échouée"; }
|
||||||
|
done
|
||||||
|
sbctl verify || warn "sbctl verify signale des binaires non signés (voir ci-dessus)."
|
||||||
|
|
||||||
|
cat <<'EOF'
|
||||||
|
|
||||||
|
=== Étapes MANUELLES restantes (firmware) ===
|
||||||
|
1. Redémarre dans le setup du firmware (UEFI/BIOS).
|
||||||
|
2. Active Secure Boot (les clés viennent d'être enrôlées, Microsoft conservé).
|
||||||
|
3. Au boot FWS suivant, vérifie :
|
||||||
|
bootctl status | grep 'Secure Boot' → Secure Boot: enabled
|
||||||
|
cat /sys/kernel/security/lockdown → [none] (hibernation OK)
|
||||||
|
cat /sys/power/state → contient « disk »
|
||||||
|
Puis : fws-gameboot doctor
|
||||||
|
|
||||||
|
Re-signature AUTOMATIQUE après chaque MAJ noyau/GRUB via le hook pacman
|
||||||
|
/usr/share/libalpm/hooks/95-fws-secureboot-sign.hook (+ le hook natif de sbctl).
|
||||||
|
EOF
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================================
|
||||||
|
# FWS — détection matérielle + drivers + stack « remplacement de Windows ».
|
||||||
|
#
|
||||||
|
# Appelé par le %post du kickstart : fws-setup-hardware <bureau>
|
||||||
|
# (<bureau> = cli|gnome|kde|xfce|hyprland). Réseau requis ; CHAQUE étape est
|
||||||
|
# NON bloquante (pas de set -e) : un paquet manquant ou pas de réseau ne casse
|
||||||
|
# pas l'installation.
|
||||||
|
#
|
||||||
|
# Installe :
|
||||||
|
# - les drivers GPU détectés via lspci (Intel / AMD / NVIDIA) + Mesa/Vulkan,
|
||||||
|
# - les outils invité selon l'hyperviseur (VMware/VirtualBox/QEMU/Hyper-V),
|
||||||
|
# - le son/micro/casque (PipeWire), le Bluetooth (BlueZ), les conteneurs
|
||||||
|
# (Podman),
|
||||||
|
# - et — si un bureau est choisi — la stack « comme Windows » : Steam, Wine,
|
||||||
|
# VR (Monado/OpenXR → SteamVR pour HTC Vive / Valve Index ; Oculus n'a pas
|
||||||
|
# de support Linux officiel), winboat-bin (apps Windows).
|
||||||
|
# ============================================================================
|
||||||
|
DESK="${1:-cli}"
|
||||||
|
log() { echo "[FWS-hw] $*"; }
|
||||||
|
|
||||||
|
# ── 1) Drivers GPU (détection lspci) ─────────────────────────────────────────
|
||||||
|
DRIVERS="mesa"
|
||||||
|
GPU="$(lspci 2>/dev/null | grep -iE 'VGA compatible controller|3D controller|Display controller')"
|
||||||
|
case "$GPU" in
|
||||||
|
*NVIDIA*|*GeForce*) DRIVERS="$DRIVERS nvidia-dkms nvidia-utils linux-headers dkms" ;;
|
||||||
|
*AMD*|*ATI*|*Radeon*) DRIVERS="$DRIVERS vulkan-radeon libva-mesa-driver" ;;
|
||||||
|
*Intel*) DRIVERS="$DRIVERS vulkan-intel intel-media-driver" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# ── 2) Outils invité selon l'hyperviseur ─────────────────────────────────────
|
||||||
|
VMSVC=""
|
||||||
|
case "$(systemd-detect-virt 2>/dev/null)" in
|
||||||
|
vmware) DRIVERS="$DRIVERS open-vm-tools"; VMSVC="vmtoolsd vmware-vmblock-fuse" ;;
|
||||||
|
oracle) DRIVERS="$DRIVERS virtualbox-guest-utils"; VMSVC="vboxservice" ;;
|
||||||
|
qemu|kvm) DRIVERS="$DRIVERS qemu-guest-agent spice-vdagent"; VMSVC="qemu-guest-agent spice-vdagentd" ;;
|
||||||
|
microsoft) DRIVERS="$DRIVERS hyperv"; VMSVC="hv_fcopy_daemon hv_kvp_daemon hv_vss_daemon" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# ── 3) Stack de base : son / micro / casque, Bluetooth, luminosité, conteneurs
|
||||||
|
# brightnessctl : réglage de luminosité sans root (via logind) — consommé par
|
||||||
|
# les touches XF86MonBrightness* et la molette du module backlight de waybar.
|
||||||
|
BASE="pipewire pipewire-alsa pipewire-pulse pipewire-jack wireplumber \
|
||||||
|
pavucontrol bluez bluez-utils brightnessctl podman"
|
||||||
|
# Gestionnaire Bluetooth graphique : KDE et GNOME ont le leur ; blueman pour
|
||||||
|
# les bureaux qui n'en fournissent pas (tray + assistant d'appairage).
|
||||||
|
case "$DESK" in
|
||||||
|
hyprland|xfce|i3) BASE="$BASE blueman" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# ── 4) Stack desktop : jeux, Windows-compat, VR (si un bureau est choisi) ────
|
||||||
|
DESKTOP=""
|
||||||
|
if [ "$DESK" != "cli" ]; then
|
||||||
|
# multilib requis (Steam / Wine 32 bits).
|
||||||
|
grep -qE '^\[multilib\]' /etc/pacman.conf \
|
||||||
|
|| printf '\n[multilib]\nInclude = /etc/pacman.d/mirrorlist\n' >> /etc/pacman.conf
|
||||||
|
# mangohud : overlay FPS/perfs en jeu (mangohud %command% dans Steam).
|
||||||
|
DESKTOP="steam wine winetricks wine-mono wine-gecko \
|
||||||
|
lib32-mesa lib32-pipewire monado gamemode \
|
||||||
|
mangohud lib32-mangohud \
|
||||||
|
noto-fonts ttf-liberation"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Sync + filtre aux paquets RÉELLEMENT existants ───────────────────────────
|
||||||
|
# (un nom de paquet manquant/renommé ne doit pas faire échouer tout le reste).
|
||||||
|
pacman -Sy --noconfirm || log "synchro pacman échouée (réseau ?)"
|
||||||
|
VALID=""
|
||||||
|
for p in $DRIVERS $BASE $DESKTOP; do
|
||||||
|
pacman -Si "$p" >/dev/null 2>&1 && VALID="$VALID $p"
|
||||||
|
done
|
||||||
|
if [ -n "$VALID" ]; then
|
||||||
|
log "installation :$VALID"
|
||||||
|
pacman -S --noconfirm --needed $VALID || log "certains paquets non installés"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# NB : winboat-bin (AUR) n'est PAS installé ici (fws-local n'existe pas sur la
|
||||||
|
# cible) : il est intégré à l'image FWS via packages.x86_64 et recopié par le
|
||||||
|
# payload LiveOS d'Anaconda → déjà présent sur le système installé.
|
||||||
|
|
||||||
|
# ── Services matériels ───────────────────────────────────────────────────────
|
||||||
|
[ -n "$VMSVC" ] && systemctl enable $VMSVC 2>/dev/null
|
||||||
|
systemctl enable bluetooth.service 2>/dev/null
|
||||||
|
systemctl enable podman.socket 2>/dev/null
|
||||||
|
exit 0
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# FWS — mise à jour du système en une commande (bouton de la barre, fws-hello,
|
||||||
|
# ou terminal). pacman -Syu + nettoyage du cache + rapport des orphelins.
|
||||||
|
set -u
|
||||||
|
say() { printf '\n\033[1;34m==> %s\033[0m\n' "$*"; }
|
||||||
|
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
|
exec sudo "$0" "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
say "Mise à jour du système (pacman -Syu)…"
|
||||||
|
pacman -Syu || { echo "Échec de la mise à jour (réseau ?)"; read -rp "Entrée pour fermer. " _; exit 1; }
|
||||||
|
|
||||||
|
if command -v paccache >/dev/null 2>&1; then
|
||||||
|
say "Nettoyage du cache des paquets (2 dernières versions gardées)…"
|
||||||
|
paccache -rk2
|
||||||
|
fi
|
||||||
|
|
||||||
|
ORPHANS="$(pacman -Qtdq 2>/dev/null)"
|
||||||
|
if [ -n "$ORPHANS" ]; then
|
||||||
|
say "Paquets orphelins (plus requis par rien) :"
|
||||||
|
echo "$ORPHANS"
|
||||||
|
read -rp "Les supprimer ? [o/N] " _rm
|
||||||
|
case "$_rm" in o|O|oui) echo "$ORPHANS" | pacman -Rns --noconfirm - ;; esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v flatpak >/dev/null 2>&1; then
|
||||||
|
say "Mise à jour des Flatpaks…"
|
||||||
|
flatpak update -y || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
say "Système à jour ✔"
|
||||||
|
read -rp "Entrée pour fermer. " _
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# FWS — indicateur « mises à jour disponibles » pour waybar/polybar.
|
||||||
|
# checkupdates (pacman-contrib) : lit les dépôts SANS toucher la base pacman.
|
||||||
|
# Sortie vide = rien à faire → le module de la barre se cache.
|
||||||
|
command -v checkupdates >/dev/null 2>&1 || exit 0
|
||||||
|
n="$(checkupdates 2>/dev/null | wc -l)"
|
||||||
|
[ "$n" -gt 0 ] && printf ' %s\n' "$n"
|
||||||
|
exit 0
|
||||||
+115
@@ -0,0 +1,115 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================================
|
||||||
|
# FWS — installation des drivers/runtimes VR selon le casque.
|
||||||
|
#
|
||||||
|
# À lancer depuis un terminal (Super+Shift+V sous i3, ou l'entrée
|
||||||
|
# « Installation VR (FWS) » du menu d'applications). Réservé aux sessions
|
||||||
|
# X11 (i3) : SteamVR ne fonctionne PAS sous Wayland/Hyprland.
|
||||||
|
#
|
||||||
|
# Réalité du support Linux (résumé honnête) :
|
||||||
|
# • HTC Vive / Vive Pro / Valve Index (stations lighthouse) → NATIF via
|
||||||
|
# SteamVR. C'est le chemin le plus fiable.
|
||||||
|
# • Meta/Oculus Quest 1-3, Pico → PAS de support officiel ; streaming
|
||||||
|
# Wi-Fi via ALVR (+ SteamVR). ALVR n'est pas dans les dépôts Arch :
|
||||||
|
# le script installe la base et donne la marche à suivre.
|
||||||
|
# • Oculus Rift CV1/S (filaire) → pas de support utilisable.
|
||||||
|
# • Monado : runtime OpenXR libre (déjà proposé par fws-setup-hardware),
|
||||||
|
# utile pour les applis OpenXR natives sans SteamVR.
|
||||||
|
# ============================================================================
|
||||||
|
set -u
|
||||||
|
say() { printf '\n\033[1;34m==> %s\033[0m\n' "$*"; }
|
||||||
|
warn() { printf '\033[1;33m ⚠ %s\033[0m\n' "$*"; }
|
||||||
|
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
|
exec sudo "$0" "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# VM = cul-de-sac VR : pas d'accès direct au GPU ni à l'USB/DisplayPort du
|
||||||
|
# casque, et Steam (steamwebhelper) est INSTABLE en rendu logiciel (llvmpipe).
|
||||||
|
# On prévient au lieu de laisser l'utilisateur croire à une panne de FWS.
|
||||||
|
if systemd-detect-virt --quiet 2>/dev/null; then
|
||||||
|
warn "Machine VIRTUELLE détectée ($(systemd-detect-virt 2>/dev/null))."
|
||||||
|
warn "La VR ne fonctionnera PAS ici : casque et GPU inaccessibles depuis une VM."
|
||||||
|
warn "Steam lui-même peut planter en rendu logiciel (steamwebhelper)."
|
||||||
|
echo " → Fais cette installation sur la machine RÉELLE avec le casque branché."
|
||||||
|
read -rp " Continuer quand même (tests uniquement) ? [o/N] " _ok
|
||||||
|
case "$_ok" in o|O|oui) ;; *) exit 0 ;; esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# lib32 Vulkan selon le GPU (indispensable à SteamVR, qui est 32/64 bits).
|
||||||
|
GPU="$(lspci 2>/dev/null | grep -iE 'VGA compatible controller|3D controller|Display controller')"
|
||||||
|
LIB32="lib32-mesa"
|
||||||
|
case "$GPU" in
|
||||||
|
*NVIDIA*|*GeForce*) LIB32="$LIB32 lib32-nvidia-utils" ;;
|
||||||
|
*AMD*|*ATI*|*Radeon*) LIB32="$LIB32 lib32-vulkan-radeon" ;;
|
||||||
|
*Intel*) LIB32="$LIB32 lib32-vulkan-intel" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
install_pkgs() {
|
||||||
|
# multilib requis pour Steam/lib32.
|
||||||
|
grep -qE '^\[multilib\]' /etc/pacman.conf \
|
||||||
|
|| printf '\n[multilib]\nInclude = /etc/pacman.d/mirrorlist\n' >> /etc/pacman.conf
|
||||||
|
pacman -Sy --noconfirm || { warn "synchro pacman échouée (réseau ?)"; return 1; }
|
||||||
|
local valid="" p
|
||||||
|
for p in "$@"; do
|
||||||
|
pacman -Si "$p" >/dev/null 2>&1 && valid="$valid $p" || warn "paquet inconnu ignoré : $p"
|
||||||
|
done
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
pacman -S --noconfirm --needed $valid
|
||||||
|
}
|
||||||
|
|
||||||
|
steamvr_base() {
|
||||||
|
say "Installation de la base SteamVR (Steam + Vulkan 32 bits : $LIB32)…"
|
||||||
|
# ttf-liberation : polices attendues par l'interface Steam (évite des
|
||||||
|
# rendus cassés/plantages cosmétiques sur install minimale).
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
install_pkgs steam $LIB32 ttf-liberation || return 1
|
||||||
|
say "Base installée. Étapes suivantes :"
|
||||||
|
echo " 1. Lance Steam, connecte-toi, puis installe « SteamVR » (Bibliothèque → Outils)."
|
||||||
|
echo " 2. Branche le casque et les stations, puis lance SteamVR."
|
||||||
|
echo " 3. Premier lancement : SteamVR peut demander à ajuster les règles udev — accepte."
|
||||||
|
say "Si Steam PLANTE (fenêtre qui se ferme, souvent steamwebhelper) :"
|
||||||
|
echo " • Vérifie l'espace disque (SteamVR ≈ 5 Go) : df -h ~"
|
||||||
|
echo " • GPU faible / VM / rendu logiciel — lance :"
|
||||||
|
echo " steam -cef-disable-gpu -cef-disable-gpu-compositing"
|
||||||
|
echo " • Pour voir l'erreur exacte : lance « steam » depuis un terminal,"
|
||||||
|
echo " ou regarde ~/.steam/steam/logs/ (console_log.txt, bootstrap_log.txt)."
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-menu}" in
|
||||||
|
menu)
|
||||||
|
cat <<'MENU'
|
||||||
|
|
||||||
|
╔══════════════════════════════════════════════════════════╗
|
||||||
|
║ FWS — Installation VR (session X11 requise) ║
|
||||||
|
╠══════════════════════════════════════════════════════════╣
|
||||||
|
║ 1) HTC Vive / Vive Pro / Valve Index (natif, SteamVR) ║
|
||||||
|
║ 2) Meta Quest / Pico (streaming ALVR) ║
|
||||||
|
║ 3) Monado (runtime OpenXR libre, sans SteamVR) ║
|
||||||
|
║ q) Quitter ║
|
||||||
|
╚══════════════════════════════════════════════════════════╝
|
||||||
|
MENU
|
||||||
|
read -rp " Choix : " choice
|
||||||
|
exec "$0" "$choice"
|
||||||
|
;;
|
||||||
|
1|vive|index)
|
||||||
|
steamvr_base || exit 1
|
||||||
|
;;
|
||||||
|
2|quest|pico)
|
||||||
|
steamvr_base || exit 1
|
||||||
|
say "Streaming Quest/Pico : il faut AUSSI ALVR (pas dans les dépôts Arch) :"
|
||||||
|
echo " 1. Télécharge le .tar.gz « ALVR Launcher » : https://github.com/alvr-org/ALVR/releases"
|
||||||
|
echo " 2. Sur le casque, installe le client ALVR (App Lab / SideQuest)."
|
||||||
|
echo " 3. Lance ALVR sur le PC (même Wi-Fi 5 GHz que le casque) — il pilote SteamVR."
|
||||||
|
;;
|
||||||
|
3|monado)
|
||||||
|
say "Installation de Monado (OpenXR) + outils…"
|
||||||
|
install_pkgs monado || exit 1
|
||||||
|
say "Monado installé. Les applis OpenXR le trouvent via /usr/share/openxr/."
|
||||||
|
;;
|
||||||
|
q|Q) exit 0 ;;
|
||||||
|
*) warn "choix inconnu : ${1}"; exec "$0" menu ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo
|
||||||
|
read -rp "Terminé — appuie sur Entrée pour fermer. " _
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================
|
||||||
|
# fws-windows-bcdfix — rend Windows bootable en exécutant le VRAI `bcdboot`
|
||||||
|
# dans une WinPE lancée UNE FOIS (BootNext), juste après l'install FWS.
|
||||||
|
#
|
||||||
|
# ⚠ NON VALIDÉ SUR MATÉRIEL RÉEL. Voie RECOMMANDÉE par défaut : bcdboot écrit un
|
||||||
|
# BCD correct. Coût : 1 reboot automatique en WinPE, puis retour à FWS
|
||||||
|
# (BootOrder[0]=FWS réasserté par fws-windows-deploy + fws-gameboot-bootfix).
|
||||||
|
#
|
||||||
|
# Mécanique :
|
||||||
|
# 1. Copier la WinPE bootable de l'ISO (bootmgr + BCD WinPE + boot.wim) sur l'ESP.
|
||||||
|
# 2. Injecter winpeshl.ini + fwspe.cmd DANS l'image AMORÇABLE de boot.wim
|
||||||
|
# (Boot Index, typiquement 2 « Setup », + index 1 en ceinture) : au démarrage,
|
||||||
|
# WinPE lance fwspe.cmd → trouve le lecteur Windows (\Windows\System32\
|
||||||
|
# winload.efi) et l'ESP (label WINESP via diskpart) → bcdboot → wpeutil reboot.
|
||||||
|
# 3. Poser une entrée UEFI + BootNext one-shot vers cette WinPE.
|
||||||
|
#
|
||||||
|
# Args : <ISO_MOUNT> <ESP_MOUNT> <WIN_DISK> <ESP_PARTNUM>
|
||||||
|
# ============================================================
|
||||||
|
set -u
|
||||||
|
ISO="${1:-}"; ESP="${2:-}"; WIN_DISK="${3:-}"; ESP_PN="${4:-1}"; DRV="${5:-}"
|
||||||
|
log(){ printf '\e[36m[bcdfix]\e[0m %s\n' "$*"; }
|
||||||
|
die(){ printf '\e[31m[bcdfix] ERREUR : %s\e[0m\n' "$*" >&2; exit 1; }
|
||||||
|
[ -d "$ISO" ] && [ -d "$ESP" ] && [ -b "$WIN_DISK" ] || die "args invalides"
|
||||||
|
command -v wimlib-imagex >/dev/null 2>&1 || die "wimlib absent"
|
||||||
|
|
||||||
|
# 1) Structure de boot WinPE depuis l'ISO (bootmgr + BCD WinPE prêts).
|
||||||
|
log "Staging de la WinPE de l'ISO sur l'ESP…"
|
||||||
|
mkdir -p "$ESP/EFI/Microsoft/Boot" "$ESP/sources"
|
||||||
|
cp -rf "$ISO"/efi/microsoft/boot/. "$ESP/EFI/Microsoft/Boot/" 2>/dev/null || true
|
||||||
|
[ -f "$ESP/EFI/Microsoft/Boot/bootmgfw.efi" ] \
|
||||||
|
|| cp -f "$ISO/efi/boot/bootx64.efi" "$ESP/EFI/Microsoft/Boot/bootmgfw.efi" 2>/dev/null \
|
||||||
|
|| die "bootmgfw.efi WinPE introuvable dans l'ISO"
|
||||||
|
|
||||||
|
# 2) Injecter l'automation dans l'image AMORÇABLE de boot.wim.
|
||||||
|
TMP="$(mktemp -d)"
|
||||||
|
cp -f "$ISO/sources/boot.wim" "$TMP/boot.wim" || die "boot.wim introuvable dans l'ISO"
|
||||||
|
cat > "$TMP/winpeshl.ini" <<'INI'
|
||||||
|
[LaunchApps]
|
||||||
|
"cmd.exe", "/c X:\fwspe.cmd"
|
||||||
|
INI
|
||||||
|
# ⚠ NON VALIDÉ. bcdboot dans WinPE : trouver le lecteur Windows par présence de
|
||||||
|
# winload.efi (NTFS auto-monté), et l'ESP par son label WINESP via diskpart.
|
||||||
|
cat > "$TMP/fwspe.cmd" <<'CMD'
|
||||||
|
@echo off
|
||||||
|
rem Pilote Intel VMD (si injecté) : le charger pour que WinPE VOIE les NVMe.
|
||||||
|
if exist X:\fwsdrv\*.inf for %%I in (X:\fwsdrv\*.inf) do drvload %%I
|
||||||
|
set WIN=
|
||||||
|
for %%D in (C D E F G H I J K L M N O P) do if exist %%D:\Windows\System32\winload.efi set WIN=%%D:
|
||||||
|
echo list volume > X:\dp.txt
|
||||||
|
diskpart /s X:\dp.txt > X:\vol.txt
|
||||||
|
for /f "tokens=2" %%V in ('findstr /i "WINESP" X:\vol.txt') do (
|
||||||
|
echo select volume %%V > X:\dp2.txt
|
||||||
|
echo assign letter=S >> X:\dp2.txt
|
||||||
|
diskpart /s X:\dp2.txt
|
||||||
|
)
|
||||||
|
if not defined WIN goto reboot
|
||||||
|
rem Enregistrer le pilote VMD dans le Windows appliqué (boot device) via DISM.
|
||||||
|
if exist X:\fwsdrv\*.inf dism /image:%WIN%\ /add-driver /driver:X:\fwsdrv /recurse /forceunsigned
|
||||||
|
if exist S:\ ( bcdboot %WIN%\Windows /s S: /f UEFI ) else ( bcdboot %WIN%\Windows /f UEFI )
|
||||||
|
:reboot
|
||||||
|
wpeutil reboot
|
||||||
|
CMD
|
||||||
|
# Pilote Intel VMD à embarquer dans la WinPE (pour drvload + dism) ?
|
||||||
|
if [ -n "$DRV" ] && [ -d "$DRV" ]; then
|
||||||
|
mkdir -p "$TMP/fwsdrv"; cp -rf "$DRV"/. "$TMP/fwsdrv/" 2>/dev/null || true
|
||||||
|
log "Pilote Intel VMD embarqué dans la WinPE (drvload + dism)."
|
||||||
|
fi
|
||||||
|
# Image amorcée = « Boot Index » (typiquement 2) ; on injecte aussi l'index 1 en ceinture.
|
||||||
|
BOOTIDX="$(wimlib-imagex info "$TMP/boot.wim" 2>/dev/null | sed -n 's/^Boot Index:[[:space:]]*//p' | head -1)"
|
||||||
|
[ -n "$BOOTIDX" ] || BOOTIDX=2
|
||||||
|
INJECTED=0
|
||||||
|
for i in $BOOTIDX 1; do
|
||||||
|
wimlib-imagex update "$TMP/boot.wim" "$i" \
|
||||||
|
--command="add '$TMP/winpeshl.ini' /Windows/System32/winpeshl.ini" 2>/dev/null \
|
||||||
|
&& wimlib-imagex update "$TMP/boot.wim" "$i" \
|
||||||
|
--command="add '$TMP/fwspe.cmd' /fwspe.cmd" 2>/dev/null \
|
||||||
|
&& INJECTED=1
|
||||||
|
[ -d "$TMP/fwsdrv" ] && wimlib-imagex update "$TMP/boot.wim" "$i" \
|
||||||
|
--command="add '$TMP/fwsdrv' /fwsdrv" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
[ "$INJECTED" = 1 ] || die "injection winpeshl/fwspe dans boot.wim échouée"
|
||||||
|
cp -f "$TMP/boot.wim" "$ESP/sources/boot.wim" || die "copie boot.wim → ESP échouée"
|
||||||
|
rm -rf "$TMP"
|
||||||
|
|
||||||
|
# 3) Entrée UEFI one-shot vers la WinPE stagée.
|
||||||
|
log "Création de l'entrée UEFI WinPE (one-shot BootNext)…"
|
||||||
|
efibootmgr -c -d "$WIN_DISK" -p "$ESP_PN" -L 'FWS WinPE (bcdboot)' \
|
||||||
|
-l '\EFI\Microsoft\Boot\bootmgfw.efi' >/dev/null 2>&1 \
|
||||||
|
|| die "création de l'entrée UEFI WinPE échouée"
|
||||||
|
PE_BN="$(efibootmgr 2>/dev/null | sed -n "s/^Boot\([0-9A-Fa-f]\{4\}\)\*\? *FWS WinPE.*/\1/p" | head -1)"
|
||||||
|
[ -n "$PE_BN" ] || die "numéro de boot WinPE introuvable"
|
||||||
|
efibootmgr -n "$PE_BN" >/dev/null || die "BootNext WinPE non armé"
|
||||||
|
log "WinPE armée en BootNext (Boot$PE_BN). Au 1er reboot : bcdboot puis retour FWS."
|
||||||
|
log "⚠ NON VALIDÉ sur matériel — à tester (assignation lettres WinPE, index boot.wim)."
|
||||||
|
exit 0
|
||||||
@@ -0,0 +1,301 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================
|
||||||
|
# fws-windows-deploy — déploie Windows 11 en OFFLINE-APPLY sur un disque dédié,
|
||||||
|
# depuis le live FWS (appelé en %post --nochroot, cible sous /mnt/sysroot).
|
||||||
|
#
|
||||||
|
# Windows Setup ne tourne JAMAIS : on carve les partitions, on applique
|
||||||
|
# install.wim (wimlib), on rend bootable (BCD), on injecte l'answer file offline
|
||||||
|
# + le composant gameboot, on provisionne l'hibernation FWS.
|
||||||
|
#
|
||||||
|
# SÉCURITÉ (risque n°1 = effacer le mauvais NVMe) : garde-fous FAIL-CLOSED —
|
||||||
|
# état PARSÉ (jamais sourcé → pas d'injection), disque cible VALIDÉ contre la
|
||||||
|
# liste des disques réels, comparé à TOUS les disques protégés (racine FWS
|
||||||
|
# multi-PV incluse, ESP, média ISO, support live), aucun label FWS résiduel
|
||||||
|
# ailleurs, adressage des partitions par NUMÉRO sur le disque cible (jamais par
|
||||||
|
# by-partlabel global) avec vérif du parent. Le moindre doute → abort. Échec →
|
||||||
|
# DEPLOY_FAILED, aucune entrée UEFI piégeuse, FWS reste seul bootable.
|
||||||
|
#
|
||||||
|
# ⚠ NON VALIDÉ SUR MATÉRIEL RÉEL (déploiement Windows réel + BCD).
|
||||||
|
# ============================================================
|
||||||
|
set -u
|
||||||
|
|
||||||
|
STATE=/tmp/fws-windows
|
||||||
|
SYSROOT=/mnt/sysroot
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--state) STATE="${2:-}"; shift 2 ;;
|
||||||
|
--sysroot) SYSROOT="${2:-}"; shift 2 ;;
|
||||||
|
*) shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
log(){ printf '\e[36m[win-deploy]\e[0m %s\n' "$*"; }
|
||||||
|
warn(){ printf '\e[33m[win-deploy] %s\e[0m\n' "$*" >&2; }
|
||||||
|
status(){ mkdir -p "$SYSROOT/var/lib/fws" 2>/dev/null; echo "$1" > "$SYSROOT/var/lib/fws/windows-deploy.status" 2>/dev/null || true; }
|
||||||
|
die(){ printf '\e[31m[win-deploy] ABORT : %s\e[0m\n' "$*" >&2; status "DEPLOY_FAILED: $*"; exit 1; }
|
||||||
|
canon(){ readlink -f "$1" 2>/dev/null || printf '%s' "$1"; }
|
||||||
|
# Device de la partition N sur le disque D (nvme/mmc → pN ; sata → N).
|
||||||
|
part_dev(){ case "$1" in *[0-9]) printf '%sp%s' "$1" "$2";; *) printf '%s%s' "$1" "$2";; esac; }
|
||||||
|
|
||||||
|
# --- 0) État du spoke : PARSÉ en clé=valeur littéral (JAMAIS sourcé) [M3] -----
|
||||||
|
[ -r "$STATE" ] || { log "pas de $STATE → dual-boot non demandé, rien à faire."; exit 0; }
|
||||||
|
enabled=0; disk=""; size_gib=0; iso=""; edition=""; confirm_erase=0; intel_vmd=0
|
||||||
|
while IFS='=' read -r k v; do
|
||||||
|
case "$k" in
|
||||||
|
enabled) enabled="$v" ;;
|
||||||
|
disk) disk="$v" ;;
|
||||||
|
size_gib) size_gib="$v" ;;
|
||||||
|
iso) iso="$v" ;;
|
||||||
|
edition) edition="$v" ;;
|
||||||
|
confirm_erase) confirm_erase="$v" ;;
|
||||||
|
intel_vmd) intel_vmd="$v" ;;
|
||||||
|
esac
|
||||||
|
done < "$STATE"
|
||||||
|
[ "${enabled:-0}" = 1 ] || { log "dual-boot désactivé, rien à faire."; exit 0; }
|
||||||
|
[ "${size_gib:-0}" -eq "${size_gib:-0}" ] 2>/dev/null || die "taille non numérique"
|
||||||
|
log "Dual-boot Windows demandé : disque=$disk taille=${size_gib}Gio iso=$iso"
|
||||||
|
|
||||||
|
# --- Résolveur : TOUS les disques physiques sous un montage (fail-closed) [M1]
|
||||||
|
fws_disks_of(){
|
||||||
|
local src
|
||||||
|
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}'
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 1) Garde-fous AVANT toute écriture (fail-closed) -----------------------
|
||||||
|
WIN_DISK="$disk"
|
||||||
|
[ -b "$WIN_DISK" ] || die "cible '$WIN_DISK' n'est pas un disque bloc"
|
||||||
|
# [M3] 'disk' DOIT être un vrai disque physique (neutralise toute valeur piégée).
|
||||||
|
lsblk -dpno NAME,TYPE 2>/dev/null | awk '$2=="disk"{print $1}' | grep -qxF "$WIN_DISK" \
|
||||||
|
|| die "cible '$WIN_DISK' absente de la liste des disques physiques"
|
||||||
|
findmnt -no SOURCE "$SYSROOT" >/dev/null 2>&1 || die "$SYSROOT non monté"
|
||||||
|
[ -f "$iso" ] || die "ISO introuvable : $iso"
|
||||||
|
[ "${size_gib:-0}" -ge 100 ] || die "taille < 100 Gio"
|
||||||
|
|
||||||
|
# Swap d'hibernation (RAM+VRAM+marge, plancher 96) — calculé ICI pour le garde taille [M7].
|
||||||
|
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
|
||||||
|
|
||||||
|
# Le disque cible ne doit être AUCUN disque protégé. On sonde uniquement de VRAIS
|
||||||
|
# montages [m1], et on compare à TOUS les disques porteurs [M1]. Resolver muet → abort.
|
||||||
|
WIN_C="$(canon "$WIN_DISK")"
|
||||||
|
for probe in "$SYSROOT" "$SYSROOT/boot/efi" "$SYSROOT/boot" /run/archiso/bootmnt; do
|
||||||
|
findmnt -M "$probe" >/dev/null 2>&1 || continue
|
||||||
|
mapfile -t pds < <(fws_disks_of "$probe")
|
||||||
|
[ "${#pds[@]}" -gt 0 ] || die "resolver muet sur '$probe' → abort (fail-closed)"
|
||||||
|
for d in "${pds[@]}"; do
|
||||||
|
[ "$WIN_C" != "$(canon "$d")" ] || die "$WIN_DISK == disque protégé ($probe via $d) — refus d'effacer"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
# L'ISO ne doit pas vivre sur la cible — fail-closed si le média est indéterminable [m2].
|
||||||
|
iso_mnt="$(df --output=target "$iso" 2>/dev/null | tail -1)"
|
||||||
|
[ -n "$iso_mnt" ] || die "média de l'ISO indéterminable → abort (fail-closed)"
|
||||||
|
mapfile -t isods < <(fws_disks_of "$iso_mnt")
|
||||||
|
[ "${#isods[@]}" -gt 0 ] || die "resolver muet sur le média ISO → abort"
|
||||||
|
for d in "${isods[@]}"; do
|
||||||
|
[ "$WIN_C" != "$(canon "$d")" ] || die "l'ISO vit sur la cible $WIN_DISK — refus"
|
||||||
|
done
|
||||||
|
# Taille : NTFS + ESP + MSR + swap doivent tenir [M7].
|
||||||
|
DGIB=$(( $(lsblk -dbno SIZE "$WIN_DISK" 2>/dev/null || echo 0) / 1073741824 ))
|
||||||
|
[ "$DGIB" -gt 0 ] && [ "$size_gib" -le $(( DGIB - SWAP_GIB - 4 )) ] \
|
||||||
|
|| die "taille ${size_gib} + swap ${SWAP_GIB} + réserve dépasse le disque ${DGIB} Gio"
|
||||||
|
|
||||||
|
# [B1] Aucun label FWS résiduel ne doit exister sur un AUTRE disque (collision
|
||||||
|
# by-partlabel = risque d'écrire ailleurs). On refuse plutôt que de deviner.
|
||||||
|
while read -r dev lbl; do
|
||||||
|
case "$lbl" in
|
||||||
|
FWSWIN|WINESP|FWSSWAP)
|
||||||
|
pk="/dev/$(lsblk -no PKNAME "$dev" 2>/dev/null | head -1)"
|
||||||
|
[ "$(canon "$pk")" = "$WIN_C" ] \
|
||||||
|
|| die "label '$lbl' déjà présent sur $pk (≠ cible) — nettoie-le d'abord (wipefs -a $pk)" ;;
|
||||||
|
esac
|
||||||
|
done < <(lsblk -lnpo NAME,PARTLABEL 2>/dev/null)
|
||||||
|
log "Garde-fous OK : $WIN_DISK ($DGIB Gio) sûr à dédier à Windows (swap ${SWAP_GIB} Gio)."
|
||||||
|
|
||||||
|
# --- 2) Neutraliser toute activation du disque cible [M2] -------------------
|
||||||
|
log "Neutralisation des partitions de $WIN_DISK…"
|
||||||
|
for part in $(lsblk -lnpo NAME "$WIN_DISK" 2>/dev/null | tail -n +2); do
|
||||||
|
umount "$part" 2>/dev/null || true
|
||||||
|
swapoff "$part" 2>/dev/null || true
|
||||||
|
for vg in $(pvs --noheadings -o vg_name "$part" 2>/dev/null); do vgchange -an "$vg" 2>/dev/null || true; done
|
||||||
|
for h in $(lsblk -lno NAME "$part" 2>/dev/null | tail -n +2); do
|
||||||
|
case "$(lsblk -no TYPE "/dev/$h" 2>/dev/null | head -1)" in
|
||||||
|
raid*) mdadm --stop "/dev/$h" 2>/dev/null || true ;;
|
||||||
|
crypt) cryptsetup close "$h" 2>/dev/null || true ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
done
|
||||||
|
# Arrays md dont le disque cible est lui-même membre (détection par colonne TYPE).
|
||||||
|
for md in $(lsblk -lnpo NAME,TYPE "$WIN_DISK" 2>/dev/null | awk '$2 ~ /raid/{print $1}'); do
|
||||||
|
mdadm --stop "$md" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
# Re-vérif fail-closed : plus rien d'actif sur le disque cible.
|
||||||
|
for part in $(lsblk -lnpo NAME "$WIN_DISK" 2>/dev/null | tail -n +2); do
|
||||||
|
findmnt -S "$part" >/dev/null 2>&1 && die "$part encore monté après neutralisation"
|
||||||
|
swapon --show=NAME --noheadings 2>/dev/null | grep -qx "$part" && die "$part encore en swap"
|
||||||
|
[ -n "$(lsblk -lno NAME "$part" 2>/dev/null | tail -n +2)" ] && die "$part a encore des holders (md/dm) actifs"
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- 3) Carve (mode DEDICATE) — adressage par NUMÉRO sur le disque cible [B1]
|
||||||
|
log "Carve : ESP 1Gio + MSR 16Mio + NTFS ${size_gib}Gio + swap ${SWAP_GIB}Gio"
|
||||||
|
sgdisk --zap-all "$WIN_DISK" || die "sgdisk --zap-all a échoué"
|
||||||
|
sgdisk -n1:0:+1GiB -t1:ef00 -c1:WINESP "$WIN_DISK" || die "création WINESP"
|
||||||
|
sgdisk -n2:0:+16MiB -t2:0c01 -c2:MSR "$WIN_DISK" || warn "MSR non créée (non bloquant)"
|
||||||
|
sgdisk -n3:0:+${size_gib}GiB -t3:0700 -c3:FWSWIN "$WIN_DISK" || die "création partition Windows"
|
||||||
|
sgdisk -n4:0:+${SWAP_GIB}GiB -t4:8200 -c4:FWSSWAP "$WIN_DISK" || die "création swap"
|
||||||
|
partprobe "$WIN_DISK"; udevadm settle
|
||||||
|
ESP_PART="$(part_dev "$WIN_DISK" 1)"
|
||||||
|
WIN_PART="$(part_dev "$WIN_DISK" 3)"
|
||||||
|
SWAP_PART="$(part_dev "$WIN_DISK" 4)"
|
||||||
|
for p in "$ESP_PART" "$WIN_PART" "$SWAP_PART"; do
|
||||||
|
[ -b "$p" ] || die "partition $p absente après carve"
|
||||||
|
pk="/dev/$(lsblk -no PKNAME "$p" 2>/dev/null | head -1)"
|
||||||
|
[ "$(canon "$pk")" = "$WIN_C" ] || die "$p n'est PAS sur $WIN_DISK — abort"
|
||||||
|
done
|
||||||
|
mkfs.fat -F32 -n WINESP "$ESP_PART" || die "mkfs.fat WINESP"
|
||||||
|
mkswap -L FWSSWAP "$SWAP_PART" || die "mkswap FWSSWAP"
|
||||||
|
mkntfs -Q -f -L FWSWIN "$WIN_PART" || mkntfs -f -L FWSWIN "$WIN_PART" || die "mkntfs FWSWIN"
|
||||||
|
|
||||||
|
# --- 4) Appliquer install.wim (wimlib) sur la NTFS --------------------------
|
||||||
|
ISO_MNT="$(mktemp -d)"; WIN_MNT="$(mktemp -d)"; ESP_MNT="$(mktemp -d)"
|
||||||
|
cleanup(){ for m in "$WIN_MNT" "$ESP_MNT" "$ISO_MNT"; do umount "$m" 2>/dev/null || true; done; }
|
||||||
|
trap cleanup EXIT
|
||||||
|
mount -o loop,ro "$iso" "$ISO_MNT" || die "montage ISO échoué"
|
||||||
|
IMG="$ISO_MNT/sources/install.wim"; [ -f "$IMG" ] || IMG="$ISO_MNT/sources/install.esd"
|
||||||
|
[ -f "$IMG" ] || die "install.wim/.esd introuvable dans l'ISO"
|
||||||
|
# Édition par NOM (insensible à la casse) ; repli image unique [m4].
|
||||||
|
WANT="${edition:-Windows 11 Pro}"
|
||||||
|
IDX="$(wimlib-imagex info "$IMG" 2>/dev/null | awk -v w="$(printf '%s' "$WANT" | tr 'A-Z' 'a-z')" -F': *' '
|
||||||
|
tolower($1) ~ /^index/{i=$2} index(tolower($0), w){print i; exit}')"
|
||||||
|
if [ -z "$IDX" ]; then
|
||||||
|
CNT="$(wimlib-imagex info "$IMG" 2>/dev/null | awk -F': *' 'tolower($1) ~ /image count/{print $2; exit}')"
|
||||||
|
[ "${CNT:-0}" = 1 ] && IDX=1
|
||||||
|
fi
|
||||||
|
[ -n "$IDX" ] || die "édition « $WANT » absente de l'ISO (wimlib-imagex info) — ISO Home/N/tiny ?"
|
||||||
|
log "Application de l'image (index $IDX = $WANT) sur $WIN_PART — long…"
|
||||||
|
wimlib-imagex apply "$IMG" "$IDX" "$WIN_PART" || die "wimlib-imagex apply a échoué"
|
||||||
|
|
||||||
|
# --- 5) Bootabilité : BCD (hivex → repli WinPE-bcdboot) ---------------------
|
||||||
|
mount "$WIN_PART" "$WIN_MNT" || die "montage NTFS échoué"
|
||||||
|
mount "$ESP_PART" "$ESP_MNT" || die "montage WINESP échoué"
|
||||||
|
mkdir -p "$ESP_MNT/EFI/Microsoft/Boot"
|
||||||
|
cp -rn "$WIN_MNT/Windows/Boot/EFI/." "$ESP_MNT/EFI/Microsoft/Boot/" 2>/dev/null || true
|
||||||
|
WIN_PARTUUID="$(blkid -s PARTUUID -o value "$WIN_PART" 2>/dev/null)"
|
||||||
|
DISK_GUID="$(sgdisk -p "$WIN_DISK" 2>/dev/null | sed -n 's/^Disk identifier (GUID): *//p')"
|
||||||
|
# Pilote Intel VMD à injecter ? (case cochée dans le spoke + .inf présents).
|
||||||
|
DRV=""
|
||||||
|
if [ "${intel_vmd:-0}" = 1 ]; then
|
||||||
|
if ls /usr/local/share/fws/drivers/intel-vmd/*.inf >/dev/null 2>&1; then
|
||||||
|
DRV=/usr/local/share/fws/drivers/intel-vmd
|
||||||
|
log "Intel VMD demandé + pilote présent → injection via WinPE (dism)."
|
||||||
|
else
|
||||||
|
warn "Intel VMD demandé mais AUCUN pilote dans .../drivers/intel-vmd/ — Windows tentera son pilote VMD inbox (25H2). Dépose le pilote Intel si le boot échoue."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# Avec un pilote VMD à injecter, on FORCE la voie WinPE (dism y tourne). Sinon
|
||||||
|
# hivex (qui bascule de toute façon sur WinPE tant que non validé).
|
||||||
|
if [ -z "$DRV" ] && /usr/local/lib/fws/fws-bcd-write "$ESP_MNT" "$WIN_MNT" "$WIN_PARTUUID" "$DISK_GUID"; then
|
||||||
|
log "BCD écrit (voie hivex)."
|
||||||
|
BCDFIX=0
|
||||||
|
else
|
||||||
|
[ -n "$DRV" ] && log "Bascule WinPE (bcdboot + injection pilote VMD)." \
|
||||||
|
|| warn "Voie hivex non validée → repli WinPE-bcdboot (1 reboot auto)."
|
||||||
|
umount "$WIN_MNT" 2>/dev/null || true
|
||||||
|
/usr/local/bin/fws-windows-bcdfix "$ISO_MNT" "$ESP_MNT" "$WIN_DISK" 1 "$DRV" \
|
||||||
|
|| die "repli WinPE-bcdfix a échoué — Windows non bootable (FWS reste seul bootable)"
|
||||||
|
BCDFIX=1
|
||||||
|
mount "$WIN_PART" "$WIN_MNT" 2>/dev/null || die "remontage NTFS échoué"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- 6) Anti-BitLocker HORS LIGNE (crucial : évite le lockout) --------------
|
||||||
|
HIVE="$WIN_MNT/Windows/System32/config/SYSTEM"
|
||||||
|
if command -v hivexget >/dev/null 2>&1 && [ -f "$HIVE" ]; then
|
||||||
|
SEL="$(hivexget "$HIVE" Select Current 2>/dev/null | tr -cd '0-9')"; [ -n "$SEL" ] || SEL=1
|
||||||
|
printf '[HKEY_LOCAL_MACHINE\\SYSTEM\\ControlSet%03d\\Control\\BitLocker]\n"PreventDeviceEncryption"=dword:00000001\n' "$SEL" \
|
||||||
|
| hivexregedit --merge --prefix 'HKEY_LOCAL_MACHINE\SYSTEM' "$HIVE" \
|
||||||
|
&& log "Anti-BitLocker offline posé (ControlSet$(printf '%03d' "$SEL"))." \
|
||||||
|
|| warn "écriture anti-BitLocker offline échouée (ceinture reg add en specialize)."
|
||||||
|
else
|
||||||
|
warn "hivex/SYSTEM absent → anti-BitLocker via la ceinture specialize seule."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- 7) Answer file offline + injection du composant gameboot ---------------
|
||||||
|
TMPL=/usr/local/share/fws/gameboot-windows/unattend.offline.xml.tmpl
|
||||||
|
HN="$(tr -cd 'a-zA-Z0-9-' < /tmp/fws-hostname 2>/dev/null)"; [ -n "$HN" ] || HN=fws-win
|
||||||
|
LOC="$(. "$SYSROOT/etc/locale.conf" 2>/dev/null; printf '%s' "${LANG%%.*}")"; [ -n "$LOC" ] || LOC=fr-CH
|
||||||
|
LOC="${LOC/_/-}"
|
||||||
|
FIRSTBOOT='C:\ProgramData\FWS\deploy\FirstBoot-FwsGameboot.ps1'
|
||||||
|
mkdir -p "$WIN_MNT/Windows/Panther" "$WIN_MNT/ProgramData/FWS/deploy"
|
||||||
|
sed -e "s|__HOSTNAME__|$HN|g" -e "s|__USER__|gaming|g" -e "s|__DISPLAY__|Gaming|g" \
|
||||||
|
-e "s|__LOCALE__|$LOC|g" -e "s|__INPUT__|$LOC|g" -e "s|__FIRSTBOOT__|${FIRSTBOOT//\\/\\\\}|g" \
|
||||||
|
"$TMPL" > "$WIN_MNT/Windows/Panther/unattend.xml" || die "rendu unattend.xml échoué"
|
||||||
|
cp -rf /usr/local/share/fws/gameboot-windows/. "$WIN_MNT/ProgramData/FWS/deploy/" || die "injection gameboot échouée"
|
||||||
|
rm -f "$WIN_MNT/ProgramData/FWS/deploy/unattend.offline.xml.tmpl" 2>/dev/null || true
|
||||||
|
log "unattend.xml + composant gameboot injectés sur la NTFS."
|
||||||
|
|
||||||
|
# --- 8) Vérification AVANT toute écriture NVRAM -----------------------------
|
||||||
|
[ -f "$WIN_MNT/Windows/System32/ntoskrnl.exe" ] || die "ntoskrnl.exe absent → apply incomplet"
|
||||||
|
[ -f "$WIN_MNT/Windows/System32/winload.efi" ] || die "winload.efi absent"
|
||||||
|
[ -f "$ESP_MNT/EFI/Microsoft/Boot/bootmgfw.efi" ] || die "bootmgfw.efi absent de l'ESP"
|
||||||
|
[ "$BCDFIX" = 1 ] || [ -s "$ESP_MNT/EFI/Microsoft/Boot/BCD" ] || die "BCD absent/vide (voie hivex)"
|
||||||
|
sync
|
||||||
|
|
||||||
|
# --- 9) Entrées de boot : FWS TOUJOURS en tête [M5][M6] ---------------------
|
||||||
|
# Voie hivex directe : on crée l'entrée Windows persistante (en queue).
|
||||||
|
if [ "$BCDFIX" = 0 ]; then
|
||||||
|
efibootmgr -c -d "$WIN_DISK" -p 1 -L 'Windows Boot Manager' -l '\EFI\Microsoft\Boot\bootmgfw.efi' >/dev/null 2>&1 || warn "création entrée UEFI Windows échouée"
|
||||||
|
WBN="$(efibootmgr 2>/dev/null | sed -n 's/^Boot\([0-9A-Fa-f]\{4\}\)\*\? *Windows Boot Manager.*/\1/p' | head -1)"
|
||||||
|
[ -n "$WBN" ] && sed -i "s/^FWS_WINDOWS_BOOTNUM=.*/FWS_WINDOWS_BOOTNUM=\"$WBN\"/" \
|
||||||
|
"$SYSROOT/etc/fws/gameboot.conf" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
# Dans TOUS les cas : FWS en BootOrder[0] (Windows/WinPE jamais 1er). Si aucune
|
||||||
|
# entrée NVRAM FWS (booté via \EFI\BOOT\BOOTX64.EFI removable), en créer une.
|
||||||
|
FBN="$(efibootmgr 2>/dev/null | sed -n 's/^Boot\([0-9A-Fa-f]\{4\}\)\*\? *\(FWS\|GRUB\).*/\1/p' | head -1)"
|
||||||
|
if [ -z "$FBN" ]; then
|
||||||
|
esp_src="$(findmnt -no SOURCE "$SYSROOT/boot/efi" 2>/dev/null)"
|
||||||
|
if [ -b "$esp_src" ]; then
|
||||||
|
sdisk="/dev/$(lsblk -no PKNAME "$esp_src" 2>/dev/null | head -1)"
|
||||||
|
spn="$(lsblk -no PARTN "$esp_src" 2>/dev/null | head -1)"
|
||||||
|
efibootmgr -c -d "$sdisk" -p "${spn:-1}" -L 'FWS' -l '\EFI\BOOT\BOOTX64.EFI' >/dev/null 2>&1 || true
|
||||||
|
FBN="$(efibootmgr 2>/dev/null | sed -n 's/^Boot\([0-9A-Fa-f]\{4\}\)\*\? *FWS.*/\1/p' | head -1)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
order="$(efibootmgr 2>/dev/null | sed -n 's/^BootOrder: //p' | tr -d '[:space:]')"
|
||||||
|
if [ -n "$FBN" ] && [ -n "$order" ]; then
|
||||||
|
rest="$(printf '%s' "$order" | tr ',' '\n' | grep -vx "$FBN" | paste -sd, -)"
|
||||||
|
efibootmgr -o "${FBN}${rest:+,$rest}" >/dev/null 2>&1 || true # FWS 1er, reste (Windows/WinPE) derrière
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- 10) Hibernation FWS (chroot cible) : swap dédié → resume= --------------
|
||||||
|
SWAP_UUID="$(blkid -s UUID -o value "$SWAP_PART" 2>/dev/null)"
|
||||||
|
if [ -n "$SWAP_UUID" ] && command -v arch-chroot >/dev/null 2>&1; then
|
||||||
|
log "Config hibernation FWS (resume=UUID=$SWAP_UUID)…"
|
||||||
|
cat > "$SYSROOT/etc/fws-gameboot-swap.sh" <<CHROOT
|
||||||
|
set -u
|
||||||
|
grep -q "UUID=$SWAP_UUID" /etc/fstab 2>/dev/null || printf 'UUID=%s none swap defaults 0 0\n' "$SWAP_UUID" >> /etc/fstab
|
||||||
|
grep -qE '^HOOKS=.*\bresume\b' /etc/mkinitcpio.conf || sed -i -E 's/^(HOOKS=\(.*)\bfilesystems\b/\1resume filesystems/' /etc/mkinitcpio.conf
|
||||||
|
mkinitcpio -P || true
|
||||||
|
if grep -q '^GRUB_CMDLINE_LINUX_DEFAULT=' /etc/default/grub; then
|
||||||
|
grep -q 'resume=UUID=$SWAP_UUID' /etc/default/grub || sed -i 's|^GRUB_CMDLINE_LINUX_DEFAULT="\(.*\)"|GRUB_CMDLINE_LINUX_DEFAULT="\1 resume=UUID=$SWAP_UUID hibernate.compressor=lz4"|' /etc/default/grub
|
||||||
|
fi
|
||||||
|
grep -q '^GRUB_DISABLE_OS_PROBER=' /etc/default/grub || echo 'GRUB_DISABLE_OS_PROBER=true' >> /etc/default/grub
|
||||||
|
grub-mkconfig -o /boot/grub/grub.cfg || true
|
||||||
|
install -Dm644 /dev/stdin /etc/modprobe.d/nvidia-hibernate.conf <<'NV'
|
||||||
|
options nvidia NVreg_PreserveVideoMemoryAllocations=1 NVreg_TemporaryFilePath=/var/tmp
|
||||||
|
NV
|
||||||
|
systemctl enable nvidia-suspend.service nvidia-hibernate.service nvidia-resume.service 2>/dev/null || true
|
||||||
|
systemctl enable fws-gameboot-bootfix.service 2>/dev/null || true
|
||||||
|
CHROOT
|
||||||
|
arch-chroot "$SYSROOT" bash /etc/fws-gameboot-swap.sh 2>&1 | tee -a /tmp/fws-windows-deploy.log || warn "config hibernation chroot partielle"
|
||||||
|
rm -f "$SYSROOT/etc/fws-gameboot-swap.sh"
|
||||||
|
else
|
||||||
|
warn "swap UUID introuvable ou arch-chroot absent → hibernation non configurée (reboot simple restera dispo)."
|
||||||
|
fi
|
||||||
|
|
||||||
|
sync
|
||||||
|
status "DEPLOY_OK: Windows sur $WIN_DISK (${size_gib}Gio) — SB à activer au firmware"
|
||||||
|
log "Déploiement Windows terminé.$([ "$BCDFIX" = 1 ] && echo ' (BCD via WinPE au 1er reboot)')"
|
||||||
|
log "⚠ Reste manuel : activer Secure Boot au firmware (sbctl verify déjà clean)."
|
||||||
|
exit 0
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================
|
||||||
|
# fws-bcd-write — construit un magasin BCD Windows OFFLINE depuis Linux (hivex).
|
||||||
|
#
|
||||||
|
# ⚠⚠ NON VALIDÉ SUR MATÉRIEL RÉEL. Le BCD est un hive registre propriétaire ;
|
||||||
|
# `bcdboot` (l'outil officiel qui l'écrit correctement) est Windows-only. On
|
||||||
|
# copie le BCD-Template livré avec Windows et on patche les éléments STRING
|
||||||
|
# qu'on maîtrise ; l'élément BINAIRE « device »/« osdevice » (blob encodant la
|
||||||
|
# partition GPT — DISK_GUID + PARTUUID) est LA surface à auditer et n'est PAS
|
||||||
|
# encodé de façon fiable ici. Tant que ce n'est pas prouvé sur un Windows 11
|
||||||
|
# 25H2 réel, on renvoie un code d'échec → l'orchestrateur bascule sur
|
||||||
|
# fws-windows-bcdfix (WinPE-bcdboot), qui utilise le vrai bcdboot.
|
||||||
|
#
|
||||||
|
# Args : <ESP_MOUNT> <WIN_MOUNT> <WINDOWS_PARTUUID> <DISK_GUID>
|
||||||
|
# Codes : 0 = BCD écrit & jugé sûr ; 1 = bascule sur le repli WinPE.
|
||||||
|
# ============================================================
|
||||||
|
set -u
|
||||||
|
ESP="${1:-}"; WIN="${2:-}"; PARTUUID="${3:-}"; DISKGUID="${4:-}"
|
||||||
|
[ -d "$ESP" ] && [ -d "$WIN" ] || { echo "[bcd-write] args invalides"; exit 1; }
|
||||||
|
command -v hivexregedit >/dev/null 2>&1 || { echo "[bcd-write] hivex absent → repli"; exit 1; }
|
||||||
|
|
||||||
|
TMPL="$WIN/Windows/System32/config/BCD-Template"
|
||||||
|
[ -f "$TMPL" ] || { echo "[bcd-write] BCD-Template absent ($TMPL) → repli"; exit 1; }
|
||||||
|
|
||||||
|
DST="$ESP/EFI/Microsoft/Boot/BCD"
|
||||||
|
mkdir -p "$(dirname "$DST")"
|
||||||
|
cp -f "$TMPL" "$DST"
|
||||||
|
echo "[bcd-write] BCD-Template copié → $DST (device cible $PARTUUID / disque $DISKGUID)."
|
||||||
|
|
||||||
|
# ⚠ Le patch du blob « device » (Element 0x11000001) encodant la partition n'est
|
||||||
|
# pas implémenté de façon vérifiée ici → on force la voie robuste WinPE-bcdboot.
|
||||||
|
echo "[bcd-write] ⚠ NON VALIDÉ (blob device non encodé) → bascule sur fws-windows-bcdfix."
|
||||||
|
exit 1
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
# FWS — lanceur de l'application de réparation dans l'OS INSTALLÉ (copié tel
|
||||||
|
# quel du live). L'app tourne en utilisateur ; chaque appel du moteur passe
|
||||||
|
# par pkexec (cf. org.fws.recovery.policy).
|
||||||
|
Type=Application
|
||||||
|
Name=Récupération FWS
|
||||||
|
Name[en]=FWS Recovery
|
||||||
|
GenericName=Réparation du système
|
||||||
|
GenericName[en]=System repair
|
||||||
|
Comment=Réparer GRUB, l'initramfs ou pacman — le point de restauration FWS
|
||||||
|
Comment[en]=Repair GRUB, the initramfs or pacman — the FWS restore point
|
||||||
|
Exec=/usr/local/bin/fws-recovery-gui
|
||||||
|
Icon=fws-recovery
|
||||||
|
# Associe la fenêtre (WM_CLASS via GLib.set_prgname) à ce lanceur → bonne
|
||||||
|
# icône dans la barre de titre / barre des tâches (sinon icône générique).
|
||||||
|
StartupWMClass=fws-recovery-gui
|
||||||
|
Terminal=false
|
||||||
|
Categories=System;
|
||||||
|
Keywords=recovery;réparation;restauration;grub;boot;secours;dépannage;
|
||||||
|
Keywords[en]=recovery;repair;restore;grub;boot;rescue;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
# FWS — masque « À propos de Xfce » du menu d'applications. Le binaire
|
||||||
|
# xfce4-about vient de libxfce4ui (dépendance de Thunar) : il n'implique
|
||||||
|
# AUCUNE session XFCE. /usr/local/share étant prioritaire sur /usr/share
|
||||||
|
# dans XDG_DATA_DIRS, cette entrée NoDisplay masque celle du paquet partout.
|
||||||
|
Type=Application
|
||||||
|
Name=About Xfce
|
||||||
|
Exec=xfce4-about
|
||||||
|
NoDisplay=true
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Pilote Intel VMD/RST (iaStorVD) — à déposer ici
|
||||||
|
|
||||||
|
FWS **ne peut pas embarquer** le pilote Intel par défaut (binaire propriétaire).
|
||||||
|
Pour activer le support des machines **Intel avec VMD/RST activé dans le BIOS**,
|
||||||
|
dépose ici les fichiers du pilote **Intel VMD** (« f6 » VMD driver) :
|
||||||
|
|
||||||
|
```
|
||||||
|
iaStorVD.inf
|
||||||
|
iaStorVD.sys
|
||||||
|
iaStorVD.cat
|
||||||
|
(+ éventuels iaStorAC.* / autres .inf/.sys du paquet)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Où le télécharger
|
||||||
|
|
||||||
|
- Intel « Rapid Storage Technology (RST) » / VMD driver, package **f6vmdflpy**
|
||||||
|
(Intel Download Center, ou le site du fabricant de la carte mère).
|
||||||
|
- Extraire le contenu du dossier `f6vmdflpy-x64\` ici.
|
||||||
|
|
||||||
|
## Ce que FWS en fait (automatique)
|
||||||
|
|
||||||
|
Si la case **« Intel VMD/RST support »** est cochée dans l'installateur
|
||||||
|
(auto-cochée quand un contrôleur VMD est détecté) **et** que des `.inf` sont
|
||||||
|
présents ici, `fws-windows-deploy` les injecte dans le Windows déployé via la
|
||||||
|
WinPE (`drvload` pour que WinPE voie les NVMe, puis `dism /add-driver` pour
|
||||||
|
enregistrer le pilote comme boot-critique) → Windows boote sur les NVMe Intel VMD.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- **Windows 11 25H2** inclut déjà iaStorVD *inbox* pour beaucoup de contrôleurs
|
||||||
|
VMD → l'injection n'est nécessaire que pour les contrôleurs non couverts.
|
||||||
|
- **Aucun effet sur AMD** : sans contrôleur VMD, l'option reste décochée / ignorée.
|
||||||
|
- ⚠ Chemin **NON VALIDÉ** sur matériel Intel réel (impossible à tester ici).
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
<#
|
||||||
|
FirstBoot-FwsGameboot.ps1 — orchestrateur de PREMIER DEMARRAGE (une seule fois).
|
||||||
|
|
||||||
|
Au tout premier boot du Windows gaming, execute automatiquement :
|
||||||
|
1. Install-FwsGameboot.ps1 (composant de retour + auto-login + taches) ;
|
||||||
|
2. Install-Games.ps1 (telecharge+lance les installeurs officiels des
|
||||||
|
jeux marques autoinstall dans installers.json).
|
||||||
|
Puis pose un marqueur pour ne PLUS rien refaire aux boots suivants.
|
||||||
|
|
||||||
|
Cablage (le declencheur du "premier boot") : soit via autounattend.xml
|
||||||
|
<FirstLogonCommands> quand FWS pilote l'install Windows (cf. README), soit via
|
||||||
|
une entree RunOnce. Idempotent : re-lancable sans risque (le marqueur protege).
|
||||||
|
#>
|
||||||
|
#Requires -RunAsAdministrator
|
||||||
|
$ErrorActionPreference = 'Continue'
|
||||||
|
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$done = Join-Path $Env:ProgramData 'FWS\.firstboot-done'
|
||||||
|
|
||||||
|
if (Test-Path $done) { Write-Host "FWS first boot deja effectue — rien a faire."; return }
|
||||||
|
|
||||||
|
Write-Host "== FWS premier demarrage : configuration gameboot + installation des jeux =="
|
||||||
|
|
||||||
|
# 1) Composant de retour + auto-login (compte gaming SANS mot de passe recommande).
|
||||||
|
try { & (Join-Path $here 'Install-FwsGameboot.ps1') }
|
||||||
|
catch { Write-Warning "Install-FwsGameboot a echoue : $_" }
|
||||||
|
|
||||||
|
# 2) Installation des jeux (sources officielles Riot).
|
||||||
|
try { & (Join-Path $here 'Install-Games.ps1') }
|
||||||
|
catch { Write-Warning "Install-Games a echoue : $_" }
|
||||||
|
|
||||||
|
# Marqueur "fait" (dans le meme dossier durci que le reste).
|
||||||
|
New-Item -ItemType Directory -Force -Path (Split-Path $done) | Out-Null
|
||||||
|
New-Item -ItemType File -Force -Path $done | Out-Null
|
||||||
|
Write-Host "== FWS premier demarrage termine =="
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
<#
|
||||||
|
Install-FwsGameboot.ps1 — installe le composant de RETOUR + LANCEMENT AUTO.
|
||||||
|
|
||||||
|
A lancer UNE FOIS, EN ADMIN, dans le Windows gaming. Met en place le
|
||||||
|
"sans login, jeu lance tout seul" :
|
||||||
|
- AUTO-LOGIN du compte gaming (pas d'ecran de mot de passe) ;
|
||||||
|
- tache AU LOGON qui lance fws-play (lit le jeton, lance le bon jeu, retour) ;
|
||||||
|
- tache ONSTART qui arme le boot suivant vers FWS (filet anti-crash) ;
|
||||||
|
- powercfg /h off (reboot PLEIN → NTFS jamais "sale").
|
||||||
|
|
||||||
|
PREREQUIS VERIFIES par ce script (echec explicite sinon) :
|
||||||
|
- le compte courant est ADMINISTRATEUR local (bcdedit + taches SYSTEM) ;
|
||||||
|
- le compte n'a PAS de mot de passe, OU -Password est fourni.
|
||||||
|
|
||||||
|
Empreinte minimale, SANS interference avec Vanguard (aucune injection/hook noyau).
|
||||||
|
|
||||||
|
Parametres :
|
||||||
|
-Password <str> Mot de passe du compte pour l'auto-login. OMETTRE si le
|
||||||
|
compte n'a pas de mot de passe. Fourni => stocke EN CLAIR
|
||||||
|
dans le registre (limitation AutoAdminLogon) : preferer
|
||||||
|
Sysinternals Autologon.exe (secret LSA chiffre).
|
||||||
|
#>
|
||||||
|
#Requires -RunAsAdministrator
|
||||||
|
param([string]$Password)
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
# --- Prereq 1 : le compte gaming DOIT etre admin local (SID, locale-independant) ---
|
||||||
|
try {
|
||||||
|
$adminGrp = (Get-LocalGroup -SID 'S-1-5-32-544').Name # "Administrators"/"Administrateurs"
|
||||||
|
$members = Get-LocalGroupMember -Group $adminGrp -ErrorAction Stop
|
||||||
|
$isAdmin = $members | Where-Object { $_.Name -ieq "$Env:COMPUTERNAME\$Env:USERNAME" -or $_.Name -ieq $Env:USERNAME }
|
||||||
|
} catch { $isAdmin = $null }
|
||||||
|
if (-not $isAdmin) {
|
||||||
|
throw "Le compte '$Env:USERNAME' doit etre ADMINISTRATEUR local : fws-play appelle bcdedit et pilote des taches SYSTEM. Ajoute-le au groupe administrateurs puis relance."
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Prereq 2 : auto-login fiable => compte sans mot de passe OU -Password fourni ---
|
||||||
|
$pwdRequired = $true
|
||||||
|
try { $pwdRequired = (Get-LocalUser -Name $Env:USERNAME).PasswordRequired } catch { }
|
||||||
|
if ($pwdRequired -and -not $PSBoundParameters.ContainsKey('Password')) {
|
||||||
|
throw "Le compte '$Env:USERNAME' exige un mot de passe : relance avec -Password '<mdp>', ou retire le mot de passe du compte. Sinon l'auto-login echoue -> ecran de connexion -> la boucle gameboot se bloque."
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Deploiement des scripts + durcissement ACL (exe potentiellement lance ELEVE) ---
|
||||||
|
$dest = Join-Path $Env:ProgramData 'FWS'
|
||||||
|
New-Item -ItemType Directory -Force -Path $dest | Out-Null
|
||||||
|
foreach ($f in 'fws-return.ps1','fws-play.ps1','games.json','installers.json','Install-Games.ps1') {
|
||||||
|
$src = Join-Path $PSScriptRoot $f
|
||||||
|
if (Test-Path $src) { Copy-Item $src $dest -Force }
|
||||||
|
}
|
||||||
|
# SYSTEM + Administrateurs = plein controle ; Utilisateurs = lecture seule ; heritage retire.
|
||||||
|
& icacls $dest /inheritance:r /grant:r 'SYSTEM:(OI)(CI)F' '*S-1-5-32-544:(OI)(CI)F' '*S-1-5-32-545:(OI)(CI)RX' | Out-Null
|
||||||
|
|
||||||
|
# (1) Reboot PLEIN : pas de Fast Startup → volumes propres.
|
||||||
|
& powercfg /h off 2>$null
|
||||||
|
|
||||||
|
# (2) AUTO-LOGIN du compte courant.
|
||||||
|
$winlogon = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'
|
||||||
|
Set-ItemProperty $winlogon 'AutoAdminLogon' '1' -Force
|
||||||
|
Set-ItemProperty $winlogon 'ForceAutoLogon' '1' -Force
|
||||||
|
Set-ItemProperty $winlogon 'DefaultUserName' $Env:USERNAME -Force
|
||||||
|
Set-ItemProperty $winlogon 'DefaultDomainName' '.' -Force # '.' = local, immune au renommage
|
||||||
|
if ($PSBoundParameters.ContainsKey('Password')) {
|
||||||
|
Set-ItemProperty $winlogon 'DefaultPassword' $Password -Force
|
||||||
|
Write-Warning "Mot de passe stocke EN CLAIR dans le registre. Preferer Sysinternals Autologon.exe (LSA chiffre)."
|
||||||
|
} else {
|
||||||
|
Remove-ItemProperty $winlogon 'DefaultPassword' -ErrorAction SilentlyContinue
|
||||||
|
Write-Host "Auto-login sans mot de passe stocke (compte sans mot de passe verifie)."
|
||||||
|
}
|
||||||
|
|
||||||
|
# (3) Tache AU LOGON : lancer fws-play (lit le jeton, lance le jeu, gere le retour).
|
||||||
|
$playAction = New-ScheduledTaskAction -Execute 'powershell.exe' `
|
||||||
|
-Argument ('-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "{0}\fws-play.ps1"' -f $dest)
|
||||||
|
$playTrigger = New-ScheduledTaskTrigger -AtLogOn -User $Env:USERNAME
|
||||||
|
$playPrincipal = New-ScheduledTaskPrincipal -UserId $Env:USERNAME -LogonType Interactive -RunLevel Highest
|
||||||
|
$playSettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
|
||||||
|
Register-ScheduledTask -TaskName 'FWS-Play-OnLogon' -Action $playAction -Trigger $playTrigger `
|
||||||
|
-Principal $playPrincipal -Settings $playSettings -Force | Out-Null
|
||||||
|
|
||||||
|
# (4) Tache ONSTART (SYSTEM) : armer le boot suivant vers FWS au demarrage.
|
||||||
|
# Filet anti-crash : si Windows redemarre seul (WU, plantage) sans fws-play,
|
||||||
|
# le boot suivant repart vers FWS. fws-play RE-AFFIRME Windows apres lancement
|
||||||
|
# (gere la course avec cette tache — voir fws-play.ps1).
|
||||||
|
$retAction = New-ScheduledTaskAction -Execute 'powershell.exe' `
|
||||||
|
-Argument ('-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "{0}\fws-return.ps1" -Target FWS' -f $dest)
|
||||||
|
$retTrigger = New-ScheduledTaskTrigger -AtStartup
|
||||||
|
$retPrincipal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
|
||||||
|
$retSettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
|
||||||
|
Register-ScheduledTask -TaskName 'FWS-Return-OnStart' -Action $retAction -Trigger $retTrigger `
|
||||||
|
-Principal $retPrincipal -Settings $retSettings -Force | Out-Null
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Composant FWS installe dans $dest." -ForegroundColor Green
|
||||||
|
Write-Host "Auto-login + lancement auto du jeu configures. Flux : boot -> login auto -> jeu." -ForegroundColor Green
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "RAPPELS IMPORTANTS :" -ForegroundColor Yellow
|
||||||
|
Write-Host " - POSTE KIOSQUE : auto-login + taches non-UAC => acces physique = bureau admin."
|
||||||
|
Write-Host " - NE PAS activer BitLocker sans sauvegarder la cle (flip Secure Boot cote FWS"
|
||||||
|
Write-Host " change PCR7 -> ecran de recuperation). Verifie : manage-bde -status"
|
||||||
|
Write-Host " - Ne desactive pas Secure Boot / TPM / VBS-HVCI : Vanguard les exige."
|
||||||
|
Write-Host " - Ajouter des jeux : editer $dest\games.json (id en [a-z0-9_-])."
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<#
|
||||||
|
Install-Games.ps1 — telecharge et lance les installeurs OFFICIELS des jeux
|
||||||
|
marques autoinstall=true dans installers.json.
|
||||||
|
|
||||||
|
FWS ne redistribue AUCUN binaire Riot : chaque installeur est telecharge
|
||||||
|
depuis les serveurs de Riot. Riot change regulierement ses URLs -> en cas
|
||||||
|
d'echec, verifier/mettre a jour installers.json.
|
||||||
|
|
||||||
|
NB : l'installation de Valorant n'est PAS pleinement silencieuse (Vanguard
|
||||||
|
installe un driver noyau et impose un redemarrage) : ce script LANCE
|
||||||
|
l'installeur, l'utilisateur termine les eventuelles confirmations.
|
||||||
|
#>
|
||||||
|
$ErrorActionPreference = 'Continue'
|
||||||
|
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$cfg = Join-Path $here 'installers.json'
|
||||||
|
if (-not (Test-Path $cfg)) { Write-Warning "installers.json introuvable — aucun jeu installe."; return }
|
||||||
|
|
||||||
|
$installers = Get-Content $cfg -Raw | ConvertFrom-Json
|
||||||
|
$tmp = Join-Path $Env:TEMP 'fws-games'
|
||||||
|
New-Item -ItemType Directory -Force -Path $tmp | Out-Null
|
||||||
|
|
||||||
|
# TLS 1.2 pour Invoke-WebRequest sur d'anciennes stacks .NET.
|
||||||
|
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch { }
|
||||||
|
|
||||||
|
$names = $installers.PSObject.Properties.Name | Where-Object { $_ -notlike '_*' }
|
||||||
|
foreach ($name in $names) {
|
||||||
|
$g = $installers.$name
|
||||||
|
if (-not $g.autoinstall) { Write-Host "$name : autoinstall=false, ignore."; continue }
|
||||||
|
if (-not $g.url) {
|
||||||
|
Write-Warning "$name : aucune URL dans installers.json — installe-le manuellement."
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
$out = Join-Path $tmp $g.installer
|
||||||
|
try {
|
||||||
|
Write-Host "Telechargement de l'installeur $name ..."
|
||||||
|
Invoke-WebRequest -Uri $g.url -OutFile $out -UseBasicParsing
|
||||||
|
} catch {
|
||||||
|
Write-Warning "$name : telechargement echoue ($_). URL a verifier dans installers.json (Riot les change souvent)."
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Write-Host "Lancement de l'installeur $name ..."
|
||||||
|
if ($g.silentargs) { Start-Process $out -ArgumentList $g.silentargs -Wait }
|
||||||
|
else { Start-Process $out }
|
||||||
|
} catch {
|
||||||
|
Write-Warning "$name : lancement de l'installeur echoue ($_)."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Installation des jeux lancee. Termine les eventuelles fenetres d'installation + le redemarrage impose par Vanguard."
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# FWS Gameboot — composant Windows
|
||||||
|
|
||||||
|
Composant à déployer **dans le Windows 11 gaming** (partition bare-metal dédiée)
|
||||||
|
pour le dual-boot orchestré « hibernate-swap » de FWS : **retour vers FWS** après
|
||||||
|
la session, **lancement automatique du jeu**, **auto-login**, et **premier
|
||||||
|
démarrage tout automatique** (config + installation des jeux).
|
||||||
|
|
||||||
|
> ⚠️ **Statut : non validé sur matériel réel.** Sémantique `bcdedit bootsequence`,
|
||||||
|
> auto-login, URLs d'installeurs Riot, resume GPU — à tester. Plan :
|
||||||
|
> `docs/hibernate-swap-dualboot.md`.
|
||||||
|
|
||||||
|
## Fichiers
|
||||||
|
|
||||||
|
| Fichier | Rôle |
|
||||||
|
|---|---|
|
||||||
|
| `FirstBoot-FwsGameboot.ps1` | **1er démarrage (une fois)** : lance Install-FwsGameboot puis Install-Games, pose un marqueur. |
|
||||||
|
| `Install-FwsGameboot.ps1` | Auto-login + tâche **logon** (fws-play) + tâche **ONSTART** + `powercfg /h off`. Vérifie compte admin/sans mot de passe. |
|
||||||
|
| `Install-Games.ps1` | Télécharge + lance les installeurs **officiels** des jeux `autoinstall` (Riot). |
|
||||||
|
| `installers.json` | URLs des installeurs Riot (à vérifier — Riot les change). |
|
||||||
|
| `fws-play.ps1` | Lit le jeu demandé, le lance, attend, rebascule vers FWS. |
|
||||||
|
| `fws-return.ps1` | Arme le boot suivant vers `FWS` ou `Windows` (locale-indépendant). |
|
||||||
|
| `games.json` | Registre des jeux : `id → {type, …, process}`. |
|
||||||
|
|
||||||
|
## Ce que FWS ne peut PAS faire (et ce qu'il fait à la place)
|
||||||
|
|
||||||
|
FWS **ne pré-embarque pas** Valorant/LoL : bundler Windows (licence) et
|
||||||
|
redistribuer les binaires Riot est interdit, et Vanguard flaggerait un jeu
|
||||||
|
pré-copié. **À la place**, `Install-Games.ps1` **télécharge les installeurs
|
||||||
|
officiels depuis Riot** au premier boot → même résultat (« ils sont déjà là »)
|
||||||
|
sans rien redistribuer. L'install de Valorant n'est pas 100 % silencieuse
|
||||||
|
(Vanguard installe un driver noyau + impose un reboot).
|
||||||
|
|
||||||
|
## Câbler le « premier démarrage automatique »
|
||||||
|
|
||||||
|
Le déclencheur dépend de comment Windows est installé :
|
||||||
|
|
||||||
|
**A. Install Windows piloté par FWS (recommandé, via `autounattend.xml`)** — ajouter
|
||||||
|
dans `<FirstLogonCommands>` :
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>1</Order>
|
||||||
|
<CommandLine>powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\ProgramData\FWS\deploy\FirstBoot-FwsGameboot.ps1"</CommandLine>
|
||||||
|
<Description>FWS Gameboot first boot</Description>
|
||||||
|
</SynchronousCommand>
|
||||||
|
```
|
||||||
|
(en ayant copié ce dossier dans `C:\ProgramData\FWS\deploy\` pendant le déploiement).
|
||||||
|
|
||||||
|
**B. Manuel (pour tester maintenant)** — dans le Windows gaming, PowerShell admin :
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Set-ExecutionPolicy -Scope Process Bypass -Force
|
||||||
|
.\FirstBoot-FwsGameboot.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
Idempotent : le marqueur `C:\ProgramData\FWS\.firstboot-done` empêche toute
|
||||||
|
réexécution.
|
||||||
|
|
||||||
|
## Prérequis (vérifiés par Install-FwsGameboot, échec explicite sinon)
|
||||||
|
|
||||||
|
- Compte gaming = **administrateur local** (bcdedit + tâches SYSTEM).
|
||||||
|
- Compte **sans mot de passe** (auto-login fiable), ou lancer avec `-Password`.
|
||||||
|
- **Secure Boot + TPM 2.0 + VBS/HVCI** actifs (Vanguard les exige).
|
||||||
|
|
||||||
|
## Flux complet (aucune interaction, hors confirmations Vanguard)
|
||||||
|
|
||||||
|
1. **1er boot** → `FirstBoot` → auto-login configuré + Valo/LoL téléchargés et installés.
|
||||||
|
2. Depuis FWS : `fws-gameboot to-windows --game valorant` → jeton sur l'ESP, hibernation, bascule.
|
||||||
|
3. Windows démarre **bare-metal** → **auto-login** → `fws-play` lit le jeton, lance le jeu.
|
||||||
|
4. À la fermeture/crash → retour, **session FWS restaurée** (apps/pages intactes).
|
||||||
|
|
||||||
|
Filet permanent : côté FWS, `BootOrder[0] = FWS` ré-affirmé à chaque démarrage.
|
||||||
|
|
||||||
|
## Ajouter un jeu
|
||||||
|
|
||||||
|
- Lancement : ajouter une entrée dans `games.json` (`id` en `[a-z0-9_-]`).
|
||||||
|
- Auto-install : ajouter l'URL de l'installeur officiel dans `installers.json`
|
||||||
|
(`autoinstall: true`). Types de lancement : `riot` / `uri` (`steam://…`,
|
||||||
|
`com.epicgames.launcher://…`) / `exe`.
|
||||||
|
|
||||||
|
## Rappels sécurité
|
||||||
|
|
||||||
|
- **Poste kiosque** : auto-login + tâches non-UAC ⇒ accès physique = bureau admin.
|
||||||
|
- **BitLocker** : ne pas l'activer sans sauvegarder la clé (flip Secure Boot →
|
||||||
|
écran de récupération). `manage-bde -status`.
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
<#
|
||||||
|
fws-play.ps1 — session de jeu Windows orchestree, lancement AUTO du jeu choisi.
|
||||||
|
|
||||||
|
Lit (et CONSOMME) le jeton depose par FWS sur l'ESP
|
||||||
|
(\EFI\FWS\launch.json = {"game":"<id>"}), cherche <id> dans games.json, lance
|
||||||
|
le jeu, attend sa fermeture/crash, puis rebascule vers FWS.
|
||||||
|
|
||||||
|
Robustesse (aucune interaction requise) :
|
||||||
|
- resolution du jeton sur TOUTES les ESP (dual-disque) + suppression apres
|
||||||
|
lecture (consume-once : pas de rejeu d'un ancien choix) ;
|
||||||
|
- resolution du Riot Client par sa source canonique (pas de chemin en dur) ;
|
||||||
|
- re-affirmation de BootNext=Windows apres le lancement, pour gagner la course
|
||||||
|
contre la tache ONSTART (fws-play = dernier ecrivain) — un crash pendant le
|
||||||
|
jeu revient donc bien sur Windows ;
|
||||||
|
- FINALLY : rearme FWS et reboote SI le jeu a bien ete vu.
|
||||||
|
|
||||||
|
Filet permanent cote FWS : BootOrder[0]=FWS. A lancer a l'ouverture de session
|
||||||
|
(autologin du compte gaming, qui DOIT etre admin local). Tourne EN ADMIN. #>
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$return = Join-Path $here 'fws-return.ps1'
|
||||||
|
$gamesDb = Join-Path $here 'games.json'
|
||||||
|
$seen = $false
|
||||||
|
|
||||||
|
$EFI_TYPE = '{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}' # GUID GPT "EFI System Partition"
|
||||||
|
|
||||||
|
function Get-RequestedGame {
|
||||||
|
# Sonde toutes les ESP, lit \EFI\FWS\launch.json, le SUPPRIME (consume-once).
|
||||||
|
# Defaut : valorant. Distingue "jeton absent" de "jeton illisible" (log).
|
||||||
|
$default = 'valorant'; $result = $default
|
||||||
|
$mounts = @()
|
||||||
|
try { $esps = Get-Partition -ErrorAction SilentlyContinue | Where-Object { $_.GptType -eq $EFI_TYPE } }
|
||||||
|
catch { $esps = @() }
|
||||||
|
|
||||||
|
foreach ($p in $esps) {
|
||||||
|
$used = (Get-PSDrive -PSProvider FileSystem -ErrorAction SilentlyContinue).Name
|
||||||
|
$letter = (67..90 | ForEach-Object { [char]$_ }) |
|
||||||
|
Where-Object { $used -notcontains "$_" -and ($mounts.Letter -notcontains "$_`:") } |
|
||||||
|
Select-Object -First 1
|
||||||
|
if (-not $letter) { continue }
|
||||||
|
try {
|
||||||
|
Add-PartitionAccessPath -DiskNumber $p.DiskNumber -PartitionNumber $p.PartitionNumber -AccessPath "$letter`:" -ErrorAction Stop
|
||||||
|
$mounts += @{ Letter = "$letter`:"; Disk = $p.DiskNumber; Part = $p.PartitionNumber }
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($m in $mounts) {
|
||||||
|
$tok = "$($m.Letter)\EFI\FWS\launch.json"
|
||||||
|
if (Test-Path $tok) {
|
||||||
|
try {
|
||||||
|
$g = (Get-Content $tok -Raw -ErrorAction Stop | ConvertFrom-Json).game
|
||||||
|
if ($g) { $result = "$g".ToLower() }
|
||||||
|
} catch { Write-Warning "Jeton illisible ($tok) : $_ — repli sur $default." }
|
||||||
|
Remove-Item $tok -Force -ErrorAction SilentlyContinue # consume-once
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($m in $mounts) {
|
||||||
|
try { Remove-PartitionAccessPath -DiskNumber $m.Disk -PartitionNumber $m.Part -AccessPath $m.Letter -ErrorAction SilentlyContinue } catch { }
|
||||||
|
}
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
|
||||||
|
function Resolve-RiotClient {
|
||||||
|
# Riot s'installe par defaut a la RACINE (C:\Riot Games\...), pas sous
|
||||||
|
# Program Files. Source canonique : RiotClientInstalls.json (rc_default).
|
||||||
|
$cands = @()
|
||||||
|
$inst = Join-Path $Env:ProgramData 'Riot Games\RiotClientInstalls.json'
|
||||||
|
if (Test-Path $inst) { try { $cands += (Get-Content $inst -Raw | ConvertFrom-Json).rc_default } catch { } }
|
||||||
|
$cands += 'C:\Riot Games\Riot Client\RiotClientServices.exe'
|
||||||
|
$cands += (Join-Path ${Env:ProgramFiles} 'Riot Games\Riot Client\RiotClientServices.exe')
|
||||||
|
if (${Env:ProgramFiles(x86)}) { $cands += (Join-Path ${Env:ProgramFiles(x86)} 'Riot Games\Riot Client\RiotClientServices.exe') }
|
||||||
|
return $cands | Where-Object { $_ -and (Test-Path $_) } | Select-Object -First 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function Start-Game($entry) {
|
||||||
|
switch ($entry.type) {
|
||||||
|
'riot' {
|
||||||
|
$riot = Resolve-RiotClient
|
||||||
|
if ($riot) { Start-Process $riot -ArgumentList "--launch-product=$($entry.product)","--launch-patchline=$($entry.patchline)" }
|
||||||
|
else { Write-Warning "Riot Client introuvable (ni RiotClientInstalls.json, ni chemins connus)." }
|
||||||
|
}
|
||||||
|
'uri' { Start-Process $entry.uri }
|
||||||
|
'exe' {
|
||||||
|
if ($entry.args) { Start-Process -FilePath $entry.path -ArgumentList @($entry.args) }
|
||||||
|
else { Start-Process -FilePath $entry.path }
|
||||||
|
}
|
||||||
|
default { Write-Warning "Type de lancement inconnu : $($entry.type)" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
# Pendant la session, un crash/reboot doit rester sur WINDOWS.
|
||||||
|
try { & $return -Target Windows } catch { Write-Warning "Arm Windows a echoue : $_" }
|
||||||
|
|
||||||
|
# Resoudre + VALIDER le jeu (rejeter les cles '_*' et les entrees sans .type).
|
||||||
|
$game = Get-RequestedGame
|
||||||
|
$games = Get-Content $gamesDb -Raw | ConvertFrom-Json
|
||||||
|
$entry = $games.$game
|
||||||
|
if ($game -like '_*' -or -not $entry -or -not $entry.type) {
|
||||||
|
Write-Warning "Jeu '$game' invalide/absent de games.json — repli sur valorant."
|
||||||
|
$game = 'valorant'; $entry = $games.valorant
|
||||||
|
}
|
||||||
|
if (-not $entry) { throw "Entree 'valorant' absente de games.json." }
|
||||||
|
Write-Host "Lancement du jeu : $game"
|
||||||
|
Start-Game $entry
|
||||||
|
|
||||||
|
# Course avec la tache ONSTART (qui arme FWS au boot) : on la laisse passer
|
||||||
|
# puis on RE-AFFIRME Windows → fws-play est le dernier ecrivain de BootNext.
|
||||||
|
Start-Sleep -Seconds 25
|
||||||
|
try { & $return -Target Windows } catch { Write-Warning "Re-arm Windows a echoue : $_" }
|
||||||
|
|
||||||
|
# Attendre l'apparition PUIS la disparition du process (boucle de presence,
|
||||||
|
# robuste aux relances client). Ne rebooter que si le jeu a ete VU.
|
||||||
|
$proc = $entry.process
|
||||||
|
if ($proc) {
|
||||||
|
for ($i = 0; $i -lt 1440; $i++) { # ~2 h max d'attente d'apparition
|
||||||
|
if (Get-Process $proc -ErrorAction SilentlyContinue) { $seen = $true; break }
|
||||||
|
Start-Sleep -Seconds 5
|
||||||
|
}
|
||||||
|
if ($seen) {
|
||||||
|
while (Get-Process $proc -ErrorAction SilentlyContinue) { Start-Sleep -Seconds 5 }
|
||||||
|
} else {
|
||||||
|
Write-Warning "Process '$proc' jamais detecte — pas de reboot automatique."
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Warning "Aucun 'process' defini pour '$game' — pas d'attente ni de reboot auto."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
# Rearmer FWS, QUOI QU'IL ARRIVE.
|
||||||
|
try { & $return -Target FWS } catch { Write-Warning "Arm FWS a echoue : $_ — BootOrder[0]=FWS reste le filet." }
|
||||||
|
if ($seen) {
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
Restart-Computer -Force
|
||||||
|
} else {
|
||||||
|
Write-Warning "Session sans jeu detecte : redemarre manuellement pour revenir a FWS (BootNext deja arme vers FWS)."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<#
|
||||||
|
fws-return.ps1 — arme le "boot suivant" du firmware (one-shot) vers une cible.
|
||||||
|
|
||||||
|
Cote FWS, l'equivalent est « efibootmgr --bootnext ». Cote Windows, on utilise
|
||||||
|
« bcdedit /set {fwbootmgr} bootsequence <id> » : la sequence de boot UNE FOIS
|
||||||
|
du firmware (consommee au prochain demarrage puis oubliee), pendant exact de
|
||||||
|
BootNext.
|
||||||
|
|
||||||
|
IMPORTANT (robustesse locale) : on NE parse PAS les libelles traduits de
|
||||||
|
« bcdedit /enum firmware » (sur un Windows francais « identifier » devient
|
||||||
|
« identificateur », « Windows Boot Manager » -> « Gestionnaire d'amorçage
|
||||||
|
Windows »). On resout :
|
||||||
|
- Windows : par l'alias NON localise {bootmgr} (aucun parsing) ;
|
||||||
|
- FWS : par le CHEMIN EFI (\EFI\FWS\ ou \EFI\GRUB\ ou grubx64.efi), qui
|
||||||
|
n'est pas traduit, en associant le GUID (motif stable) du bloc.
|
||||||
|
Si FWS est introuvable, on DESARME la one-shot (deletevalue) pour retomber
|
||||||
|
proprement sur BootOrder[0]=FWS plutot que de laisser une bascule erronee.
|
||||||
|
|
||||||
|
Doit tourner EN ADMIN. Usage : fws-return.ps1 -Target FWS | -Target Windows
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[ValidateSet('FWS','Windows')]
|
||||||
|
[string]$Target = 'FWS'
|
||||||
|
)
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
function Set-BootSequence([string]$id) {
|
||||||
|
& bcdedit /set '{fwbootmgr}' bootsequence $id | Out-Null
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "bcdedit bootsequence $id a echoue (code $LASTEXITCODE)." }
|
||||||
|
}
|
||||||
|
function Clear-BootSequence {
|
||||||
|
& bcdedit /deletevalue '{fwbootmgr}' bootsequence 2>$null | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Target -eq 'Windows') {
|
||||||
|
# Alias non localise -> aucun parsing, aucune dependance a la langue.
|
||||||
|
Set-BootSequence '{bootmgr}'
|
||||||
|
Write-Host "Boot suivant arme vers Windows ({bootmgr})."
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Target = FWS : resoudre le GUID de l'entree firmware pointant vers FWS ---
|
||||||
|
# /v force l'affichage des GUID complets. On matche le CHEMIN (non localise),
|
||||||
|
# pas la description (localisee).
|
||||||
|
$fw = & bcdedit /enum firmware /v 2>$null
|
||||||
|
if (-not $fw) { throw "bcdedit /enum firmware n'a rien renvoye (droits admin ?)." }
|
||||||
|
|
||||||
|
$reId = '\{[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\}|\{[a-z]+\}'
|
||||||
|
$rePath = '(?i)\\EFI\\(FWS|GRUB)\\|grubx64\.efi'
|
||||||
|
|
||||||
|
$curId = $null; $fwsId = $null
|
||||||
|
foreach ($line in $fw) {
|
||||||
|
$m = [regex]::Match($line, $reId)
|
||||||
|
if ($m.Success) { $curId = $m.Value; continue }
|
||||||
|
if ($line -match $rePath -and $curId) { $fwsId = $curId; break }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $fwsId) {
|
||||||
|
# Rien trouve par le chemin : on ne devine PAS. On desarme toute one-shot
|
||||||
|
# pour retomber sur BootOrder[0]=FWS (filet permanent cote FWS).
|
||||||
|
Clear-BootSequence
|
||||||
|
throw "Entree firmware FWS introuvable (chemin \EFI\FWS ou \EFI\GRUB). One-shot desarmee -> BootOrder assure le retour vers FWS."
|
||||||
|
}
|
||||||
|
|
||||||
|
Set-BootSequence $fwsId
|
||||||
|
Write-Host "Boot suivant arme vers FWS = $fwsId"
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"_comment": "Registre des jeux lançables par fws-play. Cle = identifiant (charte [a-z0-9_-]) passe a 'fws-gameboot to-windows --game <id>'. 'process' = nom du processus a attendre (sans .exe). Types: 'riot' (Riot Client), 'uri' (steam://rungameid/NNN, com.epicgames.launcher://...), 'exe' (chemin direct, args = tableau). Les cles commencant par '_' sont ignorees. Pour ajouter un jeu, copie une entree ci-dessous.",
|
||||||
|
|
||||||
|
"valorant": {
|
||||||
|
"type": "riot",
|
||||||
|
"product": "valorant",
|
||||||
|
"patchline": "live",
|
||||||
|
"process": "VALORANT-Win64-Shipping"
|
||||||
|
},
|
||||||
|
|
||||||
|
"lol": {
|
||||||
|
"type": "riot",
|
||||||
|
"product": "league_of_legends",
|
||||||
|
"patchline": "live",
|
||||||
|
"process": "League of Legends"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"_comment": "Sources d'installation OFFICIELLES, telechargees au 1er boot par Install-Games.ps1. FWS ne redistribue AUCUN binaire de jeu : tout est telecharge depuis Riot. ATTENTION : Riot CHANGE regulierement ces URLs (region/patchline) -> a VERIFIER (bouton Telecharger sur playvalorant.com / leagueoflegends.com, copier le lien). Region par defaut : EU (utilisateur fr_CH). Mets 'autoinstall': false pour ne pas installer un jeu au 1er boot.",
|
||||||
|
|
||||||
|
"valorant": {
|
||||||
|
"autoinstall": true,
|
||||||
|
"url": "https://valorant.secure.dyn.riotcdn.net/channels/public/x/installer/current/live.live.eu.exe",
|
||||||
|
"installer": "valorant-setup.exe",
|
||||||
|
"silentargs": ""
|
||||||
|
},
|
||||||
|
|
||||||
|
"lol": {
|
||||||
|
"autoinstall": true,
|
||||||
|
"url": "https://lol.secure.dyn.riotcdn.net/channels/public/x/installer/current/live.euw.exe",
|
||||||
|
"installer": "lol-setup.exe",
|
||||||
|
"silentargs": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
+95
@@ -0,0 +1,95 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
FWS — answer file OFFLINE pour Windows 11, deploye a \Windows\Panther\unattend.xml
|
||||||
|
par fws-windows-deploy APRES l'application de install.wim (wimlib). Setup NE
|
||||||
|
TOURNE JAMAIS : pas de passe windowsPE, pas de DiskConfiguration (la partition
|
||||||
|
cible est adressee cote Linux par PARTLABEL). Seuls specialize + oobeSystem
|
||||||
|
s'executent au 1er boot de l'image generalisee.
|
||||||
|
|
||||||
|
Tokens substitues par sed (fws-windows-deploy) :
|
||||||
|
__HOSTNAME__ __USER__ __DISPLAY__ __LOCALE__ __INPUT__ __FIRSTBOOT__
|
||||||
|
Compte gaming LOCAL admin SANS mot de passe (contrat auto-login) — JAMAIS nomme
|
||||||
|
'Administrator' (25H2 le confond avec le compte integre). AutoLogon temporaire
|
||||||
|
(LogonCount) survit au reboot Vanguard le temps que FirstBoot pose l'auto-login
|
||||||
|
permanent (Winlogon) via Install-FwsGameboot.
|
||||||
|
-->
|
||||||
|
<unattend xmlns="urn:schemas-microsoft-com:unattend">
|
||||||
|
|
||||||
|
<settings pass="specialize">
|
||||||
|
<component name="Microsoft-Windows-Shell-Setup"
|
||||||
|
processorArchitecture="amd64"
|
||||||
|
language="neutral"
|
||||||
|
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
|
||||||
|
versionScope="nonSxS">
|
||||||
|
<ComputerName>__HOSTNAME__</ComputerName>
|
||||||
|
</component>
|
||||||
|
<component name="Microsoft-Windows-Deployment"
|
||||||
|
processorArchitecture="amd64"
|
||||||
|
language="neutral"
|
||||||
|
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
|
||||||
|
versionScope="nonSxS">
|
||||||
|
<RunSynchronous>
|
||||||
|
<!-- Ceintures (doublons du reg offline hivex de fws-windows-deploy). -->
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>1</Order>
|
||||||
|
<Path>reg add "HKLM\SYSTEM\CurrentControlSet\Control\BitLocker" /v PreventDeviceEncryption /t REG_DWORD /d 1 /f</Path>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>2</Order>
|
||||||
|
<Path>reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" /v BypassNRO /t REG_DWORD /d 1 /f</Path>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
</RunSynchronous>
|
||||||
|
</component>
|
||||||
|
</settings>
|
||||||
|
|
||||||
|
<settings pass="oobeSystem">
|
||||||
|
<component name="Microsoft-Windows-International-Core"
|
||||||
|
processorArchitecture="amd64"
|
||||||
|
language="neutral"
|
||||||
|
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
|
||||||
|
versionScope="nonSxS">
|
||||||
|
<InputLocale>__INPUT__</InputLocale>
|
||||||
|
<SystemLocale>__LOCALE__</SystemLocale>
|
||||||
|
<!-- Pas de <UILanguage> : une install.wim mono-langue (en-US) n'a pas le
|
||||||
|
pack UI __LOCALE__ → le forcer casse specialize. On garde en-US pour l'UI. -->
|
||||||
|
<UserLocale>__LOCALE__</UserLocale>
|
||||||
|
</component>
|
||||||
|
<component name="Microsoft-Windows-Shell-Setup"
|
||||||
|
processorArchitecture="amd64"
|
||||||
|
language="neutral"
|
||||||
|
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
|
||||||
|
versionScope="nonSxS">
|
||||||
|
<OOBE>
|
||||||
|
<HideEULAPage>true</HideEULAPage>
|
||||||
|
<HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
|
||||||
|
<HideOnlineAccountScreens>true</HideOnlineAccountScreens>
|
||||||
|
<HideLocalAccountScreen>true</HideLocalAccountScreen>
|
||||||
|
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
|
||||||
|
<ProtectYourPC>3</ProtectYourPC>
|
||||||
|
</OOBE>
|
||||||
|
<UserAccounts>
|
||||||
|
<LocalAccounts>
|
||||||
|
<LocalAccount wcm:action="add">
|
||||||
|
<Name>__USER__</Name>
|
||||||
|
<DisplayName>__DISPLAY__</DisplayName>
|
||||||
|
<Group>Administrators</Group>
|
||||||
|
<!-- PAS de <Password> : compte sans mot de passe (contrat auto-login FWS). -->
|
||||||
|
</LocalAccount>
|
||||||
|
</LocalAccounts>
|
||||||
|
</UserAccounts>
|
||||||
|
<AutoLogon>
|
||||||
|
<Enabled>true</Enabled>
|
||||||
|
<Username>__USER__</Username>
|
||||||
|
<LogonCount>5</LogonCount>
|
||||||
|
</AutoLogon>
|
||||||
|
<FirstLogonCommands>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>1</Order>
|
||||||
|
<Description>FWS Gameboot first boot</Description>
|
||||||
|
<CommandLine>powershell.exe -NoProfile -ExecutionPolicy Bypass -File "__FIRSTBOOT__"</CommandLine>
|
||||||
|
</SynchronousCommand>
|
||||||
|
</FirstLogonCommands>
|
||||||
|
</component>
|
||||||
|
</settings>
|
||||||
|
|
||||||
|
</unattend>
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
# FWS — configuration Hyprland par défaut.
|
||||||
|
# Le clavier (__KB_LAYOUT__ / __KB_VARIANT__) est injecté à l'installation,
|
||||||
|
# depuis le choix fait dans Anaconda. Édite ce fichier librement.
|
||||||
|
#
|
||||||
|
# Raccourcis : transposition de la config i3/sway FWS (mêmes touches).
|
||||||
|
# Couleurs : nuances de bleu (palette Catppuccin Mocha).
|
||||||
|
#
|
||||||
|
# Les raccourcis sont déclarés avec « bindd » (bind + description) : la
|
||||||
|
# description alimente l'écran « Raccourcis clavier » (bouton ⌨ de la barre
|
||||||
|
# ou SUPER+K → fws-keys, qui lit les binds RÉELS via hyprctl). Ajoute
|
||||||
|
# tes propres bindd : ils apparaîtront automatiquement dans la liste.
|
||||||
|
# ⚠ Pas de virgule dans les descriptions (séparateur de champs Hyprland).
|
||||||
|
|
||||||
|
monitor = , preferred, auto, 1
|
||||||
|
|
||||||
|
input {
|
||||||
|
kb_layout = __KB_LAYOUT__
|
||||||
|
kb_variant = __KB_VARIANT__
|
||||||
|
follow_mouse = 1
|
||||||
|
numlock_by_default = true
|
||||||
|
touchpad {
|
||||||
|
natural_scroll = true
|
||||||
|
tap-to-click = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
general {
|
||||||
|
gaps_in = 4
|
||||||
|
gaps_out = 8
|
||||||
|
border_size = 2
|
||||||
|
# Nuances de bleu : bleu → sapphire en dégradé sur la fenêtre active.
|
||||||
|
col.active_border = rgb(89b4fa) rgb(74c7ec) 45deg
|
||||||
|
col.inactive_border = rgb(313244)
|
||||||
|
layout = dwindle
|
||||||
|
}
|
||||||
|
|
||||||
|
decoration {
|
||||||
|
rounding = 8
|
||||||
|
# Flou et ombres COUPÉS : rendu logiciel (llvmpipe) en VM — cf. le
|
||||||
|
# wrapper fws-hyprland écrit par l'installateur. Sur un vrai GPU tu
|
||||||
|
# peux les réactiver (enabled = true).
|
||||||
|
blur {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
shadow {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
animations {
|
||||||
|
enabled = true
|
||||||
|
bezier = fws, 0.22, 1.0, 0.36, 1.0
|
||||||
|
animation = windows, 1, 3, fws
|
||||||
|
animation = workspaces, 1, 3, fws
|
||||||
|
animation = fade, 1, 2, default
|
||||||
|
}
|
||||||
|
|
||||||
|
misc {
|
||||||
|
disable_hyprland_logo = true
|
||||||
|
disable_splash_rendering = true # pas de « Thanks xxx! » en bas d'écran
|
||||||
|
force_default_wallpaper = 0
|
||||||
|
background_color = 0x1a1a2e # bleu nuit FWS (aucun fond requis)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Démarrage de session : barre d'état, notifications, réseau dans le tray,
|
||||||
|
# agent d'authentification GUI (indispensable pour les apps qui demandent
|
||||||
|
# les droits admin : gestionnaire de disques, blueman…).
|
||||||
|
exec-once = waybar
|
||||||
|
exec-once = mako
|
||||||
|
exec-once = nm-applet
|
||||||
|
# Applet Bluetooth : seulement si un adaptateur existe (en VM il n'y en a
|
||||||
|
# pas → blueman afficherait une erreur « Connection to BlueZ failed »).
|
||||||
|
exec-once = sh -c '[ -n "$(ls -A /sys/class/bluetooth 2>/dev/null)" ] && exec blueman-applet'
|
||||||
|
exec-once = /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1
|
||||||
|
# Fenêtre de bienvenue (1er démarrage seulement — se retire ensuite).
|
||||||
|
exec-once = fws-hello --autostart
|
||||||
|
|
||||||
|
$mod = SUPER
|
||||||
|
|
||||||
|
# --- Applications ---------------------------------------------------------
|
||||||
|
bindd = $mod, Return, Ouvrir un terminal (kitty), exec, kitty
|
||||||
|
bindd = $mod SHIFT, Return, Terminal flottant, exec, kitty --class floating-term
|
||||||
|
bindd = $mod, R, Menu des applications, exec, wofi --show drun
|
||||||
|
bindd = $mod, E, Gestionnaire de fichiers, exec, dolphin
|
||||||
|
bindd = $mod, B, Ouvrir Firefox, exec, firefox
|
||||||
|
bindd = $mod, K, Afficher les raccourcis clavier, exec, fws-keys
|
||||||
|
bindd = $mod, X, Menu d'alimentation (arrêt/redémarrage…), exec, fws-powermenu
|
||||||
|
|
||||||
|
# --- Fenêtres ---------------------------------------------------------------
|
||||||
|
bindd = $mod, Q, Fermer la fenêtre, killactive,
|
||||||
|
bindd = $mod, V, Fenêtre flottante ou ancrée, togglefloating,
|
||||||
|
bindd = $mod, F, Plein écran, fullscreen,
|
||||||
|
bindd = $mod, J, Basculer la découpe horizontale/verticale, layoutmsg, togglesplit
|
||||||
|
bindd = $mod, L, Verrouiller l'écran, exec, hyprlock
|
||||||
|
bindd = $mod SHIFT, E, Quitter la session (retour à SDDM), exit,
|
||||||
|
bindd = $mod SHIFT, C, Recharger la configuration Hyprland, exec, hyprctl reload
|
||||||
|
|
||||||
|
# --- Focus (flèches) ---------------------------------------------------------
|
||||||
|
bindd = $mod, left, Focus à gauche, movefocus, l
|
||||||
|
bindd = $mod, right, Focus à droite, movefocus, r
|
||||||
|
bindd = $mod, up, Focus en haut, movefocus, u
|
||||||
|
bindd = $mod, down, Focus en bas, movefocus, d
|
||||||
|
|
||||||
|
# --- Déplacer la fenêtre (Maj + flèches) ---------------------------------------
|
||||||
|
bindd = $mod SHIFT, left, Déplacer la fenêtre à gauche, movewindow, l
|
||||||
|
bindd = $mod SHIFT, right, Déplacer la fenêtre à droite, movewindow, r
|
||||||
|
bindd = $mod SHIFT, up, Déplacer la fenêtre en haut, movewindow, u
|
||||||
|
bindd = $mod SHIFT, down, Déplacer la fenêtre en bas, movewindow, d
|
||||||
|
|
||||||
|
# --- Redimensionner (Ctrl + flèches, maintien possible) ------------------------
|
||||||
|
binde = $mod CTRL, left, resizeactive, -30 0
|
||||||
|
binde = $mod CTRL, right, resizeactive, 30 0
|
||||||
|
binde = $mod CTRL, up, resizeactive, 0 -30
|
||||||
|
binde = $mod CTRL, down, resizeactive, 0 30
|
||||||
|
|
||||||
|
# --- Espaces de travail 1-10 (0 = espace 10, comme i3) --------------------------
|
||||||
|
bindd = $mod, 1, Aller à l'espace 1, workspace, 1
|
||||||
|
bindd = $mod, 2, Aller à l'espace 2, workspace, 2
|
||||||
|
bindd = $mod, 3, Aller à l'espace 3, workspace, 3
|
||||||
|
bindd = $mod, 4, Aller à l'espace 4, workspace, 4
|
||||||
|
bindd = $mod, 5, Aller à l'espace 5, workspace, 5
|
||||||
|
bindd = $mod, 6, Aller à l'espace 6, workspace, 6
|
||||||
|
bindd = $mod, 7, Aller à l'espace 7, workspace, 7
|
||||||
|
bindd = $mod, 8, Aller à l'espace 8, workspace, 8
|
||||||
|
bindd = $mod, 9, Aller à l'espace 9, workspace, 9
|
||||||
|
bindd = $mod, 0, Aller à l'espace 10, workspace, 10
|
||||||
|
bindd = $mod SHIFT, 1, Envoyer la fenêtre vers l'espace 1, movetoworkspace, 1
|
||||||
|
bindd = $mod SHIFT, 2, Envoyer la fenêtre vers l'espace 2, movetoworkspace, 2
|
||||||
|
bindd = $mod SHIFT, 3, Envoyer la fenêtre vers l'espace 3, movetoworkspace, 3
|
||||||
|
bindd = $mod SHIFT, 4, Envoyer la fenêtre vers l'espace 4, movetoworkspace, 4
|
||||||
|
bindd = $mod SHIFT, 5, Envoyer la fenêtre vers l'espace 5, movetoworkspace, 5
|
||||||
|
bindd = $mod SHIFT, 6, Envoyer la fenêtre vers l'espace 6, movetoworkspace, 6
|
||||||
|
bindd = $mod SHIFT, 7, Envoyer la fenêtre vers l'espace 7, movetoworkspace, 7
|
||||||
|
bindd = $mod SHIFT, 8, Envoyer la fenêtre vers l'espace 8, movetoworkspace, 8
|
||||||
|
bindd = $mod SHIFT, 9, Envoyer la fenêtre vers l'espace 9, movetoworkspace, 9
|
||||||
|
bindd = $mod SHIFT, 0, Envoyer la fenêtre vers l'espace 10, movetoworkspace, 10
|
||||||
|
|
||||||
|
# --- Multi-écrans (comme i3 : Super+Ctrl+1/2 = écran gauche/droit) -------------
|
||||||
|
bindd = $mod CTRL, 1, Envoyer la fenêtre vers l'écran de gauche, movewindow, mon:l
|
||||||
|
bindd = $mod CTRL, 2, Envoyer la fenêtre vers l'écran de droite, movewindow, mon:r
|
||||||
|
|
||||||
|
# --- Molette = changer d'espace de travail --------------------------------------
|
||||||
|
bindd = $mod, mouse_up, Espace de travail précédent, workspace, e-1
|
||||||
|
bindd = $mod, mouse_down, Espace de travail suivant, workspace, e+1
|
||||||
|
|
||||||
|
# --- Scratchpad (espace spécial) -------------------------------------------------
|
||||||
|
bindd = $mod, S, Afficher le scratchpad, togglespecialworkspace, magic
|
||||||
|
bindd = $mod SHIFT, S, Envoyer la fenêtre au scratchpad, movetoworkspace, special:magic
|
||||||
|
|
||||||
|
# --- Captures d'écran (grim/slurp → presse-papier ou ~/Images) -------------------
|
||||||
|
bindd = , Print, Capture plein écran vers le presse-papier, exec, grim - | wl-copy
|
||||||
|
bindd = $mod, Print, Capture d'une zone vers le presse-papier, exec, grim -g "$(slurp)" - | wl-copy
|
||||||
|
bindd = $mod SHIFT, Print, Capture d'une zone vers ~/Images, exec, sh -c 'mkdir -p ~/Images && grim -g "$(slurp)" ~/Images/capture_$(date +%Y-%m-%d_%H-%M-%S).png'
|
||||||
|
|
||||||
|
# --- Multimédia (touches XF86 : volume via PipeWire, lecture via playerctl) ------
|
||||||
|
bindel = , XF86AudioRaiseVolume, exec, wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+
|
||||||
|
bindel = , XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
|
||||||
|
bindl = , XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
|
||||||
|
bindl = , XF86AudioPlay, exec, playerctl play-pause
|
||||||
|
bindl = , XF86AudioNext, exec, playerctl next
|
||||||
|
bindl = , XF86AudioPrev, exec, playerctl previous
|
||||||
|
|
||||||
|
# --- Luminosité de l'écran (portables : touches Fn) ------------------------------
|
||||||
|
bindel = , XF86MonBrightnessUp, exec, brightnessctl set +5%
|
||||||
|
bindel = , XF86MonBrightnessDown, exec, brightnessctl set 5%-
|
||||||
|
|
||||||
|
# --- Souris : déplacer / redimensionner les fenêtres -----------------------------
|
||||||
|
bindm = $mod, mouse:272, movewindow
|
||||||
|
bindm = $mod, mouse:273, resizewindow
|
||||||
|
|
||||||
|
# --- Règles de fenêtres (comme i3) ------------------------------------------------
|
||||||
|
# Syntaxe Hyprland ≥ 0.55 : champs « <règle> <valeur> » + cibles « match:… »
|
||||||
|
# (validé contre le binaire avec --verify-config).
|
||||||
|
windowrule = float on, match:class ^(floating-term)$
|
||||||
|
windowrule = size 900 550, match:class ^(floating-term)$
|
||||||
|
windowrule = float on, match:class ^([Pp]avucontrol|org.pulseaudio.pavucontrol)$
|
||||||
|
windowrule = float on, match:class ^(.?[Bb]lueman-manager.*)$
|
||||||
|
windowrule = float on, match:class ^([Ff]ws-hello)$
|
||||||
|
windowrule = float on, match:class ^([Ff]ws-about)$
|
||||||
|
windowrule = float on, match:title ^(Picture-in-Picture)$
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
# FWS — configuration i3 par défaut (session X11, COMPATIBLE VR).
|
||||||
|
# SteamVR fonctionne en X11 : c'est le bureau à choisir pour un casque VR
|
||||||
|
# (HTC Vive, Valve Index…). Lance « fws-vr-setup » (Super+Shift+V, ou menu)
|
||||||
|
# pour installer les drivers/runtimes selon ton casque.
|
||||||
|
# Le clavier X11 est configuré système via /etc/X11/xorg.conf.d/00-keyboard.conf
|
||||||
|
# (écrit par l'installateur selon ton choix Anaconda) : rien à régler ici.
|
||||||
|
|
||||||
|
set $mod Mod4
|
||||||
|
set $term kitty
|
||||||
|
set $filemanager dolphin
|
||||||
|
set $menu rofi -show drun -show-icons
|
||||||
|
set $browser firefox
|
||||||
|
|
||||||
|
# Couleurs — nuances de bleu (Catppuccin Mocha, comme SDDM/waybar FWS)
|
||||||
|
set $base #1e1e2e
|
||||||
|
set $surface0 #313244
|
||||||
|
set $text #cdd6f4
|
||||||
|
set $blue #89b4fa
|
||||||
|
set $sapphire #74c7ec
|
||||||
|
set $red #f38ba8
|
||||||
|
|
||||||
|
# classe bordure fond texte indicateur child_border
|
||||||
|
client.focused $blue $blue $base $sapphire $blue
|
||||||
|
client.focused_inactive $surface0 $surface0 $text $surface0 $surface0
|
||||||
|
client.unfocused $surface0 $surface0 $text $surface0 $surface0
|
||||||
|
client.urgent $red $red $base $red $red
|
||||||
|
client.background $base
|
||||||
|
|
||||||
|
font pango:Noto Sans 10
|
||||||
|
default_border pixel 2
|
||||||
|
gaps inner 4
|
||||||
|
gaps outer 4
|
||||||
|
|
||||||
|
##################
|
||||||
|
## APPLICATIONS ##
|
||||||
|
##################
|
||||||
|
bindsym $mod+Return exec $term
|
||||||
|
bindsym $mod+Shift+Return exec kitty --class floating-term
|
||||||
|
bindsym $mod+r exec $menu
|
||||||
|
bindsym $mod+e exec $filemanager
|
||||||
|
bindsym $mod+b exec $browser
|
||||||
|
# VR : installation des drivers/runtimes casque (HTC Vive, Index, Quest…)
|
||||||
|
bindsym $mod+Shift+v exec kitty -e fws-vr-setup
|
||||||
|
# Panneau des raccourcis clavier (aussi via le bouton ⌌ de la barre)
|
||||||
|
bindsym $mod+k exec fws-keys
|
||||||
|
# Menu d'alimentation (verrouiller/déconnexion/redémarrer/éteindre)
|
||||||
|
bindsym $mod+x exec fws-powermenu
|
||||||
|
|
||||||
|
##############
|
||||||
|
## FENETRES ##
|
||||||
|
##############
|
||||||
|
bindsym $mod+q kill
|
||||||
|
bindsym $mod+v floating toggle
|
||||||
|
bindsym $mod+f fullscreen toggle
|
||||||
|
bindsym $mod+j split toggle
|
||||||
|
bindsym $mod+l exec i3lock -c 1e1e2e
|
||||||
|
# Quitter i3 → retour à SDDM
|
||||||
|
bindsym $mod+Shift+e exec "i3-nagbar -t warning -m 'Quitter i3 et revenir à SDDM ?' -B 'Oui, quitter' 'i3-msg exit'"
|
||||||
|
bindsym $mod+Shift+c reload
|
||||||
|
|
||||||
|
# Focus (flèches)
|
||||||
|
bindsym $mod+Left focus left
|
||||||
|
bindsym $mod+Right focus right
|
||||||
|
bindsym $mod+Up focus up
|
||||||
|
bindsym $mod+Down focus down
|
||||||
|
# Déplacer la fenêtre
|
||||||
|
bindsym $mod+Shift+Left move left
|
||||||
|
bindsym $mod+Shift+Right move right
|
||||||
|
bindsym $mod+Shift+Up move up
|
||||||
|
bindsym $mod+Shift+Down move down
|
||||||
|
# Redimensionner (Ctrl + flèches)
|
||||||
|
bindsym $mod+Ctrl+Left resize shrink width 30 px
|
||||||
|
bindsym $mod+Ctrl+Right resize grow width 30 px
|
||||||
|
bindsym $mod+Ctrl+Up resize shrink height 30 px
|
||||||
|
bindsym $mod+Ctrl+Down resize grow height 30 px
|
||||||
|
|
||||||
|
################
|
||||||
|
## WORKSPACES ##
|
||||||
|
################
|
||||||
|
bindsym $mod+1 workspace number 1
|
||||||
|
bindsym $mod+2 workspace number 2
|
||||||
|
bindsym $mod+3 workspace number 3
|
||||||
|
bindsym $mod+4 workspace number 4
|
||||||
|
bindsym $mod+5 workspace number 5
|
||||||
|
bindsym $mod+6 workspace number 6
|
||||||
|
bindsym $mod+7 workspace number 7
|
||||||
|
bindsym $mod+8 workspace number 8
|
||||||
|
bindsym $mod+9 workspace number 9
|
||||||
|
bindsym $mod+0 workspace number 10
|
||||||
|
bindsym $mod+Shift+1 move container to workspace number 1
|
||||||
|
bindsym $mod+Shift+2 move container to workspace number 2
|
||||||
|
bindsym $mod+Shift+3 move container to workspace number 3
|
||||||
|
bindsym $mod+Shift+4 move container to workspace number 4
|
||||||
|
bindsym $mod+Shift+5 move container to workspace number 5
|
||||||
|
bindsym $mod+Shift+6 move container to workspace number 6
|
||||||
|
bindsym $mod+Shift+7 move container to workspace number 7
|
||||||
|
bindsym $mod+Shift+8 move container to workspace number 8
|
||||||
|
bindsym $mod+Shift+9 move container to workspace number 9
|
||||||
|
bindsym $mod+Shift+0 move container to workspace number 10
|
||||||
|
# Multi-écrans
|
||||||
|
bindsym $mod+Ctrl+1 move container to output left
|
||||||
|
bindsym $mod+Ctrl+2 move container to output right
|
||||||
|
# Molette = changer de workspace
|
||||||
|
bindsym $mod+button4 workspace prev
|
||||||
|
bindsym $mod+button5 workspace next
|
||||||
|
# Scratchpad
|
||||||
|
bindsym $mod+s scratchpad show
|
||||||
|
bindsym $mod+Shift+s move scratchpad
|
||||||
|
|
||||||
|
##############
|
||||||
|
## CAPTURES ##
|
||||||
|
##############
|
||||||
|
bindsym Print exec --no-startup-id "maim | xclip -selection clipboard -t image/png"
|
||||||
|
bindsym $mod+Print exec --no-startup-id "maim -s | xclip -selection clipboard -t image/png"
|
||||||
|
bindsym $mod+Shift+Print exec --no-startup-id "mkdir -p ~/Images && maim -s ~/Images/capture_$(date +%Y-%m-%d_%H-%M-%S).png"
|
||||||
|
|
||||||
|
################
|
||||||
|
## MULTIMEDIA ##
|
||||||
|
################
|
||||||
|
bindsym XF86AudioRaiseVolume exec --no-startup-id wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+
|
||||||
|
bindsym XF86AudioLowerVolume exec --no-startup-id wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
|
||||||
|
bindsym XF86AudioMute exec --no-startup-id wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
|
||||||
|
bindsym XF86AudioPlay exec --no-startup-id playerctl play-pause
|
||||||
|
bindsym XF86AudioNext exec --no-startup-id playerctl next
|
||||||
|
bindsym XF86AudioPrev exec --no-startup-id playerctl previous
|
||||||
|
bindsym XF86MonBrightnessUp exec --no-startup-id brightnessctl set +5%
|
||||||
|
bindsym XF86MonBrightnessDown exec --no-startup-id brightnessctl set 5%-
|
||||||
|
|
||||||
|
#####################
|
||||||
|
## REGLES FENETRES ##
|
||||||
|
#####################
|
||||||
|
for_window [class="floating-term"] floating enable, resize set 900 550
|
||||||
|
for_window [class="(?i)fws-hello"] floating enable
|
||||||
|
for_window [class="(?i)fws-about"] floating enable
|
||||||
|
for_window [class="(?i)pavucontrol"] floating enable, resize set 700 500
|
||||||
|
for_window [class="(?i)blueman-manager"] floating enable
|
||||||
|
for_window [title="Picture-in-Picture"] floating enable
|
||||||
|
|
||||||
|
###########
|
||||||
|
## BARRE ##
|
||||||
|
###########
|
||||||
|
# Polybar (config ~/.config/polybar/config.ini) : clone visuel de la waybar
|
||||||
|
# Hyprland FWS — pilules, icônes, horloge+date au centre, bouton Raccourcis.
|
||||||
|
# exec_always + pkill : la barre survit proprement aux reload ($mod+Shift+c).
|
||||||
|
exec_always --no-startup-id sh -c 'pkill -x polybar; sleep 0.3; exec polybar -q fws'
|
||||||
|
|
||||||
|
###############
|
||||||
|
## AUTOSTART ##
|
||||||
|
###############
|
||||||
|
# picom : coins arrondis (parité visuelle avec Hyprland) — config FWS légère.
|
||||||
|
# PAS en machine virtuelle : le compositing sur GPU virtuel (vmwgfx/llvmpipe)
|
||||||
|
# rate le repaint des fenêtres GTK → contenus noirs/artefacts. Les coins
|
||||||
|
# arrondis sont sacrifiés en VM ; sur matériel réel picom se lance normalement.
|
||||||
|
exec --no-startup-id sh -c 'systemd-detect-virt --quiet || exec picom --config ~/.config/picom.conf'
|
||||||
|
exec --no-startup-id dunst
|
||||||
|
exec --no-startup-id nm-applet
|
||||||
|
exec --no-startup-id /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1
|
||||||
|
exec --no-startup-id sh -c '[ -n "$(ls -A /sys/class/bluetooth 2>/dev/null)" ] && exec blueman-applet'
|
||||||
|
# Fenêtre de bienvenue (1er démarrage seulement — se retire ensuite).
|
||||||
|
exec --no-startup-id fws-hello --autostart
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
$1████████ $2██$5████$2██
|
||||||
|
$1████████ $2█$5██$2██$5██$2█
|
||||||
|
$1████████ $2█$5██████$2█
|
||||||
|
$1████████ $2██$6█$2██$6█$2██
|
||||||
|
|
||||||
|
$3████████ $4████████
|
||||||
|
$3████████ $4████████
|
||||||
|
$3████████ $4████████
|
||||||
|
$3████████ $4████████
|
||||||
@@ -3,7 +3,7 @@ PRETTY_NAME="FWS Linux"
|
|||||||
ID=fws
|
ID=fws
|
||||||
ID_LIKE=arch
|
ID_LIKE=arch
|
||||||
BUILD_ID=rolling
|
BUILD_ID=rolling
|
||||||
VERSION_ID="1.0.0"
|
VERSION_ID="0.6.0"
|
||||||
ANSI_COLOR="38;2;23;147;209"
|
ANSI_COLOR="38;2;23;147;209"
|
||||||
HOME_URL="https://github.com/You-re-like-Windows-a-bitch/fws"
|
HOME_URL="https://github.com/You-re-like-Windows-a-bitch/fws"
|
||||||
SUPPORT_URL="https://github.com/You-re-like-Windows-a-bitch/fws/issues"
|
SUPPORT_URL="https://github.com/You-re-like-Windows-a-bitch/fws/issues"
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# FWS — picom minimal (session i3) : coins arrondis comme sous Hyprland.
|
||||||
|
# xrender : fiable partout, y compris en VM (rendu logiciel) ; pas de flou ni
|
||||||
|
# d'ombres (parité avec la config Hyprland FWS, et léger pour la VR).
|
||||||
|
backend = "xrender";
|
||||||
|
vsync = true;
|
||||||
|
|
||||||
|
corner-radius = 8;
|
||||||
|
rounded-corners-exclude = [
|
||||||
|
"window_type = 'dock'", # polybar gère son propre arrondi
|
||||||
|
"class_g = 'i3-frame'"
|
||||||
|
];
|
||||||
|
|
||||||
|
shadow = false;
|
||||||
|
fading = true;
|
||||||
|
fade-in-step = 0.08;
|
||||||
|
fade-out-step = 0.08;
|
||||||
|
|
||||||
|
# VR / jeux plein écran : ne pas composer par-dessus (latence).
|
||||||
|
unredir-if-possible = true;
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
; FWS — polybar (session i3) : clone visuel de la waybar Hyprland.
|
||||||
|
; Barre flottante arrondie, modules en « pilules » Catppuccin Mocha,
|
||||||
|
; icônes Nerd Font (ttf-nerd-fonts-symbols). Horloge + date au centre,
|
||||||
|
; bouton « ⌌ Raccourcis » (fws-keys), volume/réseau/CPU/RAM/tray à droite.
|
||||||
|
|
||||||
|
[colors]
|
||||||
|
base = #E01e1e2e
|
||||||
|
surface0 = #313244
|
||||||
|
text = #cdd6f4
|
||||||
|
subtext = #a6adc8
|
||||||
|
blue = #89b4fa
|
||||||
|
sapphire = #74c7ec
|
||||||
|
teal = #94e2d5
|
||||||
|
mauve = #cba6f7
|
||||||
|
maroon = #eba0ac
|
||||||
|
yellow = #f9e2af
|
||||||
|
red = #f38ba8
|
||||||
|
darkbase = #1e1e2e
|
||||||
|
|
||||||
|
[bar/fws]
|
||||||
|
monitor = ${env:MONITOR:}
|
||||||
|
width = 100%:-20
|
||||||
|
offset-x = 10
|
||||||
|
offset-y = 6
|
||||||
|
height = 36
|
||||||
|
radius = 14
|
||||||
|
background = ${colors.base}
|
||||||
|
foreground = ${colors.text}
|
||||||
|
border-size = 1
|
||||||
|
border-color = #5989b4fa
|
||||||
|
padding-left = 1
|
||||||
|
padding-right = 1
|
||||||
|
module-margin = 1
|
||||||
|
line-size = 0
|
||||||
|
font-0 = Noto Sans:size=10;2
|
||||||
|
font-1 = Symbols Nerd Font:size=12;3
|
||||||
|
font-2 = Noto Sans:weight=bold:size=10;2
|
||||||
|
modules-left = i3 xwindow
|
||||||
|
modules-center = date
|
||||||
|
modules-right = updates keyhelp backlight pulseaudio network cpu memory tray power
|
||||||
|
enable-ipc = true
|
||||||
|
wm-restack = i3
|
||||||
|
|
||||||
|
[module/i3]
|
||||||
|
type = internal/i3
|
||||||
|
pin-workspaces = true
|
||||||
|
show-urgent = true
|
||||||
|
strip-wsnumbers = false
|
||||||
|
format = <label-state> <label-mode>
|
||||||
|
label-focused = %name%
|
||||||
|
label-focused-background = ${colors.blue}
|
||||||
|
label-focused-foreground = ${colors.darkbase}
|
||||||
|
label-focused-padding = 2
|
||||||
|
label-unfocused = %name%
|
||||||
|
label-unfocused-background = ${colors.surface0}
|
||||||
|
label-unfocused-foreground = ${colors.subtext}
|
||||||
|
label-unfocused-padding = 2
|
||||||
|
label-visible = %name%
|
||||||
|
label-visible-background = ${colors.surface0}
|
||||||
|
label-visible-padding = 2
|
||||||
|
label-urgent = %name%
|
||||||
|
label-urgent-background = ${colors.red}
|
||||||
|
label-urgent-foreground = ${colors.darkbase}
|
||||||
|
label-urgent-padding = 2
|
||||||
|
|
||||||
|
[module/xwindow]
|
||||||
|
type = internal/xwindow
|
||||||
|
label = %title:0:45:…%
|
||||||
|
label-foreground = ${colors.subtext}
|
||||||
|
format-padding = 1
|
||||||
|
|
||||||
|
[module/date]
|
||||||
|
type = internal/date
|
||||||
|
interval = 1
|
||||||
|
date = %A %d %B %Y
|
||||||
|
time = %H:%M
|
||||||
|
label = %time% %date%
|
||||||
|
label-font = 3
|
||||||
|
format-background = ${colors.surface0}
|
||||||
|
format-padding = 3
|
||||||
|
|
||||||
|
[module/updates]
|
||||||
|
type = custom/script
|
||||||
|
exec = fws-updates-check
|
||||||
|
interval = 1800
|
||||||
|
click-left = kitty -e fws-update
|
||||||
|
format-background = ${colors.surface0}
|
||||||
|
format-foreground = ${colors.yellow}
|
||||||
|
format-padding = 2
|
||||||
|
|
||||||
|
[module/keyhelp]
|
||||||
|
type = custom/script
|
||||||
|
exec = echo " Raccourcis"
|
||||||
|
interval = 3600
|
||||||
|
click-left = fws-keys
|
||||||
|
format-background = ${colors.blue}
|
||||||
|
format-foreground = ${colors.darkbase}
|
||||||
|
format-padding = 2
|
||||||
|
label-font = 3
|
||||||
|
|
||||||
|
[module/backlight]
|
||||||
|
type = internal/backlight
|
||||||
|
enable-scroll = true
|
||||||
|
format = <label>
|
||||||
|
label = %percentage%%
|
||||||
|
format-background = ${colors.surface0}
|
||||||
|
format-foreground = ${colors.yellow}
|
||||||
|
format-padding = 2
|
||||||
|
|
||||||
|
[module/pulseaudio]
|
||||||
|
type = internal/pulseaudio
|
||||||
|
use-ui-max = false
|
||||||
|
click-right = pavucontrol
|
||||||
|
format-volume = <label-volume>
|
||||||
|
label-volume = %percentage%%
|
||||||
|
format-volume-background = ${colors.surface0}
|
||||||
|
format-volume-foreground = ${colors.blue}
|
||||||
|
format-volume-padding = 2
|
||||||
|
label-muted = muet
|
||||||
|
format-muted-background = ${colors.surface0}
|
||||||
|
format-muted-foreground = ${colors.subtext}
|
||||||
|
format-muted-padding = 2
|
||||||
|
|
||||||
|
[module/network]
|
||||||
|
type = internal/network
|
||||||
|
interface-type = wired
|
||||||
|
interval = 5
|
||||||
|
format-connected = <label-connected>
|
||||||
|
label-connected = %local_ip%
|
||||||
|
format-connected-background = ${colors.surface0}
|
||||||
|
format-connected-foreground = ${colors.teal}
|
||||||
|
format-connected-padding = 2
|
||||||
|
format-disconnected = <label-disconnected>
|
||||||
|
label-disconnected = hors ligne
|
||||||
|
format-disconnected-background = ${colors.surface0}
|
||||||
|
format-disconnected-foreground = ${colors.red}
|
||||||
|
format-disconnected-padding = 2
|
||||||
|
|
||||||
|
[module/cpu]
|
||||||
|
type = internal/cpu
|
||||||
|
interval = 3
|
||||||
|
label = %percentage%%
|
||||||
|
format-background = ${colors.surface0}
|
||||||
|
format-foreground = ${colors.mauve}
|
||||||
|
format-padding = 2
|
||||||
|
|
||||||
|
[module/memory]
|
||||||
|
type = internal/memory
|
||||||
|
interval = 3
|
||||||
|
label = %percentage_used%%
|
||||||
|
format-background = ${colors.surface0}
|
||||||
|
format-foreground = ${colors.maroon}
|
||||||
|
format-padding = 2
|
||||||
|
|
||||||
|
[module/power]
|
||||||
|
type = custom/text
|
||||||
|
content =
|
||||||
|
content-foreground = ${colors.red}
|
||||||
|
content-background = ${colors.surface0}
|
||||||
|
content-padding = 2
|
||||||
|
content-font = 2
|
||||||
|
click-left = fws-powermenu
|
||||||
|
|
||||||
|
[module/tray]
|
||||||
|
type = internal/tray
|
||||||
|
format = <tray>
|
||||||
|
format-background = ${colors.surface0}
|
||||||
|
format-padding = 1
|
||||||
|
tray-spacing = 8px
|
||||||
|
tray-size = 16px
|
||||||
|
|
||||||
|
[settings]
|
||||||
|
screenchange-reload = true
|
||||||
|
pseudo-transparency = false
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
/* FWS — rofi (menu d'applications i3, Super+R) : même look que wofi sous
|
||||||
|
Hyprland — fond sombre, bordure et sélection bleues, icônes. */
|
||||||
|
|
||||||
|
configuration {
|
||||||
|
show-icons: true;
|
||||||
|
display-drun: "Applications";
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
bg: #1e1e2eF5;
|
||||||
|
surface: #313244;
|
||||||
|
text: #cdd6f4;
|
||||||
|
subtext: #a6adc8;
|
||||||
|
blue: #89b4fa;
|
||||||
|
darkbase: #1e1e2e;
|
||||||
|
font: "Noto Sans 12";
|
||||||
|
background-color: transparent;
|
||||||
|
text-color: @text;
|
||||||
|
}
|
||||||
|
|
||||||
|
window {
|
||||||
|
background-color: @bg;
|
||||||
|
border: 2px;
|
||||||
|
border-color: @blue;
|
||||||
|
border-radius: 14px;
|
||||||
|
width: 560px;
|
||||||
|
}
|
||||||
|
|
||||||
|
mainbox {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
inputbar {
|
||||||
|
background-color: @surface;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin: 4px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
children: [ prompt, entry ];
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt {
|
||||||
|
text-color: @blue;
|
||||||
|
margin: 0 8px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry {
|
||||||
|
placeholder: "Rechercher…";
|
||||||
|
placeholder-color: @subtext;
|
||||||
|
}
|
||||||
|
|
||||||
|
listview {
|
||||||
|
margin: 4px;
|
||||||
|
lines: 8;
|
||||||
|
scrollbar: false;
|
||||||
|
}
|
||||||
|
|
||||||
|
element {
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
spacing: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
element selected.normal {
|
||||||
|
background-color: @blue;
|
||||||
|
text-color: @darkbase;
|
||||||
|
}
|
||||||
|
|
||||||
|
element-icon {
|
||||||
|
size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
element-text {
|
||||||
|
text-color: inherit;
|
||||||
|
vertical-align: 0.5;
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
{
|
||||||
|
"layer": "top",
|
||||||
|
"position": "top",
|
||||||
|
"height": 36,
|
||||||
|
"spacing": 2,
|
||||||
|
"margin-top": 6,
|
||||||
|
"margin-left": 10,
|
||||||
|
"margin-right": 10,
|
||||||
|
"modules-left": ["hyprland/workspaces", "hyprland/window"],
|
||||||
|
"modules-center": ["clock"],
|
||||||
|
"modules-right": ["custom/updates", "custom/keyhelp", "backlight", "pulseaudio", "bluetooth", "network", "cpu", "memory", "battery", "tray", "custom/power"],
|
||||||
|
|
||||||
|
"custom/power": {
|
||||||
|
"format": "",
|
||||||
|
"tooltip-format": "Alimentation (SUPER+X)",
|
||||||
|
"on-click": "fws-powermenu"
|
||||||
|
},
|
||||||
|
|
||||||
|
"custom/updates": {
|
||||||
|
"exec": "fws-updates-check",
|
||||||
|
"interval": 1800,
|
||||||
|
"tooltip-format": "Mises à jour disponibles — clic pour installer",
|
||||||
|
"on-click": "kitty -e fws-update"
|
||||||
|
},
|
||||||
|
|
||||||
|
"hyprland/workspaces": {
|
||||||
|
"format": "{id}",
|
||||||
|
"on-click": "activate",
|
||||||
|
"on-scroll-up": "hyprctl dispatch workspace e-1",
|
||||||
|
"on-scroll-down": "hyprctl dispatch workspace e+1"
|
||||||
|
},
|
||||||
|
"hyprland/window": {
|
||||||
|
"max-length": 45,
|
||||||
|
"separate-outputs": true
|
||||||
|
},
|
||||||
|
"clock": {
|
||||||
|
"format": " {:%H:%M %A %d %B %Y}",
|
||||||
|
"format-alt": " {:%H:%M}",
|
||||||
|
"tooltip-format": "<tt>{calendar}</tt>",
|
||||||
|
"calendar": {
|
||||||
|
"format": {
|
||||||
|
"today": "<b><span color='#89b4fa'>{}</span></b>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"custom/keyhelp": {
|
||||||
|
"format": " Raccourcis",
|
||||||
|
"tooltip-format": "Raccourcis clavier (SUPER+K)",
|
||||||
|
"on-click": "fws-keys"
|
||||||
|
},
|
||||||
|
"backlight": {
|
||||||
|
"format": "{icon} {percent}%",
|
||||||
|
"format-icons": ["", "", ""],
|
||||||
|
"on-scroll-up": "brightnessctl set +5%",
|
||||||
|
"on-scroll-down": "brightnessctl set 5%-",
|
||||||
|
"tooltip-format": "Luminosité {percent}% (molette pour régler)"
|
||||||
|
},
|
||||||
|
"pulseaudio": {
|
||||||
|
"format": "{icon} {volume}%",
|
||||||
|
"format-muted": " muet",
|
||||||
|
"format-icons": { "default": ["", "", ""] },
|
||||||
|
"on-click": "pavucontrol",
|
||||||
|
"on-click-right": "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle",
|
||||||
|
"on-scroll-up": "wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 2%+",
|
||||||
|
"on-scroll-down": "wpctl set-volume @DEFAULT_AUDIO_SINK@ 2%-",
|
||||||
|
"tooltip-format": "{desc} — clic : mixeur, clic droit : muet"
|
||||||
|
},
|
||||||
|
"bluetooth": {
|
||||||
|
"format": "",
|
||||||
|
"format-disabled": "",
|
||||||
|
"format-connected": " {num_connections}",
|
||||||
|
"format-no-controller": "",
|
||||||
|
"on-click": "blueman-manager",
|
||||||
|
"tooltip-format": "{controller_alias}\t{controller_address}",
|
||||||
|
"tooltip-format-connected": "{device_enumerate}"
|
||||||
|
},
|
||||||
|
"network": {
|
||||||
|
"format-wifi": " {essid}",
|
||||||
|
"format-ethernet": " {ipaddr}",
|
||||||
|
"format-disconnected": " hors ligne",
|
||||||
|
"tooltip-format": "{ifname} — {ipaddr}\n⇡ {bandwidthUpBytes} ⇣ {bandwidthDownBytes}"
|
||||||
|
},
|
||||||
|
"cpu": {
|
||||||
|
"format": " {usage}%",
|
||||||
|
"interval": 3
|
||||||
|
},
|
||||||
|
"memory": {
|
||||||
|
"format": " {percentage}%",
|
||||||
|
"interval": 3,
|
||||||
|
"tooltip-format": "{used:0.1f} / {total:0.1f} Gio"
|
||||||
|
},
|
||||||
|
"battery": {
|
||||||
|
"states": { "warning": 25, "critical": 10 },
|
||||||
|
"format": "{icon} {capacity}%",
|
||||||
|
"format-charging": " {capacity}%",
|
||||||
|
"format-icons": ["", "", "", "", ""],
|
||||||
|
"tooltip-format": "{timeTo}"
|
||||||
|
},
|
||||||
|
"tray": {
|
||||||
|
"spacing": 8,
|
||||||
|
"icon-size": 16
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
/* FWS — style Waybar v2 (Catppuccin Mocha, nuances de bleu).
|
||||||
|
Barre flottante arrondie, modules en « pilules », icônes Nerd Font
|
||||||
|
(paquet ttf-nerd-fonts-symbols, installé avec le bureau Hyprland). */
|
||||||
|
|
||||||
|
* {
|
||||||
|
font-family: "Noto Sans", "Symbols Nerd Font", sans-serif;
|
||||||
|
font-size: 13px;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
window#waybar {
|
||||||
|
background: rgba(30, 30, 46, 0.88); /* base #1e1e2e */
|
||||||
|
color: #cdd6f4; /* text */
|
||||||
|
border: 1px solid rgba(137, 180, 250, 0.35);
|
||||||
|
border-radius: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
tooltip {
|
||||||
|
background: #1e1e2e;
|
||||||
|
border: 1px solid #89b4fa;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
tooltip label { color: #cdd6f4; }
|
||||||
|
|
||||||
|
/* --- Espaces de travail --------------------------------------------------- */
|
||||||
|
#workspaces {
|
||||||
|
background: #313244; /* surface0 */
|
||||||
|
margin: 5px 4px;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces button {
|
||||||
|
color: #a6adc8; /* subtext0 */
|
||||||
|
padding: 0 8px;
|
||||||
|
margin: 3px 2px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: transparent;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces button.active {
|
||||||
|
color: #1e1e2e;
|
||||||
|
background: #89b4fa; /* bleu */
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces button:hover {
|
||||||
|
color: #cdd6f4;
|
||||||
|
background: #45475a;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#window {
|
||||||
|
color: #a6adc8;
|
||||||
|
margin-left: 10px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Horloge (centre) ------------------------------------------------------ */
|
||||||
|
#clock {
|
||||||
|
color: #cdd6f4;
|
||||||
|
font-weight: bold;
|
||||||
|
background: #313244;
|
||||||
|
padding: 0 16px;
|
||||||
|
margin: 5px 0;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Modules de droite : pilules ------------------------------------------- */
|
||||||
|
#custom-keyhelp,
|
||||||
|
#backlight,
|
||||||
|
#pulseaudio,
|
||||||
|
#bluetooth,
|
||||||
|
#network,
|
||||||
|
#cpu,
|
||||||
|
#memory,
|
||||||
|
#battery,
|
||||||
|
#tray {
|
||||||
|
background: #313244;
|
||||||
|
padding: 0 12px;
|
||||||
|
margin: 5px 3px;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: #cdd6f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
#custom-keyhelp {
|
||||||
|
background: #89b4fa; /* bouton d'appel : bleu plein */
|
||||||
|
color: #1e1e2e;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
#custom-keyhelp:hover { background: #b4d0fb; }
|
||||||
|
|
||||||
|
#custom-power {
|
||||||
|
color: #f38ba8; /* rouge doux (Catppuccin) */
|
||||||
|
}
|
||||||
|
#custom-power:hover { color: #ff6b81; }
|
||||||
|
|
||||||
|
#backlight { color: #f9e2af; } /* jaune doux */
|
||||||
|
#pulseaudio { color: #89b4fa; }
|
||||||
|
#pulseaudio.muted { color: #6c7086; }
|
||||||
|
#bluetooth { color: #74c7ec; } /* sapphire */
|
||||||
|
#bluetooth.disabled { color: #6c7086; }
|
||||||
|
#network { color: #94e2d5; } /* teal */
|
||||||
|
#network.disconnected { color: #f38ba8; }
|
||||||
|
#cpu { color: #cba6f7; } /* mauve */
|
||||||
|
#memory { color: #eba0ac; } /* maroon */
|
||||||
|
|
||||||
|
#battery.warning { color: #f9e2af; }
|
||||||
|
#battery.critical { color: #f38ba8; }
|
||||||
|
#battery.charging { color: #a6e3a1; }
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# FWS — configuration wofi (menu d'applications).
|
||||||
|
insensitive=true
|
||||||
|
allow_images=true
|
||||||
|
image_size=24
|
||||||
|
prompt=Applications
|
||||||
|
width=560
|
||||||
|
height=440
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/* FWS — style du panneau « Raccourcis clavier » (fws-keys).
|
||||||
|
Même palette que le menu wofi, mais police MONOSPACE : les colonnes
|
||||||
|
combinaison/description sont alignées par des espaces. */
|
||||||
|
|
||||||
|
window {
|
||||||
|
background-color: rgba(30, 30, 46, 0.97);
|
||||||
|
border: 2px solid #89b4fa;
|
||||||
|
border-radius: 14px;
|
||||||
|
font-family: "Noto Sans Mono", monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#input {
|
||||||
|
background-color: #313244;
|
||||||
|
color: #cdd6f4;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin: 12px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-family: "Noto Sans", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
#input image { color: #89b4fa; }
|
||||||
|
|
||||||
|
#inner-box {
|
||||||
|
margin: 0 8px 8px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#entry {
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #cdd6f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
#entry:selected {
|
||||||
|
background-color: #313244;
|
||||||
|
color: #89b4fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
#text { color: inherit; }
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/* FWS — style wofi (menu d'applications Super+R). Catppuccin Mocha, bleu. */
|
||||||
|
|
||||||
|
window {
|
||||||
|
background-color: rgba(30, 30, 46, 0.96); /* base #1e1e2e */
|
||||||
|
border: 2px solid #89b4fa;
|
||||||
|
border-radius: 14px;
|
||||||
|
font-family: "Noto Sans", sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#input {
|
||||||
|
background-color: #313244; /* surface0 */
|
||||||
|
color: #cdd6f4;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin: 12px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#input image { color: #89b4fa; }
|
||||||
|
|
||||||
|
#inner-box {
|
||||||
|
margin: 0 8px 8px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#entry {
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: #cdd6f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
#entry:selected {
|
||||||
|
background-color: #89b4fa;
|
||||||
|
color: #1e1e2e;
|
||||||
|
}
|
||||||
|
|
||||||
|
#entry:selected #text { color: #1e1e2e; }
|
||||||
|
|
||||||
|
#text {
|
||||||
|
margin-left: 8px;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- FWS — icône « Récupération FWS » : croix de secours, bleus FWS. -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
|
||||||
|
<rect x="3" y="3" width="58" height="58" rx="13" fill="#1e1e2e"/>
|
||||||
|
<rect x="3" y="3" width="58" height="58" rx="13" fill="none" stroke="#89b4fa" stroke-width="3"/>
|
||||||
|
<path d="M25 13h14v12h12v14H39v12H25V39H13V25h12z" fill="#89b4fa"/>
|
||||||
|
<path d="M28 16h8v12h12v8H36v12h-8V36H16v-8h12z" fill="#74c7ec"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 515 B |
@@ -5,33 +5,36 @@
|
|||||||
# lancée en mode interactif (chemin fixe codé en dur dans Anaconda :
|
# lancée en mode interactif (chemin fixe codé en dur dans Anaconda :
|
||||||
# /usr/share/anaconda/interactive-defaults.ks). Aucun inst.ks= n'est requis.
|
# /usr/share/anaconda/interactive-defaults.ks). Aucun inst.ks= n'est requis.
|
||||||
#
|
#
|
||||||
# RÔLE : empêcher Anaconda d'installer Fedora.
|
# RÔLE : finaliser une install « copie du live » (surtout pas Fedora).
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
# Sans payload défini, Anaconda utilise DNF → des RPM depuis les dépôts
|
# Anaconda n'a AUCUN backend pacman ; son payload par défaut (DNF) poserait
|
||||||
# Fedora = il installe Fedora. Anaconda n'a AUCUN backend pacman. La seule
|
# des RPM Fedora. Ici le payload est LiveOS, imposé par « anaconda --liveinst »
|
||||||
# façon de poser FWS/Arch est le payload « liveimg » : il recopie une image
|
# (cf. bloc « Source d'installation » ci-dessous) : il recopie le squashfs du
|
||||||
# de système de fichiers (ici le squashfs du live = FWS) sur le disque, puis
|
# live (= FWS) sur le disque, puis on finalise nous-mêmes (grub, mkinitcpio,
|
||||||
# on finalise nous-mêmes (grub, mkinitcpio, bureau) en %post. C'est le modèle
|
# bureau) en %post. C'est le modèle « Sabayon » : le système installé EST une
|
||||||
# « Sabayon » : le système installé EST une copie du live.
|
# copie du live.
|
||||||
#
|
#
|
||||||
# L'utilisateur garde la main dans le GUI sur tout ce qui N'EST PAS fixé ici :
|
# L'utilisateur garde la main dans le GUI sur tout ce qui N'EST PAS fixé ici :
|
||||||
# partitionnement, fuseau, clavier, langue, création d'utilisateur, mot de
|
# partitionnement, fuseau, clavier, langue, création d'utilisateur, mot de
|
||||||
# passe root, etc.
|
# passe root, etc.
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
# --- Source d'installation : l'image du live (FWS), surtout pas Fedora -------
|
# --- Source d'installation : le payload LiveOS (surtout pas Fedora/DNF) ------
|
||||||
# Squashfs archiso sur le média booté :
|
# AUCUNE directive de payload ici, et c'est VOULU. liveinst lance
|
||||||
# /run/archiso/bootmnt/<install_dir>/<arch>/airootfs.sfs
|
# « anaconda --liveinst » et anaconda.py teste opts.liveinst AVANT le type du
|
||||||
# install_dir=fws, arch=x86_64, airootfs_image_type=squashfs (cf. profiledef.sh).
|
# payload DBus → il crée TOUJOURS un payload LiveOS et IGNORE tout payload du
|
||||||
# Anaconda loop-monte ce .sfs et rsync son contenu vers la cible.
|
# kickstart (un « liveimg » ici serait du code mort — vérifié dans
|
||||||
|
# pyanaconda/anaconda.py, propriété payload, et reproduit hors VM).
|
||||||
#
|
#
|
||||||
# ⚠ À VALIDER sur ISO réelle : certaines versions d'Anaconda attendent une
|
# Le payload LiveOS détecte l'image live via /dev/mapper/live-base ou
|
||||||
# structure « LiveOS/rootfs.img » à l'intérieur du squashfs (format Fedora
|
# /run/rootfsbase (chemins Fedora/dracut, absents d'archiso → c'était le crash
|
||||||
# Live) et non un squashfs « plat » comme celui d'archiso. Si l'install
|
# « SourceSetupError: No Live OS image found! » au démarrage). Le .xinitrc du
|
||||||
# échoue à monter la source, basculer sur un tarball de rootfs dédié :
|
# live bind-monte donc /run/archiso/airootfs (squashfs FWS, ro) sur
|
||||||
# liveimg --url=file:///run/archiso/bootmnt/fws/x86_64/fws-rootfs.tar.gz
|
# /run/rootfsbase AVANT de lancer liveinst : Anaconda monte le loop device et
|
||||||
# (un build-rootfs.sh produirait alors ce tar via pacstrap).
|
# rsync son contenu vers la cible = le système installé EST une copie du live,
|
||||||
liveimg --url=file:///run/archiso/bootmnt/fws/x86_64/airootfs.sfs
|
# puis on finalise en %post (grub, mkinitcpio, bureau, matériel).
|
||||||
|
# DNF/libdnf5 n'est jamais importé : le module DNF n'est chargé QUE si le
|
||||||
|
# payload actif est DNF (import lazy dans modules/payloads/payload/factory.py).
|
||||||
|
|
||||||
# --- Disque : repartir d'un disque PROPRE, même s'il a déjà servi ------------
|
# --- Disque : repartir d'un disque PROPRE, même s'il a déjà servi ------------
|
||||||
# Sans ça, une réinstallation sur un disque déjà partitionné (typiquement une
|
# Sans ça, une réinstallation sur un disque déjà partitionné (typiquement une
|
||||||
@@ -42,24 +45,25 @@ liveimg --url=file:///run/archiso/bootmnt/fws/x86_64/airootfs.sfs
|
|||||||
# zerombr : réinitialise toute table de partitions invalide
|
# zerombr : réinitialise toute table de partitions invalide
|
||||||
# (répond « oui » au lieu de bloquer sur un disque
|
# (répond « oui » au lieu de bloquer sur un disque
|
||||||
# non reconnu).
|
# non reconnu).
|
||||||
# clearpart --all : supprime TOUTES les partitions de TOUS les disques.
|
# clearpart --all : supprime TOUTES les partitions des disques
|
||||||
|
# SÉLECTIONNÉS dans le GUI (l'utilisateur choisit le
|
||||||
|
# disque cible — les autres, ex. disques de jeux, ne
|
||||||
|
# sont pas touchés s'il ne les sélectionne pas).
|
||||||
# --initlabel : réécrit une étiquette de disque neuve.
|
# --initlabel : réécrit une étiquette de disque neuve.
|
||||||
# --disklabel=msdos : force une table MBR (et non GPT). CRUCIAL en BIOS :
|
# --disklabel : type de table CHOISI SELON LE FIRMWARE par le %pre
|
||||||
# en BIOS+GPT, GRUB i386-pc exige une partition « BIOS
|
# plus bas (ne surtout PAS coder en dur) :
|
||||||
# Boot » (~1 Mo, type EF02) pour embarquer core.img,
|
# • UEFI → gpt : l'ESP peut être créée → grub-install
|
||||||
# qu'Anaconda NE crée PAS avec « bootloader --disabled
|
# EFI fonctionne. Forcer « msdos » en UEFI empêchait
|
||||||
# » → « this GPT partition label contains no BIOS Boot
|
# l'ESP (→ install NON bootable) et réécrivait la
|
||||||
# Partition; embedding won't be possible » →
|
# table pour rien : c'est LE bug corrigé ici.
|
||||||
# grub-install échoue. En MBR, core.img se loge dans
|
# • BIOS → msdos : core.img se loge dans l'espace
|
||||||
# l'espace post-MBR, aucune partition dédiée requise.
|
# post-MBR (pas de partition BIOS Boot EF02 requise,
|
||||||
# ⚠ Ce choix vise le BIOS (cas VMware actuel). Pour l'UEFI il faudra du GPT
|
# qu'Anaconda ne crée pas avec « bootloader --disabled »).
|
||||||
# (+ ESP) — à rendre dépendant du firmware (%pre) le jour où on cible l'UEFI.
|
|
||||||
# L'utilisateur garde la main dans le GUI (il peut repasser en manuel), mais le
|
# L'utilisateur garde la main dans le GUI (il peut repasser en manuel), mais le
|
||||||
# DÉFAUT est désormais un disque net → réinstall fiable à chaque fois.
|
# DÉFAUT est un disque net → réinstall fiable à chaque fois.
|
||||||
# ⚠ VM mono-disque : « --all » efface le seul disque, c'est voulu. Sur une
|
|
||||||
# machine multi-disques il faudrait cibler avec « --drives=sda ».
|
|
||||||
zerombr
|
zerombr
|
||||||
clearpart --all --initlabel --disklabel=msdos
|
# Table de partitions injectée par le %pre (GPT en UEFI, MBR en BIOS).
|
||||||
|
%include /tmp/fws-clearpart.ks
|
||||||
|
|
||||||
# --- Bootloader : géré à la main en %post ------------------------------------
|
# --- Bootloader : géré à la main en %post ------------------------------------
|
||||||
# Anaconda lancerait « grub2-install » / « grub2-mkconfig » (binaires Fedora,
|
# Anaconda lancerait « grub2-install » / « grub2-mkconfig » (binaires Fedora,
|
||||||
@@ -69,9 +73,38 @@ clearpart --all --initlabel --disklabel=msdos
|
|||||||
# partitionnement (auto ou manuel) car la plateforme l'impose.
|
# partitionnement (auto ou manuel) car la plateforme l'impose.
|
||||||
bootloader --disabled
|
bootloader --disabled
|
||||||
|
|
||||||
# Pas de section %packages : le payload liveimg n'installe aucun paquet
|
# Pas de section %packages : le payload LiveOS n'installe aucun paquet
|
||||||
# (il copie une image). Toute installation de paquets se fait en %post.
|
# (il copie une image). Toute installation de paquets se fait en %post.
|
||||||
|
|
||||||
|
# --- Récapitulatif avant installation : PAGE GUI dédiée (pas de %pre-install) -
|
||||||
|
# L'écran « RÉCAPITULATIF AVANT INSTALLATION » est une standalone spoke
|
||||||
|
# Anaconda (overlay airootfs : pyanaconda/ui/gui/spokes/fws_recap.py,
|
||||||
|
# postForHub=SummaryHub, priority -10) intercalée ENTRE le hub et l'écran de
|
||||||
|
# progression : l'utilisateur clique « Commencer l'installation », voit le
|
||||||
|
# récap (bureau choisi, hostname, disques, GPU, réseau — AUCUNE écriture
|
||||||
|
# disque à ce stade), et l'installation ne démarre qu'à son clic « Lancer
|
||||||
|
# l'installation ». L'ancien récap zenity en %pre-install a été abandonné :
|
||||||
|
# le module scripts tourne avec LC_ALL=C et un env X incertain (zenity 4
|
||||||
|
# refuse les libellés UTF-8 en locale C → rc=255 pris pour « Annuler » →
|
||||||
|
# poweroff en plein install au 1er test réel).
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# %pre — TABLE DE PARTITIONS SELON LE FIRMWARE
|
||||||
|
# Génère /tmp/fws-clearpart.ks (inclus dans la section commandes ci-dessus) :
|
||||||
|
# GPT en UEFI (→ ESP créable → grub-install EFI fonctionne), MBR/msdos en BIOS.
|
||||||
|
# Les scripts %pre s'exécutent AVANT l'application des commandes du kickstart,
|
||||||
|
# donc le %include voit toujours le fichier généré ici.
|
||||||
|
# ============================================================================
|
||||||
|
%pre --log=/tmp/fws-pre.log
|
||||||
|
if [ -d /sys/firmware/efi ]; then
|
||||||
|
disklabel=gpt
|
||||||
|
else
|
||||||
|
disklabel=msdos
|
||||||
|
fi
|
||||||
|
echo "clearpart --all --initlabel --disklabel=$disklabel" > /tmp/fws-clearpart.ks
|
||||||
|
%end
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# %post --nochroot — s'exécute dans le LIVE, la cible montée sous /mnt/sysroot
|
# %post --nochroot — s'exécute dans le LIVE, la cible montée sous /mnt/sysroot
|
||||||
@@ -89,12 +122,29 @@ SYSROOT=/mnt/sysroot
|
|||||||
# DNS pour que « pacman -S <bureau> » fonctionne dans le chroot.
|
# DNS pour que « pacman -S <bureau> » fonctionne dans le chroot.
|
||||||
cp -f /etc/resolv.conf "$SYSROOT/etc/resolv.conf" 2>/dev/null || true
|
cp -f /etc/resolv.conf "$SYSROOT/etc/resolv.conf" 2>/dev/null || true
|
||||||
|
|
||||||
|
# --- Hostname : valeur saisie dans le champ « Nom de machine » du spoke User --
|
||||||
|
# Anaconda ne capte PAS le hostname du spoke Réseau (→ /etc/hostname vide). On a
|
||||||
|
# donc ajouté un champ Hostname au spoke Utilisateur (cf. patch du paquet
|
||||||
|
# anaconda) : user.py écrit la valeur dans /tmp/fws-hostname (live). On la
|
||||||
|
# nettoie (caractères de hostname valides) et on l'écrit dans la cible ; défaut
|
||||||
|
# « fws » si vide/absent → plus jamais de hostname vide ni « archiso ».
|
||||||
|
HN=""
|
||||||
|
[ -f /tmp/fws-hostname ] && HN="$(tr -cd 'a-zA-Z0-9._-' < /tmp/fws-hostname)"
|
||||||
|
[ -n "$HN" ] || HN="fws"
|
||||||
|
echo "$HN" > "$SYSROOT/etc/hostname"
|
||||||
|
|
||||||
# Choix du bureau (écrit par l'addon « Choix du bureau », à venir ; valeur lue
|
# Choix du bureau (écrit par l'addon « Choix du bureau », à venir ; valeur lue
|
||||||
# par le %post chrooté ci-dessous). « cli » par défaut si absent.
|
# par le %post chrooté ci-dessous). « cli » par défaut si absent.
|
||||||
if [ -f /tmp/fws-desktop ]; then
|
if [ -f /tmp/fws-desktop ]; then
|
||||||
cp -f /tmp/fws-desktop "$SYSROOT/tmp/fws-desktop" 2>/dev/null || true
|
cp -f /tmp/fws-desktop "$SYSROOT/tmp/fws-desktop" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Choix « Dual-boot Windows gaming » (spoke fws_windows) → relu par le %post
|
||||||
|
# chrooté (Secure Boot) ET par fws-windows-deploy (%post --nochroot, plus bas).
|
||||||
|
if [ -f /tmp/fws-windows ]; then
|
||||||
|
cp -f /tmp/fws-windows "$SYSROOT/tmp/fws-windows" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
# --- Noyau : archiso RETIRE /boot/vmlinuz-linux du squashfs ------------------
|
# --- Noyau : archiso RETIRE /boot/vmlinuz-linux du squashfs ------------------
|
||||||
# mkarchiso boote le noyau depuis l'ISO et l'EXCLUT de l'airootfs squashfs (gain
|
# mkarchiso boote le noyau depuis l'ISO et l'EXCLUT de l'airootfs squashfs (gain
|
||||||
# de place). Conséquence : le payload LiveOS recopie un rootfs SANS noyau →
|
# de place). Conséquence : le payload LiveOS recopie un rootfs SANS noyau →
|
||||||
@@ -194,28 +244,356 @@ else
|
|||||||
echo "[FWS] AVERTISSEMENT : grub-install a échoué — bootloader à refaire à la main"
|
echo "[FWS] AVERTISSEMENT : grub-install a échoué — bootloader à refaire à la main"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# --- 3bis) Clavier (X11) + LOCALE --------------------------------------------
|
||||||
|
# CLAVIER : Anaconda passe par localed et n'écrit pas fiablement la disposition X
|
||||||
|
# sur la CIBLE → SDDM / sessions X en QWERTY US. On CALCULE la disposition CHOISIE
|
||||||
|
# (kickstart généré, sinon dérivée de la locale via langtable) en XL/XV, réutilisés
|
||||||
|
# plus bas (00-keyboard.conf pour SDDM/X ET la config Hyprland qui a son propre
|
||||||
|
# kb_layout). LOCALE : le locale choisi (ex. fr_CH.UTF-8) doit être GÉNÉRÉ —
|
||||||
|
# Anaconda vient de Fedora (locales pré-générées) et ne lance pas locale-gen →
|
||||||
|
# sans ça LANG casse et tout retombe en C.
|
||||||
|
XL=""; XV=""
|
||||||
|
XKB="$(sed -n "s/.*--xlayouts=//p" /root/anaconda-ks.cfg 2>/dev/null | head -1)"
|
||||||
|
XKB="${XKB#[\'\"]}"; XKB="${XKB%%[\'\"]*}"; XKB="${XKB%%,*}" # 1re dispo, ex. « ch (fr) »
|
||||||
|
if [ -z "$XKB" ]; then
|
||||||
|
_loc="$(. /etc/locale.conf 2>/dev/null; printf '%s' "${LANG%%.*}")" # fr_CH
|
||||||
|
_lng="${_loc%%_*}"; _terr="${_loc#*_}"; [ "$_terr" = "$_loc" ] && _terr=""
|
||||||
|
[ -n "$_terr" ] && XKB="$(python3 -c "import langtable; k=langtable.list_keyboards(languageId='$_lng', territoryId='$_terr'); print(k[0] if k else '')" 2>/dev/null)"
|
||||||
|
fi
|
||||||
|
if [ -n "$XKB" ]; then
|
||||||
|
XL="${XKB%%(*}"; XL="${XL%% *}" # ch
|
||||||
|
case "$XKB" in *'('*')') XV="${XKB#*(}"; XV="${XV%)}" ;; *) XV="" ;; esac # fr
|
||||||
|
install -d /etc/X11/xorg.conf.d
|
||||||
|
cat > /etc/X11/xorg.conf.d/00-keyboard.conf <<KBEOF
|
||||||
|
Section "InputClass"
|
||||||
|
Identifier "system-keyboard"
|
||||||
|
MatchIsKeyboard "on"
|
||||||
|
Option "XkbLayout" "$XL"
|
||||||
|
Option "XkbVariant" "$XV"
|
||||||
|
EndSection
|
||||||
|
KBEOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
FWSLANG="$(. /etc/locale.conf 2>/dev/null; printf '%s' "${LANG:-}")"
|
||||||
|
if [ -n "$FWSLANG" ]; then
|
||||||
|
_cs="${FWSLANG##*.}"; [ "$_cs" = "$FWSLANG" ] && _cs="UTF-8"
|
||||||
|
grep -qE "^[[:space:]]*${FWSLANG}[[:space:]]+${_cs}\b" /etc/locale.gen 2>/dev/null \
|
||||||
|
|| printf '%s %s\n' "$FWSLANG" "$_cs" >> /etc/locale.gen
|
||||||
|
locale-gen || echo "[FWS] locale-gen a échoué pour $FWSLANG"
|
||||||
|
fi
|
||||||
|
|
||||||
# --- 4) Bureau choisi : pacman -S du DE (non bloquant si pas de réseau) -------
|
# --- 4) Bureau choisi : pacman -S du DE (non bloquant si pas de réseau) -------
|
||||||
DESK="cli"
|
DESK="cli"
|
||||||
[ -f /tmp/fws-desktop ] && DESK="$(tr -d '[:space:]' < /tmp/fws-desktop)"
|
[ -f /tmp/fws-desktop ] && DESK="$(tr -d '[:space:]' < /tmp/fws-desktop)"
|
||||||
DM=""
|
DM=""
|
||||||
case "$DESK" in
|
case "$DESK" in
|
||||||
kde) PKGS="plasma-meta sddm konsole dolphin"; DM="sddm" ;;
|
kde) PKGS="plasma-meta sddm konsole dolphin"; DM="sddm" ;;
|
||||||
gnome) PKGS="gnome gdm"; DM="gdm" ;;
|
gnome) PKGS="gnome gdm"; DM="gdm" ;;
|
||||||
xfce) PKGS="xfce4 xfce4-goodies lightdm lightdm-gtk-greeter"; DM="lightdm" ;;
|
# noto-fonts : KDE l'a via plasma-meta et GNOME a cantarell, mais XFCE et
|
||||||
hyprland) PKGS="hyprland kitty wofi waybar sddm"; DM="sddm" ;;
|
# Hyprland ne garantissent AUCUNE police TTF → greeter SDDM/apps illisibles.
|
||||||
|
# Hyprland : la stack des raccourcis FWS (mêmes touches que la config
|
||||||
|
# i3/sway maison) — dolphin (Super+E), hyprlock (Super+L), grim+slurp+
|
||||||
|
# wl-clipboard (captures), playerctl (touches multimédia) — plus la
|
||||||
|
# plomberie de bureau : mako+libnotify (notifications), polkit-gnome
|
||||||
|
# (agent d'authentification GUI), network-manager-applet (réseau dans le
|
||||||
|
# tray), xdg-desktop-portal-hyprland (partage d'écran/portails), gvfs
|
||||||
|
# (montage USB via udisks2), ttf-nerd-fonts-symbols (icônes waybar).
|
||||||
|
# Son et Bluetooth GARANTIS avec le bureau : la stack PipeWire + le
|
||||||
|
# mixeur (pavucontrol) + bluez/blueman partent dans la MÊME transaction
|
||||||
|
# pacman que le DE — si le bureau s'installe, ses gestionnaires aussi
|
||||||
|
# (fws-setup-hardware les re-propose ensuite en --needed : sans effet
|
||||||
|
# s'ils sont déjà là, filet de secours sinon).
|
||||||
|
xfce) PKGS="xfce4 xfce4-goodies sddm noto-fonts pipewire pipewire-pulse wireplumber pavucontrol"; DM="sddm" ;;
|
||||||
|
hyprland) PKGS="hyprland kitty wofi waybar sddm noto-fonts dolphin kio-extras udisks2 hyprlock grim slurp wl-clipboard playerctl mako libnotify polkit-gnome network-manager-applet xdg-desktop-portal-hyprland ttf-nerd-fonts-symbols pipewire pipewire-alsa pipewire-pulse wireplumber pavucontrol bluez bluez-utils blueman"; DM="sddm" ;;
|
||||||
|
# i3 : session X11 → COMPATIBLE VR (SteamVR ne marche pas sous Wayland).
|
||||||
|
# xorg-server est requis (SDDM ne le tire pas) ; maim+xclip = captures X11,
|
||||||
|
# dunst = notifications X11 (équivalent de mako), i3lock = verrouillage.
|
||||||
|
# fws-vr-setup (Super+Shift+V ou menu) installe les drivers du casque.
|
||||||
|
i3) PKGS="i3-wm i3lock xorg-server polybar picom rofi kitty dolphin kio-extras udisks2 sddm noto-fonts ttf-nerd-fonts-symbols maim xclip dunst libnotify polkit-gnome network-manager-applet playerctl brightnessctl pipewire pipewire-alsa pipewire-pulse wireplumber pavucontrol bluez bluez-utils blueman"; DM="sddm" ;;
|
||||||
cli|*) PKGS="" ;;
|
cli|*) PKGS="" ;;
|
||||||
esac
|
esac
|
||||||
|
# Navigateur de base ajouté DÈS QU'un bureau est choisi (jamais en CLI).
|
||||||
|
# + python-gobject : fenêtre de bienvenue fws-hello (GTK3 vient avec Firefox)
|
||||||
|
# + pacman-contrib : checkupdates/paccache (indicateur de MàJ + fws-update)
|
||||||
|
[ -n "$PKGS" ] && PKGS="$PKGS firefox python-gobject pacman-contrib"
|
||||||
if [ -n "$PKGS" ]; then
|
if [ -n "$PKGS" ]; then
|
||||||
pacman-key --init &>/dev/null || true
|
pacman-key --init &>/dev/null || true
|
||||||
pacman-key --populate archlinux &>/dev/null || true
|
pacman-key --populate archlinux &>/dev/null || true
|
||||||
if pacman -Sy --noconfirm $PKGS; then
|
|
||||||
|
# Script de RATTRAPAGE généré avec les valeurs de CE choix : si l'install
|
||||||
|
# du bureau échoue (réseau/miroirs), l'utilisateur la rejoue en une
|
||||||
|
# commande depuis le système installé. --needed → inoffensif si déjà fait.
|
||||||
|
cat > /usr/local/bin/fws-desktop-retry <<RETRYEOF
|
||||||
|
#!/bin/bash
|
||||||
|
# Généré par l'installateur FWS — (ré)installe le bureau « $DESK ».
|
||||||
|
set -u
|
||||||
|
[ "\$(id -u)" -eq 0 ] || exec sudo "\$0" "\$@"
|
||||||
|
echo "==> Installation du bureau $DESK…"
|
||||||
|
pacman -Syy --noconfirm --needed $PKGS || { echo "Échec (réseau ?) — réessayez."; exit 1; }
|
||||||
|
[ -n "$DM" ] && systemctl enable "$DM"
|
||||||
|
systemctl set-default graphical.target
|
||||||
|
rm -f /var/lib/fws/desktop-failed
|
||||||
|
[ -f /etc/motd ] && grep -q fws-desktop-retry /etc/motd && rm -f /etc/motd
|
||||||
|
echo "==> Bureau installé ✔ — redémarrez (reboot) pour arriver sur l'écran de connexion."
|
||||||
|
RETRYEOF
|
||||||
|
chmod 755 /usr/local/bin/fws-desktop-retry
|
||||||
|
|
||||||
|
# Le réseau peut mettre quelques secondes à monter dans l'environnement
|
||||||
|
# d'installation : on attend la résolution DNS avant le premier essai,
|
||||||
|
# puis on tente pacman jusqu'à 3 fois (miroir capricieux ≠ échec définitif).
|
||||||
|
for _i in 1 2 3 4 5 6 7 8 9 10; do
|
||||||
|
getent hosts geo.mirror.pkgbuild.com >/dev/null 2>&1 && break
|
||||||
|
sleep 3
|
||||||
|
done
|
||||||
|
_DE_OK=""
|
||||||
|
for _try in 1 2 3; do
|
||||||
|
pacman -Sy --noconfirm --needed $PKGS && { _DE_OK=1; break; }
|
||||||
|
echo "[FWS] installation du bureau : tentative $_try échouée — nouvel essai dans 15 s…"
|
||||||
|
sleep 15
|
||||||
|
done
|
||||||
|
if [ -n "$_DE_OK" ]; then
|
||||||
[ -n "$DM" ] && systemctl enable "$DM" || true
|
[ -n "$DM" ] && systemctl enable "$DM" || true
|
||||||
systemctl set-default graphical.target || true
|
systemctl set-default graphical.target || true
|
||||||
|
# SDDM (KDE / XFCE / Hyprland) : pavé numérique + écran de connexion FWS.
|
||||||
|
# Le thème « fws » (livré par l'airootfs → recopié par le payload dans
|
||||||
|
# /usr/share/sddm/themes/fws) est en pur QtQuick/Controls Basic :
|
||||||
|
# qt6-declarative, dépendance de sddm, suffit → même écran soigné pour
|
||||||
|
# les 3 DE SANS paquet de thème supplémentaire (Breeze n'existe qu'avec
|
||||||
|
# plasma). Accent adapté au bureau via theme.conf.user (mécanisme de
|
||||||
|
# surcharge standard de SDDM, lu par-dessus theme.conf du thème).
|
||||||
|
if [ "$DM" = "sddm" ]; then
|
||||||
|
install -d /etc/sddm.conf.d
|
||||||
|
cat > /etc/sddm.conf.d/10-fws.conf <<'SDDMEOF'
|
||||||
|
[General]
|
||||||
|
Numlock=on
|
||||||
|
|
||||||
|
[Theme]
|
||||||
|
Current=fws
|
||||||
|
SDDMEOF
|
||||||
|
case "$DESK" in
|
||||||
|
xfce) _ACCENT="#16a085" ;; # sarcelle (assorti à XFCE)
|
||||||
|
hyprland) _ACCENT="#89b4fa" ;; # bleu Catppuccin (assorti à Hyprland)
|
||||||
|
*) _ACCENT="#3daee9" ;; # bleu Breeze (KDE, défaut)
|
||||||
|
esac
|
||||||
|
if [ -d /usr/share/sddm/themes/fws ]; then
|
||||||
|
printf '[General]\naccent=%s\n' "$_ACCENT" \
|
||||||
|
> /usr/share/sddm/themes/fws/theme.conf.user
|
||||||
|
else
|
||||||
|
echo "[FWS] thème SDDM fws introuvable — thème par défaut conservé"
|
||||||
|
sed -i '/^Current=fws$/d' /etc/sddm.conf.d/10-fws.conf
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# GDM (GNOME) : « la même chose » autant que possible — le greeter de
|
||||||
|
# GDM EST GNOME Shell, il ne se thème pas comme SDDM. On passe par
|
||||||
|
# dconf (base système « gdm ») : bannière d'accueil FWS, horloge avec
|
||||||
|
# date complète, pavé numérique allumé — même confort qu'avec SDDM.
|
||||||
|
if [ "$DM" = "gdm" ]; then
|
||||||
|
install -d /etc/dconf/profile /etc/dconf/db/gdm.d
|
||||||
|
cat > /etc/dconf/profile/gdm <<'GDMEOF'
|
||||||
|
user-db:user
|
||||||
|
system-db:gdm
|
||||||
|
file-db:/usr/share/gdm/greeter-dconf-defaults
|
||||||
|
GDMEOF
|
||||||
|
cat > /etc/dconf/db/gdm.d/10-fws <<'GDMEOF'
|
||||||
|
[org/gnome/login-screen]
|
||||||
|
banner-message-enable=true
|
||||||
|
banner-message-text='Bienvenue sur FWS'
|
||||||
|
|
||||||
|
[org/gnome/desktop/interface]
|
||||||
|
clock-show-date=true
|
||||||
|
clock-show-weekday=true
|
||||||
|
|
||||||
|
[org/gnome/desktop/peripherals/keyboard]
|
||||||
|
numlock-state=true
|
||||||
|
remember-numlock-state=false
|
||||||
|
GDMEOF
|
||||||
|
dconf update || echo "[FWS] dconf update a échoué (bannière GDM à refaire)"
|
||||||
|
fi
|
||||||
|
# Hyprland en VM : sur GPU virtuel (VMware/VirtualBox) le compositeur
|
||||||
|
# crashe au lancement → SDDM « remet au login ». On le lance via un
|
||||||
|
# wrapper qui force le rendu LOGICIEL UNIQUEMENT en VM (systemd-detect-virt)
|
||||||
|
# ; sur un vrai GPU le rendu matériel est conservé.
|
||||||
|
if [ "$DESK" = "hyprland" ]; then
|
||||||
|
cat > /usr/local/bin/fws-hyprland <<'HYEOF'
|
||||||
|
#!/bin/sh
|
||||||
|
if systemd-detect-virt --quiet 2>/dev/null; then
|
||||||
|
# GPU virtuel (VMware vmwgfx…) : on FORCE le chemin d'affichage 100% logiciel.
|
||||||
|
# La PIÈCE MAÎTRESSE est MESA_LOADER_DRIVER_OVERRIDE=kms_swrast : elle force
|
||||||
|
# Mesa à allouer le GBM/EGL via kms_swrast (rendu logiciel dans les « dumb
|
||||||
|
# buffers » KMS) AU LIEU du pilote vmwgfx (qui ne fournit pas le GBM dont
|
||||||
|
# aquamarine a besoin → « CBackend::create() failed »). + llvmpipe pour le GL.
|
||||||
|
export MESA_LOADER_DRIVER_OVERRIDE=kms_swrast
|
||||||
|
export GALLIUM_DRIVER=llvmpipe LIBGL_ALWAYS_SOFTWARE=1
|
||||||
|
export WLR_RENDERER_ALLOW_SOFTWARE=1 WLR_NO_HARDWARE_CURSORS=1
|
||||||
|
export AQ_NO_ATOMIC=1
|
||||||
|
fi
|
||||||
|
# Hyprland ≥ 0.53 : start-hyprland est le lanceur officiel (prépare la
|
||||||
|
# session/env systemd) — sans lui, bandeau « started without start-hyprland ».
|
||||||
|
if command -v start-hyprland >/dev/null 2>&1; then
|
||||||
|
exec start-hyprland "$@"
|
||||||
|
fi
|
||||||
|
exec Hyprland "$@"
|
||||||
|
HYEOF
|
||||||
|
chmod 755 /usr/local/bin/fws-hyprland
|
||||||
|
if [ -f /usr/share/wayland-sessions/hyprland.desktop ]; then
|
||||||
|
sed -i 's|^Exec=.*|Exec=/usr/local/bin/fws-hyprland|' \
|
||||||
|
/usr/share/wayland-sessions/hyprland.desktop
|
||||||
|
fi
|
||||||
|
# Bluetooth prêt au 1er démarrage (bluez vient d'être installé
|
||||||
|
# avec le bureau ; fws-setup-hardware le refait — idempotent).
|
||||||
|
systemctl enable bluetooth.service 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
if [ "$DESK" = "i3" ]; then
|
||||||
|
systemctl enable bluetooth.service 2>/dev/null || true
|
||||||
|
# Icône du lanceur « Installation VR » (casque) visible dans rofi.
|
||||||
|
gtk-update-icon-cache -f /usr/share/icons/hicolor 2>/dev/null || true
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo "[FWS] Bureau '$DESK' non installé (réseau ?) — à refaire après le 1er boot."
|
echo "[FWS] Bureau '$DESK' NON installé après 3 tentatives (réseau/miroirs ?)."
|
||||||
|
echo "[FWS] Rattrapage sur le système installé : sudo fws-desktop-retry"
|
||||||
|
mkdir -p /var/lib/fws
|
||||||
|
echo "$DESK" > /var/lib/fws/desktop-failed
|
||||||
|
# Le premier login (console) explique quoi faire — le motd est retiré
|
||||||
|
# par fws-desktop-retry une fois le bureau posé.
|
||||||
|
cat > /etc/motd <<'MOTD'
|
||||||
|
|
||||||
|
⚠ FWS : l'installation du bureau a échoué (réseau indisponible pendant
|
||||||
|
l'installation). Le système fonctionne, mais en console.
|
||||||
|
|
||||||
|
Une fois le réseau branché, lancez : sudo fws-desktop-retry
|
||||||
|
puis redémarrez : le bureau choisi sera installé et activé.
|
||||||
|
|
||||||
|
MOTD
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# --- 4quater) ADN FWS : détection matériel + drivers + stack « Windows » ------
|
||||||
|
# Script dédié (NON bloquant ; réseau requis) : drivers GPU détectés via lspci +
|
||||||
|
# outils invité VM, son/micro/casque (PipeWire), Bluetooth, Podman, et — si un
|
||||||
|
# bureau est choisi — Steam, Wine, VR (Monado/SteamVR), winboat-bin (apps
|
||||||
|
# Windows). C'est le cœur « remplacement de Windows » de FWS.
|
||||||
|
if [ -x /usr/local/bin/fws-setup-hardware ]; then
|
||||||
|
/usr/local/bin/fws-setup-hardware "$DESK" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Config Hyprland (si choisi) : clavier + apps + waybar -------------------
|
||||||
|
# Hyprland (Wayland) NE lit PAS /etc/X11/xorg.conf.d/00-keyboard.conf : sa
|
||||||
|
# disposition est dans SA config (input { kb_layout }). Sans config il démarre nu
|
||||||
|
# (QWERTY US, aucune touche utile). On déploie une config FWS (template +
|
||||||
|
# clavier XL/XV injecté) dans /etc/skel ET le home de chaque utilisateur déjà
|
||||||
|
# créé par Anaconda (le %post tourne APRÈS la création de l'utilisateur).
|
||||||
|
if [ "$DESK" = "hyprland" ] && [ -f /usr/local/share/fws/hyprland.conf.tmpl ]; then
|
||||||
|
sed -e "s/__KB_LAYOUT__/${XL:-us}/" -e "s/__KB_VARIANT__/${XV}/" \
|
||||||
|
/usr/local/share/fws/hyprland.conf.tmpl > /tmp/fws-hyprland.conf
|
||||||
|
install -Dm644 /tmp/fws-hyprland.conf /etc/skel/.config/hypr/hyprland.conf
|
||||||
|
# Waybar FWS : barre assortie + bouton « ⌨ Raccourcis » (fws-keys,
|
||||||
|
# aussi sur SUPER+K) qui liste les binds RÉELS chargés par Hyprland
|
||||||
|
# (hyprctl binds -j) — les descriptions viennent des bindd de la config.
|
||||||
|
install -Dm644 /usr/local/share/fws/waybar-config.json /etc/skel/.config/waybar/config
|
||||||
|
install -Dm644 /usr/local/share/fws/waybar-style.css /etc/skel/.config/waybar/style.css
|
||||||
|
# wofi : style du menu d'applications (Super+R) — le panneau « Raccourcis »
|
||||||
|
# a son propre style (wofi-keys.css), passé en --style par fws-keys.
|
||||||
|
install -Dm644 /usr/local/share/fws/wofi-config /etc/skel/.config/wofi/config
|
||||||
|
install -Dm644 /usr/local/share/fws/wofi-style.css /etc/skel/.config/wofi/style.css
|
||||||
|
while IFS=: read -r _u _x _uid _g _gec _home _sh; do
|
||||||
|
[ "$_uid" -ge 1000 ] && [ "$_uid" -le 60000 ] && [ -d "$_home" ] || continue
|
||||||
|
install -Dm644 /tmp/fws-hyprland.conf "$_home/.config/hypr/hyprland.conf"
|
||||||
|
install -Dm644 /usr/local/share/fws/waybar-config.json "$_home/.config/waybar/config"
|
||||||
|
install -Dm644 /usr/local/share/fws/waybar-style.css "$_home/.config/waybar/style.css"
|
||||||
|
install -Dm644 /usr/local/share/fws/wofi-config "$_home/.config/wofi/config"
|
||||||
|
install -Dm644 /usr/local/share/fws/wofi-style.css "$_home/.config/wofi/style.css"
|
||||||
|
chown "$_u": "$_home/.config" 2>/dev/null || true
|
||||||
|
chown -R "$_u": "$_home/.config/hypr" "$_home/.config/waybar" \
|
||||||
|
"$_home/.config/wofi" 2>/dev/null || true
|
||||||
|
done < /etc/passwd
|
||||||
|
rm -f /tmp/fws-hyprland.conf
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Config i3 (si choisi) : raccourcis FWS + barre + autostart -------------
|
||||||
|
# Pas d'injection clavier ici : les sessions X11 lisent la disposition dans
|
||||||
|
# /etc/X11/xorg.conf.d/00-keyboard.conf, écrite à l'étape 3bis.
|
||||||
|
if [ "$DESK" = "i3" ] && [ -f /usr/local/share/fws/i3-config ]; then
|
||||||
|
install -Dm644 /usr/local/share/fws/i3-config /etc/skel/.config/i3/config
|
||||||
|
# Polybar (clone de la waybar) + picom (coins arrondis) : même rendu
|
||||||
|
# visuel que la session Hyprland FWS.
|
||||||
|
install -Dm644 /usr/local/share/fws/polybar-config.ini /etc/skel/.config/polybar/config.ini
|
||||||
|
install -Dm644 /usr/local/share/fws/picom.conf /etc/skel/.config/picom.conf
|
||||||
|
# rofi : menu d'applications au look FWS (équivalent du wofi d'Hyprland).
|
||||||
|
install -Dm644 /usr/local/share/fws/rofi-config.rasi /etc/skel/.config/rofi/config.rasi
|
||||||
|
while IFS=: read -r _u _x _uid _g _gec _home _sh; do
|
||||||
|
[ "$_uid" -ge 1000 ] && [ "$_uid" -le 60000 ] && [ -d "$_home" ] || continue
|
||||||
|
install -Dm644 /usr/local/share/fws/i3-config "$_home/.config/i3/config"
|
||||||
|
install -Dm644 /usr/local/share/fws/polybar-config.ini "$_home/.config/polybar/config.ini"
|
||||||
|
install -Dm644 /usr/local/share/fws/picom.conf "$_home/.config/picom.conf"
|
||||||
|
install -Dm644 /usr/local/share/fws/rofi-config.rasi "$_home/.config/rofi/config.rasi"
|
||||||
|
chown "$_u": "$_home/.config" 2>/dev/null || true
|
||||||
|
chown -R "$_u": "$_home/.config/i3" "$_home/.config/polybar" \
|
||||||
|
"$_home/.config/rofi" "$_home/.config/picom.conf" 2>/dev/null || true
|
||||||
|
done < /etc/passwd
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- 4bis) Sudo : l'utilisateur créé doit pouvoir administrer -----------------
|
||||||
|
# Arch laisse « %wheel » COMMENTÉ dans sudoers → même un membre de wheel ne peut
|
||||||
|
# pas sudo. On active le groupe wheel (drop-in) ET on y ajoute tous les
|
||||||
|
# utilisateurs HUMAINS (UID 1000-60000) créés par Anaconda → ils sont sudoers.
|
||||||
|
install -d -m 750 /etc/sudoers.d
|
||||||
|
printf '%%wheel ALL=(ALL:ALL) ALL\n' > /etc/sudoers.d/10-fws-wheel
|
||||||
|
chmod 440 /etc/sudoers.d/10-fws-wheel
|
||||||
|
while IFS=: read -r _u _x _uid _rest; do
|
||||||
|
if [ "$_uid" -ge 1000 ] && [ "$_uid" -le 60000 ]; then
|
||||||
|
usermod -aG wheel "$_u" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
done < /etc/passwd
|
||||||
|
|
||||||
|
# --- 4ter) Branding FWS sur le système installé -------------------------------
|
||||||
|
# Le hook os-release du build n'est PAS sur la cible et « pacman -S » a pu
|
||||||
|
# remettre l'os-release d'Arch → d'où « Arch Linux » au menu GRUB, le logo Arch
|
||||||
|
# sur l'écran de connexion (GDM lit os-release LOGO) et le nom « Arch ». On FORCE
|
||||||
|
# donc l'os-release FWS (+ lsb-release + GRUB_DISTRIBUTOR), on RÉGÉNÈRE le menu
|
||||||
|
# GRUB (10_linux lit le NAME d'os-release), et on retire le motd « live ».
|
||||||
|
if [ -f /usr/local/share/fws/os-release ]; then
|
||||||
|
cp -f /usr/local/share/fws/os-release /usr/lib/os-release
|
||||||
|
ln -sf ../usr/lib/os-release /etc/os-release
|
||||||
|
fi
|
||||||
|
if [ -f /etc/lsb-release ]; then
|
||||||
|
sed -i 's/^DISTRIB_ID=.*/DISTRIB_ID=FWS/; s/^DISTRIB_DESCRIPTION=.*/DISTRIB_DESCRIPTION="FWS Linux"/' \
|
||||||
|
/etc/lsb-release
|
||||||
|
fi
|
||||||
|
if grep -q '^GRUB_DISTRIBUTOR=' /etc/default/grub 2>/dev/null; then
|
||||||
|
sed -i 's/^GRUB_DISTRIBUTOR=.*/GRUB_DISTRIBUTOR="FWS"/' /etc/default/grub
|
||||||
|
else
|
||||||
|
echo 'GRUB_DISTRIBUTOR="FWS"' >> /etc/default/grub
|
||||||
|
fi
|
||||||
|
grub-mkconfig -o /boot/grub/grub.cfg 2>/dev/null || true
|
||||||
|
# motd « live » (texte inadapté + codes ANSI bruts affichés crus par GDM) —
|
||||||
|
# SAUF si c'est notre message « fws-desktop-retry » (échec d'install du
|
||||||
|
# bureau), qui doit survivre jusqu'au rattrapage.
|
||||||
|
grep -q fws-desktop-retry /etc/motd 2>/dev/null || rm -f /etc/motd
|
||||||
|
|
||||||
|
# --- 4quinquies) Pare-feu par défaut -------------------------------------------
|
||||||
|
# firewalld est dans l'IMAGE (packages.x86_64) → recopié par le payload,
|
||||||
|
# activation possible HORS LIGNE. Zone par défaut « public » : entrées
|
||||||
|
# bloquées, sortant libre — le comportement attendu d'un poste de travail.
|
||||||
|
# GUI incluse dans le paquet (firewall-config) ; KDE a en plus plasma-firewall.
|
||||||
|
systemctl enable firewalld.service 2>/dev/null || true
|
||||||
|
|
||||||
|
# --- 4quinquies-bis) Horloge juste : synchronisation NTP -----------------------
|
||||||
|
# Sans NTP l'horloge dérive (TLS/pacman finissent par râler, et un utilisateur
|
||||||
|
# venant de Windows s'attend à une heure exacte). systemd-timesyncd est dans
|
||||||
|
# l'image ; on l'ACTIVE explicitement — les symlinks d'activation hérités du
|
||||||
|
# live étaient des FICHIERS ordinaires (artefact de checkout) que systemd
|
||||||
|
# ignore (« not a symlink »), corrigés côté live mais on assure ici quoi
|
||||||
|
# qu'il arrive sur le système installé.
|
||||||
|
systemctl enable systemd-timesyncd.service 2>/dev/null || true
|
||||||
|
|
||||||
|
# NB Récupération : l'entrée GRUB « Réparer FWS » (générée par
|
||||||
|
# /etc/grub.d/42_fws-recovery lors des grub-mkconfig ci-dessus) boote
|
||||||
|
# systemd.unit=fws-recovery.target → fws-recovery-boot.service prend tty1 et
|
||||||
|
# lance l'application de réparation. RIEN à activer : le service n'a pas de
|
||||||
|
# [Install] (son Conflicts=getty@tty1 ne doit jamais entrer dans un boot
|
||||||
|
# normal), la target le tire par Requires. Dans le bureau, le lanceur
|
||||||
|
# « Récupération FWS » (fws-recovery.desktop, polkit) fait le même travail.
|
||||||
|
|
||||||
# --- 5) Sécurité : verrouiller root s'il est resté SANS mot de passe ----------
|
# --- 5) Sécurité : verrouiller root s'il est resté SANS mot de passe ----------
|
||||||
# L'airootfs live a souvent un root sans mot de passe ; si l'utilisateur n'en a
|
# L'airootfs live a souvent un root sans mot de passe ; si l'utilisateur n'en a
|
||||||
# pas défini dans le GUI, on verrouille plutôt que de laisser le compte ouvert.
|
# pas défini dans le GUI, on verrouille plutôt que de laisser le compte ouvert.
|
||||||
@@ -223,9 +601,52 @@ if passwd -S root 2>/dev/null | grep -qE '\bNP\b'; then
|
|||||||
passwd -l root || true
|
passwd -l root || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- 6) Le resolv.conf du live ne doit pas rester figé -----------------------
|
# --- 6) DNS du système installé : NetworkManager + systemd-resolved ----------
|
||||||
# NetworkManager (activé sur le système installé) régénère /etc/resolv.conf.
|
# Le live utilise systemd-resolved (/etc/resolv.conf → stub 127.0.0.53). L'ANCIEN
|
||||||
rm -f /tmp/fws-desktop /etc/resolv.conf 2>/dev/null || true
|
# « rm /etc/resolv.conf » comptait sur NM pour recréer le fichier — MAIS avec
|
||||||
|
# systemd-resolved actif, NM ne réécrit PAS resolv.conf (il pousse les DNS vers
|
||||||
|
# resolved). Sans le lien, la résolution est MORTE → « Limited Connectivity » /
|
||||||
|
# Firefox « Server Not Found » (le réseau marche, le DNS non). Fix robuste :
|
||||||
|
# - resolv.conf → stub systemd-resolved,
|
||||||
|
# - service systemd-resolved activé,
|
||||||
|
# - on FORCE NM à utiliser resolved (dns=systemd-resolved) → il lui fournit les
|
||||||
|
# DNS DHCP, la résolution marche dès le 1er boot.
|
||||||
|
rm -f /tmp/fws-desktop 2>/dev/null || true
|
||||||
|
ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
|
||||||
|
systemctl enable systemd-resolved.service 2>/dev/null || true
|
||||||
|
install -d /etc/NetworkManager/conf.d
|
||||||
|
printf '[main]\ndns=systemd-resolved\n' > /etc/NetworkManager/conf.d/dns.conf
|
||||||
|
|
||||||
|
# --- 7) Secure Boot (uniquement si « Dual-boot Windows gaming » demandé) ------
|
||||||
|
# Vanguard exige Secure Boot. On PRÉPARE la chaîne pendant l'install : sbctl
|
||||||
|
# create-keys + signature des 3 chemins réels (\EFI\FWS, \EFI\BOOT\BOOTX64, noyaux).
|
||||||
|
# L'enrôlement des clés + l'activation de SB au firmware restent MANUELS (Setup
|
||||||
|
# Mode requis, non automatisable) — instructions au 1er boot. Idempotent.
|
||||||
|
# On NE source PAS /tmp/fws-windows (un chemin d'ISO piégé pourrait injecter du
|
||||||
|
# code) : simple test littéral de la ligne « enabled=1 ».
|
||||||
|
if grep -qx 'enabled=1' /tmp/fws-windows 2>/dev/null \
|
||||||
|
&& [ -x /usr/local/bin/fws-secureboot-setup ]; then
|
||||||
|
/usr/local/bin/fws-secureboot-setup \
|
||||||
|
|| echo "[FWS] Secure Boot : préparation partielle (à finaliser au 1er boot)."
|
||||||
|
fi
|
||||||
|
%end
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# %post --nochroot — DÉPLOIEMENT WINDOWS (option A) si « Dual-boot Windows »
|
||||||
|
# demandé. Tourne dans le LIVE (cible sous /mnt/sysroot). NON bloquant : un
|
||||||
|
# échec de déploiement N'EMPÊCHE PAS FWS de booter (garde-fous fail-closed +
|
||||||
|
# statut DEPLOY_FAILED, aucune entrée UEFI Windows piégeuse). Le carve du disque
|
||||||
|
# Windows vit ICI (le %pre tourne avant le GUI → ne voit pas /tmp/fws-windows).
|
||||||
|
# Voir fws-windows-deploy + docs/windows-spoke-plan.md.
|
||||||
|
# ============================================================================
|
||||||
|
%post --nochroot --log=/tmp/fws-windows-deploy.log
|
||||||
|
SYSROOT=/mnt/sysroot
|
||||||
|
[ -d "$SYSROOT" ] || SYSROOT=/mnt/sysimage
|
||||||
|
if [ -f /tmp/fws-windows ] && [ -x /usr/local/bin/fws-windows-deploy ]; then
|
||||||
|
/usr/local/bin/fws-windows-deploy --state /tmp/fws-windows --sysroot "$SYSROOT" \
|
||||||
|
|| echo "[FWS] Déploiement Windows échoué (voir le log) — FWS reste bootable."
|
||||||
|
fi
|
||||||
%end
|
%end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=À propos de FWS
|
||||||
|
Name[en]=About FWS
|
||||||
|
Comment=Informations sur le projet FWS et sur le système
|
||||||
|
Exec=fws-about
|
||||||
|
Icon=help-about
|
||||||
|
Terminal=false
|
||||||
|
Categories=System;
|
||||||
|
Keywords=about;système;projet;info;FWS;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
# FWS — lanceur gameboot : hiberne FWS et bascule vers le Windows bare-metal
|
||||||
|
# pour jouer à League of Legends (anticheat Vanguard). Aucun mot de passe pour la
|
||||||
|
# session active (cf. polkit org.fws.gameboot.policy).
|
||||||
|
Type=Application
|
||||||
|
Name=Jouer à League of Legends
|
||||||
|
Name[en]=Play League of Legends
|
||||||
|
GenericName=Bascule vers Windows (gameboot)
|
||||||
|
GenericName[en]=Switch to Windows (gameboot)
|
||||||
|
Comment=Hiberne FWS et démarre League of Legends sur le Windows bare-metal
|
||||||
|
Comment[en]=Hibernate FWS and boot League of Legends on bare-metal Windows
|
||||||
|
Exec=pkexec /usr/local/bin/fws-gameboot to-windows --game lol
|
||||||
|
Icon=fws-gameboot
|
||||||
|
Terminal=false
|
||||||
|
Categories=Game;
|
||||||
|
Keywords=league;legends;lol;vanguard;windows;gameboot;dual-boot;jeu;
|
||||||
|
Keywords[en]=league;legends;lol;vanguard;windows;gameboot;dual-boot;game;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
# FWS — lanceur gameboot : hiberne FWS et bascule vers le Windows bare-metal
|
||||||
|
# pour jouer à Valorant (anticheat Vanguard). Aucun mot de passe pour la session
|
||||||
|
# active (cf. polkit org.fws.gameboot.policy).
|
||||||
|
Type=Application
|
||||||
|
Name=Jouer à Valorant
|
||||||
|
Name[en]=Play Valorant
|
||||||
|
GenericName=Bascule vers Windows (gameboot)
|
||||||
|
GenericName[en]=Switch to Windows (gameboot)
|
||||||
|
Comment=Hiberne FWS et démarre Valorant sur le Windows bare-metal
|
||||||
|
Comment[en]=Hibernate FWS and boot Valorant on bare-metal Windows
|
||||||
|
Exec=pkexec /usr/local/bin/fws-gameboot to-windows --game valorant
|
||||||
|
Icon=fws-gameboot
|
||||||
|
Terminal=false
|
||||||
|
Categories=Game;
|
||||||
|
Keywords=valorant;vanguard;windows;gameboot;dual-boot;jeu;
|
||||||
|
Keywords[en]=valorant;vanguard;windows;gameboot;dual-boot;game;
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=Installation VR (FWS)
|
||||||
|
Name[en]=VR Setup (FWS)
|
||||||
|
Comment=Installer les drivers/runtimes pour casque VR (HTC Vive, Index, Quest…)
|
||||||
|
Exec=kitty -e fws-vr-setup
|
||||||
|
Icon=fws-vr
|
||||||
|
Terminal=false
|
||||||
|
Categories=System;Settings;
|
||||||
|
Keywords=VR;SteamVR;Vive;Index;Quest;casque;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- FWS — icône générique « gameboot » (lanceur de bascule vers Windows gaming).
|
||||||
|
Neutre, sans logo de marque (Valorant/LoL sont des marques de Riot). -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="fwsGameGrad" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="0" stop-color="#89b4fa"/>
|
||||||
|
<stop offset="1" stop-color="#5b7fd4"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect x="12" y="12" width="104" height="104" rx="26" fill="url(#fwsGameGrad)"/>
|
||||||
|
<!-- Poignées de manette stylisées -->
|
||||||
|
<rect x="30" y="52" width="68" height="34" rx="17" fill="#ffffff" opacity="0.18"/>
|
||||||
|
<!-- Triangle « play » = lancer le jeu -->
|
||||||
|
<path d="M54 44 L88 64 L54 84 Z" fill="#ffffff"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 809 B |
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- FWS — icône « casque VR » (lanceur fws-vr-setup). Palette FWS/Catppuccin. -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
|
||||||
|
<!-- sangles -->
|
||||||
|
<path d="M8 28 C1 29 1 41 8 42" fill="none" stroke="#89b4fa" stroke-width="4" stroke-linecap="round"/>
|
||||||
|
<path d="M56 28 C63 29 63 41 56 42" fill="none" stroke="#89b4fa" stroke-width="4" stroke-linecap="round"/>
|
||||||
|
<!-- corps du casque avec encoche du nez -->
|
||||||
|
<path d="M16 18 h32 a10 10 0 0 1 10 10 v10 a10 10 0 0 1 -10 10 h-7
|
||||||
|
a9 9 0 0 0 -18 0 h-7 a10 10 0 0 1 -10 -10 v-10 a10 10 0 0 1 10 -10 z"
|
||||||
|
fill="#1e1e2e" stroke="#89b4fa" stroke-width="3" stroke-linejoin="round"/>
|
||||||
|
<!-- lentilles -->
|
||||||
|
<circle cx="23" cy="32" r="5.5" fill="#74c7ec"/>
|
||||||
|
<circle cx="41" cy="32" r="5.5" fill="#74c7ec"/>
|
||||||
|
<circle cx="21.5" cy="30.5" r="1.8" fill="#cdd6f4"/>
|
||||||
|
<circle cx="39.5" cy="30.5" r="1.8" fill="#cdd6f4"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 964 B |
@@ -0,0 +1,26 @@
|
|||||||
|
# ============================================================
|
||||||
|
# FWS — re-signature Secure Boot (sbctl) après une MAJ de noyau ou de GRUB.
|
||||||
|
#
|
||||||
|
# Sans ça, « pacman -Syu » qui met à jour linux/grub réécrit des binaires NON
|
||||||
|
# signés → au reboot sous Secure Boot, le firmware refuse de démarrer FWS.
|
||||||
|
# « sbctl sign-all » re-signe tous les fichiers suivis (idempotent). Complète
|
||||||
|
# le hook natif de sbctl (filet redondant, sans effet s'il est déjà passé).
|
||||||
|
#
|
||||||
|
# Inoffensif tant que sbctl n'a rien enrôlé (sign-all ne fait alors rien).
|
||||||
|
# ============================================================
|
||||||
|
[Trigger]
|
||||||
|
Type = Path
|
||||||
|
Operation = Install
|
||||||
|
Operation = Upgrade
|
||||||
|
Target = usr/lib/modules/*/vmlinuz
|
||||||
|
Target = boot/vmlinuz-*
|
||||||
|
Target = usr/lib/grub/*
|
||||||
|
Target = boot/grub/x86_64-efi/*
|
||||||
|
Target = boot/efi/EFI/*/grubx64.efi
|
||||||
|
Target = efi/EFI/*/grubx64.efi
|
||||||
|
|
||||||
|
[Action]
|
||||||
|
Description = FWS : re-signature Secure Boot des binaires de boot (sbctl)…
|
||||||
|
When = PostTransaction
|
||||||
|
Exec = /usr/bin/sbctl sign-all
|
||||||
|
Depends = sbctl
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE policyconfig PUBLIC
|
||||||
|
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
|
||||||
|
"http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
|
||||||
|
<!--
|
||||||
|
FWS — autorise les lanceurs gameboot (entrées .desktop « Jouer à … ») à
|
||||||
|
exécuter /usr/local/bin/fws-gameboot via pkexec.
|
||||||
|
|
||||||
|
allow_active = yes : AUCUN mot de passe pour l'utilisateur physiquement présent
|
||||||
|
(poste gaming perso). La bascule n'est PAS destructive — FWS hiberne, la
|
||||||
|
session est préservée et restaurée au retour. auth_admin pour les sessions
|
||||||
|
inactives/distantes.
|
||||||
|
-->
|
||||||
|
<policyconfig>
|
||||||
|
<vendor>FWS</vendor>
|
||||||
|
<action id="org.fws.gameboot">
|
||||||
|
<description>Basculer vers Windows pour jouer (gameboot)</description>
|
||||||
|
<description xml:lang="en">Switch to Windows to play (gameboot)</description>
|
||||||
|
<message>Authentification requise pour basculer vers Windows</message>
|
||||||
|
<message xml:lang="en">Authentication is required to switch to Windows</message>
|
||||||
|
<icon_name>fws-gameboot</icon_name>
|
||||||
|
<defaults>
|
||||||
|
<allow_any>auth_admin</allow_any>
|
||||||
|
<allow_inactive>auth_admin</allow_inactive>
|
||||||
|
<allow_active>yes</allow_active>
|
||||||
|
</defaults>
|
||||||
|
<annotate key="org.freedesktop.policykit.exec.path">/usr/local/bin/fws-gameboot</annotate>
|
||||||
|
</action>
|
||||||
|
</policyconfig>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE policyconfig PUBLIC
|
||||||
|
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
|
||||||
|
"http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
|
||||||
|
<!--
|
||||||
|
FWS — autorise fws-recovery-gui (lancée en utilisateur depuis le menu de
|
||||||
|
l'OS installé) à exécuter le MOTEUR fws-recovery via pkexec.
|
||||||
|
auth_admin_keep : le mot de passe administrateur n'est demandé qu'une fois
|
||||||
|
(mémorisé quelques minutes), pas à chaque bouton.
|
||||||
|
-->
|
||||||
|
<policyconfig>
|
||||||
|
<vendor>FWS</vendor>
|
||||||
|
<action id="org.fws.recovery">
|
||||||
|
<description>Réparer le système FWS</description>
|
||||||
|
<description xml:lang="en">Repair the FWS system</description>
|
||||||
|
<message>Authentification requise pour réparer le système</message>
|
||||||
|
<message xml:lang="en">Authentication is required to repair the system</message>
|
||||||
|
<icon_name>fws-recovery</icon_name>
|
||||||
|
<defaults>
|
||||||
|
<allow_any>auth_admin_keep</allow_any>
|
||||||
|
<allow_inactive>auth_admin_keep</allow_inactive>
|
||||||
|
<allow_active>auth_admin_keep</allow_active>
|
||||||
|
</defaults>
|
||||||
|
<annotate key="org.freedesktop.policykit.exec.path">/usr/local/bin/fws-recovery</annotate>
|
||||||
|
</action>
|
||||||
|
</policyconfig>
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
// Thème SDDM « fws » — écran de connexion FWS (KDE / XFCE / Hyprland).
|
||||||
|
//
|
||||||
|
// VOLONTAIREMENT en pur QtQuick + QtQuick.Controls (style Basic) : ces
|
||||||
|
// modules arrivent avec qt6-declarative, dépendance de sddm lui-même →
|
||||||
|
// AUCUN paquet supplémentaire requis (pas de Breeze/plasma-workspace, pas
|
||||||
|
// de QtGraphicalEffects — supprimé en Qt6). Le greeter fournit en contexte
|
||||||
|
// les objets sddm, userModel, sessionModel, keyboard et config (theme.conf,
|
||||||
|
// surchargé par theme.conf.user écrit par le %post : accent selon le DE).
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls.Basic
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
width: 1280
|
||||||
|
height: 800
|
||||||
|
|
||||||
|
readonly property color accent: config.accent || "#3daee9"
|
||||||
|
readonly property color fieldBg: "#2a2a44"
|
||||||
|
|
||||||
|
gradient: Gradient {
|
||||||
|
GradientStop { position: 0.0; color: config.colorTop || "#1a1a2e" }
|
||||||
|
GradientStop { position: 1.0; color: config.colorBottom || "#16213e" }
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: sddm
|
||||||
|
function onLoginFailed() {
|
||||||
|
errorLabel.text = "Échec de la connexion — vérifiez le mot de passe"
|
||||||
|
passwordField.text = ""
|
||||||
|
passwordField.forceActiveFocus()
|
||||||
|
}
|
||||||
|
function onLoginSucceeded() {
|
||||||
|
errorLabel.text = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function doLogin() {
|
||||||
|
errorLabel.text = ""
|
||||||
|
sddm.login(userField.text, passwordField.text, sessionBox.currentIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Horloge -----------------------------------------------------------
|
||||||
|
Timer {
|
||||||
|
interval: 1000
|
||||||
|
running: true
|
||||||
|
repeat: true
|
||||||
|
onTriggered: {
|
||||||
|
var now = new Date()
|
||||||
|
clockLabel.text = Qt.formatTime(now, "hh:mm")
|
||||||
|
dateLabel.text = Qt.formatDate(now, "dddd d MMMM yyyy")
|
||||||
|
}
|
||||||
|
Component.onCompleted: triggered()
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: parent.height * 0.10
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: clockLabel
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
color: "#f0f0f5"
|
||||||
|
font.pixelSize: 84
|
||||||
|
font.weight: Font.Light
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
id: dateLabel
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
color: "#a0a0b8"
|
||||||
|
font.pixelSize: 20
|
||||||
|
font.capitalization: Font.Capitalize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Carte de connexion --------------------------------------------------
|
||||||
|
Rectangle {
|
||||||
|
id: card
|
||||||
|
anchors.centerIn: parent
|
||||||
|
anchors.verticalCenterOffset: parent.height * 0.08
|
||||||
|
width: 400
|
||||||
|
height: cardContent.implicitHeight + 56
|
||||||
|
radius: 14
|
||||||
|
color: "#232338"
|
||||||
|
border.color: Qt.rgba(1, 1, 1, 0.10)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: cardContent
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width - 56
|
||||||
|
spacing: 14
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
text: config.greeting || "Bienvenue sur FWS"
|
||||||
|
color: "#f0f0f5"
|
||||||
|
font.pixelSize: 22
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle { width: 44; height: 3; radius: 1.5; color: accent
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter }
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: userField
|
||||||
|
width: parent.width
|
||||||
|
text: userModel.lastUser
|
||||||
|
placeholderText: "Utilisateur"
|
||||||
|
placeholderTextColor: "#7d7d95"
|
||||||
|
color: "#f0f0f5"
|
||||||
|
font.pixelSize: 15
|
||||||
|
leftPadding: 14
|
||||||
|
background: Rectangle {
|
||||||
|
radius: 8
|
||||||
|
color: fieldBg
|
||||||
|
border.color: userField.activeFocus ? accent : "transparent"
|
||||||
|
border.width: 2
|
||||||
|
}
|
||||||
|
onAccepted: passwordField.forceActiveFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: passwordField
|
||||||
|
width: parent.width
|
||||||
|
placeholderText: "Mot de passe"
|
||||||
|
placeholderTextColor: "#7d7d95"
|
||||||
|
color: "#f0f0f5"
|
||||||
|
font.pixelSize: 15
|
||||||
|
leftPadding: 14
|
||||||
|
echoMode: TextInput.Password
|
||||||
|
focus: true
|
||||||
|
background: Rectangle {
|
||||||
|
radius: 8
|
||||||
|
color: fieldBg
|
||||||
|
border.color: passwordField.activeFocus ? accent : "transparent"
|
||||||
|
border.width: 2
|
||||||
|
}
|
||||||
|
onAccepted: doLogin()
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
visible: keyboard.capsLock
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
text: "⚠ Verrouillage majuscules activé"
|
||||||
|
color: "#e6c07b"
|
||||||
|
font.pixelSize: 13
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: errorLabel
|
||||||
|
width: parent.width
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
color: "#e06c75"
|
||||||
|
font.pixelSize: 13
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: loginButton
|
||||||
|
width: parent.width
|
||||||
|
height: 44
|
||||||
|
onClicked: doLogin()
|
||||||
|
contentItem: Text {
|
||||||
|
text: "Se connecter"
|
||||||
|
color: "#ffffff"
|
||||||
|
font.pixelSize: 16
|
||||||
|
font.weight: Font.Medium
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
background: Rectangle {
|
||||||
|
radius: 8
|
||||||
|
color: loginButton.down ? Qt.darker(accent, 1.25)
|
||||||
|
: loginButton.hovered ? Qt.lighter(accent, 1.10)
|
||||||
|
: accent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ComboBox {
|
||||||
|
id: sessionBox
|
||||||
|
width: parent.width
|
||||||
|
model: sessionModel
|
||||||
|
currentIndex: sessionModel.lastIndex
|
||||||
|
textRole: "name"
|
||||||
|
font.pixelSize: 13
|
||||||
|
contentItem: Text {
|
||||||
|
leftPadding: 14
|
||||||
|
text: sessionBox.displayText
|
||||||
|
color: "#a0a0b8"
|
||||||
|
font.pixelSize: 13
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
background: Rectangle {
|
||||||
|
radius: 8
|
||||||
|
color: "transparent"
|
||||||
|
border.color: Qt.rgba(1, 1, 1, 0.15)
|
||||||
|
border.width: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Boutons d'alimentation ----------------------------------------------
|
||||||
|
// PAS de Repeater avec des fonctions dans un modèle JS : en Qt6 elles ne
|
||||||
|
// survivent pas toujours à la conversion QVariant (clic silencieusement
|
||||||
|
// inopérant). Deux boutons explicites via un composant inline.
|
||||||
|
component PowerButton: Button {
|
||||||
|
id: powerButton
|
||||||
|
property string label: ""
|
||||||
|
height: 36
|
||||||
|
contentItem: Text {
|
||||||
|
text: powerButton.label
|
||||||
|
color: powerButton.hovered ? "#f0f0f5" : "#a0a0b8"
|
||||||
|
font.pixelSize: 14
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
leftPadding: 14
|
||||||
|
rightPadding: 14
|
||||||
|
}
|
||||||
|
background: Rectangle {
|
||||||
|
radius: 8
|
||||||
|
color: powerButton.hovered ? Qt.rgba(1, 1, 1, 0.08) : "transparent"
|
||||||
|
border.color: Qt.rgba(1, 1, 1, 0.15)
|
||||||
|
border.width: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.margins: 24
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
PowerButton { label: "Redémarrer"; onClicked: sddm.reboot() }
|
||||||
|
PowerButton { label: "Éteindre"; onClicked: sddm.powerOff() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nom de la machine, discret, en bas à gauche.
|
||||||
|
Text {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.margins: 24
|
||||||
|
text: sddm.hostName
|
||||||
|
color: "#5d5d78"
|
||||||
|
font.pixelSize: 13
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
[SddmGreeterTheme]
|
||||||
|
Name=FWS
|
||||||
|
Description=Écran de connexion FWS
|
||||||
|
Author=FWS
|
||||||
|
Copyright=FWS
|
||||||
|
License=GPL-2.0-or-later
|
||||||
|
Type=sddm-theme
|
||||||
|
Version=1.0
|
||||||
|
Website=
|
||||||
|
Screenshot=
|
||||||
|
MainScript=Main.qml
|
||||||
|
ConfigFile=theme.conf
|
||||||
|
TranslationsDirectory=
|
||||||
|
Email=
|
||||||
|
Theme-Id=fws
|
||||||
|
Theme-API=2.0
|
||||||
|
QtVersion=6
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# Thème SDDM « fws » — valeurs par défaut, surchargées par theme.conf.user
|
||||||
|
# (écrit par le %post du kickstart : accent différent selon le bureau choisi).
|
||||||
|
[General]
|
||||||
|
accent=#3daee9
|
||||||
|
colorTop=#1a1a2e
|
||||||
|
colorBottom=#16213e
|
||||||
|
greeting=Bienvenue sur FWS
|
||||||
@@ -2,4 +2,4 @@ title FWS install medium (%ARCH%, UEFI)
|
|||||||
sort-key 01
|
sort-key 01
|
||||||
linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux
|
linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux
|
||||||
initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
|
initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
|
||||||
options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID%
|
options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% udev.event_timeout=45
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Mode graphique de secours : SANS KMS (nomodeset). Filet de sécurité
|
||||||
|
# standard pour les GPU/hyperviseurs dont le pilote KMS pose problème :
|
||||||
|
# X démarre alors sur le framebuffer VESA/EFI (plus lent mais fiable).
|
||||||
|
title FWS install medium (%ARCH%, UEFI, safe graphics)
|
||||||
|
sort-key 01b
|
||||||
|
linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux
|
||||||
|
initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
|
||||||
|
options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% nomodeset udev.event_timeout=45
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# « Réparer FWS » : boote le live SANS installateur et lance l'assistant de
|
||||||
|
# réparation en console (fws-recovery, via le hook fws.recovery=1 du
|
||||||
|
# .zprofile) : réinstaller GRUB, régénérer l'initramfs, réparer pacman,
|
||||||
|
# shell chroot — le point de restauration FWS.
|
||||||
|
title FWS - Reparer une installation (recovery)
|
||||||
|
sort-key 01d
|
||||||
|
linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux
|
||||||
|
initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
|
||||||
|
options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% udev.event_timeout=45 fws.recovery=1
|
||||||
@@ -2,4 +2,4 @@ title FWS install medium (%ARCH%, UEFI) with speech
|
|||||||
sort-key 02
|
sort-key 02
|
||||||
linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux
|
linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux
|
||||||
initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
|
initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
|
||||||
options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% accessibility=on
|
options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% accessibility=on udev.event_timeout=45
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user