unos-repository

APK repository for unos
git clone git://git.finwo.net/misc/unos-repository
Log | Files | Refs | README

commit 40626fe3a9ed50ec119acc41f0965462b4868120
parent 42df5f9ef26eb60af7e1db352f615087ecdfe9c5
Author: finwo <finwo@pm.me>
Date:   Fri, 18 Sep 2026 17:12:36 +0200

More packages, moving towards self-installing

Diffstat:
Mmk/README.md | 71++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Amk/bootstrap-host.sh | 212+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amk/build-all.sh | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmk/build.sh | 36++++++++++++++++++++++++++++++++++++
Amk/deps.sh | 176+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmk/rootfs.sh | 4++--
Mpackages/README.md | 9+++++++++
Mpackages/apk-tools/template | 2++
Apackages/e2fsprogs/files/post-install | 6++++++
Apackages/e2fsprogs/template | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/efibootmgr/template | 1+
Mpackages/glibc/template | 1+
Apackages/gptfdisk/template | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/grub/template | 2++
Mpackages/iproute2/template | 2++
Apackages/libstdc++/files/post-install | 6++++++
Apackages/libstdc++/template | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/linux-longterm/template | 1+
Mpackages/linux-mainline/template | 1+
Mpackages/linux-stable/template | 1+
Mpackages/openssl/template | 1+
Apackages/smartmontools/template | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/unosd/template | 1+
23 files changed, 953 insertions(+), 13 deletions(-)

