Phase 22: auto-update (Watchtower-style), local + agent (0.28.0)
Per-stack auto-update policy on the stack Overview tab. When the background
image-update check finds a newer registry digest for one of a stack's images,
the stack is pulled + redeployed (or just flagged, "notify only"). Only running
stacks are auto-redeployed; a stopped stack is skipped, never silently started.
- models/auto_update.py: AutoUpdate(stack_id, agent_id, enabled, redeploy,
last_run/status/result) + schemas; registered in models/__init__.py.
- update_service: DB-free stack_images/stack_updates helpers (agent reuses
them); agent GET /agent/stacks/{id}/updates.
- services/auto_update_service.py: run_due/run_policy (local pull+up via
compose_service, remote via agent_service POST /agent/stacks/{id}/update,
notify-only with per-transition dedup); lazy-called from
update_service.background_loop. New stack_auto_updated notify event.
- routers: GET/PUT/run /api/stacks/{id}/auto-update and the
/api/agents/{id}/stacks/{sid}/auto-update variants (policy stored centrally).
- frontend: api/autoUpdate.ts + AutoUpdatePanel (enable, redeploy|notify-only,
Check now, last-run status) on StackDetail + RemoteStackDetail; EVENT_LABELS
gains stack_auto_updated + backup_failed.
Live-verified all four paths (updated / update-available / up-to-date /
skipped) against real compose.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
be3568274f
commit
255c8441c6
@@ -1,6 +1,7 @@
|
||||
"""SQLModel table models. Importing this package registers all tables."""
|
||||
from models.agent import Agent
|
||||
from models.audit import AuditLog
|
||||
from models.auto_update import AutoUpdate
|
||||
from models.backup_destination import BackupDestination
|
||||
from models.backup_schedule import BackupSchedule
|
||||
from models.setting import Setting, Webhook
|
||||
@@ -10,5 +11,5 @@ from models.user import User
|
||||
|
||||
__all__ = [
|
||||
"User", "Stack", "AuditLog", "Template", "Setting", "Webhook", "Agent",
|
||||
"BackupDestination", "BackupSchedule",
|
||||
"BackupDestination", "BackupSchedule", "AutoUpdate",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
|
||||
from sqlmodel import Field, SQLModel
|
||||
|
||||
|
||||
def _now() -> datetime:
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
|
||||
class AutoUpdate(SQLModel, table=True):
|
||||
"""Per-stack auto-update policy (Watchtower-style).
|
||||
|
||||
When the background image-update check finds a newer registry digest for one
|
||||
of the stack's images, the stack is either pulled + redeployed
|
||||
(``redeploy=True``) or merely notified about (``redeploy=False``).
|
||||
``agent_id`` None = local host, otherwise a remote agent's stack.
|
||||
"""
|
||||
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
stack_id: str
|
||||
agent_id: Optional[int] = None
|
||||
enabled: bool = True
|
||||
redeploy: bool = True # True = pull + up -d; False = notify only
|
||||
last_run: Optional[datetime] = None
|
||||
last_status: Optional[str] = None # "updated" | "up-to-date" | "update-available" | "skipped" | "error"
|
||||
last_result: Optional[str] = None # detail (stale images / error text)
|
||||
created_at: datetime = Field(default_factory=_now)
|
||||
|
||||
|
||||
# --- API schemas ---
|
||||
|
||||
|
||||
class AutoUpdateWrite(SQLModel):
|
||||
enabled: bool = True
|
||||
redeploy: bool = True
|
||||
|
||||
|
||||
class AutoUpdateRead(SQLModel):
|
||||
id: Optional[int]
|
||||
stack_id: str
|
||||
agent_id: Optional[int]
|
||||
agent_name: Optional[str] = None
|
||||
enabled: bool
|
||||
redeploy: bool
|
||||
last_run: Optional[datetime]
|
||||
last_status: Optional[str]
|
||||
last_result: Optional[str]
|
||||
@@ -17,6 +17,7 @@ EVENT_STACK_STOP = "stack_stop"
|
||||
EVENT_STACK_ERROR = "stack_error"
|
||||
EVENT_PULL_FAILED = "pull_failed"
|
||||
EVENT_BACKUP_FAILED = "backup_failed"
|
||||
EVENT_STACK_AUTO_UPDATED = "stack_auto_updated"
|
||||
|
||||
ALL_EVENTS = [
|
||||
EVENT_UPDATE_AVAILABLE,
|
||||
@@ -25,6 +26,7 @@ ALL_EVENTS = [
|
||||
EVENT_STACK_ERROR,
|
||||
EVENT_PULL_FAILED,
|
||||
EVENT_BACKUP_FAILED,
|
||||
EVENT_STACK_AUTO_UPDATED,
|
||||
]
|
||||
|
||||
WEBHOOK_TYPES = ["ntfy", "discord", "slack", "gotify", "generic"]
|
||||
|
||||
Reference in New Issue
Block a user