#!/usr/bin/env bash
# scripts/install/server.sh - install and setup all server related apps and environments
set -euo pipefail
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
SCRIPT_REPO_ROOT=$(cd -- "$SCRIPT_DIR/../.." && pwd)
DEFAULT_REPO_URL=ssh://git@scm.bstein.dev:2242/bstein/lesavka.git
export TMPDIR=${TMPDIR:-/var/tmp}

resolve_install_user() {
  if [[ -n ${SUDO_USER:-} && ${SUDO_USER} != root ]]; then
    printf '%s\n' "$SUDO_USER"
    return 0
  fi

  local current_user
  current_user=$(id -un)
  if [[ $current_user != root ]]; then
    printf '%s\n' "$current_user"
    return 0
  fi

  local owner_uid owner_user
  owner_uid=$(stat -c '%u' "$SCRIPT_REPO_ROOT" 2>/dev/null || true)
  if [[ -n $owner_uid && $owner_uid != 0 ]]; then
    owner_user=$(getent passwd "$owner_uid" | cut -d: -f1)
    if [[ -n $owner_user ]]; then
      printf '%s\n' "$owner_user"
      return 0
    fi
  fi

  printf 'root\n'
}

ORIG_USER=$(resolve_install_user)

persisted_env_value() {
  local file=$1
  local key=$2
  [[ -r $file ]] || return 1
  awk -F= -v key="$key" '
    $0 ~ /^[[:space:]]*#/ { next }
    $1 == key {
      sub(/^[^=]*=/, "")
      print
      found=1
      exit
    }
    END { if (!found) exit 1 }
  ' "$file"
}

persisted_uvc_value() {
  persisted_env_value /etc/lesavka/uvc.env "$1"
}

normalize_uvc_codec() {
  local raw=${1:-mjpeg}
  case "${raw,,}" in
    mjpeg|mjpg|jpeg|"")
      echo "mjpeg"
      ;;
    *)
      echo "mjpeg"
      ;;
  esac
}

normalize_cam_codec() {
  local raw=${1:-mjpeg}
  case "${raw,,}" in
    mjpeg|mjpg|jpeg|"")
      echo "mjpeg"
      ;;
    hevc|h265|h.265)
      echo "hevc"
      ;;
    *)
      echo "❌ unsupported upstream camera codec '${raw}'. Use mjpeg or hevc." >&2
      exit 1
      ;;
  esac
}

uvc_env_value() {
  local key=$1
  local default=$2
  if [[ -n ${!key+x} ]]; then
    printf '%s\n' "${!key}"
    return 0
  fi
  persisted_uvc_value "$key" && return 0
  printf '%s\n' "$default"
}

REF=${LESAVKA_REF:-master}        # fallback
REPO_URL=${LESAVKA_REPO_URL:-}
INSTALL_SOURCE=${LESAVKA_INSTALL_SOURCE:-auto}
USER_HOME=$(getent passwd "$ORIG_USER" | cut -d: -f6)
PERSISTED_UVC_CODEC=$(persisted_uvc_value LESAVKA_UVC_CODEC || true)
REQUESTED_UVC_CODEC=${LESAVKA_INSTALL_UVC_CODEC:-${PERSISTED_UVC_CODEC:-mjpeg}}
INSTALL_UVC_CODEC=$(normalize_uvc_codec "$REQUESTED_UVC_CODEC")
if [[ "${REQUESTED_UVC_CODEC,,}" != "${INSTALL_UVC_CODEC}" ]]; then
  echo "⚠️  UVC gadget output codec '${REQUESTED_UVC_CODEC}' is not supported by the MJPEG UVC helper; using '${INSTALL_UVC_CODEC}' for the host-facing gadget."
  echo "   Use LESAVKA_INSTALL_CAM_CODEC=hevc to choose HEVC for the client-to-server upstream transport."
fi
INSTALL_CAM_CODEC_EXPLICIT=0
if [[ -n "${LESAVKA_INSTALL_CAM_CODEC+x}" || -n "${LESAVKA_CAM_CODEC+x}" ]]; then
  INSTALL_CAM_CODEC_EXPLICIT=1
fi
REQUESTED_CAM_CODEC=${LESAVKA_INSTALL_CAM_CODEC:-${LESAVKA_CAM_CODEC:-mjpeg}}
INSTALL_CAM_CODEC=$(normalize_cam_codec "${REQUESTED_CAM_CODEC}")
INSTALL_HEVC_DECODER=${LESAVKA_INSTALL_HEVC_DECODER:-${LESAVKA_HEVC_DECODER:-}}
INSTALL_ALLOW_SOFTWARE_VIDEO=${LESAVKA_INSTALL_ALLOW_SOFTWARE_VIDEO:-${LESAVKA_ALLOW_SOFTWARE_VIDEO:-0}}
INSTALL_UPLINK_AUDIO_CODEC=${LESAVKA_INSTALL_UPLINK_AUDIO_CODEC:-${LESAVKA_UPLINK_AUDIO_CODEC:-pcm}}
INSTALL_UVC_FRAME_META=${LESAVKA_INSTALL_UVC_FRAME_META:-${LESAVKA_UVC_FRAME_META:-0}}
INSTALL_UVC_FRAME_META_LOG_PATH=${LESAVKA_INSTALL_UVC_FRAME_META_LOG_PATH:-${LESAVKA_UVC_FRAME_META_LOG_PATH:-/tmp/lesavka-uvc-frame-meta.jsonl}}
INSTALL_SERVER_BIND_ADDR=${LESAVKA_INSTALL_SERVER_BIND_ADDR:-0.0.0.0:50051}
LESAVKA_TLS_DIR=${LESAVKA_TLS_DIR:-/etc/lesavka/pki}
LESAVKA_CLIENT_BUNDLE=${LESAVKA_CLIENT_BUNDLE:-/etc/lesavka/lesavka-client-pki.tar.gz}
LESAVKA_SYNTHETIC_PKI_DIR=${LESAVKA_SYNTHETIC_PKI_DIR:-$USER_HOME/.config/lesavka/pki}
LESAVKA_PASTE_KEY_FILE=${LESAVKA_PASTE_KEY_FILE:-/etc/lesavka/paste-key}
DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=0
DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=135090
DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=0,1280x720@30=0,1920x1080@20=0,1920x1080@30=0
DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=162659,1280x720@30=135090,1920x1080@20=160045,1920x1080@30=127952
DEFAULT_MJPEG_OPUS_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=-34199
DEFAULT_MJPEG_OPUS_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=513850
DEFAULT_MJPEG_OPUS_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=-34199,1280x720@30=-34199,1920x1080@20=-34199,1920x1080@30=-34199
DEFAULT_MJPEG_OPUS_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=541419,1280x720@30=513850,1920x1080@20=538805,1920x1080@30=506712
DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=0
DEFAULT_HEVC_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=110000
DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=0,1280x720@30=0,1920x1080@20=0,1920x1080@30=0
DEFAULT_HEVC_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=173852,1280x720@30=110000,1920x1080@20=160045,1920x1080@30=127952
DEFAULT_MJPEG_PCM_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=$DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US
DEFAULT_MJPEG_PCM_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=$DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US
DEFAULT_MJPEG_PCM_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=$DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US
DEFAULT_MJPEG_PCM_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=$DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US
DEFAULT_HEVC_PCM_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=$DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US
DEFAULT_HEVC_PCM_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=$DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US
DEFAULT_HEVC_PCM_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=$DEFAULT_HEVC_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US
DEFAULT_HEVC_PCM_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=$DEFAULT_HEVC_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US
DEFAULT_HEVC_OPUS_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=$DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US
DEFAULT_HEVC_OPUS_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=$DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US
DEFAULT_HEVC_OPUS_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=$DEFAULT_HEVC_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US
DEFAULT_HEVC_OPUS_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=$DEFAULT_HEVC_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US
LEGACY_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=-45000
PREVIOUS_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=720000
PREVIOUS_TUNED_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=1260000
PREVIOUS_ZERO_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=0
PREVIOUS_DELAYED_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=350000
PREVIOUS_BROWSER_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=130000
PREVIOUS_SCALAR_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=170000
PREVIOUS_OVERSHOT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=1090000

