222 lines
9.5 KiB
Markdown
Executable File
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.*
|