diff --git a/configs/releng/airootfs/usr/local/bin/fws-recovery b/configs/releng/airootfs/usr/local/bin/fws-recovery index 1f1e0b9..cecbb62 100755 --- a/configs/releng/airootfs/usr/local/bin/fws-recovery +++ b/configs/releng/airootfs/usr/local/bin/fws-recovery @@ -1,109 +1,189 @@ #!/bin/bash # ============================================================================ -# FWS — réparation d'une installation existante depuis le live (« point de -# restauration à la Windows »). Lancé par l'entrée de boot « Réparer FWS » -# (paramètre noyau fws.recovery=1 → hook dans /root/.zprofile) ou à la main. +# FWS — moteur de réparation d'une installation existante depuis le live +# (« point de restauration à la Windows »). +# +# L'interface GRAPHIQUE est fws-recovery-gui (GTK3 python — le live embarque +# python-gobject/gtk3 via Anaconda) ; elle pilote CE script en sous-commandes. +# Sans argument : assistant CONSOLE (repli sans X, ou usage à la main). +# +# Sous-commandes (pour la GUI / scripts) : +# scan → une ligne « périphérique|nom » par install FWS +# repair → monte, répare, démonte ; sortie en continu +# action ∈ grub|initramfs|pacman|pacman-update|all +# shell → terminal DANS le système (xterm si DISPLAY) # -# Étapes : détecte les installations FWS sur les disques (LVM compris), -# monte la cible + /boot + ESP (fstab), puis menu : réinstaller GRUB, -# régénérer l'initramfs, réparer pacman, shell chroot. # 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 -say() { printf '\n\033[1;34m==> %s\033[0m\n' "$*"; } -warn() { printf '\033[1;33m ⚠ %s\033[0m\n' "$*"; } 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() { - umount -R "$MNT" 2>/dev/null +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 } -trap cleanup EXIT -say "Recherche des installations FWS…" -# Réactive un éventuel LVM (partitionnement auto d'Anaconda : / sur LVM). -vgscan --mknodes >/dev/null 2>&1 -vgchange -ay >/dev/null 2>&1 -udevadm settle --timeout=10 >/dev/null 2>&1 +# ── Moteur ─────────────────────────────────────────────────────────────────── -mkdir -p "$MNT" -CANDIDATES=() -while read -r dev; do - [ -b "$dev" ] || continue - 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}")" - CANDIDATES+=("$dev|$name") +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 - umount "$MNT" 2>/dev/null -done < <(lsblk -rpno NAME,TYPE | awk '$2=="part"||$2=="lvm"{print $1}') + 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}') +} -if [ "${#CANDIDATES[@]}" -eq 0 ]; then - warn "Aucune installation FWS trouvée sur les disques." - warn "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}" -ROOTDEV="${CANDIDATES[$((pick-1))]%%|*}" || exit 1 - -say "Montage de $ROOTDEV…" -mount "$ROOTDEV" "$MNT" || { warn "montage impossible"; exit 1; } -# /boot et l'ESP depuis le fstab de la cible (montage par la source UUID/dev). -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é (continuer quand même)" -done +# 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 - arch-chroot "$MNT" grub-install --target=x86_64-efi \ + in_target grub-install --target=x86_64-efi \ --efi-directory=/boot/efi --bootloader-id=FWS --recheck || true - arch-chroot "$MNT" grub-install --target=x86_64-efi \ + in_target grub-install --target=x86_64-efi \ --efi-directory=/boot/efi --bootloader-id=FWS --removable --recheck else - local rootsrc disk - rootsrc="$ROOTDEV" - disk="$(lsblk -spno NAME,TYPE "$rootsrc" 2>/dev/null \ + 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; } - arch-chroot "$MNT" grub-install --target=i386-pc --recheck "$disk" + in_target grub-install --target=i386-pc --recheck "$disk" fi - arch-chroot "$MNT" grub-mkconfig -o /boot/grub/grub.cfg + in_target grub-mkconfig -o /boot/grub/grub.cfg } fix_initramfs() { say "Régénération de l'initramfs (mkinitcpio -P)…" - arch-chroot "$MNT" 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)" - arch-chroot "$MNT" pacman-key --init - arch-chroot "$MNT" pacman-key --populate archlinux - read -rp " Lancer aussi une mise à jour complète (réseau requis) ? [o/N] " _u - case "$_u" in o|O|oui) - cp -f /etc/resolv.conf "$MNT/etc/resolv.conf" 2>/dev/null - arch-chroot "$MNT" pacman -Syu ;; - esac + in_target pacman-key --init + in_target pacman-key --populate archlinux + if [ -n "$DO_UPDATE" ]; then + say "Mise à jour complète (pacman -Syu)…" + [ -z "$SELF" ] && cp -f /etc/resolv.conf "$MNT/etc/resolv.conf" 2>/dev/null + in_target pacman -Syu --noconfirm + fi } +fix_all() { fix_initramfs && fix_grub; } + +# ── Sous-commandes (pilotées par fws-recovery-gui) ─────────────────────────── +case "${1:-}" in + scan) + scan_installs + exit 0 ;; + repair) + action="${2:?action manquante}"; dev="${3:?périphérique manquant}" + trap cleanup EXIT + say "Montage de $dev…" + mount_target "$dev" || { warn "montage impossible"; exit 1; } + rc=0 + case "$action" in + grub) fix_grub || rc=$? ;; + initramfs) fix_initramfs || rc=$? ;; + pacman) fix_pacman || rc=$? ;; + pacman-update) DO_UPDATE=1; fix_pacman || rc=$? ;; + all) fix_all || rc=$? ;; + *) warn "action inconnue : $action"; rc=2 ;; + esac + cleanup; trap - EXIT + [ "$rc" -eq 0 ] && say "Terminé ✔" || say "Échec (code $rc)" + exit "$rc" ;; + shell) + dev="${2:?périphérique manquant}" + trap cleanup EXIT + mount_target "$dev" || { warn "montage impossible"; exit 1; } + if [ -z "$SELF" ] && [ -n "${DISPLAY:-}" ] && command -v xterm >/dev/null 2>&1; then + xterm -title "FWS — terminal dans le système (chroot)" \ + -e arch-chroot "$MNT" /bin/bash + else + in_target /bin/bash + fi + cleanup; trap - EXIT + exit 0 ;; + ""|tui) : ;; # assistant console ci-dessous + *) echo "usage : fws-recovery [scan|repair |shell ]"; exit 2 ;; +esac + +# ── Assistant CONSOLE (repli sans X / usage manuel) ────────────────────────── +trap cleanup EXIT +say "Recherche des installations FWS…" +mapfile -t CANDIDATES < <(scan_installs) + +if [ "${#CANDIDATES[@]}" -eq 0 ]; then + warn "Aucune installation FWS trouvée. Disques visibles :" + lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS + read -rp "Entrée pour ouvrir un shell (exit pour redémarrer). " _ + bash; exit 0 +fi + +echo; i=1 +for c in "${CANDIDATES[@]}"; do echo " $i) ${c%%|*} — ${c##*|}"; i=$((i+1)); done +read -rp "Installation à réparer [1] : " pick; pick="${pick:-1}" +dev="${CANDIDATES[$((pick-1))]%%|*}" +[ -n "$dev" ] || exit 1 +say "Montage de $dev…" +mount_target "$dev" || { warn "montage impossible"; exit 1; } + while :; do cat <<'MENU' @@ -113,7 +193,7 @@ while :; do ║ 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 (2 puis 1) ║ + ║ 4) Tout réparer (initramfs + GRUB) ║ ║ 5) Ouvrir un shell DANS le système (chroot) ║ ║ q) Quitter et redémarrer ║ ╚════════════════════════════════════════════════════════╝ @@ -122,17 +202,19 @@ MENU case "$choice" in 1) fix_grub ;; 2) fix_initramfs ;; - 3) fix_pacman ;; - 4) fix_initramfs && fix_grub ;; + 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." - arch-chroot "$MNT" /bin/bash ;; + in_target /bin/bash ;; q|Q) break ;; *) warn "choix inconnu" ;; esac done -cleanup -trap - EXIT +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