mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-08 03:01:47 +00:00
feat(update): make pre-update backup opt-in (off by default) (#16566)
The zip backup could add minutes to every 'hermes update' on large HERMES_HOME directories. Flip the default to off and add a --backup flag for one-off opt-in runs. - updates.pre_update_backup default: True -> False - hermes update: new --backup flag (opposite of existing --no-backup) - Silent no-op when disabled (no message spam on every update) - Existing --no-backup still works and wins over --backup - Users who explicitly set pre_update_backup: true keep the old behavior - Tests updated to cover default-off, --backup opt-in, and config-enabled paths
This commit is contained in:
parent
ec671c4154
commit
ea3c5a14c3
3 changed files with 69 additions and 22 deletions
|
|
@ -1055,10 +1055,11 @@ DEFAULT_CONFIG = {
|
||||||
"updates": {
|
"updates": {
|
||||||
# Run a full ``hermes backup``-style zip of HERMES_HOME before every
|
# Run a full ``hermes backup``-style zip of HERMES_HOME before every
|
||||||
# ``hermes update``. Backups land in ``<HERMES_HOME>/backups/`` and
|
# ``hermes update``. Backups land in ``<HERMES_HOME>/backups/`` and
|
||||||
# can be restored with ``hermes import <path>``. Set to false to
|
# can be restored with ``hermes import <path>``. Off by default —
|
||||||
# skip the backup entirely; use the ``--no-backup`` flag on a single
|
# on large HERMES_HOME directories the zip can add minutes to every
|
||||||
# update invocation to override just that run.
|
# update. Set to true to re-enable, or pass ``--backup`` to opt in
|
||||||
"pre_update_backup": True,
|
# for a single update run.
|
||||||
|
"pre_update_backup": False,
|
||||||
# How many pre-update backup zips to retain. Older ones are pruned
|
# How many pre-update backup zips to retain. Older ones are pruned
|
||||||
# automatically after each successful backup.
|
# automatically after each successful backup.
|
||||||
"backup_keep": 5,
|
"backup_keep": 5,
|
||||||
|
|
|
||||||
|
|
@ -6145,16 +6145,21 @@ def _ensure_fhs_path_guard() -> None:
|
||||||
def _run_pre_update_backup(args) -> None:
|
def _run_pre_update_backup(args) -> None:
|
||||||
"""Create a full zip backup of HERMES_HOME before running the update.
|
"""Create a full zip backup of HERMES_HOME before running the update.
|
||||||
|
|
||||||
Gated on ``updates.pre_update_backup`` in config (default true). The
|
Gated on ``updates.pre_update_backup`` in config (default false). Off
|
||||||
``--no-backup`` flag on ``hermes update`` overrides it for one run.
|
by default because the zip can add minutes to every update on large
|
||||||
Never raises — a backup failure should not block the update itself.
|
HERMES_HOME directories. The ``--backup`` flag on ``hermes update``
|
||||||
|
opts in for a single run; ``--no-backup`` forces it off when config
|
||||||
|
has it enabled. Never raises — a backup failure should not block the
|
||||||
|
update itself.
|
||||||
"""
|
"""
|
||||||
# CLI flag wins over config
|
# CLI flags win over config. --no-backup beats --backup if both are set.
|
||||||
if getattr(args, "no_backup", False):
|
if getattr(args, "no_backup", False):
|
||||||
print("◆ Pre-update backup: skipped (--no-backup)")
|
print("◆ Pre-update backup: skipped (--no-backup)")
|
||||||
print()
|
print()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
force_backup = bool(getattr(args, "backup", False))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from hermes_cli.config import load_config
|
from hermes_cli.config import load_config
|
||||||
cfg = load_config()
|
cfg = load_config()
|
||||||
|
|
@ -6163,12 +6168,13 @@ def _run_pre_update_backup(args) -> None:
|
||||||
cfg = {}
|
cfg = {}
|
||||||
|
|
||||||
updates_cfg = cfg.get("updates", {}) if isinstance(cfg, dict) else {}
|
updates_cfg = cfg.get("updates", {}) if isinstance(cfg, dict) else {}
|
||||||
enabled = updates_cfg.get("pre_update_backup", True)
|
enabled = updates_cfg.get("pre_update_backup", False)
|
||||||
keep = updates_cfg.get("backup_keep", 5)
|
keep = updates_cfg.get("backup_keep", 5)
|
||||||
|
|
||||||
if not enabled:
|
if not enabled and not force_backup:
|
||||||
print("◆ Pre-update backup: disabled (updates.pre_update_backup=false in config.yaml)")
|
# Silent by default — the backup is off, most users don't need to
|
||||||
print()
|
# hear about it on every update. They can opt in via --backup
|
||||||
|
# or by flipping the config knob.
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -6221,8 +6227,8 @@ def _run_pre_update_backup(args) -> None:
|
||||||
|
|
||||||
print(f" Saved: {display_path} ({size_str}, {elapsed:.1f}s)")
|
print(f" Saved: {display_path} ({size_str}, {elapsed:.1f}s)")
|
||||||
print(f" Restore: hermes import {out_path}")
|
print(f" Restore: hermes import {out_path}")
|
||||||
print(f" Disable: set updates.pre_update_backup: false in config.yaml")
|
print(f" Disable: omit --backup (backups are off by default)")
|
||||||
print(f" (or pass --no-backup on a single update)")
|
print(f" set updates.pre_update_backup: false in config.yaml")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -9671,6 +9677,12 @@ Examples:
|
||||||
default=False,
|
default=False,
|
||||||
help="Skip the pre-update backup for this run (overrides updates.pre_update_backup)",
|
help="Skip the pre-update backup for this run (overrides updates.pre_update_backup)",
|
||||||
)
|
)
|
||||||
|
update_parser.add_argument(
|
||||||
|
"--backup",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help="Force a pre-update backup for this run (off by default; overrides updates.pre_update_backup)",
|
||||||
|
)
|
||||||
update_parser.set_defaults(func=cmd_update)
|
update_parser.set_defaults(func=cmd_update)
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
|
|
|
||||||
|
|
@ -1344,9 +1344,10 @@ class TestRunPreUpdateBackup:
|
||||||
del __import__("sys").modules[mod]
|
del __import__("sys").modules[mod]
|
||||||
return root
|
return root
|
||||||
|
|
||||||
def test_default_enabled_creates_backup(self, hermes_home, capsys):
|
def test_backup_flag_creates_backup(self, hermes_home, capsys):
|
||||||
|
"""--backup forces the pre-update backup for one run even when config is off."""
|
||||||
from hermes_cli.main import _run_pre_update_backup
|
from hermes_cli.main import _run_pre_update_backup
|
||||||
_run_pre_update_backup(Namespace(no_backup=False))
|
_run_pre_update_backup(Namespace(no_backup=False, backup=True))
|
||||||
out = capsys.readouterr().out
|
out = capsys.readouterr().out
|
||||||
assert "Creating pre-update backup" in out
|
assert "Creating pre-update backup" in out
|
||||||
assert "Saved:" in out
|
assert "Saved:" in out
|
||||||
|
|
@ -1357,9 +1358,20 @@ class TestRunPreUpdateBackup:
|
||||||
backups = list((hermes_home / "backups").glob("pre-update-*.zip"))
|
backups = list((hermes_home / "backups").glob("pre-update-*.zip"))
|
||||||
assert len(backups) == 1
|
assert len(backups) == 1
|
||||||
|
|
||||||
|
def test_default_disabled_is_silent(self, hermes_home, capsys):
|
||||||
|
"""With the default-off config and no --backup flag, the hook is silent
|
||||||
|
and creates no backup. This is the common case for every update."""
|
||||||
|
from hermes_cli.main import _run_pre_update_backup
|
||||||
|
_run_pre_update_backup(Namespace(no_backup=False, backup=False))
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert out == ""
|
||||||
|
assert not (hermes_home / "backups").exists() or not list(
|
||||||
|
(hermes_home / "backups").glob("pre-update-*.zip")
|
||||||
|
)
|
||||||
|
|
||||||
def test_no_backup_flag_skips(self, hermes_home, capsys):
|
def test_no_backup_flag_skips(self, hermes_home, capsys):
|
||||||
from hermes_cli.main import _run_pre_update_backup
|
from hermes_cli.main import _run_pre_update_backup
|
||||||
_run_pre_update_backup(Namespace(no_backup=True))
|
_run_pre_update_backup(Namespace(no_backup=True, backup=False))
|
||||||
out = capsys.readouterr().out
|
out = capsys.readouterr().out
|
||||||
assert "skipped (--no-backup)" in out
|
assert "skipped (--no-backup)" in out
|
||||||
assert "Creating pre-update backup" not in out
|
assert "Creating pre-update backup" not in out
|
||||||
|
|
@ -1368,7 +1380,30 @@ class TestRunPreUpdateBackup:
|
||||||
(hermes_home / "backups").glob("pre-update-*.zip")
|
(hermes_home / "backups").glob("pre-update-*.zip")
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_config_disabled_skips(self, hermes_home, capsys):
|
def test_config_enabled_creates_backup(self, hermes_home, capsys):
|
||||||
|
"""Users who explicitly set updates.pre_update_backup: true still get
|
||||||
|
a backup on every update — this is the opt-in legacy behavior."""
|
||||||
|
import yaml
|
||||||
|
(hermes_home / "config.yaml").write_text(yaml.safe_dump({
|
||||||
|
"_config_version": 22,
|
||||||
|
"updates": {"pre_update_backup": True},
|
||||||
|
}))
|
||||||
|
import sys as _sys
|
||||||
|
for mod in list(_sys.modules.keys()):
|
||||||
|
if mod.startswith("hermes_cli.config"):
|
||||||
|
del _sys.modules[mod]
|
||||||
|
|
||||||
|
from hermes_cli.main import _run_pre_update_backup
|
||||||
|
_run_pre_update_backup(Namespace(no_backup=False, backup=False))
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert "Creating pre-update backup" in out
|
||||||
|
assert "Saved:" in out
|
||||||
|
backups = list((hermes_home / "backups").glob("pre-update-*.zip"))
|
||||||
|
assert len(backups) == 1
|
||||||
|
|
||||||
|
def test_config_disabled_is_silent(self, hermes_home, capsys):
|
||||||
|
"""Explicit pre_update_backup: false behaves the same as the default —
|
||||||
|
silent no-op, no message spam."""
|
||||||
import yaml
|
import yaml
|
||||||
(hermes_home / "config.yaml").write_text(yaml.safe_dump({
|
(hermes_home / "config.yaml").write_text(yaml.safe_dump({
|
||||||
"_config_version": 22,
|
"_config_version": 22,
|
||||||
|
|
@ -1381,10 +1416,9 @@ class TestRunPreUpdateBackup:
|
||||||
del _sys.modules[mod]
|
del _sys.modules[mod]
|
||||||
|
|
||||||
from hermes_cli.main import _run_pre_update_backup
|
from hermes_cli.main import _run_pre_update_backup
|
||||||
_run_pre_update_backup(Namespace(no_backup=False))
|
_run_pre_update_backup(Namespace(no_backup=False, backup=False))
|
||||||
out = capsys.readouterr().out
|
out = capsys.readouterr().out
|
||||||
assert "disabled" in out
|
assert out == ""
|
||||||
assert "updates.pre_update_backup=false" in out
|
|
||||||
assert not list((hermes_home / "backups").glob("pre-update-*.zip")) \
|
assert not list((hermes_home / "backups").glob("pre-update-*.zip")) \
|
||||||
if (hermes_home / "backups").exists() else True
|
if (hermes_home / "backups").exists() else True
|
||||||
|
|
||||||
|
|
@ -1401,6 +1435,6 @@ class TestRunPreUpdateBackup:
|
||||||
del _sys.modules[mod]
|
del _sys.modules[mod]
|
||||||
|
|
||||||
from hermes_cli.main import _run_pre_update_backup
|
from hermes_cli.main import _run_pre_update_backup
|
||||||
_run_pre_update_backup(Namespace(no_backup=True))
|
_run_pre_update_backup(Namespace(no_backup=True, backup=False))
|
||||||
out = capsys.readouterr().out
|
out = capsys.readouterr().out
|
||||||
assert "skipped (--no-backup)" in out
|
assert "skipped (--no-backup)" in out
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue