This repository has no description
3.7 kB
124 lines
1#!/usr/bin/env node
2// ─── Sync vault + carry to R2 ───────────────────────────────────────
3// Walks the local vault and carry directories, uploads each file to R2
4// under {did}/{relativePath}.
5//
6// Usage:
7// node scripts/sync-to-r2.mjs --did did:plc:xxx --vault /home/nandi/vault --carry /home/nandi/.local/share/carry-vault
8//
9// Requires R2 credentials as env vars:
10// R2_ACCOUNT_ID, R2_ACCESS_KEY, R2_SECRET_KEY
11
12import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
13import { readdir, readFile, stat } from "fs/promises";
14import { join, relative } from "path";
15
16const args = process.argv.slice(2);
17function getArg(name: string): string | undefined {
18 const idx = args.indexOf(`--${name}`);
19 return idx >= 0 ? args[idx + 1] : undefined;
20}
21
22const DID = getArg("did") || process.env.STRATA_DID;
23const VAULT_DIR = getArg("vault") || process.env.VAULT_DIR || "/home/nandi/vault";
24const CARRY_DIR = getArg("carry") || process.env.CARRY_DIR || "/home/nandi/.local/share/carry-vault";
25const R2_ACCOUNT_ID = process.env.R2_ACCOUNT_ID;
26const R2_ACCESS_KEY = process.env.R2_ACCESS_KEY;
27const R2_SECRET_KEY = process.env.R2_SECRET_KEY;
28
29if (!DID) {
30 console.error("Usage: node sync-to-r2.mjs --did did:plc:xxx [--vault path] [--carry path]");
31 console.error(" or set STRATA_DID, VAULT_DIR, CARRY_DIR env vars");
32 process.exit(1);
33}
34
35if (!R2_ACCOUNT_ID || !R2_ACCESS_KEY || !R2_SECRET_KEY) {
36 console.error("Set R2_ACCOUNT_ID, R2_ACCESS_KEY, R2_SECRET_KEY env vars");
37 process.exit(1);
38}
39
40const s3 = new S3Client({
41 region: "auto",
42 endpoint: `https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
43 credentials: {
44 accessKeyId: R2_ACCESS_KEY,
45 secretAccessKey: R2_SECRET_KEY,
46 },
47});
48
49const VAULT_BUCKET = "stigmergic-vault";
50const CARRY_BUCKET = "stigmergic-carry";
51
52async function walkDir(dir: string): Promise<string[]> {
53 const files: string[] = [];
54 try {
55 const entries = await readdir(dir, { withFileTypes: true });
56 for (const entry of entries) {
57 const fullPath = join(dir, entry.name);
58 if (entry.isDirectory()) {
59 files.push(...await walkDir(fullPath));
60 } else {
61 files.push(fullPath);
62 }
63 }
64 } catch {
65 // Directory doesn't exist, skip
66 }
67 return files;
68}
69
70async function syncDir(
71 baseDir: string,
72 bucket: string,
73 prefix: string,
74 extensions: Set<string>,
75): Promise<number> {
76 const files = await walkDir(baseDir);
77 let uploaded = 0;
78
79 for (const filePath of files) {
80 const relPath = relative(baseDir, filePath);
81 const ext = relPath.split(".").pop() || "";
82
83 if (!extensions.has(ext)) continue;
84
85 const key = `${prefix}/${relPath}`;
86 const body = await readFile(filePath);
87
88 try {
89 await s3.send(
90 new PutObjectCommand({
91 Bucket: bucket,
92 Key: key,
93 Body: body,
94 ContentType: ext === "md" ? "text/markdown" : "application/json",
95 }),
96 );
97 uploaded++;
98 console.log(` ✓ ${key}`);
99 } catch (e: any) {
100 console.error(` ✗ ${key}: ${e.message}`);
101 }
102 }
103
104 return uploaded;
105}
106
107async function main() {
108 console.log(`Syncing data for ${DID}`);
109 console.log();
110
111 console.log("Syncing vault...");
112 const vaultCount = await syncDir(VAULT_DIR, VAULT_BUCKET, DID, new Set(["md", "canvas"]));
113 console.log(` ${vaultCount} vault files uploaded`);
114
115 console.log();
116 console.log("Syncing carry...");
117 const carryCount = await syncDir(CARRY_DIR, CARRY_BUCKET, DID, new Set(["json"]));
118 console.log(` ${carryCount} carry files uploaded`);
119
120 console.log();
121 console.log("Done.");
122}
123
124main().catch(console.error);