mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-27 19:35:13 +01:00
fix: stream and consistently copy state snapshots
This commit is contained in:
1 parent
f9a60389d6
commit
b89d6adb39
2 files changed
+14
-4
No files matched your search
@@ -87,6 +87,11 @@ async function expandPattern(pattern: string): Promise<string[]> {
|
||||
async function copyEntry(entry: StateClassEntry, source: string, destination: string): Promise<void> {
|
||||
const stat = await fs.stat(source);
|
||||
await fs.mkdir(path.dirname(destination), { recursive: true });
|
||||
if (entry.consistency === "sqlite_backup" && stat.isDirectory()) {
|
||||
await fs.mkdir(destination, { recursive: true });
|
||||
for (const child of await fs.readdir(source)) await copyEntry(entry, path.join(source, child), path.join(destination, child));
|
||||
return;
|
||||
}
|
||||
if (entry.consistency === "sqlite_backup" && stat.isFile() && /\.(sqlite|sqlite3|db)$/i.test(source)) {
|
||||
await execFileAsync("sqlite3", [source, `.backup '${destination.replace(/'/g, "''")}'`]);
|
||||
return;
|
||||
@@ -138,10 +143,12 @@ export function createInstanceStateSnapshotService(opts: {
|
||||
await execFileAsync("tar", ["-czf", archivePath, "-C", stageDir, "."]);
|
||||
const encryptedPath = `${archivePath}.enc`;
|
||||
await opts.encryptionProvider.encrypt(archivePath, encryptedPath);
|
||||
const body = await fs.readFile(encryptedPath);
|
||||
const encryptedStat = await fs.stat(encryptedPath);
|
||||
const objectKey = `instance-state/${context.instanceId ?? "default"}/${startedAt.toISOString().replace(/[:.]/g, "-")}.tar.gz.enc`;
|
||||
await opts.storageProvider.putObject({ objectKey, body, contentType: "application/vnd.paperclip.state-snapshot", contentLength: body.length });
|
||||
const result = { objectKey, sizeBytes: body.length, sha256: createHash("sha256").update(body).digest("hex"), entryCount, startedAt: startedAt.toISOString(), finishedAt: new Date().toISOString() };
|
||||
const sha256 = createHash("sha256");
|
||||
for await (const chunk of createReadStream(encryptedPath)) sha256.update(chunk);
|
||||
await opts.storageProvider.putObject({ objectKey, body: createReadStream(encryptedPath), contentType: "application/vnd.paperclip.state-snapshot", contentLength: encryptedStat.size });
|
||||
const result = { objectKey, sizeBytes: encryptedStat.size, sha256: sha256.digest("hex"), entryCount, startedAt: startedAt.toISOString(), finishedAt: new Date().toISOString() };
|
||||
await fs.mkdir(markerDir, { recursive: true });
|
||||
await fs.writeFile(path.join(markerDir, "state-snapshot.success.json"), JSON.stringify(result));
|
||||
await fs.rm(path.join(markerDir, "state-snapshot.failure"), { force: true });
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { createReadStream, promises as fs } from "node:fs";
|
||||
import { createWriteStream } from "node:fs";
|
||||
import { pipeline } from "node:stream/promises";
|
||||
import path from "node:path";
|
||||
import type { StorageProvider, GetObjectResult, HeadObjectResult } from "./types.js";
|
||||
import { notFound, badRequest } from "../errors.js";
|
||||
@@ -47,7 +49,8 @@ export function createLocalDiskStorageProvider(baseDir: string): StorageProvider
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
|
||||
const tempPath = `${targetPath}.tmp-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
await fs.writeFile(tempPath, input.body);
|
||||
if (Buffer.isBuffer(input.body)) await fs.writeFile(tempPath, input.body);
|
||||
else await pipeline(input.body, createWriteStream(tempPath));
|
||||
await fs.rename(tempPath, targetPath);
|
||||
},
|
||||
|
||||
|
||||
Reference in new issue
Block a user