DriveStage/BLOG.md

222 lines
9.5 KiB
Markdown
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](https://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:
1. Locates the payload partition by UUID
2. Iterates over `/payloads/isos/*.iso`
3. For each ISO, runs a `regexp` cascade to identify the distribution from the filename
4. Invokes a function that emits the appropriate `menuentry` with distro-specific kernel arguments
5. Iterates over `/payloads/rootfs/*/` and emits direct-boot entries
6. Iterates over `/payloads/images/*.img` and 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`:
```grub
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:
1. **Setup wizard** — select a drive, configure ESP size and labels, choose a bootloader, type the device name to confirm, click PROVISION
2. **Payload manager** — drag-and-drop ISOs, view sizes, delete, eject
3. **Bootloader switcher** — install GRUB2, systemd-boot, Limine, or rEFInd per-drive
4. **Menu editor** — edit `/boot/grub/custom.cfg` with a monospace editor (custom entries appear after the auto-scan entries)
5. **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.cfg` is a program, not a static list.
- **Standard `custom.cfg` mechanism.** Custom menu entries use GRUB's built-in `source` directive. No sidecar `.mnu` format.
### What was not implemented
- **Contiguous file allocation.** Unnecessary on ext4.
- **`.mnu` sidecar 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](QUICKSTART.md) to begin, or examine
[scripts/blkstage.sh](scripts/blkstage.sh) for the implementation.*