kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 2fd574b3dbb533dc05f32f8aaa52812579e193c8
parent 0b2ac3d8b7b48659b680243876657755192b1f9a
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed, 17 Jun 2026 13:28:39 -0700

selfdist/doc: reconcile trusted_keys + sysroots paths to the single-root layout (DISTRIBUTE.md, SYSROOTS.md)

Diffstat:
Mdoc/DISTRIBUTE.md | 14++++++++++----
Mdoc/plan/SYSROOTS.md | 10+++++++++-
Atest/dist/run.sh | 280+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 299 insertions(+), 5 deletions(-)

diff --git a/doc/DISTRIBUTE.md b/doc/DISTRIBUTE.md @@ -186,10 +186,16 @@ recomputes the manifest hash and rejects the package if the trusted comment's pkgid does not match. This binds the signature to the exact manifest content, not merely to a name. -Trust anchors live in a trusted-keys file (`$KIT_TRUSTED_KEYS`, else -`$HOME/.config/kit/trusted_keys`; `src/dist/trust.c`), one -`keyid pubkey label` line each. A `.pub` bundled inside a package is never -trusted on its own. The verifier picks a key by the signature's key id: +Trust anchors live in a trusted-keys file. Under the single-root layout +(see [plan/SELFDIST.md](plan/SELFDIST.md)) the path is `$KIT_TRUSTED_KEYS`, +else `$KIT_HOME/config/trusted_keys` (with `$KIT_HOME` defaulting to +`$XDG_DATA_HOME/kit` ≡ `~/.local/share/kit`, `%LOCALAPPDATA%\kit` on Windows); +the legacy `$HOME/.config/kit/trusted_keys` is still read when the preferred +file is absent, so existing setups keep working. The path policy lives in the +driver (`driver/cmd/pkg.c`); the file format helpers are path-agnostic in +`src/dist/trust.c`. Each line is one `keyid pubkey label`. A `.pub` bundled +inside a package is never trusted on its own. The verifier picks a key by the +signature's key id: ``` -p PUBKEY verify against an explicitly supplied public key diff --git a/doc/plan/SYSROOTS.md b/doc/plan/SYSROOTS.md @@ -35,7 +35,15 @@ Freestanding targets require no sysroot kpkg; `libkit_rt.a` and ## Sysroot kpkg layout -Each installed sysroot unpacks into `~/.kit/sysroots/<triple>/`: +> Single-root layout note (see [SELFDIST.md](SELFDIST.md)): kit consolidates +> everything it manages under one root, `$KIT_HOME` (default `$XDG_DATA_HOME/kit` +> ≡ `~/.local/share/kit`). Installed sysroots therefore belong at +> `$KIT_HOME/sysroots/<triple>/`. The build/test provisioning today caches them +> under `$XDG_CACHE_HOME/kit/sysroots` (`KIT_SYSROOTS_DIR` in `mk/env.mk`); the +> SYSROOTS implementation should adopt the `$KIT_HOME/sysroots` location when it +> lands as a user-facing feature. + +Each installed sysroot unpacks into `$KIT_HOME/sysroots/<triple>/`: ``` <triple>/ diff --git a/test/dist/run.sh b/test/dist/run.sh @@ -0,0 +1,280 @@ +#!/bin/sh +# Hermetic self-distribution suite — exercises `kit update` over packages built +# the same way `make dist` does (kit pkg create), without any network or any +# dependency on `make dist`. Every install root lives under the sandbox, so the +# whole flow is offline-first and reproducible (SOURCE_DATE_EPOCH pinned). +# +# Type-K mode-P harness: shared assert verbs route through the unified counters +# over $work. SERIAL by design — the version layout under $KIT_HOME is mutated +# in place across cases, so do not parallelize. +# +# Run by: make test-selfdist (KIT=<binary> sh test/dist/run.sh) + +set -u + +script_dir=$(cd "$(dirname "$0")" && pwd) +repo_root=$(cd "$script_dir/../.." && pwd) + +KIT="${KIT:-$repo_root/build/kit}" +if [ ! -x "$KIT" ]; then + echo "dist: kit binary not found at $KIT" >&2 + exit 2 +fi + +SECKEY="$repo_root/test/dist/keys/nonrelease.key" +PUBKEY="$repo_root/test/dist/keys/nonrelease.pub" + +work=$(mktemp -d "${TMPDIR:-/tmp}/kit-dist-test.XXXXXX") +trap 'rm -rf "$work"' EXIT + +# HOME + KIT_HOME live inside the sandbox so nothing escapes $work and the host +# user's real ~/.local/share/kit is never touched. KIT_HOME is repointed per +# scenario via the kit_home() helper below; HOME stays put as a fallback root. +HOME="$work/home" +SOURCE_DATE_EPOCH=1 +export HOME SOURCE_DATE_EPOCH +mkdir -p "$HOME" "$work/stage" "$work/pkg" + +KIT_KIT_DIR="$repo_root/test/lib" +. "$repo_root/test/lib/kit_sh_kit.sh" +kit_report_init + +# ---- local helpers --------------------------------------------------------- + +have_cmd() { command -v "$1" >/dev/null 2>&1; } + +# kit_home DIR : point every subsequent `kit update` at this install root. +kh= +kit_home() { kh="$work/$1"; export KIT_HOME="$kh"; } + +# stage_kpkg VER : build a signed fat kit kpkg containing bin/kit (= $KIT), a +# VERSION file, and a small lib/ payload. Echoes the kpkg path; records the +# package id in $LAST_KPKG_ID for the channel-index cases. +LAST_KPKG_ID= +stage_kpkg() { + ver=$1 + sdir="$work/stage/$ver" + out="$work/pkg/kit-$ver.kpkg" + mkdir -p "$sdir/bin" "$sdir/lib" + cp "$KIT" "$sdir/bin/kit" + printf '%s\n' "$ver" > "$sdir/VERSION" + printf 'self-distribution payload for %s\n' "$ver" > "$sdir/lib/data.txt" + run_ok "create-$ver" "$KIT" pkg create \ + --name kit --version "$ver" --format kpkg --native-shape fat \ + -s "$SECKEY" --root "$sdir" -o "$out" + LAST_KPKG_ID=$(first_hex_id "$work/create-$ver.out") + printf '%s\n' "$out" +} + +# current_is NAME KH_DIR WANT : assert $KH_DIR/current resolves to versions/WANT. +current_is() { + name=$1; kdir=$2; want=$3 + got=$(readlink "$kdir/current" 2>/dev/null) + if [ "$got" = "versions/$want" ]; then ok "$name" + else + echo "current -> '$got', want 'versions/$want'" > "$work/$name.diag" + not_ok "$name" "$work/$name.diag" + fi +} + +# version_runs NAME EXE WANT_VER : run EXE --version and assert WANT_VER appears. +version_runs() { + name=$1; exe=$2; wantver=$3 + if "$exe" --version > "$work/$name.out" 2>&1 && + grep -F "$wantver" "$work/$name.out" >/dev/null 2>&1; then + ok "$name" + else + not_ok "$name" "$work/$name.out" + fi +} + +flip_byte() { + file=$1; off=$2 + printf '\377' | dd of="$file" bs=1 seek="$off" count=1 conv=notrunc >/dev/null 2>&1 +} + +# host_triple : 3rd field of `kit <ver> (<build>, <TRIPLE>)`. +host_triple() { + "$KIT" --version 2>/dev/null | sed -n 's/^kit [^ ]* (.*, \(.*\))/\1/p' | sed -n '1p' +} + +# =========================================================================== # +# (a) offline install of 2026.6.0 +# =========================================================================== # +kit_home kh +kpkg_60=$(stage_kpkg 2026.6.0) + +run_ok "a-install-60" "$KIT" update "$kpkg_60" +contains "a-install-60-msg" "$work/a-install-60.out" "installed kit 2026.6.0" +current_is "a-current-60" "$kh" 2026.6.0 +assert_file_exists "a-versioned-bin" "$kh/versions/2026.6.0/bin/kit" +assert_file_exists "a-link-kit" "$kh/bin/kit" +assert_file_exists "a-link-cc" "$kh/bin/cc" +is_executable "a-link-kit-exec" "$kh/bin/kit" +is_executable "a-link-cc-exec" "$kh/bin/cc" +version_runs "a-link-kit-runs" "$kh/bin/kit" 2026.6.0 + +# =========================================================================== # +# (b) second install + atomic flip; --list marks the new current +# =========================================================================== # +kpkg_61=$(stage_kpkg 2026.6.1) +run_ok "b-install-61" "$KIT" update "$kpkg_61" +current_is "b-current-61" "$kh" 2026.6.1 +version_runs "b-link-kit-runs-61" "$kh/bin/kit" 2026.6.1 +run_ok "b-list" "$KIT" update --list +contains "b-list-has-60" "$work/b-list.out" "2026.6.0" +contains "b-list-star-61" "$work/b-list.out" "* 2026.6.1" + +# =========================================================================== # +# (c) offline --version flip back to 2026.6.0 (no network) +# =========================================================================== # +run_ok "c-flip-60" "$KIT" update --version 2026.6.0 +contains "c-flip-60-msg" "$work/c-flip-60.out" "kit 2026.6.0 is now current" +current_is "c-current-60" "$kh" 2026.6.0 + +# =========================================================================== # +# (d) --rollback from a known current to the previous version +# =========================================================================== # +# Make 2026.6.1 current first, then roll back to 2026.6.0 (the prior CalVer). +run_ok "d-prep-flip-61" "$KIT" update --version 2026.6.1 +current_is "d-prep-current-61" "$kh" 2026.6.1 +run_ok "d-rollback" "$KIT" update --rollback +current_is "d-current-60" "$kh" 2026.6.0 +contains "d-rollback-warn" "$work/d-rollback.out" "re-upgrade" + +# =========================================================================== # +# (e) --prune removes every non-current version +# =========================================================================== # +# current is 2026.6.0; prune must drop versions/2026.6.1. +run_ok "e-prune" "$KIT" update --prune +contains "e-prune-msg" "$work/e-prune.out" "removed kit 2026.6.1" +if [ -e "$kh/versions/2026.6.1" ]; then + echo "versions/2026.6.1 still present after prune" > "$work/e-pruned-gone.diag" + not_ok "e-pruned-gone" "$work/e-pruned-gone.diag" +else + ok "e-pruned-gone" +fi +run_ok "e-list-after" "$KIT" update --list +contains "e-list-only-60" "$work/e-list-after.out" "* 2026.6.0" +if grep -F "2026.6.1" "$work/e-list-after.out" >/dev/null 2>&1; then + echo "2026.6.1 still listed after prune" > "$work/e-list-no-61.diag" + not_ok "e-list-no-61" "$work/e-list-no-61.diag" +else + ok "e-list-no-61" +fi + +# =========================================================================== # +# (f) explicit-key verify paths (pkg verify + update --key) +# =========================================================================== # +run_ok "f-verify-pubkey" "$KIT" pkg verify -p "$PUBKEY" "$kpkg_60" +contains "f-verify-name" "$work/f-verify-pubkey.out" "ok: kit 2026.6.0" +kit_home kh-f +run_ok "f-update-key" "$KIT" update --key "$PUBKEY" "$kpkg_60" +current_is "f-current-60" "$work/kh-f" 2026.6.0 + +# =========================================================================== # +# (g) wrong key is rejected and leaves current untouched +# =========================================================================== # +# Fresh root with 2026.6.0 already current, then try installing 2026.6.1 with a +# foreign key — must fail and keep current at 2026.6.0. +kit_home kh-g +run_ok "g-seed-60" "$KIT" update "$kpkg_60" +current_is "g-seed-current-60" "$work/kh-g" 2026.6.0 +run_ok "g-keygen-other" "$KIT" pkg keygen -o "$work/other" +run_fail "g-update-wrong-key" "$KIT" update --key "$work/other.pub" "$kpkg_61" +current_is "g-current-unchanged" "$work/kh-g" 2026.6.0 + +# =========================================================================== # +# (h) tamper is rejected and leaves current untouched +# =========================================================================== # +# Reuse kh-g (current = 2026.6.0). Flip one byte well inside the kpkg. +cp "$kpkg_61" "$work/pkg/tampered.kpkg" +tsize=$(wc -c < "$work/pkg/tampered.kpkg") +flip_byte "$work/pkg/tampered.kpkg" $((tsize / 2)) +run_fail "h-update-tampered" "$KIT" update "$work/pkg/tampered.kpkg" +current_is "h-current-unchanged" "$work/kh-g" 2026.6.0 + +# =========================================================================== # +# (i) bootstrap: stock minisign verifies a kit-signed tar.gz +# =========================================================================== # +run_ok "i-create-targz" "$KIT" pkg create \ + --name kit --version 2026.6.0 --format tar.gz --native-shape fat \ + -s "$SECKEY" --root "$work/stage/2026.6.0" -o "$work/pkg/kit-boot.tar.gz" +run_ok "i-sign-targz" "$KIT" pkg sign -s "$SECKEY" "$work/pkg/kit-boot.tar.gz" +assert_file_exists "i-sig-written" "$work/pkg/kit-boot.tar.gz.minisig" +if have_cmd minisign; then + run_ok "i-minisign-verify" minisign -Vm "$work/pkg/kit-boot.tar.gz" -p "$PUBKEY" +else + skip_test "i-minisign-verify" "minisign not installed" +fi + +# =========================================================================== # +# (j) networked + monotonic via file:// channel index +# =========================================================================== # +# A signed package's trust comes from its own signature; the index is only a +# fetch hint. We hand-write a `kit-release 1` index (format per +# test/api/release_index_test.c) pointing at the local 2026.6.0 kpkg over a +# file:// URL. If the fetch transport is unavailable here, the whole block is +# skipped rather than failed. +if ! have_cmd curl; then + skip_test "j-network" "curl not available for file:// fetch" +else + triple=$(host_triple) + if [ -z "$triple" ]; then + skip_test "j-network" "could not determine host triple" + else + # Re-derive the 2026.6.0 kpkg id from its create output. + id60=$(first_hex_id "$work/create-2026.6.0.out") + idx="$work/channel-60.index" + { + printf 'kit-release 1\n' + printf 'channel = stable\n' + printf 'version = 2026.6.0\n' + printf 'hash = blake2b-256\n' + printf '\n' + printf '[host]\n' + printf 'target = %s\n' "$triple" + printf 'kpkg = %s\n' "$id60" + printf 'url = file://%s\n' "$kpkg_60" + } > "$idx" + + # Probe the file:// transport against a fresh root before asserting: + # if it cannot fetch+install here, skip the block gracefully. + kit_home kh-j-probe + if "$KIT" update --index "file://$idx" > "$work/j-probe.out" 2>&1; then + current_is "j-probe-install-60" "$work/kh-j-probe" 2026.6.0 + + # --check reports the latest channel version, changing nothing. + kit_home kh-j-check + run_ok "j-check" "$KIT" update --index "file://$idx" --check + contains "j-check-latest" "$work/j-check.out" "latest 2026.6.0" + contains "j-check-none" "$work/j-check.out" "installed current: (none)" + if [ -e "$work/kh-j-check/current" ]; then + echo "--check created a current pointer" > "$work/j-check-nochange.diag" + not_ok "j-check-nochange" "$work/j-check-nochange.diag" + else + ok "j-check-nochange" + fi + + # Fresh root: networked fetch + install 2026.6.0. + kit_home kh-j-fetch + run_ok "j-fetch-install" "$KIT" update --index "file://$idx" + current_is "j-fetch-current-60" "$work/kh-j-fetch" 2026.6.0 + + # Monotonic guard: with current = 2026.6.1, an index advertising the + # older 2026.6.0 is refused, but --allow-downgrade succeeds. + run_ok "j-fetch-up-to-61" "$KIT" update "$kpkg_61" + current_is "j-fetch-current-61" "$work/kh-j-fetch" 2026.6.1 + run_fail "j-monotonic-reject" "$KIT" update --index "file://$idx" + contains "j-monotonic-msg" "$work/j-monotonic-reject.err" "older than installed" + current_is "j-monotonic-unchanged" "$work/kh-j-fetch" 2026.6.1 + run_ok "j-allow-downgrade" "$KIT" update --index "file://$idx" --allow-downgrade + current_is "j-downgrade-current-60" "$work/kh-j-fetch" 2026.6.0 + else + skip_test "j-network" "file:// fetch not supported in this environment" + fi + fi +fi + +kit_summary dist +kit_exit