"""Distribution and update delivery. Part 35 established, with live evidence, that a subscriber currently has no way to install the APK and no way to learn a new version exists: * the advertised APK URL returned 404 and no .apk existed anywhere on the Space * family_device_router was never mounted, so the only enrollment endpoint was unreachable * the exe's Tauri updater pointed at api.starkindustries.com, which is NXDOMAIN * the APK had no update mechanism at all Those were findings. This module is the fix. Every route here is deliberately unauthenticated or lightly authenticated, because a person who has just paid and is standing at a download page does not yet have the app that holds their token -- gating the download itself behind the app you are trying to download is a bootstrap the product cannot survive. Entitlement is enforced where it belongs (on the features, server-side, per the standing plan-gating rule), not on the installer bytes. """ from __future__ import annotations import hashlib import logging import os from pathlib import Path from fastapi import APIRouter, Request from fastapi.responses import FileResponse, HTMLResponse, JSONResponse, Response from backend.version import ( ANDROID_VERSION_CODE, EXE_TARGET, VERSION, manifest, ) logger = logging.getLogger(__name__) router = APIRouter(prefix="/distribution", tags=["distribution"]) _ROOT = Path(__file__).resolve().parents[2] # Where a real signed artifact may actually sit. Checked in order; the first that exists wins. # OMEGA_APK_PATH lets the operator drop the artifact anywhere without a code change. _APK_CANDIDATES = [ os.environ.get("OMEGA_APK_PATH", ""), str(_ROOT / "dist" / "app-release.apk"), str(_ROOT / "phone" / "jarvis-mobile-guardian" / "app" / "build" / "outputs" / "apk" / "release" / "app-release.apk"), ] _EXE_CANDIDATES = [ os.environ.get("OMEGA_EXE_PATH", ""), str(_ROOT / "dist" / "JARVIS-OS-setup.exe"), str(_ROOT / "src-tauri" / "target" / "release" / "bundle" / "msi" / f"JARVIS OS_{VERSION}_x64_en-US.msi"), ] def _first_existing(candidates: list[str]) -> Path | None: for c in candidates: if not c: continue p = Path(c) if p.is_file() and p.stat().st_size > 0: return p return None def _sha256(path: Path) -> str: h = hashlib.sha256() with path.open("rb") as fh: for chunk in iter(lambda: fh.read(1024 * 1024), b""): h.update(chunk) return h.hexdigest() def _base_url(request: Request) -> str: """Absolute base URL, honouring the proxy that actually terminates TLS. The Space runs behind HF's reverse proxy, so `request.base_url` sees the *internal* hop and reports http://. That made the live manifest advertise `http://.../distribution/android` from a page served over HTTPS -- a mixed-content download link, which browsers block outright and which no updater should be asked to follow. Verified live: the deployed manifest returned an http:// URL until this read X-Forwarded-Proto. """ base = str(request.base_url).rstrip("/") proto = request.headers.get("x-forwarded-proto", "").split(",")[0].strip() if proto in ("http", "https") and base.startswith(("http://", "https://")): scheme, _, rest = base.partition("://") if scheme != proto: base = f"{proto}://{rest}" return base @router.get("/manifest") async def distribution_manifest(request: Request): """The one payload every update check on every platform reads. The exe's updater, the APK's in-app check, and any operator tooling all read this, so the six-surfaces-five-versions drift Part 35 found cannot recur: there is one source (backend/version.py) and one route. """ data = manifest(_base_url(request)) # Report what is genuinely downloadable right now rather than advertising a URL that 404s, # which is precisely the failure this module exists to correct. apk = _first_existing(_APK_CANDIDATES) data["android"]["available"] = apk is not None if apk is not None: data["android"]["sizeBytes"] = apk.stat().st_size data["android"]["sha256"] = _sha256(apk) else: data["android"]["notes"] += " -- artifact not present on this host yet" exe = _first_existing(_EXE_CANDIDATES) data["exe"]["available"] = exe is not None if exe is not None: data["exe"]["sizeBytes"] = exe.stat().st_size return JSONResponse(data) @router.get("/updates/{target}/{current_version}") async def tauri_update_check(target: str, current_version: str, request: Request): """Tauri updater endpoint. Tauri's contract: HTTP 204 means "you are current", HTTP 200 with a body means "here is the update". Returning 204 is the correct, non-noisy answer for an up-to-date client -- the old config pointed at a domain that does not resolve, so every check failed at DNS and the app silently believed it had checked. """ if target != EXE_TARGET: # An unknown target is not an error the user should see; there is simply nothing for it. return Response(status_code=204) if current_version == VERSION: return Response(status_code=204) exe = _first_existing(_EXE_CANDIDATES) if exe is None: # Honest: we know a newer version exists but have nothing to hand out on this host. return Response(status_code=204) # src-tauri/tauri.conf.json sets `updater.pubkey`, so Tauri v1 verifies a minisign signature # over the artifact and REFUSES anything that does not match. This endpoint previously sent # `"signature": ""`, which means the client downloads the whole installer and then rejects it # -- a failure the user experiences as an update that silently never applies. # # Offering an update we know will be refused is worse than offering none, so the honest answer # while no signature exists is "you are current". OMEGA_EXE_SIGNATURE carries the output of # `tauri signer sign` for the artifact being served. signature = os.environ.get("OMEGA_EXE_SIGNATURE", "").strip() if not signature: return Response(status_code=204) return JSONResponse({ "version": VERSION, "notes": f"JARVIS OS {VERSION}", "pub_date": None, "platforms": { EXE_TARGET: { "signature": signature, "url": f"{_base_url(request)}/distribution/windows", } }, }) @router.get("/android") async def download_android(request: Request): """Serve the actual signed release APK.""" apk = _first_existing(_APK_CANDIDATES) if apk is None: # 503, not 404. A 404 says "there is no such thing"; the truth is "this exists but is # not staged on this host", which is a different problem with a different fix. return JSONResponse( status_code=503, content={ "error": "apk_not_staged", "message": ( "The Guardian APK is not present on this host. Set OMEGA_APK_PATH, or " "publish the signed artifact and set OMEGA_APK_URL." ), "expectedVersion": VERSION, "expectedVersionCode": ANDROID_VERSION_CODE, }, ) return FileResponse( apk, media_type="application/vnd.android.package-archive", filename=f"jarvis-guardian-{VERSION}.apk", ) @router.get("/windows") async def download_windows(request: Request): exe = _first_existing(_EXE_CANDIDATES) if exe is None: return JSONResponse( status_code=503, content={ "error": "installer_not_staged", "message": "The Windows installer is not present on this host.", "expectedVersion": VERSION, }, ) return FileResponse(exe, media_type="application/octet-stream", filename=exe.name) @router.get("/install", response_class=HTMLResponse) async def install_page(request: Request): """The page a subscriber is actually sent to. Android blocks installs from outside Play by default, and that prompt is unavoidable outside the Play Store. The honest thing is to tell the user it is coming, in the exact words their phone will use, *before* it appears -- an unexplained system security prompt mid-install is where sideload flows lose people. Same principle as explaining the camera permission before the WebAR gate triggers it. """ base = _base_url(request) apk = _first_existing(_APK_CANDIDATES) staged = apk is not None size_mb = f"{apk.stat().st_size / (1024 * 1024):.0f} MB" if staged else "—" cta = ( f'Download Guardian · {size_mb}' if staged else '
The download is not available on this server yet. ' 'Nothing is wrong with your account.
' ) return HTMLResponse(f"""JARVIS · OMEGA
The Guardian companion app pairs with JARVIS on your PC. It is distributed directly by us rather than through the Play Store, so there is one extra confirmation step your phone will ask for.
{cta}Yes — the prompt is Android protecting you from apps it has not personally reviewed, not a warning about this app specifically. The download is served over HTTPS and the APK is signed with our release key, so your phone will refuse any future update that is not signed by the same key.