diff --git a/backend/app/services/migrators/backup.py b/backend/app/services/migrators/backup.py index 4d3cb96..d0bbf72 100644 --- a/backend/app/services/migrators/backup.py +++ b/backend/app/services/migrators/backup.py @@ -37,12 +37,21 @@ def _backups_dir(images_root: Path | None = None) -> Path: return p +_DEFAULT_SUBPROCESS_TIMEOUT_S = 30 * 60 # 30 minutes + + def _run_subprocess(cmd: list[str], **kwargs: Any): - # Overridable for tests via monkeypatch. + # Overridable for tests via monkeypatch. Hard wall-clock timeout + # guards against pg_dump / tar / zstd hangs on NFS — without it the + # task pretends to be 'running' forever (operator hit this 2026-05- + # 23 with two backups stuck in MigrationRun). On timeout + # subprocess.run raises TimeoutExpired which the caller surfaces as + # a task error. return subprocess.run( cmd, capture_output=True, check=True, + timeout=_DEFAULT_SUBPROCESS_TIMEOUT_S, **{k: v for k, v in kwargs.items() if not k.startswith("_")}, ) diff --git a/frontend/src/components/AppShell.vue b/frontend/src/components/AppShell.vue index 3546e40..a583526 100644 --- a/frontend/src/components/AppShell.vue +++ b/frontend/src/components/AppShell.vue @@ -18,5 +18,11 @@ const route = useRoute()