README.md (12193B)
1 # mk/ 2 3 UNOS package driver: `channels/<layer>/packages/<name>/template` in, signed 4 `.apk` (v2) out. The whole of `build/` is generated output. 5 6 ## Channels 7 8 Packages live in layers under `channels/`. Each channel declares its parent in 9 `channel.conf` (`upstream=`), and composing a channel means applying every 10 layer from the root down, replacing whole package directories. `publish=no` 11 marks a layer that is never built in CI and never uploaded. 12 13 base publish=no, the foundation 14 lts upstream=base 15 stable upstream=lts 16 edge upstream=stable 17 18 `channel-assemble.sh <channel>` writes the composed tree to 19 `build/channels/<channel>/packages`, which is what every other script reads. 20 Published at `apk/<channel>/<arch>/`; there is no `base` in the bucket. 21 22 Promoting a package between channels is a `git mv`, never a copy -- 23 `channel-lint.sh` fails the build on a layer identical to the one it shadows, 24 because such a layer silently stops tracking its upstream forever. 25 26 ## Builder image 27 28 Pipeline tasks run on `finwo/unos-builder:<builder/VERSION>` rather than 29 installing an apt toolchain per task. `builder/Dockerfile` bakes it by running 30 `mk/ci-setup.sh --install-only`, so the image and the toolchain list cannot 31 drift apart. 32 33 Tags are immutable: `publish-builder.sh` leaves an existing tag alone and 34 reports the task skipped. A toolchain change therefore needs a `builder/VERSION` 35 bump to take effect, and forgetting that bump is caught by `ci-setup.sh --check` 36 in the `check` task, which runs inside the image that was pushed. 37 38 Needs the `docker` worker feature and `REGISTRY_USERNAME` / `REGISTRY_TOKEN` 39 project variables (`REGISTRY_HOST` for a registry other than Docker Hub). 40 41 ## Files 42 43 - `publish-builder.sh [--dry-run]` -- build and push the builder image. 44 - `channels.inc` -- chain resolution and repo path layout. Sourced, not run. 45 - `channel-assemble.sh <channel>` -- compose a channel's package tree. 46 - `channel-lint.sh` -- reject dead overlays. 47 - `bootstrap-host.sh [--check|--force]` -- builds the host tools in 48 `build/host/bin` from pinned, checksummed tarballs: `muon` (bootstrapped from 49 a single amalgamated C file, so the chain terminates at a C compiler) and 50 `apk`. `--check` verifies presence *and version* and is the gate CI should 51 run first. Everything else assumes these exist. 52 - `deps.sh [--channel <c>] {list|vars|graph|order|rdeps|changed|check|seed|owner| 53 subpkgs|pkgver|rtdeps|channels|layers}` -- the build-dependency graph, read 54 from each template's `makedepends`. `order` is a topological sort; `check` 55 validates it (dangling deps, self-deps, cycles, missing hostmakedepends). 56 Defaults to `$UNOS_CHANNEL`, then `lts`. 57 - `gen-pipeline.sh [--write|--check]` -- emits `.conductor.yml` from that 58 graph. The pipeline is data (the conductor will not run repository code to 59 build its task graph), so the per-package `needs` edges must be written out 60 literally; generating them keeps `makedepends` the single source of truth. 61 `--check` is run by `tests/unit/test_deps.sh`, so a template change that is 62 not reflected in the pipeline fails the suite. 63 - `ci-setup.sh [--check]` -- installs the build toolchain into an ephemeral CI 64 container, then verifies every `hostmakedepends` in the graph resolves. That 65 verification is the point: it stops the apt list falling behind a template. 66 - `sysroot.sh` -- seed `build/sysroot` from the published apk repo, so a CI 67 container can build one package without rebuilding the other 26. 68 - `publish.sh [--arch A] [--index-only] [pkg...]` -- upload `.apk` files and 69 `APKINDEX.tar.gz` to the S3 bucket serving the public repo, using 70 `curl --aws-sigv4` (no awscli or rclone). Reads `BUCKET_ACCESS_KEY`, 71 `BUCKET_SECRET_KEY`, `BUCKET_NAME`, `BUCKET_ENDPOINT` (URL) and 72 `BUCKET_REGION` (region name); it rejects those last two transposed, since 73 the natural symptom is an opaque signature mismatch. The index is always 74 uploaded last, so it never advertises packages that are not there yet. 75 - `build-all.sh [--dry-run] [--skip-built] [pkg...]` -- build everything, or 76 one package and its closure, in `deps.sh order`. 77 - `build.sh <pkg>` -- the pipeline: source template -> check 78 `hostmakedepends`/`makedepends` -> fetch+verify -> extract -> patches -> 79 `do_build`/`do_install` into `DESTDIR` -> emit signed `.apk` into 80 `build/repo/<arch>/`. Dependency checks run before any fetching, so a missing 81 host tool is named up front rather than surfacing 200 lines into someone 82 else's generated parser. 83 - `repo-index.sh [arch]` -- rebuild `APKINDEX.tar.gz` with host `apk index`, 84 then sign it in place (abuild-sign layout: gzipped sig tar record 85 prepended, RSA/SHA1 over the index bytes). 86 - `pax-tar.c` -- tar writer for all three segments (C, std=c99, no 87 dependencies): sorted entries, uid/gid 0, per-file SHA1 in 88 `APK-TOOLS.checksum.SHA1` pax headers in data-segment mode (`--no-checksum` 89 for control/signature). Compiled on first use by the scripts; the binary is 90 output, not source. Host `tar` is used only for extracting upstreams. 91 - `sign-key.inc` -- `resolve_sign_key()` sourced by both scripts above. 92 Precedence: `$UNOS_SIGN_KEY` > repo-root `.sign-key` > single key in 93 `~/.unos-keys/`. 94 - `keymgmt.sh` -- key management: `new [--type rsa|supercop] [--for apk|unos] 95 <stem>` generates `<stem>-<8 hex>` in `~/.unos-keys/` (rsa: 4096-bit; 96 supercop: ed25519) and stages the public half into 97 `channels/base/packages/unos-keys/files/<for>/`; `use <name|file>` pins the package 98 signing key by writing its full path to `.sign-key` (never committed; CI 99 sets `UNOS_SIGN_KEY` or writes `.sign-key` itself); `list` shows local keys 100 and marks the active one. apk mandates RSA -- supercop-for-apk is refused. 101 102 C and shell only. Python is banned from our tooling and from every rootfs; 103 the sole exception is an upstream package whose own build mandates it 104 (glibc needs python3 to build) - used for that build only, never shipped. 105 106 ## Template contract 107 108 Shell fragment, not a script. Variables: `pkgname`, `version`, `revision` 109 (default 0), `short_desc`, `maintainer`, `license`, `homepage`, `distfiles` 110 (space-separated URLs, empty = no upstream source), `checksum` 111 (space-separated sha256, same order -- mandatory when `distfiles` is set), 112 `depends`, `provides`, `replaces` (space-separated, may be empty). 113 114 Optional phase overrides: `do_configure`, `do_build`, `do_install` (default: 115 no-op). Environment and helpers provided: `DESTDIR`, `WRKSRC`, `FILESDIR`, `WORK` 116 (scratch root for out-of-tree builds), `vinstall <file> <mode> <targetdir> 117 [name]`, `vmkdir <dir>`, `msg`, `die`. 118 119 Control-script pickup: `pre-install`, `post-install`, `pre-deinstall`, 120 `post-deinstall`, `pre-upgrade`, `post-upgrade`, `trigger` in `files/` are 121 packed as `.<name>` into the control segment when present. 122 123 ## Keys 124 125 `UNOS_SIGN_KEY` selects the RSA key (4096-bit minimum); unset means the 126 single `~/.unos-keys/*.rsa` key. Keys never enter git. The public half 127 (`<name>.rsa.pub`) is what lands in `/etc/apk/keys` via the `unos-keys` 128 package; v2 requires the on-disk filename to equal the key name embedded in 129 `.SIGN.RSA.<name>.rsa.pub`, which the driver guarantees by construction. 130 131 Key ceremony: private halves live only in `~/.unos-keys/`. Public halves are 132 committed by `keymgmt.sh new` under `channels/base/packages/unos-keys/files/<for>/` (public 133 by design) and installed by that package -- `files/apk/*` to `/etc/apk/keys/`, 134 `files/unos/*` to `/etc/unos/keys/`. Rotation = generate, commit the new 135 `.pub` files, bump the `unos-keys` version. 136 137 ## Repo layout 138 139 `build/repo/<arch>/` holds `*.apk` + signed `APKINDEX.tar.gz`. The arch level 140 is mandatory: apk appends `$arch` to the repository URL when fetching the 141 index. Current keys (dev estate, rotate before any release): 142 143 - RSA/apk: `unos-dev@finwo.dev-096b7b41` 144 - ed25519/supercop: `unos-dev@finwo.dev-6a2764d4` 145 146 ## Format notes (learned against real Alpine packages) 147 148 - `.apk` v2 = signed-control gzip member + data gzip member. The signature 149 covers the control gzip bytes; the control tar carries NO end-of-tar 150 markers (only the data segment terminates the archive). 151 - `datahash` is plain sha256 hex of `data.tar.gz` -- no `Q1` prefix (that 152 prefix form lives in indexes, where `apk index` computes it itself). 153 - Per-file `APK-TOOLS.checksum.SHA1` records are required for symlinks too 154 (v2 warns otherwise): symlinks hash the target string, regular files their 155 content (verified against `abuild-tar.c`). 156 157 ## Host dependencies 158 159 sh, cc, curl or wget, tar, gzip, openssl, sha256sum, install, patch. 160 apk-tools and muon are **not** host prerequisites -- `bootstrap-host.sh` builds 161 them into `build/host/bin` from pinned sources. 162 163 Per-package host tools (bison, flex, bc, perl, python3, ...) are declared as 164 `hostmakedepends` in the templates that need them and checked by `build.sh`, so 165 a CI image can be validated against the real requirement set with: 166 167 ```sh 168 ./mk/deps.sh check 169 ``` 170 171 A clean build from nothing is therefore: 172 173 ```sh 174 ./mk/bootstrap-host.sh # muon + apk into build/host/bin 175 ./mk/build-all.sh # every package, in dependency order 176 ./mk/repo-index.sh # signed APKINDEX 177 ./mk/publish.sh # upload packages + index to S3 178 ``` 179 180 ## Assembling an OS 181 182 Not here. `rootfs.sh`, `chroot.sh`, `make-img.sh`, `run-qemu.sh` and the 183 installer live in the `unos` repo, which consumes the published repository over 184 HTTP rather than reaching into `build/repo` on a shared filesystem. This repo 185 produces packages and stops there. 186 187 ## Host apk-tools v3 (bootstrap input, pinned) 188 189 Upstream `apk-tools` v3.0.8, tarball 190 `https://gitlab.alpinelinux.org/alpine/apk-tools/-/archive/v3.0.8/apk-tools-v3.0.8.tar.gz` 191 sha256 `e81c64a6e7c3806d45d4622c253e757aee970be84048554fa0eed79310e9453e`, 192 fetched once into `build/work/sources/`. Built in-tree **with muon, never 193 meson** (Python is banned from our tooling; upstream supports muon): 194 195 - dynamic, prefix `build/host/` (`-Dlua=disabled -Dpython=disabled 196 -Dhelp=disabled -Ddocs=disabled`, rpath `$ORIGIN/../lib`): 197 `build/host/bin/apk`, used by `repo-index.sh` for index generation. The 198 `unos` repo bootstraps its own copy for rootfs assembly. 199 200 There is deliberately **no `apk.static`**. An earlier revision built one and 201 justified it here as "the future target binary - no libcrypto needed on the 202 switch". That was already wrong: UNOS packages `openssl`, so `libcrypto.so.3` 203 and `libssl.so.3` are present on the switch and the target `apk` links them 204 dynamically. Its only consumer was rootfs assembly, which produces a 205 byte-identical rootfs with the dynamic binary. Building it cost 10.6MB against 206 2.0MB and 207 required `libcrypto.a`/`libssl.a`/`libz.a`/`libzstd.a` on the build host -- 208 static libraries that most CI containers do not ship, turning a cosmetic 209 preference into a hard bootstrap failure. If a static apk is ever wanted for 210 recovering a switch with a broken loader, it belongs in `channels/` as a target 211 artifact. 212 213 Muon itself is pinned: v0.6.0, 214 `https://github.com/muon-build/muon/archive/refs/tags/0.6.0.tar.gz`, 215 sha256 `5300e58c4b4d43e3026856004c79d746075aaa9d9e66d76ba9f32ce249495b81`, 216 bootstrapped (`bootstrap.sh`, pure C) into `build/host/bin/muon`. muon is 217 GPL-3.0 - fine as a host-only build tool (same standing as GCC); it never 218 enters any package or rootfs. 219 220 Our v2 artifacts verify and install cleanly under both v2.14 (host) and 221 v3.0.8, untrusted and fully trusted (signed index + seeded key). v3 needs 222 `--usermode` for non-root `--root` installs. This becomes a real package 223 template once the toolchain work lands; until then the pin + hash above are 224 the record. 225 226 **This is no longer prose to follow by hand** -- `mk/bootstrap-host.sh` is the 227 executable form of the above. `--check` verifies presence *and version* (a 228 stale binary from an older pin is worse than a missing one, because everything 229 downstream still appears to work), and `--force` rebuilds from the pinned 230 tarballs. muon's `meson-docs`/`meson-tests` subprojects are disabled because 231 its `setup` step otherwise git-clones them from GitHub mid-build, which has no 232 place in a bootstrap driven by checksummed tarballs.