jishushell 0.4.2 → 0.4.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/Dockerfile.openclaw-slim +58 -0
  2. package/INSTALL-NOTICE +45 -0
  3. package/dist/auth.js +3 -3
  4. package/dist/auth.js.map +1 -1
  5. package/dist/cli/app.d.ts +3 -0
  6. package/dist/cli/app.js +156 -0
  7. package/dist/cli/app.js.map +1 -0
  8. package/dist/{doctor.d.ts → cli/doctor.d.ts} +6 -1
  9. package/dist/{doctor.js → cli/doctor.js} +389 -27
  10. package/dist/cli/doctor.js.map +1 -0
  11. package/dist/cli/helpers.d.ts +4 -0
  12. package/dist/cli/helpers.js +32 -0
  13. package/dist/cli/helpers.js.map +1 -0
  14. package/dist/cli/job.d.ts +3 -0
  15. package/dist/cli/job.js +260 -0
  16. package/dist/cli/job.js.map +1 -0
  17. package/dist/cli/llm.d.ts +24 -0
  18. package/dist/cli/llm.js +593 -0
  19. package/dist/cli/llm.js.map +1 -0
  20. package/dist/cli/openclaw.d.ts +12 -0
  21. package/dist/cli/openclaw.js +156 -0
  22. package/dist/cli/openclaw.js.map +1 -0
  23. package/dist/cli/panel.d.ts +25 -0
  24. package/dist/cli/panel.js +734 -0
  25. package/dist/cli/panel.js.map +1 -0
  26. package/dist/cli.js +476 -219
  27. package/dist/cli.js.map +1 -1
  28. package/dist/config.d.ts +22 -4
  29. package/dist/config.js +96 -55
  30. package/dist/config.js.map +1 -1
  31. package/dist/control.d.ts +13 -41
  32. package/dist/control.js +12 -1355
  33. package/dist/control.js.map +1 -1
  34. package/dist/install.d.ts +1 -1
  35. package/dist/install.js +15 -29
  36. package/dist/install.js.map +1 -1
  37. package/dist/routes/apps.d.ts +3 -0
  38. package/dist/routes/apps.js +99 -0
  39. package/dist/routes/apps.js.map +1 -0
  40. package/dist/routes/backup.d.ts +2 -0
  41. package/dist/routes/backup.js +370 -0
  42. package/dist/routes/backup.js.map +1 -0
  43. package/dist/routes/instances.d.ts +1 -0
  44. package/dist/routes/instances.js +61 -15
  45. package/dist/routes/instances.js.map +1 -1
  46. package/dist/routes/llm.d.ts +15 -0
  47. package/dist/routes/llm.js +246 -0
  48. package/dist/routes/llm.js.map +1 -0
  49. package/dist/routes/setup.js +32 -7
  50. package/dist/routes/setup.js.map +1 -1
  51. package/dist/routes/system.js +31 -6
  52. package/dist/routes/system.js.map +1 -1
  53. package/dist/server.js +69 -5
  54. package/dist/server.js.map +1 -1
  55. package/dist/services/app-compiler.d.ts +15 -0
  56. package/dist/services/app-compiler.js +169 -0
  57. package/dist/services/app-compiler.js.map +1 -0
  58. package/dist/services/app-manager.d.ts +17 -0
  59. package/dist/services/app-manager.js +168 -0
  60. package/dist/services/app-manager.js.map +1 -0
  61. package/dist/services/backup-manager.d.ts +253 -0
  62. package/dist/services/backup-manager.js +2014 -0
  63. package/dist/services/backup-manager.js.map +1 -0
  64. package/dist/services/backup-verify.d.ts +26 -0
  65. package/dist/services/backup-verify.js +240 -0
  66. package/dist/services/backup-verify.js.map +1 -0
  67. package/dist/services/instance-manager.d.ts +73 -5
  68. package/dist/services/instance-manager.js +446 -74
  69. package/dist/services/instance-manager.js.map +1 -1
  70. package/dist/services/job-manager.d.ts +22 -0
  71. package/dist/services/job-manager.js +102 -0
  72. package/dist/services/job-manager.js.map +1 -0
  73. package/dist/services/llm-proxy/adapters.js +5 -1
  74. package/dist/services/llm-proxy/adapters.js.map +1 -1
  75. package/dist/services/llm-proxy/index.d.ts +30 -0
  76. package/dist/services/llm-proxy/index.js +71 -1
  77. package/dist/services/llm-proxy/index.js.map +1 -1
  78. package/dist/services/llm-proxy/ssrf.js +1 -1
  79. package/dist/services/llm-proxy/ssrf.js.map +1 -1
  80. package/dist/services/nomad-manager.js +263 -159
  81. package/dist/services/nomad-manager.js.map +1 -1
  82. package/dist/services/panel-manager.d.ts +40 -0
  83. package/dist/services/panel-manager.js +346 -0
  84. package/dist/services/panel-manager.js.map +1 -0
  85. package/dist/services/process-manager.js +24 -10
  86. package/dist/services/process-manager.js.map +1 -1
  87. package/dist/services/setup-manager.d.ts +4 -2
  88. package/dist/services/setup-manager.js +578 -154
  89. package/dist/services/setup-manager.js.map +1 -1
  90. package/dist/services/telemetry/activation.js +10 -7
  91. package/dist/services/telemetry/activation.js.map +1 -1
  92. package/dist/services/telemetry/client.js +7 -18
  93. package/dist/services/telemetry/client.js.map +1 -1
  94. package/dist/services/telemetry/heartbeat.js +12 -6
  95. package/dist/services/telemetry/heartbeat.js.map +1 -1
  96. package/dist/services/update-manager.d.ts +47 -0
  97. package/dist/services/update-manager.js +305 -0
  98. package/dist/services/update-manager.js.map +1 -0
  99. package/dist/types.d.ts +62 -0
  100. package/dist/utils/fs.d.ts +85 -0
  101. package/dist/utils/fs.js +111 -0
  102. package/dist/utils/fs.js.map +1 -0
  103. package/dist/utils/safe-json.d.ts +2 -0
  104. package/dist/utils/safe-json.js +22 -16
  105. package/dist/utils/safe-json.js.map +1 -1
  106. package/install/jishu-install.sh +582 -138
  107. package/install/jishu-uninstall.sh +276 -391
  108. package/install/post-install.sh +85 -3
  109. package/openclaw-entry.sh +15 -0
  110. package/package.json +12 -5
  111. package/public/assets/Dashboard-CQsp1Mr9.js +1 -0
  112. package/public/assets/InitPassword-BEC8SE4A.js +1 -0
  113. package/public/assets/InstanceDetail-B5wTgNEg.js +17 -0
  114. package/public/assets/{Login-RkjzTNWg.js → Login-D1Bt-Lyk.js} +1 -1
  115. package/public/assets/NewInstance-GQzm3K9D.js +1 -0
  116. package/public/assets/Settings-ByjGlqhP.js +1 -0
  117. package/public/assets/Setup-cMF21Y-8.js +1 -0
  118. package/public/assets/index-B6qQP4mH.css +1 -0
  119. package/public/assets/index-BuTQtuNy.js +16 -0
  120. package/public/assets/logo-black-theme-DywLAtFy.png +0 -0
  121. package/public/assets/logo-white-theme-DXffFAWw.png +0 -0
  122. package/public/assets/{providers-lBSOjUWy.js → providers-V-vwrExZ.js} +1 -1
  123. package/public/assets/{usePolling-CqQ8hrNc.js → usePolling-CK0DfI4h.js} +1 -1
  124. package/public/assets/{vendor-i18n-Bvxxh8Di.js → vendor-i18n-CfW0RvgE.js} +1 -1
  125. package/public/assets/vendor-react-B1-3Yrt-.js +59 -0
  126. package/public/index.html +4 -4
  127. package/dist/doctor.js.map +0 -1
  128. package/public/assets/Dashboard-CAOQDYDR.js +0 -1
  129. package/public/assets/InitPassword-CkehIkJG.js +0 -1
  130. package/public/assets/InstanceDetail-CzW2S95J.js +0 -14
  131. package/public/assets/NewInstance-DdbErdjA.js +0 -1
  132. package/public/assets/Settings-BUD7zwv9.js +0 -1
  133. package/public/assets/Setup-RRTIERGG.js +0 -1
  134. package/public/assets/index-77Ug7feY.css +0 -1
  135. package/public/assets/index-DfRnVUQR.js +0 -16
  136. package/public/assets/vendor-react-DONn7uBV.js +0 -59
@@ -0,0 +1,370 @@
1
+ import { join } from "path";
2
+ import { existsSync, statSync, createReadStream, mkdirSync, rmSync } from "fs";
3
+ import { listInstanceBackups, assertNotLocked, } from "../services/backup-manager.js";
4
+ import { verifyArchive } from "../services/backup-verify.js";
5
+ import { getInstance } from "../services/instance-manager.js";
6
+ import { BACKUPS_DIR } from "../config.js";
7
+ import { validateId } from "./instances.js";
8
+ /**
9
+ * Pre-flight validation shared by backup/restore/export write routes:
10
+ * 1. `:id` is a well-formed instance ID (reject malformed with 400)
11
+ * 2. The instance actually exists (reject missing with 404)
12
+ * 3. The instance is not currently locked by another operation (409)
13
+ *
14
+ * Without (2), write routes enqueue a job and return {ok:true,job_id} even
15
+ * for nonexistent instances; the "Instance not found" error only surfaces
16
+ * later inside the background worker, giving CLI/frontend a misleading
17
+ * success response. Matches the synchronous 404 behavior of the auto-backup
18
+ * config routes (GET/PUT /api/instances/:id/auto-backup).
19
+ *
20
+ * Returns `null` if the reply has already been sent; otherwise returns the
21
+ * validated instance ID so the caller can proceed.
22
+ */
23
+ function preflightInstanceWrite(req, reply) {
24
+ const id = req.params.id;
25
+ const idErr = validateId(id);
26
+ if (idErr) {
27
+ reply.status(400).send({ detail: idErr });
28
+ return null;
29
+ }
30
+ if (!getInstance(id)) {
31
+ reply.status(404).send({ detail: "Instance not found" });
32
+ return null;
33
+ }
34
+ try {
35
+ assertNotLocked(id);
36
+ }
37
+ catch (e) {
38
+ reply.status(e.statusCode || 409).send({ detail: e.message });
39
+ return null;
40
+ }
41
+ return id;
42
+ }
43
+ export default async function backupRoutes(app) {
44
+ // POST /api/instances/:id/backup — Create backup
45
+ app.post("/api/instances/:id/backup", async (req, reply) => {
46
+ const id = preflightInstanceWrite(req, reply);
47
+ if (!id)
48
+ return reply;
49
+ try {
50
+ const { enqueueJob, updateJobProgress, backupInstance } = await import("../services/backup-manager.js");
51
+ const includeSessions = req.body?.include_sessions;
52
+ const includeWorkspace = req.body?.include_workspace;
53
+ const onlyConfig = req.body?.only_config;
54
+ const scope = req.body?.scope ?? "home";
55
+ const job = enqueueJob(id, "manual-backup", async (j) => {
56
+ updateJobProgress(j.id, onlyConfig ? "Creating config backup..." : "Creating backup...");
57
+ const result = await backupInstance(id, {
58
+ includeSessions,
59
+ includeWorkspace,
60
+ onlyConfig,
61
+ scope,
62
+ });
63
+ return { filename: result.filename, size: result.size };
64
+ });
65
+ return { ok: true, job_id: job.id, status: job.status };
66
+ }
67
+ catch (e) {
68
+ return reply.status(e.statusCode || 500).send({ detail: e.message });
69
+ }
70
+ });
71
+ // GET /api/instances/:id/backups — List backups
72
+ app.get("/api/instances/:id/backups", async (req) => {
73
+ return listInstanceBackups(req.params.id);
74
+ });
75
+ // GET /api/instances/:id/backup/download/:file — Download backup file
76
+ app.get("/api/instances/:id/backup/download/:file", async (req, reply) => {
77
+ const filename = req.params.file;
78
+ // Sanitize filename to prevent path traversal
79
+ if (filename.includes("/") || filename.includes("\\") || filename.includes("..")) {
80
+ return reply.status(400).send({ detail: "Invalid filename" });
81
+ }
82
+ const filePath = join(BACKUPS_DIR, req.params.id, filename);
83
+ if (!existsSync(filePath)) {
84
+ return reply.status(404).send({ detail: "Backup file not found" });
85
+ }
86
+ const stat = statSync(filePath);
87
+ const downloadName = `${req.params.id}-${filename}`;
88
+ reply.header("Content-Type", "application/gzip");
89
+ reply.header("Content-Length", stat.size);
90
+ reply.header("Content-Disposition", `attachment; filename="${downloadName}"`);
91
+ return reply.send(createReadStream(filePath));
92
+ });
93
+ // DELETE /api/instances/:id/backups/:file — Delete a backup
94
+ app.delete("/api/instances/:id/backups/:file", async (req, reply) => {
95
+ const filename = req.params.file;
96
+ if (filename.includes("/") || filename.includes("\\") || filename.includes("..")) {
97
+ return reply.status(400).send({ detail: "Invalid filename" });
98
+ }
99
+ const filePath = join(BACKUPS_DIR, req.params.id, filename);
100
+ if (!existsSync(filePath)) {
101
+ return reply.status(404).send({ detail: "Backup file not found" });
102
+ }
103
+ rmSync(filePath, { force: true });
104
+ return { ok: true };
105
+ });
106
+ // POST /api/instances/:id/restore/:file — Restore from server-side backup
107
+ app.post("/api/instances/:id/restore/:file", async (req, reply) => {
108
+ const id = preflightInstanceWrite(req, reply);
109
+ if (!id)
110
+ return reply;
111
+ const filename = req.params.file;
112
+ if (filename.includes("/") || filename.includes("\\") || filename.includes("..")) {
113
+ return reply.status(400).send({ detail: "Invalid filename" });
114
+ }
115
+ const filePath = join(BACKUPS_DIR, id, filename);
116
+ if (!existsSync(filePath)) {
117
+ return reply.status(404).send({ detail: "Backup file not found" });
118
+ }
119
+ try {
120
+ const { enqueueJob, updateJobProgress, restoreInstance } = await import("../services/backup-manager.js");
121
+ const job = enqueueJob(id, "restore", async (j) => {
122
+ updateJobProgress(j.id, "Restoring...");
123
+ return await restoreInstance(id, filePath);
124
+ });
125
+ return { ok: true, job_id: job.id, status: job.status };
126
+ }
127
+ catch (e) {
128
+ return reply.status(e.statusCode || 500).send({ detail: e.message });
129
+ }
130
+ });
131
+ // POST /api/instances/create-from-backup — Create new instance from backup
132
+ app.post("/api/instances/create-from-backup", async (req, reply) => {
133
+ const { source_id, backup_file, new_id, new_name, new_description } = req.body;
134
+ if (!source_id || !backup_file || !new_id || !new_name) {
135
+ return reply.status(400).send({ detail: "Missing required fields: source_id, backup_file, new_id, new_name" });
136
+ }
137
+ const srcErr = validateId(source_id);
138
+ if (srcErr)
139
+ return reply.status(400).send({ detail: `Invalid source_id: ${srcErr}` });
140
+ const idErr = validateId(new_id);
141
+ if (idErr)
142
+ return reply.status(400).send({ detail: idErr });
143
+ if (new_name.length === 0 || new_name.length > 256) {
144
+ return reply.status(400).send({ detail: "Name must be 1-256 characters" });
145
+ }
146
+ // Sanitize backup_file
147
+ if (backup_file.includes("/") || backup_file.includes("\\") || backup_file.includes("..")) {
148
+ return reply.status(400).send({ detail: "Invalid backup_file" });
149
+ }
150
+ const filePath = join(BACKUPS_DIR, source_id, backup_file);
151
+ if (!existsSync(filePath)) {
152
+ return reply.status(404).send({ detail: "Backup file not found" });
153
+ }
154
+ try {
155
+ const { enqueueJobAndWait, updateJobProgress, createFromBackup } = await import("../services/backup-manager.js");
156
+ const job = await enqueueJobAndWait(new_id, "create-from-backup", async (j) => {
157
+ updateJobProgress(j.id, "Creating instance from backup...");
158
+ return await createFromBackup(filePath, { newId: new_id, newName: new_name, newDescription: new_description });
159
+ });
160
+ return job.result || { ok: false, detail: "Job completed without result" };
161
+ }
162
+ catch (e) {
163
+ return reply.status(e.statusCode || 500).send({ detail: e.message });
164
+ }
165
+ });
166
+ // POST /api/instances/:id/backup/verify/:file — Verify a backup
167
+ app.post("/api/instances/:id/backup/verify/:file", async (req, reply) => {
168
+ const filename = req.params.file;
169
+ if (filename.includes("/") || filename.includes("\\") || filename.includes("..")) {
170
+ return reply.status(400).send({ detail: "Invalid filename" });
171
+ }
172
+ const filePath = join(BACKUPS_DIR, req.params.id, filename);
173
+ if (!existsSync(filePath)) {
174
+ return reply.status(404).send({ detail: "Backup file not found" });
175
+ }
176
+ const result = await verifyArchive(filePath);
177
+ return result;
178
+ });
179
+ // GET /api/backups/orphaned — List orphaned backups (instances deleted but backups remain)
180
+ app.get("/api/backups/orphaned", async () => {
181
+ const { listAllBackupInstanceIds } = await import("../services/backup-manager.js");
182
+ const { getInstance } = await import("../services/instance-manager.js");
183
+ const allBackupIds = listAllBackupInstanceIds();
184
+ const orphaned = [];
185
+ for (const id of allBackupIds) {
186
+ if (!getInstance(id)) {
187
+ orphaned.push({ instance_id: id, backups: listInstanceBackups(id) });
188
+ }
189
+ }
190
+ return orphaned;
191
+ });
192
+ // DELETE /api/backups/orphaned/:id/:file — Delete an orphaned backup
193
+ app.delete("/api/backups/orphaned/:id/:file", async (req, reply) => {
194
+ const { id, file } = req.params;
195
+ if (id.includes("/") || id.includes("\\") || id.includes("..")) {
196
+ return reply.status(400).send({ detail: "Invalid id" });
197
+ }
198
+ if (file.includes("/") || file.includes("\\") || file.includes("..")) {
199
+ return reply.status(400).send({ detail: "Invalid filename" });
200
+ }
201
+ const filePath = join(BACKUPS_DIR, id, file);
202
+ if (!existsSync(filePath)) {
203
+ return reply.status(404).send({ detail: "File not found" });
204
+ }
205
+ rmSync(filePath, { force: true });
206
+ return { ok: true };
207
+ });
208
+ // ── Auto-backup configuration ──
209
+ // GET /api/instances/:id/auto-backup
210
+ app.get("/api/instances/:id/auto-backup", async (req) => {
211
+ const { getAutoBackupConfig } = await import("../services/backup-manager.js");
212
+ return (await getAutoBackupConfig(req.params.id)) || { enabled: false };
213
+ });
214
+ // PUT /api/instances/:id/auto-backup
215
+ app.put("/api/instances/:id/auto-backup", async (req, reply) => {
216
+ const { enabled, interval_hours = 24, keep_count = 7 } = req.body || {};
217
+ const { updateInstanceMeta, getInstance } = await import("../services/instance-manager.js");
218
+ const { scheduleAutoBackup, cancelAutoBackup } = await import("../services/backup-manager.js");
219
+ const meta = getInstance(req.params.id);
220
+ if (!meta)
221
+ return reply.status(404).send({ detail: "Instance not found" });
222
+ const current = meta.auto_backup || {};
223
+ const config = {
224
+ ...current,
225
+ enabled: !!enabled,
226
+ interval_hours,
227
+ keep_count,
228
+ };
229
+ updateInstanceMeta(req.params.id, { auto_backup: config });
230
+ if (enabled) {
231
+ scheduleAutoBackup(req.params.id, config);
232
+ }
233
+ else {
234
+ cancelAutoBackup(req.params.id);
235
+ }
236
+ return { ok: true, config };
237
+ });
238
+ // ── Export ──
239
+ // POST /api/instances/:id/export — Create export package
240
+ app.post("/api/instances/:id/export", async (req, reply) => {
241
+ const id = preflightInstanceWrite(req, reply);
242
+ if (!id)
243
+ return reply;
244
+ try {
245
+ const { enqueueJob, updateJobProgress, exportInstance } = await import("../services/backup-manager.js");
246
+ const includeSessions = req.body?.include_sessions;
247
+ const job = enqueueJob(id, "export", async (j) => {
248
+ updateJobProgress(j.id, "Exporting...");
249
+ const result = await exportInstance(id, { includeSessions });
250
+ return { filename: result.filename, size: result.size, warnings: result.manifest?.warnings };
251
+ });
252
+ return { ok: true, job_id: job.id, status: job.status };
253
+ }
254
+ catch (e) {
255
+ return reply.status(e.statusCode || 500).send({ detail: e.message });
256
+ }
257
+ });
258
+ // GET /api/instances/:id/export/download/:file — Download export file
259
+ app.get("/api/instances/:id/export/download/:file", async (req, reply) => {
260
+ const filename = req.params.file;
261
+ if (filename.includes("/") || filename.includes("\\") || filename.includes("..")) {
262
+ return reply.status(400).send({ detail: "Invalid filename" });
263
+ }
264
+ const idErr = validateId(req.params.id);
265
+ if (idErr)
266
+ return reply.status(400).send({ detail: idErr });
267
+ // Export files live under TMP_DIR/exports/<instanceId>/ — isolated so
268
+ // one instance's :id cannot reach another's archive.
269
+ const { TMP_DIR } = await import("../config.js");
270
+ const filePath = join(TMP_DIR, "exports", req.params.id, filename);
271
+ if (!existsSync(filePath)) {
272
+ return reply.status(404).send({ detail: "Export file not found or expired" });
273
+ }
274
+ const stat = statSync(filePath);
275
+ const downloadName = `${req.params.id}-${filename}`;
276
+ reply.header("Content-Type", "application/gzip");
277
+ reply.header("Content-Length", stat.size);
278
+ reply.header("Content-Disposition", `attachment; filename="${downloadName}"`);
279
+ return reply.send(createReadStream(filePath));
280
+ });
281
+ // ── Import (three-step flow) ──
282
+ // POST /api/instances/import/upload — Upload file to temp storage
283
+ app.post("/api/instances/import/upload", async (req, reply) => {
284
+ try {
285
+ const data = await req.file();
286
+ if (!data) {
287
+ return reply.status(400).send({ detail: "No file uploaded" });
288
+ }
289
+ // Write multipart stream to temp file
290
+ const { TMP_DIR } = await import("../config.js");
291
+ const { randomUUID } = await import("crypto");
292
+ const { pipeline } = await import("stream/promises");
293
+ const { createWriteStream } = await import("fs");
294
+ const tmpPath = join(TMP_DIR, `upload-${randomUUID()}.tar.gz`);
295
+ if (!existsSync(TMP_DIR))
296
+ mkdirSync(TMP_DIR, { recursive: true });
297
+ await pipeline(data.file, createWriteStream(tmpPath));
298
+ // Use storeUpload to register it
299
+ const { storeUpload } = await import("../services/backup-manager.js");
300
+ const result = await storeUpload(tmpPath);
301
+ // Clean up the initial upload temp file (storeUpload copies it)
302
+ if (existsSync(tmpPath))
303
+ rmSync(tmpPath, { force: true });
304
+ return { ok: true, temp_id: result.temp_id };
305
+ }
306
+ catch (e) {
307
+ return reply.status(e.statusCode || 500).send({ detail: e.message });
308
+ }
309
+ });
310
+ // POST /api/instances/import/preview — Preview uploaded archive
311
+ app.post("/api/instances/import/preview", async (req, reply) => {
312
+ try {
313
+ if (!req.body?.temp_id) {
314
+ return reply.status(400).send({ detail: "Missing temp_id" });
315
+ }
316
+ const { previewImport } = await import("../services/backup-manager.js");
317
+ return await previewImport(req.body.temp_id);
318
+ }
319
+ catch (e) {
320
+ return reply.status(e.statusCode || 500).send({ detail: e.message });
321
+ }
322
+ });
323
+ // POST /api/instances/import — Create instance from uploaded archive
324
+ app.post("/api/instances/import", async (req, reply) => {
325
+ try {
326
+ const { temp_id, id, name, description } = req.body || {};
327
+ if (!temp_id || !id || !name) {
328
+ return reply.status(400).send({ detail: "Missing required fields: temp_id, id, name" });
329
+ }
330
+ const idErr = validateId(id);
331
+ if (idErr)
332
+ return reply.status(400).send({ detail: idErr });
333
+ if (name.length === 0 || name.length > 256) {
334
+ return reply.status(400).send({ detail: "Name must be 1-256 characters" });
335
+ }
336
+ const { enqueueJobAndWait, updateJobProgress, importInstance } = await import("../services/backup-manager.js");
337
+ const job = await enqueueJobAndWait(id, "import", async (j) => {
338
+ updateJobProgress(j.id, "Importing...");
339
+ return await importInstance(temp_id, { id, name, description });
340
+ });
341
+ return job.result || { ok: false, detail: "Job completed without result" };
342
+ }
343
+ catch (e) {
344
+ return reply.status(e.statusCode || 500).send({ detail: e.message });
345
+ }
346
+ });
347
+ // ── Job Queue ──
348
+ // GET /api/backup/queue — Get queue status
349
+ app.get("/api/backup/queue", async () => {
350
+ const { getQueueStatus } = await import("../services/backup-manager.js");
351
+ return getQueueStatus();
352
+ });
353
+ // GET /api/backup/jobs/:id — Get specific job
354
+ app.get("/api/backup/jobs/:id", async (req, reply) => {
355
+ const { getJob } = await import("../services/backup-manager.js");
356
+ const job = getJob(req.params.id);
357
+ if (!job)
358
+ return reply.status(404).send({ detail: "Job not found" });
359
+ return job;
360
+ });
361
+ // DELETE /api/backup/jobs/:id — Cancel a queued job
362
+ app.delete("/api/backup/jobs/:id", async (req, reply) => {
363
+ const { cancelJob } = await import("../services/backup-manager.js");
364
+ const cancelled = cancelJob(req.params.id);
365
+ if (!cancelled)
366
+ return reply.status(404).send({ detail: "Job not found or already running" });
367
+ return { ok: true };
368
+ });
369
+ }
370
+ //# sourceMappingURL=backup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backup.js","sourceRoot":"","sources":["../../src/routes/backup.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,3 @@
1
1
  import { FastifyInstance } from "fastify";
