blob: f1eacb494f9ffe2b068567bd189ec54f770f976e (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
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
|