A 3 MB boot ISO that installs 20+ server operating systems — fully automated.
Free and open source, from FluxBilling.app —
billing, DCIM and IPAM for hosting providers.
Rack the server, attach the ISO to iDRAC/iLO/IPMI virtual media, answer five prompts, pick an OS, walk away. It reboots into a configured system with SSH root login.
Every heavy payload — kernels, initrds, install ISOs — streams at boot over the data NIC you choose in the menu, straight from the official distro mirrors. The slow management port only ever carries the 3 MB image, and there is no private config webserver: installer identity and network config are generated on the machine itself from your answers.
- Tiny — ~3 MB, attaches over IPMI virtual media in seconds.
- Zero-touch — hostname, static IP and root password typed once.
- No PXE server, no DHCP — static IP from the menu; works in any colo.
That holds for the installer image too, not just the installed system: the
Ubuntu seed carries a
network-configso cloud-init configures the live environment from the address you typed instead of falling back to DHCP and waiting for a lease that is never coming. - Manual mode — one toggle boots the same installer interactively.
- BIOS + UEFI, serial console (SOL) mirrored, /31 subnets supported.
| Family | Versions | Automated config |
|---|---|---|
| Ubuntu (subiquity) | 26.04 LTS, 24.04 LTS, 22.04 LTS | NoCloud seed generated at boot |
| Ubuntu (d-i) | 20.04, 18.04 | preseed + cmdline |
| Debian | 13, 12, 11 | preseed + cmdline |
| AlmaLinux | 10, 9, 8 | kickstart (%pre from cmdline) |
| Rocky Linux | 10, 9, 8 | kickstart |
| CentOS Stream | 10, 9 | kickstart |
| Oracle Linux† | 10, 9, 8 | kickstart (same file as Alma/Rocky) |
| openSUSE Leap | 15.6 | AutoYaST profile |
| openSUSE Leap | 16.0 | Agama profile injected into the initrd |
| Proxmox VE† | 9 (9.2), 8 (8.4) | answer.toml generated in-installer from cmdline |
† Oracle publishes no netboot images and Proxmox no netboot installer at
all, so these entries boot kernel/initrd (and Oracle's anaconda stage2) from
this repo's signed boot-* release — they stay on the menu but fail
cleanly until that release is cut (see
docs/SIGNED-BOOT.md and src/sign-boot-images.sh).
The Proxmox install ISO itself streams unmodified from
download.proxmox.com into RAM, where the stock installer finds it as
/proxmox.iso — manual mode is the ordinary Proxmox GUI installer, automated
mode generates the official answer.toml on the machine from your typed
answers (password as a SHA-512 hash, never plaintext at rest).
-
Grab
FluxBilling-OS-Installer_v1.6.isofrom Releases — or build it. -
Attach via virtual media — or flash a USB stick:
# macOS - replace diskN with the USB stick diskutil unmountDisk /dev/diskN sudo dd if=FluxBilling-OS-Installer_v1.6.iso of=/dev/rdiskN bs=1m# Linux - replace sdX with the USB stick sudo dd if=FluxBilling-OS-Installer_v1.6.iso of=/dev/sdX bs=1M status=progress conv=fsyncWindows — Windows has no
dd. Use Rufus: select the ISO, and when it asks, pick DD Image mode (not ISO mode), then Start.USB boot needs a hybrid MBR. Virtual media — the intended path — is unaffected: iDRAC/iLO present the image as a CD and El Torito handles it. Verify a build is USB-bootable before trusting a stick:
xxd -s 510 -l 2 *.isomust read55aa, not0000. -
Boot it and answer:
Port number [ENTER = 0] <- live NIC list (pick the data NIC) IP / subnet: 203.0.113.111/27 <- one field; /1../31 (incl. /31 p2p) Gateway [ENTER = 203.0.113.97] <- auto-calculated, ENTER accepts Hostname: Password: <- hidden (TAB to the field, ENTER) -
Review screen, fix any field, pick an OS.
-
Automated mode asks Erase every disk and install? — answer Yes. The OS goes on the first disk and every disk in the server is wiped first; see Disks. The default is No, which goes back to the OS menu without touching anything. Done.
The OS menu opens with two toggles. Install mode switches AUTOMATED
(answer files injected, zero-touch, every disk erased after you confirm) and
MANUAL (same installer, no answer file, driven by hand — nothing is
erased for you). Installer output picks which console the installer
UI actually draws on - SOL (serial, the automated default and what a
headless node gives you) or Video (the iDRAC/iLO/VNC screen, the manual
default). Kernel messages go to both either way, so a video console left on
SOL shows dmesg, then nothing: that silence is the installer running
elsewhere, not a hang. BOOTIF=01-<mac> lets every installer find the boot
NIC by MAC, so there is no NIC-name guessing on any hardware.
One iPXE image with a custom CIDR parser (fluxcidr, C, compiled in) and the
payload files listed in build.sh's EMBEDLIST. At boot iPXE fetches the
official kernel/initrd for the chosen OS and injects the matching answer
file into the initrd in memory (cpio append) — no vendor artifact is ever
rebuilt or redistributed. Answer files ride under flux-* names so no
installer auto-probes them during a manual install.
Per-family mechanics, mirror URLs and the hard-won gotchas are documented inline in fluxbilling.ipxe and src/builder.Dockerfile.
Every URL is baked in at build time, and an ISO burned to a customer's iDRAC cannot be edited — so an upstream move would otherwise kill that menu entry forever. Two mechanisms cover it, neither of which needs FluxBilling to host anything:
- Fallback hosts. A failed casper fetch retries
old-releases.ubuntu.com, where Ubuntu moves superseded point releases; the d-i entries walk a country mirror and then the archive host (archive.debian.orgfor a deleted Debian suite). Both paths are exercised before a release by booting ISOs with the primary host deliberately dead. - A watchdog. upstream-watch probes every URL the menu fetches — generated from the menu itself by src/menu-urls.sh, never hand-copied — the way iPXE fetches it, and opens a bump PR when Ubuntu supersedes a pinned release.
- A pairing check, on every entry. Each entry boots images from one
upstream path and then has the installer download a payload — an ISO, a
repo, an apt mirror — from another, and the two sides are refreshed on
different schedules. Ubuntu 24.04 showed what that costs: Canonical rebuilds
<point>/netboot/in place while the ISO beside it stays frozen, casper found a UUID that did not match, unmounted the ISO it had just downloaded and panicked onto/dev/console— the serial line in automated mode, so the operator watching the video console saw nothing at all. src/boot-pairing-check.sh now asserts the pairing for all 23 entries on every push: textually where both halves come from one menu variable (Debian, Alma/Rocky/CentOS, Leap 15.6), and by content where they do not — the netboot kernel must be one of the kernels inside the exact ISO casper downloads, the Leap 16 loader kernel must be the one inside the Agama ISO, the Proxmox ISO the menu boots must be the one the signing manifest extracted from. An entry with no rule fails the build, so the next distro cannot ship unverified, and src/pairing-selftest.sh proves the gate still rejects each breakage it was written for. The Ubuntu entries boot from the codename tree (noble/,resolute/), whose netboot images upstream rebuilds alongside the newest point-release ISO: 26.04 rides theubuntu-26.04-latest-…isoalias, and 24.04 names that ISO (24.04.5) itself, since Canonical stopped serving the noble alias; 22.04 has no codename netboot tree, so it keeps a pin frozen on both halves. - A readable failure. If an installer does stop, it says so on every
console the machine has. Stock casper prints its reason and opens its debug
shell on a single console — the serial line in automated mode — which is how
a real failure looked like a frozen screen on an iDRAC. A
panic()wrapper injected as/conf/param.confprints a labelled INSTALL FAILED banner with the reason to the video console and the serial line, states that nothing was written to the target disk, and then holds: it never reboots, and it ignores apanic=boot argument so a wedge cannot turn into a silent reboot loop. The dracut-based installers (Alma, Rocky, CentOS Stream, Oracle, Leap 16.0) get the same banner from an emergency hook, run just before dracut opens its emergency shell. dracut keeps that hook directory underusr/libon EL8/EL9 and undervar/libon EL10 and Leap 16, and no injected path reaches both — so a small/etc/conf.dsnippet copies the hook into whichever one dracut itself uses, at boot. The anaconda entries also have to get to that shell when the mirror or the network is down, which stock dracut does not do in any useful time: anaconda retries its installer image on every pass of dracut's initqueue, and dracut gives up only after 360 passes — a count, not a clock. With nothing answering, one pass is a stack of curl timeouts; measured on Alma 9 that put the shell 2½ hours (mirror refusing connections) to almost four days (every SYN dropped) away, with nothing on screen butfailed to fetch stage2scrolling past. src/flux-giveup.sh, installed by the same snippet, gives up after five minutes of failed passes instead, caps each curl attempt so a pass cannot overshoot by much, and puts the mirror host and curl's last error in the banner. A download that is still running is never cut short, and exiting the shell (manual mode) retries for another five minutes. Leap 16.0 had the same hole in a different place: dracut's livenet module fetches the Agama ISO inside a single initqueue pass — 100 tries, 5 s apart — and in QEMU each try took 92 s with DNS unreachable and 5 minutes with every SYN dropped, so that one pass ran 2½ to 8½ hours before dracut even started counting its passes. The same job hands that loop ten tries instead, under the same curl caps, and opens the shell on the very next pass: the banner came 4 minutes after the first try with DNS down and 6 with every SYN dropped. Resetting the server is what returns you to the boot menu — iPXE is gone once a kernel is running, so there is no way back to it from inside the installer. src/console-ux-test.sh asserts all of that, including the case that caused the original bug: onlyttyS0registered, banner still reaches the video console. - A visible, fast download. casper fetches a 3–4 GB ISO with
wget, whose progress bar goes to one console — so the other one shows nothing for as long as the fetch takes, which is how a working install gets power-cycled by hand. A watcher injected as part of/conf/param.confmirrors the download to the consoleswgetis not drawing on. The same hook also picks where to fetch from: src/flux-mirror-pick gives the origin a two-second trial and, if it is slow, races Ubuntu mirrors and moves to the fastest one that beats it by a clear margin. A mirror copy is mounted with casper's ownmatches_uuidcheck restored, so a mirror that is a different compose is rejected and the origin is used instead — measured on a customer box whose unmetered 10 Gbps port still only pulled 0.8 MB/s from releases.ubuntu.com.
Automated mode installs to the first disk and wipes every disk in the
server — after asking. Picking an OS in AUTOMATED mode opens an
Erase every disk and install? screen. No is the default and returns to
the OS menu; Yes is the only thing that authorises a wipe (it adds
fluxwipe=1 to the kernel command line, and nothing is wiped without it).
MANUAL mode never asks and never wipes: the installer is yours to drive.
One rule, src/flux-disks, decides for every entry, so Ubuntu, Debian, the EL family, openSUSE and Proxmox all agree on which disk is "first" (Ubuntu 22.04+ used to take the largest disk, and Leap whatever its proposal settled on):
- First means the first eligible whole disk in kernel-name order — the
order
/sys/blocksorts in:nvme0n1beforenvme1n1, NVMe before SATA/SAS/RAID volumes (sda,sdb, …), those before virtio (vda). On a box that mixes NVMe with a RAID volume or a BOSS card, the OS therefore goes onnvme0n1. - Never touched, never chosen: removable media, anything attached over USB (including the stick this ISO was booted from and BMC virtual media), Fibre Channel and iSCSI LUNs (shared storage is someone else's data), read-only and empty devices, and virtual devices (loop, zram, md, dm, …).
- The wipe runs before the installer looks at the disks. It stops whatever lives on them (swap, md arrays, LVM, dm-crypt), zeroes the first and last 16 MiB of every partition and of every disk — partition tables (MBR and both GPT copies), RAID metadata, LVM, ZFS labels, LUKS headers and filesystem superblocks — and then discards the whole device on SSD and NVMe, which drops the data itself. It takes seconds to a few minutes, not hours.
- What it does not do: overwrite the rest of a spinning disk. The old
data in the middle of an HDD is unreachable by any OS but recoverable with
forensic tools. If that matters, run a dedicated erase (
hdparmsecurity erase,nvme format -s1,shred) before installing. - A disk that is still busy after all that — something the installer itself depends on — is skipped, not pulled out from under it, and the log says so.
- No eligible disk at all: nothing is wiped, and the installer stops (d-i at its own disk question) rather than guess.
The installer logs what it chose and what it wiped — FluxBilling: install disk: … and FluxBilling: wiped … in the kernel log (dmesg) and the
installer's own log. src/disks-test.sh proves the rule
against a fake /sys and /dev under dash, bash and d-i's busybox, and holds
every installer's copy of it to the one file.
The Ubuntu 18.04/20.04 and Proxmox entries carry their answer files inside
the signed boot-* release images, so they follow this rule once that
release is cut with the current src/sign-boot-images.sh; an image cut
before it installs to the first disk without wiping the others.
- RAM: Ubuntu 22.04+ stream the full live ISO to RAM — 8 GB+. Proxmox VE streams its full install ISO the same way — 6 GB+. Alma/Rocky/CentOS/Oracle stage2 — 4 GB+. Leap 16.0 — 2.5 GB+.
- CPU: Alma/Rocky/Oracle 10 and CentOS Stream 10 need x86_64-v3 (Haswell/EPYC or newer). Older Xeons: use the 9.x entries.
- Internet reachability from the static IP you enter.
- Password rides the kernel command line: letters, digits and
._-!@#%^*+=are safe; avoid spaces, quotes,;,\,/. - Fallback hostname if the prompt arrives empty:
fluxserver. There is no fallback password — iffluxpass=is missing, both accounts are left locked rather than given a known default. - Secure Boot must be off.
ipxe.efiis built here and is not signed by a Microsoft-trusted CA, so UEFI firmware with Secure Boot enabled refuses to load it. No practical fix exists for a custom iPXE build; disable Secure Boot for the install, and re-enable it afterwards if the installed OS ships a signed shim (Ubuntu, Debian, Alma, Rocky and Leap all do). - Tested end-to-end: all automated entries (24 at the time, including the since-removed 25.10), under both legacy BIOS (SeaBIOS) and UEFI (OVMF) — each install ran to completion in KVM, the guest rebooted off its own disk and accepted a root SSH login with the menu-typed password, with hostname, static IP and os-release asserted (48/48 PASS, 2026-08-15). Ubuntu 24.04 additionally verified on physical Dell iDRAC; the other entries share the same verified mechanics but still deserve a hardware smoke test on real NICs and RAID controllers, which QEMU's single virtio disk and slirp network do not exercise.
What is and is not protected, honestly:
- Packages are verified. APT and DNF check every package against the distro GPG keys, so what lands on disk is signature-checked end to end.
- Bulk downloads use HTTPS. The Ubuntu ISO, the anaconda stage2 and
repos, the Leap live squashfs and
install=tree are all fetched by the installer, which carries a full CA bundle. - Kernel and initrd fetches are plain HTTP and unverified until the first
signed
boot-*release ships. The binary now bakes in a set of public root CAs (CERT=/TRUST=in build.sh), so the https fetches iPXE does make — the 22.04 GitHub release, and mirrors that redirect http→https — validate with noca.ipxe.orgdependency; but the plain-http kernel/initrd fetches still trust the network. Run the installer on a trusted management network, and cut the signed release to close the gap for good. - Credentials never persist in cleartext. The root password rides the
kernel command line, so every family creates its accounts locked, sets the
real password through
chpasswd(hash only, in/etc/shadow), and scrubsfluxpass=out of the installer logs on first boot. One exception: a manual openSUSE Leap 16.0 install also carrieslive.password=(the Agama web-UI login) on the command line, and manual mode runs no profile — so nothing installs the scrub. Clear/var/log/agama-installationafter a manual Leap 16 install, or use automated mode, which never passes it. Proxmox has no post-install hook a stock ISO can run, so its generatedanswer.tomlcarries a SHA-512 crypt hash instead of the password — what/etc/shadowwould hold anyway — and the plaintext never exists outside the live installer's kernel command line. - The Proxmox install ISO is fetched over plain HTTP — forced, not
chosen:
download.proxmox.com's TLS certificate does not name that host (it names thecdn.proxmox.compool), so an https fetch fails hostname validation. The signedboot-*release stages a detached signature for the exact ISO (proxmox-N-iso.sig, fetched for signing over valid https fromenterprise.proxmox.com), closing this the same way the kernel/initrd gap closes. - Root SSH with password authentication is enabled on every family. That is a deliberate provisioning convenience — harden it immediately after install if the box faces the internet.
To close the kernel/initrd gap, see docs/SIGNED-BOOT.md:
mirror and sign the ~350 MB of boot images, bake your own CA fingerprint into
the iPXE binary, and let imgverify reject anything tampered with — over
plain HTTP, with no dependency on anyone else's PKI.
Needs Docker. Everything is pinned:
./build.sh # outputs FluxBilling-OS-Installer_v1.6.isoFirst run bakes the builder image (~10 min); every rebuild after that is ~15 seconds. The build is reproducible from a clean clone: every toolchain version, iPXE commit and payload URL is pinned in src/builder.Dockerfile and build.sh.
FluxBilling does not use this ISO. Inside the panel, bare-metal deployment and IPAM are fully automated — nothing to attach, nothing to type.
But plenty of machines sit outside a panel: a box you're rebuilding, a one-off install, a rack you don't manage yet, someone else's colo. That is the job this image does, and we release it free because provisioning a server should not require a PXE stack or an afternoon of virtual media.
Billing, DCIM and IPAM for hosting providers — in one place.
Servers, IPs, invoices and clients, with bare-metal deployment fully
automated inside the panel. Built like a native app.
→ See the panel
Thank you @AlexIancu98 for your contribution.
GPL-2.0-or-later — see LICENSE.
The ISO is a modified iPXE binary (copyright Michael
Brown and contributors), distributed under the GNU GPL v2 or later with
iPXE's Unmodified Binary Distribution Licence exception (COPYING.UBDL).
Base: iPXE commit
56a4f695.
Every modification is an explicit patch step in
src/builder.Dockerfile — config edits, the EFI
autoexec stub, and the added fluxcidr command
(src/fluxcidr_cmd.c). Corresponding source is this
repository; ./build.sh rebuilds the released ISO from it. The embedded
answer files are original work under the same terms.
No operating system is redistributed here — with narrow, documented
exceptions. Kernels, initrds, install ISOs and repositories are fetched at
boot, unmodified, from the vendors' own mirrors, except where a vendor
publishes no netboot artifacts at all: the Ubuntu 22.04 kernel/initrd come
from a netboot.xyz GitHub release (Canonical never
published a netboot tree for that series — see the boot2204 note in
fluxbilling.ipxe), and the signed boot-* release
described in docs/SIGNED-BOOT.md additionally carries
the Oracle Linux kernel/initrd/stage2 and the Proxmox VE installer
kernel/initrd, each extracted unmodified from the vendor's official ISO
(both are freely redistributable under their respective licenses; sources
are the vendors' own repositories). Answer files are injected into the
vendor initrd in RAM, on the operator's own machine.
Trademarks. Ubuntu is a trademark of Canonical Ltd; Debian of Software in the Public Interest, Inc; Red Hat and CentOS of Red Hat, Inc; AlmaLinux of the AlmaLinux OS Foundation; Rocky Linux of the Rocky Enterprise Software Foundation; openSUSE of SUSE LLC; Oracle and Oracle Linux of Oracle and/or its affiliates; Proxmox and Proxmox VE of Proxmox Server Solutions GmbH. All marks belong to their respective owners and are used descriptively to identify the operating systems this installer can fetch. This project is not affiliated with, sponsored by or endorsed by any of them, nor by the iPXE project or netboot.xyz.
FluxBilling OS Installer v1.6 — powered by iPXE.