199 Commits

Author SHA1 Message Date
nocode e313fc4dde docs(drivers/intel-vmd): add intel vmd/rst driver installation guide
Add README documentation for Intel VMD (Virtual Machine Device) and RST (Rapid Storage Technology) driver installation. This guide explains:

- Where to place Intel VMD driver files (iaStorVD.inf, .sys, .cat)
- Download location and extraction instructions
- How FWS automatically injects drivers into Windows deployments
- Notes on Windows 11 25H2 compatibility and AMD systems

This enables support for machines with Intel VMD/RST enabled in BIOS.
2026-07-09 15:58:00 +02:00
nocode 384a45f70e feat(fws-windows-deploy): add intel vmd driver injection support
Add support for injecting Intel VMD drivers during Windows deployment. The script now:

- Parses the intel_vmd configuration option from the state file
- Checks for Intel VMD drivers in /usr/local/share/fws/drivers/intel-vmd/
- Injects drivers via WinPE (dism) when requested and available
- Falls back to Windows inbox VMD driver with a warning if no driver found
- Forces WinPE path when VMD driver injection is needed
- Passes driver path to fws-windows-bcdfix for processing
2026-07-09 15:57:52 +02:00
nocode 89e1321ab7 feat(fws-windows-bcdfix): add Intel VMD driver injection support for WinPE
Add support for injecting Intel VMD drivers into WinPE environment via a new DRV parameter. This enables proper NVMe device detection during Windows PE boot and installation.

Changes:
- Accept optional 5th parameter (DRV) for driver directory path
- Embed driver files into WinPE boot image via drvload command
- Register driver in target Windows installation via DISM
- Copy driver directory into boot.wim fwsdrv folder for accessibility
2026-07-09 15:57:45 +02:00
nocode e2dd1abc18 feat(fws_windows): add intel vmd/rst support checkbox to windows deployment ui
Add a new GtkCheckButton for Intel VMD/RST support in the Anaconda Windows deployment UI. The checkbox is auto-detected and injects the iaStorVD driver into deployed Windows systems to enable boot on Intel machines with VMD/RST enabled in BIOS. The control has no effect on AMD systems. Update position index for subsequent UI elements.
2026-07-09 15:57:38 +02:00
nocode d050af8279 feat(fws_windows): add intel vmd device detection and support
Add support for detecting Intel VMD (Volume Management Device) controllers and enable/disable the VMD checkbox based on detection.

Changes:
- Add _intel_vmd_present() function to detect VMD controllers via lspci
- Initialize _vmd attribute based on controller detection
- Add VMD checkbox widget binding and state management
- Update checkbox sensitivity when enabling/disabling dual-boot
- Persist VMD state in configuration file
- Load VMD state from saved configuration
2026-07-09 15:57:33 +02:00
nocode 64763c3707 feat(fws_recap): add intel vmd/rst support indicator to windows dualboot summary
Display a notification in the Windows dualboot recap when Intel VMD/RST support is enabled, indicating that the driver has been injected during Windows boot.
2026-07-09 15:57:27 +02:00
nocode 2e2108d2b8 build(pyanaconda): update fws_windows python bytecode 2026-07-09 15:57:24 +02:00
nocode d4e479cc15 build(pyanaconda): update fws_recap pyc cache file 2026-07-09 15:56:58 +02:00
nocode d592b24858 test(vm): add test-vm.sh script for safe gameboot validation
Add test-vm.sh script for VM-based testing of gameboot and dual-boot Windows functionality.

The script sets up a QEMU VM with:
- UEFI firmware (OVMF) with persistent boot entries
- TPM 2.0 emulation (swtpm)
- 3 virtual NVMe disks (sparse qcow2 format)
- Optional Windows ISO mounting for deployment testing

Validates: ISO build/boot, dual-boot Windows spoke, conflict detection, fail-safe mechanisms, carving, wimlib apply, unattend injection, BCD configuration, Windows OOBE and FirstBoot, hibernation cycles.

Does not validate: GPU resume, real Secure Boot activation, VANGUARD (which rejects VMs).
2026-07-09 14:41:01 +02:00
nocode 9836925a37 docs(gameboot): add hardware validation checklist for gameboot dual-boot
Add comprehensive test checklist for validating gameboot Windows dual-boot functionality across 8 phases, from ISO build through hibernation, Secure Boot, Windows deployment, BCD configuration, and full gameboot cycle testing. Includes safety precautions, prerequisite checks, and recovery procedures.
2026-07-09 14:40:53 +02:00
nocode 47d59e51fe chore: add python cache file for fws_recap module 2026-07-09 14:40:46 +02:00
nocode 6a1ce74169 feat(fws_recap): add windows dual-boot configuration display section
Add _windows_dualboot_lines() function to read and parse /tmp/fws-windows configuration file. Display dual-boot status, disk allocation, ISO information, and conflict warnings when the selected Windows disk overlaps with FWS installation targets. Integrate the windows dual-boot section into the installation summary output.
2026-07-09 14:40:35 +02:00
nocode fa3b953d99 docs(windows): add comprehensive dual-boot windows installation plan
Add detailed technical specification for offline Windows 11 deployment via FWS spoke installer. Covers:

- Architecture overview: automatic offline WIM application without Windows Setup
- Security considerations: unified Secure Boot signing chain and protection against wrong-disk erasure
- Five-phase implementation roadmap: package dependencies, boot fixes, deployer tool, kickstart integration, validation
- Spoke amendments: safe defaults, disk occupation warnings, explicit confirmation for non-empty disks
- Safe partitioning with closed-fail guards against resolver failures
- Offline BCD construction via hivex with WinPE fallback
- Integration with FWS hibernation and gameboot-windows components
- Honest documentation of single irreducible manual action: firmware Setup Mode for Secure Boot activation
2026-07-08 23:58:11 +02:00
nocode d47686efd9 feat(polkit): add gameboot polkit policy for fws-gameboot execution
Add PolicyKit policy configuration that authorizes gameboot launchers (desktop entries) to execute /usr/local/bin/fws-gameboot via pkexec.

The policy allows active users (physically present) to switch to Windows without password prompt since the switch is non-destructive (FWS hibernates and restores the session on return). Remote or inactive sessions require admin authentication.
2026-07-08 23:58:03 +02:00
nocode fe9b30b745 feat(icons): add fws-gameboot scalable icon for gaming boot launcher
Add a new SVG icon for the FWS gameboot launcher application. The icon features a gradient blue background with stylized game controller handles and a play triangle, designed as a neutral generic gameboot icon without brand-specific logos.
2026-07-08 23:57:53 +02:00
nocode 7a4f36c916 feat(gameboot): add valorant gameboot desktop launcher
Add desktop application entry for launching Valorant via gameboot, which hibernates FWS and switches to bare-metal Windows. Includes French and English translations for name, description, and keywords.
2026-07-08 23:57:46 +02:00
nocode e3a0b988a5 feat(releng): add desktop entry for league of legends gameboot launcher
Add fws-play-lol.desktop file that provides a graphical launcher for switching to bare-metal Windows to play League of Legends with Vanguard anticheat support. Includes localized French and English labels and descriptions.
2026-07-08 23:57:40 +02:00
nocode 05a44af161 feat(gameboot-windows): add windows 11 offline unattend answer file template
Add unattend.offline.xml.tmpl for Windows 11 deployment via fws-windows-deploy.

This offline answer file handles specialize and oobeSystem passes after wimlib
image application. It configures:
- Computer name and locale settings
- BitLocker and OOBE registry tweaks
- Local admin gaming account with temporary auto-login
- FirstBoot PowerShell script execution

Tokens (__HOSTNAME__, __USER__, __DISPLAY__, __LOCALE__, __INPUT__,
__FIRSTBOOT__) are substituted by fws-windows-deploy via sed.
2026-07-08 23:57:28 +02:00
nocode 4bc9ccc1f7 feat(fws): add fws-bcd-write script for offline bcd generation
Add a new script that constructs a Windows BCD store offline from Linux using hivex.

The script copies the BCD-Template from Windows and patches string elements.
However, the binary device element encoding (GPT partition info) is not reliably
encoded in this implementation, so the script returns exit code 1 to trigger
fallback to the WinPE-based fws-windows-bcdfix which uses the official bcdboot tool.

Args: ESP_MOUNT WIN_MOUNT WINDOWS_PARTUUID DISK_GUID
Exit codes: 0 = BCD written safely; 1 = fallback to WinPE approach
2026-07-08 23:57:14 +02:00
nocode d1fa5143e8 feat(windows-deploy): add offline Windows 11 deployment script for dual-boot provisioning
Add fws-windows-deploy script for secure, offline Windows 11 deployment on dedicated disk.

Features:
- Fail-closed safety validation (disk, ISO, protected mounts)
- Partition carving with sgdisk (ESP, MSR, NTFS, swap)
- WIM image application via wimlib
- BCD bootloader setup with hivex fallback
- Offline BitLocker prevention
- Answer file injection and gameboot component
- FWS hibernation swap provisioning
- Comprehensive guards against accidental data loss

Script runs in %post --nochroot during ISO build, targets /mnt/sysroot.
2026-07-08 23:57:07 +02:00
nocode 39baaa67e6 feat(releng): add fws-windows-bcdfix script for windows bcd repair
Add new script to make Windows bootable by executing bcdboot in WinPE launched once via BootNext after FWS installation.

The script:
1. Stages bootable WinPE from ISO (bootmgr + BCD + boot.wim) to ESP
2. Injects automation (winpeshl.ini + fwspe.cmd) into boot.wim boot image
   - WinPE finds Windows drive and ESP partition
   - Executes bcdboot and reboots
3. Creates UEFI entry with one-shot BootNext to WinPE

Note: Not validated on real hardware. Recommended path: bcdboot writes correct BCD with one automatic WinPE reboot before returning to FWS.
2026-07-08 23:56:56 +02:00
nocode 976e24876d feat(ui): add windows gaming dual-boot installation spoke
Add fws_windows.glade UI definition for Windows 11 dual-boot configuration screen in Anaconda installer. Implements spoke interface with disk selection, partition size configuration, Windows ISO image selection, and validation feedback. Supports UEFI/TPM 2.0 requirements for kernel-anticheat game compatibility.
2026-07-08 23:56:48 +02:00
nocode ce6e9533c2 feat(gui): add dual-boot windows spoke for anaconda installer
Add new FWSWindowsSpoke to handle dual-boot Windows gaming configuration in the Anaconda GUI. This spoke allows users to:

- Enable/disable dual-boot Windows gaming mode
- Select a disk to dedicate to Windows
- Configure NTFS partition size (100-200 GiB default)
- Provide path to Windows 11 ISO

The spoke persists configuration to /tmp/fws-windows for processing by the kickstart script. It implements validation for disk selection, ISO path, and partition size constraints. Platform detection ensures the feature only appears on UEFI systems.
2026-07-08 23:56:41 +02:00
nocode 4d15dc2632 chore(pyanaconda): add python 3.14 compiled cache for fws_windows module 2026-07-08 23:56:34 +02:00
nocode e811cce88d docs(hibernate-swap-dualboot): update windows version references from 24H2 to 25H2
Update documentation to reflect Windows 11 25H2 as the current default version in three locations:
- Initial requirements section
- BitLocker risk mitigation (row 5)
- VBS/Hyper-V validation note (row 11)

These changes align the documentation with the latest Windows 11 release cycle.
2026-07-08 23:56:29 +02:00
nocode 0dc5329d90 chore(releng): add file permissions for windows deployment utilities
Add executable permissions configuration for three new Windows deployment related utilities:
- fws-windows-deploy
- fws-windows-bcdfix
- fws-bcd-write library script
2026-07-08 23:56:23 +02:00
nocode fd21293725 feat(anaconda): add secure boot setup and windows deployment support
Add support for Secure Boot preparation and Windows dual-boot deployment during Anaconda installation.

- Implement Secure Boot chain preparation using sbctl when Windows gaming dual-boot is selected
- Add Windows deployment phase via fws-windows-deploy in nochroot post-installation hook
- Include safety checks: literal file inspection to prevent code injection, fail-closed guards
- Deployment is non-blocking: failures don't prevent FWS from booting
- Add comprehensive comments explaining the Windows spoke integration and deployment flow
2026-07-08 23:56:17 +02:00
nocode e69c071969 fix(fws-secureboot-setup): update grub bootloader id to FWS and sign all firmware boot paths
Change GRUB bootloader identity from GRUB to FWS to match kickstart configuration.

Add signing of both primary (\EFI\FWS\grubx64.efi) and fallback removable
(\EFI\BOOT\BOOTX64.EFI) boot paths, as these are the paths actually loaded
by the firmware.

Extract GRUB_MODULES to a variable for reuse in both standard and removable
installation commands. Add objdump availability check before attempting SBAT
verification. Add sbat.csv existence check before reinstalling GRUB.
2026-07-08 23:56:10 +02:00
nocode 3b47ef30e7 feat(releng): add gameboot packages for windows gaming deployment
Add packages required for offline Windows gaming dual-boot deployment with Secure Boot support:
- wimlib: for applying Windows install.wim/.esd images
- hivex: for offline BCD and anti-BitLocker modifications
- sbctl/sbsigntools/mokutil: for Secure Boot management
- binutils: for objdump (.sbat) inspection

Note: nvidia driver is conditional and managed separately via fws-setup-hardware.
2026-07-08 23:56:00 +02:00
nocode e5bc082049 fix(fws-gameboot-bootfix): remove residual fws winpe boot entries
Clean up residual 'FWS WinPE' boot entries left over from Windows deployment fallback (bcdboot one-shot already consumed). These entries were cluttering the boot menu and needed to be removed during the boot fix process.
2026-07-08 23:55:48 +02:00
nocode 8d1b6bf23b fix(fws-gameboot): improve windows not found error messaging
Replace terse die message with user-friendly notification before the error.

When Windows Boot Manager entry is not found, first notify the user with
clear instructions about installing gaming Windows and checking UEFI boot
entries before terminating with the technical error message.
2026-07-08 23:55:41 +02:00
nocode 3f6fb08af6 chore(packages): add gameboot and secure boot related packages
Add wimlib, hivex, sbctl, sbsigntools, mokutil, and binutils packages required for dual-boot Windows gaming support and Secure Boot setup. These are universal tools used by fws-windows-deploy and fws-secureboot-setup scripts.
2026-07-08 23:55:21 +02:00
nocode c90e48f14f feat(gameboot-windows): add game installers configuration with valorant and lol
Add installers.json configuration file with official installation sources for Valorant and League of Legends. This file specifies download URLs from Riot's CDN and installation parameters for the game installer script.

Note: URLs are region-specific (EU default) and may require periodic verification as Riot frequently updates these resources.
2026-07-08 03:38:19 +02:00
nocode 226af87379 feat(gameboot-windows): add powershell script to install games from official sources
Add Install-Games.ps1 script that downloads and launches official game installers marked with autoinstall=true in installers.json.

The script:
- Downloads installers from official Riot servers (not redistributed by FWS)
- Supports silent installation via configurable arguments
- Handles TLS 1.2 compatibility for older .NET stacks
- Provides user feedback and error handling for download/launch failures
- Note: Valorant installation requires manual confirmation due to Vanguard kernel driver and mandatory restart
2026-07-08 03:38:12 +02:00
nocode 8e3bd2b53c feat(gameboot-windows): add FirstBoot-FwsGameboot orchestrator script
Add PowerShell script to orchestrate first-boot initialization on Windows gaming system.

The script executes on first boot only:
1. Install-FwsGameboot.ps1 for gaming account setup and auto-login
2. Install-Games.ps1 to download and install configured games from official sources

A marker file prevents re-execution on subsequent boots. Idempotent and safe to re-run.
2026-07-08 03:38:04 +02:00
nocode af7ed4e469 docs(gameboot-windows): expand README with first boot automation and game installer details
- Add FirstBoot-FwsGameboot.ps1 script for automated first boot setup
- Document Install-Games.ps1 for downloading official Riot game installers
- Add installers.json for managing Riot installer URLs
- Explain why games cannot be pre-bundled (licensing and Vanguard constraints)
- Provide two methods for triggering first boot: autounattend.xml integration and manual execution
- Clarify prerequisites: local admin account, no password, Secure Boot/TPM/VBS-HVCI
- Expand complete flow documentation with first boot step
- Add security reminders about BitLocker and physical access implications
- Reorganize sections for better clarity and structure
2026-07-08 03:37:44 +02:00
nocode a1be0e6d49 feat(gameboot-windows): add support for installers.json and Install-Games.ps1 deployment
Extend the file deployment loop to include installers.json and Install-Games.ps1 files. Add conditional path validation before copying to gracefully handle missing files instead of failing on non-existent paths.
2026-07-08 03:37:31 +02:00
nocode afe884b9e4 feat(gameboot-windows): add games configuration for fws gameboot
Add games.json configuration file with support for Riot client games (Valorant and League of Legends). This file defines launchable games with their process names and patchline information for fws-gameboot to-windows command.
2026-07-08 03:21:36 +02:00
nocode 2bee8ed359 docs(hibernate-swap-dualboot): add hibernation performance optimization tips
Add recommended GRUB parameters for faster hibernation and resume:
- hibernation.compressor=lz4 for faster compression with lower CPU overhead
- GRUB_TIMEOUT and GRUB_TIMEOUT_STYLE settings for faster boot experience

These optimizations are particularly beneficial for large swap images (60-90 GB).
2026-07-08 03:21:30 +02:00
nocode e6c0224db9 feat(gameboot-windows): implement automatic game launch with consume-once token mechanism
Replace hardcoded Valorant launch with flexible game resolution system:

- Add Get-RequestedGame() to read and consume launch.json tokens from all ESP partitions (dual-disk support)
- Add Resolve-RiotClient() to locate Riot Client via RiotClientInstalls.json canonical source instead of hardcoded paths
- Add Start-Game() to support multiple game types (riot, uri, exe) via games.json configuration
- Implement token consume-once pattern to prevent replay of stale launch requests
- Remove scheduled task management (FWS-Return-OnStart) - now handled purely via BootNext assertions
- Add re-assertion of BootNext=Windows after 25s delay to win race against ONSTART task
- Validate requested game against games.json with fallback to valorant
- Improve robustness: handle dual disks, missing tokens, unreadable tokens, and missing processes gracefully
- Update documentation to reflect new auto-launch architecture and admin requirement
2026-07-08 03:21:21 +02:00
nocode 057898524a docs(gameboot-windows): clarify workflow and add game registration documentation
Update README to reflect the complete automated game launch flow:
- Clarify that fws-play reads the requested game from launch.json on ESP
- Add games.json registration system allowing users to add custom games
- Document auto-login setup and password handling in installer
- Update workflow steps with auto-login and logon task details
- Add section explaining how to register new games
- Improve clarity on security considerations and boot fallback mechanism
- Refine status notes and technical details for better accuracy
2026-07-08 03:21:10 +02:00
nocode 11a531003e feat(gameboot-windows): add auto-login and game launch task to fws gameboot installer
Enhance Install-FwsGameboot.ps1 with automatic login and game launch capabilities:

- Add -Password parameter for optional auto-login password configuration
- Verify prerequisites: local admin account and password requirements
- Deploy games.json configuration file alongside scripts
- Implement auto-login via Windows registry (AutoAdminLogon settings)
- Add scheduled task for auto-launching fws-play at user logon
- Tighten file ACLs on deployment directory (SYSTEM/Admins full, Users read-only)
- Replace hibernation note with full reboot explanation
- Enhance documentation with parameter descriptions and prerequisite validation details
- Update user warnings to reflect auto-login kiosk security implications

Workflow: boot → auto-login → fws-play launch → game execution → return to FWS.
2026-07-08 03:21:02 +02:00
nocode 9b80effc77 feat(fws-gameboot): add game selection support for windows boot with --game option
Add ability to specify which game to launch on Windows via --game CLI argument.

- Introduce FWS_DEFAULT_GAME environment variable (defaults to 'valorant')
- Parse --game option in to-windows command to override default
- Sanitize game identifiers using lowercase and allowed chars [a-z0-9_-]
- Write atomic game token (launch.json) to ESP FAT partition for Windows to consume
- Purge stale tokens on write failure to prevent replay of old choices
- Update help text with new option and game identifier constraints
2026-07-08 03:20:50 +02:00
nocode 810a98fc34 chore(gameboot): add default game configuration option
Add FWS_DEFAULT_GAME configuration parameter to specify the default game launched when fws-gameboot to-windows is called without the --game argument. The game identifier must exist in the Windows games.json file (e.g., valorant, lol).
2026-07-08 03:20:28 +02:00
nocode 96da52f0e1 feat(releng): add pacman hook for secure boot signing after kernel updates
Add a new libalpm hook that automatically re-signs bootloader binaries with sbctl after kernel or GRUB updates. This ensures that Secure Boot remains functional after system updates that modify unsigned binaries.

The hook triggers on updates to kernel, GRUB, and EFI files, and runs sbctl sign-all to maintain signature consistency. This is idempotent and harmless if sbctl has no enrolled keys.
2026-07-08 02:39:31 +02:00
nocode beec38a899 feat(gameboot-windows): add Windows dual-boot orchestration scripts for FWS gaming
Add PowerShell scripts to manage dual-boot transitions between FWS and Windows 11 for gaming sessions with Valorant.

Includes:
- fws-return.ps1: Arms firmware boot-next (one-shot) to target OS via bcdedit
- fws-play.ps1: Orchestrates Valorant session with robust crash recovery
- Install-FwsGameboot.ps1: Deploys scripts, disables hibernation, creates ONSTART task
- README.md: Documentation on dual-boot workflow and prerequisites

Design principles:
- Minimal footprint, no Vanguard interference (no injection/kernel hooks)
- Permanent fallback: BootOrder[0]=FWS catches unmanaged reboots
- Robust locale-independent bcdedit parsing via EFI paths and GUIDs
- Crash-proof game session: ONSTART task disabled during play to prevent mid-match boot changes
2026-07-08 02:39:27 +02:00
nocode 3cb4132727 feat(secureboot): add fws-secureboot-setup script for secure boot configuration
Add a new script that prepares FWS to boot signed under Secure Boot without enabling kernel lockdown, preserving hibernation capability.

The script:
- Checks UEFI firmware availability
- Installs sbctl if needed
- Locates ESP and GRUB EFI binary
- Reinstalls GRUB with SBAT section if missing
- Creates and enrolls sbctl keys while preserving Microsoft keys
- Signs GRUB and available kernel images
- Provides instructions for manual firmware setup steps
- Enables automatic re-signing after kernel/GRUB updates via pacman hooks

This allows FWS to meet Vanguard's Secure Boot requirement without sacrificing hibernation support.
2026-07-08 02:39:17 +02:00
nocode 367233eae5 feat(gameboot): add fws-gameboot-bootfix systemd oneshot service
Add boot fix script that executes at every FWS startup to reassert BootOrder[0] = FWS.

This serves as a permanent safety net to handle cases where Windows Update or system crashes might reorder the EFI boot options, ensuring the system always boots into FWS when no explicit BootNext is set, preventing the system from becoming stuck in Windows.

The script is inactive on live systems via ConditionPathExists check.
2026-07-08 02:39:10 +02:00
nocode 3d91ef3ac5 feat(gameboot): add fws-gameboot dual-boot hibernation orchestrator script
Implement fws-gameboot, an orchestrator for seamless dual-boot between FWS and Windows
bare-metal using hibernate-swap mechanism. Enables playing kernel anticheat games
(e.g., Valorant/Vanguard) without persistent virtualization by hibernating FWS
to swap, setting BootNext to Windows UEFI one-shot, and rebooting. Upon Windows
shutdown, the kernel resumes the FWS session via resume= parameter.

Features:
- doctor/status subcommand: validates hibernation readiness (swap size, resume=
  parameter, VRAM usage, Secure Boot status)
- to-windows subcommand: hibernates FWS, unmounts NTFS filesystems, arms BootNext,
  and initiates hibernation; resumes FWS session on return from Windows
- Safety mechanisms: BootOrder[0] fallback to FWS via fws-gameboot-bootfix,
  permanent BootNext cleanup on resume, optional simple reboot fallback
- Configuration via /etc/fws/gameboot.conf (Windows boot entry number, swap margin,
  hibernation fallback mode)
- User notification via D-Bus notify-send for graphical feedback
- Comprehensive error handling and preflight validation

Requires validated hardware support for NVIDIA GPU hibernation/resume and firmware
BootNext semantics.
2026-07-08 02:39:04 +02:00
nocode bc84731290 feat(systemd): add fws-gameboot-bootfix service unit
Add a new systemd service that reasserts BootOrder=FWS and the Windows boot marker on gameboot systems. This service runs only on installed UEFI systems (not on live media) and is designed to be best-effort without delaying boot. It handles unavailable efivars gracefully by not failing the boot process.
2026-07-08 02:38:55 +02:00
nocode e5b36d6c07 feat(gameboot): add gameboot configuration file for hibernate-swap dual-boot
Add gameboot.conf configuration file for the FWS orchestrator. This file defines settings for dual-boot hibernation-swap mode including:
- Windows boot entry detection
- Hibernation preflight failure behavior (reboot or abort)
- Swap margin for hibernation image sizing

See docs/hibernate-swap-dualboot.md for details.
2026-07-08 02:38:51 +02:00
nocode 585c4691a1 docs(hibernate-swap-dualboot): clarify row 6 mitigation for systemctl hibernate hang
expand explanation of why timeout/watchdog approaches fail for S4 hangs since the process is frozen. document the actual viable mitigations: strict preflight validation, auto-recovery via reset, and BootOrder fallback. add note about testing and post-hang recovery documentation.
2026-07-08 02:38:46 +02:00
nocode 732082144a chore(releng): add file permissions for fws-gameboot and fws-secureboot-setup scripts
Add permission entries for three new executable scripts:
- fws-gameboot
- fws-gameboot-bootfix
- fws-secureboot-setup

All scripts are set with 0:0:755 permissions (owner:group:mode).
2026-07-08 02:38:30 +02:00
nocode ae8140ccff fix(anaconda): use firmware-dependent disklabel for bootable installs
Replace hardcoded msdos disklabel with firmware-aware configuration:
- UEFI systems now use GPT, allowing ESP creation for grub-install EFI
- BIOS systems continue using MBR/msdos where core.img fits in post-MBR space

This fixes the bug where forcing msdos on UEFI prevented ESP creation,
resulting in non-bootable installations. The disklabel is now dynamically
generated by a %pre script that detects firmware type and writes the
appropriate clearpart command to /tmp/fws-clearpart.ks for inclusion.

Clarifies that clearpart --all only affects GUI-selected disks, addressing
multi-disk scenarios where users may not want all disks wiped.
2026-07-08 02:03:54 +02:00
nocode f3ffcb62b8 docs: add hibernation and dual-boot setup guide for valorant with windows
Adds comprehensive documentation for FWS Hibernate-Swap Dual-Boot feature, enabling seamless switching between Arch Linux and Windows 11 bare-metal for Valorant gameplay.

Covers:
- System architecture and boot flow with UEFI BootNext
- Hardware prerequisites (RTX 5090, NVMe, TPM 2.0)
- Phased implementation roadmap from disk reconnaissance to end-to-end testing
- Secure Boot integration with sbctl while maintaining hibernation capability
- UEFI boot orchestration and failsafe mechanisms
- Installer integration for both new installations and existing systems
- GPU NVIDIA-specific hibernation handling
- Windows integration via PowerShell scripts for automatic boot target management
- Vanguard anti-cheat compatibility constraints
2026-07-08 02:03:36 +02:00
nocode 7730ac7b00 feat(build): add preflight package resolution and fws-dnd package
Add preflight package resolution step in build-offi.sh to validate all profile
packages against pacman repositories before mkarchiso execution. This prevents
'target not found' errors during ISO creation by catching missing packages early
with helpful error messages and recovery instructions.

Introduce fws-dnd package (D&D 5e manager for FWS with GTK4 interface) to the
local package build process in setup-aur.sh. Include PKGBUILD definition and
source tarball.

When package resolution fails, the build now exits with clear feedback pointing
users to run setup-aur.sh to build missing dependencies like fws-dnd.
2026-07-07 21:39:29 +02:00
nocode bee59a2fb3 docs(changelog): update changelog with new features and fixes
Add entries for:
- Machine name selection at installation
- Interactive build script with versioning and naming
- systemd-timesyncd explicit activation
- Keyboard layout derivation from full locale
- DNS in build container configuration
2026-07-07 21:07:10 +02:00
nocode 00d34dfc49 merge(Multi-Desktop)!: Add Multi DE on the installer 2026-07-07 21:04:19 +02:00
nocode ac810f2ee3 docs(trademarks): correct fws version number from 1.0 to 0.6 2026-07-07 13:57:22 +02:00
nocode 5592c57642 docs(readme): update fws version from 1.0 to 0.6 in installation header 2026-07-07 13:57:17 +02:00
nocode 8f6b8e7a95 docs(licensing): update fws version from 1.0 to 0.6 in installation header example 2026-07-07 13:57:12 +02:00
nocode 1826dbfead docs(changelog): update fws version from 1.0 to 0.6 2026-07-07 13:57:08 +02:00
nocode b7bf497cd6 feat(releng): enable systemd-timesyncd ntp synchronization
Add explicit activation of systemd-timesyncd service to ensure proper clock synchronization on installed systems. This prevents clock drift issues that can affect TLS and pacman operations, and meets user expectations for accurate system time.

The service is already present in the image but inherited symlinks from the live environment may be ordinary files that systemd ignores, so explicit enablement ensures correct behavior on all installations.
2026-07-07 13:57:04 +02:00
nocode ae70926ed1 chore(release): bump version to 0.6.0 2026-07-07 13:56:58 +02:00
nocode aac3c8b59e chore(.xinitrc): update anaconda default product version to 0.6 2026-07-07 13:56:54 +02:00
nocode 260547f6b6 chore(releng): convert systemd service files to symlinks
Convert three systemd service configuration files from regular files to symlinks pointing to their respective system libraries. This ensures that the services reference the actual systemd unit files from /usr/lib/systemd/system rather than maintaining duplicate copies.
2026-07-07 13:56:43 +02:00
nocode 730cfd697c chore: bump version to 0.6.0 2026-07-07 02:53:18 +02:00
nocode 292e3071c6 docs(changelog): release version 0.6.0
Update changelog with version 0.6.0 release notes dated 2026-07-07.

Documents major additions including:
- Anaconda installer 45 ported to Arch with Live OS payload
- FWS tooling suite (fws-hello, fws-update, fws-keys, etc.)
- System recovery functionality (fws-recovery-gui and engine)
- zram swap support and firewalld enablement
- Hyprland and i3 desktop parity

Includes changelog entries for changed items (FWS branding, boot entries, compression), fixed issues (splash watchdog, low RAM handling, profile synchronization), and removed items (syslinux config).
2026-07-07 02:53:14 +02:00
nocode 8a90ef4794 docs(environnement): add native arch linux build reference section
Document the native Arch Linux build environment that has been the reference since July 2026. This section explains that build.sh and setup-aur.sh now detect and execute directly on Arch Linux, replacing the previous Windows 11 + WSL setup as the primary reference.

The Windows + WSL path remains functional with automatic fallback from Git Bash but is no longer the reference environment.
2026-07-07 02:48:34 +02:00
nocode 9c7f9b46c5 docs(trademarks): document branding removals and anaconda customization
Add detailed documentation of trademark and branding changes:

- BIOS boot menu: replaced archiso syslinux splash with FWS original artwork
- Login banner: uses FWS PRETTY_NAME without kernel version suffix
- User-facing text: replaced references to Arch Linux/Fedora with generic descriptions
- Anaconda installer: configured to display "FWS 1.0" via environment variables
- Splash screen filtering: removes Fedora mentions from visible logs
- Technical logs: intentionally preserve upstream component identification for diagnostics

All changes maintain nominative fair use and preserve functional upstream tool identifiers.
2026-07-07 02:48:29 +02:00
nocode bf8b29c975 docs(readme): update fws architecture and recovery system documentation
- Add FWS branding details for Anaconda installer (ANACONDA_PRODUCT* exports via .xinitrc)
- Enhance splash screen description with real-time anaconda activity monitoring and auto-diagnostic
- Document memory requirements (4 GB recommended, 2 GB minimum with zram swap)
- Clarify boot menu entries and recovery accessibility from live, GRUB, and installed system
- Add fws-recovery as GTK application with bash engine and console fallback
- Document zram swap configuration for both live and installed system
- Detail recovery system integration: GRUB entry, systemd target, polkit policy, and desktop launcher
- Update syslinux splash and DEFAULT entry documentation
- Expand merge rule warning with syslinux-linux.cfg example and BIOS boot status
- Enhance troubleshooting section with detailed recovery procedures and known issues resolutions
- Add information about splash watchdog, RAM warnings, and diagnostic auto-generation
2026-07-07 02:48:22 +02:00
nocode 9c9891d98e docs(licensing): mark anaconda branding removal as completed and update date
Update the Anaconda branding removal checklist item from incomplete to completed, documenting the implementation details:
- Product name changed to FWS via ANACONDA_PRODUCTNAME and ANACONDA_PRODUCTVERSION variables
- Variables exported by live .xinitrc and read from FWS os-release
- Installation header displays "FWS 1.0 INSTALLATION"
- BIOS boot menu visual replaced with FWS splash screen
- Modified Anaconda version documented in LICENSING.md and TRADEMARKS.md

Also update the compliance date from 2026-06-17 to 2026-07-07.
2026-07-07 02:48:09 +02:00
nocode de05361b28 feat(releng): add polkit policy for fws-recovery privileged operations
Add PolicyKit authorization policy file to allow fws-recovery-gui to execute fws-recovery engine via pkexec with administrative privileges. The policy uses auth_admin_keep to require authentication only once per session.
2026-07-07 02:48:03 +02:00
nocode 9e3a02d96d feat(icons): add fws-recovery scalable application icon
Add a new scalable SVG icon for FWS recovery application featuring a medical cross symbol in FWS brand colors (blues) on a dark background. Icon is placed in the standard hicolor scalable apps directory.
2026-07-07 02:47:59 +02:00
nocode 881f771ed1 feat(releng): add fws-recovery desktop application launcher
Add desktop entry file for FWS Recovery application launcher in the installed OS. Provides bilingual (French/English) interface for system repair operations including GRUB, initramfs, and pacman restoration. The application runs as a regular user with privilege escalation handled via pkexec.
2026-07-07 02:47:54 +02:00
nocode bc08f59e74 feat(releng): add fws-recovery-session script for recovery mode
Add a new script that launches an X session with Openbox and the fws-recovery-gui application. This script is executed by startx during the FWS repair boot entry, providing a minimal graphical environment with Openbox as a fallback if the GUI crashes.
2026-07-07 02:47:48 +02:00
nocode e35b352a2a feat(recovery): add fws-recovery-gui GTK3 application for system repair
Implement a graphical interface for the FWS system recovery tool using GTK3.

Features:
- Scans for FWS installations on disk
- Provides action buttons for common repairs: GRUB reinstall, initramfs
  regeneration, pacman repair, and full system repair
- Displays real-time engine output in a log viewer
- Supports both root session (live mode) and regular user (pkexec elevation)
- Includes terminal access (chroot) for manual diagnostics
- Offers system reboot/poweroff controls

The GUI replaces Arch Linux branding with FWS in output for consistency
while preserving technical identifiers (package names, file paths) for
accurate diagnostics.
2026-07-07 02:47:43 +02:00
nocode 44b476e1d0 feat(zram): add zram-generator configuration for live environment
Enable compressed swap in RAM (zram) for the live environment to prevent memory saturation issues on systems with limited RAM. This configuration provides necessary headroom during Anaconda installation by leveraging zram's ~3:1 compression ratio on Python heaps. The configuration is automatically copied to installed systems via LiveOS payload, maintaining this safety net post-installation on modest hardware.
2026-07-07 02:47:35 +02:00
nocode f3feff4dc2 feat(systemd): add fws-recovery.target unit
Add new systemd target unit for FWS system recovery mode. This target is invoked via GRUB boot menu entry "Reparer FWS" and provides a multi-user environment with network access plus recovery session. The service includes proper dependency management to ensure getty@tty1 is properly handled.
2026-07-07 02:47:30 +02:00
nocode d00bcd4144 feat(releng): add fws-recovery-boot systemd service
Add systemd service unit for FWS system recovery boot mode. This service handles recovery session initialization on tty1 with fallback from GUI (via startx/openbox) to console assistant if graphical startup fails. The service is only activated via fws-recovery.target kernel command line parameter and does not interfere with normal boot processes.
2026-07-07 02:47:24 +02:00
nocode 34bf7909ad chore(releng): add issue file to airootfs
Add /etc/issue file to the releng airootfs configuration. This file provides pre-login banner information displayed to users connecting to the system.
2026-07-07 02:47:19 +02:00
nocode 50773635e8 feat(grub): add fws-recovery boot menu entry script
Add a new GRUB menu entry script that provides system recovery functionality.

The script generates a 'Réparer FWS (récupération du système)' menu entry that boots the system into fws-recovery.target with the fws.recovery=1 marker. This allows the recovery application to launch and perform system repairs via GUI or console fallback.

The script uses the fallback initramfs for maximum hardware compatibility during recovery operations, matching the UUID-based root detection logic of the standard 10_linux script.
2026-07-07 02:47:14 +02:00
nocode a75964e391 chore(releng): optimize syslinux splash image
reduce splash.png file size from 45400 to 14037 bytes through image optimization
2026-07-07 02:47:06 +02:00
nocode 7a44c4c291 chore(releng): change default syslinux boot option from arch to fws 2026-07-07 02:47:01 +02:00
nocode e25935fa75 chore(releng): add file permissions for recovery utilities and grub module
Add file permissions configuration for new recovery-related executables and grub module:
- fws-recovery-session executable
- fws-recovery-gui executable
- grub.d recovery module

All set with standard executable permissions (755) and root ownership.
2026-07-07 02:46:56 +02:00
nocode 40d49cb9e2 feat(releng): add zram-generator to x86_64 packages 2026-07-07 02:46:49 +02:00
nocode 3a437a8bfe docs(anaconda): add comment about fws-recovery boot entry and service
Document the FWS recovery boot entry generated by GRUB configuration and explain how the fws-recovery.target and fws-recovery-boot.service work together. Clarify that no explicit activation is needed as the service has no [Install] section and is pulled by the target through Requires, while the desktop launcher provides an alternative way to invoke recovery.
2026-07-07 02:46:43 +02:00
nocode 8e2fc2ec6a refactor(fws-recovery): restructure recovery script with subcommands and GUI support
Split monolithic script into modular functions and add subcommand interface:

- Add scan subcommand to list FWS installations
- Add repair subcommand with actions: grub, initramfs, pacman, pacman-update, all
- Add shell subcommand for chroot terminal access
- Implement support for operating on running system (no mount/chroot needed)
- Extract detection and mounting logic into reusable functions
- Add in_target() helper to execute commands in target (chroot or direct)
- Improve mkinitcpio warning documentation
- Maintain backward-compatible TUI mode when no args provided
- Enable GUI (fws-recovery-gui) to pilot repairs via subcommands
2026-07-07 02:46:30 +02:00
nocode c7221247f2 fix(fws-about): update distribution description to be more generic
Change the description from "Une distribution Arch Linux" to "Une distribution Linux" to better reflect the project's identity and avoid limiting it to Arch Linux specifically.
2026-07-07 02:46:20 +02:00
nocode 57f86ef5f7 chore(syslinux): remove legacy syslinux linux configuration file
Remove the outdated syslinux-linux.cfg configuration file from the baseline syslinux directory. This file is no longer needed as the project has moved away from syslinux-based boot configurations.
2026-07-07 02:46:11 +02:00
nocode a9ba4be111 chore(baseline): add file permissions for recovery tools and grub config
Add file permission entries for new recovery-related executables and grub configuration:
- /usr/local/bin/fws-recovery-session
- /usr/local/bin/fws-recovery-gui
- /etc/grub.d/42_fws-recovery

All entries set to 0:0:755 (root owner, executable).
2026-07-07 02:46:01 +02:00
nocode 6f5fc95e82 feat(baseline): add zram-generator package 2026-07-07 02:45:52 +02:00
nocode 680ee39e94 fix(baseline): update recovery boot flow to use X with gtk application
Changed recovery mode (fws.recovery=1) to start X server with the GTK recovery application (fws-recovery-session) instead of directly launching the console assistant. This provides a graphical interface when possible, with a fallback to the console assistant if X fails to start.

Also updated comments to clarify the different recovery mechanisms on the live ISO versus the installed system.
2026-07-07 02:45:47 +02:00
nocode ec442193f5 feat(xinitrc): enhance anaconda startup splash with better diagnostics and feedback
Add memory check warning for systems with less than 2GB RAM.

Replace simple pulsing splash with active monitoring that displays:
- Real-time anaconda activity from log files
- Elapsed time and current operation
- Stall detection after 4 minutes of inactivity
- Automatic diagnostic report collection (journal, dmesg, udev status)
- Detection of GUI readiness to auto-hide splash

Add anaconda branding environment variables (ANACONDA_PRODUCTNAME,
ANACONDA_PRODUCTVERSION, ANACONDA_ISFINAL) to show "FWS" instead of
"Fedora" in installer headers and logs.

Improve splash cleanup to handle both feeder process and optional
diagnostic dialogs.
2026-07-07 02:45:32 +02:00
nocode a0fc2c1019 chore(releng): add zenity, xterm, and fws-dnd to installer packages
Add zenity for graphical dialogs in the installer, xterm for terminal debugging and Openbox menu entry, and fws-dnd for D&D 5e management functionality (dice, characters, grimoire, sessions, combat).
2026-07-06 18:40:42 +02:00
nocode 9e62cedff0 feat(anaconda): add desktop installation retry mechanism for network failures
Implement a robust desktop installation retry system that handles transient network issues during the Arch installation process.

Changes:
- Create fws-desktop-retry script in /usr/local/bin for manual recovery after installation
- Add DNS resolution wait loop (up to 10 attempts with 3-second intervals)
- Implement pacman retry logic with up to 3 attempts (15-second delay between retries)
- Generate /var/lib/fws/desktop-failed marker file on installation failure
- Create informative MOTD message guiding users to retry desktop installation
- Preserve FWS-specific MOTD when it contains retry instructions

This allows users to recover from network/mirror failures without reinstalling the entire system.
2026-07-06 18:40:37 +02:00
nocode bfd43ff41a refactor(baseline syslinux)!: restructure syslinux configuration for multi-boot selection
Replace simple serial-based menu with system detection logic using whichsys.c32 to automatically select between PXE and direct system boot. This enables dynamic boot method selection while maintaining support for both boot scenarios.

BREAKING CHANGE: Changes syslinux.cfg structure from single menu-based boot to conditional boot selection. Systems relying on the previous serial menu interface and default 'arch' label will need to adapt to the new whichsys-based selection mechanism.
2026-07-06 18:40:32 +02:00
nocode 6fb48bd3a8 chore(pacman): remove fws-local repository configuration
Remove the custom fws-local repository section from the baseline pacman configuration. This local repository reference is no longer needed in the baseline setup.
2026-07-06 18:40:27 +02:00
nocode 61fa81dd24 feat(baseline): add firewalld to baseline packages
Include firewalld in the baseline x86_64 package list for system firewall management capabilities.
2026-07-06 18:40:23 +02:00
nocode 45ff906097 chore(efiboot): update efifi boot loader entry configuration
Update the UEFI boot loader entry to reflect FWS install medium branding, add sort-key directive for boot order control, align configuration formatting, and increase udev event timeout to 45 seconds for improved hardware compatibility during boot.
2026-07-06 18:40:19 +02:00
nocode 2daac4a455 docs(readme): clarify baseline and releng config merge rules
Expand the ISO branding documentation to include a detailed warning about the build process where baseline configs overwrite releng configs. Add a list of critical files that must be kept in sync between both profiles (profiledef.sh, packages.x86_64, pacman.conf, efiboot/loader/entries/01-archiso-linux.conf, syslinux/syslinux.cfg) and include a verification command to detect file divergences. This prevents silent data loss from config mismatches during the build.
2026-07-06 18:40:10 +02:00
nocode d65f08cfad fix(i3-config): disable picom in virtual machines to prevent rendering artifacts
picom's GPU compositing causes GTK window repaint issues in virtual environments (vmwgfx/llvmpipe), resulting in black content and visual artifacts. Use systemd-detect-virt to skip picom execution on VMs while keeping it enabled on real hardware.

The rounded corners feature is sacrificed in VM environments to maintain stable window rendering.
2026-07-04 14:10:27 +02:00
nocode 7268e7e23d docs(readme): document anaconda integration and fws tooling comprehensive updates
Update README to reflect completed Anaconda 45 integration on Arch Linux, replacing Calamares. Document:

- Desktop choices (GNOME, KDE Plasma, XFCE, Hyprland, i3) with VR compatibility matrix
- Installation flow: Anaconda hub with FWS spokes, LiveOS payload, recap page before write
- fws-* tooling ecosystem: fws-hello, fws-update, fws-keys, fws-powermenu, fws-about, fws-vr-setup, fws-proton-ge, fws-recovery
- Hyprland & i3 visual parity: Catppuccin Mocha theme, waybar/polybar, wofi/rofi, custom SDDM theme
- Boot sequence: menu entries (normal, safe graphics, recovery, speech) → Openbox live → Anaconda → recap → installation
- Build process: incremental AUR + local PKGBUILD compilation (anaconda stack)
- Expanded troubleshooting: VM rendering issues, SteamVR, Bluetooth, recovery mode, BIOS/UEFI boot
- Log locations for live and post-install debugging
- Clarified file locations: kickstart, spokes, configs, tools, branding
2026-07-04 14:10:15 +02:00
nocode d4bef2fe91 chore(releng): add fws-about desktop entry file
Add desktop entry file for the FWS About application with localized names and metadata for system integration.
2026-07-04 13:43:54 +02:00
nocode a1255eb5c6 build(releng): hide xfce4-about application from menus
Add desktop entry file to hide the xfce4-about application from application menus. Since xfce4-about is a dependency of Thunar through libxfce4ui and does not require a full XFCE session, this entry prevents it from appearing in application launchers by leveraging XDG_DATA_DIRS priority where /usr/local/share takes precedence over /usr/share.
2026-07-04 13:43:50 +02:00
nocode cbdb386ba0 feat(powermenu): add fws-powermenu universal power menu utility
Add a Python-based power menu script that works with both Hyprland (Wayland) and i3 (X11) desktop environments. Supports lock, logout, suspend, reboot, and poweroff actions with appropriate display server backends (wofi for Hyprland, rofi for i3).
2026-07-04 13:43:45 +02:00
nocode 4df4b2bff3 feat(fws-about): add fws-about dialog application for system information
Add a new GTK3-based Python application that displays an "About FWS" dialog with two tabs:

- "Le projet FWS" tab: shows project name, description, features, and home URL
- "Système" tab: displays system information including OS, kernel, desktop environment, hardware (CPU, memory, GPUs, disks)

The application detects system information from /proc, lsblk, lspci, and environment variables. It replaces the default Xfce About dialog with FWS branding.
2026-07-04 13:43:39 +02:00
nocode 73cf60e876 chore(releng): add fws-powermenu and fws-about to file permissions 2026-07-04 13:43:32 +02:00
nocode 07ff62af55 chore(anaconda): replace thunar with dolphin and gvfs with udisks2 in desktop environments
Update package lists for Hyprland and i3 desktop environments:
- Replace thunar file manager with dolphin (KDE's file manager)
- Add kio-extras for dolphin functionality
- Replace gvfs with udisks2 for USB mounting
- Update comments to reflect new mount mechanism

This change improves consistency and provides better integration for the respective desktop environments.
2026-07-04 13:43:29 +02:00
nocode 22a95badcf style(waybar): add custom power button styling
Add styling for custom power button with Catppuccin color scheme.

- Default color: #f38ba8 (rouge doux)
- Hover color: #ff6b81 (lighter red)
2026-07-04 13:42:44 +02:00
nocode a1adefe890 feat(waybar): add power menu module to waybar config
Add custom power menu module to waybar configuration with power icon and SUPER+X keyboard shortcut that launches fws-powermenu utility.
2026-07-04 13:42:41 +02:00
nocode 1f465bf647 feat(polybar): add power module to polybar configuration
Add a new power module to the polybar configuration that displays a power icon and triggers the fws-powermenu command on click. The module is styled with red foreground color and integrated into the modules-right bar.
2026-07-04 13:42:37 +02:00
nocode ba2dc4637e chore(i3-config): update file manager and add new keybindings and window rules
- Change default file manager from thunar to dolphin
- Add $mod+x keybinding for fws-powermenu (power menu)
- Add floating window rules for fws-hello and fws-about dialogs
2026-07-04 13:42:32 +02:00
nocode ccdbdf4adf chore(hyprland): update hyprland config with dolphin, powermenu binding, and window rules
Replace thunar file manager with dolphin for consistency.
Add power menu keybinding (Super+X) for shutdown/restart operations.
Add floating window rules for fws-hello and fws-about applications.
2026-07-04 13:42:28 +02:00
nocode 1c74b59459 fix(fws-hello): set window type hint to dialog and prevent resizing
Import Gdk and GLib modules to properly configure window behavior.

Set deterministic WM_CLASS using GLib.set_prgname() so that i3/Hyprland
window rules targeting "fws-hello" floating window behavior work correctly.

Set window type hint to DIALOG and disable resizing to ensure the window
remains floating with its natural size instead of being stretched across
the entire workspace, which would result in unreadable layout.
2026-07-04 13:42:24 +02:00
nocode 1cb48ce4e7 chore(baseline): add file permissions for fws-powermenu and fws-about scripts 2026-07-04 13:42:11 +02:00
nocode 17f9c79983 feat(rofi): add rofi configuration for fws application menu
Add rofi configuration file with FWS styling matching the Hyprland wofi look. Includes dark background, blue borders and selection, icon support, and Catppuccin Mocha color scheme.
2026-07-04 13:15:45 +02:00
nocode 3cdfdcfaeb feat(i3-config): add rofi configuration for i3 desktop environment
Install rofi configuration file (rofi-config.rasi) for i3 desktop to provide an application menu with FWS styling, equivalent to wofi in Hyprland. Configure rofi for both skeleton and existing user home directories with proper ownership settings.
2026-07-04 13:15:33 +02:00
nocode 9e59be79c3 feat(efiboot): add FWS recovery boot entry configuration
Add a new EFI boot loader entry for FWS recovery mode that boots the live system without the installer and launches the recovery assistant console. This enables system repair functionality including GRUB reinstallation, initramfs regeneration, and pacman repair.
2026-07-04 12:57:05 +02:00
nocode 080a77c5e3 feat(releng): add fws-updates-check script for update notifications
Add a new shell script that checks for available package updates using checkupdates from pacman-contrib. The script outputs the number of available updates with an icon for use in waybar/polybar status bars. Silent output when no updates are available allows the bar module to hide.
2026-07-04 12:57:01 +02:00
nocode 7e40a565cc feat(fws-update): add system update utility script
Add fws-update script to provide a one-command system update solution accessible from taskbar, fws-hello menu, or terminal.

The script performs:
- System update via pacman -Syu
- Package cache cleanup (retains 2 latest versions)
- Orphaned package detection and optional removal
- Flatpak updates if available

Requires root privileges and prompts for user confirmation on orphan removal.
2026-07-04 12:56:57 +02:00
nocode 338f6b87ee feat(recovery): add fws-recovery script for system repair from live environment
Add a comprehensive system recovery tool that:
- Detects FWS installations on disks including LVM volumes
- Mounts target root filesystem with /boot and ESP partitions
- Provides interactive menu for common repair operations:
  - GRUB reinstallation (UEFI and BIOS support)
  - Initramfs regeneration via mkinitcpio
  - Pacman database repair and system updates
  - Interactive chroot shell for manual intervention

The script handles automatic LVM activation, filesystem detection via os-release, and proper cleanup on exit.
2026-07-04 12:56:51 +02:00
nocode 4f4afec6b2 feat(releng): add fws-proton-ge script for proton-ge installation
Add a user-friendly bash script that automates downloading and installing
Proton-GE (GloriousEggroll) for Steam. The script fetches the latest release
from GitHub, verifies integrity with SHA512 checksums, and installs it to the
user's Steam compatibility tools directory. Includes error handling and user
guidance for Steam configuration.
2026-07-04 12:56:44 +02:00
nocode 640aa15af8 feat(releng): add fws-hello welcome window script
Add a new GTK3-based welcome window script that displays on first boot after installation. The script provides quick access to system setup tasks including system update, multimedia codecs installation, Proton-GE setup, VR configuration, and keyboard shortcuts (on Hyprland/i3). Users can opt to hide the window on subsequent boots via a checkbox and flag file.
2026-07-04 12:56:39 +02:00
nocode 73f73ff17e feat(releng): add fws-hello desktop autostart entry
Add desktop entry file to automatically launch fws-hello welcome window on first startup. This provides a user-friendly greeting interface when the system boots.
2026-07-04 12:56:33 +02:00
nocode 6c8e7c0f78 feat(syslinux): add fws recovery boot option to syslinux config
Add a new boot menu entry for FWS installation recovery that boots into a guided console assistant without reinstalling. The recovery mode is triggered via the fws.recovery=1 kernel parameter.
2026-07-04 12:56:28 +02:00
nocode 5762f59d95 chore(releng): add new executable file permissions to profiledef
Add permission entries for five new utility scripts:
- fws-hello
- fws-update
- fws-updates-check
- fws-proton-ge
- fws-recovery

All are configured with standard permissions (0:0:755).
2026-07-04 12:56:24 +02:00
nocode affba77349 feat(releng): add firewalld to x86_64 packages 2026-07-04 12:56:17 +02:00
nocode 87977ce8d7 build(releng): add python-gobject, pacman-contrib packages and enable firewalld
- Add python-gobject for fws-hello welcome window (GTK3 comes with Firefox)
- Add pacman-contrib for checkupdates/paccache (update indicator and fws-update)
- Enable firewalld.service by default with public zone (blocks inbound, allows outbound)
- firewalld is already in the base image and can be activated offline
- GUI included in firewall-config package; KDE has additional plasma-firewall
2026-07-04 12:56:13 +02:00
nocode b5f00d8dd2 feat(waybar): add custom updates module to waybar configuration
Add a new custom/updates module to the waybar right-side modules that displays available system updates. The module executes fws-updates-check every 30 minutes (1800 seconds) and allows users to install updates by clicking, which launches a kitty terminal running fws-update. Tooltip text is displayed in French.
2026-07-04 12:56:08 +02:00
nocode 349cc28d7d feat(polybar): add updates module to polybar configuration
Add a new custom script module to check for system updates in the polybar status bar. The module runs fws-updates-check every 30 minutes and allows users to trigger updates with a left click to open the update utility in kitty terminal.
2026-07-04 12:56:03 +02:00
nocode fc1bc218a4 feat(i3-config): add fws-hello welcome window on startup
Add execution of fws-hello with --autostart flag to display a welcome window on first boot only. The window will automatically hide after the initial startup.
2026-07-04 12:55:56 +02:00
nocode d399fc5fa6 feat(hyprland): add fws-hello welcome window on first startup
Add execution of fws-hello with --autostart flag to display a welcome window on first launch. The window will be automatically hidden on subsequent startups.
2026-07-04 12:55:50 +02:00
nocode 4d36e86f51 feat(hardware-setup): add mangohud gaming performance overlay packages
Add mangohud and lib32-mangohud packages to the desktop environment setup for gaming performance monitoring. Mangohud provides an in-game FPS and performance metrics overlay that can be used with Steam games via '%command%' prefix.
2026-07-04 12:55:45 +02:00
nocode a0f269cf90 chore(baseline): add file permissions for new fws utility scripts
Add file permission entries for five new utility scripts in the fws toolkit:
- fws-hello
- fws-update
- fws-updates-check
- fws-proton-ge
- fws-recovery

All scripts are configured with owner:group 0:0 and executable permission 755.
2026-07-04 12:55:38 +02:00
nocode 7831b537d7 feat(baseline): add recovery mode boot option to zprofile
Add support for recovery mode that can be triggered via the fws.recovery=1 kernel parameter. When recovery mode is enabled, the system launches the fws-recovery console assistant directly instead of starting the graphical environment.

This allows users to access repair tools without needing to start X server, improving system recovery capabilities.
2026-07-04 12:55:25 +02:00
nocode 2c5f08330a refactor(fws-keys): make keyboard shortcuts tool universal for hyprland and i3
Rename fws-hypr-keys to fws-keys and extend it to support both Hyprland and i3 desktop environments. The tool now detects the session type and uses appropriate methods:

- Hyprland: queries hyprctl binds for real loaded bindings (wofi dmenu)
- i3: parses ~/.config/i3/config with variable resolution (rofi dmenu)

Update all references in configs and templates. Add VM detection warning in fws-vr-setup to prevent VR attempts in virtual machines where GPU/USB access is unavailable.

Add ttf-liberation font dependency for Steam UI stability on minimal installations and improve error guidance for Steam crashes.
2026-07-04 03:59:09 +02:00
nocode a63021cde7 feat(icons): add fws-vr vr headset icon
Add scalable SVR icon for the fws-vr-setup launcher using FWS/Catppuccin color palette. The icon depicts a VR headset with straps, lenses, and nose notch in blue and dark theme colors.
2026-07-04 03:30:46 +02:00
nocode b408c84a8c feat(releng): add fws-vr-setup desktop application entry
Add desktop application entry for VR setup tool that allows users to install drivers and runtimes for VR headsets including HTC Vive, Valve Index, and Meta Quest devices. The application launches fws-vr-setup command in a kitty terminal.
2026-07-04 03:30:41 +02:00
nocode 2a3824a991 feat(i3): add default i3 window manager configuration
Add FWS default i3 configuration file with X11 session support.

Includes:
- Color scheme based on Catppuccin Mocha palette
- Keyboard bindings for common applications and window management
- VR headset driver setup integration (Super+Shift+V)
- Multi-monitor and workspace navigation support
- Screenshot and multimedia control bindings
- Status bar configuration with i3status
- Autostart for dunst, nm-applet, polkit-gnome, and blueman
2026-07-04 03:30:38 +02:00
nocode 06121343e7 feat(releng): add fws-vr-setup script for VR driver installation
Add interactive shell script for installing VR drivers and runtimes based on headset type. Supports HTC Vive/Valve Index (native SteamVR), Meta Quest/Pico (ALVR streaming), and Monado (OpenXR runtime). Script automatically detects GPU and installs appropriate 32-bit Vulkan libraries required by SteamVR. Includes multilib configuration and user guidance for each VR setup path.
2026-07-04 03:30:28 +02:00
nocode 3c12d9514c chore(releng): add fws-vr-setup executable to file permissions
Register the new fws-vr-setup script with executable permissions (755) in the releng profile configuration.
2026-07-04 03:30:23 +02:00
nocode 5f108c607b feat(releng): add i3 window manager desktop environment configuration
Add i3 window manager as a selectable desktop environment option in the anaconda kickstart configuration. Includes:

- i3-wm with essential packages (i3status, i3lock, rofi, kitty, etc.)
- X11 server requirement for VR compatibility (SteamVR incompatible with Wayland)
- Bluetooth service enablement for i3 sessions
- Icon cache update for VR launcher visibility in rofi
- i3 configuration template installation for skeleton and existing users
- Keyboard layout handling via X11 xorg.conf.d (no keyboard injection needed)

This enables users to install a lightweight X11-based desktop environment with VR support capabilities.
2026-07-04 03:30:18 +02:00
nocode 95eb6c15e0 fix(fws-setup-hardware): add blueman bluetooth manager support for i3 desktop
Include blueman in the base packages for i3 desktop environment, alongside hyprland and xfce, to provide graphical bluetooth management and pairing assistant for i3 users.
2026-07-04 03:30:12 +02:00
nocode 4a598c854a chore(baseline): add fws-vr-setup executable permission 2026-07-04 03:30:07 +02:00
nocode 9f46b9bb1b feat(fws_desktop): add i3 window manager option with VR compatibility note
Add i3 X11 window manager as a desktop environment option with explicit VR compatibility information. Update Hyprland label to clarify it uses Wayland and is not VR compatible. Adjust position indices for subsequent radio button options.
2026-07-04 03:30:02 +02:00
nocode 14d138cae4 feat(fws_recap): add i3 desktop option and update hyprland description
Add i3 desktop environment option with X11 and VR compatibility note.
Update hyprland description to clarify Wayland protocol and VR incompatibility.
Both descriptions now include information about included applications (Firefox, SDDM).
2026-07-04 03:29:56 +02:00
nocode 8e2e74a227 feat(fws_desktop): add i3 desktop option and update hyprland description
Add i3 window manager as a desktop choice with X11 and SteamVR compatibility note. Update Hyprland description to clarify it uses Wayland and is not compatible with VR.
2026-07-04 03:29:44 +02:00
nocode d0e431329d refactor(hyprland): disable splash rendering in hyprland configuration
Add disable_splash_rendering option to misc section to hide the startup splash message at the bottom of the screen.
2026-07-04 03:06:39 +02:00
nocode 75113980f0 refactor(waybar): add format-no-controller for bluetooth module
Configure waybar bluetooth module to use an empty format when no bluetooth controller is detected, improving UI consistency.
2026-07-04 03:06:25 +02:00
nocode 4d123b6f5d fix(hyprland): only launch blueman-applet if bluetooth adapter exists
Prevent blueman-applet from showing connection errors in virtual machines where no Bluetooth adapter is present. The applet now checks for the existence of a Bluetooth adapter in /sys/class/bluetooth before launching.

This change adds a conditional check that gracefully skips the applet initialization when no hardware is available, improving user experience in VM environments.
2026-07-04 03:05:57 +02:00
nocode a09278b63f feat(anaconda): add modified anaconda summary hub glade template
Add anaconda-summary-hub.glade file, a customized copy of anaconda 45.8's summary.glade without the "We won't touch your disks…" disclaimer label at the bottom of the hub.

This file is placed in a neutral path (usr/local/share/fws/) because mkarchiso applies the airootfs before pacstrap. Placing it at the anaconda package path would cause pacman to refuse installation due to filesystem conflicts. The live environment's .xinitrc copies this file to /usr/share/anaconda/ui/hubs/summary.glade during boot.
2026-07-04 02:52:33 +02:00
nocode dd71664b9e chore(baseline): remove anaconda summary glade file overlay
Delete the customized summary.glade file that was overlaying the anaconda package file. This file was previously used to hide the "We won't touch your disks…" message at the bottom of the installation summary hub.
2026-07-04 02:52:24 +02:00
nocode 2aaf1cbfd5 chore(baseline): add anaconda summary hub glade override in xinitrc
Add logic to override the anaconda summary hub glade file at live boot time. The custom glade file is stored in /usr/local/share/fws and copied to /usr/share/anaconda/ui/hubs/summary.glade during X initialization.

This workaround is necessary because mkarchiso applies the airootfs overlay before pacstrap, which prevents pacman from installing anaconda over existing files. The file is therefore delivered to /usr/local/share/fws and overlaid at boot when the live overlay is writable.
2026-07-04 02:52:18 +02:00
nocode e20523470f feat(anaconda): add customized summary glade ui file
Add a customized version of anaconda's summary.glade UI definition file to the baseline airootfs configuration. This file is based on anaconda 45.8 but with the "We won't touch your disks..." label removed from the bottom of the hub, allowing the overlay to mask the original package file without requiring an anaconda rebuild.
2026-07-04 01:07:39 +02:00
nocode c56db3de04 chore(releng): remove fws-dnd package from x86_64 build
Remove the fws-dnd package and its associated comment from the x86_64 package list. This package is no longer needed for the release engineering build.
2026-07-04 01:02:50 +02:00
nocode 81dfac3762 chore(releng): add fws-dnd package to x86_64 configuration
Add fws-dnd (D&D 5e manager for dice, characters, grimoire, sessions, and combat) to the releng packages.x86_64 configuration.
2026-07-04 01:01:27 +02:00
nocode 43ecf7f54f chore(releng): add pipewire and bluetooth packages to desktop environments
Add PipeWire audio stack (pipewire, pipewire-pulse, wireplumber, pavucontrol)
and Bluetooth support (bluez, bluez-utils, blueman) to xfce and hyprland
package lists to ensure they are installed alongside the desktop environment.

Enable bluetooth.service at installation time for hyprland to have Bluetooth
ready at first boot. Update comments to reflect the new guaranteed installation
of audio and Bluetooth components as part of the desktop environment transaction.
2026-07-04 01:01:20 +02:00
nocode 96e5721ad8 chore(hyprland): add blueman applet and remove steam keybinding
Add blueman-applet to the exec-once commands for bluetooth management.
Remove the Steam launcher keybinding (SUPER+KP_Multiply) from the configuration.
2026-07-04 01:01:12 +02:00
nocode cef3b291d1 chore(packages): add fws-dnd package to baseline x86_64 configuration
Add the fws-dnd package (D&D 5e Manager for dice, characters, grimoire, sessions, and combat) to the baseline x86_64 packages list under the FWS section.
2026-07-04 01:01:09 +02:00
nocode 6e59e1d4c4 chore(baseline): update installer preparation message in french
Change the estimated time message from '2 à 4 minutes' to 'Encore une à deux minutes' to provide more accurate timing information to users during the FWS installer preparation phase.
2026-07-04 01:00:53 +02:00
nocode 3a5efffc61 feat(wofi-style): add wofi application menu styling with catppuccin mocha theme
Add a new CSS stylesheet for wofi (application launcher menu triggered with Super+R) that implements the Catppuccin Mocha blue color scheme. The stylesheet includes styling for the window, input field, menu entries, and selection states with rounded corners and appropriate contrast ratios.
2026-07-04 00:26:37 +02:00
nocode 53942aece0 feat(releng): add wofi keyboard shortcuts panel styling
Add CSS stylesheet for the FWS keyboard shortcuts panel (fws-hypr-keys) using monospace font to align key combinations with descriptions. Uses the same color palette as the wofi menu with Catppuccin theme colors.
2026-07-04 00:26:31 +02:00
nocode e98a8320fb feat(wofi): add wofi application menu configuration
Add wofi configuration file for FWS with settings for case-insensitive search, image support, custom dimensions, and application prompt.
2026-07-04 00:26:27 +02:00
nocode 038700e22d feat(hyprland): add desktop portal and notification support to hyprland setup
Add missing packages for hyprland desktop environment:
- mako + libnotify for notifications
- polkit-gnome for GUI authentication
- network-manager-applet for network management in tray
- xdg-desktop-portal-hyprland for screen sharing
- gvfs for USB mounting in thunar
- ttf-nerd-fonts-symbols for waybar icons

Add start-hyprland launcher support for hyprland ≥0.53 to properly prepare systemd session/environment.

Add wofi configuration files (config and style.css) for application menu styling.

Update skeleton directory setup to install wofi configs for new users and fix chown path formatting.
2026-07-04 00:26:23 +02:00
nocode 4857a9fda9 style(waybar): redesign waybar stylesheet with catppuccin mocha theme and pill-style modules
Update waybar-style.css to version 2 with the following changes:

- Switch to Catppuccin Mocha color scheme with blue accents
- Redesign bar as floating rounded container instead of bottom border
- Style modules as pill-shaped boxes with individual backgrounds
- Add Nerd Font symbols support for better icons
- Improve workspace buttons with hover effects and transitions
- Add tooltip styling and proper color coding for each module
- Update clock styling with dedicated background
- Enhance battery, network, and bluetooth status colors
- Refine spacing, padding, and border-radius throughout for modern appearance
2026-07-04 00:26:16 +02:00
nocode 289a70c10c style(waybar): enhance waybar configuration with improved styling and additional modules
Update waybar configuration with:
- Adjusted height (34 to 36) and spacing (4 to 2)
- Added margins (top: 6, left: 10, right: 10)
- Added workspace navigation with scroll (e-1/e+1)
- Added backlight, bluetooth, and battery modules
- Enhanced clock format with calendar styling
- Improved audio controls with pavucontrol integration
- Added network bandwidth display
- Updated all module icons to use modern emoji/nerd font glyphs
- Improved tooltips with more detailed information
- Adjusted max window name length from 55 to 45 characters
2026-07-04 00:26:09 +02:00
nocode 8224a24d02 chore(hyprland): add startup services and brightness/blueman window rules
- Add mako notification daemon to startup services
- Add nm-applet for network tray icon
- Add polkit-gnome authentication agent for privilege escalation dialogs
- Add brightness control keybinds for XF86MonBrightnessUp/Down
- Add float window rule for blueman-manager
- Update startup comment to describe all session initialization tasks
2026-07-04 00:26:02 +02:00
nocode 0ab90857e5 feat(fws-setup-hardware): add brightnessctl and blueman to base stack
Add brightnessctl for brightness control without root via logind,
consumed by XF86MonBrightness* keys and waybar backlight module.

Add blueman as graphical Bluetooth manager for desktop environments
that don't provide their own (KDE and GNOME have built-in managers).
Blueman provides system tray integration and pairing assistant.

Update comments to clarify the purpose of these new packages.
2026-07-04 00:25:57 +02:00
nocode d6212deaa9 feat(fws-hypr-keys): add optional style support and adjust wofi dimensions
- Import os module to check for style file existence
- Add _STYLE constant pointing to wofi-keys.css stylesheet
- Increase wofi window dimensions from 780x560 to 820x600
- Conditionally apply custom style if the stylesheet exists
- Refactor wofi command construction for better readability
2026-07-04 00:25:45 +02:00
nocode 754b6e8802 fix(syslinux): add udev event timeout to all boot options
Add udev.event_timeout=45 parameter to all APPEND kernel boot options in the syslinux configuration to prevent udev event timeout issues during boot on systems with slow device initialization.
2026-07-04 00:13:13 +02:00
nocode 053998d1c0 fix(releng): add udev event timeout to speech boot entry
Add udev.event_timeout=45 parameter to the accessibility boot entry to prevent timeout issues during device initialization on systems with slower hardware or many devices.
2026-07-04 00:13:09 +02:00
nocode 218b0707a4 fix(efiboot): add udev event timeout to nomodeset boot entry
Add udev.event_timeout=45 parameter to the safe graphics boot option to prevent timeout issues during device initialization on systems with slower hardware or complex device trees.
2026-07-04 00:13:04 +02:00
nocode 7c11f53520 fix(releng): add udev event timeout to archiso linux boot entry
Add udev.event_timeout=45 kernel parameter to the UEFI boot entry for the archiso linux image. This timeout helps prevent boot delays caused by udev waiting for device events to complete.
2026-07-04 00:12:53 +02:00
nocode be0844b50b feat(releng): add uefi boot entry with nomodeset graphics option
Add a new UEFI boot loader entry for Arch Linux installation medium with the nomodeset kernel parameter. This provides a fallback boot option for systems with GPU or hypervisor drivers that have issues with KMS (kernel mode setting), allowing the system to fall back to VESA/EFI framebuffer graphics mode which is slower but more reliable.
2026-07-04 00:00:11 +02:00
nocode 24370461ea feat(sddm): add fws login theme for kde xfce and hyprland
Introduce a new SDDM theme "fws" designed for FWS with support for KDE, XFCE, and Hyprland desktop environments.

The theme is built using pure QtQuick and QtQuick.Controls (Basic style) with no additional dependencies beyond what SDDM already requires. It includes:

- Main.qml: Custom login screen with clock, date, user/password fields, session selector, and power controls
- metadata.desktop: Theme metadata and configuration
- theme.conf: Default configuration with customizable accent color and gradient colors

The design uses a dark theme with accent colors and supports capslock detection and login error handling.
2026-07-04 00:00:05 +02:00
nocode 5d6d1c6db8 feat(waybar): add waybar style configuration for fws theme
Add CSS stylesheet for Waybar that matches the FWS theme, SDDM appearance, and Hyprland borders. Includes styling for workspaces, clock, custom key help button, and system status modules with Catppuccin blue accent color (#89b4fa).
2026-07-03 23:59:58 +02:00
nocode 23df7656e5 feat(waybar): add waybar configuration for hyprland desktop environment
Add waybar panel configuration with modules for workspaces, window title, clock, volume, network, CPU, and memory monitoring. Configured for top panel layout with French localization support.
2026-07-03 23:59:53 +02:00
nocode a28a64310a feat(releng): add fws-hypr-keys script to display hyprland keyboard shortcuts
Add a new Python utility script that displays actual Hyprland keyboard shortcuts from hyprctl binds output. The script:

- Queries hyprctl binds to get loaded key bindings from the compositor
- Formats keybinds with human-readable modifiers and key labels (French localization)
- Displays descriptions from bindd config entries or falls back to dispatcher actions
- Opens results in wofi dmenu for incremental search and filtering
- Handles special keys like mouse buttons, arrow keys, and common special keys
- Deduplicates repeated bindings (e.g., from resize actions)

No additional dependencies required beyond python3 and wofi (already in FWS + Hyprland packages).
2026-07-03 23:59:49 +02:00
nocode 7aa1276e2c feat(anaconda): add fws recap glade ui definition
Add new Glade UI definition file for the FWS recap (summary before installation) spoke. This defines the AnacondaStandaloneWindow layout with a scrollable text view to display installation summary information.
2026-07-03 23:59:44 +02:00
nocode 9df1f444bc feat(fws): add recap spoke for pre-installation summary
Add FWSRecapSpoke, a standalone spoke that displays a comprehensive summary of user configuration choices and detected hardware before installation begins. The spoke intercepts the installation flow between the SummaryHub and ProgressSpoke, allowing users to review and modify their choices before any disk writes occur.

Features:
- Displays user-selected configuration (language, keyboard, timezone, users, partitioning, desktop, hostname)
- Shows detected hardware (virtualization, GPUs, disks, network status)
- Provides installation flow overview and warnings about automatic partitioning
- Implements back navigation to return to the hub for modifications
- Never marks as completed to ensure proper flow control in anaconda's action queue
2026-07-03 23:59:39 +02:00
nocode 7fe54d0791 fix(setup-aur.sh)!: use explicit package list instead of glob for install and publish
Change publish_to_repo() to accept explicit file list via variadic arguments
instead of a directory glob. This prevents re-publishing stale .pkg files
from previous builds that may still exist in BUILD_DIR.

Update build_or_skip() to get the actual list of packages produced by the
current makepkg invocation using --packagelist, filter for .pkg.tar.* files,
verify they exist on disk, and pass only those to pacman -U and publish_to_repo().

This fixes issues where:
- After pkgrel bumps, old .pkg files would cause pacman to receive duplicate
  package names ("repeated target" error)
- Stale packages would be republished over the purge in local-repo
- Optional debug packages would cause errors when not produced

BREAKING CHANGE: publish_to_repo() function signature changed from publish_to_repo(name, directory) to publish_to_repo(name, file1, file2, ...) - callers must pass explicit file paths instead of a directory path
2026-07-03 23:59:32 +02:00
nocode 4249282052 fix(python-meh): fix python 3.11 compatibility by replacing removed gettext.ldgettext
Replace gettext.ldgettext with gettext.dgettext in Anaconda exception handler
since ldgettext was removed in Python 3.11. Without this fix, the exception
handler crashes with AttributeError when trying to display errors, resulting
in raw tracebacks instead of the meh dialog in both GUI and TUI modes.

Also clean up stale __pycache__ directories from previous extractions and
add validation to ensure ldgettext is completely removed.
2026-07-03 23:59:25 +02:00
nocode 5e66f99816 feat(syslinux): add safe graphics boot option with nomodeset kernel parameter
Add a new boot menu entry for FWS without kernel mode setting (KMS) to provide a fallback option for systems where KMS causes issues. This entry uses the nomodeset kernel parameter to force the framebuffer to VESA/EFI mode, which is slower but more reliable for problematic GPU drivers and hypervisors like VMware.
2026-07-03 23:59:20 +02:00
nocode 35f77ba33d chore(releng): add file permissions for fws-hypr-keys script 2026-07-03 23:59:13 +02:00
nocode d8e67fe861 refactor(anaconda): clarify payload mechanism and enhance desktop environment configurations
Update interactive-defaults.ks kickstart file with:

- Clarified documentation on LiveOS payload detection and mounting via /run/rootfsbase bind-mount instead of direct liveimg directive
- Added comprehensive comments explaining why liveimg directive is intentionally omitted and how anaconda --liveinst handles payload creation
- Enhanced XFCE and Hyprland package sets with noto-fonts for proper TTF rendering and additional tools (thunar, hyprlock, grim, slurp, wl-clipboard, playerctl)
- Implemented SDDM theme configuration with FWS theme support and accent colors per desktop environment (teal for XFCE, Catppuccin blue for Hyprland, Breeze blue for KDE)
- Added GDM (GNOME) configuration via dconf with welcome banner, clock enhancements, and numlock settings
- Expanded Waybar configuration installation for Hyprland with config and style files
- Added comprehensive documentation for greeter customization and Hyprland VM compatibility
- Included details about FWS recap spoke (standalone GUI summary screen) replacing zenity-based pre-install approach
2026-07-03 23:59:09 +02:00
nocode 68850f0ed2 feat(hyprland): expand hyprland config with keybindings, animations, and window rules
Add comprehensive keybinding descriptions using bindd/binde for keyboard shortcut display, implement Catppuccin Mocha color scheme with blue gradient borders, enable animations with custom bezier curve, configure touchpad settings, add blur/shadow configuration for VM compatibility, implement scratchpad support, add screenshot and multimedia key bindings with PipeWire/playerctl integration, and define window floating rules.

Keybindings now match i3/sway FWS configuration with French descriptions that automatically appear in fws-hypr-keys shortcut viewer. Configuration includes multi-screen support, workspace navigation (1-10), window resizing, and various utility bindings.
2026-07-03 23:59:00 +02:00
nocode aa9cb13648 chore(baseline): add file permissions for selinux and hardware setup scripts
Add executable permissions for load_policy, setstatus, fws-setup-hardware, and fws-hypr-keys scripts in the baseline profile configuration.
2026-07-03 23:58:53 +02:00
nocode 8b7af60239 chore: remove pycache file 2026-07-03 23:58:48 +02:00
nocode ee0082baa1 feat(xinitrc): add anaconda installer improvements and splash screen
Add several improvements to the liveinst installer workflow:

- Expose archiso squashfs at /run/rootfsbase where Anaconda expects to find
  the Live OS image, fixing SourceSetupError crashes
- Display immediate splash screen with zenity during Anaconda startup (2-4 min
  wait time) to provide user feedback and prevent perceived hangs
- Wrap liveinst with dbus-run-session to provide D-Bus session bus, fixing
  GLib.GError crashes from missing ScreenSaver service
- Add hardware summary log output to debug information collection

These changes address three crash scenarios identified during testing and
improve user experience during installer initialization.
2026-07-03 23:58:28 +02:00
nocode 0b0fcd8ce4 feat(releng): add hardware detection and driver setup script
Add fws-setup-hardware script that automatically detects and installs:
- GPU drivers (NVIDIA, AMD, Intel) based on lspci output
- Hypervisor guest tools (VMware, VirtualBox, QEMU, Hyper-V)
- Audio/mic/headset stack (PipeWire), Bluetooth (BlueZ), containers (Podman)
- Optional desktop stack when non-CLI desktop is selected: Steam, Wine, VR support (Monado/OpenXR), and winboat-bin

The script is integrated into the Anaconda kickstart post-installation phase and is non-blocking to prevent installation failures from missing packages or network issues.

Also update profiledef.sh to set correct permissions (755) for the new script.
2026-06-24 23:32:37 +02:00
nocode 99006b9ede feat(hyprland): add hyprland configuration with keyboard layout injection and vm compatibility
Add Hyprland desktop environment support with:

- New hyprland.conf template with keyboard layout placeholders (__KB_LAYOUT__ and __KB_VARIANT__) that get injected during installation
- Hyprland wrapper script (/usr/local/bin/fws-hyprland) that detects virtual machines and forces software rendering to prevent compositor crashes on virtual GPUs (VMware vmwgfx, VirtualBox)
- Automatic configuration deployment to /etc/skel and existing user home directories
- Refactored keyboard layout detection logic (moved to section 3bis) to support both X11/SDDM and Hyprland configurations
- Added locale generation during installation to ensure proper language support

Keyboard layout is now derived from either kickstart configuration or system locale using langtable, providing consistent keyboard behavior across all desktop environments.
2026-06-24 23:06:20 +02:00
nocode ff98a1e186 fix(anaconda): improve desktop environment setup and system configuration
- Fix XFCE display manager: replace lightdm-gtk-greeter with sddm for consistency
- Add SDDM numlock initialization for KDE, XFCE, and Hyprland
- Configure X11 keyboard layout from kickstart or locale via langtable to ensure correct SDDM login keyboard
- Enable wheel group sudo access and add all human users to wheel group
- Apply FWS branding to os-release, lsb-release, and GRUB configuration
- Remove inappropriate live system motd file
- Fix DNS resolution: configure systemd-resolved stub resolver with NetworkManager integration to prevent "Limited Connectivity" on first boot
- Reformat case statement for better readability
2026-06-24 19:02:31 +02:00
nocode 7daca57714 feat(anaconda): add desktop environment selection spoke for FWS
Implement a new Anaconda spoke that allows users to select their preferred desktop environment (CLI, GNOME, KDE Plasma, Hyprland, or XFCE) during installation.

The spoke:
- Displays radio buttons for five desktop choices with CLI as the default
- Persists the selection to /tmp/fws-desktop for the kickstart %post script
- Supports revisiting the spoke and resuming previous selections
- Includes a GTK UI definition file (glade) with translatable labels
- Integrates as a SoftwareCategory spoke in the Anaconda installer

The implementation leverages Anaconda's runtime spoke discovery mechanism via the airootfs overlay, avoiding the need to rebuild the anaconda package.
2026-06-24 17:58:52 +02:00
nocode d409406fde feat(build-offi): add build date to iso filename and prompt for version/variant on host
Refactor version and variant handling to prompt for metadata on the host before entering container, enabling proper terminal display. Pass FWS_ISO_VERSION and FWS_ISO_TAG via environment variables to container.

Add build date (AAAA.MM.JJ format) to the final ISO filename: fws-<version>-<variant>-<AAAA.MM.JJ>.iso

Implement ask_iso_meta_host() function to validate and collect version (major.minor.build) and variant name inputs before containerization. Use sudo -E to preserve environment variables across privilege escalation.

Update ISO metadata prompt logic to skip if already provided, supporting both container execution and native Arch/WSL environments.
2026-06-23 12:50:45 +02:00
nocode 0de970239f feat(build): refactor build script with interactive version and variant selection
Replace automatic version reading from VERSION file with interactive prompts for version (major.minor.build) and ISO variant name (e.g., cli, gnome, kde).

Changes:
- Remove automatic VERSION file reading; now serves as default with prompt
- Add interactive prompts for version and variant name with validation
- Simplify ISO naming from FWS-v{major}-{minor}-{build}-{date}-x86_64.iso to fws-{version}-{variant}.iso
- Update hostname from 'archiso' to 'fws'
- Add custom FWS logo for fastfetch with color configuration
- Improve error messages and user feedback

This enables flexible build variants and streamlines the build process.
2026-06-23 11:02:41 +02:00
nocode 7d6bba42b2 feat(anaconda): add hostname field to user spoke and fix keyboard layout by territory
Replace keyboard correction logic in kickstart with anaconda package patches:

- Add "Nom de machine" (hostname) field to the User spoke in anaconda GUI
- user.py writes the hostname value to /tmp/fws-hostname for the kickstart %post
- Simplify kickstart to read from /tmp/fws-hostname instead of live hostname
- Fix keyboard layout selection to use full locale (e.g. fr_CH) instead of just language (fr)
  to correctly derive territory-specific keymaps (ch(fr) for Switzerland, not fr for France)
- Remove outdated fix_keyboard() function from kickstart

Also add --network=host flag to podman run in setup-aur.sh to ensure DNS and internet
connectivity through the host stack instead of the bridge (fixes timeout issues on
certain networks with firewalld/NAT).

Bump anaconda pkgrel from 2 to 4.
2026-06-23 10:48:32 +02:00
nocode 1184ba17d9 fix(releng): correct hostname and keyboard mapping in anaconda post-installation
Fix two bugs in the anaconda post-installation script:

1. Hostname handling: Anaconda writes an empty /etc/hostname to the target system. Now we preserve the hostname set during installation from the live environment, falling back to 'fws' if using default values.

2. Keyboard mapping: Anaconda derives keyboard layout from language only, ignoring territory. For locales with territory variants (e.g., fr_CH for Switzerland), we now apply territory-specific keyboard maps to both console (vconsole.conf) and X11 (xorg.conf.d) using langtable for proper mapping.
2026-06-23 08:40:42 +02:00
nocode d25ab9b4da fix(build): add --network=host to podman run commands for dns connectivity
Add --network=host flag to podman run commands in both build.sh and build-offi.sh to fix DNS resolution issues.

The podman bridge network was unable to route to external hosts on certain wifi networks due to firewalld/NAT misconfiguration after network changes, resulting in "core.db: Resolving timed out" errors. By sharing the host's network stack, the container inherits the same network connectivity including DNS resolution via 127.0.0.53/systemd-resolved that is available on the host.
2026-06-23 08:06:53 +02:00
113 changed files with 7741 additions and 379 deletions
+42 -2
View File
@@ -2,15 +2,41 @@
Changelog Changelog
######### #########
[XX] - YYYY-MM-DD [0.6.0] - 2026-07-07
================= ====================
Added Added
----- -----
- **Installateur Anaconda 45 porté sur Arch** (payload LiveOS) : page « Récapitulatif avant installation » avec bouton
Précédent, choix du bureau intégré (GNOME, KDE, XFCE, Hyprland, i3, CLI) avec compatibilité VR affichée, splash de
préparation alimenté en direct par les logs (diagnostic automatique en cas de blocage), garde-fou RAM (< 2 Go),
mécanisme de retry réseau pour l'installation du bureau (``fws-desktop-retry``).
- **Outillage FWS** : ``fws-hello`` (accueil 1er démarrage), ``fws-update`` + indicateur de mises à jour,
``fws-keys`` (raccourcis réels), ``fws-powermenu``, ``fws-about``, ``fws-vr-setup`` (SteamVR/ALVR/Monado),
``fws-proton-ge``, ``fws-setup-hardware`` (drivers GPU/VM/audio/Bluetooth), ``fws-dnd``.
- **Récupération système** (« point de restauration ») : application GTK ``fws-recovery-gui`` + moteur ``fws-recovery``
(GRUB, initramfs, pacman, chroot), accessible depuis le live (entrée de boot), le menu GRUB du système installé
(``fws-recovery.target``) et son menu d'applications (élévation polkit).
- **Swap zram** sur le live et le système installé (``zram-generator``).
- **firewalld** activé par défaut sur le système installé.
- Bureaux Hyprland et i3 à parité visuelle (waybar/polybar, wofi/rofi, Catppuccin Mocha), thème SDDM « fws »,
bannière GDM.
- **Choix du nom de machine à l'installation** : champ « Nom de machine » dans la page Utilisateur d'anaconda,
appliqué au système installé (``/etc/hostname``, repli ``fws``).
- **Script de build interactif** (``build-offi.sh``) : version (``major.minor.build``) et variante (``cli``, ``gnome``,
``kde``…) demandées sur l'hôte avant d'entrer dans le conteneur, ISO nommée
``fws-<version>-<variante>-<AAAA.MM.JJ>.iso``, logo FWS dans fastfetch.
Changed Changed
------- -------
- **Branding FWS complet** : l'installateur s'annonce « FWS 0.6 » (variables ``ANACONDA_PRODUCT*``), splash FWS au menu
de boot BIOS (l'image Arch d'archiso est remplacée), ``/etc/issue`` sans version de noyau, filtres d'affichage
Fedora/Arch sur le splash et le journal de récupération.
- Toutes les entrées de boot plafonnent les workers udev bloqués (``udev.event_timeout=45``) ; le menu BIOS démarre
explicitement l'entrée FWS (``DEFAULT fws``).
- ``systemd-timesyncd`` explicitement activé sur le système installé pour éviter la dérive d'horloge (TLS, pacman).
- Use xz compression for the baseline profile's bootstrap tarball so that it has more differences when compared to the - Use xz compression for the baseline profile's bootstrap tarball so that it has more differences when compared to the
releng profile. releng profile.
- Match network interfaces by their type, instead of name, in systemd-networkd configuration. - Match network interfaces by their type, instead of name, in systemd-networkd configuration.
@@ -21,12 +47,26 @@ Deprecated
Fixed Fixed
----- -----
- Watchdog du splash qui affichait son diagnostic PAR-DESSUS l'installateur (il se désarme désormais dès que la GUI
d'anaconda est à l'écran, et mesure l'inactivité en itérations — insensible aux sauts d'horloge des VM).
- Gel de l'installateur sur machines à faible RAM (1 Go) : zram + avertissement préalable.
- Profils ``baseline``/``releng`` synchronisés (la fusion baseline-écrase-releng avait fait perdre ``firewalld``,
le cap udev et les entrées de boot FWS d'ISO précédentes).
- DNS du système installé (NetworkManager + systemd-resolved), hook ``lvm2`` dans l'initramfs, GRUB de secours
``--removable`` en UEFI.
- Disposition clavier dérivée de la locale complète et non de la seule langue : ``fr_CH`` donne désormais ``ch(fr)``
au lieu de ``fr``, appliqué à la console (``vconsole.conf``) et à X11 (via langtable) ; anaconda écrivait par
ailleurs un ``/etc/hostname`` vide sur le système installé.
- DNS dans le conteneur de build : ``podman run --network=host`` pour passer par la pile réseau de l'hôte
(corrige les timeouts avec firewalld/NAT).
- Use ``systemd-run`` to invoke ``curl`` with a transient service that depends on ``network-online.target`` since no - Use ``systemd-run`` to invoke ``curl`` with a transient service that depends on ``network-online.target`` since no
services are currently pulling in ``network_online.target``. services are currently pulling in ``network_online.target``.
Removed Removed
------- -------
- ``configs/baseline/syslinux/syslinux-linux.cfg`` (label ``arch`` stock, orphelin et sans cap udev).
[87] - 2025-10-29 [87] - 2025-10-29
================= =================
+7 -3
View File
@@ -85,8 +85,12 @@ projet (« inbound = outbound », `GPL-3.0-or-later`), avec attestation d'origin
plus « Arch Linux ») ; `motd` ne renvoie plus vers `archlinux.org` plus « Arch Linux ») ; `motd` ne renvoie plus vers `archlinux.org`
- [ ] **Dépôt rendu publiquement accessible** (sinon l'offre écrite n'est pas - [ ] **Dépôt rendu publiquement accessible** (sinon l'offre écrite n'est pas
opposable) — action hors-code opposable) — action hors-code
- [ ] Branding Red Hat/Fedora retiré de l'UI Anaconda ; installateur marqué - [x] Branding Red Hat/Fedora retiré de l'UI Anaconda : nom de produit **FWS**
« modifié » — à faire pendant la migration (config `product.d`) via les variables `ANACONDA_PRODUCTNAME`/`ANACONDA_PRODUCTVERSION`
(exportées par le `.xinitrc` du live, lues depuis l'os-release FWS —
l'en-tête affiche « FWS 0.6 INSTALLATION ») ; visuel **Arch** du menu de
boot BIOS remplacé par un splash FWS ; le caractère « version modifiée
d'Anaconda » reste documenté ici et dans TRADEMARKS.md
- [ ] Décision tranchée sur `python-iso639` (AGPL-3.0-only) : garder ou remplacer - [ ] Décision tranchée sur `python-iso639` (AGPL-3.0-only) : garder ou remplacer
- [ ] Revue par un conseil FOSS (recommandé avant diffusion commerciale) - [ ] Revue par un conseil FOSS (recommandé avant diffusion commerciale)
@@ -96,4 +100,4 @@ projet (« inbound = outbound », `GPL-3.0-or-later`), avec attestation d'origin
> dépôt** (construits depuis l'AUR) : à corriger à la source AUR, pas ici. > dépôt** (construits depuis l'AUR) : à corriger à la source AUR, pas ici.
> **Ceci n'est pas un avis juridique.** Base de conformité de bonne foi au > **Ceci n'est pas un avis juridique.** Base de conformité de bonne foi au
> 2026-06-17. > 2026-07-07.
+75 -20
View File
@@ -1,6 +1,6 @@
# FWS # FWS
**FWS** est une distribution Linux live/installable basée sur **Arch Linux**, livrée sous forme d'ISO bootable BIOS/UEFI. L'installateur graphique est **en cours de migration de Calamares vers Anaconda** (Calamares retiré au commit 05ae9c3, intégration d'Anaconda en cours — voir le chantier dédié). **FWS** est une distribution Linux live/installable basée sur **Arch Linux**, pensée pour **remplacer Windows** : bureau prêt à l'emploi, jeux (Steam/Proton-GE), réalité virtuelle (SteamVR), compatibilité applicative (winboat/Wine) et outillage de maintenance intégré. L'installateur graphique est **Anaconda** (celui de Fedora/RHEL), **porté sur Arch** : payload LiveOS (copie du live sur le disque), spokes FWS (choix du bureau, récapitulatif avant installation), finalisation pacman/GRUB/mkinitcpio en `%post`.
Le projet est conçu pour être **construit depuis Windows via WSL** (les scripts détectent Git Bash et basculent automatiquement dans WSL), mais fonctionne aussi directement depuis une **Arch Linux native** ou depuis **toute distribution non-Arch (Fedora, Ubuntu…) via une bascule automatique dans un conteneur Arch podman**. Le projet est conçu pour être **construit depuis Windows via WSL** (les scripts détectent Git Bash et basculent automatiquement dans WSL), mais fonctionne aussi directement depuis une **Arch Linux native** ou depuis **toute distribution non-Arch (Fedora, Ubuntu…) via une bascule automatique dans un conteneur Arch podman**.
@@ -9,14 +9,40 @@ Le projet est conçu pour être **construit depuis Windows via WSL** (les script
## Aperçu ## Aperçu
- **Base** : Arch Linux (rolling release) - **Base** : Arch Linux (rolling release)
- **Installateur** : en migration Calamares → Anaconda (Calamares retiré, Anaconda en préparation ; payload `liveimg` pour installer FWS et non Fedora) - **Installateur** : **Anaconda 45 porté sur Arch** — payload LiveOS (installe FWS, pas Fedora), page « Récapitulatif avant installation » avec bouton Précédent, choix du bureau intégré, **brandé FWS** (en-tête « FWS 0.6 INSTALLATION », via les variables `ANACONDA_PRODUCT*` exportées par le `.xinitrc`)
- **Affichage live** : Xorg + Openbox (session graphique conservée pour l'installateur) - **Affichage live** : Xorg + Openbox + splash de préparation qui montre l'activité réelle d'anaconda (dernier log actif + chrono ; si rien ne bouge avant l'apparition de l'installateur, il le dit puis affiche un diagnostic complet — le watchdog se désarme dès que la GUI est à l'écran)
- **Bureaux installables** : XFCE, KDE Plasma, GNOME, Hyprland (choix à l'installation) - **Mémoire** : **4 Go recommandés** pour installer (2 Go minimum — swap **zram** intégré au live ET au système installé ; sous 2 Go le live avertit, à 1 Go sans zram l'installateur graphique gèle)
- **Boot** : BIOS (syslinux) + UEFI (systemd-boot) - **Bureaux installables** : GNOME, KDE Plasma, XFCE, **Hyprland** (Wayland — non compatible VR), **i3** (X11 — compatible VR/SteamVR), ou ligne de commande seule
- **Boot** : UEFI (systemd-boot) + BIOS (syslinux, splash **FWS**, `DEFAULT fws`) — entrées *normale*, *safe graphics (nomodeset)*, *Réparer FWS (recovery)* et *speech*
- **Récupération** : accessible depuis le live (entrée de boot), depuis le **menu GRUB du système installé** (« Réparer FWS ») et depuis son **menu d'applications** (« Récupération FWS », élévation polkit)
- **Compression** : squashfs (xz) - **Compression** : squashfs (xz)
--- ---
## Ce qu'il y a dans FWS
### À l'installation
- **Choix du bureau** dans Anaconda, avec compatibilité VR affichée (i3 = SteamVR ✔ ; Hyprland = ✘)
- **Page récapitulative** avant toute écriture disque : config choisie (langue, clavier, fuseau, comptes, partitionnement, bureau, nom de machine) + matériel détecté (GPU, disques, virtualisation, réseau)
- **Automatique en fin d'install** : drivers GPU (NVIDIA/AMD/Intel), outils invité VM (VMware/VirtualBox/QEMU/Hyper-V), PipeWire + pavucontrol, Bluetooth (BlueZ + blueman), luminosité (brightnessctl), **pare-feu firewalld activé**, Steam + Wine + Monado + MangoHud + gamemode, clavier/locale, hostname, sudo pour l'utilisateur créé, branding FWS
### Sur le système installé (outillage `fws-*`)
| Commande | Rôle | Accès |
|---|---|---|
| `fws-hello` | Bienvenue au 1er démarrage (mise à jour, codecs, Proton-GE, VR, raccourcis) | auto au 1er login |
| `fws-update` | Mise à jour complète (pacman + cache + orphelins + flatpak) | bouton 󰚰 de la barre |
| `fws-keys` | Panneau des **raccourcis clavier réels** (lit la config Hyprland/i3) | `Super+K`, bouton ⌨ |
| `fws-powermenu` | Verrouiller / déconnexion / suspendre / redémarrer / éteindre | `Super+X`, bouton 󰐥 |
| `fws-about` | « À propos » : le projet FWS + infos système | menu d'applications |
| `fws-vr-setup` | Drivers/runtimes VR (Vive/Index → SteamVR natif ; Quest → ALVR ; Monado) | `Super+Shift+V` (i3), menu |
| `fws-proton-ge` | Installe/actualise Proton-GE pour Steam (vérif sha512) | fws-hello, terminal |
| `fws-recovery` | **Application GTK de réparation** : GRUB, initramfs, pacman, terminal chroot (moteur bash + repli console) | menu « Récupération FWS » de l'OS installé, entrée GRUB « Réparer FWS », entrée de boot du live |
### Hyprland & i3 : parité visuelle
Même identité Catppuccin Mocha (nuances de bleu) sur les deux : barre flottante arrondie (**waybar** / **polybar**) avec date+heure, indicateur de mises à jour, volume/réseau/CPU/RAM/luminosité/Bluetooth cliquables ; menus assortis (**wofi** / **rofi**) ; mêmes raccourcis (déclarés avec descriptions → `Super+K`) ; **Dolphin** en gestionnaire de fichiers ; thème **SDDM « fws »** maison (pur QtQuick, accent adapté au bureau ; GNOME garde GDM avec bannière FWS).
---
## Environnement requis — INSTALLATION ULTRA PRÉCISE ## Environnement requis — INSTALLATION ULTRA PRÉCISE
### Côté Windows (machine hôte) ### Côté Windows (machine hôte)
@@ -146,9 +172,9 @@ Détails utiles :
Depuis **Git Bash sur Windows** (à la racine du repo) : Depuis **Git Bash sur Windows** (à la racine du repo) :
```bash ```bash
# Étape 1 : compilation des paquets AUR (winboat) + deps locales # Étape 1 : compilation des paquets AUR (winboat) + PKGBUILD locaux
# (anaconda porté sur Arch + ses dépendances python)
# → crée local-repo/ avec les .pkg.tar.zst # → crée local-repo/ avec les .pkg.tar.zst
# (Calamares retiré ; l'installateur Anaconda est un chantier en cours)
./setup-aur.sh ./setup-aur.sh
# Étape 2 : build de l'ISO via mkarchiso # Étape 2 : build de l'ISO via mkarchiso
@@ -170,11 +196,11 @@ L'ISO finale est générée dans `./out/FWS-YYYY.MM.DD-x86_64.iso`.
4. **`pacman -Syu`** complet (obligatoire — sinon `makepkg` plante sur les "conflicting files" lors des splits de paquets type `gcc-libs`). 4. **`pacman -Syu`** complet (obligatoire — sinon `makepkg` plante sur les "conflicting files" lors des splits de paquets type `gcc-libs`).
5. **Install des outils de build** : `base-devel git`. 5. **Install des outils de build** : `base-devel git`.
6. **Création d'un utilisateur `fwsbuild`** non-root avec `sudo` NOPASSWD (makepkg refuse de tourner en root). 6. **Création d'un utilisateur `fwsbuild`** non-root avec `sudo` NOPASSWD (makepkg refuse de tourner en root).
7. **Clone + build AUR** : `winboat-bin` via `makepkg -s --noconfirm`, plus les PKGBUILD locaux de `pkgbuilds/` (deps Anaconda). Calamares n'est plus compilé. 7. **Clone + build AUR** : `winboat-bin` via `makepkg -s --noconfirm`, plus les PKGBUILD locaux de `pkgbuilds/` (anaconda, anaconda-widgets, pykickstart, blivet, python-meh patché…). Le build est **incrémental** : un paquet déjà présent dans `local-repo/` à la bonne version est sauté.
8. **Création du repo local** : `repo-add local-repo/fws-local.db.tar.gz`. 8. **Création du repo local** : `repo-add local-repo/fws-local.db.tar.gz`.
9. **Cleanup** : suppression de l'utilisateur `fwsbuild`. 9. **Cleanup** : suppression de l'utilisateur `fwsbuild`.
**Durée** : quelques minutes (winboat-bin + deps locales ; Calamares et ses ~12 dépendances Qt6 ne sont plus compilés). **Durée** : quelques minutes en incrémental ; le premier build complet (anaconda + deps) est plus long.
--- ---
@@ -220,11 +246,12 @@ L'ISO finale est générée dans `./out/FWS-YYYY.MM.DD-x86_64.iso`.
## Séquence de boot de l'ISO ## Séquence de boot de l'ISO
1. Démarrage de l'ISO FWS (BIOS ou UEFI) 1. Menu de boot : **installation** (défaut), **safe graphics** (`nomodeset`), **Réparer FWS** (`fws.recovery=1` → app GTK `fws-recovery-gui` via openbox, repli console `fws-recovery`), **speech**. Toutes les entrées plafonnent les workers udev bloqués (`udev.event_timeout=45`)
2. `systemd` → autologin root sur `tty1` 2. `systemd` → autologin root sur `tty1`
3. `.zprofile``startx` 3. `.zprofile` mode recovery si demandé, sinon `startx`
4. `.xinitrc` → Openbox (session graphique du live) 4. `.xinitrc` → Openbox + **garde-fou RAM** (avertissement sous 2 Go) + **splash « Préparation de l'installateur »** (label alimenté en direct par les logs anaconda ; à 4 min sans activité → diagnostic auto `/tmp/fws-stall.txt` ; le tout se désarme dès que la GUI d'anaconda apparaît), bind mount `/run/rootfsbase` (source du payload LiveOS), patch du hub Anaconda, **exports `ANACONDA_PRODUCT*`** (branding FWS), puis `dbus-run-session liveinst`
5. **[Chantier C]** lancement de l'installateur Anaconda — pas encore câblé (Calamares retiré au commit 05ae9c3) 5. **Anaconda** : accueil → hub (langue, clavier, disque, utilisateur, **bureau FWS**) → « Commencer l'installation » → **page Récapitulatif** (bouton Précédent / Lancer l'installation) → installation → écran de réussite → extinction
6. En cas d'échec : écran de debug avec tous les logs (liveinst, %post chrooté, anaconda)
--- ---
@@ -233,8 +260,18 @@ L'ISO finale est générée dans `./out/FWS-YYYY.MM.DD-x86_64.iso`.
- **Paquets du live** : `configs/releng/packages.x86_64` - **Paquets du live** : `configs/releng/packages.x86_64`
- **Paquets baseline** : `configs/baseline/packages.x86_64` - **Paquets baseline** : `configs/baseline/packages.x86_64`
- **Overlay rootfs** : `configs/releng/airootfs/` ou `configs/baseline/airootfs/` - **Overlay rootfs** : `configs/releng/airootfs/` ou `configs/baseline/airootfs/`
- **Kickstart de l'installateur** : `configs/releng/airootfs/usr/share/anaconda/interactive-defaults.ks` (payload liveimg + %post) - **Kickstart de l'installateur** : `configs/releng/airootfs/usr/share/anaconda/interactive-defaults.ks` (payload LiveOS via `--liveinst` + tout le `%post` : bureaux, drivers, pare-feu, branding)
- **Branding ISO** : `configs/releng/profiledef.sh` (`iso_name`, `iso_label`, `iso_publisher`) - **Spokes Anaconda FWS** : `configs/baseline/airootfs/usr/lib/python3.14/site-packages/pyanaconda/ui/gui/spokes/` (`fws_desktop.py`, `fws_recap.py`) + glades dans `usr/share/anaconda/ui/spokes/`
- **Configs bureaux & thèmes** : `configs/releng/airootfs/usr/local/share/fws/` (hyprland, i3, waybar, polybar, wofi, rofi, picom) et `usr/share/sddm/themes/fws/`
- **Outils fws-\*** : `configs/releng/airootfs/usr/local/bin/`
- **Récupération sur l'OS installé** : entrée GRUB générée par `configs/releng/airootfs/etc/grub.d/42_fws-recovery``fws-recovery.target` + `fws-recovery-boot.service` (`etc/systemd/system/`) ; lanceur `usr/local/share/applications/fws-recovery.desktop` + politique polkit `usr/share/polkit-1/actions/org.fws.recovery.policy`
- **Swap zram** (live + installé) : `configs/releng/airootfs/etc/systemd/zram-generator.conf` (paquet `zram-generator`)
- **Branding ISO** : `configs/releng/profiledef.sh` (`iso_name`, `iso_label`, `iso_publisher`) ; **visuel du menu BIOS** : `configs/releng/syslinux/splash.png` (PNG 8-bit) ; **nom/version affichés par l'installateur** : exports `ANACONDA_PRODUCTNAME`/`ANACONDA_PRODUCTVERSION` dans le `.xinitrc` (lus depuis l'os-release du live)
> ⚠ **Règle d'or de la fusion** : au build, `configs/baseline/*` est copié **par-dessus** `configs/releng/*` — tout fichier homonyme de baseline **écrase** celui de releng. Ça concerne notamment `profiledef.sh`, `packages.x86_64`, `pacman.conf`, `efiboot/loader/entries/01-archiso-linux.conf` et `syslinux/syslinux.cfg` : **toute modification d'un de ces fichiers doit être reportée dans les deux profils** (des divergences ont déjà fait perdre silencieusement `firewalld` et le cap `udev.event_timeout` d'une ISO ; un `syslinux-linux.cfg` stock orphelin côté baseline définissait aussi un label `arch` sans le cap — supprimé, et le menu BIOS pointe désormais explicitement sur `DEFAULT fws`). Vérifier avec :
> ```bash
> comm -12 <(cd configs/baseline && find . -type f | sort) <(cd configs/releng && find . -type f | sort)
> ```
--- ---
@@ -247,7 +284,20 @@ L'ISO finale est générée dans `./out/FWS-YYYY.MM.DD-x86_64.iso`.
| `pacman: signature unknown trust` | Keyring périmé → `pacman -Sy --noconfirm archlinux-keyring` puis relancer. | | `pacman: signature unknown trust` | Keyring périmé → `pacman -Sy --noconfirm archlinux-keyring` puis relancer. |
| `makepkg: conflicting files` | `pacman -Syu` incomplet avant le build. Relancer `setup-aur.sh`. | | `makepkg: conflicting files` | `pacman -Syu` incomplet avant le build. Relancer `setup-aur.sh`. |
| `mkarchiso` plante sur `[fws-local]` | Le repo local n'a pas été généré. Relancer `setup-aur.sh`. | | `mkarchiso` plante sur `[fws-local]` | Le repo local n'a pas été généré. Relancer `setup-aur.sh`. |
| Écran noir après login Hyprland | Tu testes dans VirtualBox → pas d'accélération GPU. Utiliser QEMU/KVM avec `virtio-gpu` ou bare-metal. | | Écran bleu 1-2 min au boot du live | Normal en VM : un worker udev traîne (plafonné à 45 s) — le splash « Préparation de l'installateur » l'indique. Attendre. |
| L'installateur semble figé / fenêtre blanche | Rendu logiciel en VM : la fenêtre Anaconda se peint lentement. Patienter quelques secondes. |
| Session Hyprland/i3 : fenêtres au contenu noir en VM | Compositing sur GPU virtuel — picom est auto-désactivé en VM (config FWS) ; si tu l'as réactivé, `pkill picom`. |
| Hyprland crash au lancement en VM | Géré : le wrapper `fws-hyprland` force le rendu logiciel (`kms_swrast`/llvmpipe) sous hyperviseur. Sur vrai GPU, rendu matériel normal. |
| Steam plante à l'install de SteamVR | En VM : `steam -cef-disable-gpu -cef-disable-gpu-compositing` (et la VR ne peut PAS marcher en VM — GPU/USB inaccessibles). |
| Icône Bluetooth absente / blueman en erreur | Pas d'adaptateur Bluetooth (cas VM) : le module de barre se cache et l'applet ne se lance pas — comportement voulu. |
| Le système installé ne boote plus | Menu GRUB → « **Réparer FWS (récupération du système)** » (boote l'initramfs fallback + l'app de réparation). Si GRUB lui-même est mort : booter l'ISO → entrée « **Réparer FWS (recovery)** ». |
| Réparer depuis le système installé (il boote encore) | Menu d'applications → « **Récupération FWS** » (mot de passe admin demandé une fois — polkit). |
| Réinstallation par-dessus une install existante | Le kickstart nettoie tout (`zerombr` + `clearpart --all`), mais en cas de comportement étrange au 2ᵉ passage, effacer/recréer le disque virtuel. |
| Boot BIOS | Fonctionne (résolu) : syslinux est sous `/boot/syslinux` de l'ISO (pas `/syslinux`), El-Torito BIOS + UEFI présents ; install BIOS complète validée en VM. Le menu BIOS démarre l'entrée FWS par défaut (`DEFAULT fws`). |
| Splash « Préparation de l'installateur » figé | **RAM insuffisante** (reproduit : gel à 1 Go, OK à 2 Go, aucun swap sur le live avant zram). En VM : allouer **4 Go** (le wizard VMware « Other 64-bit » n'en met que 1 !). Le live avertit désormais sous 2 Go, embarque un swap zram, et le splash affiche l'activité réelle d'anaconda (+ diagnostic auto si les logs ne bougent plus). |
| Dialogue « préparation bloquée : diagnostic » PAR-DESSUS l'installateur | Bug corrigé (ISO ≤ 2026-07-07 01:04) : le watchdog du splash se déclenchait alors qu'anaconda était à l'écran (logs silencieux = normal quand il attend l'utilisateur). Il se désarme désormais dès l'apparition de la GUI ; ferme le dialogue et continue, ou rebuild. |
| « Possibly missing firmware for module … » pendant une réparation Recovery | Avertissements `mkinitcpio` **normaux** : firmware de matériel exotique (SCSI/fibre channel…) absent de `linux-firmware`. Sans effet, sauf si tu possèdes ce matériel précis. Le journal Recovery l'explique désormais. |
| Recovery : « tout est grisé » | Corrigé : seul le sélecteur était désactivé (une seule installation détectée) mais toute l'app semblait morte ; il reste actif désormais. Les boutons d'action, eux, ont toujours été fonctionnels. |
| Retours à la ligne CRLF | `build.sh` lance `dos2unix` automatiquement, mais si tu modifies pendant le build, relancer. | | Retours à la ligne CRLF | `build.sh` lance `dos2unix` automatiquement, mais si tu modifies pendant le build, relancer. |
### Logs utiles ### Logs utiles
@@ -256,11 +306,16 @@ L'ISO finale est générée dans `./out/FWS-YYYY.MM.DD-x86_64.iso`.
# Pendant le build # Pendant le build
tail -f /tmp/fws-build/work/iso/arch/pkglist.x86_64.txt tail -f /tmp/fws-build/work/iso/arch/pkglist.x86_64.txt
# Dans l'ISO (autologin tty1) # Dans le LIVE (Ctrl+Alt+F2, login root) — pendant/après l'installation
journalctl -b journalctl -b # système
cat /tmp/fws-liveinst.log # sortie complète de liveinst/anaconda
cat /tmp/anaconda.log # log principal anaconda
cat /tmp/fws-post-chroot.log # %post chrooté = grub-install/mkinitcpio
# En cas d'échec, l'écran de debug du live affiche déjà tout ça (zenity).
# Hyprland post-install # Sur le système installé
cat ~/.local/share/hyprland/hyprland.log cat /var/log/fws-post.log # trace complète du %post d'installation
journalctl -b # premier démarrage
``` ```
--- ---
+26 -4
View File
@@ -47,12 +47,24 @@ Le système live héritait de l'`os-release` du paquet `filesystem` d'Arch et
donc sans conflit, et est retiré du système final par donc sans conflit, et est retiré du système final par
`zzzz99-remove-custom-hooks-from-airootfs.hook`. `zzzz99-remove-custom-hooks-from-airootfs.hook`.
- **`motd`** : ne référence plus `archlinux.org` ; pointe vers le dépôt FWS. - **`motd`** : ne référence plus `archlinux.org` ; pointe vers le dépôt FWS.
- **Menu de boot BIOS (2026-07-07)** : l'image de fond `syslinux/splash.png`
héritée d'archiso affichait le **logo Arch Linux plein écran** — remplacée
par un visuel FWS original (générique, sans logo tiers).
- **Bannière de login (`/etc/issue`)** : `\S (\l)` — affiche le `PRETTY_NAME`
FWS, sans la version du noyau (dont le suffixe `-archN`).
- **Textes utilisateur** : `fws-about` dit « distribution Linux » ; le splash
de préparation et le journal de l'app Récupération remplacent à l'affichage
les mentions « Arch Linux »/« Fedora » issues des logs d'outils tiers (les
identifiants techniques réels — `archlinux-keyring`, miroirs — restent
intacts : usage nominatif fonctionnel).
> Note technique : `ID=fws` + `ID_LIKE=arch` préserve la détection « famille > Note technique : `ID=fws` + `ID_LIKE=arch` préserve la détection « famille
> Arch » des outils tiers (et satisfait les checks `^ID(_LIKE)?=.*arch` de > Arch » des outils tiers (et satisfait les checks `^ID(_LIKE)?=.*arch` de
> `build.sh`/`setup-aur.sh`, qui portent de toute façon sur l'hôte de build). > `build.sh`/`setup-aur.sh`, qui portent de toute façon sur l'hôte de build).
> Si la migration Anaconda révèle un composant testant `ID=arch` en dur, basculer > Si la migration Anaconda révèle un composant testant `ID=arch` en dur, basculer
> transitoirement le hook sur `ID=arch` tout en gardant `NAME`/`PRETTY_NAME` FWS. > transitoirement le hook sur `ID=arch` tout en gardant `NAME`/`PRETTY_NAME` FWS.
> Le suffixe `-archN` de `uname -r` vient du binaire du noyau Arch : usage
> nominatif inévitable sans recompiler le kernel.
--- ---
@@ -60,7 +72,7 @@ Le système live héritait de l'`os-release` du paquet `filesystem` d'Arch et
> **Red Hat®** et **Fedora®** sont des marques déposées de **Red Hat, Inc.** > **Red Hat®** et **Fedora®** sont des marques déposées de **Red Hat, Inc.**
> L'installateur **Anaconda** est développé par Red Hat. FWS distribue une > L'installateur **Anaconda** est développé par Red Hat. FWS distribue une
> **version MODIFIÉE** d'Anaconda (portée sur Arch, payload `liveimg`), **non > **version MODIFIÉE** d'Anaconda (portée sur Arch, payload LiveOS), **non
> officielle**, **non fournie ni approuvée** par Red Hat ou le projet Fedora. > officielle**, **non fournie ni approuvée** par Red Hat ou le projet Fedora.
> *« This product includes a modified version of Anaconda. Red Hat and Fedora > *« This product includes a modified version of Anaconda. Red Hat and Fedora
> are trademarks of Red Hat, Inc. This is an unofficial build, not provided or > are trademarks of Red Hat, Inc. This is an unofficial build, not provided or
@@ -74,9 +86,19 @@ Engagements FWS :
passer une version modifiée pour officielle) ; passer une version modifiée pour officielle) ;
- **recommandation béton :** retirer tout **logo / nom de produit Red Hat ou - **recommandation béton :** retirer tout **logo / nom de produit Red Hat ou
Fedora** de l'interface de l'installateur si présent, et y afficher Fedora** de l'interface de l'installateur si présent, et y afficher
« Installateur FWS (basé sur Anaconda) ». La configuration produit d'Anaconda « Installateur FWS (basé sur Anaconda) ».
se règle via `product.d` / les données de branding — à auditer pendant la
migration. ### ✅ Remédiation appliquée (2026-07-07 — l'installateur s'annonce « FWS »)
Sans `/.buildstamp`, Anaconda s'annonçait « ANACONDA BLUESKY INSTALLATION » et
ses logs parlent de Fedora. `pyanaconda/core/product.py` donne priorité aux
variables d'environnement : le `.xinitrc` du live exporte
`ANACONDA_PRODUCTNAME`/`ANACONDA_PRODUCTVERSION` (depuis l'os-release FWS) et
`ANACONDA_ISFINAL=true` → l'en-tête et l'accueil affichent **« FWS 0.6 »**. Le
splash de préparation filtre en outre les mentions Fedora des lignes de log
affichées. Les mentions profondes dans les **logs techniques** (fichiers
`/tmp/*.log`, écrans de debug) sont conservées volontairement : identification
loyale du composant amont (version modifiée d'Anaconda), utile au diagnostic.
--- ---
+1 -1
View File
@@ -1 +1 @@
0.5.3 0.6.0
+116 -30
View File
@@ -2,10 +2,13 @@
# ============================================================ # ============================================================
# FWS — Build OFFICIEL versionné de l'ISO (archiso) # FWS — Build OFFICIEL versionné de l'ISO (archiso)
# #
# Identique à build.sh dans sa structure, mais : # IDENTIQUE à build.sh (même bascule conteneur/WSL, --network=host, fusion
# - lit la version dans le fichier VERSION (format Major.Minor.Build) # baseline, MÊME injection local-repo → donc même paquet anaconda avec le champ
# - nomme l'ISO FWS-vMAJOR-MINOR-BUILD-DATE-x86_64.iso # Hostname), avec EN PLUS uniquement le RENOMMAGE :
# - range l'ISO dans out/vMAJOR/vMAJOR.MINOR/ # - demande la version (major.minor.build) + le nom de variante, le fichier
# VERSION servant de défaut,
# - nomme l'ISO fws-<version>-<nom>.iso (ex. fws-1.5.0-cli.iso),
# - range l'ISO dans out/vMAJOR/vMAJOR.MINOR/.
# #
# Fonctionne sur : # Fonctionne sur :
# - Windows 10 / 11 (Git Bash → bascule auto dans WSL Arch) # - Windows 10 / 11 (Git Bash → bascule auto dans WSL Arch)
@@ -121,6 +124,37 @@ if [ -z "$FWS_IN_CONTAINER" ] && ! host_is_arch; then
exit 1 exit 1
} }
# La version + le nom sont DEMANDÉS sur l'hôte (avant le conteneur) et passés
# via -e, pour que le prompt s'affiche au bon endroit (terminal de l'hôte).
# (Définis FWS_ISO_VERSION / FWS_ISO_TAG ; lus plus bas, prompt sauté si déjà
# fournis — c.-à-d. dans le conteneur.)
ask_iso_meta_host() {
[ -n "$FWS_ISO_VERSION" ] && [ -n "$FWS_ISO_TAG" ] && return 0
local def=""
if [ -f "$(dirname "${BASH_SOURCE[0]}")/VERSION" ]; then
def="$(tr -d ' \t\r\n' < "$(dirname "${BASH_SOURCE[0]}")/VERSION")"
[[ "$def" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || def=""
fi
while :; do
if [ -n "$def" ]; then
read -rp "==> Version (major.minor.build) [Entrée = $def] : " FWS_ISO_VERSION || exit 1
[ -n "$FWS_ISO_VERSION" ] || FWS_ISO_VERSION="$def"
else
read -rp "==> Version (major.minor.build, ex. 1.5.0) : " FWS_ISO_VERSION || exit 1
fi
[[ "$FWS_ISO_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] && break
echo -e "\e[31m Format attendu : major.minor.build (ex. 1.5.0)\e[0m" >&2
done
while :; do
read -rp "==> Nom de la variante (ex. cli, gnome, kde) : " FWS_ISO_TAG || exit 1
FWS_ISO_TAG="$(printf '%s' "$FWS_ISO_TAG" | tr '[:upper:]' '[:lower:]' | tr -cd 'a-z0-9-')"
[ -n "$FWS_ISO_TAG" ] && break
echo -e "\e[31m Nom requis (lettres, chiffres, tirets)\e[0m" >&2
done
export FWS_ISO_VERSION FWS_ISO_TAG
}
ask_iso_meta_host
# Rootful : préfixe sudo si on n'est pas déjà root. # Rootful : préfixe sudo si on n'est pas déjà root.
SUDO=(); [ "$EUID" -eq 0 ] || SUDO=(sudo) SUDO=(); [ "$EUID" -eq 0 ] || SUDO=(sudo)
# -it seulement si on est dans un terminal (sinon podman casse en CI). # -it seulement si on est dans un terminal (sinon podman casse en CI).
@@ -138,8 +172,14 @@ if [ -z "$FWS_IN_CONTAINER" ] && ! host_is_arch; then
"${SUDO[@]}" podman pull "$IMAGE" "${SUDO[@]}" podman pull "$IMAGE"
echo "==> Relance dans le conteneur ($SELF)..." echo "==> Relance dans le conteneur ($SELF)..."
"${SUDO[@]}" podman run --rm "${TTY[@]}" --privileged \ # --network=host : le réseau bridge de podman ne route plus vers l'extérieur
# sur ce wifi (firewalld/NAT cassé après changement de réseau → « core.db :
# Resolving timed out », même avec --dns). En partageant la pile réseau de
# l'hôte (qui, lui, a internet + DNS via 127.0.0.53/systemd-resolved joignable
# dans son propre netns), le conteneur récupère une connectivité identique.
"${SUDO[@]}" podman run --rm "${TTY[@]}" --network=host --privileged \
-e FWS_IN_CONTAINER=1 \ -e FWS_IN_CONTAINER=1 \
-e FWS_ISO_VERSION -e FWS_ISO_TAG \
-v "$REPO_DIR":"$REPO_DIR" \ -v "$REPO_DIR":"$REPO_DIR" \
-v fws-pacman-cache:/var/cache/pacman/pkg \ -v fws-pacman-cache:/var/cache/pacman/pkg \
-w "$REPO_DIR" \ -w "$REPO_DIR" \
@@ -168,7 +208,8 @@ command -v pacman >/dev/null \
# $0 est cité pour gérer les chemins à espaces (ex. "1. FWS"). # $0 est cité pour gérer les chemins à espaces (ex. "1. FWS").
if [ "$EUID" -ne 0 ]; then if [ "$EUID" -ne 0 ]; then
if command -v sudo >/dev/null 2>&1; then if command -v sudo >/dev/null 2>&1; then
exec sudo bash "$0" "$@" # -E : conserve FWS_ISO_VERSION / FWS_ISO_TAG (demandés avant) à travers sudo.
exec sudo -E bash "$0" "$@"
else else
exec su -c "bash \"$0\"" root exec su -c "bash \"$0\"" root
fi fi
@@ -188,29 +229,48 @@ WORK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
echo "==> Dossier de travail : $WORK_DIR" echo "==> Dossier de travail : $WORK_DIR"
# ============================================================ # ============================================================
# Lecture et validation de la version (fichier VERSION) # Version (major.minor.build) + nom de la variante.
# Déjà demandés sur l'hôte (FWS_ISO_VERSION/FWS_ISO_TAG passés via -e) ; sinon
# (exécution native Arch / WSL directe) on les demande ici. Le fichier VERSION
# sert de défaut (Entrée = le reprendre). Produit fws-<version>-<nom>.iso.
# ============================================================ # ============================================================
VERSION_FILE="$WORK_DIR/VERSION" VERSION="$FWS_ISO_VERSION"
[ -f "$VERSION_FILE" ] \ ISO_TAG="$FWS_ISO_TAG"
|| { echo -e "\e[31mFichier VERSION introuvable : $VERSION_FILE\e[0m"; exit 1; } VERSION_DEFAULT=""
if [ -f "$WORK_DIR/VERSION" ]; then
# Trim espaces / tabulations / CR / LF VERSION_DEFAULT="$(tr -d ' \t\r\n' < "$WORK_DIR/VERSION")"
VERSION="$(tr -d ' \t\r\n' < "$VERSION_FILE")" [[ "$VERSION_DEFAULT" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || VERSION_DEFAULT=""
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo -e "\e[31mVERSION invalide : '$VERSION' (attendu Major.Minor.Build, ex. 1.2.3)\e[0m"
exit 1
fi fi
while [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; do
if [ -n "$VERSION_DEFAULT" ]; then
read -rp "==> Version (major.minor.build) [Entrée = $VERSION_DEFAULT] : " VERSION \
|| { echo -e "\e[31mEntrée requise — lance le script en interactif.\e[0m" >&2; exit 1; }
[ -n "$VERSION" ] || VERSION="$VERSION_DEFAULT"
else
read -rp "==> Version (major.minor.build, ex. 1.5.0) : " VERSION \
|| { echo -e "\e[31mEntrée requise — lance le script en interactif.\e[0m" >&2; exit 1; }
fi
[[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] \
|| echo -e "\e[31m Format attendu : major.minor.build (ex. 1.5.0)\e[0m" >&2
done
while [ -z "$ISO_TAG" ]; do
read -rp "==> Nom de la variante (ex. cli, gnome, kde) : " ISO_TAG \
|| { echo -e "\e[31mEntrée requise — lance le script en interactif.\e[0m" >&2; exit 1; }
ISO_TAG="$(printf '%s' "$ISO_TAG" | tr '[:upper:]' '[:lower:]' | tr -cd 'a-z0-9-')"
[ -n "$ISO_TAG" ] || echo -e "\e[31m Nom requis (lettres, chiffres, tirets)\e[0m" >&2
done
IFS='.' read -r MAJOR MINOR BUILD <<< "$VERSION" IFS='.' read -r MAJOR MINOR BUILD <<< "$VERSION"
BUILD_DATE="$(date +%Y.%m.%d)" BUILD_DATE="$(date +%Y.%m.%d)"
ISO_VERSION="v${MAJOR}-${MINOR}-${BUILD}-${BUILD_DATE}" # iso_version interne (passé à mkarchiso) ; l'ISO finale est renommée plus bas.
ISO_VERSION="${VERSION}-${ISO_TAG}"
OUT_DIR="$WORK_DIR/out/v${MAJOR}/v${MAJOR}.${MINOR}" OUT_DIR="$WORK_DIR/out/v${MAJOR}/v${MAJOR}.${MINOR}"
ISO_FILE="$OUT_DIR/FWS-${ISO_VERSION}-x86_64.iso" # Date ajoutée À LA FIN du nom, avant .iso → fws-<version>-<nom>-<AAAA.MM.JJ>.iso
ISO_FILE="$OUT_DIR/fws-${VERSION}-${ISO_TAG}-${BUILD_DATE}.iso"
echo "==> Version officielle : ${MAJOR}.${MINOR}.${BUILD}" echo "==> Version : ${VERSION} · variante : ${ISO_TAG} · date : ${BUILD_DATE}"
echo "==> Dossier de sortie : $OUT_DIR" echo "==> ISO finale : fws-${VERSION}-${ISO_TAG}-${BUILD_DATE}.iso"
echo "==> ISO finale : FWS-${ISO_VERSION}-x86_64.iso" echo "==> Dossier : $OUT_DIR"
# pacman ≥ 7 sandboxe les ops FS via landlock, non supporté par le kernel # pacman ≥ 7 sandboxe les ops FS via landlock, non supporté par le kernel
# WSL ni (selon le noyau) par le conteneur → désactivation. Idempotent, # WSL ni (selon le noyau) par le conteneur → désactivation. Idempotent,
@@ -249,8 +309,8 @@ if [ -d "$WORK_DIR/configs/baseline" ]; then
fi fi
# ── Injection de la version dans profiledef.sh (copie temporaire) ── # ── Injection de la version dans profiledef.sh (copie temporaire) ──
# mkarchiso nomme l'ISO ${iso_name}-${iso_version}-${arch}.iso. # mkarchiso nomme l'ISO ${iso_name}-${iso_version}-${arch}.iso ; on force
# On force iso_version pour obtenir vMAJOR-MINOR-BUILD-DATE. # iso_version pour obtenir le nom voulu (l'ISO est renommée fws-… plus bas).
if [ -f /tmp/fws-build/releng/profiledef.sh ]; then if [ -f /tmp/fws-build/releng/profiledef.sh ]; then
sed -i "s|^iso_version=.*|iso_version=\"${ISO_VERSION}\"|" \ sed -i "s|^iso_version=.*|iso_version=\"${ISO_VERSION}\"|" \
/tmp/fws-build/releng/profiledef.sh /tmp/fws-build/releng/profiledef.sh
@@ -293,13 +353,39 @@ echo "==> Correction des retours à la ligne Windows (dos2unix)..."
pacman -S --needed --noconfirm dos2unix pacman -S --needed --noconfirm dos2unix
find /tmp/fws-build/releng -type f -exec dos2unix {} + 2>/dev/null find /tmp/fws-build/releng -type f -exec dos2unix {} + 2>/dev/null
# ── Pré-vol : résolution de TOUS les paquets du profil ──
# « error: target not found » ne doit plus surgir en plein mkarchiso : on
# résout la liste complète (packages.x86_64 fusionné) contre le pacman.conf
# du profil (fws-local injecté inclus) dans une base temporaire. S'il manque
# un paquet, on échoue ICI avec la liste + le geste correctif.
echo "==> Pré-vol : résolution des paquets du profil..."
PREFLIGHT_DB="$(mktemp -d)"
pacman -Sy --config /tmp/fws-build/releng/pacman.conf --dbpath "$PREFLIGHT_DB" >/dev/null
mapfile -t PROFILE_PKGS < <(sed -e 's/#.*//' -e 's/[[:space:]]//g' -e '/^$/d' \
/tmp/fws-build/releng/packages.x86_64)
# -Sp n'installe rien (affiche les URLs) ; LC_ALL=C fige le motif d'erreur.
# stderr → capture, stdout → poubelle ; le pipeline sed sort toujours 0.
MISSING="$(LC_ALL=C pacman -Sp --config /tmp/fws-build/releng/pacman.conf \
--dbpath "$PREFLIGHT_DB" "${PROFILE_PKGS[@]}" 2>&1 >/dev/null \
| sed -n 's/^error: target not found:[[:space:]]*//p')"
rm -rf "$PREFLIGHT_DB"
if [ -n "$MISSING" ]; then
echo -e "\e[31mPaquets introuvables dans les dépôts du profil :\e[0m" >&2
printf ' \e[31m%s\e[0m\n' $MISSING >&2
echo -e "\e[33mDépôt fws-local absent/incomplet ? Lance ./setup-aur.sh (construit" >&2
echo -e "anaconda, winboat-bin, fws-dnd, …) puis relance ce script.\e[0m" >&2
exit 1
fi
echo "==> Pré-vol OK : ${#PROFILE_PKGS[@]} paquets résolus."
echo "==> Lancement de mkarchiso..." echo "==> Lancement de mkarchiso..."
mkdir -p "$OUT_DIR" mkdir -p "$OUT_DIR"
# mkarchiso supprime l'ISO cible avant de la réécrire : si elle est VERROUILLÉE # mkarchiso fait un « rm » interne de l'ISO cible avant de la ()écrire ; si
# par Windows (VM ouverte dessus, ISO montée), ce rm casse tout le build à la # une ISO du même nom est VERROUILLÉE par Windows (VM encore ouverte dessus,
# fin. Parade : staging neuf puis déplacement ; si la cible (cette même # ISO montée, antivirus), ce rm échoue et tout le build casse à la toute fin.
# version) est verrouillée, on bascule sur un nom horodaté. On ne touche # Parade : mkarchiso écrit dans un dossier de staging neuf, puis on déplace
# jamais aux ISO des autres versions. # l'ISO. Si la cible (cette même version) est verrouillée, on bascule sur un
# nom horodaté → on ne touche jamais aux ISO des autres versions.
STAGE="$OUT_DIR/.staging-$$" STAGE="$OUT_DIR/.staging-$$"
rm -rf "$STAGE"; mkdir -p "$STAGE" rm -rf "$STAGE"; mkdir -p "$STAGE"
mkarchiso -v -w /tmp/fws-build/work -o "$STAGE" /tmp/fws-build/releng mkarchiso -v -w /tmp/fws-build/work -o "$STAGE" /tmp/fws-build/releng
@@ -319,4 +405,4 @@ echo "==> Nettoyage..."
rm -rf /tmp/fws-build/work rm -rf /tmp/fws-build/work
echo "==> Build officiel terminé ! ISO : $FINAL" echo "==> Build officiel terminé ! ISO : $FINAL"
echo -e "\e[32mScript de build officiel (v${MAJOR}.${MINOR}.${BUILD}) terminé avec succès.\e[0m" echo -e "\e[32mScript de build officiel (fws-${VERSION}-${ISO_TAG}-${BUILD_DATE}) terminé avec succès.\e[0m"
+6 -1
View File
@@ -133,7 +133,12 @@ if [ -z "$FWS_IN_CONTAINER" ] && ! host_is_arch; then
"${SUDO[@]}" podman pull "$IMAGE" "${SUDO[@]}" podman pull "$IMAGE"
echo "==> Relance dans le conteneur ($SELF)..." echo "==> Relance dans le conteneur ($SELF)..."
"${SUDO[@]}" podman run --rm "${TTY[@]}" --privileged \ # --network=host : le réseau bridge de podman ne route plus vers l'extérieur
# sur ce wifi (firewalld/NAT cassé après changement de réseau → « core.db :
# Resolving timed out », même avec --dns). En partageant la pile réseau de
# l'hôte (qui, lui, a internet + DNS via 127.0.0.53/systemd-resolved joignable
# dans son propre netns), le conteneur récupère une connectivité identique.
"${SUDO[@]}" podman run --rm "${TTY[@]}" --network=host --privileged \
-e FWS_IN_CONTAINER=1 \ -e FWS_IN_CONTAINER=1 \
-v "$REPO_DIR":"$REPO_DIR" \ -v "$REPO_DIR":"$REPO_DIR" \
-v fws-pacman-cache:/var/cache/pacman/pkg \ -v fws-pacman-cache:/var/cache/pacman/pkg \
+136 -20
View File
@@ -20,30 +20,146 @@ _OB_PID=$!
# boucle (symptôme « fond bleu qui clignote/reboote »). On NE rend jamais la main # boucle (symptôme « fond bleu qui clignote/reboote »). On NE rend jamais la main
# à startx : on reste sur Openbox pour lire les logs / rebooter proprement. # à startx : on reste sur Openbox pour lire les logs / rebooter proprement.
if command -v liveinst >/dev/null 2>&1; then if command -v liveinst >/dev/null 2>&1; then
# Chantier D : RÉCAPITULATIF matériel AVANT de lancer l'installateur. # Payload LiveOS : avec --liveinst (passé par liveinst), Anaconda IGNORE le
# fws-hw-detect est la MÊME source de vérité que le %post du kickstart # payload du kickstart (anaconda.py teste opts.liveinst AVANT le type du
# (mode --env) → ce que cet écran annonce = ce que l'install appliquera # payload DBus — un « liveimg » dans le ks serait du code mort) et détecte
# (drivers GPU, guest tools de l'hyperviseur, effacement des disques…). # l'image live sur des chemins Fedora/dracut : /dev/mapper/live-base,
# « Éteindre » est le seul moyen propre d'annuler : on ne rend jamais la # /dev/mapper/live-osimg-min ou /run/rootfsbase — tous ABSENTS d'archiso →
# main à startx (cf. plus bas), et liveinst n'a pas encore touché au disque. # « SourceSetupError: No Live OS image found! » = crash au démarrage
# Tout est best-effort : sans script ou sans zenity, on installe comme avant. # (reproduit hors VM dans un chroot de l'airootfs). On expose donc le
if command -v fws-hw-detect >/dev/null 2>&1; then # squashfs archiso (monté ro sur /run/archiso/airootfs) là où Anaconda le
fws-hw-detect --text >/tmp/fws-hw-summary.txt 2>/dev/null || true # cherche : findmnt y résout le loop device → montage → rsync vers la cible.
if command -v zenity >/dev/null 2>&1 && [ -s /tmp/fws-hw-summary.txt ]; then if [ -d /run/archiso/airootfs ] && [ ! -e /run/rootfsbase ]; then
if ! zenity --text-info --title="FWS — Récapitulatif avant installation" \ mkdir -p /run/rootfsbase
--filename=/tmp/fws-hw-summary.txt \ mount --bind /run/archiso/airootfs /run/rootfsbase || rmdir /run/rootfsbase
--ok-label="Lancer l'installateur" --cancel-label="Éteindre" \
--width=800 --height=700 2>/dev/null; then
systemctl poweroff
exit 0
fi
fi
fi fi
# GARDE-FOU RAM : le live n'a que le zram comme marge — en dessous de
# ~2 Go, Anaconda (GUI Python + squashfs) peut geler la machine en plein
# « Préparation de l'installateur ». On prévient AVANT plutôt que de
# laisser l'utilisateur croire à un bug.
_MEMKB=$(awk '/^MemTotal:/{print $2}' /proc/meminfo 2>/dev/null)
if [ -n "$_MEMKB" ] && [ "$_MEMKB" -lt 2000000 ] && command -v zenity >/dev/null 2>&1; then
zenity --warning --width=460 --title="FWS — mémoire insuffisante ?" \
--text="<b>Cette machine n'a que $(( _MEMKB / 1024 )) Mo de RAM.</b>\n\nL'installateur graphique peut geler en dessous de 2 Go.\nEn VM : augmente la mémoire (4 Go recommandés) puis redémarre.\n\nOK pour tenter quand même." 2>/dev/null
fi
# ÉCRAN D'ATTENTE immédiat : le démarrage d'anaconda peut prendre 2 à
# 4 minutes (observé en VM VMware : un worker udev se bloque au boot et
# n'est tué par udevd qu'après 45 s — udev.event_timeout). Sans feedback,
# l'utilisateur croit à une panne alors que TOUT va bien. Le splash montre
# EN DIRECT ce que fait anaconda : un nourrisseur écrit sur stdin de
# zenity --progress (« # texte » = mise à jour du libellé) le log anaconda
# le plus récemment modifié + son dernier message + un chrono. Si les logs
# ne bougent plus (vrai blocage), le libellé le DIT (au lieu d'une barre
# qui pulse pour rien) et, au-delà de 4 min d'inactivité, on collecte
# journal+dmesg dans /tmp/fws-stall.txt et on l'affiche : l'écran devient
# lui-même le rapport de bug. Le nourrisseur meurt par SIGPIPE quand le
# splash est tué à la sortie de liveinst.
_SPLASH_PID=""
if command -v zenity >/dev/null 2>&1; then
(
_el=0; _quiet=0; _prevstamp=""
while :; do
sleep 3
# GUI d'anaconda à l'écran (marqueur ui.gui) → le splash n'a
# plus de raison d'être et le watchdog DOIT se désarmer : des
# logs silencieux pendant que l'utilisateur remplit les écrans
# sont NORMAUX (bug vécu : le rapport « préparation bloquée »
# s'affichait PAR-DESSUS l'installateur au bout de 4 min).
if grep -q 'ui\.gui' /tmp/anaconda.log 2>/dev/null; then
pkill -f 'zenity --progress' 2>/dev/null
exit 0
fi
# Compteurs en itérations (pas d'heure murale : l'horloge du
# guest SAUTE en VM — resynchro RTC — et gonflerait les âges,
# vu en vrai : « 242 s d'inactivité » 90 s après le boot).
_el=$(( _el + 3 ))
_last=$(ls -t /tmp/storage.log /tmp/anaconda.log /tmp/program.log /tmp/dbus.log 2>/dev/null | head -n1)
if [ -z "$_last" ]; then
printf '# Démarrage d'"'"'Anaconda… (%d min %02d s)\n' $(( _el / 60 )) $(( _el % 60 )) || exit 0
continue
fi
_stamp=$(stat -c %Y "$_last" 2>/dev/null)
if [ "$_stamp" = "$_prevstamp" ]; then
_quiet=$(( _quiet + 3 ))
else
_quiet=0; _prevstamp="$_stamp"
fi
# Rebranding d'affichage : anaconda (installateur Fedora) parle
# de Fedora/Arch dans ses logs — on ne montre pas ça à l'écran.
_msg=$(tail -n1 "$_last" 2>/dev/null | tr -d '\r' \
| sed -E 's/Fedora( Linux)?/FWS/g; s/Arch Linux/FWS/g; s/\<Arch\>/FWS/g' \
| cut -c1-90)
if [ "$_quiet" -ge 240 ] && [ ! -f /tmp/fws-stall.txt ]; then
{
echo "=== FWS : préparation bloquée (aucune activité log depuis ${_quiet}s) ==="
echo "--- dernier log actif : $_last"; tail -n 25 "$_last" 2>/dev/null
echo "--- udev (workers bloqués ?)"; udevadm settle --timeout=0 2>&1
echo "--- journal (fin)"; journalctl -b --no-pager 2>/dev/null | tail -n 60
echo "--- dmesg (fin)"; dmesg 2>/dev/null | tail -n 40
} > /tmp/fws-stall.txt 2>&1
zenity --text-info --title="FWS — préparation bloquée : diagnostic" \
--filename=/tmp/fws-stall.txt --width=1000 --height=650 2>/dev/null &
fi
if [ "$_quiet" -ge 60 ]; then
printf '# ⚠ Aucune activité depuis %d s (%s) — dernier message : %s\n' "$_quiet" "$(basename "$_last")" "$_msg" || exit 0
else
printf '# Préparation (%d min %02d s) — %s : %s\n' $(( _el / 60 )) $(( _el % 60 )) "$(basename "$_last")" "$_msg" || exit 0
fi
done
) | zenity --progress --pulsate --no-cancel \
--title="FWS" \
--text="<b>Préparation de l'installateur FWS…</b>\n\nAnalyse du matériel et des disques en cours.\nEncore une à deux minutes, c'est normal." \
--width=560 2>/dev/null &
_SPLASH_PID=$!
fi
# Hub Résumé SANS le libellé « We won't touch your disks… » : notre glade
# modifié NE PEUT PAS être livré directement à son chemin final —
# mkarchiso pose l'overlay airootfs AVANT pacstrap, et pacman refuse
# alors d'installer anaconda par-dessus (« exists in filesystem »). On le
# livre donc sous /usr/local/share/fws et on ÉCRASE ici, au boot du live
# (l'overlay du live est inscriptible), le fichier du paquet.
if [ -f /usr/local/share/fws/anaconda-summary-hub.glade ]; then
cp -f /usr/local/share/fws/anaconda-summary-hub.glade \
/usr/share/anaconda/ui/hubs/summary.glade 2>/dev/null || true
fi
# BRANDING : sans /.buildstamp, anaconda s'appelle « anaconda bluesky »
# (défauts de pyanaconda/core/product.py) et ses logs parlent de Fedora.
# Les variables d'environnement ANACONDA_* ont priorité → l'en-tête
# devient « FWS 1.0 INSTALLATION » et le log « anaconda … for FWS started ».
# (Les mentions Fedora PROFONDES dans les logs — GeoIP, profils — restent :
# ce sont des chaînes internes de l'installateur, sans effet sur l'install.)
export ANACONDA_PRODUCTNAME="$(. /etc/os-release 2>/dev/null; echo "${NAME:-FWS}")"
export ANACONDA_PRODUCTVERSION="$(. /etc/os-release 2>/dev/null; echo "${VERSION_ID:-0.6}")"
export ANACONDA_ISFINAL=true
LIVEINST_LOG=/tmp/fws-liveinst.log LIVEINST_LOG=/tmp/fws-liveinst.log
# Bus D-Bus de SESSION : au démarrage (--liveinst), inhibit_screensaver()
# de pyanaconda se connecte au bus de session. S'il n'y en a AUCUN (notre
# startx root minimal n'en garantit pas), Gio lève une GLib.GError que
# pyanaconda n'attrape PAS (il n'attrape que DBusError) → crash immédiat
# (2e crash reproduit hors VM). dbus-run-session fournit un bus de session
# pour toute la durée de l'installateur ; avec un bus présent, l'absence du
# service ScreenSaver est attrapée (simple warning dans anaconda.log).
if command -v dbus-run-session >/dev/null 2>&1 && [ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ]; then
dbus-run-session -- liveinst >"$LIVEINST_LOG" 2>&1
else
liveinst >"$LIVEINST_LOG" 2>&1 liveinst >"$LIVEINST_LOG" 2>&1
fi
rc=$? rc=$?
# liveinst terminé (install finie ou échec) : le splash n'a plus de raison
# d'être — kill de zenity ; le nourrisseur meurt de SIGPIPE à son
# prochain printf. Idem pour un éventuel dialogue de diagnostic de stall.
if [ -n "$_SPLASH_PID" ]; then
pkill -P "$_SPLASH_PID" 2>/dev/null
kill "$_SPLASH_PID" 2>/dev/null
fi
pkill -f "fws-stall.txt" 2>/dev/null
# ATTENTION : liveinst NE propage PAS le code de sortie d'anaconda — il # ATTENTION : liveinst NE propage PAS le code de sortie d'anaconda — il
# enchaîne son propre nettoyage et sort souvent 0 même quand anaconda a # enchaîne son propre nettoyage et sort souvent 0 même quand anaconda a
# planté. On ne se fie donc PAS à $rc : en phase debug on regroupe TOUS les # planté. On ne se fie donc PAS à $rc : en phase debug on regroupe TOUS les
@@ -87,8 +203,8 @@ if command -v liveinst >/dev/null 2>&1; then
echo "########## %post --nochroot (/tmp/fws-post-nochroot.log) ##########" echo "########## %post --nochroot (/tmp/fws-post-nochroot.log) ##########"
cat /tmp/fws-post-nochroot.log 2>/dev/null || echo "(absent)" cat /tmp/fws-post-nochroot.log 2>/dev/null || echo "(absent)"
echo echo
echo "########## Récapitulatif matériel montré avant l'install ##########" echo "########## Récapitulatif (page GUI fws_recap, avant install) ##########"
cat /tmp/fws-hw-summary.txt 2>/dev/null || echo "(absent)" cat /tmp/fws-hw-summary.txt 2>/dev/null || echo "(absent — page récap pas atteinte)"
for f in /tmp/anaconda.log /tmp/syslog /tmp/program.log \ for f in /tmp/anaconda.log /tmp/syslog /tmp/program.log \
/tmp/storage.log /tmp/dbus.log /tmp/packaging.log /tmp/X.log; do /tmp/storage.log /tmp/dbus.log /tmp/packaging.log /tmp/X.log; do
echo echo
+14 -1
View File
@@ -1,5 +1,18 @@
# Démarrage automatique de Xorg uniquement dans le live FWS. # Démarrage automatique dans le live FWS uniquement.
# Sur le système installé, /run/archiso n'existe pas, donc rien ne se lance. # Sur le système installé, /run/archiso n'existe pas, donc rien ne se lance.
# Mode RÉPARATION (entrée de boot « Réparer FWS », paramètre fws.recovery=1) :
# on démarre X + l'application GTK (fws-recovery-session lance openbox puis
# fws-recovery-gui, qui pilote le moteur fws-recovery). Si X échoue à
# démarrer, startx rend la main → repli sur l'assistant console fws-recovery.
# (Sur le SYSTÈME INSTALLÉ, l'équivalent est fws-recovery-boot.service via
# l'entrée GRUB « Réparer FWS » — pas ce fichier.)
if [[ -d /run/archiso && "$(tty)" == "/dev/tty1" ]] \
&& grep -qw 'fws.recovery=1' /proc/cmdline 2>/dev/null; then
startx /usr/local/bin/fws-recovery-session -- vt1
exec fws-recovery
fi
if [[ -d /run/archiso && "$(tty)" == "/dev/tty1" && -z "$DISPLAY" ]]; then if [[ -d /run/archiso && "$(tty)" == "/dev/tty1" && -z "$DISPLAY" ]]; then
exec startx -- vt1 exec startx -- vt1
fi fi
@@ -0,0 +1,106 @@
# FWS — Spoke « Desktop Environment » : nouvel onglet de choix du bureau.
#
# POURQUOI ICI (airootfs, pas dans le paquet anaconda) : Anaconda découvre les
# spokes au RUNTIME via collect()/collect_spokes (os.listdir du dossier des
# spokes puis import de chaque .py). Déposer ce fichier dans le dossier des
# spokes via l'overlay airootfs suffit donc à le faire apparaître — SANS rebuild
# du paquet anaconda (juste ./build.sh). Le glade associé est dans
# /usr/share/anaconda/ui/spokes/fws_desktop.glade (aussi via l'airootfs).
#
# ⚠ Le chemin /usr/lib/python3.14/... est couplé à la version de Python d'Arch
# (3.14 au moment du build d'anaconda). À mettre à jour si Python change de
# version majeure (en même temps que le rebuild d'anaconda de toute façon).
#
# FONCTION : afficher 5 choix (CLI/GNOME/KDE/Hyprland/XFCE) et écrire la clé
# choisie dans /tmp/fws-desktop. Le %post du kickstart lit ce fichier, fait
# « pacman -S » du bureau + d'un navigateur (Firefox), et active le display
# manager. « cli » (défaut) = aucun bureau, rien à installer.
from pyanaconda.core.i18n import CN_, _
from pyanaconda.ui.categories.software import SoftwareCategory
from pyanaconda.ui.gui.spokes import NormalSpoke
__all__ = ["FWSDesktopSpoke"]
# clé interne (écrite dans /tmp/fws-desktop) → libellé affiché dans le statut.
_CHOICES = [
("cli", _("Command line only (no desktop)")),
("gnome", "GNOME"),
("kde", "KDE Plasma"),
("hyprland", "Hyprland — Wayland (non compatible VR)"),
("i3", "i3 — X11 (compatible VR : SteamVR)"),
("xfce", "XFCE"),
]
_DESKTOP_FILE = "/tmp/fws-desktop"
class FWSDesktopSpoke(NormalSpoke):
"""Choix de l'environnement de bureau FWS (écrit /tmp/fws-desktop)."""
builderObjects = ["fwsDesktopWindow"]
mainWidgetName = "fwsDesktopWindow"
uiFile = "spokes/fws_desktop.glade"
category = SoftwareCategory
icon = "preferences-desktop-display-symbolic"
title = CN_("GUI|Spoke", "_Desktop Environment")
@staticmethod
def get_screen_id():
"""Identifiant unique de cet écran."""
return "fws-desktop-selection"
def __init__(self, *args):
NormalSpoke.__init__(self, *args)
self._choice = "cli"
self._radios = {}
def initialize(self):
NormalSpoke.initialize(self)
for key, _label in _CHOICES:
self._radios[key] = self.builder.get_object("radio_" + key)
# Reprend un éventuel choix déjà écrit (revisite du spoke).
try:
with open(_DESKTOP_FILE) as fh:
val = fh.read().strip()
if val in self._radios:
self._choice = val
except OSError:
pass
def refresh(self):
# Coche le bouton correspondant au choix courant.
rb = self._radios.get(self._choice) or self._radios.get("cli")
if rb is not None:
rb.set_active(True)
def apply(self):
# Relit le bouton actif et persiste le choix pour le %post du kickstart.
for key, rb in self._radios.items():
if rb is not None and rb.get_active():
self._choice = key
break
try:
with open(_DESKTOP_FILE, "w") as fh:
fh.write(self._choice)
except OSError:
pass
@property
def status(self):
for key, label in _CHOICES:
if key == self._choice:
return label
return _("Command line only (no desktop)")
@property
def completed(self):
# « cli » est un défaut valide → le spoke n'est jamais bloquant.
return True
@property
def mandatory(self):
return False
@property
def ready(self):
return True
@@ -0,0 +1,388 @@
# FWS — Page « Récapitulatif » : écran À PART ENTIÈRE avant l'installation.
#
# POURQUOI ICI (airootfs, pas dans le paquet anaconda) : même mécanisme que la
# spoke « Desktop Environment » (fws_desktop.py) — Anaconda découvre les spokes
# au RUNTIME (collect) dans ce dossier → l'overlay airootfs suffit, AUCUN
# rebuild du paquet anaconda. Le glade associé est dans
# /usr/share/anaconda/ui/spokes/fws_recap.glade (aussi via l'airootfs).
#
# PLACEMENT DANS LE FLUX : standalone spoke « postForHub = SummaryHub » avec
# priority = -10. L'ordre des écrans est pre(SummaryHub) → SummaryHub →
# post(SummaryHub) trié par priority croissante (UserInterface.
# _orderActionClasses). ProgressSpoke — l'écran qui DÉMARRE l'installation
# dans son refresh() — est aussi post(SummaryHub), priority 0 par défaut →
# cette page s'intercale ENTRE les deux : l'utilisateur clique « Commencer
# l'installation » sur le hub, voit CE récapitulatif (le disque n'a encore
# subi AUCUNE écriture), et l'installation ne démarre qu'à son clic
# « Lancer l'installation ». Le bouton « Précédent » ramène au hub pour
# modifier les choix (cf. _on_back_clicked : la file d'actions du GUI est
# consommée à chaque écran, on y réinsère le hub).
#
# CONTENU : configuration choisie par l'utilisateur (via les modules D-Bus
# d'Anaconda : langue, clavier, fuseau, comptes, partitionnement + les
# fichiers /tmp des spokes FWS : bureau, hostname) ET matériel détecté
# (virtualisation, GPU, disques, réseau) — mêmes sources que ce que le
# %post du kickstart et fws-setup-hardware appliqueront.
#
# ⚠ « completed » doit rester False : _instantiateAction SAUTE les standalone
# spokes complétées (mécanisme prévu pour les installs kickstart).
# ⚠ Chemin couplé à la version Python d'Arch (3.14) — comme fws_desktop.py —
# et le bouton « Précédent » s'appuie sur les internes du GUI d'anaconda
# 45.8 (_actions/_currentAction) : à re-vérifier à chaque bump d'anaconda.
import inspect
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
from pyanaconda.anaconda_loggers import get_module_logger
from pyanaconda.core import util
from pyanaconda.modules.common.constants.objects import DISK_SELECTION
from pyanaconda.modules.common.constants.services import (
LOCALIZATION,
STORAGE,
TIMEZONE,
USERS,
)
from pyanaconda.modules.common.structures.user import UserData
from pyanaconda.ui.gui import GraphicalUserInterface
from pyanaconda.ui.gui.hubs.summary import SummaryHub
from pyanaconda.ui.gui.spokes import StandaloneSpoke
log = get_module_logger(__name__)
__all__ = ["FWSRecapSpoke"]
# Doit rester aligné sur le %post du kickstart (étape « Bureau choisi ») et
# sur les libellés de la spoke fws_desktop.
_DESKTOP_LABELS = {
"cli": "ligne de commande uniquement (aucun bureau)",
"gnome": "GNOME (+ Firefox, GDM)",
"kde": "KDE Plasma (+ Firefox, SDDM)",
"hyprland": "Hyprland — Wayland, non compatible VR (+ Firefox, SDDM)",
"i3": "i3 — X11, compatible VR/SteamVR (+ Firefox, SDDM)",
"xfce": "XFCE (+ Firefox, SDDM)",
}
_PARTITIONING_LABELS = {
"AUTOMATIC": "automatique (recommandé)",
"INTERACTIVE": "personnalisé (assistant)",
"CUSTOM": "personnalisé",
"BLIVET": "avancé (blivet-gui)",
"MANUAL": "points de montage manuels",
}
def _safe(getter, default):
"""Valeur d'un getter D-Bus/système, ou défaut : le récap ne doit JAMAIS
faire tomber l'installateur, quel que soit le module indisponible (d'
le except large : dasbus lève des types variés selon la panne)."""
try:
return getter()
except Exception: # pylint: disable=broad-except
return default
def _capture(*argv):
"""stdout d'une commande, chaîne vide si absente ou en échec."""
try:
return util.execWithCapture(argv[0], list(argv[1:])) or ""
except (OSError, RuntimeError):
return ""
def _read_flag(path):
try:
with open(path) as fh:
return fh.read().strip()
except OSError:
return ""
def _config_lines():
"""Section « configuration choisie » : interroge les modules D-Bus
d'Anaconda (remplis par les spokes) + les fichiers /tmp des spokes FWS."""
lines = []
lang = _safe(lambda: LOCALIZATION.get_proxy().Language, "")
if lang:
lines.append(f" • Langue : {lang}")
layouts = _safe(lambda: list(LOCALIZATION.get_proxy().XLayouts), [])
if layouts:
lines.append(f" • Clavier (X11) : {', '.join(layouts)}")
tz = _safe(lambda: TIMEZONE.get_proxy().Timezone, "")
if tz:
ntp = _safe(lambda: TIMEZONE.get_proxy().NTPEnabled, None)
suffix = {True: " — synchronisation NTP activée",
False: " — synchronisation NTP désactivée"}.get(ntp, "")
lines.append(f" • Fuseau horaire : {tz}{suffix}")
users = _safe(
lambda: UserData.from_structure_list(USERS.get_proxy().Users), [])
if users:
names = ", ".join(u.name for u in users if u.name)
lines.append(f" • Utilisateur(s) : {names} (sudo via wheel)")
else:
lines.append(" • Utilisateur(s) : AUCUN — pensez-y avant de lancer !")
root_locked = _safe(lambda: USERS.get_proxy().IsRootAccountLocked, None)
root_pw = _safe(lambda: USERS.get_proxy().IsRootPasswordSet, None)
if root_locked:
root_txt = "verrouillé (recommandé)"
elif root_pw:
root_txt = "actif, mot de passe défini"
elif root_pw is not None:
# Même règle que l'étape « Sécurité » du %post : un root resté sans
# mot de passe sera verrouillé en fin d'installation.
root_txt = "sans mot de passe → sera VERROUILLÉ en fin d'install"
else:
root_txt = ""
if root_txt:
lines.append(f" • Compte root : {root_txt}")
# Hostname : écrit par le spoke Utilisateur (patch user.py du paquet
# anaconda). Même nettoyage / même défaut que le %post --nochroot.
hn = "".join(c for c in _read_flag("/tmp/fws-hostname")
if c.isalnum() or c in "._-") or "fws"
lines.append(f" • Nom de machine : {hn}")
# Bureau : écrit par fws_desktop.apply() (sortie de la spoke). Absent =
# spoke jamais visitée = « cli », exactement le défaut du %post.
desk = _read_flag("/tmp/fws-desktop") or "cli"
lines.append(f" • Bureau : {_DESKTOP_LABELS.get(desk, desk)}")
method = _safe(
lambda: STORAGE.get_proxy(
STORAGE.get_proxy().AppliedPartitioning).PartitioningMethod, "")
if method:
lines.append(" • Partitionnement : "
f"{_PARTITIONING_LABELS.get(method, method)}")
return lines, desk, method
def _windows_dualboot_lines(selected):
"""Section dual-boot Windows : lit /tmp/fws-windows (parse LITTÉRAL, jamais
sourcé), montre le disque qui SERA EFFACÉ et ALERTE en cas de conflit avec la
cible d'installation FWS (même disque choisi pour les deux)."""
state = {}
try:
with open("/tmp/fws-windows") as fh:
for line in fh:
if "=" in line and not line.lstrip().startswith("#"):
key, _, val = line.partition("=")
state[key.strip()] = val.strip()
except OSError:
return []
if state.get("enabled") != "1":
return [" • Dual-boot Windows : désactivé (FWS uniquement)"]
disk = state.get("disk", "?")
size = state.get("size_gib", "?")
iso = state.get("iso", "")
iso_name = iso.rsplit("/", 1)[-1] if iso else "(aucune ISO fournie)"
lines = [
f" • Dual-boot Windows : ACTIVÉ — {disk} ({size} GiO NTFS)",
f" ISO : {iso_name}",
f" ⚠ TOUT le disque {disk} sera EFFACÉ et dédié à Windows.",
]
if state.get("intel_vmd") == "1":
lines.append(" • Support Intel VMD/RST : activé (pilote injecté au boot Windows)")
base = disk.rsplit("/", 1)[-1]
if base and base in selected:
lines.append(
f" ⛔ CONFLIT : {disk} est AUSSI une cible d'installation FWS —"
" choisissez des disques DIFFÉRENTS (sinon l'un écrase l'autre).")
return lines
def _build_summary():
config_lines, desk, method = _config_lines()
config_text = "\n".join(config_lines) \
or " (configuration non disponible)"
# Disques ciblés par l'installation (spoke « Destination »).
selected = set(_safe(
lambda: list(STORAGE.get_proxy(DISK_SELECTION).SelectedDisks), []))
disks = []
for line in _capture("lsblk", "-dno", "NAME,SIZE,MODEL,TYPE").splitlines():
fields = line.split()
if fields and fields[-1] == "disk":
mark = " ← CIBLE de l'installation" if fields[0] in selected else ""
disks.append("" + " ".join(fields[:-1]) + mark)
disk_text = "\n".join(disks) or " (aucun disque détecté)"
if method == "AUTOMATIC":
disk_warn = (
" ⚠ Rien n'a encore été écrit. Le partitionnement automatique\n"
" EFFACERA ENTIÈREMENT le(s) disque(s) cible(s) au lancement.")
else:
disk_warn = (
" ⚠ Rien n'a encore été écrit. Au lancement, ce sont vos choix\n"
" de partitionnement faits dans l'installateur qui s'appliqueront.")
virt = (_capture("systemd-detect-virt").strip().splitlines() or ["none"])[0]
if virt in ("", "none"):
virt = "aucune (machine physique)"
gpus = []
for line in _capture("lspci").splitlines():
low = line.lower()
if ("vga" in low or "3d" in low or "display" in low) and ":" in line:
gpus.append("" + line.split(":", 2)[-1].strip())
gpu_text = "\n".join(gpus) or " (aucune carte graphique détectée)"
try:
net_ok = util.execWithRedirect("ip", ["route", "get", "1.1.1.1"]) == 0
except (OSError, RuntimeError):
net_ok = False
if net_ok:
net = "disponible ✔"
else:
net = ("INDISPONIBLE ✖ — bureau, drivers et stack FWS ne\n"
" s'installeront PAS (rattrapable au 1er boot)")
extras = ("Steam, Wine, VR (Monado/OpenXR), winboat-bin"
if desk != "cli" else "rien de plus (choix CLI)")
win_text = "\n".join(_windows_dualboot_lines(selected)) \
or " • Dual-boot Windows : désactivé (FWS uniquement)"
return f"""\
══════════════════════════════════════════════════════════
FWS — RÉCAPITULATIF AVANT INSTALLATION
══════════════════════════════════════════════════════════
CONFIGURATION CHOISIE
{config_text}
DISQUES
{disk_warn}
{disk_text}
DUAL-BOOT WINDOWS (gaming — Valorant/LoL)
{win_text}
MATÉRIEL DÉTECTÉ
• Virtualisation : {virt}
• Réseau : {net}
Cartes graphiques (drivers installés automatiquement en fin d'install) :
{gpu_text}
DÉROULEMENT DE L'INSTALLATION
1. Partitionnement, puis copie du système live FWS (payload LiveOS)
2. Noyau + initramfs régénérés (mkinitcpio), GRUB installé
3. Bureau choisi + navigateur installés (réseau requis)
4. Matériel : drivers GPU, outils VM, son, Bluetooth, Podman
+ si bureau choisi : {extras}
« Précédent » pour revenir modifier vos choix —
« Lancer l'installation » pour démarrer réellement l'installation.
"""
class FWSRecapSpoke(StandaloneSpoke):
"""Récapitulatif des choix, affiché entre le hub et l'installation."""
builderObjects = ["fwsRecapWindow"]
mainWidgetName = "fwsRecapWindow"
uiFile = "spokes/fws_recap.glade"
postForHub = SummaryHub
priority = -10
@staticmethod
def get_screen_id():
"""Identifiant unique de cet écran."""
return "fws-recap"
@property
def completed(self):
# False, sinon _instantiateAction saute l'écran (cf. en-tête).
return False
def apply(self):
# Rien à persister : cette page ne fait que montrer l'état.
pass
def initialize(self):
StandaloneSpoke.initialize(self)
# Le bouton « continuer » de CETTE page est le vrai déclencheur :
# l'écran suivant (ProgressSpoke) démarre la file d'installation.
self.window.get_continue_button().set_label("_Lancer l'installation")
# Capture pour « Précédent » : initialize() est appelé DEPUIS
# GraphicalUserInterface._on_continue_clicked (clic « Commencer
# l'installation » du hub), AVANT la bascule de _currentAction → à
# cet instant ui._currentAction est ENCORE l'instance du hub Résumé,
# et le « self » de l'appelant est l'UI. Aucune API publique ne les
# expose ; on remonte la pile d'appels (déterministe — pas de scan
# mémoire, pas de variable globale).
self._gui = None
self._hub = None
frame = inspect.currentframe()
try:
frame = frame.f_back
while frame is not None:
candidate = frame.f_locals.get("self")
if isinstance(candidate, GraphicalUserInterface):
self._gui = candidate
current = getattr(candidate, "_currentAction", None)
if isinstance(current, SummaryHub):
self._hub = current
break
frame = frame.f_back
finally:
del frame
# Bouton « Précédent », placé avec les boutons de navigation
# (à côté de « Quitter » — leur parent est la GtkButtonBox du bas).
quit_button = self.window.get_quit_button()
button_box = quit_button.get_parent()
self._back_button = Gtk.Button.new_with_mnemonic("_Précédent")
self._back_button.connect("clicked", self._on_back_clicked)
button_box.add(self._back_button)
button_box.reorder_child(self._back_button, 0)
if self._gui is None or self._hub is None:
# Références introuvables (flux inattendu) : bouton grisé plutôt
# qu'une navigation cassée — l'install reste possible.
log.warning("FWS recap: hub/UI introuvables, « Précédent » désactivé.")
self._back_button.set_sensitive(False)
self._back_button.show()
def _on_back_clicked(self, _button):
ui = self._gui
hub = self._hub
if ui is None or hub is None:
return
log.info("FWS recap: retour au hub Résumé pour modification.")
# La file d'actions du GUI est CONSOMMÉE (pop(0)) à chaque écran :
# ici _actions = [FWSRecapSpoke, ProgressSpoke] et le hub en est déjà
# sorti. On le réinsère en tête pour que le prochain « Commencer
# l'installation » ré-enchaîne hub → NOUVELLE page récap (résumé
# recalculé avec les choix modifiés) → installation.
if not ui._actions or ui._actions[0] is not SummaryHub:
ui._actions.insert(0, SummaryHub)
# _on_continue_clicked a désactivé le « continuer » du hub en le
# quittant : on le réarme, sinon « Commencer l'installation » est mort.
hub.window.set_may_continue(True)
# Même séquence que la transition officielle (_on_continue_clicked).
self.exited.emit(self)
hub.refresh()
ui.mainWindow.setCurrentAction(hub)
hub.entered.emit(hub)
ui._currentAction = hub
def refresh(self):
StandaloneSpoke.refresh(self)
text = _build_summary()
self.builder.get_object("recapView").get_buffer().set_text(text)
# Copie pour le harnais de debug du .xinitrc (fws-debug.txt).
try:
with open("/tmp/fws-hw-summary.txt", "w") as fh:
fh.write(text)
except OSError:
pass
@@ -0,0 +1,343 @@
# FWS — Spoke « Dual-boot Windows gaming » : case + disque + taille + ISO.
#
# POURQUOI ICI (airootfs, pas dans le paquet anaconda) : identique à
# fws_desktop.py / fws_recap.py — Anaconda découvre les spokes au RUNTIME via
# collect() (os.listdir du dossier des spokes puis import). Déposer ce .py dans
# le dossier des spokes via l'overlay airootfs suffit à le faire apparaître SANS
# rebuild du paquet anaconda (juste ./build.sh). Le glade associé est dans
# /usr/share/anaconda/ui/spokes/fws_windows.glade (aussi via l'airootfs).
#
# CATÉGORIE = SystemCategory (pas SoftwareCategory) : c'est du disque/boot, il
# vit sur le hub Résumé à côté de « Destination de l'installation », pas dans
# « Logiciel ».
#
# CE QU'IL FAIT : coche « Dual-boot Windows gaming », choix du disque à dédier à
# Windows, taille NTFS (défaut 200 GiO, min 100, max = taille du disque), chemin
# de l'ISO Windows 11 fournie par l'utilisateur. Il n'exécute AUCUNE opération
# disque : son seul canal vers l'installation est /tmp/fws-windows, relu par le
# kickstart (%post --nochroot : carve + wimlib apply + injection gameboot ;
# %post chroot : swap/resume + Secure Boot). Exactement le modèle fws_desktop.
#
# ⚠ Chemin /usr/lib/python3.14/... couplé à la version de Python d'Arch (3.14 au
# build d'anaconda) — à mettre à jour au bump majeur de Python (= rebuild
# anaconda de toute façon). SystemCategory / internes GUI (anaconda 45.8) à
# re-vérifier à chaque bump, comme l'avertit l'en-tête de fws_recap.py.
import os
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
from pyanaconda.anaconda_loggers import get_module_logger
from pyanaconda.core import util
from pyanaconda.core.i18n import CN_, _
from pyanaconda.ui.categories.system import SystemCategory
from pyanaconda.ui.gui.spokes import NormalSpoke
log = get_module_logger(__name__)
__all__ = ["FWSWindowsSpoke"]
# Contrat de fichier relu par le kickstart (clé=valeur, une par ligne).
_STATE_FILE = "/tmp/fws-windows"
_DEFAULT_SIZE_GIB = 200
_MIN_SIZE_GIB = 100
# Marge à laisser sur le disque en fin de partition NTFS (ESP Windows + MSR).
_TAIL_RESERVE_GIB = 3
def _capture(cmd, args):
try:
return util.execWithCapture(cmd, args) or ""
except (OSError, RuntimeError):
return ""
def _list_disks():
"""[(devnode, libellé, taille_gib)] pour chaque disque physique (lsblk)."""
disks = []
for line in _capture("lsblk", ["-dpno", "NAME,SIZE,MODEL,TYPE"]).splitlines():
parts = line.split()
# NAME SIZE [MODEL les mots du milieu] TYPE ; TYPE = dernier champ.
if len(parts) < 2 or parts[-1] != "disk":
continue
name = parts[0]
size = parts[1]
model = " ".join(parts[2:-1]) or "?"
label = f"{name}{size} {model}"
disks.append((name, label, _disk_size_gib(name)))
return disks
def _disk_size_gib(devnode):
"""Taille du disque en GiO entiers (0 si inconnue)."""
raw = _capture("lsblk", ["-dbno", "SIZE", devnode]).strip().split("\n")[0]
try:
return int(raw) // (1024 ** 3)
except (ValueError, TypeError):
return 0
def _disk_nonempty(devnode):
"""True si le disque porte des partitions (donc des données à écraser)."""
return "part" in _capture("lsblk", ["-rno", "TYPE", devnode]).split()
def _hib_reserve_gib():
"""Réserve de queue = swap d'hibernation (RAM+VRAM+marge) + ESP/MSR, ALIGNÉE
sur fws-windows-deploy (qui exige size_gib <= disque swap 4). Sans ça, le
GUI proposerait une taille NTFS que le déployeur refuserait (ou qui déborde)."""
mem = 0
try:
with open("/proc/meminfo") as fh:
for line in fh:
if line.startswith("MemTotal"):
mem = int(line.split()[1]) // (1024 * 1024)
break
except OSError:
pass
vram = 0
for tok in _capture(
"nvidia-smi",
["--query-gpu=memory.total", "--format=csv,noheader,nounits"]).split():
if tok.isdigit():
vram += int(tok) // 1024
swap = max(96, mem + vram + 4)
return swap + 6 # + ESP (1 GiO) + MSR + marge
def _intel_vmd_present():
"""True si un contrôleur Intel VMD (Volume Management Device) est présent →
le Windows déployé aura besoin du pilote iaStorVD pour voir/booter les NVMe.
Sur AMD ou machine sans VMD : False (case décochée, no-op)."""
return "volume management device" in _capture("lspci", ["-nn"]).lower()
class FWSWindowsSpoke(NormalSpoke):
"""Dual-boot Windows gaming : écrit /tmp/fws-windows (enabled/disk/size/iso)."""
builderObjects = ["adj_size", "fwsWindowsWindow"]
mainWidgetName = "fwsWindowsWindow"
uiFile = "spokes/fws_windows.glade"
category = SystemCategory
icon = "applications-games-symbolic"
title = CN_("GUI|Spoke", "_Dual-boot Windows")
@staticmethod
def get_screen_id():
return "fws-windows-dualboot"
def __init__(self, *args):
NormalSpoke.__init__(self, *args)
# État courant (persisté dans /tmp/fws-windows par apply()).
self._enabled = False
self._disk = ""
self._size = _DEFAULT_SIZE_GIB
self._iso = ""
self._uefi = os.path.exists("/sys/firmware/efi")
self._disks = []
# Réserve de queue alignée sur le swap d'hibernation du déployeur [M7].
self._reserve = _hib_reserve_gib()
# Support Intel VMD/RST : auto-coché si un contrôleur VMD est détecté.
self._vmd = _intel_vmd_present()
# ------------------------------------------------------------------ cycle
def initialize(self):
NormalSpoke.initialize(self)
self._chk = self.builder.get_object("chk_enable")
self._box = self.builder.get_object("box_options")
self._combo = self.builder.get_object("combo_disk")
self._adj = self.builder.get_object("adj_size")
self._spin = self.builder.get_object("spin_size")
self._entry = self.builder.get_object("entry_iso")
self._btn = self.builder.get_object("btn_iso")
self._warn = self.builder.get_object("iso_warn")
self._note = self.builder.get_object("note_platform")
self._vmd_chk = self.builder.get_object("chk_vmd")
# Peuple le combo des disques (une fois).
self._disks = _list_disks()
for name, label, _gib in self._disks:
self._combo.append(name, label)
# Reprend un choix déjà écrit (revisite du spoke).
self._load_state()
# Câblage des signaux (pas de handler dans le .glade, comme fws_recap).
self._chk.connect("toggled", self._on_toggle)
self._combo.connect("changed", self._on_disk_changed)
self._entry.connect("changed", self._on_iso_changed)
self._btn.connect("clicked", self._on_browse)
# Sur BIOS (non-UEFI), le dual-boot est impossible : on grise tout.
if not self._uefi:
self._enabled = False
self._chk.set_sensitive(False)
self._note.set_visible(True)
def refresh(self):
# Reflète l'état courant dans les widgets.
self._chk.set_active(self._enabled)
if self._disk:
self._combo.set_active_id(self._disk)
# AUCUNE présélection par défaut : sans choix explicite de l'utilisateur,
# `completed` refuse (il exige `disk`) → jamais d'effacement « par
# accident » d'un disque en cliquant simplement Suivant. (Bug corrigé :
# set_active(0) ciblait le 1er disque lsblk — souvent un disque de jeux.)
self._update_size_bounds()
self._spin.set_value(self._size)
self._entry.set_text(self._iso)
self._box.set_sensitive(self._enabled and self._uefi)
self._vmd_chk.set_active(self._vmd)
self._vmd_chk.set_sensitive(self._enabled and self._uefi)
self._validate()
def apply(self):
# Relit les widgets et persiste pour le kickstart.
self._enabled = self._chk.get_active() and self._uefi
self._disk = self._combo.get_active_id() or ""
self._size = int(self._spin.get_value())
self._iso = self._entry.get_text().strip()
self._vmd = self._vmd_chk.get_active()
self._save_state()
# --------------------------------------------------------------- handlers
def _on_toggle(self, _btn):
active = self._chk.get_active()
self._box.set_sensitive(active)
self._vmd_chk.set_sensitive(active)
self._validate()
def _on_disk_changed(self, _combo):
self._update_size_bounds()
self._validate()
def _on_iso_changed(self, _entry):
self._validate()
def _on_browse(self, _btn):
dialog = Gtk.FileChooserNative.new(
_("Select the Windows 11 ISO"), self.window,
Gtk.FileChooserAction.OPEN, _("_Select"), _("_Cancel"))
flt = Gtk.FileFilter()
flt.set_name(_("ISO images"))
flt.add_pattern("*.iso")
flt.add_pattern("*.ISO")
dialog.add_filter(flt)
# Démarrer sur les médias amovibles montés (clé USB avec l'ISO).
for start in ("/run/media", "/mnt", os.path.expanduser("~")):
if os.path.isdir(start):
dialog.set_current_folder(start)
break
if dialog.run() == Gtk.ResponseType.ACCEPT:
path = dialog.get_filename()
if path:
self._entry.set_text(path)
dialog.destroy()
# ----------------------------------------------------------------- helpers
def _update_size_bounds(self):
"""Borne haute du spin = taille du disque choisi réserve ESP/MSR."""
name = self._combo.get_active_id()
gib = 0
for dn, _label, dgib in self._disks:
if dn == name:
gib = dgib
break
upper = max(_MIN_SIZE_GIB, gib - self._reserve) if gib else 2000
self._adj.set_lower(_MIN_SIZE_GIB)
self._adj.set_upper(upper)
if self._spin.get_value() > upper:
self._spin.set_value(upper)
def _validate(self):
"""Message inline (jamais bloquant) : reflète la validité des choix."""
if not self._chk.get_active():
self._warn.set_text("")
return
iso = self._entry.get_text().strip()
msgs = []
if not self._combo.get_active_id():
msgs.append(_("Select the disk to dedicate to Windows."))
if not iso:
msgs.append(_("Provide the path to your Windows 11 ISO."))
elif not os.path.isfile(iso):
msgs.append(_("ISO not found: %s") % iso)
if msgs:
self._warn.set_text("" + " ".join(msgs))
else:
disk = self._combo.get_active_id()
extra = _(" — ⚠ THIS DISK CONTAINS DATA") if _disk_nonempty(disk) else ""
self._warn.set_text(
(_("⚠ The whole disk %s will be ERASED and dedicated to Windows.")
% disk) + extra)
def _load_state(self):
try:
with open(_STATE_FILE) as fh:
data = fh.read()
except OSError:
return
for line in data.splitlines():
if "=" not in line:
continue
key, _, val = line.partition("=")
key = key.strip()
val = val.strip()
if key == "enabled":
self._enabled = val == "1"
elif key == "disk":
self._disk = val
elif key == "size_gib" and val.isdigit():
self._size = int(val)
elif key == "iso":
self._iso = val
elif key == "intel_vmd":
self._vmd = val == "1"
def _save_state(self):
lines = [
"# Écrit par le spoke FWS « Dual-boot Windows ». Relu par le kickstart.",
"enabled=%d" % (1 if self._enabled else 0),
"disk=%s" % self._disk,
"size_gib=%d" % self._size,
"iso=%s" % self._iso,
"intel_vmd=%d" % (1 if self._vmd else 0),
]
try:
with open(_STATE_FILE, "w") as fh:
fh.write("\n".join(lines) + "\n")
except OSError as exc:
log.warning("FWS windows: écriture %s impossible : %s",
_STATE_FILE, exc)
# -------------------------------------------------------------- propriétés
@property
def status(self):
if not self._uefi:
return _("Unavailable (requires UEFI)")
if not self._enabled:
return _("Disabled (FWS only)")
iso = os.path.basename(self._iso) if self._iso else _("no ISO")
return _("Windows on %(disk)s%(size)d GiB — %(iso)s") % {
"disk": self._disk or "?", "size": self._size, "iso": iso}
@property
def completed(self):
# Désactivé = choix valide → jamais bloquant. Activé = exige disque +
# ISO lisible (sinon l'écran passe « orange » : l'utilisateur voit qu'il
# manque quelque chose, mais l'install FWS reste possible).
if not self._enabled:
return True
return bool(self._disk) and bool(self._iso) and os.path.isfile(self._iso)
@property
def mandatory(self):
return False
@property
def ready(self):
return True
@@ -1,173 +0,0 @@
#!/bin/bash
# ============================================================================
# fws-hw-detect — détection matériel FWS (hyperviseur + GPU).
#
# SOURCE UNIQUE de vérité, consommée par DEUX endroits :
# • l'écran « Récapitulatif avant installation » du live (--text, affiché
# par /root/.xinitrc via zenity juste avant de lancer liveinst) ;
# • le %post chrooté du kickstart Anaconda (--env), qui APPLIQUE :
# activation des guest tools du bon hyperviseur, purge des autres,
# installation des drivers GPU.
# → ce que l'écran ANNONCE = ce que l'install FERA (aucune divergence
# possible, la détection vit ici et nulle part ailleurs).
#
# Fonctionne dans le LIVE comme dans le CHROOT d'Anaconda : celui-ci
# bind-monte /sys et /proc du live → même matériel visible. Le script est
# livré par l'airootfs (/usr/local/bin) → recopié dans la cible par le
# payload liveimg → présent dans le chroot du %post. Aucune dépendance
# dure : lspci (pciutils) et ip (iproute2) ne servent qu'aux libellés
# humains de --text, avec repli si absents.
#
# Usage : fws-hw-detect --env lignes FWS_*='…' à eval par le kickstart
# fws-hw-detect --text récapitulatif humain en français (zenity)
# ============================================================================
set -u
MODE="${1:---text}"
# --- Hyperviseur --------------------------------------------------------------
# systemd-detect-virt lit CPUID/DMI → OK en chroot, sans systemd actif.
# ATTENTION : hors VM il AFFICHE « none » ET sort en code ≠ 0 → surtout pas de
# « || echo none » DANS le $() (on récolterait « none » en double).
VIRT="$(systemd-detect-virt 2>/dev/null | head -n1)"
[ -n "$VIRT" ] || VIRT=none
# Les guest tools des 3 hyperviseurs sont DÉJÀ dans l'image live
# (packages.x86_64 : open-vm-tools, virtualbox-guest-utils-nox,
# qemu-guest-agent) → activer un service marche HORS LIGNE ; on retire ceux
# des autres hyperviseurs (inutiles sur cette machine).
VIRT_SVC="" VIRT_RM="" VIRT_LABEL=""
case "$VIRT" in
vmware)
VIRT_LABEL="VMware"
VIRT_SVC="vmtoolsd.service vmware-vmblock-fuse.service"
VIRT_RM="virtualbox-guest-utils-nox qemu-guest-agent" ;;
oracle) # = VirtualBox (nom renvoyé par systemd-detect-virt)
VIRT_LABEL="VirtualBox"
VIRT_SVC="vboxservice.service"
VIRT_RM="open-vm-tools qemu-guest-agent" ;;
kvm|qemu)
VIRT_LABEL="QEMU/KVM"
VIRT_SVC="qemu-guest-agent.service"
VIRT_RM="open-vm-tools virtualbox-guest-utils-nox" ;;
none)
VIRT_LABEL="aucune (machine physique)"
VIRT_RM="open-vm-tools virtualbox-guest-utils-nox qemu-guest-agent" ;;
*) # Hyperviseur non géré (hyper-v…) : aucun de nos guest tools ne sert.
VIRT_LABEL="$VIRT (intégration non gérée)"
VIRT_RM="open-vm-tools virtualbox-guest-utils-nox qemu-guest-agent" ;;
esac
# --- GPU ----------------------------------------------------------------------
# Classe PCI 0x03xxxx = contrôleur d'affichage ; lecture DIRECTE de /sys, pas
# besoin de lspci. Vendors : 0x10de NVIDIA, 0x1002 AMD, 0x8086 Intel,
# 0x15ad VMware (le VMSVGA de VirtualBox porte AUSSI 0x15ad → couvert).
# QEMU virtio-gpu/QXL et VBoxVGA : modesetting + mesa (déjà tirés par
# xorg-server dans l'image) suffisent → rien à installer.
DRV_PKGS="" GPU_TEXT=""
for _dev in /sys/bus/pci/devices/*; do
case "$(cat "$_dev/class" 2>/dev/null)" in 0x03*) ;; *) continue ;; esac
_vendor="$(cat "$_dev/vendor" 2>/dev/null)"
_pkgs=""
case "$_vendor" in
# NVIDIA : le module des dépôts est bâti pour le NOYAU des dépôts →
# « linux » DOIT partir dans la même transaction pacman (le noyau
# installé est celui du live, plus vieux), sinon module ≠ noyau au
# 1er boot. Les chemins /boot/* sont stables → grub.cfg reste bon.
0x10de) _pkgs="linux nvidia nvidia-utils" ;;
0x1002) _pkgs="mesa vulkan-radeon xf86-video-amdgpu" ;;
0x8086) _pkgs="mesa vulkan-intel" ;;
0x15ad) _pkgs="mesa xf86-video-vmware" ;;
esac
# Libellé humain : lspci si dispo, sinon adresse PCI + vendor brut.
_name=""
if command -v lspci >/dev/null 2>&1; then
_name="$(lspci -s "${_dev##*/}" 2>/dev/null | head -n1 \
| cut -d: -f3- | sed 's/^ //')"
fi
[ -n "$_name" ] || _name="GPU PCI ${_dev##*/} (vendor $_vendor)"
if [ -n "$_pkgs" ]; then
GPU_TEXT="${GPU_TEXT} • ${_name}\n → paquets : ${_pkgs}\n"
DRV_PKGS="$DRV_PKGS $_pkgs"
else
GPU_TEXT="${GPU_TEXT} • ${_name}\n → rien à installer (modesetting + mesa, déjà dans l'image)\n"
fi
done
# Dédoublonnage (machine multi-GPU : « mesa » listé 2× ferait échouer pacman
# sur « duplicate target »).
if [ -n "$DRV_PKGS" ]; then
DRV_PKGS="$(printf '%s\n' $DRV_PKGS | sort -u | tr '\n' ' ')"
DRV_PKGS="${DRV_PKGS% }"
fi
# --- Mode machine : sortie shell pour le kickstart ----------------------------
if [ "$MODE" = "--env" ]; then
printf "FWS_VIRT='%s'\n" "$VIRT"
printf "FWS_VIRT_SVC='%s'\n" "$VIRT_SVC"
printf "FWS_VIRT_RM='%s'\n" "$VIRT_RM"
printf "FWS_DRV_PKGS='%s'\n" "$DRV_PKGS"
exit 0
fi
# --- Mode humain : récapitulatif --text (zenity, avant liveinst) ---------------
CPU="$(awk -F': +' '/^model name/{print $2; exit}' /proc/cpuinfo)"
[ -n "$CPU" ] || CPU="inconnu"
case "$(awk -F': +' '/^vendor_id/{print $2; exit}' /proc/cpuinfo)" in
GenuineIntel) UCODE="intel-ucode" ;;
AuthenticAMD) UCODE="amd-ucode" ;;
*) UCODE="" ;;
esac
RAM="$(awk '/^MemTotal/{printf "%.1f Gio", $2/1048576}' /proc/meminfo)"
if [ -d /sys/firmware/efi ]; then
BOOT="UEFI — GRUB x86_64-efi + entrée de secours EFI/BOOT/BOOTX64.EFI"
else
BOOT="BIOS hérité — GRUB i386-pc, table de partitions MBR forcée"
fi
if command -v ip >/dev/null 2>&1 && ip route get 1.1.1.1 >/dev/null 2>&1; then
NET="disponible ✔"
else
NET="INDISPONIBLE ✖ — drivers GPU et bureau ne s'installeront PAS (rattrapable au 1er boot)"
fi
# Type « disk » uniquement (exclut lecteur ISO sr0/loop). La clé USB du live,
# elle, EST un disque → il est juste qu'elle apparaisse sous l'avertissement
# d'effacement (clearpart --all du kickstart efface bien TOUS les disques).
DISKS="$(lsblk -dno NAME,SIZE,MODEL,TYPE 2>/dev/null \
| awk '$NF=="disk"{NF--; print " • " $0}')"
[ -n "$DISKS" ] || DISKS=" (aucun disque détecté)"
[ -n "$GPU_TEXT" ] || GPU_TEXT=" (aucune carte graphique détectée)\n"
printf '%b' \
"══════════════════════════════════════════════════════════
FWS — RÉCAPITULATIF AVANT INSTALLATION
══════════════════════════════════════════════════════════
MACHINE
• Démarrage : ${BOOT}
• Virtualisation : ${VIRT_LABEL}
• Processeur : ${CPU}"
[ -n "$UCODE" ] && printf '%b' "
• Microcode : ${UCODE} — déjà intégré à l'image, rien à faire"
printf '%b' "
• Mémoire : ${RAM}
• Réseau : ${NET}
DISQUES DÉTECTÉS
⚠ Par défaut, TOUS les disques ci-dessous seront ENTIÈREMENT
EFFACÉS (partitionnement automatique). Le partitionnement
manuel reste possible dans l'installateur.
${DISKS}
CARTES GRAPHIQUES → drivers installés AUTOMATIQUEMENT en fin d'install
${GPU_TEXT}
INTÉGRATION HYPERVISEUR (automatique, fonctionne hors ligne)
• Services activés : ${VIRT_SVC:-aucun}
• Guest tools retirés (inutiles) : ${VIRT_RM:-aucun}
DÉROULEMENT DE L'INSTALLATION
1. Réglages dans Anaconda : langue, clavier, fuseau, disque, utilisateur
2. Copie du système live FWS sur le disque (payload liveimg)
3. Noyau + initramfs régénérés (mkinitcpio), GRUB installé
4. Drivers GPU + intégration hyperviseur appliqués (voir ci-dessus)
5. Bureau choisi installé (réseau requis)
« Lancer l'installateur » pour continuer — « Éteindre » pour annuler.
"
@@ -0,0 +1,136 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<interface>
<!-- FWS : copie de summary.glade d'anaconda 45.8 SANS le libellé
« We won't touch your disks… » du bas du hub. Livré ICI (chemin neutre)
car mkarchiso pose l'airootfs AVANT pacstrap : au chemin du paquet,
pacman refuserait d'installer anaconda (« exists in filesystem »).
C'est le .xinitrc du live qui le copie sur
/usr/share/anaconda/ui/hubs/summary.glade au boot. -->
<requires lib="gtk+" version="3.0"/>
<requires lib="AnacondaWidgets" version="3.0"/>
<object class="AnacondaHubWindow" id="summaryWindow">
<property name="can_focus">False</property>
<property name="window_name" translatable="yes">INSTALLATION SUMMARY</property>
<property name="quit_button">quitButton</property>
<property name="continue_button">continueButton</property>
<child internal-child="main_box">
<object class="GtkBox" id="AnacondaHubWindow-main_box1">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child internal-child="nav_box">
<object class="GtkEventBox" id="AnacondaHubWindow-nav_box1">
<property name="can_focus">False</property>
<child internal-child="nav_area">
<object class="GtkGrid" id="AnacondaHubWindow-nav_area1">
<property name="can_focus">False</property>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="alignment">
<object class="GtkAlignment" id="AnacondaHubWindow-alignment1">
<property name="can_focus">False</property>
<property name="yalign">0</property>
<property name="left_padding">12</property>
<property name="right_padding">6</property>
<child internal-child="action_area">
<object class="GtkBox" id="summaryWindow-actionArea">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child internal-child="scrolled_window">
<object class="GtkScrolledWindow" id="summaryWindow-scroll">
<property name="can_focus">True</property>
<property name="is_focus">True</property>
<property name="hscrollbar_policy">never</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButtonBox" id="buttonbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">6</property>
<property name="margin_right">6</property>
<property name="spacing">12</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="quitButton">
<property name="label" translatable="yes" context="GUI|Summary">_Quit</property>
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="hexpand">True</property>
<property name="use_underline">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="continueButton">
<property name="label" translatable="yes" context="GUI|Summary">_Begin Installation</property>
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_underline">True</property>
<style>
<class name="suggested-action"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
<child internal-child="accessible">
<object class="AtkObject" id="summaryWindow-atkobject">
<property name="AtkObject::accessible-name" translatable="yes">INSTALLATION SUMMARY</property>
</object>
</child>
</object>
</interface>
@@ -0,0 +1,168 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- FWS — onglet « Desktop Environment » (choix du bureau à installer). -->
<interface>
<requires lib="gtk+" version="3.6"/>
<requires lib="AnacondaWidgets" version="1.0"/>
<object class="AnacondaSpokeWindow" id="fwsDesktopWindow">
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="window-name" translatable="yes">DESKTOP ENVIRONMENT</property>
<signal name="button-clicked" handler="on_back_clicked" swapped="no"/>
<child internal-child="main_box">
<object class="GtkBox" id="AnacondaSpokeWindow-main_box1">
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child internal-child="nav_box">
<object class="GtkEventBox" id="AnacondaSpokeWindow-nav_box1">
<property name="can-focus">False</property>
<child internal-child="nav_area">
<object class="GtkGrid" id="AnacondaSpokeWindow-nav_area1">
<property name="can-focus">False</property>
<property name="margin-left">6</property>
<property name="margin-right">6</property>
<property name="margin-top">6</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="alignment">
<object class="GtkAlignment" id="AnacondaSpokeWindow-alignment1">
<property name="can-focus">False</property>
<property name="margin-top">12</property>
<property name="yalign">0</property>
<property name="xscale">0</property>
<property name="yscale">0</property>
<property name="bottom-padding">48</property>
<property name="left-padding">24</property>
<property name="right-padding">24</property>
<child internal-child="action_area">
<object class="GtkBox" id="AnacondaSpokeWindow-action_area1">
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">8</property>
<child>
<object class="GtkLabel" id="fws_desktop_desc">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="xalign">0</property>
<property name="margin-bottom">12</property>
<property name="wrap">True</property>
<property name="max-width-chars">60</property>
<property name="label" translatable="yes">Choose the desktop environment to install. A web browser (Firefox) is installed automatically with any desktop.</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio_cli">
<property name="label" translatable="yes">Command line only (no desktop)</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio_gnome">
<property name="label" translatable="yes">GNOME</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="draw-indicator">True</property>
<property name="group">radio_cli</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio_kde">
<property name="label" translatable="yes">KDE Plasma</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="draw-indicator">True</property>
<property name="group">radio_cli</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio_hyprland">
<property name="label" translatable="yes">Hyprland — Wayland (NOT VR compatible)</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="draw-indicator">True</property>
<property name="group">radio_cli</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio_i3">
<property name="label" translatable="yes">i3 — X11 (VR compatible: SteamVR, HTC Vive, Valve Index…)</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="draw-indicator">True</property>
<property name="group">radio_cli</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio_xfce">
<property name="label" translatable="yes">XFCE</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="draw-indicator">True</property>
<property name="group">radio_cli</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">6</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child internal-child="accessible">
<object class="AtkObject" id="fwsDesktopWindow-atkobject">
<property name="AtkObject::accessible-name" translatable="yes">DESKTOP ENVIRONMENT</property>
</object>
</child>
</object>
</interface>
@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- FWS — page « Récapitulatif avant installation » (cf. fws_recap.py).
Squelette calqué sur installation_progress.glade : AnacondaStandaloneWindow
avec ses enfants internes (nav_box/nav_area, alignment/action_area). -->
<interface>
<requires lib="gtk+" version="3.0"/>
<requires lib="AnacondaWidgets" version="3.0"/>
<object class="AnacondaStandaloneWindow" id="fwsRecapWindow">
<property name="can_focus">False</property>
<property name="window_name" translatable="yes">RÉCAPITULATIF AVANT INSTALLATION</property>
<child internal-child="main_box">
<object class="GtkBox" id="AnacondaStandaloneWindow-main_box1">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child internal-child="nav_box">
<object class="GtkEventBox" id="AnacondaStandaloneWindow-nav_box1">
<property name="can_focus">False</property>
<child internal-child="nav_area">
<object class="GtkGrid" id="AnacondaStandaloneWindow-nav_area1">
<property name="can_focus">False</property>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="alignment">
<object class="GtkAlignment" id="AnacondaStandaloneWindow-alignment1">
<property name="can_focus">False</property>
<property name="yalign">0</property>
<property name="left_padding">12</property>
<property name="right_padding">6</property>
<child internal-child="action_area">
<object class="GtkBox" id="fwsRecapWindow-actionArea">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkScrolledWindow" id="recapScroll">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTextView" id="recapView">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="cursor_visible">False</property>
<property name="monospace">True</property>
<property name="left_margin">12</property>
<property name="right_margin">12</property>
<property name="top_margin">12</property>
<property name="bottom_margin">12</property>
<property name="wrap_mode">word-char</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface>
@@ -0,0 +1,290 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- FWS — onglet « Dual-boot Windows gaming » (case + disque + taille + ISO).
Calqué sur fws_desktop.glade (AnacondaSpokeWindow + ses enfants internes
nav_box/nav_area, alignment/action_area). Catégorie SYSTÈME côté .py :
l'écran vit à côté de « Destination de l'installation », pas dans « Logiciel ».
Les widgets sont pilotés/peuplés au runtime par fws_windows.py. -->
<interface>
<requires lib="gtk+" version="3.6"/>
<requires lib="AnacondaWidgets" version="1.0"/>
<!-- Bornes de la taille NTFS ; upper réévalué en Python selon le disque choisi. -->
<object class="GtkAdjustment" id="adj_size">
<property name="lower">100</property>
<property name="upper">2000</property>
<property name="value">200</property>
<property name="step-increment">10</property>
<property name="page-increment">50</property>
</object>
<object class="AnacondaSpokeWindow" id="fwsWindowsWindow">
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="window-name" translatable="yes">DUAL-BOOT WINDOWS</property>
<signal name="button-clicked" handler="on_back_clicked" swapped="no"/>
<child internal-child="main_box">
<object class="GtkBox" id="AnacondaSpokeWindow-main_box1">
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child internal-child="nav_box">
<object class="GtkEventBox" id="AnacondaSpokeWindow-nav_box1">
<property name="can-focus">False</property>
<child internal-child="nav_area">
<object class="GtkGrid" id="AnacondaSpokeWindow-nav_area1">
<property name="can-focus">False</property>
<property name="margin-left">6</property>
<property name="margin-right">6</property>
<property name="margin-top">6</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="alignment">
<object class="GtkAlignment" id="AnacondaSpokeWindow-alignment1">
<property name="can-focus">False</property>
<property name="margin-top">12</property>
<property name="yalign">0</property>
<property name="xscale">0</property>
<property name="yscale">0</property>
<property name="bottom-padding">48</property>
<property name="left-padding">24</property>
<property name="right-padding">24</property>
<child internal-child="action_area">
<object class="GtkBox" id="AnacondaSpokeWindow-action_area1">
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">8</property>
<!-- Description -->
<child>
<object class="GtkLabel" id="fws_windows_desc">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="xalign">0</property>
<property name="margin-bottom">6</property>
<property name="wrap">True</property>
<property name="max-width-chars">64</property>
<property name="label" translatable="yes">Install a bare-metal Windows 11 alongside FWS to play kernel-anticheat games (Valorant, League of Legends) that refuse virtualization. FWS deploys Windows automatically from the ISO you provide. Requires UEFI + TPM 2.0.</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<!-- Case maître : active/grise le bloc d'options ci-dessous -->
<child>
<object class="GtkCheckButton" id="chk_enable">
<property name="label" translatable="yes">Enable Windows gaming dual-boot</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<!-- Bloc d'options (grisé tant que la case est décochée) -->
<child>
<object class="GtkGrid" id="box_options">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="sensitive">False</property>
<property name="margin-left">24</property>
<property name="margin-top">4</property>
<property name="row-spacing">8</property>
<property name="column-spacing">12</property>
<!-- Ligne 0 : disque -->
<child>
<object class="GtkLabel" id="label_disk">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Disk dedicated to Windows:</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="combo_disk">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
<!-- Ligne 1 : taille NTFS -->
<child>
<object class="GtkLabel" id="label_size">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Windows partition size (GiB):</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spin_size">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="halign">start</property>
<property name="adjustment">adj_size</property>
<property name="numeric">True</property>
<property name="value">200</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">1</property>
</packing>
</child>
<!-- Ligne 2 : ISO (champ libre + bouton Parcourir) -->
<child>
<object class="GtkLabel" id="label_iso">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Windows 11 ISO image:</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box_iso">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">horizontal</property>
<property name="spacing">6</property>
<property name="hexpand">True</property>
<child>
<object class="GtkEntry" id="entry_iso">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="hexpand">True</property>
<property name="placeholder-text" translatable="yes">/run/media/…/Win11_25H2.iso</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="btn_iso">
<property name="label" translatable="yes">Browse…</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">2</property>
</packing>
</child>
<!-- Ligne 3 : avertissement de validation (rempli en Python) -->
<child>
<object class="GtkLabel" id="iso_warn">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="xalign">0</property>
<property name="wrap">True</property>
<property name="max-width-chars">64</property>
<property name="label"></property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">3</property>
<property name="width">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<!-- Support Intel VMD/RST : auto-coché si un contrôleur Intel VMD
est détecté. Injecte le pilote iaStorVD dans le Windows
déployé pour qu'il boote sur les machines Intel (BIOS en mode
VMD/RST). Aucun effet sur AMD. -->
<child>
<object class="GtkCheckButton" id="chk_vmd">
<property name="label" translatable="yes">Intel VMD/RST support (auto-detected — Intel machines only)</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="sensitive">False</property>
<property name="margin-left">24</property>
<property name="margin-top">4</property>
<property name="draw-indicator">True</property>
<property name="tooltip-text" translatable="yes">Injects the Intel VMD/RST driver into the deployed Windows so it boots on Intel systems with VMD enabled in the BIOS. Auto-detected; has no effect on AMD.</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<!-- Note plate-forme (affichée par le .py si BIOS/non-UEFI) -->
<child>
<object class="GtkLabel" id="note_platform">
<property name="visible">False</property>
<property name="can-focus">False</property>
<property name="xalign">0</property>
<property name="margin-top">8</property>
<property name="wrap">True</property>
<property name="max-width-chars">64</property>
<property name="label" translatable="yes">Dual-boot requires UEFI firmware (this machine booted in legacy BIOS mode). The option is disabled.</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child internal-child="accessible">
<object class="AtkObject" id="fwsWindowsWindow-atkobject">
<property name="AtkObject::accessible-name" translatable="yes">DUAL-BOOT WINDOWS</property>
</object>
</child>
</object>
</interface>
@@ -1,4 +1,5 @@
title Arch Linux (%ARCH%, UEFI) title FWS install medium (%ARCH%, UEFI)
sort-key 01
linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux
initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% udev.event_timeout=45
+14
View File
@@ -62,6 +62,7 @@ zsh
grml-zsh-config grml-zsh-config
bash bash
zenity zenity
zram-generator
nano nano
vim vim
less less
@@ -79,6 +80,7 @@ pacman-contrib
dmidecode dmidecode
usbutils usbutils
pciutils pciutils
firewalld
fastfetch fastfetch
htop htop
@@ -103,6 +105,8 @@ open-vm-tools
# --- FWS (fws-local) --- # --- FWS (fws-local) ---
winboat-bin winboat-bin
# Gestionnaire D&D 5e (dés, personnages, grimoire, sessions, combat).
fws-dnd
# --- Installateur Anaconda (fws-local) --- # --- Installateur Anaconda (fws-local) ---
# 'anaconda' tire automatiquement anaconda-widgets + toutes ses dépendances # 'anaconda' tire automatiquement anaconda-widgets + toutes ses dépendances
@@ -117,3 +121,13 @@ python-crypt_r
# Namespace GI 'OSTree-1.0' : pyanaconda/rescue.py importe le payload rpm_ostree # Namespace GI 'OSTree-1.0' : pyanaconda/rescue.py importe le payload rpm_ostree
# (inconditionnellement, même si FWS utilise liveimg) → fourni par 'ostree'. # (inconditionnellement, même si FWS utilise liveimg) → fourni par 'ostree'.
ostree ostree
# --- Gameboot : dual-boot Windows gaming (déploiement offline + Secure Boot) ---
# Outils universels utilisés par fws-windows-deploy (%post, hors ligne) et
# fws-secureboot-setup. NB : PAS de 'nvidia' ici (conditionnel via fws-setup-hardware).
wimlib
hivex
sbctl
sbsigntools
mokutil
binutils
-5
View File
@@ -1,8 +1,3 @@
[fws-local]
SigLevel = Optional TrustAll
Server = file:///mnt/c/Users/Murasaki/Documents/FWS/fws/local-repo
# #
# /etc/pacman.conf # /etc/pacman.conf
# #
+15 -1
View File
@@ -24,7 +24,21 @@ file_permissions=(
["/root/.automated_script.sh"]="0:0:755" ["/root/.automated_script.sh"]="0:0:755"
["/root/.gnupg"]="0:0:700" ["/root/.gnupg"]="0:0:700"
["/usr/local/bin/choose-mirror"]="0:0:755" ["/usr/local/bin/choose-mirror"]="0:0:755"
["/usr/local/bin/fws-hw-detect"]="0:0:755"
["/usr/local/bin/Installation_guide"]="0:0:755" ["/usr/local/bin/Installation_guide"]="0:0:755"
["/usr/local/bin/livecd-sound"]="0:0:755" ["/usr/local/bin/livecd-sound"]="0:0:755"
["/usr/bin/load_policy"]="0:0:755"
["/usr/bin/setstatus"]="0:0:755"
["/usr/local/bin/fws-setup-hardware"]="0:0:755"
["/usr/local/bin/fws-keys"]="0:0:755"
["/usr/local/bin/fws-vr-setup"]="0:0:755"
["/usr/local/bin/fws-hello"]="0:0:755"
["/usr/local/bin/fws-update"]="0:0:755"
["/usr/local/bin/fws-updates-check"]="0:0:755"
["/usr/local/bin/fws-proton-ge"]="0:0:755"
["/usr/local/bin/fws-recovery"]="0:0:755"
["/usr/local/bin/fws-recovery-session"]="0:0:755"
["/usr/local/bin/fws-recovery-gui"]="0:0:755"
["/usr/local/bin/fws-powermenu"]="0:0:755"
["/usr/local/bin/fws-about"]="0:0:755"
["/etc/grub.d/42_fws-recovery"]="0:0:755"
) )
@@ -1,5 +0,0 @@
LABEL arch
MENU LABEL Arch Linux (%ARCH%, BIOS)
LINUX /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux
INITRD /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID%
+9 -7
View File
@@ -1,9 +1,11 @@
SERIAL 0 115200 DEFAULT select
UI menu.c32
MENU TITLE Arch Linux
MENU CLEAR
DEFAULT arch LABEL select
TIMEOUT 30 COM32 whichsys.c32
APPEND -pxe- pxe -sys- sys -iso- sys
INCLUDE syslinux-linux.cfg LABEL pxe
CONFIG archiso_pxe.cfg
LABEL sys
CONFIG archiso_sys.cfg
@@ -0,0 +1,26 @@
# ============================================================
# Configuration de l'orchestrateur FWS « gameboot » (dual-boot hibernate-swap)
# Lu par /usr/local/bin/fws-gameboot. Voir docs/hibernate-swap-dualboot.md.
# ============================================================
# Entrée de boot Windows (numéro Boot#### hex, ex. "0003").
# Vide = auto-détection de l'entrée « Windows Boot Manager » via efibootmgr.
FWS_WINDOWS_BOOTNUM=""
# Comportement si le préflight d'hibernation ÉCHOUE (ou si l'hibernation
# elle-même échoue) AVANT d'être entré dans Windows :
# reboot = on bascule quand même vers Windows par un REBOOT SIMPLE. La session
# FWS n'est pas préservée, mais on entre dans le jeu (c'est le "filet"
# demandé : ne jamais laisser l'utilisateur bloqué).
# abort = on annule, on efface BootNext, on RESTE sur FWS avec une erreur
# claire (choix prudent : jamais de bascule surprise).
FWS_HIBERNATE_FALLBACK="reboot"
# Marge de sécurité (Mo) ajoutée à (RAM + VRAM en usage) pour vérifier que
# l'image d'hibernation tiendra dans le swap. La VRAM des GPU récents (ex. 32 Go
# sur une RTX 5090) est recopiée dans l'image → le swap doit couvrir RAM+VRAM.
FWS_SWAP_MARGIN_MB=2048
# Jeu lancé par défaut si « fws-gameboot to-windows » est appelé sans --game.
# L'identifiant doit exister dans games.json côté Windows (valorant, lol, …).
FWS_DEFAULT_GAME="valorant"
+40
View File
@@ -0,0 +1,40 @@
#!/bin/sh
# ============================================================================
# FWS — entrée « Réparer FWS » dans le menu GRUB du SYSTÈME INSTALLÉ
# (l'équivalent de l'environnement de récupération de Windows).
#
# Ce script est exécuté par grub-mkconfig (à l'installation via le %post du
# kickstart, puis à chaque fws-recovery « Réinstaller GRUB »). Il démarre le
# MÊME système sur fws-recovery.target (+ marqueur fws.recovery=1) :
# fws-recovery-boot.service prend alors tty1 et lance l'application de
# réparation (GUI via startx, repli console).
#
# Sur le live, ce fichier est inerte (grub-mkconfig n'y tourne jamais).
# ============================================================================
set -e
. /usr/share/grub/grub-mkconfig_lib
kernel=/boot/vmlinuz-linux
# L'initramfs FALLBACK (sans autodetect) : c'est l'entrée de secours, elle
# doit démarrer même si le matériel a changé depuis le dernier mkinitcpio.
initrd=/boot/initramfs-linux-fallback.img
[ -f "$initrd" ] || initrd=/boot/initramfs-linux.img
[ -f "$kernel" ] && [ -f "$initrd" ] || exit 0
# Même logique racine que 10_linux : UUID sauf refus explicite.
if [ -n "${GRUB_DEVICE_UUID:-}" ] && [ "x${GRUB_DISABLE_LINUX_UUID:-}" != xtrue ]; then
rootarg="root=UUID=$GRUB_DEVICE_UUID"
else
rootarg="root=$GRUB_DEVICE"
fi
rel_kernel="$(make_system_path_relative_to_its_root "$kernel")"
rel_initrd="$(make_system_path_relative_to_its_root "$initrd")"
cat <<EOF
menuentry 'Réparer FWS (récupération du système)' --class recovery {
$(prepare_grub_to_access_device "${GRUB_DEVICE_BOOT}" | grub_add_tab)
linux $rel_kernel $rootarg rw ${GRUB_CMDLINE_LINUX:-} systemd.unit=fws-recovery.target fws.recovery=1
initrd $rel_initrd
}
EOF
+1 -1
View File
@@ -1 +1 @@
archiso fws
+2
View File
@@ -0,0 +1,2 @@
\S (\l)
@@ -5,5 +5,14 @@
# On utilise donc un autre nom. # On utilise donc un autre nom.
_fastfetch_rc=$? _fastfetch_rc=$?
if [ "${_fastfetch_rc}" -eq 0 ]; then if [ "${_fastfetch_rc}" -eq 0 ]; then
fastfetch # Logo FWS maison (4 carrés + Tux) au lieu du logo Arch détecté par défaut.
fastfetch \
--logo-type file \
--logo /usr/local/share/fws/logo.txt \
--logo-color-1 '38;2;30;160;100' \
--logo-color-2 '38;2;45;125;210' \
--logo-color-3 '38;2;240;180;40' \
--logo-color-4 '38;2;25;85;145' \
--logo-color-5 '38;2;28;28;34' \
--logo-color-6 '38;2;245;165;35'
fi fi
@@ -1 +0,0 @@
/usr/lib/systemd/system/systemd-timesyncd.service
@@ -0,0 +1 @@
/usr/lib/systemd/system/systemd-timesyncd.service
@@ -0,0 +1,19 @@
[Unit]
Description=FWS — réasserte BootOrder=FWS et le marqueur de retour Windows (gameboot)
Documentation=file:///usr/share/doc/fws/hibernate-swap-dualboot.md
# Jamais sur le live, uniquement sur un système installé UEFI.
ConditionPathExists=!/run/archiso
ConditionPathExists=/sys/firmware/efi
After=local-fs.target
# Ne pas retarder le boot : purement best-effort.
DefaultDependencies=no
Before=multi-user.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/fws-gameboot-bootfix
# Ne jamais faire échouer le boot si efivars indisponible.
SuccessExitStatus=0 1
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,24 @@
[Unit]
Description=FWS - Reparation du systeme (session de secours sur tty1)
# Demarre UNIQUEMENT via fws-recovery.target (entree GRUB « Reparer FWS » du
# systeme installe : systemd.unit=fws-recovery.target). PAS de [Install] :
# enrole dans aucun boot normal, donc son Conflicts=getty@tty1 ne peut jamais
# casser le login tty1 d'un boot ordinaire.
# Sur le live, c'est /root/.zprofile (autologin tty1) qui gere la reparation.
ConditionKernelCommandLine=fws.recovery=1
ConditionPathExists=!/run/archiso
Conflicts=getty@tty1.service display-manager.service
After=systemd-user-sessions.service getty@tty1.service display-manager.service
[Service]
Type=idle
# GUI via startx (openbox + fws-recovery-gui) ; si X ne demarre pas (pilote
# video casse — un cas de reparation typique), repli sur l'assistant CONSOLE.
ExecStart=/bin/sh -c '/usr/bin/startx /usr/local/bin/fws-recovery-session -- vt1 || exec /usr/local/bin/fws-recovery'
StandardInput=tty
StandardOutput=tty
StandardError=tty
TTYPath=/dev/tty1
TTYReset=yes
TTYVHangup=yes
Restart=no
@@ -0,0 +1,10 @@
[Unit]
Description=FWS - Cible de reparation du systeme
# Demandee par l'entree GRUB « Reparer FWS » du systeme installe
# (kernel : systemd.unit=fws-recovery.target fws.recovery=1).
# Un multi-user complet (reseau pour pacman -Syu) + la session de reparation ;
# le Conflicts du service libere tty1 (son start est REQUIS ici, il gagne
# contre le Wants de getty@tty1).
Requires=multi-user.target fws-recovery-boot.service
After=multi-user.target fws-recovery-boot.service
AllowIsolate=yes
@@ -1 +0,0 @@
/usr/lib/systemd/system/systemd-time-wait-sync.service
@@ -0,0 +1 @@
/usr/lib/systemd/system/systemd-time-wait-sync.service
@@ -1 +0,0 @@
/usr/lib/systemd/system/systemd-timesyncd.service
@@ -0,0 +1 @@
/usr/lib/systemd/system/systemd-timesyncd.service
@@ -0,0 +1,12 @@
# FWS — swap compressé en RAM (zram), activé automatiquement par
# zram-generator (paquet dans packages.x86_64, aucun service à activer).
#
# Le LIVE n'a AUCUN swap : sur une machine/VM à 2 Go, Anaconda + le rootfs
# squashfs saturent la mémoire → livelock, écran figé pendant « Préparation
# de l'installateur ». Le zram (compression ~3:1 sur les tas Python) donne
# la marge nécessaire. Copié tel quel sur le SYSTÈME INSTALLÉ par le payload
# LiveOS → FWS garde ce filet de sécurité après installation (esprit
# « remplaçant de Windows » sur des machines modestes).
[zram0]
zram-size = min(ram, 4096)
compression-algorithm = zstd
@@ -0,0 +1,9 @@
[Desktop Entry]
Type=Application
Name=Bienvenue sur FWS
Comment=Fenêtre de bienvenue au premier démarrage
Exec=fws-hello --autostart
Icon=help-about
Terminal=false
Categories=System;
X-GNOME-Autostart-enabled=true
+185
View File
@@ -0,0 +1,185 @@
#!/usr/bin/env python3
# FWS — fenêtre « À propos » : deux onglets, « Le projet FWS » (ce qu'est FWS,
# version, liens) et « Système » (matériel + logiciel détectés). Remplace le
# « About Xfce » de libxfce4ui par une page de marque FWS. GTK3 (python-gobject
# installé avec le bureau).
import os
import re
import subprocess
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("Gdk", "3.0") # sinon gi charge Gdk 4.0 (conflit avec Gtk3)
from gi.repository import Gdk, GLib, Gtk # noqa: E402
GLib.set_prgname("fws-about")
def _osrelease():
data = {}
for path in ("/etc/os-release", "/usr/lib/os-release"):
try:
with open(path) as fh:
for line in fh:
m = re.match(r'^([A-Z_]+)=(.*)$', line.strip())
if m:
data[m.group(1)] = m.group(2).strip('"')
break
except OSError:
continue
return data
def _cmd(*argv):
try:
return subprocess.check_output(argv, text=True,
stderr=subprocess.DEVNULL).strip()
except (OSError, subprocess.CalledProcessError):
return ""
def _cpu():
try:
with open("/proc/cpuinfo") as fh:
for line in fh:
if line.startswith("model name"):
return line.split(":", 1)[1].strip()
except OSError:
pass
return "inconnu"
def _mem():
try:
with open("/proc/meminfo") as fh:
for line in fh:
if line.startswith("MemTotal"):
kib = int(line.split()[1])
return f"{kib / 1048576:.1f} Gio"
except OSError:
pass
return "inconnu"
def _gpus():
out = []
for line in _cmd("lspci").splitlines():
low = line.lower()
if "vga" in low or "3d" in low or "display" in low:
out.append(line.split(":", 2)[-1].strip())
return out or ["inconnu"]
def _desktop():
if os.environ.get("HYPRLAND_INSTANCE_SIGNATURE"):
return "Hyprland"
env = os.environ.get("XDG_CURRENT_DESKTOP") \
or os.environ.get("DESKTOP_SESSION") or ""
return env or "inconnu"
def _session_type():
t = os.environ.get("XDG_SESSION_TYPE", "")
return {"wayland": "Wayland", "x11": "X11"}.get(t, t or "inconnu")
def _disks():
out = []
for line in _cmd("lsblk", "-dno", "NAME,SIZE,MODEL,TYPE").splitlines():
f = line.split()
if f and f[-1] == "disk":
out.append(" ".join(f[:-1]))
return out or ["inconnu"]
def _grid(rows):
grid = Gtk.Grid(column_spacing=18, row_spacing=8, margin=18)
for i, (key, val) in enumerate(rows):
k = Gtk.Label(xalign=1)
k.set_markup(f"<b>{GLib.markup_escape_text(key)}</b>")
k.get_style_context().add_class("dim-label")
v = Gtk.Label(label=val, xalign=0, selectable=True)
v.set_line_wrap(True)
grid.attach(k, 0, i, 1, 1)
grid.attach(v, 1, i, 1, 1)
return grid
def _project_tab(osr):
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10, margin=20)
title = Gtk.Label()
name = osr.get("PRETTY_NAME") or osr.get("NAME") or "FWS Linux"
title.set_markup(f"<span size='xx-large' weight='bold'>{name}</span>")
box.pack_start(title, False, False, 0)
tag = Gtk.Label(label="Une distribution Linux pensée pour remplacer "
"Windows : bureau prêt à l'emploi, jeux, VR et "
"compatibilité applicative.")
tag.set_line_wrap(True)
tag.set_max_width_chars(52)
box.pack_start(tag, False, False, 0)
feats = Gtk.Label()
feats.set_markup(
"<b>Inclus :</b> installateur graphique · plusieurs bureaux "
"(GNOME, KDE, Hyprland, i3, XFCE) · Steam &amp; Proton-GE · "
"réalité virtuelle (SteamVR) · winboat (applications Windows) · "
"pare-feu · réparation système intégrée.")
feats.set_line_wrap(True)
feats.set_max_width_chars(52)
feats.set_xalign(0)
box.pack_start(feats, False, False, 6)
home = osr.get("HOME_URL") or "https://github.com/You-re-like-Windows-a-bitch/fws"
link = Gtk.LinkButton.new_with_label(home, "Site du projet")
box.pack_start(link, False, False, 0)
return box
def _system_tab(osr):
rows = [
("Système", osr.get("PRETTY_NAME", "FWS Linux")),
("Build", osr.get("BUILD_ID", "rolling")),
("Noyau", _cmd("uname", "-r") or "inconnu"),
("Bureau", _desktop()),
("Affichage", _session_type()),
("Nom de machine", _cmd("hostname") or os.uname().nodename),
("Processeur", _cpu()),
("Mémoire", _mem()),
("Carte(s) graphique(s)", "\n".join(_gpus())),
("Disque(s)", "\n".join(_disks())),
]
scroll = Gtk.ScrolledWindow()
scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
scroll.add(_grid(rows))
return scroll
class AboutWindow(Gtk.Window):
def __init__(self):
super().__init__(title="À propos de FWS")
self.set_default_size(520, 460)
self.set_position(Gtk.WindowPosition.CENTER)
self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
osr = _osrelease()
notebook = Gtk.Notebook()
notebook.append_page(_project_tab(osr), Gtk.Label(label="Le projet FWS"))
notebook.append_page(_system_tab(osr), Gtk.Label(label="Système"))
self.add(notebook)
self.connect("destroy", Gtk.main_quit)
def main():
if not Gtk.init_check()[0]:
return 1
win = AboutWindow()
win.show_all()
Gtk.main()
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,256 @@
#!/bin/bash
# ============================================================
# fws-gameboot — orchestrateur dual-boot « hibernate-swap » (FWS ⇄ Windows)
#
# But : jouer à un jeu à anticheat kernel (Valorant/Vanguard) qui REFUSE la
# virtualisation, sans quitter durablement FWS. On hiberne FWS (RAM → swap), on
# arme un boot UEFI one-shot (BootNext) vers un Windows 11 BARE-METAL, et on
# redémarre. Windows tourne sur le vrai matériel (AUCUN hyperviseur) → Vanguard
# content. Au retour de Windows, le noyau reprend la session FWS via resume=.
#
# Ce N'EST PAS une VM et il n'y a AUCUN hyperviseur : c'est un dual-boot
# orchestré. Le « seamless » vient de l'hibernation (session restaurée), pas
# d'une exécution simultanée (impossible sans hyperviseur = ban Vanguard).
#
# Filet de sûreté PERMANENT : BootOrder[0] = FWS (posé par fws-gameboot-bootfix
# à chaque démarrage). BootNext ne sert QUE pour les transitions one-shot ; tout
# crash/reboot non géré retombe sur FWS.
#
# ⚠ Exige une VALIDATION MATÉRIELLE réelle (hibernation + resume GPU NVIDIA,
# sémantique BootNext du firmware). Voir docs/hibernate-swap-dualboot.md §7.
# ============================================================
set -u
CONF=/etc/fws/gameboot.conf
# shellcheck source=/dev/null
[ -r "$CONF" ] && . "$CONF"
: "${FWS_WINDOWS_BOOTNUM:=}"
: "${FWS_HIBERNATE_FALLBACK:=reboot}"
: "${FWS_SWAP_MARGIN_MB:=2048}"
: "${FWS_DEFAULT_GAME:=valorant}"
log() { printf '\e[36m[gameboot]\e[0m %s\n' "$*" >&2; }
warn() { printf '\e[33m[gameboot] %s\e[0m\n' "$*" >&2; }
die() { printf '\e[31m[gameboot] ERREUR : %s\e[0m\n' "$*" >&2; exit 1; }
# Notifie l'utilisateur graphique (best-effort ; on tourne en root via pkexec).
notify_user() {
command -v notify-send >/dev/null 2>&1 || return 0
local u uid
while IFS=: read -r u _ uid _; do
[ "$uid" -ge 1000 ] && [ "$uid" -le 60000 ] || continue
[ -S "/run/user/$uid/bus" ] || continue
sudo -u "$u" DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$uid/bus" \
notify-send -a FWS -u critical "FWS Gaming" "$1" 2>/dev/null || true
done < /etc/passwd
}
need_root() { [ "$(id -u)" -eq 0 ] || die "root requis — lance : pkexec fws-gameboot ${1:-}"; }
# --- Entrée UEFI « Windows Boot Manager » (numéro Boot####, HEX MAJUSCULE) ---
find_windows_bootnum() {
local n
if [ -n "$FWS_WINDOWS_BOOTNUM" ]; then n="$FWS_WINDOWS_BOOTNUM"; else
n="$(efibootmgr 2>/dev/null | sed -n 's/^Boot\([0-9A-Fa-f]\{4\}\)\*\? *Windows Boot Manager.*/\1/p' | head -1)"
fi
# Normaliser en MAJUSCULE : efibootmgr affiche « BootNext: 000A » en majuscule ;
# une valeur config en minuscule ferait échouer la relecture (m6).
printf '%s' "${n^^}"
}
# --- Notre propre entrée (FWS ou, à défaut, GRUB) ---
find_fws_bootnum() {
efibootmgr 2>/dev/null | sed -n 's/^Boot\([0-9A-Fa-f]\{4\}\)\*\? *\(FWS\|GRUB\).*/\1/p' | head -1
}
# --- VRAM en usage (Mo), 0 si pas de NVIDIA — recopiée dans l'image S4 ---
vram_used_mb() {
command -v nvidia-smi >/dev/null 2>&1 || { echo 0; return; }
nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits 2>/dev/null \
| awk '{s+=$1} END{print s+0}'
}
# Renvoie 0 si tout est OK pour hiberner. Décrit chaque échec sur stderr.
preflight() {
local mem_kb swap_kb vram_mb need_mb have_mb
grep -q disk /sys/power/state 2>/dev/null || { warn "hibernation indisponible (/sys/power/state sans « disk »)"; return 1; }
grep -q 'resume=' /proc/cmdline || { warn "resume= absent de la cmdline noyau (hibernation non reprenable)"; return 1; }
swap_kb=$(awk '/^SwapTotal:/{print $2}' /proc/meminfo)
[ "${swap_kb:-0}" -gt 0 ] || { warn "aucun swap actif (rien où écrire l'image)"; return 1; }
mem_kb=$(awk '/^MemTotal:/{print $2}' /proc/meminfo)
vram_mb=$(vram_used_mb)
need_mb=$(( mem_kb/1024 + vram_mb + FWS_SWAP_MARGIN_MB ))
have_mb=$(( swap_kb/1024 ))
if [ "$have_mb" -lt "$need_mb" ]; then
warn "swap trop petit : ${have_mb} Mo < ${need_mb} Mo requis (RAM $((mem_kb/1024)) + VRAM ${vram_mb} + marge ${FWS_SWAP_MARGIN_MB})"
return 1
fi
return 0
}
# Résout le point de montage de l'ESP (FAT). Vide si aucun.
find_esp() {
findmnt -rno TARGET /boot/efi 2>/dev/null \
|| findmnt -rno TARGET /efi 2>/dev/null \
|| findmnt -rno TARGET /boot 2>/dev/null
}
cmd_doctor() {
local sb f w
sb=$(bootctl status 2>/dev/null | sed -n 's/^ *Secure Boot: *//p' | head -1)
f="$(find_fws_bootnum)"; w="$(find_windows_bootnum)"
echo "== fws-gameboot doctor =="
echo "Entrée FWS : ${f:-?}"
echo "Entrée Windows : ${w:-INTROUVABLE}"
echo "RAM : $(( $(awk '/^MemTotal:/{print $2}' /proc/meminfo)/1024 )) Mo"
echo "Swap : $(( $(awk '/^SwapTotal:/{print $2}' /proc/meminfo)/1024 )) Mo"
echo "VRAM en usage : $(vram_used_mb) Mo"
echo "resume= cmdline : $(grep -o 'resume=[^ ]*' /proc/cmdline || echo 'ABSENT')"
echo "Secure Boot : ${sb:-inconnu}"
echo -n "Préflight : "
if preflight 2>/dev/null; then printf '\e[32mOK\e[0m\n'; else printf '\e[31mÉCHEC\e[0m\n'; preflight; fi
case "$sb" in
enabled) ;;
*) warn "Secure Boot inactif → Vanguard refusera Windows (VAN9003). Configure d'abord : sudo fws-secureboot-setup" ;;
esac
}
cmd_to_windows() {
need_root "to-windows"
local win esp tok bootnext_set="" rc m game="${FWS_DEFAULT_GAME:-valorant}" game_req=""
while [ $# -gt 0 ]; do
case "$1" in
--game) game="${2:-}"; game_req=1; shift 2 ;;
--game=*) game="${1#--game=}"; game_req=1; shift ;;
*) die "argument inconnu : $1 (usage : to-windows [--game <id>])" ;;
esac
done
# Charte d'identifiant alignée sur les clés de games.json : [a-z0-9_-]
# (l'underscore doit passer, sinon divergence FWS↔Windows).
game="$(printf '%s' "$game" | tr '[:upper:]' '[:lower:]' | tr -cd 'a-z0-9_-')"
[ -n "$game_req" ] && [ -z "$game" ] && warn "identifiant --game invalide après nettoyage — jeu par défaut appliqué."
win="$(find_windows_bootnum)"
if [ -z "$win" ]; then
notify_user "Windows introuvable — installe d'abord le Windows gaming (entrée de boot UEFI absente)."
die "entrée « Windows Boot Manager » introuvable (Windows installé + entrée UEFI présente ?)"
fi
esp="$(find_esp)"
# Jeton « quel jeu lancer », lu ET CONSOMMÉ par fws-play côté Windows.
# Écriture ATOMIQUE (.tmp puis mv) sur l'ESP FAT uniquement. En cas d'échec,
# on PURGE tout jeton périmé (jamais de rejeu d'un ancien choix) → Windows
# lancera le jeu par défaut plutôt qu'un mauvais jeu.
if [ -n "$esp" ] && [ "$(findmnt -no FSTYPE "$esp" 2>/dev/null)" = vfat ]; then
if [ -n "$game" ]; then
mkdir -p "$esp/EFI/FWS" 2>/dev/null || true
tok="$esp/EFI/FWS/launch.json"
if printf '{"game":"%s"}\n' "$game" > "$tok.tmp" 2>/dev/null && mv -f "$tok.tmp" "$tok" 2>/dev/null; then
log "Jeu demandé : $game"
else
rm -f "$tok" "$tok.tmp" 2>/dev/null || true
warn "écriture du jeton échouée → jeton purgé (Windows lancera le jeu par défaut)."
notify_user "Choix du jeu non transmis — Windows lancera le jeu par défaut."
fi
fi
elif [ -n "$game_req" ]; then
warn "ESP FAT introuvable → le choix du jeu ne peut pas être transmis à Windows."
fi
# Filet : ne JAMAIS laisser BootNext armé si on ne bascule pas réellement
# (sinon départ surprise vers Windows au prochain reboot manuel).
cleanup() { [ -n "$bootnext_set" ] && { efibootmgr -N >/dev/null 2>&1 || true; log "BootNext annulé (nettoyage)"; }; }
trap cleanup EXIT
# Démonter toute NTFS montée : jamais rw persistante à travers le cycle.
# `findmnt -lno` (liste, PAS -r/raw) → chemins NON échappés (espaces OK) ; on
# se limite aux vrais types NTFS (pas fuseblk générique).
while IFS= read -r m; do
[ -n "$m" ] && umount "$m" 2>/dev/null || true
done < <(findmnt -lno TARGET -t ntfs,ntfs3 2>/dev/null)
if ! preflight; then
case "$FWS_HIBERNATE_FALLBACK" in
reboot)
warn "Préflight hibernation KO → bascule en REBOOT SIMPLE (session FWS NON préservée)."
notify_user "Hibernation indisponible — bascule vers Windows par reboot simple (session non sauvegardée)."
efibootmgr -n "$win" >/dev/null || die "écriture de BootNext échouée"
bootnext_set=1 # ARMÉ : si la relecture échoue, le trap nettoie (M2)
efibootmgr 2>/dev/null | grep -qi "BootNext: $win" || die "BootNext non confirmé par le firmware"
trap - EXIT # bascule assumée
log "Reboot simple vers Windows (Boot$win)…"
exec systemctl reboot
;;
*) die "Préflight hibernation KO et fallback='abort' → on reste sur FWS." ;;
esac
fi
# --- Bascule NOMINALE : BootNext=Windows puis hibernation ---
efibootmgr -n "$win" >/dev/null || die "écriture de BootNext échouée"
bootnext_set=1
# RELECTURE obligatoire : certains firmwares ignorent/écrasent BootNext.
efibootmgr 2>/dev/null | grep -qi "BootNext: $win" \
|| die "BootNext non pris en compte par le firmware (entrée Boot$win) — carte non compatible ?"
log "BootNext armé vers Windows (Boot$win). Hibernation…"
notify_user "Lancement de Windows pour le jeu — FWS s'endort, ta session sera restaurée au retour."
# M3 : l'ESP (FAT, potentiellement partagée avec Windows) ne doit pas rester
# montée rw à travers le cycle — un cache FAT périmé réécrit au resume
# corromprait les bootloaders (seul chemin réaliste vers un vrai brick). On
# la remonte en lecture seule le temps de l'hibernation, rw au retour.
[ -n "$esp" ] && { mount -o remount,ro "$esp" 2>/dev/null || true; }
# NB : on n'enveloppe PAS « systemctl hibernate » dans un timeout. Un
# hibernate RÉUSSI ne « rend la main » qu'au RESUME (après la session
# Windows, potentiellement > 1 h) : un timeout tuerait les hibernations
# réussies. Une hibernation qui HANG (driver refusant S4) laisse la machine
# allumée et n'est PAS mitigeable en userspace (le process est gelé) ; c'est
# un risque à valider sur matériel réel (docs §7 #6). Le préflight a déjà
# éliminé les causes courantes (swap absent/petit, resume= manquant).
if systemctl hibernate; then
# On revient ici au RESUME. Bascule réussie : le firmware a normalement
# consommé BootNext au boot de Windows — MAIS un override manuel (menu
# F12) peut le laisser armé. On le purge donc INCONDITIONNELLEMENT (M1) :
# no-op s'il est déjà consommé, rétablit le filet BootOrder=FWS sinon.
[ -n "$esp" ] && { mount -o remount,rw "$esp" 2>/dev/null || true; }
efibootmgr -N >/dev/null 2>&1 || true
bootnext_set=""
trap - EXIT
log "Session FWS restaurée. Bon retour."
else
rc=$?
[ -n "$esp" ] && { mount -o remount,rw "$esp" 2>/dev/null || true; }
warn "Hibernation échouée (rc=$rc)."
if [ "$FWS_HIBERNATE_FALLBACK" = reboot ]; then
notify_user "Hibernation impossible — bascule vers Windows par reboot simple (session non sauvegardée)."
if efibootmgr -n "$win" >/dev/null 2>&1 && efibootmgr 2>/dev/null | grep -qi "BootNext: $win"; then
bootnext_set="" # on assume la bascule, pas de nettoyage
trap - EXIT
log "Reboot simple vers Windows…"
exec systemctl reboot
fi
fi
die "Bascule annulée — on reste sur FWS (BootNext nettoyé)."
fi
}
usage() {
cat <<EOF
fws-gameboot — dual-boot hibernate-swap (FWS ⇄ Windows bare-metal, anticheat kernel)
fws-gameboot doctor Vérifie l'environnement (préflight, boot)
fws-gameboot to-windows [--game <id>] Hiberne FWS, bascule + lance <id> (root/pkexec)
fws-gameboot status Alias de « doctor »
Le jeu par défaut (FWS_DEFAULT_GAME) est « valorant ». Les identifiants connus
sont définis dans games.json côté Windows (valorant, lol, …).
Config : $CONF
Détails et risques : docs/hibernate-swap-dualboot.md
EOF
}
case "${1:-}" in
to-windows) shift; cmd_to_windows "$@" ;;
doctor|status) cmd_doctor ;;
''|-h|--help|help) usage ;;
*) die "sous-commande inconnue : $1 (voir --help)" ;;
esac
@@ -0,0 +1,31 @@
#!/bin/bash
# ============================================================
# fws-gameboot-bootfix — exécuté à CHAQUE démarrage de FWS (oneshot systemd).
#
# Réasserte BootOrder[0] = FWS. Filet de sûreté PERMANENT : après un Windows
# Update qui remet « Windows Boot Manager » en tête de BootOrder, ou après tout
# crash/reboot non géré, un boot SANS BootNext retombe toujours sur FWS (jamais
# coincé côté Windows ; l'image d'hibernation reste accessible).
#
# Inerte sur le live (le service porte ConditionPathExists=!/run/archiso).
# ============================================================
set -u
[ -d /sys/firmware/efi ] || exit 0
command -v efibootmgr >/dev/null 2>&1 || exit 0
fws="$(efibootmgr 2>/dev/null | sed -n 's/^Boot\([0-9A-Fa-f]\{4\}\)\*\? *\(FWS\|GRUB\).*/\1/p' | head -1)"
[ -n "$fws" ] || exit 0
# Nettoyer une entrée « FWS WinPE » résiduelle du déploiement Windows (repli
# bcdboot, one-shot déjà consommé) : sinon elle traîne dans le menu de boot.
for pe in $(efibootmgr 2>/dev/null | sed -n 's/^Boot\([0-9A-Fa-f]\{4\}\)\*\? *FWS WinPE.*/\1/p'); do
efibootmgr -b "$pe" -B >/dev/null 2>&1 || true
done
# BootOrder[0] = FWS (sans dupliquer, en préservant l'ordre existant derrière).
order="$(efibootmgr 2>/dev/null | sed -n 's/^BootOrder: //p' | tr -d '[:space:]')"
if [ -n "$order" ] && [ "${order%%,*}" != "$fws" ]; then
rest="$(printf '%s' "$order" | tr ',' '\n' | grep -vx "$fws" | paste -sd, -)"
efibootmgr -o "${fws}${rest:+,$rest}" >/dev/null 2>&1 || true
fi
exit 0
+158
View File
@@ -0,0 +1,158 @@
#!/usr/bin/env python3
# FWS — fenêtre de bienvenue au premier démarrage (pendant post-install de la
# page récap de l'installateur). Boutons : mise à jour, codecs, VR, raccourcis,
# Proton-GE. GTK3 : présent sur TOUS les bureaux FWS (Firefox le tire) ;
# python-gobject est installé avec le bureau par le kickstart.
#
# Lancement : /etc/xdg/autostart (KDE/GNOME/XFCE) et exec des configs
# Hyprland/i3, toujours avec --autostart : on se retire sans bruit si
# l'utilisateur a coché « ne plus afficher » (~/.config/fws/hello-done).
import os
import shutil
import subprocess
import sys
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("Gdk", "3.0") # sinon gi charge Gdk 4.0 (conflit avec Gtk3)
from gi.repository import Gdk, GLib, Gtk # noqa: E402
# WM_CLASS déterministe : les règles i3/Hyprland « fenêtre flottante »
# ciblent cette classe.
GLib.set_prgname("fws-hello")
_FLAG = os.path.expanduser("~/.config/fws/hello-done")
_CODECS = ("gst-plugins-base gst-plugins-good gst-plugins-bad "
"gst-plugins-ugly gst-libav ffmpeg mpv")
# (libellé, description, commande, en_terminal)
_ACTIONS = [
("Mettre à jour le système",
"pacman -Syu + nettoyage du cache",
"fws-update", True),
("Installer les codecs multimédia",
"lecture vidéo/audio (GStreamer, ffmpeg, mpv)",
f"sudo pacman -S --needed {_CODECS}", True),
("Proton-GE pour Steam",
"meilleure compatibilité des jeux Windows",
"fws-proton-ge", True),
("Configurer la VR",
"drivers casque : HTC Vive, Index, Quest…",
"fws-vr-setup", True),
]
_TERMINALS = ["kitty", "konsole", "gnome-terminal", "xfce4-terminal", "xterm"]
def _terminal():
for term in _TERMINALS:
if shutil.which(term):
return term
return None
def _run(command, in_terminal):
if in_terminal:
term = _terminal()
if term is None:
return
if term == "gnome-terminal":
argv = [term, "--", "sh", "-c", command]
else:
argv = [term, "-e", "sh", "-c", command]
else:
argv = ["sh", "-c", command]
subprocess.Popen(argv) # noqa: S603 — commandes internes FWS
class HelloWindow(Gtk.Window):
def __init__(self):
super().__init__(title="Bienvenue sur FWS")
self.set_default_size(460, -1)
self.set_position(Gtk.WindowPosition.CENTER)
self.set_border_width(18)
# DIALOGUE : les tuileurs (i3/Hyprland) laissent les dialogues en
# flottant → la fenêtre garde sa taille naturelle au lieu d'être
# étirée sur tout l'espace de travail (layout illisible sinon).
self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
self.set_resizable(False)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
self.add(box)
title = Gtk.Label()
title.set_markup("<span size='x-large' weight='bold'>Bienvenue sur FWS !</span>")
box.pack_start(title, False, False, 0)
subtitle = Gtk.Label(
label="Ton système est prêt. Quelques réglages utiles pour bien démarrer :")
subtitle.set_line_wrap(True)
subtitle.set_xalign(0)
box.pack_start(subtitle, False, False, 0)
actions = list(_ACTIONS)
# Raccourcis clavier : uniquement sur les sessions qui ont fws-keys
# (Hyprland/i3) — les autres bureaux ont leurs propres réglages.
session = os.environ.get("XDG_CURRENT_DESKTOP", "").lower()
if os.environ.get("HYPRLAND_INSTANCE_SIGNATURE") or "i3" in session:
actions.append(("Voir les raccourcis clavier",
"aussi disponible à tout moment : Super+K",
"fws-keys", False))
for label, desc, command, in_term in actions:
button = Gtk.Button()
inner = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2)
lab = Gtk.Label()
lab.set_markup(f"<b>{label}</b>")
lab.set_xalign(0)
sub = Gtk.Label(label=desc)
sub.set_xalign(0)
sub.get_style_context().add_class("dim-label")
inner.pack_start(lab, False, False, 0)
inner.pack_start(sub, False, False, 0)
button.add(inner)
button.connect("clicked",
lambda _b, c=command, t=in_term: _run(c, t))
box.pack_start(button, False, False, 0)
self._again = Gtk.CheckButton(label="Afficher cette fenêtre au prochain démarrage")
self._again.set_active(False)
box.pack_start(self._again, False, False, 6)
close = Gtk.Button(label="Fermer")
close.connect("clicked", lambda _b: self.close())
box.pack_start(close, False, False, 0)
self.connect("destroy", self._on_destroy)
def _on_destroy(self, _win):
if not self._again.get_active():
os.makedirs(os.path.dirname(_FLAG), exist_ok=True)
with open(_FLAG, "w") as fh:
fh.write("ok\n")
else:
try:
os.unlink(_FLAG)
except OSError:
pass
Gtk.main_quit()
def main():
if "--autostart" in sys.argv and os.path.exists(_FLAG):
return 0
# Pas d'affichage joignable (lancement hors session graphique) : on se
# retire sans traceback plutôt que de crasher.
if not Gtk.init_check()[0]:
return 1
win = HelloWindow()
win.show_all()
Gtk.main()
return 0
if __name__ == "__main__":
sys.exit(main())
+129
View File
@@ -0,0 +1,129 @@
#!/usr/bin/env python3
# FWS — affiche les raccourcis clavier RÉELS de la session (bouton ⌨ de la
# barre ou Super+K). Universel :
# • Hyprland : « hyprctl binds -j » = les binds effectivement chargés
# (descriptions des bindd) — affichage wofi (Wayland).
# • i3 : parse ~/.config/i3/config (bindsym, variables résolues)
# — affichage rofi (X11).
# Aucune dépendance hors python3 + wofi/rofi (déjà installés avec le bureau).
import json
import os
import re
import subprocess
import sys
_WOFI_STYLE = "/usr/local/share/fws/wofi-keys.css"
# Bits de modmask Hyprland (masques X11) dans l'ordre d'affichage voulu.
_MODS = [(64, "SUPER"), (4, "CTRL"), (8, "ALT"), (1, "SHIFT")]
_KEY_LABELS = {
"mouse:272": "clic gauche",
"mouse:273": "clic droit",
"mouse:274": "clic molette",
"mouse_down": "molette bas",
"mouse_up": "molette haut",
"button4": "molette haut",
"button5": "molette bas",
"return": "Entrée",
"space": "Espace",
"escape": "Échap",
"tab": "Tab",
"left": "←",
"right": "→",
"up": "↑",
"down": "↓",
"print": "Impr écran",
}
def _label(key):
return _KEY_LABELS.get(key.lower(), key)
# --- Hyprland : binds réels via hyprctl ------------------------------------
def hyprland_lines():
raw = subprocess.check_output(["hyprctl", "binds", "-j"], text=True)
lines, seen = [], set()
for bind in json.loads(raw):
parts = [name for bit, name in _MODS if bind.get("modmask", 0) & bit]
key = bind.get("key") or (f"code {bind['keycode']}" if bind.get("keycode") else "")
parts.append(_label(key))
combo = " + ".join(p for p in parts if p)
if not combo or combo in seen:
continue
seen.add(combo)
desc = (bind.get("description") or "").strip() \
or f"{bind.get('dispatcher', '?')} {(bind.get('arg') or '').strip()}".strip()
lines.append(f"{combo:<24} {desc}")
return lines
# --- i3 : parse de la config (variables résolues) ----------------------------
def i3_lines():
path = os.path.expanduser("~/.config/i3/config")
variables = {}
lines, seen = [], set()
with open(path, encoding="utf-8", errors="replace") as fh:
content = fh.readlines()
for line in content:
m = re.match(r"\s*set\s+(\$\S+)\s+(.+)$", line)
if m:
variables[m.group(1)] = m.group(2).strip()
def resolve(text):
# $var les plus longs d'abord ($filemanager avant $file…).
for var in sorted(variables, key=len, reverse=True):
text = text.replace(var, variables[var])
return text
for line in content:
m = re.match(r"\s*bindsym\s+(?:--\S+\s+)*(\S+)\s+(.+)$", line)
if not m:
continue
combo_raw, action = m.groups()
combo_raw = resolve(combo_raw).replace("Mod4", "Super").replace("Mod1", "Alt")
combo = " + ".join(_label(k) for k in combo_raw.split("+"))
if combo in seen:
continue
seen.add(combo)
action = resolve(action)
action = re.sub(r"^exec\s+(--no-startup-id\s+)?", "", action).strip().strip('"')
lines.append(f"{combo:<24} {action}")
return lines
def show(lines):
if not lines:
lines = ["(aucun raccourci trouvé)"]
text = "\n".join(lines)
if os.environ.get("HYPRLAND_INSTANCE_SIGNATURE"):
cmd = ["wofi", "--dmenu", "--prompt", "Raccourcis clavier — taper pour filtrer",
"--width", "820", "--height", "600", "--insensitive",
"--cache-file", "/dev/null"]
if os.path.exists(_WOFI_STYLE):
cmd += ["--style", _WOFI_STYLE]
else:
# X11/i3 : rofi en dmenu, style assorti (mono pour l'alignement).
cmd = ["rofi", "-dmenu", "-i", "-p", "Raccourcis clavier",
"-no-custom",
"-theme-str", 'window { width: 880px; } listview { lines: 18; }',
"-theme-str", '* { font: "Noto Sans Mono 11"; }']
subprocess.run(cmd, input=text, text=True, check=False)
def main():
try:
if os.environ.get("HYPRLAND_INSTANCE_SIGNATURE"):
lines = hyprland_lines()
else:
lines = i3_lines()
except Exception as exc: # noqa: BLE001 — best effort, jamais de crash muet
subprocess.run(["notify-send", "Raccourcis FWS", f"Erreur : {exc}"],
check=False)
return 1
show(lines)
return 0
if __name__ == "__main__":
sys.exit(main())
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env python3
# FWS — menu d'alimentation (verrouiller / déconnexion / suspendre /
# redémarrer / éteindre). Bouton de la barre ou Super+X. Universel :
# • Hyprland (Wayland) → wofi, verrouillage hyprlock, sortie hyprctl.
# • i3 (X11) → rofi, verrouillage i3lock, sortie i3-msg exit.
# Aucune dépendance hors python3 + wofi/rofi (déjà là avec le bureau).
import os
import subprocess
import sys
_WOFI_STYLE = "/usr/local/share/fws/wofi-style.css"
_HYPR = bool(os.environ.get("HYPRLAND_INSTANCE_SIGNATURE"))
# (libellé affiché, commande shell). L'icône Nerd Font aide au repérage.
if _HYPR:
_lock = "hyprlock"
_logout = "hyprctl dispatch exit"
else:
_lock = "i3lock -c 1e1e2e"
_logout = "i3-msg exit"
_ITEMS = [
("󰌾 Verrouiller", _lock),
("󰗽 Déconnexion", _logout),
("󰤄 Suspendre", "systemctl suspend"),
("󰜉 Redémarrer", "systemctl reboot"),
("󰐥 Éteindre", "systemctl poweroff"),
]
def _menu(entries):
text = "\n".join(entries)
if _HYPR:
cmd = ["wofi", "--dmenu", "--prompt", "Alimentation",
"--width", "300", "--height", "260", "--cache-file", "/dev/null"]
if os.path.exists(_WOFI_STYLE):
cmd += ["--style", _WOFI_STYLE]
else:
cmd = ["rofi", "-dmenu", "-i", "-p", "Alimentation", "-no-custom",
"-theme-str", "window { width: 300px; } listview { lines: 5; }"]
out = subprocess.run(cmd, input=text, text=True,
capture_output=True, check=False)
return out.stdout.strip()
def main():
choice = _menu([label for label, _ in _ITEMS])
if not choice:
return 0
for label, command in _ITEMS:
if label == choice:
subprocess.Popen(["sh", "-c", command]) # noqa: S603,S607
break
return 0
if __name__ == "__main__":
sys.exit(main())
+46
View File
@@ -0,0 +1,46 @@
#!/bin/bash
# FWS — installe/actualise Proton-GE (GloriousEggroll) pour Steam :
# meilleure compatibilité des jeux Windows que le Proton officiel.
# S'exécute en UTILISATEUR (installe dans ~/.steam/root/compatibilitytools.d).
set -u
say() { printf '\n\033[1;34m==> %s\033[0m\n' "$*"; }
fail() { printf '\033[1;31m ✗ %s\033[0m\n' "$*"; read -rp "Entrée pour fermer. " _; exit 1; }
[ "$(id -u)" -eq 0 ] && fail "à lancer en utilisateur normal (pas root) : l'outil s'installe dans ton Steam."
API="https://api.github.com/repos/GloriousEggroll/proton-ge-custom/releases/latest"
DEST="$HOME/.steam/root/compatibilitytools.d"
say "Recherche de la dernière version de Proton-GE…"
JSON="$(curl -fsSL "$API")" || fail "GitHub inaccessible (réseau ?)"
URL="$(printf '%s' "$JSON" | grep -o '"browser_download_url": *"[^"]*\.tar\.gz"' | cut -d'"' -f4 | head -1)"
SUM_URL="$(printf '%s' "$JSON" | grep -o '"browser_download_url": *"[^"]*\.sha512sum"' | cut -d'"' -f4 | head -1)"
[ -n "$URL" ] || fail "archive introuvable dans la release GitHub"
NAME="$(basename "$URL" .tar.gz)"
if [ -d "$DEST/$NAME" ]; then
say "$NAME est déjà installé ✔"
read -rp "Entrée pour fermer. " _; exit 0
fi
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
say "Téléchargement de $NAME (~450 Mo)…"
curl -fL --progress-bar -o "$TMP/$NAME.tar.gz" "$URL" || fail "téléchargement échoué"
if [ -n "$SUM_URL" ]; then
say "Vérification de l'intégrité (sha512)…"
curl -fsSL -o "$TMP/sum" "$SUM_URL" \
&& (cd "$TMP" && sed "s|[^ ]*\.tar\.gz|$NAME.tar.gz|" sum | sha512sum -c -) \
|| fail "somme de contrôle invalide — archive corrompue ?"
fi
say "Installation dans $DEST…"
mkdir -p "$DEST"
tar -xzf "$TMP/$NAME.tar.gz" -C "$DEST" || fail "extraction échouée"
say "$NAME installé ✔"
echo " Dans Steam : Paramètres → Compatibilité → activer, puis choisir"
echo " « $NAME » (ou par jeu : Propriétés → Compatibilité)."
echo " Redémarre Steam pour qu'il apparaisse."
read -rp "Entrée pour fermer. " _
+220
View File
@@ -0,0 +1,220 @@
#!/bin/bash
# ============================================================================
# FWS — moteur de réparation d'une installation existante depuis le live
# (« point de restauration à la Windows »).
#
# L'interface GRAPHIQUE est fws-recovery-gui (GTK3 python — le live embarque
# python-gobject/gtk3 via Anaconda) ; elle pilote CE script en sous-commandes.
# Sans argument : assistant CONSOLE (repli sans X, ou usage à la main).
#
# Sous-commandes (pour la GUI / scripts) :
# scan → une ligne « périphérique|nom » par install FWS
# repair <action> <dev> → monte, répare, démonte ; sortie en continu
# action ∈ grub|initramfs|pacman|pacman-update|all
# shell <dev> → terminal DANS le système (xterm si DISPLAY)
#
# arch-chroot (arch-install-scripts, présent sur le live) gère les binds.
#
# Fonctionne depuis le LIVE (autre disque) ET depuis le SYSTÈME INSTALLÉ
# lui-même (menu d'applications / entrée GRUB « Réparer FWS ») : si la cible
# est la racine en cours d'exécution, on opère directement, sans mount/chroot.
# ============================================================================
set -u
MNT=/mnt/fws-recovery
ROOTDEV=""
DO_UPDATE=""
SELF="" # cible = système courant
ROOTSRC="$(findmnt -no SOURCE / 2>/dev/null)" # sur le live : overlay → jamais un lsblk
say() { printf '\n==> %s\n' "$*"; }
warn() { printf ' ⚠ %s\n' "$*"; }
[ "$(id -u)" -eq 0 ] || { echo "root requis"; exit 1; }
cleanup() { [ -n "$MNT" ] && [ "$MNT" != "/" ] && umount -R "$MNT" 2>/dev/null; }
# Exécute DANS la cible : chroot si elle est montée, directement si c'est nous.
in_target() {
if [ -n "$SELF" ]; then "$@"; else arch-chroot "$MNT" "$@"; fi
}
# ── Moteur ───────────────────────────────────────────────────────────────────
scan_installs() {
vgscan --mknodes >/dev/null 2>&1
vgchange -ay >/dev/null 2>&1
udevadm settle --timeout=10 >/dev/null 2>&1
# Le système en cours d'exécution d'abord (app lancée depuis l'OS installé).
if [ ! -d /run/archiso ] && grep -qiE '^(ID|NAME)=.*fws' /etc/os-release 2>/dev/null; then
printf '%s|%s (ce système)\n' "$ROOTSRC" \
"$(. /etc/os-release 2>/dev/null; echo "${PRETTY_NAME:-FWS}")"
fi
mkdir -p "$MNT"
local dev name
while read -r dev; do
[ -b "$dev" ] || continue
[ "$dev" = "$ROOTSRC" ] && continue # déjà listé ci-dessus
mount -o ro "$dev" "$MNT" 2>/dev/null || continue
if [ -f "$MNT/etc/os-release" ] && [ -d "$MNT/boot" ] \
&& grep -qiE '^(ID|NAME)=.*fws' "$MNT/etc/os-release" 2>/dev/null; then
name="$(. "$MNT/etc/os-release" 2>/dev/null; echo "${PRETTY_NAME:-FWS}")"
printf '%s|%s\n' "$dev" "$name"
fi
umount "$MNT" 2>/dev/null
done < <(lsblk -rpno NAME,TYPE | awk '$2=="part"||$2=="lvm"{print $1}')
}
# Monte la cible + /boot + ESP d'après son fstab.
# Cas particulier : la cible EST la racine en cours d'exécution (app lancée
# depuis l'OS installé, ou entrée GRUB « Réparer FWS ») → tout est déjà là,
# on opère en place (MNT="" : les chemins "$MNT/…" pointent sur la vraie racine).
mount_target() {
ROOTDEV="$1"
if [ "$ROOTDEV" = "$ROOTSRC" ] && [ ! -d /run/archiso ]; then
SELF=1; MNT=""
echo " cible = système en cours d'exécution (pas de montage)"
return 0
fi
mkdir -p "$MNT"
mount "$ROOTDEV" "$MNT" || return 1
local mp src
for mp in /boot /boot/efi; do
src="$(awk -v mp="$mp" '$2==mp && $1!~/^#/{print $1; exit}' "$MNT/etc/fstab" 2>/dev/null)"
[ -n "$src" ] || continue
mount "$(findfs "$src" 2>/dev/null || echo "$src")" "$MNT$mp" 2>/dev/null \
&& echo " $mp monté" || warn "$mp non monté (on continue)"
done
return 0
}
fix_grub() {
say "Réinstallation de GRUB…"
if [ -d /sys/firmware/efi ]; then
in_target grub-install --target=x86_64-efi \
--efi-directory=/boot/efi --bootloader-id=FWS --recheck || true
in_target grub-install --target=x86_64-efi \
--efi-directory=/boot/efi --bootloader-id=FWS --removable --recheck
else
local disk
disk="$(lsblk -spno NAME,TYPE "$ROOTDEV" 2>/dev/null \
| awk '$2=="disk"{print $1; exit}' | grep -oE '/dev/[a-z0-9]+')"
[ -n "$disk" ] || { warn "disque BIOS introuvable"; return 1; }
in_target grub-install --target=i386-pc --recheck "$disk"
fi
in_target grub-mkconfig -o /boot/grub/grub.cfg
}
fix_initramfs() {
say "Régénération de l'initramfs (mkinitcpio -P)…"
echo " NB : les avertissements « Possibly missing firmware for module … » sont"
echo " NORMAUX sur Arch : firmware de matériel exotique (cartes SCSI/fibre…)"
echo " non fourni par linux-firmware. Sans effet, sauf si tu AS ce matériel."
in_target mkinitcpio -P
}
fix_pacman() {
say "Réparation de pacman…"
rm -f "$MNT/var/lib/pacman/db.lck" && echo " verrou db.lck retiré (s'il existait)"
in_target pacman-key --init
in_target pacman-key --populate archlinux
if [ -n "$DO_UPDATE" ]; then
say "Mise à jour complète (pacman -Syu)…"
[ -z "$SELF" ] && cp -f /etc/resolv.conf "$MNT/etc/resolv.conf" 2>/dev/null
in_target pacman -Syu --noconfirm
fi
}
fix_all() { fix_initramfs && fix_grub; }
# ── Sous-commandes (pilotées par fws-recovery-gui) ───────────────────────────
case "${1:-}" in
scan)
scan_installs
exit 0 ;;
repair)
action="${2:?action manquante}"; dev="${3:?périphérique manquant}"
trap cleanup EXIT
say "Montage de $dev…"
mount_target "$dev" || { warn "montage impossible"; exit 1; }
rc=0
case "$action" in
grub) fix_grub || rc=$? ;;
initramfs) fix_initramfs || rc=$? ;;
pacman) fix_pacman || rc=$? ;;
pacman-update) DO_UPDATE=1; fix_pacman || rc=$? ;;
all) fix_all || rc=$? ;;
*) warn "action inconnue : $action"; rc=2 ;;
esac
cleanup; trap - EXIT
[ "$rc" -eq 0 ] && say "Terminé ✔" || say "Échec (code $rc)"
exit "$rc" ;;
shell)
dev="${2:?périphérique manquant}"
trap cleanup EXIT
mount_target "$dev" || { warn "montage impossible"; exit 1; }
if [ -z "$SELF" ] && [ -n "${DISPLAY:-}" ] && command -v xterm >/dev/null 2>&1; then
xterm -title "FWS — terminal dans le système (chroot)" \
-e arch-chroot "$MNT" /bin/bash
else
in_target /bin/bash
fi
cleanup; trap - EXIT
exit 0 ;;
""|tui) : ;; # assistant console ci-dessous
*) echo "usage : fws-recovery [scan|repair <action> <dev>|shell <dev>]"; exit 2 ;;
esac
# ── Assistant CONSOLE (repli sans X / usage manuel) ──────────────────────────
trap cleanup EXIT
say "Recherche des installations FWS…"
mapfile -t CANDIDATES < <(scan_installs)
if [ "${#CANDIDATES[@]}" -eq 0 ]; then
warn "Aucune installation FWS trouvée. Disques visibles :"
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS
read -rp "Entrée pour ouvrir un shell (exit pour redémarrer). " _
bash; exit 0
fi
echo; i=1
for c in "${CANDIDATES[@]}"; do echo " $i) ${c%%|*} — ${c##*|}"; i=$((i+1)); done
read -rp "Installation à réparer [1] : " pick; pick="${pick:-1}"
dev="${CANDIDATES[$((pick-1))]%%|*}"
[ -n "$dev" ] || exit 1
say "Montage de $dev…"
mount_target "$dev" || { warn "montage impossible"; exit 1; }
while :; do
cat <<'MENU'
╔════════════════════════════════════════════════════════╗
║ FWS — Réparation du système ║
╠════════════════════════════════════════════════════════╣
║ 1) Réinstaller GRUB (le système ne démarre plus) ║
║ 2) Régénérer l'initramfs (mkinitcpio -P) ║
║ 3) Réparer pacman (verrou, clés, mise à jour) ║
║ 4) Tout réparer (initramfs + GRUB) ║
║ 5) Ouvrir un shell DANS le système (chroot) ║
║ q) Quitter et redémarrer ║
╚════════════════════════════════════════════════════════╝
MENU
read -rp " Choix : " choice
case "$choice" in
1) fix_grub ;;
2) fix_initramfs ;;
3) DO_UPDATE=""
read -rp " Mise à jour complète aussi (réseau) ? [o/N] " _u
case "$_u" in o|O|oui) DO_UPDATE=1 ;; esac
fix_pacman ;;
4) fix_all ;;
5) say "Shell chroot — « exit » pour revenir au menu."
in_target /bin/bash ;;
q|Q) break ;;
*) warn "choix inconnu" ;;
esac
done
cleanup; trap - EXIT
say "Réparation terminée."
read -rp "Redémarrer maintenant ? [O/n] " _r
case "$_r" in n|N) bash ;; *) systemctl reboot ;; esac
+269
View File
@@ -0,0 +1,269 @@
#!/usr/bin/env python3
# FWS — application GRAPHIQUE de réparation du système (mode « Réparer FWS »
# du live). GTK3 : le live embarque python-gobject/gtk3 via Anaconda.
#
# Pilote le MOTEUR fws-recovery en sous-commandes :
# fws-recovery scan → détecte les installations FWS
# fws-recovery repair <action> <dev> → répare (sortie streamée ici)
# fws-recovery shell <dev> → terminal chroot (xterm)
#
# Deux contextes de lancement :
# • mode « Réparer FWS » (live OU entrée GRUB de l'OS installé) : session
# root via fws-recovery-session → le moteur est appelé directement ;
# • menu d'applications de l'OS installé : lancée en UTILISATEUR → chaque
# appel du moteur passe par pkexec (politique org.fws.recovery,
# auth_admin_keep : un seul mot de passe pour toute la session).
import os
import shutil
import subprocess
import sys
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import GLib, Gtk, Pango # noqa: E402
GLib.set_prgname("fws-recovery-gui")
_ENGINE = "/usr/local/bin/fws-recovery"
def _engine_cmd(args):
"""Commande moteur, élevée via pkexec si on n'est pas root."""
cmd = [_ENGINE] + args
if os.geteuid() != 0:
cmd = ["pkexec"] + cmd
return cmd
_ACTIONS = [
("grub", "Réinstaller GRUB",
"le système ne démarre plus / « Operating System not found »"),
("initramfs", "Régénérer l'initramfs",
"kernel panic au démarrage, module manquant (mkinitcpio -P)"),
("pacman", "Réparer pacman",
"verrou bloqué, clés de signature cassées"),
("pacman-update", "Réparer pacman + mise à jour complète",
"idem, puis pacman -Syu (réseau requis)"),
("all", "Tout réparer",
"initramfs puis GRUB — le réflexe « ça ne boote plus »"),
]
class RecoveryWindow(Gtk.Window):
def __init__(self):
super().__init__(title="FWS — Réparation du système")
self.set_icon_name("fws-recovery")
self.set_default_size(720, 560)
self.set_position(Gtk.WindowPosition.CENTER)
self.set_border_width(18)
self.connect("destroy", Gtk.main_quit)
self._proc = None
self._installs = [] # [(dev, nom), …]
self._action_buttons = []
root = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
self.add(root)
title = Gtk.Label()
title.set_markup("<span size='x-large' weight='bold'>Réparation du système FWS</span>")
root.pack_start(title, False, False, 0)
self._status = Gtk.Label(label="Recherche des installations FWS sur les disques…")
self._status.set_xalign(0)
root.pack_start(self._status, False, False, 0)
# Sélecteur d'installation (rempli après le scan).
picker_row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
picker_row.pack_start(Gtk.Label(label="Système à réparer :"), False, False, 0)
self._picker = Gtk.ComboBoxText()
picker_row.pack_start(self._picker, True, True, 0)
root.pack_start(picker_row, False, False, 0)
# Boutons d'action (style fws-hello : titre + description).
for key, label, desc in _ACTIONS:
button = Gtk.Button()
inner = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2)
lab = Gtk.Label()
lab.set_markup(f"<b>{label}</b>")
lab.set_xalign(0)
sub = Gtk.Label(label=desc)
sub.set_xalign(0)
sub.get_style_context().add_class("dim-label")
inner.pack_start(lab, False, False, 0)
inner.pack_start(sub, False, False, 0)
button.add(inner)
button.connect("clicked", self._on_action, key)
root.pack_start(button, False, False, 0)
self._action_buttons.append(button)
shell_btn = Gtk.Button(label="Ouvrir un terminal dans le système (chroot)")
shell_btn.connect("clicked", self._on_shell)
root.pack_start(shell_btn, False, False, 0)
self._action_buttons.append(shell_btn)
# Journal (sortie du moteur, en continu).
frame = Gtk.Frame(label="Journal")
scroll = Gtk.ScrolledWindow()
scroll.set_min_content_height(150)
self._log = Gtk.TextView()
self._log.set_editable(False)
self._log.set_cursor_visible(False)
self._log.set_monospace(True)
self._log.modify_font(Pango.FontDescription("Monospace 9"))
scroll.add(self._log)
frame.add(scroll)
root.pack_start(frame, True, True, 0)
# Pied : redémarrer / éteindre.
footer = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
reboot = Gtk.Button(label="Redémarrer")
reboot.connect("clicked", lambda _b: self._power("reboot"))
poweroff = Gtk.Button(label="Éteindre")
poweroff.connect("clicked", lambda _b: self._power("poweroff"))
footer.pack_end(reboot, False, False, 0)
footer.pack_end(poweroff, False, False, 0)
root.pack_start(footer, False, False, 0)
self._set_busy(True)
GLib.idle_add(self._start_scan)
# ── Utilitaires ─────────────────────────────────────────────────────────
def _append(self, text):
# Rebranding d'affichage : les outils sous-jacents parlent
# d'« Arch Linux » — pas le journal FWS. (Les identifiants techniques
# comme archlinux-keyring restent tels quels : ce sont de vrais noms
# de fichiers/paquets, les masquer fausserait le diagnostic.)
text = text.replace("Arch Linux", "FWS").replace("Fedora", "FWS")
buf = self._log.get_buffer()
buf.insert(buf.get_end_iter(), text)
mark = buf.create_mark(None, buf.get_end_iter(), False)
self._log.scroll_mark_onscreen(mark)
buf.delete_mark(mark)
def _set_busy(self, busy):
for b in self._action_buttons:
b.set_sensitive(not busy and bool(self._installs))
# Sensible même avec UNE seule install : un combo grisé donne
# l'impression que TOUTE l'app est désactivée (retour utilisateur).
self._picker.set_sensitive(not busy and bool(self._installs))
def _selected_dev(self):
idx = self._picker.get_active()
if 0 <= idx < len(self._installs):
return self._installs[idx][0]
return None
def _power(self, what):
subprocess.run(["systemctl", what], check=False)
# ── Scan des installations ──────────────────────────────────────────────
def _start_scan(self):
self._run_engine(["scan"], self._scan_done, quiet=True)
return False
def _scan_done(self, rc, output):
self._installs = []
for line in output.splitlines():
if "|" in line:
dev, _, name = line.partition("|")
self._installs.append((dev.strip(), name.strip()))
self._picker.append_text(f"{name.strip()} — {dev.strip()}")
if self._installs:
self._picker.set_active(0)
n = len(self._installs)
self._status.set_markup(
f"<b>{n}</b> installation{'s' if n > 1 else ''} FWS détectée{'s' if n > 1 else ''}."
" Choisis une réparation :")
elif rc in (126, 127):
# pkexec : authentification annulée ou refusée.
self._status.set_markup(
"<b>Authentification annulée.</b> Ferme puis relance"
" l'application pour réessayer.")
else:
self._status.set_markup(
"<b>Aucune installation FWS détectée.</b> Vérifie les disques"
" (terminal ci-dessous) ou redémarre.")
self._append("Aucune installation FWS trouvée.\n\n"
+ subprocess.run(["lsblk", "-o", "NAME,SIZE,TYPE,FSTYPE"],
capture_output=True, text=True,
check=False).stdout)
self._set_busy(False)
# Le terminal reste utile même sans installation détectée (diagnostic).
self._action_buttons[-1].set_sensitive(True)
# ── Exécution du moteur avec sortie en continu ──────────────────────────
def _run_engine(self, args, done_cb, quiet=False):
self._set_busy(True)
self._proc = subprocess.Popen(
_engine_cmd(args), stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, text=True, bufsize=1)
chunks = []
def on_io(source, _cond):
line = source.readline()
if line:
chunks.append(line)
if not quiet:
self._append(line)
return True
rc = self._proc.wait()
done_cb(rc, "".join(chunks))
return False
GLib.io_add_watch(self._proc.stdout,
GLib.IO_IN | GLib.IO_HUP, on_io)
def _on_action(self, _button, key):
dev = self._selected_dev()
if not dev:
return
label = next(lab for k, lab, _d in _ACTIONS if k == key)
self._status.set_markup(f"<b>{label}</b> sur {dev} — en cours…")
self._append(f"\n───── {label} ({dev}) ─────\n")
self._run_engine(["repair", key, dev], self._action_done)
def _action_done(self, rc, _output):
if rc == 0:
self._status.set_markup("<b>Terminé ✔</b> — autre réparation, ou redémarre.")
elif rc in (126, 127):
self._status.set_markup(
"<b>Authentification annulée</b> — rien n'a été modifié.")
else:
self._status.set_markup(
f"<b>Échec (code {rc})</b> — détail dans le journal ci-dessous.")
self._set_busy(False)
def _on_shell(self, _button):
dev = self._selected_dev()
if dev and os.geteuid() == 0:
subprocess.Popen([_ENGINE, "shell", dev])
elif dev:
# pkexec purge DISPLAY → le moteur ouvre le chroot DANS cet xterm
# (lancé en utilisateur, donc visible sur X11 comme Wayland).
subprocess.Popen(["xterm", "-title",
"FWS — terminal dans le système (chroot)",
"-e", "pkexec", _ENGINE, "shell", dev])
elif shutil.which("xterm"):
subprocess.Popen(["xterm", "-title", "FWS — terminal"])
def main():
if not Gtk.init_check()[0]:
# Pas d'affichage : le .zprofile retombera sur l'assistant console.
return 1
if os.geteuid() != 0 and not shutil.which("pkexec"):
dlg = Gtk.MessageDialog(message_type=Gtk.MessageType.ERROR,
buttons=Gtk.ButtonsType.CLOSE,
text="pkexec introuvable : lance fws-recovery-gui en root.")
dlg.run()
return 1
win = RecoveryWindow()
win.show_all()
Gtk.main()
return 0
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,8 @@
#!/bin/sh
# FWS — session X du mode réparation. Lancé par startx (cf. /root/.zprofile,
# entrée de boot « Réparer FWS »). Openbox minimal + l'application GTK
# fws-recovery-gui. Si la GUI meurt, openbox reste pour un xterm de secours.
xsetroot -solid "#1a1a2e" 2>/dev/null
openbox &
fws-recovery-gui
exec openbox
@@ -0,0 +1,93 @@
#!/bin/bash
# ============================================================
# fws-secureboot-setup — prépare FWS à booter SIGNÉ sous Secure Boot,
# SANS activer le lockdown noyau (→ hibernation préservée).
#
# POURQUOI : Vanguard exige Secure Boot (réglage firmware GLOBAL) → FWS doit
# lui aussi booter sous SB, sinon le firmware refuse de le lancer. Fait vérifié :
# sur noyau mainline Arch, activer Secure Boot n'arme PAS le lockdown (le patch
# Fedora/Ubuntu qui lie les deux a été refusé upstream). Signature (firmware) et
# lockdown (LSM runtime) sont ORTHOGONAUX → on peut signer ET hiberner.
#
# VOIE RETENUE : sbctl (clés « db » custom) en CONSERVANT les clés Microsoft
# (--microsoft), pour que Windows et les OpROM continuent de booter.
#
# ⚠ ORDRE IMPÉRATIF : tout SIGNER avant d'activer Secure Boot au firmware.
# Détails, alternative shim+MOK et vérifications : docs §4.
# ============================================================
set -u
[ "$(id -u)" -eq 0 ] || { echo "root requis (sudo fws-secureboot-setup)"; exit 1; }
[ -d /sys/firmware/efi ] || { echo "Système non-UEFI : Secure Boot sans objet."; exit 1; }
log() { printf '\e[36m==>\e[0m %s\n' "$*"; }
warn() { printf '\e[33m[!] %s\e[0m\n' "$*"; }
die() { printf '\e[31m[ERREUR] %s\e[0m\n' "$*"; exit 1; }
command -v sbctl >/dev/null 2>&1 || { log "Installation de sbctl…"; pacman -S --noconfirm --needed sbctl || die "sbctl introuvable (réseau ?)"; }
command -v objdump >/dev/null 2>&1 || pacman -S --noconfirm --needed binutils || true
# Localiser l'ESP et grubx64.efi. IDENTITÉ = FWS, cohérente avec le kickstart
# install_grub (--bootloader-id=FWS + --removable → \EFI\FWS\ ET \EFI\BOOT\).
# Signer un \EFI\GRUB\ séparé ne servirait à RIEN : le firmware charge \EFI\FWS\
# (ou le secours \EFI\BOOT\BOOTX64.EFI) → ce sont EUX qu'il faut signer.
ESP_DIR=/boot/efi; [ -d "$ESP_DIR/EFI" ] || ESP_DIR=/boot
GRUB_EFI="$ESP_DIR/EFI/FWS/grubx64.efi"
[ -f "$GRUB_EFI" ] || GRUB_EFI="$(find "$ESP_DIR/EFI" -iname 'grubx64.efi' 2>/dev/null | head -1)"
[ -n "$GRUB_EFI" ] && [ -f "$GRUB_EFI" ] || die "grubx64.efi introuvable sous $ESP_DIR/EFI"
BOOTX64="$ESP_DIR/EFI/BOOT/BOOTX64.EFI" # chemin de secours removable (à signer aussi)
# 1) GRUB doit embarquer SBAT (+ shim_lock), sinon shim refusera de le charger.
# Un grub-install « nu » produit un binaire sans .sbat → on réinstalle EN FWS
# (et on régénère le secours removable \EFI\BOOT\BOOTX64.EFI avec le même SBAT).
if ! command -v objdump >/dev/null 2>&1; then
warn "objdump (binutils) absent → SBAT non vérifiable, réinstallation de GRUB SAUTÉE (on suppose GRUB déjà correct)."
elif ! objdump -h "$GRUB_EFI" 2>/dev/null | grep -q '\.sbat'; then
[ -f /usr/share/grub/sbat.csv ] || die "sbat.csv introuvable — impossible d'ajouter SBAT"
warn "grubx64.efi sans section .sbat → réinstallation de GRUB (--bootloader-id=FWS + --sbat)."
GRUB_MODULES='normal search part_gpt part_msdos fat ext2 configfile linux echo test true loadenv all_video efi_gop tpm'
grub-install --target=x86_64-efi --efi-directory="$ESP_DIR" --bootloader-id=FWS \
--sbat /usr/share/grub/sbat.csv --modules="$GRUB_MODULES" \
|| die "grub-install (FWS) a échoué"
grub-install --target=x86_64-efi --efi-directory="$ESP_DIR" --bootloader-id=FWS --removable \
--sbat /usr/share/grub/sbat.csv --modules="$GRUB_MODULES" \
|| warn "grub-install --removable (secours) a échoué"
GRUB_EFI="$ESP_DIR/EFI/FWS/grubx64.efi"
objdump -h "$GRUB_EFI" 2>/dev/null | grep -q '\.sbat' || die "SBAT toujours absent après réinstallation"
grub-mkconfig -o /boot/grub/grub.cfg 2>/dev/null || true
fi
# 2) Clés sbctl (idempotent).
if ! sbctl status 2>/dev/null | grep -qi 'setup mode.*✓\|setup mode.*enabled\|Setup Mode.*Enabled'; then
warn "Firmware pas en Setup Mode : « enroll-keys » échouera. Mets le firmware en Setup Mode (efface les clés d'usine), puis relance. On continue quand même la génération/signature."
fi
[ -d /var/lib/sbctl/keys ] || sbctl create-keys || die "sbctl create-keys a échoué"
# 3) Enrôler NOS clés + CONSERVER Microsoft (sinon Windows/OpROM cassés).
log "Enrôlement des clés (avec Microsoft)…"
sbctl enroll-keys --microsoft \
|| warn "enroll-keys a échoué (firmware pas en Setup Mode ?) — à refaire une fois en Setup Mode."
# 4) Signer les TROIS chemins réellement chargés par le firmware + les noyaux :
# \EFI\FWS\grubx64.efi, le secours \EFI\BOOT\BOOTX64.EFI, et chaque vmlinuz.
log "Signature de GRUB (FWS + secours removable) et des noyaux…"
sbctl sign -s "$GRUB_EFI" || die "signature de $GRUB_EFI échouée"
[ -f "$BOOTX64" ] && { sbctl sign -s "$BOOTX64" || warn "signature de $BOOTX64 échouée"; }
for k in /boot/vmlinuz-linux /boot/vmlinuz-linux-lts /boot/vmlinuz-linux-zen /boot/vmlinuz-linux-hardened; do
[ -f "$k" ] && { sbctl sign -s "$k" || warn "signature de $k échouée"; }
done
sbctl verify || warn "sbctl verify signale des binaires non signés (voir ci-dessus)."
cat <<'EOF'
=== Étapes MANUELLES restantes (firmware) ===
1. Redémarre dans le setup du firmware (UEFI/BIOS).
2. Active Secure Boot (les clés viennent d'être enrôlées, Microsoft conservé).
3. Au boot FWS suivant, vérifie :
bootctl status | grep 'Secure Boot' → Secure Boot: enabled
cat /sys/kernel/security/lockdown → [none] (hibernation OK)
cat /sys/power/state → contient « disk »
Puis : fws-gameboot doctor
Re-signature AUTOMATIQUE après chaque MAJ noyau/GRUB via le hook pacman
/usr/share/libalpm/hooks/95-fws-secureboot-sign.hook (+ le hook natif de sbctl).
EOF
+84
View File
@@ -0,0 +1,84 @@
#!/bin/bash
# ============================================================================
# FWS — détection matérielle + drivers + stack « remplacement de Windows ».
#
# Appelé par le %post du kickstart : fws-setup-hardware <bureau>
# (<bureau> = cli|gnome|kde|xfce|hyprland). Réseau requis ; CHAQUE étape est
# NON bloquante (pas de set -e) : un paquet manquant ou pas de réseau ne casse
# pas l'installation.
#
# Installe :
# - les drivers GPU détectés via lspci (Intel / AMD / NVIDIA) + Mesa/Vulkan,
# - les outils invité selon l'hyperviseur (VMware/VirtualBox/QEMU/Hyper-V),
# - le son/micro/casque (PipeWire), le Bluetooth (BlueZ), les conteneurs
# (Podman),
# - et — si un bureau est choisi — la stack « comme Windows » : Steam, Wine,
# VR (Monado/OpenXR → SteamVR pour HTC Vive / Valve Index ; Oculus n'a pas
# de support Linux officiel), winboat-bin (apps Windows).
# ============================================================================
DESK="${1:-cli}"
log() { echo "[FWS-hw] $*"; }
# ── 1) Drivers GPU (détection lspci) ─────────────────────────────────────────
DRIVERS="mesa"
GPU="$(lspci 2>/dev/null | grep -iE 'VGA compatible controller|3D controller|Display controller')"
case "$GPU" in
*NVIDIA*|*GeForce*) DRIVERS="$DRIVERS nvidia-dkms nvidia-utils linux-headers dkms" ;;
*AMD*|*ATI*|*Radeon*) DRIVERS="$DRIVERS vulkan-radeon libva-mesa-driver" ;;
*Intel*) DRIVERS="$DRIVERS vulkan-intel intel-media-driver" ;;
esac
# ── 2) Outils invité selon l'hyperviseur ─────────────────────────────────────
VMSVC=""
case "$(systemd-detect-virt 2>/dev/null)" in
vmware) DRIVERS="$DRIVERS open-vm-tools"; VMSVC="vmtoolsd vmware-vmblock-fuse" ;;
oracle) DRIVERS="$DRIVERS virtualbox-guest-utils"; VMSVC="vboxservice" ;;
qemu|kvm) DRIVERS="$DRIVERS qemu-guest-agent spice-vdagent"; VMSVC="qemu-guest-agent spice-vdagentd" ;;
microsoft) DRIVERS="$DRIVERS hyperv"; VMSVC="hv_fcopy_daemon hv_kvp_daemon hv_vss_daemon" ;;
esac
# ── 3) Stack de base : son / micro / casque, Bluetooth, luminosité, conteneurs
# brightnessctl : réglage de luminosité sans root (via logind) — consommé par
# les touches XF86MonBrightness* et la molette du module backlight de waybar.
BASE="pipewire pipewire-alsa pipewire-pulse pipewire-jack wireplumber \
pavucontrol bluez bluez-utils brightnessctl podman"
# Gestionnaire Bluetooth graphique : KDE et GNOME ont le leur ; blueman pour
# les bureaux qui n'en fournissent pas (tray + assistant d'appairage).
case "$DESK" in
hyprland|xfce|i3) BASE="$BASE blueman" ;;
esac
# ── 4) Stack desktop : jeux, Windows-compat, VR (si un bureau est choisi) ────
DESKTOP=""
if [ "$DESK" != "cli" ]; then
# multilib requis (Steam / Wine 32 bits).
grep -qE '^\[multilib\]' /etc/pacman.conf \
|| printf '\n[multilib]\nInclude = /etc/pacman.d/mirrorlist\n' >> /etc/pacman.conf
# mangohud : overlay FPS/perfs en jeu (mangohud %command% dans Steam).
DESKTOP="steam wine winetricks wine-mono wine-gecko \
lib32-mesa lib32-pipewire monado gamemode \
mangohud lib32-mangohud \
noto-fonts ttf-liberation"
fi
# ── Sync + filtre aux paquets RÉELLEMENT existants ───────────────────────────
# (un nom de paquet manquant/renommé ne doit pas faire échouer tout le reste).
pacman -Sy --noconfirm || log "synchro pacman échouée (réseau ?)"
VALID=""
for p in $DRIVERS $BASE $DESKTOP; do
pacman -Si "$p" >/dev/null 2>&1 && VALID="$VALID $p"
done
if [ -n "$VALID" ]; then
log "installation :$VALID"
pacman -S --noconfirm --needed $VALID || log "certains paquets non installés"
fi
# NB : winboat-bin (AUR) n'est PAS installé ici (fws-local n'existe pas sur la
# cible) : il est intégré à l'image FWS via packages.x86_64 et recopié par le
# payload LiveOS d'Anaconda → déjà présent sur le système installé.
# ── Services matériels ───────────────────────────────────────────────────────
[ -n "$VMSVC" ] && systemctl enable $VMSVC 2>/dev/null
systemctl enable bluetooth.service 2>/dev/null
systemctl enable podman.socket 2>/dev/null
exit 0
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
# FWS — mise à jour du système en une commande (bouton de la barre, fws-hello,
# ou terminal). pacman -Syu + nettoyage du cache + rapport des orphelins.
set -u
say() { printf '\n\033[1;34m==> %s\033[0m\n' "$*"; }
if [ "$(id -u)" -ne 0 ]; then
exec sudo "$0" "$@"
fi
say "Mise à jour du système (pacman -Syu)…"
pacman -Syu || { echo "Échec de la mise à jour (réseau ?)"; read -rp "Entrée pour fermer. " _; exit 1; }
if command -v paccache >/dev/null 2>&1; then
say "Nettoyage du cache des paquets (2 dernières versions gardées)…"
paccache -rk2
fi
ORPHANS="$(pacman -Qtdq 2>/dev/null)"
if [ -n "$ORPHANS" ]; then
say "Paquets orphelins (plus requis par rien) :"
echo "$ORPHANS"
read -rp "Les supprimer ? [o/N] " _rm
case "$_rm" in o|O|oui) echo "$ORPHANS" | pacman -Rns --noconfirm - ;; esac
fi
if command -v flatpak >/dev/null 2>&1; then
say "Mise à jour des Flatpaks…"
flatpak update -y || true
fi
say "Système à jour ✔"
read -rp "Entrée pour fermer. " _
+8
View File
@@ -0,0 +1,8 @@
#!/bin/sh
# FWS — indicateur « mises à jour disponibles » pour waybar/polybar.
# checkupdates (pacman-contrib) : lit les dépôts SANS toucher la base pacman.
# Sortie vide = rien à faire → le module de la barre se cache.
command -v checkupdates >/dev/null 2>&1 || exit 0
n="$(checkupdates 2>/dev/null | wc -l)"
[ "$n" -gt 0 ] && printf '󰚰 %s\n' "$n"
exit 0
+115
View File
@@ -0,0 +1,115 @@
#!/bin/bash
# ============================================================================
# FWS — installation des drivers/runtimes VR selon le casque.
#
# À lancer depuis un terminal (Super+Shift+V sous i3, ou l'entrée
# « Installation VR (FWS) » du menu d'applications). Réservé aux sessions
# X11 (i3) : SteamVR ne fonctionne PAS sous Wayland/Hyprland.
#
# Réalité du support Linux (résumé honnête) :
# • HTC Vive / Vive Pro / Valve Index (stations lighthouse) → NATIF via
# SteamVR. C'est le chemin le plus fiable.
# • Meta/Oculus Quest 1-3, Pico → PAS de support officiel ; streaming
# Wi-Fi via ALVR (+ SteamVR). ALVR n'est pas dans les dépôts Arch :
# le script installe la base et donne la marche à suivre.
# • Oculus Rift CV1/S (filaire) → pas de support utilisable.
# • Monado : runtime OpenXR libre (déjà proposé par fws-setup-hardware),
# utile pour les applis OpenXR natives sans SteamVR.
# ============================================================================
set -u
say() { printf '\n\033[1;34m==> %s\033[0m\n' "$*"; }
warn() { printf '\033[1;33m ⚠ %s\033[0m\n' "$*"; }
if [ "$(id -u)" -ne 0 ]; then
exec sudo "$0" "$@"
fi
# VM = cul-de-sac VR : pas d'accès direct au GPU ni à l'USB/DisplayPort du
# casque, et Steam (steamwebhelper) est INSTABLE en rendu logiciel (llvmpipe).
# On prévient au lieu de laisser l'utilisateur croire à une panne de FWS.
if systemd-detect-virt --quiet 2>/dev/null; then
warn "Machine VIRTUELLE détectée ($(systemd-detect-virt 2>/dev/null))."
warn "La VR ne fonctionnera PAS ici : casque et GPU inaccessibles depuis une VM."
warn "Steam lui-même peut planter en rendu logiciel (steamwebhelper)."
echo " → Fais cette installation sur la machine RÉELLE avec le casque branché."
read -rp " Continuer quand même (tests uniquement) ? [o/N] " _ok
case "$_ok" in o|O|oui) ;; *) exit 0 ;; esac
fi
# lib32 Vulkan selon le GPU (indispensable à SteamVR, qui est 32/64 bits).
GPU="$(lspci 2>/dev/null | grep -iE 'VGA compatible controller|3D controller|Display controller')"
LIB32="lib32-mesa"
case "$GPU" in
*NVIDIA*|*GeForce*) LIB32="$LIB32 lib32-nvidia-utils" ;;
*AMD*|*ATI*|*Radeon*) LIB32="$LIB32 lib32-vulkan-radeon" ;;
*Intel*) LIB32="$LIB32 lib32-vulkan-intel" ;;
esac
install_pkgs() {
# multilib requis pour Steam/lib32.
grep -qE '^\[multilib\]' /etc/pacman.conf \
|| printf '\n[multilib]\nInclude = /etc/pacman.d/mirrorlist\n' >> /etc/pacman.conf
pacman -Sy --noconfirm || { warn "synchro pacman échouée (réseau ?)"; return 1; }
local valid="" p
for p in "$@"; do
pacman -Si "$p" >/dev/null 2>&1 && valid="$valid $p" || warn "paquet inconnu ignoré : $p"
done
# shellcheck disable=SC2086
pacman -S --noconfirm --needed $valid
}
steamvr_base() {
say "Installation de la base SteamVR (Steam + Vulkan 32 bits : $LIB32)…"
# ttf-liberation : polices attendues par l'interface Steam (évite des
# rendus cassés/plantages cosmétiques sur install minimale).
# shellcheck disable=SC2086
install_pkgs steam $LIB32 ttf-liberation || return 1
say "Base installée. Étapes suivantes :"
echo " 1. Lance Steam, connecte-toi, puis installe « SteamVR » (Bibliothèque → Outils)."
echo " 2. Branche le casque et les stations, puis lance SteamVR."
echo " 3. Premier lancement : SteamVR peut demander à ajuster les règles udev — accepte."
say "Si Steam PLANTE (fenêtre qui se ferme, souvent steamwebhelper) :"
echo " • Vérifie l'espace disque (SteamVR ≈ 5 Go) : df -h ~"
echo " • GPU faible / VM / rendu logiciel — lance :"
echo " steam -cef-disable-gpu -cef-disable-gpu-compositing"
echo " • Pour voir l'erreur exacte : lance « steam » depuis un terminal,"
echo " ou regarde ~/.steam/steam/logs/ (console_log.txt, bootstrap_log.txt)."
}
case "${1:-menu}" in
menu)
cat <<'MENU'
╔══════════════════════════════════════════════════════════╗
║ FWS — Installation VR (session X11 requise) ║
╠══════════════════════════════════════════════════════════╣
║ 1) HTC Vive / Vive Pro / Valve Index (natif, SteamVR) ║
║ 2) Meta Quest / Pico (streaming ALVR) ║
║ 3) Monado (runtime OpenXR libre, sans SteamVR) ║
║ q) Quitter ║
╚══════════════════════════════════════════════════════════╝
MENU
read -rp " Choix : " choice
exec "$0" "$choice"
;;
1|vive|index)
steamvr_base || exit 1
;;
2|quest|pico)
steamvr_base || exit 1
say "Streaming Quest/Pico : il faut AUSSI ALVR (pas dans les dépôts Arch) :"
echo " 1. Télécharge le .tar.gz « ALVR Launcher » : https://github.com/alvr-org/ALVR/releases"
echo " 2. Sur le casque, installe le client ALVR (App Lab / SideQuest)."
echo " 3. Lance ALVR sur le PC (même Wi-Fi 5 GHz que le casque) — il pilote SteamVR."
;;
3|monado)
say "Installation de Monado (OpenXR) + outils…"
install_pkgs monado || exit 1
say "Monado installé. Les applis OpenXR le trouvent via /usr/share/openxr/."
;;
q|Q) exit 0 ;;
*) warn "choix inconnu : ${1}"; exec "$0" menu ;;
esac
echo
read -rp "Terminé — appuie sur Entrée pour fermer. " _
@@ -0,0 +1,96 @@
#!/bin/bash
# ============================================================
# fws-windows-bcdfix — rend Windows bootable en exécutant le VRAI `bcdboot`
# dans une WinPE lancée UNE FOIS (BootNext), juste après l'install FWS.
#
# ⚠ NON VALIDÉ SUR MATÉRIEL RÉEL. Voie RECOMMANDÉE par défaut : bcdboot écrit un
# BCD correct. Coût : 1 reboot automatique en WinPE, puis retour à FWS
# (BootOrder[0]=FWS réasserté par fws-windows-deploy + fws-gameboot-bootfix).
#
# Mécanique :
# 1. Copier la WinPE bootable de l'ISO (bootmgr + BCD WinPE + boot.wim) sur l'ESP.
# 2. Injecter winpeshl.ini + fwspe.cmd DANS l'image AMORÇABLE de boot.wim
# (Boot Index, typiquement 2 « Setup », + index 1 en ceinture) : au démarrage,
# WinPE lance fwspe.cmd → trouve le lecteur Windows (\Windows\System32\
# winload.efi) et l'ESP (label WINESP via diskpart) → bcdboot → wpeutil reboot.
# 3. Poser une entrée UEFI + BootNext one-shot vers cette WinPE.
#
# Args : <ISO_MOUNT> <ESP_MOUNT> <WIN_DISK> <ESP_PARTNUM>
# ============================================================
set -u
ISO="${1:-}"; ESP="${2:-}"; WIN_DISK="${3:-}"; ESP_PN="${4:-1}"; DRV="${5:-}"
log(){ printf '\e[36m[bcdfix]\e[0m %s\n' "$*"; }
die(){ printf '\e[31m[bcdfix] ERREUR : %s\e[0m\n' "$*" >&2; exit 1; }
[ -d "$ISO" ] && [ -d "$ESP" ] && [ -b "$WIN_DISK" ] || die "args invalides"
command -v wimlib-imagex >/dev/null 2>&1 || die "wimlib absent"
# 1) Structure de boot WinPE depuis l'ISO (bootmgr + BCD WinPE prêts).
log "Staging de la WinPE de l'ISO sur l'ESP…"
mkdir -p "$ESP/EFI/Microsoft/Boot" "$ESP/sources"
cp -rf "$ISO"/efi/microsoft/boot/. "$ESP/EFI/Microsoft/Boot/" 2>/dev/null || true
[ -f "$ESP/EFI/Microsoft/Boot/bootmgfw.efi" ] \
|| cp -f "$ISO/efi/boot/bootx64.efi" "$ESP/EFI/Microsoft/Boot/bootmgfw.efi" 2>/dev/null \
|| die "bootmgfw.efi WinPE introuvable dans l'ISO"
# 2) Injecter l'automation dans l'image AMORÇABLE de boot.wim.
TMP="$(mktemp -d)"
cp -f "$ISO/sources/boot.wim" "$TMP/boot.wim" || die "boot.wim introuvable dans l'ISO"
cat > "$TMP/winpeshl.ini" <<'INI'
[LaunchApps]
"cmd.exe", "/c X:\fwspe.cmd"
INI
# ⚠ NON VALIDÉ. bcdboot dans WinPE : trouver le lecteur Windows par présence de
# winload.efi (NTFS auto-monté), et l'ESP par son label WINESP via diskpart.
cat > "$TMP/fwspe.cmd" <<'CMD'
@echo off
rem Pilote Intel VMD (si injecté) : le charger pour que WinPE VOIE les NVMe.
if exist X:\fwsdrv\*.inf for %%I in (X:\fwsdrv\*.inf) do drvload %%I
set WIN=
for %%D in (C D E F G H I J K L M N O P) do if exist %%D:\Windows\System32\winload.efi set WIN=%%D:
echo list volume > X:\dp.txt
diskpart /s X:\dp.txt > X:\vol.txt
for /f "tokens=2" %%V in ('findstr /i "WINESP" X:\vol.txt') do (
echo select volume %%V > X:\dp2.txt
echo assign letter=S >> X:\dp2.txt
diskpart /s X:\dp2.txt
)
if not defined WIN goto reboot
rem Enregistrer le pilote VMD dans le Windows appliqué (boot device) via DISM.
if exist X:\fwsdrv\*.inf dism /image:%WIN%\ /add-driver /driver:X:\fwsdrv /recurse /forceunsigned
if exist S:\ ( bcdboot %WIN%\Windows /s S: /f UEFI ) else ( bcdboot %WIN%\Windows /f UEFI )
:reboot
wpeutil reboot
CMD
# Pilote Intel VMD à embarquer dans la WinPE (pour drvload + dism) ?
if [ -n "$DRV" ] && [ -d "$DRV" ]; then
mkdir -p "$TMP/fwsdrv"; cp -rf "$DRV"/. "$TMP/fwsdrv/" 2>/dev/null || true
log "Pilote Intel VMD embarqué dans la WinPE (drvload + dism)."
fi
# Image amorcée = « Boot Index » (typiquement 2) ; on injecte aussi l'index 1 en ceinture.
BOOTIDX="$(wimlib-imagex info "$TMP/boot.wim" 2>/dev/null | sed -n 's/^Boot Index:[[:space:]]*//p' | head -1)"
[ -n "$BOOTIDX" ] || BOOTIDX=2
INJECTED=0
for i in $BOOTIDX 1; do
wimlib-imagex update "$TMP/boot.wim" "$i" \
--command="add '$TMP/winpeshl.ini' /Windows/System32/winpeshl.ini" 2>/dev/null \
&& wimlib-imagex update "$TMP/boot.wim" "$i" \
--command="add '$TMP/fwspe.cmd' /fwspe.cmd" 2>/dev/null \
&& INJECTED=1
[ -d "$TMP/fwsdrv" ] && wimlib-imagex update "$TMP/boot.wim" "$i" \
--command="add '$TMP/fwsdrv' /fwsdrv" 2>/dev/null || true
done
[ "$INJECTED" = 1 ] || die "injection winpeshl/fwspe dans boot.wim échouée"
cp -f "$TMP/boot.wim" "$ESP/sources/boot.wim" || die "copie boot.wim → ESP échouée"
rm -rf "$TMP"
# 3) Entrée UEFI one-shot vers la WinPE stagée.
log "Création de l'entrée UEFI WinPE (one-shot BootNext)…"
efibootmgr -c -d "$WIN_DISK" -p "$ESP_PN" -L 'FWS WinPE (bcdboot)' \
-l '\EFI\Microsoft\Boot\bootmgfw.efi' >/dev/null 2>&1 \
|| die "création de l'entrée UEFI WinPE échouée"
PE_BN="$(efibootmgr 2>/dev/null | sed -n "s/^Boot\([0-9A-Fa-f]\{4\}\)\*\? *FWS WinPE.*/\1/p" | head -1)"
[ -n "$PE_BN" ] || die "numéro de boot WinPE introuvable"
efibootmgr -n "$PE_BN" >/dev/null || die "BootNext WinPE non armé"
log "WinPE armée en BootNext (Boot$PE_BN). Au 1er reboot : bcdboot puis retour FWS."
log "⚠ NON VALIDÉ sur matériel — à tester (assignation lettres WinPE, index boot.wim)."
exit 0
@@ -0,0 +1,301 @@
#!/bin/bash
# ============================================================
# fws-windows-deploy — déploie Windows 11 en OFFLINE-APPLY sur un disque dédié,
# depuis le live FWS (appelé en %post --nochroot, cible sous /mnt/sysroot).
#
# Windows Setup ne tourne JAMAIS : on carve les partitions, on applique
# install.wim (wimlib), on rend bootable (BCD), on injecte l'answer file offline
# + le composant gameboot, on provisionne l'hibernation FWS.
#
# SÉCURITÉ (risque n°1 = effacer le mauvais NVMe) : garde-fous FAIL-CLOSED —
# état PARSÉ (jamais sourcé → pas d'injection), disque cible VALIDÉ contre la
# liste des disques réels, comparé à TOUS les disques protégés (racine FWS
# multi-PV incluse, ESP, média ISO, support live), aucun label FWS résiduel
# ailleurs, adressage des partitions par NUMÉRO sur le disque cible (jamais par
# by-partlabel global) avec vérif du parent. Le moindre doute → abort. Échec →
# DEPLOY_FAILED, aucune entrée UEFI piégeuse, FWS reste seul bootable.
#
# ⚠ NON VALIDÉ SUR MATÉRIEL RÉEL (déploiement Windows réel + BCD).
# ============================================================
set -u
STATE=/tmp/fws-windows
SYSROOT=/mnt/sysroot
while [ $# -gt 0 ]; do
case "$1" in
--state) STATE="${2:-}"; shift 2 ;;
--sysroot) SYSROOT="${2:-}"; shift 2 ;;
*) shift ;;
esac
done
log(){ printf '\e[36m[win-deploy]\e[0m %s\n' "$*"; }
warn(){ printf '\e[33m[win-deploy] %s\e[0m\n' "$*" >&2; }
status(){ mkdir -p "$SYSROOT/var/lib/fws" 2>/dev/null; echo "$1" > "$SYSROOT/var/lib/fws/windows-deploy.status" 2>/dev/null || true; }
die(){ printf '\e[31m[win-deploy] ABORT : %s\e[0m\n' "$*" >&2; status "DEPLOY_FAILED: $*"; exit 1; }
canon(){ readlink -f "$1" 2>/dev/null || printf '%s' "$1"; }
# Device de la partition N sur le disque D (nvme/mmc → pN ; sata → N).
part_dev(){ case "$1" in *[0-9]) printf '%sp%s' "$1" "$2";; *) printf '%s%s' "$1" "$2";; esac; }
# --- 0) État du spoke : PARSÉ en clé=valeur littéral (JAMAIS sourcé) [M3] -----
[ -r "$STATE" ] || { log "pas de $STATE → dual-boot non demandé, rien à faire."; exit 0; }
enabled=0; disk=""; size_gib=0; iso=""; edition=""; confirm_erase=0; intel_vmd=0
while IFS='=' read -r k v; do
case "$k" in
enabled) enabled="$v" ;;
disk) disk="$v" ;;
size_gib) size_gib="$v" ;;
iso) iso="$v" ;;
edition) edition="$v" ;;
confirm_erase) confirm_erase="$v" ;;
intel_vmd) intel_vmd="$v" ;;
esac
done < "$STATE"
[ "${enabled:-0}" = 1 ] || { log "dual-boot désactivé, rien à faire."; exit 0; }
[ "${size_gib:-0}" -eq "${size_gib:-0}" ] 2>/dev/null || die "taille non numérique"
log "Dual-boot Windows demandé : disque=$disk taille=${size_gib}Gio iso=$iso"
# --- Résolveur : TOUS les disques physiques sous un montage (fail-closed) [M1]
fws_disks_of(){
local src
src="$(findmnt -no SOURCE "$1" 2>/dev/null)" || return 1
[ -n "$src" ] || return 1
lsblk -spno NAME,TYPE "$src" 2>/dev/null | awk '$2=="disk"{print $1}'
}
# --- 1) Garde-fous AVANT toute écriture (fail-closed) -----------------------
WIN_DISK="$disk"
[ -b "$WIN_DISK" ] || die "cible '$WIN_DISK' n'est pas un disque bloc"
# [M3] 'disk' DOIT être un vrai disque physique (neutralise toute valeur piégée).
lsblk -dpno NAME,TYPE 2>/dev/null | awk '$2=="disk"{print $1}' | grep -qxF "$WIN_DISK" \
|| die "cible '$WIN_DISK' absente de la liste des disques physiques"
findmnt -no SOURCE "$SYSROOT" >/dev/null 2>&1 || die "$SYSROOT non monté"
[ -f "$iso" ] || die "ISO introuvable : $iso"
[ "${size_gib:-0}" -ge 100 ] || die "taille < 100 Gio"
# Swap d'hibernation (RAM+VRAM+marge, plancher 96) — calculé ICI pour le garde taille [M7].
MEM=$(awk '/^MemTotal/{print int($2/1024)}' /proc/meminfo)
VRAM=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits 2>/dev/null | awk '{s+=$1}END{print s+0}')
SWAP_GIB=$(( (MEM + VRAM + 4096 + 1023) / 1024 )); [ "$SWAP_GIB" -lt 96 ] && SWAP_GIB=96
# Le disque cible ne doit être AUCUN disque protégé. On sonde uniquement de VRAIS
# montages [m1], et on compare à TOUS les disques porteurs [M1]. Resolver muet → abort.
WIN_C="$(canon "$WIN_DISK")"
for probe in "$SYSROOT" "$SYSROOT/boot/efi" "$SYSROOT/boot" /run/archiso/bootmnt; do
findmnt -M "$probe" >/dev/null 2>&1 || continue
mapfile -t pds < <(fws_disks_of "$probe")
[ "${#pds[@]}" -gt 0 ] || die "resolver muet sur '$probe' → abort (fail-closed)"
for d in "${pds[@]}"; do
[ "$WIN_C" != "$(canon "$d")" ] || die "$WIN_DISK == disque protégé ($probe via $d) — refus d'effacer"
done
done
# L'ISO ne doit pas vivre sur la cible — fail-closed si le média est indéterminable [m2].
iso_mnt="$(df --output=target "$iso" 2>/dev/null | tail -1)"
[ -n "$iso_mnt" ] || die "média de l'ISO indéterminable → abort (fail-closed)"
mapfile -t isods < <(fws_disks_of "$iso_mnt")
[ "${#isods[@]}" -gt 0 ] || die "resolver muet sur le média ISO → abort"
for d in "${isods[@]}"; do
[ "$WIN_C" != "$(canon "$d")" ] || die "l'ISO vit sur la cible $WIN_DISK — refus"
done
# Taille : NTFS + ESP + MSR + swap doivent tenir [M7].
DGIB=$(( $(lsblk -dbno SIZE "$WIN_DISK" 2>/dev/null || echo 0) / 1073741824 ))
[ "$DGIB" -gt 0 ] && [ "$size_gib" -le $(( DGIB - SWAP_GIB - 4 )) ] \
|| die "taille ${size_gib} + swap ${SWAP_GIB} + réserve dépasse le disque ${DGIB} Gio"
# [B1] Aucun label FWS résiduel ne doit exister sur un AUTRE disque (collision
# by-partlabel = risque d'écrire ailleurs). On refuse plutôt que de deviner.
while read -r dev lbl; do
case "$lbl" in
FWSWIN|WINESP|FWSSWAP)
pk="/dev/$(lsblk -no PKNAME "$dev" 2>/dev/null | head -1)"
[ "$(canon "$pk")" = "$WIN_C" ] \
|| die "label '$lbl' déjà présent sur $pk (≠ cible) — nettoie-le d'abord (wipefs -a $pk)" ;;
esac
done < <(lsblk -lnpo NAME,PARTLABEL 2>/dev/null)
log "Garde-fous OK : $WIN_DISK ($DGIB Gio) sûr à dédier à Windows (swap ${SWAP_GIB} Gio)."
# --- 2) Neutraliser toute activation du disque cible [M2] -------------------
log "Neutralisation des partitions de $WIN_DISK…"
for part in $(lsblk -lnpo NAME "$WIN_DISK" 2>/dev/null | tail -n +2); do
umount "$part" 2>/dev/null || true
swapoff "$part" 2>/dev/null || true
for vg in $(pvs --noheadings -o vg_name "$part" 2>/dev/null); do vgchange -an "$vg" 2>/dev/null || true; done
for h in $(lsblk -lno NAME "$part" 2>/dev/null | tail -n +2); do
case "$(lsblk -no TYPE "/dev/$h" 2>/dev/null | head -1)" in
raid*) mdadm --stop "/dev/$h" 2>/dev/null || true ;;
crypt) cryptsetup close "$h" 2>/dev/null || true ;;
esac
done
done
# Arrays md dont le disque cible est lui-même membre (détection par colonne TYPE).
for md in $(lsblk -lnpo NAME,TYPE "$WIN_DISK" 2>/dev/null | awk '$2 ~ /raid/{print $1}'); do
mdadm --stop "$md" 2>/dev/null || true
done
# Re-vérif fail-closed : plus rien d'actif sur le disque cible.
for part in $(lsblk -lnpo NAME "$WIN_DISK" 2>/dev/null | tail -n +2); do
findmnt -S "$part" >/dev/null 2>&1 && die "$part encore monté après neutralisation"
swapon --show=NAME --noheadings 2>/dev/null | grep -qx "$part" && die "$part encore en swap"
[ -n "$(lsblk -lno NAME "$part" 2>/dev/null | tail -n +2)" ] && die "$part a encore des holders (md/dm) actifs"
done
# --- 3) Carve (mode DEDICATE) — adressage par NUMÉRO sur le disque cible [B1]
log "Carve : ESP 1Gio + MSR 16Mio + NTFS ${size_gib}Gio + swap ${SWAP_GIB}Gio"
sgdisk --zap-all "$WIN_DISK" || die "sgdisk --zap-all a échoué"
sgdisk -n1:0:+1GiB -t1:ef00 -c1:WINESP "$WIN_DISK" || die "création WINESP"
sgdisk -n2:0:+16MiB -t2:0c01 -c2:MSR "$WIN_DISK" || warn "MSR non créée (non bloquant)"
sgdisk -n3:0:+${size_gib}GiB -t3:0700 -c3:FWSWIN "$WIN_DISK" || die "création partition Windows"
sgdisk -n4:0:+${SWAP_GIB}GiB -t4:8200 -c4:FWSSWAP "$WIN_DISK" || die "création swap"
partprobe "$WIN_DISK"; udevadm settle
ESP_PART="$(part_dev "$WIN_DISK" 1)"
WIN_PART="$(part_dev "$WIN_DISK" 3)"
SWAP_PART="$(part_dev "$WIN_DISK" 4)"
for p in "$ESP_PART" "$WIN_PART" "$SWAP_PART"; do
[ -b "$p" ] || die "partition $p absente après carve"
pk="/dev/$(lsblk -no PKNAME "$p" 2>/dev/null | head -1)"
[ "$(canon "$pk")" = "$WIN_C" ] || die "$p n'est PAS sur $WIN_DISK — abort"
done
mkfs.fat -F32 -n WINESP "$ESP_PART" || die "mkfs.fat WINESP"
mkswap -L FWSSWAP "$SWAP_PART" || die "mkswap FWSSWAP"
mkntfs -Q -f -L FWSWIN "$WIN_PART" || mkntfs -f -L FWSWIN "$WIN_PART" || die "mkntfs FWSWIN"
# --- 4) Appliquer install.wim (wimlib) sur la NTFS --------------------------
ISO_MNT="$(mktemp -d)"; WIN_MNT="$(mktemp -d)"; ESP_MNT="$(mktemp -d)"
cleanup(){ for m in "$WIN_MNT" "$ESP_MNT" "$ISO_MNT"; do umount "$m" 2>/dev/null || true; done; }
trap cleanup EXIT
mount -o loop,ro "$iso" "$ISO_MNT" || die "montage ISO échoué"
IMG="$ISO_MNT/sources/install.wim"; [ -f "$IMG" ] || IMG="$ISO_MNT/sources/install.esd"
[ -f "$IMG" ] || die "install.wim/.esd introuvable dans l'ISO"
# Édition par NOM (insensible à la casse) ; repli image unique [m4].
WANT="${edition:-Windows 11 Pro}"
IDX="$(wimlib-imagex info "$IMG" 2>/dev/null | awk -v w="$(printf '%s' "$WANT" | tr 'A-Z' 'a-z')" -F': *' '
tolower($1) ~ /^index/{i=$2} index(tolower($0), w){print i; exit}')"
if [ -z "$IDX" ]; then
CNT="$(wimlib-imagex info "$IMG" 2>/dev/null | awk -F': *' 'tolower($1) ~ /image count/{print $2; exit}')"
[ "${CNT:-0}" = 1 ] && IDX=1
fi
[ -n "$IDX" ] || die "édition « $WANT » absente de l'ISO (wimlib-imagex info) — ISO Home/N/tiny ?"
log "Application de l'image (index $IDX = $WANT) sur $WIN_PART — long…"
wimlib-imagex apply "$IMG" "$IDX" "$WIN_PART" || die "wimlib-imagex apply a échoué"
# --- 5) Bootabilité : BCD (hivex → repli WinPE-bcdboot) ---------------------
mount "$WIN_PART" "$WIN_MNT" || die "montage NTFS échoué"
mount "$ESP_PART" "$ESP_MNT" || die "montage WINESP échoué"
mkdir -p "$ESP_MNT/EFI/Microsoft/Boot"
cp -rn "$WIN_MNT/Windows/Boot/EFI/." "$ESP_MNT/EFI/Microsoft/Boot/" 2>/dev/null || true
WIN_PARTUUID="$(blkid -s PARTUUID -o value "$WIN_PART" 2>/dev/null)"
DISK_GUID="$(sgdisk -p "$WIN_DISK" 2>/dev/null | sed -n 's/^Disk identifier (GUID): *//p')"
# Pilote Intel VMD à injecter ? (case cochée dans le spoke + .inf présents).
DRV=""
if [ "${intel_vmd:-0}" = 1 ]; then
if ls /usr/local/share/fws/drivers/intel-vmd/*.inf >/dev/null 2>&1; then
DRV=/usr/local/share/fws/drivers/intel-vmd
log "Intel VMD demandé + pilote présent → injection via WinPE (dism)."
else
warn "Intel VMD demandé mais AUCUN pilote dans .../drivers/intel-vmd/ — Windows tentera son pilote VMD inbox (25H2). Dépose le pilote Intel si le boot échoue."
fi
fi
# Avec un pilote VMD à injecter, on FORCE la voie WinPE (dism y tourne). Sinon
# hivex (qui bascule de toute façon sur WinPE tant que non validé).
if [ -z "$DRV" ] && /usr/local/lib/fws/fws-bcd-write "$ESP_MNT" "$WIN_MNT" "$WIN_PARTUUID" "$DISK_GUID"; then
log "BCD écrit (voie hivex)."
BCDFIX=0
else
[ -n "$DRV" ] && log "Bascule WinPE (bcdboot + injection pilote VMD)." \
|| warn "Voie hivex non validée → repli WinPE-bcdboot (1 reboot auto)."
umount "$WIN_MNT" 2>/dev/null || true
/usr/local/bin/fws-windows-bcdfix "$ISO_MNT" "$ESP_MNT" "$WIN_DISK" 1 "$DRV" \
|| die "repli WinPE-bcdfix a échoué — Windows non bootable (FWS reste seul bootable)"
BCDFIX=1
mount "$WIN_PART" "$WIN_MNT" 2>/dev/null || die "remontage NTFS échoué"
fi
# --- 6) Anti-BitLocker HORS LIGNE (crucial : évite le lockout) --------------
HIVE="$WIN_MNT/Windows/System32/config/SYSTEM"
if command -v hivexget >/dev/null 2>&1 && [ -f "$HIVE" ]; then
SEL="$(hivexget "$HIVE" Select Current 2>/dev/null | tr -cd '0-9')"; [ -n "$SEL" ] || SEL=1
printf '[HKEY_LOCAL_MACHINE\\SYSTEM\\ControlSet%03d\\Control\\BitLocker]\n"PreventDeviceEncryption"=dword:00000001\n' "$SEL" \
| hivexregedit --merge --prefix 'HKEY_LOCAL_MACHINE\SYSTEM' "$HIVE" \
&& log "Anti-BitLocker offline posé (ControlSet$(printf '%03d' "$SEL"))." \
|| warn "écriture anti-BitLocker offline échouée (ceinture reg add en specialize)."
else
warn "hivex/SYSTEM absent → anti-BitLocker via la ceinture specialize seule."
fi
# --- 7) Answer file offline + injection du composant gameboot ---------------
TMPL=/usr/local/share/fws/gameboot-windows/unattend.offline.xml.tmpl
HN="$(tr -cd 'a-zA-Z0-9-' < /tmp/fws-hostname 2>/dev/null)"; [ -n "$HN" ] || HN=fws-win
LOC="$(. "$SYSROOT/etc/locale.conf" 2>/dev/null; printf '%s' "${LANG%%.*}")"; [ -n "$LOC" ] || LOC=fr-CH
LOC="${LOC/_/-}"
FIRSTBOOT='C:\ProgramData\FWS\deploy\FirstBoot-FwsGameboot.ps1'
mkdir -p "$WIN_MNT/Windows/Panther" "$WIN_MNT/ProgramData/FWS/deploy"
sed -e "s|__HOSTNAME__|$HN|g" -e "s|__USER__|gaming|g" -e "s|__DISPLAY__|Gaming|g" \
-e "s|__LOCALE__|$LOC|g" -e "s|__INPUT__|$LOC|g" -e "s|__FIRSTBOOT__|${FIRSTBOOT//\\/\\\\}|g" \
"$TMPL" > "$WIN_MNT/Windows/Panther/unattend.xml" || die "rendu unattend.xml échoué"
cp -rf /usr/local/share/fws/gameboot-windows/. "$WIN_MNT/ProgramData/FWS/deploy/" || die "injection gameboot échouée"
rm -f "$WIN_MNT/ProgramData/FWS/deploy/unattend.offline.xml.tmpl" 2>/dev/null || true
log "unattend.xml + composant gameboot injectés sur la NTFS."
# --- 8) Vérification AVANT toute écriture NVRAM -----------------------------
[ -f "$WIN_MNT/Windows/System32/ntoskrnl.exe" ] || die "ntoskrnl.exe absent → apply incomplet"
[ -f "$WIN_MNT/Windows/System32/winload.efi" ] || die "winload.efi absent"
[ -f "$ESP_MNT/EFI/Microsoft/Boot/bootmgfw.efi" ] || die "bootmgfw.efi absent de l'ESP"
[ "$BCDFIX" = 1 ] || [ -s "$ESP_MNT/EFI/Microsoft/Boot/BCD" ] || die "BCD absent/vide (voie hivex)"
sync
# --- 9) Entrées de boot : FWS TOUJOURS en tête [M5][M6] ---------------------
# Voie hivex directe : on crée l'entrée Windows persistante (en queue).
if [ "$BCDFIX" = 0 ]; then
efibootmgr -c -d "$WIN_DISK" -p 1 -L 'Windows Boot Manager' -l '\EFI\Microsoft\Boot\bootmgfw.efi' >/dev/null 2>&1 || warn "création entrée UEFI Windows échouée"
WBN="$(efibootmgr 2>/dev/null | sed -n 's/^Boot\([0-9A-Fa-f]\{4\}\)\*\? *Windows Boot Manager.*/\1/p' | head -1)"
[ -n "$WBN" ] && sed -i "s/^FWS_WINDOWS_BOOTNUM=.*/FWS_WINDOWS_BOOTNUM=\"$WBN\"/" \
"$SYSROOT/etc/fws/gameboot.conf" 2>/dev/null || true
fi
# Dans TOUS les cas : FWS en BootOrder[0] (Windows/WinPE jamais 1er). Si aucune
# entrée NVRAM FWS (booté via \EFI\BOOT\BOOTX64.EFI removable), en créer une.
FBN="$(efibootmgr 2>/dev/null | sed -n 's/^Boot\([0-9A-Fa-f]\{4\}\)\*\? *\(FWS\|GRUB\).*/\1/p' | head -1)"
if [ -z "$FBN" ]; then
esp_src="$(findmnt -no SOURCE "$SYSROOT/boot/efi" 2>/dev/null)"
if [ -b "$esp_src" ]; then
sdisk="/dev/$(lsblk -no PKNAME "$esp_src" 2>/dev/null | head -1)"
spn="$(lsblk -no PARTN "$esp_src" 2>/dev/null | head -1)"
efibootmgr -c -d "$sdisk" -p "${spn:-1}" -L 'FWS' -l '\EFI\BOOT\BOOTX64.EFI' >/dev/null 2>&1 || true
FBN="$(efibootmgr 2>/dev/null | sed -n 's/^Boot\([0-9A-Fa-f]\{4\}\)\*\? *FWS.*/\1/p' | head -1)"
fi
fi
order="$(efibootmgr 2>/dev/null | sed -n 's/^BootOrder: //p' | tr -d '[:space:]')"
if [ -n "$FBN" ] && [ -n "$order" ]; then
rest="$(printf '%s' "$order" | tr ',' '\n' | grep -vx "$FBN" | paste -sd, -)"
efibootmgr -o "${FBN}${rest:+,$rest}" >/dev/null 2>&1 || true # FWS 1er, reste (Windows/WinPE) derrière
fi
# --- 10) Hibernation FWS (chroot cible) : swap dédié → resume= --------------
SWAP_UUID="$(blkid -s UUID -o value "$SWAP_PART" 2>/dev/null)"
if [ -n "$SWAP_UUID" ] && command -v arch-chroot >/dev/null 2>&1; then
log "Config hibernation FWS (resume=UUID=$SWAP_UUID)…"
cat > "$SYSROOT/etc/fws-gameboot-swap.sh" <<CHROOT
set -u
grep -q "UUID=$SWAP_UUID" /etc/fstab 2>/dev/null || printf 'UUID=%s none swap defaults 0 0\n' "$SWAP_UUID" >> /etc/fstab
grep -qE '^HOOKS=.*\bresume\b' /etc/mkinitcpio.conf || sed -i -E 's/^(HOOKS=\(.*)\bfilesystems\b/\1resume filesystems/' /etc/mkinitcpio.conf
mkinitcpio -P || true
if grep -q '^GRUB_CMDLINE_LINUX_DEFAULT=' /etc/default/grub; then
grep -q 'resume=UUID=$SWAP_UUID' /etc/default/grub || sed -i 's|^GRUB_CMDLINE_LINUX_DEFAULT="\(.*\)"|GRUB_CMDLINE_LINUX_DEFAULT="\1 resume=UUID=$SWAP_UUID hibernate.compressor=lz4"|' /etc/default/grub
fi
grep -q '^GRUB_DISABLE_OS_PROBER=' /etc/default/grub || echo 'GRUB_DISABLE_OS_PROBER=true' >> /etc/default/grub
grub-mkconfig -o /boot/grub/grub.cfg || true
install -Dm644 /dev/stdin /etc/modprobe.d/nvidia-hibernate.conf <<'NV'
options nvidia NVreg_PreserveVideoMemoryAllocations=1 NVreg_TemporaryFilePath=/var/tmp
NV
systemctl enable nvidia-suspend.service nvidia-hibernate.service nvidia-resume.service 2>/dev/null || true
systemctl enable fws-gameboot-bootfix.service 2>/dev/null || true
CHROOT
arch-chroot "$SYSROOT" bash /etc/fws-gameboot-swap.sh 2>&1 | tee -a /tmp/fws-windows-deploy.log || warn "config hibernation chroot partielle"
rm -f "$SYSROOT/etc/fws-gameboot-swap.sh"
else
warn "swap UUID introuvable ou arch-chroot absent → hibernation non configurée (reboot simple restera dispo)."
fi
sync
status "DEPLOY_OK: Windows sur $WIN_DISK (${size_gib}Gio) — SB à activer au firmware"
log "Déploiement Windows terminé.$([ "$BCDFIX" = 1 ] && echo ' (BCD via WinPE au 1er reboot)')"
log "⚠ Reste manuel : activer Secure Boot au firmware (sbctl verify déjà clean)."
exit 0
@@ -0,0 +1,33 @@
#!/bin/bash
# ============================================================
# fws-bcd-write — construit un magasin BCD Windows OFFLINE depuis Linux (hivex).
#
# ⚠⚠ NON VALIDÉ SUR MATÉRIEL RÉEL. Le BCD est un hive registre propriétaire ;
# `bcdboot` (l'outil officiel qui l'écrit correctement) est Windows-only. On
# copie le BCD-Template livré avec Windows et on patche les éléments STRING
# qu'on maîtrise ; l'élément BINAIRE « device »/« osdevice » (blob encodant la
# partition GPT — DISK_GUID + PARTUUID) est LA surface à auditer et n'est PAS
# encodé de façon fiable ici. Tant que ce n'est pas prouvé sur un Windows 11
# 25H2 réel, on renvoie un code d'échec → l'orchestrateur bascule sur
# fws-windows-bcdfix (WinPE-bcdboot), qui utilise le vrai bcdboot.
#
# Args : <ESP_MOUNT> <WIN_MOUNT> <WINDOWS_PARTUUID> <DISK_GUID>
# Codes : 0 = BCD écrit & jugé sûr ; 1 = bascule sur le repli WinPE.
# ============================================================
set -u
ESP="${1:-}"; WIN="${2:-}"; PARTUUID="${3:-}"; DISKGUID="${4:-}"
[ -d "$ESP" ] && [ -d "$WIN" ] || { echo "[bcd-write] args invalides"; exit 1; }
command -v hivexregedit >/dev/null 2>&1 || { echo "[bcd-write] hivex absent → repli"; exit 1; }
TMPL="$WIN/Windows/System32/config/BCD-Template"
[ -f "$TMPL" ] || { echo "[bcd-write] BCD-Template absent ($TMPL) → repli"; exit 1; }
DST="$ESP/EFI/Microsoft/Boot/BCD"
mkdir -p "$(dirname "$DST")"
cp -f "$TMPL" "$DST"
echo "[bcd-write] BCD-Template copié → $DST (device cible $PARTUUID / disque $DISKGUID)."
# ⚠ Le patch du blob « device » (Element 0x11000001) encodant la partition n'est
# pas implémenté de façon vérifiée ici → on force la voie robuste WinPE-bcdboot.
echo "[bcd-write] ⚠ NON VALIDÉ (blob device non encodé) → bascule sur fws-windows-bcdfix."
exit 1
@@ -0,0 +1,20 @@
[Desktop Entry]
# FWS — lanceur de l'application de réparation dans l'OS INSTALLÉ (copié tel
# quel du live). L'app tourne en utilisateur ; chaque appel du moteur passe
# par pkexec (cf. org.fws.recovery.policy).
Type=Application
Name=Récupération FWS
Name[en]=FWS Recovery
GenericName=Réparation du système
GenericName[en]=System repair
Comment=Réparer GRUB, l'initramfs ou pacman — le point de restauration FWS
Comment[en]=Repair GRUB, the initramfs or pacman — the FWS restore point
Exec=/usr/local/bin/fws-recovery-gui
Icon=fws-recovery
# Associe la fenêtre (WM_CLASS via GLib.set_prgname) à ce lanceur → bonne
# icône dans la barre de titre / barre des tâches (sinon icône générique).
StartupWMClass=fws-recovery-gui
Terminal=false
Categories=System;
Keywords=recovery;réparation;restauration;grub;boot;secours;dépannage;
Keywords[en]=recovery;repair;restore;grub;boot;rescue;
@@ -0,0 +1,9 @@
[Desktop Entry]
# FWS — masque « À propos de Xfce » du menu d'applications. Le binaire
# xfce4-about vient de libxfce4ui (dépendance de Thunar) : il n'implique
# AUCUNE session XFCE. /usr/local/share étant prioritaire sur /usr/share
# dans XDG_DATA_DIRS, cette entrée NoDisplay masque celle du paquet partout.
Type=Application
Name=About Xfce
Exec=xfce4-about
NoDisplay=true
@@ -0,0 +1,33 @@
# Pilote Intel VMD/RST (iaStorVD) — à déposer ici
FWS **ne peut pas embarquer** le pilote Intel par défaut (binaire propriétaire).
Pour activer le support des machines **Intel avec VMD/RST activé dans le BIOS**,
dépose ici les fichiers du pilote **Intel VMD** (« f6 » VMD driver) :
```
iaStorVD.inf
iaStorVD.sys
iaStorVD.cat
(+ éventuels iaStorAC.* / autres .inf/.sys du paquet)
```
## Où le télécharger
- Intel « Rapid Storage Technology (RST) » / VMD driver, package **f6vmdflpy**
(Intel Download Center, ou le site du fabricant de la carte mère).
- Extraire le contenu du dossier `f6vmdflpy-x64\` ici.
## Ce que FWS en fait (automatique)
Si la case **« Intel VMD/RST support »** est cochée dans l'installateur
(auto-cochée quand un contrôleur VMD est détecté) **et** que des `.inf` sont
présents ici, `fws-windows-deploy` les injecte dans le Windows déployé via la
WinPE (`drvload` pour que WinPE voie les NVMe, puis `dism /add-driver` pour
enregistrer le pilote comme boot-critique) → Windows boote sur les NVMe Intel VMD.
## Notes
- **Windows 11 25H2** inclut déjà iaStorVD *inbox* pour beaucoup de contrôleurs
VMD → l'injection n'est nécessaire que pour les contrôleurs non couverts.
- **Aucun effet sur AMD** : sans contrôleur VMD, l'option reste décochée / ignorée.
- ⚠ Chemin **NON VALIDÉ** sur matériel Intel réel (impossible à tester ici).
@@ -0,0 +1,34 @@
<#
FirstBoot-FwsGameboot.ps1 — orchestrateur de PREMIER DEMARRAGE (une seule fois).
Au tout premier boot du Windows gaming, execute automatiquement :
1. Install-FwsGameboot.ps1 (composant de retour + auto-login + taches) ;
2. Install-Games.ps1 (telecharge+lance les installeurs officiels des
jeux marques autoinstall dans installers.json).
Puis pose un marqueur pour ne PLUS rien refaire aux boots suivants.
Cablage (le declencheur du "premier boot") : soit via autounattend.xml
<FirstLogonCommands> quand FWS pilote l'install Windows (cf. README), soit via
une entree RunOnce. Idempotent : re-lancable sans risque (le marqueur protege).
#>
#Requires -RunAsAdministrator
$ErrorActionPreference = 'Continue'
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$done = Join-Path $Env:ProgramData 'FWS\.firstboot-done'
if (Test-Path $done) { Write-Host "FWS first boot deja effectue — rien a faire."; return }
Write-Host "== FWS premier demarrage : configuration gameboot + installation des jeux =="
# 1) Composant de retour + auto-login (compte gaming SANS mot de passe recommande).
try { & (Join-Path $here 'Install-FwsGameboot.ps1') }
catch { Write-Warning "Install-FwsGameboot a echoue : $_" }
# 2) Installation des jeux (sources officielles Riot).
try { & (Join-Path $here 'Install-Games.ps1') }
catch { Write-Warning "Install-Games a echoue : $_" }
# Marqueur "fait" (dans le meme dossier durci que le reste).
New-Item -ItemType Directory -Force -Path (Split-Path $done) | Out-Null
New-Item -ItemType File -Force -Path $done | Out-Null
Write-Host "== FWS premier demarrage termine =="
@@ -0,0 +1,101 @@
<#
Install-FwsGameboot.ps1 — installe le composant de RETOUR + LANCEMENT AUTO.
A lancer UNE FOIS, EN ADMIN, dans le Windows gaming. Met en place le
"sans login, jeu lance tout seul" :
- AUTO-LOGIN du compte gaming (pas d'ecran de mot de passe) ;
- tache AU LOGON qui lance fws-play (lit le jeton, lance le bon jeu, retour) ;
- tache ONSTART qui arme le boot suivant vers FWS (filet anti-crash) ;
- powercfg /h off (reboot PLEIN → NTFS jamais "sale").
PREREQUIS VERIFIES par ce script (echec explicite sinon) :
- le compte courant est ADMINISTRATEUR local (bcdedit + taches SYSTEM) ;
- le compte n'a PAS de mot de passe, OU -Password est fourni.
Empreinte minimale, SANS interference avec Vanguard (aucune injection/hook noyau).
Parametres :
-Password <str> Mot de passe du compte pour l'auto-login. OMETTRE si le
compte n'a pas de mot de passe. Fourni => stocke EN CLAIR
dans le registre (limitation AutoAdminLogon) : preferer
Sysinternals Autologon.exe (secret LSA chiffre).
#>
#Requires -RunAsAdministrator
param([string]$Password)
$ErrorActionPreference = 'Stop'
# --- Prereq 1 : le compte gaming DOIT etre admin local (SID, locale-independant) ---
try {
$adminGrp = (Get-LocalGroup -SID 'S-1-5-32-544').Name # "Administrators"/"Administrateurs"
$members = Get-LocalGroupMember -Group $adminGrp -ErrorAction Stop
$isAdmin = $members | Where-Object { $_.Name -ieq "$Env:COMPUTERNAME\$Env:USERNAME" -or $_.Name -ieq $Env:USERNAME }
} catch { $isAdmin = $null }
if (-not $isAdmin) {
throw "Le compte '$Env:USERNAME' doit etre ADMINISTRATEUR local : fws-play appelle bcdedit et pilote des taches SYSTEM. Ajoute-le au groupe administrateurs puis relance."
}
# --- Prereq 2 : auto-login fiable => compte sans mot de passe OU -Password fourni ---
$pwdRequired = $true
try { $pwdRequired = (Get-LocalUser -Name $Env:USERNAME).PasswordRequired } catch { }
if ($pwdRequired -and -not $PSBoundParameters.ContainsKey('Password')) {
throw "Le compte '$Env:USERNAME' exige un mot de passe : relance avec -Password '<mdp>', ou retire le mot de passe du compte. Sinon l'auto-login echoue -> ecran de connexion -> la boucle gameboot se bloque."
}
# --- Deploiement des scripts + durcissement ACL (exe potentiellement lance ELEVE) ---
$dest = Join-Path $Env:ProgramData 'FWS'
New-Item -ItemType Directory -Force -Path $dest | Out-Null
foreach ($f in 'fws-return.ps1','fws-play.ps1','games.json','installers.json','Install-Games.ps1') {
$src = Join-Path $PSScriptRoot $f
if (Test-Path $src) { Copy-Item $src $dest -Force }
}
# SYSTEM + Administrateurs = plein controle ; Utilisateurs = lecture seule ; heritage retire.
& icacls $dest /inheritance:r /grant:r 'SYSTEM:(OI)(CI)F' '*S-1-5-32-544:(OI)(CI)F' '*S-1-5-32-545:(OI)(CI)RX' | Out-Null
# (1) Reboot PLEIN : pas de Fast Startup → volumes propres.
& powercfg /h off 2>$null
# (2) AUTO-LOGIN du compte courant.
$winlogon = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'
Set-ItemProperty $winlogon 'AutoAdminLogon' '1' -Force
Set-ItemProperty $winlogon 'ForceAutoLogon' '1' -Force
Set-ItemProperty $winlogon 'DefaultUserName' $Env:USERNAME -Force
Set-ItemProperty $winlogon 'DefaultDomainName' '.' -Force # '.' = local, immune au renommage
if ($PSBoundParameters.ContainsKey('Password')) {
Set-ItemProperty $winlogon 'DefaultPassword' $Password -Force
Write-Warning "Mot de passe stocke EN CLAIR dans le registre. Preferer Sysinternals Autologon.exe (LSA chiffre)."
} else {
Remove-ItemProperty $winlogon 'DefaultPassword' -ErrorAction SilentlyContinue
Write-Host "Auto-login sans mot de passe stocke (compte sans mot de passe verifie)."
}
# (3) Tache AU LOGON : lancer fws-play (lit le jeton, lance le jeu, gere le retour).
$playAction = New-ScheduledTaskAction -Execute 'powershell.exe' `
-Argument ('-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "{0}\fws-play.ps1"' -f $dest)
$playTrigger = New-ScheduledTaskTrigger -AtLogOn -User $Env:USERNAME
$playPrincipal = New-ScheduledTaskPrincipal -UserId $Env:USERNAME -LogonType Interactive -RunLevel Highest
$playSettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
Register-ScheduledTask -TaskName 'FWS-Play-OnLogon' -Action $playAction -Trigger $playTrigger `
-Principal $playPrincipal -Settings $playSettings -Force | Out-Null
# (4) Tache ONSTART (SYSTEM) : armer le boot suivant vers FWS au demarrage.
# Filet anti-crash : si Windows redemarre seul (WU, plantage) sans fws-play,
# le boot suivant repart vers FWS. fws-play RE-AFFIRME Windows apres lancement
# (gere la course avec cette tache — voir fws-play.ps1).
$retAction = New-ScheduledTaskAction -Execute 'powershell.exe' `
-Argument ('-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "{0}\fws-return.ps1" -Target FWS' -f $dest)
$retTrigger = New-ScheduledTaskTrigger -AtStartup
$retPrincipal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
$retSettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
Register-ScheduledTask -TaskName 'FWS-Return-OnStart' -Action $retAction -Trigger $retTrigger `
-Principal $retPrincipal -Settings $retSettings -Force | Out-Null
Write-Host ""
Write-Host "Composant FWS installe dans $dest." -ForegroundColor Green
Write-Host "Auto-login + lancement auto du jeu configures. Flux : boot -> login auto -> jeu." -ForegroundColor Green
Write-Host ""
Write-Host "RAPPELS IMPORTANTS :" -ForegroundColor Yellow
Write-Host " - POSTE KIOSQUE : auto-login + taches non-UAC => acces physique = bureau admin."
Write-Host " - NE PAS activer BitLocker sans sauvegarder la cle (flip Secure Boot cote FWS"
Write-Host " change PCR7 -> ecran de recuperation). Verifie : manage-bde -status"
Write-Host " - Ne desactive pas Secure Boot / TPM / VBS-HVCI : Vanguard les exige."
Write-Host " - Ajouter des jeux : editer $dest\games.json (id en [a-z0-9_-])."
@@ -0,0 +1,52 @@
<#
Install-Games.ps1 — telecharge et lance les installeurs OFFICIELS des jeux
marques autoinstall=true dans installers.json.
FWS ne redistribue AUCUN binaire Riot : chaque installeur est telecharge
depuis les serveurs de Riot. Riot change regulierement ses URLs -> en cas
d'echec, verifier/mettre a jour installers.json.
NB : l'installation de Valorant n'est PAS pleinement silencieuse (Vanguard
installe un driver noyau et impose un redemarrage) : ce script LANCE
l'installeur, l'utilisateur termine les eventuelles confirmations.
#>
$ErrorActionPreference = 'Continue'
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$cfg = Join-Path $here 'installers.json'
if (-not (Test-Path $cfg)) { Write-Warning "installers.json introuvable — aucun jeu installe."; return }
$installers = Get-Content $cfg -Raw | ConvertFrom-Json
$tmp = Join-Path $Env:TEMP 'fws-games'
New-Item -ItemType Directory -Force -Path $tmp | Out-Null
# TLS 1.2 pour Invoke-WebRequest sur d'anciennes stacks .NET.
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch { }
$names = $installers.PSObject.Properties.Name | Where-Object { $_ -notlike '_*' }
foreach ($name in $names) {
$g = $installers.$name
if (-not $g.autoinstall) { Write-Host "$name : autoinstall=false, ignore."; continue }
if (-not $g.url) {
Write-Warning "$name : aucune URL dans installers.json — installe-le manuellement."
continue
}
$out = Join-Path $tmp $g.installer
try {
Write-Host "Telechargement de l'installeur $name ..."
Invoke-WebRequest -Uri $g.url -OutFile $out -UseBasicParsing
} catch {
Write-Warning "$name : telechargement echoue ($_). URL a verifier dans installers.json (Riot les change souvent)."
continue
}
try {
Write-Host "Lancement de l'installeur $name ..."
if ($g.silentargs) { Start-Process $out -ArgumentList $g.silentargs -Wait }
else { Start-Process $out }
} catch {
Write-Warning "$name : lancement de l'installeur echoue ($_)."
}
}
Write-Host "Installation des jeux lancee. Termine les eventuelles fenetres d'installation + le redemarrage impose par Vanguard."
@@ -0,0 +1,85 @@
# FWS Gameboot — composant Windows
Composant à déployer **dans le Windows 11 gaming** (partition bare-metal dédiée)
pour le dual-boot orchestré « hibernate-swap » de FWS : **retour vers FWS** après
la session, **lancement automatique du jeu**, **auto-login**, et **premier
démarrage tout automatique** (config + installation des jeux).
> ⚠️ **Statut : non validé sur matériel réel.** Sémantique `bcdedit bootsequence`,
> auto-login, URLs d'installeurs Riot, resume GPU — à tester. Plan :
> `docs/hibernate-swap-dualboot.md`.
## Fichiers
| Fichier | Rôle |
|---|---|
| `FirstBoot-FwsGameboot.ps1` | **1er démarrage (une fois)** : lance Install-FwsGameboot puis Install-Games, pose un marqueur. |
| `Install-FwsGameboot.ps1` | Auto-login + tâche **logon** (fws-play) + tâche **ONSTART** + `powercfg /h off`. Vérifie compte admin/sans mot de passe. |
| `Install-Games.ps1` | Télécharge + lance les installeurs **officiels** des jeux `autoinstall` (Riot). |
| `installers.json` | URLs des installeurs Riot (à vérifier — Riot les change). |
| `fws-play.ps1` | Lit le jeu demandé, le lance, attend, rebascule vers FWS. |
| `fws-return.ps1` | Arme le boot suivant vers `FWS` ou `Windows` (locale-indépendant). |
| `games.json` | Registre des jeux : `id → {type, …, process}`. |
## Ce que FWS ne peut PAS faire (et ce qu'il fait à la place)
FWS **ne pré-embarque pas** Valorant/LoL : bundler Windows (licence) et
redistribuer les binaires Riot est interdit, et Vanguard flaggerait un jeu
pré-copié. **À la place**, `Install-Games.ps1` **télécharge les installeurs
officiels depuis Riot** au premier boot → même résultat (« ils sont déjà là »)
sans rien redistribuer. L'install de Valorant n'est pas 100 % silencieuse
(Vanguard installe un driver noyau + impose un reboot).
## Câbler le « premier démarrage automatique »
Le déclencheur dépend de comment Windows est installé :
**A. Install Windows piloté par FWS (recommandé, via `autounattend.xml`)** — ajouter
dans `<FirstLogonCommands>` :
```xml
<SynchronousCommand wcm:action="add">
<Order>1</Order>
<CommandLine>powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\ProgramData\FWS\deploy\FirstBoot-FwsGameboot.ps1"</CommandLine>
<Description>FWS Gameboot first boot</Description>
</SynchronousCommand>
```
(en ayant copié ce dossier dans `C:\ProgramData\FWS\deploy\` pendant le déploiement).
**B. Manuel (pour tester maintenant)** — dans le Windows gaming, PowerShell admin :
```powershell
Set-ExecutionPolicy -Scope Process Bypass -Force
.\FirstBoot-FwsGameboot.ps1
```
Idempotent : le marqueur `C:\ProgramData\FWS\.firstboot-done` empêche toute
réexécution.
## Prérequis (vérifiés par Install-FwsGameboot, échec explicite sinon)
- Compte gaming = **administrateur local** (bcdedit + tâches SYSTEM).
- Compte **sans mot de passe** (auto-login fiable), ou lancer avec `-Password`.
- **Secure Boot + TPM 2.0 + VBS/HVCI** actifs (Vanguard les exige).
## Flux complet (aucune interaction, hors confirmations Vanguard)
1. **1er boot**`FirstBoot` → auto-login configuré + Valo/LoL téléchargés et installés.
2. Depuis FWS : `fws-gameboot to-windows --game valorant` → jeton sur l'ESP, hibernation, bascule.
3. Windows démarre **bare-metal****auto-login**`fws-play` lit le jeton, lance le jeu.
4. À la fermeture/crash → retour, **session FWS restaurée** (apps/pages intactes).
Filet permanent : côté FWS, `BootOrder[0] = FWS` ré-affirmé à chaque démarrage.
## Ajouter un jeu
- Lancement : ajouter une entrée dans `games.json` (`id` en `[a-z0-9_-]`).
- Auto-install : ajouter l'URL de l'installeur officiel dans `installers.json`
(`autoinstall: true`). Types de lancement : `riot` / `uri` (`steam://…`,
`com.epicgames.launcher://…`) / `exe`.
## Rappels sécurité
- **Poste kiosque** : auto-login + tâches non-UAC ⇒ accès physique = bureau admin.
- **BitLocker** : ne pas l'activer sans sauvegarder la clé (flip Secure Boot →
écran de récupération). `manage-bde -status`.
@@ -0,0 +1,140 @@
<#
fws-play.ps1 — session de jeu Windows orchestree, lancement AUTO du jeu choisi.
Lit (et CONSOMME) le jeton depose par FWS sur l'ESP
(\EFI\FWS\launch.json = {"game":"<id>"}), cherche <id> dans games.json, lance
le jeu, attend sa fermeture/crash, puis rebascule vers FWS.
Robustesse (aucune interaction requise) :
- resolution du jeton sur TOUTES les ESP (dual-disque) + suppression apres
lecture (consume-once : pas de rejeu d'un ancien choix) ;
- resolution du Riot Client par sa source canonique (pas de chemin en dur) ;
- re-affirmation de BootNext=Windows apres le lancement, pour gagner la course
contre la tache ONSTART (fws-play = dernier ecrivain) — un crash pendant le
jeu revient donc bien sur Windows ;
- FINALLY : rearme FWS et reboote SI le jeu a bien ete vu.
Filet permanent cote FWS : BootOrder[0]=FWS. A lancer a l'ouverture de session
(autologin du compte gaming, qui DOIT etre admin local). Tourne EN ADMIN. #>
$ErrorActionPreference = 'Stop'
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$return = Join-Path $here 'fws-return.ps1'
$gamesDb = Join-Path $here 'games.json'
$seen = $false
$EFI_TYPE = '{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}' # GUID GPT "EFI System Partition"
function Get-RequestedGame {
# Sonde toutes les ESP, lit \EFI\FWS\launch.json, le SUPPRIME (consume-once).
# Defaut : valorant. Distingue "jeton absent" de "jeton illisible" (log).
$default = 'valorant'; $result = $default
$mounts = @()
try { $esps = Get-Partition -ErrorAction SilentlyContinue | Where-Object { $_.GptType -eq $EFI_TYPE } }
catch { $esps = @() }
foreach ($p in $esps) {
$used = (Get-PSDrive -PSProvider FileSystem -ErrorAction SilentlyContinue).Name
$letter = (67..90 | ForEach-Object { [char]$_ }) |
Where-Object { $used -notcontains "$_" -and ($mounts.Letter -notcontains "$_`:") } |
Select-Object -First 1
if (-not $letter) { continue }
try {
Add-PartitionAccessPath -DiskNumber $p.DiskNumber -PartitionNumber $p.PartitionNumber -AccessPath "$letter`:" -ErrorAction Stop
$mounts += @{ Letter = "$letter`:"; Disk = $p.DiskNumber; Part = $p.PartitionNumber }
} catch { }
}
foreach ($m in $mounts) {
$tok = "$($m.Letter)\EFI\FWS\launch.json"
if (Test-Path $tok) {
try {
$g = (Get-Content $tok -Raw -ErrorAction Stop | ConvertFrom-Json).game
if ($g) { $result = "$g".ToLower() }
} catch { Write-Warning "Jeton illisible ($tok) : $_ — repli sur $default." }
Remove-Item $tok -Force -ErrorAction SilentlyContinue # consume-once
break
}
}
foreach ($m in $mounts) {
try { Remove-PartitionAccessPath -DiskNumber $m.Disk -PartitionNumber $m.Part -AccessPath $m.Letter -ErrorAction SilentlyContinue } catch { }
}
return $result
}
function Resolve-RiotClient {
# Riot s'installe par defaut a la RACINE (C:\Riot Games\...), pas sous
# Program Files. Source canonique : RiotClientInstalls.json (rc_default).
$cands = @()
$inst = Join-Path $Env:ProgramData 'Riot Games\RiotClientInstalls.json'
if (Test-Path $inst) { try { $cands += (Get-Content $inst -Raw | ConvertFrom-Json).rc_default } catch { } }
$cands += 'C:\Riot Games\Riot Client\RiotClientServices.exe'
$cands += (Join-Path ${Env:ProgramFiles} 'Riot Games\Riot Client\RiotClientServices.exe')
if (${Env:ProgramFiles(x86)}) { $cands += (Join-Path ${Env:ProgramFiles(x86)} 'Riot Games\Riot Client\RiotClientServices.exe') }
return $cands | Where-Object { $_ -and (Test-Path $_) } | Select-Object -First 1
}
function Start-Game($entry) {
switch ($entry.type) {
'riot' {
$riot = Resolve-RiotClient
if ($riot) { Start-Process $riot -ArgumentList "--launch-product=$($entry.product)","--launch-patchline=$($entry.patchline)" }
else { Write-Warning "Riot Client introuvable (ni RiotClientInstalls.json, ni chemins connus)." }
}
'uri' { Start-Process $entry.uri }
'exe' {
if ($entry.args) { Start-Process -FilePath $entry.path -ArgumentList @($entry.args) }
else { Start-Process -FilePath $entry.path }
}
default { Write-Warning "Type de lancement inconnu : $($entry.type)" }
}
}
try {
# Pendant la session, un crash/reboot doit rester sur WINDOWS.
try { & $return -Target Windows } catch { Write-Warning "Arm Windows a echoue : $_" }
# Resoudre + VALIDER le jeu (rejeter les cles '_*' et les entrees sans .type).
$game = Get-RequestedGame
$games = Get-Content $gamesDb -Raw | ConvertFrom-Json
$entry = $games.$game
if ($game -like '_*' -or -not $entry -or -not $entry.type) {
Write-Warning "Jeu '$game' invalide/absent de games.json — repli sur valorant."
$game = 'valorant'; $entry = $games.valorant
}
if (-not $entry) { throw "Entree 'valorant' absente de games.json." }
Write-Host "Lancement du jeu : $game"
Start-Game $entry
# Course avec la tache ONSTART (qui arme FWS au boot) : on la laisse passer
# puis on RE-AFFIRME Windows → fws-play est le dernier ecrivain de BootNext.
Start-Sleep -Seconds 25
try { & $return -Target Windows } catch { Write-Warning "Re-arm Windows a echoue : $_" }
# Attendre l'apparition PUIS la disparition du process (boucle de presence,
# robuste aux relances client). Ne rebooter que si le jeu a ete VU.
$proc = $entry.process
if ($proc) {
for ($i = 0; $i -lt 1440; $i++) { # ~2 h max d'attente d'apparition
if (Get-Process $proc -ErrorAction SilentlyContinue) { $seen = $true; break }
Start-Sleep -Seconds 5
}
if ($seen) {
while (Get-Process $proc -ErrorAction SilentlyContinue) { Start-Sleep -Seconds 5 }
} else {
Write-Warning "Process '$proc' jamais detecte — pas de reboot automatique."
}
} else {
Write-Warning "Aucun 'process' defini pour '$game' — pas d'attente ni de reboot auto."
}
}
finally {
# Rearmer FWS, QUOI QU'IL ARRIVE.
try { & $return -Target FWS } catch { Write-Warning "Arm FWS a echoue : $_ — BootOrder[0]=FWS reste le filet." }
if ($seen) {
Start-Sleep -Seconds 2
Restart-Computer -Force
} else {
Write-Warning "Session sans jeu detecte : redemarre manuellement pour revenir a FWS (BootNext deja arme vers FWS)."
}
}
@@ -0,0 +1,66 @@
<#
fws-return.ps1 — arme le "boot suivant" du firmware (one-shot) vers une cible.
Cote FWS, l'equivalent est « efibootmgr --bootnext ». Cote Windows, on utilise
« bcdedit /set {fwbootmgr} bootsequence <id> » : la sequence de boot UNE FOIS
du firmware (consommee au prochain demarrage puis oubliee), pendant exact de
BootNext.
IMPORTANT (robustesse locale) : on NE parse PAS les libelles traduits de
« bcdedit /enum firmware » (sur un Windows francais « identifier » devient
« identificateur », « Windows Boot Manager » -> « Gestionnaire d'amorçage
Windows »). On resout :
- Windows : par l'alias NON localise {bootmgr} (aucun parsing) ;
- FWS : par le CHEMIN EFI (\EFI\FWS\ ou \EFI\GRUB\ ou grubx64.efi), qui
n'est pas traduit, en associant le GUID (motif stable) du bloc.
Si FWS est introuvable, on DESARME la one-shot (deletevalue) pour retomber
proprement sur BootOrder[0]=FWS plutot que de laisser une bascule erronee.
Doit tourner EN ADMIN. Usage : fws-return.ps1 -Target FWS | -Target Windows
#>
param(
[ValidateSet('FWS','Windows')]
[string]$Target = 'FWS'
)
$ErrorActionPreference = 'Stop'
function Set-BootSequence([string]$id) {
& bcdedit /set '{fwbootmgr}' bootsequence $id | Out-Null
if ($LASTEXITCODE -ne 0) { throw "bcdedit bootsequence $id a echoue (code $LASTEXITCODE)." }
}
function Clear-BootSequence {
& bcdedit /deletevalue '{fwbootmgr}' bootsequence 2>$null | Out-Null
}
if ($Target -eq 'Windows') {
# Alias non localise -> aucun parsing, aucune dependance a la langue.
Set-BootSequence '{bootmgr}'
Write-Host "Boot suivant arme vers Windows ({bootmgr})."
return
}
# --- Target = FWS : resoudre le GUID de l'entree firmware pointant vers FWS ---
# /v force l'affichage des GUID complets. On matche le CHEMIN (non localise),
# pas la description (localisee).
$fw = & bcdedit /enum firmware /v 2>$null
if (-not $fw) { throw "bcdedit /enum firmware n'a rien renvoye (droits admin ?)." }
$reId = '\{[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\}|\{[a-z]+\}'
$rePath = '(?i)\\EFI\\(FWS|GRUB)\\|grubx64\.efi'
$curId = $null; $fwsId = $null
foreach ($line in $fw) {
$m = [regex]::Match($line, $reId)
if ($m.Success) { $curId = $m.Value; continue }
if ($line -match $rePath -and $curId) { $fwsId = $curId; break }
}
if (-not $fwsId) {
# Rien trouve par le chemin : on ne devine PAS. On desarme toute one-shot
# pour retomber sur BootOrder[0]=FWS (filet permanent cote FWS).
Clear-BootSequence
throw "Entree firmware FWS introuvable (chemin \EFI\FWS ou \EFI\GRUB). One-shot desarmee -> BootOrder assure le retour vers FWS."
}
Set-BootSequence $fwsId
Write-Host "Boot suivant arme vers FWS = $fwsId"
@@ -0,0 +1,17 @@
{
"_comment": "Registre des jeux lançables par fws-play. Cle = identifiant (charte [a-z0-9_-]) passe a 'fws-gameboot to-windows --game <id>'. 'process' = nom du processus a attendre (sans .exe). Types: 'riot' (Riot Client), 'uri' (steam://rungameid/NNN, com.epicgames.launcher://...), 'exe' (chemin direct, args = tableau). Les cles commencant par '_' sont ignorees. Pour ajouter un jeu, copie une entree ci-dessous.",
"valorant": {
"type": "riot",
"product": "valorant",
"patchline": "live",
"process": "VALORANT-Win64-Shipping"
},
"lol": {
"type": "riot",
"product": "league_of_legends",
"patchline": "live",
"process": "League of Legends"
}
}
@@ -0,0 +1,17 @@
{
"_comment": "Sources d'installation OFFICIELLES, telechargees au 1er boot par Install-Games.ps1. FWS ne redistribue AUCUN binaire de jeu : tout est telecharge depuis Riot. ATTENTION : Riot CHANGE regulierement ces URLs (region/patchline) -> a VERIFIER (bouton Telecharger sur playvalorant.com / leagueoflegends.com, copier le lien). Region par defaut : EU (utilisateur fr_CH). Mets 'autoinstall': false pour ne pas installer un jeu au 1er boot.",
"valorant": {
"autoinstall": true,
"url": "https://valorant.secure.dyn.riotcdn.net/channels/public/x/installer/current/live.live.eu.exe",
"installer": "valorant-setup.exe",
"silentargs": ""
},
"lol": {
"autoinstall": true,
"url": "https://lol.secure.dyn.riotcdn.net/channels/public/x/installer/current/live.euw.exe",
"installer": "lol-setup.exe",
"silentargs": ""
}
}
@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
FWS — answer file OFFLINE pour Windows 11, deploye a \Windows\Panther\unattend.xml
par fws-windows-deploy APRES l'application de install.wim (wimlib). Setup NE
TOURNE JAMAIS : pas de passe windowsPE, pas de DiskConfiguration (la partition
cible est adressee cote Linux par PARTLABEL). Seuls specialize + oobeSystem
s'executent au 1er boot de l'image generalisee.
Tokens substitues par sed (fws-windows-deploy) :
__HOSTNAME__ __USER__ __DISPLAY__ __LOCALE__ __INPUT__ __FIRSTBOOT__
Compte gaming LOCAL admin SANS mot de passe (contrat auto-login) — JAMAIS nomme
'Administrator' (25H2 le confond avec le compte integre). AutoLogon temporaire
(LogonCount) survit au reboot Vanguard le temps que FirstBoot pose l'auto-login
permanent (Winlogon) via Install-FwsGameboot.
-->
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<settings pass="specialize">
<component name="Microsoft-Windows-Shell-Setup"
processorArchitecture="amd64"
language="neutral"
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
versionScope="nonSxS">
<ComputerName>__HOSTNAME__</ComputerName>
</component>
<component name="Microsoft-Windows-Deployment"
processorArchitecture="amd64"
language="neutral"
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
versionScope="nonSxS">
<RunSynchronous>
<!-- Ceintures (doublons du reg offline hivex de fws-windows-deploy). -->
<RunSynchronousCommand wcm:action="add">
<Order>1</Order>
<Path>reg add "HKLM\SYSTEM\CurrentControlSet\Control\BitLocker" /v PreventDeviceEncryption /t REG_DWORD /d 1 /f</Path>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>2</Order>
<Path>reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" /v BypassNRO /t REG_DWORD /d 1 /f</Path>
</RunSynchronousCommand>
</RunSynchronous>
</component>
</settings>
<settings pass="oobeSystem">
<component name="Microsoft-Windows-International-Core"
processorArchitecture="amd64"
language="neutral"
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
versionScope="nonSxS">
<InputLocale>__INPUT__</InputLocale>
<SystemLocale>__LOCALE__</SystemLocale>
<!-- Pas de <UILanguage> : une install.wim mono-langue (en-US) n'a pas le
pack UI __LOCALE__ → le forcer casse specialize. On garde en-US pour l'UI. -->
<UserLocale>__LOCALE__</UserLocale>
</component>
<component name="Microsoft-Windows-Shell-Setup"
processorArchitecture="amd64"
language="neutral"
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
versionScope="nonSxS">
<OOBE>
<HideEULAPage>true</HideEULAPage>
<HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
<HideOnlineAccountScreens>true</HideOnlineAccountScreens>
<HideLocalAccountScreen>true</HideLocalAccountScreen>
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
<ProtectYourPC>3</ProtectYourPC>
</OOBE>
<UserAccounts>
<LocalAccounts>
<LocalAccount wcm:action="add">
<Name>__USER__</Name>
<DisplayName>__DISPLAY__</DisplayName>
<Group>Administrators</Group>
<!-- PAS de <Password> : compte sans mot de passe (contrat auto-login FWS). -->
</LocalAccount>
</LocalAccounts>
</UserAccounts>
<AutoLogon>
<Enabled>true</Enabled>
<Username>__USER__</Username>
<LogonCount>5</LogonCount>
</AutoLogon>
<FirstLogonCommands>
<SynchronousCommand wcm:action="add">
<Order>1</Order>
<Description>FWS Gameboot first boot</Description>
<CommandLine>powershell.exe -NoProfile -ExecutionPolicy Bypass -File "__FIRSTBOOT__"</CommandLine>
</SynchronousCommand>
</FirstLogonCommands>
</component>
</settings>
</unattend>
@@ -0,0 +1,180 @@
# FWS — configuration Hyprland par défaut.
# Le clavier (__KB_LAYOUT__ / __KB_VARIANT__) est injecté à l'installation,
# depuis le choix fait dans Anaconda. Édite ce fichier librement.
#
# Raccourcis : transposition de la config i3/sway FWS (mêmes touches).
# Couleurs : nuances de bleu (palette Catppuccin Mocha).
#
# Les raccourcis sont déclarés avec « bindd » (bind + description) : la
# description alimente l'écran « Raccourcis clavier » (bouton ⌨ de la barre
# ou SUPER+K → fws-keys, qui lit les binds RÉELS via hyprctl). Ajoute
# tes propres bindd : ils apparaîtront automatiquement dans la liste.
# ⚠ Pas de virgule dans les descriptions (séparateur de champs Hyprland).
monitor = , preferred, auto, 1
input {
kb_layout = __KB_LAYOUT__
kb_variant = __KB_VARIANT__
follow_mouse = 1
numlock_by_default = true
touchpad {
natural_scroll = true
tap-to-click = true
}
}
general {
gaps_in = 4
gaps_out = 8
border_size = 2
# Nuances de bleu : bleu → sapphire en dégradé sur la fenêtre active.
col.active_border = rgb(89b4fa) rgb(74c7ec) 45deg
col.inactive_border = rgb(313244)
layout = dwindle
}
decoration {
rounding = 8
# Flou et ombres COUPÉS : rendu logiciel (llvmpipe) en VM — cf. le
# wrapper fws-hyprland écrit par l'installateur. Sur un vrai GPU tu
# peux les réactiver (enabled = true).
blur {
enabled = false
}
shadow {
enabled = false
}
}
animations {
enabled = true
bezier = fws, 0.22, 1.0, 0.36, 1.0
animation = windows, 1, 3, fws
animation = workspaces, 1, 3, fws
animation = fade, 1, 2, default
}
misc {
disable_hyprland_logo = true
disable_splash_rendering = true # pas de « Thanks xxx! » en bas d'écran
force_default_wallpaper = 0
background_color = 0x1a1a2e # bleu nuit FWS (aucun fond requis)
}
# Démarrage de session : barre d'état, notifications, réseau dans le tray,
# agent d'authentification GUI (indispensable pour les apps qui demandent
# les droits admin : gestionnaire de disques, blueman…).
exec-once = waybar
exec-once = mako
exec-once = nm-applet
# Applet Bluetooth : seulement si un adaptateur existe (en VM il n'y en a
# pas → blueman afficherait une erreur « Connection to BlueZ failed »).
exec-once = sh -c '[ -n "$(ls -A /sys/class/bluetooth 2>/dev/null)" ] && exec blueman-applet'
exec-once = /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1
# Fenêtre de bienvenue (1er démarrage seulement — se retire ensuite).
exec-once = fws-hello --autostart
$mod = SUPER
# --- Applications ---------------------------------------------------------
bindd = $mod, Return, Ouvrir un terminal (kitty), exec, kitty
bindd = $mod SHIFT, Return, Terminal flottant, exec, kitty --class floating-term
bindd = $mod, R, Menu des applications, exec, wofi --show drun
bindd = $mod, E, Gestionnaire de fichiers, exec, dolphin
bindd = $mod, B, Ouvrir Firefox, exec, firefox
bindd = $mod, K, Afficher les raccourcis clavier, exec, fws-keys
bindd = $mod, X, Menu d'alimentation (arrêt/redémarrage…), exec, fws-powermenu
# --- Fenêtres ---------------------------------------------------------------
bindd = $mod, Q, Fermer la fenêtre, killactive,
bindd = $mod, V, Fenêtre flottante ou ancrée, togglefloating,
bindd = $mod, F, Plein écran, fullscreen,
bindd = $mod, J, Basculer la découpe horizontale/verticale, layoutmsg, togglesplit
bindd = $mod, L, Verrouiller l'écran, exec, hyprlock
bindd = $mod SHIFT, E, Quitter la session (retour à SDDM), exit,
bindd = $mod SHIFT, C, Recharger la configuration Hyprland, exec, hyprctl reload
# --- Focus (flèches) ---------------------------------------------------------
bindd = $mod, left, Focus à gauche, movefocus, l
bindd = $mod, right, Focus à droite, movefocus, r
bindd = $mod, up, Focus en haut, movefocus, u
bindd = $mod, down, Focus en bas, movefocus, d
# --- Déplacer la fenêtre (Maj + flèches) ---------------------------------------
bindd = $mod SHIFT, left, Déplacer la fenêtre à gauche, movewindow, l
bindd = $mod SHIFT, right, Déplacer la fenêtre à droite, movewindow, r
bindd = $mod SHIFT, up, Déplacer la fenêtre en haut, movewindow, u
bindd = $mod SHIFT, down, Déplacer la fenêtre en bas, movewindow, d
# --- Redimensionner (Ctrl + flèches, maintien possible) ------------------------
binde = $mod CTRL, left, resizeactive, -30 0
binde = $mod CTRL, right, resizeactive, 30 0
binde = $mod CTRL, up, resizeactive, 0 -30
binde = $mod CTRL, down, resizeactive, 0 30
# --- Espaces de travail 1-10 (0 = espace 10, comme i3) --------------------------
bindd = $mod, 1, Aller à l'espace 1, workspace, 1
bindd = $mod, 2, Aller à l'espace 2, workspace, 2
bindd = $mod, 3, Aller à l'espace 3, workspace, 3
bindd = $mod, 4, Aller à l'espace 4, workspace, 4
bindd = $mod, 5, Aller à l'espace 5, workspace, 5
bindd = $mod, 6, Aller à l'espace 6, workspace, 6
bindd = $mod, 7, Aller à l'espace 7, workspace, 7
bindd = $mod, 8, Aller à l'espace 8, workspace, 8
bindd = $mod, 9, Aller à l'espace 9, workspace, 9
bindd = $mod, 0, Aller à l'espace 10, workspace, 10
bindd = $mod SHIFT, 1, Envoyer la fenêtre vers l'espace 1, movetoworkspace, 1
bindd = $mod SHIFT, 2, Envoyer la fenêtre vers l'espace 2, movetoworkspace, 2
bindd = $mod SHIFT, 3, Envoyer la fenêtre vers l'espace 3, movetoworkspace, 3
bindd = $mod SHIFT, 4, Envoyer la fenêtre vers l'espace 4, movetoworkspace, 4
bindd = $mod SHIFT, 5, Envoyer la fenêtre vers l'espace 5, movetoworkspace, 5
bindd = $mod SHIFT, 6, Envoyer la fenêtre vers l'espace 6, movetoworkspace, 6
bindd = $mod SHIFT, 7, Envoyer la fenêtre vers l'espace 7, movetoworkspace, 7
bindd = $mod SHIFT, 8, Envoyer la fenêtre vers l'espace 8, movetoworkspace, 8
bindd = $mod SHIFT, 9, Envoyer la fenêtre vers l'espace 9, movetoworkspace, 9
bindd = $mod SHIFT, 0, Envoyer la fenêtre vers l'espace 10, movetoworkspace, 10
# --- Multi-écrans (comme i3 : Super+Ctrl+1/2 = écran gauche/droit) -------------
bindd = $mod CTRL, 1, Envoyer la fenêtre vers l'écran de gauche, movewindow, mon:l
bindd = $mod CTRL, 2, Envoyer la fenêtre vers l'écran de droite, movewindow, mon:r
# --- Molette = changer d'espace de travail --------------------------------------
bindd = $mod, mouse_up, Espace de travail précédent, workspace, e-1
bindd = $mod, mouse_down, Espace de travail suivant, workspace, e+1
# --- Scratchpad (espace spécial) -------------------------------------------------
bindd = $mod, S, Afficher le scratchpad, togglespecialworkspace, magic
bindd = $mod SHIFT, S, Envoyer la fenêtre au scratchpad, movetoworkspace, special:magic
# --- Captures d'écran (grim/slurp → presse-papier ou ~/Images) -------------------
bindd = , Print, Capture plein écran vers le presse-papier, exec, grim - | wl-copy
bindd = $mod, Print, Capture d'une zone vers le presse-papier, exec, grim -g "$(slurp)" - | wl-copy
bindd = $mod SHIFT, Print, Capture d'une zone vers ~/Images, exec, sh -c 'mkdir -p ~/Images && grim -g "$(slurp)" ~/Images/capture_$(date +%Y-%m-%d_%H-%M-%S).png'
# --- Multimédia (touches XF86 : volume via PipeWire, lecture via playerctl) ------
bindel = , XF86AudioRaiseVolume, exec, wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+
bindel = , XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
bindl = , XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
bindl = , XF86AudioPlay, exec, playerctl play-pause
bindl = , XF86AudioNext, exec, playerctl next
bindl = , XF86AudioPrev, exec, playerctl previous
# --- Luminosité de l'écran (portables : touches Fn) ------------------------------
bindel = , XF86MonBrightnessUp, exec, brightnessctl set +5%
bindel = , XF86MonBrightnessDown, exec, brightnessctl set 5%-
# --- Souris : déplacer / redimensionner les fenêtres -----------------------------
bindm = $mod, mouse:272, movewindow
bindm = $mod, mouse:273, resizewindow
# --- Règles de fenêtres (comme i3) ------------------------------------------------
# Syntaxe Hyprland ≥ 0.55 : champs « <règle> <valeur> » + cibles « match:… »
# (validé contre le binaire avec --verify-config).
windowrule = float on, match:class ^(floating-term)$
windowrule = size 900 550, match:class ^(floating-term)$
windowrule = float on, match:class ^([Pp]avucontrol|org.pulseaudio.pavucontrol)$
windowrule = float on, match:class ^(.?[Bb]lueman-manager.*)$
windowrule = float on, match:class ^([Ff]ws-hello)$
windowrule = float on, match:class ^([Ff]ws-about)$
windowrule = float on, match:title ^(Picture-in-Picture)$
@@ -0,0 +1,160 @@
# FWS — configuration i3 par défaut (session X11, COMPATIBLE VR).
# SteamVR fonctionne en X11 : c'est le bureau à choisir pour un casque VR
# (HTC Vive, Valve Index…). Lance « fws-vr-setup » (Super+Shift+V, ou menu)
# pour installer les drivers/runtimes selon ton casque.
# Le clavier X11 est configuré système via /etc/X11/xorg.conf.d/00-keyboard.conf
# (écrit par l'installateur selon ton choix Anaconda) : rien à régler ici.
set $mod Mod4
set $term kitty
set $filemanager dolphin
set $menu rofi -show drun -show-icons
set $browser firefox
# Couleurs — nuances de bleu (Catppuccin Mocha, comme SDDM/waybar FWS)
set $base #1e1e2e
set $surface0 #313244
set $text #cdd6f4
set $blue #89b4fa
set $sapphire #74c7ec
set $red #f38ba8
# classe bordure fond texte indicateur child_border
client.focused $blue $blue $base $sapphire $blue
client.focused_inactive $surface0 $surface0 $text $surface0 $surface0
client.unfocused $surface0 $surface0 $text $surface0 $surface0
client.urgent $red $red $base $red $red
client.background $base
font pango:Noto Sans 10
default_border pixel 2
gaps inner 4
gaps outer 4
##################
## APPLICATIONS ##
##################
bindsym $mod+Return exec $term
bindsym $mod+Shift+Return exec kitty --class floating-term
bindsym $mod+r exec $menu
bindsym $mod+e exec $filemanager
bindsym $mod+b exec $browser
# VR : installation des drivers/runtimes casque (HTC Vive, Index, Quest…)
bindsym $mod+Shift+v exec kitty -e fws-vr-setup
# Panneau des raccourcis clavier (aussi via le bouton ⌌ de la barre)
bindsym $mod+k exec fws-keys
# Menu d'alimentation (verrouiller/déconnexion/redémarrer/éteindre)
bindsym $mod+x exec fws-powermenu
##############
## FENETRES ##
##############
bindsym $mod+q kill
bindsym $mod+v floating toggle
bindsym $mod+f fullscreen toggle
bindsym $mod+j split toggle
bindsym $mod+l exec i3lock -c 1e1e2e
# Quitter i3 → retour à SDDM
bindsym $mod+Shift+e exec "i3-nagbar -t warning -m 'Quitter i3 et revenir à SDDM ?' -B 'Oui, quitter' 'i3-msg exit'"
bindsym $mod+Shift+c reload
# Focus (flèches)
bindsym $mod+Left focus left
bindsym $mod+Right focus right
bindsym $mod+Up focus up
bindsym $mod+Down focus down
# Déplacer la fenêtre
bindsym $mod+Shift+Left move left
bindsym $mod+Shift+Right move right
bindsym $mod+Shift+Up move up
bindsym $mod+Shift+Down move down
# Redimensionner (Ctrl + flèches)
bindsym $mod+Ctrl+Left resize shrink width 30 px
bindsym $mod+Ctrl+Right resize grow width 30 px
bindsym $mod+Ctrl+Up resize shrink height 30 px
bindsym $mod+Ctrl+Down resize grow height 30 px
################
## WORKSPACES ##
################
bindsym $mod+1 workspace number 1
bindsym $mod+2 workspace number 2
bindsym $mod+3 workspace number 3
bindsym $mod+4 workspace number 4
bindsym $mod+5 workspace number 5
bindsym $mod+6 workspace number 6
bindsym $mod+7 workspace number 7
bindsym $mod+8 workspace number 8
bindsym $mod+9 workspace number 9
bindsym $mod+0 workspace number 10
bindsym $mod+Shift+1 move container to workspace number 1
bindsym $mod+Shift+2 move container to workspace number 2
bindsym $mod+Shift+3 move container to workspace number 3
bindsym $mod+Shift+4 move container to workspace number 4
bindsym $mod+Shift+5 move container to workspace number 5
bindsym $mod+Shift+6 move container to workspace number 6
bindsym $mod+Shift+7 move container to workspace number 7
bindsym $mod+Shift+8 move container to workspace number 8
bindsym $mod+Shift+9 move container to workspace number 9
bindsym $mod+Shift+0 move container to workspace number 10
# Multi-écrans
bindsym $mod+Ctrl+1 move container to output left
bindsym $mod+Ctrl+2 move container to output right
# Molette = changer de workspace
bindsym $mod+button4 workspace prev
bindsym $mod+button5 workspace next
# Scratchpad
bindsym $mod+s scratchpad show
bindsym $mod+Shift+s move scratchpad
##############
## CAPTURES ##
##############
bindsym Print exec --no-startup-id "maim | xclip -selection clipboard -t image/png"
bindsym $mod+Print exec --no-startup-id "maim -s | xclip -selection clipboard -t image/png"
bindsym $mod+Shift+Print exec --no-startup-id "mkdir -p ~/Images && maim -s ~/Images/capture_$(date +%Y-%m-%d_%H-%M-%S).png"
################
## MULTIMEDIA ##
################
bindsym XF86AudioRaiseVolume exec --no-startup-id wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+
bindsym XF86AudioLowerVolume exec --no-startup-id wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
bindsym XF86AudioMute exec --no-startup-id wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
bindsym XF86AudioPlay exec --no-startup-id playerctl play-pause
bindsym XF86AudioNext exec --no-startup-id playerctl next
bindsym XF86AudioPrev exec --no-startup-id playerctl previous
bindsym XF86MonBrightnessUp exec --no-startup-id brightnessctl set +5%
bindsym XF86MonBrightnessDown exec --no-startup-id brightnessctl set 5%-
#####################
## REGLES FENETRES ##
#####################
for_window [class="floating-term"] floating enable, resize set 900 550
for_window [class="(?i)fws-hello"] floating enable
for_window [class="(?i)fws-about"] floating enable
for_window [class="(?i)pavucontrol"] floating enable, resize set 700 500
for_window [class="(?i)blueman-manager"] floating enable
for_window [title="Picture-in-Picture"] floating enable
###########
## BARRE ##
###########
# Polybar (config ~/.config/polybar/config.ini) : clone visuel de la waybar
# Hyprland FWS — pilules, icônes, horloge+date au centre, bouton Raccourcis.
# exec_always + pkill : la barre survit proprement aux reload ($mod+Shift+c).
exec_always --no-startup-id sh -c 'pkill -x polybar; sleep 0.3; exec polybar -q fws'
###############
## AUTOSTART ##
###############
# picom : coins arrondis (parité visuelle avec Hyprland) — config FWS légère.
# PAS en machine virtuelle : le compositing sur GPU virtuel (vmwgfx/llvmpipe)
# rate le repaint des fenêtres GTK → contenus noirs/artefacts. Les coins
# arrondis sont sacrifiés en VM ; sur matériel réel picom se lance normalement.
exec --no-startup-id sh -c 'systemd-detect-virt --quiet || exec picom --config ~/.config/picom.conf'
exec --no-startup-id dunst
exec --no-startup-id nm-applet
exec --no-startup-id /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1
exec --no-startup-id sh -c '[ -n "$(ls -A /sys/class/bluetooth 2>/dev/null)" ] && exec blueman-applet'
# Fenêtre de bienvenue (1er démarrage seulement — se retire ensuite).
exec --no-startup-id fws-hello --autostart
@@ -0,0 +1,9 @@
$1████████ $2██$5████$2██
$1████████ $2█$5██$2██$5██$2█
$1████████ $2█$5██████$2█
$1████████ $2██$6█$2██$6█$2██
$3████████ $4████████
$3████████ $4████████
$3████████ $4████████
$3████████ $4████████
@@ -3,7 +3,7 @@ PRETTY_NAME="FWS Linux"
ID=fws ID=fws
ID_LIKE=arch ID_LIKE=arch
BUILD_ID=rolling BUILD_ID=rolling
VERSION_ID="1.0.0" VERSION_ID="0.6.0"
ANSI_COLOR="38;2;23;147;209" ANSI_COLOR="38;2;23;147;209"
HOME_URL="https://github.com/You-re-like-Windows-a-bitch/fws" HOME_URL="https://github.com/You-re-like-Windows-a-bitch/fws"
SUPPORT_URL="https://github.com/You-re-like-Windows-a-bitch/fws/issues" SUPPORT_URL="https://github.com/You-re-like-Windows-a-bitch/fws/issues"
@@ -0,0 +1,19 @@
# FWS — picom minimal (session i3) : coins arrondis comme sous Hyprland.
# xrender : fiable partout, y compris en VM (rendu logiciel) ; pas de flou ni
# d'ombres (parité avec la config Hyprland FWS, et léger pour la VR).
backend = "xrender";
vsync = true;
corner-radius = 8;
rounded-corners-exclude = [
"window_type = 'dock'", # polybar gère son propre arrondi
"class_g = 'i3-frame'"
];
shadow = false;
fading = true;
fade-in-step = 0.08;
fade-out-step = 0.08;
# VR / jeux plein écran : ne pas composer par-dessus (latence).
unredir-if-possible = true;
@@ -0,0 +1,174 @@
; FWS — polybar (session i3) : clone visuel de la waybar Hyprland.
; Barre flottante arrondie, modules en « pilules » Catppuccin Mocha,
; icônes Nerd Font (ttf-nerd-fonts-symbols). Horloge + date au centre,
; bouton « ⌌ Raccourcis » (fws-keys), volume/réseau/CPU/RAM/tray à droite.
[colors]
base = #E01e1e2e
surface0 = #313244
text = #cdd6f4
subtext = #a6adc8
blue = #89b4fa
sapphire = #74c7ec
teal = #94e2d5
mauve = #cba6f7
maroon = #eba0ac
yellow = #f9e2af
red = #f38ba8
darkbase = #1e1e2e
[bar/fws]
monitor = ${env:MONITOR:}
width = 100%:-20
offset-x = 10
offset-y = 6
height = 36
radius = 14
background = ${colors.base}
foreground = ${colors.text}
border-size = 1
border-color = #5989b4fa
padding-left = 1
padding-right = 1
module-margin = 1
line-size = 0
font-0 = Noto Sans:size=10;2
font-1 = Symbols Nerd Font:size=12;3
font-2 = Noto Sans:weight=bold:size=10;2
modules-left = i3 xwindow
modules-center = date
modules-right = updates keyhelp backlight pulseaudio network cpu memory tray power
enable-ipc = true
wm-restack = i3
[module/i3]
type = internal/i3
pin-workspaces = true
show-urgent = true
strip-wsnumbers = false
format = <label-state> <label-mode>
label-focused = %name%
label-focused-background = ${colors.blue}
label-focused-foreground = ${colors.darkbase}
label-focused-padding = 2
label-unfocused = %name%
label-unfocused-background = ${colors.surface0}
label-unfocused-foreground = ${colors.subtext}
label-unfocused-padding = 2
label-visible = %name%
label-visible-background = ${colors.surface0}
label-visible-padding = 2
label-urgent = %name%
label-urgent-background = ${colors.red}
label-urgent-foreground = ${colors.darkbase}
label-urgent-padding = 2
[module/xwindow]
type = internal/xwindow
label = %title:0:45:…%
label-foreground = ${colors.subtext}
format-padding = 1
[module/date]
type = internal/date
interval = 1
date = %A %d %B %Y
time = %H:%M
label = 󰥔 %time% %date%
label-font = 3
format-background = ${colors.surface0}
format-padding = 3
[module/updates]
type = custom/script
exec = fws-updates-check
interval = 1800
click-left = kitty -e fws-update
format-background = ${colors.surface0}
format-foreground = ${colors.yellow}
format-padding = 2
[module/keyhelp]
type = custom/script
exec = echo "󰌌 Raccourcis"
interval = 3600
click-left = fws-keys
format-background = ${colors.blue}
format-foreground = ${colors.darkbase}
format-padding = 2
label-font = 3
[module/backlight]
type = internal/backlight
enable-scroll = true
format = <label>
label = 󰃟 %percentage%%
format-background = ${colors.surface0}
format-foreground = ${colors.yellow}
format-padding = 2
[module/pulseaudio]
type = internal/pulseaudio
use-ui-max = false
click-right = pavucontrol
format-volume = <label-volume>
label-volume = 󰕾 %percentage%%
format-volume-background = ${colors.surface0}
format-volume-foreground = ${colors.blue}
format-volume-padding = 2
label-muted = 󰝟 muet
format-muted-background = ${colors.surface0}
format-muted-foreground = ${colors.subtext}
format-muted-padding = 2
[module/network]
type = internal/network
interface-type = wired
interval = 5
format-connected = <label-connected>
label-connected = 󰈀 %local_ip%
format-connected-background = ${colors.surface0}
format-connected-foreground = ${colors.teal}
format-connected-padding = 2
format-disconnected = <label-disconnected>
label-disconnected = 󰤭 hors ligne
format-disconnected-background = ${colors.surface0}
format-disconnected-foreground = ${colors.red}
format-disconnected-padding = 2
[module/cpu]
type = internal/cpu
interval = 3
label = %percentage%%
format-background = ${colors.surface0}
format-foreground = ${colors.mauve}
format-padding = 2
[module/memory]
type = internal/memory
interval = 3
label = %percentage_used%%
format-background = ${colors.surface0}
format-foreground = ${colors.maroon}
format-padding = 2
[module/power]
type = custom/text
content = 󰐥
content-foreground = ${colors.red}
content-background = ${colors.surface0}
content-padding = 2
content-font = 2
click-left = fws-powermenu
[module/tray]
type = internal/tray
format = <tray>
format-background = ${colors.surface0}
format-padding = 1
tray-spacing = 8px
tray-size = 16px
[settings]
screenchange-reload = true
pseudo-transparency = false
@@ -0,0 +1,75 @@
/* FWS — rofi (menu d'applications i3, Super+R) : même look que wofi sous
Hyprland — fond sombre, bordure et sélection bleues, icônes. */
configuration {
show-icons: true;
display-drun: "Applications";
}
* {
bg: #1e1e2eF5;
surface: #313244;
text: #cdd6f4;
subtext: #a6adc8;
blue: #89b4fa;
darkbase: #1e1e2e;
font: "Noto Sans 12";
background-color: transparent;
text-color: @text;
}
window {
background-color: @bg;
border: 2px;
border-color: @blue;
border-radius: 14px;
width: 560px;
}
mainbox {
padding: 8px;
}
inputbar {
background-color: @surface;
border-radius: 10px;
margin: 4px;
padding: 8px 12px;
children: [ prompt, entry ];
}
prompt {
text-color: @blue;
margin: 0 8px 0 0;
}
entry {
placeholder: "Rechercher…";
placeholder-color: @subtext;
}
listview {
margin: 4px;
lines: 8;
scrollbar: false;
}
element {
padding: 8px 10px;
border-radius: 10px;
spacing: 8px;
}
element selected.normal {
background-color: @blue;
text-color: @darkbase;
}
element-icon {
size: 24px;
}
element-text {
text-color: inherit;
vertical-align: 0.5;
}
@@ -0,0 +1,103 @@
{
"layer": "top",
"position": "top",
"height": 36,
"spacing": 2,
"margin-top": 6,
"margin-left": 10,
"margin-right": 10,
"modules-left": ["hyprland/workspaces", "hyprland/window"],
"modules-center": ["clock"],
"modules-right": ["custom/updates", "custom/keyhelp", "backlight", "pulseaudio", "bluetooth", "network", "cpu", "memory", "battery", "tray", "custom/power"],
"custom/power": {
"format": "󰐥",
"tooltip-format": "Alimentation (SUPER+X)",
"on-click": "fws-powermenu"
},
"custom/updates": {
"exec": "fws-updates-check",
"interval": 1800,
"tooltip-format": "Mises à jour disponibles — clic pour installer",
"on-click": "kitty -e fws-update"
},
"hyprland/workspaces": {
"format": "{id}",
"on-click": "activate",
"on-scroll-up": "hyprctl dispatch workspace e-1",
"on-scroll-down": "hyprctl dispatch workspace e+1"
},
"hyprland/window": {
"max-length": 45,
"separate-outputs": true
},
"clock": {
"format": "󰥔 {:%H:%M %A %d %B %Y}",
"format-alt": "󰥔 {:%H:%M}",
"tooltip-format": "<tt>{calendar}</tt>",
"calendar": {
"format": {
"today": "<b><span color='#89b4fa'>{}</span></b>"
}
}
},
"custom/keyhelp": {
"format": "󰌌 Raccourcis",
"tooltip-format": "Raccourcis clavier (SUPER+K)",
"on-click": "fws-keys"
},
"backlight": {
"format": "{icon} {percent}%",
"format-icons": ["󰃞", "󰃟", "󰃠"],
"on-scroll-up": "brightnessctl set +5%",
"on-scroll-down": "brightnessctl set 5%-",
"tooltip-format": "Luminosité {percent}% (molette pour régler)"
},
"pulseaudio": {
"format": "{icon} {volume}%",
"format-muted": "󰝟 muet",
"format-icons": { "default": ["󰕿", "󰖀", "󰕾"] },
"on-click": "pavucontrol",
"on-click-right": "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle",
"on-scroll-up": "wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 2%+",
"on-scroll-down": "wpctl set-volume @DEFAULT_AUDIO_SINK@ 2%-",
"tooltip-format": "{desc} — clic : mixeur, clic droit : muet"
},
"bluetooth": {
"format": "󰂯",
"format-disabled": "󰂲",
"format-connected": "󰂱 {num_connections}",
"format-no-controller": "",
"on-click": "blueman-manager",
"tooltip-format": "{controller_alias}\t{controller_address}",
"tooltip-format-connected": "{device_enumerate}"
},
"network": {
"format-wifi": "󰤨 {essid}",
"format-ethernet": "󰈀 {ipaddr}",
"format-disconnected": "󰤭 hors ligne",
"tooltip-format": "{ifname} — {ipaddr}\n⇡ {bandwidthUpBytes} ⇣ {bandwidthDownBytes}"
},
"cpu": {
"format": " {usage}%",
"interval": 3
},
"memory": {
"format": " {percentage}%",
"interval": 3,
"tooltip-format": "{used:0.1f} / {total:0.1f} Gio"
},
"battery": {
"states": { "warning": 25, "critical": 10 },
"format": "{icon} {capacity}%",
"format-charging": "󰂄 {capacity}%",
"format-icons": ["󰁺", "󰁼", "󰁾", "󰂀", "󰁹"],
"tooltip-format": "{timeTo}"
},
"tray": {
"spacing": 8,
"icon-size": 16
}
}
@@ -0,0 +1,112 @@
/* FWS — style Waybar v2 (Catppuccin Mocha, nuances de bleu).
Barre flottante arrondie, modules en « pilules », icônes Nerd Font
(paquet ttf-nerd-fonts-symbols, installé avec le bureau Hyprland). */
* {
font-family: "Noto Sans", "Symbols Nerd Font", sans-serif;
font-size: 13px;
min-height: 0;
}
window#waybar {
background: rgba(30, 30, 46, 0.88); /* base #1e1e2e */
color: #cdd6f4; /* text */
border: 1px solid rgba(137, 180, 250, 0.35);
border-radius: 14px;
}
tooltip {
background: #1e1e2e;
border: 1px solid #89b4fa;
border-radius: 10px;
}
tooltip label { color: #cdd6f4; }
/* --- Espaces de travail --------------------------------------------------- */
#workspaces {
background: #313244; /* surface0 */
margin: 5px 4px;
border-radius: 10px;
padding: 0 4px;
}
#workspaces button {
color: #a6adc8; /* subtext0 */
padding: 0 8px;
margin: 3px 2px;
border: none;
border-radius: 8px;
background: transparent;
transition: all 0.2s ease;
}
#workspaces button.active {
color: #1e1e2e;
background: #89b4fa; /* bleu */
font-weight: bold;
}
#workspaces button:hover {
color: #cdd6f4;
background: #45475a;
box-shadow: none;
}
#window {
color: #a6adc8;
margin-left: 10px;
font-style: italic;
}
/* --- Horloge (centre) ------------------------------------------------------ */
#clock {
color: #cdd6f4;
font-weight: bold;
background: #313244;
padding: 0 16px;
margin: 5px 0;
border-radius: 10px;
}
/* --- Modules de droite : pilules ------------------------------------------- */
#custom-keyhelp,
#backlight,
#pulseaudio,
#bluetooth,
#network,
#cpu,
#memory,
#battery,
#tray {
background: #313244;
padding: 0 12px;
margin: 5px 3px;
border-radius: 10px;
color: #cdd6f4;
}
#custom-keyhelp {
background: #89b4fa; /* bouton d'appel : bleu plein */
color: #1e1e2e;
font-weight: bold;
}
#custom-keyhelp:hover { background: #b4d0fb; }
#custom-power {
color: #f38ba8; /* rouge doux (Catppuccin) */
}
#custom-power:hover { color: #ff6b81; }
#backlight { color: #f9e2af; } /* jaune doux */
#pulseaudio { color: #89b4fa; }
#pulseaudio.muted { color: #6c7086; }
#bluetooth { color: #74c7ec; } /* sapphire */
#bluetooth.disabled { color: #6c7086; }
#network { color: #94e2d5; } /* teal */
#network.disconnected { color: #f38ba8; }
#cpu { color: #cba6f7; } /* mauve */
#memory { color: #eba0ac; } /* maroon */
#battery.warning { color: #f9e2af; }
#battery.critical { color: #f38ba8; }
#battery.charging { color: #a6e3a1; }
@@ -0,0 +1,7 @@
# FWS — configuration wofi (menu d'applications).
insensitive=true
allow_images=true
image_size=24
prompt=Applications
width=560
height=440
@@ -0,0 +1,40 @@
/* FWS — style du panneau « Raccourcis clavier » (fws-keys).
Même palette que le menu wofi, mais police MONOSPACE : les colonnes
combinaison/description sont alignées par des espaces. */
window {
background-color: rgba(30, 30, 46, 0.97);
border: 2px solid #89b4fa;
border-radius: 14px;
font-family: "Noto Sans Mono", monospace;
font-size: 13px;
}
#input {
background-color: #313244;
color: #cdd6f4;
border: none;
border-radius: 10px;
margin: 12px;
padding: 8px 12px;
font-family: "Noto Sans", sans-serif;
}
#input image { color: #89b4fa; }
#inner-box {
margin: 0 8px 8px 8px;
}
#entry {
padding: 5px 10px;
border-radius: 8px;
color: #cdd6f4;
}
#entry:selected {
background-color: #313244;
color: #89b4fa;
}
#text { color: inherit; }
@@ -0,0 +1,42 @@
/* FWS — style wofi (menu d'applications Super+R). Catppuccin Mocha, bleu. */
window {
background-color: rgba(30, 30, 46, 0.96); /* base #1e1e2e */
border: 2px solid #89b4fa;
border-radius: 14px;
font-family: "Noto Sans", sans-serif;
font-size: 14px;
}
#input {
background-color: #313244; /* surface0 */
color: #cdd6f4;
border: none;
border-radius: 10px;
margin: 12px;
padding: 8px 12px;
}
#input image { color: #89b4fa; }
#inner-box {
margin: 0 8px 8px 8px;
}
#entry {
padding: 8px 10px;
border-radius: 10px;
color: #cdd6f4;
}
#entry:selected {
background-color: #89b4fa;
color: #1e1e2e;
}
#entry:selected #text { color: #1e1e2e; }
#text {
margin-left: 8px;
color: inherit;
}
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- FWS — icône « Récupération FWS » : croix de secours, bleus FWS. -->
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
<rect x="3" y="3" width="58" height="58" rx="13" fill="#1e1e2e"/>
<rect x="3" y="3" width="58" height="58" rx="13" fill="none" stroke="#89b4fa" stroke-width="3"/>
<path d="M25 13h14v12h12v14H39v12H25V39H13V25h12z" fill="#89b4fa"/>
<path d="M28 16h8v12h12v8H36v12h-8V36H16v-8h12z" fill="#74c7ec"/>
</svg>

After

Width:  |  Height:  |  Size: 515 B

@@ -5,33 +5,36 @@
# lancée en mode interactif (chemin fixe codé en dur dans Anaconda : # lancée en mode interactif (chemin fixe codé en dur dans Anaconda :
# /usr/share/anaconda/interactive-defaults.ks). Aucun inst.ks= n'est requis. # /usr/share/anaconda/interactive-defaults.ks). Aucun inst.ks= n'est requis.
# #
# RÔLE : empêcher Anaconda d'installer Fedora. # RÔLE : finaliser une install « copie du live » (surtout pas Fedora).
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
# Sans payload défini, Anaconda utilise DNF → des RPM depuis les dépôts # Anaconda n'a AUCUN backend pacman ; son payload par défaut (DNF) poserait
# Fedora = il installe Fedora. Anaconda n'a AUCUN backend pacman. La seule # des RPM Fedora. Ici le payload est LiveOS, imposé par « anaconda --liveinst »
# façon de poser FWS/Arch est le payload « liveimg » : il recopie une image # (cf. bloc « Source d'installation » ci-dessous) : il recopie le squashfs du
# de système de fichiers (ici le squashfs du live = FWS) sur le disque, puis # live (= FWS) sur le disque, puis on finalise nous-mêmes (grub, mkinitcpio,
# on finalise nous-mêmes (grub, mkinitcpio, bureau) en %post. C'est le modèle # bureau) en %post. C'est le modèle « Sabayon » : le système installé EST une
# « Sabayon » : le système installé EST une copie du live. # copie du live.
# #
# L'utilisateur garde la main dans le GUI sur tout ce qui N'EST PAS fixé ici : # L'utilisateur garde la main dans le GUI sur tout ce qui N'EST PAS fixé ici :
# partitionnement, fuseau, clavier, langue, création d'utilisateur, mot de # partitionnement, fuseau, clavier, langue, création d'utilisateur, mot de
# passe root, etc. # passe root, etc.
# ============================================================================ # ============================================================================
# --- Source d'installation : l'image du live (FWS), surtout pas Fedora ------- # --- Source d'installation : le payload LiveOS (surtout pas Fedora/DNF) ------
# Squashfs archiso sur le média booté : # AUCUNE directive de payload ici, et c'est VOULU. liveinst lance
# /run/archiso/bootmnt/<install_dir>/<arch>/airootfs.sfs # « anaconda --liveinst » et anaconda.py teste opts.liveinst AVANT le type du
# install_dir=fws, arch=x86_64, airootfs_image_type=squashfs (cf. profiledef.sh). # payload DBus → il crée TOUJOURS un payload LiveOS et IGNORE tout payload du
# Anaconda loop-monte ce .sfs et rsync son contenu vers la cible. # kickstart (un « liveimg » ici serait du code mort — vérifié dans
# pyanaconda/anaconda.py, propriété payload, et reproduit hors VM).
# #
# ⚠ À VALIDER sur ISO réelle : certaines versions d'Anaconda attendent une # Le payload LiveOS détecte l'image live via /dev/mapper/live-base ou
# structure « LiveOS/rootfs.img » à l'intérieur du squashfs (format Fedora # /run/rootfsbase (chemins Fedora/dracut, absents d'archiso → c'était le crash
# Live) et non un squashfs « plat » comme celui d'archiso. Si l'install # « SourceSetupError: No Live OS image found! » au démarrage). Le .xinitrc du
# échoue à monter la source, basculer sur un tarball de rootfs dédié : # live bind-monte donc /run/archiso/airootfs (squashfs FWS, ro) sur
# liveimg --url=file:///run/archiso/bootmnt/fws/x86_64/fws-rootfs.tar.gz # /run/rootfsbase AVANT de lancer liveinst : Anaconda monte le loop device et
# (un build-rootfs.sh produirait alors ce tar via pacstrap). # rsync son contenu vers la cible = le système installé EST une copie du live,
liveimg --url=file:///run/archiso/bootmnt/fws/x86_64/airootfs.sfs # puis on finalise en %post (grub, mkinitcpio, bureau, matériel).
# DNF/libdnf5 n'est jamais importé : le module DNF n'est chargé QUE si le
# payload actif est DNF (import lazy dans modules/payloads/payload/factory.py).
# --- Disque : repartir d'un disque PROPRE, même s'il a déjà servi ------------ # --- Disque : repartir d'un disque PROPRE, même s'il a déjà servi ------------
# Sans ça, une réinstallation sur un disque déjà partitionné (typiquement une # Sans ça, une réinstallation sur un disque déjà partitionné (typiquement une
@@ -42,24 +45,25 @@ liveimg --url=file:///run/archiso/bootmnt/fws/x86_64/airootfs.sfs
# zerombr : réinitialise toute table de partitions invalide # zerombr : réinitialise toute table de partitions invalide
# (répond « oui » au lieu de bloquer sur un disque # (répond « oui » au lieu de bloquer sur un disque
# non reconnu). # non reconnu).
# clearpart --all : supprime TOUTES les partitions de TOUS les disques. # clearpart --all : supprime TOUTES les partitions des disques
# SÉLECTIONNÉS dans le GUI (l'utilisateur choisit le
# disque cible — les autres, ex. disques de jeux, ne
# sont pas touchés s'il ne les sélectionne pas).
# --initlabel : réécrit une étiquette de disque neuve. # --initlabel : réécrit une étiquette de disque neuve.
# --disklabel=msdos : force une table MBR (et non GPT). CRUCIAL en BIOS : # --disklabel : type de table CHOISI SELON LE FIRMWARE par le %pre
# en BIOS+GPT, GRUB i386-pc exige une partition « BIOS # plus bas (ne surtout PAS coder en dur) :
# Boot » (~1 Mo, type EF02) pour embarquer core.img, # • UEFI → gpt : l'ESP peut être créée → grub-install
# qu'Anaconda NE crée PAS avec « bootloader --disabled # EFI fonctionne. Forcer « msdos » en UEFI empêchait
# » → « this GPT partition label contains no BIOS Boot # l'ESP (→ install NON bootable) et réécrivait la
# Partition; embedding won't be possible » → # table pour rien : c'est LE bug corrigé ici.
# grub-install échoue. En MBR, core.img se loge dans # • BIOS → msdos : core.img se loge dans l'espace
# l'espace post-MBR, aucune partition dédiée requise. # post-MBR (pas de partition BIOS Boot EF02 requise,
# ⚠ Ce choix vise le BIOS (cas VMware actuel). Pour l'UEFI il faudra du GPT # qu'Anaconda ne crée pas avec « bootloader --disabled »).
# (+ ESP) — à rendre dépendant du firmware (%pre) le jour où on cible l'UEFI.
# L'utilisateur garde la main dans le GUI (il peut repasser en manuel), mais le # L'utilisateur garde la main dans le GUI (il peut repasser en manuel), mais le
# DÉFAUT est désormais un disque net → réinstall fiable à chaque fois. # DÉFAUT est un disque net → réinstall fiable à chaque fois.
# ⚠ VM mono-disque : « --all » efface le seul disque, c'est voulu. Sur une
# machine multi-disques il faudrait cibler avec « --drives=sda ».
zerombr zerombr
clearpart --all --initlabel --disklabel=msdos # Table de partitions injectée par le %pre (GPT en UEFI, MBR en BIOS).
%include /tmp/fws-clearpart.ks
# --- Bootloader : géré à la main en %post ------------------------------------ # --- Bootloader : géré à la main en %post ------------------------------------
# Anaconda lancerait « grub2-install » / « grub2-mkconfig » (binaires Fedora, # Anaconda lancerait « grub2-install » / « grub2-mkconfig » (binaires Fedora,
@@ -69,9 +73,38 @@ clearpart --all --initlabel --disklabel=msdos
# partitionnement (auto ou manuel) car la plateforme l'impose. # partitionnement (auto ou manuel) car la plateforme l'impose.
bootloader --disabled bootloader --disabled
# Pas de section %packages : le payload liveimg n'installe aucun paquet # Pas de section %packages : le payload LiveOS n'installe aucun paquet
# (il copie une image). Toute installation de paquets se fait en %post. # (il copie une image). Toute installation de paquets se fait en %post.
# --- Récapitulatif avant installation : PAGE GUI dédiée (pas de %pre-install) -
# L'écran « RÉCAPITULATIF AVANT INSTALLATION » est une standalone spoke
# Anaconda (overlay airootfs : pyanaconda/ui/gui/spokes/fws_recap.py,
# postForHub=SummaryHub, priority -10) intercalée ENTRE le hub et l'écran de
# progression : l'utilisateur clique « Commencer l'installation », voit le
# récap (bureau choisi, hostname, disques, GPU, réseau — AUCUNE écriture
# disque à ce stade), et l'installation ne démarre qu'à son clic « Lancer
# l'installation ». L'ancien récap zenity en %pre-install a été abandonné :
# le module scripts tourne avec LC_ALL=C et un env X incertain (zenity 4
# refuse les libellés UTF-8 en locale C → rc=255 pris pour « Annuler » →
# poweroff en plein install au 1er test réel).
# ============================================================================
# %pre — TABLE DE PARTITIONS SELON LE FIRMWARE
# Génère /tmp/fws-clearpart.ks (inclus dans la section commandes ci-dessus) :
# GPT en UEFI (→ ESP créable → grub-install EFI fonctionne), MBR/msdos en BIOS.
# Les scripts %pre s'exécutent AVANT l'application des commandes du kickstart,
# donc le %include voit toujours le fichier généré ici.
# ============================================================================
%pre --log=/tmp/fws-pre.log
if [ -d /sys/firmware/efi ]; then
disklabel=gpt
else
disklabel=msdos
fi
echo "clearpart --all --initlabel --disklabel=$disklabel" > /tmp/fws-clearpart.ks
%end
# ============================================================================ # ============================================================================
# %post --nochroot — s'exécute dans le LIVE, la cible montée sous /mnt/sysroot # %post --nochroot — s'exécute dans le LIVE, la cible montée sous /mnt/sysroot
@@ -89,12 +122,29 @@ SYSROOT=/mnt/sysroot
# DNS pour que « pacman -S <bureau> » fonctionne dans le chroot. # DNS pour que « pacman -S <bureau> » fonctionne dans le chroot.
cp -f /etc/resolv.conf "$SYSROOT/etc/resolv.conf" 2>/dev/null || true cp -f /etc/resolv.conf "$SYSROOT/etc/resolv.conf" 2>/dev/null || true
# --- Hostname : valeur saisie dans le champ « Nom de machine » du spoke User --
# Anaconda ne capte PAS le hostname du spoke Réseau (→ /etc/hostname vide). On a
# donc ajouté un champ Hostname au spoke Utilisateur (cf. patch du paquet
# anaconda) : user.py écrit la valeur dans /tmp/fws-hostname (live). On la
# nettoie (caractères de hostname valides) et on l'écrit dans la cible ; défaut
# « fws » si vide/absent → plus jamais de hostname vide ni « archiso ».
HN=""
[ -f /tmp/fws-hostname ] && HN="$(tr -cd 'a-zA-Z0-9._-' < /tmp/fws-hostname)"
[ -n "$HN" ] || HN="fws"
echo "$HN" > "$SYSROOT/etc/hostname"
# Choix du bureau (écrit par l'addon « Choix du bureau », à venir ; valeur lue # Choix du bureau (écrit par l'addon « Choix du bureau », à venir ; valeur lue
# par le %post chrooté ci-dessous). « cli » par défaut si absent. # par le %post chrooté ci-dessous). « cli » par défaut si absent.
if [ -f /tmp/fws-desktop ]; then if [ -f /tmp/fws-desktop ]; then
cp -f /tmp/fws-desktop "$SYSROOT/tmp/fws-desktop" 2>/dev/null || true cp -f /tmp/fws-desktop "$SYSROOT/tmp/fws-desktop" 2>/dev/null || true
fi fi
# Choix « Dual-boot Windows gaming » (spoke fws_windows) → relu par le %post
# chrooté (Secure Boot) ET par fws-windows-deploy (%post --nochroot, plus bas).
if [ -f /tmp/fws-windows ]; then
cp -f /tmp/fws-windows "$SYSROOT/tmp/fws-windows" 2>/dev/null || true
fi
# --- Noyau : archiso RETIRE /boot/vmlinuz-linux du squashfs ------------------ # --- Noyau : archiso RETIRE /boot/vmlinuz-linux du squashfs ------------------
# mkarchiso boote le noyau depuis l'ISO et l'EXCLUT de l'airootfs squashfs (gain # mkarchiso boote le noyau depuis l'ISO et l'EXCLUT de l'airootfs squashfs (gain
# de place). Conséquence : le payload LiveOS recopie un rootfs SANS noyau → # de place). Conséquence : le payload LiveOS recopie un rootfs SANS noyau →
@@ -194,78 +244,409 @@ else
echo "[FWS] AVERTISSEMENT : grub-install a échoué — bootloader à refaire à la main" echo "[FWS] AVERTISSEMENT : grub-install a échoué — bootloader à refaire à la main"
fi fi
# --- 4) Drivers matériels : détection + activation/installation AUTO ---------- # --- 3bis) Clavier (X11) + LOCALE --------------------------------------------
# Toute la DÉTECTION vit dans /usr/local/bin/fws-hw-detect : livré par # CLAVIER : Anaconda passe par localed et n'écrit pas fiablement la disposition X
# l'airootfs → recopié dans la cible par le payload liveimg → présent dans ce # sur la CIBLE → SDDM / sessions X en QWERTY US. On CALCULE la disposition CHOISIE
# chroot. C'est la MÊME source de vérité que l'écran « Récapitulatif avant # (kickstart généré, sinon dérivée de la locale via langtable) en XL/XV, réutilisés
# installation » du live (.xinitrc, mode --text) : ce qui a été ANNONCÉ à # plus bas (00-keyboard.conf pour SDDM/X ET la config Hyprland qui a son propre
# l'utilisateur = ce qu'on APPLIQUE ici. La détection marche en chroot car # kb_layout). LOCALE : le locale choisi (ex. fr_CH.UTF-8) doit être GÉNÉRÉ —
# Anaconda bind-monte le /sys et le /proc du LIVE (le code plus haut s'appuie # Anaconda vient de Fedora (locales pré-générées) et ne lance pas locale-gen →
# déjà dessus : /sys/firmware/efi, findmnt) → on voit le VRAI matériel. # sans ça LANG casse et tout retombe en C.
# Variables produites par --env : XL=""; XV=""
# FWS_VIRT hyperviseur (vmware/oracle/kvm/qemu/none…) XKB="$(sed -n "s/.*--xlayouts=//p" /root/anaconda-ks.cfg 2>/dev/null | head -1)"
# FWS_VIRT_SVC services invité à activer (paquets DÉJÀ dans l'image → OK XKB="${XKB#[\'\"]}"; XKB="${XKB%%[\'\"]*}"; XKB="${XKB%%,*}" # 1re dispo, ex. « ch (fr) »
# hors ligne ; c'était le chaînon manquant, rien n'activait if [ -z "$XKB" ]; then
# vmtoolsd & co sur le système installé) _loc="$(. /etc/locale.conf 2>/dev/null; printf '%s' "${LANG%%.*}")" # fr_CH
# FWS_VIRT_RM guest tools des AUTRES hyperviseurs, à purger _lng="${_loc%%_*}"; _terr="${_loc#*_}"; [ "$_terr" = "$_loc" ] && _terr=""
# FWS_DRV_PKGS paquets GPU à installer, dédoublonnés. NVIDIA embarque [ -n "$_terr" ] && XKB="$(python3 -c "import langtable; k=langtable.list_keyboards(languageId='$_lng', territoryId='$_terr'); print(k[0] if k else '')" 2>/dev/null)"
# « linux » dans la même transaction : le module des dépôts fi
# est bâti pour le noyau des dépôts, pas celui du live ; les if [ -n "$XKB" ]; then
# chemins /boot/* étant stables, le grub.cfg de l'étape 3 XL="${XKB%%(*}"; XL="${XL%% *}" # ch
# reste valable (le hook pacman de nvidia relance mkinitcpio). case "$XKB" in *'('*')') XV="${XKB#*(}"; XV="${XV%)}" ;; *) XV="" ;; esac # fr
# NB : microcode CPU (amd-ucode/intel-ucode) et linux-firmware sont DÉJÀ dans install -d /etc/X11/xorg.conf.d
# l'image → recopiés, et grub-mkconfig (étape 3) a généré les initrd qui vont cat > /etc/X11/xorg.conf.d/00-keyboard.conf <<KBEOF
# bien. Rien à faire pour eux. Section "InputClass"
if [ -x /usr/local/bin/fws-hw-detect ]; then Identifier "system-keyboard"
eval "$(/usr/local/bin/fws-hw-detect --env)" MatchIsKeyboard "on"
# (a) Hyperviseur : activer le bon service invité, purger les autres. Option "XkbLayout" "$XL"
[ -n "$FWS_VIRT_SVC" ] && { systemctl enable $FWS_VIRT_SVC || true; } Option "XkbVariant" "$XV"
[ -n "$FWS_VIRT_RM" ] && { pacman -Rns --noconfirm $FWS_VIRT_RM || true; } EndSection
# (b) GPU : install réseau, NON bloquante (même philosophie que le KBEOF
# bureau). --needed : ne réinstalle pas ce que l'image a déjà (mesa…).
if [ -n "$FWS_DRV_PKGS" ]; then
pacman-key --init &>/dev/null || true
pacman-key --populate archlinux &>/dev/null || true
if ! pacman -Sy --noconfirm --needed $FWS_DRV_PKGS; then
echo "[FWS] Drivers non installés ($FWS_DRV_PKGS) — réseau ? À refaire au 1er boot."
fi
fi
else
echo "[FWS] fws-hw-detect introuvable — drivers/guest tools NON configurés."
fi fi
# --- 5) Bureau choisi : pacman -S du DE (non bloquant si pas de réseau) ------- FWSLANG="$(. /etc/locale.conf 2>/dev/null; printf '%s' "${LANG:-}")"
if [ -n "$FWSLANG" ]; then
_cs="${FWSLANG##*.}"; [ "$_cs" = "$FWSLANG" ] && _cs="UTF-8"
grep -qE "^[[:space:]]*${FWSLANG}[[:space:]]+${_cs}\b" /etc/locale.gen 2>/dev/null \
|| printf '%s %s\n' "$FWSLANG" "$_cs" >> /etc/locale.gen
locale-gen || echo "[FWS] locale-gen a échoué pour $FWSLANG"
fi
# --- 4) Bureau choisi : pacman -S du DE (non bloquant si pas de réseau) -------
DESK="cli" DESK="cli"
[ -f /tmp/fws-desktop ] && DESK="$(tr -d '[:space:]' < /tmp/fws-desktop)" [ -f /tmp/fws-desktop ] && DESK="$(tr -d '[:space:]' < /tmp/fws-desktop)"
DM="" DM=""
case "$DESK" in case "$DESK" in
kde) PKGS="plasma-meta sddm konsole dolphin"; DM="sddm" ;; kde) PKGS="plasma-meta sddm konsole dolphin"; DM="sddm" ;;
gnome) PKGS="gnome gdm"; DM="gdm" ;; gnome) PKGS="gnome gdm"; DM="gdm" ;;
xfce) PKGS="xfce4 xfce4-goodies lightdm lightdm-gtk-greeter"; DM="lightdm" ;; # noto-fonts : KDE l'a via plasma-meta et GNOME a cantarell, mais XFCE et
hyprland) PKGS="hyprland kitty wofi waybar sddm"; DM="sddm" ;; # Hyprland ne garantissent AUCUNE police TTF → greeter SDDM/apps illisibles.
# Hyprland : la stack des raccourcis FWS (mêmes touches que la config
# i3/sway maison) — dolphin (Super+E), hyprlock (Super+L), grim+slurp+
# wl-clipboard (captures), playerctl (touches multimédia) — plus la
# plomberie de bureau : mako+libnotify (notifications), polkit-gnome
# (agent d'authentification GUI), network-manager-applet (réseau dans le
# tray), xdg-desktop-portal-hyprland (partage d'écran/portails), gvfs
# (montage USB via udisks2), ttf-nerd-fonts-symbols (icônes waybar).
# Son et Bluetooth GARANTIS avec le bureau : la stack PipeWire + le
# mixeur (pavucontrol) + bluez/blueman partent dans la MÊME transaction
# pacman que le DE — si le bureau s'installe, ses gestionnaires aussi
# (fws-setup-hardware les re-propose ensuite en --needed : sans effet
# s'ils sont déjà là, filet de secours sinon).
xfce) PKGS="xfce4 xfce4-goodies sddm noto-fonts pipewire pipewire-pulse wireplumber pavucontrol"; DM="sddm" ;;
hyprland) PKGS="hyprland kitty wofi waybar sddm noto-fonts dolphin kio-extras udisks2 hyprlock grim slurp wl-clipboard playerctl mako libnotify polkit-gnome network-manager-applet xdg-desktop-portal-hyprland ttf-nerd-fonts-symbols pipewire pipewire-alsa pipewire-pulse wireplumber pavucontrol bluez bluez-utils blueman"; DM="sddm" ;;
# i3 : session X11 → COMPATIBLE VR (SteamVR ne marche pas sous Wayland).
# xorg-server est requis (SDDM ne le tire pas) ; maim+xclip = captures X11,
# dunst = notifications X11 (équivalent de mako), i3lock = verrouillage.
# fws-vr-setup (Super+Shift+V ou menu) installe les drivers du casque.
i3) PKGS="i3-wm i3lock xorg-server polybar picom rofi kitty dolphin kio-extras udisks2 sddm noto-fonts ttf-nerd-fonts-symbols maim xclip dunst libnotify polkit-gnome network-manager-applet playerctl brightnessctl pipewire pipewire-alsa pipewire-pulse wireplumber pavucontrol bluez bluez-utils blueman"; DM="sddm" ;;
cli|*) PKGS="" ;; cli|*) PKGS="" ;;
esac esac
# Navigateur de base ajouté DÈS QU'un bureau est choisi (jamais en CLI).
# + python-gobject : fenêtre de bienvenue fws-hello (GTK3 vient avec Firefox)
# + pacman-contrib : checkupdates/paccache (indicateur de MàJ + fws-update)
[ -n "$PKGS" ] && PKGS="$PKGS firefox python-gobject pacman-contrib"
if [ -n "$PKGS" ]; then if [ -n "$PKGS" ]; then
pacman-key --init &>/dev/null || true pacman-key --init &>/dev/null || true
pacman-key --populate archlinux &>/dev/null || true pacman-key --populate archlinux &>/dev/null || true
if pacman -Sy --noconfirm $PKGS; then
# Script de RATTRAPAGE généré avec les valeurs de CE choix : si l'install
# du bureau échoue (réseau/miroirs), l'utilisateur la rejoue en une
# commande depuis le système installé. --needed → inoffensif si déjà fait.
cat > /usr/local/bin/fws-desktop-retry <<RETRYEOF
#!/bin/bash
# Généré par l'installateur FWS — (ré)installe le bureau « $DESK ».
set -u
[ "\$(id -u)" -eq 0 ] || exec sudo "\$0" "\$@"
echo "==> Installation du bureau $DESK…"
pacman -Syy --noconfirm --needed $PKGS || { echo "Échec (réseau ?) — réessayez."; exit 1; }
[ -n "$DM" ] && systemctl enable "$DM"
systemctl set-default graphical.target
rm -f /var/lib/fws/desktop-failed
[ -f /etc/motd ] && grep -q fws-desktop-retry /etc/motd && rm -f /etc/motd
echo "==> Bureau installé ✔ — redémarrez (reboot) pour arriver sur l'écran de connexion."
RETRYEOF
chmod 755 /usr/local/bin/fws-desktop-retry
# Le réseau peut mettre quelques secondes à monter dans l'environnement
# d'installation : on attend la résolution DNS avant le premier essai,
# puis on tente pacman jusqu'à 3 fois (miroir capricieux ≠ échec définitif).
for _i in 1 2 3 4 5 6 7 8 9 10; do
getent hosts geo.mirror.pkgbuild.com >/dev/null 2>&1 && break
sleep 3
done
_DE_OK=""
for _try in 1 2 3; do
pacman -Sy --noconfirm --needed $PKGS && { _DE_OK=1; break; }
echo "[FWS] installation du bureau : tentative $_try échouée — nouvel essai dans 15 s…"
sleep 15
done
if [ -n "$_DE_OK" ]; then
[ -n "$DM" ] && systemctl enable "$DM" || true [ -n "$DM" ] && systemctl enable "$DM" || true
systemctl set-default graphical.target || true systemctl set-default graphical.target || true
# SDDM (KDE / XFCE / Hyprland) : pavé numérique + écran de connexion FWS.
# Le thème « fws » (livré par l'airootfs → recopié par le payload dans
# /usr/share/sddm/themes/fws) est en pur QtQuick/Controls Basic :
# qt6-declarative, dépendance de sddm, suffit → même écran soigné pour
# les 3 DE SANS paquet de thème supplémentaire (Breeze n'existe qu'avec
# plasma). Accent adapté au bureau via theme.conf.user (mécanisme de
# surcharge standard de SDDM, lu par-dessus theme.conf du thème).
if [ "$DM" = "sddm" ]; then
install -d /etc/sddm.conf.d
cat > /etc/sddm.conf.d/10-fws.conf <<'SDDMEOF'
[General]
Numlock=on
[Theme]
Current=fws
SDDMEOF
case "$DESK" in
xfce) _ACCENT="#16a085" ;; # sarcelle (assorti à XFCE)
hyprland) _ACCENT="#89b4fa" ;; # bleu Catppuccin (assorti à Hyprland)
*) _ACCENT="#3daee9" ;; # bleu Breeze (KDE, défaut)
esac
if [ -d /usr/share/sddm/themes/fws ]; then
printf '[General]\naccent=%s\n' "$_ACCENT" \
> /usr/share/sddm/themes/fws/theme.conf.user
else else
echo "[FWS] Bureau '$DESK' non installé (réseau ?) — à refaire après le 1er boot." echo "[FWS] thème SDDM fws introuvable — thème par défaut conservé"
sed -i '/^Current=fws$/d' /etc/sddm.conf.d/10-fws.conf
fi
fi
# GDM (GNOME) : « la même chose » autant que possible — le greeter de
# GDM EST GNOME Shell, il ne se thème pas comme SDDM. On passe par
# dconf (base système « gdm ») : bannière d'accueil FWS, horloge avec
# date complète, pavé numérique allumé — même confort qu'avec SDDM.
if [ "$DM" = "gdm" ]; then
install -d /etc/dconf/profile /etc/dconf/db/gdm.d
cat > /etc/dconf/profile/gdm <<'GDMEOF'
user-db:user
system-db:gdm
file-db:/usr/share/gdm/greeter-dconf-defaults
GDMEOF
cat > /etc/dconf/db/gdm.d/10-fws <<'GDMEOF'
[org/gnome/login-screen]
banner-message-enable=true
banner-message-text='Bienvenue sur FWS'
[org/gnome/desktop/interface]
clock-show-date=true
clock-show-weekday=true
[org/gnome/desktop/peripherals/keyboard]
numlock-state=true
remember-numlock-state=false
GDMEOF
dconf update || echo "[FWS] dconf update a échoué (bannière GDM à refaire)"
fi
# Hyprland en VM : sur GPU virtuel (VMware/VirtualBox) le compositeur
# crashe au lancement → SDDM « remet au login ». On le lance via un
# wrapper qui force le rendu LOGICIEL UNIQUEMENT en VM (systemd-detect-virt)
# ; sur un vrai GPU le rendu matériel est conservé.
if [ "$DESK" = "hyprland" ]; then
cat > /usr/local/bin/fws-hyprland <<'HYEOF'
#!/bin/sh
if systemd-detect-virt --quiet 2>/dev/null; then
# GPU virtuel (VMware vmwgfx…) : on FORCE le chemin d'affichage 100% logiciel.
# La PIÈCE MAÎTRESSE est MESA_LOADER_DRIVER_OVERRIDE=kms_swrast : elle force
# Mesa à allouer le GBM/EGL via kms_swrast (rendu logiciel dans les « dumb
# buffers » KMS) AU LIEU du pilote vmwgfx (qui ne fournit pas le GBM dont
# aquamarine a besoin → « CBackend::create() failed »). + llvmpipe pour le GL.
export MESA_LOADER_DRIVER_OVERRIDE=kms_swrast
export GALLIUM_DRIVER=llvmpipe LIBGL_ALWAYS_SOFTWARE=1
export WLR_RENDERER_ALLOW_SOFTWARE=1 WLR_NO_HARDWARE_CURSORS=1
export AQ_NO_ATOMIC=1
fi
# Hyprland ≥ 0.53 : start-hyprland est le lanceur officiel (prépare la
# session/env systemd) — sans lui, bandeau « started without start-hyprland ».
if command -v start-hyprland >/dev/null 2>&1; then
exec start-hyprland "$@"
fi
exec Hyprland "$@"
HYEOF
chmod 755 /usr/local/bin/fws-hyprland
if [ -f /usr/share/wayland-sessions/hyprland.desktop ]; then
sed -i 's|^Exec=.*|Exec=/usr/local/bin/fws-hyprland|' \
/usr/share/wayland-sessions/hyprland.desktop
fi
# Bluetooth prêt au 1er démarrage (bluez vient d'être installé
# avec le bureau ; fws-setup-hardware le refait — idempotent).
systemctl enable bluetooth.service 2>/dev/null || true
fi
if [ "$DESK" = "i3" ]; then
systemctl enable bluetooth.service 2>/dev/null || true
# Icône du lanceur « Installation VR » (casque) visible dans rofi.
gtk-update-icon-cache -f /usr/share/icons/hicolor 2>/dev/null || true
fi
else
echo "[FWS] Bureau '$DESK' NON installé après 3 tentatives (réseau/miroirs ?)."
echo "[FWS] Rattrapage sur le système installé : sudo fws-desktop-retry"
mkdir -p /var/lib/fws
echo "$DESK" > /var/lib/fws/desktop-failed
# Le premier login (console) explique quoi faire — le motd est retiré
# par fws-desktop-retry une fois le bureau posé.
cat > /etc/motd <<'MOTD'
⚠ FWS : l'installation du bureau a échoué (réseau indisponible pendant
l'installation). Le système fonctionne, mais en console.
Une fois le réseau branché, lancez : sudo fws-desktop-retry
puis redémarrez : le bureau choisi sera installé et activé.
MOTD
fi fi
fi fi
# --- 6) Sécurité : verrouiller root s'il est resté SANS mot de passe ---------- # --- 4quater) ADN FWS : détection matériel + drivers + stack « Windows » ------
# Script dédié (NON bloquant ; réseau requis) : drivers GPU détectés via lspci +
# outils invité VM, son/micro/casque (PipeWire), Bluetooth, Podman, et — si un
# bureau est choisi — Steam, Wine, VR (Monado/SteamVR), winboat-bin (apps
# Windows). C'est le cœur « remplacement de Windows » de FWS.
if [ -x /usr/local/bin/fws-setup-hardware ]; then
/usr/local/bin/fws-setup-hardware "$DESK" || true
fi
# --- Config Hyprland (si choisi) : clavier + apps + waybar -------------------
# Hyprland (Wayland) NE lit PAS /etc/X11/xorg.conf.d/00-keyboard.conf : sa
# disposition est dans SA config (input { kb_layout }). Sans config il démarre nu
# (QWERTY US, aucune touche utile). On déploie une config FWS (template +
# clavier XL/XV injecté) dans /etc/skel ET le home de chaque utilisateur déjà
# créé par Anaconda (le %post tourne APRÈS la création de l'utilisateur).
if [ "$DESK" = "hyprland" ] && [ -f /usr/local/share/fws/hyprland.conf.tmpl ]; then
sed -e "s/__KB_LAYOUT__/${XL:-us}/" -e "s/__KB_VARIANT__/${XV}/" \
/usr/local/share/fws/hyprland.conf.tmpl > /tmp/fws-hyprland.conf
install -Dm644 /tmp/fws-hyprland.conf /etc/skel/.config/hypr/hyprland.conf
# Waybar FWS : barre assortie + bouton « ⌨ Raccourcis » (fws-keys,
# aussi sur SUPER+K) qui liste les binds RÉELS chargés par Hyprland
# (hyprctl binds -j) — les descriptions viennent des bindd de la config.
install -Dm644 /usr/local/share/fws/waybar-config.json /etc/skel/.config/waybar/config
install -Dm644 /usr/local/share/fws/waybar-style.css /etc/skel/.config/waybar/style.css
# wofi : style du menu d'applications (Super+R) — le panneau « Raccourcis »
# a son propre style (wofi-keys.css), passé en --style par fws-keys.
install -Dm644 /usr/local/share/fws/wofi-config /etc/skel/.config/wofi/config
install -Dm644 /usr/local/share/fws/wofi-style.css /etc/skel/.config/wofi/style.css
while IFS=: read -r _u _x _uid _g _gec _home _sh; do
[ "$_uid" -ge 1000 ] && [ "$_uid" -le 60000 ] && [ -d "$_home" ] || continue
install -Dm644 /tmp/fws-hyprland.conf "$_home/.config/hypr/hyprland.conf"
install -Dm644 /usr/local/share/fws/waybar-config.json "$_home/.config/waybar/config"
install -Dm644 /usr/local/share/fws/waybar-style.css "$_home/.config/waybar/style.css"
install -Dm644 /usr/local/share/fws/wofi-config "$_home/.config/wofi/config"
install -Dm644 /usr/local/share/fws/wofi-style.css "$_home/.config/wofi/style.css"
chown "$_u": "$_home/.config" 2>/dev/null || true
chown -R "$_u": "$_home/.config/hypr" "$_home/.config/waybar" \
"$_home/.config/wofi" 2>/dev/null || true
done < /etc/passwd
rm -f /tmp/fws-hyprland.conf
fi
# --- Config i3 (si choisi) : raccourcis FWS + barre + autostart -------------
# Pas d'injection clavier ici : les sessions X11 lisent la disposition dans
# /etc/X11/xorg.conf.d/00-keyboard.conf, écrite à l'étape 3bis.
if [ "$DESK" = "i3" ] && [ -f /usr/local/share/fws/i3-config ]; then
install -Dm644 /usr/local/share/fws/i3-config /etc/skel/.config/i3/config
# Polybar (clone de la waybar) + picom (coins arrondis) : même rendu
# visuel que la session Hyprland FWS.
install -Dm644 /usr/local/share/fws/polybar-config.ini /etc/skel/.config/polybar/config.ini
install -Dm644 /usr/local/share/fws/picom.conf /etc/skel/.config/picom.conf
# rofi : menu d'applications au look FWS (équivalent du wofi d'Hyprland).
install -Dm644 /usr/local/share/fws/rofi-config.rasi /etc/skel/.config/rofi/config.rasi
while IFS=: read -r _u _x _uid _g _gec _home _sh; do
[ "$_uid" -ge 1000 ] && [ "$_uid" -le 60000 ] && [ -d "$_home" ] || continue
install -Dm644 /usr/local/share/fws/i3-config "$_home/.config/i3/config"
install -Dm644 /usr/local/share/fws/polybar-config.ini "$_home/.config/polybar/config.ini"
install -Dm644 /usr/local/share/fws/picom.conf "$_home/.config/picom.conf"
install -Dm644 /usr/local/share/fws/rofi-config.rasi "$_home/.config/rofi/config.rasi"
chown "$_u": "$_home/.config" 2>/dev/null || true
chown -R "$_u": "$_home/.config/i3" "$_home/.config/polybar" \
"$_home/.config/rofi" "$_home/.config/picom.conf" 2>/dev/null || true
done < /etc/passwd
fi
# --- 4bis) Sudo : l'utilisateur créé doit pouvoir administrer -----------------
# Arch laisse « %wheel » COMMENTÉ dans sudoers → même un membre de wheel ne peut
# pas sudo. On active le groupe wheel (drop-in) ET on y ajoute tous les
# utilisateurs HUMAINS (UID 1000-60000) créés par Anaconda → ils sont sudoers.
install -d -m 750 /etc/sudoers.d
printf '%%wheel ALL=(ALL:ALL) ALL\n' > /etc/sudoers.d/10-fws-wheel
chmod 440 /etc/sudoers.d/10-fws-wheel
while IFS=: read -r _u _x _uid _rest; do
if [ "$_uid" -ge 1000 ] && [ "$_uid" -le 60000 ]; then
usermod -aG wheel "$_u" 2>/dev/null || true
fi
done < /etc/passwd
# --- 4ter) Branding FWS sur le système installé -------------------------------
# Le hook os-release du build n'est PAS sur la cible et « pacman -S » a pu
# remettre l'os-release d'Arch → d'où « Arch Linux » au menu GRUB, le logo Arch
# sur l'écran de connexion (GDM lit os-release LOGO) et le nom « Arch ». On FORCE
# donc l'os-release FWS (+ lsb-release + GRUB_DISTRIBUTOR), on RÉGÉNÈRE le menu
# GRUB (10_linux lit le NAME d'os-release), et on retire le motd « live ».
if [ -f /usr/local/share/fws/os-release ]; then
cp -f /usr/local/share/fws/os-release /usr/lib/os-release
ln -sf ../usr/lib/os-release /etc/os-release
fi
if [ -f /etc/lsb-release ]; then
sed -i 's/^DISTRIB_ID=.*/DISTRIB_ID=FWS/; s/^DISTRIB_DESCRIPTION=.*/DISTRIB_DESCRIPTION="FWS Linux"/' \
/etc/lsb-release
fi
if grep -q '^GRUB_DISTRIBUTOR=' /etc/default/grub 2>/dev/null; then
sed -i 's/^GRUB_DISTRIBUTOR=.*/GRUB_DISTRIBUTOR="FWS"/' /etc/default/grub
else
echo 'GRUB_DISTRIBUTOR="FWS"' >> /etc/default/grub
fi
grub-mkconfig -o /boot/grub/grub.cfg 2>/dev/null || true
# motd « live » (texte inadapté + codes ANSI bruts affichés crus par GDM) —
# SAUF si c'est notre message « fws-desktop-retry » (échec d'install du
# bureau), qui doit survivre jusqu'au rattrapage.
grep -q fws-desktop-retry /etc/motd 2>/dev/null || rm -f /etc/motd
# --- 4quinquies) Pare-feu par défaut -------------------------------------------
# firewalld est dans l'IMAGE (packages.x86_64) → recopié par le payload,
# activation possible HORS LIGNE. Zone par défaut « public » : entrées
# bloquées, sortant libre — le comportement attendu d'un poste de travail.
# GUI incluse dans le paquet (firewall-config) ; KDE a en plus plasma-firewall.
systemctl enable firewalld.service 2>/dev/null || true
# --- 4quinquies-bis) Horloge juste : synchronisation NTP -----------------------
# Sans NTP l'horloge dérive (TLS/pacman finissent par râler, et un utilisateur
# venant de Windows s'attend à une heure exacte). systemd-timesyncd est dans
# l'image ; on l'ACTIVE explicitement — les symlinks d'activation hérités du
# live étaient des FICHIERS ordinaires (artefact de checkout) que systemd
# ignore (« not a symlink »), corrigés côté live mais on assure ici quoi
# qu'il arrive sur le système installé.
systemctl enable systemd-timesyncd.service 2>/dev/null || true
# NB Récupération : l'entrée GRUB « Réparer FWS » (générée par
# /etc/grub.d/42_fws-recovery lors des grub-mkconfig ci-dessus) boote
# systemd.unit=fws-recovery.target → fws-recovery-boot.service prend tty1 et
# lance l'application de réparation. RIEN à activer : le service n'a pas de
# [Install] (son Conflicts=getty@tty1 ne doit jamais entrer dans un boot
# normal), la target le tire par Requires. Dans le bureau, le lanceur
# « Récupération FWS » (fws-recovery.desktop, polkit) fait le même travail.
# --- 5) Sécurité : verrouiller root s'il est resté SANS mot de passe ----------
# L'airootfs live a souvent un root sans mot de passe ; si l'utilisateur n'en a # L'airootfs live a souvent un root sans mot de passe ; si l'utilisateur n'en a
# pas défini dans le GUI, on verrouille plutôt que de laisser le compte ouvert. # pas défini dans le GUI, on verrouille plutôt que de laisser le compte ouvert.
if passwd -S root 2>/dev/null | grep -qE '\bNP\b'; then if passwd -S root 2>/dev/null | grep -qE '\bNP\b'; then
passwd -l root || true passwd -l root || true
fi fi
# --- 7) Le resolv.conf du live ne doit pas rester figé ----------------------- # --- 6) DNS du système installé : NetworkManager + systemd-resolved ----------
# NetworkManager (activé sur le système installé) régénère /etc/resolv.conf. # Le live utilise systemd-resolved (/etc/resolv.conf → stub 127.0.0.53). L'ANCIEN
rm -f /tmp/fws-desktop /etc/resolv.conf 2>/dev/null || true # « rm /etc/resolv.conf » comptait sur NM pour recréer le fichier — MAIS avec
# systemd-resolved actif, NM ne réécrit PAS resolv.conf (il pousse les DNS vers
# resolved). Sans le lien, la résolution est MORTE → « Limited Connectivity » /
# Firefox « Server Not Found » (le réseau marche, le DNS non). Fix robuste :
# - resolv.conf → stub systemd-resolved,
# - service systemd-resolved activé,
# - on FORCE NM à utiliser resolved (dns=systemd-resolved) → il lui fournit les
# DNS DHCP, la résolution marche dès le 1er boot.
rm -f /tmp/fws-desktop 2>/dev/null || true
ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
systemctl enable systemd-resolved.service 2>/dev/null || true
install -d /etc/NetworkManager/conf.d
printf '[main]\ndns=systemd-resolved\n' > /etc/NetworkManager/conf.d/dns.conf
# --- 7) Secure Boot (uniquement si « Dual-boot Windows gaming » demandé) ------
# Vanguard exige Secure Boot. On PRÉPARE la chaîne pendant l'install : sbctl
# create-keys + signature des 3 chemins réels (\EFI\FWS, \EFI\BOOT\BOOTX64, noyaux).
# L'enrôlement des clés + l'activation de SB au firmware restent MANUELS (Setup
# Mode requis, non automatisable) — instructions au 1er boot. Idempotent.
# On NE source PAS /tmp/fws-windows (un chemin d'ISO piégé pourrait injecter du
# code) : simple test littéral de la ligne « enabled=1 ».
if grep -qx 'enabled=1' /tmp/fws-windows 2>/dev/null \
&& [ -x /usr/local/bin/fws-secureboot-setup ]; then
/usr/local/bin/fws-secureboot-setup \
|| echo "[FWS] Secure Boot : préparation partielle (à finaliser au 1er boot)."
fi
%end
# ============================================================================
# %post --nochroot — DÉPLOIEMENT WINDOWS (option A) si « Dual-boot Windows »
# demandé. Tourne dans le LIVE (cible sous /mnt/sysroot). NON bloquant : un
# échec de déploiement N'EMPÊCHE PAS FWS de booter (garde-fous fail-closed +
# statut DEPLOY_FAILED, aucune entrée UEFI Windows piégeuse). Le carve du disque
# Windows vit ICI (le %pre tourne avant le GUI → ne voit pas /tmp/fws-windows).
# Voir fws-windows-deploy + docs/windows-spoke-plan.md.
# ============================================================================
%post --nochroot --log=/tmp/fws-windows-deploy.log
SYSROOT=/mnt/sysroot
[ -d "$SYSROOT" ] || SYSROOT=/mnt/sysimage
if [ -f /tmp/fws-windows ] && [ -x /usr/local/bin/fws-windows-deploy ]; then
/usr/local/bin/fws-windows-deploy --state /tmp/fws-windows --sysroot "$SYSROOT" \
|| echo "[FWS] Déploiement Windows échoué (voir le log) — FWS reste bootable."
fi
%end %end
@@ -0,0 +1,10 @@
[Desktop Entry]
Type=Application
Name=À propos de FWS
Name[en]=About FWS
Comment=Informations sur le projet FWS et sur le système
Exec=fws-about
Icon=help-about
Terminal=false
Categories=System;
Keywords=about;système;projet;info;FWS;
@@ -0,0 +1,17 @@
[Desktop Entry]
# FWS — lanceur gameboot : hiberne FWS et bascule vers le Windows bare-metal
# pour jouer à League of Legends (anticheat Vanguard). Aucun mot de passe pour la
# session active (cf. polkit org.fws.gameboot.policy).
Type=Application
Name=Jouer à League of Legends
Name[en]=Play League of Legends
GenericName=Bascule vers Windows (gameboot)
GenericName[en]=Switch to Windows (gameboot)
Comment=Hiberne FWS et démarre League of Legends sur le Windows bare-metal
Comment[en]=Hibernate FWS and boot League of Legends on bare-metal Windows
Exec=pkexec /usr/local/bin/fws-gameboot to-windows --game lol
Icon=fws-gameboot
Terminal=false
Categories=Game;
Keywords=league;legends;lol;vanguard;windows;gameboot;dual-boot;jeu;
Keywords[en]=league;legends;lol;vanguard;windows;gameboot;dual-boot;game;
@@ -0,0 +1,17 @@
[Desktop Entry]
# FWS — lanceur gameboot : hiberne FWS et bascule vers le Windows bare-metal
# pour jouer à Valorant (anticheat Vanguard). Aucun mot de passe pour la session
# active (cf. polkit org.fws.gameboot.policy).
Type=Application
Name=Jouer à Valorant
Name[en]=Play Valorant
GenericName=Bascule vers Windows (gameboot)
GenericName[en]=Switch to Windows (gameboot)
Comment=Hiberne FWS et démarre Valorant sur le Windows bare-metal
Comment[en]=Hibernate FWS and boot Valorant on bare-metal Windows
Exec=pkexec /usr/local/bin/fws-gameboot to-windows --game valorant
Icon=fws-gameboot
Terminal=false
Categories=Game;
Keywords=valorant;vanguard;windows;gameboot;dual-boot;jeu;
Keywords[en]=valorant;vanguard;windows;gameboot;dual-boot;game;
@@ -0,0 +1,10 @@
[Desktop Entry]
Type=Application
Name=Installation VR (FWS)
Name[en]=VR Setup (FWS)
Comment=Installer les drivers/runtimes pour casque VR (HTC Vive, Index, Quest…)
Exec=kitty -e fws-vr-setup
Icon=fws-vr
Terminal=false
Categories=System;Settings;
Keywords=VR;SteamVR;Vive;Index;Quest;casque;
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- FWS — icône générique « gameboot » (lanceur de bascule vers Windows gaming).
Neutre, sans logo de marque (Valorant/LoL sont des marques de Riot). -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128">
<defs>
<linearGradient id="fwsGameGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#89b4fa"/>
<stop offset="1" stop-color="#5b7fd4"/>
</linearGradient>
</defs>
<rect x="12" y="12" width="104" height="104" rx="26" fill="url(#fwsGameGrad)"/>
<!-- Poignées de manette stylisées -->
<rect x="30" y="52" width="68" height="34" rx="17" fill="#ffffff" opacity="0.18"/>
<!-- Triangle « play » = lancer le jeu -->
<path d="M54 44 L88 64 L54 84 Z" fill="#ffffff"/>
</svg>

After

Width:  |  Height:  |  Size: 809 B

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- FWS — icône « casque VR » (lanceur fws-vr-setup). Palette FWS/Catppuccin. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
<!-- sangles -->
<path d="M8 28 C1 29 1 41 8 42" fill="none" stroke="#89b4fa" stroke-width="4" stroke-linecap="round"/>
<path d="M56 28 C63 29 63 41 56 42" fill="none" stroke="#89b4fa" stroke-width="4" stroke-linecap="round"/>
<!-- corps du casque avec encoche du nez -->
<path d="M16 18 h32 a10 10 0 0 1 10 10 v10 a10 10 0 0 1 -10 10 h-7
a9 9 0 0 0 -18 0 h-7 a10 10 0 0 1 -10 -10 v-10 a10 10 0 0 1 10 -10 z"
fill="#1e1e2e" stroke="#89b4fa" stroke-width="3" stroke-linejoin="round"/>
<!-- lentilles -->
<circle cx="23" cy="32" r="5.5" fill="#74c7ec"/>
<circle cx="41" cy="32" r="5.5" fill="#74c7ec"/>
<circle cx="21.5" cy="30.5" r="1.8" fill="#cdd6f4"/>
<circle cx="39.5" cy="30.5" r="1.8" fill="#cdd6f4"/>
</svg>

After

Width:  |  Height:  |  Size: 964 B

@@ -0,0 +1,26 @@
# ============================================================
# FWS — re-signature Secure Boot (sbctl) après une MAJ de noyau ou de GRUB.
#
# Sans ça, « pacman -Syu » qui met à jour linux/grub réécrit des binaires NON
# signés → au reboot sous Secure Boot, le firmware refuse de démarrer FWS.
# « sbctl sign-all » re-signe tous les fichiers suivis (idempotent). Complète
# le hook natif de sbctl (filet redondant, sans effet s'il est déjà passé).
#
# Inoffensif tant que sbctl n'a rien enrôlé (sign-all ne fait alors rien).
# ============================================================
[Trigger]
Type = Path
Operation = Install
Operation = Upgrade
Target = usr/lib/modules/*/vmlinuz
Target = boot/vmlinuz-*
Target = usr/lib/grub/*
Target = boot/grub/x86_64-efi/*
Target = boot/efi/EFI/*/grubx64.efi
Target = efi/EFI/*/grubx64.efi
[Action]
Description = FWS : re-signature Secure Boot des binaires de boot (sbctl)…
When = PostTransaction
Exec = /usr/bin/sbctl sign-all
Depends = sbctl
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policyconfig PUBLIC
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
"http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
<!--
FWS — autorise les lanceurs gameboot (entrées .desktop « Jouer à … ») à
exécuter /usr/local/bin/fws-gameboot via pkexec.
allow_active = yes : AUCUN mot de passe pour l'utilisateur physiquement présent
(poste gaming perso). La bascule n'est PAS destructive — FWS hiberne, la
session est préservée et restaurée au retour. auth_admin pour les sessions
inactives/distantes.
-->
<policyconfig>
<vendor>FWS</vendor>
<action id="org.fws.gameboot">
<description>Basculer vers Windows pour jouer (gameboot)</description>
<description xml:lang="en">Switch to Windows to play (gameboot)</description>
<message>Authentification requise pour basculer vers Windows</message>
<message xml:lang="en">Authentication is required to switch to Windows</message>
<icon_name>fws-gameboot</icon_name>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>yes</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.exec.path">/usr/local/bin/fws-gameboot</annotate>
</action>
</policyconfig>
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policyconfig PUBLIC
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
"http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
<!--
FWS — autorise fws-recovery-gui (lancée en utilisateur depuis le menu de
l'OS installé) à exécuter le MOTEUR fws-recovery via pkexec.
auth_admin_keep : le mot de passe administrateur n'est demandé qu'une fois
(mémorisé quelques minutes), pas à chaque bouton.
-->
<policyconfig>
<vendor>FWS</vendor>
<action id="org.fws.recovery">
<description>Réparer le système FWS</description>
<description xml:lang="en">Repair the FWS system</description>
<message>Authentification requise pour réparer le système</message>
<message xml:lang="en">Authentication is required to repair the system</message>
<icon_name>fws-recovery</icon_name>
<defaults>
<allow_any>auth_admin_keep</allow_any>
<allow_inactive>auth_admin_keep</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.exec.path">/usr/local/bin/fws-recovery</annotate>
</action>
</policyconfig>
@@ -0,0 +1,249 @@
// Thème SDDM « fws » — écran de connexion FWS (KDE / XFCE / Hyprland).
//
// VOLONTAIREMENT en pur QtQuick + QtQuick.Controls (style Basic) : ces
// modules arrivent avec qt6-declarative, dépendance de sddm lui-même →
// AUCUN paquet supplémentaire requis (pas de Breeze/plasma-workspace, pas
// de QtGraphicalEffects — supprimé en Qt6). Le greeter fournit en contexte
// les objets sddm, userModel, sessionModel, keyboard et config (theme.conf,
// surchargé par theme.conf.user écrit par le %post : accent selon le DE).
import QtQuick
import QtQuick.Controls.Basic
Rectangle {
id: root
width: 1280
height: 800
readonly property color accent: config.accent || "#3daee9"
readonly property color fieldBg: "#2a2a44"
gradient: Gradient {
GradientStop { position: 0.0; color: config.colorTop || "#1a1a2e" }
GradientStop { position: 1.0; color: config.colorBottom || "#16213e" }
}
Connections {
target: sddm
function onLoginFailed() {
errorLabel.text = "Échec de la connexion — vérifiez le mot de passe"
passwordField.text = ""
passwordField.forceActiveFocus()
}
function onLoginSucceeded() {
errorLabel.text = ""
}
}
function doLogin() {
errorLabel.text = ""
sddm.login(userField.text, passwordField.text, sessionBox.currentIndex)
}
// --- Horloge -----------------------------------------------------------
Timer {
interval: 1000
running: true
repeat: true
onTriggered: {
var now = new Date()
clockLabel.text = Qt.formatTime(now, "hh:mm")
dateLabel.text = Qt.formatDate(now, "dddd d MMMM yyyy")
}
Component.onCompleted: triggered()
}
Column {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: parent.height * 0.10
spacing: 4
Text {
id: clockLabel
anchors.horizontalCenter: parent.horizontalCenter
color: "#f0f0f5"
font.pixelSize: 84
font.weight: Font.Light
}
Text {
id: dateLabel
anchors.horizontalCenter: parent.horizontalCenter
color: "#a0a0b8"
font.pixelSize: 20
font.capitalization: Font.Capitalize
}
}
// --- Carte de connexion --------------------------------------------------
Rectangle {
id: card
anchors.centerIn: parent
anchors.verticalCenterOffset: parent.height * 0.08
width: 400
height: cardContent.implicitHeight + 56
radius: 14
color: "#232338"
border.color: Qt.rgba(1, 1, 1, 0.10)
border.width: 1
Column {
id: cardContent
anchors.centerIn: parent
width: parent.width - 56
spacing: 14
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: config.greeting || "Bienvenue sur FWS"
color: "#f0f0f5"
font.pixelSize: 22
font.weight: Font.Medium
}
Rectangle { width: 44; height: 3; radius: 1.5; color: accent
anchors.horizontalCenter: parent.horizontalCenter }
TextField {
id: userField
width: parent.width
text: userModel.lastUser
placeholderText: "Utilisateur"
placeholderTextColor: "#7d7d95"
color: "#f0f0f5"
font.pixelSize: 15
leftPadding: 14
background: Rectangle {
radius: 8
color: fieldBg
border.color: userField.activeFocus ? accent : "transparent"
border.width: 2
}
onAccepted: passwordField.forceActiveFocus()
}
TextField {
id: passwordField
width: parent.width
placeholderText: "Mot de passe"
placeholderTextColor: "#7d7d95"
color: "#f0f0f5"
font.pixelSize: 15
leftPadding: 14
echoMode: TextInput.Password
focus: true
background: Rectangle {
radius: 8
color: fieldBg
border.color: passwordField.activeFocus ? accent : "transparent"
border.width: 2
}
onAccepted: doLogin()
}
Text {
visible: keyboard.capsLock
anchors.horizontalCenter: parent.horizontalCenter
text: "⚠ Verrouillage majuscules activé"
color: "#e6c07b"
font.pixelSize: 13
}
Text {
id: errorLabel
width: parent.width
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
color: "#e06c75"
font.pixelSize: 13
}
Button {
id: loginButton
width: parent.width
height: 44
onClicked: doLogin()
contentItem: Text {
text: "Se connecter"
color: "#ffffff"
font.pixelSize: 16
font.weight: Font.Medium
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
radius: 8
color: loginButton.down ? Qt.darker(accent, 1.25)
: loginButton.hovered ? Qt.lighter(accent, 1.10)
: accent
}
}
ComboBox {
id: sessionBox
width: parent.width
model: sessionModel
currentIndex: sessionModel.lastIndex
textRole: "name"
font.pixelSize: 13
contentItem: Text {
leftPadding: 14
text: sessionBox.displayText
color: "#a0a0b8"
font.pixelSize: 13
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
radius: 8
color: "transparent"
border.color: Qt.rgba(1, 1, 1, 0.15)
border.width: 1
}
}
}
}
// --- Boutons d'alimentation ----------------------------------------------
// PAS de Repeater avec des fonctions dans un modèle JS : en Qt6 elles ne
// survivent pas toujours à la conversion QVariant (clic silencieusement
// inopérant). Deux boutons explicites via un composant inline.
component PowerButton: Button {
id: powerButton
property string label: ""
height: 36
contentItem: Text {
text: powerButton.label
color: powerButton.hovered ? "#f0f0f5" : "#a0a0b8"
font.pixelSize: 14
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
leftPadding: 14
rightPadding: 14
}
background: Rectangle {
radius: 8
color: powerButton.hovered ? Qt.rgba(1, 1, 1, 0.08) : "transparent"
border.color: Qt.rgba(1, 1, 1, 0.15)
border.width: 1
}
}
Row {
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: 24
spacing: 10
PowerButton { label: "Redémarrer"; onClicked: sddm.reboot() }
PowerButton { label: "Éteindre"; onClicked: sddm.powerOff() }
}
// Nom de la machine, discret, en bas à gauche.
Text {
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.margins: 24
text: sddm.hostName
color: "#5d5d78"
font.pixelSize: 13
}
}
@@ -0,0 +1,17 @@
[SddmGreeterTheme]
Name=FWS
Description=Écran de connexion FWS
Author=FWS
Copyright=FWS
License=GPL-2.0-or-later
Type=sddm-theme
Version=1.0
Website=
Screenshot=
MainScript=Main.qml
ConfigFile=theme.conf
TranslationsDirectory=
Email=
Theme-Id=fws
Theme-API=2.0
QtVersion=6
@@ -0,0 +1,7 @@
# Thème SDDM « fws » — valeurs par défaut, surchargées par theme.conf.user
# (écrit par le %post du kickstart : accent différent selon le bureau choisi).
[General]
accent=#3daee9
colorTop=#1a1a2e
colorBottom=#16213e
greeting=Bienvenue sur FWS
@@ -2,4 +2,4 @@ title FWS install medium (%ARCH%, UEFI)
sort-key 01 sort-key 01
linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux
initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% udev.event_timeout=45
@@ -0,0 +1,8 @@
# Mode graphique de secours : SANS KMS (nomodeset). Filet de sécurité
# standard pour les GPU/hyperviseurs dont le pilote KMS pose problème :
# X démarre alors sur le framebuffer VESA/EFI (plus lent mais fiable).
title FWS install medium (%ARCH%, UEFI, safe graphics)
sort-key 01b
linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux
initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% nomodeset udev.event_timeout=45
@@ -0,0 +1,9 @@
# « Réparer FWS » : boote le live SANS installateur et lance l'assistant de
# réparation en console (fws-recovery, via le hook fws.recovery=1 du
# .zprofile) : réinstaller GRUB, régénérer l'initramfs, réparer pacman,
# shell chroot — le point de restauration FWS.
title FWS - Reparer une installation (recovery)
sort-key 01d
linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux
initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% udev.event_timeout=45 fws.recovery=1

Some files were not shown because too many files have changed in this diff Show More