#!/bin/sh # Container half of packaging/void/build-release.sh (tickets #28 and #29). # Runs inside the pinned Void glibc image, once per phase. Each phase gets only # the privileges it needs: # # build root, network on, NO signing key. Clones void-packages at the # pinned commit, renders the voculinux build tree from the tag, and # builds every template in smoke-gate order as the unprivileged # builder user (xbps-src refuses root). Build scripts run here, so # the key is never mounted in this phase. # sign root, NO network, the RSA4096 key mounted read-only. Runs no # build code: it indexes and signs the packages the build produced. # install root, network on (the official repository supplies dependencies), # NO key. In a clean image, installs voculinux from the staged signed # repository and checks what a user sees: the version, the payload, # the imports, and that the repository is refused until its key is # trusted. # # Not executed directly: build-release.sh mounts this file and runs it with one # phase argument. Inputs arrive as environment variables set there. set -eu export PATH=/usr/bin:/bin:/usr/sbin:/sbin KEY=/run/xbps-sign-key.pem PKG_STAGE=/mnt/packages XBPS_STAGE=/mnt/xbps OFFICIAL=https://repo-default.voidlinux.org/current fail() { echo "container-release.sh: $*" >&2 exit 1 } phase_build() { : "${VOID_COMMIT:?}" "${PKGS:?}" "${XBPS_VERSION:?}" "${SDIST_NAME:?}" export VOID_COMMIT PKGS XBPS_VERSION SDIST_NAME xbps-install -Suy xbps >/dev/null xbps-install -y git base-devel xtools binutils zstd openssl >/dev/null git clone --quiet --depth 1 https://github.com/void-linux/void-packages /void-packages git -C /void-packages fetch --quiet --depth 1 origin "$VOID_COMMIT" git -C /void-packages checkout --quiet "$VOID_COMMIT" cp -r /overlay/. /void-packages/srcpkgs/ id builder >/dev/null 2>&1 || useradd -m builder # Build tree only: the committed template in the repository is never changed. for PKG in $PKGS; do if [ "$PKG" = voculinux ]; then VOID_PACKAGES=/void-packages SDIST_DIR=/mnt/inputs sh /mnt/render-voculinux.sh fi done chown -R builder:builder /void-packages su -s /bin/sh builder -c 'cd /void-packages && ./xbps-src binary-bootstrap' for PKG in $PKGS; do echo "== xlint $PKG" su -s /bin/sh builder -c "cd /void-packages && xlint 'srcpkgs/$PKG/template'" echo "== xbps-src pkg $PKG" su -s /bin/sh builder -c "cd /void-packages && ./xbps-src pkg '$PKG'" done # The distfile the template names must be the sdist staged for it. su -s /bin/sh builder -c 'cd /void-packages && ./xbps-src show voculinux' >/tmp/show.txt 2>&1 || fail "xbps-src show voculinux failed" grep -qF "/$SDIST_NAME" /tmp/show.txt || fail "xbps-src does not resolve distfile $SDIST_NAME" mkdir -p "$PKG_STAGE" for PKG in $PKGS; do PKGFILE=$(ls /void-packages/hostdir/binpkgs/"$PKG"-[0-9]*.x86_64.xbps 2>/dev/null | head -n1) [ -n "$PKGFILE" ] || fail "built package missing from hostdir/binpkgs: $PKG" echo "collected: $(basename "$PKGFILE")" cp "$PKGFILE" "$PKG_STAGE/" done } phase_sign() { : "${SIGNEDBY:?}" [ -r "$KEY" ] && [ -s "$KEY" ] || fail "signing key is not mounted at $KEY" mkdir -p "$XBPS_STAGE/repo" cp "$PKG_STAGE"/*.x86_64.xbps "$XBPS_STAGE/repo/" echo "== index the full repository" xbps-rindex -a "$XBPS_STAGE"/repo/*.x86_64.xbps echo "== sign every binary package" xbps-rindex -S --privkey "$KEY" --signedby "$SIGNEDBY" "$XBPS_STAGE"/repo/*.x86_64.xbps echo "== sign the repository metadata" xbps-rindex -s --privkey "$KEY" --signedby "$SIGNEDBY" "$XBPS_STAGE/repo" echo "signed repository staged: $(ls "$XBPS_STAGE/repo" | wc -l) files" } # The payload checks the package must pass before anyone installs it. payload_voculinux() { PKGFILE=$1 tar -tf "$PKGFILE" | grep -q "usr/share/applications/voculinux.desktop" || fail "renamed desktop entry missing from the package" tar -tf "$PKGFILE" | grep -q "usr/share/icons/hicolor/scalable/apps/voculinux.svg" || fail "renamed icon missing from the package" tar -tf "$PKGFILE" | grep -q "usr/share/voculinux/packaged" || fail "packaged-install marker missing from the package" if tar -tf "$PKGFILE" | grep -E "models/|\.ggml$|\.bin$|\.pt$|\.whl$"; then fail "package payload carries models or wheels" fi if tar -tf "$PKGFILE" | grep -E "usr/share/applications/vocalinux|usr/share/icons/.*/vocalinux"; then fail "pre-rename file names in the OS-visible payload" fi echo "payload ok: desktop entry, icon and marker present; no models, wheels or pre-rename names" } # whisper.cpp must ship its Vulkan backend and no host-native CPU tuning. payload_pywhispercpp() { PKGFILE=$1 tar -tf "$PKGFILE" | grep -q "libggml-vulkan" || fail "the Vulkan backend is missing from $PKGFILE" LIBMEMBER=$(tar -tf "$PKGFILE" | grep -E "libggml-cpu\.so" | head -n1) [ -n "$LIBMEMBER" ] || fail "libggml-cpu is missing from $PKGFILE" mkdir -p /flagscan tar -xf "$PKGFILE" -C /flagscan "$LIBMEMBER" LIBSCAN="/flagscan/${LIBMEMBER#./}" if readelf -p .GCC.command.line "$LIBSCAN" 2>/dev/null | grep -E "march=native|mcpu=native|mtune=native"; then fail "host-native CPU tuning in the shipped libggml-cpu" fi echo "payload ok: Vulkan backend present; no host-native tuning in libggml-cpu" } phase_install() { : "${PKGS:?}" "${XBPS_VERSION:?}" "${VERSION:?}" [ -d "$XBPS_STAGE/repo" ] && [ -d "$XBPS_STAGE/keys" ] || fail "staged repository or keys are missing" xbps-install -Sy xbps binutils zstd >/dev/null echo "== the signed repository is refused while its key is not trusted" # No -y: the documented first sync must not accept an unknown key. set +e REFUSAL=$(xbps-install -S --repository="$XBPS_STAGE/repo" voculinux &1) REFUSAL_RC=$? set -e printf '%s\n' "$REFUSAL" | grep -i "fingerprint" || true [ "$REFUSAL_RC" -ne 0 ] || fail "the repository installed without its key being trusted" xbps-query voculinux >/dev/null 2>&1 && fail "voculinux was installed by the untrusted attempt" echo "refused without the key, as required (rc=$REFUSAL_RC)" echo "== trust the staged key, then install voculinux from the signed repository" cp "$XBPS_STAGE"/keys/*.plist /var/db/xbps/keys/ xbps-install -y --repository="$XBPS_STAGE/repo" --repository="$OFFICIAL" -S voculinux >/dev/null xbps-query voculinux | grep -qx "pkgver: voculinux-${XBPS_VERSION}_1" || fail "installed package is not voculinux-${XBPS_VERSION}_1" echo "installed: voculinux-${XBPS_VERSION}_1" echo "== the renamed binary reports its version" VERSION_OUT=$(voculinux --version 2>&1) || fail "voculinux --version exited non-zero" printf '%s\n' "$VERSION_OUT" printf '%s' "$VERSION_OUT" | grep -qF "$VERSION" || fail "voculinux --version does not show $VERSION" echo "== payload of the built packages; every package needs a smoke recipe" for PKG in $PKGS; do PKGFILE=$(ls "$PKG_STAGE"/"$PKG"-[0-9]*.x86_64.xbps | head -n1) case "$PKG" in voculinux) payload_voculinux "$PKGFILE" ;; python3-pywhispercpp) payload_pywhispercpp "$PKGFILE" ;; python3-pynput | python3-PyAudio) ;; # checked by import below *) fail "no smoke recipe for package $PKG; add one alongside its template" ;; esac done echo "== imports, without a display or hardware" # pynput: the dummy backend proves the package and its loader. The X backend # needs an X server and is covered by the manual hardware checklist. PYNPUT_BACKEND=dummy python3 -c "import pynput.keyboard, pynput.mouse; print('pynput dummy backend ok')" python3 -c "import evdev; print('evdev ok')" python3 -c "import gi; gi.require_version('Gtk', '3.0'); from gi.repository import Gtk; print('gi ok: Gtk', Gtk.get_major_version(), Gtk.get_minor_version())" python3 -c "from pywhispercpp.model import Model; print('pywhispercpp.model ok')" python3 -c "import pyaudio; print('pyaudio ok')" python3 -c "import vocalinux.main, vocalinux.speech_recognition.recognition_manager; print('vocalinux imports ok')" echo "INSTALL SMOKE OK: voculinux $VERSION installed from the signed repository" } case "${1:-}" in build) phase_build ;; sign) phase_sign ;; install) phase_install ;; *) fail "usage: container-release.sh build|sign|install" ;; esac