skills/numericalos-build-initramfs/SKILL.md
back to source
---
name: numericalos-build-initramfs
description: Build a NumericalOS initramfs (cpio.gz) whose /init is the graph-driven bootstrap. Use when numericalos-target-classifier routed here, or when the user asks for a NumericalOS initramfs, initrd, or the userspace half of bootable media. Assembles a static busybox, the boot chain, and a verified numos.state into a cpio archive that a kernel can hand control to.
---
# Build a NumericalOS initramfs
This produces the **userspace** half of a NumericalOS boot: a `cpio.gz` archive
whose `/init` is `bootstrap.sh`. It does not produce a kernel. Pairing it with a
kernel is `numericalos-build-iso`, or the user's own bootloader configuration.
**Prerequisite:** `numericalos-target-classifier` has run and handed you `ARCH`,
`REPO`, `STATE`, and probe results. Requires a Linux userspace - `cpio`, `gzip`,
`find`. On Windows this needs WSL2 or a VM; git-bash cannot build a valid
initramfs because it cannot create the required device nodes or preserve
permissions.
## The shape you are building
```
/init -> bootstrap.sh (kernel execs this as PID 1)
/bin/busybox -> static, arch-matched
/bin/sh -> busybox (plus the applet symlinks numinit needs)
/boot/numinit.sh
/boot/lib/arch_table.sh
/etc/numos.state
/proc /sys /dev /run /tmp (empty mount points)
```
Nothing else. The whole point of this project is that the configuration is one
file; resist adding a userland.
## Step 1 - get a static busybox for the target arch
`numinit.sh` is POSIX shell and needs a shell plus `grep`, `cut`, `head`, `sort`,
`sed`, `tr`, `mount`, `sha256sum`. A static busybox provides all of them in about
1 MB.
Do not compile it. Fetch a published static build and verify it:
```sh
ARCH_BB=<busybox_variant from the repo's ArchTarget registry>
curl -fLo busybox "https://busybox.net/downloads/binaries/1.35.0-$ARCH_BB/busybox"
sha256sum busybox
```
The `busybox_variant` for the target arch comes from `numos/seed.py`'s
`ARCH_TARGETS` - it is a graph-derived field, not something to guess. Read it:
```sh
python3 -c "from numos import seed; print([ (t['canonical'], t['busybox_variant']) for t in seed.ARCH_TARGETS ])"
```
**Verify before trusting.** If the user has a known-good checksum, compare it. If
not, say plainly that you are shipping an unverified third-party binary into
their init, and offer the alternative: build busybox from source with
`make defconfig && make LDFLAGS=-static`, or use their distribution's
`busybox-static` package. Do not quietly ship an unverified binary into PID 1.
## Step 2 - export a state file
```sh
cd "$REPO"
python3 -m numos.export_state --out dist
```
Or seed-only if no IntrikataTopology instance is reachable:
```sh
python3 -c "from numos.export_state import export; export([], 'dist')"
```
Verify it before packing - a state that fails here will halt the machine at boot,
and finding out in QEMU costs far more than finding out now:
```sh
python3 -c "
from numos.state import verify
verify(open('dist/numos.state').read())
print('state verifies')
"
```
## Step 3 - assemble the tree
```sh
set -eu
BUILD=$(mktemp -d)
mkdir -p "$BUILD"/{bin,boot/lib,etc,proc,sys,dev,run,tmp}
install -m 0755 busybox "$BUILD/bin/busybox"
install -m 0755 boot/bootstrap.sh "$BUILD/init"
install -m 0755 boot/numinit.sh "$BUILD/boot/numinit.sh"
install -m 0644 boot/lib/arch_table.sh "$BUILD/boot/lib/arch_table.sh"
install -m 0644 dist/numos.state "$BUILD/etc/numos.state"
# Applet symlinks. numinit needs these by name.
for a in sh ash grep cut head sort sed tr mount umount sha256sum \
echo cat ls mkdir rm sleep printf uname; do
ln -sf busybox "$BUILD/bin/$a"
done
```
**Two details that decide whether this boots at all:**
- **`/init` must be executable and must be the bootstrap.** The kernel execs
`/init` in the initramfs root. If it is not executable you get
`Kernel panic - not syncing: No working init found`.
- **`NUMOS_LIB` resolves via `dirname "$0"`.** When the kernel execs `/init`,
`$0` is `/init`, so `dirname` yields `/` and `NUMOS_LIB` becomes `/lib`. The
layout above puts `arch_table.sh` at `/boot/lib/`, which will **not** be found.
Either place it at `/lib/arch_table.sh`, or set `NUMOS_LIB=/boot/lib` in the
environment. Choose one and be explicit - this is the single most likely cause
of a first-boot halt.
The simplest correct fix:
```sh
mkdir -p "$BUILD/lib"
install -m 0644 boot/lib/arch_table.sh "$BUILD/lib/arch_table.sh"
```
## Step 4 - pack
```sh
( cd "$BUILD" && find . -print0 \
| cpio --null --create --format=newc --owner=root:root ) \
| gzip -9 > numericalos-initramfs-$ARCH.cpio.gz
ls -l numericalos-initramfs-$ARCH.cpio.gz
sha256sum numericalos-initramfs-$ARCH.cpio.gz
```
`--owner=root:root` matters: files owned by the building user's UID will have
that UID at boot, and a non-root `/init` may fail in ways that are hard to read.
Expect roughly 500 KB - 1.5 MB, dominated by busybox.
## Step 5 - inspect before you claim anything
```sh
zcat numericalos-initramfs-$ARCH.cpio.gz | cpio -t | head -30
```
Confirm `init` is present at the root, is not `./boot/init`, and that
`lib/arch_table.sh` (or wherever you put it) is where `bootstrap.sh` will look.
You have now built an artifact. **You have not booted it.** Do not describe it as
bootable, working, or verified. Route to `numericalos-verify-boot` to find out,
or hand it to `numericalos-build-iso` to pair with a kernel.
## Failure modes worth pre-empting
| Symptom at boot | Cause |
|---|---|
| `No working init found` | `/init` missing, not executable, or wrong path |
| `numos: HALT: unsupported architecture: <x>` | busybox arch does not match the kernel arch |
| `arch_table.sh: not found` | the `NUMOS_LIB` path problem in Step 3 |
| `numos: HALT: state hash mismatch` | `numos.state` was edited after export - re-export, never hand-edit |
| `numos: HALT: NUMOS_STATE is unset` | `bootstrap.sh` did not pass it through; set it in the kernel cmdline or the script |
| Immediate kernel panic after phase walk | PID 1 exited; expected if all units are `oneshot` - the phase walk completed and returned |
That last one is not a bug. A kernel panics when PID 1 exits, even successfully.
A real deployment needs a `longrun` unit that keeps running, and `numinit` does
not yet invoke its supervisor - say so rather than describing the panic as a
failure of the build.
## Reporting
State: output path, size, sha256, target arch, busybox source and whether it was
checksum-verified, which state file was packed and whether it verified. Then the
boundary: "artifact built; not booted."
## Related skills
- `numericalos-target-classifier` - run first
- `numericalos-build-iso` - pairs this with a kernel into bootable media
- `numericalos-verify-boot` - QEMU; the only skill that may claim a boot