diff options
| -rw-r--r-- | README.org | 62 | ||||
| -rwxr-xr-x | build.sh | 10 | ||||
| -rw-r--r-- | custom/40_zfs_snapshots | 13 | ||||
| -rw-r--r-- | custom/grub-zfs-snap | 160 | ||||
| -rwxr-xr-x | custom/install-archzfs | 21 | ||||
| -rw-r--r-- | custom/zz-grub-zfs-snap.hook | 22 |
6 files changed, 287 insertions, 1 deletions
@@ -21,6 +21,7 @@ manual module loading or package installation during the install process. - *Genesis Snapshot* - Automatic pristine-state snapshot after installation - *Rollback Script* - One-command factory reset via ~/root/rollback-to-genesis~ - *Pre-Pacman Snapshots* - Automatic snapshots before package operations +- *GRUB Snapshot Boot* - Boot into any ZFS snapshot directly from GRUB menu - *NetworkManager* - WiFi configuration copied to installed system - *SSH Ready* - Optional SSH with root login for headless servers - *LTS Kernel* - Uses linux-lts for stability with ZFS @@ -91,7 +92,10 @@ archzfs/ ├── build.sh # Main ISO build script ├── custom/ │ ├── install-archzfs # Interactive installation script -│ └── install-archzfs.conf.example # Example config for unattended install +│ ├── install-archzfs.conf.example # Example config for unattended install +│ ├── grub-zfs-snap # ZFS snapshot GRUB menu generator +│ ├── 40_zfs_snapshots # GRUB generator script (for /etc/grub.d/) +│ └── zz-grub-zfs-snap.hook # Pacman hook for auto-regeneration ├── scripts/ │ └── test-vm.sh # QEMU test VM launcher ├── profile/ # archiso profile (generated during build) @@ -408,6 +412,62 @@ If you enabled SSH during installation: * Post-Installation +** ZFS Snapshot Boot Entries (grub-zfs-snap) + +The installer configures GRUB to display bootable ZFS snapshots in the boot menu. +This allows you to boot directly into a previous system state without manually +rolling back. + +*** How It Works + +- A GRUB submenu "ZFS Snapshots" appears in the boot menu +- Shows up to 10 most recent snapshots of the root dataset +- Selecting a snapshot boots the system in read-only mode from that snapshot +- The running system sees the snapshot's files, but changes go to a temporary overlay + +*** When Snapshots Appear in GRUB + +The GRUB menu is automatically updated: +- After kernel updates (~linux-lts~) +- After ZFS package updates (~zfs-dkms~, ~zfs-utils~) +- After GRUB updates +- After any kernel module installation + +*** Manual Snapshot Management + +#+BEGIN_SRC bash +# Create a snapshot before making changes +zfs snapshot zroot/ROOT/default@before-experiment + +# Regenerate GRUB menu to include new snapshot +grub-zfs-snap + +# List snapshots that will appear in GRUB +grub-zfs-snap --list +#+END_SRC + +*** Customization + +Environment variables control behavior: + +#+BEGIN_SRC bash +# Change pool name (default: zroot) +POOL_NAME=mypool grub-zfs-snap + +# Change root dataset (default: ROOT/default) +ROOT_DATASET=ROOT/arch grub-zfs-snap + +# Show more snapshots in menu (default: 10) +MAX_SNAPSHOTS=20 grub-zfs-snap +#+END_SRC + +*** Booting from a Snapshot + +1. Reboot and select "ZFS Snapshots" from GRUB menu +2. Choose the desired snapshot by date/name +3. System boots with that snapshot's root filesystem +4. Files are read-only - to make permanent, rollback from normal boot + ** Genesis Snapshot The installer creates a recursive snapshot of the entire pool named ~genesis~. @@ -180,6 +180,13 @@ cp "$CUSTOM_DIR/install-archzfs" "$PROFILE_DIR/airootfs/usr/local/bin/" cp "$CUSTOM_DIR/install-claude" "$PROFILE_DIR/airootfs/usr/local/bin/" cp "$CUSTOM_DIR/archsetup-zfs" "$PROFILE_DIR/airootfs/usr/local/bin/" +# Copy grub-zfs-snap for ZFS snapshot boot entries +info "Copying grub-zfs-snap..." +cp "$CUSTOM_DIR/grub-zfs-snap" "$PROFILE_DIR/airootfs/usr/local/bin/" +mkdir -p "$PROFILE_DIR/airootfs/usr/local/share/grub-zfs-snap" +cp "$CUSTOM_DIR/40_zfs_snapshots" "$PROFILE_DIR/airootfs/usr/local/share/grub-zfs-snap/" +cp "$CUSTOM_DIR/zz-grub-zfs-snap.hook" "$PROFILE_DIR/airootfs/usr/local/share/grub-zfs-snap/" + # Copy example config for unattended installs mkdir -p "$PROFILE_DIR/airootfs/root" cp "$CUSTOM_DIR/install-archzfs.conf.example" "$PROFILE_DIR/airootfs/root/" @@ -197,6 +204,9 @@ if grep -q "file_permissions=" "$PROFILE_DIR/profiledef.sh"; then /)/ i\ ["/usr/local/bin/archsetup-zfs"]="0:0:755" }' "$PROFILE_DIR/profiledef.sh" sed -i '/^file_permissions=(/,/)/ { + /)/ i\ ["/usr/local/bin/grub-zfs-snap"]="0:0:755" + }' "$PROFILE_DIR/profiledef.sh" + sed -i '/^file_permissions=(/,/)/ { /)/ i\ ["/etc/shadow"]="0:0:400" }' "$PROFILE_DIR/profiledef.sh" fi 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 |
