mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-11 12:07:52 +01:00
phone_focus_mode: reach the phone over adb without touching it
Android's Wireless debugging is deliberately ephemeral: the listener dies when you leave the settings screen or the device sleeps, and it picks a new random port each time. In one session it offered 35545, then 40401, then 43781, and vanished entirely twice. Every deploy therefore started with tapping through Settings and reading a port off the screen. enable_persistent_adb.sh waits for any such transient window, then uses it to pin adbd to port 5555 - the port deploy.sh has always assumed - and installs a Magisk service.d boot script so the same happens on every boot. The phone is on DHCP with no reservation, so the script identifies it by MAC rather than trusting a hardcoded address: a ping sweep populates the neighbour table and the address is looked up there. That beats a router reservation because it needs no router access and works on any network. One bug worth naming: the first version took the first `device` line from `adb devices`. Pairing also leaves an mDNS _adb-tls-connect._tcp entry for the same phone, so it reported port 5555 as reachable while nmap showed it closed. It now matches the exact ip:port. Verified: nmap showed 5555 closed then open across the switch; a run against a deliberately wrong IP reported "phone moved" and connected; and after `adb reboot` the script connected with no arguments and no interaction, with /proc/uptime at 149s and service.adb.tcp.port reading 5555, so the boot script really ran. shellcheck clean. Trade-off accepted by the user: adbd on a fixed port is reachable to anything on the LAN, though it still requires an authorised key. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ed1a224ce7
commit
287512f011
19
docs/superpowers/contracts/persistent-adb-2026-08-03.json
Normal file
19
docs/superpowers/contracts/persistent-adb-2026-08-03.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"title": "phone_focus_mode/enable_persistent_adb.sh: make adb-over-Wi-Fi reachable without anyone touching the phone",
|
||||
"objective": "Android's Wireless debugging is deliberately ephemeral: the listener dies when you leave the settings screen or the device sleeps, and it picks a new random port every time it comes back (observed three different ports in one session: 35545, 40401, 43781). Every deploy therefore began with tapping through Settings on the phone. Pin adbd to a fixed port that comes back on its own after a reboot, and make the laptop able to find the phone even if DHCP moves it. Success: with the phone merely on the same network - screen off, nothing opened, nothing tapped - `adb connect` reaches it, and it still does after a reboot.",
|
||||
"acceptance_criteria": [
|
||||
"adbd listens on the fixed port 5555, which is the port deploy.sh has always assumed",
|
||||
"A Magisk service.d boot script re-applies the port on every boot, verified by an actual reboot rather than inferred from the mechanism",
|
||||
"The laptop finds the phone by MAC when the configured IP is wrong, since the phone is on DHCP and has no reservation; verified by running the script against a deliberately wrong address",
|
||||
"The script never reports success for a connection it does not actually have - it matches the exact ip:port, not the first `device` line, because pairing also leaves an mDNS _adb-tls-connect._tcp entry for the same phone that made an earlier version report a closed port as reachable",
|
||||
"It fails loudly and bounded rather than spinning forever when no window ever appears",
|
||||
"shellcheck clean, set -uo pipefail, no inline logic in another language"
|
||||
],
|
||||
"out_of_scope": [
|
||||
"A router DHCP reservation - it would work but needs router access and only helps on one network; MAC discovery is strictly better and needs nothing",
|
||||
"USB debugging - the point is to not touch the phone at all",
|
||||
"Turning off Android's Wireless debugging UI - it stays available as the bootstrap path for a phone that has never been set up",
|
||||
"Any change to what deploy.sh does once connected"
|
||||
],
|
||||
"verifier": "./enable_persistent_adb.sh 192.168.1.64 <ephemeral-port> (installs the boot script and switches the port); nmap -p 5555 -Pn 192.168.1.64 shows open; ./enable_persistent_adb.sh 192.168.1.99 (wrong IP on purpose) reports 'phone moved' and still connects; adb reboot, then ./enable_persistent_adb.sh with no arguments connects with zero interaction; shellcheck enable_persistent_adb.sh"
|
||||
}
|
||||
51
docs/superpowers/evidence/persistent-adb-2026-08-03.json
Normal file
51
docs/superpowers/evidence/persistent-adb-2026-08-03.json
Normal file
@ -0,0 +1,51 @@
|
||||
{
|
||||
"intent": "Let the laptop reach the phone over adb at any time without anyone opening Settings, tapping a pairing dialog, or reading a port off the screen.",
|
||||
"scope": [
|
||||
"phone_focus_mode/enable_persistent_adb.sh - new script",
|
||||
"On-device: /data/adb/service.d/99-adb-tcp-5555.sh - new Magisk boot script",
|
||||
"Non-goal: no change to deploy.sh, which already assumes port 5555",
|
||||
"Non-goal: no router DHCP reservation - MAC discovery replaces the need for one"
|
||||
],
|
||||
"changes": [
|
||||
"Wait for any transient Wireless debugging window (trying a known port first, then an nmap sweep of the ephemeral range), then use it to setprop service.adb.tcp.port=5555 and restart adbd.",
|
||||
"Install a Magisk service.d boot script that re-applies the port on every boot, so the phone comes back reachable without interaction.",
|
||||
"Find the phone by MAC when the configured IP is wrong: the phone is on DHCP with no reservation, so a hardcoded address is a latent failure. A ping sweep populates the neighbour table and the MAC is looked up there.",
|
||||
"Match the exact ip:port when deciding whether a connection exists. The first version took the first `device` line, and since pairing also leaves an mDNS _adb-tls-connect._tcp entry for the same phone, it reported port 5555 as reachable while nmap showed it closed."
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"command": "./enable_persistent_adb.sh 192.168.1.64 40401",
|
||||
"result": "pass",
|
||||
"evidence": "Installed the boot script (-rwxr-xr-x root root 373) and switched the port; nmap -p 5555 then showed '5555/tcp open' where it had shown 'closed' immediately before."
|
||||
},
|
||||
{
|
||||
"command": "./enable_persistent_adb.sh 192.168.1.99 # deliberately wrong IP",
|
||||
"result": "pass",
|
||||
"evidence": "'phone moved: 192.168.1.99 -> 192.168.1.64' then 'reachable on 5555 at the new address'. MAC discovery works."
|
||||
},
|
||||
{
|
||||
"command": "adb reboot; ./enable_persistent_adb.sh # no arguments, no interaction",
|
||||
"result": "pass",
|
||||
"evidence": "'already reachable on 5555 - nothing to do'. /proc/uptime was 149s so the reboot was real, and getprop service.adb.tcp.port returned 5555, proving the boot script ran rather than a stale adbd surviving."
|
||||
},
|
||||
{
|
||||
"command": "shellcheck enable_persistent_adb.sh",
|
||||
"result": "pass",
|
||||
"evidence": "No findings."
|
||||
},
|
||||
{
|
||||
"command": "Observed port drift during one session",
|
||||
"result": "pass",
|
||||
"evidence": "Wireless debugging offered 35545, then 40401, then 43781, and the listener vanished entirely twice when the screen slept. This is the failure mode the fixed port removes."
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"adbd on a fixed port is reachable to anything on the LAN. It still requires an authorised key and an unknown key prompts on-device, but this is a wider surface than ephemeral Wireless debugging. Accepted explicitly by the user.",
|
||||
"PHONE_MAC is hardcoded to this handset; a new phone needs that constant updated. The script would otherwise silently fail to rediscover a moved device.",
|
||||
"Credential-encrypted storage means a freshly rebooted, still-locked phone accepts adb but cannot screencap or read app data until it is unlocked once - relevant when scripting post-reboot verification."
|
||||
],
|
||||
"rollback": [
|
||||
"rm /data/adb/service.d/99-adb-tcp-5555.sh and reboot, or setprop service.adb.tcp.port -1 followed by stop/start adbd to drop back to USB-only.",
|
||||
"Confirm with nmap -p 5555 that the port is closed afterwards, and that Wireless debugging still works as the bootstrap path."
|
||||
]
|
||||
}
|
||||
193
phone_focus_mode/enable_persistent_adb.sh
Executable file
193
phone_focus_mode/enable_persistent_adb.sh
Executable file
@ -0,0 +1,193 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# Make adb-over-Wi-Fi permanent on the phone (port 5555).
|
||||
#
|
||||
# Android's "Wireless debugging" is deliberately ephemeral: the listener dies
|
||||
# when you leave the settings screen or the device sleeps, and it picks a new
|
||||
# random port every time it comes back. That is why every deploy so far has
|
||||
# needed someone to tap through the phone first.
|
||||
#
|
||||
# This script waits for ANY transient wireless-debugging window, then uses it
|
||||
# to switch adbd to a fixed port 5555 and installs a Magisk boot script so the
|
||||
# same thing happens on every boot. After one successful run the phone is
|
||||
# reachable at $PHONE_IP:5555 forever, with no tapping.
|
||||
#
|
||||
# Requires: root (Magisk) on the device, and the host already paired.
|
||||
#
|
||||
# Usage:
|
||||
# ./enable_persistent_adb.sh [PHONE_IP] [KNOWN_PORT]
|
||||
# PHONE_IP defaults to 192.168.1.64.
|
||||
# KNOWN_PORT, if given, is tried first before falling back to a port scan.
|
||||
# ============================================================================
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
PHONE_IP="${1:-192.168.1.64}"
|
||||
readonly KNOWN_PORT="${2:-}"
|
||||
readonly FIXED_PORT=5555
|
||||
# The phone is on DHCP, so its address can move. Its MAC cannot, so that is
|
||||
# what we actually identify it by; PHONE_IP is only a first guess.
|
||||
readonly PHONE_MAC="00:08:22:68:81:fc"
|
||||
# Android allocates the wireless-debugging port from the ephemeral range.
|
||||
readonly SCAN_RANGE="30000-65535"
|
||||
# Give up rather than spin forever if nobody ever wakes the phone.
|
||||
readonly DEADLINE_SECONDS=1800
|
||||
|
||||
readonly BOOT_SCRIPT=/data/adb/service.d/99-adb-tcp-5555.sh
|
||||
|
||||
log() { printf '[persistent-adb] %s\n' "$1"; }
|
||||
|
||||
require() {
|
||||
if ! command -v "$1" >/dev/null 2>&1; then
|
||||
echo "Error: $1 is required but not installed" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Echo the serial iff THIS endpoint is connected and not offline.
|
||||
#
|
||||
# Deliberately matches the exact ip:port rather than the first `device` line.
|
||||
# After pairing, adb also auto-connects an mDNS `_adb-tls-connect._tcp` entry
|
||||
# for the same phone; taking the first line made `try_connect 5555` report
|
||||
# success while port 5555 was in fact closed.
|
||||
connected_serial() {
|
||||
local endpoint="$1"
|
||||
adb devices 2>/dev/null |
|
||||
awk -v want="$endpoint" '$1 == want && $2 == "device" {print $1; exit}'
|
||||
}
|
||||
|
||||
# Try to reach adbd. Returns 0 and sets REPLY to the serial when connected.
|
||||
try_connect() {
|
||||
local port="$1" endpoint
|
||||
endpoint="${PHONE_IP}:${port}"
|
||||
adb connect "$endpoint" >/dev/null 2>&1 || return 1
|
||||
# `connect` succeeds optimistically; the device can still be offline.
|
||||
sleep 2
|
||||
REPLY="$(connected_serial "$endpoint")"
|
||||
[[ -n "$REPLY" ]]
|
||||
}
|
||||
|
||||
# Locate the phone on the current subnet by MAC and echo its IP.
|
||||
#
|
||||
# DHCP can hand the phone a different address at any time, which would break a
|
||||
# hardcoded PHONE_IP. A ping sweep populates the neighbour table, then we look
|
||||
# the MAC up in it. Prefer this over a router DHCP reservation because it needs
|
||||
# no router access and keeps working on any network the laptop joins.
|
||||
discover_ip_by_mac() {
|
||||
local subnet
|
||||
subnet="$(ip -4 route show dev "$(ip -4 route show default |
|
||||
awk '/default/ {print $5; exit}')" 2>/dev/null |
|
||||
awk '/proto kernel/ {print $1; exit}')"
|
||||
[[ -n "$subnet" ]] || return 1
|
||||
|
||||
nmap -sn -T4 "$subnet" >/dev/null 2>&1
|
||||
ip neigh show 2>/dev/null |
|
||||
awk -v mac="$PHONE_MAC" 'tolower($5) == tolower(mac) {print $1; exit}'
|
||||
}
|
||||
|
||||
# Find the current ephemeral wireless-debugging port, if one is listening.
|
||||
scan_for_port() {
|
||||
nmap -p "$SCAN_RANGE" --open -T4 -Pn --min-rate 3000 "$PHONE_IP" 2>/dev/null |
|
||||
awk -F/ '/^[0-9]+\/tcp[[:space:]]+open/ {print $1}' | head -1
|
||||
}
|
||||
|
||||
# Switch adbd to the fixed port and persist it across reboots.
|
||||
make_permanent() {
|
||||
local serial="$1"
|
||||
|
||||
if ! adb -s "$serial" shell 'su -c id' 2>/dev/null | grep -q 'uid=0'; then
|
||||
echo "Error: no root on the device; cannot install the boot script." >&2
|
||||
echo " adb tcpip $FIXED_PORT alone would not survive a reboot." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "installing Magisk boot script at $BOOT_SCRIPT"
|
||||
# service.d runs late in boot, after the network is up, which is what adbd
|
||||
# needs. Restarting adbd is what actually makes it re-read the port.
|
||||
adb -s "$serial" shell "su -c 'cat > $BOOT_SCRIPT'" <<EOF
|
||||
#!/system/bin/sh
|
||||
# Installed by phone_focus_mode/enable_persistent_adb.sh.
|
||||
# Pin adb-over-Wi-Fi to a fixed port so the laptop can always connect without
|
||||
# anyone tapping through Settings -> Wireless debugging first.
|
||||
setprop service.adb.tcp.port $FIXED_PORT
|
||||
stop adbd
|
||||
start adbd
|
||||
EOF
|
||||
adb -s "$serial" shell "su -c 'chmod 755 $BOOT_SCRIPT'"
|
||||
|
||||
log "switching adbd to port $FIXED_PORT now"
|
||||
adb -s "$serial" shell "su -c 'setprop service.adb.tcp.port $FIXED_PORT; stop adbd; start adbd'"
|
||||
# adbd restarting drops every current connection, including this one.
|
||||
sleep 4
|
||||
}
|
||||
|
||||
main() {
|
||||
require adb
|
||||
require nmap
|
||||
|
||||
log "waiting for a wireless-debugging window on $PHONE_IP (up to $((DEADLINE_SECONDS / 60))m)"
|
||||
log "wake the phone and open Settings -> Developer options -> Wireless debugging"
|
||||
|
||||
local deadline=$((SECONDS + DEADLINE_SECONDS))
|
||||
local serial=""
|
||||
|
||||
while ((SECONDS < deadline)); do
|
||||
# Cheapest first: the fixed port may already be live from a prior run.
|
||||
if try_connect "$FIXED_PORT"; then
|
||||
serial="$REPLY"
|
||||
log "already reachable on $FIXED_PORT - nothing to do"
|
||||
printf '%s\n' "$serial"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Not at the expected address — DHCP may have moved it. Re-find the
|
||||
# phone by MAC before concluding that adbd is down.
|
||||
local moved
|
||||
moved="$(discover_ip_by_mac)"
|
||||
if [[ -n "$moved" && "$moved" != "$PHONE_IP" ]]; then
|
||||
log "phone moved: $PHONE_IP -> $moved"
|
||||
PHONE_IP="$moved"
|
||||
if try_connect "$FIXED_PORT"; then
|
||||
serial="$REPLY"
|
||||
log "reachable on $FIXED_PORT at the new address"
|
||||
printf '%s\n' "$serial"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "$KNOWN_PORT" ]] && try_connect "$KNOWN_PORT"; then
|
||||
serial="$REPLY"
|
||||
break
|
||||
fi
|
||||
|
||||
local found
|
||||
found="$(scan_for_port)"
|
||||
if [[ -n "$found" ]] && try_connect "$found"; then
|
||||
serial="$REPLY"
|
||||
break
|
||||
fi
|
||||
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [[ -z "$serial" ]]; then
|
||||
echo "Error: no wireless-debugging window appeared before the deadline." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "connected as $serial"
|
||||
make_permanent "$serial" || return 1
|
||||
|
||||
if try_connect "$FIXED_PORT"; then
|
||||
log "SUCCESS: phone is now reachable at ${PHONE_IP}:${FIXED_PORT}"
|
||||
log "it will come back on this port after every reboot"
|
||||
printf '%s\n' "${PHONE_IP}:${FIXED_PORT}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Error: adbd did not come back on $FIXED_PORT after the switch." >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Loading…
Reference in New Issue
Block a user