2
+ export declare function validateId(id: string): string | null;
2
3
  export declare function instanceRoutes(app: FastifyInstance): Promise<void>;
@@ -5,10 +5,12 @@ import { request as httpRequest } from "http";
5
5
  import { join } from "path";
6
6
  import { getServiceManagerType } from "../config.js";
7
7
  import { PROXY_IDENTITY_HEADERS } from "../constants.js";
8
+ import { assertNotLocked } from "../services/backup-manager.js";
8
9
  import * as instanceManager from "../services/instance-manager.js";
9
10
  import * as llmProxy from "../services/llm-proxy/index.js";
10
11
  import * as pluginInstaller from "../services/plugin-installer.js";
11
12
  import { TtlMap } from "../utils/ttl-cache.js";
13
+ import { writeConfigFile, writeSecretFile, ensureDirContainer } from "../utils/fs.js";
12
14
  // Hop-by-hop headers that must not be forwarded by a proxy (RFC 2616 §13.5.1)
13
15
  const HOP_BY_HOP = new Set([
14
16
  "connection", "keep-alive", "proxy-authenticate", "proxy-authorization",
@@ -68,7 +70,7 @@ async function getSvc() {
68
70
  _svcType = currentType;
69
71
  return _svc;
70
72
  }
71
- function validateId(id) {
73
+ export function validateId(id) {
72
74
  if (!/^[a-z0-9][a-z0-9-]{0,62}$/.test(id)) {
73
75
  return "ID must be lowercase alphanumeric with hyphens, 1-63 chars";
74
76
  }
@@ -138,7 +140,8 @@ export async function instanceRoutes(app) {
138
140
  return reply.status(400).send({ detail: `Source instance '${req.body.clone_from}' not found` });
139
141
  }
140
142
  try {
141
- const meta = await instanceManager.createInstance(req.body.id, req.body.name, req.body.description || "", req.body.clone_from, req.body.openclaw_home);
143
+ const meta = await instanceManager.createInstance(req.body.id, req.body.name, req.body.description || "", req.body.clone_from, req.body.openclaw_home, undefined, // appSpec — not used from HTTP route
144
+ req.body.clone_options);
142
145
  // Auto-start if default provider is configured (model ready to use)
143
146
  const { getPanelConfig } = await import("../config.js");
144
147
  const dp = getPanelConfig().default_provider;
@@ -165,7 +168,7 @@ export async function instanceRoutes(app) {
165
168
  if (!inst)
166
169
  return reply.status(404).send({ detail: "Instance not found" });
167
170
  const status = await svc.getStatus(req.params.id);
168
- return { ...inst, service: status };
171
+ return { ...inst, service: status, llmError: llmProxy.getLastProxyError(req.params.id) ?? null };
169
172
  });
170
173
  // Update
171
174
  app.put("/api/instances/:id", async (req, reply) => {
@@ -178,6 +181,12 @@ export async function instanceRoutes(app) {
178
181
  if (req.body.description !== undefined && req.body.description.length > 2048) {
179
182
  return reply.status(400).send({ detail: "Description must be at most 2048 characters" });
180
183
  }
184
+ try {
185
+ assertNotLocked(req.params.id);
186
+ }
187
+ catch (e) {
188
+ return reply.status(e.statusCode || 409).send({ detail: e.message });
189
+ }
181
190
  const inst = instanceManager.updateInstance(req.params.id, req.body.name, req.body.description);
182
191
  if (!inst)
183
192
  return reply.status(404).send({ detail: "Instance not found" });
@@ -188,6 +197,12 @@ export async function instanceRoutes(app) {
188
197
  const idErr = validateId(req.params.id);
189
198
  if (idErr)
190
199
  return reply.status(400).send({ detail: idErr });
200
+ try {
201
+ assertNotLocked(req.params.id);
202
+ }
203
+ catch (e) {
204
+ return reply.status(e.statusCode || 409).send({ detail: e.message });
205
+ }
191
206
  const svc = await getSvc();
192
207
  let stopFailed = false;
193
208
  try {
@@ -212,7 +227,8 @@ export async function instanceRoutes(app) {
212
227
  catch { /* ignore */ }
213
228
  statusCache.delete(req.params.id);
214
229
  llmProxy.cleanupInstance(req.params.id);
215
- const result = instanceManager.deleteInstance(req.params.id);
230
+ const purgeBackups = req.query.purge_backups === "true";
231
+ const result = await instanceManager.deleteInstance(req.params.id, purgeBackups);
216
232
  if (!result.ok && result.warnings?.some(w => w.includes("not found"))) {
217
233
  return reply.status(404).send({ detail: "Instance not found" });
218
234
  }
@@ -247,6 +263,7 @@ export async function instanceRoutes(app) {
247
263
  return reply.status(400).send({ detail: "Config too large (max 512KB)" });
248
264
  }
249
265
  try {
266
+ assertNotLocked(req.params.id);
250
267
  const saved = await llmProxy.saveInstanceConfig(req.params.id, body);
251
268
  return { ok: true, config: saved };
252
269
  }
@@ -272,6 +289,12 @@ export async function instanceRoutes(app) {
272
289
  const idErr = validateId(req.params.id);
273
290
  if (idErr)
274
291
  return reply.status(400).send({ detail: idErr });
292
+ try {
293
+ assertNotLocked(req.params.id);
294
+ }
295
+ catch (e) {
296
+ return reply.status(e.statusCode || 409).send({ detail: e.message });
297
+ }
275
298
  const svc = await getSvc();
276
299
  if (!instanceManager.getInstance(req.params.id)) {
277
300
  return reply.status(404).send({ detail: "Instance not found" });
@@ -286,6 +309,12 @@ export async function instanceRoutes(app) {
286
309
  const idErr = validateId(req.params.id);
287
310
  if (idErr)
288
311
  return reply.status(400).send({ detail: idErr });
312
+ try {
313
+ assertNotLocked(req.params.id);
314
+ }
315
+ catch (e) {
316
+ return reply.status(e.statusCode || 409).send({ detail: e.message });
317
+ }
289
318
  const svc = await getSvc();
290
319
  if (!instanceManager.getInstance(req.params.id)) {
291
320
  return reply.status(404).send({ detail: "Instance not found" });
@@ -300,6 +329,12 @@ export async function instanceRoutes(app) {
300
329
  const idErr = validateId(req.params.id);
301
330
  if (idErr)
302
331
  return reply.status(400).send({ detail: idErr });
332
+ try {
333
+ assertNotLocked(req.params.id);
334
+ }
335
+ catch (e) {
336
+ return reply.status(e.statusCode || 409).send({ detail: e.message });
337
+ }
303
338
  const svc = await getSvc();
304
339
  if (!instanceManager.getInstance(req.params.id)) {
305
340
  return reply.status(404).send({ detail: "Instance not found" });
@@ -434,7 +469,7 @@ export async function instanceRoutes(app) {
434
469
  const openclawHome = instanceManager.getOpenclawHome(req.params.id);
435
470
  const workspaceDir = join(openclawHome, ".openclaw", "workspace");
436
471
  const mcporterCfgPath = join(workspaceDir, "config", "mcporter.json");
437
- const { readFileSync, writeFileSync, mkdirSync } = await import("fs");
472
+ const { readFileSync } = await import("fs");
438
473
  let cfg = { mcpServers: {}, imports: [] };
439
474
  try {
440
475
  cfg = JSON.parse(readFileSync(mcporterCfgPath, "utf8"));
@@ -450,8 +485,8 @@ export async function instanceRoutes(app) {
450
485
  cfg.mcpServers[k] = v;
451
486
  }
452
487
  try {
453
- mkdirSync(join(workspaceDir, "config"), { recursive: true });
454
- writeFileSync(mcporterCfgPath, JSON.stringify(cfg, null, 2), "utf8");
488
+ ensureDirContainer(join(workspaceDir, "config"));
489
+ writeConfigFile(mcporterCfgPath, JSON.stringify(cfg, null, 2));
455
490
  }
456
491
  catch (err) {
457
492
  return reply.status(500).send({ detail: `Write failed: ${err.message}` });
@@ -473,7 +508,7 @@ export async function instanceRoutes(app) {
473
508
  const openclawHome = instanceManager.getOpenclawHome(req.params.id);
474
509
  const workspaceDir = join(openclawHome, ".openclaw", "workspace");
475
510
  const mcporterCfgPath = join(workspaceDir, "config", "mcporter.json");
476
- const { readFileSync, writeFileSync } = await import("fs");
511
+ const { readFileSync } = await import("fs");
477
512
  let cfg = { mcpServers: {}, imports: [] };
478
513
  try {
479
514
  cfg = JSON.parse(readFileSync(mcporterCfgPath, "utf8"));
@@ -484,7 +519,7 @@ export async function instanceRoutes(app) {
484
519
  }
485
520
  delete cfg.mcpServers[serverName];
486
521
  try {
487
- writeFileSync(mcporterCfgPath, JSON.stringify(cfg, null, 2), "utf8");
522
+ writeConfigFile(mcporterCfgPath, JSON.stringify(cfg, null, 2));
488
523
  }
489
524
  catch (err) {
490
525
  return reply.status(500).send({ detail: `Write failed: ${err.message}` });
@@ -525,6 +560,12 @@ export async function instanceRoutes(app) {
525
560
  if (!instanceManager.getInstance(req.params.id)) {
526
561
  return reply.status(404).send({ detail: "Instance not found" });
527
562
  }
563
+ try {
564
+ assertNotLocked(req.params.id);
565
+ }
566
+ catch (e) {
567
+ return reply.status(e.statusCode || 409).send({ detail: e.message });
568
+ }
528
569
  const { channelId } = req.body;
529
570
  if (!channelId || typeof channelId !== "string") {
530
571
  return reply.status(400).send({ detail: "channelId is required" });
@@ -959,7 +1000,7 @@ export async function instanceRoutes(app) {
959
1000
  app.post("/api/admin/migrate-secrets", async (_req, reply) => {
960
1001
  const { getAesKey, getJwtSecret } = await import("../config.js");
961
1002
  const { scryptSync, createDecipheriv, createCipheriv, randomBytes } = await import("crypto");
962
- const { readFileSync, writeFileSync, existsSync } = await import("fs");
1003
+ const { readFileSync, existsSync: fsExistsSync } = await import("fs");
963
1004
  const instances = instanceManager.listInstances();
964
1005
  const results = [];
965
1006
  const currentKey = getAesKey();
@@ -967,7 +1008,7 @@ export async function instanceRoutes(app) {
967
1008
  for (const inst of instances) {
968
1009
  const envFiles = instanceManager.getRuntimeEnvFiles(inst.id);
969
1010
  const providerEnvFile = envFiles[0]?.replace(/model\.env$/, "provider.env");
970
- if (!providerEnvFile || !existsSync(providerEnvFile)) {
1011
+ if (!providerEnvFile || !fsExistsSync(providerEnvFile)) {
971
1012
  results.push({ id: inst.id, status: "skipped", error: "no provider.env" });
972
1013
  continue;
973
1014
  }
@@ -1019,7 +1060,7 @@ export async function instanceRoutes(app) {
1019
1060
  const newEncrypted = "enc:" + Buffer.concat([newIv, newTag, enc]).toString("base64");
1020
1061
  // Write back
1021
1062
  const newContent = envContent.replace(/UPSTREAM_API_KEY=.+/, `UPSTREAM_API_KEY=${newEncrypted}`);
1022
- writeFileSync(providerEnvFile, newContent, { mode: 0o600 });
1063
+ writeSecretFile(providerEnvFile, newContent);
1023
1064
  results.push({ id: inst.id, status: "migrated" });
1024
1065
  }
1025
1066
  }
@@ -1073,8 +1114,13 @@ export async function instanceRoutes(app) {
1073
1114
  const gwHost = await instanceManager.getGatewayHost(id);
1074
1115
  const suffix = request.params["*"] || "";
1075
1116
  const qs = request.url.includes("?") ? request.url.slice(request.url.indexOf("?")) : "";
1076
- // Raw HTTP proxy — stream the request body and preserve headers
1077
- const targetUrl = `http://${gwHost}:${port}/${suffix}${qs}`;
1117
+ // Raw HTTP proxy — stream the request body and preserve headers.
1118
+ // Bracket IPv6 literals (`::1`) because `http://::1:18789/` is not a
1119
+ // valid URL; see instance-manager.urlHost. Nomad 1.6.5 on Linux with
1120
+ // `network_interface = "lo"` can allocate the task port to `::1`
1121
+ // whenever the kernel enumerates the lo interface's v6 address first.
1122
+ const urlGwHost = instanceManager.urlHost(gwHost);
1123
+ const targetUrl = `http://${urlGwHost}:${port}/${suffix}${qs}`;
1078
1124
  try {
1079
1125
  // Build upstream headers: keep everything except hop-by-hop, rewrite host/origin
1080
1126
  const fwdHeaders = {};
@@ -1092,7 +1138,7 @@ export async function instanceRoutes(app) {
1092
1138
  continue;
1093
1139
  fwdHeaders[k] = Array.isArray(v) ? v.join(", ") : v;
1094
1140
  }
1095
- fwdHeaders["host"] = `${gwHost}:${port}`;
1141
+ fwdHeaders["host"] = `${urlGwHost}:${port}`;
1096
1142
  // Request uncompressed responses — avoids having to decompress before HTML injection,
1097
1143
  // and lets us stream non-HTML bodies without content-encoding mismatch.
1098
1144
  fwdHeaders["accept-encoding"] = "identity";