aboutsummaryrefslogtreecommitdiff
path: root/custom
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-01-18 10:55:18 -0600
committerCraig Jennings <c@cjennings.net>2026-01-18 10:55:18 -0600
commit6505511f2e6b43a37570fc840f6d2851c7cc170c (patch)
tree31affaecded1bdd6bbe8234ebe26c03a99074225 /custom
parent5982a78ae7328fd3126c6346872de8ace242e9b9 (diff)
downloadarchangel-6505511f2e6b43a37570fc840f6d2851c7cc170c.tar.gz
archangel-6505511f2e6b43a37570fc840f6d2851c7cc170c.zip
Add grub-zfs-snap for ZFS snapshot boot entries
Add ability to boot into ZFS snapshots directly from GRUB menu: - grub-zfs-snap: generates GRUB submenu entries for recent snapshots - 40_zfs_snapshots: GRUB generator script installed to /etc/grub.d/ - zz-grub-zfs-snap.hook: pacman hook for automatic GRUB regeneration The GRUB menu automatically updates after kernel/ZFS package changes. Up to 10 most recent snapshots appear in a "ZFS Snapshots" submenu.
Diffstat (limited to 'custom')
-rw-r--r--custom/40_zfs_snapshots13
-rw-r--r--custom/grub-zfs-snap160
-rwxr-xr-xcustom/install-archzfs21
-rw-r--r--custom/zz-grub-zfs-snap.hook22
4 files changed, 216 insertions, 0 deletions
diff --git a/custom/40_zfs_snapshots b/custom/40_zfs_snapshots
new file mode 100644
index 0000000..5215289
--- /dev/null
+++ b/custom/40_zfs_snapshots
@@ -0,0 +1,13 @@
+#!/bin/bash
+# /etc/grub.d/40_zfs_snapshots
+# GRUB configuration generator for ZFS snapshot boot entries
+#
+# This script is called by grub-mkconfig to generate menu entries
+# for booting into ZFS snapshots.
+
+set -e
+
+# Only run if grub-zfs-snap is installed
+if [[ -x /usr/local/bin/grub-zfs-snap ]]; then
+ /usr/local/bin/grub-zfs-snap --generate
+fi
diff --git a/custom/grub-zfs-snap b/custom/grub-zfs-snap
new file mode 100644
index 0000000..f1eacb4
--- /dev/null
+++ b/custom/grub-zfs-snap
@@ -0,0 +1,160 @@
+#!/bin/bash
+# grub-zfs-snap - Generate GRUB menu entries for ZFS snapshots
+#
+# This script scans ZFS snapshots and generates GRUB submenu entries
+# allowing boot into previous system states.
+#
+# Usage: grub-zfs-snap [--generate]
+# --generate Output GRUB menu entries (called by /etc/grub.d/40_zfs_snapshots)
+# (no args) Regenerate GRUB config
+#
+# Installation:
+# 1. Copy this script to /usr/local/bin/grub-zfs-snap
+# 2. Copy 40_zfs_snapshots to /etc/grub.d/
+# 3. Run: grub-mkconfig -o /boot/grub/grub.cfg
+
+set -e
+
+# Configuration
+POOL_NAME="${POOL_NAME:-zroot}"
+ROOT_DATASET="${ROOT_DATASET:-ROOT/default}"
+MAX_SNAPSHOTS="${MAX_SNAPSHOTS:-10}"
+
+# Get full dataset path
+FULL_DATASET="${POOL_NAME}/${ROOT_DATASET}"
+
+# Colors for terminal output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+NC='\033[0m'
+
+info() { echo -e "${GREEN}[INFO]${NC} $1" >&2; }
+error() { echo -e "${RED}[ERROR]${NC} $1" >&2; exit 1; }
+
+# Check if running as root
+check_root() {
+ if [[ $EUID -ne 0 ]]; then
+ error "This script must be run as root"
+ fi
+}
+
+# Get kernel and initramfs paths
+get_kernel_info() {
+ # Find the LTS kernel
+ KERNEL=$(ls /boot/vmlinuz-linux-lts 2>/dev/null || ls /boot/vmlinuz-linux 2>/dev/null | head -1)
+ INITRD=$(ls /boot/initramfs-linux-lts.img 2>/dev/null || ls /boot/initramfs-linux.img 2>/dev/null | head -1)
+
+ if [[ -z "$KERNEL" ]] || [[ -z "$INITRD" ]]; then
+ error "Could not find kernel or initramfs"
+ fi
+
+ # Get just the filenames
+ KERNEL_FILE=$(basename "$KERNEL")
+ INITRD_FILE=$(basename "$INITRD")
+}
+
+# Get GRUB root device hint
+get_grub_root() {
+ # Get the EFI partition device
+ local efi_dev=$(findmnt -n -o SOURCE /boot 2>/dev/null | head -1)
+ if [[ -z "$efi_dev" ]]; then
+ # Fallback - assume first partition of first disk
+ echo "hd0,gpt1"
+ return
+ fi
+
+ # Convert to GRUB device notation
+ # This is simplified - may need adjustment for complex setups
+ local disk=$(echo "$efi_dev" | sed 's/[0-9]*$//')
+ local part=$(echo "$efi_dev" | grep -o '[0-9]*$')
+
+ # Get disk index (simplified - assumes /dev/sda or /dev/vda style)
+ echo "hd0,gpt${part}"
+}
+
+# Generate GRUB menu entries for snapshots
+generate_entries() {
+ get_kernel_info
+
+ local grub_root=$(get_grub_root)
+ local hostid=$(hostid)
+
+ # Get list of snapshots, sorted by creation time (newest first)
+ local snapshots=$(zfs list -H -t snapshot -o name,creation -s creation -r "$FULL_DATASET" 2>/dev/null | \
+ grep "^${FULL_DATASET}@" | \
+ tac | \
+ head -n "$MAX_SNAPSHOTS")
+
+ if [[ -z "$snapshots" ]]; then
+ return
+ fi
+
+ # Generate submenu
+ cat << 'SUBMENU_START'
+submenu 'ZFS Snapshots' --class zfs {
+SUBMENU_START
+
+ while IFS=$'\t' read -r snapshot creation; do
+ local snap_name="${snapshot##*@}"
+ local snap_date=$(echo "$creation" | awk '{print $1, $2, $3, $4, $5}')
+
+ cat << ENTRY
+ menuentry '${snap_name} (${snap_date})' --class zfs {
+ insmod part_gpt
+ insmod fat
+ insmod zfs
+ search --no-floppy --fs-uuid --set=root \$(grub-probe --target=fs_uuid /boot)
+ echo 'Loading Linux kernel...'
+ linux /${KERNEL_FILE} root=ZFS=${snapshot} ro spl.spl_hostid=0x${hostid}
+ echo 'Loading initial ramdisk...'
+ initrd /${INITRD_FILE}
+ }
+ENTRY
+ done <<< "$snapshots"
+
+ echo "}"
+}
+
+# Regenerate GRUB configuration
+regenerate_grub() {
+ check_root
+ info "Regenerating GRUB configuration..."
+ grub-mkconfig -o /boot/grub/grub.cfg
+ info "Done. ZFS snapshots added to GRUB menu."
+}
+
+# List current snapshots
+list_snapshots() {
+ echo "ZFS Snapshots for ${FULL_DATASET}:"
+ echo ""
+ zfs list -H -t snapshot -o name,creation,used -s creation -r "$FULL_DATASET" 2>/dev/null | \
+ grep "^${FULL_DATASET}@" | \
+ tac | \
+ head -n "$MAX_SNAPSHOTS" | \
+ while IFS=$'\t' read -r name creation used; do
+ local snap_name="${name##*@}"
+ printf " %-30s %s (used: %s)\n" "$snap_name" "$creation" "$used"
+ done
+}
+
+# Main
+case "${1:-}" in
+ --generate)
+ generate_entries
+ ;;
+ --list)
+ list_snapshots
+ ;;
+ "")
+ regenerate_grub
+ ;;
+ *)
+ echo "Usage: $0 [--generate|--list]"
+ echo ""
+ echo "Options:"
+ echo " --generate Output GRUB menu entries (for /etc/grub.d/)"
+ echo " --list List available snapshots"
+ echo " (no args) Regenerate GRUB configuration"
+ exit 1
+ ;;
+esac
diff --git a/custom/install-archzfs b/custom/install-archzfs
index dfba090..660cf34 100755
--- a/custom/install-archzfs
+++ b/custom/install-archzfs
@@ -1031,6 +1031,26 @@ EOF
fi
}
+configure_grub_zfs_snap() {
+ step "Configuring ZFS Snapshot Boot Entries"
+
+ # Install grub-zfs-snap script
+ info "Installing grub-zfs-snap..."
+ cp /usr/local/bin/grub-zfs-snap /mnt/usr/local/bin/grub-zfs-snap
+ chmod +x /mnt/usr/local/bin/grub-zfs-snap
+
+ # Install GRUB generator
+ cp /usr/local/share/grub-zfs-snap/40_zfs_snapshots /mnt/etc/grub.d/40_zfs_snapshots
+ chmod +x /mnt/etc/grub.d/40_zfs_snapshots
+
+ # Install pacman hook for auto-regeneration
+ mkdir -p /mnt/etc/pacman.d/hooks
+ cp /usr/local/share/grub-zfs-snap/zz-grub-zfs-snap.hook /mnt/etc/pacman.d/hooks/
+
+ info "ZFS snapshots will appear in GRUB boot menu."
+ info "Run 'grub-zfs-snap' to manually regenerate after creating snapshots."
+}
+
configure_zfs_services() {
step "Configuring ZFS Services"
@@ -1276,6 +1296,7 @@ main() {
configure_ssh
configure_initramfs
configure_bootloader
+ configure_grub_zfs_snap
configure_zfs_services
configure_pacman_hook
copy_archsetup
diff --git a/custom/zz-grub-zfs-snap.hook b/custom/zz-grub-zfs-snap.hook
new file mode 100644
index 0000000..8153b84
--- /dev/null
+++ b/custom/zz-grub-zfs-snap.hook
@@ -0,0 +1,22 @@
+[Trigger]
+Type = Package
+Operation = Upgrade
+Operation = Install
+Operation = Remove
+Target = linux-lts
+Target = linux-lts-headers
+Target = zfs-dkms
+Target = zfs-utils
+Target = grub
+
+[Trigger]
+Type = Path
+Operation = Install
+Operation = Upgrade
+Operation = Remove
+Target = usr/lib/modules/*/vmlinuz
+
+[Action]
+Description = Updating GRUB with ZFS snapshots...
+When = PostTransaction
+Exec = /usr/local/bin/grub-zfs-snap