feat: implement update backend API and client logic
Add complete update mechanism for client-side updates: - backend/update.py: Core update logic (check, apply, rollback, status/logs) - backend/app.py: REST API endpoints (GET /update/status, POST /update/check, POST /update/apply, POST /update/rollback, GET /update/logs) - backend/models.py: Pydantic models for update API responses - backend/settings.py: Update config (status/log file paths) - scripts/rollback_client.sh: Rollback script for failed updates - scripts/update_client.sh: Enhanced update client script - CLAUDE.md: Documentation for future Claude Code instances Complete US_000026-028 and TASK_000027-029: - US_000026: Client pulls updates from remote service - US_000027: Client verifies and applies updates atomically - US_000028: Client reports update status to backend All endpoints require authentication. Updates run asynchronously. Documentation updated per SOP (CHANGELOG, PROJECT_STATUS, stories/tasks). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -15,9 +15,20 @@ from backend.auth import (
|
||||
issue_token,
|
||||
list_manageable_users,
|
||||
)
|
||||
from backend.models import ActionRequest, ActionResponse, LoginRequest, LoginResponse, UserStatus
|
||||
from backend.models import (
|
||||
ActionRequest,
|
||||
ActionResponse,
|
||||
LoginRequest,
|
||||
LoginResponse,
|
||||
UpdateActionResponse,
|
||||
UpdateCheckResponse,
|
||||
UpdateLogEntry,
|
||||
UpdateStatus,
|
||||
UserStatus,
|
||||
)
|
||||
from backend.oidc import OIDCClient, OIDCError
|
||||
from backend.settings import Settings, get_settings
|
||||
from backend import update
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
@ -219,6 +230,67 @@ def enable_user(
|
||||
)
|
||||
|
||||
|
||||
@app.get("/update/status", response_model=UpdateStatus, dependencies=[Depends(get_current_admin)])
|
||||
def update_status(settings: Settings = Depends(get_settings)) -> UpdateStatus:
|
||||
status_data = update.get_status(settings)
|
||||
return UpdateStatus(**status_data)
|
||||
|
||||
|
||||
@app.post("/update/check", response_model=UpdateCheckResponse, dependencies=[Depends(get_current_admin)])
|
||||
def update_check(settings: Settings = Depends(get_settings)) -> UpdateCheckResponse:
|
||||
try:
|
||||
check_data = update.check_update(settings)
|
||||
except Exception as exc:
|
||||
logger.exception("Update check failed")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail=f"Update check failed: {str(exc)}",
|
||||
) from exc
|
||||
return UpdateCheckResponse(**check_data)
|
||||
|
||||
|
||||
@app.post("/update/apply", response_model=UpdateActionResponse, dependencies=[Depends(get_current_admin)])
|
||||
def update_apply(
|
||||
settings: Settings = Depends(get_settings),
|
||||
payload: dict | None = Body(default=None),
|
||||
) -> UpdateActionResponse:
|
||||
version = payload.get("version") if payload else None
|
||||
try:
|
||||
update.start_update(settings, version)
|
||||
except Exception as exc:
|
||||
logger.exception("Failed to start update")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to start update: {str(exc)}",
|
||||
) from exc
|
||||
return UpdateActionResponse(started=True, message="Update started")
|
||||
|
||||
|
||||
@app.post("/update/rollback", response_model=UpdateActionResponse, dependencies=[Depends(get_current_admin)])
|
||||
def update_rollback(settings: Settings = Depends(get_settings)) -> UpdateActionResponse:
|
||||
try:
|
||||
update.start_rollback(settings)
|
||||
except Exception as exc:
|
||||
logger.exception("Failed to start rollback")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to start rollback: {str(exc)}",
|
||||
) from exc
|
||||
return UpdateActionResponse(started=True, message="Rollback started")
|
||||
|
||||
|
||||
@app.get("/update/logs", dependencies=[Depends(get_current_admin)])
|
||||
def update_logs(settings: Settings = Depends(get_settings), limit: int = 200) -> list[dict]:
|
||||
try:
|
||||
return update.get_logs(settings, limit)
|
||||
except Exception as exc:
|
||||
logger.exception("Failed to retrieve update logs")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to retrieve logs: {str(exc)}",
|
||||
) from exc
|
||||
|
||||
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
def index(request: Request) -> HTMLResponse:
|
||||
return templates.TemplateResponse("index.html", {"request": request})
|
||||
|
||||
Reference in New Issue
Block a user