Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

0008 — Packaging & delivery (curl|sh first, then native repos)

Status: accepted (2026-06-22) — ratified by maintainer; proposed 2026-06-04

Context

SPEC §3/§10 commit keyboot to “CI-signed artifacts shipped as native packages (apk, ebuild, deb) in our own repos; ansible manages the repo definitions,” with a signed curl | sh bootstrap (§13.10) and a host at packages.osterman.co. None of that exists yet, and two new userland tools (keyboot-autosnap, keyboot-be-upgrade) plus the boot image now need a distribution story. This ADR fixes the artifact set, the package split, the channels, and the staging order so we can build the lowest-friction path first without re-architecting when the native repos land.

Decision

Artifact set & package split

Three packages from the build, split so the dangerous/heavy bits are isolated:

  • keyboot — runtime keyboot binary + keyboot-autosnap + keyboot-be-upgrade + the grub.d/10_keyboot seam. Installs on the running host. (be-upgrade is host-safe: it clones, never wipes.)
  • keyboot-boot — the kernel + initramfs (large, per-kernel). Its post-install stages to /var/lib/keyboot/staged/ first, then — per the superseding ADR 0009 (A/B ESP slots) — auto-promotes: writes the new keyboot into the inactive A/B slot and arms a boot-once trial, so a failure auto-reverts to the known-good slot. This answers the §10.3/§16.3 “package scripts touching /boot make operators nervous” without leaving the commit manual: the boot-counter is the safety net the original “stage-and-operator- commits” stance lacked. KEYBOOT_AUTOPROMOTE=0 opts back into staging-only (the operator then commits with keyboot install). ADR 0009 closes ADR 0004’s keyboot-self-upgrade boundary.
  • keyboot-install — rescue/install only; never auto-installed on a running host (SPEC §3 binary split).

The two shell tools are arch-independent; keyboot/keyboot-install are x86_64-musl static (aarch64 later). Shell stays shell deliberately (zero-dep, runs in recovery, easy to audit); Rust stays for crypto/FFI.

Channels & staging order

Phase A — packages.osterman.co static host + signed curl | sh (build first). A small static site (nginx container behind the existing proxy; new /root/layout entry + proxy route) that CI publish rsyncs into:

packages.osterman.co/keyboot/<ver>/{keyboot-x86_64-musl, keyboot-install-x86_64-musl,
    keyboot-autosnap, keyboot-be-upgrade, keyboot-vmlinuz, keyboot-initramfs.img,
    manifest.json, SHA256SUMS, SHA256SUMS.minisig}
packages.osterman.co/keyboot/install            # the bootstrap (trailing minisig)
packages.osterman.co/keyboot/latest             # version pointer
  • Signing: minisign for this channel (one keypair, trailing-block-friendly, tiny verifier). Pubkey baked into the keyboot image + documented for pre-fetch. The bootstrap verifies before exec / before install.
  • ci/install.sh is the bootstrap: detect arch/distro → fetch latest (or a pinned KEYBOOT_VERSION) → verify SHA256SUMS.minisig → install keyboot-autosnap+cron, keyboot-be-upgrade, and the keyboot binary; --rescue also pulls keyboot-install + the boot image. Idempotent.

Phase B — native repos (§10.3 end state).

  • Alpine abuild → signed .apk + APKINDEX at packages.osterman.co/alpine/ <branch>/; pubkey to /etc/apk/keys/. install-os can pre-add the repo so BEs auto-update.
  • Debian .deb → reprepro/aptly (InRelease GPG) at …/debian/<codename>/.
  • Gentoo ebuild overlay git repo git.osterman.co/west17m/keyboot-overlay (+ Manifest GPG; optional binhost).
  • Ansible role adds the repo def + key per host.

Signing identities

minisign for curl|sh; native repos use their own (apk RSA, apt GPG, gentoo Manifest GPG) — all keys in GitLab CI variables (§10.2). Open: unify GPG across apt+gentoo.

Version ↔ ZFS-floor coupling

keyboot/keyboot-boot package versions encode the OpenZFS feature floor (ADR 0004), so the package manager can refuse a pool-feature bump until keyboot is upgraded. manifest.json carries the zfs version + compat baseline.

Alternatives considered

  • Native repos first: higher friction to stand up (per-distro signing + index tooling) before anything is installable. curl|sh from a static host is the faster MVP and serves every distro at once; native repos layer on.
  • GitLab Package Registry only: fine for generic binary pulls, awkward for the predictable repo-index paths apk/apt expect. Use a static host.
  • Rewrite the shell tools in Rust for one package format: loses the audit/recovery-friendliness; the tools must run in minimal envs. Rejected.
  • Auto-commit the ESP on package upgrade: originally rejected as the operator-nervous footgun (a bad write bricks the boot stage). ADR 0009 revisits this: with A/B ESP slots + a boot-once trial that auto-reverts, the staged image can be auto-promoted safely — the failure mode the footgun worried about is now caught by the boot-counter. KEYBOOT_AUTOPROMOTE=0 keeps the manual stage-then-commit for the conservative operator.

Consequences

  • New infra: packages.osterman.co (static + minisign) must be stood up — a /root/layout entry + proxy route + a CI publish stage. Until then, ci/install.sh can point at a KEYBOOT_PKG_BASE override (e.g. a GitLab artifact URL) so the bootstrap is testable before the host exists.
  • The build pipeline (§10.1) gains package (apk/deb/ebuild + tarballs) and publish stages after sign.
  • ESP integrity: keyboot-boot’s post-install is now governed by ADR 0009 (A/B ESP slots, auto-promote with boot-once auto-revert) — no longer an open boundary.
  • install-os already installs the BE tools directly (build-image staging); the native packages are the update path for already-running hosts.