OpenRiot Image Builder - Functional Specification
Command
openriot --make-image [site|clean|help]
make image # Wrapper that runs as root
Purpose
Build a bootable OpenBSD installer image with OpenRiot packages pre-bundled. The image contains:
- OpenBSD base installer (
install79.img) site79.tgz(custom set with 81 packages +install.sitepost-install script)- Modified
index.txtfor installer set discovery
Target size: < 2.0GB. Actual: ~1932MB.
Architecture
Components
| Component | Location | Description |
|---|---|---|
openriot --make-image |
source/imaging/*.go |
Main image builder logic |
| Base image | Build/Images/install79.img |
OpenBSD 7.9 installer (~801MB) |
| Output image | Build/Images/openriot.img |
Final bootable image (~1932MB) |
| Work dir | Build/work/ |
Temporary build artifacts |
| Site tarball | Build/work/site79.tgz |
Custom set (~781MB) |
Directory Structure
Build/
├── Images/
│ ├── install79.img # Base OpenBSD installer (source, read-only)
│ └── openriot.img # Output image (bootable)
├── work/
│ ├── site79.tgz # Custom installer set
│ └── site-XXXXXX/ # Temp staging dir (auto-removed)
└── packages/snapshots/amd64/ # Downloaded .tgz files (~781MB)
Source Files
| File | Purpose |
|---|---|
source/imaging/runner.go |
Entry point, mode dispatch, config loading |
source/imaging/prereqs.go |
Host checks, base image validation/download |
source/imaging/download.go |
Package downloading from CDN |
source/imaging/site.go |
site79.tgz creation, install.site generation |
source/imaging/build.go |
Image expansion, content injection, checksum |
source/imaging/burn.go |
USB drive detection and burning |
source/fsutil/copy.go |
File copy utility (io.Copy for large files) |
Build Flow
1. Prerequisites Check (CheckPrereqs)
- Must be OpenBSD
- Must be root (for
vnconfig,mount,disklabel,growfs) - Base image exists at
Build/Images/install79.img(auto-downloaded if missing) - Binary exists at
install/openriot
2. Download Packages (DownloadPackages)
- Reads package list from
install/packages.yaml - Downloads to
Build/work/packages/snapshots/amd64/ - 2-minute HTTP timeout per file
- 81 packages, ~781MB total
3. Create Site Tarball (CreateSite)
- Uses
os.MkdirTempfor clean staging directory - Copies packages into
openriot/packages/snapshots/amd64/ - Copies
install/motdtoetc/motd - Generates
install.sitepost-install script:- Configures
doas.conf,installurl,/etc/shells - Installs all packages from local path via
pkg_add - Adds users to
wheel, sets fish shell - Adds welcome message to
.profile
- Configures
- Creates
site79.tgzviatar czf
4. Build Image (BuildImage)
- Copy base:
fsutil.CopyFile(install79.img, openriot.img) - Expand:
truncate -stobase + tarball + 350MBvnconfig vnd0 openriot.img- Parse
disklabel vnd0for partition info disklabel -Rto updatea:andc:sizesgrowfs -y /dev/vnd0a
- Inject:
vnconfig vnd0 openriot.imgmount /dev/vnd0a /mnt- Create
7.9/amd64/directory - Copy
site79.tgzinto sets directory - Update
index.txtwithls -lToutput syncbefore unmountumount /mnt,vnconfig -u vnd0
- Checksum: Generate SHA256
Known Issues
Critical: Unvalidated End-to-End
The image has been built but NEVER successfully booted to completion.
| Issue | Status | Detail |
|---|---|---|
| Image builds without errors | ✅ Working | make image completes |
| Image size under 2.0GB | ✅ Working | ~1932MB |
site79.tgz injects into 7.9/amd64/ |
✅ Working | Verified in build logs |
index.txt contains site79.tgz entry |
✅ Working | Uses real ls -lT output |
| Boot test | ❌ NOT DONE | Image has never been booted |
site79.tgz discovered by installer |
❌ NOT VALIDATED | May require manual * selection |
install.site execution |
❌ NOT VALIDATED | Script generated, never observed running |
| Package installation | ❌ NOT VALIDATED | pkg_add from local path untested |
| First-login welcome | ❌ NOT VALIDATED | .profile append untested |
Installer UX (Interactive Install)
The OpenBSD interactive installer has intrinsic behaviors that cannot be changed without modifying bsd.rd or install.sub:
- Sets location defaults to HTTP when network interface detected
- User must manually select
disk - Cannot be overridden from media filesystem
- User must manually select
site79.tgzis deselected by default- Custom
site*.tgzsets are not inDEFAULTSETS - User must add with
* - Cannot be pre-selected without modifying installer
- Custom
- SHA256.sig verification fails
site79.tgzis not signed by OpenBSD release key- Installer prompts “Continue without verification? [no]”
- User must answer
yes - Cannot be signed without private release key
Historical Bugs (Fixed)
| Bug | Fix | Commit |
|---|---|---|
Permission collisions from root-owned site/ dir |
os.MkdirTemp + defer os.RemoveAll |
Earlier |
args slice mutation in RunMakeImage |
Build filtered slice instead of mutating |
Earlier |
| Indefinite HTTP hangs | http.Client{Timeout: 2*time.Minute} |
Earlier |
Leaked vnd0 devices on error paths |
defer cleanup blocks |
Earlier |
| Wrong fallback image path | Build/Images/install79.img |
Earlier |
| I/O error at 154MB during install | Removed fsck -y from injectContent() |
Current session |
| Copy verification | Size check + sync after injection |
Current session |
| Memory pressure on 781MB tarball | io.Copy instead of os.ReadFile/WriteFile |
Current session |
Abandoned: Autoinstall via bsd.rd
Attempted to patch bsd.rd ramdisk with auto_install.conf using rdsetroot.
Reverted — the approach broke the build (rdsetroot: not an elf) and introduced complexity without proven benefit.
The install.conf / autoinstall approach was used in v1.0–v1.2’s ISO builder (build-iso.sh), which was deleted in commit 4e217db. That workflow embedded response files differently and is not applicable to disk images.
Size Budget
| Component | Size |
|---|---|
Base install79.img |
~801MB |
site79.tgz (81 packages) |
~781MB |
| FFS metadata + 5% minfree | ~350MB |
| Total | ~1932MB |
FFS overhead is significant: a 1932MB image file only exposes ~1.7GB usable FFS space. After injection, ~218MB remains free.
Security
- Root required for
vnconfig,mount,disklabel - Work directory must be absolute path (rejects relative/
..) - Cleanup failures are returned, not swallowed
Next Steps
Immediate (Before Next Release)
- Boot the image on live hardware
- Flash
Build/Images/openriot.imgto USB - Boot on OpenBSD-compatible machine
- Document exact manual steps required
- Flash
- Validate installer discovery
- Confirm
site79.tgzappears in sets list - If not: debug
index.txtformat against standard entries
- Confirm
- Validate
install.siteexecution- Confirm post-install script runs after base install
- Check
/var/log/or addset -xfor debugging
- Validate package installation
- After install:
which fish,which alacritty rcctl check xenodmshould show disableddoas whoamiworks
- After install:
Future (Post-Validation)
- Automate installer UX (if desired)
- Options:
a. Modify
bsd.rdto embedauto_install.confin ramdisk (requiresrdsetrootexpertise) b. Serveinstall.confvia HTTP during netboot c. Accept manual steps and document them clearly
- Options:
a. Modify
- Add
openriot.zipfor offline install- Requires ~350MB additional space
- Options: trim 3-4 large packages, or relax 2GB target
- Shrink image
- Truncating FFS after write corrupts superblock — not viable
- Only option is reducing package count
Files Changed (This Session)
| File | Change |
|---|---|
source/imaging/build.go |
Removed fsck, added copy verification + sync, reverted patchBsdRd |
source/imaging/site.go |
Added createAutoInstallConf() (unused but kept for reference) |
source/fsutil/copy.go |
io.Copy for large files |
Makefile |
Cleanup rm -f updated for new artifacts |
docs/Image-Builder-Spec.md |
Updated with current architecture (see docs/) |
Reference
docs/Image-Builder-Spec.md— Canonical spec in repodocs/v6.5-Release-Notes.md— Release notes mentioning image builderautoinstall(8)— OpenBSD autoinstall mechanisminstall.site(5)— Post-install script documentationinstall.sub— OpenBSD installer source (/distrib/miniroot/)