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.
- package/Dockerfile.openclaw-slim +58 -0
- package/INSTALL-NOTICE +47 -0
- package/dist/auth.js +3 -3
- package/dist/auth.js.map +1 -1
- package/dist/cli.js +517 -1
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +21 -4
- package/dist/config.js +88 -54
- package/dist/config.js.map +1 -1
- package/dist/control.js +5 -5
- package/dist/control.js.map +1 -1
- package/dist/doctor.js +47 -14
- package/dist/doctor.js.map +1 -1
- package/dist/install.d.ts +1 -1
- package/dist/install.js +15 -29
- package/dist/install.js.map +1 -1
- package/dist/routes/backup.d.ts +2 -0
- package/dist/routes/backup.js +370 -0
- package/dist/routes/backup.js.map +1 -0
- package/dist/routes/instances.d.ts +1 -0
- package/dist/routes/instances.js +51 -11
- package/dist/routes/instances.js.map +1 -1
- package/dist/routes/setup.js +3 -5
- package/dist/routes/setup.js.map +1 -1
- package/dist/server.js +29 -1
- package/dist/server.js.map +1 -1
- package/dist/services/backup-manager.d.ts +253 -0
- package/dist/services/backup-manager.js +2014 -0
- package/dist/services/backup-manager.js.map +1 -0
- package/dist/services/backup-verify.d.ts +26 -0
- package/dist/services/backup-verify.js +240 -0
- package/dist/services/backup-verify.js.map +1 -0
- package/dist/services/instance-manager.d.ts +24 -4
- package/dist/services/instance-manager.js +218 -49
- package/dist/services/instance-manager.js.map +1 -1
- package/dist/services/nomad-manager.js +72 -131
- package/dist/services/nomad-manager.js.map +1 -1
- package/dist/services/process-manager.js +4 -3
- package/dist/services/process-manager.js.map +1 -1
- package/dist/services/setup-manager.d.ts +4 -2
- package/dist/services/setup-manager.js +268 -129
- package/dist/services/setup-manager.js.map +1 -1
- package/dist/services/telemetry/activation.js +10 -7
- package/dist/services/telemetry/activation.js.map +1 -1
- package/dist/services/telemetry/client.js +7 -18
- package/dist/services/telemetry/client.js.map +1 -1
- package/dist/services/telemetry/heartbeat.js +12 -6
- package/dist/services/telemetry/heartbeat.js.map +1 -1
- package/dist/utils/fs.d.ts +85 -0
- package/dist/utils/fs.js +111 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/safe-json.d.ts +2 -0
- package/dist/utils/safe-json.js +22 -16
- package/dist/utils/safe-json.js.map +1 -1
- package/install/jishu-install-china.sh +3092 -0
- package/install/jishu-install.sh +310 -108
- package/install/jishu-uninstall.sh +276 -391
- package/install/post-install.sh +23 -0
- package/openclaw-entry.sh +15 -0
- package/package.json +7 -4
- package/public/assets/Dashboard-DhsrzJ4F.js +1 -0
- package/public/assets/{InitPassword-CkehIkJG.js → InitPassword-BjubiVdd.js} +1 -1
- package/public/assets/InstanceDetail-DMcywsof.js +17 -0
- package/public/assets/{Login-RkjzTNWg.js → Login-CUoEZOWR.js} +1 -1
- package/public/assets/NewInstance-Bk0G4EiJ.js +1 -0
- package/public/assets/Settings-D5tHL_h5.js +1 -0
- package/public/assets/Setup-4t6E3Rut.js +1 -0
- package/public/assets/index-BJ47MWpF.css +1 -0
- package/public/assets/index-DbX85irc.js +16 -0
- package/public/assets/logo-black-theme-DywLAtFy.png +0 -0
- package/public/assets/logo-white-theme-DXffFAWw.png +0 -0
- package/public/assets/{usePolling-CqQ8hrNc.js → usePolling-CK0DfI4h.js} +1 -1
- package/public/assets/{vendor-i18n-Bvxxh8Di.js → vendor-i18n-CfW0RvgE.js} +1 -1
- package/public/assets/vendor-react-B1-3Yrt-.js +59 -0
- package/public/index.html +4 -4
- package/public/assets/Dashboard-CAOQDYDR.js +0 -1
- package/public/assets/InstanceDetail-CzW2S95J.js +0 -14
- package/public/assets/NewInstance-DdbErdjA.js +0 -1
- package/public/assets/Settings-BUD7zwv9.js +0 -1
- package/public/assets/Setup-RRTIERGG.js +0 -1
- package/public/assets/index-77Ug7feY.css +0 -1
- package/public/assets/index-DfRnVUQR.js +0 -16
- 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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"}
|
package/dist/routes/instances.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
454
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
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 || !
|
|
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
|
-
|
|
1062
|
+
writeSecretFile(providerEnvFile, newContent);
|
|
1023
1063
|
results.push({ id: inst.id, status: "migrated" });
|
|
1024
1064
|
}
|
|
1025
1065
|
}
|