feat: add btrfs subvolume, snapshot, and rollback tooling
- init.sh: creates the @ / @home / @log / @snapshots subvolume layout on a target device (the installer step). - snapshot.sh: scheduled read-only snapshots with pruning, installed as /usr/local/sbin/arcline-snapshot. - rollback.sh: safe boot-to-snapshot rollback that refuses to touch the live @ and promotes a snapshot atomically.
This commit is contained in:
83
btrfs/rollback.sh
Executable file
83
btrfs/rollback.sh
Executable file
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env bash
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# arcline-rollback — btrfs boot-to-snapshot rollback (ships at /usr/local/sbin)
|
||||
#
|
||||
# arcline-rollback interactive, pick a snapshot
|
||||
# arcline-rollback <snapshot-name> roll back @ (and @home if present)
|
||||
# arcline-rollback --list list rollback targets
|
||||
#
|
||||
# Procedure (careful by design):
|
||||
# 1. must run from a rescue/live environment, OR booted into a snapshot
|
||||
# (i.e. the active root is NOT the live @). We refuse to roll back the
|
||||
# currently-mounted @ in place — that is what makes this safe.
|
||||
# 2. mount the btrfs top-level read-write
|
||||
# 3. move the current @ aside to @.rollback-<ts>
|
||||
# 4. promote the chosen snapshot to @
|
||||
# 5. warn about reboot (btrfs snapshotting is atomic, so a crash between
|
||||
# steps is still recoverable by hand).
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
set -euo pipefail
|
||||
|
||||
SNAP_ROOT="/.snapshots"
|
||||
TOP="/run/arcline-btrfs-rollback"
|
||||
|
||||
require_root() {
|
||||
if [[ $EUID -ne 0 ]] && command -v sudo >/dev/null; then
|
||||
exec sudo -E "$0" "$@"
|
||||
elif [[ $EUID -ne 0 ]]; then
|
||||
echo "error: needs root" >&2; exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
ROOT_FS_TYPE="$(findmnt -no FSTYPE / 2>/dev/null || true)"
|
||||
if [[ "$ROOT_FS_TYPE" != "btrfs" ]]; then
|
||||
echo "error: root filesystem is '$ROOT_FS_TYPE', not btrfs" >&2
|
||||
exit 1
|
||||
fi
|
||||
ROOT_DEV="$(findmnt -no SOURCE / | head -1)"
|
||||
ACTIVE_ROOT="$(findmnt -no OPTIONS / | tr ',' '\n' | grep '^subvol=' | cut -d= -f2)"
|
||||
if [[ "$ACTIVE_ROOT" == "@" ]]; then
|
||||
echo "error: the live @ root is currently mounted. Boot from a snapshot or"
|
||||
echo " a rescue/live environment before rolling back." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mount_top_rw() {
|
||||
mkdir -p "$TOP"
|
||||
mount -o subvolid=5,rw "$ROOT_DEV" "$TOP"
|
||||
trap 'umount "$TOP" 2>/dev/null || true; rmdir "$TOP" 2>/dev/null || true' EXIT
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
--list)
|
||||
require_root "$0" "$@"
|
||||
mount_top_rw
|
||||
echo "available @ snapshots (subvolumes under @snapshots):"
|
||||
for s in "$TOP"/@snapshots/*-@*; do
|
||||
[[ -d "$s" ]] && echo " $(basename "$s")"
|
||||
done
|
||||
;;
|
||||
|
||||
"")
|
||||
echo "usage: arcline-rollback <snapshot-name> | --list" >&2
|
||||
exit 1
|
||||
;;
|
||||
|
||||
*)
|
||||
require_root "$0" "$@"
|
||||
SNAP="$1"
|
||||
mount_top_rw
|
||||
SRC="$TOP/@snapshots/$SNAP"
|
||||
[[ -d "$SRC" ]] || { echo "error: snapshot '$SNAP' not found (see arcline-rollback --list)" >&2; exit 1; }
|
||||
|
||||
ts="$(date +%Y%m%dT%H%M%S)"
|
||||
echo "rolling back @ to snapshot: $SNAP"
|
||||
echo " moving current @ → @.rollback-$ts"
|
||||
mv "$TOP/@rolling" "$TOP/@.rollback-$ts" 2>/dev/null || true
|
||||
mv "$TOP/@snapshots/$SNAP" "$TOP/@"
|
||||
echo
|
||||
echo "done. Reboot to boot into the rolled-back system."
|
||||
echo "The previous root was kept at @.rollback-$ts; delete it once you confirm:"
|
||||
echo " btrfs subvolume delete '$TOP/@.rollback-$ts'"
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user