NumericalOS

skills/numericalos-build-iso/SKILL.md

back to source

---
name: numericalos-build-iso
description: Build NumericalOS bootable media - a hybrid ISO that boots from optical or USB, pairing a kernel with the graph-driven initramfs. Use when numericalos-target-classifier routed here, or when the user asks for a NumericalOS ISO, USB stick, bootable image, or bare-metal media. Builds the initramfs first, then wraps kernel plus initramfs in a BIOS/UEFI-bootable image.
---

# Build NumericalOS bootable media

This is the only skill in the family that produces something a physical machine
can boot. It is also the one with the most host prerequisites and the most ways
to produce an artifact that looks right and does not boot.

**Prerequisite:** `numericalos-target-classifier` routed here, which means it
confirmed an ISO builder (`xorriso`/`genisoimage`) and a kernel image are
present. If it did not, do not proceed on hope.

## What NumericalOS does not ship

**A kernel.** By design - "smallest bootstrap" scopes to userspace. You are
pairing NumericalOS with a kernel the user already has or fetches. Say this
plainly; a user expecting a self-contained OS image should hear that this is a
userspace that rides someone else's kernel.

## Step 1 - build the initramfs

Follow `numericalos-build-initramfs` completely, including its Step 5 inspection
and its `NUMOS_LIB` path warning. Do not shortcut it - a bad initramfs inside a
correct ISO fails at exactly the point that is hardest to debug.

You need: `numericalos-initramfs-$ARCH.cpio.gz`.

## Step 2 - obtain a kernel

Three sources, in order of preference:

1. **The host's own kernel** - already matched to the arch, already known-good:
   ```sh
   ls -l /boot/vmlinuz-$(uname -r)          # x86
   ls -l /boot/Image /boot/Image.gz         # arm64
   ```
2. **A distribution package** - `linux-image-*` on Debian/Ubuntu, `kernel` on
   Fedora. Extract without installing if the user prefers.
3. **Upstream build** - correct but slow, and out of scope for this skill. Say so
   rather than starting a kernel build inside a media-build task.

**The kernel must match `$ARCH`.** An x86_64 kernel with an aarch64 initramfs
produces a machine that panics in a way that reads like a NumericalOS bug and is
not one.

## Step 3 - lay out the ISO tree

```sh
set -eu
ISO=$(mktemp -d)
mkdir -p "$ISO"/boot/grub

cp /boot/vmlinuz-$(uname -r) "$ISO/boot/vmlinuz"
cp numericalos-initramfs-$ARCH.cpio.gz "$ISO/boot/initramfs.cpio.gz"

cat > "$ISO/boot/grub/grub.cfg" <<'EOF'
set timeout=3
set default=0

menuentry "NumericalOS" {
    linux  /boot/vmlinuz console=tty0 NUMOS_STATE=/etc/numos.state
    initrd /boot/initramfs.cpio.gz
}

menuentry "NumericalOS (serial console)" {
    linux  /boot/vmlinuz console=ttyS0,115200 NUMOS_STATE=/etc/numos.state
    initrd /boot/initramfs.cpio.gz
}
EOF
```

**The serial entry is not optional decoration.** `numericalos-verify-boot` runs
QEMU headless and reads the serial console; without this entry you cannot see the
`numos:` output that tells you whether the boot worked. Keep it.

**On the kernel cmdline.** `NUMOS_STATE=...` on the cmdline is *not*
automatically an environment variable in `/init` - the kernel passes unrecognized
`key=value` pairs to init's environment on most setups, but do not rely on it.
The robust approach is that `bootstrap.sh` defaults it, or the initramfs sets it.
Verify which mechanism is actually in play rather than assuming the cmdline
worked; a silent unset produces `numos: HALT: NUMOS_STATE is unset`.

## Step 4 - build a hybrid image

Hybrid means the same file boots from optical media and from a USB stick written
with `dd`.

```sh
grub-mkrescue -o numericalos-$ARCH.iso "$ISO" \
  -- -volid NUMERICALOS
```

`grub-mkrescue` wraps `xorriso` and handles BIOS and UEFI El Torito layout, which
is fiddly to get right by hand. If it is unavailable, `xorriso` directly:

```sh
xorriso -as mkisofs \
  -o numericalos-$ARCH.iso \
  -isohybrid-mbr /usr/lib/ISOLINUX/isohdpfx.bin \
  -c boot/boot.cat -b boot/isolinux.bin \
  -no-emul-boot -boot-load-size 4 -boot-info-table \
  -eltorito-alt-boot -e boot/efiboot.img -no-emul-boot -isohybrid-gpt-basdat \
  "$ISO"
```

The `xorriso` path needs isolinux/EFI stubs staged in the tree. If they are not
present, say so and prefer `grub-mkrescue` rather than producing an ISO that
silently lacks a boot sector.

## Step 5 - verify the image structurally, before claiming anything

```sh
ls -lh numericalos-$ARCH.iso
sha256sum numericalos-$ARCH.iso
file numericalos-$ARCH.iso          # expect: ISO 9660 ... (bootable)
```

`file` reporting `(bootable)` is a real check - an ISO without a boot sector says
so here, and that is the cheapest possible catch.

## Step 6 - writing to USB (only if the user asks)

**This destroys the target device.** Never run it without the user naming the
device explicitly, and never infer the device yourself.

```sh
lsblk -o NAME,SIZE,MODEL,TRAN          # let the USER identify the device
sudo dd if=numericalos-$ARCH.iso of=/dev/sdX bs=4M status=progress oflag=sync
```

Before running: show the user `lsblk` output, state which device you are about to
overwrite and its size and model, and get explicit confirmation of that device.
Writing to the wrong `/dev/sdX` destroys a disk. If there is any ambiguity, stop
and ask.

## Reporting

State: ISO path, size, sha256, target arch, kernel source and version, whether
`file` reported it bootable, and whether it has been booted (it has not, unless
`numericalos-verify-boot` ran).

"Bootable" from `file` means a boot sector exists. It does not mean NumericalOS
boots. Keep those two claims separate in what you tell the user.

## Related skills

- `numericalos-target-classifier` - run first
- `numericalos-build-initramfs` - stage one of this skill
- `numericalos-verify-boot` - run this next; it is the only skill that may claim a boot