commit 4efa6149ed102c9dccaa63907a52463d9975e45e
parent 3ad1b3cc4754dced4b828dc2036dd1cd3ad3c76e
Author: finwo <finwo@pm.me>
Date: Mon, 14 Sep 2026 13:29:27 +0200
More packages to move towards base system
Diffstat:
19 files changed, 394 insertions(+), 17 deletions(-)
diff --git a/mk/build.sh b/mk/build.sh
@@ -36,6 +36,11 @@ SRCDEST="${ROOT}/build/work/sources"
DESTDIR="${WORK}/dest"
WRKSRC=
OUTDIR="${ROOT}/build/repo"
+# Build-time sysroot (generated, never committed): libraries that other
+# packages build against install twice - once into DESTDIR (the shipped
+# .apk) and once into SYSROOT (headers + .so + prefix-fixed .pc files).
+# Consumers find it via PKG_CONFIG_PATH; host tools (muon) via PATH.
+SYSROOT="${ROOT}/build/sysroot"
# --- helpers available to templates ---
msg() { printf '==> %s\n' "$*"; }
@@ -60,7 +65,7 @@ PKGVER="${version}-r${revision}"
ARCH="x86_64"
OUTDIR="${ROOT}/build/repo/${ARCH}"
-mkdir -p "${SRCDEST}" "${WORK}" "${OUTDIR}"
+mkdir -p "${SRCDEST}" "${WORK}" "${OUTDIR}" "${SYSROOT}"
# fresh staging every build: templates must be idempotent AND ghost-free
rm -rf "${DESTDIR}"
mkdir -p "${DESTDIR}"
@@ -99,7 +104,11 @@ if [ -n "${distfiles}" ]; then
patch -d "${WRKSRC}" -p1 --no-backup-if-mismatch -i "${p}"
done
fi
-export DESTDIR WRKSRC FILESDIR WORK
+export DESTDIR WRKSRC FILESDIR WORK SYSROOT
+# host-built tools first (muon), target sysroot visible to pkg-config.
+# Both are additive only; system cc/patch/curl/pkg-config resolve as before.
+export PATH="${ROOT}/build/host/bin:${PATH}"
+export PKG_CONFIG_PATH="${SYSROOT}/usr/lib/pkgconfig${PKG_CONFIG_PATH:+:${PKG_CONFIG_PATH}}"
# --- build + install phases (defaults: no-op) ---
command -v do_configure >/dev/null 2>&1 || do_configure() { :; }
diff --git a/mk/rootfs.sh b/mk/rootfs.sh
@@ -31,6 +31,12 @@ echo "==> trust seed: unos-keys via explicit --allow-untrusted"
echo "==> UNOS system (fully trusted from here on)"
"${APK}" --root "${ROOTFS}" --usermode \
--cache-dir "${CACHE}" --repository "${REPO}" \
- add base-files glibc busybox unos-firstboot tinyssh
+ add base-files glibc busybox unos-firstboot tinyssh libmnl zlib openssl iproute2 apk-tools
+
+# Deterministic loader cache: package post-installs refresh it during the
+# transaction, but assembly must not depend on script-execution order.
+# Runs OUR ldconfig inside the namespace (writes rootfs/etc/ld.so.cache).
+echo "==> loader cache"
+./mk/chroot.sh /sbin/ldconfig
echo "==> done: ${ROOTFS}"
diff --git a/packages/README.md b/packages/README.md
@@ -47,3 +47,23 @@ directory is ever built by hand on a target switch.
a `.ko` has to be rebuilt whenever the `linux` package changes; a stale module
fails at `insmod` time on the switch, which is the worst possible place to
find out.
+- **Shared-only C libraries.** Nothing in UNOS links statically, so library
+ templates pass `--disable-static` (or equivalent) and must not ship `.a`
+ files. Keep the rootfs lean; revisit only if a static-link need appears.
+- **Build-time sysroot (`build/sysroot`, generated, never committed).**
+ Libraries that other packages build against install twice: once into
+ `DESTDIR` (the shipped `.apk`) and once into `SYSROOT` (headers + `.so` +
+ `.pc` files). The driver exports `SYSROOT`, prepends
+ `build/host/bin` (host tools like muon) to `PATH`, and points
+ `PKG_CONFIG_PATH` at the sysroot. Library templates must rewrite the
+ `prefix=` line of their sysroot `.pc` copies to `${SYSROOT}/usr` - a
+ plain `DESTDIR` install bakes `prefix=/usr`, which would point consumers
+ at host headers.
+- **Loader cache is generated, never shipped.** The loader's baked-in
+ search covers `/lib64` + `/usr/lib64` only, so every library package
+ carries a `files/post-install` running `exec /sbin/ldconfig` (absolute:
+ glibc installs it to `/sbin`, and the busybox applet is compiled out).
+ Script-bearing packages order themselves with `depends="glibc busybox"`
+ (`/bin/sh` must exist before apk runs their scripts). `mk/rootfs.sh`
+ additionally runs `ldconfig` explicitly after assembly so the chroot
+ cache never depends on script-execution order.
diff --git a/packages/apk-tools/template b/packages/apk-tools/template
@@ -0,0 +1,47 @@
+# Template file for 'apk-tools'
+pkgname=apk-tools
+version=3.0.8
+revision=1
+short_desc="Package manager for apk packages"
+maintainer="finwo <finwo@pm.me>"
+license="GPL-2.0-only"
+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"
+
+# Notes:
+# - Same version the host manager was built from (v3.0.8); target and host
+# stay on one version until there is a reason to diverge.
+# - muon (build/host/bin, on PATH via the driver) configures, ninja builds;
+# out-of-tree in ${WORK}/build, DESTDIR install.
+# - url_backend=libfetch (in-tree, no extra package) over wget; crypto
+# openssl (zlib + openssl come from the sysroot via PKG_CONFIG_PATH).
+# - Disabled: docs (needs host scdoc), help/lua/python (needs lua/python),
+# tests (CI runs upstream's suite, not the target build), zstd (no zstd
+# package; our artifacts are gzip). No on-device man pages or embedded
+# help DB yet - revisit when the operator story needs them.
+
+do_configure() {
+ mkdir -p "${WORK}/build"
+ cd "${WRKSRC}"
+ muon setup \
+ -Dprefix=/usr \
+ -Ddocs=disabled \
+ -Dhelp=disabled \
+ -Dlua=disabled \
+ -Dpython=disabled \
+ -Dtests=disabled \
+ -Dzstd=disabled \
+ -Durl_backend=libfetch \
+ -Dcrypto_backend=openssl \
+ "${WORK}/build"
+}
+
+do_build() {
+ ninja -C "${WORK}/build"
+}
+
+do_install() {
+ DESTDIR="${DESTDIR}" ninja -C "${WORK}/build" install
+}
diff --git a/packages/base-files/files/ld.so.conf b/packages/base-files/files/ld.so.conf
@@ -1 +1,3 @@
+/lib64
+/usr/lib
include /etc/ld.so.conf.d/*.conf
diff --git a/packages/base-files/template b/packages/base-files/template
@@ -1,7 +1,7 @@
# Template file for 'base-files'
pkgname=base-files
version=0.1.0
-revision=2
+revision=3
short_desc="UNOS filesystem skeleton - passwd, profile, nsswitch, fstab"
maintainer="finwo <finwo@pm.me>"
license="GPL-2.0-only"
diff --git a/packages/busybox/template b/packages/busybox/template
@@ -1,8 +1,8 @@
# Template file for 'busybox'
pkgname=busybox
version=1.36.1
-revision=3
-short_desc="UNOS userland - init, supervision, shell, coreutils"
+revision=5
+short_desc="Swiss Army knife of embedded Linux utilities"
maintainer="finwo <finwo@pm.me>"
license="GPL-2.0-only"
homepage="https://busybox.net"
@@ -19,17 +19,19 @@ depends=
# defconfig defaults; listing them makes the requirement explicit.)
# OFF: IP applets (iproute2 is the only `ip`), TC (iproute2's `tc` is
# full-featured; busybox's references kernel-removed CBQ UAPI and does not
-# build against modern headers), udhcpd (dnsmasq covers the server side),
-# httpd/inetd (tcpsvd is our super-server).
+# build against modern headers), LDCONFIG (glibc's real ldconfig owns
+# /sbin/ldconfig; the applet would conflict on that path and its cache
+# output must never feed glibc's loader), udhcpd (dnsmasq covers the
+# server side), httpd/inetd (tcpsvd is our super-server).
# Untouched (defconfig default) until their design lands: SYSLOGD/KLOGD.
# No checked-in .config on purpose: defconfig + CFG_* is the source of truth
# (this kconfig has no savedefconfig anyway); the assertions below enforce it.
CFG_ON="INIT HALT POWEROFF REBOOT GETTY LOGIN MOUNT UMOUNT SWITCH_ROOT ASH ASH_JOB_CONTROL RUNSV RUNSVDIR SV SVLOGD CHPST SETUIDGID TCPSVD UDHCPC"
-CFG_OFF="IP FEATURE_IP_ADDRESS FEATURE_IP_LINK FEATURE_IP_ROUTE FEATURE_IP_RULE FEATURE_IP_TUNNEL TC UDHCPD HTTPD INETD"
+CFG_OFF="IP FEATURE_IP_ADDRESS FEATURE_IP_LINK FEATURE_IP_ROUTE FEATURE_IP_RULE FEATURE_IP_TUNNEL TC LDCONFIG UDHCPD HTTPD INETD"
# applet names as `busybox --list` prints them
REQUIRE="init halt poweroff reboot getty login mount umount switch_root ash runsv runsvdir sv svlogd chpst setuidgid tcpsvd udhcpc"
-FORBID="ip tc udhcpd httpd inetd"
+FORBID="ip tc ldconfig udhcpd httpd inetd"
set_config() {
# $1 = symbol, $2 = y|n
diff --git a/packages/glibc/files/post-install b/packages/glibc/files/post-install
@@ -1,4 +1,7 @@
#!/bin/sh
# glibc post-install: (re)build the loader cache in the target root.
# Runs on the target (apk executes control scripts chrooted at install).
-/sbin/ldconfig
+# Absolute path on purpose: glibc installs ldconfig to /sbin (its sbindir
+# default); the busybox ldconfig applet is compiled out so nothing shadows
+# it, but scripts must never depend on PATH order for this.
+exec /sbin/ldconfig
diff --git a/packages/glibc/template b/packages/glibc/template
@@ -1,15 +1,16 @@
# Template file for 'glibc'
pkgname=glibc
version=2.44
-revision=1
-short_desc="UNOS C library - glibc"
+revision=6
+short_desc="GNU C library"
maintainer="finwo <finwo@pm.me>"
license="LGPL-2.1-or-later"
homepage="https://www.gnu.org/software/libc/"
distfiles="https://ftp.gnu.org/gnu/glibc/glibc-2.44.tar.xz"
checksum=37f600f2bef3c5e8300147059568b2a2e40a7ad6ccc65ce942556d49429cc667
-# runtime needs from base-files: /etc/nsswitch.conf, /etc/ld.so.conf
-depends="base-files"
+# 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"
# Notes:
# - glibc forbids in-source builds; configure runs in a sibling build dir.
@@ -17,6 +18,12 @@ depends="base-files"
# shipped). No `make check` locally (slow/flaky outside CI); CI runs it.
# - --enable-kernel=5.4: floor for any plausible UNOS kernel (generic target
# and switch kernels are 6.x; ONIE's kernel is irrelevant).
+# - --libdir=/usr/lib (slibdir stays /lib64 for the loader + libc): without
+# it glibc defaults libdir to /usr/lib64 on x86_64, scattering libraries
+# across /lib64 + /usr/lib64 while every other UNOS package installs to
+# /usr/lib - and the loader's baked-in search then misses /usr/lib
+# entirely (`ip` failed to find libmnl). With /usr/lib as libdir the
+# loader searches /lib64 + /usr/lib and one layout covers everything.
# - Single fat package (runtime + headers + static libs); split if size
# ever matters. No debuginfo split either.
# - Locales: C.UTF-8 only. No tzdata (UTC everywhere).
@@ -27,6 +34,7 @@ do_configure() {
cd "${WORK}/build"
"${WRKSRC}/configure" \
--prefix=/usr \
+ --libdir=/usr/lib \
--enable-kernel=5.4 \
--disable-werror
}
diff --git a/packages/iproute2/template b/packages/iproute2/template
@@ -0,0 +1,75 @@
+# Template file for 'iproute2'
+pkgname=iproute2
+version=6.19.0
+revision=2
+short_desc="IP routing and network device configuration tools"
+maintainer="finwo <finwo@pm.me>"
+license="GPL-2.0-only"
+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"
+
+# Notes:
+# - `ip` ONLY (PLAN section 10): the top-level `install: all` would build
+# every subdir (tc needs flex/bison/xtables/elf), so the template builds
+# SUBDIRS="lib ip" and cherry-picks the `ip` binary plus the iproute2
+# config files (rt_tables symbolic names live there). tc/bridge/misc
+# (ss!)/rtmon land as separate design decisions, not silent extras.
+# - libmnl comes from the sysroot (extended-error-ack decoding) and libcap
+# (drop_cap ambient-capability hygiene); configure finds both via the
+# driver-exported PKG_CONFIG_PATH.
+# - Host-independence scrub: configure appends every detected optional to
+# GLOBAL LDLIBS, so any host dev library would silently link into `ip`.
+# BPF/XDP program loading (elf, libbpf), selinux and tirpc go - none of
+# them has consumers in lib/ or ip/. Patterns are anchored; absent lines
+# are no-ops; do_build asserts the final linkage so a rotted scrub fails
+# the build, never the boot.
+# - CONF_USR_DIR bakes to /usr/share/iproute2; the config files are
+# installed to match.
+
+do_configure() {
+ cd "${WRKSRC}"
+ ./configure
+ sed -i \
+ -e '/^HAVE_ELF:=/d' \
+ -e '/^HAVE_LIBBPF.*:=/d' \
+ -e '/^HAVE_SELINUX:=/d' \
+ -e '/^HAVE_RPC:=/d' \
+ -e '/^CFLAGS += -DHAVE_ELF/d' \
+ -e '/^CFLAGS += -DHAVE_LIBBPF/d' \
+ -e '/^CFLAGS += -DHAVE_SELINUX/d' \
+ -e '/^CFLAGS += -DHAVE_RPC/d' \
+ -e '/^LDLIBS +=.*-lelf/d' \
+ -e '/^LDLIBS +=.*selinux/d' \
+ -e '/^LDLIBS +=.*tirpc/d' \
+ config.mk
+}
+
+do_build() {
+ cd "${WRKSRC}"
+ # iproute2 has no kbuild-style flag tracking: objects from a previous
+ # configure do not rebuild when config.mk flags change. Clean
+ # unconditionally (cheap for lib+ip; full-tree cost is why SUBDIRS is
+ # limited here too).
+ make SUBDIRS="lib ip" clean >/dev/null
+ make -j"$(nproc)" SUBDIRS="lib ip"
+ [ -x ip/ip ] || die "iproute2: ip binary missing after build"
+ needed=$(readelf -d ip/ip | sed -n 's/^.*NEEDED.*\[\(.*\)\].*$/\1/p' | sort)
+ for lib in ${needed}; do
+ case "${lib}" in
+ libmnl.so.*|libcap.so.*|libc.so.*) : ;;
+ *) die "iproute2: unexpected NEEDED entry: ${lib}" ;;
+ esac
+ done
+}
+
+do_install() {
+ cd "${WRKSRC}"
+ install -D -m 755 ip/ip "${DESTDIR}/sbin/ip"
+ for f in etc/iproute2/*; do
+ [ -f "${f}" ] || continue
+ install -D -m 644 "${f}" "${DESTDIR}/usr/share/iproute2/$(basename "${f}")"
+ done
+ [ -f "${DESTDIR}/usr/share/iproute2/rt_tables" ] || die "iproute2: rt_tables missing"
+}
diff --git a/packages/libcap/files/post-install b/packages/libcap/files/post-install
@@ -0,0 +1,6 @@
+#!/bin/sh
+# libcap 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/libcap/template b/packages/libcap/template
@@ -0,0 +1,48 @@
+# Template file for 'libcap'
+pkgname=libcap
+version=2.78
+revision=4
+short_desc="POSIX capability library and utilities"
+maintainer="finwo <finwo@pm.me>"
+license="BSD-3-Clause OR GPL-2.0-only"
+homepage="https://sites.google.com/site/fullycapable/"
+distfiles="https://www.kernel.org/pub/linux/libs/security/linux-privs/libcap2/libcap-2.78.tar.xz"
+checksum=0d621e562fd932ccf67b9660fb018e468a683d7b827541df27813228c996bb11
+# glibc first: the post-install runs /sbin/ldconfig, which needs libc
+# present. busybox first: control scripts run under /bin/sh, which must
+# exist before the script executes (ordering, not just runtime truth -
+# apk runs each package's scripts right after unpacking it).
+depends="glibc busybox"
+
+# Notes:
+# - Pin cross-checked against kernel.org sha256sums.asc.
+# - No configure step upstream; plain make with prefix/lib vars. Install
+# honors FAKEROOT (not DESTDIR).
+# - iproute2's `ip` uses libcap for drop_cap() (ambient-capability hygiene),
+# and `ip vrf/netns exec` keeps working with it; FRR will want it later
+# for daemon privilege drops. Only libcap + progs are built: no pam_cap
+# (no PAM in UNOS), no go bindings, no C++ wrapper, no tests.
+# - setcap/getcap/capsh ship as admin tools for the file-caps story.
+# - Shared-only per packages/README.md: the .a files are removed.
+# - Sysroot seed (second lib install + .pc prefix rewrite) so iproute2's
+# configure finds it via PKG_CONFIG_PATH.
+
+do_build() {
+ cd "${WRKSRC}"
+ make -j"$(nproc)" -C libcap prefix=/usr lib=lib
+ make -j"$(nproc)" -C progs prefix=/usr lib=lib
+ [ -x libcap/libcap.so ] || die "libcap: shared lib missing after build"
+}
+
+do_install() {
+ cd "${WRKSRC}"
+ make -C libcap install prefix=/usr lib=lib FAKEROOT="${DESTDIR}"
+ make -C libcap install prefix=/usr lib=lib FAKEROOT="${SYSROOT}"
+ make -C progs install prefix=/usr lib=lib FAKEROOT="${DESTDIR}"
+ rm -f "${DESTDIR}"/usr/lib/libcap.a "${DESTDIR}"/usr/lib/libpsx.a
+ rm -f "${SYSROOT}"/usr/lib/libcap.a "${SYSROOT}"/usr/lib/libpsx.a
+ for pc in libcap.pc libpsx.pc; do
+ [ -f "${SYSROOT}/usr/lib/pkgconfig/${pc}" ] || die "libcap: no sysroot ${pc}"
+ sed -i "s|^prefix=.*$|prefix=${SYSROOT}/usr|" "${SYSROOT}/usr/lib/pkgconfig/${pc}"
+ done
+}
diff --git a/packages/libmnl/files/post-install b/packages/libmnl/files/post-install
@@ -0,0 +1,6 @@
+#!/bin/sh
+# libmnl 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/libmnl/template b/packages/libmnl/template
@@ -0,0 +1,43 @@
+# Template file for 'libmnl'
+pkgname=libmnl
+version=1.0.5
+revision=5
+short_desc="Minimalistic Netlink library"
+maintainer="finwo <finwo@pm.me>"
+license="LGPL-2.1-or-later"
+homepage="https://www.netfilter.org/projects/libmnl/"
+distfiles="https://www.netfilter.org/pub/libmnl/libmnl-1.0.5.tar.bz2"
+checksum=274b9b919ef3152bfb3da3a13c950dd60d6e2bcd54230ffeca298d03b40d0525
+# glibc first: the post-install runs /sbin/ldconfig, which needs libc
+# present. busybox first: control scripts run under /bin/sh, which must
+# exist before the script executes (ordering, not just runtime truth -
+# apk runs each package's scripts right after unpacking it).
+depends="glibc busybox"
+
+# Notes:
+# - iproute2's `ip` builds without libmnl but loses extended-error-ack
+# decoding (netlink error messages become opaque). On a netlink-driven
+# NOS that is a debugging tax we refuse to pay; libmnl is tiny and
+# dependency-free, so it is a real package, not vendored.
+# - Shared-only per packages/README.md. Sysroot seed (second install +
+# .pc prefix rewrite) so iproute2's configure finds it via PKG_CONFIG_PATH.
+
+do_configure() {
+ cd "${WRKSRC}"
+ ./configure --prefix=/usr --disable-static
+}
+
+do_build() {
+ cd "${WRKSRC}"
+ make -j"$(nproc)"
+}
+
+do_install() {
+ cd "${WRKSRC}"
+ make install DESTDIR="${DESTDIR}"
+ make install DESTDIR="${SYSROOT}"
+ # .la files are build-time libtool metadata, never shipped
+ rm -f "${DESTDIR}"/usr/lib/*.la "${SYSROOT}"/usr/lib/*.la
+ [ -f "${SYSROOT}/usr/lib/pkgconfig/libmnl.pc" ] || die "libmnl: no sysroot .pc"
+ sed -i "s|^prefix=.*$|prefix=${SYSROOT}/usr|" "${SYSROOT}/usr/lib/pkgconfig/libmnl.pc"
+}
diff --git a/packages/openssl/files/post-install b/packages/openssl/files/post-install
@@ -0,0 +1,6 @@
+#!/bin/sh
+# openssl 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/openssl/template b/packages/openssl/template
@@ -0,0 +1,48 @@
+# Template file for 'openssl'
+pkgname=openssl
+version=3.5.8
+revision=4
+short_desc="Cryptography and TLS library and tools"
+maintainer="finwo <finwo@pm.me>"
+license="Apache-2.0"
+homepage="https://www.openssl.org"
+distfiles="https://www.openssl.org/source/openssl-3.5.8.tar.gz"
+checksum=a8f84a39918ec6415ce765d9b429d313ba97b8143169c172e734b9514464f5b2
+# glibc first: the post-install runs /sbin/ldconfig, which needs libc
+# present. busybox first: control scripts run under /bin/sh, which must
+# exist before the script executes (ordering, not just runtime truth -
+# apk runs each package's scripts right after unpacking it).
+depends="glibc busybox"
+
+# Notes:
+# - 3.5 is the LTS line (supported to 2030); the 3.6 feature line is
+# deliberately not taken. Pin cross-checked against the published .sha256.
+# - perl is build-only (same category as python3 for glibc), never shipped.
+# - no-docs skips the multi-thousand-page man build; ship code only.
+# - Shared-only per packages/README.md: the .a files (tens of MB) are
+# removed after install.
+# - install_sw (software without docs) into DESTDIR and SYSROOT; the
+# sysroot .pc copies get the prefix rewrite so apk-tools' muon/meson
+# configure links under the sysroot.
+
+do_configure() {
+ cd "${WRKSRC}"
+ perl ./Configure --prefix=/usr --libdir=lib no-docs linux-x86_64
+}
+
+do_build() {
+ cd "${WRKSRC}"
+ make -j"$(nproc)"
+}
+
+do_install() {
+ cd "${WRKSRC}"
+ make install_sw DESTDIR="${DESTDIR}"
+ make install_sw DESTDIR="${SYSROOT}"
+ rm -f "${DESTDIR}/usr/lib/libcrypto.a" "${DESTDIR}/usr/lib/libssl.a"
+ rm -f "${SYSROOT}/usr/lib/libcrypto.a" "${SYSROOT}/usr/lib/libssl.a"
+ for pc in libcrypto.pc libssl.pc openssl.pc; do
+ [ -f "${SYSROOT}/usr/lib/pkgconfig/${pc}" ] || die "openssl: no sysroot ${pc}"
+ sed -i "s|^prefix=.*$|prefix=${SYSROOT}/usr|" "${SYSROOT}/usr/lib/pkgconfig/${pc}"
+ done
+}
diff --git a/packages/tinyssh/template b/packages/tinyssh/template
@@ -1,8 +1,8 @@
# Template file for 'tinyssh'
pkgname=tinyssh
version=20260906
-revision=2
-short_desc="UNOS ssh daemon - tinysshd behind busybox tcpsvd"
+revision=3
+short_desc="Small SSH server"
maintainer="finwo <finwo@pm.me>"
license="CC0-1.0"
homepage="https://tinyssh.org"
diff --git a/packages/zlib/files/post-install b/packages/zlib/files/post-install
@@ -0,0 +1,6 @@
+#!/bin/sh
+# zlib 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/zlib/template b/packages/zlib/template
@@ -0,0 +1,42 @@
+# Template file for 'zlib'
+pkgname=zlib
+version=1.3.1
+revision=4
+short_desc="General-purpose compression library"
+maintainer="finwo <finwo@pm.me>"
+license="Zlib"
+homepage="https://zlib.net"
+# NOTE: canonical zlib.net flaked on fetch during pinning; GitHub mirror
+# used instead. Hash matches the widely published zlib-1.3.1 sha256.
+distfiles="https://github.com/madler/zlib/releases/download/v1.3.1/zlib-1.3.1.tar.gz"
+checksum=9a93b2b7dfdac77ceba5a558a580e74667dd6fede4585b91eefb60f03b72df23
+# glibc first: the post-install runs /sbin/ldconfig, which needs libc
+# present. busybox first: control scripts run under /bin/sh, which must
+# exist before the script executes (ordering, not just runtime truth -
+# apk runs each package's scripts right after unpacking it).
+depends="glibc busybox"
+
+# Notes:
+# - apk-tools links zlib unconditionally (gzip .apk members). --shared for
+# the .so; the static .a is removed per the shared-only policy.
+# - Sysroot seed (second install + .pc prefix rewrite) so apk-tools'
+# muon/meson configure finds it via PKG_CONFIG_PATH.
+
+do_configure() {
+ cd "${WRKSRC}"
+ ./configure --prefix=/usr --shared
+}
+
+do_build() {
+ cd "${WRKSRC}"
+ make -j"$(nproc)"
+}
+
+do_install() {
+ cd "${WRKSRC}"
+ make install DESTDIR="${DESTDIR}"
+ make install DESTDIR="${SYSROOT}"
+ rm -f "${DESTDIR}/usr/lib/libz.a" "${SYSROOT}/usr/lib/libz.a"
+ [ -f "${SYSROOT}/usr/lib/pkgconfig/zlib.pc" ] || die "zlib: no sysroot .pc"
+ sed -i "s|^prefix=.*$|prefix=${SYSROOT}/usr|" "${SYSROOT}/usr/lib/pkgconfig/zlib.pc"
+}