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.
- package/Dockerfile.openclaw-slim +58 -0
- package/INSTALL-NOTICE +45 -0
- package/dist/auth.js +3 -3
- package/dist/auth.js.map +1 -1
- package/dist/cli/app.d.ts +3 -0
- package/dist/cli/app.js +156 -0
- package/dist/cli/app.js.map +1 -0
- package/dist/{doctor.d.ts → cli/doctor.d.ts} +6 -1
- package/dist/{doctor.js → cli/doctor.js} +389 -27
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/helpers.d.ts +4 -0
- package/dist/cli/helpers.js +32 -0
- package/dist/cli/helpers.js.map +1 -0
- package/dist/cli/job.d.ts +3 -0
- package/dist/cli/job.js +260 -0
- package/dist/cli/job.js.map +1 -0
- package/dist/cli/llm.d.ts +24 -0
- package/dist/cli/llm.js +593 -0
- package/dist/cli/llm.js.map +1 -0
- package/dist/cli/openclaw.d.ts +12 -0
- package/dist/cli/openclaw.js +156 -0
- package/dist/cli/openclaw.js.map +1 -0
- package/dist/cli/panel.d.ts +25 -0
- package/dist/cli/panel.js +734 -0
- package/dist/cli/panel.js.map +1 -0
- package/dist/cli.js +476 -219
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +22 -4
- package/dist/config.js +96 -55
- package/dist/config.js.map +1 -1
- package/dist/control.d.ts +13 -41
- package/dist/control.js +12 -1355
- package/dist/control.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/apps.d.ts +3 -0
- package/dist/routes/apps.js +99 -0
- package/dist/routes/apps.js.map +1 -0
- 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 +61 -15
- package/dist/routes/instances.js.map +1 -1
- package/dist/routes/llm.d.ts +15 -0
- package/dist/routes/llm.js +246 -0
- package/dist/routes/llm.js.map +1 -0
- package/dist/routes/setup.js +32 -7
- package/dist/routes/setup.js.map +1 -1
- package/dist/routes/system.js +31 -6
- package/dist/routes/system.js.map +1 -1
- package/dist/server.js +69 -5
- package/dist/server.js.map +1 -1
- package/dist/services/app-compiler.d.ts +15 -0
- package/dist/services/app-compiler.js +169 -0
- package/dist/services/app-compiler.js.map +1 -0
- package/dist/services/app-manager.d.ts +17 -0
- package/dist/services/app-manager.js +168 -0
- package/dist/services/app-manager.js.map +1 -0
- 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 +73 -5
- package/dist/services/instance-manager.js +446 -74
- package/dist/services/instance-manager.js.map +1 -1
- package/dist/services/job-manager.d.ts +22 -0
- package/dist/services/job-manager.js +102 -0
- package/dist/services/job-manager.js.map +1 -0
- package/dist/services/llm-proxy/adapters.js +5 -1
- package/dist/services/llm-proxy/adapters.js.map +1 -1
- package/dist/services/llm-proxy/index.d.ts +30 -0
- package/dist/services/llm-proxy/index.js +71 -1
- package/dist/services/llm-proxy/index.js.map +1 -1
- package/dist/services/llm-proxy/ssrf.js +1 -1
- package/dist/services/llm-proxy/ssrf.js.map +1 -1
- package/dist/services/nomad-manager.js +263 -159
- package/dist/services/nomad-manager.js.map +1 -1
- package/dist/services/panel-manager.d.ts +40 -0
- package/dist/services/panel-manager.js +346 -0
- package/dist/services/panel-manager.js.map +1 -0
- package/dist/services/process-manager.js +24 -10
- package/dist/services/process-manager.js.map +1 -1
- package/dist/services/setup-manager.d.ts +4 -2
- package/dist/services/setup-manager.js +578 -154
- 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/services/update-manager.d.ts +47 -0
- package/dist/services/update-manager.js +305 -0
- package/dist/services/update-manager.js.map +1 -0
- package/dist/types.d.ts +62 -0
- 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.sh +582 -138
- package/install/jishu-uninstall.sh +276 -391
- package/install/post-install.sh +85 -3
- package/openclaw-entry.sh +15 -0
- package/package.json +12 -5
- package/public/assets/Dashboard-CQsp1Mr9.js +1 -0
- package/public/assets/InitPassword-BEC8SE4A.js +1 -0
- package/public/assets/InstanceDetail-B5wTgNEg.js +17 -0
- package/public/assets/{Login-RkjzTNWg.js → Login-D1Bt-Lyk.js} +1 -1
- package/public/assets/NewInstance-GQzm3K9D.js +1 -0
- package/public/assets/Settings-ByjGlqhP.js +1 -0
- package/public/assets/Setup-cMF21Y-8.js +1 -0
- package/public/assets/index-B6qQP4mH.css +1 -0
- package/public/assets/index-BuTQtuNy.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/{providers-lBSOjUWy.js → providers-V-vwrExZ.js} +1 -1
- 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/dist/doctor.js.map +0 -1
- package/public/assets/Dashboard-CAOQDYDR.js +0 -1
- package/public/assets/InitPassword-CkehIkJG.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,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
|
|
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
|
|
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
|
-
|
|
454
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
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 || !
|
|
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
|
-
|
|
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
|
-
|
|
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"] = `${
|
|
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";
|