jishushell 0.4.2-beta2 → 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 +7 -1
- 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/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 +9 -0
- package/openclaw-entry.sh +15 -0
- package/package.json +4 -1
- package/public/assets/Dashboard-DhsrzJ4F.js +1 -0
- package/public/assets/{InitPassword-CslWYy8G.js → InitPassword-BjubiVdd.js} +1 -1
- package/public/assets/InstanceDetail-DMcywsof.js +17 -0
- package/public/assets/{Login-d45wtgVA.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/{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-Dxsq690N.js +0 -1
- package/public/assets/InstanceDetail-DmEkMj-t.js +0 -14
- package/public/assets/NewInstance-Czp5-AJe.js +0 -1
- package/public/assets/Settings-BKMGck05.js +0 -1
- package/public/assets/Setup-D3rfLWjZ.js +0 -1
- package/public/assets/index-77Ug7feY.css +0 -1
- package/public/assets/index-DkDnIohs.js +0 -16
- package/public/assets/vendor-react-DONn7uBV.js +0 -59
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1
|
|
2
|
+
# Self-contained OpenClaw runtime image with entrypoint version-switch.
|
|
3
|
+
# Build: docker build -f Dockerfile.openclaw-slim -t jishushell-openclaw:slim .
|
|
4
|
+
ARG NODE_IMAGE=node:22-bookworm-slim
|
|
5
|
+
|
|
6
|
+
# ── Stage 1: install ──
|
|
7
|
+
FROM ${NODE_IMAGE} AS builder
|
|
8
|
+
ARG OPENCLAW_VERSION=latest
|
|
9
|
+
WORKDIR /app
|
|
10
|
+
RUN apt-get update && apt-get install -y --no-install-recommends python3 make g++ git ca-certificates && \
|
|
11
|
+
rm -rf /var/lib/apt/lists/*
|
|
12
|
+
|
|
13
|
+
# Install the requested OpenClaw version. The build arg becomes part of the
|
|
14
|
+
# layer cache key so a new upstream release cannot silently reuse an older install.
|
|
15
|
+
RUN npm install -g --prefix /app "openclaw@${OPENCLAW_VERSION}" \
|
|
16
|
+
--omit=dev --no-audit --no-fund
|
|
17
|
+
|
|
18
|
+
# OpenClaw's postinstall script installs bundled plugin runtime deps
|
|
19
|
+
# (e.g. @buape/carbon for Discord) into the top-level node_modules.
|
|
20
|
+
# npm runs it automatically but it may fail silently (non-fatal).
|
|
21
|
+
# Re-run it explicitly to ensure all deps are installed.
|
|
22
|
+
# Requires git (some deps use git+ssh: protocol — rewrite to https).
|
|
23
|
+
RUN git config --global url."https://github.com/".insteadOf "ssh://git@github.com/" && \
|
|
24
|
+
cd /app/lib/node_modules/openclaw && \
|
|
25
|
+
node scripts/postinstall-bundled-plugins.mjs 2>&1 || true
|
|
26
|
+
|
|
27
|
+
# ── Stage 2: final runtime image ──
|
|
28
|
+
FROM ${NODE_IMAGE}
|
|
29
|
+
# Re-declare the ARG so it's available in this stage (Docker ARG scope is
|
|
30
|
+
# per-stage). Inject it as an OCI label so `docker inspect` reports the real
|
|
31
|
+
# bundled OpenClaw version for both CI and local-fallback builds.
|
|
32
|
+
ARG OPENCLAW_VERSION=latest
|
|
33
|
+
LABEL org.opencontainers.image.title="OpenClaw Runtime"
|
|
34
|
+
LABEL org.opencontainers.image.description="Self-contained OpenClaw runtime with Python and version-switch entrypoint"
|
|
35
|
+
LABEL org.opencontainers.image.version="${OPENCLAW_VERSION}"
|
|
36
|
+
LABEL org.opencontainers.image.source="https://github.com/x-aijishu/jishushell"
|
|
37
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
38
|
+
procps hostname curl git lsof openssl \
|
|
39
|
+
python3 python3-pip python3-venv && \
|
|
40
|
+
ln -sf /usr/bin/python3 /usr/local/bin/python && \
|
|
41
|
+
rm -rf /var/lib/apt/lists/* && \
|
|
42
|
+
rm -f /usr/lib/python*/EXTERNALLY-MANAGED
|
|
43
|
+
|
|
44
|
+
# Copy OpenClaw from builder
|
|
45
|
+
COPY --from=builder /app/lib/node_modules/ /app/node_modules/
|
|
46
|
+
RUN ln -sf /app/node_modules/openclaw/openclaw.mjs /app/openclaw.mjs && \
|
|
47
|
+
# Backward-compat: old code references /usr/local/bin/openclaw
|
|
48
|
+
ln -sf /usr/local/bin/openclaw-entry.sh /usr/local/bin/openclaw && \
|
|
49
|
+
cp /app/node_modules/openclaw/package.json /app/package.json 2>/dev/null || true
|
|
50
|
+
|
|
51
|
+
# Entrypoint wrapper
|
|
52
|
+
COPY openclaw-entry.sh /usr/local/bin/openclaw-entry.sh
|
|
53
|
+
RUN chmod +x /usr/local/bin/openclaw-entry.sh
|
|
54
|
+
|
|
55
|
+
WORKDIR /app
|
|
56
|
+
USER node
|
|
57
|
+
ENTRYPOINT ["/usr/local/bin/openclaw-entry.sh"]
|
|
58
|
+
CMD ["gateway", "--allow-unconfigured"]
|
package/INSTALL-NOTICE
CHANGED
|
@@ -23,9 +23,15 @@ software bundled with or installed by this product.
|
|
|
23
23
|
Docker Engine
|
|
24
24
|
URL : https://github.com/moby/moby
|
|
25
25
|
License : Apache License, Version 2.0
|
|
26
|
-
https://
|
|
26
|
+
https://github.com/moby/moby/blob/master/LICENSE
|
|
27
27
|
Author : Docker, Inc.
|
|
28
28
|
|
|
29
|
+
Colima
|
|
30
|
+
URL : https://github.com/abiosoft/colima
|
|
31
|
+
License : MIT License
|
|
32
|
+
https://github.com/abiosoft/colima/blob/main/LICENSE
|
|
33
|
+
Author : Abiola Ibrahim
|
|
34
|
+
|
|
29
35
|
Nomad v1.11.3+ (>= 1.7.0)
|
|
30
36
|
URL : https://github.com/hashicorp/nomad
|
|
31
37
|
License : Business Source License 1.1 (BSL 1.1)
|
package/dist/auth.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AUTH_FILE, getJwtSecret, JWT_ALGORITHM, JWT_EXPIRE_HOURS } from "./config.js";
|
|
2
|
-
import {
|
|
2
|
+
import { safeReadSecretJson, safeWriteSecretJson } from "./utils/safe-json.js";
|
|
3
3
|
import bcrypt from "bcryptjs";
|
|
4
4
|
import jwt from "jsonwebtoken";
|
|
5
5
|
const AUTH_CACHE_TTL = 60_000;
|
|
@@ -9,10 +9,10 @@ let _authCache = null;
|
|
|
9
9
|
let _initInProgress = false;
|
|
10
10
|
function invalidateAuthCache() { _authCache = null; }
|
|
11
11
|
function readAuthData() {
|
|
12
|
-
return
|
|
12
|
+
return safeReadSecretJson(AUTH_FILE, "auth");
|
|
13
13
|
}
|
|
14
14
|
function writeAuthData(data) {
|
|
15
|
-
|
|
15
|
+
safeWriteSecretJson(AUTH_FILE, data, true);
|
|
16
16
|
}
|
|
17
17
|
export function isInitialized() {
|
|
18
18
|
return readAuthData() !== null;
|
package/dist/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACvF,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
if (process.platform === "darwin") {
|
|
3
|
-
const extra = ["/usr/local/bin", "/opt/homebrew/bin", "/opt/homebrew/sbin"
|
|
3
|
+
const extra = ["/usr/local/bin", "/opt/homebrew/bin", "/opt/homebrew/sbin"];
|
|
4
4
|
const current = process.env.PATH ?? "";
|
|
5
5
|
const missing = extra.filter(p => !current.split(":").includes(p));
|
|
6
6
|
if (missing.length > 0)
|
|
@@ -10,6 +10,12 @@ import { existsSync } from "fs";
|
|
|
10
10
|
import { join } from "path";
|
|
11
11
|
import { homedir } from "os";
|
|
12
12
|
import { exec } from "child_process";
|
|
13
|
+
if (process.platform === "darwin") {
|
|
14
|
+
const jHome = process.env.JISHUSHELL_HOME || join(homedir(), ".jishushell");
|
|
15
|
+
const sock = join(jHome, "colima", "jishushell", "docker.sock");
|
|
16
|
+
if (existsSync(sock))
|
|
17
|
+
process.env.DOCKER_HOST = `unix://${sock}`;
|
|
18
|
+
}
|
|
13
19
|
import { createServer } from "./server.js";
|
|
14
20
|
import { runInstall, parseInstallArgs } from "./install.js";
|
|
15
21
|
import { startPanel, stopPanel, stopWithJobs, restartPanel, runDoctor, runOnboard, showStatus, resetAll, checkUpdate, runUpdate } from "./control.js";
|
|
@@ -24,6 +30,32 @@ function openBrowser(url) {
|
|
|
24
30
|
exec(cmd, () => { });
|
|
25
31
|
}
|
|
26
32
|
const [subcommand, ...rest] = process.argv.slice(2);
|
|
33
|
+
// ── HTTP thin-client helpers for backup commands ───────────────────────────
|
|
34
|
+
async function apiCall(path, opts = {}) {
|
|
35
|
+
const port = process.env.JISHUSHELL_PORT || "8090";
|
|
36
|
+
const url = `http://127.0.0.1:${port}/api${path}`;
|
|
37
|
+
const res = await fetch(url, {
|
|
38
|
+
method: opts.method || "GET",
|
|
39
|
+
headers: opts.body ? { "Content-Type": "application/json" } : {},
|
|
40
|
+
body: opts.body ? JSON.stringify(opts.body) : undefined,
|
|
41
|
+
});
|
|
42
|
+
if (!res.ok) {
|
|
43
|
+
const err = await res.json().catch(() => ({ detail: res.statusText }));
|
|
44
|
+
throw new Error(err.detail || `HTTP ${res.status}`);
|
|
45
|
+
}
|
|
46
|
+
return res.json();
|
|
47
|
+
}
|
|
48
|
+
async function waitForJob(jobId) {
|
|
49
|
+
while (true) {
|
|
50
|
+
const job = await apiCall(`/backup/jobs/${jobId}`);
|
|
51
|
+
if (job.status === "completed")
|
|
52
|
+
return job;
|
|
53
|
+
if (job.status === "failed")
|
|
54
|
+
throw new Error(job.error || "Job failed");
|
|
55
|
+
process.stdout.write(`\r ${job.progress || job.status}...`);
|
|
56
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
27
59
|
// ── Helper to parse --port from remaining args ─────────────────────────────
|
|
28
60
|
function parsePort(args) {
|
|
29
61
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -230,6 +262,469 @@ else if (subcommand === "uninstall") {
|
|
|
230
262
|
// Step 2: remove the npm global package (now ~/.jishushell is already gone)
|
|
231
263
|
const npmResult = spawnSync("npm", ["uninstall", "-g", "jishushell"], { stdio: "inherit" });
|
|
232
264
|
process.exit((cleanResult.status ?? 0) || (npmResult.status ?? 0));
|
|
265
|
+
// ── backup ─────────────────────────────────────────────────────────────────
|
|
266
|
+
}
|
|
267
|
+
else if (subcommand === "backup") {
|
|
268
|
+
// Handle "backup verify <instance-id> <filename>" subcommand
|
|
269
|
+
if (rest[0] === "verify") {
|
|
270
|
+
const instanceId = rest[1];
|
|
271
|
+
const filename = rest[2];
|
|
272
|
+
if (!instanceId || !filename) {
|
|
273
|
+
console.error("Usage: jishushell backup verify <instance-id> <filename>");
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
const result = await apiCall(`/instances/${instanceId}/backup/verify/${encodeURIComponent(filename)}`, { method: "POST" });
|
|
278
|
+
console.log(result.valid ? "Verification PASSED." : "Verification FAILED.");
|
|
279
|
+
for (const check of result.checks || []) {
|
|
280
|
+
console.log(` ${check.passed ? "+" : "x"} ${check.name}${check.detail ? ": " + check.detail : ""}`);
|
|
281
|
+
}
|
|
282
|
+
if (rest.includes("--json"))
|
|
283
|
+
console.log(JSON.stringify(result, null, 2));
|
|
284
|
+
}
|
|
285
|
+
catch (e) {
|
|
286
|
+
console.error(`Verify failed: ${e.message}`);
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
process.exit(0);
|
|
290
|
+
}
|
|
291
|
+
const id = rest[0];
|
|
292
|
+
if (!id) {
|
|
293
|
+
console.error("Usage: jishushell backup <id> [--with-sessions] [--only-config] [--state-only] [--no-workspace]");
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
const withSessions = rest.includes("--with-sessions");
|
|
297
|
+
const onlyConfig = rest.includes("--only-config");
|
|
298
|
+
const stateOnly = rest.includes("--state-only");
|
|
299
|
+
const noWorkspace = rest.includes("--no-workspace");
|
|
300
|
+
try {
|
|
301
|
+
console.log(`Creating backup for ${id}...`);
|
|
302
|
+
const result = await apiCall(`/instances/${id}/backup`, {
|
|
303
|
+
method: "POST",
|
|
304
|
+
body: {
|
|
305
|
+
include_sessions: withSessions,
|
|
306
|
+
only_config: onlyConfig,
|
|
307
|
+
scope: stateOnly ? "state" : "home",
|
|
308
|
+
// Only send include_workspace when the user explicitly opted out;
|
|
309
|
+
// otherwise let the backend default (include=true unless only_config).
|
|
310
|
+
...(noWorkspace ? { include_workspace: false } : {}),
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
if (result.job_id) {
|
|
314
|
+
const job = await waitForJob(result.job_id);
|
|
315
|
+
console.log(`\nBackup complete: ${job.result?.filename} (${job.result?.size} bytes)`);
|
|
316
|
+
}
|
|
317
|
+
if (rest.includes("--verify") && result.job_id) {
|
|
318
|
+
const job = await apiCall(`/backup/jobs/${result.job_id}`);
|
|
319
|
+
if (job.result?.filename) {
|
|
320
|
+
console.log("Verifying...");
|
|
321
|
+
const v = await apiCall(`/instances/${id}/backup/verify/${encodeURIComponent(job.result.filename)}`, { method: "POST" });
|
|
322
|
+
console.log(v.valid ? "Verification passed." : "Verification FAILED.");
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
catch (e) {
|
|
327
|
+
console.error(`Backup failed: ${e.message}`);
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
process.exit(0);
|
|
331
|
+
// ── restore ────────────────────────────────────────────────────────────────
|
|
332
|
+
}
|
|
333
|
+
else if (subcommand === "restore") {
|
|
334
|
+
// Handle restore --new (create new instance from backup, no existing instance ID required)
|
|
335
|
+
if (rest.includes("--new")) {
|
|
336
|
+
const fromIdx = rest.indexOf("--from");
|
|
337
|
+
const idxNewId = rest.indexOf("--id");
|
|
338
|
+
if (fromIdx !== -1) {
|
|
339
|
+
// restore --new --from <src-id> --latest|--pick [--id <new-id>]
|
|
340
|
+
const sourceId = rest[fromIdx + 1];
|
|
341
|
+
if (!sourceId) {
|
|
342
|
+
console.error("Usage: jishushell restore --new --from <src-id> --latest|--pick [--id <new-id>]");
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
let backups;
|
|
346
|
+
try {
|
|
347
|
+
backups = await apiCall(`/instances/${sourceId}/backups`);
|
|
348
|
+
}
|
|
349
|
+
catch (e) {
|
|
350
|
+
console.error(`Restore failed: ${e.message}`);
|
|
351
|
+
process.exit(1);
|
|
352
|
+
}
|
|
353
|
+
if (!backups.length) {
|
|
354
|
+
console.error("No backups found.");
|
|
355
|
+
process.exit(1);
|
|
356
|
+
}
|
|
357
|
+
let backupFile = "";
|
|
358
|
+
if (rest.includes("--latest")) {
|
|
359
|
+
backupFile = backups[0].filename;
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
console.log("Available backups:");
|
|
363
|
+
backups.forEach((b, i) => {
|
|
364
|
+
console.log(` ${i + 1}. [${b.type}] ${new Date(b.created_at).toLocaleString()} (${(b.size / 1024 / 1024).toFixed(1)}MB)`);
|
|
365
|
+
});
|
|
366
|
+
const readline = await import("readline");
|
|
367
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
368
|
+
const answer = await new Promise(r => rl.question("Select number: ", r));
|
|
369
|
+
rl.close();
|
|
370
|
+
const idx = parseInt(answer, 10) - 1;
|
|
371
|
+
if (idx < 0 || idx >= backups.length) {
|
|
372
|
+
console.error("Invalid selection.");
|
|
373
|
+
process.exit(1);
|
|
374
|
+
}
|
|
375
|
+
backupFile = backups[idx].filename;
|
|
376
|
+
}
|
|
377
|
+
const newId = idxNewId !== -1 ? rest[idxNewId + 1] : `${sourceId}-copy`;
|
|
378
|
+
console.log(`Creating new instance ${newId} from ${sourceId}/${backupFile}...`);
|
|
379
|
+
try {
|
|
380
|
+
const result = await apiCall("/instances/create-from-backup", {
|
|
381
|
+
method: "POST",
|
|
382
|
+
body: { source_id: sourceId, backup_file: backupFile, new_id: newId, new_name: newId },
|
|
383
|
+
});
|
|
384
|
+
if (result.ok) {
|
|
385
|
+
console.log(`Instance created: ${result.instance_id}`);
|
|
386
|
+
if (result.warnings?.length)
|
|
387
|
+
result.warnings.forEach((w) => console.log(` Warning: ${w}`));
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
console.error(`Create from backup failed: ${result.detail || "unknown error"}`);
|
|
391
|
+
if (result.warnings?.length)
|
|
392
|
+
result.warnings.forEach((w) => console.log(` Warning: ${w}`));
|
|
393
|
+
process.exit(1);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
catch (e) {
|
|
397
|
+
console.error(`Restore --new failed: ${e.message}`);
|
|
398
|
+
process.exit(1);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
// restore --new <file> [--id <new-id>]
|
|
403
|
+
const file = rest.find((a) => !a.startsWith("--") && a !== "restore");
|
|
404
|
+
if (!file) {
|
|
405
|
+
console.error("Usage: jishushell restore --new <file> [--id <new-id>]");
|
|
406
|
+
process.exit(1);
|
|
407
|
+
}
|
|
408
|
+
console.log("Note: restore --new from local file requires import flow.");
|
|
409
|
+
console.log(`Use: jishushell import ${file}`);
|
|
410
|
+
}
|
|
411
|
+
process.exit(0);
|
|
412
|
+
}
|
|
413
|
+
const id = rest[0];
|
|
414
|
+
if (!id) {
|
|
415
|
+
console.error("Usage: jishushell restore <id> <--latest|--pick|file>");
|
|
416
|
+
process.exit(1);
|
|
417
|
+
}
|
|
418
|
+
let file = "";
|
|
419
|
+
if (rest.includes("--latest") || rest.includes("--pick")) {
|
|
420
|
+
let backups;
|
|
421
|
+
try {
|
|
422
|
+
backups = await apiCall(`/instances/${id}/backups`);
|
|
423
|
+
}
|
|
424
|
+
catch (e) {
|
|
425
|
+
console.error(`Restore failed: ${e.message}`);
|
|
426
|
+
process.exit(1);
|
|
427
|
+
}
|
|
428
|
+
if (!backups.length) {
|
|
429
|
+
console.error("No backups found.");
|
|
430
|
+
process.exit(1);
|
|
431
|
+
}
|
|
432
|
+
if (rest.includes("--latest")) {
|
|
433
|
+
file = backups[0].filename;
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
console.log("Available backups:");
|
|
437
|
+
backups.forEach((b, i) => {
|
|
438
|
+
console.log(` ${i + 1}. ${b.type} ${new Date(b.created_at).toLocaleString()} (${(b.size / 1024 / 1024).toFixed(1)}MB)`);
|
|
439
|
+
});
|
|
440
|
+
const readline = await import("readline");
|
|
441
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
442
|
+
const answer = await new Promise(r => rl.question("Select number: ", r));
|
|
443
|
+
rl.close();
|
|
444
|
+
const idx = parseInt(answer, 10) - 1;
|
|
445
|
+
if (idx < 0 || idx >= backups.length) {
|
|
446
|
+
console.error("Invalid selection.");
|
|
447
|
+
process.exit(1);
|
|
448
|
+
}
|
|
449
|
+
file = backups[idx].filename;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
file = rest[1];
|
|
454
|
+
}
|
|
455
|
+
if (!file) {
|
|
456
|
+
console.error("No backup file specified.");
|
|
457
|
+
process.exit(1);
|
|
458
|
+
}
|
|
459
|
+
try {
|
|
460
|
+
console.log(`Restoring ${id} from ${file}...`);
|
|
461
|
+
const result = await apiCall(`/instances/${id}/restore/${encodeURIComponent(file)}`, { method: "POST" });
|
|
462
|
+
if (result.job_id) {
|
|
463
|
+
const job = await waitForJob(result.job_id);
|
|
464
|
+
const r = job.result || {};
|
|
465
|
+
if (r.ok) {
|
|
466
|
+
console.log(`\nRestore complete.`);
|
|
467
|
+
if (r.api_key_status === "lost") {
|
|
468
|
+
console.log("Warning: archive was built on a different platform/arch; verify API Key configuration still matches this host.");
|
|
469
|
+
}
|
|
470
|
+
if (r.warnings?.length)
|
|
471
|
+
r.warnings.forEach((w) => console.log(` Warning: ${w}`));
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
console.error(`\nRestore failed.${r.rolled_back ? " Auto-rollback completed." : ""}`);
|
|
475
|
+
if (r.warnings?.length)
|
|
476
|
+
r.warnings.forEach((w) => console.log(` ${w}`));
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
catch (e) {
|
|
481
|
+
console.error(`Restore failed: ${e.message}`);
|
|
482
|
+
process.exit(1);
|
|
483
|
+
}
|
|
484
|
+
process.exit(0);
|
|
485
|
+
// ── export ─────────────────────────────────────────────────────────────────
|
|
486
|
+
}
|
|
487
|
+
else if (subcommand === "export") {
|
|
488
|
+
const id = rest[0];
|
|
489
|
+
if (!id) {
|
|
490
|
+
console.error("Usage: jishushell export <id> [--with-sessions]");
|
|
491
|
+
process.exit(1);
|
|
492
|
+
}
|
|
493
|
+
const withSessions = rest.includes("--with-sessions");
|
|
494
|
+
try {
|
|
495
|
+
console.log(`Exporting ${id}...`);
|
|
496
|
+
const result = await apiCall(`/instances/${id}/export`, {
|
|
497
|
+
method: "POST",
|
|
498
|
+
body: { include_sessions: withSessions },
|
|
499
|
+
});
|
|
500
|
+
if (result.job_id) {
|
|
501
|
+
const job = await waitForJob(result.job_id);
|
|
502
|
+
const r = job.result || {};
|
|
503
|
+
console.log(`\nExport complete: ${r.filename}`);
|
|
504
|
+
console.log(`Download: http://127.0.0.1:${process.env.JISHUSHELL_PORT || "8090"}/api/instances/${id}/export/download/${encodeURIComponent(r.filename)}`);
|
|
505
|
+
if (r.warnings?.length)
|
|
506
|
+
r.warnings.forEach((w) => console.log(` Warning: ${w}`));
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
catch (e) {
|
|
510
|
+
console.error(`Export failed: ${e.message}`);
|
|
511
|
+
process.exit(1);
|
|
512
|
+
}
|
|
513
|
+
process.exit(0);
|
|
514
|
+
// ── import ─────────────────────────────────────────────────────────────────
|
|
515
|
+
}
|
|
516
|
+
else if (subcommand === "import") {
|
|
517
|
+
const file = rest[0];
|
|
518
|
+
if (!file) {
|
|
519
|
+
console.error("Usage: jishushell import <file> [--id <id>] [--name <name>]");
|
|
520
|
+
process.exit(1);
|
|
521
|
+
}
|
|
522
|
+
const idIdx = rest.indexOf("--id");
|
|
523
|
+
const nameIdx = rest.indexOf("--name");
|
|
524
|
+
const customId = idIdx !== -1 ? rest[idIdx + 1] : undefined;
|
|
525
|
+
const customName = nameIdx !== -1 ? rest[nameIdx + 1] : undefined;
|
|
526
|
+
try {
|
|
527
|
+
const { readFileSync } = await import("fs");
|
|
528
|
+
const { basename } = await import("path");
|
|
529
|
+
const port = process.env.JISHUSHELL_PORT || "8090";
|
|
530
|
+
console.log("Uploading file...");
|
|
531
|
+
const fileData = readFileSync(file);
|
|
532
|
+
const boundary = "----JishuShellCLI" + Date.now();
|
|
533
|
+
const fileName = basename(file);
|
|
534
|
+
const body = Buffer.concat([
|
|
535
|
+
Buffer.from(`--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${fileName}"\r\nContent-Type: application/gzip\r\n\r\n`),
|
|
536
|
+
fileData,
|
|
537
|
+
Buffer.from(`\r\n--${boundary}--\r\n`),
|
|
538
|
+
]);
|
|
539
|
+
const uploadRes = await fetch(`http://127.0.0.1:${port}/api/instances/import/upload`, {
|
|
540
|
+
method: "POST",
|
|
541
|
+
headers: { "Content-Type": `multipart/form-data; boundary=${boundary}` },
|
|
542
|
+
body,
|
|
543
|
+
});
|
|
544
|
+
if (!uploadRes.ok) {
|
|
545
|
+
const err = await uploadRes.json().catch(() => ({ detail: uploadRes.statusText }));
|
|
546
|
+
throw new Error(err.detail || `Upload failed: ${uploadRes.status}`);
|
|
547
|
+
}
|
|
548
|
+
const { temp_id } = await uploadRes.json();
|
|
549
|
+
console.log("Previewing...");
|
|
550
|
+
const preview = await apiCall("/instances/import/preview", { method: "POST", body: { temp_id } });
|
|
551
|
+
console.log(` Name: ${preview.manifest?.name || "unknown"}`);
|
|
552
|
+
console.log(` Type: ${preview.manifest?.type || "unknown"}`);
|
|
553
|
+
console.log(` Size: ${((preview.estimated_size || 0) / 1024 / 1024).toFixed(1)}MB`);
|
|
554
|
+
if (preview.warnings?.length) {
|
|
555
|
+
for (const w of preview.warnings)
|
|
556
|
+
console.log(` Warning: ${w}`);
|
|
557
|
+
}
|
|
558
|
+
const instanceId = customId || preview.manifest?.name?.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 63) || `import-${Date.now()}`;
|
|
559
|
+
const instanceName = customName || preview.manifest?.name || instanceId;
|
|
560
|
+
console.log(`Creating instance ${instanceId}...`);
|
|
561
|
+
const result = await apiCall("/instances/import", {
|
|
562
|
+
method: "POST",
|
|
563
|
+
body: { temp_id, id: instanceId, name: instanceName },
|
|
564
|
+
});
|
|
565
|
+
if (result.ok) {
|
|
566
|
+
console.log(`Instance created: ${result.instance_id}`);
|
|
567
|
+
if (result.warnings?.length) {
|
|
568
|
+
for (const w of result.warnings)
|
|
569
|
+
console.log(` Warning: ${w}`);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
console.error(`Import failed: ${result.detail || "unknown error"}`);
|
|
574
|
+
if (result.warnings?.length) {
|
|
575
|
+
for (const w of result.warnings)
|
|
576
|
+
console.log(` Warning: ${w}`);
|
|
577
|
+
}
|
|
578
|
+
process.exit(1);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
catch (e) {
|
|
582
|
+
console.error(`Import failed: ${e.message}`);
|
|
583
|
+
process.exit(1);
|
|
584
|
+
}
|
|
585
|
+
process.exit(0);
|
|
586
|
+
// ── clone ──────────────────────────────────────────────────────────────────
|
|
587
|
+
}
|
|
588
|
+
else if (subcommand === "clone") {
|
|
589
|
+
const src = rest[0];
|
|
590
|
+
const newId = rest[1];
|
|
591
|
+
if (!src || !newId) {
|
|
592
|
+
console.error("Usage: jishushell clone <src-id> <new-id> [--with-sessions] [--no-memory]");
|
|
593
|
+
process.exit(1);
|
|
594
|
+
}
|
|
595
|
+
const withSessions = rest.includes("--with-sessions");
|
|
596
|
+
const noMemory = rest.includes("--no-memory");
|
|
597
|
+
try {
|
|
598
|
+
console.log(`Cloning ${src} to ${newId}...`);
|
|
599
|
+
const result = await apiCall("/instances", {
|
|
600
|
+
method: "POST",
|
|
601
|
+
body: {
|
|
602
|
+
id: newId,
|
|
603
|
+
name: newId,
|
|
604
|
+
clone_from: src,
|
|
605
|
+
clone_options: {
|
|
606
|
+
include_sessions: withSessions,
|
|
607
|
+
include_memory: !noMemory,
|
|
608
|
+
},
|
|
609
|
+
},
|
|
610
|
+
});
|
|
611
|
+
console.log(`Clone created: ${result.id || newId}`);
|
|
612
|
+
}
|
|
613
|
+
catch (e) {
|
|
614
|
+
console.error(`Clone failed: ${e.message}`);
|
|
615
|
+
process.exit(1);
|
|
616
|
+
}
|
|
617
|
+
process.exit(0);
|
|
618
|
+
// ── backups ────────────────────────────────────────────────────────────────
|
|
619
|
+
}
|
|
620
|
+
else if (subcommand === "backups") {
|
|
621
|
+
const sub = rest[0];
|
|
622
|
+
if (sub === "list") {
|
|
623
|
+
const id = rest[1];
|
|
624
|
+
try {
|
|
625
|
+
if (id) {
|
|
626
|
+
const backups = await apiCall(`/instances/${id}/backups`);
|
|
627
|
+
if (!backups.length) {
|
|
628
|
+
console.log("No backups.");
|
|
629
|
+
}
|
|
630
|
+
else {
|
|
631
|
+
backups.forEach((b, i) => {
|
|
632
|
+
console.log(` ${i + 1}. [${b.type}] ${new Date(b.created_at).toLocaleString()} ${(b.size / 1024 / 1024).toFixed(1)}MB ${b.filename}`);
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
const orphans = await apiCall("/backups/orphaned");
|
|
638
|
+
if (orphans.length) {
|
|
639
|
+
console.log("Orphaned backups:");
|
|
640
|
+
for (const o of orphans) {
|
|
641
|
+
console.log(` ${o.instance_id}: ${o.backups?.length || 0} files`);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
console.log("No orphaned backups.");
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
catch (e) {
|
|
650
|
+
console.error(`backups list failed: ${e.message}`);
|
|
651
|
+
process.exit(1);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
else if (sub === "clean") {
|
|
655
|
+
try {
|
|
656
|
+
const orphans = await apiCall("/backups/orphaned");
|
|
657
|
+
if (!orphans.length) {
|
|
658
|
+
console.log("No orphaned backups to clean.");
|
|
659
|
+
process.exit(0);
|
|
660
|
+
}
|
|
661
|
+
const force = rest.includes("--force");
|
|
662
|
+
console.log(`Found ${orphans.length} orphaned backup(s):`);
|
|
663
|
+
for (const o of orphans) {
|
|
664
|
+
console.log(` ${o.instance_id}: ${o.backups?.length || 0} files`);
|
|
665
|
+
}
|
|
666
|
+
if (!force) {
|
|
667
|
+
const readline = await import("readline");
|
|
668
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
669
|
+
const answer = await new Promise(r => rl.question("Delete all orphaned backups? (y/N): ", r));
|
|
670
|
+
rl.close();
|
|
671
|
+
if (answer.toLowerCase() !== "y") {
|
|
672
|
+
console.log("Cancelled.");
|
|
673
|
+
process.exit(0);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
for (const o of orphans) {
|
|
677
|
+
for (const b of o.backups || []) {
|
|
678
|
+
await apiCall(`/backups/orphaned/${encodeURIComponent(o.instance_id)}/${encodeURIComponent(b.filename)}`, { method: "DELETE" });
|
|
679
|
+
}
|
|
680
|
+
console.log(` Cleaned: ${o.instance_id}`);
|
|
681
|
+
}
|
|
682
|
+
console.log("Done.");
|
|
683
|
+
}
|
|
684
|
+
catch (e) {
|
|
685
|
+
console.error(`backups clean failed: ${e.message}`);
|
|
686
|
+
process.exit(1);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
console.error("Usage: jishushell backups list [id]\n jishushell backups clean [--force]");
|
|
691
|
+
process.exit(1);
|
|
692
|
+
}
|
|
693
|
+
process.exit(0);
|
|
694
|
+
// ── auto-backup ────────────────────────────────────────────────────────────
|
|
695
|
+
}
|
|
696
|
+
else if (subcommand === "auto-backup") {
|
|
697
|
+
const id = rest[0];
|
|
698
|
+
const action = rest[1];
|
|
699
|
+
if (!id || !action) {
|
|
700
|
+
console.error("Usage: jishushell auto-backup <id> enable|disable|status");
|
|
701
|
+
process.exit(1);
|
|
702
|
+
}
|
|
703
|
+
try {
|
|
704
|
+
if (action === "status") {
|
|
705
|
+
const config = await apiCall(`/instances/${id}/auto-backup`);
|
|
706
|
+
console.log(JSON.stringify(config, null, 2));
|
|
707
|
+
}
|
|
708
|
+
else if (action === "enable") {
|
|
709
|
+
const interval = parseInt(rest[rest.indexOf("--interval") + 1], 10) || 24;
|
|
710
|
+
const keep = parseInt(rest[rest.indexOf("--keep") + 1], 10) || 7;
|
|
711
|
+
await apiCall(`/instances/${id}/auto-backup`, { method: "PUT", body: { enabled: true, interval_hours: interval, keep_count: keep } });
|
|
712
|
+
console.log(`Auto-backup enabled for ${id} (every ${interval}h, keep ${keep})`);
|
|
713
|
+
}
|
|
714
|
+
else if (action === "disable") {
|
|
715
|
+
await apiCall(`/instances/${id}/auto-backup`, { method: "PUT", body: { enabled: false } });
|
|
716
|
+
console.log(`Auto-backup disabled for ${id}`);
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
719
|
+
console.error(`Unknown action: ${action}. Use enable|disable|status`);
|
|
720
|
+
process.exit(1);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
catch (e) {
|
|
724
|
+
console.error(`auto-backup failed: ${e.message}`);
|
|
725
|
+
process.exit(1);
|
|
726
|
+
}
|
|
727
|
+
process.exit(0);
|
|
233
728
|
// ── help ───────────────────────────────────────────────────────────────────
|
|
234
729
|
}
|
|
235
730
|
else if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
|
|
@@ -254,8 +749,29 @@ Commands:
|
|
|
254
749
|
update-check 检查是否有新版本(0=有新版本,1=已是最新)
|
|
255
750
|
update 安装最新版本并重启面板
|
|
256
751
|
install [options] 安装依赖并配置 JishuShell
|
|
752
|
+
llm list 列出已配置的 LLM provider
|
|
753
|
+
llm add <name> <url> <key> 新增 LLM provider
|
|
754
|
+
llm update <name> <url> <key> 更新 LLM provider
|
|
755
|
+
llm delete <name> 删除 LLM provider
|
|
756
|
+
llm set-default <name> 设置默认 LLM provider
|
|
757
|
+
llm help LLM 子命令帮助
|
|
758
|
+
job list 列出所有实例及运行状态
|
|
759
|
+
job status <id> 查看实例详细状态
|
|
760
|
+
job start/stop/restart <id> 启停实例
|
|
761
|
+
job logs <id> 查看实例日志
|
|
762
|
+
job exec <id> -- <cmd> 在容器内执行命令
|
|
257
763
|
pairing list 列出等待审批的 DM 配对请求
|
|
258
764
|
pairing approve <ch> <code> 审批飞书/Lark 配对码(在容器内执行)
|
|
765
|
+
backup <id> [--with-sessions] [--only-config] 备份实例(加 --verify 校验完整性)
|
|
766
|
+
backup verify <id> <filename> 校验备份文件完整性
|
|
767
|
+
restore <id> <--latest|--pick|file> 从备份文件还原实例
|
|
768
|
+
restore --new --from <src-id> --latest|--pick [--id <new-id>] 从备份创建新实例
|
|
769
|
+
export <id> [--with-sessions] 导出实例为可迁移归档包
|
|
770
|
+
import <file> [--id <id>] [--name <name>] 导入归档包为新实例
|
|
771
|
+
clone <src-id> <new-id> [--with-sessions] [--no-memory] 克隆实例
|
|
772
|
+
backups list [id] 列出备份(不指定 id 则列孤立备份)
|
|
773
|
+
backups clean [--force] 清理孤立备份文件
|
|
774
|
+
auto-backup <id> enable|disable|status 管理定时自动备份
|
|
259
775
|
help 显示此帮助信息
|
|
260
776
|
|
|
261
777
|
Server options:
|