9.5 KiB
Executable File
A Linux-Native DriveStage for the ext4 Era
Design rationale and architecture notes for a clean-slate multi-payload USB toolkit. Inspired by Easy2Boot and RMPrepUSB. Built from scratch for modern Linux.
Author: Jeremy Anderson info@dcos.net — dcos.net
Motivation
Easy2Boot and RMPrepUSB by Steve Si defined the multi-payload USB use case. They demonstrated that a single USB drive can host dozens of bootable ISOs and present a selection menu at boot time. That workflow is the correct workflow. The original implementation, however, targets a computing era that predates UEFI, ext4, and modern GRUB2.
Easy2Boot originated when:
- Windows XP was current. The tooling is Windows batch and .NET.
- FAT32 was the only viable filesystem. The 4 GB file size limit forced ISO splitting, contiguous-file allocation tricks, and partition table patching at boot time.
- BIOS was the only firmware. MBR ruled. UEFI did not exist in the consumer market.
- grub4dos was the bootloader. A branch of an earlier GRUB lineage, unmaintained for over a decade.
The accumulated design constraints of that era — NTFS driver loading,
contiguous file defragmentation, .mnu sidecar files, a switch utility
that rewrites the partition table on every selection — function in their
niche, but they impose fragility and opacity. On Linux, provisioning a
USB stick through that pipeline requires WINE or a Windows VM. That is
the wrong tool for the job.
This project is an independent implementation of the same use case, designed for the assumptions that hold today: Linux as the host, UEFI as the firmware, ext4 as the filesystem, and GRUB2 as the bootloader.
Project naming
DriveStage is the public project name. BlkStage is the internal backend
that ships as scripts/blkstage.sh and installs on PATH as the
drivestage CLI command. The GUI binary is drivestage-gui. The
previous codename carried a trademark conflict; the rename is deliberate
and complete across source, docs, install paths, partition labels, and
GRUB cfg identifiers.
Architecture
Partition layout
Three partitions. GPT. Standard-compliant. Deterministic.
├─ Partition 1: ESP (FAT32, 512 MB) ← UEFI requirement
├─ Partition 2: BIOS Boot (unformatted, 1 MB) ← GRUB GPT embedding
└─ Partition 3: Payloads (ext4, remainder) ← all payload data
The ESP holds only GRUB's .efi binaries. The BIOS Boot partition is a
1 MB slot for GRUB's i386-pc boot code (a GPT requirement for BIOS
booting). Everything else — ISOs, rootfs tarballs, kernel images,
configuration files — resides on the ext4 partition.
Ext4 by decision. GRUB2 ships built-in ext2/ext3/ext4 drivers. UEFI loads GRUB from the FAT32 ESP; GRUB loads its drivers and reads menu entries and kernels directly from the ext4 partition. The FAT32 4 GB file size limit no longer applies. NTFS drivers are unnecessary. Contiguous-file defragmentation is unnecessary. Partition table rewriting is unnecessary. Files reside on the filesystem as files.
Bootloader
GRUB2. Both targets. Same drive.
grub-install --target=x86_64-efi --efi-directory=/mnt/.../boot/efi --removable
grub-install --target=i386-pc --boot-directory=/mnt/.../boot /dev/sdX
Two commands. The drive boots on a 2024 UEFI machine and a 2008 BIOS machine without reconfiguration. Modern GRUB is mature, universally packaged, and handles ext4 + loopback + ISO boot patterns for every major distribution.
Boot-time auto-discovery
The defining feature. Drop an ISO onto the stick and it appears in the
boot menu. No host-side menu generation. No config file editing. No
scan command for ISOs.
GRUB's scripting language is more capable than commonly assumed. It
supports for loops, if/elif/else, regexp, and user-defined
functions. The auto-scan grub.cfg performs the following at boot:
- Locates the payload partition by UUID
- Iterates over
/payloads/isos/*.iso - For each ISO, runs a
regexpcascade to identify the distribution from the filename - Invokes a function that emits the appropriate
menuentrywith distro-specific kernel arguments - Iterates over
/payloads/rootfs/*/and emits direct-boot entries - Iterates over
/payloads/images/*.imgand emits chainload attempts
The result: drop an ISO into the payloads directory, eject the drive, boot it, and the entry appears in the menu.
A snippet from the auto-scan grub.cfg:
function ds_iso_entry {
set iso_path="$1"
set iso_name="$2"
set distro="unknown"
if regexp --quiet --ignore-case 'ubuntu' "$iso_name"; then set distro="ubuntu"
elif regexp --quiet --ignore-case 'archlinux' "$iso_name"; then set distro="arch"
elif regexp --quiet --ignore-case 'debian.*live' "$iso_name"; then set distro="debian-live"
# ... 30 additional patterns
fi
if [ "$distro" = "ubuntu" ]; then
menuentry "$iso_name [ubuntu]" "$iso_path" {
set iso_path="$1"
search --no-floppy --fs-uuid --set=root $ds_payload_uuid
loopback loop "$iso_path"
linux (loop)/casper/vmlinuz boot=casper iso-scan/filename=$iso_path quiet splash ---
initrd (loop)/casper/initrd
}
elif [ "$distro" = "arch" ]; then
# Arch-specific kernel arguments
fi
}
for f in /payloads/isos/*.[iI][sS][oO]; do
if [ -f "$f" ]; then
ds_iso_entry "$f" "$f"
fi
done
The complete configuration is approximately 350 lines. It is written once at setup time to embed the partition UUID and requires no further modification.
The engine (BlkStage)
The engine is a single bash file, approximately 1500 lines, with six subcommands:
| Subcommand | Purpose |
|---|---|
setup |
Partition, format, install GRUB, write the auto-scan grub.cfg |
deploy |
Copy a payload file; extract tarballs on copy |
scan |
Extract tarballs; refresh the UUID in grub.cfg |
list |
Display registered payloads and sizes |
shell |
Mount partitions and open a helper shell |
clean |
Wipe the partition table and signatures |
The engine refuses to operate on the disk hosting / or /boot.
Destructive operations require typed confirmation. It handles
/dev/sda versus /dev/nvme0n1p1 versus /dev/mmcblk0p1 naming
conventions correctly. It waits for udev to materialize partition nodes
before proceeding. The design prioritizes defensive reliability over
brevity.
The GUI (drivestage-gui)
The engine is the source of truth. The GUI is a wrapper. Rust handles
disk discovery (via lsblk -J JSON parsing), UI state, and async
orchestration. Privileged operations delegate to the bash engine.
The GUI presents five tabs:
- Setup wizard — select a drive, configure ESP size and labels, choose a bootloader, type the device name to confirm, click PROVISION
- Payload manager — drag-and-drop ISOs, view sizes, delete, eject
- Bootloader switcher — install GRUB2, systemd-boot, Limine, or rEFInd per-drive
- Menu editor — edit
/boot/grub/custom.cfgwith a monospace editor (custom entries appear after the auto-scan entries) - Diagnostics — SMART attributes, partition table dump, filesystem UUID and label browser
The visual theme is "dark tech utility" — inspired by btop and htop, with monospace accents for paths, UUIDs, and sizes. It targets the sysadmin-tool aesthetic, not the consumer-application aesthetic.
Design decisions
What was adopted from Easy2Boot / RMPrepUSB
- The drop-and-boot workflow. Files placed in a payload directory appear in the boot menu without explicit registration. This is the correct user experience.
- The multi-payload USB use case itself. One stick, many ISOs, selectable at boot.
- Distro-specific kernel argument injection. Different distributions require different boot parameters for ISO loopback. Centralizing this knowledge in a pattern-matching dispatcher is the right design.
What was implemented differently
- Linux-native host. No WINE, no Windows VM, no cross-platform shims. The host is Linux; the tooling is Linux.
- Ext4 payload partition. Files are files. No contiguous-file allocation, no defragmentation passes, no 4 GB limit.
- GPT with proper BIOS Boot partition. Standard-compliant BIOS booting on GPT, no MBR hacks.
- GRUB2 as the bootloader. Mature, universally packaged, actively maintained. Both UEFI and BIOS targets installed in two commands.
- Boot-time auto-discovery via GRUB scripting. No host-side menu generation required for ISOs. The
grub.cfgis a program, not a static list. - Standard
custom.cfgmechanism. Custom menu entries use GRUB's built-insourcedirective. No sidecar.mnuformat.
What was not implemented
- Contiguous file allocation. Unnecessary on ext4.
.mnusidecar files. Superseded by/boot/grub/custom.cfg— a standard GRUB mechanism.- Partition table rewriting at boot. Eliminated. The partition table is written once at setup time.
- Windows support. Out of scope. This is a Linux-native tool.
- FAT32 payload partition. Eliminated. The ESP remains FAT32 (UEFI requires it); payloads reside on ext4.
Outcome
The result is a toolkit that is smaller, more reliable, and more transparent than its inspirations. The bash engine is 1500 lines. The Rust GUI is 3200 lines. Together they implement the multi-payload USB workflow for modern Linux without carrying forward constraints from an obsolete computing era.
Drop in an ISO. Boot. Done.
Read QUICKSTART.md to begin, or examine scripts/blkstage.sh for the implementation.