diff --git a/mk/README.md b/mk/README.md @@ -5,9 +5,22 @@ The whole of `build/` is generated output. Only `mk/` is source. ## Files -- `build.sh <pkg>` -- the pipeline: source template -> fetch+verify -> - extract -> patches -> `do_build`/`do_install` into `DESTDIR` -> emit signed - `.apk` into `build/repo/<arch>/`. +- `bootstrap-host.sh [--check|--force]` -- builds the host tools in + `build/host/bin` from pinned, checksummed tarballs: `muon` (bootstrapped from + a single amalgamated C file, so the chain terminates at a C compiler) and + `apk`. `--check` verifies presence *and version* and is the gate CI should + run first. Everything else assumes these exist. +- `deps.sh {list|vars|graph|order|check}` -- the build-dependency graph, read + from each template's `makedepends`. `order` is a topological sort; `check` + validates it (dangling deps, self-deps, cycles, missing hostmakedepends). +- `build-all.sh [--dry-run] [--skip-built] [pkg...]` -- build everything, or + one package and its closure, in `deps.sh order`. +- `build.sh <pkg>` -- the pipeline: source template -> check + `hostmakedepends`/`makedepends` -> fetch+verify -> extract -> patches -> + `do_build`/`do_install` into `DESTDIR` -> emit signed `.apk` into + `build/repo/<arch>/`. Dependency checks run before any fetching, so a missing + host tool is named up front rather than surfacing 200 lines into someone + else's generated parser. - `repo-index.sh [arch]` -- rebuild `APKINDEX.tar.gz` with host `apk index`, then sign it in place (abuild-sign layout: gzipped sig tar record prepended, RSA/SHA1 over the index bytes). @@ -84,9 +97,27 @@ index. Current keys (dev estate, rotate before any release): ## Host dependencies -sh, cc, curl or wget, tar, gzip, openssl, sha256sum, install, patch, -apk-tools (validation only: `apk verify`, `apk index`, `--root` installs). -`unshare` (user namespaces) for `chroot.sh`. +sh, cc, curl or wget, tar, gzip, openssl, sha256sum, install, patch. +`unshare` (user namespaces) for `chroot.sh` and `rootfs.sh`. +apk-tools and muon are **not** host prerequisites -- `bootstrap-host.sh` builds +them into `build/host/bin` from pinned sources. + +Per-package host tools (bison, flex, bc, perl, python3, ...) are declared as +`hostmakedepends` in the templates that need them and checked by `build.sh`, so +a CI image can be validated against the real requirement set with: + +```sh +./mk/deps.sh check +``` + +A clean build from nothing is therefore: + +```sh +./mk/bootstrap-host.sh # muon + apk into build/host/bin +./mk/build-all.sh # every package, in dependency order +./mk/repo-index.sh # signed APKINDEX +./mk/rootfs.sh --force # assemble rootfs/ +``` ## Rootfs (`rootfs.sh`, `chroot.sh`) @@ -109,11 +140,21 @@ fetched once into `build/work/sources/`. Built in-tree **with muon, never meson** (Python is banned from our tooling; upstream supports muon): - dynamic, prefix `build/host/` (`-Dlua=disabled -Dpython=disabled - -Dhelp=disabled`, rpath `$ORIGIN/../lib`): `build/host/bin/apk` for index - generation and validation; -- static (`build/host/bin/apk.static`): the future target binary - no - libcrypto needed on the switch. glibc-static NSS caveats apply in theory, - irrelevant to package ops. + -Dhelp=disabled -Ddocs=disabled`, rpath `$ORIGIN/../lib`): + `build/host/bin/apk`, used by `repo-index.sh` for index generation and by + `rootfs.sh` for assembly. + +There is deliberately **no `apk.static`**. An earlier revision built one and +justified it here as "the future target binary - no libcrypto needed on the +switch". That was already wrong: UNOS packages `openssl`, so `libcrypto.so.3` +and `libssl.so.3` are present on the switch and the target `apk` links them +dynamically. Its only consumer was `rootfs.sh`, which produces a byte-identical +rootfs with the dynamic binary. Building it cost 10.6MB against 2.0MB and +required `libcrypto.a`/`libssl.a`/`libz.a`/`libzstd.a` on the build host -- +static libraries that most CI containers do not ship, turning a cosmetic +preference into a hard bootstrap failure. If a static apk is ever wanted for +recovering a switch with a broken loader, it belongs in `packages/` as a target +artifact. Muon itself is pinned: v0.6.0, `https://github.com/muon-build/muon/archive/refs/tags/0.6.0.tar.gz`, @@ -127,3 +168,11 @@ v3.0.8, untrusted and fully trusted (signed index + seeded key). v3 needs `--usermode` for non-root `--root` installs. This becomes a real package template once the toolchain work lands; until then the pin + hash above are the record. + +**This is no longer prose to follow by hand** -- `mk/bootstrap-host.sh` is the +executable form of the above. `--check` verifies presence *and version* (a +stale binary from an older pin is worse than a missing one, because everything +downstream still appears to work), and `--force` rebuilds from the pinned +tarballs. muon's `meson-docs`/`meson-tests` subprojects are disabled because +its `setup` step otherwise git-clones them from GitHub mid-build, which has no +place in a bootstrap driven by checksummed tarballs. diff --git a/mk/bootstrap-host.sh b/mk/bootstrap-host.sh @@ -0,0 +1,212 @@ +#!/bin/sh +# mk/bootstrap-host.sh - build the host bootstrap tools into build/host/. +# +# ./mk/bootstrap-host.sh # build what is missing +# ./mk/bootstrap-host.sh --force # rebuild everything +# ./mk/bootstrap-host.sh --check # verify only, build nothing (CI gate) +# +# Produces: +# build/host/bin/muon pinned muon, bootstrapped from C (no Python) +# build/host/bin/apk dynamic apk v3, used by repo-index.sh/rootfs.sh + +# +# WHY THIS EXISTS +# --------------- +# These three binaries are the input to every other build: mk/build.sh puts +# build/host/bin on PATH, repo-index.sh generates APKINDEX with `apk index`, +# and rootfs.sh installs with `apk add`. Until now they were built by hand +# following prose in mk/README.md, which meant a fresh checkout or a CI +# container could not reproduce the toolchain at all. This script is that +# prose, executable. +# +# Python is banned from our tooling, so apk-tools is configured with muon +# rather than meson; upstream supports this. muon itself bootstraps from a +# single amalgamated C file, so the chain terminates at a C compiler. +set -eu + +HERE=$(cd "$(dirname "$0")" && pwd) +ROOT=$(cd "${HERE}/.." && pwd) +HOSTDIR="${ROOT}/build/host" +SRCDEST="${ROOT}/build/work/sources" +WORK="${ROOT}/build/work/hostbootstrap" + +# --- pins (keep in sync with mk/README.md "Host apk-tools v3") ------------- +MUON_VERSION=0.6.0 +MUON_URL="https://github.com/muon-build/muon/archive/refs/tags/${MUON_VERSION}.tar.gz" +MUON_TARBALL="muon-${MUON_VERSION}.tar.gz" +MUON_SHA256=5300e58c4b4d43e3026856004c79d746075aaa9d9e66d76ba9f32ce249495b81 + +APK_VERSION=3.0.8 +APK_URL="https://gitlab.alpinelinux.org/alpine/apk-tools/-/archive/v${APK_VERSION}/apk-tools-v${APK_VERSION}.tar.gz" +APK_TARBALL="apk-tools-v${APK_VERSION}.tar.gz" +APK_SHA256=e81c64a6e7c3806d45d4622c253e757aee970be84048554fa0eed79310e9453e + +FORCE=0 +CHECK=0 +for a in "$@"; do + case "$a" in + --force) FORCE=1 ;; + --check) CHECK=1 ;; + -h|--help) sed -n '2,12p' "$0"; exit 0 ;; + *) echo "bootstrap-host.sh: unknown argument: $a" >&2; exit 1 ;; + esac +done + +msg() { printf '==> %s\n' "$*"; } +die() { printf 'bootstrap-host.sh: error: %s\n' "$*" >&2; exit 1; } + +# Run a build step quietly, but dump the whole log if it fails. Build noise is +# useless until something breaks, at which point you want all of it. +LOGFILE="${WORK}/bootstrap.log" +run() { + # run <description> <cmd...> + what="$1"; shift + if ! "$@" >>"${LOGFILE}" 2>&1; then + echo "bootstrap-host.sh: ${what} failed; last 40 lines:" >&2 + tail -40 "${LOGFILE}" >&2 + exit 1 + fi +} + +fetch() { + # fetch <url> <tarball> <sha256> + f="${SRCDEST}/$2" + mkdir -p "${SRCDEST}" + if [ ! -f "${f}" ]; then + msg "fetching $2" + curl -fL -o "${f}" "$1" || wget -O "${f}" "$1" || die "could not fetch $1" + fi + echo "$3 ${f}" | sha256sum -c - >/dev/null || die "checksum mismatch: $2" +} + +# --- verification ---------------------------------------------------------- +# Version-checked, not just existence-checked: a stale binary from an older pin +# is worse than a missing one, because everything downstream still "works". +check_tools() { + rc=0 + if [ -x "${HOSTDIR}/bin/muon" ]; then + v=$("${HOSTDIR}/bin/muon" version 2>/dev/null | head -1 || true) + case "$v" in + *"${MUON_VERSION}"*) msg "muon ok (${v})" ;; + *) echo " muon present but not ${MUON_VERSION}: ${v:-unknown}" >&2; rc=1 ;; + esac + else + echo " muon missing" >&2; rc=1 + fi + + if [ -x "${HOSTDIR}/bin/apk" ]; then + v=$("${HOSTDIR}/bin/apk" --version 2>/dev/null | head -1 || true) + case "$v" in + *"${APK_VERSION}"*) msg "apk ok (${v})" ;; + *) echo " apk present but not ${APK_VERSION}: ${v:-unknown}" >&2; rc=1 ;; + esac + else + echo " apk missing" >&2; rc=1 + fi + return $rc +} + +if [ "${CHECK}" = "1" ]; then + if check_tools; then + msg "host tools present and pinned" + exit 0 + fi + die "host tools missing or stale; run ./mk/bootstrap-host.sh" +fi + +if [ "${FORCE}" != "1" ] && check_tools 2>/dev/null; then + msg "host tools already present (use --force to rebuild)" + exit 0 +fi + +command -v cc >/dev/null 2>&1 || command -v gcc >/dev/null 2>&1 \ + || die "no C compiler on PATH" + +mkdir -p "${HOSTDIR}/bin" "${WORK}" +rm -f "${LOGFILE}" + +# --- muon ------------------------------------------------------------------ +# bootstrap.sh compiles src/amalgam.c with a plain c99 compiler, giving a +# muon capable of building the real muon. Pure C, no Python anywhere. +fetch "${MUON_URL}" "${MUON_TARBALL}" "${MUON_SHA256}" + +msg "building muon ${MUON_VERSION}" +rm -rf "${WORK}/muon" +mkdir -p "${WORK}/muon" +tar -xzf "${SRCDEST}/${MUON_TARBALL}" -C "${WORK}/muon" +MUONSRC="${WORK}/muon/muon-${MUON_VERSION}" +[ -d "${MUONSRC}" ] || die "unexpected muon tarball layout" + +# meson-docs and meson-tests are wrap subprojects that muon git-clones from +# GitHub during setup. They are documentation and upstream's own test corpus; +# we need neither, and an unpinned network fetch has no business in a bootstrap +# that is otherwise driven entirely by pinned, checksummed tarballs. tracy is a +# profiler. Disabling all three makes this step hermetic. +MUON_OPTS="-Dmeson-docs=disabled -Dmeson-tests=disabled -Dtracy=disabled -Dui=disabled" + +cd "${MUONSRC}" +run "muon bootstrap" sh bootstrap.sh build-boot +[ -x build-boot/muon-bootstrap ] || die "muon bootstrap produced no binary" + +# Build the real muon with the bootstrap one. Samurai is vendored in muon, so +# no ninja on the host is required either. +# shellcheck disable=SC2086 +run "muon setup" ./build-boot/muon-bootstrap setup ${MUON_OPTS} build +run "muon build" ./build-boot/muon-bootstrap -C build samu +[ -x build/muon ] || die "muon self-build produced no binary" +install -m 0755 build/muon "${HOSTDIR}/bin/muon" +cd "${ROOT}" +msg "muon -> ${HOSTDIR}/bin/muon" + +PATH="${HOSTDIR}/bin:${PATH}" +export PATH + +# --- apk-tools ------------------------------------------------------------- +fetch "${APK_URL}" "${APK_TARBALL}" "${APK_SHA256}" + +rm -rf "${WORK}/apk" +mkdir -p "${WORK}/apk" +tar -xzf "${SRCDEST}/${APK_TARBALL}" -C "${WORK}/apk" +APKSRC="${WORK}/apk/apk-tools-v${APK_VERSION}" +[ -d "${APKSRC}" ] || APKSRC="${WORK}/apk/$(ls -A "${WORK}/apk" | head -1)" +[ -d "${APKSRC}" ] || die "unexpected apk-tools tarball layout" + +# lua/python/docs off: lua is a binding we do not use, python is banned, and +# docs need scdoc. help needs lua, so it goes too. +APK_OPTS="-Dlua=disabled -Dpython=disabled -Dhelp=disabled -Ddocs=disabled" + +msg "building apk ${APK_VERSION} (dynamic)" +cd "${APKSRC}" +rm -rf build-dyn +# RUNPATH $ORIGIN/../lib so the binary finds any libs we later place in +# build/host/lib without needing LD_LIBRARY_PATH at every call site. +# shellcheck disable=SC2086 +run "apk setup (dynamic)" muon setup \ + -Dprefix="${HOSTDIR}" \ + ${APK_OPTS} \ + -Dc_link_args="-Wl,-rpath,\$ORIGIN/../lib" \ + build-dyn +run "apk build (dynamic)" muon -C build-dyn samu +run "apk install (dynamic)" muon -C build-dyn install +[ -x "${HOSTDIR}/bin/apk" ] || die "dynamic apk not installed" +msg "apk -> ${HOSTDIR}/bin/apk" + +# NOTE: we deliberately do NOT build an apk.static. +# +# There used to be one, justified in mk/README.md as "the future target binary +# - no libcrypto needed on the switch". That justification was already false: +# UNOS packages openssl, so libcrypto.so.3/libssl.so.3 are on the switch and +# the target apk links them dynamically like everything else. Its only real +# consumer was mk/rootfs.sh, which works identically with the dynamic binary +# (verified: same 24 packages, same rootfs). +# +# Building it cost 10.6MB versus 2.0MB and, worse, required libcrypto.a, +# libssl.a, libz.a and libzstd.a on the build host -- static variants that many +# distributions and most CI containers do not ship at all. That is a hard +# bootstrap failure in exchange for nothing. +# +# If a statically linked apk is ever wanted for disaster recovery on a switch +# with a broken loader, it belongs in packages/ as a target artifact, not here. + +check_tools || die "post-build verification failed" +msg "host bootstrap complete" diff --git a/mk/build-all.sh b/mk/build-all.sh @@ -0,0 +1,64 @@ +#!/bin/sh +# mk/build-all.sh - build packages in dependency order. +# +# ./mk/build-all.sh every package, correctly ordered +# ./mk/build-all.sh gptfdisk that package and everything it needs +# ./mk/build-all.sh --skip-built don't rebuild packages already in the repo +# ./mk/build-all.sh --dry-run print the order, build nothing +# +# Order comes from mk/deps.sh, which derives it from each template's +# `makedepends`. Before this existed, build order lived only in the head of +# whoever was driving the build, which is precisely what a CI runner does not +# have. +set -eu + +HERE=$(cd "$(dirname "$0")" && pwd) +ROOT=$(cd "${HERE}/.." && pwd) + +DRY=0 +SKIP=0 +PKGS="" +for a in "$@"; do + case "$a" in + --dry-run) DRY=1 ;; + --skip-built) SKIP=1 ;; + -h|--help) sed -n '2,10p' "$0"; exit 0 ;; + -*) echo "build-all.sh: unknown option: $a" >&2; exit 1 ;; + *) PKGS="${PKGS} $a" ;; + esac +done + +msg() { printf '==> %s\n' "$*"; } +die() { printf 'build-all.sh: error: %s\n' "$*" >&2; exit 1; } + +# Host tools are the root of the dependency graph: without them nothing builds. +"${ROOT}/mk/bootstrap-host.sh" --check >/dev/null 2>&1 \ + || die "host tools missing or stale; run ./mk/bootstrap-host.sh" + +# shellcheck disable=SC2086 +ORDER=$("${ROOT}/mk/deps.sh" order ${PKGS}) || die "could not resolve build order" + +if [ "${DRY}" = "1" ]; then + printf '%s\n' "${ORDER}" + exit 0 +fi + +total=$(printf '%s\n' "${ORDER}" | wc -l | tr -d ' ') +n=0 +built=0 +skipped=0 + +for p in ${ORDER}; do + n=$((n + 1)) + if [ "${SKIP}" = "1" ] && ls "${ROOT}/build/repo/x86_64/${p}"-*.apk >/dev/null 2>&1; then + msg "[${n}/${total}] ${p}: already built, skipping" + skipped=$((skipped + 1)) + continue + fi + msg "[${n}/${total}] ${p}" + "${ROOT}/mk/build.sh" "${p}" || die "${p} failed" + built=$((built + 1)) +done + +msg "built ${built}, skipped ${skipped}, of ${total}" +msg "now run ./mk/repo-index.sh to refresh APKINDEX" diff --git a/mk/build.sh b/mk/build.sh @@ -27,6 +27,13 @@ homepage= distfiles= checksum= depends= +# makedepends: other UNOS packages whose headers/libs must be in build/sysroot +# before this one compiles. Determines build order (see mk/deps.sh). +# hostmakedepends: commands that must exist on the build host. Checked, never +# built by us. +# Both are declared in packages/README.md and were, until now, read by nothing. +makedepends= +hostmakedepends= provides= replaces= subpackages= @@ -71,6 +78,35 @@ ARCH="x86_64" OUTDIR="${ROOT}/build/repo/${ARCH}" mkdir -p "${SRCDEST}" "${WORK}" "${OUTDIR}" "${SYSROOT}" + +# Host-built tools (muon) must be on PATH before hostmakedepends is checked, +# otherwise a package declaring `muon` fails the check despite muon being +# present in build/host/bin. Exported again below with PKG_CONFIG_PATH; doing +# it here as well is harmless and keeps the check honest. +export PATH="${ROOT}/build/host/bin:${PATH}" + +# --- build dependencies --------------------------------------------------- +# Checked before anything is fetched or compiled. A missing bison surfacing as +# a syntax error 200 lines into someone else's generated parser is the kind of +# failure that costs an afternoon; naming it up front costs nothing. +for _h in ${hostmakedepends}; do + command -v "${_h}" >/dev/null 2>&1 \ + || die "hostmakedepends: '${_h}' not found on PATH (required to build ${pkgname})" +done + +for _m in ${makedepends}; do + [ -f "${ROOT}/packages/${_m}/template" ] \ + || die "makedepends: '${_m}' is not a package under packages/" + # Presence of a built .apk is the signal that its sysroot seed has been + # installed; templates that other packages build against install twice + # (DESTDIR + SYSROOT), so one implies the other. + if ! ls "${OUTDIR}/${_m}"-*.apk >/dev/null 2>&1; then + die "makedepends '${_m}' has not been built. + Build it first: ./mk/build.sh ${_m} + Or build in order: ./mk/build-all.sh ${pkgname}" + fi +done + # fresh staging every build: templates must be idempotent AND ghost-free rm -rf "${DESTDIR}" mkdir -p "${DESTDIR}" diff --git a/mk/deps.sh b/mk/deps.sh @@ -0,0 +1,176 @@ +#!/bin/sh +# mk/deps.sh - the build-dependency graph. +# +# ./mk/deps.sh list every package name +# ./mk/deps.sh vars <pkg> that package's declared dependencies +# ./mk/deps.sh order [pkg...] topological build order (all if no args) +# ./mk/deps.sh graph machine-readable "pkg<TAB>dep" edges +# ./mk/deps.sh check validate the whole graph, exit 1 if bad +# +# WHY THIS EXISTS +# --------------- +# packages/README.md has always documented hostmakedepends/makedepends, but +# nothing read them and no template set them, so build order lived only in +# whoever's head was driving the build. That is fine until CI, a fresh +# checkout, or a second person needs to build from nothing. +# +# Three kinds of dependency, deliberately distinct: +# +# depends runtime. Recorded in the .apk, resolved by apk on the +# switch. Does NOT affect build order. +# makedepends other UNOS packages whose headers/libs must be in +# build/sysroot before this one compiles. This is what +# determines build order. +# hostmakedepends commands that must exist on the build host (bison, +# flex, python3 for glibc...). Checked, never built. +# +# Templates are shell fragments, so reading them means sourcing them. That is +# exactly what mk/build.sh does; here it happens in a subshell that only ever +# reads variables, and the do_* functions are defined but never called. +set -eu + +HERE=$(cd "$(dirname "$0")" && pwd) +ROOT=$(cd "${HERE}/.." && pwd) +PKGDIR="${ROOT}/packages" + +die() { printf 'deps.sh: error: %s\n' "$*" >&2; exit 1; } + +# Same PATH that mk/build.sh gives templates, so hostmakedepends like `muon` +# resolve from build/host/bin rather than appearing absent. +PATH="${ROOT}/build/host/bin:${PATH}" +export PATH + +list_pkgs() { + for d in "${PKGDIR}"/*/; do + [ -f "${d}template" ] || continue + basename "${d}" + done | sort +} + +# Print "depends|makedepends|hostmakedepends" for one package. +# Runs in a subshell: template assignments cannot leak into the caller. +read_vars() { + pkg="$1" + t="${PKGDIR}/${pkg}/template" + [ -f "${t}" ] || die "no such package: ${pkg}" + ( + depends= + makedepends= + hostmakedepends= + subpackages= + # Templates reference these; define them so sourcing cannot fail on an + # unset variable under `set -u`. + SYSROOT=; DESTDIR=; WRKSRC=; FILESDIR=; WORK=; SRCDEST=; UNOS_MKDIR= + die() { :; } + msg() { :; } + # shellcheck disable=SC1090 + . "${t}" >/dev/null 2>&1 || true + printf '%s|%s|%s\n' "${depends}" "${makedepends}" "${hostmakedepends}" + ) +} + +field() { read_vars "$1" | cut -d'|' -f"$2"; } + +cmd_vars() { + pkg="${1:?usage: deps.sh vars <pkg>}" + v=$(read_vars "${pkg}") + printf 'package: %s\n' "${pkg}" + printf 'depends: %s\n' "$(printf '%s' "${v}" | cut -d'|' -f1)" + printf 'makedepends: %s\n' "$(printf '%s' "${v}" | cut -d'|' -f2)" + printf 'hostmakedepends: %s\n' "$(printf '%s' "${v}" | cut -d'|' -f3)" +} + +cmd_graph() { + for p in $(list_pkgs); do + for d in $(field "${p}" 2); do + printf '%s\t%s\n' "${p}" "${d}" + done + done +} + +# Depth-first topological sort. State lives in a temp dir because POSIX sh has +# no associative arrays: <tmp>/seen.<pkg> marks done, <tmp>/path.<pkg> marks +# "currently on the recursion stack", which is how cycles are detected. +TMP= +cleanup() { [ -n "${TMP}" ] && rm -rf "${TMP}"; } +trap cleanup EXIT + +# NOTE: everything here uses "$1" rather than a named variable. POSIX sh has no +# function-local variables, so a recursive function that assigns `pkg="$1"` +# has that assignment clobbered by its own recursive calls -- on return it +# would mark and emit the last child instead of itself, producing duplicates +# and silently dropping every package that has dependencies. Positional +# parameters, by contrast, are saved and restored per invocation. +visit() { + [ -e "${TMP}/seen.$1" ] && return 0 + if [ -e "${TMP}/path.$1" ]; then + die "dependency cycle involving '$1'" + fi + [ -f "${PKGDIR}/$1/template" ] || die "'$1' is a makedepends of something but has no template" + : > "${TMP}/path.$1" + for d in $(field "$1" 2); do + visit "${d}" + done + rm -f "${TMP}/path.$1" + : > "${TMP}/seen.$1" + echo "$1" >> "${TMP}/order" +} + +cmd_order() { + TMP=$(mktemp -d /tmp/unos-deps.XXXXXX) + : > "${TMP}/order" + if [ $# -gt 0 ]; then + for p in "$@"; do visit "${p}"; done + else + for p in $(list_pkgs); do visit "${p}"; done + fi + cat "${TMP}/order" +} + +cmd_check() { + rc=0 + TMP=$(mktemp -d /tmp/unos-deps.XXXXXX) + : > "${TMP}/order" + + for p in $(list_pkgs); do + # every makedepends must name a real package + for d in $(field "${p}" 2); do + if [ ! -f "${PKGDIR}/${d}/template" ]; then + echo "FAIL: ${p}: makedepends '${d}' is not a package" >&2 + rc=1 + fi + if [ "${d}" = "${p}" ]; then + echo "FAIL: ${p}: depends on itself" >&2 + rc=1 + fi + done + # hostmakedepends must be resolvable on this host + for h in $(field "${p}" 3); do + command -v "${h}" >/dev/null 2>&1 || { + echo "FAIL: ${p}: hostmakedepends '${h}' not found on PATH" >&2 + rc=1 + } + done + done + + # cycle detection, via the same DFS the ordering uses + if [ "${rc}" = "0" ]; then + for p in $(list_pkgs); do visit "${p}"; done >/dev/null 2>&1 || { + echo "FAIL: dependency graph has a cycle" >&2 + for p in $(list_pkgs); do visit "${p}" >/dev/null; done + rc=1 + } + fi + + [ "${rc}" = "0" ] && echo "deps.sh: graph OK ($(list_pkgs | wc -l | tr -d ' ') packages)" + return $rc +} + +case "${1:-}" in + list) list_pkgs ;; + vars) shift; cmd_vars "$@" ;; + graph) cmd_graph ;; + order) shift; cmd_order "$@" ;; + check) cmd_check ;; + *) sed -n '2,10p' "$0"; exit 1 ;; +esac diff --git a/mk/rootfs.sh b/mk/rootfs.sh @@ -13,7 +13,7 @@ set -eu HERE=$(cd "$(dirname "$0")" && pwd) ROOT=$(cd "${HERE}/.." && pwd) ROOTFS="${ROOT}/rootfs" -APK="${ROOT}/build/host/bin/apk.static" +APK="${ROOT}/build/host/bin/apk" CACHE="${ROOT}/build/work/apk-cache" REPO="file://${ROOT}/build/repo" @@ -47,7 +47,7 @@ apk_ns "${APK}" --root "${ROOTFS}" --initdb \ echo "==> UNOS system (fully trusted from here on)" apk_ns "${APK}" --root "${ROOTFS}" \ --cache-dir "${CACHE}" --repository "${REPO}" \ - add base-files glibc busybox unos-firstboot tinyssh libmnl zlib openssl iproute2 apk-tools linux-longterm grub popt efivar efibootmgr unosd + add base-files glibc busybox unos-firstboot tinyssh libmnl zlib openssl iproute2 apk-tools linux-longterm grub popt efivar efibootmgr unosd libstdc++ e2fsprogs gptfdisk smartmontools # Deterministic loader cache: package post-installs refresh it during the # transaction, but assembly must not depend on script-execution order. diff --git a/packages/README.md b/packages/README.md @@ -35,6 +35,15 @@ directory is ever built by hand on a target switch. ## Rules +- **Declare build dependencies.** `makedepends` lists other UNOS packages whose + headers/libs must be in `build/sysroot` before this one compiles; + `hostmakedepends` lists commands that must exist on the build host. Both are + enforced by `mk/build.sh` before anything is fetched, and `makedepends` is + what `mk/deps.sh` uses to compute build order. Keep them accurate in both + directions: a missing entry breaks a clean-checkout build, and a spurious one + fails a build that would otherwise work. `depends` is runtime only and does + **not** affect build order, so a package needed at both times appears twice + (see `gptfdisk`). - **Pin everything.** Explicit versions and recorded checksums. No moving targets, no floating branches. - **Never vendor upstream source into this repository.** Sources are fetched at diff --git a/packages/apk-tools/template b/packages/apk-tools/template @@ -9,6 +9,8 @@ homepage="https://gitlab.alpinelinux.org/alpine/apk-tools" distfiles="https://gitlab.alpinelinux.org/alpine/apk-tools/-/archive/v3.0.8/apk-tools-v3.0.8.tar.gz" checksum=e81c64a6e7c3806d45d4622c253e757aee970be84048554fa0eed79310e9453e depends="zlib openssl" +makedepends="zlib openssl" +hostmakedepends="muon" # Notes: # - Same version the host manager was built from (v3.0.8); target and host diff --git a/packages/e2fsprogs/files/post-install b/packages/e2fsprogs/files/post-install @@ -0,0 +1,6 @@ +#!/bin/sh +# e2fsprogs post-install: refresh the loader cache so the new .so resolves. +# Every UNOS library package carries this (apk runs scripts per package at +# install time; a single glibc-side run would be order-dependent). Absolute +# path on purpose (see glibc post-install). +exec /sbin/ldconfig diff --git a/packages/e2fsprogs/template b/packages/e2fsprogs/template @@ -0,0 +1,109 @@ +# Template file for 'e2fsprogs' +pkgname=e2fsprogs +version=1.47.4 +revision=0 +short_desc="ext2/3/4 filesystem utilities and libraries" +maintainer="finwo <finwo@pm.me>" +license="GPL-2.0-only, LGPL-2.0-only, BSD-3-Clause, MIT" +homepage="https://e2fsprogs.sourceforge.net/" +distfiles="https://www.kernel.org/pub/linux/kernel/people/tytso/e2fsprogs/v${version}/e2fsprogs-${version}.tar.xz" +checksum=fd5bf388cbdbe006a3d3b318d983b2948382440acc85a87f1e7d108653e8db0b +depends="glibc busybox" + +# Notes: +# - Why this exists: without e2fsck a switch cannot repair its own root after +# an unclean shutdown, which is not a recoverable position. mke2fs is also +# what installer/install.sh formats the root partition with, so shipping this +# is what lets a UNOS image install UNOS instead of borrowing host binaries. +# - libuuid/libblkid: upstream defaults these OFF, expecting util-linux to +# provide them. We do not package util-linux, and gptfdisk needs libuuid, so +# we build e2fsprogs' own copies and they become the canonical providers. +# - --with-root-prefix=/usr keeps everything under /usr/{sbin,lib} rather than +# the historical /sbin + /lib split, matching glibc's --libdir=/usr/lib. +# - Busybox already provides a cut-down blkid/findfs; those applets stay as +# they are. The real blkid here ships as a library, not as a second binary, +# to avoid an apk file conflict (apk refuses cross-package overwrites). + +do_configure() { + cd "${WRKSRC}" + # --sysconfdir=/etc is load-bearing: with a bare --prefix=/usr, autotools + # puts mke2fs.conf in /usr/etc, but mke2fs only ever reads /etc/mke2fs.conf. + # It would silently fall back to built-in defaults and ignore our config. + ./configure \ + --prefix=/usr \ + --with-root-prefix=/usr \ + --sysconfdir=/etc \ + --enable-elf-shlibs \ + --disable-static \ + --enable-libuuid \ + --enable-libblkid \ + --disable-uuidd \ + --disable-fsck \ + --disable-debugfs \ + --disable-nls \ + --disable-rpath \ + --disable-testio-debug +} + +do_build() { + cd "${WRKSRC}" + make -j"$(nproc)" +} + +do_install() { + cd "${WRKSRC}" + make install DESTDIR="${DESTDIR}" + make install-libs DESTDIR="${DESTDIR}" + + # Sysroot seed: gptfdisk and smartmontools build against libuuid. + make install DESTDIR="${SYSROOT}" + make install-libs DESTDIR="${SYSROOT}" + + # Shared-only (packages/README.md): no .a, no libtool turds. + rm -f "${DESTDIR}"/usr/lib/*.a "${DESTDIR}"/usr/lib/*.la + rm -f "${SYSROOT}"/usr/lib/*.a "${SYSROOT}"/usr/lib/*.la + + # `blkid` and `findfs` would collide with the busybox applets of the same + # name; apk refuses cross-package file overwrites, so drop ours. + rm -f "${DESTDIR}"/usr/sbin/blkid "${DESTDIR}"/usr/sbin/findfs + rm -f "${DESTDIR}"/usr/share/man/man8/blkid.8 \ + "${DESTDIR}"/usr/share/man/man8/findfs.8 + + # No docs/locale in the image. + rm -rf "${DESTDIR}/usr/share/info" "${DESTDIR}/usr/share/locale" + + # e2scrub drives online fsck of LVM snapshots via cron and udev. UNOS has + # none of those three, so the scripts could only ever fail confusingly. + rm -f "${DESTDIR}"/usr/sbin/e2scrub "${DESTDIR}"/usr/sbin/e2scrub_all + rm -f "${DESTDIR}"/etc/e2scrub.conf + rm -rf "${DESTDIR}"/etc/cron.d "${DESTDIR}"/usr/libexec + rm -f "${DESTDIR}"/usr/share/man/man8/e2scrub.8 \ + "${DESTDIR}"/usr/share/man/man8/e2scrub_all.8 \ + "${DESTDIR}"/usr/share/man/man5/e2scrub.conf.5 + # udev rules: no udev (devtmpfs + mdev territory), and upstream hardcodes + # a lib64 path that does not match our /usr/lib layout either way. + rm -rf "${DESTDIR}"/usr/lib64 "${DESTDIR}"/usr/lib/udev + # initrd helper: UNOS boots without an initrd by design (see grub.cfg.in). + rm -f "${DESTDIR}"/usr/lib/e2initrd_helper + + # compile_et/mk_cmds are build-time code generators for downstream + # consumers; they belong in the sysroot, not on a switch. + rm -f "${DESTDIR}"/usr/bin/compile_et "${DESTDIR}"/usr/bin/mk_cmds + rm -rf "${DESTDIR}"/usr/share/et "${DESTDIR}"/usr/share/ss + rm -f "${DESTDIR}"/usr/share/man/man1/compile_et.1 \ + "${DESTDIR}"/usr/share/man/man1/mk_cmds.1 + + # The whole point of the package: assert the tools the installer needs. + for t in mke2fs e2fsck resize2fs tune2fs dumpe2fs; do + [ -x "${DESTDIR}/usr/sbin/${t}" ] || die "e2fsprogs: missing ${t}" + done + # mkfs.ext4 is a symlink to mke2fs; installer/install.sh calls it by name. + [ -e "${DESTDIR}/usr/sbin/mkfs.ext4" ] || die "e2fsprogs: no mkfs.ext4" + [ -e "${DESTDIR}/usr/sbin/fsck.ext4" ] || die "e2fsprogs: no fsck.ext4" + [ -f "${SYSROOT}/usr/include/uuid/uuid.h" ] || die "e2fsprogs: no sysroot uuid.h" + [ -f "${SYSROOT}/usr/lib/pkgconfig/uuid.pc" ] || die "e2fsprogs: no sysroot uuid.pc" + # mke2fs silently uses built-in defaults if this is not where it looks. + [ -f "${DESTDIR}/etc/mke2fs.conf" ] || die "e2fsprogs: mke2fs.conf not in /etc" + [ ! -d "${DESTDIR}/usr/etc" ] || die "e2fsprogs: stray /usr/etc" + [ ! -d "${DESTDIR}/usr/lib64" ] || die "e2fsprogs: stray /usr/lib64" +} diff --git a/packages/efibootmgr/template b/packages/efibootmgr/template @@ -10,6 +10,7 @@ distfiles="https://github.com/rhboot/efibootmgr/archive/refs/tags/18.tar.gz" checksum=442867d12f8525034a404fc8af3036dba8e1fc970998af2486c3b940dfad0874 # runtime links (readelf-proven at build): libefivar + libpopt. depends="efivar popt" +makedepends="efivar popt" # Notes: # - No configure step; lowercase make vars (prefix/libdir/sbindir) plus diff --git a/packages/glibc/template b/packages/glibc/template @@ -11,6 +11,7 @@ checksum=37f600f2bef3c5e8300147059568b2a2e40a7ad6ccc65ce942556d49429cc667 # runtime needs from base-files: /etc/nsswitch.conf, /etc/ld.so.conf. # busybox first: the post-install runs under /bin/sh (see libmnl). depends="base-files busybox" +hostmakedepends="python3 bison gawk sed make" # Notes: # - glibc forbids in-source builds; configure runs in a sibling build dir. diff --git a/packages/gptfdisk/template b/packages/gptfdisk/template @@ -0,0 +1,52 @@ +# Template file for 'gptfdisk' +pkgname=gptfdisk +version=1.0.10 +revision=0 +short_desc="GPT partitioning tools (sgdisk)" +maintainer="finwo <finwo@pm.me>" +license="GPL-2.0-only" +homepage="https://www.rodsbooks.com/gdisk/" +distfiles="https://downloads.sourceforge.net/project/gptfdisk/gptfdisk/${version}/gptfdisk-${version}.tar.gz" +checksum=2abed61bc6d2b9ec498973c0440b8b804b7a72d7144069b5a9209b2ad693a282 +# libstdc++ is a real runtime dep: this is the first C++ package in UNOS. +depends="glibc busybox libstdc++ e2fsprogs popt" +makedepends="e2fsprogs popt libstdc++" + +# Notes: +# - Why: installer/install.sh partitions with sgdisk. ONIE provides it on a +# switch, but without it here a UNOS image cannot install UNOS -- which is +# what makes the plain qemu image usable as a rescue/installer environment. +# - C++: linked against our packaged libstdc++ (see that template for why we +# build the runtime from GCC source rather than static-linking it). +# - libuuid comes from e2fsprogs (upstream e2fsprogs defaults it off; our +# template enables it precisely because this package needs it). +# - Upstream ships a plain Makefile, no configure. It also builds gdisk and +# cgdisk; cgdisk needs ncurses, which UNOS does not package, so only the +# non-curses tools are built and only sgdisk is shipped. + +do_build() { + cd "${WRKSRC}" + # Build against the sysroot so we link our glibc/libuuid/libstdc++ rather + # than the host's. gdisk/sgdisk/fixparts only; cgdisk needs ncurses. + make -j"$(nproc)" \ + CXX="g++" \ + CXXFLAGS="-O2 -D_FILE_OFFSET_BITS=64 -I${SYSROOT}/usr/include" \ + LDFLAGS="-L${SYSROOT}/usr/lib -Wl,-rpath-link,${SYSROOT}/usr/lib" \ + sgdisk +} + +do_install() { + cd "${WRKSRC}" + vmkdir usr/sbin + vinstall sgdisk 0755 usr/sbin + vmkdir usr/share/man/man8 + [ -f sgdisk.8 ] && vinstall sgdisk.8 0644 usr/share/man/man8 + + [ -x "${DESTDIR}/usr/sbin/sgdisk" ] || die "gptfdisk: no sgdisk" + # Assert we picked up our own C++ runtime, not a static copy and not the + # host's: a static libstdc++ would silently violate the shared-only rule. + readelf -d "${DESTDIR}/usr/sbin/sgdisk" | grep -q 'libstdc++\.so\.6' \ + || die "gptfdisk: sgdisk does not link libstdc++.so.6 (static C++ runtime?)" + readelf -d "${DESTDIR}/usr/sbin/sgdisk" | grep -q 'libuuid\.so\.1' \ + || die "gptfdisk: sgdisk does not link libuuid.so.1" +} diff --git a/packages/grub/template b/packages/grub/template @@ -11,6 +11,8 @@ distfiles="https://ftp.gnu.org/gnu/grub/grub-2.14.tar.xz" checksum=bc8d3c73535b8838d8c8e2654d73edc4e6ae8c8acdb45d5df5dc9a1547446d43 # xz: grub-install links liblzma (readelf-proven, not guessed). depends="xz" +makedepends="xz" +hostmakedepends="bison flex" # Notes: # - x86_64-efi ONLY in v1 (not i386-pc): grub 2.14's pc kernel.img links diff --git a/packages/iproute2/template b/packages/iproute2/template @@ -9,6 +9,8 @@ homepage="https://www.kernel.org/pub/linux/utils/net/iproute2/" distfiles="https://www.kernel.org/pub/linux/utils/net/iproute2/iproute2-6.19.0.tar.xz" checksum=9332213d35480b647086a70c302de8568de83455a98774d35de216c4ce191006 depends="libmnl libcap" +makedepends="libmnl libcap" +hostmakedepends="bison flex" # Notes: # - `ip` + `rtmon` + `ss` ONLY (PLAN section 10): the top-level diff --git a/packages/libstdc++/files/post-install b/packages/libstdc++/files/post-install @@ -0,0 +1,6 @@ +#!/bin/sh +# libstdc++ post-install: refresh the loader cache so the new .so resolves. +# Every UNOS library package carries this (apk runs scripts per package at +# install time; a single glibc-side run would be order-dependent). Absolute +# path on purpose (see glibc post-install). +exec /sbin/ldconfig diff --git a/packages/libstdc++/template b/packages/libstdc++/template @@ -0,0 +1,143 @@ +# Template file for 'libstdc++' +pkgname=libstdc++ +version=14.2.0 +revision=0 +short_desc="GNU Standard C++ library and GCC unwinder runtime" +maintainer="finwo <finwo@pm.me>" +license="GPL-3.0-or-later WITH GCC-exception-3.1" +homepage="https://gcc.gnu.org/" +# GCC plus its pinned in-tree prerequisites. Only the first distfile is +# extracted by the driver; do_configure places the rest (see below). +# Prerequisite versions are GCC 14.2.0's own pins, taken verbatim from +# contrib/download_prerequisites -- not chosen by us. +distfiles="https://ftp.gnu.org/gnu/gcc/gcc-${version}/gcc-${version}.tar.xz + https://gcc.gnu.org/pub/gcc/infrastructure/gmp-6.2.1.tar.bz2 + https://gcc.gnu.org/pub/gcc/infrastructure/mpfr-4.1.0.tar.bz2 + https://gcc.gnu.org/pub/gcc/infrastructure/mpc-1.2.1.tar.gz" +checksum="a7b39bc69cbf9e25826c5a60ab26477001f7c08d85cec04bc0e29cabed6f3cc9 + eae9326beb4158c386e39a356818031bd28f3124cf915f8c5b1dc4c7a36b4d7c + feced2d430dd5a97805fa289fed3fc8ff2b094c02d05287fd6133e7f1f0ec926 + 17503d2c395dfcf106b622dc142683c1199431d095367c6aacba6eec30340459" +depends="glibc busybox" + +# Notes: +# - Why: sgdisk (gptfdisk) and smartctl (smartmontools) are C++, and UNOS +# shipped no C++ runtime at all. Static-linking the runtime into each binary +# was the alternative; packaging it keeps packages/README.md's shared-only +# rule intact and amortises across any future C++ package. +# - We build GCC, but we ship only libstdc++.so and libgcc_s.so. No compiler, +# no headers, no libsupc++.a. GCC is a means to the runtime here, nothing +# more; do_install takes exactly two .so trees out of the tree. +# - ABI: host g++ 14.2.1 emits at most GLIBCXX_3.4.33, which GCC 14.2.0's +# libstdc++ provides. Bumping this package below the host compiler version +# would break C++ packages at link time -- the assert in do_install guards it. +# - --disable-bootstrap: a 3-stage bootstrap builds the compiler three times to +# prove it can rebuild itself. We are not shipping the compiler, so stage1 is +# sufficient and roughly a third of the build time. +# - gmp/mpfr/mpc are build-time only and never packaged. GCC builds them +# in-tree when their source dirs are present, which is the upstream-supported +# way and avoids three packages we would otherwise never install. + +do_configure() { + cd "${WRKSRC}" + + # In-tree prerequisites: GCC's build system picks these up automatically + # when unpacked under the source root with their version suffix stripped. + for dep in gmp-6.2.1 mpfr-4.1.0 mpc-1.2.1; do + name=${dep%%-*} + [ -d "${name}" ] && continue + case "${dep}" in + *.gz|mpc-*) tar -xf "${SRCDEST}/${dep}.tar.gz" -C . ;; + *) tar -xf "${SRCDEST}/${dep}.tar.bz2" -C . ;; + esac + mv "${dep}" "${name}" || die "libstdc++: could not stage ${name}" + done + for name in gmp mpfr mpc; do + [ -d "${name}" ] || die "libstdc++: missing in-tree ${name}" + done + + # Target libraries ignore --libdir: their location comes from the multilib + # osdir, which on x86_64 defaults to ../lib64 even with --disable-multilib. + # UNOS is /usr/lib throughout (glibc is built --libdir=/usr/lib), so + # retarget m64 the way distros do rather than shipping a stray /usr/lib64. + sed -i 's|m64=../lib64|m64=../lib|' gcc/config/i386/t-linux64 \ + || die "libstdc++: could not retarget MULTILIB_OSDIRNAMES" + grep -q 'm64=../lib$' gcc/config/i386/t-linux64 \ + || grep -q 'm64=../lib[^6]' gcc/config/i386/t-linux64 \ + || die "libstdc++: MULTILIB_OSDIRNAMES retarget did not apply" + + # GCC refuses to configure in its own source directory. + mkdir -p "${WORK}/gccbuild" + cd "${WORK}/gccbuild" + "${WRKSRC}/configure" \ + --prefix=/usr \ + --libdir=/usr/lib \ + --disable-bootstrap \ + --enable-languages=c++ \ + --enable-shared \ + --enable-threads=posix \ + --enable-__cxa_atexit \ + --enable-clocale=gnu \ + --disable-multilib \ + --disable-nls \ + --disable-libstdcxx-pch \ + --disable-werror \ + --disable-libsanitizer \ + --disable-libssp \ + --disable-libquadmath \ + --disable-libvtv +} + +do_build() { + cd "${WORK}/gccbuild" + # Only the two target runtimes. Building `all` would produce a full + # compiler we then throw away. + make -j"$(nproc)" all-target-libgcc + make -j"$(nproc)" all-target-libstdc++-v3 +} + +do_install() { + cd "${WORK}/gccbuild" + make install-target-libgcc DESTDIR="${DESTDIR}" + make install-target-libstdc++-v3 DESTDIR="${DESTDIR}" + + # Sysroot seed so gptfdisk/smartmontools link against our runtime. + make install-target-libgcc DESTDIR="${SYSROOT}" + make install-target-libstdc++-v3 DESTDIR="${SYSROOT}" + + # Ship the runtime only. Everything below is compiler-side: static + # archives, linker scripts, headers and GCC's private lib tree. + rm -rf "${DESTDIR}/usr/lib/gcc" "${DESTDIR}/usr/include" + rm -rf "${DESTDIR}/usr/share" + rm -f "${DESTDIR}"/usr/lib/*.a "${DESTDIR}"/usr/lib/*.la + rm -f "${DESTDIR}"/usr/lib/*.py + # libstdc++ ships GDB pretty-printers as .py; Python is banned in the + # rootfs and they are debugger-side anyway. + find "${DESTDIR}" -name '*-gdb.py' -delete 2>/dev/null || true + + # GCC builds target libs with -g; unstripped libstdc++.so is ~20MB against + # ~2.5MB stripped, which is not a reasonable thing to put on a switch. + # --strip-debug (not --strip-unneeded) is deliberate: it drops debug info + # only and cannot touch the dynamic symbol table the loader resolves. + for so in "${DESTDIR}"/usr/lib/*.so.*; do + [ -f "${so}" ] || continue + strip --strip-debug "${so}" || die "libstdc++: strip failed on ${so}" + done + + [ -e "${DESTDIR}/usr/lib/libstdc++.so.6" ] || die "libstdc++: no libstdc++.so.6" + [ -e "${DESTDIR}/usr/lib/libgcc_s.so.1" ] || die "libstdc++: no libgcc_s.so.1" + # Catch a silent regression of the MULTILIB_OSDIRNAMES retarget above. + [ ! -d "${DESTDIR}/usr/lib64" ] || die "libstdc++: stray /usr/lib64 (multilib osdir not retargeted)" + + # ABI floor: our runtime must be at least what the host compiler emits, + # or every C++ binary we build will fail to resolve symbols on the switch. + have=$(strings "${DESTDIR}/usr/lib/libstdc++.so.6"* 2>/dev/null \ + | grep -oE 'GLIBCXX_3\.4\.[0-9]+' | sort -V | tail -1) + need=$(strings /usr/lib/libstdc++.so.6 2>/dev/null \ + | grep -oE 'GLIBCXX_3\.4\.[0-9]+' | sort -V | tail -1) + [ -n "${have}" ] || die "libstdc++: could not read our GLIBCXX versions" + if [ -n "${need}" ] && [ "$(printf '%s\n%s\n' "${need}" "${have}" | sort -V | tail -1)" != "${have}" ]; then + die "libstdc++: ours (${have}) is older than the host compiler needs (${need})" + fi + msg "libstdc++: ABI ${have} (host compiler needs ${need:-unknown})" +} diff --git a/packages/linux-longterm/template b/packages/linux-longterm/template @@ -19,6 +19,7 @@ checksum=ba2f60f858bf4d1f929101faa356c93dc8b925b17aaa9f95eabd4627758df613 # the meta points at this line's kernel build (exact pin incl. revision - # apk's `=` only matches the full pkgver, so a bare version never matches) depends="linux=${version}-r${revision}" +hostmakedepends="bc flex bison" subpackages="linux-longterm-headers linux linux-headers" short_desc_linux_longterm_headers="UNOS kernel headers meta - longterm line" diff --git a/packages/linux-mainline/template b/packages/linux-mainline/template @@ -24,6 +24,7 @@ checksum=49b24119d7b92da75bba0daf5da4f7735c8cb948757a282e849092dcc681d8dd # the meta points at this line's kernel build (exact pin incl. revision - # apk's `=` only matches the full pkgver, so a bare version never matches) depends="linux=${version}-r${revision}" +hostmakedepends="bc flex bison" subpackages="linux-mainline-headers linux linux-headers" short_desc_linux_mainline_headers="UNOS kernel headers meta - mainline snapshot" diff --git a/packages/linux-stable/template b/packages/linux-stable/template @@ -19,6 +19,7 @@ checksum=55ddf0df8325d9dad96fcff7bd93977d22e3f50af06527572af59b77c7632b78 # the meta points at this line's kernel build (exact pin incl. revision - # apk's `=` only matches the full pkgver, so a bare version never matches) depends="linux=${version}-r${revision}" +hostmakedepends="bc flex bison" subpackages="linux-stable-headers linux linux-headers" short_desc_linux_stable_headers="UNOS kernel headers meta - stable line" diff --git a/packages/openssl/template b/packages/openssl/template @@ -13,6 +13,7 @@ checksum=a8f84a39918ec6415ce765d9b429d313ba97b8143169c172e734b9514464f5b2 # exist before the script executes (ordering, not just runtime truth - # apk runs each package's scripts right after unpacking it). depends="glibc busybox" +hostmakedepends="perl" # Notes: # - 3.5 is the LTS line (supported to 2030); the 3.6 feature line is diff --git a/packages/smartmontools/template b/packages/smartmontools/template @@ -0,0 +1,65 @@ +# Template file for 'smartmontools' +pkgname=smartmontools +version=7.5 +revision=0 +short_desc="S.M.A.R.T. disk health monitoring (smartctl)" +maintainer="finwo <finwo@pm.me>" +license="GPL-2.0-or-later" +homepage="https://www.smartmontools.org/" +distfiles="https://downloads.sourceforge.net/project/smartmontools/smartmontools/${version}/smartmontools-${version}.tar.gz" +checksum=690b83ca331378da9ea0d9d61008c4b22dde391387b9bbad7f29387f2595f76e +depends="glibc busybox libstdc++" +makedepends="libstdc++" + +# Notes: +# - Why: a switch's boot media is usually an eMMC or a small SSD, and its wear +# state is the thing that predicts the next outage. smartctl is how you read +# it. The S5248F-ON captured in PROGRESS.md 2.1 boots from /dev/sda. +# - smartd (the polling daemon) is deliberately NOT shipped: it wants cron-like +# scheduling and a mail transport, neither of which UNOS has. Health polling +# belongs in the control plane later, not in a second supervisor. +# - The drive database is built in (--with-nvme-devicescan aside, no runtime +# update path). update-smart-drivedb needs curl/gpg and is not shipped. + +do_configure() { + cd "${WRKSRC}" + ./configure \ + --prefix=/usr \ + --sbindir=/usr/sbin \ + --sysconfdir=/etc \ + --disable-dependency-tracking \ + --without-libcap-ng \ + --without-libsystemd \ + --without-selinux \ + --without-gnupg \ + --without-update-smart-drivedb \ + --with-nvme-devicescan +} + +do_build() { + cd "${WRKSRC}" + make -j"$(nproc)" +} + +do_install() { + cd "${WRKSRC}" + make install DESTDIR="${DESTDIR}" + + # smartd is not shipped (see notes): drop the daemon, its config, its + # init/service glue and its manpages rather than leaving dead weight. + rm -f "${DESTDIR}"/usr/sbin/smartd + rm -f "${DESTDIR}"/etc/smartd.conf + rm -f "${DESTDIR}"/usr/share/man/man8/smartd.8 \ + "${DESTDIR}"/usr/share/man/man5/smartd.conf.5 + rm -rf "${DESTDIR}"/etc/smartd_warning.d \ + "${DESTDIR}"/usr/share/smartmontools \ + "${DESTDIR}"/usr/libexec \ + "${DESTDIR}"/lib/systemd "${DESTDIR}"/usr/lib/systemd \ + "${DESTDIR}"/etc/init.d "${DESTDIR}"/etc/rc.d + rm -rf "${DESTDIR}/usr/share/doc" "${DESTDIR}/usr/share/locale" + + [ -x "${DESTDIR}/usr/sbin/smartctl" ] || die "smartmontools: no smartctl" + [ ! -e "${DESTDIR}/usr/sbin/smartd" ] || die "smartmontools: smartd should not ship" + readelf -d "${DESTDIR}/usr/sbin/smartctl" | grep -q 'libstdc++\.so\.6' \ + || die "smartmontools: smartctl does not link libstdc++.so.6" +} diff --git a/packages/unosd/template b/packages/unosd/template @@ -7,6 +7,7 @@ maintainer="finwo <finwo@pm.me>" license="GPL-2.0-only" homepage="https://unos.finwo.dev" depends="busybox iproute2" +hostmakedepends="dep" do_build() { # Single multicall binary (src/main.c dispatch + src/cli/*.c constructors)