aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-01-18 02:01:01 -0600
committerCraig Jennings <c@cjennings.net>2026-01-18 02:01:01 -0600
commit5bd18b6f6a3ea89c900549c530af2a256094b917 (patch)
treed0dd600519c376d767da333311faa9358936588b
parentf1bcb073a09254bbac9725bec47648df72438bee (diff)
downloadarchangel-5bd18b6f6a3ea89c900549c530af2a256094b917.tar.gz
archangel-5bd18b6f6a3ea89c900549c530af2a256094b917.zip
Add config file support for unattended installations
Features: - --config-file option for automated installs - Example config at /root/install-archzfs.conf.example - Validates required fields before install - Config only used when explicitly specified (safety) Bug fixes: - Fix pacstrap hanging on provider prompts (use yes pipe) - Remove fsck from mkinitcpio HOOKS (ZFS doesn't use fsck) - Add hostid support for ZFS boot - Add spl.spl_hostid to kernel command line Documentation: - Comprehensive README.org with 15 sections - Session context tracking file
-rw-r--r--.gitignore2
-rw-r--r--README.org399
-rwxr-xr-xbuild.sh4
-rwxr-xr-xcustom/install-archzfs127
-rw-r--r--custom/install-archzfs.conf.example77
-rw-r--r--docs/NOTES.org257
-rw-r--r--docs/session-context.org67
7 files changed, 876 insertions, 57 deletions
diff --git a/.gitignore b/.gitignore
index 0c1b4b8..82a91f6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,5 @@ profile/
# Downloaded packages
zfs-packages/
+vm/*.qcow2
+vm/OVMF_VARS.fd
diff --git a/README.org b/README.org
new file mode 100644
index 0000000..6a14d05
--- /dev/null
+++ b/README.org
@@ -0,0 +1,399 @@
+#+TITLE: archzfs - Arch Linux ZFS Root Installation ISO
+#+AUTHOR:
+#+OPTIONS: toc:3
+
+* Overview
+
+archzfs is a custom Arch Linux ISO build system that creates a live environment
+optimized for installing Arch Linux on ZFS root filesystems. It provides an
+interactive installer with support for encrypted ZFS, multi-disk RAID
+configurations, and automatic snapshot management.
+
+The ISO includes all necessary ZFS tools pre-loaded, eliminating the need for
+manual module loading or package installation during the install process.
+
+* Features
+
+- *ZFS Root Filesystem* - Full ZFS installation with native encryption
+- *Multi-Disk RAID* - Support for mirror, stripe, raidz1/2/3 configurations
+- *EFI Boot Redundancy* - GRUB installed on all disks for boot resilience
+- *fzf-Based Interface* - Fuzzy search for timezone, locale, keymap, disk, RAID, and WiFi selection
+- *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
+- *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
+
+* Quick Start
+
+#+BEGIN_SRC bash
+# Build the ISO (requires root)
+sudo ./build.sh
+
+# Test in a VM
+./scripts/test-vm.sh
+
+# Or test with multiple disks for RAID
+./scripts/test-vm.sh --multi-disk
+#+END_SRC
+
+Boot the ISO and run ~install-archzfs~ to start the installation.
+
+* Prerequisites
+
+** Build Host Requirements
+- Arch Linux (or Arch-based distribution)
+- Root/sudo access
+- ~archiso~ package installed (~pacman -S archiso~)
+- ~10GB free disk space for build
+
+** Runtime Dependencies (included in ISO)
+- ZFS kernel modules (via zfs-dkms)
+- GRUB with ZFS support
+- NetworkManager
+- fzf for interactive selection
+
+* Building the ISO
+
+** Basic Build
+
+#+BEGIN_SRC bash
+sudo ./build.sh
+#+END_SRC
+
+The build script will:
+1. Copy the base Arch releng profile
+2. Switch to linux-lts kernel
+3. Add the archzfs repository
+4. Add custom packages (ZFS, NetworkManager, fzf, etc.)
+5. Copy the install-archzfs script
+6. Build the ISO using mkarchiso
+
+** Build Output
+
+- ISO location: ~out/archzfs-claude-YYYY.MM.DD-x86_64.iso~
+- Build logs: visible in terminal output
+- Build time: typically 5-15 minutes depending on cache
+
+** Clean Rebuild
+
+#+BEGIN_SRC bash
+sudo rm -rf work out
+sudo ./build.sh
+#+END_SRC
+
+* Project Structure
+
+#+BEGIN_EXAMPLE
+archzfs/
+├── build.sh # Main ISO build script
+├── custom/
+│ └── install-archzfs # Interactive installation script
+├── scripts/
+│ └── test-vm.sh # QEMU test VM launcher
+├── profile/ # archiso profile (generated during build)
+│ ├── airootfs/ # Files copied to live ISO
+│ │ └── usr/local/bin/ # Contains install-archzfs
+│ ├── packages.x86_64 # Package list for ISO
+│ └── pacman.conf # Pacman config with archzfs repo
+├── vm/ # VM disk images (created by test-vm.sh)
+├── work/ # Build working directory (created by build.sh)
+├── out/ # Built ISO output (created by build.sh)
+└── docs/ # Documentation
+#+END_EXAMPLE
+
+** Script Descriptions
+
+| Script | Description |
+|--------+-------------|
+| ~build.sh~ | Builds the ISO. Copies releng profile, adds ZFS packages, configures kernel, runs mkarchiso |
+| ~custom/install-archzfs~ | Interactive installer run on the live ISO. Handles disk partitioning, ZFS pool creation, base system install, bootloader setup |
+| ~scripts/test-vm.sh~ | Launches QEMU VM for testing. Supports single and multi-disk configurations |
+
+* Testing with VMs
+
+** Basic VM Test
+
+#+BEGIN_SRC bash
+./scripts/test-vm.sh
+#+END_SRC
+
+This creates a 50GB virtual disk and boots the ISO.
+
+** Multi-Disk RAID Test
+
+#+BEGIN_SRC bash
+# Two 50GB disks (for mirror or stripe)
+./scripts/test-vm.sh --multi-disk
+
+# Three 50GB disks (for raidz1)
+./scripts/test-vm.sh --multi-disk=3
+#+END_SRC
+
+** SSH Access to VM
+
+#+BEGIN_SRC bash
+# Password: archzfs
+ssh -p 2222 root@localhost
+
+# Or with sshpass
+sshpass -p archzfs ssh -p 2222 root@localhost
+#+END_SRC
+
+** Clean VM State
+
+#+BEGIN_SRC bash
+./scripts/test-vm.sh --clean
+#+END_SRC
+
+** Boot from Installed Disk
+
+#+BEGIN_SRC bash
+./scripts/test-vm.sh --boot-disk
+#+END_SRC
+
+* Development Workflow
+
+** Iterative Testing with Genesis Rollback
+
+After completing an installation in the VM, you can rollback to the genesis
+snapshot and re-test without reinstalling. This is useful for testing archsetup
+or other post-install scripts.
+
+*** In the VM (after installation and reboot):
+
+#+BEGIN_SRC bash
+# Rollback to pristine post-install state
+/root/rollback-to-genesis
+
+# Reboot to apply
+reboot
+#+END_SRC
+
+*** From the host:
+
+#+BEGIN_SRC bash
+# The VM disk retains the ZFS pool
+# Just boot from disk again
+./scripts/test-vm.sh --boot-disk
+#+END_SRC
+
+** Updating the Install Script
+
+#+BEGIN_SRC bash
+# Edit the script
+vim custom/install-archzfs
+
+# Copy to a running VM for immediate testing
+sshpass -p archzfs scp -P 2222 custom/install-archzfs root@localhost:/usr/local/bin/
+
+# Or rebuild the ISO for fresh testing
+sudo ./build.sh
+#+END_SRC
+
+* Installation Walkthrough
+
+The ~install-archzfs~ script provides a guided installation with fzf-based
+selection interfaces.
+
+** Phase 1: Configuration Gathering
+
+1. *Hostname* - System hostname
+2. *Timezone* - Fuzzy search through all timezones (preview shows current time)
+3. *Locale* - All locales available (preview shows format examples)
+4. *Keymap* - Console keyboard layout
+5. *Disk Selection* - Multi-select with TAB (preview shows disk details)
+6. *RAID Level* - For multi-disk: mirror, stripe, raidz1/2/3 (preview shows capacity calculations)
+7. *WiFi* - Scan and select network (preview shows signal/security)
+8. *ZFS Passphrase* - Encryption passphrase (required at every boot)
+9. *Root Password* - System root password
+10. *SSH* - Enable SSH with root login (default: yes)
+
+** Phase 2: Unattended Installation
+
+After configuration, the installation runs without intervention:
+- Disk partitioning (EFI + ZFS on each disk)
+- ZFS pool creation with encryption
+- Dataset creation (ROOT, home, var, etc.)
+- Base system installation via pacstrap
+- System configuration (locale, timezone, hostname)
+- Bootloader installation (GRUB on all EFI partitions)
+- Genesis snapshot creation
+
+** RAID Level Options
+
+| Disks | Available Options |
+|-------+-------------------|
+| 1 | Single (no RAID) |
+| 2 | Mirror, Stripe |
+| 3+ | Mirror, Stripe, RAIDZ1 |
+| 4+ | Mirror, Stripe, RAIDZ1, RAIDZ2 |
+| 5+ | Mirror, Stripe, RAIDZ1, RAIDZ2, RAIDZ3 |
+
+* Bare Metal Installation
+
+** Preparing Installation Media
+
+#+BEGIN_SRC bash
+# Write ISO to USB drive (replace /dev/sdX)
+sudo dd if=out/archzfs-claude-*.iso of=/dev/sdX bs=4M status=progress oflag=sync
+#+END_SRC
+
+** Booting
+
+1. Boot from USB (may need to disable Secure Boot)
+2. Wait for live environment to load
+3. Run ~install-archzfs~
+
+** WiFi Setup
+
+The installer will scan for WiFi networks and present them in fzf.
+Select your network and enter the password. The connection is tested
+before proceeding and will be copied to the installed system.
+
+** SSH Access After Reboot
+
+If you enabled SSH during installation:
+1. The system will have NetworkManager enabled
+2. WiFi credentials are copied from the live environment
+3. SSH is enabled with root password login
+4. Connect via: ~ssh root@<ip-address>~
+
+*Important*: Harden SSH after first login (disable password auth, use keys).
+
+** Post-Reboot Steps
+
+1. Enter ZFS encryption passphrase at boot
+2. Log in as root
+3. Run ~archsetup~ for further configuration
+
+* Post-Installation
+
+** Genesis Snapshot
+
+The installer creates a recursive snapshot of the entire pool named ~genesis~.
+This represents the pristine post-install state.
+
+#+BEGIN_SRC bash
+# View genesis snapshots
+zfs list -t snapshot | grep genesis
+#+END_SRC
+
+** Rollback to Factory State
+
+#+BEGIN_SRC bash
+# Interactive rollback with confirmation
+/root/rollback-to-genesis
+
+# Reboot to apply
+reboot
+#+END_SRC
+
+*Warning*: This destroys all changes since installation!
+
+** Useful ZFS Commands
+
+#+BEGIN_SRC bash
+# List all snapshots
+zfs list -t snapshot
+
+# Create manual snapshot
+zfs snapshot zroot/home@my-backup
+
+# Rollback to snapshot
+zfs rollback zroot/home@my-backup
+
+# Pool status
+zpool status
+
+# Pool usage
+zpool list
+#+END_SRC
+
+* Keeping Up-to-Date
+
+** When to Rebuild
+
+Rebuild the ISO when:
+- New Linux LTS kernel is released
+- New ZFS version is released
+- You've made changes to install-archzfs
+- archzfs repository packages are updated
+
+** Rebuild Process
+
+#+BEGIN_SRC bash
+# Clean and rebuild
+sudo rm -rf work out
+sudo ./build.sh
+#+END_SRC
+
+The build automatically pulls the latest packages from:
+- Official Arch repositories
+- archzfs repository (https://archzfs.com)
+
+** Checking for Updates
+
+#+BEGIN_SRC bash
+# Check current linux-lts version in repos
+pacman -Si linux-lts | grep Version
+
+# Check archzfs package versions
+curl -s https://archzfs.com/archzfs/x86_64/ | grep -o 'zfs-linux-lts-[^"]*'
+#+END_SRC
+
+* Troubleshooting
+
+** Build Fails with Package Conflicts
+
+Clean the work directory and rebuild:
+#+BEGIN_SRC bash
+sudo rm -rf work
+sudo ./build.sh
+#+END_SRC
+
+** ZFS Module Not Loading
+
+The ISO includes DKMS-built ZFS modules. If modules fail to load:
+- Check ~dmesg | grep -i zfs~ for errors
+- Ensure you're using the LTS kernel
+- The archzfs repo may be out of sync with kernel updates (wait for update)
+
+** Disk Not Showing in Selection
+
+- Ensure the disk is not mounted
+- Check ~lsblk~ to verify disk visibility
+- USB drives may need a moment to be detected
+
+** WiFi Networks Not Found
+
+- Verify WiFi hardware is present: ~ip link~
+- In VMs, there is no WiFi adapter (expected)
+- Try rescanning: ~nmcli device wifi rescan~
+
+** Boot Fails After Installation
+
+- Verify EFI boot entries: ~efibootmgr -v~
+- Check GRUB config: ~/boot/grub/grub.cfg~
+- Ensure ZFS modules in initramfs: ~lsinitcpio /boot/initramfs-linux-lts.img | grep zfs~
+
+** Encryption Passphrase Not Accepted
+
+- Keyboard layout at boot is US by default
+- Passphrase is case-sensitive
+- Check for num-lock state
+
+* Links
+
+- [[https://archzfs.com][archzfs Repository]] - ZFS packages for Arch Linux
+- [[https://openzfs.github.io/openzfs-docs/][OpenZFS Documentation]] - Official ZFS documentation
+- [[https://wiki.archlinux.org/title/ZFS][Arch Wiki - ZFS]] - Arch-specific ZFS information
+- [[https://wiki.archlinux.org/title/Archiso][Arch Wiki - Archiso]] - Building custom Arch ISOs
+- [[https://github.com/openzfs/zfs][OpenZFS on GitHub]] - ZFS source code
+
+* License
+
+This project is licensed under the GNU General Public License v3.0 (GPL-3.0).
+
+See [[file:LICENSE][LICENSE]] file for the full license text.
diff --git a/build.sh b/build.sh
index 7fdbee7..9e2be40 100755
--- a/build.sh
+++ b/build.sh
@@ -168,6 +168,10 @@ 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 example config for unattended installs
+mkdir -p "$PROFILE_DIR/airootfs/root"
+cp "$CUSTOM_DIR/install-archzfs.conf.example" "$PROFILE_DIR/airootfs/root/"
+
# Set permissions in profiledef.sh
info "Setting file permissions..."
if grep -q "file_permissions=" "$PROFILE_DIR/profiledef.sh"; then
diff --git a/custom/install-archzfs b/custom/install-archzfs
index 5f8b2a9..ae9e793 100755
--- a/custom/install-archzfs
+++ b/custom/install-archzfs
@@ -10,6 +10,11 @@
# - Optional WiFi configuration with connection test
# - ZFS native encryption (passphrase required at boot)
# - Pre-pacman ZFS snapshots for safe upgrades
+#
+# UNATTENDED MODE:
+# Use --config-file /path/to/install-archzfs.conf for automated installs.
+# Config file must be explicitly specified to prevent accidental disk wipes.
+# See /root/install-archzfs.conf.example for a template with all options.
set -e
@@ -50,6 +55,72 @@ 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
+}
+
+#############################
# Pre-flight Checks
#############################
@@ -71,6 +142,37 @@ preflight_checks() {
#############################
gather_input() {
+ if [[ "$UNATTENDED" == true ]]; then
+ # Validate required config values
+ [[ -z "$HOSTNAME" ]] && error "Config missing required: HOSTNAME"
+ [[ -z "$TIMEZONE" ]] && error "Config missing required: TIMEZONE"
+ [[ -z "$ZFS_PASSPHRASE" ]] && error "Config missing required: ZFS_PASSPHRASE"
+ [[ -z "$ROOT_PASSWORD" ]] && error "Config missing required: ROOT_PASSWORD"
+ [[ ${#SELECTED_DISKS[@]} -eq 0 ]] && error "Config missing required: DISKS"
+
+ # Set defaults for optional values
+ [[ -z "$LOCALE" ]] && LOCALE="en_US.UTF-8"
+ [[ -z "$KEYMAP" ]] && KEYMAP="us"
+ [[ -z "$ENABLE_SSH" ]] && ENABLE_SSH="yes"
+
+ # Determine RAID level if not specified
+ if [[ -z "$RAID_LEVEL" && ${#SELECTED_DISKS[@]} -gt 1 ]]; then
+ RAID_LEVEL="mirror"
+ info "Defaulting to mirror for ${#SELECTED_DISKS[@]} disks"
+ fi
+
+ info "Configuration loaded:"
+ info " Hostname: $HOSTNAME"
+ info " Timezone: $TIMEZONE"
+ info " Locale: $LOCALE"
+ info " Keymap: $KEYMAP"
+ info " Disks: ${SELECTED_DISKS[*]}"
+ [[ -n "$RAID_LEVEL" ]] && info " RAID: $RAID_LEVEL"
+ info " SSH: $ENABLE_SSH"
+ [[ -n "$WIFI_SSID" ]] && info " WiFi: $WIFI_SSID"
+ return
+ fi
+
echo ""
echo "╔═══════════════════════════════════════════════════════════════╗"
echo "║ Arch Linux ZFS Root ║"
@@ -731,7 +833,8 @@ EOF
info "Installing base packages (this takes a while)..."
info "ZFS will be built from source via DKMS - this ensures kernel compatibility."
- pacstrap -K /mnt \
+ # Use yes to auto-select defaults for provider prompts
+ yes "" | pacstrap -K /mnt \
base \
base-devel \
linux-lts \
@@ -843,7 +946,8 @@ configure_initramfs() {
cp /mnt/etc/mkinitcpio.conf /mnt/etc/mkinitcpio.conf.bak
# Configure hooks for ZFS
- sed -i 's/^HOOKS=.*/HOOKS=(base udev autodetect microcode modconf kms keyboard keymap consolefont block zfs filesystems fsck)/' /mnt/etc/mkinitcpio.conf
+ # ZFS doesn't use fsck - remove it to avoid confusing error messages
+ sed -i 's/^HOOKS=.*/HOOKS=(base udev autodetect microcode modconf kms keyboard keymap consolefont block zfs filesystems)/' /mnt/etc/mkinitcpio.conf
info "Regenerating initramfs..."
arch-chroot /mnt mkinitcpio -P
@@ -852,13 +956,17 @@ configure_initramfs() {
configure_bootloader() {
step "Configuring GRUB Bootloader"
+ # Get hostid for kernel parameter
+ local host_id
+ host_id=$(hostid)
+
# Configure GRUB defaults
cat > /mnt/etc/default/grub << EOF
GRUB_DEFAULT=0
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="Arch Linux (ZFS)"
-GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3 quiet"
-GRUB_CMDLINE_LINUX="root=ZFS=$POOL_NAME/ROOT/default"
+GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3"
+GRUB_CMDLINE_LINUX="root=ZFS=$POOL_NAME/ROOT/default spl.spl_hostid=$host_id"
GRUB_PRELOAD_MODULES="part_gpt part_msdos zfs"
GRUB_TERMINAL_OUTPUT="console"
GRUB_DISABLE_OS_PROBER=true
@@ -912,6 +1020,15 @@ configure_zfs_services() {
arch-chroot /mnt systemctl enable zfs-mount
arch-chroot /mnt systemctl enable zfs-import.target
+ # Copy hostid to installed system (ZFS uses this for pool ownership)
+ if [[ -f /etc/hostid ]]; then
+ cp /etc/hostid /mnt/etc/hostid
+ else
+ # Generate hostid if it doesn't exist
+ zgenhostid
+ cp /etc/hostid /mnt/etc/hostid
+ fi
+
# Generate zpool cache
mkdir -p /mnt/etc/zfs
zpool set cachefile=/etc/zfs/zpool.cache "$POOL_NAME"
@@ -1118,7 +1235,9 @@ print_summary() {
#############################
main() {
+ parse_args "$@"
preflight_checks
+ check_config
gather_input
# Unattended installation begins
diff --git a/custom/install-archzfs.conf.example b/custom/install-archzfs.conf.example
new file mode 100644
index 0000000..e97fe68
--- /dev/null
+++ b/custom/install-archzfs.conf.example
@@ -0,0 +1,77 @@
+# install-archzfs.conf - Unattended Installation Configuration
+#
+# Copy this file to /root/install-archzfs.conf and edit values.
+# Or use: install-archzfs --config-file /path/to/your-config.conf
+#
+# Required fields: HOSTNAME, TIMEZONE, DISKS, ZFS_PASSPHRASE, ROOT_PASSWORD
+# All other fields have sensible defaults.
+
+#############################
+# System Configuration
+#############################
+
+# Hostname for the installed system (required)
+HOSTNAME=archzfs
+
+# Timezone (required) - Use format: Region/City
+# Examples: America/Los_Angeles, Europe/London, Asia/Tokyo
+TIMEZONE=America/Los_Angeles
+
+# Locale (optional, default: en_US.UTF-8)
+LOCALE=en_US.UTF-8
+
+# Console keymap (optional, default: us)
+KEYMAP=us
+
+#############################
+# Disk Configuration
+#############################
+
+# Disks to use for installation (required)
+# Single disk: DISKS=/dev/vda
+# Multiple disks: DISKS=/dev/vda,/dev/vdb,/dev/vdc
+DISKS=/dev/vda
+
+# RAID level for multi-disk setups (optional)
+# Options: mirror, stripe, raidz1, raidz2, raidz3
+# Default: mirror (when multiple disks specified)
+# Leave empty for single disk
+RAID_LEVEL=
+
+#############################
+# Security
+#############################
+
+# ZFS encryption passphrase (required)
+# This will be required at every boot to unlock the pool
+ZFS_PASSPHRASE=changeme
+
+# Root password (required)
+ROOT_PASSWORD=changeme
+
+#############################
+# Network Configuration
+#############################
+
+# Enable SSH with root login (optional, default: yes)
+# Set to "no" to disable SSH
+ENABLE_SSH=yes
+
+# WiFi configuration (optional)
+# Leave empty for ethernet-only or to skip WiFi setup
+WIFI_SSID=
+WIFI_PASSWORD=
+
+#############################
+# Advanced ZFS Options
+#############################
+
+# Pool name (optional, default: zroot)
+#POOL_NAME=zroot
+
+# Compression algorithm (optional, default: zstd)
+#COMPRESSION=zstd
+
+# Sector size shift (optional, default: 12 for 4K sectors)
+# Use 13 for 8K sector drives
+#ASHIFT=12
diff --git a/docs/NOTES.org b/docs/NOTES.org
index f56e7bd..89c02ad 100644
--- a/docs/NOTES.org
+++ b/docs/NOTES.org
@@ -1,6 +1,6 @@
-#+TITLE: Claude Code Notes - [Project Name]
+#+TITLE: Claude Code Notes - archzfs
#+AUTHOR: Craig Jennings & Claude
-#+DATE: [Date]
+#+DATE: 2026-01-17
* About This File
@@ -22,50 +22,146 @@ This file contains project-specific information for this project.
* Project-Specific Context
-This is where context regarding this project is placed.
-
-Examples of what goes here:
-- Project overview and goals
-- People and their relationships to the user
-- Contact information for companies and people
-- Current state of the project
-- Status report
-- Links to important documents
-- Technical architecture notes
-- Key decisions and rationale
-
-** Welcome to Your First Session!
-
-This is your first session with Craig on this project.
-
-**First session checklist:**
-1. **Determine project type and git/docs policy:**
- - Is this project in a git repository?
- - **Is this a code project** (Emacs package, library, software project)?
- - **YES (code project):** Add docs/ to .gitignore (private notes, not part of codebase)
- - Examples: org-msg, chime.el, wttrin, or any future Emacs packages/libraries
- - docs/ contains session notes and should remain private
- - **NO (non-code project):** Commit docs/ normally (part of the project documentation)
- - Examples: personal projects, business planning, documentation projects
- - docs/ is project documentation and should be versioned
- - What are the remote repositories (if any)?
-
-2. **Understand the project:**
- - Ask what the project is about
- - Ask what the goals of the project are
- - Ask any clarifying questions along the way
-
-3. **Brainstorm how to help:**
- - Discuss approaches and strategies
- - Identify immediate next steps
-
-4. **Document learnings:**
- - Record what you learned in relevant sections of this document
- - Add project-specific context below this section
-
-5. **Clean up:**
- - Remove this "Welcome to Your First Session!" heading and its content
- - Update Project-Specific Context section with actual project information
+** Overview
+
+Build system for creating a custom Arch Linux installation ISO with ZFS support. The goal is to have a bootable ISO that can install Arch Linux on ZFS root without needing to manually compile ZFS or deal with kernel version mismatches.
+
+** Repository
+
+- Remote: =cjennings@cjennings.net:git/archzfs.git=
+- Branch: =main=
+- docs/ is committed (not private)
+
+** Key Components
+
+- =build.sh= - Main build script (runs as root)
+ - Downloads ZFS packages from archzfs GitHub releases
+ - Creates custom archiso profile based on releng
+ - Adds custom packages (nodejs, npm, jq, zsh, htop, ripgrep, etc.)
+ - Copies custom installer scripts into ISO
+ - Builds ISO with mkarchiso
+
+- =custom/= - Custom scripts included in ISO
+ - =install-archzfs= - Main installer script
+ - =install-claude= - Claude Code installer
+ - =archsetup-zfs= - ZFS-specific Arch setup
+ - =zfs-setup= - Installs ZFS packages and loads module (generated by build.sh)
+
+- =scripts/test-vm.sh= - QEMU VM for testing the ISO
+
+** Current State
+
+TESTING: install-archzfs script almost complete.
+
+- ISO builds successfully (4.8G) with linux-lts + zfs-dkms
+- ZFS module loads correctly in live environment
+- install-archzfs runs through partitioning, pool creation, base install, system config
+- Last fix: added freetype2 for grub-mkfont (needs rebuild to test)
+
+Next: Rebuild ISO, complete install test, boot installed system.
+
+** Goals
+
+Create a bootable Arch Linux installation ISO that:
+1. Installs Arch on ZFS root with native encryption
+2. Uses sane defaults for dataset layout
+3. Configures automatic snapshots (sanoid)
+4. Sets up replication to TrueNAS for backups
+5. Includes Claude Code on live ISO for emergency troubleshooting
+
+** Design Decisions
+
+*** Kernel Strategy
+- Use =linux-lts= + =zfs-dkms= from archzfs.com repo
+- DKMS builds ZFS from source, guaranteeing kernel compatibility
+- Slower build time but eliminates version mismatch issues entirely
+- LTS kernel provides stability, DKMS provides flexibility
+
+*** ZFS Pool Configuration
+| Setting | Value | Rationale |
+|---------+-------+-----------|
+| Pool name | =zroot= | Standard convention |
+| Encryption | AES-256-GCM, passphrase | Required at every boot |
+| Compression | =zstd= (default) | Good balance of speed/ratio |
+| Ashift | 12 (4K sectors) | Modern drives |
+| Root reservation | 50GB | Prevents pool from filling |
+
+*** Dataset Layout
+| Dataset | Mountpoint | Special Settings | Purpose |
+|---------+------------+------------------+---------|
+| zroot/ROOT/default | / | reservation=50G | Root filesystem |
+| zroot/home | /home | | Home directories (archsetup creates user subdataset) |
+| zroot/media | /media | compression=off | Pre-compressed media files |
+| zroot/vms | /vms | recordsize=64K | VM disk images (qemu/libvirt + virtualbox) |
+| zroot/var/log | /var/log | | System logs |
+| zroot/var/cache | /var/cache | | Package cache |
+| zroot/var/lib/pacman | /var/lib/pacman | | Package database |
+| zroot/var/lib/docker | /var/lib/docker | | Docker storage |
+| zroot/tmp | /tmp | auto-snapshot=false | Temp files |
+| zroot/var/tmp | /var/tmp | auto-snapshot=false | Temp files |
+
+*** Snapshot Policy (Sanoid)
+Less aggressive since TrueNAS handles long-term backups:
+
+| Template | Hourly | Daily | Weekly | Monthly | Used For |
+|----------+--------+-------+--------+---------+----------|
+| production | 6 | 7 | 2 | 1 | root, home, var/log, pacman |
+| backup | 0 | 3 | 2 | 1 | media, vms |
+| none | 0 | 0 | 0 | 0 | tmp, cache |
+
+Plus: Pacman hook creates snapshot before every transaction.
+
+*** TrueNAS Replication
+- Primary: =truenas.local= (local network)
+- Fallback: =truenas= (tailscale)
+- Destination pool: =vault/[TBD]=
+- Schedule: Nightly at 2:00 AM
+- Datasets: ROOT/default, home, media, vms
+
+*** Included Packages
+- Base system + development tools
+- =nodejs=, =npm=, =jq= (for Claude Code)
+- =zsh=, =htop=, =ripgrep=, =eza=, =fd=, =fzf=
+- =sanoid= (snapshot management)
+- =dialog= (installer UI)
+
+*** Installation UX
+- All questions asked upfront, then unattended installation
+- WiFi tested before installation begins (if provided)
+- User can walk away during install and come back
+- Summary + final confirmation before starting
+
+*** User Account Strategy
+- install-archzfs creates root account only (asks for root password)
+- No user account created during install
+- Just create =zroot/home= dataset (no user-specific subdataset)
+- archsetup creates user account + home dataset post-reboot
+
+*** GRUB HiDPI Support
+- Generate 32px DejaVuSansMono font during install
+- Set =GRUB_FONT= to use custom font
+- Works well on HiDPI and regular displays
+
+*** WiFi Configuration
+- Ask for SSID + password during install (optional)
+- Test connection before installation starts
+- Copy connection profile to installed system
+- Auto-connects after reboot
+
+*** Post-Install Workflow
+1. install-archzfs: Minimal ZFS system + root account
+2. Reboot, login as root
+3. Run archsetup manually for full workstation setup
+
+*** Testing/Debugging (VM)
+- SSH access on live ISO: sshd enabled, known root password
+- Serial console: =-serial mon:stdio= in QEMU for terminal copy/paste
+- Port forwarding: 2222→22 (already configured)
+- Allows easy copy/paste of error messages during testing
+
+** Open Questions
+
+- [ ] TrueNAS destination dataset path (vault/???)
* AVAILABLE WORKFLOWS
@@ -233,15 +329,70 @@ Each entry should use this format:
** Session Entries
-*** [First session date]
+*** 2026-01-17 Sat @ 17:10 -0600
-*Time:* Initial setup
-*Status:* COMPLETE - Template created
+*Status:* IN PROGRESS
*What We Completed:*
-- Project initialized with Claude Code templates
-- Ready for first work session
+- Fixed ZFS kernel module mismatch by switching to linux-lts + zfs-dkms
+- Fixed bootloader to use linux-lts kernel (was defaulting to regular linux)
+- Fixed broadcom-wl dependency (switched to broadcom-wl-dkms)
+- Updated mkinitcpio preset for linux-lts with archiso config
+- Fixed install-archzfs bugs:
+ - =[[ ]] && error= pattern causing early exit with =set -e= (changed to if/then)
+ - 50G reservation on 50G disk (now dynamic: 20% of pool, capped 5-20G)
+ - sanoid not in official repos (moved to archsetup)
+ - grub-mkfont needs freetype2 package (added to pacstrap)
+- Removed sanoid/syncoid from install-archzfs (archsetup will handle)
+- Created inbox item for archsetup with full sanoid/syncoid config
+- ISO now 4.8G (was 5.4G) - only linux-lts kernel
+
+*Key Technical Insights:*
+- =broadcom-wl= depends on =linux= kernel specifically - use =broadcom-wl-dkms= instead
+- archiso releng profile has linux.preset in airootfs that needs renaming to linux-lts.preset
+- With =set -e=, =[[ test ]] && command= returns exit code 1 if test is false, causing script exit
+- =grub-mkfont= requires =freetype2= package (not installed by default with grub)
+
+*Files Modified:*
+- [[file:../build.sh][build.sh]] - major updates for linux-lts, bootloader configs, mkinitcpio preset
+- [[file:../custom/install-archzfs][custom/install-archzfs]] - multiple bug fixes, removed sanoid/syncoid
+- [[file:~/code/archsetup/inbox/zfs-sanoid-detection.txt][archsetup inbox]] - sanoid/syncoid config for archsetup to implement
+
+*Current State:*
+- ISO builds successfully with linux-lts + zfs-dkms
+- ZFS module loads correctly in live environment
+- install-archzfs runs through most steps
+- Last error: grub-mkfont missing freetype2 (now fixed, needs rebuild/test)
*Next Steps:*
-- Begin first actual work session
-- Follow "Welcome to Your First Session!" checklist above
+- Rebuild ISO with freetype2 fix
+- Complete full install-archzfs test in VM
+- Test booting the installed system
+- Git commit all changes
+
+*** 2026-01-17 Sat @ 13:16 -0600
+
+*Status:* COMPLETE (continued above)
+
+*What We Completed:*
+- Initialized git repository
+- Created .gitignore (excludes work/, out/, profile/, zfs-packages/)
+- Initial commit with all build scripts
+- Added docs/ to git (decided to track publicly)
+- Built fresh ISO (archzfs-claude-2026.01.17-x86_64.iso, 4.9G)
+- Tested ISO in QEMU VM
+- Documented project goals and design decisions in NOTES.org
+
+*Key Decisions Made:*
+- Use linux-lts + zfs-dkms from archzfs.com (DKMS ensures kernel compatibility)
+- Less aggressive snapshot policy (TrueNAS handles long-term backups)
+- All install questions upfront, then unattended installation
+- Root account only (archsetup creates user post-reboot)
+- 32px GRUB font for HiDPI displays
+- WiFi config tested before install starts
+
+*Files Modified:*
+- [[file:../.gitignore][.gitignore]] - created
+- [[file:../build.sh][build.sh]] - major rewrite
+- [[file:../custom/install-archzfs][custom/install-archzfs]] - complete rewrite
+- [[file:../scripts/test-vm.sh][scripts/test-vm.sh]] - added serial console
diff --git a/docs/session-context.org b/docs/session-context.org
new file mode 100644
index 0000000..5274ae3
--- /dev/null
+++ b/docs/session-context.org
@@ -0,0 +1,67 @@
+#+TITLE: Session Context - archzfs
+#+DATE: 2026-01-18
+
+* Current Session State
+
+** What We're Working On
+Testing unattended installation with --config-file option. Currently running 2-disk mirror test.
+
+** Key Progress This Session
+
+*** Config File Support for Unattended Installs
+- Added --config-file /path/to/config argument
+- Config only used when explicitly specified (prevents accidental disk wipes)
+- Example config at /root/install-archzfs.conf.example on ISO
+- Validates required fields: HOSTNAME, TIMEZONE, DISKS, ZFS_PASSPHRASE, ROOT_PASSWORD
+- Sets sensible defaults for optional fields
+
+*** Boot Fixes for ZFS
+- Removed fsck from mkinitcpio HOOKS (ZFS doesn't use fsck)
+- Added hostid generation/copy to installed system
+- Added spl.spl_hostid to kernel command line
+- Removed 'quiet' from kernel params for visible boot messages
+
+*** Multi-Disk RAID Support
+- RAID level selection: mirror, stripe, raidz1/2/3
+- EFI partitions on all disks for boot redundancy
+- Stripe option for max capacity (no redundancy)
+- Dynamic capacity calculations in preview
+
+*** fzf-Based User Interface
+- Replaced all select menus with fzf fuzzy finder
+- Timezone: fuzzy search with current time preview
+- Locale: all locales shown, format examples in preview
+- Keymap: all keymaps with layout info
+- Disk selection: multi-select (TAB), disk details preview
+- RAID level: explanatory preview with capacity calculations
+- WiFi: network list by signal strength, security info preview
+
+*** Documentation
+- Created comprehensive README.org (15 sections)
+- TODO.org with project tasks
+
+** Files Modified This Session
+- custom/install-archzfs - Config file support, boot fixes
+- custom/install-archzfs.conf.example - NEW: template for unattended installs
+- build.sh - Copies example config to ISO
+- README.org - Comprehensive documentation
+- TODO.org - Project task tracking
+
+** Bugs Being Investigated
+- Install script exiting early in unattended mode - FIXED with return 0
+- Still need to verify full installation completes and boots successfully
+
+** Testing Status
+- Test 1: 2-disk mirror - IN PROGRESS (packages installing)
+- Test 2: 2-disk stripe - PENDING
+- Test 3: Single disk - PENDING
+
+** Test Configuration
+- VM: ./scripts/test-vm.sh --multi-disk
+- SSH: sshpass -p archzfs ssh -p 2222 root@localhost
+- Config file: /root/test-mirror.conf
+
+** Test Credentials
+- Live ISO root password: archzfs
+- Test ZFS passphrase: testpass123
+- Test root password: testpass123