import html from pathlib import Path from fastapi import APIRouter, Form from fastapi.responses import HTMLResponse, RedirectResponse from ..backups import is_backup_path, list_backups from ..config import BACKUP_SCRIPT from ..shell import run_command from ..templates.layout import page, render_result router = APIRouter() @router.get("/backups", response_class=HTMLResponse) def backups_page(): rows = "" for path in list_backups(): size_mb = path.stat().st_size / 1024 / 1024 backup_name = html.escape(path.name) backup_path = html.escape(str(path), quote=True) restore_cmd = html.escape( f"sudo /home/jiri/workspace/appfactory-tools/scripts/restore-appfactory.sh --force {path}", quote=True, ) rows += f""" {backup_name}
{size_mb:.2f} MB
Restore is dangerous and must be run manually over SSH.
""" if not rows: rows = 'No backups found.' return page( "Backups", f"""

Backup Management

Backups do not include the backup directory itself. Restoring an older backup should not delete newer .tar.gz files. Restore remains manual to avoid accidental overwrite.

Available Backups

{rows}
Backup Path Restore Command Actions
""", ) @router.post("/backups/create", response_class=HTMLResponse) def create_backup(): result = run_command([BACKUP_SCRIPT]) status = "OK" if result.returncode == 0 else "FAILED" return render_result( title=f"Create Backup: {status}", back_url="/portal/backups", sections=[("Output", result.stdout), ("Error", result.stderr)], ) @router.post("/backups/delete") def delete_backup(backup_path: str = Form(...)): target = Path(backup_path) if not is_backup_path(target): return HTMLResponse("Invalid backup path", status_code=400) target = target.resolve() if target.exists(): target.unlink() return RedirectResponse(url="/portal/backups", status_code=303)