mirror of
https://github.com/agent0ai/agent-zero.git
synced 2026-07-09 17:08:29 +00:00
Exclude Time Travel history from backups
Skip usr/.time_travel during self-update user-data backups and add the same Time Travel shadow-history exclusion to default BackupService metadata.\n\nAdd focused regressions proving regular user files stay in backup coverage while Time Travel shadow repositories are omitted.
This commit is contained in:
parent
bc96386260
commit
3d87998821
6 changed files with 63 additions and 0 deletions
|
|
@ -22,6 +22,7 @@
|
|||
- Do not bake secrets, local `.env` values, or user data into the image.
|
||||
- Runtime startup must ensure `/a0/usr/uploads` exists before supervised services start.
|
||||
- Runtime startup raises the soft open-file limit toward `A0_NOFILE_LIMIT` (default `65535`) before supervisord starts, bounded by the container hard limit.
|
||||
- Self-update user-data backups skip Time Travel shadow history under `usr/.time_travel/` and transient Desktop agent state.
|
||||
|
||||
## Work Guidance
|
||||
|
||||
|
|
|
|||
|
|
@ -405,6 +405,11 @@ def should_exclude_from_usr_backup(
|
|||
logger: AttemptLogger,
|
||||
) -> bool:
|
||||
parts = relative_dir.parts
|
||||
if parts and parts[0] == ".time_travel":
|
||||
logger.log(
|
||||
f"Skipping Time Travel history during usr backup: {Path('usr') / relative_dir}"
|
||||
)
|
||||
return True
|
||||
if (
|
||||
len(parts) >= 6
|
||||
and parts[0] == "plugins"
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ class BackupService:
|
|||
return f"""# User data
|
||||
# All persistent user data is now centralized in /usr for easier backup and restore
|
||||
{agent_root}/usr/**
|
||||
!{agent_root}/usr/.time_travel/**
|
||||
"""
|
||||
|
||||
def _get_agent_zero_version(self) -> str:
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
- Observed side-effect areas: filesystem reads, filesystem writes, filesystem deletion, settings/state persistence, secret handling.
|
||||
- Imported dependency areas include: `datetime`, `helpers`, `helpers.localization`, `helpers.print_style`, `json`, `os`, `pathspec`, `platform`, `tempfile`, `typing`, `zipfile`.
|
||||
- `test_patterns(..., max_files=None)` is the unlimited scan mode. UI preview and dry-run callers may pass bounded limits, but real backup creation and restore clean-before-restore must use unlimited matching so archives and cleanup are not silently truncated.
|
||||
- Default backup metadata includes persistent `/usr` data but excludes Time Travel shadow history under `usr/.time_travel/**`.
|
||||
|
||||
## Key Concepts
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,28 @@ class UploadedBackup:
|
|||
shutil.copyfile(self.path, target)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_default_backup_patterns_exclude_time_travel_history(tmp_path):
|
||||
root = tmp_path / "a0"
|
||||
usr = root / "usr"
|
||||
time_travel = usr / ".time_travel" / "workspaces" / "demo" / "repo.git"
|
||||
time_travel.mkdir(parents=True)
|
||||
(usr / "settings.json").write_text('{"ok": true}\n', encoding="utf-8")
|
||||
(time_travel / "objects.pack").write_text("history\n", encoding="utf-8")
|
||||
|
||||
service = BackupService()
|
||||
service.agent_zero_root = str(root)
|
||||
service.base_paths = {str(root): str(root)}
|
||||
metadata = service.get_default_backup_metadata()
|
||||
|
||||
files = await service.test_patterns(metadata, max_files=None)
|
||||
paths = {item["real_path"] for item in files}
|
||||
|
||||
assert str(usr / "settings.json") in paths
|
||||
assert str(time_travel / "objects.pack") not in paths
|
||||
assert f"{root}/usr/.time_travel/**" in metadata["exclude_patterns"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pattern_scan_can_run_without_file_limit(tmp_path):
|
||||
root = tmp_path / "a0"
|
||||
|
|
|
|||
|
|
@ -829,6 +829,39 @@ def test_self_update_manager_usr_backup_skips_runtime_sockets():
|
|||
)
|
||||
|
||||
|
||||
def test_self_update_manager_usr_backup_skips_time_travel_history(tmp_path):
|
||||
manager = load_self_update_manager()
|
||||
repo_dir = tmp_path / "repo"
|
||||
usr_dir = repo_dir / "usr"
|
||||
time_travel = usr_dir / ".time_travel" / "workspaces" / "demo" / "repo.git"
|
||||
time_travel.mkdir(parents=True)
|
||||
(usr_dir / "settings.json").write_text('{"ok": true}\n', encoding="utf-8")
|
||||
(time_travel / "objects.pack").write_text("history\n", encoding="utf-8")
|
||||
messages = []
|
||||
|
||||
class ListLogger:
|
||||
def log(self, message=""):
|
||||
messages.append(message)
|
||||
|
||||
backup_path = manager.create_usr_backup(
|
||||
repo_dir=repo_dir,
|
||||
backup_path=str(tmp_path / "backups"),
|
||||
backup_name="usr-backup.zip",
|
||||
conflict_policy="rename",
|
||||
logger=ListLogger(),
|
||||
)
|
||||
|
||||
with zipfile.ZipFile(backup_path) as archive:
|
||||
names = set(archive.namelist())
|
||||
|
||||
assert "usr/settings.json" in names
|
||||
assert "usr/.time_travel/workspaces/demo/repo.git/objects.pack" not in names
|
||||
assert any(
|
||||
"Skipping Time Travel history during usr backup: usr/.time_travel" in message
|
||||
for message in messages
|
||||
)
|
||||
|
||||
|
||||
def test_self_update_manager_usr_backup_skips_transient_desktop_ssh_agent_dir(tmp_path):
|
||||
manager = load_self_update_manager()
|
||||
repo_dir = tmp_path / "repo"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue