feat(523): the backup job - an hourly tick that runs at the chosen hour, Back up now, and the last 30 runs
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m9s
CI & Build / Python tests (push) Successful in 1m54s
CI & Build / Build & push image (push) Successful in 23s

services/backup_job.py reads its config when it fires, so startup reads
nothing (#4181) and a changed hour needs no reschedule. A run is held to
a 60-minute deadline (a hung NFS mount would otherwise hold the lock and
skip every later backup), never runs twice at once, and keeps its outcome
- file, size, duration, or the reason in words - in the bounded
backup_runs setting, which no backup carries. summarise() gives the page
its verdict: overdue when on and no success for 26h.

Admin API: GET/PUT /api/admin/backups, POST /backups/check (tests unsaved
form values, 45s deadline), POST /backups/run (202, background).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-15 17:34:01 -04:00
co-authored by Claude Opus 5.5
parent ff4f2ee5c2
commit 4248b450ea
6 changed files with 563 additions and 1 deletions
+7
View File
@@ -304,6 +304,11 @@ def create_app() -> Quart:
asyncio.get_running_loop(), await get_maintenance_hour()
)
# Scheduled backups (milestone 523). An hourly tick that reads its
# config when it fires, so starting it reads nothing here (#4181).
from scribe.services.backup_job import start_backup_scheduler
start_backup_scheduler(asyncio.get_running_loop())
# Diagnostic instrumentation — heartbeat, signal handlers, asyncio
# exception hook. Cheap (~1 log line/min), high diagnostic value when
# the app crashes mysteriously. See services/diagnostics.py.
@@ -330,6 +335,8 @@ def create_app() -> Quart:
stop_db_maintenance_scheduler,
)
stop_db_maintenance_scheduler()
from scribe.services.backup_job import stop_backup_scheduler
stop_backup_scheduler()
from scribe.services.diagnostics import stop_diagnostics
stop_diagnostics()