fix(sync): rebuild incomplete uvc gadgets

This commit is contained in:
Brad Stein 2026-04-27 23:04:18 -03:00
parent 5173e3cea7
commit e89271e46c
8 changed files with 48 additions and 10 deletions

6
Cargo.lock generated
View File

@ -1642,7 +1642,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "lesavka_client"
version = "0.14.33"
version = "0.14.34"
dependencies = [
"anyhow",
"async-stream",
@ -1676,7 +1676,7 @@ dependencies = [
[[package]]
name = "lesavka_common"
version = "0.14.33"
version = "0.14.34"
dependencies = [
"anyhow",
"base64",
@ -1688,7 +1688,7 @@ dependencies = [
[[package]]
name = "lesavka_server"
version = "0.14.33"
version = "0.14.34"
dependencies = [
"anyhow",
"base64",

View File

@ -4,7 +4,7 @@ path = "src/main.rs"
[package]
name = "lesavka_client"
version = "0.14.33"
version = "0.14.34"
edition = "2024"
[dependencies]

View File

@ -1,6 +1,6 @@
[package]
name = "lesavka_common"
version = "0.14.33"
version = "0.14.34"
edition = "2024"
build = "build.rs"

View File

@ -324,6 +324,7 @@ fi
log "✅ UDC detected: $UDC"
# If a gadget is already configured, avoid tearing it down unless forced.
GADGET_NEEDS_REBUILD=0
if [[ -d $G && -z $ALLOW_RESET ]]; then
if [[ -s $G/UDC || -d $G/configs/c.1 ]]; then
if gadget_has_expected_functions; then
@ -333,6 +334,7 @@ if [[ -d $G && -z $ALLOW_RESET ]]; then
exit 0
fi
log "⚠️ gadget is configured but missing expected functions; continuing toward rebuild."
GADGET_NEEDS_REBUILD=1
fi
fi
@ -341,7 +343,7 @@ BOUND_UDC=""
if [[ -r $G/UDC ]]; then
BOUND_UDC=$(cat "$G/UDC" 2>/dev/null || true)
fi
if [[ -n $BOUND_UDC && -z $ALLOW_RESET ]]; then
if [[ -n $BOUND_UDC && -z $ALLOW_RESET && "$GADGET_NEEDS_REBUILD" != "1" ]]; then
log "🔒 gadget already bound to '$BOUND_UDC' - refusing reset."
log " Set LESAVKA_ALLOW_GADGET_RESET=1 to force."
exit 0
@ -349,11 +351,14 @@ fi
# Guard against lockups: don't reset gadget while host is attached unless forced.
UDC_STATE="$(udc_state "$UDC")"
if [[ -z $ALLOW_RESET ]] && is_attached_state "$UDC_STATE"; then
if [[ -z $ALLOW_RESET ]] && is_attached_state "$UDC_STATE" && [[ "$GADGET_NEEDS_REBUILD" != "1" ]]; then
log "🔒 UDC state is '$UDC_STATE' - refusing gadget reset while host attached."
log " Set LESAVKA_ALLOW_GADGET_RESET=1 to force."
exit 0
fi
if [[ "$GADGET_NEEDS_REBUILD" == "1" ]]; then
log "♻️ incomplete gadget detected; rebuilding even though the old gadget is still bound."
fi
#──────────────────────────────────────────────────
# 3. (Re)create gadget

View File

@ -774,8 +774,8 @@ if [[ -n ${LESAVKA_ALLOW_GADGET_RESET:-} ]] || [[ "$FORCE_GADGET_REBUILD" == "1"
LESAVKA_UVC_FALLBACK=0 \
LESAVKA_UVC_CODEC="${INSTALL_UVC_CODEC}" \
/usr/local/bin/lesavka-core.sh
sudo systemctl restart lesavka-core
echo "✅ lesavka-core installed and restarted..."
sudo systemctl reset-failed lesavka-core >/dev/null 2>&1 || true
echo "✅ lesavka-core gadget rebuilt directly."
else
echo "⚠️ UDC state is '$UDC_STATE' - skipping lesavka-core restart."
echo " Set LESAVKA_ALLOW_GADGET_RESET=1 to force."

View File

@ -10,7 +10,7 @@ bench = false
[package]
name = "lesavka_server"
version = "0.14.33"
version = "0.14.34"
edition = "2024"
autobins = false

View File

@ -0,0 +1,25 @@
//! Contract tests for lesavka-core gadget rebuild guardrails.
//!
//! Scope: statically guard the shell logic in `scripts/daemon/lesavka-core.sh`.
//! Targets: incomplete gadget recovery behavior.
//! Why: the gadget can be bound but half-applied, and the core script must
//! rebuild that state instead of refusing reset forever.
const CORE_SCRIPT: &str = include_str!("../../scripts/daemon/lesavka-core.sh");
#[test]
fn core_script_rebuilds_incomplete_bound_gadgets() {
for expected in [
"GADGET_NEEDS_REBUILD=0",
"gadget is configured but missing expected functions; continuing toward rebuild.",
"GADGET_NEEDS_REBUILD=1",
"if [[ -n $BOUND_UDC && -z $ALLOW_RESET && \"$GADGET_NEEDS_REBUILD\" != \"1\" ]]; then",
"if [[ -z $ALLOW_RESET ]] && is_attached_state \"$UDC_STATE\" && [[ \"$GADGET_NEEDS_REBUILD\" != \"1\" ]]; then",
"incomplete gadget detected; rebuilding even though the old gadget is still bound.",
] {
assert!(
CORE_SCRIPT.contains(expected),
"lesavka-core guardrail missing: {expected}"
);
}
}

View File

@ -78,6 +78,14 @@ fn server_install_pins_hdmi_camera_and_display_defaults() {
SERVER_INSTALL.contains("UVC function is missing from the live gadget; forcing a rebuild before server start."),
"install script should force a rebuild when the live gadget is attached but missing UVC"
);
assert!(
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("sudo systemctl restart lesavka-core"),
"install script should not immediately rerun lesavka-core after a successful direct forced rebuild"
);
assert!(
SERVER_INSTALL.contains("video-output node did not appear after rebuild"),
"install script should explain why it refuses a half-applied UVC install"