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

80
ports/alpine/Makefile Normal file
View file

@ -0,0 +1,80 @@
# Alpine Linux -- the base the apk-managed root is built on: https://alpinelinux.org/
#
# Alpine is musl with a different init, which is what SchOS is, and its
# binaries run here unmodified -- Alpine's own dynamic linker, busybox, and
# apk with TLS. So rather than port each program by hand, the root becomes an
# Alpine root that apk manages, with SchOS's own programs as an apk package of
# their own (schos-base, built by tools/posix/mkaroot.sh).
#
# Two downloads, both pinned:
# the minirootfs musl, busybox, apk-tools, alpine-keys, ca-certificates:
# the smallest root apk can work in
# apk.static apk itself, static, for the build host: it makes the
# schos-base package and its index, and installs it into
# the root, without anything from Alpine on the host
#
# Nothing goes into sysroot/: this is input to the root image, not part of the
# ramdisk-root system.
include ../mk/port.mk
ALPINE_BRANCH := 3.24
ALPINE_VERSION := 3.24.3
ALPINE_MIRROR := https://dl-cdn.alpinelinux.org/alpine/v$(ALPINE_BRANCH)
MINI := alpine-minirootfs-$(ALPINE_VERSION)-x86_64.tar.gz
MINI_URL := $(ALPINE_MIRROR)/releases/x86_64/$(MINI)
MINI_SHA256 := f8e1cafba9b1b283b5d30d06f9885c8d2bc161ffe9083ade8e2e157813a53407
APKS := apk-tools-static-3.0.8-r0.apk
APKS_URL := $(ALPINE_MIRROR)/main/x86_64/$(APKS)
# Recorded on first download. Alpine signs the package itself, but nothing on
# the build host could check that signature before apk.static exists.
APKS_SHA256 := c8e2c88c13ba12a12269b79a3543e1190ff8c0ab0beb32b58cadfd5881c619e3
APK_STATIC := $(PORTDIR)/apk.static
$(DISTDIR)/$(MINI): | $(DISTDIR)
curl -fsSL -o $@ $(MINI_URL)
sh $(SHA256CHECK) "$(MINI_SHA256)" "$@"
$(DISTDIR)/$(APKS): | $(DISTDIR)
curl -fsSL -o $@ $(APKS_URL)
sh $(SHA256CHECK) "$(APKS_SHA256)" "$@"
download: $(DISTDIR)/$(MINI) $(DISTDIR)/$(APKS)
# An .apk is gzipped tar streams back to back; tar reads through them and
# warns about the signature's odd headers, which is harmless.
$(APK_STATIC): $(DISTDIR)/$(APKS)
mkdir -p $(PORTDIR)/x
tar -xzf $< -C $(PORTDIR)/x 2>/dev/null || test -f $(PORTDIR)/x/sbin/apk.static
cp $(PORTDIR)/x/sbin/apk.static $@
rm -rf $(PORTDIR)/x
build install: $(APK_STATIC) $(DISTDIR)/$(MINI) world
clean:
rm -rf $(PORTDIR)
# world.lock's packages, into dist/alpine/. Only what is missing is fetched,
# and each file is checked against the lock: a build never asks Alpine what
# is newer. `make alpine-lock` at the top level is what does that.
WORLD_DIST := $(DISTDIR)/alpine
world: | $(DISTDIR)
mkdir -p $(WORLD_DIST)
grep -v '^#' world.lock | while read -r f sum repo; do \
if [ ! -f $(WORLD_DIST)/$$f ]; then \
echo "alpine: fetching $$f"; \
curl -fsSL -o $(WORLD_DIST)/$$f.part $(ALPINE_MIRROR)/$$repo/x86_64/$$f && \
mv $(WORLD_DIST)/$$f.part $(WORLD_DIST)/$$f || exit 1; \
fi; \
sh $(SHA256CHECK) "$$sum" $(WORLD_DIST)/$$f >/dev/null || exit 1; \
done
lock: $(APK_STATIC) $(DISTDIR)/$(MINI)
sh $(TOPDIR)/tools/posix/alpine-lock.sh $(APK_STATIC) $(DISTDIR)/$(MINI) \
world world.lock $(WORLD_DIST)
.PHONY: world lock