diff --git a/clients/wearos/profiles.d/example-profile.sh.example b/clients/wearos/profiles.d/example-profile.sh.example index 2bea907..b8d54c0 100644 --- a/clients/wearos/profiles.d/example-profile.sh.example +++ b/clients/wearos/profiles.d/example-profile.sh.example @@ -17,9 +17,12 @@ SERVER_URL="http://minerva.lan:3000" # Generate with: openssl rand -base64 24 API_KEY="REPLACE_ME_WITH_PLAINTEXT_API_KEY" -# Bash array of ADB serials (one per attached watch this profile may target). -# Find a watch's serial with: ./run.sh devices +# Bash array of stable hardware serials (ro.serialno) — one per attached watch +# this profile may target. NOT the adb transport serial (the rotating ip:port +# you see over WiFi): the guard reads ro.serialno so it survives port rotation +# and is identical over WiFi and USB. +# Find a watch's ro.serialno with: ./run.sh devices (prints "transport -> ro.serialno"). # Brummel's watch can appear in multiple profiles; Krey's watch only in hers. ALLOWED_SERIALS=( - "adb-XXXXXXXX-yyyy._adb-tls-connect._tcp" + "XXXXXXXXXXXXXX" ) diff --git a/clients/wearos/run.sh b/clients/wearos/run.sh index 870b9d6..6b690d2 100755 --- a/clients/wearos/run.sh +++ b/clients/wearos/run.sh @@ -13,9 +13,10 @@ # Real Pixel Watch over ADB-WiFi. is the basename of # a file in profiles.d/.sh that holds SERVER_URL, # API_KEY, and ALLOWED_SERIALS (see profiles.d/*.sh.example). -# The build refuses to proceed if the attached watch's ADB -# serial is not listed in the profile's ALLOWED_SERIALS — this -# protects against installing the wrong key on the wrong watch. +# The build refuses to proceed if the attached watch's stable +# hardware serial (ro.serialno) is not listed in the profile's +# ALLOWED_SERIALS — this protects against installing the wrong +# key on the wrong watch. # emulator Wear OS AVD on this host. No profile — uses the hardcoded # emulator-loopback URL (10.0.2.2:3000) and the build's # MISSING_API_KEY sentinel. @@ -278,10 +279,30 @@ resolve_watch_serial() { RESOLVED_SERIAL="${PICKED%% *}" } +# Reads the stable hardware serial (ro.serialno) of the device addressed by the +# adb transport serial $1. The transport serial depends on the connection: +# ip:port over WiFi (rotates on every wireless-debugging restart) or the bare +# hardware serial over USB. ro.serialno is burned into the device and constant +# across both — the right identity for the wrong-watch guard. Writes the result +# to the global RESOLVED_HW_SERIAL (not stdout+$(), so a `die` here aborts the +# main shell rather than being swallowed by a subshell — same rationale as +# RESOLVED_SERIAL above). Aborts if the device can't be reached. +RESOLVED_HW_SERIAL="" +resolve_hardware_serial() { + detect_adb + local transport="$1" + local hw + hw=$("$ADB" -s "$transport" shell getprop ro.serialno 2>/dev/null | tr -d '\r\n' || true) + [[ -n "$hw" ]] || die "Could not read ro.serialno from '$transport'. Is the watch attached and authorized? Check './run.sh devices'." + RESOLVED_HW_SERIAL="$hw" +} + # --- Profile loading (watch target only) ----------------------------------- # Profiles live in profiles.d/.sh and define SERVER_URL, API_KEY, and -# ALLOWED_SERIALS as a bash array. The .sh files are gitignored — see -# profiles.d/example-profile.sh.example for the format. +# ALLOWED_SERIALS as a bash array. ALLOWED_SERIALS holds stable hardware serials +# (ro.serialno), NOT adb transport serials — see resolve_hardware_serial above +# for why. The .sh files are gitignored — see profiles.d/example-profile.sh.example +# for the format. PROFILE_NAME="" SERVER_URL="" @@ -308,16 +329,18 @@ load_profile() { PROFILE_NAME="$name" } -# Aborts (via die) if $1 is not present in ALLOWED_SERIALS. The error message -# names BOTH the attached serial and the allow-list, so the user can see at a -# glance whether they grabbed the wrong watch or typed the wrong profile. +# Aborts (via die) if $1 is not present in ALLOWED_SERIALS. The caller passes +# the device's stable hardware serial (ro.serialno), never the rotating adb +# transport serial. The error message names BOTH the hardware serial and the +# allow-list, so the user can see at a glance whether they grabbed the wrong +# watch or typed the wrong profile. verify_serial_allowed() { local serial="$1" local s for s in "${ALLOWED_SERIALS[@]}"; do [[ "$s" == "$serial" ]] && return 0 done - die "Watch serial '$serial' is NOT in ALLOWED_SERIALS for profile '$PROFILE_NAME'. + die "Watch hardware serial (ro.serialno) '$serial' is NOT in ALLOWED_SERIALS for profile '$PROFILE_NAME'. Allowed: ${ALLOWED_SERIALS[*]} This refusal protects against installing the wrong key on the wrong watch (e.g. Brummel-key on Krey's watch → patient data goes to dev server)." @@ -334,13 +357,18 @@ Available profiles: $(cd "$SCRIPT_DIR/profiles.d" 2>/dev/null && ls *.sh 2>/dev/ resolve_watch_serial ADB_SERIAL="$RESOLVED_SERIAL" fi - verify_serial_allowed "$ADB_SERIAL" + # ADB_SERIAL stays the transport serial (adb addressing, gradle, + # ANDROID_SERIAL). The wrong-watch guard checks the stable hardware + # serial instead, so it survives WiFi port rotation and matches the + # same value whether connected over WiFi or USB. + resolve_hardware_serial "$ADB_SERIAL" + verify_serial_allowed "$RESOLVED_HW_SERIAL" GRADLE_PROPS+=( "-Pdoctate.serverUrl=$SERVER_URL" "-Pdoctate.apiKey=$API_KEY" "-Pdoctate.profileName=$DOCTATE_PROFILE" ) - info "Target: watch (profile=$DOCTATE_PROFILE, serial=$ADB_SERIAL, server=$SERVER_URL)" + info "Target: watch (profile=$DOCTATE_PROFILE, transport=$ADB_SERIAL, hw=$RESOLVED_HW_SERIAL, server=$SERVER_URL)" ;; emulator) if [[ -z "$ADB_SERIAL" ]]; then @@ -514,6 +542,20 @@ cmd_devices() { detect_adb info "Attached devices:" "$ADB" devices -l | sed '1d;/^$/d' | sed 's/^/ /' + # Map each transport serial to its stable hardware serial (ro.serialno) — + # the value that belongs in a profile's ALLOWED_SERIALS. Over WiFi the + # transport serial is a rotating ip:port; over USB it already equals the + # hardware serial. getprop makes the right value explicit either way. + local -a transports + mapfile -t transports < <("$ADB" devices | awk 'NR>1 && $2=="device"{print $1}') + if [[ "${#transports[@]}" -gt 0 ]]; then + info "Hardware serials (ro.serialno — use these in ALLOWED_SERIALS):" + local t hw + for t in "${transports[@]}"; do + hw=$("$ADB" -s "$t" shell getprop ro.serialno 2>/dev/null | tr -d '\r\n' || true) + printf " %s -> %s\n" "$t" "${hw:-}" + done + fi if [[ -n "$ADB_SERIAL" ]]; then info "Pinned by current target: $ADB_SERIAL" fi