app: autorun mode (unattended run + upload + auto-exit); IPv6 severity rework
IPv6: absence is no longer a defect. If the network never provisioned v6 (no global address, no ::/0 route) the finding is ipv6.not_offered at INFO (green) — most networks are still IPv4-only. If v6 IS advertised but doesn't work, it's ipv6.broken at MEDIUM (yellow), because half-working v6 stalls connections. Verified on-device: our LAN advertises a v6 default route with no path, and now reports ipv6.broken. Autorun: `am start ... --ez autorun true` runs the suite immediately, POSTs the report to the collection endpoint, shows the result for 3s and finishes the activity (stays open if the upload failed). receiver.py gains POST /report + GET /reports + GET /report/<name>. Verified end to end: one adb command, report retrieved over HTTP, app closed itself. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
483de5ca54
commit
dc094d1631
@@ -18,6 +18,7 @@ import time
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
||||
STATE_PATH = os.environ.get("STATE_PATH", "/run/echolot-adb-beacon.json")
|
||||
REPORT_DIR = os.environ.get("REPORT_DIR", "/tmp/echolot-reports")
|
||||
SECRET = os.environ.get("ECHOLOT_BEACON_SECRET", "")
|
||||
BIND = os.environ.get("BEACON_BIND", "0.0.0.0")
|
||||
PORT = int(os.environ.get("BEACON_PORT", "9099"))
|
||||
@@ -55,6 +56,19 @@ class Handler(BaseHTTPRequestHandler):
|
||||
# Dev convenience: serve a staged APK so a device (which can only reach
|
||||
# this host on 443) can pull it via its own curl — more resilient than
|
||||
# adb's sustained transport over a flaky wifi. Path from APK_PATH.
|
||||
if p == "/reports":
|
||||
try:
|
||||
names = sorted(os.listdir(REPORT_DIR), reverse=True)
|
||||
except FileNotFoundError:
|
||||
names = []
|
||||
return self._send(200, json.dumps(names).encode())
|
||||
if p.startswith("/report/"):
|
||||
name = os.path.basename(p[len("/report/"):])
|
||||
try:
|
||||
with open(os.path.join(REPORT_DIR, name), "rb") as f:
|
||||
return self._send(200, f.read())
|
||||
except FileNotFoundError:
|
||||
return self._send(404, b'{"error":"no such report"}')
|
||||
if p == "/apk":
|
||||
apk = os.environ.get("APK_PATH", "/tmp/echolot-app.apk")
|
||||
try:
|
||||
@@ -65,7 +79,27 @@ class Handler(BaseHTTPRequestHandler):
|
||||
return self._send(404, b'{"error":"not found"}')
|
||||
|
||||
def do_POST(self):
|
||||
if self.path.rstrip("/") != "/beacon":
|
||||
p = self.path.rstrip("/")
|
||||
# Measurement-report drop box: the app's autorun mode POSTs its run JSON here so a
|
||||
# test run needs no adb at all (start it once, collect the result over HTTP).
|
||||
if p == "/report":
|
||||
if SECRET and self.headers.get("X-Beacon-Secret") != SECRET:
|
||||
return self._send(403, b'{"error":"bad secret"}')
|
||||
n = int(self.headers.get("Content-Length", "0"))
|
||||
body = self.rfile.read(n)
|
||||
try:
|
||||
doc = json.loads(body)
|
||||
dev = doc.get("run", {}).get("device", {}).get("model", "device")
|
||||
rid = doc.get("run", {}).get("id", "unknown")[:8]
|
||||
except Exception:
|
||||
dev, rid = "device", "unparsed"
|
||||
os.makedirs(REPORT_DIR, exist_ok=True)
|
||||
name = f"{int(time.time())}-{dev}-{rid}.json"
|
||||
with open(os.path.join(REPORT_DIR, name), "wb") as f:
|
||||
f.write(body)
|
||||
return self._send(200, json.dumps({"stored": name, "bytes": len(body)}).encode())
|
||||
|
||||
if p != "/beacon":
|
||||
return self._send(404, b'{"error":"not found"}')
|
||||
if SECRET and self.headers.get("X-Beacon-Secret") != SECRET:
|
||||
return self._send(403, b'{"error":"bad secret"}')
|
||||
|
||||
Reference in New Issue
Block a user