From e213dad9ce3f6e8b8ad0c5d75d4a911e77542a6c Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Thu, 30 Apr 2026 15:47:07 -0300 Subject: [PATCH] fix(installer): avoid unnecessary gadget resets --- Cargo.lock | 6 +-- client/Cargo.toml | 2 +- common/Cargo.toml | 2 +- scripts/install/server.sh | 39 ++++++++++++++----- server/Cargo.toml | 2 +- .../tests/server_install_script_contract.rs | 16 ++++++++ 6 files changed, 51 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6a01502..78fd977 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1652,7 +1652,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "lesavka_client" -version = "0.16.5" +version = "0.16.6" dependencies = [ "anyhow", "async-stream", @@ -1686,7 +1686,7 @@ dependencies = [ [[package]] name = "lesavka_common" -version = "0.16.5" +version = "0.16.6" dependencies = [ "anyhow", "base64", @@ -1698,7 +1698,7 @@ dependencies = [ [[package]] name = "lesavka_server" -version = "0.16.5" +version = "0.16.6" dependencies = [ "anyhow", "base64", diff --git a/client/Cargo.toml b/client/Cargo.toml index 41a9fdc..06542a5 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -4,7 +4,7 @@ path = "src/main.rs" [package] name = "lesavka_client" -version = "0.16.5" +version = "0.16.6" edition = "2024" [dependencies] diff --git a/common/Cargo.toml b/common/Cargo.toml index 006346a..c41729e 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lesavka_common" -version = "0.16.5" +version = "0.16.6" edition = "2024" build = "build.rs" diff --git a/scripts/install/server.sh b/scripts/install/server.sh index 7d190d4..ea885a5 100755 --- a/scripts/install/server.sh +++ b/scripts/install/server.sh @@ -1039,32 +1039,51 @@ sudo systemctl enable lesavka-core lesavka-server UDC_STATE=$(udc_state) FORCE_GADGET_REBUILD=0 +GADGET_REBUILD_REASON="" if [[ -z ${LESAVKA_DISABLE_UVC:-} ]] && ! uvc_gadget_present; then FORCE_GADGET_REBUILD=1 + GADGET_REBUILD_REASON="UVC function is missing from the live gadget" echo "⚠️ UVC function is missing from the live gadget; forcing a rebuild before server start." fi if [[ "$UVC_ENV_CHANGED" == "1" ]] && is_attached_state "$UDC_STATE"; then FORCE_GADGET_REBUILD=1 + GADGET_REBUILD_REASON="UVC runtime settings changed while the host is attached" echo "⚠️ UVC runtime settings changed while the host is attached; forcing a gadget rebuild so the new descriptors take effect." fi -if [[ -n ${LESAVKA_ALLOW_GADGET_RESET:-} ]] || [[ "$FORCE_GADGET_REBUILD" == "1" ]] || ! is_attached_state "$UDC_STATE"; then +if [[ -n ${LESAVKA_FORCE_GADGET_REBUILD:-} ]]; then + FORCE_GADGET_REBUILD=1 + GADGET_REBUILD_REASON="explicit LESAVKA_FORCE_GADGET_REBUILD request" + echo "⚠️ explicit LESAVKA_FORCE_GADGET_REBUILD request; forcing a gadget rebuild before server start." +fi +if [[ "$FORCE_GADGET_REBUILD" == "1" && -z ${LESAVKA_ALLOW_GADGET_RESET:-} ]] && is_attached_state "$UDC_STATE"; then + echo "⚠️ ${GADGET_REBUILD_REASON:-Gadget state} requires a rebuild, but UDC state is '$UDC_STATE' and hard reset was not allowed." >&2 + echo " Preserving the attached gadget to avoid wedging the Pi USB controller." >&2 + echo " Run during a maintenance window with LESAVKA_ALLOW_GADGET_RESET=1 LESAVKA_FORCE_GADGET_REBUILD=1 if a hard rebuild is required." >&2 + FORCE_GADGET_REBUILD=0 +fi +if [[ "$FORCE_GADGET_REBUILD" == "1" ]] || ! is_attached_state "$UDC_STATE"; then echo "⚠️ UDC state is '$UDC_STATE' - forcing a Lesavka gadget rebuild before server start." sudo systemctl stop lesavka-server >/dev/null 2>&1 || true sudo systemctl stop lesavka-uvc >/dev/null 2>&1 || true sudo systemctl reset-failed lesavka-uvc >/dev/null 2>&1 || true - sudo env \ - LESAVKA_ALLOW_GADGET_RESET=1 \ - LESAVKA_ATTACH_WRITE_UDC=1 \ - LESAVKA_DETACH_CLEAR_UDC=1 \ - LESAVKA_RELOAD_UVCVIDEO=1 \ - LESAVKA_UVC_FALLBACK=0 \ - LESAVKA_UVC_CODEC="${INSTALL_UVC_CODEC}" \ - /usr/local/bin/lesavka-core.sh + CORE_REBUILD_ENV=( + "LESAVKA_ALLOW_GADGET_RESET=1" + "LESAVKA_ATTACH_WRITE_UDC=1" + "LESAVKA_DETACH_CLEAR_UDC=1" + "LESAVKA_UVC_FALLBACK=0" + "LESAVKA_UVC_CODEC=${INSTALL_UVC_CODEC}" + ) + if [[ -n ${LESAVKA_RELOAD_UVCVIDEO:-} ]]; then + CORE_REBUILD_ENV+=("LESAVKA_RELOAD_UVCVIDEO=${LESAVKA_RELOAD_UVCVIDEO}") + fi + sudo env "${CORE_REBUILD_ENV[@]}" /usr/local/bin/lesavka-core.sh sudo systemctl reset-failed lesavka-core >/dev/null 2>&1 || true echo "✅ lesavka-core gadget rebuilt directly." +elif [[ -n ${LESAVKA_ALLOW_GADGET_RESET:-} ]]; then + echo "✅ UDC state is '$UDC_STATE'; LESAVKA_ALLOW_GADGET_RESET permits recovery, but no hard gadget rebuild is needed." else echo "⚠️ UDC state is '$UDC_STATE' - skipping lesavka-core restart." - echo " Set LESAVKA_ALLOW_GADGET_RESET=1 to force." + echo " Set LESAVKA_ALLOW_GADGET_RESET=1 LESAVKA_FORCE_GADGET_REBUILD=1 to force during a maintenance window." fi cat <<'UNIT' | sudo tee /etc/systemd/system/lesavka-uvc.service >/dev/null diff --git a/server/Cargo.toml b/server/Cargo.toml index 13e5eec..e8d99da 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -10,7 +10,7 @@ bench = false [package] name = "lesavka_server" -version = "0.16.5" +version = "0.16.6" edition = "2024" autobins = false diff --git a/testing/tests/server_install_script_contract.rs b/testing/tests/server_install_script_contract.rs index 03d5fa0..e7d70d9 100644 --- a/testing/tests/server_install_script_contract.rs +++ b/testing/tests/server_install_script_contract.rs @@ -98,6 +98,22 @@ fn server_install_pins_hdmi_camera_and_display_defaults() { SERVER_INSTALL.contains("lesavka-core gadget rebuilt directly."), "install script should trust the direct forced rebuild instead of immediately rerunning the oneshot core unit" ); + assert!( + SERVER_INSTALL.contains("LESAVKA_FORCE_GADGET_REBUILD"), + "install script should require an explicit force knob for attached-host gadget rebuilds" + ); + assert!( + SERVER_INSTALL.contains("no hard gadget rebuild is needed"), + "LESAVKA_ALLOW_GADGET_RESET should permit recovery without causing unconditional hard rebuilds" + ); + assert!( + !SERVER_INSTALL.contains("[[ -n ${LESAVKA_ALLOW_GADGET_RESET:-} ]] || [[ \"$FORCE_GADGET_REBUILD\" == \"1\" ]]"), + "LESAVKA_ALLOW_GADGET_RESET must not itself trigger a hard UDC detach/rebind" + ); + assert!( + !SERVER_INSTALL.contains("LESAVKA_RELOAD_UVCVIDEO=1"), + "install script should not unload/reload uvcvideo during ordinary server installs" + ); assert!( SERVER_INSTALL.contains("sudo systemctl stop lesavka-uvc"), "install script should stop the UVC helper before directly rebuilding the gadget underneath it"