diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ffb20e2..1e90632 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,15 +2,35 @@ Changelog ######### -[XX] - YYYY-MM-DD -================= +[0.6.0] - 2026-07-07 +==================== 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 ------- +- **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 releng profile. - Match network interfaces by their type, instead of name, in systemd-networkd configuration. @@ -21,12 +41,21 @@ Deprecated 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 services are currently pulling in ``network_online.target``. Removed ------- +- ``configs/baseline/syslinux/syslinux-linux.cfg`` (label ``arch`` stock, orphelin et sans cap udev). + [87] - 2025-10-29 ================= diff --git a/LICENSING.md b/LICENSING.md index 1098c13..742d82d 100644 --- a/LICENSING.md +++ b/LICENSING.md @@ -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` - [ ] **Dépôt rendu publiquement accessible** (sinon l'offre écrite n'est pas opposable) — action hors-code -- [ ] Branding Red Hat/Fedora retiré de l'UI Anaconda ; installateur marqué - « modifié » — à faire pendant la migration (config `product.d`) +- [x] Branding Red Hat/Fedora retiré de l'UI Anaconda : nom de produit **FWS** + 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 - [ ] 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. > **Ceci n'est pas un avis juridique.** Base de conformité de bonne foi au -> 2026-06-17. +> 2026-07-07. diff --git a/README.md b/README.md index 17396b9..9bb1eb5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 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**. @@ -9,14 +9,40 @@ Le projet est conçu pour être **construit depuis Windows via WSL** (les script ## Aperçu - **Base** : Arch Linux (rolling release) -- **Installateur** : en migration Calamares → Anaconda (Calamares retiré, Anaconda en préparation ; payload `liveimg` pour installer FWS et non Fedora) -- **Affichage live** : Xorg + Openbox (session graphique conservée pour l'installateur) -- **Bureaux installables** : XFCE, KDE Plasma, GNOME, Hyprland (choix à l'installation) -- **Boot** : BIOS (syslinux) + UEFI (systemd-boot) +- **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 + 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) +- **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) +- **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) --- +## 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 ### Côté Windows (machine hôte) @@ -146,9 +172,9 @@ Détails utiles : Depuis **Git Bash sur Windows** (à la racine du repo) : ```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 -# (Calamares retiré ; l'installateur Anaconda est un chantier en cours) ./setup-aur.sh # É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`). 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). -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`. 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 -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` -3. `.zprofile` → `startx` -4. `.xinitrc` → Openbox (session graphique du live) -5. **[Chantier C]** lancement de l'installateur Anaconda — pas encore câblé (Calamares retiré au commit 05ae9c3) +3. `.zprofile` → mode recovery si demandé, sinon `startx` +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. **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 baseline** : `configs/baseline/packages.x86_64` - **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) -- **Branding ISO** : `configs/releng/profiledef.sh` (`iso_name`, `iso_label`, `iso_publisher`) +- **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) +- **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. | | `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`. | -| É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. | ### 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 tail -f /tmp/fws-build/work/iso/arch/pkglist.x86_64.txt -# Dans l'ISO (autologin tty1) -journalctl -b +# Dans le LIVE (Ctrl+Alt+F2, login root) — pendant/après l'installation +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 -cat ~/.local/share/hyprland/hyprland.log +# Sur le système installé +cat /var/log/fws-post.log # trace complète du %post d'installation +journalctl -b # premier démarrage ``` --- diff --git a/TRADEMARKS.md b/TRADEMARKS.md index c8abf75..babcc53 100644 --- a/TRADEMARKS.md +++ b/TRADEMARKS.md @@ -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 `zzzz99-remove-custom-hooks-from-airootfs.hook`. - **`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 > 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). > 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. +> 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.** > 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. > *« 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 @@ -74,9 +86,19 @@ Engagements FWS : passer une version modifiée pour officielle) ; - **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 - « Installateur FWS (basé sur Anaconda) ». La configuration produit d'Anaconda - se règle via `product.d` / les données de branding — à auditer pendant la - migration. + « Installateur FWS (basé sur Anaconda) ». + +### ✅ 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. --- diff --git a/VERSION b/VERSION index 7d85683..a918a2a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.5.4 +0.6.0 diff --git a/configs/baseline/airootfs/root/.xinitrc b/configs/baseline/airootfs/root/.xinitrc index b7b050e..414a96b 100644 --- a/configs/baseline/airootfs/root/.xinitrc +++ b/configs/baseline/airootfs/root/.xinitrc @@ -20,10 +20,146 @@ _OB_PID=$! # 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. 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="Cette machine n'a que $(( _MEMKB / 1024 )) Mo de RAM.\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/\/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="Préparation de l'installateur FWS…\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 >"$LIVEINST_LOG" 2>&1 + # Bus D-Bus de SESSION : au démarrage (--liveinst), inhibit_screensaver() + # de pyanaconda se connecte au bus de session. S'il n'y en a AUCUN (notre + # startx root minimal n'en garantit pas), Gio lève une GLib.GError que + # pyanaconda n'attrape PAS (il n'attrape que DBusError) → crash immédiat + # (2e crash reproduit hors VM). dbus-run-session fournit un bus de session + # pour toute la durée de l'installateur ; avec un bus présent, l'absence du + # service ScreenSaver est attrapée (simple warning dans anaconda.log). + if command -v dbus-run-session >/dev/null 2>&1 && [ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ]; then + dbus-run-session -- liveinst >"$LIVEINST_LOG" 2>&1 + else + liveinst >"$LIVEINST_LOG" 2>&1 + fi rc=$? + # 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 # 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 @@ -66,6 +202,9 @@ if command -v liveinst >/dev/null 2>&1; then echo echo "########## %post --nochroot (/tmp/fws-post-nochroot.log) ##########" 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 \ /tmp/storage.log /tmp/dbus.log /tmp/packaging.log /tmp/X.log; do echo diff --git a/configs/baseline/airootfs/root/.zprofile b/configs/baseline/airootfs/root/.zprofile index 11af8b8..ce7430f 100644 --- a/configs/baseline/airootfs/root/.zprofile +++ b/configs/baseline/airootfs/root/.zprofile @@ -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. + +# 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 exec startx -- vt1 fi diff --git a/configs/baseline/airootfs/usr/lib/python3.14/site-packages/pyanaconda/ui/gui/spokes/fws_desktop.py b/configs/baseline/airootfs/usr/lib/python3.14/site-packages/pyanaconda/ui/gui/spokes/fws_desktop.py new file mode 100644 index 0000000..607e3cc --- /dev/null +++ b/configs/baseline/airootfs/usr/lib/python3.14/site-packages/pyanaconda/ui/gui/spokes/fws_desktop.py @@ -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 diff --git a/configs/baseline/airootfs/usr/lib/python3.14/site-packages/pyanaconda/ui/gui/spokes/fws_recap.py b/configs/baseline/airootfs/usr/lib/python3.14/site-packages/pyanaconda/ui/gui/spokes/fws_recap.py new file mode 100644 index 0000000..b2ceb8a --- /dev/null +++ b/configs/baseline/airootfs/usr/lib/python3.14/site-packages/pyanaconda/ui/gui/spokes/fws_recap.py @@ -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'où + le except large : dasbus lève des types variés selon la panne).""" + try: + return getter() + except Exception: # pylint: disable=broad-except + return default + + +def _capture(*argv): + """stdout d'une commande, chaîne vide si absente ou en échec.""" + try: + return util.execWithCapture(argv[0], list(argv[1:])) or "" + except (OSError, RuntimeError): + return "" + + +def _read_flag(path): + try: + with open(path) as fh: + return fh.read().strip() + except OSError: + return "" + + +def _config_lines(): + """Section « configuration choisie » : interroge les modules D-Bus + d'Anaconda (remplis par les spokes) + les fichiers /tmp des spokes FWS.""" + lines = [] + + lang = _safe(lambda: LOCALIZATION.get_proxy().Language, "") + if lang: + lines.append(f" • Langue : {lang}") + + layouts = _safe(lambda: list(LOCALIZATION.get_proxy().XLayouts), []) + if layouts: + lines.append(f" • Clavier (X11) : {', '.join(layouts)}") + + tz = _safe(lambda: TIMEZONE.get_proxy().Timezone, "") + if tz: + ntp = _safe(lambda: TIMEZONE.get_proxy().NTPEnabled, None) + suffix = {True: " — synchronisation NTP activée", + False: " — synchronisation NTP désactivée"}.get(ntp, "") + lines.append(f" • Fuseau horaire : {tz}{suffix}") + + users = _safe( + lambda: UserData.from_structure_list(USERS.get_proxy().Users), []) + if users: + names = ", ".join(u.name for u in users if u.name) + lines.append(f" • Utilisateur(s) : {names} (sudo via wheel)") + else: + lines.append(" • Utilisateur(s) : AUCUN — pensez-y avant de lancer !") + + root_locked = _safe(lambda: USERS.get_proxy().IsRootAccountLocked, None) + root_pw = _safe(lambda: USERS.get_proxy().IsRootPasswordSet, None) + if root_locked: + root_txt = "verrouillé (recommandé)" + elif root_pw: + root_txt = "actif, mot de passe défini" + elif root_pw is not None: + # Même règle que l'étape « Sécurité » du %post : un root resté sans + # mot de passe sera verrouillé en fin d'installation. + root_txt = "sans mot de passe → sera VERROUILLÉ en fin d'install" + else: + root_txt = "" + if root_txt: + lines.append(f" • Compte root : {root_txt}") + + # Hostname : écrit par le spoke Utilisateur (patch user.py du paquet + # anaconda). Même nettoyage / même défaut que le %post --nochroot. + hn = "".join(c for c in _read_flag("/tmp/fws-hostname") + if c.isalnum() or c in "._-") or "fws" + lines.append(f" • Nom de machine : {hn}") + + # Bureau : écrit par fws_desktop.apply() (sortie de la spoke). Absent = + # spoke jamais visitée = « cli », exactement le défaut du %post. + desk = _read_flag("/tmp/fws-desktop") or "cli" + lines.append(f" • Bureau : {_DESKTOP_LABELS.get(desk, desk)}") + + method = _safe( + lambda: STORAGE.get_proxy( + STORAGE.get_proxy().AppliedPartitioning).PartitioningMethod, "") + if method: + lines.append(" • Partitionnement : " + f"{_PARTITIONING_LABELS.get(method, method)}") + + return lines, desk, method + + +def _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 diff --git a/configs/baseline/airootfs/usr/local/share/fws/anaconda-summary-hub.glade b/configs/baseline/airootfs/usr/local/share/fws/anaconda-summary-hub.glade new file mode 100644 index 0000000..db733f0 --- /dev/null +++ b/configs/baseline/airootfs/usr/local/share/fws/anaconda-summary-hub.glade @@ -0,0 +1,136 @@ + + + + + + + + False + INSTALLATION SUMMARY + quitButton + continueButton + + + False + vertical + + + False + + + False + + + + + + + + + + + False + False + 0 + + + + + False + 0 + 12 + 6 + + + False + vertical + 6 + + + True + True + never + + + + + + True + True + 1 + + + + + + + True + True + 1 + + + + + True + False + 6 + 6 + 12 + end + + + _Quit + False + True + True + True + True + True + + + False + True + 0 + + + + + _Begin Installation + False + True + False + True + True + True + + + + False + True + end + 1 + + + + + False + True + 2 + + + + + + + INSTALLATION SUMMARY + + + + diff --git a/configs/baseline/airootfs/usr/share/anaconda/ui/spokes/fws_desktop.glade b/configs/baseline/airootfs/usr/share/anaconda/ui/spokes/fws_desktop.glade new file mode 100644 index 0000000..6d5fafb --- /dev/null +++ b/configs/baseline/airootfs/usr/share/anaconda/ui/spokes/fws_desktop.glade @@ -0,0 +1,168 @@ + + + + + + + False + True + True + DESKTOP ENVIRONMENT + + + + False + vertical + 6 + + + False + + + False + 6 + 6 + 6 + + + + + False + False + 0 + + + + + False + 12 + 0 + 0 + 0 + 48 + 24 + 24 + + + False + vertical + 8 + + + True + False + 0 + 12 + True + 60 + Choose the desktop environment to install. A web browser (Firefox) is installed automatically with any desktop. + + + False + True + 0 + + + + + Command line only (no desktop) + True + True + False + True + True + + + False + True + 1 + + + + + GNOME + True + True + False + True + radio_cli + + + False + True + 2 + + + + + KDE Plasma + True + True + False + True + radio_cli + + + False + True + 3 + + + + + Hyprland — Wayland (NOT VR compatible) + True + True + False + True + radio_cli + + + False + True + 4 + + + + + i3 — X11 (VR compatible: SteamVR, HTC Vive, Valve Index…) + True + True + False + True + radio_cli + + + False + True + 5 + + + + + XFCE + True + True + False + True + radio_cli + + + False + True + 6 + + + + + + + + + + + DESKTOP ENVIRONMENT + + + + diff --git a/configs/baseline/airootfs/usr/share/anaconda/ui/spokes/fws_recap.glade b/configs/baseline/airootfs/usr/share/anaconda/ui/spokes/fws_recap.glade new file mode 100644 index 0000000..2b8885a --- /dev/null +++ b/configs/baseline/airootfs/usr/share/anaconda/ui/spokes/fws_recap.glade @@ -0,0 +1,85 @@ + + + + + + + False + RÉCAPITULATIF AVANT INSTALLATION + + + False + vertical + + + False + + + False + + + + + + + + + + + False + False + 0 + + + + + False + 0 + 12 + 6 + + + False + vertical + 6 + + + True + True + in + + + True + True + False + False + True + 12 + 12 + 12 + 12 + word-char + + + + + True + True + 0 + + + + + + + True + True + 1 + + + + + + diff --git a/configs/baseline/efiboot/loader/entries/01-archiso-linux.conf b/configs/baseline/efiboot/loader/entries/01-archiso-linux.conf index 8c7964f..19928c5 100644 --- a/configs/baseline/efiboot/loader/entries/01-archiso-linux.conf +++ b/configs/baseline/efiboot/loader/entries/01-archiso-linux.conf @@ -1,4 +1,5 @@ -title Arch Linux (%ARCH%, UEFI) -linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux -initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img -options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% +title FWS install medium (%ARCH%, UEFI) +sort-key 01 +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 diff --git a/configs/baseline/packages.x86_64 b/configs/baseline/packages.x86_64 index 86cd99c..b9e025b 100644 --- a/configs/baseline/packages.x86_64 +++ b/configs/baseline/packages.x86_64 @@ -62,6 +62,7 @@ zsh grml-zsh-config bash zenity +zram-generator nano vim less @@ -79,6 +80,7 @@ pacman-contrib dmidecode usbutils pciutils +firewalld fastfetch htop @@ -103,6 +105,8 @@ open-vm-tools # --- FWS (fws-local) --- winboat-bin +# Gestionnaire D&D 5e (dés, personnages, grimoire, sessions, combat). +fws-dnd # --- Installateur Anaconda (fws-local) --- # 'anaconda' tire automatiquement anaconda-widgets + toutes ses dépendances diff --git a/configs/baseline/pacman.conf b/configs/baseline/pacman.conf index c9118e9..e7e6497 100644 --- a/configs/baseline/pacman.conf +++ b/configs/baseline/pacman.conf @@ -1,8 +1,3 @@ -[fws-local] -SigLevel = Optional TrustAll -Server = file:///mnt/c/Users/Murasaki/Documents/FWS/fws/local-repo - - # # /etc/pacman.conf # diff --git a/configs/baseline/profiledef.sh b/configs/baseline/profiledef.sh index f0ba624..1ab0b95 100644 --- a/configs/baseline/profiledef.sh +++ b/configs/baseline/profiledef.sh @@ -26,4 +26,19 @@ file_permissions=( ["/usr/local/bin/choose-mirror"]="0:0:755" ["/usr/local/bin/Installation_guide"]="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" ) diff --git a/configs/baseline/syslinux/syslinux-linux.cfg b/configs/baseline/syslinux/syslinux-linux.cfg deleted file mode 100644 index 10bb394..0000000 --- a/configs/baseline/syslinux/syslinux-linux.cfg +++ /dev/null @@ -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% diff --git a/configs/baseline/syslinux/syslinux.cfg b/configs/baseline/syslinux/syslinux.cfg index 561ac7c..cbda72f 100644 --- a/configs/baseline/syslinux/syslinux.cfg +++ b/configs/baseline/syslinux/syslinux.cfg @@ -1,9 +1,11 @@ -SERIAL 0 115200 -UI menu.c32 -MENU TITLE Arch Linux -MENU CLEAR +DEFAULT select -DEFAULT arch -TIMEOUT 30 +LABEL select +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 diff --git a/configs/releng/airootfs/etc/grub.d/42_fws-recovery b/configs/releng/airootfs/etc/grub.d/42_fws-recovery new file mode 100755 index 0000000..d0b300c --- /dev/null +++ b/configs/releng/airootfs/etc/grub.d/42_fws-recovery @@ -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 <{GLib.markup_escape_text(key)}") + 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"{name}") + 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( + "Inclus : installateur graphique · plusieurs bureaux " + "(GNOME, KDE, Hyprland, i3, XFCE) · Steam & Proton-GE · " + "réalité virtuelle (SteamVR) · winboat (applications Windows) · " + "pare-feu · réparation système intégrée.") + feats.set_line_wrap(True) + feats.set_max_width_chars(52) + feats.set_xalign(0) + box.pack_start(feats, False, False, 6) + + home = osr.get("HOME_URL") or "https://github.com/You-re-like-Windows-a-bitch/fws" + link = Gtk.LinkButton.new_with_label(home, "Site du projet") + box.pack_start(link, False, False, 0) + return box + + +def _system_tab(osr): + rows = [ + ("Système", osr.get("PRETTY_NAME", "FWS Linux")), + ("Build", osr.get("BUILD_ID", "rolling")), + ("Noyau", _cmd("uname", "-r") or "inconnu"), + ("Bureau", _desktop()), + ("Affichage", _session_type()), + ("Nom de machine", _cmd("hostname") or os.uname().nodename), + ("Processeur", _cpu()), + ("Mémoire", _mem()), + ("Carte(s) graphique(s)", "\n".join(_gpus())), + ("Disque(s)", "\n".join(_disks())), + ] + scroll = Gtk.ScrolledWindow() + scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + scroll.add(_grid(rows)) + return scroll + + +class AboutWindow(Gtk.Window): + def __init__(self): + super().__init__(title="À propos de FWS") + self.set_default_size(520, 460) + self.set_position(Gtk.WindowPosition.CENTER) + self.set_type_hint(Gdk.WindowTypeHint.DIALOG) + + osr = _osrelease() + notebook = Gtk.Notebook() + notebook.append_page(_project_tab(osr), Gtk.Label(label="Le projet FWS")) + notebook.append_page(_system_tab(osr), Gtk.Label(label="Système")) + self.add(notebook) + self.connect("destroy", Gtk.main_quit) + + +def main(): + if not Gtk.init_check()[0]: + return 1 + win = AboutWindow() + win.show_all() + Gtk.main() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/configs/releng/airootfs/usr/local/bin/fws-hello b/configs/releng/airootfs/usr/local/bin/fws-hello new file mode 100755 index 0000000..0891730 --- /dev/null +++ b/configs/releng/airootfs/usr/local/bin/fws-hello @@ -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("Bienvenue sur FWS !") + 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"{label}") + 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()) diff --git a/configs/releng/airootfs/usr/local/bin/fws-keys b/configs/releng/airootfs/usr/local/bin/fws-keys new file mode 100755 index 0000000..0e3dc5e --- /dev/null +++ b/configs/releng/airootfs/usr/local/bin/fws-keys @@ -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()) diff --git a/configs/releng/airootfs/usr/local/bin/fws-powermenu b/configs/releng/airootfs/usr/local/bin/fws-powermenu new file mode 100755 index 0000000..4002796 --- /dev/null +++ b/configs/releng/airootfs/usr/local/bin/fws-powermenu @@ -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()) diff --git a/configs/releng/airootfs/usr/local/bin/fws-proton-ge b/configs/releng/airootfs/usr/local/bin/fws-proton-ge new file mode 100755 index 0000000..8aa035a --- /dev/null +++ b/configs/releng/airootfs/usr/local/bin/fws-proton-ge @@ -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. " _ diff --git a/configs/releng/airootfs/usr/local/bin/fws-recovery b/configs/releng/airootfs/usr/local/bin/fws-recovery new file mode 100755 index 0000000..cecbb62 --- /dev/null +++ b/configs/releng/airootfs/usr/local/bin/fws-recovery @@ -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 → monte, répare, démonte ; sortie en continu +# action ∈ grub|initramfs|pacman|pacman-update|all +# shell → 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 |shell ]"; 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 diff --git a/configs/releng/airootfs/usr/local/bin/fws-recovery-gui b/configs/releng/airootfs/usr/local/bin/fws-recovery-gui new file mode 100755 index 0000000..cd46998 --- /dev/null +++ b/configs/releng/airootfs/usr/local/bin/fws-recovery-gui @@ -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 → répare (sortie streamée ici) +# fws-recovery shell → 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("Réparation du système FWS") + 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"{label}") + 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"{n} 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( + "Authentification annulée. Ferme puis relance" + " l'application pour réessayer.") + else: + self._status.set_markup( + "Aucune installation FWS détectée. 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"{label} 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("Terminé ✔ — autre réparation, ou redémarre.") + elif rc in (126, 127): + self._status.set_markup( + "Authentification annulée — rien n'a été modifié.") + else: + self._status.set_markup( + f"Échec (code {rc}) — 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()) diff --git a/configs/releng/airootfs/usr/local/bin/fws-recovery-session b/configs/releng/airootfs/usr/local/bin/fws-recovery-session new file mode 100755 index 0000000..56ec69d --- /dev/null +++ b/configs/releng/airootfs/usr/local/bin/fws-recovery-session @@ -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 diff --git a/configs/releng/airootfs/usr/local/bin/fws-setup-hardware b/configs/releng/airootfs/usr/local/bin/fws-setup-hardware new file mode 100755 index 0000000..526f631 --- /dev/null +++ b/configs/releng/airootfs/usr/local/bin/fws-setup-hardware @@ -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 +# ( = 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 diff --git a/configs/releng/airootfs/usr/local/bin/fws-update b/configs/releng/airootfs/usr/local/bin/fws-update new file mode 100755 index 0000000..c8acd4a --- /dev/null +++ b/configs/releng/airootfs/usr/local/bin/fws-update @@ -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. " _ diff --git a/configs/releng/airootfs/usr/local/bin/fws-updates-check b/configs/releng/airootfs/usr/local/bin/fws-updates-check new file mode 100755 index 0000000..860430a --- /dev/null +++ b/configs/releng/airootfs/usr/local/bin/fws-updates-check @@ -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 diff --git a/configs/releng/airootfs/usr/local/bin/fws-vr-setup b/configs/releng/airootfs/usr/local/bin/fws-vr-setup new file mode 100755 index 0000000..34beca4 --- /dev/null +++ b/configs/releng/airootfs/usr/local/bin/fws-vr-setup @@ -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. " _ diff --git a/configs/releng/airootfs/usr/local/share/applications/fws-recovery.desktop b/configs/releng/airootfs/usr/local/share/applications/fws-recovery.desktop new file mode 100644 index 0000000..dce1081 --- /dev/null +++ b/configs/releng/airootfs/usr/local/share/applications/fws-recovery.desktop @@ -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; diff --git a/configs/releng/airootfs/usr/local/share/applications/xfce4-about.desktop b/configs/releng/airootfs/usr/local/share/applications/xfce4-about.desktop new file mode 100644 index 0000000..74b6c36 --- /dev/null +++ b/configs/releng/airootfs/usr/local/share/applications/xfce4-about.desktop @@ -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 diff --git a/configs/releng/airootfs/usr/local/share/fws/hyprland.conf.tmpl b/configs/releng/airootfs/usr/local/share/fws/hyprland.conf.tmpl new file mode 100644 index 0000000..52487d8 --- /dev/null +++ b/configs/releng/airootfs/usr/local/share/fws/hyprland.conf.tmpl @@ -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 « » + 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)$ diff --git a/configs/releng/airootfs/usr/local/share/fws/i3-config b/configs/releng/airootfs/usr/local/share/fws/i3-config new file mode 100644 index 0000000..8b6ead8 --- /dev/null +++ b/configs/releng/airootfs/usr/local/share/fws/i3-config @@ -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 diff --git a/configs/releng/airootfs/usr/local/share/fws/os-release b/configs/releng/airootfs/usr/local/share/fws/os-release index 115f156..ed96250 100644 --- a/configs/releng/airootfs/usr/local/share/fws/os-release +++ b/configs/releng/airootfs/usr/local/share/fws/os-release @@ -3,7 +3,7 @@ PRETTY_NAME="FWS Linux" ID=fws ID_LIKE=arch BUILD_ID=rolling -VERSION_ID="1.0.0" +VERSION_ID="0.6.0" ANSI_COLOR="38;2;23;147;209" 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" diff --git a/configs/releng/airootfs/usr/local/share/fws/picom.conf b/configs/releng/airootfs/usr/local/share/fws/picom.conf new file mode 100644 index 0000000..6edbb89 --- /dev/null +++ b/configs/releng/airootfs/usr/local/share/fws/picom.conf @@ -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; diff --git a/configs/releng/airootfs/usr/local/share/fws/polybar-config.ini b/configs/releng/airootfs/usr/local/share/fws/polybar-config.ini new file mode 100644 index 0000000..8babbec --- /dev/null +++ b/configs/releng/airootfs/usr/local/share/fws/polybar-config.ini @@ -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-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 =