|
1 |
+ |
# mountaineer-build
|
|
2 |
+ |
|
|
3 |
+ |
The implementation side of Mountaineer. Spec lives in `../mountaineer/`;
|
|
4 |
+ |
this repo builds artifacts (apkovl, ISO) from that spec.
|
|
5 |
+ |
|
|
6 |
+ |
**Status: MVP, installer end-to-end in QEMU.** 660 MB ISO at
|
|
7 |
+ |
`out/mountaineer-server-v3.23-x86_64.iso` boots in UEFI QEMU. The
|
|
8 |
+ |
installer runs all 17 steps against a clean disk — ZFS pool, dataset
|
|
9 |
+ |
layout, base packages, apkovl unpack, ZBM (zfsbootmenu) install,
|
|
10 |
+ |
per-host identity, finalize/reboot — and the installed disk boots
|
|
11 |
+ |
standalone via ZBM. Ready for Framework 13 hardware verification.
|
|
12 |
+ |
|
|
13 |
+ |
Build/install history and rediscovered traps live in
|
|
14 |
+ |
[`build_notes.md`](build_notes.md). Open work tracked in
|
|
15 |
+ |
`../mountaineer/todo.md`.
|
|
16 |
+ |
|
|
17 |
+ |
Anything marked `TODO-verify` or `TODO` in the source needs real
|
|
18 |
+ |
hardware testing or upstream-vendoring to commit to a final shape.
|
|
19 |
+ |
|
|
20 |
+ |
## Scope (MVP)
|
|
21 |
+ |
|
|
22 |
+ |
What the MVP aims for, in plain terms: a Mountaineer Server ISO that boots
|
|
23 |
+ |
in a UEFI VM, runs an installer, lays down ZFS-on-root, unpacks the
|
|
24 |
+ |
Mountaineer apkovl, reboots, and passes every step of the first-boot route
|
|
25 |
+ |
from the spec.
|
|
26 |
+ |
|
|
27 |
+ |
What is deliberately **not** in MVP scope:
|
|
28 |
+ |
|
|
29 |
+ |
- **Sysadmin profile / graphical layer.** Only Server is built. Sysadmin
|
|
30 |
+ |
comes when Server is stable.
|
|
31 |
+ |
- **The `sysop` CLI.** Routes use canonical tools directly (`nft -f`,
|
|
32 |
+ |
`s6-rc -u change`, etc.) until `sysop` lands.
|
|
33 |
+ |
- **Reproducible builds, bit-for-bit.** The apkovl builder is
|
|
34 |
+ |
reproducible-best-effort (same source + same builder → same bytes).
|
|
35 |
+ |
Bit-identical across builder versions is a v0.2 goal.
|
|
36 |
+ |
- **Caddy / Nebula / podman / Vector running by default.** They're
|
|
37 |
+ |
installed but not started; the operator configures and starts them per
|
|
38 |
+ |
the routes in `../mountaineer/ROUTES.md`.
|
|
39 |
+ |
- **Mountaineer overlay apk repository.** Until we have one, everything
|
|
40 |
+ |
comes from Alpine repos. Mountaineer-authored packages (`sysop` when it
|
|
41 |
+ |
ships, the installer eventually) are TBD.
|
|
42 |
+ |
|
|
43 |
+ |
## Layout
|
|
44 |
+ |
|
|
45 |
+ |
```
|
|
46 |
+ |
mountaineer-build/
|
|
47 |
+ |
├── apkovl-src/ # declarative source for the apkovl
|
|
48 |
+ |
│ ├── common/ # files shipped in every Mountaineer build
|
|
49 |
+ |
│ ├── manifest.toml # per-profile metadata (sources, version)
|
|
50 |
+ |
│ └── README.md # what lives where, how to verify reproducibility
|
|
51 |
+ |
├── tools/
|
|
52 |
+ |
│ └── build-apkovl.sh # apkovl-src/ → reproducible tarball
|
|
53 |
+ |
├── mkimg/
|
|
54 |
+ |
│ └── mkimg.mountaineer-server.sh # mkimage profile (drop into aports/scripts/)
|
|
55 |
+ |
├── installer/
|
|
56 |
+ |
│ └── install.sh # 17-step shell+dialog installer
|
|
57 |
+ |
├── build.sh # top-level: apkovl + ISO
|
|
58 |
+ |
└── README.md # this file
|
|
59 |
+ |
```
|
|
60 |
+ |
|
|
61 |
+ |
## Building
|
|
62 |
+ |
|
|
63 |
+ |
### Quickest path: Alpine host
|
|
64 |
+ |
|
|
65 |
+ |
On any host running Alpine (physical, VM, container), in this directory:
|
|
66 |
+ |
|
|
67 |
+ |
```sh
|
|
68 |
+ |
apk add alpine-sdk abuild xorriso squashfs-tools mkinitfs grub-efi git tar
|
|
69 |
+ |
./build.sh
|
|
70 |
+ |
```
|
|
71 |
+ |
|
|
72 |
+ |
Output:
|
|
73 |
+ |
|
|
74 |
+ |
- `out/mountaineer-server-<version>.apkovl.tar.gz` — the apkovl
|
|
75 |
+ |
- `out/mountaineer-server-*.iso` — the bootable installer ISO
|
|
76 |
+ |
|
|
77 |
+ |
The first run clones a shallow copy of Alpine's `aports` repo into
|
|
78 |
+ |
`.aports/` (≈100 MiB). Subsequent runs reuse it.
|
|
79 |
+ |
|
|
80 |
+ |
### From a Mac (the dev loop)
|
|
81 |
+ |
|
|
82 |
+ |
The canonical Mac iteration loop uses qemu to run a long-lived Alpine
|
|
83 |
+ |
builder VM. See `tools/qemu/README.md` for the full walkthrough; the
|
|
84 |
+ |
short version, once `brew install qemu` is done:
|
|
85 |
+ |
|
|
86 |
+ |
```sh
|
|
87 |
+ |
./tools/qemu/vm-up.sh --provision # first time only: download ISO + setup-alpine
|
|
88 |
+ |
./tools/qemu/vm-seed-builder.sh # install build deps in the builder VM
|
|
89 |
+ |
./tools/qemu/vm-build.sh # rsync repo + run ./build.sh inside builder
|
|
90 |
+ |
./tools/qemu/vm-test-install.sh # boot the new ISO in a disposable target VM
|
|
91 |
+ |
```
|
|
92 |
+ |
|
|
93 |
+ |
Apple Silicon caveat: x86_64 guests run via TCG emulation (no HVF for
|
|
94 |
+ |
cross-arch), so a full build cycle takes ~10-20 minutes. On Intel Macs
|
|
95 |
+ |
HVF makes it ~2-3 minutes.
|
|
96 |
+ |
|
|
97 |
+ |
### From a non-Alpine Linux workstation
|
|
98 |
+ |
|
|
99 |
+ |
Run the build inside an Alpine container via podman:
|
|
100 |
+ |
|
|
101 |
+ |
```sh
|
|
102 |
+ |
podman run --rm -it --privileged \
|
|
103 |
+ |
-v "$PWD":/build \
|
|
104 |
+ |
-w /build \
|
|
105 |
+ |
docker.io/alpine:edge \
|
|
106 |
+ |
sh -c 'apk add alpine-sdk abuild xorriso squashfs-tools mkinitfs grub-efi git tar && ./build.sh'
|
|
107 |
+ |
```
|
|
108 |
+ |
|
|
109 |
+ |
`--privileged` is required for `mkfs`/`mksquashfs`/loop-device work inside
|
|
110 |
+ |
the container. Outputs land on the host via the bind mount.
|
|
111 |
+ |
|
|
112 |
+ |
We do not use Nix, devbox, or any other ecosystem-replacing tooling.
|
|
113 |
+ |
Mountaineer's build environment is apk-native (see
|
|
114 |
+ |
`../mountaineer/BUILD-TOOLS.md`).
|
|
115 |
+ |
|
|
116 |
+ |
### Just the apkovl
|
|
117 |
+ |
|
|
118 |
+ |
`./tools/build-apkovl.sh server` produces only the apkovl tarball (no ISO).
|
|
119 |
+ |
Useful for iterating on `apkovl-src/` content without rebuilding the ISO
|
|
120 |
+ |
every time. Requires GNU tar; the script auto-detects and prints the fix
|
|
121 |
+ |
if it's missing.
|
|
122 |
+ |
|
|
123 |
+ |
## Testing the installer
|
|
124 |
+ |
|
|
125 |
+ |
The installer's `--dry-run` flag walks every prompt and prints every
|
|
126 |
+ |
destructive command instead of executing it. Run it inside a VM that has
|
|
127 |
+ |
`dialog` installed to exercise the prompt flow without losing data:
|
|
128 |
+ |
|
|
129 |
+ |
```sh
|
|
130 |
+ |
./installer/install.sh --dry-run
|
|
131 |
+ |
```
|
|
132 |
+ |
|
|
133 |
+ |
End-to-end testing requires a fresh UEFI VM with a blank disk. The
|
|
134 |
+ |
canonical loop:
|
|
135 |
+ |
|
|
136 |
+ |
1. `./build.sh` to produce the ISO.
|
|
137 |
+ |
2. Boot the ISO in a UEFI VM (qemu, UTM, VMware, etc.) with at least
|
|
138 |
+ |
2 GiB RAM and a 20 GiB blank disk.
|
|
139 |
+ |
3. Log in to the live environment, run `mountaineer-install` (or the path
|
|
140 |
+ |
to `install.sh` if not yet installed in PATH on the live image).
|
|
141 |
+ |
4. Walk through the prompts; complete the install; reboot.
|
|
142 |
+ |
5. Verify the installed system passes the first-boot route in
|
|
143 |
+ |
`../mountaineer/ROUTES.md`.
|
|
144 |
+ |
|
|
145 |
+ |
The v0.1 dogfood gate is eight criteria documented in
|
|
146 |
+ |
`../mountaineer/INSTALL.md`.
|
|
147 |
+ |
|
|
148 |
+ |
## Known gaps (recorded here so we don't lose them)
|
|
149 |
+ |
|
|
150 |
+ |
These are the load-bearing things still TODO:
|
|
151 |
+ |
|
|
152 |
+ |
- **zfsbootmenu install path.** Installer step 12 has a placeholder. Need
|
|
153 |
+ |
to decide: apk-package ZBM if Alpine ships it, or fetch upstream
|
|
154 |
+ |
release signed by the ZBM project. Affects build reproducibility and
|
|
155 |
+ |
the signing-trust story.
|
|
156 |
+ |
- **mkimg profile bootloader.** The profile script declares
|
|
157 |
+ |
`boot_efi="grub-efi"` for the live ISO; need to verify mkimage's
|
|
158 |
+ |
expected token names against the current aports tree.
|
|
159 |
+ |
- **OpenRC vs s6-rc on the live ISO.** The installed system uses s6-rc.
|
|
160 |
+ |
The live ISO inherits Alpine's default (OpenRC) for now; the installer
|
|
161 |
+ |
itself doesn't care which init it ran under. Migrating the live ISO to
|
|
162 |
+ |
s6-rc is a v0.2 cleanup.
|
|
163 |
+ |
- **Vector packaging on Alpine.** The world file lists `vector`;
|
|
164 |
+ |
TODO-verify the package name and repo placement before the first build
|
|
165 |
+ |
is attempted.
|
|
166 |
+ |
- **Non-interactive install config schema.** `--config <file>` is in the
|
|
167 |
+ |
spec but the schema doesn't exist yet.
|
|
168 |
+ |
- **Installer log routing.** Right now the installer logs to stderr;
|
|
169 |
+ |
decide whether to also tee to tmpfs and copy into the installed root
|
|
170 |
+ |
before reboot for post-mortem debugging.
|
|
171 |
+ |
- **Signing.** ISO and apkovl signing keys, lifecycle, where they live.
|
|
172 |
+ |
Open in `../mountaineer/INSTALL.md` and `../mountaineer/todo.md`.
|
|
173 |
+ |
|
|
174 |
+ |
When any of these get resolved, the corresponding `TODO-verify` or
|
|
175 |
+ |
`TODO` comment in the source code gets removed in the same commit that
|
|
176 |
+ |
resolves it.
|
|
177 |
+ |
|
|
178 |
+ |
## Relationship to the spec
|
|
179 |
+ |
|
|
180 |
+ |
This repo implements `../mountaineer/`. When the two disagree:
|
|
181 |
+ |
|
|
182 |
+ |
- If the spec is right and the implementation is wrong, fix the code.
|
|
183 |
+ |
- If the implementation is right and the spec is wrong, fix the spec —
|
|
184 |
+ |
but only after the implementation has actually been tested. A spec
|
|
185 |
+ |
changed to match unverified code is worse than a spec at odds with it.
|
|
186 |
+ |
|
|
187 |
+ |
The spec docs that matter most for implementation work:
|
|
188 |
+ |
|
|
189 |
+ |
- `STACK.md` — what packages, what configs, what defaults.
|
|
190 |
+ |
- `INSTALL.md` — disk layout, dataset hierarchy, ARC sizing, installer
|
|
191 |
+ |
flow, dogfood gate.
|
|
192 |
+ |
- `ROUTES.md` — the end-state UX the installed system must support.
|
|
193 |
+ |
- `BUILD-TOOLS.md` — the toolchain orientation.
|
|
194 |
+ |
- `MANIFESTO.md` — the principles to consult when a question doesn't
|
|
195 |
+ |
have an obvious answer.
|