Matthias Nott
2026-02-21 7300351bb9fb147f4de81a60423c4561a4924c21
app/routers/backups.py
....@@ -3,7 +3,7 @@
33 from fastapi import APIRouter, Depends, HTTPException
44
55 from app.auth import verify_token
6
-from app.ops_runner import run_ops, run_ops_json, _BACKUP_TIMEOUT
6
+from app.ops_runner import run_ops, run_ops_json, run_ops_host, _BACKUP_TIMEOUT
77
88 router = APIRouter()
99
....@@ -12,15 +12,10 @@
1212 async def list_backups(
1313 _: str = Depends(verify_token),
1414 ) -> list[dict[str, Any]]:
15
- """
16
- Returns a list of local backup records from `ops backups --json`.
17
- """
15
+ """Returns a list of local backup records from `ops backups --json`."""
1816 result = await run_ops_json(["backups"])
1917 if not result["success"]:
20
- raise HTTPException(
21
- status_code=500,
22
- detail=f"Failed to list backups: {result['error']}",
23
- )
18
+ raise HTTPException(status_code=500, detail=f"Failed to list backups: {result['error']}")
2419
2520 data = result["data"]
2621 if isinstance(data, list):
....@@ -37,9 +32,7 @@
3732 async def list_offsite_backups(
3833 _: str = Depends(verify_token),
3934 ) -> list[dict[str, Any]]:
40
- """
41
- Returns a list of offsite backup records, querying each project separately.
42
- """
35
+ """Returns a list of offsite backup records."""
4336 all_backups = []
4437 for project in ["mdf", "seriousletter"]:
4538 result = await run_ops_json(["offsite", "list", project])
....@@ -57,9 +50,12 @@
5750 _: str = Depends(verify_token),
5851 ) -> dict[str, Any]:
5952 """
60
- Runs `ops backup {project} {env}` and returns the result.
53
+ Runs `ops backup {project} {env}` on the host.
54
+
55
+ Runs via nsenter because ops backup delegates to project CLIs
56
+ that use host Python venvs.
6157 """
62
- result = await run_ops(["backup", project, env], timeout=_BACKUP_TIMEOUT)
58
+ result = await run_ops_host(["backup", project, env], timeout=_BACKUP_TIMEOUT)
6359 if not result["success"]:
6460 raise HTTPException(
6561 status_code=500,
....@@ -79,10 +75,8 @@
7975 env: str,
8076 _: str = Depends(verify_token),
8177 ) -> dict[str, Any]:
82
- """
83
- Runs `ops offsite upload {project} {env}` and returns the result.
84
- """
85
- result = await run_ops(
78
+ """Runs `ops offsite upload {project} {env}` on the host."""
79
+ result = await run_ops_host(
8680 ["offsite", "upload", project, env], timeout=_BACKUP_TIMEOUT
8781 )
8882 if not result["success"]:
....@@ -90,28 +84,18 @@
9084 status_code=500,
9185 detail=f"Offsite upload failed: {result['error'] or result['output']}",
9286 )
93
- return {
94
- "success": True,
95
- "output": result["output"],
96
- "project": project,
97
- "env": env,
98
- }
87
+ return {"success": True, "output": result["output"], "project": project, "env": env}
9988
10089
10190 @router.post("/offsite/retention", summary="Apply offsite retention policy")
10291 async def apply_retention(
10392 _: str = Depends(verify_token),
10493 ) -> dict[str, Any]:
105
- """
106
- Runs `ops offsite retention` and returns the result.
107
- """
108
- result = await run_ops(["offsite", "retention"], timeout=_BACKUP_TIMEOUT)
94
+ """Runs `ops offsite retention` on the host."""
95
+ result = await run_ops_host(["offsite", "retention"], timeout=_BACKUP_TIMEOUT)
10996 if not result["success"]:
11097 raise HTTPException(
11198 status_code=500,
11299 detail=f"Retention policy failed: {result['error'] or result['output']}",
113100 )
114
- return {
115
- "success": True,
116
- "output": result["output"],
117
- }
101
+ return {"success": True, "output": result["output"]}