jishushell 0.4.30 → 0.5.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/Dockerfile.hermes-slim +2 -5
  2. package/apps/anythingllm-container.yaml +287 -0
  3. package/apps/browserless-chromium-container.yaml +18 -6
  4. package/apps/filebrowser-container.yaml +164 -0
  5. package/apps/ollama-binary.yaml +44 -0
  6. package/apps/ollama-with-hollama-binary.yaml +45 -1
  7. package/apps/openclaw-binary.yaml +8 -0
  8. package/apps/openclaw-container.yaml +9 -1
  9. package/apps/openclaw-with-searxng-container.yaml +4 -0
  10. package/apps/searxng-container.yaml +5 -4
  11. package/apps/weknora-container.yaml +471 -0
  12. package/dist/cli/doctor.js +144 -16
  13. package/dist/cli/doctor.js.map +1 -1
  14. package/dist/cli/panel.js.map +1 -1
  15. package/dist/config.d.ts +19 -0
  16. package/dist/config.js +99 -1
  17. package/dist/config.js.map +1 -1
  18. package/dist/install.js +4 -4
  19. package/dist/install.js.map +1 -1
  20. package/dist/routes/auth.js +2 -2
  21. package/dist/routes/auth.js.map +1 -1
  22. package/dist/routes/backup.js +64 -11
  23. package/dist/routes/backup.js.map +1 -1
  24. package/dist/routes/external-mounts.d.ts +17 -0
  25. package/dist/routes/external-mounts.js +73 -0
  26. package/dist/routes/external-mounts.js.map +1 -0
  27. package/dist/routes/file-mounts.d.ts +13 -0
  28. package/dist/routes/file-mounts.js +90 -0
  29. package/dist/routes/file-mounts.js.map +1 -0
  30. package/dist/routes/files-organize.d.ts +28 -0
  31. package/dist/routes/files-organize.js +167 -0
  32. package/dist/routes/files-organize.js.map +1 -0
  33. package/dist/routes/files.d.ts +31 -0
  34. package/dist/routes/files.js +321 -0
  35. package/dist/routes/files.js.map +1 -0
  36. package/dist/routes/instances.js +87 -12
  37. package/dist/routes/instances.js.map +1 -1
  38. package/dist/routes/internal.d.ts +2 -0
  39. package/dist/routes/internal.js +59 -0
  40. package/dist/routes/internal.js.map +1 -0
  41. package/dist/routes/llm.js +29 -0
  42. package/dist/routes/llm.js.map +1 -1
  43. package/dist/routes/setup.js +9 -9
  44. package/dist/routes/setup.js.map +1 -1
  45. package/dist/routes/system.js +1 -1
  46. package/dist/routes/system.js.map +1 -1
  47. package/dist/routes/webdav.d.ts +17 -0
  48. package/dist/routes/webdav.js +114 -0
  49. package/dist/routes/webdav.js.map +1 -0
  50. package/dist/server.js +358 -6
  51. package/dist/server.js.map +1 -1
  52. package/dist/services/agent-apps/catalog.d.ts +3 -0
  53. package/dist/services/agent-apps/catalog.js +40 -13
  54. package/dist/services/agent-apps/catalog.js.map +1 -1
  55. package/dist/services/agent-apps/installers/shell-script.d.ts +1 -1
  56. package/dist/services/agent-apps/installers/shell-script.js +19 -2
  57. package/dist/services/agent-apps/installers/shell-script.js.map +1 -1
  58. package/dist/services/agent-apps/types.d.ts +3 -0
  59. package/dist/services/app/app-compiler.d.ts +1 -1
  60. package/dist/services/app/app-compiler.js +5 -5
  61. package/dist/services/app/app-compiler.js.map +1 -1
  62. package/dist/services/app/app-manager.d.ts +9 -0
  63. package/dist/services/app/app-manager.js +248 -43
  64. package/dist/services/app/app-manager.js.map +1 -1
  65. package/dist/services/app/custom-manager.js.map +1 -1
  66. package/dist/services/app/hermes-agent-manager.js +1 -0
  67. package/dist/services/app/hermes-agent-manager.js.map +1 -1
  68. package/dist/services/app/ollama-manager.js +1 -1
  69. package/dist/services/app/ollama-manager.js.map +1 -1
  70. package/dist/services/app/openclaw-manager.js +37 -5
  71. package/dist/services/app/openclaw-manager.js.map +1 -1
  72. package/dist/services/app/platform-transform.d.ts +32 -0
  73. package/dist/services/app/platform-transform.js +65 -0
  74. package/dist/services/app/platform-transform.js.map +1 -0
  75. package/dist/services/app-passwords.d.ts +61 -0
  76. package/dist/services/app-passwords.js +173 -0
  77. package/dist/services/app-passwords.js.map +1 -0
  78. package/dist/services/backup-manager.d.ts +11 -0
  79. package/dist/services/backup-manager.js +220 -8
  80. package/dist/services/backup-manager.js.map +1 -1
  81. package/dist/services/capability-endpoint-validator.js +26 -7
  82. package/dist/services/capability-endpoint-validator.js.map +1 -1
  83. package/dist/services/connection-apply.d.ts +2 -0
  84. package/dist/services/connection-apply.js +55 -1
  85. package/dist/services/connection-apply.js.map +1 -1
  86. package/dist/services/connection-resolver.js +1 -1
  87. package/dist/services/connection-resolver.js.map +1 -1
  88. package/dist/services/connection-transactor.d.ts +2 -0
  89. package/dist/services/connection-transactor.js +12 -2
  90. package/dist/services/connection-transactor.js.map +1 -1
  91. package/dist/services/external-mounts.d.ts +40 -0
  92. package/dist/services/external-mounts.js +187 -0
  93. package/dist/services/external-mounts.js.map +1 -0
  94. package/dist/services/files-manager.d.ts +252 -0
  95. package/dist/services/files-manager.js +1075 -0
  96. package/dist/services/files-manager.js.map +1 -0
  97. package/dist/services/files-mounts.d.ts +42 -0
  98. package/dist/services/files-mounts.js +207 -0
  99. package/dist/services/files-mounts.js.map +1 -0
  100. package/dist/services/instance-manager.js +90 -32
  101. package/dist/services/instance-manager.js.map +1 -1
  102. package/dist/services/llm-proxy/index.d.ts +28 -0
  103. package/dist/services/llm-proxy/index.js +76 -3
  104. package/dist/services/llm-proxy/index.js.map +1 -1
  105. package/dist/services/llm-proxy/ssrf.js +6 -2
  106. package/dist/services/llm-proxy/ssrf.js.map +1 -1
  107. package/dist/services/llm-proxy/validate-key.d.ts +41 -0
  108. package/dist/services/llm-proxy/validate-key.js +672 -0
  109. package/dist/services/llm-proxy/validate-key.js.map +1 -0
  110. package/dist/services/macos-launchd.d.ts +89 -0
  111. package/dist/services/macos-launchd.js +273 -0
  112. package/dist/services/macos-launchd.js.map +1 -0
  113. package/dist/services/nomad-manager.d.ts +11 -0
  114. package/dist/services/nomad-manager.js +343 -98
  115. package/dist/services/nomad-manager.js.map +1 -1
  116. package/dist/services/organize/applier.d.ts +46 -0
  117. package/dist/services/organize/applier.js +218 -0
  118. package/dist/services/organize/applier.js.map +1 -0
  119. package/dist/services/organize/rules.d.ts +57 -0
  120. package/dist/services/organize/rules.js +286 -0
  121. package/dist/services/organize/rules.js.map +1 -0
  122. package/dist/services/organize/scanner.d.ts +50 -0
  123. package/dist/services/organize/scanner.js +366 -0
  124. package/dist/services/organize/scanner.js.map +1 -0
  125. package/dist/services/organize/store.d.ts +14 -0
  126. package/dist/services/organize/store.js +82 -0
  127. package/dist/services/organize/store.js.map +1 -0
  128. package/dist/services/panel-manager.js +40 -11
  129. package/dist/services/panel-manager.js.map +1 -1
  130. package/dist/services/process-manager.js +3 -2
  131. package/dist/services/process-manager.js.map +1 -1
  132. package/dist/services/runtime/adapters/custom.js +56 -0
  133. package/dist/services/runtime/adapters/custom.js.map +1 -1
  134. package/dist/services/runtime/adapters/hermes.d.ts +4 -3
  135. package/dist/services/runtime/adapters/hermes.js +166 -64
  136. package/dist/services/runtime/adapters/hermes.js.map +1 -1
  137. package/dist/services/runtime/adapters/openclaw-routes.d.ts +8 -2
  138. package/dist/services/runtime/adapters/openclaw-routes.js +68 -0
  139. package/dist/services/runtime/adapters/openclaw-routes.js.map +1 -1
  140. package/dist/services/runtime/adapters/openclaw.d.ts +118 -0
  141. package/dist/services/runtime/adapters/openclaw.js +1459 -49
  142. package/dist/services/runtime/adapters/openclaw.js.map +1 -1
  143. package/dist/services/runtime/instance.d.ts +1 -1
  144. package/dist/services/runtime/instance.js +1 -1
  145. package/dist/services/runtime/instance.js.map +1 -1
  146. package/dist/services/runtime/mcp-shims/anythingllm-shim.d.ts +46 -0
  147. package/dist/services/runtime/mcp-shims/anythingllm-shim.js +281 -0
  148. package/dist/services/runtime/mcp-shims/anythingllm-shim.js.map +1 -0
  149. package/dist/services/runtime/mcp-shims/drive-shim.d.ts +54 -0
  150. package/dist/services/runtime/mcp-shims/drive-shim.js +489 -0
  151. package/dist/services/runtime/mcp-shims/drive-shim.js.map +1 -0
  152. package/dist/services/runtime/types.d.ts +31 -0
  153. package/dist/services/setup-manager.js +190 -68
  154. package/dist/services/setup-manager.js.map +1 -1
  155. package/dist/services/suggestions.js.map +1 -1
  156. package/dist/services/update-manager.js +32 -14
  157. package/dist/services/update-manager.js.map +1 -1
  158. package/dist/services/webdav/server.d.ts +24 -0
  159. package/dist/services/webdav/server.js +420 -0
  160. package/dist/services/webdav/server.js.map +1 -0
  161. package/dist/services/webdav/xml-builder.d.ts +73 -0
  162. package/dist/services/webdav/xml-builder.js +156 -0
  163. package/dist/services/webdav/xml-builder.js.map +1 -0
  164. package/dist/services/workspace-builder.d.ts +29 -0
  165. package/dist/services/workspace-builder.js +188 -0
  166. package/dist/services/workspace-builder.js.map +1 -0
  167. package/dist/types.d.ts +61 -0
  168. package/dist/utils/path-locks.d.ts +30 -0
  169. package/dist/utils/path-locks.js +63 -0
  170. package/dist/utils/path-locks.js.map +1 -0
  171. package/dist/utils/path-safety.d.ts +41 -0
  172. package/dist/utils/path-safety.js +119 -0
  173. package/dist/utils/path-safety.js.map +1 -0
  174. package/dist/utils/safe-write.d.ts +24 -0
  175. package/dist/utils/safe-write.js +82 -0
  176. package/dist/utils/safe-write.js.map +1 -0
  177. package/install/jishu-install.sh +247 -35
  178. package/install/jishu-uninstall.sh +45 -5
  179. package/package.json +20 -2
  180. package/public/assets/ApiKeyField-CvyAOcJS.js +1 -0
  181. package/public/assets/Dashboard-AuJESBlJ.js +1 -0
  182. package/public/assets/{HermesChatPanel-_GHoklgo.js → HermesChatPanel-CByPREwb.js} +1 -1
  183. package/public/assets/HermesConfigForm-DRda8FKX.js +4 -0
  184. package/public/assets/InitPassword-ka4wNpM5.js +1 -0
  185. package/public/assets/InstanceDetail-Cg1nS8HX.js +92 -0
  186. package/public/assets/Login-aPajuQzf.js +1 -0
  187. package/public/assets/NewInstance-Dd1ebNIx.js +1 -0
  188. package/public/assets/ProviderRecommendations-DFmADQ7V.js +1 -0
  189. package/public/assets/Settings-BYQnbLYL.js +1 -0
  190. package/public/assets/Setup-D05lwDOV.js +1 -0
  191. package/public/assets/WeixinLoginPanel-D89kdhP4.js +9 -0
  192. package/public/assets/index-HSXCsceK.css +1 -0
  193. package/public/assets/index-bnBu0nlQ.js +19 -0
  194. package/public/assets/registry-C_qeFTkZ.js +2 -0
  195. package/public/assets/usePolling-Bn93fe7M.js +1 -0
  196. package/public/assets/{vendor-i18n-ucpM0OR0.js → vendor-i18n-flxcMVeP.js} +2 -2
  197. package/public/assets/{vendor-react-Bk1hRGiY.js → vendor-react-ZC5T_huj.js} +7 -7
  198. package/public/index.html +4 -4
  199. package/scripts/check-app-spec.mjs +18 -4
  200. package/scripts/check-colima-launchd.mjs +230 -0
  201. package/scripts/check-new-file-tests.mjs +230 -0
  202. package/scripts/check-quarantine-expiry.mjs +105 -0
  203. package/scripts/perf/README.md +49 -0
  204. package/scripts/perf/auth.js +99 -0
  205. package/scripts/perf/config.js +63 -0
  206. package/scripts/perf/instances.js +143 -0
  207. package/scripts/perf/proxy.js +96 -0
  208. package/scripts/smoke/files-w1.sh +142 -0
  209. package/scripts/smoke-backend.mjs +122 -0
  210. package/scripts/smoke-post-publish.mjs +346 -0
  211. package/public/assets/Dashboard-rkWp-CXd.js +0 -1
  212. package/public/assets/HermesConfigForm-anDnwUp_.js +0 -4
  213. package/public/assets/InitPassword-ZU9_-hDr.js +0 -1
  214. package/public/assets/InstanceDetail-CN0FH1aw.js +0 -92
  215. package/public/assets/Login-BItXqYAJ.js +0 -1
  216. package/public/assets/NewInstance-BousE6kY.js +0 -1
  217. package/public/assets/ProviderRecommendations-DFYj7Fb6.js +0 -1
  218. package/public/assets/Settings-Bttc6QmM.js +0 -1
  219. package/public/assets/Setup-Bsxx1zgj.js +0 -1
  220. package/public/assets/WeixinLoginPanel-DPZpAKgO.js +0 -9
  221. package/public/assets/index-8xZy1z5k.css +0 -1
  222. package/public/assets/index-Dw3HhUYE.js +0 -19
  223. package/public/assets/input-paste-CrNVAyOy.js +0 -1
  224. package/public/assets/providers-DtNXh9JD.js +0 -1
  225. package/public/assets/registry-5s2UB6is.js +0 -2
  226. package/public/assets/usePolling-Do5Erqm_.js +0 -1
