From 06bc0cd98deb0977a3c42a453eb216ebca086c9e Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Fri, 9 Jan 2026 22:50:09 -0300 Subject: [PATCH] server: safer gadget bring-up + kernel upgrade hook --- scripts/daemon/lesavka-core.sh | 19 +++++- scripts/install/server.sh | 9 ++- scripts/kernel/build-linux-rpi.sh | 110 ++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 scripts/kernel/build-linux-rpi.sh diff --git a/scripts/daemon/lesavka-core.sh b/scripts/daemon/lesavka-core.sh index 892aadb..7c4a83e 100644 --- a/scripts/daemon/lesavka-core.sh +++ b/scripts/daemon/lesavka-core.sh @@ -106,6 +106,7 @@ cleanup() { DISABLE_UAC=${LESAVKA_DISABLE_UAC:-} DISABLE_UVC=${LESAVKA_DISABLE_UVC:-} +ALLOW_RESET=${LESAVKA_ALLOW_GADGET_RESET:-} UVC_FALLBACK=${LESAVKA_UVC_FALLBACK:-1} UVC_STREAMING_INTERVAL=${LESAVKA_UVC_STREAMING_INTERVAL:-1} UVC_MAXPACKET=${LESAVKA_UVC_MAXPACKET:-1024} @@ -155,7 +156,9 @@ grep -q 'dtoverlay=dwc2,dr_mode=peripheral' "$CFG" || echo 'dtoverlay=dwc2,dr_mo modprobe dwc2 || { echo "dwc2 not in kernel; abort" >&2; exit 1; } modprobe libcomposite || { echo "libcomposite not in kernel; abort" >&2; exit 1; } -modprobe -r uvcvideo 2>/dev/null || true +if [[ -n ${LESAVKA_RELOAD_UVCVIDEO:-} ]]; then + modprobe -r uvcvideo 2>/dev/null || true +fi modprobe uvcvideo || { echo "uvcvideo not in kernel; abort" >&2; exit 1; } udevadm control --reload @@ -192,12 +195,22 @@ fi [[ -n $UDC ]] || { log "❌ UDC not present after manual bind"; exit 1; } log "✅ UDC detected: $UDC" +# If a gadget is already configured, avoid tearing it down unless forced. +if [[ -d $G && -z $ALLOW_RESET ]]; then + if [[ -s $G/UDC || -d $G/configs/c.1 ]]; then + log "🔒 gadget already configured; skipping reset." + log " Set LESAVKA_ALLOW_GADGET_RESET=1 to force rebuild." + attach_gadget || true + exit 0 + fi +fi + # Guard against lockups: if the gadget is already bound, don't reset unless forced. BOUND_UDC="" if [[ -r $G/UDC ]]; then BOUND_UDC=$(cat "$G/UDC" 2>/dev/null || true) fi -if [[ -n $BOUND_UDC && -z ${LESAVKA_ALLOW_GADGET_RESET:-} ]]; then +if [[ -n $BOUND_UDC && -z $ALLOW_RESET ]]; then log "🔒 gadget already bound to '$BOUND_UDC' - refusing reset." log " Set LESAVKA_ALLOW_GADGET_RESET=1 to force." exit 0 @@ -205,7 +218,7 @@ fi # Guard against lockups: don't reset gadget while host is attached unless forced. UDC_STATE="$(udc_state "$UDC")" -if [[ -z ${LESAVKA_ALLOW_GADGET_RESET:-} ]] && is_attached_state "$UDC_STATE"; then +if [[ -z $ALLOW_RESET ]] && is_attached_state "$UDC_STATE"; then log "🔒 UDC state is '$UDC_STATE' - refusing gadget reset while host attached." log " Set LESAVKA_ALLOW_GADGET_RESET=1 to force." exit 0 diff --git a/scripts/install/server.sh b/scripts/install/server.sh index ffd9bdd..1c5a152 100755 --- a/scripts/install/server.sh +++ b/scripts/install/server.sh @@ -149,7 +149,14 @@ else sudo -u "$ORIG_USER" git -C "$SRC_DIR" checkout --force "$REF" fi -echo "==> 4b. Source build" +echo "==> 4b. Kernel upgrade (optional)" +if [[ "${LESAVKA_KERNEL_UPDATE:-1}" != "0" ]]; then + sudo LESAVKA_KERNEL_BUILD_USER="$ORIG_USER" "$SRC_DIR/scripts/kernel/build-linux-rpi.sh" +else + echo "⚠️ skipping kernel upgrade (LESAVKA_KERNEL_UPDATE=0)" +fi + +echo "==> 4c. Source build" sudo -u "$ORIG_USER" bash -c "cd '$SRC_DIR/server' && cargo clean && cargo build --release --bins" echo "==> 5. Install binaries" diff --git a/scripts/kernel/build-linux-rpi.sh b/scripts/kernel/build-linux-rpi.sh new file mode 100644 index 0000000..6acaf39 --- /dev/null +++ b/scripts/kernel/build-linux-rpi.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash +# build-linux-rpi.sh - build/install a newer linux-rpi from rpi-6.18.y +set -euo pipefail + +if [[ ${EUID:-0} -ne 0 ]]; then + echo "run as root (sudo) to install build deps and kernel packages" >&2 + exit 1 +fi + +BUILD_USER=${LESAVKA_KERNEL_BUILD_USER:-${SUDO_USER:-$(id -un)}} +if [[ -z $BUILD_USER || $BUILD_USER == root ]]; then + echo "missing non-root build user; set LESAVKA_KERNEL_BUILD_USER" >&2 + exit 1 +fi + +KERNEL_REPO=${LESAVKA_KERNEL_REPO:-https://github.com/raspberrypi/linux.git} +KERNEL_BRANCH=${LESAVKA_KERNEL_BRANCH:-rpi-6.18.y} +KERNEL_COMMIT=${LESAVKA_KERNEL_COMMIT:-} +PKGBUILD_REPO=${LESAVKA_KERNEL_PKG_REPO:-https://github.com/archlinuxarm/PKGBUILDs.git} +BUILD_ROOT=${LESAVKA_KERNEL_BUILD_ROOT:-/var/tmp/lesavka-linux-rpi} +PKGREL=${LESAVKA_KERNEL_PKGREL:-2} +JOBS=${LESAVKA_KERNEL_JOBS:-2} + +HEARTBEAT=/etc/lesavka/watchdog.touch +if [[ -w $HEARTBEAT && -z ${LESAVKA_DISABLE_KEEPALIVE:-} ]]; then + (while true; do touch "$HEARTBEAT"; sleep 600; done) & + KEEPALIVE_PID=$! + trap 'kill $KEEPALIVE_PID' EXIT +fi + +if [[ -z $KERNEL_COMMIT ]]; then + KERNEL_COMMIT=$(git ls-remote "$KERNEL_REPO" "refs/heads/$KERNEL_BRANCH" | awk '{print $1}') +fi +if [[ -z $KERNEL_COMMIT ]]; then + echo "failed to resolve kernel commit for $KERNEL_BRANCH" >&2 + exit 1 +fi + +KERNEL_VERSION=$( + curl -fsSL "https://raw.githubusercontent.com/raspberrypi/linux/$KERNEL_COMMIT/Makefile" | + awk -F' = ' ' + /^VERSION =/ {v=$2} + /^PATCHLEVEL =/ {p=$2} + /^SUBLEVEL =/ {s=$2} + END { if (v && p && s) print v "." p "." s }' +) +if [[ -z $KERNEL_VERSION ]]; then + echo "failed to determine kernel version from $KERNEL_COMMIT" >&2 + exit 1 +fi + +TARGET_VERSION="${KERNEL_VERSION}-${PKGREL}" +INSTALLED_VERSION=$(pacman -Qi linux-rpi 2>/dev/null | awk -F': ' '/Version/{print $2}') +if [[ -n $INSTALLED_VERSION && $INSTALLED_VERSION == "$TARGET_VERSION"* ]]; then + echo "linux-rpi already at $TARGET_VERSION" + exit 0 +fi + +echo "==> Building linux-rpi $KERNEL_VERSION ($KERNEL_COMMIT) pkgrel=$PKGREL" + +pacman -Sy --needed --noconfirm git bc kmod inetutils base-devel + +rm -rf "$BUILD_ROOT" +mkdir -p "$BUILD_ROOT" +chown "$BUILD_USER":"$BUILD_USER" "$BUILD_ROOT" + +sudo -u "$BUILD_USER" git clone --depth 1 "$PKGBUILD_REPO" "$BUILD_ROOT/PKGBUILDs" +cp -a "$BUILD_ROOT/PKGBUILDs/core/linux-rpi" "$BUILD_ROOT/linux-rpi" +chown -R "$BUILD_USER":"$BUILD_USER" "$BUILD_ROOT/linux-rpi" + +sudo -u "$BUILD_USER" bash -c " + set -euo pipefail + cd '$BUILD_ROOT/linux-rpi' + sed -i 's/^pkgver=.*/pkgver=$KERNEL_VERSION/' PKGBUILD + sed -i 's/^pkgrel=.*/pkgrel=$PKGREL/' PKGBUILD + sed -i 's/^_commit=.*/_commit=$KERNEL_COMMIT/' PKGBUILD + makepkg -g > /tmp/lesavka-kernel.sums +" + +sudo -u "$BUILD_USER" BUILD_ROOT="$BUILD_ROOT" python - <<'PY' +import re +from pathlib import Path +import os + +root = Path(os.environ["BUILD_ROOT"]) +pkgbuild = root / "linux-rpi" / "PKGBUILD" +sums = Path("/tmp/lesavka-kernel.sums").read_text().splitlines() +text = pkgbuild.read_text() +for line in sums: + if not line.startswith("sha256sums"): + continue + key = line.split("=", 1)[0] + text = re.sub(rf"^{re.escape(key)}=.*$", line, text, flags=re.M) +pkgbuild.write_text(text) +PY + +sudo -u "$BUILD_USER" bash -c " + set -euo pipefail + cd '$BUILD_ROOT/linux-rpi' + MAKEFLAGS='-j$JOBS' makepkg -s --noconfirm +" + +mapfile -t PKGS < <(ls "$BUILD_ROOT/linux-rpi"/*.pkg.tar.* 2>/dev/null) +if [[ ${#PKGS[@]} -eq 0 ]]; then + echo "no kernel packages built" >&2 + exit 1 +fi + +pacman -U --noconfirm "${PKGS[@]}" +echo "✅ linux-rpi upgraded to $TARGET_VERSION (reboot required)"