feat: snapshot/restore checkpoints (SAND-WP-0007)
Add workspace checkpoint API with SnapshotStore, extension hooks on compose-ssh and saas-stub, manager orchestration, CLI/HTTP surface, profile.compose-checkpoint, and docs/tests.
This commit is contained in:
parent
2760ef2373
commit
952cebf2e9
21 changed files with 966 additions and 34 deletions
5
src/sandboxer/snapshots/__init__.py
Normal file
5
src/sandboxer/snapshots/__init__.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
"""Snapshot checkpoint persistence."""
|
||||
|
||||
from sandboxer.snapshots.store import SnapshotStore
|
||||
|
||||
__all__ = ["SnapshotStore"]
|
||||
47
src/sandboxer/snapshots/store.py
Normal file
47
src/sandboxer/snapshots/store.py
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
"""Persistent snapshot index (JSON file)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from sandboxer.models import SnapshotRecord
|
||||
|
||||
|
||||
def _default_store_path() -> Path:
|
||||
base = Path(os.environ.get("XDG_DATA_HOME", Path.home() / ".local" / "share"))
|
||||
return base / "sandboxer" / "snapshots.json"
|
||||
|
||||
|
||||
class SnapshotStore:
|
||||
def __init__(self, path: Path | None = None) -> None:
|
||||
self.path = path or _default_store_path()
|
||||
self.path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def _read(self) -> dict[str, dict]:
|
||||
if not self.path.exists():
|
||||
return {}
|
||||
return json.loads(self.path.read_text())
|
||||
|
||||
def _write(self, data: dict[str, dict]) -> None:
|
||||
self.path.write_text(json.dumps(data, indent=2, default=str))
|
||||
|
||||
def save(self, record: SnapshotRecord) -> None:
|
||||
data = self._read()
|
||||
data[record.snapshot_id] = record.model_dump(mode="json")
|
||||
self._write(data)
|
||||
|
||||
def get(self, snapshot_id: str) -> SnapshotRecord | None:
|
||||
raw = self._read().get(snapshot_id)
|
||||
if not raw:
|
||||
return None
|
||||
return SnapshotRecord.model_validate(raw)
|
||||
|
||||
def list_all(self) -> list[SnapshotRecord]:
|
||||
return [SnapshotRecord.model_validate(v) for v in self._read().values()]
|
||||
|
||||
def delete(self, snapshot_id: str) -> None:
|
||||
data = self._read()
|
||||
data.pop(snapshot_id, None)
|
||||
self._write(data)
|
||||
Loading…
Add table
Add a link
Reference in a new issue