blob: bb948fb951019577ade5ab50f804e7b8908dbce9 (
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
|
#!/usr/bin/env bash
# zfs.sh - ZFS-specific functions for archangel installer
# Source this file after common.sh, config.sh, disk.sh
#############################
# ZFS Constants
#############################
POOL_NAME="${POOL_NAME:-zroot}"
ASHIFT="${ASHIFT:-12}"
COMPRESSION="${COMPRESSION:-zstd}"
#############################
# ZFS Pre-flight
#############################
zfs_preflight() {
# Check ZFS module
if ! lsmod | grep -q zfs; then
info "Loading ZFS module..."
modprobe zfs || error "Failed to load ZFS module. Is zfs-linux-lts installed?"
fi
info "ZFS module loaded successfully."
}
#############################
# ZFS Pool Creation
#############################
create_zfs_pool() {
local encryption="${1:-true}"
local passphrase="$2"
step "Creating ZFS Pool"
# Destroy existing pool if present
if zpool list "$POOL_NAME" &>/dev/null; then
warn "Pool $POOL_NAME already exists. Destroying..."
zpool destroy -f "$POOL_NAME"
fi
# Get root partitions
local zfs_parts=()
for disk in "${SELECTED_DISKS[@]}"; do
zfs_parts+=("$(get_root_partition "$disk")")
done
# Build pool configuration based on RAID level
local pool_config
if [[ "$RAID_LEVEL" == "stripe" ]]; then
pool_config="${zfs_parts[*]}"
info "Creating striped pool with ${#zfs_parts[@]} disks (NO redundancy)..."
warn "Data loss will occur if ANY disk fails!"
elif [[ -n "$RAID_LEVEL" ]]; then
pool_config="$RAID_LEVEL ${zfs_parts[*]}"
info "Creating $RAID_LEVEL pool with ${#zfs_parts[@]} disks..."
else
pool_config="${zfs_parts[0]}"
info "Creating single-disk pool..."
fi
# Base pool options
local pool_opts=(
-f
-o ashift="$ASHIFT"
-o autotrim=on
-O acltype=posixacl
-O atime=off
-O canmount=off
-O compression="$COMPRESSION"
-O dnodesize=auto
-O normalization=formD
-O relatime=on
-O xattr=sa
-O mountpoint=none
-R /mnt
)
# Create pool (with or without encryption)
if [[ "$encryption" == "false" ]]; then
warn "Creating pool WITHOUT encryption"
zpool create "${pool_opts[@]}" "$POOL_NAME" $pool_config
else
info "Creating encrypted pool..."
echo "$passphrase" | zpool create "${pool_opts[@]}" \
-O encryption=aes-256-gcm \
-O keyformat=passphrase \
-O keylocation=prompt \
"$POOL_NAME" $pool_config
fi
info "ZFS pool created successfully."
zpool status "$POOL_NAME"
}
#############################
# ZFS Dataset Creation
#############################
create_zfs_datasets() {
step "Creating ZFS Datasets"
# Root dataset container
zfs create -o mountpoint=none -o canmount=off "$POOL_NAME/ROOT"
# Calculate reservation (20% of pool, capped 5-20G)
local pool_size_bytes
pool_size_bytes=$(zpool get -Hp size "$POOL_NAME" | awk '{print $3}')
local pool_size_gb=$((pool_size_bytes / 1024 / 1024 / 1024))
local reserve_gb=$((pool_size_gb / 5))
[[ $reserve_gb -gt 20 ]] && reserve_gb=20
[[ $reserve_gb -lt 5 ]] && reserve_gb=5
# Main root filesystem
zfs create -o mountpoint=/ -o canmount=noauto -o reservation=${reserve_gb}G "$POOL_NAME/ROOT/default"
zfs mount "$POOL_NAME/ROOT/default"
# Home
zfs create -o mountpoint=/home "$POOL_NAME/home"
zfs create -o mountpoint=/root "$POOL_NAME/home/root"
# Media - compression off for already-compressed files
zfs create -o mountpoint=/media -o compression=off "$POOL_NAME/media"
# VMs - 64K recordsize for VM disk images
zfs create -o mountpoint=/vms -o recordsize=64K "$POOL_NAME/vms"
# Var datasets
zfs create -o mountpoint=/var -o canmount=off "$POOL_NAME/var"
zfs create -o mountpoint=/var/log "$POOL_NAME/var/log"
zfs create -o mountpoint=/var/cache "$POOL_NAME/var/cache"
zfs create -o mountpoint=/var/lib -o canmount=off "$POOL_NAME/var/lib"
zfs create -o mountpoint=/var/lib/pacman "$POOL_NAME/var/lib/pacman"
zfs create -o mountpoint=/var/lib/docker "$POOL_NAME/var/lib/docker"
# Temp directories - excluded from snapshots
zfs create -o mountpoint=/var/tmp -o com.sun:auto-snapshot=false "$POOL_NAME/var/tmp"
zfs create -o mountpoint=/tmp -o com.sun:auto-snapshot=false "$POOL_NAME/tmp"
chmod 1777 /mnt/tmp /mnt/var/tmp
info "Datasets created:"
zfs list -r "$POOL_NAME" -o name,mountpoint,compression
}
#############################
# ZFSBootMenu Configuration
#############################
configure_zfsbootmenu() {
step "Configuring ZFSBootMenu"
# Ensure hostid exists
if [[ ! -f /etc/hostid ]]; then
zgenhostid
fi
local host_id
host_id=$(hostid)
# Copy hostid to installed system
cp /etc/hostid /mnt/etc/hostid
# Create ZFSBootMenu directory on EFI
mkdir -p /mnt/efi/EFI/ZBM
# Download ZFSBootMenu release EFI binary
info "Downloading ZFSBootMenu..."
local zbm_url="https://get.zfsbootmenu.org/efi"
if ! curl -fsSL -o /mnt/efi/EFI/ZBM/zfsbootmenu.efi "$zbm_url"; then
error "Failed to download ZFSBootMenu"
fi
info "ZFSBootMenu binary installed."
# Set kernel command line on the ROOT PARENT dataset
local cmdline="rw loglevel=3"
# Add AMD GPU workarounds if needed
if lspci | grep -qi "amd.*display\|amd.*vga"; then
info "AMD GPU detected - adding workaround parameters"
cmdline="$cmdline amdgpu.pg_mask=0 amdgpu.cwsr_enable=0"
fi
zfs set org.zfsbootmenu:commandline="$cmdline" "$POOL_NAME/ROOT"
info "Kernel command line set on $POOL_NAME/ROOT"
# Set bootfs property
zpool set bootfs="$POOL_NAME/ROOT/default" "$POOL_NAME"
info "Default boot filesystem set to $POOL_NAME/ROOT/default"
# Create EFI boot entries for each disk
local zbm_cmdline="spl_hostid=0x${host_id} zbm.timeout=3 zbm.prefer=${POOL_NAME} zbm.import_policy=hostid"
for i in "${!SELECTED_DISKS[@]}"; do
local disk="${SELECTED_DISKS[$i]}"
local label="ZFSBootMenu"
if [[ ${#SELECTED_DISKS[@]} -gt 1 ]]; then
label="ZFSBootMenu-disk$((i+1))"
fi
info "Creating EFI boot entry: $label on $disk"
efibootmgr --create \
--disk "$disk" \
--part 1 \
--label "$label" \
--loader '\EFI\ZBM\zfsbootmenu.efi' \
--unicode "$zbm_cmdline" \
--quiet
done
# Set as primary boot option
local bootnum
bootnum=$(efibootmgr | grep "ZFSBootMenu" | head -1 | grep -oP 'Boot\K[0-9A-F]+')
if [[ -n "$bootnum" ]]; then
local current_order
current_order=$(efibootmgr | grep "BootOrder" | cut -d: -f2 | tr -d ' ')
efibootmgr --bootorder "$bootnum,$current_order" --quiet
info "ZFSBootMenu set as primary boot option"
fi
info "ZFSBootMenu configuration complete."
}
#############################
# ZFS Services
#############################
configure_zfs_services() {
step "Configuring ZFS Services"
arch-chroot /mnt systemctl enable zfs.target
arch-chroot /mnt systemctl disable zfs-import-cache.service
arch-chroot /mnt systemctl enable zfs-import-scan.service
arch-chroot /mnt systemctl enable zfs-mount.service
arch-chroot /mnt systemctl enable zfs-import.target
# Disable cachefile - we use zfs-import-scan
zpool set cachefile=none "$POOL_NAME"
rm -f /mnt/etc/zfs/zpool.cache
info "ZFS services configured."
}
#############################
# Pacman Snapshot Hook
#############################
configure_zfs_pacman_hook() {
step "Configuring Pacman Snapshot Hook"
mkdir -p /mnt/etc/pacman.d/hooks
cat > /mnt/etc/pacman.d/hooks/zfs-snapshot.hook << EOF
[Trigger]
Operation = Upgrade
Operation = Install
Operation = Remove
Type = Package
Target = *
[Action]
Description = Creating ZFS snapshot before pacman transaction...
When = PreTransaction
Exec = /usr/local/bin/zfs-pre-snapshot
EOF
cat > /mnt/usr/local/bin/zfs-pre-snapshot << 'EOF'
#!/bin/bash
POOL="zroot"
DATASET="$POOL/ROOT/default"
TIMESTAMP=$(date +%Y-%m-%d_%H-%M-%S)
SNAPSHOT_NAME="pre-pacman_$TIMESTAMP"
if zfs snapshot "$DATASET@$SNAPSHOT_NAME"; then
echo "Created snapshot: $DATASET@$SNAPSHOT_NAME"
else
echo "Warning: Failed to create snapshot" >&2
fi
EOF
chmod +x /mnt/usr/local/bin/zfs-pre-snapshot
info "Pacman hook configured."
}
#############################
# ZFS Tools
#############################
install_zfs_tools() {
step "Installing ZFS Management Tools"
# Copy ZFS management scripts
cp /usr/local/bin/zfssnapshot /mnt/usr/local/bin/zfssnapshot
cp /usr/local/bin/zfsrollback /mnt/usr/local/bin/zfsrollback
chmod +x /mnt/usr/local/bin/zfssnapshot
chmod +x /mnt/usr/local/bin/zfsrollback
info "ZFS management scripts installed: zfssnapshot, zfsrollback"
}
#############################
# EFI Sync (Multi-disk)
#############################
sync_zfs_efi_partitions() {
local efi_parts=()
for disk in "${SELECTED_DISKS[@]}"; do
efi_parts+=("$(get_efi_partition "$disk")")
done
# Skip if only one disk
[[ ${#efi_parts[@]} -le 1 ]] && return
step "Syncing EFI partitions for redundancy"
local primary="${efi_parts[0]}"
for ((i=1; i<${#efi_parts[@]}; i++)); do
local secondary="${efi_parts[$i]}"
local tmp_mount="/tmp/efi_sync_$$"
mkdir -p "$tmp_mount"
mount "$secondary" "$tmp_mount"
rsync -a /mnt/efi/ "$tmp_mount/"
umount "$tmp_mount"
rmdir "$tmp_mount"
info "Synced EFI to $secondary"
done
}
#############################
# Genesis Snapshot
#############################
create_zfs_genesis_snapshot() {
step "Creating Genesis Snapshot"
local snapshot_name="genesis"
zfs snapshot -r "$POOL_NAME@$snapshot_name"
info "Genesis snapshot created: $POOL_NAME@$snapshot_name"
info "You can restore to this point anytime with: zfsrollback $snapshot_name"
}
#############################
# ZFS Cleanup
#############################
zfs_cleanup() {
step "Cleaning up ZFS"
# Unmount all ZFS datasets
zfs unmount -a 2>/dev/null || true
# Unmount EFI
umount /mnt/efi 2>/dev/null || true
# Export pool (important for clean import on boot)
zpool export "$POOL_NAME"
info "ZFS pool exported cleanly."
}
|