diff --git a/docsgpt/deploy/commands.py b/docsgpt/deploy/commands.py index aad99560..6353925a 100644 --- a/docsgpt/deploy/commands.py +++ b/docsgpt/deploy/commands.py @@ -568,6 +568,10 @@ def backup(args, context: Optional[Context] = None) -> int: env = _installed(directory) if env is None: return 1 + if _mode(directory) == "native": + raise DeployError( + f"{directory} is a native install: its database and its files are not in Docker volumes, so there is nothing here to archive. Back up the PostgreSQL that POSTGRES_URI points at with pg_dump, and copy the indexes, inputs and vectors folders from the data home." + ) out_dir = Path(args.out).expanduser() if args.out else directory / "backups" taken_at = datetime.now(timezone.utc) @@ -657,13 +661,17 @@ def _restore_data(context: Context, directory: Path, archive: Path, volumes: lis def restore(args, context: Optional[Context] = None) -> int: """Put a backup's database and data volumes back over this install.""" context = context or Context.default(args) + directory = stack.stack_dir(args.dir) + if _mode(directory) == "native": + raise DeployError( + f"{directory} is a native install: its database and its files are not in Docker volumes, so there is nothing here to archive. Back up the PostgreSQL that POSTGRES_URI points at with pg_dump, and copy the indexes, inputs and vectors folders from the data home. `docsgpt restore` puts back what `docsgpt backup` wrote for a Docker install." + ) archive = Path(args.archive).expanduser() manifest = backup_format.read_manifest(archive) backup_format.check_version(manifest, context.version, args.force) # Everything the archive declares is checked here, while DocsGPT is still up. volumes = backup_format.validate(archive, manifest) - directory = stack.stack_dir(args.dir) env = _installed(directory) if env is None: return 1 diff --git a/tests/deploy/test_native.py b/tests/deploy/test_native.py index 39a8938c..2b360803 100644 --- a/tests/deploy/test_native.py +++ b/tests/deploy/test_native.py @@ -238,6 +238,17 @@ class TestNativeLifecycle: self._installed(tmp_path, FakeServices()) assert _run(["logs", "--dir", str(tmp_path)], _native_context()) == 0, "logs work in both modes" + def test_backup_says_why_there_is_nothing_to_archive(self, tmp_path): + """Its data is not in Docker volumes, so compose would fail with no configuration file.""" + self._installed(tmp_path, FakeServices()) + with pytest.raises(DeployError, match="native install"): + _run(["backup", "--dir", str(tmp_path)], _native_context()) + + def test_restore_refuses_before_it_touches_anything(self, tmp_path): + self._installed(tmp_path, FakeServices()) + with pytest.raises(DeployError, match="native install"): + _run(["restore", str(tmp_path / "any.tar.gz"), "--dir", str(tmp_path), "--yes"], _native_context()) + class FakeLaunchctl: """launchctl, with a bootout that takes `unload_polls` polls to take effect."""