Dev build 2026-06-01 21:45
This commit is contained in:
parent
3e2700b1bc
commit
ef05da92f8
@ -826,11 +826,24 @@ def _run_pg_restore(dump_bytes: bytes) -> None:
|
||||
Before the destructive load, a safety dump of the CURRENT database is taken.
|
||||
If applying the requested dump fails, the database is rolled back to that
|
||||
safety snapshot so a failed restore never leaves an empty/broken database.
|
||||
|
||||
The safety dump is also written to the local dump store, so every restore
|
||||
leaves behind a token-free local snapshot of the pre-restore state. Regular
|
||||
backups stay Dropbox-only (they can run hourly and would otherwise fill the
|
||||
disk).
|
||||
"""
|
||||
try:
|
||||
safety_bytes, _ = _run_pg_dump()
|
||||
safety_bytes, safety_name = _run_pg_dump()
|
||||
except Exception:
|
||||
safety_bytes = None
|
||||
safety_bytes, safety_name = None, ""
|
||||
|
||||
if safety_bytes is not None:
|
||||
try:
|
||||
_save_local_dump(f"pre-restore-{safety_name}", safety_bytes)
|
||||
_enforce_local_dump_retention(_load_dropbox_retention_count())
|
||||
except Exception:
|
||||
# A local-copy failure must not block the restore.
|
||||
pass
|
||||
|
||||
try:
|
||||
_apply_pg_dump(dump_bytes)
|
||||
@ -1136,16 +1149,6 @@ def _run_backup_internal(*, dry_run: bool, progress_key: int | None = None) -> t
|
||||
uploaded_size += len(dump_data)
|
||||
uploaded_count += 1
|
||||
|
||||
# Keep a local copy of the dump so the database can be restored without a
|
||||
# Dropbox token. Only for real runs, not dry runs.
|
||||
if not dry_run:
|
||||
try:
|
||||
_save_local_dump(dump_name, dump_data)
|
||||
_enforce_local_dump_retention(retention_count)
|
||||
except Exception:
|
||||
# A local-copy failure must not fail the backup itself.
|
||||
pass
|
||||
|
||||
if not dry_run:
|
||||
_save_manifest(new_manifest)
|
||||
return total_files, uploaded_count, uploaded_size
|
||||
@ -1868,8 +1871,8 @@ async def restore_local_dump(request: Request):
|
||||
async def upload_restore_dump(file: UploadFile = File(...)):
|
||||
"""Restore the database from an uploaded .sql dump. No Dropbox token required.
|
||||
|
||||
The uploaded dump is also saved to the local dump store so it is available
|
||||
for future restores.
|
||||
A local pre-restore safety snapshot is taken automatically (by _run_pg_restore)
|
||||
before the load; the uploaded file itself is not persisted.
|
||||
"""
|
||||
if not shutil.which("psql"):
|
||||
return {"ok": False, "error": "psql is not available in this container"}
|
||||
@ -1890,10 +1893,4 @@ async def upload_restore_dump(file: UploadFile = File(...)):
|
||||
except Exception as e:
|
||||
return {"ok": False, "error": str(e)}
|
||||
|
||||
# Persist a local copy so it shows up under Local Database Restore afterwards.
|
||||
try:
|
||||
await asyncio.to_thread(_save_local_dump, filename, data)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return {"ok": True, "restored": filename, "size_bytes": len(data)}
|
||||
|
||||
@ -302,9 +302,10 @@
|
||||
<section class="card">
|
||||
<div class="card-head">Local Database Restore (no Dropbox needed)</div>
|
||||
<p class="muted" style="margin-top:0;margin-bottom:0.6rem;">
|
||||
Every backup also keeps a local copy of the database dump on the config volume,
|
||||
so the database can be restored even without a Dropbox token. You can also upload
|
||||
a <code>.sql</code> dump you downloaded from Dropbox manually.
|
||||
Before every restore a safety snapshot of the current database is saved locally
|
||||
on the config volume (named <code>pre-restore-…</code>), so you can roll back or
|
||||
restore without a Dropbox token. You can also upload a <code>.sql</code> dump you
|
||||
downloaded from Dropbox manually. Regular backups remain Dropbox-only.
|
||||
</p>
|
||||
<p class="muted" style="margin-top:0;margin-bottom:0.9rem;color:var(--err);">
|
||||
⚠ Destructive: replaces the entire current database (with automatic rollback if the dump fails to load).
|
||||
|
||||
@ -10,7 +10,7 @@ from __future__ import annotations
|
||||
|
||||
from changelog import CHANGELOG
|
||||
|
||||
BUILD = 5
|
||||
BUILD = 6
|
||||
|
||||
|
||||
def _release_version() -> str:
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
# Develop Changelog
|
||||
|
||||
## 2026-06-01 — Local database backups + token-free / upload restore
|
||||
## 2026-06-01 — Local pre-restore snapshots + token-free / upload restore
|
||||
|
||||
### Added
|
||||
- Backups now keep a **local copy** of each PostgreSQL dump on the config volume (`CONFIG_DIR/postgres_dumps/`, same retention as snapshots), so the database can be restored **without a Dropbox token**. Previously dumps lived only in Dropbox, so losing the stored token meant being locked out of every backup.
|
||||
- `routers/backup.py`: `LOCAL_DUMP_DIR`, helpers `_save_local_dump`, `_list_local_dumps`, `_enforce_local_dump_retention`, `_resolve_local_dump`; `_run_backup_internal` writes the dump locally (real runs only) after uploading to Dropbox (a local-copy failure never fails the backup).
|
||||
- Every restore now writes its **pre-restore safety dump to a local store** on the config volume (`CONFIG_DIR/postgres_dumps/`, named `pre-restore-…`, with snapshot-equal retention), so the database can be restored or rolled back **without a Dropbox token**. Regular backups are intentionally left **Dropbox-only** — production backs up hourly, so writing every backup to disk would fill the volume.
|
||||
- `routers/backup.py`: `LOCAL_DUMP_DIR`, helpers `_save_local_dump`, `_list_local_dumps`, `_enforce_local_dump_retention`, `_resolve_local_dump`; `_run_pg_restore` persists the safety dump locally (and prunes by retention) before the destructive load. `_run_backup_internal` is unchanged (no local copy).
|
||||
- New **Local Database Restore** card on the Backup page with two token-free paths:
|
||||
- Restore from a locally stored dump — `GET /api/backup/local/dumps`, `POST /api/backup/local/restore`.
|
||||
- Upload a `.sql` dump (e.g. downloaded manually from dropbox.com) and restore it — `POST /api/backup/upload-restore`; the upload is also saved to the local dump store for future use.
|
||||
- Upload a `.sql` dump (e.g. downloaded manually from dropbox.com) and restore it — `POST /api/backup/upload-restore`. The upload itself is not persisted; the automatic pre-restore safety snapshot covers the local copy.
|
||||
- Both reuse the safe restore path (pre-restore safety dump + automatic rollback) and require `psql`.
|
||||
- `templates/backup.html`: new card with a local-dump selector, an upload field, double-guarded restore buttons, and `fmtBytes` sizes.
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user