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:
parent
2c5a278d56
commit
406cff2ec0
9 changed files with 473 additions and 1 deletions
62
tools/posix/alpine-lock.sh
Normal file
62
tools/posix/alpine-lock.sh
Normal 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"
|
||||
172
tools/posix/mkaroot.sh
Normal file
172
tools/posix/mkaroot.sh
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
#!/bin/sh
|
||||
# Build the apk-managed root: Alpine's minirootfs, with SchOS installed into
|
||||
# it as an apk package.
|
||||
#
|
||||
# tools/posix/mkaroot.sh SYSROOT OUTDIR APK_STATIC MINIROOTFS KEYDIR VERSION \
|
||||
# LOCK ALPINEDIR
|
||||
#
|
||||
# SYSROOT the staged SchOS userland (sysroot/), what the ramdisk root is
|
||||
# OUTDIR gets root/ (the tree for mkschfs) and repo/ (the SchOS repository)
|
||||
# APK_STATIC Alpine's static apk, from ports/alpine
|
||||
# MINIROOTFS Alpine's minirootfs tarball, from ports/alpine
|
||||
# KEYDIR where the repository signing key lives; made on first use
|
||||
# VERSION schos-base's apk version, e.g. 0.1_git20261024-r0
|
||||
# LOCK ports/alpine/world.lock: the Alpine packages to install
|
||||
# ALPINEDIR where those are, already fetched and checked (dist/alpine)
|
||||
#
|
||||
# What comes out is a root apk can manage: `apk add` installs anything from
|
||||
# Alpine, and `apk upgrade` updates SchOS from its own repository, which is
|
||||
# copied into the root at /var/lib/schos/repo and listed after Alpine's.
|
||||
#
|
||||
# schos-base is the sysroot less what Alpine's own packages own. Those are:
|
||||
#
|
||||
# /bin/sh busybox-binsh. Alpine's package scripts are written for
|
||||
# ash; mksh stays, as /bin/mksh, and schinit's recovery
|
||||
# shell looks for it first because it is static.
|
||||
# /lib/ld-musl-* musl. Every SchOS program is static and does not use it.
|
||||
# /etc/passwd, /etc/group
|
||||
# alpine-baselayout-data. Root's shell becomes zsh below,
|
||||
# the one edit SchOS makes to them.
|
||||
# /etc/profile alpine-baselayout-data. SchOS's goes to profile.d.
|
||||
#
|
||||
# /etc/os-release and /etc/motd are SchOS's and taken over with `replaces`.
|
||||
# Alpine's programs replace SchOS's where both have one:
|
||||
#
|
||||
# zsh Alpine's, from world. SchOS keeps its zshrc and zshenv,
|
||||
# taken over with `replaces`.
|
||||
# pci.ids, usb.ids Alpine's hwdata-pci and hwdata-usb, from world; Xorg
|
||||
# depends on hwdata-pci and two owners of one file is an
|
||||
# error apk will not install past.
|
||||
# busybox's commands cat, sed, find and the rest: Alpine's package scripts
|
||||
# are written against busybox's, and toybox's same-named
|
||||
# links are left out. toybox keeps what busybox lacks.
|
||||
#
|
||||
# Runs as an ordinary user. apk's scripts need a chroot, so the install runs
|
||||
# with --no-scripts and this script makes the one change schos-base's
|
||||
# post-install would; on a running SchOS the script itself runs.
|
||||
|
||||
set -eu
|
||||
|
||||
[ $# -eq 8 ] || { echo "usage: $0 SYSROOT OUTDIR APK_STATIC MINIROOTFS KEYDIR VERSION LOCK ALPINEDIR" >&2; exit 2; }
|
||||
abs() { case "$1" in /*) echo "$1" ;; *) echo "$(pwd)/$1" ;; esac; }
|
||||
SYSROOT=$(abs "$1"); OUT=$(abs "$2"); APK=$(abs "$3")
|
||||
MINI=$(abs "$4"); KEYDIR=$(abs "$5"); VERSION=$6
|
||||
LOCK=$(abs "$7"); ADIR=$(abs "$8")
|
||||
|
||||
KEY=$KEYDIR/schos-local.rsa
|
||||
ROOT=$OUT/root
|
||||
REPO=$OUT/repo/x86_64
|
||||
PKG=$OUT/pkg
|
||||
|
||||
# --- the signing key ----------------------------------------------------------
|
||||
#
|
||||
# One per build tree, never committed: whoever builds SchOS signs their own
|
||||
# packages, and the root they build trusts exactly that key besides Alpine's.
|
||||
if [ ! -f "$KEY" ]; then
|
||||
mkdir -p "$KEYDIR"
|
||||
openssl genrsa -out "$KEY" 2048 2>/dev/null
|
||||
openssl rsa -in "$KEY" -pubout -out "$KEY.pub" 2>/dev/null
|
||||
echo "mkaroot: made repository key $KEY"
|
||||
fi
|
||||
|
||||
rm -rf "$OUT"
|
||||
mkdir -p "$ROOT" "$REPO" "$PKG/root"
|
||||
# First, because what busybox provides is read from it.
|
||||
tar -xzf "$MINI" -C "$ROOT"
|
||||
|
||||
# --- schos-base ---------------------------------------------------------------
|
||||
|
||||
cp -a "$SYSROOT/." "$PKG/root/"
|
||||
P=$PKG/root
|
||||
mv "$P/bin/sh" "$P/bin/mksh"
|
||||
rm -f "$P"/lib/ld-musl-*.so.1 "$P/etc/passwd" "$P/etc/group"
|
||||
rm -rf "$P/bin/zsh" "$P/bin/zsh-5.9" "$P/bin/zsh.old" "$P/usr/share/zsh"
|
||||
# pci.ids and usb.ids are Alpine's hwdata-pci and hwdata-usb, in world.
|
||||
rm -rf "$P/usr/share/hwdata"
|
||||
|
||||
# Every busybox link in the minirootfs, out of the package.
|
||||
(cd "$ROOT" && find bin sbin usr/bin usr/sbin -type l) | while read -r l; do
|
||||
case "$(readlink "$ROOT/$l")" in
|
||||
*/busybox) rm -f "$P/$l" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# SchOS's profile without the three lines Alpine's already sets, and sets
|
||||
# better: PATH with sbin, HOME from passwd, TERM from the console job.
|
||||
mkdir -p "$P/etc/profile.d"
|
||||
grep -v '^export \(PATH\|HOME\|TERM\)=' "$P/etc/profile" > "$P/etc/profile.d/schos.sh"
|
||||
rm -f "$P/etc/profile"
|
||||
|
||||
# Mount points and runtime directories are alpine-baselayout's; an empty
|
||||
# directory here only duplicates it.
|
||||
for d in dev proc sys run tmp srv; do
|
||||
rmdir "$P/$d" 2>/dev/null || true
|
||||
done
|
||||
|
||||
cat > "$PKG/post-install" <<'EOF'
|
||||
#!/bin/sh
|
||||
# Root logs in to zsh, as on every SchOS; Alpine's passwd says /bin/sh.
|
||||
sed -i 's|^\(root:[^:]*:0:0:[^:]*:[^:]*:\)/bin/sh$|\1/bin/zsh|' /etc/passwd
|
||||
for s in /bin/zsh /bin/mksh; do
|
||||
grep -qx "$s" /etc/shells 2>/dev/null || echo "$s" >> /etc/shells
|
||||
done
|
||||
exit 0
|
||||
EOF
|
||||
cp "$PKG/post-install" "$PKG/post-upgrade"
|
||||
|
||||
"$APK" mkpkg \
|
||||
--info name:schos-base \
|
||||
--info "version:$VERSION" \
|
||||
--info arch:x86_64 \
|
||||
--info "description:SchOS: init, console, tools and configuration" \
|
||||
--info license:GPL-2.0-or-later \
|
||||
--info url:https://github.com/Nexoniarz/SchOS \
|
||||
--info origin:schos-base \
|
||||
--info "depends:alpine-baselayout busybox musl apk-tools zsh" \
|
||||
--info "replaces:alpine-release alpine-baselayout zsh" \
|
||||
--files "$P" \
|
||||
--script "post-install:$PKG/post-install" \
|
||||
--script "post-upgrade:$PKG/post-upgrade" \
|
||||
--sign-key "$KEY" \
|
||||
--output "$REPO/schos-base-$VERSION.apk"
|
||||
|
||||
"$APK" --keys-dir "$KEYDIR" mkndx --sign-key "$KEY" \
|
||||
--output "$REPO/APKINDEX.tar.gz" "$REPO"/*.apk
|
||||
|
||||
# --- the root -----------------------------------------------------------------
|
||||
|
||||
cp "$KEY.pub" "$ROOT/etc/apk/keys/"
|
||||
|
||||
# world.lock's packages, by file: no index and no network. apk still checks
|
||||
# each one's signature against Alpine's keys in the root.
|
||||
FILES=$(grep -v '^#' "$LOCK" | while read -r f sum repo; do echo "$ADIR/$f"; done)
|
||||
# shellcheck disable=SC2086
|
||||
"$APK" --root "$ROOT" --repositories-file /dev/null --repository "$OUT/repo" \
|
||||
--no-network --no-scripts --no-chown --no-cache add $FILES schos-base
|
||||
|
||||
# What world asked for, by name rather than by file, so a later `apk upgrade`
|
||||
# on the running system follows Alpine rather than these exact versions.
|
||||
sed 's/#.*//' "$(dirname "$LOCK")/world" | tr -s ' \t' '\n' | grep . |
|
||||
while read -r w; do grep -qx "$w" "$ROOT/etc/apk/world" || echo "$w" >> "$ROOT/etc/apk/world"; done
|
||||
# Installing by file pins each one to its exact checksum ("zsh><Q1..."); none
|
||||
# of them should stay pinned, and the dependencies among them need no entry.
|
||||
sed -i '/></d' "$ROOT/etc/apk/world"
|
||||
|
||||
# The post-install above, done by hand because --no-scripts skipped it.
|
||||
ROOT_PI=$ROOT sh -c '
|
||||
sed -i "s|^\(root:[^:]*:0:0:[^:]*:[^:]*:\)/bin/sh$|\1/bin/zsh|" "$ROOT_PI/etc/passwd"
|
||||
for s in /bin/zsh /bin/mksh; do
|
||||
grep -qx "$s" "$ROOT_PI/etc/shells" 2>/dev/null || echo "$s" >> "$ROOT_PI/etc/shells"
|
||||
done'
|
||||
|
||||
# The repository goes in too, so the installed system can reinstall and
|
||||
# upgrade schos-base, and Alpine's are listed for everything else.
|
||||
mkdir -p "$ROOT/var/lib/schos"
|
||||
cp -a "$OUT/repo" "$ROOT/var/lib/schos/repo"
|
||||
{
|
||||
cat "$ROOT/etc/apk/repositories"
|
||||
echo "/var/lib/schos/repo"
|
||||
} > "$ROOT/etc/apk/repositories.new"
|
||||
mv "$ROOT/etc/apk/repositories.new" "$ROOT/etc/apk/repositories"
|
||||
|
||||
echo "mkaroot: $ROOT ($(du -sh "$ROOT" | cut -f1)), schos-base $VERSION"
|
||||
Loading…
Reference in a new issue