jishushell 0.4.2 → 0.4.10

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 (83) hide show
  1. package/Dockerfile.openclaw-slim +58 -0
  2. package/INSTALL-NOTICE +47 -0
  3. package/dist/auth.js +3 -3
  4. package/dist/auth.js.map +1 -1
  5. package/dist/cli.js +517 -1
  6. package/dist/cli.js.map +1 -1
  7. package/dist/config.d.ts +21 -4
  8. package/dist/config.js +88 -54
  9. package/dist/config.js.map +1 -1
  10. package/dist/control.js +5 -5
  11. package/dist/control.js.map +1 -1
  12. package/dist/doctor.js +47 -14
  13. package/dist/doctor.js.map +1 -1
  14. package/dist/install.d.ts +1 -1
  15. package/dist/install.js +15 -29
  16. package/dist/install.js.map +1 -1
  17. package/dist/routes/backup.d.ts +2 -0
  18. package/dist/routes/backup.js +370 -0
  19. package/dist/routes/backup.js.map +1 -0
  20. package/dist/routes/instances.d.ts +1 -0
  21. package/dist/routes/instances.js +51 -11
  22. package/dist/routes/instances.js.map +1 -1
  23. package/dist/routes/setup.js +3 -5
  24. package/dist/routes/setup.js.map +1 -1
  25. package/dist/server.js +29 -1
  26. package/dist/server.js.map +1 -1
  27. package/dist/services/backup-manager.d.ts +253 -0
  28. package/dist/services/backup-manager.js +2014 -0
  29. package/dist/services/backup-manager.js.map +1 -0
  30. package/dist/services/backup-verify.d.ts +26 -0
  31. package/dist/services/backup-verify.js +240 -0
  32. package/dist/services/backup-verify.js.map +1 -0
  33. package/dist/services/instance-manager.d.ts +24 -4
  34. package/dist/services/instance-manager.js +218 -49
  35. package/dist/services/instance-manager.js.map +1 -1
  36. package/dist/services/nomad-manager.js +72 -131
  37. package/dist/services/nomad-manager.js.map +1 -1
  38. package/dist/services/process-manager.js +4 -3
  39. package/dist/services/process-manager.js.map +1 -1
  40. package/dist/services/setup-manager.d.ts +4 -2
  41. package/dist/services/setup-manager.js +268 -129
  42. package/dist/services/setup-manager.js.map +1 -1
  43. package/dist/services/telemetry/activation.js +10 -7
  44. package/dist/services/telemetry/activation.js.map +1 -1
  45. package/dist/services/telemetry/client.js +7 -18
  46. package/dist/services/telemetry/client.js.map +1 -1
  47. package/dist/services/telemetry/heartbeat.js +12 -6
  48. package/dist/services/telemetry/heartbeat.js.map +1 -1
  49. package/dist/utils/fs.d.ts +85 -0
  50. package/dist/utils/fs.js +111 -0
  51. package/dist/utils/fs.js.map +1 -0
  52. package/dist/utils/safe-json.d.ts +2 -0
  53. package/dist/utils/safe-json.js +22 -16
  54. package/dist/utils/safe-json.js.map +1 -1
  55. package/install/jishu-install-china.sh +3092 -0
  56. package/install/jishu-install.sh +310 -108
  57. package/install/jishu-uninstall.sh +276 -391
  58. package/install/post-install.sh +23 -0
  59. package/openclaw-entry.sh +15 -0
  60. package/package.json +7 -4
  61. package/public/assets/Dashboard-DhsrzJ4F.js +1 -0
  62. package/public/assets/{InitPassword-CkehIkJG.js → InitPassword-BjubiVdd.js} +1 -1
  63. package/public/assets/InstanceDetail-DMcywsof.js +17 -0
  64. package/public/assets/{Login-RkjzTNWg.js → Login-CUoEZOWR.js} +1 -1
  65. package/public/assets/NewInstance-Bk0G4EiJ.js +1 -0
  66. package/public/assets/Settings-D5tHL_h5.js +1 -0
  67. package/public/assets/Setup-4t6E3Rut.js +1 -0
  68. package/public/assets/index-BJ47MWpF.css +1 -0
  69. package/public/assets/index-DbX85irc.js +16 -0
  70. package/public/assets/logo-black-theme-DywLAtFy.png +0 -0
  71. package/public/assets/logo-white-theme-DXffFAWw.png +0 -0
  72. package/public/assets/{usePolling-CqQ8hrNc.js → usePolling-CK0DfI4h.js} +1 -1
  73. package/public/assets/{vendor-i18n-Bvxxh8Di.js → vendor-i18n-CfW0RvgE.js} +1 -1
  74. package/public/assets/vendor-react-B1-3Yrt-.js +59 -0
  75. package/public/index.html +4 -4
  76. package/public/assets/Dashboard-CAOQDYDR.js +0 -1
  77. package/public/assets/InstanceDetail-CzW2S95J.js +0 -14
  78. package/public/assets/NewInstance-DdbErdjA.js +0 -1
  79. package/public/assets/Settings-BUD7zwv9.js +0 -1
  80. package/public/assets/Setup-RRTIERGG.js +0 -1
  81. package/public/assets/index-77Ug7feY.css +0 -1
  82. package/public/assets/index-DfRnVUQR.js +0 -16
  83. 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,7 @@ 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, req.body.clone_options);
