Files
wled-screen-controller-mixed/server/src/wled_controller/api/routes/update.py
alexei.dolgolyov 382a42755d feat: add auto-update system with release checking, notification UI, and install-type-aware apply
- Abstract ReleaseProvider interface (Gitea impl, swappable for GitHub/GitLab)
- Background UpdateService with periodic checks, debounce, dismissed version persistence
- Install type detection (installer/portable/docker/dev) with platform-aware asset matching
- Download with progress events, silent NSIS reinstall, portable ZIP/tarball swap scripts
- Version badge pulse animation, dismissible banner with icon buttons, Settings > Updates tab
- Single source of truth: pyproject.toml version via importlib.metadata, CI stamps tag with sed
- API: GET/POST status, check, dismiss, apply, GET/PUT settings
- i18n: en, ru, zh (27+ keys each)
2026-03-25 13:16:18 +03:00

82 lines
2.5 KiB
Python

"""API routes for the auto-update system."""
from fastapi import APIRouter, Depends
from fastapi.responses import JSONResponse
from wled_controller.api.dependencies import get_update_service
from wled_controller.api.schemas.update import (
DismissRequest,
UpdateSettingsRequest,
UpdateSettingsResponse,
UpdateStatusResponse,
)
from wled_controller.core.update.update_service import UpdateService
from wled_controller.utils import get_logger
logger = get_logger(__name__)
router = APIRouter(prefix="/api/v1/system/update", tags=["update"])
@router.get("/status", response_model=UpdateStatusResponse)
async def get_update_status(
service: UpdateService = Depends(get_update_service),
):
return service.get_status()
@router.post("/check", response_model=UpdateStatusResponse)
async def check_for_updates(
service: UpdateService = Depends(get_update_service),
):
return await service.check_now()
@router.post("/dismiss")
async def dismiss_update(
body: DismissRequest,
service: UpdateService = Depends(get_update_service),
):
service.dismiss(body.version)
return {"ok": True}
@router.post("/apply")
async def apply_update(
service: UpdateService = Depends(get_update_service),
):
"""Download (if needed) and apply the available update."""
status = service.get_status()
if not status["has_update"]:
return JSONResponse(status_code=400, content={"detail": "No update available"})
if not status["can_auto_update"]:
return JSONResponse(
status_code=400,
content={"detail": f"Auto-update not supported for install type: {status['install_type']}"},
)
try:
await service.apply_update()
return {"ok": True, "message": "Update applied, server shutting down"}
except Exception as exc:
logger.error("Failed to apply update: %s", exc, exc_info=True)
return JSONResponse(status_code=500, content={"detail": str(exc)})
@router.get("/settings", response_model=UpdateSettingsResponse)
async def get_update_settings(
service: UpdateService = Depends(get_update_service),
):
return service.get_settings()
@router.put("/settings", response_model=UpdateSettingsResponse)
async def update_update_settings(
body: UpdateSettingsRequest,
service: UpdateService = Depends(get_update_service),
):
return await service.update_settings(
enabled=body.enabled,
check_interval_hours=body.check_interval_hours,
include_prerelease=body.include_prerelease,
)