A scriptable, debootstrap-based Debian installer. Installs Debian Trixie onto a ZFS root pool (single, mirror, or raidz) with optional ZFS-native encryption. Supports BIOS and UEFI. Use it interactively with a text UI, or automate with env vars.
JODI focuses on the initial system install. If you're building a NAS, add your bulk storage pools after the first boot.
Status: works on my machine(s). x86_64/amd64 only for now.
1. Build the live ISO from a git checkout (the build stages tracked files via git ls-files, so an unpacked source archive won't work):
sudo apt-get install -y live-build dpkg-dev rsync
git clone https://github.com/jro/jodi.git
cd jodi
scripts/build-live-iso.sh
# output: dist/jodi-live.iso2. Flash to USB:
sudo dd if=dist/jodi-live.iso of=/dev/sdX bs=4M status=progress conv=fsync3. Boot from the USB drive, then run jodi:
Interactive:
jodiWalks you through disk selection, encryption choice, hostname, users, and shows an install summary before touching any disk.
Non-interactive (setting env vars):
JODI_TARGET_DISKS="sda sdb" \
JODI_WIPE=yes \
JODI_HOSTNAME=myhost \
JODI_ROOT_PASSWORD=changeme \
jodiJODI is idempotent at the bootstrap stage — safe to re-run if it fails partway through, as long as JODI_WIPE=yes is set (which re-wipes and re-partitions the target disks).
| Variable | Default | Description |
|---|---|---|
JODI_TARGET_DISKS |
(required) | Space-separated disk names, e.g. "vda vdb" or "sda" |
JODI_WIPE |
no |
Must be yes to allow destructive disk wipe |
JODI_FORCE_REUSE_POOL_NAMES |
no |
Set yes to skip the vdev-membership safety check and destroy an existing rpool/bpool regardless of which disks its vdevs are on. Only needed when reinstalling on a machine that already has a pool with the default names on the target disks. Dangerous if set incorrectly — will destroy the named pool without verifying it lives on the target disks. |
JODI_FIRMWARE |
auto |
auto (detect via /sys/firmware/efi) | bios | uefi |
JODI_SERIAL_CONSOLE |
no |
yes adds console=tty0 console=ttyS0,115200n8 to the installed kernel |
JODI_ROOT_PASSWORD |
(required unless JODI_USER is set) |
Root password; also sets PermitRootLogin yes in sshd |
JODI_USER |
(optional) | Username for a non-root sudo user to create |
JODI_USER_PASSWORD |
(required if JODI_USER is set) |
Password for the non-root user |
JODI_ENCRYPTION |
none |
none | native (ZFS native encryption) |
JODI_ZFS_PASSPHRASE |
(prompted when needed) | Passphrase for JODI_ENCRYPTION=native; required for non-interactive native encryption |
JODI_HOSTNAME |
debian |
Hostname for the installed system |
JODI_TIMEZONE |
live timezone or UTC |
Timezone for the installed system, e.g. America/New_York |
JODI_BOOT_OPTIONS |
(empty) | Extra installed-kernel boot options, e.g. nomodeset pci=noaer |
JODI_SYNC_CLOCK |
yes |
Best-effort live clock sync before apt/debootstrap work; set no to skip |
JODI_SYNC_CLOCK_TIMEOUT |
20 |
Seconds to wait for best-effort live clock sync |
JODI_ASSUME_DEFAULTS |
no |
Use defaults for optional prompts; useful for serial/automated installs |
JODI_TOPOLOGY |
auto |
Pool topology: auto | single | mirror | raidz1 | raidz2 | raidz3. auto resolves to single (1 disk) or mirror (2 disks); 3+ disks require an explicit value or TUI selection. |
JODI_BPOOL_SIZE |
2G |
Size of the boot pool partition |
JODI_MIRROR |
http://deb.debian.org/debian |
Debian mirror for debootstrap and apt |
JODI_LOCALE |
en_US.UTF-8 |
Locale for the installed system |
JODI_LOG_FILE |
/tmp/jodi-install.log |
Path to write the install log |
Disks are resolved to /dev/disk/by-id/ paths automatically — passing bare names like vda or sda is fine.
auto selects single for 1 disk and mirror for 2. For 3+ disks, topology must be chosen explicitly via JODI_TOPOLOGY (non-interactive) or via the TUI prompt (interactive). Options: mirror, raidz1, raidz2, raidz3.
Each disk gets four GPT partitions: 1 MB BIOS boot (EF02), 512 MB ESP (EF00), JODI_BPOOL_SIZE bpool (BF01), remainder rpool (BF00).
Two ZFS pools: bpool (compatibility=grub2, holds /boot) and rpool (full features, optional native encryption, holds everything else).
rpool/ROOT/debian / rpool/var/log /var/log
rpool/home /home rpool/var/spool /var/spool
rpool/home/root /root rpool/var/cache /var/cache
rpool/srv /srv rpool/var/www /var/www
rpool/usr/local /usr/local rpool/tmp /tmp
bpool/BOOT/debian /boot
Prerequisites: make deps installs QEMU/KVM, OVMF, expect, sshpass, shellcheck, and live-build.
Lint:
make check # bash -n syntax check + shellcheck (errors only)Tests (each spins up a QEMU VM, installs, boots, and asserts):
tests/vm.sh fetch-iso # download Debian Live 13.6 Xfce ISO once
tests/run-cases.sh # run all cases
tests/run-cases.sh bios-mirror # run one case| Case | What it covers |
|---|---|
bios-mirror |
Two-disk virtio mirror, BIOS/SeaBIOS. Baseline. |
uefi-single |
One-disk UEFI/OVMF. Common single-drive hardware path. |
uefi-mirror |
Two-disk mirror, UEFI/OVMF. |
uefi-single-zfs-native-encryption |
One-disk UEFI with ZFS native encryption and local-console unlock. |
_live-tui |
Interactive TUI walkthrough — excluded from the default run. Invoke explicitly: tests/run-cases.sh _live-tui. Boots a live VM with no env vars set and attaches to a serial console. |
See TESTING.md for the full test matrix, individual VM commands, and serial console tips.
- amd64 only for now — GRUB target, ESP fallback path, and live-build config are all x86_64-hardcoded.
- UEFI mirror ESP. Only the first disk's ESP is populated; losing it leaves the machine unbootable despite a healthy-degraded rpool. Workaround:
rsync -a /boot/efi/ /path/to/second/esp/after install. - Offline install. Network is required at install time (for now).
JODI stands on the shoulders of two prior efforts:
- The OpenZFS project's Debian Trixie Root on ZFS HOWTO. The bpool/rpool split, the dataset layout, the GPT partition scheme, and the
zfs-list.cachepriming step all come from this guide. - Dan Fossi's Debian-ZFS-Root-Installation-Script, which showed a working shape for turning the HOWTO into an actual installer script.
PRs and issues welcome.