8900 commited on
Commit
fcce6a0
Β·
verified Β·
1 Parent(s): 46c89b3

Update hf-sync-manager.mjs

Browse files
Files changed (1) hide show
  1. hf-sync-manager.mjs +248 -239
hf-sync-manager.mjs CHANGED
@@ -1,5 +1,11 @@
1
  // ============================================================
2
  // HF Sync Manager - Full Mirror Edition
 
 
 
 
 
 
3
  // ============================================================
4
 
5
  import fs from "node:fs";
@@ -23,293 +29,296 @@ var EXCLUDE_EXT = [".bak", ".tmp", ".log"];
23
  function log(msg) { console.log("[hf-sync] " + msg); }
24
 
25
  function sleep(ms) {
26
- return new Promise(function(resolve) { setTimeout(resolve, ms); });
27
  }
28
 
29
  function repoUrl() {
30
- return "https://user:" + HF_TOKEN +
31
- "@huggingface.co/datasets/" + DATASET_ID;
32
  }
33
 
34
  function git(args) {
35
- return execSync("git " + args, {
36
- cwd: REPO_DIR,
37
- stdio: "pipe",
38
- timeout: 120000,
39
- env: Object.assign({}, process.env, { GIT_TERMINAL_PROMPT: "0" })
40
- }).toString().trim();
41
  }
42
 
43
  function shouldExclude(name) {
44
- var i;
45
- for (i = 0; i < EXCLUDE.length; i++) {
46
- if (name === EXCLUDE[i]) return true;
47
- }
48
- for (i = 0; i < EXCLUDE_EXT.length; i++) {
49
- if (name.indexOf(EXCLUDE_EXT[i]) >= 0) return true;
50
- }
51
- return false;
52
  }
53
 
54
  // –––––––––––––––––––––––––––––
55
 
56
  function ensureRepo() {
57
- if (fs.existsSync(path.join(REPO_DIR, ".git"))) return;
58
- execSync("rm -rf " + REPO_DIR, { stdio: "pipe" });
59
- try {
60
- execSync("git clone --depth 1 " + repoUrl() + " " + REPO_DIR, {
61
- stdio: "pipe", timeout: 120000,
62
- env: Object.assign({}, process.env, { GIT_TERMINAL_PROMPT: "0" })
63
- });
64
- log("Repo cloned");
65
- } catch (e) {
66
- log("Clone failed, init empty: " + e.message);
67
- fs.mkdirSync(REPO_DIR, { recursive: true });
68
- execSync("git init " + REPO_DIR, { stdio: "pipe" });
69
- git("remote add origin " + repoUrl());
70
- }
71
- try {
72
- git("config user.email openclaw@hf.space");
73
- git("config user.name OpenClaw-Sync");
74
- git("config pull.rebase false");
75
- } catch (e) { /* non-fatal */ }
76
  }
77
 
78
  function pull() {
79
- try { git("fetch --quiet origin"); } catch (e) { /* ignore */ }
80
- try { git("pull --quiet --no-rebase origin main"); }
81
- catch (e) {
82
- try { git("pull --quiet --no-rebase origin master"); }
83
- catch (e2) { /* empty repo */ }
84
- }
85
  }
86
 
87
  function push() {
88
- try {
89
- git("add -A");
90
- var changed = git("diff --cached --name-only");
91
- if (!changed) return 0;
92
- var n = changed.split("\n").filter(Boolean).length;
93
- var ts = new Date().toISOString().replace("T", " ").substring(0, 19);
94
- git("commit -m \"sync " + ts + "\"");
95
- try { git("push --quiet origin HEAD:main"); }
96
- catch (e) { git("push --quiet origin HEAD:master"); }
97
- return n;
98
- } catch (e) {
99
- log("Push failed: " + e.message);
100
- return 0;
101
- }
102
  }
103
 
104
  // –––––––––––––––––––––––––––––
105
 
106
  function mirrorDirToRepo(srcDir, dstDir) {
107
- if (!fs.existsSync(srcDir)) return 0;
108
- fs.mkdirSync(dstDir, { recursive: true });
109
- var count = 0;
110
- fs.readdirSync(srcDir).forEach(function(name) {
111
- if (shouldExclude(name)) return;
112
- var src = path.join(srcDir, name);
113
- var dst = path.join(dstDir, name);
114
- try {
115
- if (fs.statSync(src).isDirectory()) {
116
- count += mirrorDirToRepo(src, dst);
117
- } else {
118
- fs.copyFileSync(src, dst);
119
- count++;
120
- }
121
- } catch (e) {
122
- log("Copy error [" + name + "]: " + e.message);
123
- }
124
- });
125
- return count;
126
  }
127
 
128
  function mirrorDirFromRepo(srcDir, dstDir) {
129
- if (!fs.existsSync(srcDir)) return 0;
130
- fs.mkdirSync(dstDir, { recursive: true });
131
- var count = 0;
132
- fs.readdirSync(srcDir).forEach(function(name) {
133
- var src = path.join(srcDir, name);
134
- var dst = path.join(dstDir, name);
135
- try {
136
- if (fs.statSync(src).isDirectory()) {
137
- count += mirrorDirFromRepo(src, dst);
138
- } else {
139
- fs.copyFileSync(src, dst);
140
- count++;
141
- }
142
- } catch (e) {
143
- log("Restore error [" + name + "]: " + e.message);
144
- }
145
- });
146
- return count;
147
  }
148
 
149
  // –––––––––––––––––––––––––––––
150
 
151
  function seedWorkspaceIfNeeded() {
152
- fs.mkdirSync(WORKSPACE, { recursive: true });
153
- fs.mkdirSync(path.join(WORKSPACE, "memory"), { recursive: true });
154
-
155
- var soul = path.join(WORKSPACE, "SOUL.md");
156
- if (!fs.existsSync(soul)) {
157
- fs.writeFileSync(soul, [
158
- "# Soul",
159
- "",
160
- "You are a helpful, warm, concise AI assistant.",
161
- "",
162
- "## Language",
163
- "",
164
- "Default language: Simplified Chinese.",
165
- "Always reply in Chinese unless the user writes in another language first.",
166
- "",
167
- "## Tone",
168
- "",
169
- "- Natural and friendly, not overly formal",
170
- "- Concise and to the point",
171
- "- Ask one clarifying question at a time when needed"
172
- ].join("\n") + "\n", "utf-8");
173
- log("Seeded workspace/SOUL.md");
174
- }
175
-
176
- var agents = path.join(WORKSPACE, "AGENTS.md");
177
- if (!fs.existsSync(agents)) {
178
- fs.writeFileSync(agents, [
179
- "# Agent Instructions",
180
- "",
181
- "## Boot sequence",
182
- "",
183
- "1. Read SOUL.md - language and persona",
184
- "2. Read USER.md if present - user profile",
185
- "3. Read MEMORY.md - long-term facts and rules",
186
- "4. Read today and yesterday memory/YYYY-MM-DD.md if present",
187
- "",
188
- "## Memory rules",
189
- "",
190
- "- Write important facts to MEMORY.md when asked",
191
- "- Log daily context to memory/YYYY-MM-DD.md",
192
- "- Do not ask for information already provided"
193
- ].join("\n") + "\n", "utf-8");
194
- log("Seeded workspace/AGENTS.md");
195
- }
196
-
197
- var mem = path.join(WORKSPACE, "MEMORY.md");
198
- if (!fs.existsSync(mem)) {
199
- fs.writeFileSync(mem, [
200
- "# Long-term Memory",
201
- "",
202
- "<!-- OpenClaw writes important facts here. -->",
203
- "<!-- Loaded at the start of every session. -->"
204
- ].join("\n") + "\n", "utf-8");
205
- log("Seeded workspace/MEMORY.md");
206
- }
207
  }
208
 
209
  // –––––––––––––––––––––––––––––
210
 
