# mountaineer-build The implementation side of Mountaineer. Spec lives in `../mountaineer/`; this repo builds artifacts (apkovl, ISO) from that spec. **Status: MVP, installer end-to-end in QEMU.** 660 MB ISO at `out/mountaineer-server-v3.23-x86_64.iso` boots in UEFI QEMU. The installer runs all 17 steps against a clean disk — ZFS pool, dataset layout, base packages, apkovl unpack, ZBM (zfsbootmenu) install, per-host identity, finalize/reboot — and the installed disk boots standalone via ZBM. Ready for Framework 13 hardware verification. Build/install history and rediscovered traps live in [`build_notes.md`](build_notes.md). Open work tracked in `../mountaineer/todo.md`. Anything marked `TODO-verify` or `TODO` in the source needs real hardware testing or upstream-vendoring to commit to a final shape. ## Scope (MVP) What the MVP aims for, in plain terms: a Mountaineer Server ISO that boots in a UEFI VM, runs an installer, lays down ZFS-on-root, unpacks the Mountaineer apkovl, reboots, and passes every step of the first-boot route from the spec. What is deliberately **not** in MVP scope: - **Sysadmin profile / graphical layer.** Only Server is built. Sysadmin comes when Server is stable. - **The `sysop` CLI.** Routes use canonical tools directly (`nft -f`, `s6-rc -u change`, etc.) until `sysop` lands. - **Reproducible builds, bit-for-bit.** The apkovl builder is reproducible-best-effort (same source + same builder → same bytes). Bit-identical across builder versions is a v0.2 goal. - **Caddy / Nebula / podman / Vector running by default.** They're installed but not started; the operator configures and starts them per the routes in `../mountaineer/ROUTES.md`. - **Mountaineer overlay apk repository.** Until we have one, everything comes from Alpine repos. Mountaineer-authored packages (`sysop` when it ships, the installer eventually) are TBD. ## Layout ``` mountaineer-build/ ├── apkovl-src/ # declarative source for the apkovl │ ├── common/ # files shipped in every Mountaineer build │ ├── manifest.toml # per-profile metadata (sources, version) │ └── README.md # what lives where, how to verify reproducibility ├── tools/ │ └── build-apkovl.sh # apkovl-src/ → reproducible tarball ├── mkimg/ │ └── mkimg.mountaineer-server.sh # mkimage profile (drop into aports/scripts/) ├── installer/ │ └── install.sh # 17-step shell+dialog installer ├── build.sh # top-level: apkovl + ISO └── README.md # this file ``` ## Building ### Quickest path: Alpine host On any host running Alpine (physical, VM, container), in this directory: ```sh apk add alpine-sdk abuild xorriso squashfs-tools mkinitfs grub-efi git tar ./build.sh ``` Output: - `out/mountaineer-server-.apkovl.tar.gz` — the apkovl - `out/mountaineer-server-*.iso` — the bootable installer ISO The first run clones a shallow copy of Alpine's `aports` repo into `.aports/` (≈100 MiB). Subsequent runs reuse it. ### From a Mac (the dev loop) The canonical Mac iteration loop uses qemu to run a long-lived Alpine builder VM. See `tools/qemu/README.md` for the full walkthrough; the short version, once `brew install qemu` is done: ```sh ./tools/qemu/vm-up.sh --provision # first time only: download ISO + setup-alpine ./tools/qemu/vm-seed-builder.sh # install build deps in the builder VM ./tools/qemu/vm-build.sh # rsync repo + run ./build.sh inside builder ./tools/qemu/vm-test-install.sh # boot the new ISO in a disposable target VM ``` Apple Silicon caveat: x86_64 guests run via TCG emulation (no HVF for cross-arch), so a full build cycle takes ~10-20 minutes. On Intel Macs HVF makes it ~2-3 minutes. ### From a non-Alpine Linux workstation Run the build inside an Alpine container via podman: ```sh podman run --rm -it --privileged \ -v "$PWD":/build \ -w /build \ docker.io/alpine:edge \ sh -c 'apk add alpine-sdk abuild xorriso squashfs-tools mkinitfs grub-efi git tar && ./build.sh' ``` `--privileged` is required for `mkfs`/`mksquashfs`/loop-device work inside the container. Outputs land on the host via the bind mount. We do not use Nix, devbox, or any other ecosystem-replacing tooling. Mountaineer's build environment is apk-native (see `../mountaineer/BUILD-TOOLS.md`). ### Just the apkovl `./tools/build-apkovl.sh server` produces only the apkovl tarball (no ISO). Useful for iterating on `apkovl-src/` content without rebuilding the ISO every time. Requires GNU tar; the script auto-detects and prints the fix if it's missing. ## Testing the installer The installer's `--dry-run` flag walks every prompt and prints every destructive command instead of executing it. Run it inside a VM that has `dialog` installed to exercise the prompt flow without losing data: ```sh ./installer/install.sh --dry-run ``` End-to-end testing requires a fresh UEFI VM with a blank disk. The canonical loop: 1. `./build.sh` to produce the ISO. 2. Boot the ISO in a UEFI VM (qemu, UTM, VMware, etc.) with at least 2 GiB RAM and a 20 GiB blank disk. 3. Log in to the live environment, run `mountaineer-install` (or the path to `install.sh` if not yet installed in PATH on the live image). 4. Walk through the prompts; complete the install; reboot. 5. Verify the installed system passes the first-boot route in `../mountaineer/ROUTES.md`. The v0.1 dogfood gate is eight criteria documented in `../mountaineer/INSTALL.md`. ## Known gaps (recorded here so we don't lose them) These are the load-bearing things still TODO: - **zfsbootmenu install path.** Installer step 12 has a placeholder. Need to decide: apk-package ZBM if Alpine ships it, or fetch upstream release signed by the ZBM project. Affects build reproducibility and the signing-trust story. - **mkimg profile bootloader.** The profile script declares `boot_efi="grub-efi"` for the live ISO; need to verify mkimage's expected token names against the current aports tree. - **OpenRC vs s6-rc on the live ISO.** The installed system uses s6-rc. The live ISO inherits Alpine's default (OpenRC) for now; the installer itself doesn't care which init it ran under. Migrating the live ISO to s6-rc is a v0.2 cleanup. - **Vector packaging on Alpine.** The world file lists `vector`; TODO-verify the package name and repo placement before the first build is attempted. - **Non-interactive install config schema.** `--config ` is in the spec but the schema doesn't exist yet. - **Installer log routing.** Right now the installer logs to stderr; decide whether to also tee to tmpfs and copy into the installed root before reboot for post-mortem debugging. - **Signing.** ISO and apkovl signing keys, lifecycle, where they live. Open in `../mountaineer/INSTALL.md` and `../mountaineer/todo.md`. When any of these get resolved, the corresponding `TODO-verify` or `TODO` comment in the source code gets removed in the same commit that resolves it. ## Relationship to the spec This repo implements `../mountaineer/`. When the two disagree: - If the spec is right and the implementation is wrong, fix the code. - If the implementation is right and the spec is wrong, fix the spec — but only after the implementation has actually been tested. A spec changed to match unverified code is worse than a spec at odds with it. The spec docs that matter most for implementation work: - `STACK.md` — what packages, what configs, what defaults. - `INSTALL.md` — disk layout, dataset hierarchy, ARC sizing, installer flow, dogfood gate. - `ROUTES.md` — the end-state UX the installed system must support. - `BUILD-TOOLS.md` — the toolchain orientation. - `MANIFESTO.md` — the principles to consult when a question doesn't have an obvious answer.