- Shell 99.2%
- Dockerfile 0.8%
| builder | ||
| config | ||
| installer | ||
| test | ||
| .gitignore | ||
| build-container.sh | ||
| Containerfile | ||
| README.md | ||
| ubuntu-installer-prd.md | ||
gubuntu-installer
Fully automated, offline-capable Ubuntu 24.04 LTS installation system. A containerized build system produces a bootable hybrid BIOS+UEFI ISO containing an Alpine Linux live environment with a pre-built Ubuntu rootfs and installation script.
Philosophy: No wizards, no questions beyond essential parameters, no network dependency at install time. Insert USB, boot, ./install, done.
Prerequisites
- Podman or Docker (podman preferred)
- QEMU with KVM for local VM testing (
qemu-system-x86_64,qemu-img) - OVMF for UEFI testing (e.g.
edk2-ovmfon Arch,ovmfon Ubuntu) - ~10 GB free disk space for the build
Quick Start
1. Build
# Auto-detects podman or docker, handles sudo escalation
./build-container.sh
# Or specify runtime explicitly
./build-container.sh --runtime podman
./build-container.sh --runtime docker
# Rebuild from scratch (no layer cache)
./build-container.sh --no-cache
The build requires root for chroot bind mounts. When using podman as a non-root user, the script automatically escalates with sudo.
Output goes to output/:
ubuntu-installer.iso— hybrid BIOS+UEFI ISO (boot as CD-ROM or dd to USB)SHA256SUMS— checksums
2. Test in a local VM
The quickest way to test is with QEMU using user-mode networking. SSH is forwarded to localhost:2222.
# Boot the installer ISO in UEFI mode (creates 20G test disks automatically)
./test/test-qemu.sh --mode uefi --net user
The ISO boots as a CD-ROM (sr0). Target disks appear as vda, vdb, etc. At the Alpine prompt, run the installer:
./install.sh --hostname testbox --net dhcp --disk /dev/vda
After installation completes, shut down the VM (poweroff), then boot from the installed disk to verify:
# Boot the installed system (just the target disk, no installer)
qemu-system-x86_64 -enable-kvm -m 2048 -smp 2 \
-drive file=test-disk-1.qcow2,format=qcow2,if=virtio \
-bios /usr/share/edk2/x64/OVMF_CODE.fd \
-netdev user,id=net0,hostfwd=tcp::2222-:22 \
-device virtio-net-pci,netdev=net0 \
-nographic -serial mon:stdio
SSH into the installed system:
ssh -p 2222 mycelium@localhost
BIOS mode
./test/test-qemu.sh --mode bios --net user
Bridge networking
For bridge networking (VM gets a real IP on your network):
# Create a Linux bridge with an IP for the host side
./test/setup-test-bridge.sh create-linux br0 --ip 192.168.100.1/24
# Boot with the VM attached to the bridge
./test/test-qemu.sh --mode uefi --net bridge,name=br0
# Clean up when done
./test/setup-test-bridge.sh delete-linux br0
Cloud Hypervisor (UEFI only)
./test/test-cloud-hypervisor.sh --net bridge,name=br0
Cloud Hypervisor has no CD-ROM device, so it boots the hybrid ISO as a raw disk.
Test script options
Both test-qemu.sh and test-cloud-hypervisor.sh accept:
| Option | Default | Description |
|---|---|---|
--image <path> |
output/ubuntu-installer.iso |
Installer ISO |
--disks <n> |
2 |
Number of target disks (QEMU only) |
--disk-size <size> |
20G |
Target disk size |
--mem <MB> |
4096 / 2048 |
Memory |
--cpus <n> |
2 |
CPUs |
--net <config> |
user / none |
Network (see below) |
--serial |
off | Serial console instead of VGA (QEMU only) |
Network configs: user (QEMU only, SSH on port 2222), tap,ifname=<name>, bridge,name=<br>, ovs,name=<br>, none.
Use Ctrl-A X to exit the QEMU console.
3. Write to USB (for real hardware)
sudo ./builder/write-to-usb.sh /dev/sdX
The hybrid ISO is dd-compatible — writing it to USB works identically to writing a raw image. Includes safety checks: confirms the device, refuses to write to system disks.
4. Install (on target machine)
Boot the USB, then:
# DHCP
./install.sh --hostname myserver --net dhcp
# Static IP
./install.sh --hostname myserver --net static,ip=192.168.1.100/24,gw=192.168.1.1,dns=8.8.8.8
# RAID1 on two disks
./install.sh --hostname myserver --net dhcp --raid raid1,/dev/sda,/dev/sdb
Unit Tests
bats test/*.bats
Configuration
| File | Purpose |
|---|---|
config/build.conf |
Ubuntu version, architecture, default user password, compression, output filename |
config/packages.list |
Packages installed into the rootfs |
config/ssh-keys.list |
SSH keys fetched at build time (GitHub and Forgejo) |
Build Dependencies
The container provides all build tools. Key dependencies:
| Package | Purpose |
|---|---|
debootstrap |
Bootstrap Ubuntu rootfs |
squashfs-tools |
Compress rootfs into squashfs |
grub-pc-bin |
GRUB BIOS boot images (cdboot.img, boot_hybrid.img) |
grub-efi-amd64-bin |
GRUB UEFI modules for grub-mkstandalone |
xorriso |
Create hybrid ISO with dual El Torito entries |
mtools |
Build FAT EFI System Partition image without mounting |
cpio |
Repack initramfs with embedded squashfs |
dosfstools |
Format FAT filesystem for EFI image |
Directory Structure
├── build-container.sh # Container build wrapper (podman/docker)
├── Containerfile # Container image for builder
├── builder/
│ ├── build.sh # Main build script
│ ├── write-to-usb.sh # Write ISO to USB
│ └── lib/
│ ├── common.sh # Shared utilities
│ ├── rootfs.sh # Rootfs creation functions
│ ├── alpine.sh # Alpine image + ISO creation
│ └── ssh-keys.sh # SSH key fetching
├── config/
│ ├── build.conf # Build configuration
│ ├── packages.list # Packages to install
│ └── ssh-keys.list # SSH key sources
├── installer/
│ ├── install.sh # Installation script (embedded in image)
│ └── lib/
│ ├── common.sh # Shared utilities
│ ├── disk.sh # Partitioning functions
│ ├── network.sh # Network configuration
│ └── bootloader.sh # GRUB installation
├── output/ # Build output (gitignored)
└── test/
├── *.bats # Unit tests (bats)
├── test-qemu.sh # QEMU test runner
├── test-cloud-hypervisor.sh # Cloud Hypervisor test runner
└── setup-test-bridge.sh # Bridge setup helper
Installed System
- User:
mycelium(password set at build time viaconfig/build.conf) - SSH: Key-only authentication, root login disabled
- Sudo: Passwordless for
mycelium - Network: systemd-networkd (DHCP or static, no netplan)
- Filesystem: btrfs with zstd compression, subvolumes:
@,@home,@snapshots,@var_log,@var_tmp