211
  function bootRestore() {
212
- if (!HF_TOKEN || !DATASET_ID) {
213
- log("No HF_TOKEN/HF_DATASET_ID - skipping Dataset restore");
214
- seedWorkspaceIfNeeded();
215
- return;
216
- }
217
-
218
- log("Boot restore from " + DATASET_ID + "…");
219
- try {
220
- ensureRepo();
221
- pull();
222
- } catch (e) {
223
- log("Repo init failed (" + e.message + ") - starting fresh");
224
- seedWorkspaceIfNeeded();
225
- return;
226
- }
227
-
228
- var repoOC = path.join(REPO_DIR, "openclaw");
229
- if (fs.existsSync(repoOC)) {
230
- var n = mirrorDirFromRepo(repoOC, OPENCLAW_DIR);
231
- log("Boot restore done: " + n + " file(s) -> " + OPENCLAW_DIR);
232
- } else {
233
- log("No data in Dataset yet - starting fresh");
234
- }
235
-
236
- seedWorkspaceIfNeeded();
237
- log("Boot restore complete");
238
  }
239
 
240
  function runSync() {
241
- if (!HF_TOKEN || !DATASET_ID) return;
242
-
243
- var ts = new Date().toISOString().replace("T", " ").substring(0, 19);
244
- log("Sync at " + ts + "…");
245
 
246
- try {
247
- ensureRepo();
248
- pull();
249
 
250
- var repoOC = path.join(REPO_DIR, "openclaw");
251
- if (fs.existsSync(repoOC)) {
252
- execSync("rm -rf " + repoOC, { stdio: "pipe" });
253
- }
254
- fs.mkdirSync(repoOC, { recursive: true });
255
 
256
- var n = mirrorDirToRepo(OPENCLAW_DIR, repoOC);
257
- log("Mirrored " + n + " file(s)");
 
 
 
258
 
259
- var pushed = push();
260
- if (pushed > 0) {
261
- log("Sync done: " + pushed + " file(s) pushed");
262
- } else {
263
- log("Sync done: no changes");
264
- }
265
 
266
- } catch (e) {
267
- log("Sync failed (retry in 30 min): " + e.message);
268
- }
 
 
 
 
 
 
269
  }
270
 
 
 
271
  // –––––––––––––––––––––––––––––
272
 
273
  (async function() {
274
- log("Starting…");
275
- log("OPENCLAW_DIR : " + OPENCLAW_DIR);
276
- log("DATASET : " + (DATASET_ID || "NOT SET"));
277
-
278
- fs.mkdirSync(OPENCLAW_DIR, { recursive: true });
279
- fs.mkdirSync(WORKSPACE, { recursive: true });
280
-
281
- try {
282
- execSync("git config --global user.email openclaw@hf.space", { stdio: "pipe" });
283
- execSync("git config --global user.name OpenClaw-Sync", { stdio: "pipe" });
284
- execSync("git config --global http.postBuffer 52428800", { stdio: "pipe" });
285
- execSync("git config --global pull.rebase false", { stdio: "pipe" });
286
- } catch (e) { /* non-fatal */ }
287
-
288
- var isGatewayRestart = fs.existsSync(RUNNING_FLAG);
289
-
290
- if (isGatewayRestart) {
291
- log("Gateway restart - skipping boot restore (data on disk)");
292
- } else {
293
- log("Container start - running full boot restore");
294
- bootRestore();
295
- }
296
-
297
- fs.writeFileSync(RUNNING_FLAG, String(process.pid), "utf-8");
298
-
299
- process.on("exit", function() { try { fs.unlinkSync(RUNNING_FLAG); } catch (e) { /* ok */ } });
300
- process.on("SIGTERM", function() { try { fs.unlinkSync(RUNNING_FLAG); } catch (e) { /* ok */ } process.exit(0); });
301
- process.on("SIGINT", function() { try { fs.unlinkSync(RUNNING_FLAG); } catch (e) { /* ok */ } process.exit(0); });
302
-
303
- if (!HF_TOKEN || !DATASET_ID) {
304
- log("Sync disabled: set HF_TOKEN and HF_DATASET_ID in Secrets");
305
- return;
306
- }
307
-
308
- log("Sync loop starting (first run in 10 min)…");
309
- await sleep(10 * 60 * 1000);
310
-
311
- while (true) {
312
- runSync();
313
- await sleep(INTERVAL);
314
- }
315
- })().catch(function(e) { console.error("[hf-sync] Fatal: " + e.message); });
 
 
 
1
  // ============================================================
2
  // HF Sync Manager - Full Mirror Edition
3
+ //
4
+ // Mirrors ~/.openclaw/ to HF Dataset every 30 min.
5
+ // Boot restore: Dataset -> local (container restart only).
6
+ // Gateway restart detection via /tmp flag file.
7
+ //
8
+ // Excluded: agents/*/sessions, agents/*/qmd, canvas, .bak, .tmp
9
  // ============================================================