@@ -1,6 +1,6 @@
1
1
  import { execFileSync, execSync, spawn as nodeSpawn } from "child_process";
2
2
  import { chmodSync, copyFileSync, existsSync, mkdtempSync, readFileSync, renameSync, rmSync, symlinkSync, unlinkSync } from "fs";
3
- import { userInfo, platform as osPlatform } from "node:os";
3
+ import { userInfo, platform as osPlatform, arch as osArch, cpus as osCpus } from "node:os";
4
4
  import { randomBytes } from "node:crypto";
5
5
  import { tmpdir } from "node:os";
6
6
  import { dirname, join } from "path";
@@ -8,7 +8,8 @@ import { StringDecoder } from "string_decoder";
8
8
  import { fileURLToPath } from "url";
9
9
  import { JISHUSHELL_HOME, getPanelConfig, savePanelConfig, setOpenclawDockerImage, getOpenclawDockerImage, DEFAULT_OPENCLAW_DOCKER_IMAGE, getRuntimeCatalogEntry, OPENCLAW_MODULES, OPENCLAW_BIN_DIR, } from "../config.js";
10
10
  import { ensureDirContainer, ensureDirHost, writeConfigFile, writeSecretFile, writeExecutableFile, writeSystemTmpFile } from "../utils/fs.js";
11
- import { buildDockerClientEnv, managedColimaSocketPath, resolveDockerHost } from "../utils/docker-host.js";
11
+ import { buildDockerClientEnv, managedColimaSocketPath, managedColimaDockerHost, resolveDockerHost } from "../utils/docker-host.js";
12
+ import { appleSiliconGuard, buildColimaWrapperScript, buildColimaPlist, buildNomadWaitWrapper, buildNomadPlist, buildPanelGateWrapper, buildPanelPlist, } from "./macos-launchd.js";
12
13
  import { getAdapter, listRegisteredAdapters } from "./runtime/index.js";
13
14
  // Internal usage of task primitives — re-exports further down preserve the
14
15
  // public import surface used by runtime/adapters/*, routes, and CLI.
@@ -21,9 +22,9 @@ const NOMAD_BIN = join(BIN_DIR, "nomad");
21
22
  const NOMAD_CONFIG_DIR = join(JISHUSHELL_HOME, "nomad");
22
23
  const NOMAD_DATA_DIR = join(JISHUSHELL_HOME, "nomad", "data");
23
24
  const NOMAD_ALLOC_DIR = join(JISHUSHELL_HOME, "nomad", "data", "alloc");
24
- const COLIMA_DIR = join(JISHUSHELL_HOME, "colima");
25
+ const _COLIMA_DIR = join(JISHUSHELL_HOME, "colima");
25
26
  const COLIMA_PROFILE = "jishushell";
