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
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { execFileSync, execSync, spawn as nodeSpawn } from "child_process";
|
|
2
|
-
import { chmodSync, existsSync,
|
|
3
|
-
import { userInfo
|
|
2
|
+
import { chmodSync, existsSync, readFileSync, symlinkSync, unlinkSync } from "fs";
|
|
3
|
+
import { userInfo } from "node:os";
|
|
4
4
|
import { randomBytes } from "node:crypto";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { dirname, join } from "path";
|
|
7
7
|
import { StringDecoder } from "string_decoder";
|
|
8
8
|
import { fileURLToPath } from "url";
|
|
9
|
-
import { JISHUSHELL_HOME, getPanelConfig, savePanelConfig, setOpenclawDockerImage, getOpenclawDockerImage,
|
|
9
|
+
import { JISHUSHELL_HOME, getPanelConfig, savePanelConfig, setOpenclawDockerImage, getOpenclawDockerImage, DEFAULT_OPENCLAW_DOCKER_IMAGE } from "../config.js";
|
|
10
|
+
import { ensureDirContainer, ensureDirHost, writeConfigFile, writeSecretFile, writeExecutableFile, writeSystemTmpFile } from "../utils/fs.js";
|
|
10
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
12
|
const __dirname = dirname(__filename);
|
|
12
13
|
// ── Paths ──────────────────────────────────────────────────────────
|
|
@@ -20,6 +21,9 @@ const NOMAD_BIN = join(BIN_DIR, "nomad");
|
|
|
20
21
|
const NOMAD_CONFIG_DIR = join(JISHUSHELL_HOME, "nomad");
|
|
21
22
|
const NOMAD_DATA_DIR = join(JISHUSHELL_HOME, "nomad", "data");
|
|
22
23
|
const NOMAD_ALLOC_DIR = join(JISHUSHELL_HOME, "nomad", "data", "alloc");
|
|
24
|
+
const COLIMA_DIR = join(JISHUSHELL_HOME, "colima");
|
|
25
|
+
const COLIMA_PROFILE = "jishushell";
|
|
26
|
+
const COLIMA_SOCKET = join(COLIMA_DIR, COLIMA_PROFILE, "docker.sock");
|
|
23
27
|
const NOMAD_VERSION = "1.11.3";
|
|
24
28
|
let _serverPort = 8090;
|
|
25
29
|
export function setServerPort(port) { _serverPort = port; }
|
|
@@ -340,7 +344,7 @@ export function ensureCgroupMemory() {
|
|
|
340
344
|
execFileSync("sudo", ["cp", f, f + ".bak"], { timeout: 5000 });
|
|
341
345
|
// Write to tmp file then sudo cp to avoid shell interpolation of file content
|
|
342
346
|
const tmpPath = join(dirname(f), ".cmdline.tmp");
|
|
343
|
-
|
|
347
|
+
writeSystemTmpFile(tmpPath, patched + "\n");
|
|
344
348
|
execFileSync("sudo", ["cp", tmpPath, f], { timeout: 5000 });
|
|
345
349
|
try {
|
|
346
350
|
unlinkSync(tmpPath);
|
|
@@ -356,13 +360,16 @@ export function ensureCgroupMemory() {
|
|
|
356
360
|
return false;
|
|
357
361
|
}
|
|
358
362
|
function canAccessDockerDaemon(timeout = 10000) {
|
|
363
|
+
const env = process.platform === "darwin" && existsSync(COLIMA_SOCKET)
|
|
364
|
+
? { ...process.env, DOCKER_HOST: `unix://${COLIMA_SOCKET}` }
|
|
365
|
+
: undefined;
|
|
359
366
|
try {
|
|
360
|
-
execFileSync("docker", ["info"], { timeout, stdio: "ignore" });
|
|
367
|
+
execFileSync("docker", ["info"], { timeout, stdio: "ignore", env });
|
|
361
368
|
return true;
|
|
362
369
|
}
|
|
363
370
|
catch { }
|
|
364
371
|
try {
|
|
365
|
-
execFileSync("sudo", ["-n", "docker", "info"], { timeout, stdio: "ignore" });
|
|
372
|
+
execFileSync("sudo", ["-n", "docker", "info"], { timeout, stdio: "ignore", env });
|
|
366
373
|
return true;
|
|
367
374
|
}
|
|
368
375
|
catch { }
|
|
@@ -386,10 +393,7 @@ export function getSetupStatus() {
|
|
|
386
393
|
const localBin = join(OPENCLAW_BIN_DIR, "openclaw");
|
|
387
394
|
const localBinOk = existsSync(localBin);
|
|
388
395
|
const fastDockerImageReady = checkDockerImageExists();
|
|
389
|
-
const
|
|
390
|
-
const official = isOfficialImage(baseTag);
|
|
391
|
-
// Official image: Docker image alone is sufficient. Slim base: also needs npm package.
|
|
392
|
-
const openclawOk = official ? fastDockerImageReady : (localBinOk || fastDockerImageReady);
|
|
396
|
+
const openclawOk = localBinOk || fastDockerImageReady;
|
|
393
397
|
// Lightweight validation: verify critical services are actually available
|
|
394
398
|
const dockerOk = canAccessDockerDaemon(5000);
|
|
395
399
|
const nomadOk = isPortListening(4646);
|
|
@@ -412,34 +416,14 @@ export function getSetupStatus() {
|
|
|
412
416
|
catch { }
|
|
413
417
|
let openclawVer = "installed";
|
|
414
418
|
let openclawPath = localBin;
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
else {
|
|
421
|
-
// Prefer npm package.json for accurate OpenClaw version.
|
|
422
|
-
try {
|
|
423
|
-
const pkg = join(OPENCLAW_MODULES, "openclaw", "package.json");
|
|
424
|
-
if (existsSync(pkg))
|
|
425
|
-
openclawVer = JSON.parse(readFileSync(pkg, "utf-8")).version || openclawVer;
|
|
426
|
-
}
|
|
427
|
-
catch { }
|
|
428
|
-
// Fallback: extract version from old-style openclaw:* image tag (legacy migration path).
|
|
429
|
-
if (openclawVer === "installed" && fastDockerImageReady) {
|
|
430
|
-
const imageTag = resolveDockerImageTag();
|
|
431
|
-
if (/^openclaw:/i.test(imageTag)) {
|
|
432
|
-
openclawVer = imageTag.replace(/^openclaw:/i, "");
|
|
433
|
-
openclawPath = imageTag;
|
|
434
|
-
}
|
|
435
|
-
}
|
|
419
|
+
// Prefer npm package.json for accurate OpenClaw version.
|
|
420
|
+
try {
|
|
421
|
+
const pkg = join(OPENCLAW_MODULES, "openclaw", "package.json");
|
|
422
|
+
if (existsSync(pkg))
|
|
423
|
+
openclawVer = JSON.parse(readFileSync(pkg, "utf-8")).version || openclawVer;
|
|
436
424
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
const needsNpmForMount = /^jishushell-base:/i.test(baseTag);
|
|
440
|
-
const ready = official
|
|
441
|
-
? fastDockerImageReady
|
|
442
|
-
: (openclawOk && fastDockerImageReady && (!needsNpmForMount || localBinOk));
|
|
425
|
+
catch { }
|
|
426
|
+
const ready = openclawOk && fastDockerImageReady;
|
|
443
427
|
return {
|
|
444
428
|
node: { name: "Node.js", installed: true, running: true, version: process.version, path: process.execPath },
|
|
445
429
|
docker: { name: "Docker", installed: true, running: true, version: dockerVer, path: "" },
|
|
@@ -510,11 +494,7 @@ export function getSetupStatus() {
|
|
|
510
494
|
// the local npm package is absent — the image is all that's needed to run instances.
|
|
511
495
|
if (!openclaw.ok && dockerImageReady) {
|
|
512
496
|
const imageTag = resolveDockerImageTag();
|
|
513
|
-
|
|
514
|
-
// Legacy image: strip openclaw: prefix
|
|
515
|
-
const tagVersion = isOfficialImage(imageTag)
|
|
516
|
-
? (imageTag.split(":").pop() || "local")
|
|
517
|
-
: imageTag.replace(/^openclaw:/i, "");
|
|
497
|
+
const tagVersion = imageTag.split(":").pop() || "local";
|
|
518
498
|
openclaw = { ok: true, version: tagVersion, path: imageTag };
|
|
519
499
|
}
|
|
520
500
|
const openclawStatus = {
|
|
@@ -839,7 +819,7 @@ export async function installNomad() {
|
|
|
839
819
|
try {
|
|
840
820
|
const systemNomad = execSync("which nomad 2>/dev/null", { encoding: "utf-8" }).trim();
|
|
841
821
|
if (systemNomad && existsSync(systemNomad)) {
|
|
842
|
-
|
|
822
|
+
ensureDirHost(BIN_DIR);
|
|
843
823
|
symlinkSync(systemNomad, NOMAD_BIN);
|
|
844
824
|
const version = execSync(`${NOMAD_BIN} version`, { encoding: "utf-8", timeout: 5000 }).trim();
|
|
845
825
|
console.log(`[nomad] Linked system nomad ${systemNomad} → ${NOMAD_BIN}`);
|
|
@@ -849,7 +829,7 @@ export async function installNomad() {
|
|
|
849
829
|
catch { /* system nomad not found — proceed to download */ }
|
|
850
830
|
}
|
|
851
831
|
const task = createTask("nomad");
|
|
852
|
-
|
|
832
|
+
ensureDirHost(BIN_DIR);
|
|
853
833
|
const url = getNomadDownloadUrl();
|
|
854
834
|
const zipPath = join(BIN_DIR, "nomad.zip");
|
|
855
835
|
emitTask(task, { type: "progress", message: "下载 Nomad...", progress: 0 });
|
|
@@ -917,12 +897,10 @@ function fixNomadDirOwnership() {
|
|
|
917
897
|
}
|
|
918
898
|
function writeNomadConfig() {
|
|
919
899
|
fixNomadDirOwnership();
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
mkdirSync(NOMAD_ALLOC_DIR, { recursive: true, mode: 0o755 });
|
|
925
|
-
chmodSync(NOMAD_ALLOC_DIR, 0o755);
|
|
900
|
+
ensureDirHost(NOMAD_CONFIG_DIR);
|
|
901
|
+
ensureDirContainer(NOMAD_DATA_DIR);
|
|
902
|
+
ensureDirContainer(NOMAD_ALLOC_DIR);
|
|
903
|
+
const loopbackIface = process.platform === "darwin" ? "lo0" : "lo";
|
|
926
904
|
const config = `
|
|
927
905
|
data_dir = "${NOMAD_DATA_DIR}"
|
|
928
906
|
|
|
@@ -944,13 +922,14 @@ server {
|
|
|
944
922
|
client {
|
|
945
923
|
enabled = true
|
|
946
924
|
servers = ["127.0.0.1:4647"]
|
|
925
|
+
network_interface = "${loopbackIface}"
|
|
947
926
|
alloc_dir = "${NOMAD_ALLOC_DIR}"
|
|
948
927
|
|
|
949
|
-
drain_on_shutdown
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
928
|
+
# drain_on_shutdown intentionally omitted: on single-node Pi there is
|
|
929
|
+
# nowhere to drain workloads to, and draining on every systemctl restart
|
|
930
|
+
# would kill every running OpenClaw instance. Without this block Nomad
|
|
931
|
+
# leaves allocations running across client restarts — the docker driver
|
|
932
|
+
# re-attaches to the existing containers on startup.
|
|
954
933
|
}
|
|
955
934
|
|
|
956
935
|
plugin "docker" {
|
|
@@ -966,7 +945,7 @@ acl {
|
|
|
966
945
|
enabled = true
|
|
967
946
|
}
|
|
968
947
|
`;
|
|
969
|
-
|
|
948
|
+
writeConfigFile(join(NOMAD_CONFIG_DIR, "nomad.hcl"), config);
|
|
970
949
|
}
|
|
971
950
|
export function loadNomadToken() {
|
|
972
951
|
if (process.env.NOMAD_TOKEN)
|
|
@@ -1077,10 +1056,10 @@ async function bootstrapNomadACL() {
|
|
|
1077
1056
|
const saveToken = (token) => {
|
|
1078
1057
|
const envFile = join(JISHUSHELL_HOME, "nomad.env");
|
|
1079
1058
|
const envContent = `NOMAD_TOKEN=${token}\n`;
|
|
1080
|
-
|
|
1059
|
+
writeSecretFile(envFile, envContent);
|
|
1081
1060
|
try {
|
|
1082
1061
|
execFileSync("sudo", ["-n", "mkdir", "-p", "/etc/jishushell"], { timeout: 5000, stdio: "pipe" });
|
|
1083
|
-
|
|
1062
|
+
writeSystemTmpFile("/tmp/.nomad-env-tmp", envContent);
|
|
1084
1063
|
execFileSync("sudo", ["-n", "cp", "/tmp/.nomad-env-tmp", "/etc/jishushell/nomad.env"], { timeout: 5000, stdio: "pipe" });
|
|
1085
1064
|
execFileSync("sudo", ["-n", "chmod", "600", "/etc/jishushell/nomad.env"], { timeout: 5000, stdio: "pipe" });
|
|
1086
1065
|
try {
|
|
@@ -1114,7 +1093,7 @@ async function bootstrapNomadACL() {
|
|
|
1114
1093
|
// Write the reset trigger file (Nomad reads this on startup to allow re-bootstrap)
|
|
1115
1094
|
const resetFile = join(NOMAD_DATA_DIR, "server", "acl-bootstrap-reset");
|
|
1116
1095
|
try {
|
|
1117
|
-
|
|
1096
|
+
writeConfigFile(resetFile, resetIndex);
|
|
1118
1097
|
}
|
|
1119
1098
|
catch (writeErr) {
|
|
1120
1099
|
console.warn("[nomad] Could not write acl-bootstrap-reset file:", writeErr.message);
|
|
@@ -1257,14 +1236,18 @@ export async function stopNomad() {
|
|
|
1257
1236
|
try {
|
|
1258
1237
|
if (!isPortListening(4646))
|
|
1259
1238
|
return { ok: true, message: "Nomad not running" };
|
|
1260
|
-
// SIGTERM: graceful shutdown — Nomad flushes state
|
|
1261
|
-
//
|
|
1239
|
+
// SIGTERM: graceful shutdown — Nomad flushes state and detaches from
|
|
1240
|
+
// running allocs without killing them (drain_on_shutdown is deliberately
|
|
1241
|
+
// not configured, so the docker containers keep running and will be
|
|
1242
|
+
// re-attached when Nomad comes back).
|
|
1243
|
+
// Nomad runs as the current user; no sudo needed to send SIGTERM.
|
|
1262
1244
|
try {
|
|
1263
1245
|
execSync("pkill -TERM -f 'nomad agent'", { timeout: 5000 });
|
|
1264
1246
|
}
|
|
1265
1247
|
catch { }
|
|
1266
|
-
// Wait up to
|
|
1267
|
-
|
|
1248
|
+
// Wait up to 10s for the process to exit. No drain means shutdown is
|
|
1249
|
+
// near-instant — most of this budget is slack for slow disks on Pi.
|
|
1250
|
+
for (let i = 0; i < 10; i++) {
|
|
1268
1251
|
await new Promise(r => setTimeout(r, 1000));
|
|
1269
1252
|
if (!isPortListening(4646))
|
|
1270
1253
|
return { ok: true, message: "Nomad stopped" };
|
|
@@ -1292,8 +1275,7 @@ export function installNomadSystemd() {
|
|
|
1292
1275
|
if (process.platform === "darwin") {
|
|
1293
1276
|
const plistLabel = "com.jishushell.nomad";
|
|
1294
1277
|
const logPath = join(NOMAD_CONFIG_DIR, "nomad.log");
|
|
1295
|
-
const
|
|
1296
|
-
const dockerSock = existsSync(primarySocket) ? primarySocket : "/var/run/docker.sock";
|
|
1278
|
+
const dockerSock = COLIMA_SOCKET;
|
|
1297
1279
|
const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1298
1280
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
1299
1281
|
<plist version="1.0">
|
|
@@ -1317,8 +1299,7 @@ export function installNomadSystemd() {
|
|
|
1317
1299
|
</dict>
|
|
1318
1300
|
</plist>`;
|
|
1319
1301
|
const plistPath = join(process.env.HOME || dirname(JISHUSHELL_HOME), `Library/LaunchAgents/${plistLabel}.plist`);
|
|
1320
|
-
|
|
1321
|
-
writeFileSync(plistPath, plistContent);
|
|
1302
|
+
writeConfigFile(plistPath, plistContent);
|
|
1322
1303
|
try {
|
|
1323
1304
|
execSync(`launchctl unload "${plistPath}" 2>/dev/null`);
|
|
1324
1305
|
}
|
|
@@ -1348,7 +1329,7 @@ WantedBy=multi-user.target
|
|
|
1348
1329
|
`;
|
|
1349
1330
|
const servicePath = "/etc/systemd/system/nomad.service";
|
|
1350
1331
|
execFileSync("sudo", ["mkdir", "-p", "/etc/jishushell"], { timeout: 5000 });
|
|
1351
|
-
|
|
1332
|
+
writeSystemTmpFile("/tmp/nomad.service", serviceContent);
|
|
1352
1333
|
execFileSync("sudo", ["cp", "/tmp/nomad.service", servicePath], { timeout: 5000 });
|
|
1353
1334
|
execFileSync("sudo", ["systemctl", "daemon-reload"], { timeout: 5000 });
|
|
1354
1335
|
execFileSync("sudo", ["systemctl", "enable", "--now", "nomad"], { timeout: 15000 });
|
|
@@ -1384,11 +1365,11 @@ export function installJishushellSystemd(port) {
|
|
|
1384
1365
|
export JISHUSHELL_HOME="${JISHUSHELL_HOME}"
|
|
1385
1366
|
export HOME="${realHome}"
|
|
1386
1367
|
export NODE_ENV=production
|
|
1387
|
-
export
|
|
1368
|
+
export DOCKER_HOST="unix://${COLIMA_SOCKET}"
|
|
1369
|
+
export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin:${dirname(nodeBin)}:\${PATH}"
|
|
1388
1370
|
exec "${nodeBin}" "${cliBin}" serve --port ${resolvedPort}
|
|
1389
1371
|
`;
|
|
1390
|
-
|
|
1391
|
-
writeFileSync(wrapperPath, wrapperContent, { mode: 0o755 });
|
|
1372
|
+
writeExecutableFile(wrapperPath, wrapperContent);
|
|
1392
1373
|
const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1393
1374
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
1394
1375
|
<plist version="1.0">
|
|
@@ -1406,8 +1387,7 @@ exec "${nodeBin}" "${cliBin}" serve --port ${resolvedPort}
|
|
|
1406
1387
|
</dict>
|
|
1407
1388
|
</plist>`;
|
|
1408
1389
|
const plistPath = join(process.env.HOME || realHome, `Library/LaunchAgents/${plistLabel}.plist`);
|
|
1409
|
-
|
|
1410
|
-
writeFileSync(plistPath, plistContent);
|
|
1390
|
+
writeConfigFile(plistPath, plistContent);
|
|
1411
1391
|
const panelAlreadyRunning = isPortListening(resolvedPort);
|
|
1412
1392
|
if (!panelAlreadyRunning) {
|
|
1413
1393
|
try {
|
|
@@ -1441,7 +1421,7 @@ Environment=NODE_ENV=production
|
|
|
1441
1421
|
WantedBy=multi-user.target
|
|
1442
1422
|
`;
|
|
1443
1423
|
execFileSync("sudo", ["mkdir", "-p", "/etc/jishushell"], { timeout: 5000 });
|
|
1444
|
-
|
|
1424
|
+
writeSystemTmpFile("/tmp/jishushell.service", serviceContent);
|
|
1445
1425
|
execFileSync("sudo", ["cp", "/tmp/jishushell.service", "/etc/systemd/system/jishushell.service"], { timeout: 5000 });
|
|
1446
1426
|
execFileSync("sudo", ["systemctl", "daemon-reload"], { timeout: 5000 });
|
|
1447
1427
|
execFileSync("sudo", ["systemctl", "enable", "jishushell"], { timeout: 15000 });
|
|
@@ -1463,7 +1443,7 @@ export async function installOpenclaw(version = "latest") {
|
|
|
1463
1443
|
return { ok: true, message: `OpenClaw already installed: ${ver}` };
|
|
1464
1444
|
}
|
|
1465
1445
|
const task = createTask("openclaw");
|
|
1466
|
-
|
|
1446
|
+
ensureDirHost(OPENCLAW_PKG_DIR);
|
|
1467
1447
|
emitTask(task, { type: "progress", message: "开始安装 OpenClaw...", progress: 0 });
|
|
1468
1448
|
// Monitor directory size for progress estimation
|
|
1469
1449
|
const sizeTracker = setInterval(() => {
|
|
@@ -1532,40 +1512,21 @@ function checkDockerImageExists() {
|
|
|
1532
1512
|
return true;
|
|
1533
1513
|
}
|
|
1534
1514
|
catch {
|
|
1515
|
+
// Backward compat: after upgrading from old image names (jishushell-openclaw:*)
|
|
1516
|
+
// to the new slim tag, the new image may not exist yet. Check if any old image
|
|
1517
|
+
// is still available so we don't kick the user back to the Setup wizard.
|
|
1518
|
+
try {
|
|
1519
|
+
const invocation = resolveDockerInvocation();
|
|
1520
|
+
const out = execFileSync(invocation.cmd, [...invocation.argsPrefix, "images", "--format", "{{.Repository}}:{{.Tag}}"], { encoding: "utf8", timeout: 5000 });
|
|
1521
|
+
if (out.split("\n").some((l) => /^jishushell-openclaw:/.test(l)))
|
|
1522
|
+
return true;
|
|
1523
|
+
}
|
|
1524
|
+
catch { }
|
|
1535
1525
|
return false;
|
|
1536
1526
|
}
|
|
1537
1527
|
}
|
|
1538
1528
|
// The stable tag for the JishuShell base image (slim — no OpenClaw binary baked in).
|
|
1539
|
-
// This tag does NOT change when OpenClaw is upgraded; the binary is bind-mounted from
|
|
1540
|
-
// the host at runtime. Rebuild this image only when system packages (Node.js, python3…)
|
|
1541
|
-
// need to change.
|
|
1542
|
-
export const BASE_IMAGE_TAG = "jishushell-base:v1";
|
|
1543
1529
|
function resolveDockerImageTag() {
|
|
1544
|
-
// 1. Environment variable takes precedence (same as runtime getOpenclawDockerImage)
|
|
1545
|
-
if (process.env.OPENCLAW_DOCKER_IMAGE)
|
|
1546
|
-
return process.env.OPENCLAW_DOCKER_IMAGE;
|
|
1547
|
-
// 2. Stored in panel.json — honours custom images and pre-existing openclaw:*
|
|
1548
|
-
// images that haven't been migrated yet (backward compatibility).
|
|
1549
|
-
const stored = getPanelConfig().openclaw_image;
|
|
1550
|
-
if (typeof stored === "string" && stored.trim())
|
|
1551
|
-
return stored;
|
|
1552
|
-
// 3. Scan local daemon — prefer official ghcr.io image, fall back to legacy jishushell-base.
|
|
1553
|
-
try {
|
|
1554
|
-
const invocation = resolveDockerInvocation();
|
|
1555
|
-
const output = execFileSync(invocation.cmd, [...invocation.argsPrefix, "images", "--format", "{{.Repository}}:{{.Tag}}"], { encoding: "utf-8", timeout: 5000 });
|
|
1556
|
-
const lines = output.split("\n").map(l => l.trim());
|
|
1557
|
-
// Prefer custom image (built with Python), then official, then legacy
|
|
1558
|
-
const custom = lines.find(l => l.startsWith(CUSTOM_IMAGE_PREFIX + ":"));
|
|
1559
|
-
if (custom)
|
|
1560
|
-
return custom;
|
|
1561
|
-
const official = lines.find(l => l.startsWith("ghcr.io/openclaw/openclaw:"));
|
|
1562
|
-
if (official)
|
|
1563
|
-
return official;
|
|
1564
|
-
const legacy = lines.find(l => /^jishushell-base:/i.test(l));
|
|
1565
|
-
if (legacy)
|
|
1566
|
-
return legacy;
|
|
1567
|
-
}
|
|
1568
|
-
catch { }
|
|
1569
1530
|
return getOpenclawDockerImage();
|
|
1570
1531
|
}
|
|
1571
1532
|
// Base image and mirror list for the OpenClaw Docker build.
|
|
@@ -1615,7 +1576,7 @@ function resolveVersionedBuildTag() {
|
|
|
1615
1576
|
if (existsSync(pkg)) {
|
|
1616
1577
|
const ver = JSON.parse(readFileSync(pkg, "utf-8")).version;
|
|
1617
1578
|
if (ver)
|
|
1618
|
-
return
|
|
1579
|
+
return `jishushell-openclaw:${ver}`;
|
|
1619
1580
|
}
|
|
1620
1581
|
}
|
|
1621
1582
|
catch { }
|
|
@@ -1665,9 +1626,9 @@ CMD ["gateway", "run", "--port", "18789", "--allow-unconfigured"]
|
|
|
1665
1626
|
`;
|
|
1666
1627
|
// Use a temp dir as build context — no files to COPY means no large transfer.
|
|
1667
1628
|
const buildDir = join(tmpdir(), `jishushell-base-build-${Date.now()}`);
|
|
1668
|
-
|
|
1629
|
+
ensureDirHost(buildDir);
|
|
1669
1630
|
const dockerfilePath = join(buildDir, "Dockerfile");
|
|
1670
|
-
|
|
1631
|
+
writeConfigFile(dockerfilePath, dockerfile);
|
|
1671
1632
|
emitTask(task, { type: "progress", message: `构建基础镜像 ${targetTag}(无需拷贝二进制,速度极快)...`, progress: 10 });
|
|
1672
1633
|
let result;
|
|
1673
1634
|
try {
|
|
@@ -1694,7 +1655,7 @@ CMD ["gateway", "run", "--port", "18789", "--allow-unconfigured"]
|
|
|
1694
1655
|
task.status = "error";
|
|
1695
1656
|
return { ok: false, message: "Docker image build failed", error: result.output, taskId: task.id };
|
|
1696
1657
|
}
|
|
1697
|
-
const localTag =
|
|
1658
|
+
const localTag = "jishushell-openclaw:local";
|
|
1698
1659
|
if (targetTag !== localTag) {
|
|
1699
1660
|
try {
|
|
1700
1661
|
execFileSync(invocation.cmd, [...invocation.argsPrefix, "tag", targetTag, localTag], { timeout: 10000, stdio: "ignore" });
|
|
@@ -1787,7 +1748,7 @@ CMD ["node", "openclaw.mjs", "gateway", "--allow-unconfigured"]
|
|
|
1787
1748
|
`;
|
|
1788
1749
|
// Write Dockerfile into the npm package directory (build context)
|
|
1789
1750
|
const dockerfilePath = join(OPENCLAW_PKG_DIR, "Dockerfile");
|
|
1790
|
-
|
|
1751
|
+
writeConfigFile(dockerfilePath, dockerfile);
|
|
1791
1752
|
let buildResult;
|
|
1792
1753
|
try {
|
|
1793
1754
|
buildResult = await spawnWithTask(task, invocation.cmd, [...invocation.argsPrefix, "build", "--network=host", "-t", targetTag, OPENCLAW_PKG_DIR], { timeout: 1800000, progressParser: dockerBuildProgressParser });
|
|
@@ -1818,24 +1779,212 @@ CMD ["node", "openclaw.mjs", "gateway", "--allow-unconfigured"]
|
|
|
1818
1779
|
return { ok: false, message: "Docker image build failed", error: e.message, taskId: task.id };
|
|
1819
1780
|
}
|
|
1820
1781
|
}
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1782
|
+
// ── Pull or build OpenClaw Docker image ───────────────────────────
|
|
1783
|
+
/** Matches a semver-ish tag suffix, e.g. "...:2026.4.9" or "...:v1.2.3-beta". */
|
|
1784
|
+
const PINNED_IMAGE_TAG_RE = /:[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?$/;
|
|
1785
|
+
/**
|
|
1786
|
+
* Query the npm registry for the current OpenClaw version. Used to bust the
|
|
1787
|
+
* Docker layer cache for `RUN npm install openclaw@${ver}` during local build.
|
|
1788
|
+
* Returns "latest" when npm is unreachable so the build can still proceed.
|
|
1789
|
+
*/
|
|
1790
|
+
function resolveOpenclawNpmVersion() {
|
|
1791
|
+
try {
|
|
1792
|
+
const out = execFileSync("npm", ["view", "openclaw", "version"], {
|
|
1793
|
+
timeout: 15000,
|
|
1794
|
+
encoding: "utf-8",
|
|
1795
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1796
|
+
}).trim();
|
|
1797
|
+
if (/^\d+\.\d+\.\d+/.test(out))
|
|
1798
|
+
return out;
|
|
1799
|
+
}
|
|
1800
|
+
catch { /* npm not reachable */ }
|
|
1801
|
+
return "latest";
|
|
1824
1802
|
}
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1803
|
+
/**
|
|
1804
|
+
* Read the OpenClaw version actually bundled at /app/ inside a Docker image,
|
|
1805
|
+
* bypassing `openclaw-entry.sh`'s `.npm-global/` override. This is the
|
|
1806
|
+
* authoritative source of truth — the image's OCI label can be wrong
|
|
1807
|
+
* (CI bugs, layer cache reuse), but `/app/node_modules/openclaw/package.json`
|
|
1808
|
+
* is the exact content that ran through `npm install`.
|
|
1809
|
+
*
|
|
1810
|
+
* Spawns a throw-away container with `--entrypoint node` so Node prints the
|
|
1811
|
+
* version directly. Returns "" when docker is unavailable or the path is
|
|
1812
|
+
* missing (e.g. a non-openclaw image).
|
|
1813
|
+
*/
|
|
1814
|
+
function readBundledOpenclawVersion(invocation, image) {
|
|
1815
|
+
try {
|
|
1816
|
+
const out = execFileSync(invocation.cmd, [
|
|
1817
|
+
...invocation.argsPrefix,
|
|
1818
|
+
"run", "--rm",
|
|
1819
|
+
"--entrypoint", "node",
|
|
1820
|
+
image,
|
|
1821
|
+
"-p",
|
|
1822
|
+
"require('/app/node_modules/openclaw/package.json').version",
|
|
1823
|
+
], { timeout: 20000, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
1824
|
+
if (/^\d+\.\d+\.\d+/.test(out))
|
|
1825
|
+
return out;
|
|
1826
|
+
}
|
|
1827
|
+
catch { /* docker unavailable, image missing, or path not present */ }
|
|
1828
|
+
return "";
|
|
1829
|
+
}
|
|
1830
|
+
/**
|
|
1831
|
+
* After a successful pull or build, capture the image's real OpenClaw version
|
|
1832
|
+
* and return a pinned tag of the form `ghcr.io/.../openclaw-runtime:<version>`.
|
|
1833
|
+
* The pinned tag is added as a local alias via `docker tag` so subsequent
|
|
1834
|
+
* Nomad allocations see an immutable reference and never re-pull on restart.
|
|
1835
|
+
*
|
|
1836
|
+
* Version discovery order:
|
|
1837
|
+
* 1. `explicitVersion` when the caller already knows it (e.g. the local build
|
|
1838
|
+
* path, which queries npm for the version and passes it as `--build-arg`).
|
|
1839
|
+
* 2. Bundled `/app/node_modules/openclaw/package.json` inside the image
|
|
1840
|
+
* (authoritative — bypasses both the `.npm-global/` override layer and a
|
|
1841
|
+
* potentially stale OCI label).
|
|
1842
|
+
*
|
|
1843
|
+
* Returns the original tag unchanged when:
|
|
1844
|
+
* - the target is already a pinned version tag
|
|
1845
|
+
* - no version can be discovered
|
|
1846
|
+
* - docker tag fails for any reason
|
|
1847
|
+
*/
|
|
1848
|
+
function capturePinnedImageTag(invocation, targetTag, explicitVersion) {
|
|
1849
|
+
// Already pinned? Nothing to do.
|
|
1850
|
+
if (PINNED_IMAGE_TAG_RE.test(targetTag))
|
|
1851
|
+
return targetTag;
|
|
1852
|
+
let version = explicitVersion && /^\d+\.\d+\.\d+/.test(explicitVersion) ? explicitVersion : "";
|
|
1853
|
+
if (!version) {
|
|
1854
|
+
version = readBundledOpenclawVersion(invocation, targetTag);
|
|
1855
|
+
}
|
|
1856
|
+
if (!version || !/^\d+\.\d+\.\d+/.test(version))
|
|
1857
|
+
return targetTag;
|
|
1858
|
+
// Build the pinned tag by replacing the mutable tag portion.
|
|
1859
|
+
// "ghcr.io/foo/bar:latest" → "ghcr.io/foo/bar:2026.4.9"
|
|
1860
|
+
// "ghcr.io/foo/bar" → "ghcr.io/foo/bar:2026.4.9"
|
|
1861
|
+
const colonIdx = targetTag.lastIndexOf(":");
|
|
1862
|
+
const slashIdx = targetTag.lastIndexOf("/");
|
|
1863
|
+
const hasTag = colonIdx > slashIdx;
|
|
1864
|
+
const repo = hasTag ? targetTag.slice(0, colonIdx) : targetTag;
|
|
1865
|
+
const pinnedTag = `${repo}:${version}`;
|
|
1866
|
+
if (pinnedTag === targetTag)
|
|
1867
|
+
return targetTag;
|
|
1868
|
+
try {
|
|
1869
|
+
execFileSync(invocation.cmd, [...invocation.argsPrefix, "tag", targetTag, pinnedTag], { timeout: 10000, stdio: "ignore" });
|
|
1870
|
+
}
|
|
1871
|
+
catch {
|
|
1872
|
+
// Could not create the local alias — fall back to original tag.
|
|
1873
|
+
return targetTag;
|
|
1874
|
+
}
|
|
1875
|
+
// Drop the mutable original alias (`:latest` / `:slim`) now that the pinned
|
|
1876
|
+
// tag is in place. Removing a tag is cheap and leaves the underlying image
|
|
1877
|
+
// alive because the new pinned reference still points to it. Best-effort:
|
|
1878
|
+
// silent when the tag is already gone or in use.
|
|
1879
|
+
if (/:(latest|slim)$/.test(targetTag)) {
|
|
1880
|
+
try {
|
|
1881
|
+
execFileSync(invocation.cmd, [...invocation.argsPrefix, "rmi", targetTag], { timeout: 10000, stdio: "ignore" });
|
|
1882
|
+
}
|
|
1883
|
+
catch { /* best-effort cleanup */ }
|
|
1884
|
+
}
|
|
1885
|
+
return pinnedTag;
|
|
1886
|
+
}
|
|
1887
|
+
async function pullOrBuildOpenclawImageWithTask(task, tag) {
|
|
1888
|
+
const targetTag = tag || DEFAULT_OPENCLAW_DOCKER_IMAGE;
|
|
1889
|
+
try {
|
|
1890
|
+
const invocation = resolveDockerInvocation();
|
|
1891
|
+
// Fast check: if image already exists locally, skip
|
|
1892
|
+
try {
|
|
1893
|
+
execFileSync(invocation.cmd, [...invocation.argsPrefix, "image", "inspect", targetTag], {
|
|
1894
|
+
timeout: 10000,
|
|
1895
|
+
stdio: "ignore",
|
|
1896
|
+
});
|
|
1897
|
+
const pinned = capturePinnedImageTag(invocation, targetTag);
|
|
1898
|
+
setOpenclawDockerImage(pinned);
|
|
1899
|
+
emitTask(task, { type: "done", message: `Docker 镜像已存在: ${pinned}`, progress: 100 });
|
|
1900
|
+
task.status = "done";
|
|
1901
|
+
return { ok: true, message: `Docker image ${pinned} already exists`, taskId: task.id };
|
|
1902
|
+
}
|
|
1903
|
+
catch { /* image not found, proceed */ }
|
|
1904
|
+
// ── Step 1: Try docker pull from registry ─────────────────────
|
|
1905
|
+
emitTask(task, { type: "progress", message: `正在拉取镜像: ${targetTag} ...`, progress: 10 });
|
|
1906
|
+
const pullResult = await spawnWithTask(task, invocation.cmd, [...invocation.argsPrefix, "pull", targetTag], { timeout: 600000 });
|
|
1907
|
+
if (pullResult.ok) {
|
|
1908
|
+
const pinned = capturePinnedImageTag(invocation, targetTag);
|
|
1909
|
+
setOpenclawDockerImage(pinned);
|
|
1910
|
+
emitTask(task, { type: "done", message: `镜像拉取成功: ${pinned}`, progress: 100 });
|
|
1911
|
+
task.status = "done";
|
|
1912
|
+
return { ok: true, message: `Docker image ${pinned} pulled`, taskId: task.id };
|
|
1913
|
+
}
|
|
1914
|
+
// ── Step 2: Fallback to local build ───────────────────────────
|
|
1915
|
+
console.log(`[setup] docker pull failed for ${targetTag}, falling back to local build...`);
|
|
1916
|
+
emitTask(task, { type: "progress", message: `拉取失败,正在本地构建镜像: ${targetTag} ...`, progress: 20 });
|
|
1917
|
+
const projectRoot = join(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
1918
|
+
const dockerfilePath = join(projectRoot, "Dockerfile.openclaw-slim");
|
|
1919
|
+
if (!existsSync(dockerfilePath)) {
|
|
1920
|
+
emitTask(task, { type: "error", message: "Dockerfile.openclaw-slim not found, cannot fallback to local build" });
|
|
1921
|
+
task.status = "error";
|
|
1922
|
+
return { ok: false, message: "Docker pull failed and Dockerfile.openclaw-slim not found", taskId: task.id };
|
|
1923
|
+
}
|
|
1924
|
+
// Resolve the OpenClaw version from npm so the build-arg busts the Docker
|
|
1925
|
+
// layer cache for `RUN npm install openclaw@${ver}`. The Dockerfile's
|
|
1926
|
+
// ARG OPENCLAW_VERSION=latest default would otherwise cause the layer to
|
|
1927
|
+
// be silently reused across releases.
|
|
1928
|
+
const openclawVersion = resolveOpenclawNpmVersion();
|
|
1929
|
+
console.log(`[setup] building openclaw image with OPENCLAW_VERSION=${openclawVersion}`);
|
|
1930
|
+
const buildResult = await spawnWithTask(task, invocation.cmd, [
|
|
1931
|
+
...invocation.argsPrefix,
|
|
1932
|
+
"build",
|
|
1933
|
+
"--network=host",
|
|
1934
|
+
"--build-arg", `OPENCLAW_VERSION=${openclawVersion}`,
|
|
1935
|
+
"-f", dockerfilePath,
|
|
1936
|
+
"-t", targetTag,
|
|
1937
|
+
projectRoot,
|
|
1938
|
+
], { timeout: 1800000, progressParser: dockerBuildProgressParser });
|
|
1939
|
+
if (!buildResult.ok) {
|
|
1940
|
+
try {
|
|
1941
|
+
execFileSync(invocation.cmd, [...invocation.argsPrefix, "image", "prune", "-f"], { timeout: 15000, stdio: "ignore" });
|
|
1942
|
+
}
|
|
1943
|
+
catch { }
|
|
1944
|
+
emitTask(task, { type: "error", message: "Docker 镜像构建失败" });
|
|
1945
|
+
task.status = "error";
|
|
1946
|
+
return { ok: false, message: "Docker image build failed", error: buildResult.output, taskId: task.id };
|
|
1947
|
+
}
|
|
1948
|
+
// Local builds don't get labels from the GitHub Action's `labels:` field,
|
|
1949
|
+
// so pass the npm version we already know to let capturePinnedImageTag
|
|
1950
|
+
// mint the pinned tag without relying on docker inspect.
|
|
1951
|
+
const pinned = capturePinnedImageTag(invocation, targetTag, openclawVersion);
|
|
1952
|
+
setOpenclawDockerImage(pinned);
|
|
1953
|
+
emitTask(task, { type: "done", message: `OpenClaw 镜像就绪 (本地构建): ${pinned}`, progress: 100 });
|
|
1954
|
+
task.status = "done";
|
|
1955
|
+
return { ok: true, message: `Docker image ${pinned} built locally`, taskId: task.id };
|
|
1956
|
+
}
|
|
1957
|
+
catch (e) {
|
|
1958
|
+
emitTask(task, { type: "error", message: `镜像获取失败: ${e.message}` });
|
|
1959
|
+
task.status = "error";
|
|
1960
|
+
return { ok: false, message: "Docker image pull/build failed", error: e.message, taskId: task.id };
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
export async function buildSlimOpenclawImage(tag) {
|
|
1964
|
+
const task = createTask("openclaw-docker-pull");
|
|
1965
|
+
return pullOrBuildOpenclawImageWithTask(task, tag);
|
|
1966
|
+
}
|
|
1967
|
+
export function startBuildSlimOpenclawImage(tag) {
|
|
1968
|
+
const task = createTask("openclaw-docker-pull");
|
|
1969
|
+
void pullOrBuildOpenclawImageWithTask(task, tag).catch((err) => {
|
|
1970
|
+
emitTask(task, { type: "error", message: `镜像获取失败: ${err?.message || err}` });
|
|
1829
1971
|
task.status = "error";
|
|
1830
1972
|
});
|
|
1831
|
-
return { ok: true, message: "Docker image
|
|
1973
|
+
return { ok: true, message: "Docker image pull started", taskId: task.id };
|
|
1974
|
+
}
|
|
1975
|
+
/** @deprecated Use buildSlimOpenclawImage instead */
|
|
1976
|
+
export async function buildCustomOpenclawImage(tag) {
|
|
1977
|
+
return buildSlimOpenclawImage(tag);
|
|
1978
|
+
}
|
|
1979
|
+
/** @deprecated Use startBuildSlimOpenclawImage instead */
|
|
1980
|
+
export function startBuildCustomOpenclawImage(tag) {
|
|
1981
|
+
return startBuildSlimOpenclawImage(tag);
|
|
1832
1982
|
}
|
|
1833
1983
|
export async function runFullSetup(options = {}) {
|
|
1834
1984
|
const steps = [];
|
|
1835
1985
|
let allOk = true;
|
|
1836
1986
|
const defaults = {
|
|
1837
1987
|
installNomad: true,
|
|
1838
|
-
installOpenclaw: true,
|
|
1839
1988
|
buildDockerImage: true,
|
|
1840
1989
|
...options,
|
|
1841
1990
|
};
|
|
@@ -1872,14 +2021,6 @@ export async function runFullSetup(options = {}) {
|
|
|
1872
2021
|
}
|
|
1873
2022
|
}
|
|
1874
2023
|
}
|
|
1875
|
-
if (defaults.installOpenclaw) {
|
|
1876
|
-
steps.push({ step: "openclaw", status: "running", message: "Installing OpenClaw..." });
|
|
1877
|
-
const result = await installOpenclaw();
|
|
1878
|
-
steps[steps.length - 1].status = result.ok ? "done" : "error";
|
|
1879
|
-
steps[steps.length - 1].message = result.message;
|
|
1880
|
-
if (!result.ok)
|
|
1881
|
-
allOk = false;
|
|
1882
|
-
}
|
|
1883
2024
|
// Prepare Docker image: pull official image or build slim base (legacy).
|
|
1884
2025
|
if (defaults.buildDockerImage) {
|
|
1885
2026
|
// Restart Nomad so it re-detects Docker driver after Docker was installed
|
|
@@ -1893,9 +2034,7 @@ export async function runFullSetup(options = {}) {
|
|
|
1893
2034
|
}
|
|
1894
2035
|
catch { }
|
|
1895
2036
|
steps.push({ step: "docker-image", status: "running", message: "Building OpenClaw Docker image..." });
|
|
1896
|
-
const imgResult =
|
|
1897
|
-
? await buildCustomOpenclawImage()
|
|
1898
|
-
: await buildOpenclawDockerImage();
|
|
2037
|
+
const imgResult = await buildSlimOpenclawImage();
|
|
1899
2038
|
steps[steps.length - 1].status = imgResult.ok ? "done" : "error";
|
|
1900
2039
|
steps[steps.length - 1].message = imgResult.message;
|
|
1901
2040
|
if (!imgResult.ok)
|