10
 
11
  import fs from "node:fs";
 
29
  function log(msg) { console.log("[hf-sync] " + msg); }
30
 
31
  function sleep(ms) {
32
+ return new Promise(function(resolve) { setTimeout(resolve, ms); });
33
  }
34
 
35
  function repoUrl() {
36
+ return "https://user:" + HF_TOKEN +
37
+ "@huggingface.co/datasets/" + DATASET_ID;
38
  }
39
 
40
  function git(args) {
41
+ return execSync("git " + args, {
42
+ cwd: REPO_DIR,
43
+ stdio: "pipe",
44
+ timeout: 120000,
45
+ env: Object.assign({}, process.env, { GIT_TERMINAL_PROMPT: "0" })
46
+ }).toString().trim();
47
  }
48
 
49
  function shouldExclude(name) {
50
+ var i;
51
+ for (i = 0; i < EXCLUDE.length; i++) {
52
+ if (name === EXCLUDE[i]) return true;
53
+ }
54
+ for (i = 0; i < EXCLUDE_EXT.length; i++) {
55
+ if (name.indexOf(EXCLUDE_EXT[i]) >= 0) return true;
56
+ }
57
+ return false;
58
  }
59
 
60
  // –––––––––––––––––––––––––––––
61
 
62
  function ensureRepo() {
63
+ if (fs.existsSync(path.join(REPO_DIR, ".git"))) return;
64
+ execSync("rm -rf " + REPO_DIR, { stdio: "pipe" });
65
+ try {
66
+ execSync("git clone --depth 1 " + repoUrl() + " " + REPO_DIR, {
67
+ stdio: "pipe", timeout: 120000,
68
+ env: Object.assign({}, process.env, { GIT_TERMINAL_PROMPT: "0" })
69
+ });
70
+ log("Repo cloned");
71
+ } catch (e) {
72
+ log("Clone failed, init empty: " + e.message);
73
+ fs.mkdirSync(REPO_DIR, { recursive: true });
74
+ execSync("git init " + REPO_DIR, { stdio: "pipe" });
75
+ git("remote add origin " + repoUrl());
76
+ }
77
+ try {
78
+ git("config user.email openclaw@hf.space");
79
+ git("config user.name OpenClaw-Sync");
80
+ git("config pull.rebase false");
81
+ } catch (e) { /* non-fatal */ }
82
  }
83
 
84
  function pull() {
85
+ try { git("fetch --quiet origin"); } catch (e) { /* ignore */ }
86
+ try { git("pull --quiet --no-rebase origin main"); }
87
+ catch (e) {
88
+ try { git("pull --quiet --no-rebase origin master"); }
89
+ catch (e2) { /* empty repo */ }
90
+ }
91
  }
92
 
93
  function push() {
94
+ try {
95
+ git("add -A");
96
+ var changed = git("diff --cached --name-only");
97
+ if (!changed) return 0;
98
+ var n = changed.split("\n").filter(Boolean).length;
99
+ var ts = new Date().toISOString().replace("T", " ").substring(0, 19);
100
+ git('commit -m "sync ' + ts + '"');
101
+ try { git("push --quiet origin HEAD:main"); }
102
+ catch (e) { git("push --quiet origin HEAD:master"); }
103
+ return n;
104
+ } catch (e) {
105
+ log("Push failed: " + e.message);
106
+ return 0;
107
+ }
108
  }
109
 
110
  // –––––––––––––––––––––––––––––
111
 
112
  function mirrorDirToRepo(srcDir, dstDir) {
113
+ if (!fs.existsSync(srcDir)) return 0;
114
+ fs.mkdirSync(dstDir, { recursive: true });
115
+ var count = 0;
116
+ fs.readdirSync(srcDir).forEach(function(name) {
117
+ if (shouldExclude(name)) return;
118
+ var src = path.join(srcDir, name);
119
+ var dst = path.join(dstDir, name);
120
+ try {
121
+ if (fs.statSync(src).isDirectory()) {
122
+ count += mirrorDirToRepo(src, dst);
123
+ } else {
124
+ fs.copyFileSync(src, dst);
125
+ count++;
126
+ }
127
+ } catch (e) {
128
+ log("Copy error [" + name + "]: " + e.message);
129
+ }
130
+ });
131
+ return count;
132
  }
133
 
134
  function mirrorDirFromRepo(srcDir, dstDir) {
135
+ if (!fs.existsSync(srcDir)) return 0;
136
+ fs.mkdirSync(dstDir, { recursive: true });
137
+ var count = 0;
138
+ fs.readdirSync(srcDir).forEach(function(name) {
139
+ var src = path.join(srcDir, name);
140
+ var dst = path.join(dstDir, name);
141
+ try {
142
+ if (fs.statSync(src).isDirectory()) {
143
+ count += mirrorDirFromRepo(src, dst);
144
+ } else {
145
+ fs.copyFileSync(src, dst);
146
+ count++;
147
+ }
148
+ } catch (e) {
149
+ log("Restore error [" + name + "]: " + e.message);
150
+ }
151
+ });
152
+ return count;
153
  }
154
 
155
  // –––––––––––––––––––––––––––––
156
 
157
  function seedWorkspaceIfNeeded() {
158
+ fs.mkdirSync(WORKSPACE, { recursive: true });
159
+ fs.mkdirSync(path.join(WORKSPACE, "memory"), { recursive: true });
160
+
161
+ var soul = path.join(WORKSPACE, "SOUL.md");
162
+ if (!fs.existsSync(soul)) {
163
+ fs.writeFileSync(soul, [
164
+ "# Soul",
165
+ "",
166
+ "You are a helpful, warm, concise AI assistant.",
167
+ "",
168
+ "## Language",
169
+ "",
170
+ "Default language: Simplified Chinese.",
171
+ "Always reply in Chinese unless the user writes in another language first.",
172
+ "",
173
+ "## Tone",
174
+ "",
175
+ "- Natural and friendly, not overly formal",
176
+ "- Concise and to the point",
177
+ "- Ask one clarifying question at a time when needed"
178
+ ].join("\n") + "\n", "utf-8");
179
+ log("Seeded workspace/SOUL.md");
180
+ }
181
+
182
+ var agents = path.join(WORKSPACE, "AGENTS.md");
183
+ if (!fs.existsSync(agents)) {
184
+ fs.writeFileSync(agents, [
185
+ "# Agent Instructions",
186
+ "",
187
+ "## Boot sequence",
188
+ "",
189
+ "1. Read SOUL.md - language and persona",
190
+ "2. Read USER.md if present - user profile",
191
+ "3. Read MEMORY.md - long-term facts and rules",
192
+ "4. Read today and yesterday memory/YYYY-MM-DD.md if present",
193
+ "",
194
+ "## Memory rules",
195
+ "",
196
+ "- Write important facts to MEMORY.md when asked",
197
+ "- Log daily context to memory/YYYY-MM-DD.md",
198
+ "- Do not ask for information already provided"
199
+ ].join("\n") + "\n", "utf-8");
200
+ log("Seeded workspace/AGENTS.md");
201
+ }
202
+
203
+ var mem = path.join(WORKSPACE, "MEMORY.md");
204
+ if (!fs.existsSync(mem)) {
205
+ fs.writeFileSync(mem, [
206
+ "# Long-term Memory",
207
+ "",
208
+ "<!-- OpenClaw writes important facts here. -->",
209
+ "<!-- Loaded at the start of every session. -->"
210
+ ].join("\n") + "\n", "utf-8");
211
+ log("Seeded workspace/MEMORY.md");
212
+ }
213
  }
214
 
215
  // –––––––––––––––––––––––––––––
216
 
217
  function bootRestore() {
218
+ if (!HF_TOKEN || !DATASET_ID) {
219
+ log("No HF_TOKEN/HF_DATASET_ID - skipping Dataset restore");
220
+ seedWorkspaceIfNeeded();
221
+ return;
222
+ }
223
+
224
+ log("Boot restore from " + DATASET_ID + "…");
225
+ try {
226
+ ensureRepo();
227
+ pull();
228
+ } catch (e) {
229
+ log("Repo init failed (" + e.message + ") - starting fresh");
230
+ seedWorkspaceIfNeeded();
231
+ return;
232
+ }
233
+
234
+ var repoOC = path.join(REPO_DIR, "openclaw");
235
+ if (fs.existsSync(repoOC)) {
236
+ var n = mirrorDirFromRepo(repoOC, OPENCLAW_DIR);
237
+ log("Boot restore done: " + n + " file(s) -> " + OPENCLAW_DIR);
238
+ } else {
239
+ log("No data in Dataset yet - starting fresh");
240
+ }
241
+
242
+ seedWorkspaceIfNeeded();
243
+ log("Boot restore complete");
244
  }
