mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-23 16:36:23 +00:00
fix(update): consolidate pre-update backups into one gated mechanism (#65754)
hermes update ran TWO separate pre-update backup mechanisms: the config-gated full zip (updates.pre_update_backup, default off) and an unconditional quick state snapshot added for #15733 that ignored the user's setting entirely. On a large state.db (observed: 24 GB) the 'cheap' snapshot silently added ~60s to every update and ate 24 GB of disk in state-snapshots/. Now there is ONE mechanism, gated by updates.pre_update_backup with three modes: - quick (new default): state snapshot of critical small files (pairing JSONs, cron jobs, config, auth, per-profile DBs). Files over 1 GiB are skipped with a warning so a bloated state.db can never stall the update again. - full: the quick snapshot plus the HERMES_HOME zip (old 'true' behavior; --backup forces it for one run). - off: nothing runs — an explicit opt-out now disables the quick snapshot too (--no-backup does the same per-run). Legacy booleans are honored: true -> full, false -> off. _run_pre_update_backup() now returns the quick-snapshot id so the post-update cron-jobs restore safety net (#34600) keeps working; the snapshot moved from the post-fetch site to the pre-mutation site, which also covers the zip-fallback update path it previously missed.
This commit is contained in:
parent
8462764367
commit
d0dcb9a5fd
9 changed files with 343 additions and 161 deletions
|
|
@ -794,18 +794,51 @@ def create_quick_snapshot(
|
|||
label: Optional[str] = None,
|
||||
hermes_home: Optional[Path] = None,
|
||||
keep: Optional[int] = None,
|
||||
max_file_size: Optional[int] = None,
|
||||
) -> Optional[str]:
|
||||
"""Create a quick state snapshot of critical files.
|
||||
|
||||
Copies STATE_FILES to a timestamped directory under state-snapshots/.
|
||||
Auto-prunes old snapshots beyond the keep limit.
|
||||
|
||||
Args:
|
||||
max_file_size: When set, individual files larger than this many bytes
|
||||
are skipped (with a printed warning) instead of copied. Used by
|
||||
the pre-update safety snapshot so a multi-GB ``state.db`` can
|
||||
never stall ``hermes update`` or silently eat disk — the small
|
||||
pairing/cron/config files the snapshot exists to protect are
|
||||
always captured. ``None`` (default) copies everything, which
|
||||
preserves manual ``/snapshot`` and ``hermes backup --quick``
|
||||
behavior.
|
||||
|
||||
Returns:
|
||||
Snapshot ID (timestamp-based), or None if no files found.
|
||||
"""
|
||||
home = hermes_home or get_hermes_home()
|
||||
root = _quick_snapshot_root(home)
|
||||
|
||||
def _too_large(path: Path, rel_name: str) -> bool:
|
||||
"""True (and warn) when ``path`` exceeds the max_file_size cap."""
|
||||
if max_file_size is None:
|
||||
return False
|
||||
try:
|
||||
size = path.stat().st_size
|
||||
except OSError:
|
||||
return False
|
||||
if size <= max_file_size:
|
||||
return False
|
||||
print(
|
||||
f" ⚠ Snapshot: skipping {rel_name} "
|
||||
f"({_format_size(size)} exceeds {_format_size(max_file_size)} limit)"
|
||||
)
|
||||
logger.warning(
|
||||
"Quick snapshot skipped %s: %d bytes exceeds %d byte limit",
|
||||
rel_name,
|
||||
size,
|
||||
max_file_size,
|
||||
)
|
||||
return True
|
||||
|
||||
ts = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")
|
||||
snap_id = f"{ts}-{label}" if label else ts
|
||||
snap_dir = root / snap_id
|
||||
|
|
@ -831,6 +864,8 @@ def create_quick_snapshot(
|
|||
# the board databases + their metadata to restore a board.
|
||||
if "/workspaces/" in f"/{sub_rel}/" or "/attachments/" in f"/{sub_rel}/":
|
||||
continue
|
||||
if _too_large(sub, sub_rel):
|
||||
continue
|
||||
dst = snap_dir / sub_rel
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
try:
|
||||
|
|
@ -850,6 +885,9 @@ def create_quick_snapshot(
|
|||
if not src.is_file():
|
||||
continue
|
||||
|
||||
if _too_large(src, rel):
|
||||
continue
|
||||
|
||||
dst = snap_dir / rel
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
|
@ -1239,7 +1277,7 @@ def _prune_pre_update_backups(backup_dir: Path, keep: int) -> int:
|
|||
than no backup at all (and the wrapper in ``main.py`` would still print
|
||||
a misleading ``Saved: <path>`` line for a file that no longer exists).
|
||||
Operators who genuinely don't want a backup should set
|
||||
``updates.pre_update_backup: false`` in config — that gates creation.
|
||||
``updates.pre_update_backup: off`` in config — that gates creation.
|
||||
"""
|
||||
keep = max(keep, 1)
|
||||
if not backup_dir.exists():
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue