install: preserve uvc helper while attached

This commit is contained in:
Brad Stein 2026-05-15 02:07:07 -03:00
parent b47627ad5b
commit 80062c1b4b
6 changed files with 41 additions and 56 deletions

6
Cargo.lock generated
View File

@ -1652,7 +1652,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]] [[package]]
name = "lesavka_client" name = "lesavka_client"
version = "0.22.34" version = "0.22.35"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-stream", "async-stream",
@ -1686,7 +1686,7 @@ dependencies = [
[[package]] [[package]]
name = "lesavka_common" name = "lesavka_common"
version = "0.22.34" version = "0.22.35"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64", "base64",
@ -1698,7 +1698,7 @@ dependencies = [
[[package]] [[package]]
name = "lesavka_server" name = "lesavka_server"
version = "0.22.34" version = "0.22.35"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64", "base64",

View File

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

View File

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

View File

@ -976,28 +976,6 @@ restart_lesavka_server_only() {
validate_server_ready validate_server_ready
} }
restart_lesavka_uvc_helper_only() {
if [[ -n ${LESAVKA_DISABLE_UVC:-} ]]; then
echo "⚠️ UVC is disabled; skipping lesavka-uvc helper refresh." >&2
return 0
fi
if ! uvc_gadget_present; then
echo "⚠️ live UVC function is missing; skipping helper-only refresh until the next safe gadget rebuild." >&2
return 0
fi
if ! systemctl list-unit-files lesavka-uvc.service --no-pager --no-legend 2>/dev/null \
| grep -q '^lesavka-uvc\.service'; then
echo "⚠️ lesavka-uvc unit is not installed yet; skipping helper-only refresh." >&2
return 0
fi
sudo install -d -m 0755 /var/log/lesavka
sudo truncate -s 0 /var/log/lesavka/uvc.stderr
sudo systemctl reset-failed lesavka-uvc >/dev/null 2>&1 || true
sudo systemctl restart lesavka-uvc
echo "✅ lesavka-uvc helper restarted with updated binaries without cycling lesavka-core or the USB gadget."
}
normalize_hdmi_connector() { normalize_hdmi_connector() {
local name="$1" local name="$1"
if [[ $name =~ (HDMI-A-[0-9]+)$ ]]; then if [[ $name =~ (HDMI-A-[0-9]+)$ ]]; then
@ -1686,7 +1664,7 @@ if [[ "$HOST_GADGET_PROTECTED" == "1" ]] \
fi fi
if [[ "$ATTACHED_UVC_CHANGE_DEFERRED" == "1" ]]; then if [[ "$ATTACHED_UVC_CHANGE_DEFERRED" == "1" ]]; then
echo "⚠️ UVC/UAC runtime settings or live descriptors differ while the host is attached." >&2 echo "⚠️ UVC/UAC runtime settings or live descriptors differ while the host is attached." >&2
echo " The installer will not restart lesavka-core or rewrite attached USB descriptors because that can wedge the Pi USB controller." >&2 echo " The installer will not restart lesavka-core or lesavka-uvc because that can wedge the Pi USB controller." >&2
echo " To apply descriptor-changing UVC settings, use a maintenance window with LESAVKA_ALLOW_GADGET_RESET=1 LESAVKA_FORCE_GADGET_REBUILD=1." >&2 echo " To apply descriptor-changing UVC settings, use a maintenance window with LESAVKA_ALLOW_GADGET_RESET=1 LESAVKA_FORCE_GADGET_REBUILD=1." >&2
if lesavka_server_has_active_clients && [[ "${LESAVKA_INSTALL_RESTART_WITH_CLIENTS:-0}" != "1" ]]; then if lesavka_server_has_active_clients && [[ "${LESAVKA_INSTALL_RESTART_WITH_CLIENTS:-0}" != "1" ]]; then
echo "⚠️ Active gRPC clients are connected; leaving the running server untouched for this install." >&2 echo "⚠️ Active gRPC clients are connected; leaving the running server untouched for this install." >&2
@ -1704,8 +1682,8 @@ if [[ "$ATTACHED_UVC_CHANGE_DEFERRED" == "1" ]]; then
exit 0 exit 0
fi fi
echo "✅ No active gRPC clients detected; applying runtime env and restarting lesavka-server." >&2 echo "✅ No active gRPC clients detected; applying runtime env and restarting lesavka-server only." >&2
echo " Preserving lesavka-core and live USB descriptors; refreshing only the user-space UVC helper if the live UVC function exists." >&2 echo " Preserving lesavka-core and lesavka-uvc so the attached USB gadget is not cycled." >&2
sudo install -m 0644 "$SERVER_ENV_TMP" /etc/lesavka/server.env sudo install -m 0644 "$SERVER_ENV_TMP" /etc/lesavka/server.env
sudo install -m 0644 "$UVC_ENV_TMP" /etc/lesavka/uvc.env sudo install -m 0644 "$UVC_ENV_TMP" /etc/lesavka/uvc.env
rm -f "$SERVER_ENV_TMP" "$UVC_ENV_TMP" rm -f "$SERVER_ENV_TMP" "$UVC_ENV_TMP"
@ -1715,12 +1693,11 @@ if [[ "$ATTACHED_UVC_CHANGE_DEFERRED" == "1" ]]; then
sudo systemctl daemon-reload sudo systemctl daemon-reload
sudo systemctl enable lesavka-server >/dev/null 2>&1 || true sudo systemctl enable lesavka-server >/dev/null 2>&1 || true
restart_lesavka_server_only restart_lesavka_server_only
restart_lesavka_uvc_helper_only
sudo /usr/local/bin/lesavka-recovery-ladder snapshot || true sudo /usr/local/bin/lesavka-recovery-ladder snapshot || true
INSTALLED_VERSION=$(manifest_package_version "$SRC_DIR/server/Cargo.toml" 2>/dev/null || true) INSTALLED_VERSION=$(manifest_package_version "$SRC_DIR/server/Cargo.toml" 2>/dev/null || true)
INSTALLED_SHA=$(git -C "$SCRIPT_REPO_ROOT" rev-parse --short HEAD 2>/dev/null || true) INSTALLED_SHA=$(git -C "$SCRIPT_REPO_ROOT" rev-parse --short HEAD 2>/dev/null || true)
sudo systemctl start lesavka-recovery-ladder.timer >/dev/null 2>&1 || true sudo systemctl start lesavka-recovery-ladder.timer >/dev/null 2>&1 || true
echo "✅ lesavka-server binaries, runtime env, and server unit installed; server restart plus helper-only UVC refresh completed." echo "✅ lesavka-server binaries, runtime env, and server unit installed; server-only restart completed."
echo "➡️ Installed and running: lesavka-server ${INSTALLED_VERSION:-unknown}${INSTALLED_SHA:+ ($INSTALLED_SHA)}" echo "➡️ Installed and running: lesavka-server ${INSTALLED_VERSION:-unknown}${INSTALLED_SHA:+ ($INSTALLED_SHA)}"
echo "➡️ Recovery ladder: installed, enabled, and started in server-only mode" echo "➡️ Recovery ladder: installed, enabled, and started in server-only mode"
echo "➡️ UVC codec request persisted for the next safe gadget rebuild: ${INSTALL_UVC_CODEC}" echo "➡️ UVC codec request persisted for the next safe gadget rebuild: ${INSTALL_UVC_CODEC}"
@ -1732,8 +1709,16 @@ sudo install -m 0644 "$UVC_ENV_TMP" /etc/lesavka/uvc.env
rm -f "$SERVER_ENV_TMP" "$UVC_ENV_TMP" rm -f "$SERVER_ENV_TMP" "$UVC_ENV_TMP"
if [[ "$ATTACHED_UVC_RESTART_DEFERRED" == "1" ]]; then if [[ "$ATTACHED_UVC_RESTART_DEFERRED" == "1" ]]; then
echo "⚠️ UVC runtime env changed while the host is attached, but the requested descriptor matches the live gadget." >&2 echo "⚠️ UVC runtime env changed while the host is attached, but the requested descriptor matches the live gadget." >&2
echo " The installer will not rebuild lesavka-core or attached USB descriptors." >&2 echo " Updated /etc/lesavka/server.env and /etc/lesavka/uvc.env without restarting live services." >&2
echo " It will refresh the user-space lesavka-uvc helper after unit files are updated so helper-only fixes become live." >&2 echo " Rerun the installer with the same UVC settings to restart services after the env is stable, or use LESAVKA_ALLOW_GADGET_RESET=1 LESAVKA_FORCE_GADGET_REBUILD=1 during a maintenance window." >&2
INSTALLED_VERSION=$(manifest_package_version "$SRC_DIR/server/Cargo.toml" 2>/dev/null || true)
INSTALLED_SHA=$(git -C "$SCRIPT_REPO_ROOT" rev-parse --short HEAD 2>/dev/null || true)
sudo systemctl start lesavka-recovery-ladder.timer >/dev/null 2>&1 || true
echo "✅ lesavka-server binaries and runtime env installed; live service restart deferred for attached-gadget safety."
echo "➡️ Installed binaries: lesavka-server ${INSTALLED_VERSION:-unknown}${INSTALLED_SHA:+ ($INSTALLED_SHA)}"
echo "➡️ Recovery ladder: installed, enabled, and started in server-only mode"
echo "➡️ Deferred restart UVC codec: ${INSTALL_UVC_CODEC}"
exit 0
fi fi
echo "==> 6a. Systemd units - lesavka-core" echo "==> 6a. Systemd units - lesavka-core"
@ -1774,8 +1759,7 @@ if [[ -z ${LESAVKA_DISABLE_UVC:-} ]] && ! uvc_gadget_present; then
GADGET_REBUILD_REASON="UVC function is missing from the live gadget" 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." echo "⚠️ UVC function is missing from the live gadget; forcing a rebuild before server start."
fi fi
if [[ "$UVC_ENV_CHANGED" == "1" ]] && [[ "$HOST_GADGET_PROTECTED" == "1" ]] \ if [[ "$UVC_ENV_CHANGED" == "1" ]] && [[ "$HOST_GADGET_PROTECTED" == "1" ]]; then
&& { [[ "$LIVE_UVC_DESCRIPTOR_MISMATCH" == "1" ]] || [[ "$LIVE_UAC_DESCRIPTOR_MISMATCH" == "1" ]] || [[ "$LIVE_UVC_FUNCTION_MISSING" == "1" ]]; }; then
FORCE_GADGET_REBUILD=1 FORCE_GADGET_REBUILD=1
GADGET_REBUILD_REASON="UVC runtime settings changed while the host is attached" 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." echo "⚠️ UVC runtime settings changed while the host is attached; forcing a gadget rebuild so the new descriptors take effect."
@ -1870,7 +1854,7 @@ if [[ "$HOST_GADGET_PROTECTED" == "1" ]] \
CAN_TOUCH_UVC_SERVICE=0 CAN_TOUCH_UVC_SERVICE=0
fi fi
if [[ "$CAN_TOUCH_UVC_SERVICE" != "1" ]] && systemctl is-active --quiet lesavka-uvc; then if [[ "$CAN_TOUCH_UVC_SERVICE" != "1" ]] && systemctl is-active --quiet lesavka-uvc; then
restart_lesavka_uvc_helper_only echo "✅ lesavka-uvc already active; preserving it during attached-gadget version update."
elif [[ "$CAN_TOUCH_UVC_SERVICE" != "1" ]]; then elif [[ "$CAN_TOUCH_UVC_SERVICE" != "1" ]]; then
echo "⚠️ lesavka-uvc is inactive, but the host/gadget is protected; not starting it during a version-only install." >&2 echo "⚠️ lesavka-uvc is inactive, but the host/gadget is protected; not starting it during a version-only install." >&2
elif [[ "$UVC_ENV_CHANGED" == "1" ]] && systemctl is-active --quiet lesavka-uvc; then elif [[ "$UVC_ENV_CHANGED" == "1" ]] && systemctl is-active --quiet lesavka-uvc; then

View File

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

View File

@ -194,7 +194,6 @@ fn server_install_pins_hdmi_camera_and_display_defaults() {
assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_WIDTH 1280")); assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_WIDTH 1280"));
assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_HEIGHT 720")); assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_HEIGHT 720"));
assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_CONTROL_READ_ONLY 0")); assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_CONTROL_READ_ONLY 0"));
assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_QUEUE_PACING 1"));
assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_BULK 1")); assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_BULK 1"));
assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_FRAME_SIZE_GUARD 1")); assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_FRAME_SIZE_GUARD 1"));
assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_FRAME_MAX_BYTES 0")); assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_FRAME_MAX_BYTES 0"));
@ -315,16 +314,16 @@ fn server_install_pins_hdmi_camera_and_display_defaults() {
); );
assert!( assert!(
SERVER_INSTALL SERVER_INSTALL
.contains("No active gRPC clients detected; applying runtime env and restarting lesavka-server") .contains("No active gRPC clients detected; applying runtime env and restarting lesavka-server only")
&& SERVER_INSTALL && SERVER_INSTALL
.contains("Preserving lesavka-core and live USB descriptors; refreshing only the user-space UVC helper") .contains("Preserving lesavka-core and lesavka-uvc so the attached USB gadget is not cycled")
&& SERVER_INSTALL.contains("restart_lesavka_server_only"), && SERVER_INSTALL.contains("restart_lesavka_server_only"),
"idle attached-gadget installs should leave the newest server and UVC helper running without cycling USB gadget descriptors" "idle attached-gadget installs should leave the newest server running without cycling USB gadget services"
); );
assert!( assert!(
SERVER_INSTALL.contains("server restart plus helper-only UVC refresh completed") SERVER_INSTALL.contains("server-only restart completed")
&& SERVER_INSTALL.contains("Live UVC descriptors may still differ"), && SERVER_INSTALL.contains("Live UVC descriptors may still differ"),
"idle attached-gadget installs should be explicit about server/helper readiness and deferred descriptor rebuilds" "idle attached-gadget installs should be explicit about server readiness and deferred descriptor rebuilds"
); );
assert!( assert!(
SERVER_INSTALL SERVER_INSTALL
@ -363,13 +362,11 @@ fn server_install_pins_hdmi_camera_and_display_defaults() {
); );
assert!( assert!(
SERVER_INSTALL.contains("ATTACHED_UVC_RESTART_DEFERRED=1"), SERVER_INSTALL.contains("ATTACHED_UVC_RESTART_DEFERRED=1"),
"attached UVC env changes that match the live descriptor should take the helper-only refresh path" "attached UVC env changes that match the live descriptor should still defer service restarts"
); );
assert!( assert!(
SERVER_INSTALL.contains( SERVER_INSTALL.contains("Updated /etc/lesavka/server.env and /etc/lesavka/uvc.env without restarting live services"),
"It will refresh the user-space lesavka-uvc helper after unit files are updated" "safe env repairs should not immediately restart the attached gadget path"
),
"safe env repairs should refresh the helper without rebuilding the attached gadget"
); );
assert!( assert!(
SERVER_INSTALL.contains("dwDefaultFrameInterval"), SERVER_INSTALL.contains("dwDefaultFrameInterval"),
@ -396,16 +393,16 @@ fn server_install_pins_hdmi_camera_and_display_defaults() {
< SERVER_INSTALL < SERVER_INSTALL
.find("if [[ \"$ATTACHED_UVC_RESTART_DEFERRED\" == \"1\" ]]") .find("if [[ \"$ATTACHED_UVC_RESTART_DEFERRED\" == \"1\" ]]")
.unwrap(), .unwrap(),
"safe env repair should install env files before choosing the helper-only refresh path" "safe env repair should install env files before exiting without service restarts"
); );
assert!( assert!(
SERVER_INSTALL SERVER_INSTALL
.find("if [[ \"$ATTACHED_UVC_RESTART_DEFERRED\" == \"1\" ]]") .find("if [[ \"$ATTACHED_UVC_RESTART_DEFERRED\" == \"1\" ]]")
.unwrap() .unwrap()
< SERVER_INSTALL < SERVER_INSTALL
.rfind("restart_lesavka_uvc_helper_only") .find("sudo systemctl restart lesavka-uvc")
.unwrap(), .unwrap(),
"safe env repair should reach the helper-only restart after unit files are refreshed" "safe env repair must exit before live UVC helper restarts"
); );
assert!( assert!(
SERVER_INSTALL SERVER_INSTALL
@ -421,9 +418,9 @@ fn server_install_pins_hdmi_camera_and_display_defaults() {
.find("ATTACHED_UVC_CHANGE_DEFERRED=1") .find("ATTACHED_UVC_CHANGE_DEFERRED=1")
.unwrap() .unwrap()
< SERVER_INSTALL < SERVER_INSTALL
.rfind("restart_lesavka_uvc_helper_only") .find("sudo systemctl restart lesavka-uvc")
.unwrap(), .unwrap(),
"attached UVC deferral must run before any helper-only refresh" "attached UVC deferral must run before any live UVC helper restart"
); );
assert!( assert!(
SERVER_INSTALL.contains("[[ \"$EXPLICIT_GADGET_REBUILD\" != \"1\" ]] || [[ -z ${LESAVKA_ALLOW_GADGET_RESET:-} ]]"), SERVER_INSTALL.contains("[[ \"$EXPLICIT_GADGET_REBUILD\" != \"1\" ]] || [[ -z ${LESAVKA_ALLOW_GADGET_RESET:-} ]]"),
@ -473,9 +470,13 @@ fn server_install_pins_hdmi_camera_and_display_defaults() {
); );
assert!( assert!(
SERVER_INSTALL.contains("CAN_TOUCH_UVC_SERVICE=0") SERVER_INSTALL.contains("CAN_TOUCH_UVC_SERVICE=0")
&& SERVER_INSTALL.contains("restart_lesavka_uvc_helper_only") && SERVER_INSTALL.contains("preserving it during attached-gadget version update")
&& SERVER_INSTALL.contains("not starting it during a version-only install"), && SERVER_INSTALL.contains("not starting it during a version-only install"),
"ordinary attached-gadget version updates should refresh an active UVC helper without starting an inactive one" "ordinary attached-gadget version updates must not start or restart the UVC helper"
);
assert!(
!SERVER_INSTALL.contains("restart_lesavka_uvc_helper_only"),
"the UVC helper must not be treated as safe to refresh independently while the host is attached"
); );
assert!( assert!(
SERVER_INSTALL.contains("sudo systemctl start lesavka-uvc"), SERVER_INSTALL.contains("sudo systemctl start lesavka-uvc"),