isobake
Bake unattended Ubuntu install ISOs for machines that have no PXE server.
One command turns a stock Ubuntu Server ISO plus a short YAML file into one ISO per device, each with its own hostname and a unique SHA-512-hashed password, optionally joining your tailnet, installing Docker and NVIDIA drivers, or registering itself with a webhook on first boot. Every ISO can be boot-tested headless under QEMU before it goes on a USB stick.
pipx install git+https://github.com/samotherguy/isobake # or: pip install git+https://github.com/samotherguy/isobake
isobake init # writes isobake.yaml
isobake build # downloads + verifies Ubuntu 24.04, bakes out/node-01.iso
isobake verify out/node-01.iso
Why
PXE, MAAS and Packer are the right answer when you own the network. They are the wrong answer when you are shipping a GPU box to a site with no IT staff, seeding a homelab, or standing up ten lab machines on a Tuesday. There you want a USB stick that installs the OS, sets a unique hostname and password, connects the machine back to you, and never asks a question.
isobake does exactly that, and nothing else.
What you get
- Presets you compose.
base,docker,nvidia,tailscale,reverse-ssh,webhook,encrypted-disk,tpm-unlock,usb-keyfile,poweroff-after-install. Write your own as a folder with apreset.yaml. Presets deep-merge in order. - Per-device credentials. Hostnames
edge-01 .. edge-NNor an explicit list. Each ISO gets a fresh 20-character password, hashed withopenssl passwd -6. Plaintext is printed once and never stored unless you ask for--credentials-file. - A plaintext guard. The build refuses any preset or override that contains a literal password.
- Verified sources. The Ubuntu
SHA256SUMSfile is checked against the Ubuntu CD image signing key with GPG, the ISO against the checksum, and the result is cached in~/.cache/isobake/isos. - Fast repack. The source ISO is never extracted. xorriso rewrites it in
place with the patched
grub.cfgand thenocloudfiles, replaying the original El Torito and hybrid MBR/GPT boot equipment. About five seconds per ISO on an SSD. - Full-disk encryption that boots unattended.
encrypted-diskputs LUKS2 under LVM with a per-device passphrase.tpm-unlockseals a key in the TPM 2.0 so the box boots on its own but the disk is dead if pulled.usb-keyfiledoes the same with a USB stick for hardware without a TPM. - A real boot test.
isobake verifyboots the ISO in QEMU with KVM and OVMF, headless, in three phases: the install must finish and power off, the installed disk must complete first boot (every preset'sruncmd) and power off, then it must come back up unattended and power off once more. With--tpmand--usb-keythe VM gets an emulated TPM or a virtual key stick, so the encryption presets are proven end to end.
Project file
# isobake.yaml
source:
release: "24.04" # or path: /path/to/ubuntu-live-server.iso
presets: [base, docker, tailscale, webhook]
devices:
prefix: edge
count: 3 # edge-01, edge-02, edge-03
# list: [{hostname: rack-a, username: ops}, lab-b]
identity:
username: ops
ssh_keys: [~/.ssh/id_ed25519.pub]
vars: # fills ${name} placeholders in presets
tailscale_authkey: tskey-auth-...
webhook_url: https://inventory.example.com/register
overrides: # raw autoinstall keys, merged last
autoinstall:
timezone: Asia/Singapore
kernel_args: [console=ttyS0,115200n8]
output: ./out
$ isobake build
signature ok: SHA256SUMS signed by the Ubuntu CD image key
cached: /home/you/.cache/isobake/isos/ubuntu-24.04.5-live-server-amd64.iso
building out/edge-01.iso
building out/edge-02.iso
building out/edge-03.iso
hostname username password
edge-01 ops kQ3xT9wLm2Zp7RfC1dHs
edge-02 ops Vb8NcJ4yPq6WtE2gXz0K
edge-03 ops mR5sYh1LqA9nD3vF7uBt
Passwords are shown once and are not stored. Copy them now.
Flash with dd, Ventoy, Rufus or balenaEtcher. Boot the machine from USB. Walk away.
Presets
| Preset | What it does | Needs |
|---|---|---|
base |
US locale, direct whole-disk layout, OpenSSH, curl, qemu-guest-agent | |
docker |
Docker Engine and compose plugin from Docker's apt repo, user in docker group |
internet |
nvidia |
Recommended NVIDIA driver plus Container Toolkit; configures Docker runtime if present | internet |
tailscale |
Joins your tailnet with Tailscale SSH on | vars.tailscale_authkey |
reverse-ssh |
Persistent autossh reverse tunnel to a host you own | vars.reverse_ssh_host/user/port |
webhook |
POSTs hostname, machine-id, MAC, IP and SSH host key on first boot | vars.webhook_url |
encrypted-disk |
LUKS2 under LVM, per-device passphrase shown once at build | |
tpm-unlock |
Seals the LUKS key in the TPM 2.0 (PCR 7) with Clevis during the install | encrypted-disk, TPM 2.0 on |
usb-keyfile |
Keyfile on a USB partition labelled linux-key, unlocked through cryptsetup's passdev |
encrypted-disk, the stick plugged in during install |
poweroff-after-install |
Test only: power off after install, after first boot, and on every boot after |
Merge rules: mappings merge recursively, lists append without duplicates,
scalars from later presets win. overrides: is applied after all presets.
Placeholders look like ${name} with a lowercase name, so shell variables
like $HOME and $(cmd) in runcmd pass through untouched. Built-in
placeholders: ${hostname}, ${username}, ${password_hash}.
Add your own:
my-presets/
k3s/
preset.yaml # an autoinstall fragment
README.md # first line is the description shown by `isobake presets`
isobake build --preset-dir ./my-presets --preset base,k3s
Verify
isobake build --verify # bakes, then boots every ISO under QEMU
isobake verify out/edge-01.iso # boot an ISO you built earlier
Needs qemu-system-x86, qemu-utils and ovmf (sudo apt install them).
The VM gets 4 GB RAM, 2 CPUs, a 20 GB qcow2 disk and user-mode networking.
Three phases, each with its own timeout:
- install: boot the ISO. Pass when the installer finishes and runs
poweroff. - first boot: boot the installed disk without the ISO. Pass when
cloud-init has run every preset's
runcmdand powers off. - reboot: boot the disk again. Pass when it comes up unattended and powers off. This is where TPM and USB unlock are proven: a passphrase prompt would sit there until the timeout.
Timeout or any other exit is a fail, and the last 40 lines of the serial
log are printed. Add console=ttyS0,115200n8 to kernel_args to get the
installer's output in that log. --no-reboot stops after phase two,
--installer-only after phase one.
--verify on build injects the poweroff-after-install preset and, when
the config selects tpm-unlock or usb-keyfile, attaches a software TPM
(swtpm) or a 16 MB virtual stick labelled linux-key. On a standalone
isobake verify pass --tpm and --usb-key yourself.
To test a production ISO without rebuilding it:
isobake verify out/edge-01.iso --inject-poweroff --tpm
That merges the poweroff preset into a temporary copy and boots the copy.
The ISO you ship is untouched. --keep-disk leaves the VM disk and TPM
state behind, and --resume DISK picks them up again without reinstalling.
isobake doctor lists every tool verify and build need and what to install.
Inspect
$ isobake inspect out/edge-01.iso
kernel lines:
linux /casper/vmlinuz autoinstall ds=nocloud\;s=/cdrom/nocloud/ console=ttyS0,115200n8 ---
linux /casper/hwe-vmlinuz autoinstall ds=nocloud\;s=/cdrom/nocloud/ console=ttyS0,115200n8 ---
nocloud files: /nocloud, /nocloud/meta-data, /nocloud/user-data
user-data:
#cloud-config
autoinstall:
version: 1
...
Encryption
presets: [base, encrypted-disk, tpm-unlock]
$ isobake build
hostname username password disk passphrase
edge-01 ops kQ3xT9wLm2Zp7RfC1dHs Hs8dKq2mN4vB7xR1tY6wP0zL
The installer formats the disk as LUKS2 under LVM with that passphrase.
Enrolment happens inside the installer, while the volume is already open,
because a first-boot script could never run: the disk would ask for the
passphrase first. tpm-unlock installs Clevis into the target and binds
the volume to the TPM 2.0 against PCR 7 (secure-boot state); Ubuntu's own
initramfs then unlocks it at boot, no dracut swap needed. Move the disk to
another machine, or change the firmware, and it asks for the passphrase
instead. The passphrase stays in keyslot 0 as the recovery key, and the
install logs that contain it are shredded on first boot.
usb-keyfile is for hardware without a TPM 2.0. With the key stick plugged
in during the install, a random 256-byte key is written to its linux-key
partition (or reused if one is there), enrolled as a keyslot, and crypttab
points at it through cryptsetup's passdev keyscript with a 10-second
wait. Stick in: boots. Stick out: passphrase prompt.
Both are verified by isobake build --verify, which attaches an emulated
TPM (swtpm) or a virtual key stick to the VM, keeps UEFI NVRAM across
phases like a real machine, and requires the first boot and a further
reboot to come up unattended.
Limits worth knowing: the LUKS passphrase has to reach the installer in
clear, so it is in /nocloud/user-data on the ISO. Treat built ISOs as
secrets and wipe the stick after use. TPM sealing against PCR 7 survives
kernel updates but not firmware or secure-boot changes; keep the passphrase.
Requirements
Linux host, Python 3.10+, xorriso, gpg, openssl, curl. On Debian
and Ubuntu: sudo apt install xorriso gnupg openssl curl. QEMU and OVMF
only for verify, swtpm for --tpm, e2fsprogs for --usb-key.
Run isobake doctor to check. Tested with Ubuntu Server 24.04 ISOs; 22.04 should work
since the grub layout is the same. Desktop ISOs are not tested.
How it works
source.pyreadsreleases.ubuntu.com/<release>/SHA256SUMS, verifies its GPG signature with a dedicated keyring, picks the newestlive-server-amd64ISO, downloads it with resume, checks the hash.presets.pymerges the chosen fragments and fills placeholders.render.pyaddsidentity, SSH keys and overrides, refuses plaintext passwords, and dumps a#cloud-configdocument with theautoinstallkey.iso.pypulls/boot/grub/grub.cfgfrom the source, addsautoinstall ds=nocloud\;s=/cdrom/nocloud/to each kernel line, sets the menu timeout to 1, and asks xorriso to write a new ISO with the patched grub plus/nocloud/user-dataand/nocloud/meta-data, boot equipment replayed from the source.verify.pyruns QEMU three times, ISO then disk then disk again, with a persistent NVRAM image so guest reboots behave like real hardware. QEMU exits only when the guest powers off; three clean exits are a pass.
Security notes
- Passwords are generated with
secrets, hashed with SHA-512 crypt, and printed to stdout once.--credentials-filewrites them with mode 0600. - Signature verification can be skipped with
--no-gpg. Do not do that for machines you care about. - Anything in
runcmdruns as root on first boot. Read a preset before you trust it. - The
reverse-sshandtailscalepresets embed the auth material you give them in the ISO. Treat built ISOs as secrets.
Development
git clone https://github.com/samotherguy/isobake
cd isobake
python3 -m venv .venv && .venv/bin/pip install -e '.[dev]'
.venv/bin/pytest
.venv/bin/ruff check src tests
The test suite builds a 1 MB synthetic ISO with xorriso and runs the whole
pipeline against it, so it is fast and needs no download. The real boot test
is manual: isobake build --verify with a real release.
Background
The USB-stick install is still how most machines outside a datacenter get their OS: edge boxes, lab hardware, kiosks, homelab nodes. The tooling for doing it unattended has been a bash script and a wiki page for a decade. isobake is the tool I wanted for that job: presets instead of hand-edited YAML, a unique identity per machine, encryption that does not need someone at the keyboard, and a boot test so the stick works the first time.
License
MIT
Metadata
Release files for isobake 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| isobake-0.2.0.tar.gz | 38.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| isobake-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 74.2 kB
Release files / isobake-0.2.0.tar.gz
| Download URL | isobake-0.2.0.tar.gz |
|---|---|
| Size | 38.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
01a3f941a3eb4699f0f747f974b0bb41c3bf1ed420d536094202c7442ee302a0
|
|
BLAKE2b-256 checksum How to use checksums |
edfa4ae4063cfbdb12dba953d11ee37425d6944c9b1c5ce92ff84b131bfdeffd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.13
|
Release files / isobake-0.2.0-py3-none-any.whl
| Download URL | isobake-0.2.0-py3-none-any.whl |
|---|---|
| Size | 35.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ab1d4c61390fb72a4997354f04ebab4e2b0c0b2f9b890ef97f2dd2820427668f
|
|
BLAKE2b-256 checksum How to use checksums |
1cafd9f8c5bca2fc513224d3070380f1ce6784093792e83ecb80ccef108bf695
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.13
|