mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 21:27:02 +01:00
On 2026-07-30 a kernel upgrade left the machine in systemd emergency mode on three consecutive boots. The upgrade replaced /usr/lib/modules/<old> with <new>, but the new kernel never reached the ESP, so the firmware kept booting the OLD vmlinuz whose module tree had just been deleted. Nothing modular could load after pivot-root — including vfat, which meant /boot itself could not be mounted, local-fs.target failed, and systemd dropped to an emergency shell. pacman reported the transaction as successful. Root cause: pacman-git with ParallelDownloads > 1 segfaults every forked alpm hook child, silently skipping 90-mkinitcpio-install and depmod. boot-repair is self-contained (no repo libs — it must run in the emergency shell) and fully offline (missing modules are recovered from /var/cache/pacman/pkg). It repairs by default; --dry-run inspects. It detects and fixes: read-only root, noauto/nofail fstab drift, orphaned kernel files shadowing an unmounted ESP, unloadable vfat, unmounted ESP, missing modules.dep, ESP kernel != installed tree, stale initramfs, and ParallelDownloads > 1. Two pacman hooks make the failure impossible rather than merely recoverable: 05-boot-mounted-guard aborts a kernel transaction when the ESP is unmounted (AbortOnFail), and 99-boot-autorepair fixes ESP drift after the transaction, before the machine is ever rebooted. Safety: a module tree counts as usable only if it has BOTH vmlinuz and kernel/, so the metadata-only wreckage this bug leaves — which sorts newer — is never chosen as the target. Files under /boot are deleted only while it is not a mountpoint AND contains no EFI/ or loader/; deleting after mounting would destroy the real kernel. Verified: - 31/31 fixture tests (--root against throwaway trees, no privileges) - 11/11 reversible live tests on the real machine: unmounted the real ESP, planted shadow files, confirmed they were deleted and the ESP then mounted with EFI/, loader/ and kernel 7.1.5-arch1-2 intact - Gate proven against a real transaction with the ESP unmounted: "failed to commit transaction", no packages upgraded, nothing written - shellcheck + shfmt clean, pre-commit passing Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
80 lines
2.7 KiB
Bash
Executable File
80 lines
2.7 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# ============================================================================
|
|
# Install boot-repair and its pacman gate hooks.
|
|
# ============================================================================
|
|
#
|
|
# Installs:
|
|
# /usr/local/sbin/boot-repair the repair tool
|
|
# /etc/pacman.d/hooks/05-boot-mounted-guard.hook pre-transaction gate
|
|
# /etc/pacman.d/hooks/99-boot-autorepair.hook post-transaction fix
|
|
#
|
|
# boot-repair must live on the ROOT filesystem: by definition the ESP is
|
|
# unreachable when it is needed.
|
|
# ============================================================================
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
|
readonly SCRIPT_DIR
|
|
readonly SBIN_DEST="/usr/local/sbin/boot-repair"
|
|
readonly HOOK_DIR="/etc/pacman.d/hooks"
|
|
|
|
if [[ $EUID -ne 0 ]]; then
|
|
echo "This installer needs root; re-running with sudo..."
|
|
exec sudo "$0" "$@"
|
|
fi
|
|
|
|
echo "=== boot-repair installer ==="
|
|
|
|
# 1. Dependencies. Everything used is in `base`, so this is a verification
|
|
# rather than an install — but it must fail loudly if something is absent,
|
|
# since the tool's whole point is working when the system is broken.
|
|
echo "[1/4] Checking required tools..."
|
|
missing=()
|
|
for tool in findmnt depmod modprobe tar install awk sed grep find sort; do
|
|
command -v "$tool" >/dev/null 2>&1 || missing+=("$tool")
|
|
done
|
|
if [[ ${#missing[@]} -gt 0 ]]; then
|
|
echo " Missing: ${missing[*]}"
|
|
echo " Installing base utilities..."
|
|
pacman -S --needed --noconfirm util-linux kmod tar coreutils gawk sed grep findutils
|
|
fi
|
|
# mkinitcpio is required to rebuild the initramfs.
|
|
if ! command -v mkinitcpio >/dev/null 2>&1; then
|
|
echo " Installing mkinitcpio..."
|
|
pacman -S --needed --noconfirm mkinitcpio
|
|
fi
|
|
echo " All required tools present."
|
|
|
|
# 2. The script itself.
|
|
echo "[2/4] Installing $SBIN_DEST..."
|
|
install -Dm755 "$SCRIPT_DIR/boot-repair" "$SBIN_DEST"
|
|
echo " Installed."
|
|
|
|
# 3. Pacman hooks.
|
|
echo "[3/4] Installing pacman hooks into $HOOK_DIR..."
|
|
mkdir -p "$HOOK_DIR"
|
|
for hook in "$SCRIPT_DIR"/hooks/*.hook; do
|
|
install -Dm644 "$hook" "$HOOK_DIR/$(basename "$hook")"
|
|
echo " $(basename "$hook")"
|
|
done
|
|
|
|
# 4. Prove the installed copy runs. A recovery tool that was never executed is
|
|
# not installed, it is merely copied.
|
|
echo "[4/4] Verifying the installed copy..."
|
|
if "$SBIN_DEST" --dry-run; then
|
|
echo " boot-repair reports the system is consistent."
|
|
else
|
|
echo " boot-repair found problems (see above). Repair with: sudo $SBIN_DEST"
|
|
fi
|
|
|
|
echo "=== Installation complete ==="
|
|
echo
|
|
echo "Usage:"
|
|
echo " sudo boot-repair # repair now"
|
|
echo " sudo boot-repair --dry-run # report only"
|
|
echo
|
|
echo "In emergency mode the root filesystem is already mounted, so:"
|
|
echo " $SBIN_DEST"
|