diff options
| author | Craig Jennings <c@cjennings.net> | 2026-04-27 18:33:03 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-04-27 18:33:03 -0500 |
| commit | 422d1098cd89beaeed81cc40488252233e2ca0ad (patch) | |
| tree | 7ea92619f7a76bc797851776cf1901d91b1e458f /installer/zfsrollback | |
| parent | ea494c7d0fc41bb1cab888f92408fab29c190e75 (diff) | |
| download | archangel-422d1098cd89beaeed81cc40488252233e2ca0ad.tar.gz archangel-422d1098cd89beaeed81cc40488252233e2ca0ad.zip | |
feat: consolidate zfssnapshot and zfsrollback into one subcommand-driven script
Problem: zfssnapshot and zfsrollback were two separate scripts with overlapping pre-flight checks (zfs / fzf / root) and parallel UX patterns (description sanitization in one, fzf selection in the other). Users had to remember which script was for which operation, and a "list" view meant typing the raw `zfs list -t snapshot` command. There was no path to destroy individual snapshots short of `zfs destroy` directly, which is dangerous without a confirmation flow.
Solution: rewrite zfssnapshot as a single multi-subcommand script (list, create, rollback, delete). Drop installer/zfsrollback. The new script uses a source-guard at the bottom (`if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@"; fi`) so bats can source it without triggering the install-time pre-flight checks, matching the pattern in installer/archangel.
Pure helpers (sanitize_description, validate_description, format_snapshot_name) get extracted as named functions so they're testable in isolation. The destructive flows (rollback, delete) keep the explicit "yes" confirmation prompt, the genesis-snapshot warning, and the recursive-rollback-destroys-newer-snapshots warning. Delete uses fzf --multi so the user can pick several snapshot names at once.
Updated build.sh to copy only the consolidated script. Dropped the zfsrollback profiledef permission line. Updated Makefile, README, scripts/sanity-test.sh, and testing-strategy.org to reflect the single-script layout.
Bats: 147 → 168 (+21). Coverage spans sanitize_description (normal / boundary / error), validate_description (alphanumerics, hyphens, underscores accepted; spaces, slashes, shell metacharacters, empty rejected), format_snapshot_name (timestamp + description composition), and main subcommand dispatch (list / create / rollback / delete / help / unknown). Lint clean. The zfs-, fzf-, and arch-chroot-shelling subcommand bodies stay VM-tested per testing-strategy.org.
Diffstat (limited to 'installer/zfsrollback')
| -rwxr-xr-x | installer/zfsrollback | 179 |
1 files changed, 0 insertions, 179 deletions
diff --git a/installer/zfsrollback b/installer/zfsrollback deleted file mode 100755 index a99a4d3..0000000 --- a/installer/zfsrollback +++ /dev/null @@ -1,179 +0,0 @@ -#!/usr/bin/env bash -# Craig Jennings (github.com/cjennings) -# Roll back ZFS datasets to a selected snapshot using fzf. - -set -euo pipefail - -# Usage info -show_help() { - cat << EOF -Usage: ${0##*/} [-h] [-s] -Roll back ZFS datasets to a selected snapshot. - - -h display this help and exit - -s single dataset mode (roll back only the selected dataset, - not all datasets with matching snapshot name) - -By default, rolling back a snapshot will roll back ALL datasets that share -that snapshot name. Use -s for single dataset rollback. - -WARNING: Rolling back destroys all data and snapshots newer than the target. - This operation cannot be undone! - -Requires: fzf, zfs -EOF -} - -# Check dependencies -for cmd in zfs fzf; do - if ! command -v "$cmd" &> /dev/null; then - echo "Error: $cmd command not found" - exit 1 - fi -done - -# Check for root/sudo -if [ "$EUID" -ne 0 ]; then - echo "Error: This script must be run as root (use sudo)" - exit 1 -fi - -# Parse arguments -single_mode=false -while getopts ":hs" opt; do - case ${opt} in - h) - show_help - exit 0 - ;; - s) - single_mode=true - ;; - \?) - echo "Invalid option: -$OPTARG" >&2 - show_help - exit 1 - ;; - esac -done - -# Get all snapshots -snapshots=$(zfs list -t snapshot -H -o name 2>/dev/null) - -if [ -z "$snapshots" ]; then - echo "No ZFS snapshots found" - exit 0 -fi - -if $single_mode; then - # Single mode: show full dataset@snapshot names (sorted newest first) - selected=$(zfs list -t snapshot -H -o name -S creation | fzf --height=70% --reverse \ - --header="Select snapshot to roll back (ESC to cancel)" \ - --preview="zfs list -t snapshot -o name,creation,used,refer -r {1}" \ - --preview-window=down:5) - - if [ -z "$selected" ]; then - echo "No snapshot selected, exiting" - exit 0 - fi - - dataset="${selected%@*}" - snap_name="${selected#*@}" - targets=("$selected") -else - # Multi mode: show unique snapshot names, roll back all matching datasets - # Sort reverse so newest (latest date) appears at top - unique_snaps=$(echo "$snapshots" | sed 's/.*@//' | sort -ru) - - snap_name=$(echo "$unique_snaps" | fzf --height=70% --reverse \ - --header="Select snapshot name to roll back ALL matching datasets (ESC to cancel)" \ - --preview="zfs list -t snapshot -o name,creation,used -H | grep '@{}$' | column -t" \ - --preview-window=down:10) - - if [ -z "$snap_name" ]; then - echo "No snapshot selected, exiting" - exit 0 - fi - - # Find all datasets with this snapshot, sorted by depth (deepest first) - # This ensures children are rolled back before parents - mapfile -t targets < <(echo "$snapshots" | grep "@${snap_name}$" | awk -F'@' '{print length($1), $0}' | sort -rn | cut -d' ' -f2-) - - if [ ${#targets[@]} -eq 0 ]; then - echo "Error: No datasets found with snapshot @${snap_name}" - exit 1 - fi -fi - -# Display what will happen -echo "" -echo "═══════════════════════════════════════════════════════════════════" -echo " ⚠️ WARNING ⚠️" -echo "═══════════════════════════════════════════════════════════════════" -echo "" - -# Special warning for genesis rollback -if [[ "$snap_name" == "genesis" ]]; then - echo " 🚨 GENESIS ROLLBACK DETECTED 🚨" - echo "" - echo " Rolling back to genesis will destroy ALL changes since installation!" - echo " This includes all packages installed, configurations, and user data." - echo "" -fi - -echo "You are about to roll back to snapshot: @${snap_name}" -echo "" -echo "The following datasets will be rolled back:" -for target in "${targets[@]}"; do - dataset="${target%@*}" - echo " • $dataset" - - # Show newer snapshots that will be destroyed - newer=$(zfs list -t snapshot -H -o name -S creation "$dataset" 2>/dev/null | \ - awk -v snap="$target" 'found {print " ✗ " $0 " (will be DESTROYED)"} $0 == snap {found=1}') - if [ -n "$newer" ]; then - echo "$newer" - fi -done - -echo "" -echo "═══════════════════════════════════════════════════════════════════" -echo " THIS OPERATION CANNOT BE UNDONE!" -echo " All data written after the snapshot will be permanently lost." -echo " All snapshots newer than the target will be destroyed." -echo "═══════════════════════════════════════════════════════════════════" -echo "" - -# Require explicit confirmation -read -r -p "Type 'yes' to confirm rollback: " confirmation - -if [ "$confirmation" != "yes" ]; then - echo "Rollback cancelled" - exit 0 -fi - -echo "" -echo "Rolling back..." - -# Perform rollbacks -failed=0 -for target in "${targets[@]}"; do - dataset="${target%@*}" - echo -n " Rolling back $dataset... " - if zfs rollback -r "$target" 2>&1; then - echo "✓" - else - echo "✗ FAILED" - ((failed++)) - fi -done - -echo "" -if [ $failed -eq 0 ]; then - echo "Rollback complete." - echo "" - echo "Note: ZFSBootMenu auto-detects snapshots - no menu regeneration needed." -else - echo "Rollback completed with $failed failure(s)" - exit 1 -fi |
