Files
voculinux/packaging/void/container-release.sh
T
xavierk 37cf549956
CI / security (push) Successful in 1m15s
CI / lint (push) Successful in 24s
CI / ai-review (push) Skipped
fix(void): carry python3-pysocks and constrain the audio and SOCKS floors (#39)
voculinux now depends on python3-PyAudio and python3-pysocks at the carried versions, so a stock Void package below the project floors is upgraded rather than silently kept. The container smoke installs the stock packages first and asserts the upgrade.
2026-11-06 16:17:19 +00:00

194 lines
8.7 KiB
Bash
Executable File

#!/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 </dev/null 2>&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"
cp "$XBPS_STAGE"/keys/*.plist /var/db/xbps/keys/
echo "== stock Void packages below the project floors, as a user who has them already"
xbps-install -y --repository="$OFFICIAL" -S python3-PyAudio python3-pysocks >/dev/null
echo "== install voculinux from the signed repository; the floors must upgrade the stock packages"
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"
for PKG in python3-PyAudio python3-pysocks; do
PKGFILE=$(ls "$PKG_STAGE"/"$PKG"-[0-9]*.x86_64.xbps | head -n1)
PKGVER=$(basename "$PKGFILE" .x86_64.xbps)
xbps-query "$PKG" | grep -qx "pkgver: $PKGVER" ||
fail "$PKG is not $PKGVER after voculinux: the stock package was not upgraded"
echo "upgraded to the carried template: $PKGVER"
done
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 | python3-pysocks) ;; # 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 socks; print('pysocks 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