From 74fb6ab9cc2b8d38ea75a6b52b67ec34281a2338 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Fri, 23 Jan 2026 15:45:55 -0600 Subject: Phase 1.1: Create lib/ directory structure for archangel refactor - Add custom/lib/common.sh: output, validation, fzf prompts, disk utils - Add custom/lib/config.sh: argument parsing, config loading, validation - Add custom/lib/disk.sh: partitioning, EFI management, disk selection - Add custom/lib/zfs.sh: pool, datasets, ZFSBootMenu, services, hooks - Update install-archzfs to source libs - Remove duplicated output/config functions from main script Prepares codebase for btrfs filesystem support (Phase 2). --- custom/install-archzfs | 95 ++----------- custom/lib/common.sh | 136 ++++++++++++++++++ custom/lib/config.sh | 127 +++++++++++++++++ custom/lib/disk.sh | 193 +++++++++++++++++++++++++ custom/lib/zfs.sh | 356 +++++++++++++++++++++++++++++++++++++++++++++++ docs/session-context.org | 14 +- 6 files changed, 839 insertions(+), 82 deletions(-) create mode 100644 custom/lib/common.sh create mode 100644 custom/lib/config.sh create mode 100644 custom/lib/disk.sh create mode 100644 custom/lib/zfs.sh diff --git a/custom/install-archzfs b/custom/install-archzfs index a17aad5..6f098cc 100755 --- a/custom/install-archzfs +++ b/custom/install-archzfs @@ -18,6 +18,16 @@ set -e +############################# +# Source Library Functions +############################# + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/lib/common.sh" +source "$SCRIPT_DIR/lib/config.sh" +source "$SCRIPT_DIR/lib/disk.sh" +source "$SCRIPT_DIR/lib/zfs.sh" + ############################# # Configuration ############################# @@ -56,93 +66,16 @@ echo "install-archzfs started @ $(date +'%Y-%m-%d %H:%M:%S')" echo "================================================================================" echo "" -info() { echo "[INFO] $1"; } -warn() { echo "[WARN] $1"; } -error() { echo "[ERROR] $1"; exit 1; } -step() { echo ""; echo "==> $1"; } -prompt() { echo "$1"; } - -############################# -# Config File Support -############################# - -CONFIG_FILE="" -UNATTENDED=false - -parse_args() { - while [[ $# -gt 0 ]]; do - case "$1" in - --config-file) - if [[ -n "$2" && ! "$2" =~ ^- ]]; then - CONFIG_FILE="$2" - shift 2 - else - error "--config-file requires a path argument" - fi - ;; - --help|-h) - echo "Usage: install-archzfs [OPTIONS]" - echo "" - echo "Options:" - echo " --config-file PATH Use config file for unattended installation" - echo " --help, -h Show this help message" - echo "" - echo "Without --config-file, runs in interactive mode." - echo "See /root/install-archzfs.conf.example for a config template." - exit 0 - ;; - *) - error "Unknown option: $1 (use --help for usage)" - ;; - esac - done -} - -load_config() { - local config_path="$1" - - if [[ ! -f "$config_path" ]]; then - error "Config file not found: $config_path" - fi - - info "Loading config from: $config_path" - - # Source the config file (it's just key=value pairs) - # shellcheck disable=SC1090 - source "$config_path" - - # Convert DISKS from comma-separated string to array - if [[ -n "$DISKS" ]]; then - IFS=',' read -ra SELECTED_DISKS <<< "$DISKS" - fi - - UNATTENDED=true - info "Running in unattended mode" -} - -check_config() { - # Only use config when explicitly specified with --config-file - # This prevents accidental disk destruction from an unnoticed config file - if [[ -n "$CONFIG_FILE" ]]; then - load_config "$CONFIG_FILE" - fi -} +# Output functions now in lib/common.sh +# Config functions now in lib/config.sh ############################# # Pre-flight Checks ############################# preflight_checks() { - # Check root - [[ $EUID -ne 0 ]] && error "This script must be run as root" - - # 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." + require_root + zfs_preflight } ############################# diff --git a/custom/lib/common.sh b/custom/lib/common.sh new file mode 100644 index 0000000..a41441d --- /dev/null +++ b/custom/lib/common.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +# common.sh - Shared functions for archangel installer +# Source this file: source "$(dirname "$0")/lib/common.sh" + +############################# +# Output Functions +############################# + +# Colors (optional, gracefully degrade if not supported) +if [[ -t 1 ]]; then + RED='\033[0;31m' + GREEN='\033[0;32m' + YELLOW='\033[0;33m' + BLUE='\033[0;34m' + BOLD='\033[1m' + NC='\033[0m' # No Color +else + RED='' + GREEN='' + YELLOW='' + BLUE='' + BOLD='' + NC='' +fi + +info() { echo -e "${GREEN}[INFO]${NC} $1"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; } +step() { echo ""; echo -e "${BOLD}==> $1${NC}"; } +prompt() { echo -e "${BLUE}$1${NC}"; } + +# Log to file if LOG_FILE is set +log() { + local msg="[$(date +'%Y-%m-%d %H:%M:%S')] $1" + if [[ -n "$LOG_FILE" ]]; then + echo "$msg" >> "$LOG_FILE" + fi +} + +############################# +# Validation Functions +############################# + +require_root() { + [[ $EUID -ne 0 ]] && error "This script must be run as root" +} + +command_exists() { + command -v "$1" &>/dev/null +} + +require_command() { + command_exists "$1" || error "Required command not found: $1" +} + +############################# +# FZF Prompts +############################# + +# Check if fzf is available +has_fzf() { + command_exists fzf +} + +# Generic fzf selection +# Usage: result=$(fzf_select "prompt" "option1" "option2" ...) +fzf_select() { + local prompt="$1" + shift + local options=("$@") + + if has_fzf; then + printf '%s\n' "${options[@]}" | fzf --prompt="$prompt " --height=15 --reverse + else + # Fallback to simple select + PS3="$prompt " + select opt in "${options[@]}"; do + if [[ -n "$opt" ]]; then + echo "$opt" + break + fi + done + fi +} + +# Multi-select with fzf +# Usage: readarray -t results < <(fzf_multi "prompt" "opt1" "opt2" ...) +fzf_multi() { + local prompt="$1" + shift + local options=("$@") + + if has_fzf; then + printf '%s\n' "${options[@]}" | fzf --prompt="$prompt " --height=20 --reverse --multi + else + # Fallback: just return all options (user must edit) + printf '%s\n' "${options[@]}" + fi +} + +############################# +# Disk Utilities +############################# + +# Get disk size in human-readable format +get_disk_size() { + local disk="$1" + lsblk -dno SIZE "$disk" 2>/dev/null | tr -d ' ' +} + +# Get disk model +get_disk_model() { + local disk="$1" + lsblk -dno MODEL "$disk" 2>/dev/null | tr -d ' ' | head -c 20 +} + +# Check if disk is in use (mounted or has holders) +disk_in_use() { + local disk="$1" + [[ -n "$(lsblk -no MOUNTPOINT "$disk" 2>/dev/null | grep -v '^$')" ]] && return 0 + [[ -n "$(ls /sys/block/"$(basename "$disk")"/holders/ 2>/dev/null)" ]] && return 0 + return 1 +} + +# List available disks (not in use) +list_available_disks() { + local disks=() + for disk in /dev/nvme[0-9]n[0-9] /dev/sd[a-z] /dev/vd[a-z]; do + [[ -b "$disk" ]] || continue + disk_in_use "$disk" && continue + local size=$(get_disk_size "$disk") + local model=$(get_disk_model "$disk") + disks+=("$disk ($size, $model)") + done + printf '%s\n' "${disks[@]}" +} diff --git a/custom/lib/config.sh b/custom/lib/config.sh new file mode 100644 index 0000000..82f5d77 --- /dev/null +++ b/custom/lib/config.sh @@ -0,0 +1,127 @@ +#!/usr/bin/env bash +# config.sh - Configuration and argument handling for archangel installer +# Source this file after common.sh + +############################# +# Global Config Variables +############################# + +CONFIG_FILE="" +UNATTENDED=false + +# These get populated by config file or interactive prompts +HOSTNAME="" +TIMEZONE="" +LOCALE="" +KEYMAP="" +SELECTED_DISKS=() +RAID_LEVEL="" +WIFI_SSID="" +WIFI_PASSWORD="" +ENCRYPTION_ENABLED=false +ZFS_PASSPHRASE="" +ROOT_PASSWORD="" +SSH_ENABLED=false +SSH_KEY="" + +############################# +# Argument Parsing +############################# + +parse_args() { + while [[ $# -gt 0 ]]; do + case "$1" in + --config-file) + if [[ -n "$2" && ! "$2" =~ ^- ]]; then + CONFIG_FILE="$2" + shift 2 + else + error "--config-file requires a path argument" + fi + ;; + --help|-h) + show_usage + exit 0 + ;; + *) + error "Unknown option: $1 (use --help for usage)" + ;; + esac + done +} + +show_usage() { + cat </dev/null || true + sleep 1 + + info "Partitioned $disk: EFI=${efi_size}, ROOT=remainder" +} + +# Partition multiple disks (for RAID configurations) +partition_disks() { + local efi_size="${1:-512M}" + shift + local disks=("$@") + + for disk in "${disks[@]}"; do + partition_disk "$disk" "$efi_size" + done +} + +############################# +# Partition Helpers +############################# + +# Get EFI partition path for a disk +get_efi_partition() { + local disk="$1" + if [[ "$disk" =~ nvme ]]; then + echo "${disk}p1" + else + echo "${disk}1" + fi +} + +# Get root partition path for a disk +get_root_partition() { + local disk="$1" + if [[ "$disk" =~ nvme ]]; then + echo "${disk}p2" + else + echo "${disk}2" + fi +} + +# Get all root partitions from disk array +get_root_partitions() { + local disks=("$@") + local parts=() + for disk in "${disks[@]}"; do + parts+=("$(get_root_partition "$disk")") + done + printf '%s\n' "${parts[@]}" +} + +# Get all EFI partitions from disk array +get_efi_partitions() { + local disks=("$@") + local parts=() + for disk in "${disks[@]}"; do + parts+=("$(get_efi_partition "$disk")") + done + printf '%s\n' "${parts[@]}" +} + +############################# +# EFI Partition Management +############################# + +# Format EFI partition +format_efi() { + local partition="$1" + local label="${2:-EFI}" + + info "Formatting EFI partition: $partition" + mkfs.fat -F32 -n "$label" "$partition" || error "Failed to format EFI: $partition" +} + +# Format all EFI partitions +format_efi_partitions() { + local disks=("$@") + local first=true + + for disk in "${disks[@]}"; do + local efi=$(get_efi_partition "$disk") + if $first; then + format_efi "$efi" "EFI" + first=false + else + format_efi "$efi" "EFI2" + fi + done +} + +# Mount EFI partition +mount_efi() { + local partition="$1" + local mount_point="${2:-/mnt/efi}" + + mkdir -p "$mount_point" + mount "$partition" "$mount_point" || error "Failed to mount EFI at $mount_point" + info "Mounted EFI: $partition -> $mount_point" +} + +############################# +# Disk Selection (Interactive) +############################# + +# Interactive disk selection using fzf +select_disks() { + local available + available=$(list_available_disks) + + if [[ -z "$available" ]]; then + error "No available disks found" + fi + + step "Select installation disk(s)" + prompt "Use Tab to select multiple disks for RAID, Enter to confirm" + + local selected + if has_fzf; then + selected=$(echo "$available" | fzf --multi --prompt="Select disk(s): " --height=15 --reverse) + else + echo "$available" + read -rp "Enter disk path(s) separated by space: " selected + fi + + [[ -z "$selected" ]] && error "No disk selected" + + # Extract just the device paths (remove size/model info) + SELECTED_DISKS=() + while IFS= read -r line; do + local disk=$(echo "$line" | cut -d' ' -f1) + SELECTED_DISKS+=("$disk") + done <<< "$selected" + + info "Selected disks: ${SELECTED_DISKS[*]}" +} + +############################# +# RAID Level Selection +############################# + +select_raid_level() { + local num_disks=${#SELECTED_DISKS[@]} + + if [[ $num_disks -eq 1 ]]; then + RAID_LEVEL="" + info "Single disk - no RAID" + return + fi + + step "Select RAID level" + + local options=() + options+=("mirror - Mirror data across disks (recommended)") + + if [[ $num_disks -ge 3 ]]; then + options+=("raidz1 - Single parity, lose 1 disk capacity") + fi + if [[ $num_disks -ge 4 ]]; then + options+=("raidz2 - Double parity, lose 2 disks capacity") + fi + + local selected + selected=$(fzf_select "RAID level:" "${options[@]}") + RAID_LEVEL=$(echo "$selected" | cut -d' ' -f1) + + info "Selected RAID level: $RAID_LEVEL" +} diff --git a/custom/lib/zfs.sh b/custom/lib/zfs.sh new file mode 100644 index 0000000..7862f0d --- /dev/null +++ b/custom/lib/zfs.sh @@ -0,0 +1,356 @@ +#!/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=$(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=$(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=$(efibootmgr | grep "ZFSBootMenu" | head -1 | grep -oP 'Boot\K[0-9A-F]+') + if [[ -n "$bootnum" ]]; then + local 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." +} diff --git a/docs/session-context.org b/docs/session-context.org index fb23fb1..8275b4f 100644 --- a/docs/session-context.org +++ b/docs/session-context.org @@ -42,7 +42,19 @@ Analyzed ~/code/archsetup/archsetup (1852 lines) for chroot-incompatible operati Added [#B] task to todo.org with full implementation details. ** Files Modified This Session -- todo.org - Added archsetup --chroot task +- todo.org - Added archsetup --chroot task, btrfs implementation TODO +- docs/PLAN-archangel-btrfs.org - Created implementation plan +- docs/research-btrfs-expansion.org - Expanded testing validation checks + +** Implementation Progress (Phase 1.1) + +Created lib/ directory structure: +- [X] custom/lib/common.sh - output, validation, fzf prompts, disk utils +- [X] custom/lib/config.sh - arg parsing, config loading, validation +- [X] custom/lib/disk.sh - partitioning, EFI management, disk selection +- [X] custom/lib/zfs.sh - pool, datasets, ZFSBootMenu, services, hooks + +Next: Integrate libs into install-archzfs, test, then add filesystem selection ** Pending Items (from earlier + this session) 1. Review code-review.org document -- cgit v1.2.3