Sanadv3/shell_scripts/reset_anker_usb.sh

120 lines
4.2 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# reset_anker_usb.sh — unbind+rebind snd-usb-audio for an Anker USB device.
#
# WHY THIS EXISTS
# The Anker PowerConf A3321 on this Jetson sometimes enumerates with only
# output USB Audio Class descriptors (no capture interface). PulseAudio
# then shows the card with only output-only profiles and the dashboard
# can't expose the mic. Restarting PulseAudio does nothing — UAC
# descriptors are parsed by snd-usb-audio at probe time, persist in
# kernel structs, and only get re-parsed on a fresh driver bind.
#
# `/api/audio/usb-reset` writes directly to
# /sys/bus/usb/drivers/snd-usb-audio/{unbind,bind} when possible. That
# path needs root. This script exists as a sudo fallback so the dashboard
# can recover without Sanad itself running as root.
#
# USAGE
# reset_anker_usb.sh <bus_id> — unbind+rebind given device
# (bus_id like "1-3")
# reset_anker_usb.sh --setup-sudoers — install one-time NOPASSWD entry
# (must be run via sudo)
#
# Exit codes:
# 0 — unbind + rebind both succeeded
# 1 — bus_id missing or device not present
# 2 — no snd-usb-audio interfaces bound to that device
# 3 — unbind or bind sysfs write failed
# 4 — --setup-sudoers used outside of sudo
set -u
USAGE="usage: $(basename "$0") <bus_id> or $(basename "$0") --setup-sudoers"
if [ "$#" -lt 1 ]; then
echo "$USAGE" >&2
exit 1
fi
# ───────────────────── --setup-sudoers ─────────────────────
if [ "$1" = "--setup-sudoers" ]; then
if [ "$(id -u)" -ne 0 ]; then
echo "❌ --setup-sudoers must run as root (use: sudo $0 --setup-sudoers)" >&2
exit 4
fi
# Install a NOPASSWD entry so the unitree user can invoke THIS exact
# script path with sudo without typing a password. Scoped to one
# binary; not a blanket sudo grant.
SELF_PATH="$(readlink -f "$0")"
SUDO_FILE="/etc/sudoers.d/sanad-anker-usb-reset"
cat > "$SUDO_FILE" <<EOF
# Installed by reset_anker_usb.sh --setup-sudoers
# Allows the unitree user to unbind+rebind snd-usb-audio scoped to the
# Anker dongle via this script ONLY — no other sudo privilege granted.
unitree ALL=(root) NOPASSWD: ${SELF_PATH}
EOF
chmod 0440 "$SUDO_FILE"
echo "✅ Installed sudoers entry at $SUDO_FILE"
echo " Dashboard's /api/audio/usb-reset can now recover the Anker without password."
exit 0
fi
BUS_ID="$1"
# Validate the device exists.
DEV_PATH="/sys/bus/usb/devices/${BUS_ID}"
if [ ! -d "$DEV_PATH" ]; then
echo "❌ USB device $BUS_ID not found at $DEV_PATH" >&2
exit 1
fi
# Discover snd-usb-audio interfaces on this device. Don't unbind anything
# else (some Anker firmwares present HID-Consumer for the mute button on
# a separate interface — we leave those alone).
declare -a IFACES=()
for iface_path in "${DEV_PATH}/${BUS_ID}:"*; do
[ -e "$iface_path" ] || continue
driver_link="${iface_path}/driver"
[ -L "$driver_link" ] || continue
driver=$(basename "$(readlink "$driver_link")")
if [ "$driver" = "snd-usb-audio" ]; then
IFACES+=("$(basename "$iface_path")")
fi
done
if [ "${#IFACES[@]}" -eq 0 ]; then
echo "❌ No snd-usb-audio interfaces bound to device $BUS_ID" >&2
exit 2
fi
echo " Re-binding snd-usb-audio for $BUS_ID (interfaces: ${IFACES[*]})"
UNBIND="/sys/bus/usb/drivers/snd-usb-audio/unbind"
BIND="/sys/bus/usb/drivers/snd-usb-audio/bind"
# Unbind first; on failure exit before rebind so we don't leave the device
# in a half-bound state.
for iface in "${IFACES[@]}"; do
if ! echo -n "$iface" > "$UNBIND" 2>/dev/null; then
echo "❌ unbind failed: $iface$UNBIND" >&2
exit 3
fi
echo " unbound: $iface"
done
# Brief settle — snd-usb-audio's release path tears down ALSA card N.
sleep 0.5
for iface in "${IFACES[@]}"; do
if ! echo -n "$iface" > "$BIND" 2>/dev/null; then
echo "❌ rebind failed: $iface$BIND" >&2
exit 3
fi
echo " bound: $iface"
done
# Let probe complete so callers can pactl list cards right after.
sleep 1.0
echo "✅ snd-usb-audio re-bound for $BUS_ID"
exit 0