implement ota updates
lint / ruff (push) Successful in 14s
tests / desktop (push) Successful in 22s
docker / image (push) Successful in 43s
tests / web (push) Successful in 39s
tests / android-core (push) Successful in 1m1s
tests / backend (push) Successful in 2m12s
android / apk (push) Successful in 6m50s
lint / ruff (push) Successful in 14s
tests / desktop (push) Successful in 22s
docker / image (push) Successful in 43s
tests / web (push) Successful in 39s
tests / android-core (push) Successful in 1m1s
tests / backend (push) Successful in 2m12s
android / apk (push) Successful in 6m50s
This commit is contained in:
25 files changed
+2353
-4
No files matched your search
@@ -8,6 +8,7 @@ from app.api import (
|
||||
routes_settings,
|
||||
routes_tasks,
|
||||
routes_timer,
|
||||
routes_updates,
|
||||
)
|
||||
|
||||
api_router = APIRouter()
|
||||
@@ -16,5 +17,6 @@ api_router.include_router(routes_tasks.router)
|
||||
api_router.include_router(routes_blocker.router)
|
||||
api_router.include_router(routes_reports.router)
|
||||
api_router.include_router(routes_settings.router)
|
||||
api_router.include_router(routes_updates.router)
|
||||
|
||||
__all__ = ["api_router"]
|
||||
@@ -0,0 +1,53 @@
|
||||
"""The Android OTA feed.
|
||||
|
||||
The APK never comes from the phone's own Gitea credentials — the repository is
|
||||
private. The backend reads the newest successful Actions artifact and re-serves
|
||||
it here, behind the same auth as the rest of `/api`.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from app.services import android_updates
|
||||
|
||||
router = APIRouter(prefix="/api/updates", tags=["updates"])
|
||||
|
||||
|
||||
def _raise_http(exc: android_updates.UpdateUnavailable) -> None:
|
||||
if isinstance(exc, android_updates.UpdateNotConfigured):
|
||||
raise HTTPException(status_code=503, detail=str(exc)) from exc
|
||||
if isinstance(exc, android_updates.UpdateNotFound):
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
raise HTTPException(status_code=502, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@router.get("/android")
|
||||
def android_update() -> dict:
|
||||
"""Metadata for the newest successful master build.
|
||||
|
||||
The client compares `version_code` with its own; the backend deliberately
|
||||
does not try to know which build the phone is running.
|
||||
"""
|
||||
try:
|
||||
return android_updates.latest_android()
|
||||
except android_updates.UpdateUnavailable as exc:
|
||||
_raise_http(exc)
|
||||
|
||||
|
||||
@router.get("/android/apk")
|
||||
def android_apk() -> FileResponse:
|
||||
"""The APK for the newest successful master build."""
|
||||
try:
|
||||
path, metadata = android_updates.android_apk()
|
||||
except android_updates.UpdateUnavailable as exc:
|
||||
_raise_http(exc)
|
||||
|
||||
version = metadata.get("version_name") or metadata.get("version_code") or "latest"
|
||||
return FileResponse(
|
||||
path,
|
||||
media_type="application/vnd.android.package-archive",
|
||||
filename=f"kadence-{version}.apk",
|
||||
headers={"Cache-Control": "no-store"},
|
||||
)
|
||||
Reference in new issue
Block a user