(docs) Add manual restore guidance for snapshots
Document the manual restore workflow in the README and surface snapshot- specific restore commands on the snapshot detail page. The guidance keeps restores intentionally manual for now: inspect the snapshot data directory, run rsync with --dry-run, restore to staging first, and treat hardlinked snapshot files as read-only.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
@@ -494,12 +495,14 @@ def snapshot_detail(request, snapshot_id: int):
|
||||
SnapshotRecord.objects.select_related("host", "base").prefetch_related("derived_snapshots", "backup_runs"),
|
||||
id=snapshot_id,
|
||||
)
|
||||
restore = _snapshot_restore_guidance(snapshot)
|
||||
context = {
|
||||
"snapshot": snapshot,
|
||||
"stats": snapshot.metadata.get("stats") if isinstance(snapshot.metadata, dict) else {},
|
||||
"metadata_json": _pretty_json(snapshot.metadata),
|
||||
"backup_runs": snapshot.backup_runs.select_related("host").order_by("-created_at"),
|
||||
"derived_snapshots": snapshot.derived_snapshots.select_related("host").order_by("-started_at", "dirname"),
|
||||
"restore": restore,
|
||||
}
|
||||
return render(request, "pobsync_backend/snapshot_detail.html", context)
|
||||
|
||||
@@ -790,6 +793,28 @@ def _pretty_json(value: object) -> str:
|
||||
return json.dumps(value or {}, indent=2, sort_keys=True)
|
||||
|
||||
|
||||
def _snapshot_restore_guidance(snapshot: SnapshotRecord) -> dict[str, str]:
|
||||
source_path = Path(snapshot.path) / "data"
|
||||
destination_path = Path("/restore") / snapshot.host.host
|
||||
quoted_source = _quote_path_with_trailing_slash(source_path)
|
||||
quoted_destination = _quote_path_with_trailing_slash(destination_path)
|
||||
quoted_remote_destination = shlex.quote(f"root@{snapshot.host.address or snapshot.host.host}:/")
|
||||
common_args = "rsync -aHAX --numeric-ids --info=progress2"
|
||||
|
||||
return {
|
||||
"source_path": str(source_path),
|
||||
"destination_path": str(destination_path),
|
||||
"inspect_command": f"ls -la {quoted_source}",
|
||||
"dry_run_command": f"{common_args} --dry-run {quoted_source} {quoted_destination}",
|
||||
"local_command": f"{common_args} {quoted_source} {quoted_destination}",
|
||||
"remote_dry_run_command": f"{common_args} --dry-run {quoted_source} {quoted_remote_destination}",
|
||||
}
|
||||
|
||||
|
||||
def _quote_path_with_trailing_slash(path: Path) -> str:
|
||||
return shlex.quote(str(path).rstrip("/") + "/")
|
||||
|
||||
|
||||
def _run_rsync_log_path(run: BackupRun) -> Path | None:
|
||||
if isinstance(run.result, dict):
|
||||
log = run.result.get("log")
|
||||
|
||||
Reference in New Issue
Block a user