unos-repository

APK repository for unos
git clone git://git.finwo.net/misc/unos-repository
Log | Files | Refs | README

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.