26
- const COLIMA_SOCKET = managedColimaSocketPath(JISHUSHELL_HOME, COLIMA_PROFILE);
27
+ const _COLIMA_SOCKET = managedColimaSocketPath(JISHUSHELL_HOME, COLIMA_PROFILE);
27
28
  const NOMAD_VERSION = "1.6.5";
28
29
  let _serverPort = 8090;
29
30
  export function setServerPort(port) { _serverPort = port; }
@@ -192,7 +193,7 @@ function curlProgressParser(line) {
192
193
  export function getDirSizeMB(dir) {
193
194
  try {
194
195
  const result = execFileSync("du", ["-sm", dir], { encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] });
195
- return parseInt(result.split("\t")[0]) || 0;
196
+ return parseInt(result.split("\t")[0], 10) || 0;
196
197
  }
197
198
  catch {
198
199
  return 0;
@@ -212,15 +213,6 @@ function checkCommand(cmd, versionFlag = "--version") {
212
213
  return { ok: false, version: "", path: "" };
213
214
  }
214
215
  }
215
- function isProcessRunning(name) {
216
- try {
217
- const result = execFileSync("pgrep", ["-f", name], { encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] });
218
- return result.trim().length > 0;
219
- }
220
- catch {
221
- return false;
222
- }
223
- }
224
216
  function isPortListening(port) {
225
217
  if (!Number.isInteger(port) || port < 1 || port > 65535)
226
218
  return false;
@@ -509,7 +501,7 @@ async function buildRuntimesStatus() {
509
501
  export async function upgradeNode(targetMajor = 22) {
510
502
  try {
511
503
  const current = process.version; // e.g. "v20.20.1"
512
- const currentMajor = parseInt(current.replace("v", "").split(".")[0]);
504
+ const currentMajor = parseInt(current.replace("v", "").split(".")[0], 10);
513
505
  if (currentMajor >= targetMajor) {
514
506
  return { ok: true, message: `Node.js ${current} already meets requirement (>= ${targetMajor})` };
515
507
  }
@@ -578,6 +570,17 @@ export async function upgradeNode(targetMajor = 22) {
578
570
  async function installDockerWithTask(task) {
579
571
  try {
580
572
  emitTask(task, { type: "progress", message: "开始安装 Docker...", progress: 0 });
573
+ // macOS short-circuit: the rest of this function targets apt/get.docker.com,
574
+ // both of which silently no-op or hard-fail on Darwin. Docker on macOS is
575
+ // provided by Colima (auto-installed by the bash installer at
576
+ // install/jishu-install.sh) — not by this code path. Refuse early with a
577
+ // clear pointer so the user doesn't see a confusing apt error.
578
+ if (process.platform === "darwin") {
579
+ const msg = "macOS Docker 需要 Colima。请运行 shell 安装器 (curl -fsSL https://raw.githubusercontent.com/nicepkg/jishushell/main/install/jishu-install.sh | bash),或参阅 docs/macos-colima.md 手动安装 Colima。";
580
+ emitTask(task, { type: "error", message: msg });
581
+ task.status = "error";
582
+ return { ok: false, message: msg, error: "darwin-not-supported-via-this-path" };
583
+ }
581
584
  const user = execSync("whoami", { encoding: "utf-8", timeout: 5000 }).trim();
582
585
  // ── Try the get.docker.com convenience script ─────────────────
583
586
  emitTask(task, { type: "log", message: "下载 Docker 安装脚本..." });
@@ -1152,6 +1155,42 @@ function writeNomadConfig() {
1152
1155
  }
1153
1156
  `
1154
1157
  : "";
1158
+ // Detect docker0 bridge CIDR so multi-container apps (weknora etc.) can
1159
+ // declare `host_network: docker_bridge` on their ports and have Nomad
1160
+ // publish them on the docker0 IP — which is what `host.docker.internal`
1161
+ // resolves to inside peer containers. Hardcoding 172.17.0.0/16 would
1162
+ // break setups where docker daemon.json overrides `bip` /
1163
+ // `default-address-pools`. Fall back to the Docker default if detection
1164
+ // fails (e.g. docker not yet installed at panel install time; doctor
1165
+ // will regenerate later).
1166
+ const dockerBridgeCidr = detectDockerBridgeCidr() || "172.17.0.0/16";
1167
+ const dockerBridgeHostNetworkBlock = `
1168
+ host_network "docker_bridge" {
1169
+ cidr = "${dockerBridgeCidr}"
1170
+ }
1171
+ `;
1172
+ // Apple Silicon CPU fingerprint workaround. Nomad 1.6.x on darwin/arm64
1173
+ // reads `hw.tbfrequency` (timebase, e.g. 24 MHz on M-series) as the CPU
1174
+ // clock, so a 14-core M4 Max reports as 48 MHz total compute and every
1175
+ // job that asks for CPU>48 gets rejected with "Resources exhausted on
1176
+ // dimension cpu". Override with a conservative estimate (cores × 2500
1177
+ // MHz) — M-series base clock is at least 2.5 GHz, so this never
1178
+ // overcommits relative to actual silicon. Intel Macs fingerprint
1179
+ // correctly and don't need the override.
1180
+ const cpuTotalComputeBlock = platform === "darwin" && osArch() === "arm64"
1181
+ ? ` cpu_total_compute = ${osCpus().length * 2500} # Apple Silicon workaround: Nomad mis-fingerprints CPU freq via tbfrequency\n`
1182
+ : "";
1183
+ // macOS docker driver endpoint. macOS users run Docker via Colima
1184
+ // (managed by this panel under `~/.jishushell/colima/jishushell/`) or
1185
+ // Docker Desktop (`~/.docker/run/docker.sock`); neither exposes
1186
+ // `/var/run/docker.sock` out of the box. Resolve the correct host once
1187
+ // and bake it into the HCL so Nomad doesn't fall back to the default
1188
+ // `unix:///var/run/docker.sock` and fail. Linux keeps the default
1189
+ // (empty endpoint = Docker's standard /var/run/docker.sock).
1190
+ const dockerHost = platform === "darwin"
1191
+ ? resolveDockerHost({ jishuHome: JISHUSHELL_HOME, colimaProfile: COLIMA_PROFILE })
1192
+ : undefined;
1193
+ const dockerEndpointLine = dockerHost ? `\n endpoint = "${dockerHost}"` : "";
1155
1194
  const config = `
1156
1195
  data_dir = "${NOMAD_DATA_DIR}"
1157
1196
 
@@ -1171,11 +1210,11 @@ server {
1171
1210
  }
1172
1211
 
1173
1212
  client {
1174
- enabled = true
1213
+ ${cpuTotalComputeBlock} enabled = true
1175
1214
  servers = ["127.0.0.1:4647"]
1176
1215
  network_interface = "${loopbackIface}"
1177
1216
  alloc_dir = "${NOMAD_ALLOC_DIR}"
1178
- ${externalHostNetworkBlock}
1217
+ ${externalHostNetworkBlock}${dockerBridgeHostNetworkBlock}
1179
1218
 
1180
1219
  # drain_on_shutdown intentionally omitted: on single-node Pi there is
1181
1220
  # nowhere to drain workloads to, and draining on every systemctl restart
@@ -1185,7 +1224,10 @@ ${externalHostNetworkBlock}
1185
1224
  }
1186
1225
 
1187
1226
  plugin "docker" {
1188
- config {
1227
+ config {${dockerEndpointLine}
1228
+ gc {
1229
+ image = false
1230
+ }
1189
1231
  volumes {
1190
1232
  enabled = true
1191
1233
  }
@@ -1208,6 +1250,22 @@ limits {
1208
1250
  `;
1209
1251
  writeConfigFile(join(NOMAD_CONFIG_DIR, "nomad.hcl"), config);
1210
1252
  }
1253
+ function detectDockerBridgeCidr() {
1254
+ // `docker network inspect bridge` returns the CIDR for docker0. The exact
1255
+ // value depends on /etc/docker/daemon.json `bip` / `default-address-pools`
1256
+ // — Docker's default is 172.17.0.0/16 but isn't guaranteed. We need the
1257
+ // real value because Nomad uses `host_network "docker_bridge" { cidr = …}`
1258
+ // to pick which interface to publish a port on for specs that declare
1259
+ // `host_network: docker_bridge`. Wrong CIDR → port silently published on
1260
+ // wrong interface → cross-task host.docker.internal calls get refused.
1261
+ try {
1262
+ const raw = execFileSync("docker", ["network", "inspect", "bridge", "--format", "{{(index .IPAM.Config 0).Subnet}}"], { encoding: "utf8", timeout: 3000, stdio: ["pipe", "pipe", "pipe"] }).trim();
1263
+ return /^\d{1,3}(\.\d{1,3}){3}\/\d{1,2}$/.test(raw) ? raw : "";
1264
+ }
1265
+ catch {
1266
+ return "";
1267
+ }
1268
+ }
1211
1269
  function detectNomadExternalInterface(loopbackIface) {
1212
1270
  try {
1213
1271
  if (osPlatform() === "darwin") {
@@ -1450,8 +1508,41 @@ function enableNomadNodeEligibility() {
1450
1508
  }
1451
1509
  export async function startNomad() {
1452
1510
  try {
1511
+ const configPath = join(NOMAD_CONFIG_DIR, "nomad.hcl");
1512
+ const readConfig = () => {
1513
+ try {
1514
+ return existsSync(configPath) ? readFileSync(configPath, "utf-8") : "";
1515
+ }
1516
+ catch {
1517
+ return "";
1518
+ }
1519
+ };
1520
+ const oldConfig = readConfig();
1453
1521
  writeNomadConfig();
1522
+ const newConfig = readConfig();
1523
+ const drifted = oldConfig.length > 0 && newConfig.length > 0 && oldConfig !== newConfig;
1454
1524
  if (isPortListening(4646)) {
1525
+ // Nomad already running, but if the rewritten config differs (typically
1526
+ // because detectNomadExternalInterface() now resolves to a different
1527
+ // interface — e.g. older install missing the host_network block, or
1528
+ // network interface renamed across reboots), reload nomad so allocations
1529
+ // bind to the right host network on next placement.
1530
+ if (drifted) {
1531
+ try {
1532
+ execFileSync("sudo", ["-n", "systemctl", "restart", "nomad"], { stdio: "pipe", timeout: 15000 });
1533
+ for (let i = 0; i < 15; i++) {
1534
+ await new Promise(r => setTimeout(r, 1000));
1535
+ if (isPortListening(4646)) {
1536
+ await finalizeNomadStartup();
1537
+ return { ok: true, message: "Nomad restarted (config reconciled)" };
1538
+ }
1539
+ }
1540
+ return { ok: false, message: "Nomad restart timed out after config reconcile", error: "Port 4646 not listening after 15s" };
1541
+ }
1542
+ catch (e) {
1543
+ console.warn(`[nomad] config drift detected but restart failed (${e.message}); existing nomad continues with stale config`);
1544
+ }
1545
+ }
1455
1546
  await finalizeNomadStartup();
1456
1547
  return { ok: true, message: "Nomad already running" };
1457
1548
  }
@@ -1501,7 +1592,6 @@ export async function startNomad() {
1501
1592
  catch {
1502
1593
  return { ok: false, message: "Nomad not installed", error: "Install Nomad first" };
1503
1594
  }
1504
- const configPath = join(NOMAD_CONFIG_DIR, "nomad.hcl");
1505
1595
  const logPath = join(NOMAD_CONFIG_DIR, "nomad.log");
1506
1596
  // Use spawn with detached to avoid execSync shell injection and ensure proper daemonization
1507
1597
  try {
@@ -1572,45 +1662,75 @@ export function installNomadSystemd() {
1572
1662
  const configPath = join(NOMAD_CONFIG_DIR, "nomad.hcl");
1573
1663
  writeNomadConfig();
1574
1664
  if (process.platform === "darwin") {
1575
- const plistLabel = "com.jishushell.nomad";
1665
+ // macOS is Apple Silicon only — fail fast (mirrors the bash uname guard).
1666
+ const guard = appleSiliconGuard();
1667
+ if (!guard.ok)
1668
+ return { ok: false, message: guard.message };
1669
+ const laDir = join(process.env.HOME || dirname(JISHUSHELL_HOME), "Library/LaunchAgents");
1670
+ // Always use JishuShell's private Colima socket — hardcoded, not
1671
+ // runtime-detected. Colima may not be running yet when the plist is
1672
+ // written; runtime fallback would pick the wrong socket (Docker
1673
+ // Desktop or /var/run/docker.sock). Mirrors the bash _COLIMA_SOCKET use.
1674
+ const colimaSocket = managedColimaSocketPath(JISHUSHELL_HOME, COLIMA_PROFILE);
1675
+ const colimaDockerHost = managedColimaDockerHost(JISHUSHELL_HOME, COLIMA_PROFILE);
1676
+ // ── 1. Colima self-retrying launchd agent ───────────────────────
1677
+ // colima binary: prefer PATH, fall back to both Homebrew prefixes
1678
+ // (mirrors the bash COLIMA_BIN resolution).
1679
+ let colimaBin = "";
1680
+ try {
1681
+ colimaBin = execFileSync("which", ["colima"], { encoding: "utf-8", timeout: 5000 }).trim().split("\n")[0];
1682
+ }
1683
+ catch {
1684
+ colimaBin = "";
1685
+ }
1686
+ if (!colimaBin && existsSync("/opt/homebrew/bin/colima"))
1687
+ colimaBin = "/opt/homebrew/bin/colima";
1688
+ if (!colimaBin && existsSync("/usr/local/bin/colima"))
1689
+ colimaBin = "/usr/local/bin/colima";
1690
+ if (!colimaBin)
1691
+ colimaBin = "colima";
1692
+ const colimaWrapperPath = join(BIN_DIR, "colima-launchd-wrapper.sh");
1693
+ writeExecutableFile(colimaWrapperPath, buildColimaWrapperScript({
1694
+ colimaHome: _COLIMA_DIR,
1695
+ profile: COLIMA_PROFILE,
1696
+ socket: colimaSocket,
1697
+ colimaBin,
1698
+ }));
1699
+ const colimaPlistPath = join(laDir, "com.jishushell.colima.plist");
1700
+ writeConfigFile(colimaPlistPath, buildColimaPlist({
1701
+ wrapperPath: colimaWrapperPath,
1702
+ colimaHome: _COLIMA_DIR,
1703
+ logPath: join(_COLIMA_DIR, "colima-launchd.log"),
1704
+ }));
1705
+ try {
1706
+ execSync(`launchctl unload "${colimaPlistPath}" 2>/dev/null`);
1707
+ }
1708
+ catch { }
1709
+ try {
1710
+ execSync(`launchctl load -w "${colimaPlistPath}"`, { timeout: 15000 });
1711
+ }
1712
+ catch { }
1713
+ // ── 2. Nomad gated behind a docker-readiness wrapper ────────────
1576
1714
  const logPath = join(NOMAD_CONFIG_DIR, "nomad.log");
1577
- const dockerHost = resolveDockerHost({
1578
- jishuHome: JISHUSHELL_HOME,
1579
- colimaProfile: COLIMA_PROFILE,
1715
+ const nomadWrapperPath = join(BIN_DIR, "nomad-launchd-wrapper.sh");
1716
+ writeExecutableFile(nomadWrapperPath, buildNomadWaitWrapper({
1717
+ dockerHost: colimaDockerHost,
1718
+ nomadBin: nomadPath,
1719
+ configPath,
1720
+ }));
1721
+ const plistContent = buildNomadPlist({
1722
+ wrapperPath: nomadWrapperPath,
1723
+ dockerHost: colimaDockerHost,
1724
+ logPath,
1580
1725
  });
1581
- const dockerHostEntry = dockerHost
1582
- ? `
1583
- <key>DOCKER_HOST</key><string>${dockerHost}</string>`
1584
- : "";
1585
- const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
1586
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1587
- <plist version="1.0">
1588
- <dict>
1589
- <key>Label</key><string>${plistLabel}</string>
1590
- <key>ProgramArguments</key>
1591
- <array>
1592
- <string>${nomadPath}</string>
1593
- <string>agent</string>
1594
- <string>-config=${configPath}</string>
1595
- </array>
1596
- <key>EnvironmentVariables</key>
1597
- <dict>${dockerHostEntry}
1598
- <key>PATH</key><string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
1599
- </dict>
1600
- <key>RunAtLoad</key><true/>
1601
- <key>KeepAlive</key><true/>
1602
- <key>StandardOutPath</key><string>${logPath}</string>
1603
- <key>StandardErrorPath</key><string>${logPath}</string>
1604
- </dict>
1605
- </plist>`;
1606
- const plistPath = join(process.env.HOME || dirname(JISHUSHELL_HOME), `Library/LaunchAgents/${plistLabel}.plist`);
1726
+ const plistPath = join(laDir, "com.jishushell.nomad.plist");
1607
1727
  writeConfigFile(plistPath, plistContent);
1608
1728
  try {
1609
1729
  execSync(`launchctl unload "${plistPath}" 2>/dev/null`);
1610
1730
  }
1611
1731
  catch { }
1612
1732
  execSync(`launchctl load -w "${plistPath}"`, { timeout: 15000 });
1613
- return { ok: true, message: "Nomad launchd agent installed and started" };
1733
+ return { ok: true, message: "Nomad launchd agent installed and started (Colima gated)" };
1614
1734
  }
1615
1735
  // Nomad 1.6.5's docker driver fingerprint requires euid==0 — PR #18197 lifted
1616
1736
  // that restriction only in 1.7+, and we intentionally stay on the 1.6 MPL line.
@@ -1664,7 +1784,10 @@ export function installJishushellSystemd(port) {
1664
1784
  // The real owner home: prefer JISHUSHELL_HOME's parent so we don't rely on $HOME.
1665
1785
  const realHome = dirname(JISHUSHELL_HOME);
1666
1786
  if (process.platform === "darwin") {
1667
- const plistLabel = "com.jishushell.panel";
1787
+ // macOS is Apple Silicon only — fail fast (mirrors the bash uname guard).
1788
+ const guard = appleSiliconGuard();
1789
+ if (!guard.ok)
1790
+ return { ok: false, message: guard.message };
1668
1791
  const logPath = join(JISHUSHELL_HOME, "panel.log");
1669
1792
  const wrapperPath = join(JISHUSHELL_HOME, "bin", "jishushell-panel-start");
1670
1793
  const nomadEnvPath = join(JISHUSHELL_HOME, "nomad.env");
@@ -1677,23 +1800,22 @@ export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/local/sbin
1677
1800
  exec "${nodeBin}" "${cliBin}" serve --port ${resolvedPort}
1678
1801
  `;
1679
1802
  writeExecutableFile(wrapperPath, wrapperContent);
1680
- const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
1681
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1682
- <plist version="1.0">
1683
- <dict>
1684
- <key>Label</key><string>${plistLabel}</string>
1685
- <key>ProgramArguments</key>
1686
- <array>
1687
- <string>/bin/sh</string>
1688
- <string>${wrapperPath}</string>
1689
- </array>
1690
- <key>RunAtLoad</key><true/>
1691
- <key>KeepAlive</key><true/>
1692
- <key>StandardOutPath</key><string>${logPath}</string>
1693
- <key>StandardErrorPath</key><string>${logPath}</string>
1694
- </dict>
1695
- </plist>`;
1696
- const plistPath = join(process.env.HOME || realHome, `Library/LaunchAgents/${plistLabel}.plist`);
1803
+ // 60s docker-readiness gate so the first post-reboot page load
1804
+ // doesn't flash "image missing"; on timeout it starts anyway
1805
+ // (mirrors the bash panel-launchd-wrapper.sh gate).
1806
+ const gateWrapperPath = join(BIN_DIR, "panel-launchd-wrapper.sh");
1807
+ writeExecutableFile(gateWrapperPath, buildPanelGateWrapper({
1808
+ socket: managedColimaSocketPath(JISHUSHELL_HOME, COLIMA_PROFILE),
1809
+ panelStartWrapper: wrapperPath,
1810
+ }));
1811
+ const plistContent = buildPanelPlist({
1812
+ gateWrapperPath,
1813
+ logPath,
1814
+ });
1815
+ // Symmetric with installNomadSystemd()'s laDir (realHome === dirname(JISHUSHELL_HOME),
1816
+ // so process.env.HOME || dirname(JISHUSHELL_HOME) is provably the same path).
1817
+ const laDir = join(process.env.HOME || dirname(JISHUSHELL_HOME), "Library/LaunchAgents");
1818
+ const plistPath = join(laDir, "com.jishushell.panel.plist");
1697
1819
  writeConfigFile(plistPath, plistContent);
1698
1820
  const panelAlreadyRunning = isPortListening(resolvedPort);
1699
1821
  if (!panelAlreadyRunning) {