feat(backup): add database backup/restore with scheduled retention and admin dashboard

- Add BackupRecord model for tracking backup archives
- Add migration 021_add_backup_records
- Add backup configuration settings (backup_enabled, backup_dir, backup_remote_destination, backup_remote_folder, backup_retain_hourly/daily/weekly)
- Add backup_tasks.py with create_backup, cleanup_old_backups, and helpers
- Register hourly/daily/weekly Celery beat schedules
- Add /api/admin/backup/* REST endpoints (list, create, download, restore, delete, cleanup)
- Add /admin/backup dashboard view and template
- Add backup link to admin dropdown navigation in base.html
- Add backup settings to SETTING_METADATA in settings_service.py
- Add comprehensive test suite (39 tests passing)

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-07 22:03:27 +00:00
parent f4a607df05
commit 2dd1ca0197
13 changed files with 1935 additions and 0 deletions
+2
View File
@@ -5,6 +5,7 @@ Aggregated view routers for the application.
from fastapi import APIRouter
from app.views.admin_users import router as admin_users_router
from app.views.backup import router as backup_router
from app.views.db_wizard import router as db_wizard_router
from app.views.dropbox import router as dropbox_router
from app.views.filemanager import router as filemanager_router
@@ -30,6 +31,7 @@ router = APIRouter()
router.include_router(wizard_router) # Wizard first (for /setup)
router.include_router(db_wizard_router) # Database wizard
router.include_router(admin_users_router) # Admin user management
router.include_router(backup_router) # Backup dashboard
router.include_router(general_router)
router.include_router(status_router)
router.include_router(onedrive_router)
+58
View File
@@ -0,0 +1,58 @@
"""
Backup management dashboard view – admin only.
"""
import logging
from fastapi import Depends, HTTPException, Request, status
from sqlalchemy.orm import Session
from app.config import settings
from app.models import BackupRecord
from app.views.base import APIRouter, get_db, require_login, templates
from app.views.settings import require_admin_access
logger = logging.getLogger(__name__)
router = APIRouter()
@router.get("/admin/backup")
@require_login
@require_admin_access
async def backup_dashboard(request: Request, db: Session = Depends(get_db)):
"""Backup management dashboard – admin only."""
try:
records = db.query(BackupRecord).order_by(BackupRecord.created_at.desc()).limit(500).all()
# Summarise counts per tier
counts: dict[str, int] = {"hourly": 0, "daily": 0, "weekly": 0}
for r in records:
if r.backup_type in counts:
counts[r.backup_type] += 1
# Compute total local size
import os
total_size = sum(r.size_bytes for r in records if r.local_path and os.path.exists(r.local_path))
return templates.TemplateResponse(
"backup.html",
{
"request": request,
"records": records,
"counts": counts,
"total_size": total_size,
"backup_enabled": getattr(settings, "backup_enabled", True),
"backup_remote_destination": getattr(settings, "backup_remote_destination", None),
"backup_retain_hourly": getattr(settings, "backup_retain_hourly", 96),
"backup_retain_daily": getattr(settings, "backup_retain_daily", 21),
"backup_retain_weekly": getattr(settings, "backup_retain_weekly", 13),
"app_version": settings.version,
},
)
except Exception as e:
logger.error(f"Error loading backup dashboard: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to load backup dashboard",
)