126b03ad26
If `findmnt -no PARTUUID /` returns nothing (root on LVM, no GPT, unusual mount state) the script would silently write a broken resume=PARTUUID= line to /etc/kernel/cmdline. Bail with an error message instead. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
80 lines
2.7 KiB
Bash
Executable File
80 lines
2.7 KiB
Bash
Executable File
#!/bin/bash
|
|
# enable-hibernation.sh — one-time setup for laptop hibernation on btrfs.
|
|
# Idempotent: re-runs are no-ops once everything is in place. Run with sudo.
|
|
# After successful run, reboot before testing `systemctl hibernate`.
|
|
|
|
set -euo pipefail
|
|
|
|
if [ "$EUID" -ne 0 ]; then
|
|
echo "Run as root: sudo bash $0" >&2
|
|
exit 1
|
|
fi
|
|
|
|
SWAPFILE=/swapfile
|
|
SWAPSIZE=16g
|
|
ROOT_PARTUUID=$(findmnt -no PARTUUID /)
|
|
[ -n "$ROOT_PARTUUID" ] || { echo "ERROR: could not determine root PARTUUID via findmnt." >&2; exit 1; }
|
|
|
|
echo "==> Step 1/6: ensure swap file exists at $SWAPFILE (size $SWAPSIZE)"
|
|
if [ ! -f "$SWAPFILE" ]; then
|
|
btrfs filesystem mkswapfile --size "$SWAPSIZE" "$SWAPFILE"
|
|
echo " created $SWAPFILE"
|
|
else
|
|
actual_bytes=$(stat -c %s "$SWAPFILE")
|
|
min_bytes=$((14 * 1024 * 1024 * 1024))
|
|
if [ "$actual_bytes" -lt "$min_bytes" ]; then
|
|
echo "ERROR: $SWAPFILE exists but is smaller than 14 GiB (got $actual_bytes bytes)." >&2
|
|
echo " Remove it manually and re-run." >&2
|
|
exit 1
|
|
fi
|
|
echo " already exists ($((actual_bytes / 1024 / 1024 / 1024)) GiB) — skipping creation"
|
|
fi
|
|
|
|
echo "==> Step 2/6: activate swap and persist in fstab"
|
|
if ! swapon --show | grep -q "^$SWAPFILE "; then
|
|
swapon "$SWAPFILE"
|
|
echo " swapon $SWAPFILE"
|
|
else
|
|
echo " already active — skipping swapon"
|
|
fi
|
|
if ! grep -qE "^$SWAPFILE\s" /etc/fstab; then
|
|
printf '%s\tnone\tswap\tdefaults\t0 0\n' "$SWAPFILE" >> /etc/fstab
|
|
echo " appended to /etc/fstab"
|
|
else
|
|
echo " /etc/fstab already references $SWAPFILE — skipping"
|
|
fi
|
|
|
|
echo "==> Step 3/6: compute resume params"
|
|
RESUME_OFFSET=$(btrfs inspect-internal map-swapfile -r "$SWAPFILE")
|
|
echo " resume=PARTUUID=$ROOT_PARTUUID"
|
|
echo " resume_offset=$RESUME_OFFSET"
|
|
|
|
echo "==> Step 4/6: ensure 'resume' hook in /etc/mkinitcpio.conf"
|
|
if grep -qE "^HOOKS=\(.*\bresume\b.*\)" /etc/mkinitcpio.conf; then
|
|
echo " resume hook already present — skipping"
|
|
else
|
|
sed -i -E 's/(^HOOKS=\(.*\bblock\b)/\1 resume/' /etc/mkinitcpio.conf
|
|
if ! grep -qE "^HOOKS=\(.*\bresume\b.*\)" /etc/mkinitcpio.conf; then
|
|
echo "ERROR: failed to insert 'resume' hook. Check /etc/mkinitcpio.conf manually." >&2
|
|
exit 1
|
|
fi
|
|
echo " inserted 'resume' after 'block'"
|
|
fi
|
|
|
|
echo "==> Step 5/6: append resume params to /etc/kernel/cmdline"
|
|
if grep -q "resume=" /etc/kernel/cmdline; then
|
|
echo " cmdline already contains resume= — skipping"
|
|
else
|
|
sed -i "1 s|\$| resume=PARTUUID=$ROOT_PARTUUID resume_offset=$RESUME_OFFSET|" /etc/kernel/cmdline
|
|
echo " appended to /etc/kernel/cmdline"
|
|
fi
|
|
|
|
echo "==> Step 6/6: regenerate UKI"
|
|
mkinitcpio -P
|
|
|
|
echo
|
|
echo "Done. Reboot to activate hibernation:"
|
|
echo " sudo reboot"
|
|
echo
|
|
echo "After reboot, test with: systemctl hibernate"
|