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
This commit is contained in:
@@ -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 <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)
|
||||
#
|
||||
# É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 <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'
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user