mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(backup): exclude state temp files (#122250)
This commit is contained in:
committed by
GitHub
parent
a432e2a586
commit
d699662083
+1
-1
@@ -110,7 +110,7 @@ SQLite databases under the state directory are captured with SQLite's online bac
|
||||
|
||||
Installed plugin source and manifest files under the state directory's `extensions/` tree are included, but their nested `node_modules/` dependency trees are skipped as rebuildable install artifacts. After restoring an archive, use `openclaw plugins update <id>` or reinstall with `openclaw plugins install <spec> --force` if a restored plugin reports missing dependencies.
|
||||
|
||||
Installer-managed and rebuildable runtime roots under the state directory are also skipped: `dev/`, `git/`, `npm/`, legacy `npm-runtime/`, and `tools/`. These contain managed checkouts, package trees, and downloaded runtimes rather than authoritative user state; reinstall or update the corresponding runtime or plugin after restore. An explicitly configured config file, credentials directory, or workspace inside one of these roots remains included.
|
||||
Installer-managed and rebuildable runtime roots under the state directory are also skipped: `dev/`, `git/`, `npm/`, legacy `npm-runtime/`, `tmp/`, and `tools/`. These contain managed checkouts, package trees, compiler caches, temporary files, and downloaded runtimes rather than authoritative user state; reinstall or update the corresponding runtime or plugin after restore. An explicitly configured config file, credentials directory, or workspace inside one of these roots remains included.
|
||||
|
||||
Local edits inside a managed `dev/` checkout are developer source, not OpenClaw product state, and are not included. Commit and push those edits or copy the checkout separately before relying on a state backup.
|
||||
|
||||
|
||||
@@ -726,7 +726,7 @@ describe("createBackupArchive", () => {
|
||||
suffix,
|
||||
).toBe(false);
|
||||
}
|
||||
expect(result.skippedVolatileCount).toBe(10);
|
||||
expect(result.skippedVolatileCount).toBe(9);
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -2524,7 +2524,9 @@ describe("createBackupArchive", () => {
|
||||
await fs.mkdir(path.join(stateDir, "dev", "openclaw", "dist"), { recursive: true });
|
||||
await fs.mkdir(path.join(stateDir, "developer"), { recursive: true });
|
||||
await fs.mkdir(path.join(stateDir, "dev-backup"), { recursive: true });
|
||||
for (const managedRoot of ["dev", "git", "npm-runtime", "tools"]) {
|
||||
await fs.mkdir(path.join(stateDir, "temporary"), { recursive: true });
|
||||
await fs.mkdir(path.join(stateDir, "tmp-data"), { recursive: true });
|
||||
for (const managedRoot of ["dev", "git", "npm-runtime", "tmp", "tools"]) {
|
||||
await fs.mkdir(path.join(stateDir, managedRoot, "runtime"), { recursive: true });
|
||||
await fs.writeFile(
|
||||
path.join(stateDir, managedRoot, "runtime", "fixture.sqlite"),
|
||||
@@ -2589,6 +2591,8 @@ describe("createBackupArchive", () => {
|
||||
);
|
||||
await fs.writeFile(path.join(stateDir, "developer", "keep.txt"), "keep\n", "utf8");
|
||||
await fs.writeFile(path.join(stateDir, "dev-backup", "keep.txt"), "keep\n", "utf8");
|
||||
await fs.writeFile(path.join(stateDir, "temporary", "keep.txt"), "keep\n", "utf8");
|
||||
await fs.writeFile(path.join(stateDir, "tmp-data", "keep.txt"), "keep\n", "utf8");
|
||||
await fs.mkdir(outputDir, { recursive: true });
|
||||
|
||||
const result = await createBackupArchive({
|
||||
@@ -2603,7 +2607,7 @@ describe("createBackupArchive", () => {
|
||||
expect(entrySuffixes).toContain("/state/extensions/demo/src/index.js");
|
||||
expect(entrySuffixes).toContain("/state/node_modules/root-dep/index.js");
|
||||
expect(entrySuffixes).toContain("/state/node_modules/root-dep/fixture.sqlite");
|
||||
for (const managedRoot of ["dev", "git", "npm", "npm-runtime", "tools"]) {
|
||||
for (const managedRoot of ["dev", "git", "npm", "npm-runtime", "tmp", "tools"]) {
|
||||
expect(
|
||||
entrySuffixes.some(
|
||||
(entry) =>
|
||||
@@ -2614,6 +2618,8 @@ describe("createBackupArchive", () => {
|
||||
}
|
||||
expect(entrySuffixes).toContain("/state/developer/keep.txt");
|
||||
expect(entrySuffixes).toContain("/state/dev-backup/keep.txt");
|
||||
expect(entrySuffixes).toContain("/state/temporary/keep.txt");
|
||||
expect(entrySuffixes).toContain("/state/tmp-data/keep.txt");
|
||||
const pluginNodeModuleEntries = entries.filter((entry) =>
|
||||
entry.includes("/state/extensions/demo/node_modules/"),
|
||||
);
|
||||
@@ -2637,6 +2643,8 @@ describe("createBackupArchive", () => {
|
||||
async (state) => {
|
||||
const stateDir = state.stateDir;
|
||||
const workspaceDir = path.join(stateDir, "dev", "workspace");
|
||||
const tmpWorkspaceDir = path.join(stateDir, "tmp", "workspace");
|
||||
const externalTmpWorkspaceDir = state.path("tmp");
|
||||
const runtimeDir = path.join(stateDir, "dev", "openclaw");
|
||||
const configPath = path.join(stateDir, "git", "config", "openclaw.json");
|
||||
const oauthDir = path.join(stateDir, "tools", "oauth");
|
||||
@@ -2647,6 +2655,9 @@ describe("createBackupArchive", () => {
|
||||
state.envVars.OPENCLAW_OAUTH_DIR = oauthDir;
|
||||
state.applyEnv();
|
||||
await fs.mkdir(workspaceDir, { recursive: true });
|
||||
await fs.mkdir(tmpWorkspaceDir, { recursive: true });
|
||||
await fs.mkdir(externalTmpWorkspaceDir, { recursive: true });
|
||||
await fs.mkdir(path.join(stateDir, "tmp", "tsx-501"), { recursive: true });
|
||||
await fs.mkdir(runtimeDir, { recursive: true });
|
||||
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
||||
await fs.mkdir(oauthDir, { recursive: true });
|
||||
@@ -2655,13 +2666,32 @@ describe("createBackupArchive", () => {
|
||||
configPath,
|
||||
`${JSON.stringify({
|
||||
agents: {
|
||||
entries: { main: { default: true, workspace: workspaceDir } },
|
||||
entries: {
|
||||
main: { default: true, workspace: workspaceDir },
|
||||
external: { workspace: externalTmpWorkspaceDir },
|
||||
worker: { workspace: tmpWorkspaceDir },
|
||||
},
|
||||
},
|
||||
})}\n`,
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(path.join(oauthDir, "credentials.json"), "{}\n", "utf8");
|
||||
await fs.writeFile(path.join(workspaceDir, "AGENTS.md"), "durable workspace\n", "utf8");
|
||||
await fs.writeFile(
|
||||
path.join(tmpWorkspaceDir, "AGENTS.md"),
|
||||
"durable tmp workspace\n",
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(
|
||||
path.join(externalTmpWorkspaceDir, "AGENTS.md"),
|
||||
"durable external tmp workspace\n",
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(
|
||||
path.join(stateDir, "tmp", "tsx-501", "cache-entry"),
|
||||
"rebuildable compiler cache\n",
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(path.join(runtimeDir, "package.json"), "{}\n", "utf8");
|
||||
await fs.writeFile(path.join(toolRuntimeDir, "tool.bin"), "runtime\n", "utf8");
|
||||
const sqlite = requireNodeSqlite();
|
||||
@@ -2688,6 +2718,10 @@ describe("createBackupArchive", () => {
|
||||
expect(
|
||||
entries.some((entry) => entry.endsWith("/state/dev/workspace/workspace.sqlite")),
|
||||
).toBe(true);
|
||||
expect(entries.some((entry) => entry.endsWith("/state/tmp/workspace/AGENTS.md"))).toBe(
|
||||
true,
|
||||
);
|
||||
expect(entries.some((entry) => entry.endsWith("/tmp/AGENTS.md"))).toBe(true);
|
||||
expect(entries.some((entry) => entry.endsWith("/state/git/config/openclaw.json"))).toBe(
|
||||
true,
|
||||
);
|
||||
@@ -2695,6 +2729,7 @@ describe("createBackupArchive", () => {
|
||||
true,
|
||||
);
|
||||
expect(entries.some((entry) => entry.includes("/state/dev/openclaw/"))).toBe(false);
|
||||
expect(entries.some((entry) => entry.includes("/state/tmp/tsx-501/"))).toBe(false);
|
||||
expect(entries.some((entry) => entry.includes("/state/tools/runtime/"))).toBe(false);
|
||||
|
||||
const runtime: RuntimeEnv = { log: vi.fn(), error: vi.fn(), exit: vi.fn() };
|
||||
|
||||
@@ -331,7 +331,7 @@ function normalizeBackupFilterPath(value: string): string {
|
||||
return value.replaceAll("\\", "/").replace(/\/+$/u, "");
|
||||
}
|
||||
|
||||
const REINSTALLABLE_STATE_ROOTS = new Set(["dev", "git", "npm", "npm-runtime", "tools"]);
|
||||
const NON_AUTHORITATIVE_STATE_ROOTS = new Set(["dev", "git", "npm", "npm-runtime", "tmp", "tools"]);
|
||||
|
||||
function buildStateBackupFilter(
|
||||
stateDir: string,
|
||||
@@ -348,7 +348,7 @@ function buildStateBackupFilter(
|
||||
}
|
||||
|
||||
const segments = normalizedFilePath.slice(statePrefix.length).split("/");
|
||||
if (REINSTALLABLE_STATE_ROOTS.has(segments[0] ?? "")) {
|
||||
if (NON_AUTHORITATIVE_STATE_ROOTS.has(segments[0] ?? "")) {
|
||||
const resolvedFilePath = path.resolve(filePath);
|
||||
// Configured workspaces nested under a managed root remain authoritative
|
||||
// user state. Keep their ancestors traversable without admitting siblings.
|
||||
|
||||
Reference in New Issue
Block a user