142
144
  // Auto-start if default provider is configured (model ready to use)
143
145
  const { getPanelConfig } = await import("../config.js");
144
146
  const dp = getPanelConfig().default_provider;
@@ -178,6 +180,12 @@ export async function instanceRoutes(app) {
178
180
  if (req.body.description !== undefined && req.body.description.length > 2048) {
179
181
  return reply.status(400).send({ detail: "Description must be at most 2048 characters" });
180
182
  }
183
+ try {
184
+ assertNotLocked(req.params.id);
185
+ }
186
+ catch (e) {
187
+ return reply.status(e.statusCode || 409).send({ detail: e.message });
188
+ }
181
189
  const inst = instanceManager.updateInstance(req.params.id, req.body.name, req.body.description);
182
190
  if (!inst)
183
191
  return reply.status(404).send({ detail: "Instance not found" });
@@ -188,6 +196,12 @@ export async function instanceRoutes(app) {
188
196
  const idErr = validateId(req.params.id);
189
197
  if (idErr)
190
198
  return reply.status(400).send({ detail: idErr });
199
+ try {
200
+ assertNotLocked(req.params.id);
201
+ }
202
+ catch (e) {
203
+ return reply.status(e.statusCode || 409).send({ detail: e.message });
204
+ }
191
205
  const svc = await getSvc();
192
206
  let stopFailed = false;
193
207
  try {
@@ -212,7 +226,8 @@ export async function instanceRoutes(app) {
212
226
  catch { /* ignore */ }
213
227
  statusCache.delete(req.params.id);
214
228
  llmProxy.cleanupInstance(req.params.id);
215
- const result = instanceManager.deleteInstance(req.params.id);
229
+ const purgeBackups = req.query.purge_backups === "true";
230
+ const result = instanceManager.deleteInstance(req.params.id, purgeBackups);
216
231
  if (!result.ok && result.warnings?.some(w => w.includes("not found"))) {
217
232
  return reply.status(404).send({ detail: "Instance not found" });
218
233
  }
@@ -247,6 +262,7 @@ export async function instanceRoutes(app) {
247
262
  return reply.status(400).send({ detail: "Config too large (max 512KB)" });
248
263
  }
249
264
  try {
265
+ assertNotLocked(req.params.id);
250
266
  const saved = await llmProxy.saveInstanceConfig(req.params.id, body);
251
267
  return { ok: true, config: saved };
252
268
  }
@@ -272,6 +288,12 @@ export async function instanceRoutes(app) {
272
288
  const idErr = validateId(req.params.id);
273
289
  if (idErr)
274
290
  return reply.status(400).send({ detail: idErr });
291
+ try {
292
+ assertNotLocked(req.params.id);
293
+ }
294
+ catch (e) {
295
+ return reply.status(e.statusCode || 409).send({ detail: e.message });
296
+ }
275
297
  const svc = await getSvc();
276
298
  if (!instanceManager.getInstance(req.params.id)) {
277
299
  return reply.status(404).send({ detail: "Instance not found" });
@@ -286,6 +308,12 @@ export async function instanceRoutes(app) {
286
308
  const idErr = validateId(req.params.id);
287
309
  if (idErr)
288
310
  return reply.status(400).send({ detail: idErr });
311
+ try {
312
+ assertNotLocked(req.params.id);
313
+ }
314
+ catch (e) {
315
+ return reply.status(e.statusCode || 409).send({ detail: e.message });
316
+ }
289
317
  const svc = await getSvc();
290
318
  if (!instanceManager.getInstance(req.params.id)) {
291
319
  return reply.status(404).send({ detail: "Instance not found" });
@@ -300,6 +328,12 @@ export async function instanceRoutes(app) {
300
328
  const idErr = validateId(req.params.id);
301
329
  if (idErr)
302
330
  return reply.status(400).send({ detail: idErr });
331
+ try {
332
+ assertNotLocked(req.params.id);
333
+ }
334
+ catch (e) {
335
+ return reply.status(e.statusCode || 409).send({ detail: e.message });
336
+ }
303
337
  const svc = await getSvc();
304
338
  if (!instanceManager.getInstance(req.params.id)) {
305
339
  return reply.status(404).send({ detail: "Instance not found" });
@@ -434,7 +468,7 @@ export async function instanceRoutes(app) {
434
468
  const openclawHome = instanceManager.getOpenclawHome(req.params.id);
435
469
  const workspaceDir = join(openclawHome, ".openclaw", "workspace");
436
470
  const mcporterCfgPath = join(workspaceDir, "config", "mcporter.json");
437
- const { readFileSync, writeFileSync, mkdirSync } = await import("fs");
471
+ const { readFileSync } = await import("fs");
438
472
  let cfg = { mcpServers: {}, imports: [] };
439
473
  try {
440
474
  cfg = JSON.parse(readFileSync(mcporterCfgPath, "utf8"));
@@ -450,8 +484,8 @@ export async function instanceRoutes(app) {
450
484
  cfg.mcpServers[k] = v;
451
485
  }
452
486
  try {
453
- mkdirSync(join(workspaceDir, "config"), { recursive: true });
454
- writeFileSync(mcporterCfgPath, JSON.stringify(cfg, null, 2), "utf8");
487
+ ensureDirContainer(join(workspaceDir, "config"));
488
+ writeConfigFile(mcporterCfgPath, JSON.stringify(cfg, null, 2));
455
489
  }
456
490
  catch (err) {
457
491
  return reply.status(500).send({ detail: `Write failed: ${err.message}` });
@@ -473,7 +507,7 @@ export async function instanceRoutes(app) {
473
507
  const openclawHome = instanceManager.getOpenclawHome(req.params.id);
474
508
  const workspaceDir = join(openclawHome, ".openclaw", "workspace");
475
509
  const mcporterCfgPath = join(workspaceDir, "config", "mcporter.json");
476
- const { readFileSync, writeFileSync } = await import("fs");
510
+ const { readFileSync } = await import("fs");
477
511
  let cfg = { mcpServers: {}, imports: [] };
478
512
  try {
479
513
  cfg = JSON.parse(readFileSync(mcporterCfgPath, "utf8"));
@@ -484,7 +518,7 @@ export async function instanceRoutes(app) {
484
518
  }
485
519
  delete cfg.mcpServers[serverName];
486
520
  try {
487
- writeFileSync(mcporterCfgPath, JSON.stringify(cfg, null, 2), "utf8");
521
+ writeConfigFile(mcporterCfgPath, JSON.stringify(cfg, null, 2));
488
522
  }
489
523
  catch (err) {
490
524
  return reply.status(500).send({ detail: `Write failed: ${err.message}` });
@@ -525,6 +559,12 @@ export async function instanceRoutes(app) {
525
559
  if (!instanceManager.getInstance(req.params.id)) {
526
560
  return reply.status(404).send({ detail: "Instance not found" });
527
561
  }
562
+ try {
563
+ assertNotLocked(req.params.id);
564
+ }
565
+ catch (e) {
566
+ return reply.status(e.statusCode || 409).send({ detail: e.message });
567
+ }
528
568
  const { channelId } = req.body;
529
569
  if (!channelId || typeof channelId !== "string") {
530
570
  return reply.status(400).send({ detail: "channelId is required" });
@@ -959,7 +999,7 @@ export async function instanceRoutes(app) {
959
999
  app.post("/api/admin/migrate-secrets", async (_req, reply) => {
960
1000
  const { getAesKey, getJwtSecret } = await import("../config.js");
961
1001
  const { scryptSync, createDecipheriv, createCipheriv, randomBytes } = await import("crypto");
962
- const { readFileSync, writeFileSync, existsSync } = await import("fs");
1002
+ const { readFileSync, existsSync: fsExistsSync } = await import("fs");
963
1003
  const instances = instanceManager.listInstances();
964
1004
  const results = [];
965
1005
  const currentKey = getAesKey();
@@ -967,7 +1007,7 @@ export async function instanceRoutes(app) {
967
1007
  for (const inst of instances) {
968
1008
  const envFiles = instanceManager.getRuntimeEnvFiles(inst.id);
969
1009
  const providerEnvFile = envFiles[0]?.replace(/model\.env$/, "provider.env");
970
- if (!providerEnvFile || !existsSync(providerEnvFile)) {
1010
+ if (!providerEnvFile || !fsExistsSync(providerEnvFile)) {
971
1011
  results.push({ id: inst.id, status: "skipped", error: "no provider.env" });
972
1012
  continue;
973
1013
  }
@@ -1019,7 +1059,7 @@ export async function instanceRoutes(app) {
1019
1059
  const newEncrypted = "enc:" + Buffer.concat([newIv, newTag, enc]).toString("base64");
1020
1060
  // Write back
1021
1061
  const newContent = envContent.replace(/UPSTREAM_API_KEY=.+/, `UPSTREAM_API_KEY=${newEncrypted}`);
1022
- writeFileSync(providerEnvFile, newContent, { mode: 0o600 });
1062
+ writeSecretFile(providerEnvFile, newContent);
1023
1063
  results.push({ id: inst.id, status: "migrated" });
1024
1064
  }
1025
1065
  }