245
 
246
  function runSync() {
247
+ if (!HF_TOKEN || !DATASET_ID) return;
 
 
 
248
 
249
+ var ts = new Date().toISOString().replace("T", " ").substring(0, 19);
250
+ log("Sync at " + ts + "…");
 
251
 
252
+ try {
253
+ ensureRepo();
254
+ pull();
 
 
255
 
256
+ var repoOC = path.join(REPO_DIR, "openclaw");
257
+ if (fs.existsSync(repoOC)) {
258
+ execSync("rm -rf " + repoOC, { stdio: "pipe" });
259
+ }
260
+ fs.mkdirSync(repoOC, { recursive: true });
261
 
262
+ var n = mirrorDirToRepo(OPENCLAW_DIR, repoOC);
263
+ log("Mirrored " + n + " file(s)");
 
 
 
 
264
 
265
+ var pushed = push();
266
+ if (pushed > 0) {
267
+ log("Sync done: " + pushed + " file(s) pushed");
268
+ } else {
269
+ log("Sync done: no changes");
270
+ }
271
+ } catch (e) {
272
+ log("Sync failed (retry in 30 min): " + e.message);
273
+ }
274
  }
275
 
276
+ // –––––––––––––––––––––––––––––
277
+ // Main - use IIFE to avoid any hoisting issues
278
  // –––––––––––––––––––––––––––––
279
 
280
  (async function() {
281
+ log("Starting…");
282
+ log("OPENCLAW_DIR : " + OPENCLAW_DIR);
283
+ log("DATASET : " + (DATASET_ID || "NOT SET"));
284
+
285
+ fs.mkdirSync(OPENCLAW_DIR, { recursive: true });
286
+ fs.mkdirSync(WORKSPACE, { recursive: true });
287
+
288
+ try {
289
+ execSync("git config --global user.email openclaw@hf.space", { stdio: "pipe" });
290
+ execSync("git config --global user.name OpenClaw-Sync", { stdio: "pipe" });
291
+ execSync("git config --global http.postBuffer 52428800", { stdio: "pipe" });
292
+ execSync("git config --global pull.rebase false", { stdio: "pipe" });
293
+ } catch (e) { /* non-fatal */ }
294
+
295
+ var isGatewayRestart = fs.existsSync(RUNNING_FLAG);
296
+
297
+ if (isGatewayRestart) {
298
+ log("Gateway restart - skipping boot restore (data on disk)");
299
+ } else {
300
+ log("Container start - running full boot restore");
301
+ bootRestore();
302
+ }
303
+
304
+ fs.writeFileSync(RUNNING_FLAG, String(process.pid), "utf-8");
305
+
306
+ process.on("exit", function() { try { fs.unlinkSync(RUNNING_FLAG); } catch (e) { /* ok */ } });
307
+ process.on("SIGTERM", function() { try { fs.unlinkSync(RUNNING_FLAG); } catch (e) { /* ok */ } process.exit(0); });
308
+ process.on("SIGINT", function() { try { fs.unlinkSync(RUNNING_FLAG); } catch (e) { /* ok */ } process.exit(0); });
309
+
310
+ if (!HF_TOKEN || !DATASET_ID) {
311
+ log("Sync disabled: set HF_TOKEN and HF_DATASET_ID in Secrets");
312
+ return;
313
+ }
314
+
315
+ log("Sync loop starting (first run in 10 min)…");
316
+ await sleep(10 * 60 * 1000);
317
+
318
+ while (true) {
319
+ runSync();
320
+ await sleep(INTERVAL);
321
+ }
322
+ })().catch(function(e) {
323
+ console.error("[hf-sync] Fatal: " + e.message);
324
+ });