build: an apk-managed root, with SchOS as a package

make aroot builds a SchFS root from Alpine's minirootfs, the packages
ports/alpine/world lists, and SchOS itself as the signed apk package
schos-base, in its own repository inside the root. apk add installs from
Alpine; apk upgrade updates both. Booted: zsh is Alpine's, apk update
sees 28669 packages, apk add htop works, and so does xorg-server.

world.lock pins every package with its sha256; a build fetches only what
dist/alpine/ lacks and never looks for newer versions. make alpine-lock
is the one step that does.

Alpine's zsh, busybox commands and hwdata replace SchOS's copies; SchOS
keeps its own tools, toybox's other commands, and static mksh, which
schinit's recovery shell now tries first.

Also: the dependency-file include matched Alpine's usr/lib/sysctl.d, a
directory, and stopped every make. See docs/plan/alpine.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
Nexoniarz 2026-10-24 15:28:32 +02:00
commit 406cff2ec0
9 changed files with 473 additions and 1 deletions

View file

@ -0,0 +1,62 @@
#!/bin/sh
# Resolve ports/alpine/world into world.lock.
#
# tools/posix/alpine-lock.sh APK_STATIC MINIROOTFS WORLD LOCK DISTDIR
#
# Asks Alpine's mirror, so it needs the network; nothing else in the build
# does. Every package in the dependency closure is fetched into DISTDIR and
# recorded as
#
# <file> <sha256> <repository>
#
# which is all a build needs to download it again and know it got the same
# bytes. apk checks Alpine's signature on top of that when it installs.
set -eu
[ $# -eq 5 ] || { echo "usage: $0 APK_STATIC MINIROOTFS WORLD LOCK DISTDIR" >&2; exit 2; }
abs() { case "$1" in /*) echo "$1" ;; *) echo "$(pwd)/$1" ;; esac; }
APK=$(abs "$1"); MINI=$(abs "$2"); WORLD=$(abs "$3"); LOCK=$(abs "$4"); DIST=$(abs "$5")
T=$(mktemp -d)
trap 'rm -rf "$T"' EXIT
mkdir -p "$T/root" "$DIST"
tar -xzf "$MINI" -C "$T/root"
# apk.static looks for CA certificates on the host, where a NixOS or a macOS
# keeps them somewhere else; Alpine's own bundle is right here.
export SSL_CERT_FILE="$T/root/etc/ssl/certs/ca-certificates.crt"
PKGS=$(sed 's/#.*//' "$WORLD" | tr -s ' \t' '\n' | grep . || true)
[ -n "$PKGS" ] || { echo "alpine-lock: $WORLD lists nothing" >&2; exit 1; }
"$APK" --root "$T/root" --no-cache update -q
# shellcheck disable=SC2086
"$APK" --root "$T/root" --no-cache fetch --recursive --output "$DIST" $PKGS
# Which repository each one came from, read from the indexes apk just used.
MIRROR=$(grep -m1 '^http' "$T/root/etc/apk/repositories" | sed 's|/[^/]*$||')
for r in main community; do
curl -fsSL "$MIRROR/$r/x86_64/APKINDEX.tar.gz" | tar -xzO APKINDEX |
awk -v r="$r" '/^P:/{p=substr($0,3)} /^V:/{print p "-" substr($0,3) ".apk", r}'
done > "$T/where"
# The closure, as apk resolved it: install it into the scratch root and read
# back what is there.
# shellcheck disable=SC2086
"$APK" --root "$T/root" --no-cache --no-scripts --no-chown add -q $PKGS
"$APK" --root "$T/root" info -v 2>/dev/null | sort > "$T/closure"
{
echo "# Generated by tools/posix/alpine-lock.sh from world. Do not edit;"
echo "# change world and run \`make alpine-lock\`."
echo "# file sha256 repository"
while read -r nv; do
f=$nv.apk
[ -f "$DIST/$f" ] || continue # in the minirootfs, not needed by world
repo=$(awk -v f="$f" '$1 == f { print $2; exit }' "$T/where")
[ -n "$repo" ] || { echo "alpine-lock: $f is in no repository" >&2; exit 1; }
sum=$(sha256sum "$DIST/$f" | cut -d' ' -f1)
echo "$f $sum $repo"
done < "$T/closure"
} > "$LOCK.new"
mv "$LOCK.new" "$LOCK"
echo "alpine-lock: $(grep -vc '^#' "$LOCK") packages in $LOCK"