mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-11-06 05:17:15 +00:00
The runtime reads only browser.* now and the AGENT_BROWSER_* -> browser.* move lived only in the versioned v52 config step, so three kinds of home lost their engine, headed mode and persistent profile without a word: a missing config.yaml (reads as current), an unstamped one (boot skipped it as below the support floor) and one first stamped by an ordinary save before any migration. The move is now done once, at the home's first stamp: - note_first_stamp (every save_config first stamp, and left-core's created file) moves the settings along with the left-core rows it already records; - stamp_missing_config creates config.yaml with that first stamp when a missing config's .env still carries them, before the missing-config shortcut in boot (post_update.step_migrate_config), migrate_config and the Docker entrypoint; - boot migrates an unstamped config whose .env still carries them through migrate_config (legacy-key steps, v52 among them, then the stamp); other unstamped configs are left alone as before. The conversion reuses the v52 step's code (move_agent_browser_env) and only ever runs while a home is unstamped or missing, so a retained .env never overrides a later config edit.
112 lines
4.0 KiB
Python
112 lines
4.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Run Docker boot-time config migrations safely."""
|
|
from __future__ import annotations
|
|
|
|
import shutil
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Iterable
|
|
|
|
from hermes_cli.config import (
|
|
InvalidUserConfigError,
|
|
get_config_path,
|
|
get_env_path,
|
|
migrate_config,
|
|
)
|
|
from hermes_cli.config_version_stamp import check_config_version, read_config_version_stamp, stamp_missing_config
|
|
from hermes_cli.config_backups import backup_config, list_config_backups
|
|
from hermes_cli.config_migrations import (
|
|
SUPPORT_FLOOR_VERSION,
|
|
support_floor_message,
|
|
)
|
|
from utils import env_var_enabled
|
|
|
|
|
|
def _backup_existing(paths: Iterable[Path]) -> dict[Path, Path]:
|
|
"""Snapshot each file into backups/config/; an identical existing snapshot is reused."""
|
|
backups: dict[Path, Path] = {}
|
|
for path in paths:
|
|
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
|
|
|
|
|
|
def _restore_backups(backups: dict[Path, Path]) -> list[Path]:
|
|
restored: list[Path] = []
|
|
for original, backup in backups.items():
|
|
if not backup.is_file():
|
|
continue
|
|
shutil.copy2(backup, original)
|
|
restored.append(original)
|
|
return restored
|
|
|
|
|
|
def main() -> int:
|
|
if env_var_enabled("HERMES_SKIP_CONFIG_MIGRATION"):
|
|
print("[config-migrate] HERMES_SKIP_CONFIG_MIGRATION is set; skipping config migration")
|
|
return 0
|
|
|
|
# Strict read: malformed YAML or a non-mapping root is left alone with a warning and the
|
|
# boot continues, instead of running the backup/migrate dance that migrate_config() would
|
|
# refuse anyway. A missing config.yaml reads as current, so its legacy browser settings get their
|
|
# first stamp before that read.
|
|
stamp_missing_config()
|
|
try:
|
|
stamp, latest_ver = read_config_version_stamp(raise_on_parse_error=True)
|
|
except InvalidUserConfigError as exc:
|
|
print(f"[config-migrate] WARNING: {exc}; leaving config.yaml untouched", file=sys.stderr)
|
|
return 0
|
|
current_ver = 0 if stamp is None else stamp
|
|
if current_ver >= latest_ver:
|
|
return 0
|
|
|
|
# Below the auto-migration support floor: migrate_config() refuses (and
|
|
# leaves the file untouched), so don't run the backup/verify dance that
|
|
# would raise "did not advance config version" and block the boot.
|
|
# Warn-and-continue matches the CLI's fail-safe posture. A config with no
|
|
# _config_version (stamp None: a volume seeded from the template) is not
|
|
# below the floor: migrate_config() stamps it.
|
|
if stamp is not None and current_ver < SUPPORT_FLOOR_VERSION:
|
|
print(
|
|
f"[config-migrate] WARNING: {support_floor_message()}",
|
|
file=sys.stderr,
|
|
)
|
|
return 0
|
|
|
|
backups = _backup_existing((get_config_path(), get_env_path()))
|
|
backup_text = ", ".join(str(path) for path in backups.values()) if backups else "none"
|
|
print(
|
|
f"[config-migrate] Migrating config schema {current_ver} -> {latest_ver}; "
|
|
f"backups: {backup_text}"
|
|
)
|
|
try:
|
|
migrate_config(interactive=False, quiet=False)
|
|
except Exception:
|
|
restored = _restore_backups(backups)
|
|
if restored:
|
|
print(
|
|
"[config-migrate] Migration failed; restored "
|
|
+ ", ".join(str(path) for path in restored)
|
|
)
|
|
raise
|
|
|
|
post_ver, _ = check_config_version()
|
|
if post_ver < latest_ver:
|
|
restored = _restore_backups(backups)
|
|
restored_text = ", ".join(str(path) for path in restored) if restored else "none"
|
|
raise RuntimeError(
|
|
f"migration did not advance config version to {latest_ver} "
|
|
f"(still {post_ver}); restored: {restored_text}"
|
|
)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
raise SystemExit(main())
|
|
except Exception as exc:
|
|
print(f"[config-migrate] ERROR: {exc}", file=sys.stderr)
|
|
raise SystemExit(1)
|