Merge branch 'Multi-Desktop' into Main

This commit is contained in:
2026-07-07 20:46:57 +02:00
73 changed files with 4386 additions and 93 deletions
+31 -2
View File
@@ -2,15 +2,35 @@
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.
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``).
- 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 +41,21 @@ 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.
- 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
View File
@@ -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.
+75 -20
View File
@@ -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
View File
@@ -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.
--- ---
+1 -1
View File
@@ -1 +1 @@
0.5.4 0.6.0
+139
View File
@@ -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
# 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 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
+14 -1
View File
@@ -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
@@ -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
@@ -0,0 +1,348 @@
# 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'
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 _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)")
return f"""\
══════════════════════════════════════════════════════════
FWS — RÉCAPITULATIF AVANT INSTALLATION
══════════════════════════════════════════════════════════
CONFIGURATION CHOISIE
{config_text}
DISQUES
{disk_warn}
{disk_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
@@ -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>
@@ -1,4 +1,5 @@
title Arch Linux (%ARCH%, UEFI) title FWS install medium (%ARCH%, UEFI)
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
+4
View File
@@ -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
-5
View File
@@ -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
# #
+15
View File
@@ -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%
+9 -7
View File
@@ -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
+40
View File
@@ -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
+2
View File
@@ -0,0 +1,2 @@
\S (\l)
@@ -1 +0,0 @@
/usr/lib/systemd/system/systemd-timesyncd.service
@@ -0,0 +1 @@
/usr/lib/systemd/system/systemd-timesyncd.service
@@ -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 +0,0 @@
/usr/lib/systemd/system/systemd-time-wait-sync.service
@@ -0,0 +1 @@
/usr/lib/systemd/system/systemd-time-wait-sync.service
@@ -1 +0,0 @@
/usr/lib/systemd/system/systemd-timesyncd.service
@@ -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
View File
@@ -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 &amp; 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())
+158
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+84
View File
@@ -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
View File
@@ -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. " _
+8
View File
@@ -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
View File
@@ -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,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,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
@@ -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
@@ -69,9 +72,21 @@ 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).
# ============================================================================ # ============================================================================
# %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
@@ -205,6 +220,44 @@ 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)"
@@ -212,21 +265,311 @@ 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 else
echo "[FWS] Bureau '$DESK' non installé (réseau ?) — à refaire après le 1er boot." 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
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.
@@ -234,9 +577,21 @@ 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
%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,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 « 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 @@
<?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
+7
View File
@@ -42,6 +42,7 @@ mdadm
parted parted
gptfdisk gptfdisk
fatresize fatresize
firewalld
testdisk testdisk
smartmontools smartmontools
nvme-cli nvme-cli
@@ -87,6 +88,8 @@ sof-firmware
# --- Xorg + Openbox (session graphique de l'installateur) --- # --- Xorg + Openbox (session graphique de l'installateur) ---
xorg-server xorg-server
zenity
zram-generator
xorg-xinit xorg-xinit
xorg-xrandr xorg-xrandr
xorg-xsetroot xorg-xsetroot
@@ -114,3 +117,7 @@ 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
# Terminal X : debug de l'installateur + entrée « Terminals » du menu Openbox.
xterm
# Gestionnaire D&D 5e (dés, personnages, grimoire, sessions, combat).
fws-dnd
+13
View File
@@ -24,4 +24,17 @@ file_permissions=(
["/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/load_policy"]="0:0:755"
["/usr/bin/setstatus"]="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"
) )
+31 -2
View File
@@ -6,7 +6,36 @@ ENDTEXT
MENU LABEL FWS install medium (%ARCH%, BIOS) MENU LABEL FWS install medium (%ARCH%, BIOS)
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
APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% udev.event_timeout=45
# 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).
# NB : la lenteur au démarrage vue en VM VMware (2-4 min d'écran bleu)
# N'EST PAS un gel graphique — c'est un worker udev tué après 180 s ;
# le boot normal finit par afficher l'installateur (splash ajouté).
LABEL fwsnomodeset
TEXT HELP
Boot FWS WITHOUT kernel mode setting (safe graphics).
Use this if the normal boot freezes on a black/blank screen
(e.g. VMware Workstation vmwgfx hang).
ENDTEXT
MENU LABEL FWS install medium (%ARCH%, BIOS, ^safe graphics)
LINUX /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux
INITRD /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% nomodeset udev.event_timeout=45
# « Réparer FWS » : live sans installateur → assistant de réparation console
# (fws-recovery via le hook fws.recovery=1 du .zprofile).
LABEL fwsrecovery
TEXT HELP
Repair an existing FWS installation (GRUB, initramfs, pacman)
without reinstalling. Boots to a guided console assistant.
ENDTEXT
MENU LABEL FWS - ^Repair installation (recovery)
LINUX /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux
INITRD /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% udev.event_timeout=45 fws.recovery=1
# Accessibility boot option # Accessibility boot option
LABEL fwsspeech LABEL fwsspeech
@@ -17,4 +46,4 @@ ENDTEXT
MENU LABEL FWS install medium (%ARCH%, BIOS) with ^speech MENU LABEL FWS install medium (%ARCH%, BIOS) with ^speech
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
APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% accessibility=on APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% accessibility=on udev.event_timeout=45
+1 -1
View File
@@ -1,6 +1,6 @@
INCLUDE archiso_head.cfg INCLUDE archiso_head.cfg
DEFAULT arch DEFAULT fws
TIMEOUT 150 TIMEOUT 150
INCLUDE archiso_sys-linux.cfg INCLUDE archiso_sys-linux.cfg
Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 14 KiB

+24
View File
@@ -6,6 +6,30 @@ Ce document décrit **l'environnement exact** sur lequel le build FWS fonctionne
--- ---
## 0. Build natif Arch Linux — la référence actuelle
Depuis juillet 2026, l'environnement de référence est un **Arch Linux natif**
(même machine, sans Windows/WSL) : `build.sh` et `setup-aur.sh` détectent le
cas et s'exécutent directement. Les ISO récentes du dépôt sont produites ainsi.
| Composant | Valeur de référence |
|---|---|
| OS | Arch Linux (rolling), kernel `7.x-archN` |
| Paquets requis | `base-devel`, `git`, `archiso`, `dos2unix` (`pacman -S --needed …`) |
| Droits | `sudo` (mkarchiso et pacstrap exigent root) |
| Réseau | requis (pacstrap télécharge les paquets ; `setup-aur.sh` clone/compile l'AUR) |
```bash
sudo ./setup-aur.sh # paquets AUR/anaconda → local-repo/
sudo ./build.sh # mkarchiso → out/FWS-<date>-x86_64.iso
```
Le reste de ce document décrit le chemin **Windows 11 + WSL Arch**, toujours
fonctionnel (bascule automatique depuis Git Bash) mais qui n'est plus la
référence.
---
## 1. Matériel (host Windows) ## 1. Matériel (host Windows)
| Composant | Valeur de référence | Minimum acceptable | Pourquoi | | Composant | Valeur de référence | Minimum acceptable | Pourquoi |
+19 -1
View File
@@ -5,7 +5,7 @@
# l'arborescence à la main plutôt que de patcher la chaîne distutils. # l'arborescence à la main plutôt que de patcher la chaîne distutils.
pkgname=python-meh pkgname=python-meh
pkgver=0.43 pkgver=0.43
pkgrel=1 pkgrel=2
pkgdesc="Python exception handling library (dépendance Anaconda)" pkgdesc="Python exception handling library (dépendance Anaconda)"
arch=('any') arch=('any')
url="https://github.com/rhinstaller/python-meh" url="https://github.com/rhinstaller/python-meh"
@@ -16,6 +16,24 @@ source=("$pkgname-$pkgver.tar.gz::https://github.com/rhinstaller/python-meh/arch
sha256sums=('cd50301794ab68a064715f1ace960f6de07aea809aa85f73087d44573c98be52') sha256sums=('cd50301794ab68a064715f1ace960f6de07aea809aa85f73087d44573c98be52')
_srcdir="python-meh-$_tag" _srcdir="python-meh-$_tag"
prepare() {
cd "$srcdir/$_srcdir"
# gettext.ldgettext a été SUPPRIMÉ en Python 3.11 : sans ce remplacement, le
# gestionnaire d'exceptions d'Anaconda plante LUI-MÊME (AttributeError) dès
# qu'il doit AFFICHER une erreur → traceback brut au lieu du dialogue meh,
# en GUI comme en TUI. dgettext est l'équivalent direct (ldgettext n'était
# que la variante bytes). Corrigé en amont après 0.43.
sed -i 's/gettext\.ldgettext(/gettext.dgettext(/' \
meh/handler.py meh/ui/text.py meh/ui/gui.py
# .pyc d'une extraction précédente (src/ n'est pas nettoyé par makepkg -f) :
# à jeter, sinon ils partiraient dans le paquet via cp -a.
find meh -type d -name __pycache__ -exec rm -rf {} +
if grep -rn --include='*.py' 'ldgettext' meh/; then
echo "ERREUR : ldgettext encore présent après patch" >&2
return 1
fi
}
package() { package() {
cd "$srcdir/$_srcdir" cd "$srcdir/$_srcdir"
local sitedir local sitedir
+22 -4
View File
@@ -232,11 +232,14 @@ repo_has() {
# Publie les .pkg d'un build vers local-repo en purgeant d'abord les anciennes # Publie les .pkg d'un build vers local-repo en purgeant d'abord les anciennes
# versions de CE paquet (+ son sous-paquet -debug). Le « -[0-9] » empêche # versions de CE paquet (+ son sous-paquet -debug). Le « -[0-9] » empêche
# « anaconda » d'attraper « anaconda-widgets » / « anaconda-debug ». # « anaconda » d'attraper « anaconda-widgets » / « anaconda-debug ».
# Prend la LISTE EXPLICITE des fichiers à publier (pas un glob sur le dossier :
# un .pkg d'un build précédent peut traîner dans $BUILD_DIR/<name> et serait
# republié par-dessus la purge).
publish_to_repo() { publish_to_repo() {
local name="$1" d="$2" local name="$1"; shift
rm -f "$LOCAL_REPO/$name"-[0-9]*.pkg.tar.* \ rm -f "$LOCAL_REPO/$name"-[0-9]*.pkg.tar.* \
"$LOCAL_REPO/$name"-debug-[0-9]*.pkg.tar.* 2>/dev/null || true "$LOCAL_REPO/$name"-debug-[0-9]*.pkg.tar.* 2>/dev/null || true
cp -f "$d"/*.pkg.tar.* "$LOCAL_REPO/" || error "Copie de $name vers local-repo échouée" cp -f "$@" "$LOCAL_REPO/" || error "Copie de $name vers local-repo échouée"
} }
# Construit OU saute <name> (source déjà déposée dans $BUILD_DIR/<name>). # Construit OU saute <name> (source déjà déposée dans $BUILD_DIR/<name>).
@@ -259,8 +262,23 @@ build_or_skip() {
chown -R "$BUILD_USER:$BUILD_USER" "$d" chown -R "$BUILD_USER:$BUILD_USER" "$d"
su - "$BUILD_USER" -c "cd '$d' && makepkg -sf --noconfirm" \ su - "$BUILD_USER" -c "cd '$d' && makepkg -sf --noconfirm" \
|| error "Build $name échoué" || error "Build $name échoué"
pacman -U --noconfirm "$d/"*.pkg.tar.* || error "Install de $name échouée" # N'installer/publier QUE les paquets produits par CE PKGBUILD dans SA
publish_to_repo "$name" "$d" # version courante (makepkg --packagelist), jamais un glob "$d/"* : après
# un bump de pkgrel, l'ancien .pkg traîne encore dans $d (BUILD_DIR n'est
# pas nettoyé entre les runs) → pacman -U recevait 2× le même nom de
# paquet (« erreur : cible répétée ») et la vieille version repartait
# dans local-repo. Le grep filtre tout bruit éventuel de su - (PAM…).
# NB : --packagelist liste aussi un -debug qui n'est PAS toujours produit
# (paquet pur python : rien à stripper) → ne garder que les fichiers
# réellement présents sur disque.
local -a pkgfiles=() existing=()
local f
mapfile -t pkgfiles < <(su - "$BUILD_USER" -c "cd '$d' && makepkg --packagelist" \
| grep -E '\.pkg\.tar\.')
for f in "${pkgfiles[@]}"; do [ -e "$f" ] && existing+=("$f"); done
[ "${#existing[@]}" -gt 0 ] || error "Aucun paquet produit trouvé pour $name"
pacman -U --noconfirm "${existing[@]}" || error "Install de $name échouée"
publish_to_repo "$name" "${existing[@]}"
success "$name build + install OK" success "$name build + install OK"
} }