lookup_mode_offset_us() {
  local mode=$1
  local offset_map=$2
  local entry key value
  IFS=',' read -r -a offset_entries <<<"${offset_map}"
  for entry in "${offset_entries[@]}"; do
    key=${entry%%=*}
    value=${entry#*=}
    if [[ "${key}" == "${mode}" && "${value}" =~ ^-?[0-9]+$ ]]; then
      printf '%s\n' "${value}"
      return 0
    fi
  done
  return 1
}

default_uvc_mode() {
  local width=${LESAVKA_UVC_WIDTH:-1280}
  local height=${LESAVKA_UVC_HEIGHT:-720}
  local fps=${LESAVKA_UVC_FPS:-20}
  printf '%sx%s@%s\n' "${width}" "${height}" "${fps}"
}

default_mjpeg_upstream_audio_playout_offset_us() {
  local mode
  mode=$(default_uvc_mode)
  lookup_mode_offset_us "${mode}" "$DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US" \
    || printf '%s\n' "$DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US"
}

default_mjpeg_upstream_video_playout_offset_us() {
  local mode
  mode=$(default_uvc_mode)
  lookup_mode_offset_us "${mode}" "$DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US" \
    || printf '%s\n' "$DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US"
}

resolve_upstream_audio_playout_offset_us() {
  local default_offset_us
  default_offset_us=$(default_mjpeg_upstream_audio_playout_offset_us)

  if [[ -n ${LESAVKA_INSTALL_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US:-} ]]; then
    printf '%s\n' "$LESAVKA_INSTALL_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US"
    return 0
  fi

  if [[ ${LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US:-} == "$LEGACY_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US" || ${LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US:-} == "$PREVIOUS_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US" || ${LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US:-} == "$PREVIOUS_TUNED_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US" ]]; then
    echo "⚠️  migrating stale upstream audio playout offset to the per-mode MJPEG/UAC default." >&2
    echo "    Use LESAVKA_INSTALL_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US to intentionally keep an older value." >&2
    printf '%s\n' "$default_offset_us"
    return 0
  fi

  if [[ -n ${LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US:-} ]]; then
    printf '%s\n' "$LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US"
    return 0
  fi

  printf '%s\n' "$default_offset_us"
}

resolve_upstream_video_playout_offset_us() {
  local default_offset_us
  default_offset_us=$(default_mjpeg_upstream_video_playout_offset_us)

  if [[ -n ${LESAVKA_INSTALL_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US:-} ]]; then
    printf '%s\n' "$LESAVKA_INSTALL_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US"
    return 0
  fi

  if [[ ${LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US:-} == "$PREVIOUS_ZERO_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US" || ${LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US:-} == "$PREVIOUS_DELAYED_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US" || ${LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US:-} == "$PREVIOUS_BROWSER_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US" || ${LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US:-} == "$PREVIOUS_SCALAR_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US" || ${LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US:-} == "$PREVIOUS_OVERSHOT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US" ]]; then
    echo "⚠️  migrating stale upstream video playout offset to the per-mode direct UVC/UAC MJPEG sync center." >&2
    echo "    Use LESAVKA_INSTALL_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US to intentionally keep an older value." >&2
    printf '%s\n' "$default_offset_us"
    return 0
  fi

  if [[ -n ${LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US:-} ]]; then
    printf '%s\n' "$LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US"
    return 0
  fi

  printf '%s\n' "$default_offset_us"
}

ensure_hevc_decode_support() {
  echo "==> 1e. HEVC/H.265 decode support"

  if sudo modprobe rpi_hevc_dec >/dev/null 2>&1; then
    echo "   ↪ rpi_hevc_dec kernel module loaded"
    echo 'rpi_hevc_dec' | sudo tee /etc/modules-load.d/lesavka-hevc.conf >/dev/null
  else
    echo "   ↪ rpi_hevc_dec kernel module unavailable; HEVC decode will require another hardware decoder"
  fi

  if getent group video >/dev/null 2>&1 && [ -n "${ORIG_USER:-}" ] && [ "${ORIG_USER}" != "root" ]; then
    sudo usermod -aG video "${ORIG_USER}" || true
    echo "   ↪ ensured ${ORIG_USER} is in the video group for media-device probing"
  fi

  rm -f "${USER_HOME}/.cache/gstreamer-1.0"/registry.* 2>/dev/null || true
  sudo rm -f /root/.cache/gstreamer-1.0/registry.* 2>/dev/null || true

  local hevc_decoder=""
  if gst-inspect-1.0 v4l2slh265dec >/dev/null 2>&1; then
    hevc_decoder=v4l2slh265dec
    echo "✅  hardware HEVC decoder exposed: v4l2slh265dec"
  elif gst-inspect-1.0 v4l2h265dec >/dev/null 2>&1; then
    hevc_decoder=v4l2h265dec
    echo "✅  hardware HEVC decoder exposed: v4l2h265dec"
  else
    echo "❌ no hardware HEVC decoder exposed to GStreamer; Lesavka will not fall back to avdec_h265 in production." >&2
    if [[ "$INSTALL_UVC_CODEC" == "hevc" || "$INSTALL_CAM_CODEC" == "hevc" ]]; then
      if [[ "$INSTALL_CAM_CODEC" == "hevc" && "$INSTALL_CAM_CODEC_EXPLICIT" == "0" ]]; then
        echo "   Default HEVC upstream cannot be proven on this host; falling back to MJPEG ingress." >&2
        INSTALL_CAM_CODEC=mjpeg
        return 0
      fi
      echo "   Install/repair v4l2slh265dec or set LESAVKA_INSTALL_CAM_CODEC=mjpeg before running the server installer." >&2
      exit 1
    fi
  fi

  if [[ -n "$hevc_decoder" ]]; then
    local hevc_smoke_log
    hevc_smoke_log=$(mktemp "${TMPDIR}/lesavka-hevc-decode-smoke.XXXXXX.log")
    local decoder_chain="$hevc_decoder"
    if [[ "$hevc_decoder" == "v4l2slh265dec" ]]; then
      decoder_chain="$hevc_decoder discard-corrupted-frames=true automatic-request-sync-points=true"
    fi

    if gst-inspect-1.0 x265enc >/dev/null 2>&1 && timeout 20 bash -o pipefail -c \
      "gst-launch-1.0 -q videotestsrc num-buffers=30 ! video/x-raw,format=I420,width=1280,height=720,framerate=30/1 ! x265enc speed-preset=ultrafast tune=zerolatency key-int-max=30 ! h265parse disable-passthrough=true config-interval=-1 ! video/x-h265,stream-format=byte-stream,alignment=au ! ${decoder_chain} ! videoconvert ! fakesink sync=false" \
      >"$hevc_smoke_log" 2>&1; then
      echo "✅  hardware HEVC decoder passed a real 1280x720 decode smoke: $hevc_decoder"
    else
      if [[ "$INSTALL_CAM_CODEC" == "hevc" ]]; then
        local software_hevc_decoder=""
        local software_hevc_smoke_log
        software_hevc_smoke_log=$(mktemp "${TMPDIR}/lesavka-hevc-software-decode-smoke.XXXXXX.log")
        for candidate in avdec_h265 libde265dec; do
          if gst-inspect-1.0 "$candidate" >/dev/null 2>&1 && timeout 20 bash -o pipefail -c \
            "gst-launch-1.0 -q videotestsrc num-buffers=30 ! video/x-raw,format=I420,width=1280,height=720,framerate=30/1 ! x265enc speed-preset=ultrafast tune=zerolatency key-int-max=30 ! h265parse disable-passthrough=true config-interval=-1 ! video/x-h265,stream-format=byte-stream,alignment=au ! ${candidate} ! videoconvert ! fakesink sync=false" \
            >"$software_hevc_smoke_log" 2>&1; then
            software_hevc_decoder=$candidate
            break
          fi
        done

        if [[ "$INSTALL_CAM_CODEC_EXPLICIT" == "0" ]]; then
          echo "⚠️  hardware HEVC decoder is exposed but the synthetic 1280x720 decode smoke failed: $hevc_decoder" >&2
          echo "   smoke log: $hevc_smoke_log" >&2
          sed -n '1,120p' "$hevc_smoke_log" >&2 || true
          echo "   Default HEVC upstream cannot be proven on this host; falling back to MJPEG ingress." >&2
          INSTALL_CAM_CODEC=mjpeg
          return 0
        fi
        echo "❌ hardware HEVC decoder is exposed but failed a real 1280x720 decode smoke: $hevc_decoder" >&2
        echo "   smoke log: $hevc_smoke_log" >&2
        sed -n '1,120p' "$hevc_smoke_log" >&2 || true
        if [[ -n "$software_hevc_decoder" ]]; then
          echo "   Software HEVC decoder passed: $software_hevc_decoder" >&2
          echo "   To explicitly use it, rerun with LESAVKA_ALLOW_SOFTWARE_VIDEO=1 LESAVKA_HEVC_DECODER=$software_hevc_decoder." >&2
        fi
        echo "   Refusing HEVC upstream install because production video decode must be hardware-accelerated and proven." >&2
        echo "   Use LESAVKA_INSTALL_CAM_CODEC=mjpeg while the HEVC decoder stack is repaired." >&2
        exit 1
      fi
      echo "⚠️  hardware HEVC decoder is exposed but the synthetic 1280x720 decode smoke failed: $hevc_decoder" >&2
      echo "   smoke log: $hevc_smoke_log" >&2
      sed -n '1,120p' "$hevc_smoke_log" >&2 || true
      echo "   Continuing because this install is not selecting HEVC upstream." >&2
      echo "   Explicit HEVC installs require a passing hardware decode smoke and will not use software fallback in production." >&2
      echo "   Use scripts/manual/run_hardware_media_smoke.sh for artifact-backed follow-up evidence." >&2
    fi
  fi

  if gst-inspect-1.0 opusdec >/dev/null 2>&1; then
    echo "✅  Opus upstream audio decoder exposed: opusdec"
  else
    echo "⚠️  no Opus decoder exposed to GStreamer; upstream Opus audio will be dropped until gst-plugins-base is available"
  fi
}

manifest_package_version() {
  local manifest=$1
  [[ -f $manifest ]] || return 1
  awk -F'"' '
    $0 ~ /^\[package\]/ { in_package=1; next }
    in_package && $0 ~ /^\[/ { exit }
    in_package && $0 ~ /^[[:space:]]*version[[:space:]]*=/ { print $2; exit }
  ' "$manifest"
}

source_revision() {
  local repo=$1
  local sha=""
  sha=$(run_as_user git -C "$repo" rev-parse --short HEAD 2>/dev/null || true)
  if [[ -n $sha ]] && ! run_as_user git -C "$repo" diff --quiet --ignore-submodules -- 2>/dev/null; then
    sha="${sha}+dirty"
  fi
  printf '%s\n' "$sha"
}

resolve_source_checkout() {
  case "$INSTALL_SOURCE" in
    auto)
      if [[ -d $SCRIPT_REPO_ROOT/.git ]]; then
        SRC_DIR=$SCRIPT_REPO_ROOT
        echo "==> 4a. Using local source checkout at $SRC_DIR"
        echo "   ↪ set LESAVKA_INSTALL_SOURCE=ref to install from ${REF} via Git"
        return 0
      fi
      ;;
    local)
      if [[ ! -d $SCRIPT_REPO_ROOT/.git ]]; then
        echo "❌ LESAVKA_INSTALL_SOURCE=local requested, but $SCRIPT_REPO_ROOT is not a Git checkout." >&2
        exit 1
      fi
      SRC_DIR=$SCRIPT_REPO_ROOT
      echo "==> 4a. Using local source checkout at $SRC_DIR"
      return 0
      ;;
    ref|git)
      ;;
    *)
      echo "❌ unsupported LESAVKA_INSTALL_SOURCE=$INSTALL_SOURCE (expected auto, local, or ref)" >&2
      exit 1
      ;;
  esac

  echo "==> 4a. Source checkout"
  SRC_DIR=/var/src/lesavka
  if [[ ! -d $SRC_DIR ]]; then
    sudo mkdir -p /var/src
    sudo chown "$ORIG_USER":"$ORIG_USER" /var/src
  fi
  if [[ -d $SRC_DIR/.git ]]; then
    run_as_user git -C "$SRC_DIR" fetch --all --tags --prune
  else
    run_as_user git clone "$REPO_URL" "$SRC_DIR"
  fi

  if run_as_user git -C "$SRC_DIR" rev-parse --verify --quiet "origin/$REF" >/dev/null; then
    run_as_user git -C "$SRC_DIR" checkout -B "$REF" "origin/$REF"
  else
    run_as_user git -C "$SRC_DIR" checkout --force "$REF"
  fi
}

SRC_DIR=/var/src/lesavka

render_uvc_env_file() {
  cat <<EOF
# generated by lesavka/scripts/install/server.sh
# Edit only for local UVC hardware overrides; rerunning the installer refreshes defaults.
LESAVKA_UVC_DEBUG=$(uvc_env_value LESAVKA_UVC_DEBUG 1)
LESAVKA_UVC_MAXPACKET=$(uvc_env_value LESAVKA_UVC_MAXPACKET 1024)
LESAVKA_UVC_LIMIT_PCT=$(uvc_env_value LESAVKA_UVC_LIMIT_PCT 100)
LESAVKA_UVC_FPS=$(uvc_env_value LESAVKA_UVC_FPS 20)
LESAVKA_UVC_INTERVAL=$(uvc_env_value LESAVKA_UVC_INTERVAL 500000)
LESAVKA_UVC_WIDTH=$(uvc_env_value LESAVKA_UVC_WIDTH 1280)
LESAVKA_UVC_HEIGHT=$(uvc_env_value LESAVKA_UVC_HEIGHT 720)
LESAVKA_UVC_CODEC=${INSTALL_UVC_CODEC}
LESAVKA_UVC_BUFFER_COUNT=$(uvc_env_value LESAVKA_UVC_BUFFER_COUNT 4)
LESAVKA_UVC_BLOCKING=$(uvc_env_value LESAVKA_UVC_BLOCKING 1)
LESAVKA_UVC_CONTROL_READ_ONLY=$(uvc_env_value LESAVKA_UVC_CONTROL_READ_ONLY 0)
LESAVKA_UVC_QUEUE_PACING=$(uvc_env_value LESAVKA_UVC_QUEUE_PACING 0)
LESAVKA_UVC_MAXBURST=$(uvc_env_value LESAVKA_UVC_MAXBURST 0)
LESAVKA_UVC_BULK=$(uvc_env_value LESAVKA_UVC_BULK 1)
LESAVKA_UVC_FRAME_SIZE_GUARD=$(uvc_env_value LESAVKA_UVC_FRAME_SIZE_GUARD 1)
LESAVKA_UVC_FRAME_MAX_BYTES=$(uvc_env_value LESAVKA_UVC_FRAME_MAX_BYTES 0)
LESAVKA_UVC_MJPEG_BUDGET_BYTES_PER_SEC=${LESAVKA_UVC_MJPEG_BUDGET_BYTES_PER_SEC:-4500000}
LESAVKA_UVC_ISOCHRONOUS_LIMIT_PCT=$(uvc_env_value LESAVKA_UVC_ISOCHRONOUS_LIMIT_PCT 85)
LESAVKA_UVC_STATS_PATH=$(uvc_env_value LESAVKA_UVC_STATS_PATH /run/lesavka-uvc-video-stats.json)
LESAVKA_UVC_CONTRACT_PATH=$(uvc_env_value LESAVKA_UVC_CONTRACT_PATH /run/lesavka-uvc-contract.json)
LESAVKA_UVC_IDLE_AFTER_MS=$(uvc_env_value LESAVKA_UVC_IDLE_AFTER_MS 2000)
LESAVKA_UVC_STRICT_VALIDATE_EVERY=$(uvc_env_value LESAVKA_UVC_STRICT_VALIDATE_EVERY 30)
LESAVKA_UVC_HANDOFF_AUDIT=$(uvc_env_value LESAVKA_UVC_HANDOFF_AUDIT 0)
LESAVKA_UVC_HANDOFF_AUDIT_PATH=$(uvc_env_value LESAVKA_UVC_HANDOFF_AUDIT_PATH /run/lesavka-uvc-handoff.jsonl)
LESAVKA_UVC_HANDOFF_AUDIT_MAX_RECORDS=$(uvc_env_value LESAVKA_UVC_HANDOFF_AUDIT_MAX_RECORDS 1024)
EOF
}

append_san_entry() {
  local value=$1
  [[ -n $value ]] || return 0
  case "$value" in
    IP:*)
      TLS_SAN_IPS+=("${value#IP:}")
      ;;
    DNS:*)
      TLS_SAN_DNS+=("${value#DNS:}")
      ;;
    *[!0-9.]*)
      TLS_SAN_DNS+=("$value")
      ;;
    *)
      TLS_SAN_IPS+=("$value")
      ;;
  esac
}

render_server_cert_ext() {
  local ext_file=$1
  local dns_index=1
  local ip_index=1
  {
    echo "basicConstraints = CA:FALSE"
    echo "keyUsage = digitalSignature,keyEncipherment"
    echo "extendedKeyUsage = serverAuth"
    echo "subjectAltName = @alt_names"
    echo "[alt_names]"
    local value
    for value in "${TLS_SAN_DNS[@]}"; do
      [[ -n $value ]] || continue
      printf 'DNS.%d = %s\n' "$dns_index" "$value"
      dns_index=$((dns_index + 1))
    done
    for value in "${TLS_SAN_IPS[@]}"; do
      [[ -n $value ]] || continue
      printf 'IP.%d = %s\n' "$ip_index" "$value"
      ip_index=$((ip_index + 1))
    done
  } >"$ext_file"
}

