96 lines
3.0 KiB
Python
96 lines
3.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Mirror the latest Android APK from the remote server to the Pi's hotspot.
|
|
|
|
Polls GET /api/v1/app/latest on the server, downloads /app/yaris-obd.apk when
|
|
the sha256 differs from the local copy, verifies it, and atomically replaces
|
|
static/app/yaris-obd.apk — so the phone can install from http://10.42.0.1/app
|
|
without leaving the car WiFi.
|
|
|
|
Runs on a systemd timer (see deploy/yaris-app-updater.{service,timer}).
|
|
Stdlib only — no pip dependencies.
|
|
"""
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import sys
|
|
import urllib.error
|
|
import urllib.request
|
|
|
|
SERVER_URL = os.environ.get("YARIS_SERVER_URL", "https://yaris.ceaf.dev").rstrip("/")
|
|
APP_DIR = os.environ.get("YARIS_APP_DIR", "/home/yaris/app/yaris-obd/static/app")
|
|
APK_PATH = os.path.join(APP_DIR, "yaris-obd.apk")
|
|
|
|
# Cloudflare blocks the default Python-urllib UA with 403 — identify ourselves.
|
|
_HEADERS = {"User-Agent": "yaris-obd-updater/1.0"}
|
|
|
|
|
|
def sha256_file(path):
|
|
h = hashlib.sha256()
|
|
with open(path, "rb") as f:
|
|
for chunk in iter(lambda: f.read(1024 * 1024), b""):
|
|
h.update(chunk)
|
|
return h.hexdigest()
|
|
|
|
|
|
def fetch_json(url, timeout=20):
|
|
req = urllib.request.Request(url, headers=_HEADERS)
|
|
with urllib.request.urlopen(req, timeout=timeout) as r:
|
|
return json.load(r)
|
|
|
|
|
|
def main():
|
|
try:
|
|
manifest = fetch_json(f"{SERVER_URL}/api/v1/app/latest")
|
|
except urllib.error.HTTPError as e:
|
|
if e.code == 404:
|
|
print("No app published on the server yet; skipping.")
|
|
return 0
|
|
print(f"latest check failed: HTTP {e.code}")
|
|
return 1
|
|
except Exception as e:
|
|
print(f"latest check failed: {e}")
|
|
return 1
|
|
|
|
sha = manifest.get("sha256")
|
|
if not sha:
|
|
print("manifest missing sha256")
|
|
return 1
|
|
|
|
if os.path.isfile(APK_PATH):
|
|
try:
|
|
if sha256_file(APK_PATH) == sha:
|
|
print(f"APK up to date (version {manifest.get('version')})")
|
|
return 0
|
|
except Exception as e:
|
|
print(f"local hash failed: {e}")
|
|
|
|
os.makedirs(APP_DIR, exist_ok=True)
|
|
tmp = APK_PATH + ".part"
|
|
try:
|
|
req = urllib.request.Request(f"{SERVER_URL}/app/yaris-obd.apk", headers=_HEADERS)
|
|
with urllib.request.urlopen(req, timeout=600) as r, open(tmp, "wb") as f:
|
|
while True:
|
|
chunk = r.read(1024 * 1024)
|
|
if not chunk:
|
|
break
|
|
f.write(chunk)
|
|
if sha256_file(tmp) != sha:
|
|
print("downloaded APK sha256 mismatch — discarding")
|
|
os.remove(tmp)
|
|
return 1
|
|
os.replace(tmp, APK_PATH)
|
|
print(f"Updated APK to version {manifest.get('version')} ({os.path.getsize(APK_PATH)} bytes)")
|
|
return 0
|
|
except Exception as e:
|
|
print(f"download failed: {e}")
|
|
if os.path.isfile(tmp):
|
|
try:
|
|
os.remove(tmp)
|
|
except Exception:
|
|
pass
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|