fix(config): one bounded backups/config/ dir replaces four config.yaml.bak schemes
Four writers each dropped their own uniquely-named copy of config.yaml next to the real file and none of them ever deleted anything: hermes setup (config.yaml.bak.YYYYMMDD_HHMMSS, one per run even with no change), the corrupt-YAML snapshot (config.yaml.corrupt.<ts>.bak), hermes migrate xai (config.yaml.bak-pre-migrate-xai-<ts>) and the Docker boot migration (config.yaml.bak-<ts>, .env.bak-<ts>). A home dir accumulated a dozen variants with no way to tell which mattered. hermes_cli/config_backups.py::backup_config is now the single writer: backups/config/config.yaml.<reason>.<YYYYMMDD-HHMMSS>, skipped when the newest copy for that reason is byte-identical, rotated to the newest five per reason. backups/ is already excluded from full backups so nothing nests. Legacy siblings written by the old schemes are moved into the dir on first use; hand-named copies (config.yaml.bak-my-note) are left alone. Live: three `hermes setup --non-interactive` runs against an unchanged config went from three .bak files in HERMES_HOME to one pre-setup copy under backups/config/; repeated loads of broken YAML produce one corrupt copy instead of one per process (deduped by content).
This commit is contained in:
@@ -4,7 +4,6 @@ from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Iterable
|
||||
|
||||
@@ -14,6 +13,7 @@ from hermes_cli.config import (
|
||||
get_env_path,
|
||||
migrate_config,
|
||||
)
|
||||
from hermes_cli.config_backups import backup_config, list_config_backups
|
||||
from hermes_cli.config_migrations import (
|
||||
SUPPORT_FLOOR_VERSION,
|
||||
support_floor_message,
|
||||
@@ -21,26 +21,14 @@ from hermes_cli.config_migrations import (
|
||||
from utils import env_var_enabled
|
||||
|
||||
|
||||
def _backup_path(path: Path, stamp: str) -> Path:
|
||||
base = path.with_name(f"{path.name}.bak-{stamp}")
|
||||
if not base.exists():
|
||||
return base
|
||||
for index in range(1, 1000):
|
||||
candidate = path.with_name(f"{path.name}.bak-{stamp}.{index}")
|
||||
if not candidate.exists():
|
||||
return candidate
|
||||
raise RuntimeError(f"could not choose a backup path for {path}")
|
||||
|
||||
|
||||
def _backup_existing(paths: Iterable[Path]) -> dict[Path, Path]:
|
||||
stamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
||||
"""Snapshot each file into backups/config/; an identical existing snapshot is reused."""
|
||||
backups: dict[Path, Path] = {}
|
||||
for path in paths:
|
||||
if not path.is_file():
|
||||
continue
|
||||
dest = _backup_path(path, stamp)
|
||||
shutil.copy2(path, dest)
|
||||
backups[path] = dest
|
||||
dest = backup_config(path, "pre-docker-migrate") or next(
|
||||
iter(list_config_backups(path, "pre-docker-migrate")), None)
|
||||
if dest is not None:
|
||||
backups[path] = dest
|
||||
return backups
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user