ensure_server_tls_pki() {
  echo "==> 5c. TLS/mTLS identity"
  sudo install -d -m 0750 "$LESAVKA_TLS_DIR"

  if ! sudo test -s "$LESAVKA_TLS_DIR/ca.key" || ! sudo test -s "$LESAVKA_TLS_DIR/ca.crt"; then
    echo "   ↪ generating Lesavka local CA"
    sudo openssl genrsa -out "$LESAVKA_TLS_DIR/ca.key" 4096 >/dev/null 2>&1
    sudo openssl req -x509 -new -nodes \
      -key "$LESAVKA_TLS_DIR/ca.key" \
      -sha256 -days "${LESAVKA_TLS_CA_DAYS:-3650}" \
      -subj "/CN=Lesavka Local Relay CA" \
      -out "$LESAVKA_TLS_DIR/ca.crt" >/dev/null 2>&1
  fi

  TLS_SAN_DNS=(lesavka-server "$(hostname -s 2>/dev/null || true)" "$(hostname -f 2>/dev/null || true)")
  TLS_SAN_IPS=(127.0.0.1 38.28.125.112)
  local extra_san
  IFS=',' read -r -a extra_san <<<"${LESAVKA_TLS_SAN:-}"
  local san
  for san in "${extra_san[@]}"; do
    append_san_entry "${san//[[:space:]]/}"
  done

  local ext_file client_ext_file
  ext_file=$(mktemp)
  client_ext_file=$(mktemp)
  render_server_cert_ext "$ext_file"
  {
    echo "basicConstraints = CA:FALSE"
    echo "keyUsage = digitalSignature,keyEncipherment"
    echo "extendedKeyUsage = clientAuth"
  } >"$client_ext_file"

  if ! sudo test -s "$LESAVKA_TLS_DIR/server.key" || ! sudo test -s "$LESAVKA_TLS_DIR/server.crt"; then
    echo "   ↪ generating server certificate"
    sudo openssl genrsa -out "$LESAVKA_TLS_DIR/server.key" 2048 >/dev/null 2>&1
    sudo openssl req -new \
      -key "$LESAVKA_TLS_DIR/server.key" \
      -subj "/CN=lesavka-server" \
      -out "$LESAVKA_TLS_DIR/server.csr" >/dev/null 2>&1
    sudo openssl x509 -req \
      -in "$LESAVKA_TLS_DIR/server.csr" \
      -CA "$LESAVKA_TLS_DIR/ca.crt" \
      -CAkey "$LESAVKA_TLS_DIR/ca.key" \
      -CAcreateserial \
      -out "$LESAVKA_TLS_DIR/server.crt" \
      -days "${LESAVKA_TLS_CERT_DAYS:-825}" \
      -sha256 \
      -extfile "$ext_file" >/dev/null 2>&1
    sudo rm -f "$LESAVKA_TLS_DIR/server.csr"
  fi

  if ! sudo test -s "$LESAVKA_TLS_DIR/client.key" || ! sudo test -s "$LESAVKA_TLS_DIR/client.crt"; then
    echo "   ↪ generating default client certificate"
    sudo openssl genrsa -out "$LESAVKA_TLS_DIR/client.key" 2048 >/dev/null 2>&1
    sudo openssl req -new \
      -key "$LESAVKA_TLS_DIR/client.key" \
      -subj "/CN=lesavka-client" \
      -out "$LESAVKA_TLS_DIR/client.csr" >/dev/null 2>&1
    sudo openssl x509 -req \
      -in "$LESAVKA_TLS_DIR/client.csr" \
      -CA "$LESAVKA_TLS_DIR/ca.crt" \
      -CAkey "$LESAVKA_TLS_DIR/ca.key" \
      -CAcreateserial \
      -out "$LESAVKA_TLS_DIR/client.crt" \
      -days "${LESAVKA_TLS_CERT_DAYS:-825}" \
      -sha256 \
      -extfile "$client_ext_file" >/dev/null 2>&1
    sudo rm -f "$LESAVKA_TLS_DIR/client.csr"
  fi

  sudo chmod 0600 "$LESAVKA_TLS_DIR/"*.key
  sudo chmod 0644 "$LESAVKA_TLS_DIR/"*.crt
  rm -f "$ext_file" "$client_ext_file"

  local paste_key_dir paste_tmp
  paste_key_dir=$(dirname "$LESAVKA_PASTE_KEY_FILE")
  sudo install -d -m 0750 "$paste_key_dir"
  if ! sudo test -s "$LESAVKA_PASTE_KEY_FILE"; then
    echo "   ↪ generating encrypted paste shared key"
    paste_tmp=$(mktemp)
    openssl rand -hex 32 >"$paste_tmp"
    sudo install -m 0600 -o root -g root "$paste_tmp" "$LESAVKA_PASTE_KEY_FILE"
    rm -f "$paste_tmp"
  fi
  sudo chmod 0600 "$LESAVKA_PASTE_KEY_FILE"

  local bundle_tmp
  bundle_tmp=$(mktemp -d)
  sudo cp "$LESAVKA_TLS_DIR/ca.crt" "$bundle_tmp/ca.crt"
  sudo cp "$LESAVKA_TLS_DIR/client.crt" "$bundle_tmp/client.crt"
  sudo cp "$LESAVKA_TLS_DIR/client.key" "$bundle_tmp/client.key"
  sudo cp "$LESAVKA_PASTE_KEY_FILE" "$bundle_tmp/paste-key"
  sudo tar -C "$bundle_tmp" -czf "$LESAVKA_CLIENT_BUNDLE" ca.crt client.crt client.key paste-key
  sudo chown "$ORIG_USER":"$ORIG_USER" "$LESAVKA_CLIENT_BUNDLE"
  sudo chmod 0600 "$LESAVKA_CLIENT_BUNDLE"
  if [[ -n $USER_HOME && $ORIG_USER != root ]]; then
    sudo install -d -m 0700 -o "$ORIG_USER" -g "$ORIG_USER" "$LESAVKA_SYNTHETIC_PKI_DIR"
    sudo install -m 0644 -o "$ORIG_USER" -g "$ORIG_USER" "$LESAVKA_TLS_DIR/ca.crt" "$LESAVKA_SYNTHETIC_PKI_DIR/ca.crt"
    sudo install -m 0644 -o "$ORIG_USER" -g "$ORIG_USER" "$LESAVKA_TLS_DIR/client.crt" "$LESAVKA_SYNTHETIC_PKI_DIR/client.crt"
    sudo install -m 0600 -o "$ORIG_USER" -g "$ORIG_USER" "$LESAVKA_TLS_DIR/client.key" "$LESAVKA_SYNTHETIC_PKI_DIR/client.key"
    echo "   ↪ local synthetic probe TLS identity: $LESAVKA_SYNTHETIC_PKI_DIR"
  fi
  sudo rm -rf "$bundle_tmp"
  echo "   ↪ client enrollment bundle: $LESAVKA_CLIENT_BUNDLE"
}

find_uvc_output_node() {
  local by_path_root=/dev/v4l/by-path
  local ctrl=""
  ctrl=$(ls /sys/class/udc 2>/dev/null | head -n1 || true)
  if [[ -n $ctrl && -e "$by_path_root/platform-$ctrl-video-index0" ]]; then
    printf '%s\n' "$by_path_root/platform-$ctrl-video-index0"
    return 0
  fi

  local candidate
  shopt -s nullglob
  for candidate in "$by_path_root"/platform-*-video-index0; do
    printf '%s\n' "$candidate"
    shopt -u nullglob
    return 0
  done
  shopt -u nullglob
  return 1
}

wait_for_uvc_output_node() {
  local node=""
  for _ in {1..50}; do
    if node=$(find_uvc_output_node); then
      printf '%s\n' "$node"
      return 0
    fi
    sleep 0.1
  done
  return 1
}

validate_uvc_gadget_ready() {
  if [[ -n ${LESAVKA_DISABLE_UVC:-} ]]; then
    return 0
  fi

  if [[ ! -d /sys/kernel/config/usb_gadget/lesavka/functions/uvc.usb0 ]]; then
    echo "❌ UVC gadget function is missing after rebuild; refusing to continue with a half-applied install." >&2
    return 1
  fi

  local node=""
  if ! node=$(wait_for_uvc_output_node); then
    echo "❌ UVC gadget video-output node did not appear after rebuild; refusing to continue." >&2
    return 1
  fi

  echo "✅  UVC gadget output ready at ${node}"
}

uvc_gadget_present() {
  [[ -d /sys/kernel/config/usb_gadget/lesavka/functions/uvc.usb0 ]]
}

live_gadget_bound() {
  [[ -s /sys/kernel/config/usb_gadget/lesavka/UDC ]]
}

live_uvc_descriptor_codec() {
  local function_root=/sys/kernel/config/usb_gadget/lesavka/functions/uvc.usb0
  if [[ -e "$function_root/streaming/header/h/mjpeg" ]]; then
    echo "mjpeg"
    return 0
  fi
  if [[ -e "$function_root/streaming/header/h/yuyv" ]]; then
    echo "yuyv"
    return 0
  fi
  if [[ -d "$function_root/streaming/mjpeg/m" ]]; then
    echo "mjpeg"
    return 0
  fi
  if [[ -d "$function_root/streaming/uncompressed/yuyv" ]]; then
    echo "yuyv"
    return 0
  fi
  echo "unknown"
}

live_uvc_descriptor_matches_request() {
  local function_root=/sys/kernel/config/usb_gadget/lesavka/functions/uvc.usb0
  local frame_root=""
  case "$(live_uvc_descriptor_codec)" in
    mjpeg)
      [[ "$INSTALL_UVC_CODEC" == "mjpeg" ]] || return 1
      case "${LESAVKA_UVC_WIDTH:-1280}x${LESAVKA_UVC_HEIGHT:-720}" in
        1920x1080) frame_root="$function_root/streaming/mjpeg/m/1080p" ;;
        1280x720) frame_root="$function_root/streaming/mjpeg/m/720p" ;;
        *) return 1 ;;
      esac
      ;;
    yuyv)
      [[ "$INSTALL_UVC_CODEC" == "yuyv" ]] || return 1
      frame_root="$function_root/streaming/uncompressed/yuyv/480p"
      ;;
    *)
      return 1
      ;;
  esac
  [[ -r "$frame_root/wWidth" && -r "$frame_root/wHeight" && -r "$frame_root/dwDefaultFrameInterval" ]] || return 1
  [[ "$(cat "$frame_root/wWidth" 2>/dev/null || true)" == "${LESAVKA_UVC_WIDTH:-1280}" ]] || return 1
  [[ "$(cat "$frame_root/wHeight" 2>/dev/null || true)" == "${LESAVKA_UVC_HEIGHT:-720}" ]] || return 1
  grep -qx "${LESAVKA_UVC_INTERVAL:-500000}" "$frame_root/dwFrameInterval" 2>/dev/null \
    || [[ "$(cat "$frame_root/dwDefaultFrameInterval" 2>/dev/null || true)" == "${LESAVKA_UVC_INTERVAL:-500000}" ]]
}

live_uac_descriptor_matches_request() {
  if [[ -n ${LESAVKA_DISABLE_UAC:-} ]]; then
    [[ ! -d /sys/kernel/config/usb_gadget/lesavka/functions/uac2.usb0 ]]
    return $?
  fi

  local function_root=/sys/kernel/config/usb_gadget/lesavka/functions/uac2.usb0
  [[ -d "$function_root" ]] || return 1

  local expected_mixer=0
  if [[ ${LESAVKA_UAC_HOST_MIXER_CONTROLS:-0} == 1 ]]; then
    expected_mixer=1
  fi

  for attr in p_volume_present p_mute_present c_volume_present c_mute_present; do
    [[ -r "$function_root/$attr" ]] || return 1
    [[ "$(cat "$function_root/$attr" 2>/dev/null || true)" == "$expected_mixer" ]] || return 1
  done
  [[ "$(cat "$function_root/p_chmask" 2>/dev/null || true)" == "3" ]] || return 1
  [[ "$(cat "$function_root/c_chmask" 2>/dev/null || true)" == "3" ]] || return 1
  [[ "$(cat "$function_root/p_srate" 2>/dev/null || true)" == "48000" ]] || return 1
  [[ "$(cat "$function_root/c_srate" 2>/dev/null || true)" == "48000" ]] || return 1
  [[ "$(cat "$function_root/p_ssize" 2>/dev/null || true)" == "2" ]] || return 1
  [[ "$(cat "$function_root/c_ssize" 2>/dev/null || true)" == "2" ]] || return 1
  [[ "$(cat "$function_root/c_sync" 2>/dev/null || true)" == "adaptive" ]]
}

udc_state() {
  local udc=""
  udc=$(ls /sys/class/udc 2>/dev/null | head -n1 || true)
  if [[ -z $udc ]]; then
    echo "unknown"
    return 0
  fi
  cat "/sys/class/udc/$udc/state" 2>/dev/null || echo "unknown"
}

is_attached_state() {
  case "$1" in
    configured|addressed|default|suspended|unknown)
      return 0
      ;;
  esac
  return 1
}

server_bind_addr() {
  printf '%s\n' "${INSTALL_SERVER_BIND_ADDR}"
}

server_bind_port() {
  local bind_addr port
  bind_addr=$(server_bind_addr)
  port=${bind_addr##*:}
  [[ $port =~ ^[0-9]+$ ]] || return 1
  printf '%s\n' "$port"
}

list_server_listener_pids() {
  local port
  port=$(server_bind_port) || return 1
  {
    sudo lsof -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null || true
    list_server_listener_pids_proc "$port"
  } | sed '/^$/d' | sort -u
}

list_server_bound_inactive_lines() {
  local port=$1
  sudo ss -H -B -tn "sport = :$port" 2>/dev/null | sed '/^$/d' || true
}

list_server_established_client_lines_proc() {
  local port=$1 hex_port
  hex_port=$(printf '%04X' "$port")
  sudo awk -v target="$hex_port" '
    FNR == 1 { next }
    $4 == "01" {
      split($2, local, ":")
      if (toupper(local[2]) == target) print $0
    }
  ' /proc/net/tcp /proc/net/tcp6 2>/dev/null | sed '/^$/d'
}

list_server_established_client_lines() {
  local port=$1
  if command -v ss >/dev/null 2>&1; then
    sudo ss -Htn state established "sport = :$port" 2>/dev/null | sed '/^$/d' || true
  fi
  list_server_established_client_lines_proc "$port"
}

lesavka_server_has_active_clients() {
  local port
  port=$(server_bind_port) || return 1
  list_server_established_client_lines "$port" | grep -q .
}

server_active_client_detail() {
  local port detail
  port=$(server_bind_port) || {
    printf 'unable to parse server bind port'
    return 0
  }
  detail=$(list_server_established_client_lines "$port" | head -n 5 | paste -sd ';' -)
  if [[ -z $detail ]]; then
    printf 'no established clients on :%s' "$port"
    return 0
  fi
  printf 'established clients on :%s => %s' "$port" "$detail"
}

list_server_listener_inodes_proc() {
  local port=$1 hex_port
  hex_port=$(printf '%04X' "$port")
  sudo awk -v target="$hex_port" '
    FNR == 1 { next }
    $4 == "0A" {
      split($2, local, ":")
      if (toupper(local[2]) == target) print $10
    }
  ' /proc/net/tcp /proc/net/tcp6 2>/dev/null | sed '/^$/d' | sort -u
}

list_server_listener_pids_proc() {
  local port=$1
  local -a inodes=()
  mapfile -t inodes < <(list_server_listener_inodes_proc "$port")
  if (( ${#inodes[@]} == 0 )); then
    return 0
  fi

  sudo bash -s -- "${inodes[@]}" <<'EOF'
set -euo pipefail
inodes=("$@")
shopt -s nullglob
for fd in /proc/[0-9]*/fd/*; do
  target=$(readlink "$fd" 2>/dev/null || true)
  for inode in "${inodes[@]}"; do
    if [[ "$target" == "socket:[$inode]" ]]; then
      pid=${fd#/proc/}
      pid=${pid%%/*}
      printf '%s\n' "$pid"
      break
    fi
  done
done | sort -u
EOF
}

server_listener_presence_detail() {
  local port=$1
  local -a inodes=()
  mapfile -t inodes < <(list_server_listener_inodes_proc "$port")
  if (( ${#inodes[@]} == 0 )); then
    printf 'no /proc listener entries for :%s' "$port"
    return 0
  fi
  printf 'listener inodes on :%s => %s' "$port" "${inodes[*]}"
}

server_bound_inactive_detail() {
  local port=$1
  local detail
  detail=$(list_server_bound_inactive_lines "$port" | paste -sd ';' -)
  if [[ -z $detail ]]; then
    printf 'no bound-inactive tcp sockets for :%s' "$port"
    return 0
  fi
  printf 'bound-inactive tcp sockets on :%s => %s' "$port" "$detail"
}

destroy_hidden_server_listener() {
  local port=$1
  echo "⚠️  TCP :$port is listening without a visible owning PID; asking the kernel to drop the stale socket state."
  sudo ss -K state connected "sport = :$port or dport = :$port" >/dev/null 2>&1 || true
  sudo ss -K state listening "sport = :$port" >/dev/null 2>&1 || true

  for _ in {1..30}; do
    if ! list_server_listener_inodes_proc "$port" | grep -q .; then
      if list_server_bound_inactive_lines "$port" | grep -q .; then
        echo "❌ TCP :$port stopped listening but remains bound after kernel cleanup; $(server_bound_inactive_detail "$port")." >&2
        return 1
      fi
      echo "✅  kernel dropped the hidden TCP :$port socket state."
      return 0
    fi
    sleep 0.1
  done

  echo "❌ TCP :$port still appears to be listening after kernel socket cleanup; $(server_listener_presence_detail "$port")." >&2
  return 1
}

clear_stale_server_listener() {
  local port pid cmdline found=0 unexpected=0
  port=$(server_bind_port) || {
    echo "⚠️  could not parse LESAVKA_SERVER_BIND_ADDR='$(server_bind_addr)'; skipping stale-listener cleanup."
    return 0
  }

  while read -r pid; do
    [[ -n $pid ]] || continue
    found=1
    cmdline=$(sudo ps -o args= -p "$pid" 2>/dev/null || true)
    if [[ $cmdline == *"/usr/local/bin/lesavka-server"* ]] || [[ $cmdline == *"lesavka-server"* ]]; then
      echo "⚠️  clearing stale lesavka-server listener on :$port (pid $pid)."
      sudo kill -TERM "$pid" 2>/dev/null || true
    else
      echo "❌ TCP :$port is already owned by an unexpected process (pid $pid): ${cmdline:-<unknown>}." >&2
      unexpected=1
    fi
  done < <(list_server_listener_pids || true)

  if [[ "$unexpected" != "0" ]]; then
    return 1
  fi
  if [[ "$found" == "0" ]]; then
    if list_server_listener_inodes_proc "$port" | grep -q .; then
      if destroy_hidden_server_listener "$port"; then
        return 0
      fi
      echo "❌ TCP :$port is listening but no owning PID could be identified; $(server_listener_presence_detail "$port")." >&2
      return 1
    fi
    if list_server_bound_inactive_lines "$port" | grep -q .; then
      echo "❌ TCP :$port is not listening but remains bound; $(server_bound_inactive_detail "$port")." >&2
      return 1
    fi
    return 0
  fi

  for _ in {1..30}; do
    if ! list_server_listener_pids | grep -q .; then
      echo "✅  cleared stale lesavka-server listeners on :$port."
      return 0
    fi
    sleep 0.1
  done

  while read -r pid; do
    [[ -n $pid ]] || continue
    cmdline=$(sudo ps -o args= -p "$pid" 2>/dev/null || true)
    if [[ $cmdline == *"/usr/local/bin/lesavka-server"* ]] || [[ $cmdline == *"lesavka-server"* ]]; then
      echo "⚠️  stale lesavka-server listener on :$port survived SIGTERM; sending SIGKILL to pid $pid."
      sudo kill -KILL "$pid" 2>/dev/null || true
    else
      echo "❌ TCP :$port remained busy after cleanup and is owned by an unexpected process (pid $pid): ${cmdline:-<unknown>}." >&2
      return 1
    fi
  done < <(list_server_listener_pids || true)

  for _ in {1..30}; do
    if ! list_server_listener_pids | grep -q .; then
      echo "✅  cleared stale lesavka-server listeners on :$port."
      return 0
    fi
    sleep 0.1
  done

  if list_server_listener_inodes_proc "$port" | grep -q .; then
    echo "❌ TCP :$port still appears to be listening after cleanup; $(server_listener_presence_detail "$port")." >&2
    return 1
  fi
  if list_server_bound_inactive_lines "$port" | grep -q .; then
    echo "❌ TCP :$port stopped listening but remains bound after cleanup; $(server_bound_inactive_detail "$port")." >&2
    return 1
  fi

  echo "❌ lesavka-server listener on :$port survived cleanup; refusing to start a duplicate server." >&2
  return 1
}

wait_for_unit_running() {
  local unit=$1
  for _ in {1..50}; do
    if systemctl is-active --quiet "$unit"; then
      if [[ $(systemctl show "$unit" -p SubState --value 2>/dev/null || true) == "running" ]]; then
        return 0
      fi
    fi
    sleep 0.2
  done
  return 1
}

validate_server_ready() {
  local bind_addr port
  bind_addr=$(server_bind_addr)
  port=$(server_bind_port) || {
    echo "❌ could not parse LESAVKA_SERVER_BIND_ADDR='${bind_addr}' while validating server readiness." >&2
    return 1
  }
  if wait_for_unit_running lesavka-server; then
    for _ in {1..50}; do
      if list_server_listener_inodes_proc "$port" | grep -q .; then
        echo "✅  lesavka-server is active and listening on ${bind_addr}."
        return 0
      fi
      sleep 0.2
    done
    echo "❌ lesavka-server reached active/running state but did not open TCP :${port}." >&2
    sudo journalctl -b -u lesavka-server -n 80 --no-pager >&2 || true
    return 1
  fi

  echo "❌ lesavka-server failed to reach active/running state on ${bind_addr}." >&2
  sudo systemctl status lesavka-server --no-pager >&2 || true
  if [[ -s /tmp/lesavka-server.stderr ]]; then
    echo "---- /tmp/lesavka-server.stderr (tail) ----" >&2
    sudo tail -n 40 /tmp/lesavka-server.stderr >&2 || true
    echo "------------------------------------------" >&2
  fi
  return 1
}

install_lesavka_server_unit_file() {
  cat <<'UNIT' | sudo tee /etc/systemd/system/lesavka-server.service >/dev/null
[Unit]
Description=lesavka gRPC relay
After=network.target lesavka-core.service lesavka-uvc.service
StartLimitIntervalSec=30
StartLimitBurst=10

[Service]
ExecStart=/usr/local/bin/lesavka-server
TimeoutStopSec=10
KillSignal=SIGTERM
KillMode=control-group
Restart=always
Environment=RUST_LOG=lesavka_server=info,lesavka_server::audio=info,lesavka_server::video=info,lesavka_server::gadget=info
Environment=RUST_BACKTRACE=1
Environment=GST_DEBUG="*:2,alsasink:6,alsasrc:6"
Environment=LESAVKA_UVC_EXTERNAL=1
Environment=LESAVKA_EYE_ADAPTIVE=1
Environment=LESAVKA_EYE_MIN_FPS=12
Environment=LESAVKA_EYE_FPS=20
Environment=LESAVKA_MIC_INIT_ATTEMPTS=5
Environment=LESAVKA_MIC_INIT_DELAY_MS=250
Environment=LESAVKA_SERVER_LOG_PATH=/var/log/lesavka/server.log
EnvironmentFile=-/etc/lesavka/uvc.env
EnvironmentFile=-/etc/lesavka/server.env
Restart=always
RestartSec=5
StandardError=append:/var/log/lesavka/server.stderr
User=root

[Install]
WantedBy=multi-user.target
UNIT
}

install_uvc_kernel_watch_unit() {
  cat <<'UNIT' | sudo tee /etc/systemd/system/lesavka-uvc-kernel-watch.service >/dev/null
[Unit]
Description=lesavka UVC/UDC kernel error watcher
After=systemd-journald.service

[Service]
ExecStart=/usr/local/lib/lesavka/lesavka-uvc-kernel-watch.py
Restart=always
RestartSec=2
User=root
Environment=LESAVKA_UVC_KERNEL_STATS_PATH=/run/lesavka-uvc-kernel-stats.json

[Install]
WantedBy=multi-user.target
UNIT
  sudo systemctl daemon-reload
  sudo systemctl enable --now lesavka-uvc-kernel-watch
}

restart_lesavka_server_only() {
  sudo truncate -s 0 /var/log/lesavka/server.stderr
  sudo systemctl stop lesavka-server >/dev/null 2>&1 || true
  clear_stale_server_listener
  sudo systemctl reset-failed lesavka-server >/dev/null 2>&1 || true
  sudo systemctl restart lesavka-server
  validate_server_ready
}

restart_lesavka_uvc_helper_only() {
  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
}

normalize_hdmi_connector() {
  local name="$1"
  if [[ $name =~ (HDMI-A-[0-9]+)$ ]]; then
    printf '%s\n' "${BASH_REMATCH[1]}"
  else
    printf '%s\n' "$name"
  fi
}

read_existing_hdmi_connector() {
  local line value
  line=$(grep -E '^LESAVKA_HDMI_CONNECTOR=' /etc/lesavka/server.env 2>/dev/null | tail -n1 || true)
  [[ -n $line ]] || return 0
  value=${line#*=}
  value=${value%\"}
  value=${value#\"}
  value=${value%\'}
  value=${value#\'}
  normalize_hdmi_connector "$value"
}

boot_config_path() {
  for path in /boot/config.txt /boot/firmware/config.txt; do
    if [[ -e $path ]]; then
      printf '%s\n' "$path"
      return 0
    fi
  done
  return 0
}

boot_cmdline_path() {
  for path in /boot/cmdline.txt /boot/firmware/cmdline.txt; do
    if [[ -e $path ]]; then
      printf '%s\n' "$path"
      return 0
    fi
  done
  return 0
}

runtime_forced_hdmi_connectors() {
  [[ -r /proc/cmdline ]] || return 0
  tr ' ' '\n' </proc/cmdline |
    sed -n 's/^video=\(HDMI-A-[0-9]\+\):.*e$/\1/p' |
    sort -u
}

remove_lesavka_hdmi_config_force() {
  local cfg tmp
  cfg=$(boot_config_path)
  [[ -n $cfg ]] || return 0
  tmp=$(mktemp)
  awk '
    /^# lesavka: force HDMI/ {next}
    /^hdmi_force_hotplug:[0-9]+=1$/ {next}
    /^hdmi_group:[0-9]+=2$/ {next}
    /^hdmi_mode:[0-9]+=82$/ {next}
    {print}
  ' "$cfg" >"$tmp"
  sudo install -m 0644 "$tmp" "$cfg"
  rm -f "$tmp"
}

write_hdmi_cmdline_force() {
  local connector="${1:-}" cmd token tmp line
  cmd=$(boot_cmdline_path)
  [[ -n $cmd ]] || return 0
  tmp=$(mktemp)
  line=$(cat "$cmd")
  for token in $line; do
    case "$token" in
      video=HDMI-A-*:1920x1080@60e)
        continue
        ;;
    esac
    printf '%s ' "$token" >>"$tmp"
  done
  if [[ -n $connector ]]; then
    printf 'video=%s:1920x1080@60e ' "$connector" >>"$tmp"
  fi
  sed -i 's/[[:space:]]*$//' "$tmp"
  printf '\n' >>"$tmp"
  sudo install -m 0644 "$tmp" "$cmd"
  rm -f "$tmp"
}

clear_lesavka_hdmi_boot_force() {
  remove_lesavka_hdmi_config_force
  write_hdmi_cmdline_force ""
}

write_hdmi_boot_force() {
  local connector="$1" idx cfg
  connector=$(normalize_hdmi_connector "$connector")
  if [[ ! $connector =~ ^HDMI-A-([0-9]+)$ ]]; then
    echo "⚠️  cannot write HDMI boot force for unexpected connector '$connector'." >&2
    return 0
  fi

  idx=$((BASH_REMATCH[1] - 1))
  cfg=$(boot_config_path)
  remove_lesavka_hdmi_config_force
  if [[ -n $cfg ]]; then
    {
      echo
      echo "# lesavka: force HDMI mode for capture dongle ($connector)"
      printf 'hdmi_force_hotplug:%s=1\n' "$idx"
      printf 'hdmi_group:%s=2\n' "$idx"
      printf 'hdmi_mode:%s=82\n' "$idx"
    } | sudo tee -a "$cfg" >/dev/null
  fi
  write_hdmi_cmdline_force "$connector"
}

prepare_hdmi_detection_boot_state() {
  local -a forced=()

  if [[ -n ${LESAVKA_HDMI_CONNECTOR:-} ]]; then
    return 0
  fi

  mapfile -t forced < <(runtime_forced_hdmi_connectors)
  if (( ${#forced[@]} <= 1 )); then
    return 0
  fi

  echo "⚠️  current boot forces multiple HDMI connectors: ${forced[*]}" >&2
  echo "    Linux will report every forced connector as connected, so auto-detection cannot be trusted yet." >&2
  clear_lesavka_hdmi_boot_force
  echo "✅  removed Lesavka-managed dual-HDMI force settings from boot config." >&2
  echo "    Reboot Theia, then rerun this installer so it can see the real connected HDMI port." >&2
  echo "    If both ports are physically connected after reboot, rerun once with LESAVKA_HDMI_CONNECTOR=HDMI-A-N." >&2
  exit 1
}

describe_hdmi_connectors() {
  local dev name status id edid_bytes
  for dev in /sys/class/drm/card*-HDMI-A-*; do
    [[ -e $dev/status ]] || continue
    name=$(basename "$dev")
    status=$(cat "$dev/status" 2>/dev/null || true)
    id=$(cat "$dev/connector_id" 2>/dev/null || true)
    edid_bytes=$(wc -c <"$dev/edid" 2>/dev/null || echo 0)
    printf '    %s (%s): %s, edid=%sB\n' "$(normalize_hdmi_connector "$name")" "${id:-no-id}" "${status:-unknown}" "${edid_bytes:-0}" >&2
  done
}

detect_connected_hdmi_connector() {
  local dev name stable_name status score existing increment edid_bytes
  local best="" best_score=0 best_count=0
  declare -A scores=()

  # HDMI status can briefly flap during gadget/display bring-up, so sample a
  # few times. Store the logical connector suffix, not the DRM card prefix, so
  # this stays valid if Linux renumbers cardN across boots.
  for _ in 1 2 3 4 5; do
    for dev in /sys/class/drm/card*-HDMI-A-*; do
      [[ -e $dev/status ]] || continue
      name=$(basename "$dev")
      stable_name=$(normalize_hdmi_connector "$name")
      status=$(cat "$dev/status" 2>/dev/null || true)
      if [[ $status == connected ]]; then
        increment=1
        # A boot-forced HDMI connector also reports "connected" on Raspberry Pi,
        # even when no sink is present. Real EDID is stronger evidence than the
        # status bit, so prefer it before falling back to older install state.
        edid_bytes=$(wc -c <"$dev/edid" 2>/dev/null || echo 0)
        if [[ ${edid_bytes:-0} =~ ^[0-9]+$ ]] && (( edid_bytes > 0 )); then
          increment=20
        fi
        scores[$stable_name]=$(( ${scores[$stable_name]:-0} + increment ))
      fi
    done
    sleep 0.2
  done

  for name in "${!scores[@]}"; do
    score=${scores[$name]}
    if (( score > best_score )); then
      best=$name
      best_score=$score
    fi
  done

  (( best_score > 0 )) || return 0

  for name in "${!scores[@]}"; do
    if (( scores[$name] == best_score )); then
      best_count=$((best_count + 1))
    fi
  done

  if (( best_count == 1 )); then
    printf '%s\n' "$best"
    return 0
  fi

  existing=$(read_existing_hdmi_connector)
  if [[ -n $existing && ${scores[$existing]:-0} -eq $best_score ]]; then
    echo "⚠️  multiple HDMI connectors are equally connected; preserving existing LESAVKA_HDMI_CONNECTOR=$existing." >&2
    describe_hdmi_connectors
    printf '%s\n' "$existing"
    return 0
  fi

  echo "❌ multiple HDMI connectors are equally connected; refusing to guess." >&2
  describe_hdmi_connectors
  echo "   Disconnect the non-target HDMI output or run once with LESAVKA_HDMI_CONNECTOR=HDMI-A-N." >&2
  return 1
}

resolve_hdmi_connector() {
  local detected existing
  if [[ -n ${LESAVKA_HDMI_CONNECTOR:-} ]]; then
    normalize_hdmi_connector "$LESAVKA_HDMI_CONNECTOR"
    return 0
  fi

  if ! detected=$(detect_connected_hdmi_connector); then
    return 1
  fi
  if [[ -n $detected ]]; then
    printf '%s\n' "$detected"
    return 0
  fi

  existing=$(read_existing_hdmi_connector)
  if [[ -n $existing ]]; then
    echo "⚠️  no connected HDMI connector detected; preserving existing LESAVKA_HDMI_CONNECTOR=$existing." >&2
    printf '%s\n' "$existing"
  fi
}

run_as_user() {
  sudo -u "$ORIG_USER" env HOME="$USER_HOME" SSH_AUTH_SOCK="${SSH_AUTH_SOCK:-}" "$@"
}

pacman_install() {
  local log_file
  log_file=$(mktemp --tmpdir="$TMPDIR" lesavka-pacman.XXXXXX.log)
  if sudo pacman -Sq --needed --noconfirm "$@" 2>&1 | tee "$log_file"; then
    rm -f "$log_file"
    return 0
  fi
  if grep -Eq "breaks dependency '.*pipewire" "$log_file"; then
    cat >&2 <<'MSG'
❌ Arch stopped the package transaction because PipeWire packages are at mixed exact versions.
   Lesavka installs PipeWire as one coherent set, but this host still needs a sync transaction.
   Run during a safe maintenance window:
     sudo pacman -Syu
   Then rerun the Lesavka server installer.
MSG
  elif grep -Eq "failed retrieving file|failed to retrieve some files|failed to commit transaction \\(failed to retrieve some files\\)" "$log_file"; then
    cat >&2 <<'MSG'
❌ Arch failed while downloading packages from the configured mirrors.
   No Lesavka files were replaced. Refresh or choose healthier mirrors, then rerun the installer.
   Good first retry:
     sudo pacman -Syu --disable-download-timeout
   If mirrors keep timing out, refresh /etc/pacman.d/mirrorlist before retrying.
MSG
  elif grep -Eq "pipewire-jack.*jack2|jack2.*pipewire-jack|are in conflict \\(jack\\)" "$log_file"; then
    cat >&2 <<'MSG'
❌ Arch refused to install pipewire-jack because JACK is already provided by jack2.
   Lesavka does not require JACK. The installer only keeps pipewire-jack coherent
   on hosts that already use it; do not remove jack2 just for Lesavka.
   Pull the latest installer and rerun the Lesavka server install.
MSG
  fi
  echo "   pacman log: $log_file" >&2
  exit 1
}

pipewire_package_set() {
  printf '%s\n' libpipewire pipewire pipewire-audio pipewire-pulse
  for package in pipewire-alsa pipewire-jack; do
    if pacman -Q "$package" >/dev/null 2>&1; then
      printf '%s\n' "$package"
    fi
  done
}

install_verified_executable() {
  local src=$1
  local dest=$2
  local label=${3:-$dest}
  local dest_dir dest_base tmp

  if [[ ! -s "$src" ]]; then
    echo "❌ refusing to install $label: source '$src' is missing or empty." >&2
    echo "   Preserving the existing installed executable at '$dest'." >&2
    exit 1
  fi
  if [[ ! -x "$src" ]]; then
    echo "❌ refusing to install $label: source '$src' is not executable." >&2
    echo "   Preserving the existing installed executable at '$dest'." >&2
    exit 1
  fi

  dest_dir=$(dirname "$dest")
  dest_base=$(basename "$dest")
  sudo install -d -m 0755 "$dest_dir"
  tmp=$(sudo mktemp "$dest_dir/.${dest_base}.install.XXXXXX")
  sudo rm -f "$tmp"
  sudo install -Dm755 "$src" "$tmp"
  if ! sudo test -s "$tmp" || ! sudo test -x "$tmp"; then
    sudo rm -f "$tmp"
    echo "❌ refusing to replace $label: staged install output was not a non-empty executable." >&2
    echo "   Preserving the existing installed executable at '$dest'." >&2
    exit 1
  fi
  sudo mv -f "$tmp" "$dest"
  sudo chmod 0755 "$dest"
}

pause_recovery_ladder_timer() {
  sudo systemctl stop lesavka-recovery-ladder.timer >/dev/null 2>&1 || true
}

install_recovery_ladder_units() {
  cat <<'UNIT' | sudo tee /etc/systemd/system/lesavka-recovery-ladder.service >/dev/null
[Unit]
Description=lesavka soft recovery ladder
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/lesavka-recovery-ladder recover
Environment=LESAVKA_RECOVERY_TIMEOUT_SECONDS=60
Environment=LESAVKA_RECOVERY_ALLOW_UVC_RESTART=0
Environment=LESAVKA_RECOVERY_ALLOW_CORE_RESTART=0
Environment=LESAVKA_RECOVERY_ALLOW_REBOOT=0
EnvironmentFile=-/etc/lesavka/server.env
EnvironmentFile=-/etc/lesavka/uvc.env
UNIT

  cat <<'UNIT' | sudo tee /etc/systemd/system/lesavka-recovery-ladder.timer >/dev/null
[Unit]
Description=periodically check and softly recover lesavka services

[Timer]
OnBootSec=90s
OnUnitActiveSec=60s
AccuracySec=10s
Persistent=true

[Install]
WantedBy=timers.target
UNIT

  sudo systemctl daemon-reload
  sudo systemctl enable lesavka-recovery-ladder.timer
}

CAPTURE_DISCOVERY_RELAY_PRESENT=0
CAPTURE_DISCOVERY_RELAY_WAS_ACTIVE=0
CAPTURE_DISCOVERY_POWER_BORROWED=0

prepare_capture_power_for_discovery() {
  if ! systemctl list-unit-files | grep -q '^relay.service'; then
    return 0
  fi

  CAPTURE_DISCOVERY_RELAY_PRESENT=1
  if systemctl is-active --quiet relay.service; then
    CAPTURE_DISCOVERY_RELAY_WAS_ACTIVE=1
    return 0
  fi

  echo "   ↪ borrowing relay GPIO power for capture discovery"
  sudo systemctl start relay.service
  CAPTURE_DISCOVERY_POWER_BORROWED=1
  sudo udevadm settle --timeout=10 || true
  sleep 2
}

restore_capture_power_after_discovery() {
  if [ "${CAPTURE_DISCOVERY_RELAY_PRESENT:-0}" -eq 0 ]; then
    return 0
  fi
  if [ "${CAPTURE_DISCOVERY_POWER_BORROWED:-0}" -eq 0 ]; then
    return 0
  fi
  if [ "${CAPTURE_DISCOVERY_RELAY_WAS_ACTIVE:-0}" -ne 0 ]; then
    return 0
  fi

  echo "   ↪ returning relay GPIO power control to Lesavka auto mode"
  sudo systemctl stop relay.service || true
  CAPTURE_DISCOVERY_POWER_BORROWED=0
}

while [[ $# -gt 0 ]]; do
  case $1 in
    -r|--ref) REF="$2"; shift 2 ;;
    -h|--help)
      echo "Usage: $0 [--ref <branch|commit>]"; exit 0 ;;
    *) echo "Unknown option: $1"; exit 1 ;;
  esac
done
echo "==> Using git ref: $REF"
mkdir -p "$TMPDIR"
pause_recovery_ladder_timer

if [[ -z $REPO_URL ]] && [[ -d $SCRIPT_REPO_ROOT/.git ]]; then
  REPO_URL=$(git -C "$SCRIPT_REPO_ROOT" config --get remote.origin.url || true)
fi
REPO_URL=${REPO_URL:-$DEFAULT_REPO_URL}

echo "==> 1a. Base packages"
mapfile -t PIPEWIRE_PACKAGES < <(pipewire_package_set)
pacman_install git \
               rustup \
               protobuf \
               abseil-cpp \
               gcc \
               alsa-utils \
               "${PIPEWIRE_PACKAGES[@]}" \
               tailscale \
               base-devel \
               v4l-utils \
               gstreamer \
               gst-plugins-base \
               gst-plugins-base-libs \
               gst-plugins-good \
               gst-plugins-bad \
               gst-plugins-bad-libs \
               gst-plugins-ugly \
               gst-libav \
               tcpdump \
               lsof \
               openssl
if ! command -v yay >/dev/null 2>&1; then
  echo "==> 1b. installing yay from AUR ..."
  run_as_user env TMPDIR="$TMPDIR" bash -c '
    rm -rf "$TMPDIR/yay" &&
    cd "$TMPDIR" && git clone --depth 1 https://aur.archlinux.org/yay.git &&
    cd yay && makepkg -si --noconfirm'
fi
# yay -S --noconfirm grpcurl-bin

echo "==> 1c. GPIO permissions for relay"
echo 'z /dev/gpiochip* 0660 root gpio -' | sudo tee /etc/tmpfiles.d/gpiochip.conf >/dev/null
sudo systemd-tmpfiles --create /etc/tmpfiles.d/gpiochip.conf || true

echo "==> 1d. Audio permissions for diagnostics"
if getent group audio >/dev/null 2>&1 && [ -n "${SUDO_USER:-}" ] && [ "${SUDO_USER}" != "root" ]; then
  sudo usermod -aG audio "${SUDO_USER}" || true
fi
ensure_hevc_decode_support

echo "==> 2a. Kernel-driver tweaks"
cat <<'EOF' | sudo tee /etc/modprobe.d/gc311-stream.conf >/dev/null
options uvcvideo quirks=0x200 timeout=10000
EOF
prepare_hdmi_detection_boot_state

echo "==> 2b. Predictable /dev names for each capture card"
prepare_capture_power_for_discovery
trap restore_capture_power_after_discovery EXIT

discover_gc_capture_pairs() {
    for dev in /dev/video*; do
        props=$(sudo udevadm info -q property -n "$dev" 2>/dev/null || true)
        index=$(cat "/sys/class/video4linux/$(basename "$dev")/index" 2>/dev/null || true)
        if echo "$props" | grep -q 'ID_VENDOR_ID=07ca' \
            && echo "$props" | grep -q 'ID_MODEL_ID=3311' \
            && [ "$index" = "0" ]; then
            tag=$(printf '%s\n' "$props" | awk -F= '/^ID_PATH_TAG=/{print $2}')
            if [ -n "$tag" ]; then
                printf '%s %s\n' "$tag" "$dev"
            fi
        fi
    done | sort -u
}

mapfile -t GC_CAPTURE_PAIRS < <(discover_gc_capture_pairs)
if [ "${#GC_CAPTURE_PAIRS[@]}" -ne 2 ]; then
    for _ in {1..20}; do
        sleep 0.5
        mapfile -t GC_CAPTURE_PAIRS < <(discover_gc_capture_pairs)
        [ "${#GC_CAPTURE_PAIRS[@]}" -eq 2 ] && break
    done
fi

if [ "${#GC_CAPTURE_PAIRS[@]}" -ne 2 ]; then
    echo "⚠️  GC311 capture cards not fully present; skipping udev eye-link refresh." >&2
    if [ "${#GC_CAPTURE_PAIRS[@]}" -eq 0 ]; then
        echo "    Detected: none" >&2
    else
        printf '    Detected: %s\n' "${GC_CAPTURE_PAIRS[@]}" >&2
    fi
    echo "    The server install will continue, and existing /dev/lesavka_* links stay untouched." >&2
else
    LEFT_TAG=${GC_CAPTURE_PAIRS[0]%% *}
    LEFT_DEV=${GC_CAPTURE_PAIRS[0]#* }
    RIGHT_TAG=${GC_CAPTURE_PAIRS[1]%% *}
    RIGHT_DEV=${GC_CAPTURE_PAIRS[1]#* }

    if [ -z "$LEFT_TAG" ] || [ -z "$RIGHT_TAG" ] || [ "$LEFT_TAG" = "$RIGHT_TAG" ]; then
        echo "⚠️  GC311 cards were detected, but the capture path tags are incomplete or duplicated." >&2
        printf '    Left candidate:  %s\n' "${GC_CAPTURE_PAIRS[0]:-missing}" >&2
        printf '    Right candidate: %s\n' "${GC_CAPTURE_PAIRS[1]:-missing}" >&2
        echo "    Skipping udev eye-link refresh and preserving any existing /dev/lesavka_* links." >&2
    else
        printf '   ↪ Left  card: %s  (%s)\n' "$LEFT_DEV" "$LEFT_TAG"
        printf '   ↪ Right card: %s  (%s)\n' "$RIGHT_DEV" "$RIGHT_TAG"

        sudo tee /etc/udev/rules.d/85-gc311.rules >/dev/null <<EOF
# auto-generated by lesavka/scripts/daemon/install-server.sh - DO NOT EDIT
SUBSYSTEM=="video4linux", ATTR{index}=="0", ENV{ID_PATH_TAG}=="$LEFT_TAG",  SYMLINK+="lesavka_l_eye"
SUBSYSTEM=="video4linux", ATTR{index}=="0", ENV{ID_PATH_TAG}=="$RIGHT_TAG", SYMLINK+="lesavka_r_eye"
EOF

        sudo udevadm control --reload
        sudo udevadm trigger --subsystem-match=video4linux
        sudo udevadm settle
    fi
fi
restore_capture_power_after_discovery
trap - EXIT

echo "==> 3. Rust toolchain"
sudo rustup default stable
run_as_user rustup default stable

resolve_source_checkout

echo "==> 4b. Kernel upgrade (optional)"
if [[ "${LESAVKA_KERNEL_UPDATE:-0}" != "0" ]]; then
  sudo LESAVKA_KERNEL_BUILD_USER="$ORIG_USER" bash "$SRC_DIR/scripts/kernel/build-linux-rpi.sh"
else
  echo "⚠️  skipping kernel upgrade (LESAVKA_KERNEL_UPDATE=0)"
fi

echo "==> 4c. Source build"
run_as_user env TMPDIR="$TMPDIR" bash -c "cd '$SRC_DIR/server' && cargo clean && cargo build --release --bins"

echo "==> 5. Install binaries"
install_verified_executable "$SRC_DIR/target/release/lesavka-server" /usr/local/bin/lesavka-server "lesavka-server"
install_verified_executable "$SRC_DIR/target/release/lesavka-uvc" /usr/local/bin/lesavka-uvc "lesavka-uvc"
install_verified_executable "$SRC_DIR/target/release/lesavka-synthetic-uplink" /usr/local/bin/lesavka-synthetic-uplink "lesavka-synthetic-uplink"
install_verified_executable "$SRC_DIR/scripts/daemon/lesavka-core.sh" /usr/local/bin/lesavka-core.sh "lesavka-core.sh"
install_verified_executable "$SRC_DIR/scripts/daemon/lesavka-uvc.sh" /usr/local/bin/lesavka-uvc.sh "lesavka-uvc.sh"
install_verified_executable "$SRC_DIR/scripts/daemon/lesavka-uvc-kernel-watch.py" /usr/local/lib/lesavka/lesavka-uvc-kernel-watch.py "lesavka-uvc-kernel-watch.py"
install_uvc_kernel_watch_unit
install_verified_executable "$SRC_DIR/scripts/daemon/lesavka-recovery-ladder.sh" /usr/local/bin/lesavka-recovery-ladder "lesavka-recovery-ladder"
install_verified_executable "$SRC_DIR/scripts/manual/run_uac_output_sanity.sh" /usr/local/bin/lesavka-uac-sanity "lesavka-uac-sanity"
install_recovery_ladder_units

echo "==> 5b. Runtime environment defaults"
sudo install -d -m 0755 /etc/lesavka
ensure_server_tls_pki
HDMI_CONNECTOR=$(resolve_hdmi_connector)
if [[ -n $HDMI_CONNECTOR ]]; then
  echo "   ↪ HDMI connector: $HDMI_CONNECTOR"
  write_hdmi_boot_force "$HDMI_CONNECTOR"
else
  echo "⚠️  no connected HDMI connector detected; leaving LESAVKA_HDMI_CONNECTOR unset." >&2
fi
SERVER_ENV_TMP=$(mktemp)
{
  echo "# generated by lesavka/scripts/install/server.sh"
  echo "# Edit only for local hardware overrides; rerunning the installer refreshes defaults."
  if [[ -n $HDMI_CONNECTOR ]]; then
    printf 'LESAVKA_HDMI_CONNECTOR=%s\n' "$HDMI_CONNECTOR"
  fi
  printf 'LESAVKA_CAM_OUTPUT=%s\n' "${LESAVKA_INSTALL_CAM_OUTPUT:-uvc}"
  printf 'LESAVKA_CAM_HDMI_MIRROR=%s\n' "${LESAVKA_INSTALL_CAM_HDMI_MIRROR:-${LESAVKA_CAM_HDMI_MIRROR:-auto}}"
  printf 'LESAVKA_CAM_CODEC=%s\n' "${INSTALL_CAM_CODEC}"
  printf 'LESAVKA_UPLINK_CAMERA_CODEC=%s\n' "${INSTALL_CAM_CODEC}"
  if [[ -n "$INSTALL_HEVC_DECODER" ]]; then
    printf 'LESAVKA_HEVC_DECODER=%s\n' "$INSTALL_HEVC_DECODER"
  fi
  printf 'LESAVKA_ALLOW_SOFTWARE_VIDEO=%s\n' "${INSTALL_ALLOW_SOFTWARE_VIDEO}"
  printf 'LESAVKA_UPLINK_AUDIO_CODEC=%s\n' "${INSTALL_UPLINK_AUDIO_CODEC}"
  printf 'LESAVKA_CAM_WIDTH=%s\n' "${LESAVKA_CAM_WIDTH:-1920}"
  printf 'LESAVKA_CAM_HEIGHT=%s\n' "${LESAVKA_CAM_HEIGHT:-1080}"
  printf 'LESAVKA_CAM_FPS=%s\n' "${LESAVKA_CAM_FPS:-30}"
  printf 'LESAVKA_HDMI_WIDTH=%s\n' "${LESAVKA_HDMI_WIDTH:-1920}"
  printf 'LESAVKA_HDMI_HEIGHT=%s\n' "${LESAVKA_HDMI_HEIGHT:-1080}"
  printf 'LESAVKA_HDMI_SINK=%s\n' "${LESAVKA_HDMI_SINK:-fbdevsink}"
  printf 'LESAVKA_HDMI_FBDEV=%s\n' "${LESAVKA_HDMI_FBDEV:-/dev/fb0}"
  printf 'LESAVKA_HDMI_DRIVER=%s\n' "${LESAVKA_HDMI_DRIVER:-vc4}"
  printf 'LESAVKA_HDMI_PRESENTATION_DELAY_US=%s\n' "${LESAVKA_HDMI_PRESENTATION_DELAY_US:-180000}"
  printf 'LESAVKA_UAC_DEV=%s\n' "${LESAVKA_UAC_DEV:-hw:UAC2Gadget,0}"
  printf 'LESAVKA_ALSA_DEV=%s\n' "${LESAVKA_ALSA_DEV:-hw:UAC2Gadget,0}"
  printf 'LESAVKA_UAC_HOST_MIXER_CONTROLS=%s\n' "${LESAVKA_UAC_HOST_MIXER_CONTROLS:-0}"
  printf 'LESAVKA_UAC_HDMI_COMPENSATION_US=%s\n' "${LESAVKA_UAC_HDMI_COMPENSATION_US:-205000}"
  printf 'LESAVKA_UAC_SESSION_CLOCK_ALIGN=%s\n' "${LESAVKA_UAC_SESSION_CLOCK_ALIGN:-0}"
  printf 'LESAVKA_UAC_APP_LEAKY_TYPE=%s\n' "${LESAVKA_UAC_APP_LEAKY_TYPE:-downstream}"
  printf 'LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS=%s\n' "${LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS:-350}"
  printf 'LESAVKA_UPSTREAM_MAX_LIVE_LAG_MS=%s\n' "${LESAVKA_UPSTREAM_MAX_LIVE_LAG_MS:-1000}"
  printf 'LESAVKA_UPSTREAM_STARTUP_TIMEOUT_MS=%s\n' "${LESAVKA_UPSTREAM_STARTUP_TIMEOUT_MS:-60000}"
  printf 'LESAVKA_UPSTREAM_MJPEG_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_MJPEG_AUDIO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US}"
  printf 'LESAVKA_UPSTREAM_MJPEG_VIDEO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_MJPEG_VIDEO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US}"
  printf 'LESAVKA_UPSTREAM_MJPEG_AUDIO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_MJPEG_AUDIO_PLAYOUT_OFFSET_US:-$DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US}"
  printf 'LESAVKA_UPSTREAM_MJPEG_VIDEO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_MJPEG_VIDEO_PLAYOUT_OFFSET_US:-$DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US}"
  printf 'LESAVKA_UPSTREAM_HEVC_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_HEVC_AUDIO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US}"
  printf 'LESAVKA_UPSTREAM_HEVC_VIDEO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_HEVC_VIDEO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_HEVC_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US}"
  printf 'LESAVKA_UPSTREAM_HEVC_AUDIO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_HEVC_AUDIO_PLAYOUT_OFFSET_US:-$DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US}"
  printf 'LESAVKA_UPSTREAM_HEVC_VIDEO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_HEVC_VIDEO_PLAYOUT_OFFSET_US:-$DEFAULT_HEVC_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US}"
  printf 'LESAVKA_UPSTREAM_MJPEG_PCM_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_MJPEG_PCM_AUDIO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_MJPEG_PCM_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US}"
  printf 'LESAVKA_UPSTREAM_MJPEG_PCM_AUDIO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_MJPEG_PCM_AUDIO_PLAYOUT_OFFSET_US:-$DEFAULT_MJPEG_PCM_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US}"
  printf 'LESAVKA_UPSTREAM_MJPEG_PCM_VIDEO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_MJPEG_PCM_VIDEO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_MJPEG_PCM_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US}"
  printf 'LESAVKA_UPSTREAM_MJPEG_PCM_VIDEO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_MJPEG_PCM_VIDEO_PLAYOUT_OFFSET_US:-$DEFAULT_MJPEG_PCM_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US}"
  printf 'LESAVKA_UPSTREAM_HEVC_PCM_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_HEVC_PCM_AUDIO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_HEVC_PCM_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US}"
  printf 'LESAVKA_UPSTREAM_HEVC_PCM_AUDIO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_HEVC_PCM_AUDIO_PLAYOUT_OFFSET_US:-$DEFAULT_HEVC_PCM_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US}"
  printf 'LESAVKA_UPSTREAM_HEVC_PCM_VIDEO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_HEVC_PCM_VIDEO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_HEVC_PCM_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US}"
  printf 'LESAVKA_UPSTREAM_HEVC_PCM_VIDEO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_HEVC_PCM_VIDEO_PLAYOUT_OFFSET_US:-$DEFAULT_HEVC_PCM_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US}"
  printf 'LESAVKA_UPSTREAM_MJPEG_OPUS_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_MJPEG_OPUS_AUDIO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_MJPEG_OPUS_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US}"
  printf 'LESAVKA_UPSTREAM_MJPEG_OPUS_AUDIO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_MJPEG_OPUS_AUDIO_PLAYOUT_OFFSET_US:-$DEFAULT_MJPEG_OPUS_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US}"
  printf 'LESAVKA_UPSTREAM_MJPEG_OPUS_VIDEO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_MJPEG_OPUS_VIDEO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_MJPEG_OPUS_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US}"
  printf 'LESAVKA_UPSTREAM_MJPEG_OPUS_VIDEO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_MJPEG_OPUS_VIDEO_PLAYOUT_OFFSET_US:-$DEFAULT_MJPEG_OPUS_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US}"
  printf 'LESAVKA_UPSTREAM_HEVC_OPUS_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_HEVC_OPUS_AUDIO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_HEVC_OPUS_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US}"
  printf 'LESAVKA_UPSTREAM_HEVC_OPUS_AUDIO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_HEVC_OPUS_AUDIO_PLAYOUT_OFFSET_US:-$DEFAULT_HEVC_OPUS_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US}"
  printf 'LESAVKA_UPSTREAM_HEVC_OPUS_VIDEO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_HEVC_OPUS_VIDEO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_HEVC_OPUS_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US}"
  printf 'LESAVKA_UPSTREAM_HEVC_OPUS_VIDEO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_HEVC_OPUS_VIDEO_PLAYOUT_OFFSET_US:-$DEFAULT_HEVC_OPUS_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US}"
  printf 'LESAVKA_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US}"
  printf 'LESAVKA_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US}"
  printf 'LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=%s\n' "$(resolve_upstream_audio_playout_offset_us)"
  printf 'LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=%s\n' "$(resolve_upstream_video_playout_offset_us)"
  printf 'LESAVKA_UPSTREAM_PAIR_SLACK_US=%s\n' "${LESAVKA_UPSTREAM_PAIR_SLACK_US:-80000}"
  printf 'LESAVKA_UPSTREAM_AUDIO_MASTER_WAIT_GRACE_MS=%s\n' "${LESAVKA_UPSTREAM_AUDIO_MASTER_WAIT_GRACE_MS:-350}"
  printf 'LESAVKA_UPSTREAM_STALE_DROP_MS=%s\n' "${LESAVKA_UPSTREAM_STALE_DROP_MS:-80}"
  printf 'LESAVKA_UVC_HEVC_JPEG_QUALITY=%s\n' "${LESAVKA_UVC_HEVC_JPEG_QUALITY:-72}"
  printf 'LESAVKA_UVC_HEVC_FREEZE_ON_SIZE_DROP=%s\n' "${LESAVKA_UVC_HEVC_FREEZE_ON_SIZE_DROP:-1}"
  printf 'LESAVKA_UVC_HEVC_SIZE_DROP_PCT=%s\n' "${LESAVKA_UVC_HEVC_SIZE_DROP_PCT:-45}"
  printf 'LESAVKA_UVC_HEVC_MIN_REFERENCE_BYTES=%s\n' "${LESAVKA_UVC_HEVC_MIN_REFERENCE_BYTES:-65536}"
  printf 'LESAVKA_UVC_HEVC_MIN_PAYLOAD_DISTINCT_BYTES=%s\n' "${LESAVKA_UVC_HEVC_MIN_PAYLOAD_DISTINCT_BYTES:-12}"
  printf 'LESAVKA_UVC_HEVC_DOMINANT_BYTE_PCT=%s\n' "${LESAVKA_UVC_HEVC_DOMINANT_BYTE_PCT:-92}"
  printf 'LESAVKA_UVC_HEVC_SPOOL_PULL_TIMEOUT_MS=%s\n' "${LESAVKA_UVC_HEVC_SPOOL_PULL_TIMEOUT_MS:-20}"
  printf 'LESAVKA_UVC_HEVC_FRESHNESS_QUEUE_BUFFERS=%s\n' "${LESAVKA_UVC_HEVC_FRESHNESS_QUEUE_BUFFERS:-2}"
  printf 'LESAVKA_UVC_HEVC_DECODE_MISS_LIMIT=%s\n' "${LESAVKA_UVC_HEVC_DECODE_MISS_LIMIT:-15}"
  printf 'LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE=%s\n' "${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE:-1}"
  printf 'LESAVKA_UVC_DIRECT_MJPEG_JPEG_QUALITY=%s\n' "${LESAVKA_UVC_DIRECT_MJPEG_JPEG_QUALITY:-60}"
  printf 'LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_PULL_TIMEOUT_MS=%s\n' "${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_PULL_TIMEOUT_MS:-0}"
  printf 'LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_MISS_LIMIT=%s\n' "${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_MISS_LIMIT:-30}"
  printf 'LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_RSS_LIMIT_MB=%s\n' "${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_RSS_LIMIT_MB:-384}"
  printf 'LESAVKA_UVC_DIRECT_MJPEG_VISUAL_GUARD=%s\n' "${LESAVKA_UVC_DIRECT_MJPEG_VISUAL_GUARD:-1}"
  printf 'LESAVKA_UVC_DIRECT_MJPEG_SIZE_DROP_PCT=%s\n' "${LESAVKA_UVC_DIRECT_MJPEG_SIZE_DROP_PCT:-18}"
  printf 'LESAVKA_UVC_DIRECT_MJPEG_MIN_REFERENCE_BYTES=%s\n' "${LESAVKA_UVC_DIRECT_MJPEG_MIN_REFERENCE_BYTES:-49152}"
  printf 'LESAVKA_SERVER_BIND_ADDR=%s\n' "${INSTALL_SERVER_BIND_ADDR}"
  printf 'LESAVKA_UVC_CODEC=%s\n' "${INSTALL_UVC_CODEC}"
  printf 'LESAVKA_UVC_WIDTH=%s\n' "${LESAVKA_UVC_WIDTH:-1280}"
  printf 'LESAVKA_UVC_HEIGHT=%s\n' "${LESAVKA_UVC_HEIGHT:-720}"
  printf 'LESAVKA_UVC_FPS=%s\n' "${LESAVKA_UVC_FPS:-20}"
  printf 'LESAVKA_UVC_INTERVAL=%s\n' "${LESAVKA_UVC_INTERVAL:-500000}"
  printf 'LESAVKA_UVC_FRAME_META=%s\n' "${INSTALL_UVC_FRAME_META}"
  printf 'LESAVKA_UVC_FRAME_META_LOG_PATH=%s\n' "${INSTALL_UVC_FRAME_META_LOG_PATH}"
  printf 'LESAVKA_REQUIRE_TLS=%s\n' "${LESAVKA_REQUIRE_TLS:-1}"
  printf 'LESAVKA_TLS_CERT=%s\n' "${LESAVKA_TLS_CERT:-$LESAVKA_TLS_DIR/server.crt}"
  printf 'LESAVKA_TLS_KEY=%s\n' "${LESAVKA_TLS_KEY:-$LESAVKA_TLS_DIR/server.key}"
  printf 'LESAVKA_TLS_CLIENT_CA=%s\n' "${LESAVKA_TLS_CLIENT_CA:-$LESAVKA_TLS_DIR/ca.crt}"
  printf 'LESAVKA_PASTE_KEY_FILE=%s\n' "$LESAVKA_PASTE_KEY_FILE"
} >"$SERVER_ENV_TMP"

UVC_ENV_TMP=$(mktemp)
render_uvc_env_file >"$UVC_ENV_TMP"
UVC_ENV_CHANGED=1
if sudo test -f /etc/lesavka/uvc.env && sudo cmp -s "$UVC_ENV_TMP" /etc/lesavka/uvc.env; then
  UVC_ENV_CHANGED=0
fi
UDC_STATE=$(udc_state)
LIVE_GADGET_BOUND=0
if live_gadget_bound; then
  LIVE_GADGET_BOUND=1
fi
HOST_GADGET_PROTECTED=0
if is_attached_state "$UDC_STATE" || [[ "$LIVE_GADGET_BOUND" == "1" ]]; then
  HOST_GADGET_PROTECTED=1
fi
EXPLICIT_GADGET_REBUILD=0
if [[ -n ${LESAVKA_FORCE_GADGET_REBUILD:-} ]]; then
  EXPLICIT_GADGET_REBUILD=1
fi
LIVE_UVC_DESCRIPTOR_MISMATCH=0
if [[ "$HOST_GADGET_PROTECTED" == "1" ]] && uvc_gadget_present && ! live_uvc_descriptor_matches_request; then
  LIVE_UVC_DESCRIPTOR_MISMATCH=1
fi
LIVE_UAC_DESCRIPTOR_MISMATCH=0
if [[ "$HOST_GADGET_PROTECTED" == "1" ]] && ! live_uac_descriptor_matches_request; then
  LIVE_UAC_DESCRIPTOR_MISMATCH=1
fi
LIVE_UVC_FUNCTION_MISSING=0
if [[ -z ${LESAVKA_DISABLE_UVC:-} ]] && [[ "$HOST_GADGET_PROTECTED" == "1" ]] && ! uvc_gadget_present; then
  LIVE_UVC_FUNCTION_MISSING=1
fi
ATTACHED_UVC_CHANGE_DEFERRED=0
if [[ "$HOST_GADGET_PROTECTED" == "1" ]] \
  && { [[ "$LIVE_UVC_DESCRIPTOR_MISMATCH" == "1" ]] || [[ "$LIVE_UAC_DESCRIPTOR_MISMATCH" == "1" ]] || [[ "$LIVE_UVC_FUNCTION_MISSING" == "1" ]]; } \
  && { [[ "$EXPLICIT_GADGET_REBUILD" != "1" ]] || [[ -z ${LESAVKA_ALLOW_GADGET_RESET:-} ]]; }; then
  ATTACHED_UVC_CHANGE_DEFERRED=1
fi
ATTACHED_UVC_RESTART_DEFERRED=0
if [[ "$HOST_GADGET_PROTECTED" == "1" ]] \
  && [[ "$UVC_ENV_CHANGED" == "1" ]] \
  && [[ "$LIVE_UVC_DESCRIPTOR_MISMATCH" != "1" ]] \
  && [[ "$LIVE_UAC_DESCRIPTOR_MISMATCH" != "1" ]] \
  && [[ "$LIVE_UVC_FUNCTION_MISSING" != "1" ]] \
  && { [[ "$EXPLICIT_GADGET_REBUILD" != "1" ]] || [[ -z ${LESAVKA_ALLOW_GADGET_RESET:-} ]]; }; then
  ATTACHED_UVC_RESTART_DEFERRED=1
fi
if [[ "$ATTACHED_UVC_CHANGE_DEFERRED" == "1" ]]; then
  echo "⚠️  UVC/UAC runtime settings or live descriptors differ while the host is attached." >&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
  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 "    $(server_active_client_detail)" >&2
    echo "    Leaving /etc/lesavka/server.env and /etc/lesavka/uvc.env unchanged; binaries were installed but running services were left untouched." >&2
    echo "    Rerun the installer after clients disconnect, or set LESAVKA_INSTALL_RESTART_WITH_CLIENTS=1 to force a server-only restart." >&2
    rm -f "$SERVER_ENV_TMP" "$UVC_ENV_TMP"
    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 installed; live service restart deferred because clients are connected."
    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 UVC codec request: ${INSTALL_UVC_CODEC}"
    exit 0
  fi

  echo "✅  No active gRPC clients detected; applying runtime env and restarting lesavka-server only." >&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 "$UVC_ENV_TMP" /etc/lesavka/uvc.env
  rm -f "$SERVER_ENV_TMP" "$UVC_ENV_TMP"
  sudo install -d -m 0755 /var/log/lesavka
  sudo truncate -s 0 /var/log/lesavka/server.log
  install_lesavka_server_unit_file
  sudo systemctl daemon-reload
  sudo systemctl enable lesavka-server >/dev/null 2>&1 || true
  restart_lesavka_server_only
  sudo /usr/local/bin/lesavka-recovery-ladder snapshot || 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)
  sudo systemctl start lesavka-recovery-ladder.timer >/dev/null 2>&1 || true
  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 "➡️  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 "➡️  Live UVC descriptors may still differ until a maintenance-window gadget rebuild is allowed."
  exit 0
fi
sudo install -m 0644 "$SERVER_ENV_TMP" /etc/lesavka/server.env
sudo install -m 0644 "$UVC_ENV_TMP" /etc/lesavka/uvc.env
rm -f "$SERVER_ENV_TMP" "$UVC_ENV_TMP"
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 "    The installer will not restart lesavka-core or lesavka-uvc because that can wedge the Pi USB controller." >&2
  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 "    $(server_active_client_detail)" >&2
    echo "    Updated /etc/lesavka/server.env and /etc/lesavka/uvc.env, but binaries were installed without restarting live services." >&2
    echo "    Rerun the installer after clients disconnect, or set LESAVKA_INSTALL_RESTART_WITH_CLIENTS=1 to force a server-only restart." >&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 because clients are connected."
    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

  echo "✅  No active gRPC clients detected; updated UVC env matches the live descriptor, restarting lesavka-server only." >&2
  echo "    Preserving lesavka-core so the attached USB gadget is not cycled." >&2
  sudo install -d -m 0755 /var/log/lesavka
  sudo truncate -s 0 /var/log/lesavka/server.log
  install_lesavka_server_unit_file
  sudo systemctl daemon-reload
  sudo systemctl enable lesavka-server >/dev/null 2>&1 || true
  if [[ "${LESAVKA_INSTALL_RESTART_UVC_HELPER:-0}" == "1" ]]; then
    restart_lesavka_uvc_helper_only
    echo "✅  lesavka-uvc helper restarted in-place by explicit request; lesavka-core and the attached USB gadget were preserved."
  else
    echo "    Preserving lesavka-uvc; set LESAVKA_INSTALL_RESTART_UVC_HELPER=1 when a helper-only refresh is required." >&2
  fi
  restart_lesavka_server_only
  sudo /usr/local/bin/lesavka-recovery-ladder snapshot || 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)
  sudo systemctl start lesavka-recovery-ladder.timer >/dev/null 2>&1 || true
  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 "➡️  Recovery ladder: installed, enabled, and started in server-only mode"
  echo "➡️  Live UVC descriptor already matches: ${INSTALL_UVC_CODEC}"
  exit 0
fi

echo "==> 6a. Systemd units - lesavka-core"
cat <<'UNIT' | sudo tee /etc/systemd/system/lesavka-core.service >/dev/null
[Unit]
Description=lesavka USB gadget bring-up
After=sys-kernel-config.mount
Requires=sys-kernel-config.mount

[Service]
Type=oneshot
ExecStart=/usr/local/bin/lesavka-core.sh
RemainAfterExit=yes
Environment=LESAVKA_UVC_FALLBACK=0
EnvironmentFile=-/etc/lesavka/server.env
CapabilityBoundingSet=CAP_SYS_ADMIN CAP_SYS_MODULE
AmbientCapabilities=CAP_SYS_MODULE
MountFlags=slave

[Install]
WantedBy=multi-user.target
UNIT

echo "==> 6b. Systemd units - lesavka-server"
install_lesavka_server_unit_file

echo "==> 6c. Systemd units - initialization"
sudo install -d -m 0755 /var/log/lesavka
sudo rm -f /tmp/lesavka-server.log
sudo truncate -s 0 /var/log/lesavka/server.log
sudo systemctl daemon-reload
sudo systemctl enable lesavka-core lesavka-server

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" ]] && [[ "$HOST_GADGET_PROTECTED" == "1" ]]; 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 [[ "$LIVE_UVC_DESCRIPTOR_MISMATCH" == "1" ]] && [[ "$HOST_GADGET_PROTECTED" == "1" ]]; then
  FORCE_GADGET_REBUILD=1
  GADGET_REBUILD_REASON="live UVC descriptor does not match requested codec ${INSTALL_UVC_CODEC}"
  echo "⚠️  live UVC descriptor does not match requested codec ${INSTALL_UVC_CODEC}; forcing a gadget rebuild so descriptors and runtime agree."
fi
if [[ "$LIVE_UAC_DESCRIPTOR_MISMATCH" == "1" ]] && [[ "$HOST_GADGET_PROTECTED" == "1" ]]; then
  FORCE_GADGET_REBUILD=1
  GADGET_REBUILD_REASON="live UAC descriptor does not match requested host mixer/default audio settings"
  echo "⚠️  live UAC descriptor does not match requested host mixer/default audio settings; forcing a gadget rebuild so descriptors and runtime agree."
fi
if [[ -n ${LESAVKA_FORCE_GADGET_REBUILD:-} ]]; then
  FORCE_GADGET_REBUILD=1
  EXPLICIT_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" ]] && [[ "$HOST_GADGET_PROTECTED" == "1" ]] \
  && { [[ "$EXPLICIT_GADGET_REBUILD" != "1" ]] || [[ -z ${LESAVKA_ALLOW_GADGET_RESET:-} ]]; }; then
  echo "⚠️  ${GADGET_REBUILD_REASON:-Gadget state} requires a rebuild, but UDC state is '$UDC_STATE' (bound=${LIVE_GADGET_BOUND}) and attached-host hard reset was not explicitly 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" ]] || [[ "$HOST_GADGET_PROTECTED" != "1" ]]; 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
  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' (bound=${LIVE_GADGET_BOUND}) - skipping lesavka-core restart."
  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
[Unit]
Description=lesavka UVC control helper
After=lesavka-core.service
Requires=lesavka-core.service

[Service]
ExecStart=/usr/local/bin/lesavka-uvc.sh
Restart=always
RestartSec=2
KillSignal=SIGTERM
KillMode=control-group
TimeoutStopSec=10
StandardError=append:/var/log/lesavka/uvc.stderr
User=root
EnvironmentFile=-/etc/lesavka/uvc.env

[Install]
WantedBy=multi-user.target
UNIT

sudo systemctl daemon-reload
sudo systemctl enable lesavka-uvc

echo "==> 6d. Systemd units - remove legacy reboot watchdog"
sudo systemctl stop lesavka-watchdog.timer lesavka-watchdog.service >/dev/null 2>&1 || true
sudo systemctl disable lesavka-watchdog.timer lesavka-watchdog.service >/dev/null 2>&1 || true
sudo systemctl unmask lesavka-watchdog.timer lesavka-watchdog.service >/dev/null 2>&1 || true
sudo rm -f /etc/systemd/system/lesavka-watchdog.timer \
          /etc/systemd/system/lesavka-watchdog.service \
          /usr/local/bin/lesavka-watchdog.sh \
          /etc/lesavka/watchdog.touch

echo "==> 6e. Systemd units - recovery ladder"
echo "✅  recovery ladder unit files installed and timer enabled; activation waits for successful service verification."

CAN_TOUCH_UVC_SERVICE=1
if [[ "$HOST_GADGET_PROTECTED" == "1" ]] \
  && { [[ "$EXPLICIT_GADGET_REBUILD" != "1" ]] || [[ -z ${LESAVKA_ALLOW_GADGET_RESET:-} ]]; }; then
  CAN_TOUCH_UVC_SERVICE=0
fi
if [[ "$CAN_TOUCH_UVC_SERVICE" != "1" ]] && systemctl is-active --quiet lesavka-uvc; then
  if [[ "${LESAVKA_INSTALL_RESTART_UVC_HELPER:-0}" == "1" ]]; then
    restart_lesavka_uvc_helper_only
    echo "✅  lesavka-uvc helper restarted in-place by explicit request; lesavka-core and the attached USB gadget were preserved."
  else
    echo "✅  lesavka-uvc already active; preserving it during attached-gadget version update."
    echo "   Set LESAVKA_INSTALL_RESTART_UVC_HELPER=1 only during a local maintenance window if the helper binary must be refreshed without cycling lesavka-core."
  fi
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
elif [[ "$UVC_ENV_CHANGED" == "1" ]] && systemctl is-active --quiet lesavka-uvc; then
  sudo systemctl restart lesavka-uvc
  echo "✅  lesavka-uvc restarted with the refreshed UVC runtime settings."
elif systemctl is-active --quiet lesavka-uvc; then
  echo "✅  lesavka-uvc already active; runtime settings unchanged."
else
  sudo truncate -s 0 /var/log/lesavka/uvc.stderr
  sudo systemctl start lesavka-uvc
  echo "✅  lesavka-uvc started to attach the UVC gadget to the host."
fi

validate_uvc_gadget_ready

restart_lesavka_server_only
sudo /usr/local/bin/lesavka-recovery-ladder snapshot || true
sudo systemctl start lesavka-recovery-ladder.timer
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)
PERSISTED_CAM_OUTPUT=$(grep '^LESAVKA_CAM_OUTPUT=' /etc/lesavka/server.env 2>/dev/null | tail -n1 | cut -d= -f2- || true)
PERSISTED_CAM_HDMI_MIRROR=$(grep '^LESAVKA_CAM_HDMI_MIRROR=' /etc/lesavka/server.env 2>/dev/null | tail -n1 | cut -d= -f2- || true)
PERSISTED_SERVER_BIND_ADDR=$(grep '^LESAVKA_SERVER_BIND_ADDR=' /etc/lesavka/server.env 2>/dev/null | tail -n1 | cut -d= -f2- || true)
PERSISTED_UVC_CODEC=$(grep '^LESAVKA_UVC_CODEC=' /etc/lesavka/uvc.env 2>/dev/null | tail -n1 | cut -d= -f2- || true)
echo "✅  lesavka-server installed and restarted..."
if [[ -n $INSTALLED_VERSION || -n $INSTALLED_SHA ]]; then
  echo "➡️  Installed: lesavka-server ${INSTALLED_VERSION:-unknown}${INSTALLED_SHA:+ ($INSTALLED_SHA)}"
fi
if [[ -n $PERSISTED_SERVER_BIND_ADDR ]]; then
  echo "➡️  Server bind: ${PERSISTED_SERVER_BIND_ADDR}"
fi
if [[ -n $PERSISTED_CAM_OUTPUT ]]; then
  echo "➡️  Camera output: ${PERSISTED_CAM_OUTPUT}"
fi
if [[ -n $PERSISTED_CAM_HDMI_MIRROR ]]; then
  echo "➡️  HDMI mirror: ${PERSISTED_CAM_HDMI_MIRROR}"
fi
if [[ -n $PERSISTED_UVC_CODEC ]]; then
  echo "➡️  UVC codec: ${PERSISTED_UVC_CODEC}"
fi
echo "➡️  Client TLS bundle: ${LESAVKA_CLIENT_BUNDLE}"
echo "➡️  Client install can use: sudo env LESAVKA_CLIENT_PKI_BUNDLE=${LESAVKA_CLIENT_BUNDLE} ./scripts/install/client.sh"
echo "➡️  Status: sudo systemctl status lesavka-server --no-pager"
echo "➡️  Logs:   sudo journalctl -u lesavka-server -f --no-pager"
echo "✅  Installed version: lesavka-server ${INSTALLED_VERSION:-unknown}${INSTALLED_SHA:+ ($INSTALLED_SHA)}"
