Build a self-contained, bootable FAI ISO for unattended Debian installation with UEFI boot, LUKS full-disk encryption, LVM, SSH key injection, per-host hostname/IP assignment, and optional remote LUKS unlock via dropbear.
- One command → bootable USB ISO with full-disk encryption
- UEFI + GPT with EFI System Partition,
/boot, and LUKS+LVM - Release-agnostic — targets any Debian release (Bookworm, Trixie, Forky, ...)
- SSH key injection from GitHub, URL, file, or inline
- MAC-based host config — per-host hostname, static IP, and dropbear settings
- Dropbear-initramfs — remote LUKS unlock over SSH (optional, per-host)
- SSH hardened — key-only auth, no password login
- Cross-platform — native Linux or macOS via Docker (with
--archfor cross-compilation) - Reproducible — pins upstream FAI config, templates everything from YAML
# 1. Clone and configure
git clone https://github.com/unredacted/fai-luks-builder.git
cd fai-luks-builder
cp build.yaml.example build.yaml
vim build.yaml # Set your passphrase, SSH key, hosts, etc.
# 2. Build the ISO
sudo ./build.sh -v
# 3. Write to USB
sudo dd if=./output/fai-luks.iso of=/dev/sdX bs=4M status=progressOn macOS, Docker is used automatically — no sudo needed:
./build.sh -v # Defaults to amd64 (uses QEMU emulation on Apple Silicon)
./build.sh -v --arch arm64 # Build arm64 ISO insteadCopy build.yaml.example to build.yaml and customize:
| Field | Required | Default | Description |
|---|---|---|---|
luks_passphrase |
Yes | LUKS encryption passphrase (min 8 chars) | |
admin_user |
Yes | Linux username for the admin account | |
admin_password |
Yes | Password (plaintext or SHA-512 hash) | |
ssh_key_github |
One of four | GitHub username to fetch SSH keys | |
ssh_key_url |
One of four | URL to fetch SSH public key | |
ssh_key_file |
One of four | Path to local SSH public key file | |
ssh_key_literal |
One of four | Inline SSH public key | |
release |
trixie |
Debian codename (bookworm, trixie, forky, ...) | |
timezone |
UTC |
System timezone | |
locale |
en_US.UTF-8 |
System locale | |
keyboard |
us |
Keyboard layout | |
disk_device |
auto |
Target disk (auto-detect or device name) | |
swap_size |
4 |
Swap size in GB | |
efi_size |
512M |
EFI System Partition size | |
boot_size |
1G |
/boot partition size | |
root_size |
4G- |
Root partition min size (- = fill remaining) |
|
extra_packages |
Space-separated list of additional packages | ||
default_hostname |
debian-server |
Default hostname | |
post_install_script |
Path to custom post-install shell script | ||
output |
./output/fai-luks.iso |
ISO output path |
Exactly one ssh_key_* field is required.
The hosts array maps MAC addresses to per-host settings. All hosts share the
same ISO — configuration is applied at install time based on MAC address.
hosts:
- hostname: server01
mac: "0c:c4:7a:69:39:30"
ip: "10.77.0.20/24" # CIDR notation. Omit for DHCP.
gateway: "10.77.0.1" # Required when ip is set.
interface: "enp94s0f0" # Optional. Empty = auto-detect.
dropbear: true # Enable remote LUKS unlock for this host.
dropbear_options: "-I 600 -j -k -p 22 -s"
- hostname: server02
mac: "aa:bb:cc:dd:ee:01"
# No ip/dropbear = DHCP + local console LUKS unlock| Per-Host Field | Required | Description |
|---|---|---|
hostname |
Yes | Target hostname |
mac |
Yes | MAC address (XX:XX:XX:XX:XX:XX) |
ip |
Static IP in CIDR notation (e.g., 10.0.0.1/24) |
|
gateway |
When ip set |
Default gateway |
interface |
Network interface name (auto-detected if empty) | |
dropbear |
true to enable remote LUKS unlock (requires ip) |
|
dropbear_options |
Dropbear CLI options (overrides global default) |
The optional dropbear section sets global defaults for remote LUKS unlock.
Per-host dropbear: true enables it for individual hosts.
dropbear:
enabled: false # Global default
options: "-I 600 -j -k -p 22 -s" # Dropbear CLI options
ssh_key: "" # Separate key for dropbear. Empty = reuse ssh_key_*When enabled, the initramfs starts a dropbear SSH server at boot with a static IP. Connect and unlock remotely:
ssh root@10.77.0.20 # Connect to initramfs dropbear
cryptroot-unlock # Enter the LUKS passphrase./build.sh [OPTIONS]
-c, --config FILE Path to build.yaml (default: ./build.yaml)
-o, --output FILE Override output ISO path
-v, --verbose Verbose output
-h, --help Show help and exit
--arch ARCH Target architecture: amd64 (default) or arm64
--skip-setup Skip fai-setup (reuse existing nfsroot)
--skip-mirror Skip fai-mirror (reuse existing mirror)
--clean Remove all build artifacts and cached data
--dry-run Validate config and show what would be built
- Clones the official faiproject/fai-config at a pinned commit (see UPGRADING.md)
- Cherry-picks useful upstream files (base classes, debconf, hooks)
- Layers custom LUKS/server configs from
overlay/on top - Templates in user-provided values from
build.yaml - Drives the FAI build pipeline (
fai-setup→fai-mirror→fai-cd)
The overlay/ directory mirrors the FAI config space structure exactly. Only
files we wrote live in this repo — upstream files are pulled fresh each build.
The ISO produces this partition layout (sizes are configurable):
GPT Disk
├── Partition 1: /boot/efi 512M vfat (EFI System Partition)
├── Partition 2: /boot 1G ext4 (unencrypted boot)
└── Partition 3: LUKS encrypted container
└── LVM VG: vg0
├── lv root: / 4G+ ext4
└── lv swap: swap 4G swap
After booting the installed system:
- Enter LUKS passphrase — at the console, or via SSH if dropbear is enabled
- Change the LUKS passphrase:
sudo cryptsetup luksChangeKey /dev/<partition> - Change the admin password:
passwd <admin_user> - SSH in:
ssh <admin_user>@<hostname>
- Debian (Bookworm or newer) or Ubuntu 24.04+
- Root access
- ~5GB disk space for build artifacts
- Docker Desktop
- ~5GB disk space (more if cross-compiling amd64 on Apple Silicon)
GNU General Public License v3.0 — see LICENSE.