jishushell 0.6.5 → 0.6.18
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/apps/anythingllm-container.yaml +15 -170
- package/apps/browserless-chromium-container.yaml +15 -10
- package/apps/filebrowser-container.yaml +14 -9
- package/apps/hermes-container.yaml +23 -2
- package/apps/jishu-kb-container.yaml +29 -161
- package/apps/ollama-binary.yaml +32 -28
- package/apps/ollama-cpu-container.yaml +5 -0
- package/apps/ollama-with-hollama-binary.yaml +33 -28
- package/apps/openclaw-binary.yaml +34 -10
- package/apps/openclaw-container.yaml +31 -7
- package/apps/openclaw-with-ollama-container.yaml +8 -2
- package/apps/openclaw-with-searxng-container.yaml +18 -6
- package/apps/searxng-container.yaml +11 -6
- package/apps/weknora-container.yaml +21 -21
- package/dependencies/jishushell-panel-0.6.18.tgz +0 -0
- package/dist/cli/app.js +244 -213
- package/dist/cli/app.js.map +1 -1
- package/dist/cli/backup.js +15 -12
- package/dist/cli/backup.js.map +1 -1
- package/dist/cli/core.d.ts +4 -3
- package/dist/cli/core.js +392 -227
- package/dist/cli/core.js.map +1 -1
- package/dist/cli/doctor.d.ts +1 -1
- package/dist/cli/doctor.js +17 -10
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/job.js +62 -14
- package/dist/cli/job.js.map +1 -1
- package/dist/cli/llm.js +80 -11
- package/dist/cli/llm.js.map +1 -1
- package/dist/cli/managed-list.d.ts +1 -3
- package/dist/cli/managed-list.js +18 -16
- package/dist/cli/managed-list.js.map +1 -1
- package/dist/cli/migrate.d.ts +2 -0
- package/dist/cli/migrate.js +160 -0
- package/dist/cli/migrate.js.map +1 -0
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +23 -19
- package/dist/config.js +60 -49
- package/dist/config.js.map +1 -1
- package/dist/control.d.ts +6 -6
- package/dist/control.js +31 -23
- package/dist/control.js.map +1 -1
- package/dist/core.d.ts +5 -5
- package/dist/core.js +5 -5
- package/dist/core.js.map +1 -1
- package/dist/install.d.ts +2 -2
- package/dist/install.js +18 -18
- package/dist/install.js.map +1 -1
- package/dist/routes/apps.d.ts +1 -1
- package/dist/routes/apps.js +101 -193
- package/dist/routes/apps.js.map +1 -1
- package/dist/routes/auth.js +1 -1
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/backup.js +1 -1
- package/dist/routes/backup.js.map +1 -1
- package/dist/routes/external-mounts.d.ts +1 -1
- package/dist/routes/external-mounts.js +1 -1
- package/dist/routes/external-mounts.js.map +1 -1
- package/dist/routes/file-mounts.d.ts +4 -3
- package/dist/routes/file-mounts.js +51 -30
- package/dist/routes/file-mounts.js.map +1 -1
- package/dist/routes/files-organize.d.ts +2 -2
- package/dist/routes/files-organize.js +5 -5
- package/dist/routes/files-organize.js.map +1 -1
- package/dist/routes/files.d.ts +1 -1
- package/dist/routes/files.js +1 -1
- package/dist/routes/files.js.map +1 -1
- package/dist/routes/instances.d.ts +10 -4
- package/dist/routes/instances.js +323 -541
- package/dist/routes/instances.js.map +1 -1
- package/dist/routes/integration-apps.d.ts +14 -0
- package/dist/routes/integration-apps.js +81 -0
- package/dist/routes/integration-apps.js.map +1 -0
- package/dist/routes/integrations.d.ts +9 -0
- package/dist/routes/integrations.js +12 -0
- package/dist/routes/integrations.js.map +1 -0
- package/dist/routes/llm-proxy.js +26 -3
- package/dist/routes/llm-proxy.js.map +1 -1
- package/dist/routes/setup.js +53 -38
- package/dist/routes/setup.js.map +1 -1
- package/dist/routes/system.js +108 -68
- package/dist/routes/system.js.map +1 -1
- package/dist/routes/webdav.d.ts +1 -1
- package/dist/routes/webdav.js +2 -2
- package/dist/routes/webdav.js.map +1 -1
- package/dist/server.js +315 -213
- package/dist/server.js.map +1 -1
- package/dist/services/app-common/app-compiler.js +186 -0
- package/dist/services/app-common/app-compiler.js.map +1 -0
- package/dist/services/app-common/app-shared.d.ts +15 -0
- package/dist/services/app-common/app-shared.js +64 -0
- package/dist/services/app-common/app-shared.js.map +1 -0
- package/dist/services/app-common/capability-service.d.ts +45 -0
- package/dist/services/app-common/capability-service.js +331 -0
- package/dist/services/app-common/capability-service.js.map +1 -0
- package/dist/services/app-common/catalog-service.d.ts +59 -0
- package/dist/services/app-common/catalog-service.js +308 -0
- package/dist/services/app-common/catalog-service.js.map +1 -0
- package/dist/services/app-common/create-pipeline.d.ts +26 -0
- package/dist/services/app-common/create-pipeline.js +298 -0
- package/dist/services/app-common/create-pipeline.js.map +1 -0
- package/dist/services/app-common/delete-service.d.ts +5 -0
- package/dist/services/app-common/delete-service.js +104 -0
- package/dist/services/app-common/delete-service.js.map +1 -0
- package/dist/services/app-common/execution-owner.d.ts +23 -0
- package/dist/services/app-common/execution-owner.js +124 -0
- package/dist/services/app-common/execution-owner.js.map +1 -0
- package/dist/services/app-common/execution-service.d.ts +23 -0
- package/dist/services/app-common/execution-service.js +105 -0
- package/dist/services/app-common/execution-service.js.map +1 -0
- package/dist/services/app-common/id-normalizer.d.ts +31 -0
- package/dist/services/app-common/id-normalizer.js +83 -0
- package/dist/services/app-common/id-normalizer.js.map +1 -0
- package/dist/services/app-common/install-store.d.ts +34 -0
- package/dist/services/app-common/install-store.js +261 -0
- package/dist/services/app-common/install-store.js.map +1 -0
- package/dist/services/app-common/instance-store.d.ts +78 -0
- package/dist/services/app-common/instance-store.js +495 -0
- package/dist/services/app-common/instance-store.js.map +1 -0
- package/dist/services/app-common/integration-refs.d.ts +17 -0
- package/dist/services/app-common/integration-refs.js +47 -0
- package/dist/services/app-common/integration-refs.js.map +1 -0
- package/dist/services/app-common/lifecycle-pipeline.d.ts +62 -0
- package/dist/services/app-common/lifecycle-pipeline.js +317 -0
- package/dist/services/app-common/lifecycle-pipeline.js.map +1 -0
- package/dist/services/app-common/lifecycle-scripts.d.ts +38 -0
- package/dist/services/app-common/lifecycle-scripts.js +935 -0
- package/dist/services/app-common/lifecycle-scripts.js.map +1 -0
- package/dist/services/app-common/lifecycle-service.d.ts +68 -0
- package/dist/services/app-common/lifecycle-service.js +467 -0
- package/dist/services/app-common/lifecycle-service.js.map +1 -0
- package/dist/services/app-common/paths.d.ts +29 -0
- package/dist/services/app-common/paths.js +34 -0
- package/dist/services/app-common/paths.js.map +1 -0
- package/dist/services/app-common/platform-transform.d.ts +32 -0
- package/dist/services/app-common/platform-transform.js +65 -0
- package/dist/services/app-common/platform-transform.js.map +1 -0
- package/dist/services/app-common/provide-resolver.d.ts +29 -0
- package/dist/services/app-common/provide-resolver.js +129 -0
- package/dist/services/app-common/provide-resolver.js.map +1 -0
- package/dist/services/app-common/remote-spec.d.ts +14 -0
- package/dist/services/app-common/remote-spec.js +58 -0
- package/dist/services/app-common/remote-spec.js.map +1 -0
- package/dist/services/app-common/runtime-builder.d.ts +1 -0
- package/dist/services/app-common/runtime-builder.js +2 -0
- package/dist/services/app-common/runtime-builder.js.map +1 -0
- package/dist/services/app-common/runtime-facts.d.ts +19 -0
- package/dist/services/app-common/runtime-facts.js +126 -0
- package/dist/services/app-common/runtime-facts.js.map +1 -0
- package/dist/services/app-common/service.d.ts +9 -0
- package/dist/services/app-common/service.js +10 -0
- package/dist/services/app-common/service.js.map +1 -0
- package/dist/services/app-common/spec-materializer.d.ts +9 -0
- package/dist/services/app-common/spec-materializer.js +361 -0
- package/dist/services/app-common/spec-materializer.js.map +1 -0
- package/dist/services/app-common/status-refresh.d.ts +33 -0
- package/dist/services/app-common/status-refresh.js +759 -0
- package/dist/services/app-common/status-refresh.js.map +1 -0
- package/dist/services/app-common/task-service.d.ts +29 -0
- package/dist/services/app-common/task-service.js +93 -0
- package/dist/services/app-common/task-service.js.map +1 -0
- package/dist/services/app-common/terminal-session-manager.js +157 -0
- package/dist/services/app-common/terminal-session-manager.js.map +1 -0
- package/dist/services/app-modules/browserless/routes.d.ts +9 -0
- package/dist/services/app-modules/browserless/routes.js +517 -0
- package/dist/services/app-modules/browserless/routes.js.map +1 -0
- package/dist/services/app-modules/routes.d.ts +2 -0
- package/dist/services/app-modules/routes.js +5 -0
- package/dist/services/app-modules/routes.js.map +1 -0
- package/dist/services/backup/backup-admin.d.ts +95 -0
- package/dist/services/backup/backup-admin.js +246 -0
- package/dist/services/backup/backup-admin.js.map +1 -0
- package/dist/services/backup/backup-manager.d.ts +264 -0
- package/dist/services/backup/backup-manager.js +2318 -0
- package/dist/services/backup/backup-manager.js.map +1 -0
- package/dist/services/backup/backup-verify.js +240 -0
- package/dist/services/backup/backup-verify.js.map +1 -0
- package/dist/services/capabilities/browser-policy.d.ts +14 -0
- package/dist/services/capabilities/browser-policy.js +141 -0
- package/dist/services/capabilities/browser-policy.js.map +1 -0
- package/dist/services/capabilities/contract.d.ts +50 -0
- package/dist/services/capabilities/contract.js +129 -0
- package/dist/services/capabilities/contract.js.map +1 -0
- package/dist/services/capabilities/endpoint-validator.d.ts +42 -0
- package/dist/services/capabilities/endpoint-validator.js +114 -0
- package/dist/services/capabilities/endpoint-validator.js.map +1 -0
- package/dist/services/capabilities/health.d.ts +16 -0
- package/dist/services/capabilities/health.js +121 -0
- package/dist/services/capabilities/health.js.map +1 -0
- package/dist/services/capabilities/registry.d.ts +56 -0
- package/dist/services/capabilities/registry.js +222 -0
- package/dist/services/capabilities/registry.js.map +1 -0
- package/dist/services/capabilities/sync.d.ts +7 -0
- package/dist/services/capabilities/sync.js +223 -0
- package/dist/services/capabilities/sync.js.map +1 -0
- package/dist/services/capability-proxy/html-rewriters/browserless.d.ts +1 -0
- package/dist/services/capability-proxy/html-rewriters/browserless.js +83 -0
- package/dist/services/capability-proxy/html-rewriters/browserless.js.map +1 -0
- package/dist/services/capability-proxy/html-rewriters/index.d.ts +12 -0
- package/dist/services/capability-proxy/html-rewriters/index.js +25 -0
- package/dist/services/capability-proxy/html-rewriters/index.js.map +1 -0
- package/dist/services/capability-proxy/html-rewriters/jishukb.d.ts +1 -0
- package/dist/services/capability-proxy/html-rewriters/jishukb.js +161 -0
- package/dist/services/capability-proxy/html-rewriters/jishukb.js.map +1 -0
- package/dist/services/connections/admin.d.ts +80 -0
- package/dist/services/connections/admin.js +327 -0
- package/dist/services/connections/admin.js.map +1 -0
- package/dist/services/connections/apply.d.ts +110 -0
- package/dist/services/connections/apply.js +444 -0
- package/dist/services/connections/apply.js.map +1 -0
- package/dist/services/connections/resolver.d.ts +82 -0
- package/dist/services/connections/resolver.js +289 -0
- package/dist/services/connections/resolver.js.map +1 -0
- package/dist/services/connections/suggestions.d.ts +27 -0
- package/dist/services/connections/suggestions.js +124 -0
- package/dist/services/connections/suggestions.js.map +1 -0
- package/dist/services/connections/transactor.d.ts +39 -0
- package/dist/services/connections/transactor.js +307 -0
- package/dist/services/connections/transactor.js.map +1 -0
- package/dist/services/files/external-mounts.js +187 -0
- package/dist/services/files/external-mounts.js.map +1 -0
- package/dist/services/files/files-manager.d.ts +265 -0
- package/dist/services/files/files-manager.js +1189 -0
- package/dist/services/files/files-manager.js.map +1 -0
- package/dist/services/files/files-mounts.d.ts +42 -0
- package/dist/services/files/files-mounts.js +207 -0
- package/dist/services/files/files-mounts.js.map +1 -0
- package/dist/services/files/organize/applier.js +218 -0
- package/dist/services/files/organize/applier.js.map +1 -0
- package/dist/services/files/organize/rules.js +286 -0
- package/dist/services/files/organize/rules.js.map +1 -0
- package/dist/services/files/organize/scanner.js +366 -0
- package/dist/services/files/organize/scanner.js.map +1 -0
- package/dist/services/files/organize/store.js +82 -0
- package/dist/services/files/organize/store.js.map +1 -0
- package/dist/services/files/webdav/server.d.ts +47 -0
- package/dist/services/files/webdav/server.js +329 -0
- package/dist/services/files/webdav/server.js.map +1 -0
- package/dist/services/files/webdav/xml-builder.js.map +1 -0
- package/dist/services/instances/admin.d.ts +23 -0
- package/dist/services/instances/admin.js +218 -0
- package/dist/services/instances/admin.js.map +1 -0
- package/dist/services/instances/clone.d.ts +26 -0
- package/dist/services/instances/clone.js +78 -0
- package/dist/services/instances/clone.js.map +1 -0
- package/dist/services/instances/config-admin.d.ts +17 -0
- package/dist/services/instances/config-admin.js +181 -0
- package/dist/services/instances/config-admin.js.map +1 -0
- package/dist/services/instances/manager.d.ts +231 -0
- package/dist/services/instances/manager.js +1348 -0
- package/dist/services/instances/manager.js.map +1 -0
- package/dist/services/instances/passwords.js +173 -0
- package/dist/services/instances/passwords.js.map +1 -0
- package/dist/services/instances/types.d.ts +21 -0
- package/dist/services/instances/types.js +2 -0
- package/dist/services/instances/types.js.map +1 -0
- package/dist/services/integrations/anythingllm/integration.d.ts +25 -0
- package/dist/services/integrations/anythingllm/integration.js +251 -0
- package/dist/services/integrations/anythingllm/integration.js.map +1 -0
- package/dist/services/integrations/catalog.d.ts +3 -0
- package/dist/services/integrations/catalog.js +73 -0
- package/dist/services/integrations/catalog.js.map +1 -0
- package/dist/services/integrations/custom/integration.d.ts +28 -0
- package/dist/services/integrations/custom/integration.js +179 -0
- package/dist/services/integrations/custom/integration.js.map +1 -0
- package/dist/services/integrations/hermes/integration.d.ts +194 -0
- package/dist/services/integrations/hermes/integration.js +1669 -0
- package/dist/services/integrations/hermes/integration.js.map +1 -0
- package/dist/services/integrations/index.d.ts +40 -0
- package/dist/services/integrations/index.js +59 -0
- package/dist/services/integrations/index.js.map +1 -0
- package/dist/services/integrations/installable/catalog.d.ts +33 -0
- package/dist/services/integrations/installable/catalog.js +88 -0
- package/dist/services/integrations/installable/catalog.js.map +1 -0
- package/dist/services/integrations/installable/index.d.ts +35 -0
- package/dist/services/integrations/installable/index.js +170 -0
- package/dist/services/integrations/installable/index.js.map +1 -0
- package/dist/services/integrations/installable/installers/integration-probes.d.ts +50 -0
- package/dist/services/integrations/installable/installers/integration-probes.js +231 -0
- package/dist/services/integrations/installable/installers/integration-probes.js.map +1 -0
- package/dist/services/integrations/installable/installers/integration.d.ts +30 -0
- package/dist/services/integrations/installable/installers/integration.js +177 -0
- package/dist/services/integrations/installable/installers/integration.js.map +1 -0
- package/dist/services/integrations/installable/installers/registry-probe.js.map +1 -0
- package/dist/services/integrations/installable/installers/shell-script.d.ts +46 -0
- package/dist/services/integrations/installable/installers/shell-script.js +487 -0
- package/dist/services/integrations/installable/installers/shell-script.js.map +1 -0
- package/dist/services/integrations/installable/types.d.ts +130 -0
- package/dist/services/integrations/installable/types.js +19 -0
- package/dist/services/integrations/installable/types.js.map +1 -0
- package/dist/services/integrations/jishukb/integration.d.ts +22 -0
- package/dist/services/integrations/jishukb/integration.js +189 -0
- package/dist/services/integrations/jishukb/integration.js.map +1 -0
- package/dist/services/integrations/openclaw/anythingllm-shim.d.ts +46 -0
- package/dist/services/integrations/openclaw/anythingllm-shim.js +281 -0
- package/dist/services/integrations/openclaw/anythingllm-shim.js.map +1 -0
- package/dist/services/integrations/openclaw/drive-shim.js +490 -0
- package/dist/services/integrations/openclaw/drive-shim.js.map +1 -0
- package/dist/services/integrations/openclaw/integration.d.ts +424 -0
- package/dist/services/integrations/openclaw/integration.js +4402 -0
- package/dist/services/integrations/openclaw/integration.js.map +1 -0
- package/dist/services/integrations/openclaw/jishukb-shim.d.ts +48 -0
- package/dist/services/integrations/openclaw/jishukb-shim.js +750 -0
- package/dist/services/integrations/openclaw/jishukb-shim.js.map +1 -0
- package/dist/services/integrations/openclaw/mcporter-lite.js +276 -0
- package/dist/services/integrations/openclaw/mcporter-lite.js.map +1 -0
- package/dist/services/integrations/openclaw/mcporter.d.ts +46 -0
- package/dist/services/integrations/openclaw/mcporter.js +112 -0
- package/dist/services/integrations/openclaw/mcporter.js.map +1 -0
- package/dist/services/integrations/openclaw/routes.d.ts +21 -0
- package/dist/services/integrations/openclaw/routes.js +1191 -0
- package/dist/services/integrations/openclaw/routes.js.map +1 -0
- package/dist/services/integrations/registry.d.ts +17 -0
- package/dist/services/integrations/registry.js +36 -0
- package/dist/services/integrations/registry.js.map +1 -0
- package/dist/services/integrations/routes.d.ts +2 -0
- package/dist/services/integrations/routes.js +9 -0
- package/dist/services/integrations/routes.js.map +1 -0
- package/dist/services/integrations/types.d.ts +469 -0
- package/dist/services/integrations/types.js +2 -0
- package/dist/services/integrations/types.js.map +1 -0
- package/dist/services/legacy-migrator/classifier.d.ts +44 -0
- package/dist/services/legacy-migrator/classifier.js +309 -0
- package/dist/services/legacy-migrator/classifier.js.map +1 -0
- package/dist/services/legacy-migrator/executor.d.ts +42 -0
- package/dist/services/legacy-migrator/executor.js +637 -0
- package/dist/services/legacy-migrator/executor.js.map +1 -0
- package/dist/services/legacy-migrator/index.d.ts +31 -0
- package/dist/services/legacy-migrator/index.js +34 -0
- package/dist/services/legacy-migrator/index.js.map +1 -0
- package/dist/services/legacy-migrator/planner.d.ts +8 -0
- package/dist/services/legacy-migrator/planner.js +154 -0
- package/dist/services/legacy-migrator/planner.js.map +1 -0
- package/dist/services/legacy-migrator/provider-settings.d.ts +6 -0
- package/dist/services/legacy-migrator/provider-settings.js +72 -0
- package/dist/services/legacy-migrator/provider-settings.js.map +1 -0
- package/dist/services/legacy-migrator/report.d.ts +9 -0
- package/dist/services/legacy-migrator/report.js +99 -0
- package/dist/services/legacy-migrator/report.js.map +1 -0
- package/dist/services/legacy-migrator/scanner.d.ts +13 -0
- package/dist/services/legacy-migrator/scanner.js +157 -0
- package/dist/services/legacy-migrator/scanner.js.map +1 -0
- package/dist/services/legacy-migrator/types.d.ts +97 -0
- package/dist/services/legacy-migrator/types.js +23 -0
- package/dist/services/legacy-migrator/types.js.map +1 -0
- package/dist/services/llm-proxy/instance-proxy.d.ts +17 -1
- package/dist/services/llm-proxy/instance-proxy.js +171 -44
- package/dist/services/llm-proxy/instance-proxy.js.map +1 -1
- package/dist/services/llm-proxy/probe.js +5 -14
- package/dist/services/llm-proxy/probe.js.map +1 -1
- package/dist/services/llm-proxy/providers.js +23 -31
- package/dist/services/llm-proxy/providers.js.map +1 -1
- package/dist/services/llm-proxy/ssrf.d.ts +11 -4
- package/dist/services/llm-proxy/ssrf.js +45 -7
- package/dist/services/llm-proxy/ssrf.js.map +1 -1
- package/dist/services/llm-proxy/validate-key.js +16 -37
- package/dist/services/llm-proxy/validate-key.js.map +1 -1
- package/dist/services/repair/runtime-repair.d.ts +22 -0
- package/dist/services/repair/runtime-repair.js +307 -0
- package/dist/services/repair/runtime-repair.js.map +1 -0
- package/dist/services/runtime/driver-registry.d.ts +21 -0
- package/dist/services/runtime/driver-registry.js +22 -0
- package/dist/services/runtime/driver-registry.js.map +1 -0
- package/dist/services/runtime/drivers/nomad.d.ts +260 -0
- package/dist/services/runtime/drivers/nomad.js +3092 -0
- package/dist/services/runtime/drivers/nomad.js.map +1 -0
- package/dist/services/runtime/errors.d.ts +3 -3
- package/dist/services/runtime/errors.js +3 -3
- package/dist/services/runtime/instance.d.ts +14 -16
- package/dist/services/runtime/instance.js +93 -123
- package/dist/services/runtime/instance.js.map +1 -1
- package/dist/services/runtime/job-id.d.ts +1 -1
- package/dist/services/runtime/job-id.js +1 -1
- package/dist/services/runtime/mcp-shims/firewall.d.ts +2 -2
- package/dist/services/runtime/mcp-shims/firewall.js +2 -2
- package/dist/services/runtime/mcp-shims/searxng-shim.d.ts +3 -5
- package/dist/services/runtime/mcp-shims/searxng-shim.js +3 -5
- package/dist/services/runtime/mcp-shims/searxng-shim.js.map +1 -1
- package/dist/services/runtime/mcp-shims/write-mcp-entry.d.ts +20 -20
- package/dist/services/runtime/mcp-shims/write-mcp-entry.js +16 -16
- package/dist/services/runtime/mcp-shims/write-mcp-entry.js.map +1 -1
- package/dist/services/runtime/ownership-marker.d.ts +83 -0
- package/dist/services/runtime/ownership-marker.js +109 -0
- package/dist/services/runtime/ownership-marker.js.map +1 -0
- package/dist/services/runtime/types.d.ts +22 -501
- package/dist/services/runtime/types.js +0 -12
- package/dist/services/runtime/types.js.map +1 -1
- package/dist/services/runtime/workload-compiler.d.ts +17 -0
- package/dist/services/runtime/workload-compiler.js +525 -0
- package/dist/services/runtime/workload-compiler.js.map +1 -0
- package/dist/services/runtime/workload-types.d.ts +11 -0
- package/dist/services/runtime/workload-types.js +2 -0
- package/dist/services/runtime/workload-types.js.map +1 -0
- package/dist/services/setup/core-manager.d.ts +50 -0
- package/dist/services/setup/core-manager.js +456 -0
- package/dist/services/setup/core-manager.js.map +1 -0
- package/dist/services/setup/plugin-installer.js +136 -0
- package/dist/services/setup/plugin-installer.js.map +1 -0
- package/dist/services/setup/setup-manager.d.ts +158 -0
- package/dist/services/setup/setup-manager.js +2768 -0
- package/dist/services/setup/setup-manager.js.map +1 -0
- package/dist/services/system/cli-command.d.ts +5 -0
- package/dist/services/system/cli-command.js +18 -0
- package/dist/services/system/cli-command.js.map +1 -0
- package/dist/services/system/macos-launchd.js +312 -0
- package/dist/services/system/macos-launchd.js.map +1 -0
- package/dist/services/system/repair-orchestrator.d.ts +71 -0
- package/dist/services/system/repair-orchestrator.js +412 -0
- package/dist/services/system/repair-orchestrator.js.map +1 -0
- package/dist/services/system/system-monitor.js +96 -0
- package/dist/services/system/system-monitor.js.map +1 -0
- package/dist/services/system/system-ollama-provider.d.ts +14 -0
- package/dist/services/system/system-ollama-provider.js +129 -0
- package/dist/services/system/system-ollama-provider.js.map +1 -0
- package/dist/services/system/system-reconciler.d.ts +59 -0
- package/dist/services/system/system-reconciler.js +710 -0
- package/dist/services/system/system-reconciler.js.map +1 -0
- package/dist/services/system/update-manager.d.ts +43 -0
- package/dist/services/system/update-manager.js +315 -0
- package/dist/services/system/update-manager.js.map +1 -0
- package/dist/services/system/upgrade-finalize.d.ts +80 -0
- package/dist/services/system/upgrade-finalize.js +507 -0
- package/dist/services/system/upgrade-finalize.js.map +1 -0
- package/dist/services/tasks/registry.d.ts +44 -0
- package/dist/services/tasks/registry.js +90 -0
- package/dist/services/tasks/registry.js.map +1 -0
- package/dist/services/telemetry/activation.d.ts +6 -2
- package/dist/services/telemetry/activation.js +6 -2
- package/dist/services/telemetry/activation.js.map +1 -1
- package/dist/services/telemetry/heartbeat.d.ts +6 -2
- package/dist/services/telemetry/heartbeat.js +6 -2
- package/dist/services/telemetry/heartbeat.js.map +1 -1
- package/dist/services/workspaces/builder.d.ts +29 -0
- package/dist/services/workspaces/builder.js +186 -0
- package/dist/services/workspaces/builder.js.map +1 -0
- package/dist/types.d.ts +331 -45
- package/dist/utils/instance-lock.d.ts +2 -2
- package/dist/utils/instance-lock.js +2 -2
- package/install/jishu-install.sh +107 -26
- package/install/jishu-uninstall.sh +8 -0
- package/install/post-install.sh +162 -185
- package/install/post-uninstall.sh +6 -0
- package/node_modules/@fastify/static/.github/workflows/ci.yml +1 -1
- package/node_modules/@fastify/static/.github/workflows/lock-threads.yml +19 -0
- package/node_modules/@fastify/static/LICENSE +1 -3
- package/node_modules/@fastify/static/example/server-benchmark.js +39 -0
- package/node_modules/@fastify/static/index.js +169 -23
- package/node_modules/@fastify/static/lib/dirList.js +20 -6
- package/node_modules/@fastify/static/package.json +10 -8
- package/node_modules/@fastify/static/test/dir-list.test.js +82 -0
- package/node_modules/@fastify/static/test/static.test.js +326 -4
- package/node_modules/@fastify/static/types/index.d.ts +0 -4
- package/node_modules/@fastify/static/types/index.test-d.ts +1 -1
- package/node_modules/content-disposition/README.md +21 -22
- package/node_modules/content-disposition/index.js +122 -44
- package/node_modules/content-disposition/package.json +16 -20
- package/node_modules/glob/README.md +39 -130
- package/node_modules/glob/dist/commonjs/glob.d.ts +8 -0
- package/node_modules/glob/dist/commonjs/glob.d.ts.map +1 -1
- package/node_modules/glob/dist/commonjs/glob.js +2 -1
- package/node_modules/glob/dist/commonjs/glob.js.map +1 -1
- package/node_modules/glob/dist/commonjs/index.min.js +4 -0
- package/node_modules/glob/dist/commonjs/index.min.js.map +7 -0
- package/node_modules/glob/dist/commonjs/pattern.d.ts +3 -0
- package/node_modules/glob/dist/commonjs/pattern.d.ts.map +1 -1
- package/node_modules/glob/dist/commonjs/pattern.js +4 -0
- package/node_modules/glob/dist/commonjs/pattern.js.map +1 -1
- package/node_modules/glob/dist/esm/glob.d.ts +8 -0
- package/node_modules/glob/dist/esm/glob.d.ts.map +1 -1
- package/node_modules/glob/dist/esm/glob.js +2 -1
- package/node_modules/glob/dist/esm/glob.js.map +1 -1
- package/node_modules/glob/dist/esm/index.min.js +4 -0
- package/node_modules/glob/dist/esm/index.min.js.map +7 -0
- package/node_modules/glob/dist/esm/pattern.d.ts +3 -0
- package/node_modules/glob/dist/esm/pattern.d.ts.map +1 -1
- package/node_modules/glob/dist/esm/pattern.js +4 -0
- package/node_modules/glob/dist/esm/pattern.js.map +1 -1
- package/node_modules/glob/package.json +38 -37
- package/node_modules/jishushell-panel/README.md +4 -4
- package/node_modules/jishushell-panel/output/dist/server.js +17 -6
- package/node_modules/jishushell-panel/output/dist/server.js.map +1 -1
- package/node_modules/jishushell-panel/output/public/assets/ApiKeyField-NKcbHjNz.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/Dashboard-Da1fL38t.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/HermesChatPanel-DZvmYsoh.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/HermesConfigForm-BLUWlKwm.js +4 -0
- package/node_modules/jishushell-panel/output/public/assets/InitPassword-BAKsshzk.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/InstanceDetail-Dgyc_TX5.js +14 -0
- package/node_modules/jishushell-panel/output/public/assets/Login-DHeOmwI8.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/NewInstance-CIy0cYtp.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/ProviderRecommendations-H0ByEYF0.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/Settings-DAT-UMfP.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/Setup-g3uckFYR.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/WeixinLoginPanel-D-T6BxkQ.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/api-C70Gt678.js +4 -0
- package/node_modules/jishushell-panel/output/public/assets/index-DnnqTf7s.css +1 -0
- package/node_modules/jishushell-panel/output/public/assets/index-ERt6_ngA.js +23 -0
- package/node_modules/jishushell-panel/output/public/assets/registry-DF93EzIb.js +2 -0
- package/node_modules/jishushell-panel/output/public/assets/rolldown-runtime-QTnfLwEv.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/setup-task-q21GnI0E.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/usePolling-DeoThIQn.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/vendor-i18n-CS8DFbkQ.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/vendor-react-Cc84NArf.js +8 -0
- package/node_modules/jishushell-panel/output/public/index.html +6 -4
- package/node_modules/jishushell-panel/package.json +2 -2
- package/node_modules/semver/classes/range.js +11 -2
- package/node_modules/semver/package.json +2 -2
- package/package.json +12 -64
- package/scripts/check-app-path-boundaries.mjs +121 -0
- package/scripts/check-app-spec.mjs +127 -25
- package/scripts/check-colima-launchd.mjs +10 -8
- package/scripts/check-integration-isolation.ts +541 -0
- package/scripts/check-new-file-tests.mjs +11 -3
- package/scripts/check-open-core-boundaries.mjs +60 -10
- package/scripts/check-test-layering.sh +1 -1
- package/scripts/fixtures/instances/hermes-sample/instance.json +3 -2
- package/scripts/fixtures/instances/legacy-openclaw-sample/instance.json +1 -1
- package/scripts/local-web-upgrade-test.README +4 -3
- package/scripts/local-web-upgrade-test.example.env +2 -2
- package/scripts/local-web-upgrade-test.sh +14 -1
- package/scripts/pack-gui-and-send-pi.sh +41 -0
- package/scripts/perf/instances.js +1 -1
- package/scripts/prune-open-core-dist.mjs +89 -2
- package/scripts/smoke/hermes-bootstrap.sh +5 -5
- package/templates/hermes-entrypoint.sh +19 -29
- package/apps/openwebui-container.yaml +0 -97
- package/apps/playwright-container.yaml +0 -126
- package/dependencies/jishushell-panel-0.6.5.tgz +0 -0
- package/dist/crypto-shim.d.ts +0 -1
- package/dist/crypto-shim.js +0 -2
- package/dist/crypto-shim.js.map +0 -1
- package/dist/routes/agent-apps.d.ts +0 -14
- package/dist/routes/agent-apps.js +0 -77
- package/dist/routes/agent-apps.js.map +0 -1
- package/dist/routes/internal.d.ts +0 -2
- package/dist/routes/internal.js +0 -55
- package/dist/routes/internal.js.map +0 -1
- package/dist/routes/openclaw-routes.d.ts +0 -22
- package/dist/routes/openclaw-routes.js +0 -1020
- package/dist/routes/openclaw-routes.js.map +0 -1
- package/dist/routes/runtime.d.ts +0 -15
- package/dist/routes/runtime.js +0 -76
- package/dist/routes/runtime.js.map +0 -1
- package/dist/services/agent-apps/catalog.d.ts +0 -33
- package/dist/services/agent-apps/catalog.js +0 -88
- package/dist/services/agent-apps/catalog.js.map +0 -1
- package/dist/services/agent-apps/index.d.ts +0 -36
- package/dist/services/agent-apps/index.js +0 -171
- package/dist/services/agent-apps/index.js.map +0 -1
- package/dist/services/agent-apps/installers/adapter-probes.d.ts +0 -49
- package/dist/services/agent-apps/installers/adapter-probes.js +0 -230
- package/dist/services/agent-apps/installers/adapter-probes.js.map +0 -1
- package/dist/services/agent-apps/installers/adapter.d.ts +0 -30
- package/dist/services/agent-apps/installers/adapter.js +0 -171
- package/dist/services/agent-apps/installers/adapter.js.map +0 -1
- package/dist/services/agent-apps/installers/registry-probe.js.map +0 -1
- package/dist/services/agent-apps/installers/shell-script.d.ts +0 -47
- package/dist/services/agent-apps/installers/shell-script.js +0 -488
- package/dist/services/agent-apps/installers/shell-script.js.map +0 -1
- package/dist/services/agent-apps/types.d.ts +0 -128
- package/dist/services/agent-apps/types.js +0 -17
- package/dist/services/agent-apps/types.js.map +0 -1
- package/dist/services/app/app-compiler.js +0 -185
- package/dist/services/app/app-compiler.js.map +0 -1
- package/dist/services/app/app-manager.d.ts +0 -184
- package/dist/services/app/app-manager.js +0 -2933
- package/dist/services/app/app-manager.js.map +0 -1
- package/dist/services/app/custom-manager.d.ts +0 -27
- package/dist/services/app/custom-manager.js +0 -382
- package/dist/services/app/custom-manager.js.map +0 -1
- package/dist/services/app/hermes-agent-manager.d.ts +0 -20
- package/dist/services/app/hermes-agent-manager.js +0 -299
- package/dist/services/app/hermes-agent-manager.js.map +0 -1
- package/dist/services/app/id-normalizer.d.ts +0 -27
- package/dist/services/app/id-normalizer.js +0 -77
- package/dist/services/app/id-normalizer.js.map +0 -1
- package/dist/services/app/ollama-manager.d.ts +0 -18
- package/dist/services/app/ollama-manager.js +0 -224
- package/dist/services/app/ollama-manager.js.map +0 -1
- package/dist/services/app/openclaw-manager.d.ts +0 -63
- package/dist/services/app/openclaw-manager.js +0 -1215
- package/dist/services/app/openclaw-manager.js.map +0 -1
- package/dist/services/app/paths.d.ts +0 -27
- package/dist/services/app/paths.js +0 -40
- package/dist/services/app/paths.js.map +0 -1
- package/dist/services/app/platform-transform.d.ts +0 -32
- package/dist/services/app/platform-transform.js +0 -65
- package/dist/services/app/platform-transform.js.map +0 -1
- package/dist/services/app/provide-resolver.d.ts +0 -29
- package/dist/services/app/provide-resolver.js +0 -135
- package/dist/services/app/provide-resolver.js.map +0 -1
- package/dist/services/app/registry.d.ts +0 -17
- package/dist/services/app/registry.js +0 -31
- package/dist/services/app/registry.js.map +0 -1
- package/dist/services/app/remote-spec.d.ts +0 -14
- package/dist/services/app/remote-spec.js +0 -58
- package/dist/services/app/remote-spec.js.map +0 -1
- package/dist/services/app/terminal-session-manager.js +0 -157
- package/dist/services/app/terminal-session-manager.js.map +0 -1
- package/dist/services/app/types.d.ts +0 -74
- package/dist/services/app/types.js +0 -16
- package/dist/services/app/types.js.map +0 -1
- package/dist/services/app-config-admin.d.ts +0 -17
- package/dist/services/app-config-admin.js +0 -177
- package/dist/services/app-config-admin.js.map +0 -1
- package/dist/services/app-create-from-installed.d.ts +0 -23
- package/dist/services/app-create-from-installed.js +0 -75
- package/dist/services/app-create-from-installed.js.map +0 -1
- package/dist/services/app-passwords.js +0 -173
- package/dist/services/app-passwords.js.map +0 -1
- package/dist/services/backup-admin.d.ts +0 -101
- package/dist/services/backup-admin.js +0 -259
- package/dist/services/backup-admin.js.map +0 -1
- package/dist/services/backup-manager.d.ts +0 -264
- package/dist/services/backup-manager.js +0 -2263
- package/dist/services/backup-manager.js.map +0 -1
- package/dist/services/backup-verify.js +0 -240
- package/dist/services/backup-verify.js.map +0 -1
- package/dist/services/capability-endpoint-validator.d.ts +0 -41
- package/dist/services/capability-endpoint-validator.js +0 -114
- package/dist/services/capability-endpoint-validator.js.map +0 -1
- package/dist/services/capability-health.d.ts +0 -16
- package/dist/services/capability-health.js +0 -121
- package/dist/services/capability-health.js.map +0 -1
- package/dist/services/capability-registry.d.ts +0 -29
- package/dist/services/capability-registry.js +0 -176
- package/dist/services/capability-registry.js.map +0 -1
- package/dist/services/capability-sync.d.ts +0 -4
- package/dist/services/capability-sync.js +0 -220
- package/dist/services/capability-sync.js.map +0 -1
- package/dist/services/connection-admin.d.ts +0 -74
- package/dist/services/connection-admin.js +0 -287
- package/dist/services/connection-admin.js.map +0 -1
- package/dist/services/connection-apply.d.ts +0 -91
- package/dist/services/connection-apply.js +0 -471
- package/dist/services/connection-apply.js.map +0 -1
- package/dist/services/connection-resolver.d.ts +0 -65
- package/dist/services/connection-resolver.js +0 -281
- package/dist/services/connection-resolver.js.map +0 -1
- package/dist/services/connection-transactor.d.ts +0 -39
- package/dist/services/connection-transactor.js +0 -354
- package/dist/services/connection-transactor.js.map +0 -1
- package/dist/services/core-manager.d.ts +0 -50
- package/dist/services/core-manager.js +0 -411
- package/dist/services/core-manager.js.map +0 -1
- package/dist/services/external-mounts.js +0 -187
- package/dist/services/external-mounts.js.map +0 -1
- package/dist/services/files-manager.d.ts +0 -252
- package/dist/services/files-manager.js +0 -1156
- package/dist/services/files-manager.js.map +0 -1
- package/dist/services/files-mounts.d.ts +0 -42
- package/dist/services/files-mounts.js +0 -207
- package/dist/services/files-mounts.js.map +0 -1
- package/dist/services/instance-admin.d.ts +0 -26
- package/dist/services/instance-admin.js +0 -218
- package/dist/services/instance-admin.js.map +0 -1
- package/dist/services/instance-manager.d.ts +0 -192
- package/dist/services/instance-manager.js +0 -1289
- package/dist/services/instance-manager.js.map +0 -1
- package/dist/services/macos-launchd.js +0 -312
- package/dist/services/macos-launchd.js.map +0 -1
- package/dist/services/nomad-manager.d.ts +0 -307
- package/dist/services/nomad-manager.js +0 -3958
- package/dist/services/nomad-manager.js.map +0 -1
- package/dist/services/organize/applier.js +0 -218
- package/dist/services/organize/applier.js.map +0 -1
- package/dist/services/organize/rules.js +0 -286
- package/dist/services/organize/rules.js.map +0 -1
- package/dist/services/organize/scanner.js +0 -366
- package/dist/services/organize/scanner.js.map +0 -1
- package/dist/services/organize/store.js +0 -82
- package/dist/services/organize/store.js.map +0 -1
- package/dist/services/plugin-installer.js +0 -128
- package/dist/services/plugin-installer.js.map +0 -1
- package/dist/services/process-manager.d.ts +0 -25
- package/dist/services/process-manager.js +0 -568
- package/dist/services/process-manager.js.map +0 -1
- package/dist/services/runtime/adapters/custom.d.ts +0 -20
- package/dist/services/runtime/adapters/custom.js +0 -188
- package/dist/services/runtime/adapters/custom.js.map +0 -1
- package/dist/services/runtime/adapters/hermes.d.ts +0 -204
- package/dist/services/runtime/adapters/hermes.js +0 -1684
- package/dist/services/runtime/adapters/hermes.js.map +0 -1
- package/dist/services/runtime/adapters/openclaw-mcporter.d.ts +0 -45
- package/dist/services/runtime/adapters/openclaw-mcporter.js +0 -108
- package/dist/services/runtime/adapters/openclaw-mcporter.js.map +0 -1
- package/dist/services/runtime/adapters/openclaw.d.ts +0 -426
- package/dist/services/runtime/adapters/openclaw.js +0 -3975
- package/dist/services/runtime/adapters/openclaw.js.map +0 -1
- package/dist/services/runtime/index.d.ts +0 -34
- package/dist/services/runtime/index.js +0 -51
- package/dist/services/runtime/index.js.map +0 -1
- package/dist/services/runtime/mcp-shims/anythingllm-shim.d.ts +0 -46
- package/dist/services/runtime/mcp-shims/anythingllm-shim.js +0 -281
- package/dist/services/runtime/mcp-shims/anythingllm-shim.js.map +0 -1
- package/dist/services/runtime/mcp-shims/drive-shim.js +0 -490
- package/dist/services/runtime/mcp-shims/drive-shim.js.map +0 -1
- package/dist/services/runtime/mcp-shims/jishukb-shim.d.ts +0 -48
- package/dist/services/runtime/mcp-shims/jishukb-shim.js +0 -723
- package/dist/services/runtime/mcp-shims/jishukb-shim.js.map +0 -1
- package/dist/services/runtime/mcp-shims/mcporter-lite.js +0 -276
- package/dist/services/runtime/mcp-shims/mcporter-lite.js.map +0 -1
- package/dist/services/runtime/migrations.d.ts +0 -23
- package/dist/services/runtime/migrations.js +0 -125
- package/dist/services/runtime/migrations.js.map +0 -1
- package/dist/services/runtime/registry.d.ts +0 -13
- package/dist/services/runtime/registry.js +0 -32
- package/dist/services/runtime/registry.js.map +0 -1
- package/dist/services/runtime-identity.d.ts +0 -13
- package/dist/services/runtime-identity.js +0 -166
- package/dist/services/runtime-identity.js.map +0 -1
- package/dist/services/runtime-repair.d.ts +0 -52
- package/dist/services/runtime-repair.js +0 -352
- package/dist/services/runtime-repair.js.map +0 -1
- package/dist/services/setup-manager.d.ts +0 -158
- package/dist/services/setup-manager.js +0 -2740
- package/dist/services/setup-manager.js.map +0 -1
- package/dist/services/suggestions.d.ts +0 -27
- package/dist/services/suggestions.js +0 -133
- package/dist/services/suggestions.js.map +0 -1
- package/dist/services/system-monitor.js +0 -79
- package/dist/services/system-monitor.js.map +0 -1
- package/dist/services/system-ollama-provider.d.ts +0 -14
- package/dist/services/system-ollama-provider.js +0 -125
- package/dist/services/system-ollama-provider.js.map +0 -1
- package/dist/services/system-reconciler.d.ts +0 -72
- package/dist/services/system-reconciler.js +0 -600
- package/dist/services/system-reconciler.js.map +0 -1
- package/dist/services/task-registry.d.ts +0 -44
- package/dist/services/task-registry.js +0 -76
- package/dist/services/task-registry.js.map +0 -1
- package/dist/services/types-shim.d.ts +0 -16
- package/dist/services/types-shim.js +0 -2
- package/dist/services/types-shim.js.map +0 -1
- package/dist/services/update-manager.d.ts +0 -47
- package/dist/services/update-manager.js +0 -351
- package/dist/services/update-manager.js.map +0 -1
- package/dist/services/webdav/server.d.ts +0 -24
- package/dist/services/webdav/server.js +0 -420
- package/dist/services/webdav/server.js.map +0 -1
- package/dist/services/webdav/xml-builder.js.map +0 -1
- package/dist/services/workspace-builder.d.ts +0 -29
- package/dist/services/workspace-builder.js +0 -188
- package/dist/services/workspace-builder.js.map +0 -1
- package/node_modules/@fastify/static/.github/stale.yml +0 -21
- package/node_modules/@isaacs/cliui/LICENSE.md +0 -63
- package/node_modules/@isaacs/cliui/README.md +0 -151
- package/node_modules/@isaacs/cliui/dist/commonjs/ansi-regex/index.d.ts +0 -4
- package/node_modules/@isaacs/cliui/dist/commonjs/ansi-regex/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/ansi-regex/index.js +0 -16
- package/node_modules/@isaacs/cliui/dist/commonjs/ansi-regex/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/ansi-styles/index.d.ts +0 -34
- package/node_modules/@isaacs/cliui/dist/commonjs/ansi-styles/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/ansi-styles/index.js +0 -170
- package/node_modules/@isaacs/cliui/dist/commonjs/ansi-styles/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/eastasianwidth/index.d.ts +0 -6
- package/node_modules/@isaacs/cliui/dist/commonjs/eastasianwidth/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/eastasianwidth/index.js +0 -307
- package/node_modules/@isaacs/cliui/dist/commonjs/eastasianwidth/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/emoji-regex/index.d.ts +0 -2
- package/node_modules/@isaacs/cliui/dist/commonjs/emoji-regex/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/emoji-regex/index.js +0 -7
- package/node_modules/@isaacs/cliui/dist/commonjs/emoji-regex/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/index.d.ts +0 -41
- package/node_modules/@isaacs/cliui/dist/commonjs/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/index.js +0 -322
- package/node_modules/@isaacs/cliui/dist/commonjs/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/index.min.js +0 -12
- package/node_modules/@isaacs/cliui/dist/commonjs/index.min.js.map +0 -7
- package/node_modules/@isaacs/cliui/dist/commonjs/package.json +0 -3
- package/node_modules/@isaacs/cliui/dist/commonjs/string-width/index.d.ts +0 -5
- package/node_modules/@isaacs/cliui/dist/commonjs/string-width/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/string-width/index.js +0 -49
- package/node_modules/@isaacs/cliui/dist/commonjs/string-width/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/strip-ansi/index.d.ts +0 -2
- package/node_modules/@isaacs/cliui/dist/commonjs/strip-ansi/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/strip-ansi/index.js +0 -8
- package/node_modules/@isaacs/cliui/dist/commonjs/strip-ansi/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/wrap-ansi/index.d.ts +0 -7
- package/node_modules/@isaacs/cliui/dist/commonjs/wrap-ansi/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/wrap-ansi/index.js +0 -176
- package/node_modules/@isaacs/cliui/dist/commonjs/wrap-ansi/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/ansi-regex/index.d.ts +0 -4
- package/node_modules/@isaacs/cliui/dist/esm/ansi-regex/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/ansi-regex/index.js +0 -12
- package/node_modules/@isaacs/cliui/dist/esm/ansi-regex/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/ansi-styles/index.d.ts +0 -34
- package/node_modules/@isaacs/cliui/dist/esm/ansi-styles/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/ansi-styles/index.js +0 -167
- package/node_modules/@isaacs/cliui/dist/esm/ansi-styles/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/eastasianwidth/index.d.ts +0 -6
- package/node_modules/@isaacs/cliui/dist/esm/eastasianwidth/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/eastasianwidth/index.js +0 -299
- package/node_modules/@isaacs/cliui/dist/esm/eastasianwidth/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/emoji-regex/index.d.ts +0 -2
- package/node_modules/@isaacs/cliui/dist/esm/emoji-regex/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/emoji-regex/index.js +0 -3
- package/node_modules/@isaacs/cliui/dist/esm/emoji-regex/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/index.d.ts +0 -41
- package/node_modules/@isaacs/cliui/dist/esm/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/index.js +0 -317
- package/node_modules/@isaacs/cliui/dist/esm/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/index.min.js +0 -12
- package/node_modules/@isaacs/cliui/dist/esm/index.min.js.map +0 -7
- package/node_modules/@isaacs/cliui/dist/esm/package.json +0 -3
- package/node_modules/@isaacs/cliui/dist/esm/string-width/index.d.ts +0 -5
- package/node_modules/@isaacs/cliui/dist/esm/string-width/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/string-width/index.js +0 -46
- package/node_modules/@isaacs/cliui/dist/esm/string-width/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/strip-ansi/index.d.ts +0 -2
- package/node_modules/@isaacs/cliui/dist/esm/strip-ansi/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/strip-ansi/index.js +0 -4
- package/node_modules/@isaacs/cliui/dist/esm/strip-ansi/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/wrap-ansi/index.d.ts +0 -7
- package/node_modules/@isaacs/cliui/dist/esm/wrap-ansi/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/wrap-ansi/index.js +0 -172
- package/node_modules/@isaacs/cliui/dist/esm/wrap-ansi/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/package.json +0 -163
- package/node_modules/content-disposition/HISTORY.md +0 -60
- package/node_modules/cross-spawn/LICENSE +0 -21
- package/node_modules/cross-spawn/README.md +0 -89
- package/node_modules/cross-spawn/index.js +0 -39
- package/node_modules/cross-spawn/lib/enoent.js +0 -59
- package/node_modules/cross-spawn/lib/parse.js +0 -91
- package/node_modules/cross-spawn/lib/util/escape.js +0 -47
- package/node_modules/cross-spawn/lib/util/readShebang.js +0 -23
- package/node_modules/cross-spawn/lib/util/resolveCommand.js +0 -52
- package/node_modules/cross-spawn/package.json +0 -73
- package/node_modules/foreground-child/LICENSE +0 -15
- package/node_modules/foreground-child/README.md +0 -128
- package/node_modules/foreground-child/dist/commonjs/all-signals.d.ts +0 -2
- package/node_modules/foreground-child/dist/commonjs/all-signals.d.ts.map +0 -1
- package/node_modules/foreground-child/dist/commonjs/all-signals.js +0 -58
- package/node_modules/foreground-child/dist/commonjs/all-signals.js.map +0 -1
- package/node_modules/foreground-child/dist/commonjs/index.d.ts +0 -58
- package/node_modules/foreground-child/dist/commonjs/index.d.ts.map +0 -1
- package/node_modules/foreground-child/dist/commonjs/index.js +0 -123
- package/node_modules/foreground-child/dist/commonjs/index.js.map +0 -1
- package/node_modules/foreground-child/dist/commonjs/package.json +0 -3
- package/node_modules/foreground-child/dist/commonjs/proxy-signals.d.ts +0 -6
- package/node_modules/foreground-child/dist/commonjs/proxy-signals.d.ts.map +0 -1
- package/node_modules/foreground-child/dist/commonjs/proxy-signals.js +0 -38
- package/node_modules/foreground-child/dist/commonjs/proxy-signals.js.map +0 -1
- package/node_modules/foreground-child/dist/commonjs/watchdog.d.ts +0 -10
- package/node_modules/foreground-child/dist/commonjs/watchdog.d.ts.map +0 -1
- package/node_modules/foreground-child/dist/commonjs/watchdog.js +0 -50
- package/node_modules/foreground-child/dist/commonjs/watchdog.js.map +0 -1
- package/node_modules/foreground-child/dist/esm/all-signals.d.ts +0 -2
- package/node_modules/foreground-child/dist/esm/all-signals.d.ts.map +0 -1
- package/node_modules/foreground-child/dist/esm/all-signals.js +0 -52
- package/node_modules/foreground-child/dist/esm/all-signals.js.map +0 -1
- package/node_modules/foreground-child/dist/esm/index.d.ts +0 -58
- package/node_modules/foreground-child/dist/esm/index.d.ts.map +0 -1
- package/node_modules/foreground-child/dist/esm/index.js +0 -115
- package/node_modules/foreground-child/dist/esm/index.js.map +0 -1
- package/node_modules/foreground-child/dist/esm/package.json +0 -3
- package/node_modules/foreground-child/dist/esm/proxy-signals.d.ts +0 -6
- package/node_modules/foreground-child/dist/esm/proxy-signals.d.ts.map +0 -1
- package/node_modules/foreground-child/dist/esm/proxy-signals.js +0 -34
- package/node_modules/foreground-child/dist/esm/proxy-signals.js.map +0 -1
- package/node_modules/foreground-child/dist/esm/watchdog.d.ts +0 -10
- package/node_modules/foreground-child/dist/esm/watchdog.d.ts.map +0 -1
- package/node_modules/foreground-child/dist/esm/watchdog.js +0 -46
- package/node_modules/foreground-child/dist/esm/watchdog.js.map +0 -1
- package/node_modules/foreground-child/package.json +0 -106
- package/node_modules/glob/dist/esm/bin.d.mts +0 -3
- package/node_modules/glob/dist/esm/bin.d.mts.map +0 -1
- package/node_modules/glob/dist/esm/bin.mjs +0 -346
- package/node_modules/glob/dist/esm/bin.mjs.map +0 -1
- package/node_modules/isexe/.npmignore +0 -2
- package/node_modules/isexe/LICENSE +0 -15
- package/node_modules/isexe/README.md +0 -51
- package/node_modules/isexe/index.js +0 -57
- package/node_modules/isexe/mode.js +0 -41
- package/node_modules/isexe/package.json +0 -31
- package/node_modules/isexe/test/basic.js +0 -221
- package/node_modules/isexe/windows.js +0 -42
- package/node_modules/jackspeak/LICENSE.md +0 -55
- package/node_modules/jackspeak/README.md +0 -394
- package/node_modules/jackspeak/dist/commonjs/index.d.ts +0 -323
- package/node_modules/jackspeak/dist/commonjs/index.d.ts.map +0 -1
- package/node_modules/jackspeak/dist/commonjs/index.js +0 -944
- package/node_modules/jackspeak/dist/commonjs/index.js.map +0 -1
- package/node_modules/jackspeak/dist/commonjs/index.min.js +0 -33
- package/node_modules/jackspeak/dist/commonjs/index.min.js.map +0 -7
- package/node_modules/jackspeak/dist/commonjs/package.json +0 -3
- package/node_modules/jackspeak/dist/esm/index.d.ts +0 -323
- package/node_modules/jackspeak/dist/esm/index.d.ts.map +0 -1
- package/node_modules/jackspeak/dist/esm/index.js +0 -936
- package/node_modules/jackspeak/dist/esm/index.js.map +0 -1
- package/node_modules/jackspeak/dist/esm/index.min.js +0 -33
- package/node_modules/jackspeak/dist/esm/index.min.js.map +0 -7
- package/node_modules/jackspeak/dist/esm/package.json +0 -3
- package/node_modules/jackspeak/package.json +0 -115
- package/node_modules/jishushell-panel/output/public/assets/ApiKeyField-D1i7zWXR.js +0 -1
- package/node_modules/jishushell-panel/output/public/assets/Dashboard-sWIvL43F.js +0 -1
- package/node_modules/jishushell-panel/output/public/assets/HermesChatPanel-DQ8RyvQY.js +0 -1
- package/node_modules/jishushell-panel/output/public/assets/HermesConfigForm-tIbPP1sB.js +0 -4
- package/node_modules/jishushell-panel/output/public/assets/InitPassword-C3Slq3Dd.js +0 -1
- package/node_modules/jishushell-panel/output/public/assets/InstanceDetail-7JqY9tq4.js +0 -92
- package/node_modules/jishushell-panel/output/public/assets/Login-BXLDJlQN.js +0 -1
- package/node_modules/jishushell-panel/output/public/assets/NewInstance-dLc5Xrpx.js +0 -1
- package/node_modules/jishushell-panel/output/public/assets/ProviderRecommendations-DIAXxesl.js +0 -1
- package/node_modules/jishushell-panel/output/public/assets/Settings-Bd5utbBh.js +0 -1
- package/node_modules/jishushell-panel/output/public/assets/Setup-Yn9_20FL.js +0 -1
- package/node_modules/jishushell-panel/output/public/assets/WeixinLoginPanel-C21doQTJ.js +0 -9
- package/node_modules/jishushell-panel/output/public/assets/index-CCkaIEjn.js +0 -20
- package/node_modules/jishushell-panel/output/public/assets/index-D7qxy-Vh.css +0 -1
- package/node_modules/jishushell-panel/output/public/assets/registry-B2ZQZXWL.js +0 -2
- package/node_modules/jishushell-panel/output/public/assets/usePolling-BFZm4do_.js +0 -1
- package/node_modules/jishushell-panel/output/public/assets/vendor-i18n-DqPtOicc.js +0 -9
- package/node_modules/jishushell-panel/output/public/assets/vendor-react-DW5juQin.js +0 -59
- package/node_modules/package-json-from-dist/LICENSE.md +0 -63
- package/node_modules/package-json-from-dist/README.md +0 -110
- package/node_modules/package-json-from-dist/dist/commonjs/index.d.ts +0 -89
- package/node_modules/package-json-from-dist/dist/commonjs/index.d.ts.map +0 -1
- package/node_modules/package-json-from-dist/dist/commonjs/index.js +0 -134
- package/node_modules/package-json-from-dist/dist/commonjs/index.js.map +0 -1
- package/node_modules/package-json-from-dist/dist/commonjs/package.json +0 -3
- package/node_modules/package-json-from-dist/dist/esm/index.d.ts +0 -89
- package/node_modules/package-json-from-dist/dist/esm/index.d.ts.map +0 -1
- package/node_modules/package-json-from-dist/dist/esm/index.js +0 -129
- package/node_modules/package-json-from-dist/dist/esm/index.js.map +0 -1
- package/node_modules/package-json-from-dist/dist/esm/package.json +0 -3
- package/node_modules/package-json-from-dist/package.json +0 -68
- package/node_modules/path-key/index.d.ts +0 -40
- package/node_modules/path-key/index.js +0 -16
- package/node_modules/path-key/license +0 -9
- package/node_modules/path-key/package.json +0 -39
- package/node_modules/path-key/readme.md +0 -61
- package/node_modules/safe-buffer/LICENSE +0 -21
- package/node_modules/safe-buffer/README.md +0 -584
- package/node_modules/safe-buffer/index.d.ts +0 -187
- package/node_modules/safe-buffer/index.js +0 -65
- package/node_modules/safe-buffer/package.json +0 -51
- package/node_modules/shebang-command/index.js +0 -19
- package/node_modules/shebang-command/license +0 -9
- package/node_modules/shebang-command/package.json +0 -34
- package/node_modules/shebang-command/readme.md +0 -34
- package/node_modules/shebang-regex/index.d.ts +0 -22
- package/node_modules/shebang-regex/index.js +0 -2
- package/node_modules/shebang-regex/license +0 -9
- package/node_modules/shebang-regex/package.json +0 -35
- package/node_modules/shebang-regex/readme.md +0 -33
- package/node_modules/signal-exit/LICENSE.txt +0 -16
- package/node_modules/signal-exit/README.md +0 -74
- package/node_modules/signal-exit/dist/cjs/browser.d.ts +0 -12
- package/node_modules/signal-exit/dist/cjs/browser.d.ts.map +0 -1
- package/node_modules/signal-exit/dist/cjs/browser.js +0 -10
- package/node_modules/signal-exit/dist/cjs/browser.js.map +0 -1
- package/node_modules/signal-exit/dist/cjs/index.d.ts +0 -48
- package/node_modules/signal-exit/dist/cjs/index.d.ts.map +0 -1
- package/node_modules/signal-exit/dist/cjs/index.js +0 -279
- package/node_modules/signal-exit/dist/cjs/index.js.map +0 -1
- package/node_modules/signal-exit/dist/cjs/package.json +0 -3
- package/node_modules/signal-exit/dist/cjs/signals.d.ts +0 -29
- package/node_modules/signal-exit/dist/cjs/signals.d.ts.map +0 -1
- package/node_modules/signal-exit/dist/cjs/signals.js +0 -42
- package/node_modules/signal-exit/dist/cjs/signals.js.map +0 -1
- package/node_modules/signal-exit/dist/mjs/browser.d.ts +0 -12
- package/node_modules/signal-exit/dist/mjs/browser.d.ts.map +0 -1
- package/node_modules/signal-exit/dist/mjs/browser.js +0 -4
- package/node_modules/signal-exit/dist/mjs/browser.js.map +0 -1
- package/node_modules/signal-exit/dist/mjs/index.d.ts +0 -48
- package/node_modules/signal-exit/dist/mjs/index.d.ts.map +0 -1
- package/node_modules/signal-exit/dist/mjs/index.js +0 -275
- package/node_modules/signal-exit/dist/mjs/index.js.map +0 -1
- package/node_modules/signal-exit/dist/mjs/package.json +0 -3
- package/node_modules/signal-exit/dist/mjs/signals.d.ts +0 -29
- package/node_modules/signal-exit/dist/mjs/signals.d.ts.map +0 -1
- package/node_modules/signal-exit/dist/mjs/signals.js +0 -39
- package/node_modules/signal-exit/dist/mjs/signals.js.map +0 -1
- package/node_modules/signal-exit/package.json +0 -106
- package/node_modules/which/CHANGELOG.md +0 -166
- package/node_modules/which/LICENSE +0 -15
- package/node_modules/which/README.md +0 -54
- package/node_modules/which/bin/node-which +0 -52
- package/node_modules/which/package.json +0 -43
- package/node_modules/which/which.js +0 -125
- package/scripts/check-adapter-isolation.ts +0 -293
- /package/dist/services/{app → app-common}/app-compiler.d.ts +0 -0
- /package/dist/services/{app → app-common}/terminal-session-manager.d.ts +0 -0
- /package/dist/services/{backup-verify.d.ts → backup/backup-verify.d.ts} +0 -0
- /package/dist/services/{external-mounts.d.ts → files/external-mounts.d.ts} +0 -0
- /package/dist/services/{organize → files/organize}/applier.d.ts +0 -0
- /package/dist/services/{organize → files/organize}/rules.d.ts +0 -0
- /package/dist/services/{organize → files/organize}/scanner.d.ts +0 -0
- /package/dist/services/{organize → files/organize}/store.d.ts +0 -0
- /package/dist/services/{webdav → files/webdav}/xml-builder.d.ts +0 -0
- /package/dist/services/{webdav → files/webdav}/xml-builder.js +0 -0
- /package/dist/services/{app-passwords.d.ts → instances/passwords.d.ts} +0 -0
- /package/dist/services/{agent-apps → integrations/installable}/installers/registry-probe.d.ts +0 -0
- /package/dist/services/{agent-apps → integrations/installable}/installers/registry-probe.js +0 -0
- /package/dist/services/{runtime/mcp-shims → integrations/openclaw}/drive-shim.d.ts +0 -0
- /package/dist/services/{runtime/mcp-shims → integrations/openclaw}/mcporter-lite.d.ts +0 -0
- /package/dist/services/{plugin-installer.d.ts → setup/plugin-installer.d.ts} +0 -0
- /package/dist/services/{macos-launchd.d.ts → system/macos-launchd.d.ts} +0 -0
- /package/dist/services/{system-monitor.d.ts → system/system-monitor.d.ts} +0 -0
|
@@ -0,0 +1,2768 @@
|
|
|
1
|
+
import { execFileSync, execSync, spawn as nodeSpawn } from "child_process";
|
|
2
|
+
import { chmodSync, copyFileSync, existsSync, lstatSync, mkdtempSync, readFileSync, renameSync, rmSync, symlinkSync, unlinkSync } from "fs";
|
|
3
|
+
import { userInfo, platform as osPlatform, arch as osArch, cpus as osCpus } from "node:os";
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { basename, dirname, join } from "path";
|
|
7
|
+
import { StringDecoder } from "string_decoder";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
import { JISHUSHELL_HOME, getCoreConfig, saveCoreConfig, setOpenclawDockerImage, getOpenclawDockerImage, DEFAULT_OPENCLAW_DOCKER_IMAGE, getIntegrationRuntimeEntry, OPENCLAW_MODULES, OPENCLAW_BIN_DIR, } from "../../config.js";
|
|
10
|
+
import { ensureDirContainer, ensureDirHost, writeConfigFile, writeSecretFile, writeExecutableFile, writeSystemTmpFile } from "../../utils/fs.js";
|
|
11
|
+
import { buildDockerClientEnv, managedColimaSocketPath, managedColimaDockerHost, resolveDockerHost } from "../../utils/docker-host.js";
|
|
12
|
+
import { appleSiliconGuard, buildColimaWrapperScript, buildColimaPlist, buildNomadWaitWrapper, buildNomadPlist, buildCoreGateWrapper, buildCorePlist, buildPanelPlist, } from "../system/macos-launchd.js";
|
|
13
|
+
import { getIntegration, listRegisteredIntegrations } from "../integrations/index.js";
|
|
14
|
+
import { hasLlmProviderSetup } from "../llm-proxy/providers.js";
|
|
15
|
+
// Internal usage of task primitives — re-exports further down preserve the
|
|
16
|
+
// public import surface used by integration modules, routes, and CLI.
|
|
17
|
+
import { createTask, emitTask, getRunningTasks } from "../tasks/registry.js";
|
|
18
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
+
const __dirname = dirname(__filename);
|
|
20
|
+
// ── Paths ──────────────────────────────────────────────────────────
|
|
21
|
+
const BIN_DIR = join(JISHUSHELL_HOME, "bin");
|
|
22
|
+
const NOMAD_BIN = join(BIN_DIR, "nomad");
|
|
23
|
+
const NOMAD_CONFIG_DIR = join(JISHUSHELL_HOME, "nomad");
|
|
24
|
+
const NOMAD_DATA_DIR = join(JISHUSHELL_HOME, "nomad", "data");
|
|
25
|
+
const NOMAD_ALLOC_DIR = join(JISHUSHELL_HOME, "nomad", "data", "alloc");
|
|
26
|
+
const _COLIMA_DIR = join(JISHUSHELL_HOME, "colima");
|
|
27
|
+
const COLIMA_PROFILE = "jishushell";
|
|
28
|
+
const _COLIMA_SOCKET = managedColimaSocketPath(JISHUSHELL_HOME, COLIMA_PROFILE);
|
|
29
|
+
const NOMAD_VERSION = "1.6.5";
|
|
30
|
+
const DEFAULT_CORE_PORT = 8091;
|
|
31
|
+
const DEFAULT_PANEL_PORT = 8090;
|
|
32
|
+
const SYSTEMD_CORE_SERVICE_PATH = "/etc/systemd/system/jishushell.service";
|
|
33
|
+
const SYSTEMD_PANEL_SERVICE_PATH = "/etc/systemd/system/jishushell-panel.service";
|
|
34
|
+
const SYSTEMD_NOMAD_SERVICE_PATH = "/etc/systemd/system/nomad.service";
|
|
35
|
+
let _serverPort = DEFAULT_CORE_PORT;
|
|
36
|
+
export function setServerPort(port) { _serverPort = port; }
|
|
37
|
+
function shellQuote(value) {
|
|
38
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
39
|
+
}
|
|
40
|
+
function sudoCopySystemFile(content, destination, mode) {
|
|
41
|
+
const stagingDir = mkdtempSync(join(tmpdir(), "jishushell-system-"));
|
|
42
|
+
const stagingPath = join(stagingDir, basename(destination));
|
|
43
|
+
try {
|
|
44
|
+
writeSystemTmpFile(stagingPath, content);
|
|
45
|
+
execFileSync("sudo", ["cp", stagingPath, destination], { timeout: 5000 });
|
|
46
|
+
execFileSync("sudo", ["chown", "root:root", destination], { timeout: 5000 });
|
|
47
|
+
execFileSync("sudo", ["chmod", mode, destination], { timeout: 5000 });
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
rmSync(stagingDir, { recursive: true, force: true });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function removeSymlinkIfPresent(path) {
|
|
54
|
+
try {
|
|
55
|
+
if (lstatSync(path).isSymbolicLink())
|
|
56
|
+
unlinkSync(path);
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
if (e?.code !== "ENOENT")
|
|
60
|
+
throw e;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function chownToUser(path, user, recursive = false) {
|
|
64
|
+
if (!user)
|
|
65
|
+
return;
|
|
66
|
+
const args = recursive ? ["-R", `${user}:`, path] : [`${user}:`, path];
|
|
67
|
+
const timeout = recursive ? 60000 : 5000;
|
|
68
|
+
if (typeof process.getuid === "function" && process.getuid() === 0) {
|
|
69
|
+
execFileSync("chown", args, { timeout });
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
execFileSync("sudo", ["chown", ...args], { timeout });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function writeManagedExecutableFile(path, content, ownerUser) {
|
|
76
|
+
try {
|
|
77
|
+
writeExecutableFile(path, content);
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
if (e?.code !== "EACCES" || !ownerUser)
|
|
81
|
+
throw e;
|
|
82
|
+
sudoCopySystemFile(content, path, "755");
|
|
83
|
+
}
|
|
84
|
+
chownToUser(path, ownerUser);
|
|
85
|
+
}
|
|
86
|
+
function resolveCoreCliBin() {
|
|
87
|
+
const candidates = [
|
|
88
|
+
process.env.JISHUSHELL_CORE_BIN,
|
|
89
|
+
join(__dirname, "..", "cli.js"),
|
|
90
|
+
]
|
|
91
|
+
.map((candidate) => candidate?.trim())
|
|
92
|
+
.filter((candidate) => Boolean(candidate));
|
|
93
|
+
for (const candidate of candidates) {
|
|
94
|
+
if (existsSync(candidate))
|
|
95
|
+
return candidate;
|
|
96
|
+
}
|
|
97
|
+
throw new Error("Cannot locate jishushell entrypoint. Set JISHUSHELL_CORE_BIN to the bundled CLI path.");
|
|
98
|
+
}
|
|
99
|
+
function resolveBundledPanelServerBin() {
|
|
100
|
+
const packageRoot = join(__dirname, "..", "..", "..");
|
|
101
|
+
const candidates = [
|
|
102
|
+
process.env.JISHUSHELL_PANEL_BIN,
|
|
103
|
+
join(packageRoot, "node_modules", "jishushell-panel", "output", "dist", "server.js"),
|
|
104
|
+
]
|
|
105
|
+
.map((candidate) => candidate?.trim())
|
|
106
|
+
.filter((candidate) => Boolean(candidate));
|
|
107
|
+
for (const candidate of candidates) {
|
|
108
|
+
if (existsSync(candidate))
|
|
109
|
+
return candidate;
|
|
110
|
+
}
|
|
111
|
+
throw new Error("Cannot locate bundled jishushell-panel entrypoint. Install the GUI jishushell package or set JISHUSHELL_PANEL_BIN.");
|
|
112
|
+
}
|
|
113
|
+
function buildWrapperEnvPrelude() {
|
|
114
|
+
return `_WRAPPER_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
115
|
+
_DEFAULT_JISHUSHELL_HOME="$(cd "$_WRAPPER_DIR/.." && pwd)"
|
|
116
|
+
|
|
117
|
+
_resolve_sudo_home() {
|
|
118
|
+
if [ -n "\${SUDO_UID:-}" ] && command -v getent >/dev/null 2>&1; then
|
|
119
|
+
_entry="$(getent passwd "$SUDO_UID" 2>/dev/null || true)"
|
|
120
|
+
if [ -n "$_entry" ]; then
|
|
121
|
+
printf '%s\\n' "$_entry" | cut -d: -f6
|
|
122
|
+
return
|
|
123
|
+
fi
|
|
124
|
+
fi
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
_SUDO_HOME="$(_resolve_sudo_home || true)"
|
|
128
|
+
if [ -z "\${JISHUSHELL_HOME:-}" ]; then
|
|
129
|
+
case "$_DEFAULT_JISHUSHELL_HOME" in
|
|
130
|
+
/usr|/usr/local|/opt/homebrew|/opt/local)
|
|
131
|
+
if [ -n "$_SUDO_HOME" ]; then
|
|
132
|
+
JISHUSHELL_HOME="$_SUDO_HOME/.jishushell"
|
|
133
|
+
else
|
|
134
|
+
JISHUSHELL_HOME="$_DEFAULT_JISHUSHELL_HOME"
|
|
135
|
+
fi
|
|
136
|
+
;;
|
|
137
|
+
*) JISHUSHELL_HOME="$_DEFAULT_JISHUSHELL_HOME" ;;
|
|
138
|
+
esac
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
_resolve_home() {
|
|
142
|
+
if [ -n "$_SUDO_HOME" ]; then
|
|
143
|
+
printf '%s\\n' "$_SUDO_HOME"
|
|
144
|
+
return
|
|
145
|
+
fi
|
|
146
|
+
case "$JISHUSHELL_HOME" in
|
|
147
|
+
*/.jishushell) printf '%s\\n' "\${JISHUSHELL_HOME%/.jishushell}" ;;
|
|
148
|
+
*) printf '%s\\n' "\${HOME:-$PWD}" ;;
|
|
149
|
+
esac
|
|
150
|
+
}
|
|
151
|
+
HOME="$(_resolve_home)"
|
|
152
|
+
export JISHUSHELL_HOME HOME`;
|
|
153
|
+
}
|
|
154
|
+
function buildCoreStartWrapperContent(opts) {
|
|
155
|
+
const pathPrefix = [
|
|
156
|
+
"/opt/homebrew/bin",
|
|
157
|
+
"/opt/homebrew/sbin",
|
|
158
|
+
"/usr/local/bin",
|
|
159
|
+
"/usr/local/sbin",
|
|
160
|
+
"/usr/bin",
|
|
161
|
+
"/bin",
|
|
162
|
+
"/usr/sbin",
|
|
163
|
+
"/sbin",
|
|
164
|
+
dirname(opts.nodeBin),
|
|
165
|
+
dirname(opts.cliBin),
|
|
166
|
+
].join(":");
|
|
167
|
+
const cliExec = opts.cliBin.endsWith(".js")
|
|
168
|
+
? `${shellQuote(opts.nodeBin)} ${shellQuote(opts.cliBin)}`
|
|
169
|
+
: shellQuote(opts.cliBin);
|
|
170
|
+
return `#!/bin/sh
|
|
171
|
+
set -eu
|
|
172
|
+
${buildWrapperEnvPrelude()}
|
|
173
|
+
[ -f "\${JISHUSHELL_HOME}/nomad.env" ] && . "\${JISHUSHELL_HOME}/nomad.env"
|
|
174
|
+
export JISHUSHELL_CORE_BIN=${shellQuote(opts.cliBin)}
|
|
175
|
+
export JISHUSHELL_CORE_PORT=${opts.port}
|
|
176
|
+
export NODE_ENV=production
|
|
177
|
+
export PATH=${shellQuote(pathPrefix)}\${PATH:+:\$PATH}
|
|
178
|
+
if command -v ss >/dev/null 2>&1 && ss -tln 2>/dev/null | grep -Eq '(^|[[:space:]])[^[:space:]]*:${opts.port}[[:space:]]'; then
|
|
179
|
+
echo "JishuShell core port ${opts.port} is already listening; not starting another core."
|
|
180
|
+
exit 0
|
|
181
|
+
fi
|
|
182
|
+
exec ${cliExec} serve --port ${opts.port}
|
|
183
|
+
`;
|
|
184
|
+
}
|
|
185
|
+
function buildCoreCliWrapperContent(opts) {
|
|
186
|
+
const pathPrefix = [
|
|
187
|
+
"/opt/homebrew/bin",
|
|
188
|
+
"/opt/homebrew/sbin",
|
|
189
|
+
"/usr/local/bin",
|
|
190
|
+
"/usr/local/sbin",
|
|
191
|
+
"/usr/bin",
|
|
192
|
+
"/bin",
|
|
193
|
+
"/usr/sbin",
|
|
194
|
+
"/sbin",
|
|
195
|
+
dirname(opts.nodeBin),
|
|
196
|
+
dirname(opts.cliBin),
|
|
197
|
+
].join(":");
|
|
198
|
+
const cliExec = opts.cliBin.endsWith(".js")
|
|
199
|
+
? `${shellQuote(opts.nodeBin)} ${shellQuote(opts.cliBin)}`
|
|
200
|
+
: shellQuote(opts.cliBin);
|
|
201
|
+
return `#!/bin/sh
|
|
202
|
+
set -eu
|
|
203
|
+
${buildWrapperEnvPrelude()}
|
|
204
|
+
[ -f "\${JISHUSHELL_HOME}/nomad.env" ] && . "\${JISHUSHELL_HOME}/nomad.env"
|
|
205
|
+
export JISHUSHELL_CORE_BIN=${shellQuote(opts.cliBin)}
|
|
206
|
+
export JISHUSHELL_CORE_PORT=${opts.port}
|
|
207
|
+
export NODE_ENV=\${NODE_ENV:-production}
|
|
208
|
+
export PATH=${shellQuote(pathPrefix)}\${PATH:+:\$PATH}
|
|
209
|
+
exec ${cliExec} "$@"
|
|
210
|
+
`;
|
|
211
|
+
}
|
|
212
|
+
function buildPanelStartWrapperContent(opts) {
|
|
213
|
+
const manageCoreDefault = opts.manageCoreDefault ? "1" : "0";
|
|
214
|
+
const pathPrefix = [
|
|
215
|
+
"/opt/homebrew/bin",
|
|
216
|
+
"/opt/homebrew/sbin",
|
|
217
|
+
"/usr/local/bin",
|
|
218
|
+
"/usr/local/sbin",
|
|
219
|
+
"/usr/bin",
|
|
220
|
+
"/bin",
|
|
221
|
+
"/usr/sbin",
|
|
222
|
+
"/sbin",
|
|
223
|
+
dirname(opts.nodeBin),
|
|
224
|
+
dirname(opts.panelBin),
|
|
225
|
+
].join(":");
|
|
226
|
+
return `#!/bin/sh
|
|
227
|
+
set -eu
|
|
228
|
+
${buildWrapperEnvPrelude()}
|
|
229
|
+
export NODE_ENV=\${NODE_ENV:-production}
|
|
230
|
+
export JISHUSHELL_CORE_BASE_URL=\${JISHUSHELL_CORE_BASE_URL:-http://127.0.0.1:${opts.corePort}}
|
|
231
|
+
export JISHUSHELL_PANEL_PORT=\${JISHUSHELL_PANEL_PORT:-${opts.panelPort}}
|
|
232
|
+
export JISHUSHELL_PANEL_MANAGE_CORE=\${JISHUSHELL_PANEL_MANAGE_CORE:-${manageCoreDefault}}
|
|
233
|
+
export PATH=${shellQuote(pathPrefix)}\${PATH:+:\$PATH}
|
|
234
|
+
exec ${shellQuote(opts.nodeBin)} ${shellQuote(opts.panelBin)} "$@"
|
|
235
|
+
`;
|
|
236
|
+
}
|
|
237
|
+
// ── Resolve non-root service user (board-agnostic) ─────────────────
|
|
238
|
+
/**
|
|
239
|
+
* Determine which user the systemd service should run as.
|
|
240
|
+
*
|
|
241
|
+
* Strategy (in order):
|
|
242
|
+
* 1. If current process is NOT root, return the real username directly.
|
|
243
|
+
* 2. Under sudo, trust the kernel-set SUDO_UID (numeric, hard to spoof)
|
|
244
|
+
* and reverse-lookup via `getent passwd <uid>`.
|
|
245
|
+
* 3. Fallback: scan /etc/passwd for the first user with UID >= 1000
|
|
246
|
+
* (works on any board — pi, orangepi, rock, nvidia, etc.).
|
|
247
|
+
* 4. Throw instead of guessing — running as the wrong user is worse
|
|
248
|
+
* than a visible installation error.
|
|
249
|
+
*/
|
|
250
|
+
function resolveServiceUser() {
|
|
251
|
+
const current = userInfo();
|
|
252
|
+
if (current.uid !== 0)
|
|
253
|
+
return current.username;
|
|
254
|
+
// Under sudo: SUDO_UID is set by the kernel and is numeric
|
|
255
|
+
const sudoUid = parseInt(process.env.SUDO_UID || "", 10);
|
|
256
|
+
if (Number.isFinite(sudoUid) && sudoUid >= 1000) {
|
|
257
|
+
try {
|
|
258
|
+
const entry = execFileSync("getent", ["passwd", String(sudoUid)], {
|
|
259
|
+
encoding: "utf8",
|
|
260
|
+
timeout: 3000,
|
|
261
|
+
}).trim();
|
|
262
|
+
const name = entry.split(":")[0];
|
|
263
|
+
if (name)
|
|
264
|
+
return name;
|
|
265
|
+
}
|
|
266
|
+
catch { /* getent failed, continue */ }
|
|
267
|
+
}
|
|
268
|
+
// Board-agnostic fallback: first real user (UID >= 1000, exclude nobody)
|
|
269
|
+
try {
|
|
270
|
+
const firstUser = execFileSync("getent", ["passwd"], {
|
|
271
|
+
encoding: "utf8",
|
|
272
|
+
timeout: 3000,
|
|
273
|
+
})
|
|
274
|
+
.split("\n")
|
|
275
|
+
.map((line) => line.split(":"))
|
|
276
|
+
.find((cols) => {
|
|
277
|
+
const uid = parseInt(cols[2], 10);
|
|
278
|
+
return uid >= 1000 && uid < 65534;
|
|
279
|
+
});
|
|
280
|
+
if (firstUser?.[0])
|
|
281
|
+
return firstUser[0];
|
|
282
|
+
}
|
|
283
|
+
catch { /* /etc/passwd unreadable */ }
|
|
284
|
+
throw new Error("Cannot determine service user. Run with a non-root user or set SUDO_UID.");
|
|
285
|
+
}
|
|
286
|
+
function resolveRealHome() {
|
|
287
|
+
return dirname(JISHUSHELL_HOME);
|
|
288
|
+
}
|
|
289
|
+
function resolveLaunchAgentsDir(realHome = resolveRealHome()) {
|
|
290
|
+
return join(process.env.HOME || realHome, "Library/LaunchAgents");
|
|
291
|
+
}
|
|
292
|
+
function resolveNomadExecutable() {
|
|
293
|
+
if (existsSync(NOMAD_BIN))
|
|
294
|
+
return NOMAD_BIN;
|
|
295
|
+
try {
|
|
296
|
+
return execFileSync("which", ["nomad"], { encoding: "utf-8", timeout: 5000 }).trim();
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
return "nomad";
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
function resolveColimaExecutable() {
|
|
303
|
+
try {
|
|
304
|
+
return execFileSync("which", ["colima"], { encoding: "utf-8", timeout: 5000 }).trim().split("\n")[0];
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
if (existsSync("/opt/homebrew/bin/colima"))
|
|
308
|
+
return "/opt/homebrew/bin/colima";
|
|
309
|
+
if (existsSync("/usr/local/bin/colima"))
|
|
310
|
+
return "/usr/local/bin/colima";
|
|
311
|
+
return "colima";
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function buildJishushellCoreSystemdUnit(opts) {
|
|
315
|
+
return `[Unit]
|
|
316
|
+
Description=JishuShell Core
|
|
317
|
+
After=network-online.target nomad.service
|
|
318
|
+
Wants=network-online.target
|
|
319
|
+
|
|
320
|
+
[Service]
|
|
321
|
+
Type=simple
|
|
322
|
+
User=${opts.currentUser}
|
|
323
|
+
SupplementaryGroups=docker
|
|
324
|
+
Environment=HOME=${opts.realHome}
|
|
325
|
+
Environment=JISHUSHELL_HOME=${JISHUSHELL_HOME}
|
|
326
|
+
EnvironmentFile=-/etc/jishushell/nomad.env
|
|
327
|
+
ExecStart=${opts.coreStartWrapperPath}
|
|
328
|
+
Restart=on-failure
|
|
329
|
+
RestartSec=3
|
|
330
|
+
Environment=NODE_ENV=production
|
|
331
|
+
ProtectSystem=strict
|
|
332
|
+
PrivateTmp=true
|
|
333
|
+
ReadWritePaths=${JISHUSHELL_HOME} /etc/jishushell /usr/local
|
|
334
|
+
|
|
335
|
+
[Install]
|
|
336
|
+
WantedBy=multi-user.target
|
|
337
|
+
`;
|
|
338
|
+
}
|
|
339
|
+
function buildJishushellPanelSystemdUnit(opts) {
|
|
340
|
+
return `[Unit]
|
|
341
|
+
Description=JishuShell Panel
|
|
342
|
+
After=network-online.target jishushell.service
|
|
343
|
+
Wants=network-online.target jishushell.service
|
|
344
|
+
|
|
345
|
+
[Service]
|
|
346
|
+
Type=simple
|
|
347
|
+
User=${opts.currentUser}
|
|
348
|
+
Environment=HOME=${opts.realHome}
|
|
349
|
+
Environment=JISHUSHELL_HOME=${JISHUSHELL_HOME}
|
|
350
|
+
Environment=JISHUSHELL_CORE_BASE_URL=http://127.0.0.1:${opts.corePort}
|
|
351
|
+
Environment=JISHUSHELL_PANEL_PORT=${opts.panelPort}
|
|
352
|
+
Environment=JISHUSHELL_PANEL_MANAGE_CORE=0
|
|
353
|
+
Environment=NODE_ENV=production
|
|
354
|
+
ExecStart=${opts.panelStartWrapperPath}
|
|
355
|
+
Restart=on-failure
|
|
356
|
+
RestartSec=3
|
|
357
|
+
ProtectSystem=strict
|
|
358
|
+
PrivateTmp=true
|
|
359
|
+
ReadWritePaths=${JISHUSHELL_HOME}
|
|
360
|
+
|
|
361
|
+
[Install]
|
|
362
|
+
WantedBy=multi-user.target
|
|
363
|
+
`;
|
|
364
|
+
}
|
|
365
|
+
function buildNomadSystemdUnit(opts) {
|
|
366
|
+
return `[Unit]
|
|
367
|
+
Description=Nomad Agent
|
|
368
|
+
After=network-online.target docker.service
|
|
369
|
+
Wants=network-online.target
|
|
370
|
+
|
|
371
|
+
[Service]
|
|
372
|
+
User=root
|
|
373
|
+
Type=simple
|
|
374
|
+
EnvironmentFile=-/etc/jishushell/nomad.env
|
|
375
|
+
ExecStart=${opts.nomadPath} agent -config=${opts.configPath}
|
|
376
|
+
Restart=on-failure
|
|
377
|
+
RestartSec=3
|
|
378
|
+
Delegate=yes
|
|
379
|
+
TasksMax=infinity
|
|
380
|
+
|
|
381
|
+
[Install]
|
|
382
|
+
WantedBy=multi-user.target
|
|
383
|
+
`;
|
|
384
|
+
}
|
|
385
|
+
export function hasBundledPanel() {
|
|
386
|
+
try {
|
|
387
|
+
resolveBundledPanelServerBin();
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
catch {
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
export function getExpectedCoreSystemFiles(port = _serverPort) {
|
|
395
|
+
const nodeBin = process.execPath;
|
|
396
|
+
const cliBin = resolveCoreCliBin();
|
|
397
|
+
const realHome = resolveRealHome();
|
|
398
|
+
const coreStartWrapperPath = join(BIN_DIR, "jishushell-core-start");
|
|
399
|
+
const coreCliWrapperPath = join(BIN_DIR, "jishushell");
|
|
400
|
+
const files = [
|
|
401
|
+
{
|
|
402
|
+
component: "wrapper",
|
|
403
|
+
id: "core.start-wrapper",
|
|
404
|
+
path: coreStartWrapperPath,
|
|
405
|
+
content: buildCoreStartWrapperContent({ nodeBin, cliBin, realHome, port }),
|
|
406
|
+
installScope: "core",
|
|
407
|
+
requiresSudo: false,
|
|
408
|
+
reloadRequired: true,
|
|
409
|
+
fix: "system-reload",
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
component: "wrapper",
|
|
413
|
+
id: "core.cli-wrapper",
|
|
414
|
+
path: coreCliWrapperPath,
|
|
415
|
+
content: buildCoreCliWrapperContent({ nodeBin, cliBin, realHome, port }),
|
|
416
|
+
installScope: "core",
|
|
417
|
+
requiresSudo: false,
|
|
418
|
+
reloadRequired: true,
|
|
419
|
+
fix: "system-reload",
|
|
420
|
+
},
|
|
421
|
+
];
|
|
422
|
+
if (process.platform === "darwin") {
|
|
423
|
+
const gateWrapperPath = join(BIN_DIR, "core-launchd-wrapper.sh");
|
|
424
|
+
files.push({
|
|
425
|
+
component: "wrapper",
|
|
426
|
+
id: "core.launchd-gate-wrapper",
|
|
427
|
+
path: gateWrapperPath,
|
|
428
|
+
content: buildCoreGateWrapper({
|
|
429
|
+
socket: managedColimaSocketPath(JISHUSHELL_HOME, COLIMA_PROFILE),
|
|
430
|
+
coreStartWrapper: coreStartWrapperPath,
|
|
431
|
+
}),
|
|
432
|
+
installScope: "core",
|
|
433
|
+
requiresSudo: false,
|
|
434
|
+
reloadRequired: true,
|
|
435
|
+
fix: "system-reload",
|
|
436
|
+
}, {
|
|
437
|
+
component: "launchd",
|
|
438
|
+
id: "core.launchd-agent",
|
|
439
|
+
path: join(resolveLaunchAgentsDir(realHome), "com.jishushell.core.plist"),
|
|
440
|
+
content: buildCorePlist({
|
|
441
|
+
gateWrapperPath,
|
|
442
|
+
logPath: join(JISHUSHELL_HOME, "jishushell-core.log"),
|
|
443
|
+
}),
|
|
444
|
+
installScope: "core",
|
|
445
|
+
requiresSudo: false,
|
|
446
|
+
reloadRequired: true,
|
|
447
|
+
fix: "system-reload",
|
|
448
|
+
});
|
|
449
|
+
return files;
|
|
450
|
+
}
|
|
451
|
+
files.push({
|
|
452
|
+
component: "systemd",
|
|
453
|
+
id: "core.systemd-unit",
|
|
454
|
+
path: SYSTEMD_CORE_SERVICE_PATH,
|
|
455
|
+
content: buildJishushellCoreSystemdUnit({
|
|
456
|
+
currentUser: resolveServiceUser(),
|
|
457
|
+
realHome,
|
|
458
|
+
coreStartWrapperPath,
|
|
459
|
+
}),
|
|
460
|
+
installScope: "core",
|
|
461
|
+
requiresSudo: true,
|
|
462
|
+
reloadRequired: true,
|
|
463
|
+
fix: "system-reload",
|
|
464
|
+
});
|
|
465
|
+
return files;
|
|
466
|
+
}
|
|
467
|
+
export function getExpectedPanelSystemFiles(corePort = _serverPort, panelPort = DEFAULT_PANEL_PORT) {
|
|
468
|
+
const nodeBin = process.execPath;
|
|
469
|
+
const panelBin = resolveBundledPanelServerBin();
|
|
470
|
+
const realHome = resolveRealHome();
|
|
471
|
+
const panelStartWrapperPath = join(BIN_DIR, "jishushell-panel-start");
|
|
472
|
+
const files = [
|
|
473
|
+
{
|
|
474
|
+
component: "wrapper",
|
|
475
|
+
id: "panel.start-wrapper",
|
|
476
|
+
path: panelStartWrapperPath,
|
|
477
|
+
content: buildPanelStartWrapperContent({
|
|
478
|
+
nodeBin,
|
|
479
|
+
panelBin,
|
|
480
|
+
realHome,
|
|
481
|
+
corePort,
|
|
482
|
+
panelPort,
|
|
483
|
+
}),
|
|
484
|
+
installScope: "panel",
|
|
485
|
+
requiresSudo: false,
|
|
486
|
+
reloadRequired: true,
|
|
487
|
+
fix: "system-reload",
|
|
488
|
+
},
|
|
489
|
+
];
|
|
490
|
+
if (process.platform === "darwin") {
|
|
491
|
+
files.push({
|
|
492
|
+
component: "launchd",
|
|
493
|
+
id: "panel.launchd-agent",
|
|
494
|
+
path: join(resolveLaunchAgentsDir(realHome), "com.jishushell.panel.plist"),
|
|
495
|
+
content: buildPanelPlist({
|
|
496
|
+
panelWrapperPath: panelStartWrapperPath,
|
|
497
|
+
logPath: join(JISHUSHELL_HOME, "jishushell-panel.log"),
|
|
498
|
+
home: realHome,
|
|
499
|
+
jishuHome: JISHUSHELL_HOME,
|
|
500
|
+
corePort,
|
|
501
|
+
panelPort,
|
|
502
|
+
}),
|
|
503
|
+
installScope: "panel",
|
|
504
|
+
requiresSudo: false,
|
|
505
|
+
reloadRequired: true,
|
|
506
|
+
fix: "system-reload",
|
|
507
|
+
});
|
|
508
|
+
return files;
|
|
509
|
+
}
|
|
510
|
+
files.push({
|
|
511
|
+
component: "systemd",
|
|
512
|
+
id: "panel.systemd-unit",
|
|
513
|
+
path: SYSTEMD_PANEL_SERVICE_PATH,
|
|
514
|
+
content: buildJishushellPanelSystemdUnit({
|
|
515
|
+
currentUser: resolveServiceUser(),
|
|
516
|
+
realHome,
|
|
517
|
+
panelStartWrapperPath,
|
|
518
|
+
corePort,
|
|
519
|
+
panelPort,
|
|
520
|
+
}),
|
|
521
|
+
installScope: "panel",
|
|
522
|
+
requiresSudo: true,
|
|
523
|
+
reloadRequired: true,
|
|
524
|
+
fix: "system-reload",
|
|
525
|
+
});
|
|
526
|
+
return files;
|
|
527
|
+
}
|
|
528
|
+
export function getExpectedNomadSystemFiles() {
|
|
529
|
+
const configPath = join(NOMAD_CONFIG_DIR, "nomad.hcl");
|
|
530
|
+
const files = [
|
|
531
|
+
{
|
|
532
|
+
component: "nomad",
|
|
533
|
+
id: "nomad.config",
|
|
534
|
+
path: configPath,
|
|
535
|
+
content: buildExpectedNomadConfigContent(),
|
|
536
|
+
installScope: "nomad",
|
|
537
|
+
requiresSudo: false,
|
|
538
|
+
reloadRequired: true,
|
|
539
|
+
fix: "system-reload",
|
|
540
|
+
},
|
|
541
|
+
];
|
|
542
|
+
const nomadPath = resolveNomadExecutable();
|
|
543
|
+
if (process.platform === "darwin") {
|
|
544
|
+
const laDir = resolveLaunchAgentsDir();
|
|
545
|
+
const colimaSocket = managedColimaSocketPath(JISHUSHELL_HOME, COLIMA_PROFILE);
|
|
546
|
+
const colimaDockerHost = managedColimaDockerHost(JISHUSHELL_HOME, COLIMA_PROFILE);
|
|
547
|
+
const colimaWrapperPath = join(BIN_DIR, "colima-launchd-wrapper.sh");
|
|
548
|
+
const nomadWrapperPath = join(BIN_DIR, "nomad-launchd-wrapper.sh");
|
|
549
|
+
files.push({
|
|
550
|
+
component: "wrapper",
|
|
551
|
+
id: "nomad.colima-wrapper",
|
|
552
|
+
path: colimaWrapperPath,
|
|
553
|
+
content: buildColimaWrapperScript({
|
|
554
|
+
colimaHome: _COLIMA_DIR,
|
|
555
|
+
profile: COLIMA_PROFILE,
|
|
556
|
+
socket: colimaSocket,
|
|
557
|
+
colimaBin: resolveColimaExecutable(),
|
|
558
|
+
}),
|
|
559
|
+
installScope: "nomad",
|
|
560
|
+
requiresSudo: false,
|
|
561
|
+
reloadRequired: true,
|
|
562
|
+
fix: "system-reload",
|
|
563
|
+
}, {
|
|
564
|
+
component: "launchd",
|
|
565
|
+
id: "nomad.colima-launchd-agent",
|
|
566
|
+
path: join(laDir, "com.jishushell.colima.plist"),
|
|
567
|
+
content: buildColimaPlist({
|
|
568
|
+
wrapperPath: colimaWrapperPath,
|
|
569
|
+
colimaHome: _COLIMA_DIR,
|
|
570
|
+
logPath: join(_COLIMA_DIR, "colima-launchd.log"),
|
|
571
|
+
}),
|
|
572
|
+
installScope: "nomad",
|
|
573
|
+
requiresSudo: false,
|
|
574
|
+
reloadRequired: true,
|
|
575
|
+
fix: "system-reload",
|
|
576
|
+
}, {
|
|
577
|
+
component: "wrapper",
|
|
578
|
+
id: "nomad.launchd-wrapper",
|
|
579
|
+
path: nomadWrapperPath,
|
|
580
|
+
content: buildNomadWaitWrapper({
|
|
581
|
+
dockerHost: colimaDockerHost,
|
|
582
|
+
nomadBin: nomadPath,
|
|
583
|
+
configPath,
|
|
584
|
+
}),
|
|
585
|
+
installScope: "nomad",
|
|
586
|
+
requiresSudo: false,
|
|
587
|
+
reloadRequired: true,
|
|
588
|
+
fix: "system-reload",
|
|
589
|
+
}, {
|
|
590
|
+
component: "launchd",
|
|
591
|
+
id: "nomad.launchd-agent",
|
|
592
|
+
path: join(laDir, "com.jishushell.nomad.plist"),
|
|
593
|
+
content: buildNomadPlist({
|
|
594
|
+
wrapperPath: nomadWrapperPath,
|
|
595
|
+
dockerHost: colimaDockerHost,
|
|
596
|
+
logPath: join(NOMAD_CONFIG_DIR, "nomad.log"),
|
|
597
|
+
}),
|
|
598
|
+
installScope: "nomad",
|
|
599
|
+
requiresSudo: false,
|
|
600
|
+
reloadRequired: true,
|
|
601
|
+
fix: "system-reload",
|
|
602
|
+
});
|
|
603
|
+
return files;
|
|
604
|
+
}
|
|
605
|
+
files.push({
|
|
606
|
+
component: "systemd",
|
|
607
|
+
id: "nomad.systemd-unit",
|
|
608
|
+
path: SYSTEMD_NOMAD_SERVICE_PATH,
|
|
609
|
+
content: buildNomadSystemdUnit({ nomadPath, configPath }),
|
|
610
|
+
installScope: "nomad",
|
|
611
|
+
requiresSudo: true,
|
|
612
|
+
reloadRequired: true,
|
|
613
|
+
fix: "system-reload",
|
|
614
|
+
});
|
|
615
|
+
return files;
|
|
616
|
+
}
|
|
617
|
+
export function getExpectedSystemFiles(options = {}) {
|
|
618
|
+
const config = getCoreConfig();
|
|
619
|
+
const envCorePort = Number(process.env.JISHUSHELL_CORE_PORT);
|
|
620
|
+
const corePort = options.corePort ??
|
|
621
|
+
(Number.isFinite(envCorePort) && envCorePort > 0 ? envCorePort : Number(config.core_port) || _serverPort);
|
|
622
|
+
const panelPort = options.panelPort ?? DEFAULT_PANEL_PORT;
|
|
623
|
+
const includePanel = options.includePanel ?? hasBundledPanel();
|
|
624
|
+
const includeNomad = options.includeNomad ?? config.service_manager === "nomad";
|
|
625
|
+
const files = getExpectedCoreSystemFiles(corePort);
|
|
626
|
+
if (includePanel)
|
|
627
|
+
files.push(...getExpectedPanelSystemFiles(corePort, panelPort));
|
|
628
|
+
if (includeNomad)
|
|
629
|
+
files.push(...getExpectedNomadSystemFiles());
|
|
630
|
+
return files;
|
|
631
|
+
}
|
|
632
|
+
function persistCoreServicePort(port) {
|
|
633
|
+
const config = getCoreConfig();
|
|
634
|
+
if (Number(config.core_port) === port)
|
|
635
|
+
return;
|
|
636
|
+
config.core_port = port;
|
|
637
|
+
saveCoreConfig(config);
|
|
638
|
+
}
|
|
639
|
+
// ── Task tracker (for SSE progress) ────────────────────────────────
|
|
640
|
+
// Storage, event dispatch, and subscribe plumbing now live in
|
|
641
|
+
// task-registry.ts so non-setup callers can publish tasks without pulling in
|
|
642
|
+
// the rest of setup-manager's bootstrap surface. These re-exports preserve the
|
|
643
|
+
// original import surface used by integration modules, routes, and CLI.
|
|
644
|
+
export { createTask, emitTask, getTask, getTaskSnapshot, getRunningTasks, subscribeTask, } from "../tasks/registry.js";
|
|
645
|
+
const ANSI_ESCAPE_RE = /\u001B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
|
|
646
|
+
function sanitizeTaskLine(line) {
|
|
647
|
+
return line
|
|
648
|
+
.replace(ANSI_ESCAPE_RE, "")
|
|
649
|
+
.replace(/[\u0000-\u0008\u000B-\u001F\u007F]/g, "")
|
|
650
|
+
.trimEnd();
|
|
651
|
+
}
|
|
652
|
+
/** Run a shell command as a spawned process, streaming output to a task */
|
|
653
|
+
export function spawnWithTask(task, cmd, args, options = {}) {
|
|
654
|
+
return new Promise((resolve) => {
|
|
655
|
+
const env = { ...process.env, ...options.env };
|
|
656
|
+
const child = nodeSpawn(cmd, args, {
|
|
657
|
+
env,
|
|
658
|
+
cwd: options.cwd,
|
|
659
|
+
timeout: options.timeout || 600000,
|
|
660
|
+
});
|
|
661
|
+
let output = "";
|
|
662
|
+
const stdoutDecoder = new StringDecoder("utf8");
|
|
663
|
+
const stderrDecoder = new StringDecoder("utf8");
|
|
664
|
+
let stdoutPending = "";
|
|
665
|
+
let stderrPending = "";
|
|
666
|
+
const emitLine = (rawLine) => {
|
|
667
|
+
const line = sanitizeTaskLine(rawLine);
|
|
668
|
+
if (!line.trim())
|
|
669
|
+
return;
|
|
670
|
+
// Try to parse progress
|
|
671
|
+
if (options.progressParser) {
|
|
672
|
+
const pct = options.progressParser(line);
|
|
673
|
+
if (pct !== null) {
|
|
674
|
+
emitTask(task, { type: "progress", message: line, progress: pct });
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
emitTask(task, { type: "log", message: line });
|
|
679
|
+
};
|
|
680
|
+
const handleDecodedText = (text, stream) => {
|
|
681
|
+
if (!text)
|
|
682
|
+
return;
|
|
683
|
+
output += text;
|
|
684
|
+
const normalized = `${stream === "stdout" ? stdoutPending : stderrPending}${text}`
|
|
685
|
+
.replace(/\r\n/g, "\n")
|
|
686
|
+
.replace(/\r/g, "\n");
|
|
687
|
+
const lines = normalized.split("\n");
|
|
688
|
+
const pending = lines.pop() ?? "";
|
|
689
|
+
if (stream === "stdout")
|
|
690
|
+
stdoutPending = pending;
|
|
691
|
+
else
|
|
692
|
+
stderrPending = pending;
|
|
693
|
+
for (const line of lines) {
|
|
694
|
+
emitLine(line);
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
child.stdout?.on("data", (data) => {
|
|
698
|
+
handleDecodedText(stdoutDecoder.write(data), "stdout");
|
|
699
|
+
});
|
|
700
|
+
child.stderr?.on("data", (data) => {
|
|
701
|
+
handleDecodedText(stderrDecoder.write(data), "stderr");
|
|
702
|
+
});
|
|
703
|
+
child.on("close", (code) => {
|
|
704
|
+
handleDecodedText(stdoutDecoder.end(), "stdout");
|
|
705
|
+
handleDecodedText(stderrDecoder.end(), "stderr");
|
|
706
|
+
emitLine(stdoutPending);
|
|
707
|
+
emitLine(stderrPending);
|
|
708
|
+
resolve({ ok: code === 0, output });
|
|
709
|
+
});
|
|
710
|
+
child.on("error", (err) => {
|
|
711
|
+
emitTask(task, { type: "log", message: `Error: ${err.message}` });
|
|
712
|
+
resolve({ ok: false, output: err.message });
|
|
713
|
+
});
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
// ── Progress parsers ───────────────────────────────────────────────
|
|
717
|
+
export function npmProgressParser(line) {
|
|
718
|
+
// npm shows "added X packages" at the end
|
|
719
|
+
if (line.includes("added") && line.includes("packages"))
|
|
720
|
+
return 100;
|
|
721
|
+
// npm progress: "reify:packagename: timing"
|
|
722
|
+
if (line.includes("reify:"))
|
|
723
|
+
return null; // just a log line
|
|
724
|
+
return null;
|
|
725
|
+
}
|
|
726
|
+
export function dockerBuildProgressParser(line) {
|
|
727
|
+
// Docker build steps: "Step 1/6", "Step 2/6", etc.
|
|
728
|
+
const legacyMatch = line.match(/Step\s+(\d+)\/(\d+)/);
|
|
729
|
+
if (legacyMatch) {
|
|
730
|
+
return Math.round((parseInt(legacyMatch[1], 10) / parseInt(legacyMatch[2], 10)) * 100);
|
|
731
|
+
}
|
|
732
|
+
// BuildKit plain output: "#5 [2/4] COPY node_modules ..."
|
|
733
|
+
const buildkitMatch = line.match(/\[(\d+)\/(\d+)\]/);
|
|
734
|
+
if (buildkitMatch) {
|
|
735
|
+
return Math.round((parseInt(buildkitMatch[1], 10) / parseInt(buildkitMatch[2], 10)) * 100);
|
|
736
|
+
}
|
|
737
|
+
if (/exporting to image/i.test(line))
|
|
738
|
+
return 95;
|
|
739
|
+
if (/naming to /i.test(line))
|
|
740
|
+
return 98;
|
|
741
|
+
return null;
|
|
742
|
+
}
|
|
743
|
+
function curlProgressParser(line) {
|
|
744
|
+
// curl with -# shows progress
|
|
745
|
+
const match = line.match(/(\d+\.?\d*)%/);
|
|
746
|
+
if (match)
|
|
747
|
+
return Math.round(parseFloat(match[1]));
|
|
748
|
+
return null;
|
|
749
|
+
}
|
|
750
|
+
// ── Dir size tracker for npm installs ──────────────────────────────
|
|
751
|
+
export function getDirSizeMB(dir) {
|
|
752
|
+
try {
|
|
753
|
+
const result = execFileSync("du", ["-sm", dir], { encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] });
|
|
754
|
+
return parseInt(result.split("\t")[0], 10) || 0;
|
|
755
|
+
}
|
|
756
|
+
catch {
|
|
757
|
+
return 0;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
function checkCommand(cmd, versionFlag = "--version") {
|
|
761
|
+
try {
|
|
762
|
+
const version = execFileSync(cmd, [versionFlag], { encoding: "utf-8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
763
|
+
let cmdPath = "";
|
|
764
|
+
try {
|
|
765
|
+
cmdPath = execFileSync("which", [cmd], { encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
766
|
+
}
|
|
767
|
+
catch { }
|
|
768
|
+
return { ok: true, version, path: cmdPath };
|
|
769
|
+
}
|
|
770
|
+
catch {
|
|
771
|
+
return { ok: false, version: "", path: "" };
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
function isPortListening(port) {
|
|
775
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
776
|
+
return false;
|
|
777
|
+
const p = String(port);
|
|
778
|
+
const matchesPort = (output) => new RegExp(`:${p}\\s`).test(output);
|
|
779
|
+
try {
|
|
780
|
+
if (process.platform === "darwin") {
|
|
781
|
+
try {
|
|
782
|
+
const result = execFileSync("lsof", ["-iTCP:" + p, "-sTCP:LISTEN", "-t"], { encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] });
|
|
783
|
+
if (result.trim().length > 0)
|
|
784
|
+
return true;
|
|
785
|
+
}
|
|
786
|
+
catch {
|
|
787
|
+
}
|
|
788
|
+
try {
|
|
789
|
+
const result = execFileSync("ss", ["-tlnp"], { encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] });
|
|
790
|
+
if (matchesPort(result))
|
|
791
|
+
return true;
|
|
792
|
+
}
|
|
793
|
+
catch {
|
|
794
|
+
}
|
|
795
|
+
const result = execFileSync("netstat", ["-tlnp"], { encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] });
|
|
796
|
+
return matchesPort(result);
|
|
797
|
+
}
|
|
798
|
+
// Linux: prefer ss, fall back to netstat
|
|
799
|
+
try {
|
|
800
|
+
const result = execFileSync("ss", ["-tlnp"], { encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] });
|
|
801
|
+
return matchesPort(result);
|
|
802
|
+
}
|
|
803
|
+
catch {
|
|
804
|
+
const result = execFileSync("netstat", ["-tlnp"], { encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] });
|
|
805
|
+
return matchesPort(result);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
catch {
|
|
809
|
+
return false;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
function waitForListeningPort(port, timeoutSeconds) {
|
|
813
|
+
const deadline = Date.now() + timeoutSeconds * 1000;
|
|
814
|
+
while (Date.now() < deadline) {
|
|
815
|
+
if (isPortListening(port))
|
|
816
|
+
return true;
|
|
817
|
+
try {
|
|
818
|
+
execFileSync("sleep", ["1"], { timeout: 2000 });
|
|
819
|
+
}
|
|
820
|
+
catch {
|
|
821
|
+
return isPortListening(port);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
return isPortListening(port);
|
|
825
|
+
}
|
|
826
|
+
function checkSudo() {
|
|
827
|
+
try {
|
|
828
|
+
execSync("sudo -n true 2>/dev/null", { timeout: 3000 });
|
|
829
|
+
return true;
|
|
830
|
+
}
|
|
831
|
+
catch {
|
|
832
|
+
return false;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
/** Check if kernel memory cgroup is enabled (required for Docker memory stats on RPi) */
|
|
836
|
+
function isCgroupMemoryEnabled() {
|
|
837
|
+
try {
|
|
838
|
+
const cgroups = readFileSync("/proc/cgroups", "utf-8");
|
|
839
|
+
const memLine = cgroups.split("\n").find(l => l.startsWith("memory\t"));
|
|
840
|
+
if (!memLine)
|
|
841
|
+
return false;
|
|
842
|
+
// Format: name hierarchy num_cgroups enabled
|
|
843
|
+
return memLine.trim().endsWith("1");
|
|
844
|
+
}
|
|
845
|
+
catch {
|
|
846
|
+
return true; /* non-Linux or unreadable — assume ok */
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
/** Enable cgroup memory in boot cmdline if not already enabled. Returns true if a reboot is needed. */
|
|
850
|
+
export function ensureCgroupMemory() {
|
|
851
|
+
if (isCgroupMemoryEnabled())
|
|
852
|
+
return false;
|
|
853
|
+
const candidates = ["/boot/firmware/cmdline.txt", "/boot/cmdline.txt"];
|
|
854
|
+
for (const f of candidates) {
|
|
855
|
+
if (!existsSync(f))
|
|
856
|
+
continue;
|
|
857
|
+
try {
|
|
858
|
+
const content = readFileSync(f, "utf-8").trim();
|
|
859
|
+
if (content.includes("cgroup_memory=1"))
|
|
860
|
+
return false; // already in cmdline, just needs reboot
|
|
861
|
+
const patched = `${content} cgroup_memory=1 cgroup_enable=memory`;
|
|
862
|
+
execFileSync("sudo", ["cp", f, f + ".bak"], { timeout: 5000 });
|
|
863
|
+
sudoCopySystemFile(patched + "\n", f, "644");
|
|
864
|
+
console.log(`[setup] enabled cgroup memory in ${f} (reboot required)`);
|
|
865
|
+
return true;
|
|
866
|
+
}
|
|
867
|
+
catch (e) {
|
|
868
|
+
console.warn(`[setup] failed to patch ${f}:`, e.message);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
return false;
|
|
872
|
+
}
|
|
873
|
+
function canAccessDockerDaemon(timeout = 10000) {
|
|
874
|
+
const env = buildDockerClientEnv({
|
|
875
|
+
jishuHome: JISHUSHELL_HOME,
|
|
876
|
+
colimaProfile: COLIMA_PROFILE,
|
|
877
|
+
});
|
|
878
|
+
try {
|
|
879
|
+
execFileSync("docker", ["info"], { timeout, stdio: "ignore", env });
|
|
880
|
+
return true;
|
|
881
|
+
}
|
|
882
|
+
catch { }
|
|
883
|
+
try {
|
|
884
|
+
execFileSync("sudo", ["-n", "docker", "info"], { timeout, stdio: "ignore", env });
|
|
885
|
+
return true;
|
|
886
|
+
}
|
|
887
|
+
catch { }
|
|
888
|
+
return false;
|
|
889
|
+
}
|
|
890
|
+
function getDockerVersionLine(timeout = 10000) {
|
|
891
|
+
try {
|
|
892
|
+
return execFileSync("docker", ["--version"], { encoding: "utf-8", timeout }).trim();
|
|
893
|
+
}
|
|
894
|
+
catch { }
|
|
895
|
+
try {
|
|
896
|
+
return execFileSync("sudo", ["-n", "docker", "--version"], { encoding: "utf-8", timeout }).trim();
|
|
897
|
+
}
|
|
898
|
+
catch { }
|
|
899
|
+
return "installed";
|
|
900
|
+
}
|
|
901
|
+
export async function getSetupStatus() {
|
|
902
|
+
// Fast path: if setup is already complete, do lightweight checks before returning cached result
|
|
903
|
+
const config = getCoreConfig();
|
|
904
|
+
if (config.service_manager) {
|
|
905
|
+
const localBin = join(OPENCLAW_BIN_DIR, "openclaw");
|
|
906
|
+
const localBinOk = existsSync(localBin);
|
|
907
|
+
const fastDockerImageReady = checkDockerImageExists();
|
|
908
|
+
const openclawOk = localBinOk || fastDockerImageReady;
|
|
909
|
+
// Lightweight validation: verify critical services are actually available
|
|
910
|
+
const dockerOk = canAccessDockerDaemon(5000);
|
|
911
|
+
const nomadOk = isPortListening(4646);
|
|
912
|
+
const hasSudo = checkSudo();
|
|
913
|
+
// If any critical check fails, fall through to the slow full check path
|
|
914
|
+
if (!openclawOk || !dockerOk || !nomadOk || !hasSudo) {
|
|
915
|
+
// Fall through to full check below
|
|
916
|
+
}
|
|
917
|
+
else {
|
|
918
|
+
// Fetch real versions (cheap execs, no fallback to slow path)
|
|
919
|
+
let dockerVer = "installed";
|
|
920
|
+
try {
|
|
921
|
+
dockerVer = execFileSync("docker", ["--version"], { encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] }).trim().split("\n")[0];
|
|
922
|
+
}
|
|
923
|
+
catch { }
|
|
924
|
+
let nomadVer = "installed";
|
|
925
|
+
try {
|
|
926
|
+
nomadVer = execFileSync(NOMAD_BIN, ["--version"], { encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] }).trim().split("\n")[0];
|
|
927
|
+
}
|
|
928
|
+
catch { }
|
|
929
|
+
let openclawVer = "installed";
|
|
930
|
+
let openclawPath = localBin;
|
|
931
|
+
// Prefer npm package.json for accurate OpenClaw version.
|
|
932
|
+
try {
|
|
933
|
+
const pkg = join(OPENCLAW_MODULES, "openclaw", "package.json");
|
|
934
|
+
if (existsSync(pkg))
|
|
935
|
+
openclawVer = JSON.parse(readFileSync(pkg, "utf-8")).version || openclawVer;
|
|
936
|
+
}
|
|
937
|
+
catch { }
|
|
938
|
+
const runtimes = await buildRuntimesStatus();
|
|
939
|
+
const ready = requiredRuntimesReady(runtimes);
|
|
940
|
+
return {
|
|
941
|
+
node: { name: "Node.js", installed: true, running: true, version: process.version, path: process.execPath },
|
|
942
|
+
docker: { name: "Docker", installed: true, running: true, version: dockerVer, path: "" },
|
|
943
|
+
nomad: { name: "Nomad", installed: true, running: true, version: nomadVer, path: NOMAD_BIN },
|
|
944
|
+
openclaw: { name: "OpenClaw", installed: openclawOk, running: false, version: openclawVer, path: openclawPath },
|
|
945
|
+
runtimeImageReady: fastDockerImageReady,
|
|
946
|
+
ready,
|
|
947
|
+
providerConfigured: hasLlmProviderSetup(),
|
|
948
|
+
hasSudo: true,
|
|
949
|
+
runtimes,
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
const NODE_MINIMUM_MAJOR = 22;
|
|
954
|
+
// Use the currently-running Node binary (process.execPath) as the ground
|
|
955
|
+
// truth — avoids false-negative when PATH doesn't include nvm's bin dir
|
|
956
|
+
// (e.g. when started via systemd with a minimal environment).
|
|
957
|
+
const nodeVersion = process.version;
|
|
958
|
+
const nodeMajor = parseInt(nodeVersion.replace("v", "").split(".")[0], 10);
|
|
959
|
+
const nodeStatus = {
|
|
960
|
+
name: "Node.js",
|
|
961
|
+
installed: true,
|
|
962
|
+
running: true,
|
|
963
|
+
version: nodeVersion,
|
|
964
|
+
path: process.execPath,
|
|
965
|
+
needsUpgrade: nodeMajor < NODE_MINIMUM_MAJOR,
|
|
966
|
+
};
|
|
967
|
+
const docker = checkCommand("docker");
|
|
968
|
+
// Use docker info to verify daemon accessibility (not just process existence)
|
|
969
|
+
// Also try sudo in case the user hasn't logged out since being added to the docker group
|
|
970
|
+
const dockerRunning = canAccessDockerDaemon(10000);
|
|
971
|
+
const dockerStatus = {
|
|
972
|
+
name: "Docker",
|
|
973
|
+
installed: docker.ok,
|
|
974
|
+
running: dockerRunning,
|
|
975
|
+
version: docker.version.split("\n")[0],
|
|
976
|
+
path: docker.path,
|
|
977
|
+
};
|
|
978
|
+
let nomad = checkCommand("nomad");
|
|
979
|
+
if (!nomad.ok && existsSync(NOMAD_BIN)) {
|
|
980
|
+
nomad = checkCommand(NOMAD_BIN);
|
|
981
|
+
}
|
|
982
|
+
const nomadRunning = isPortListening(4646);
|
|
983
|
+
const nomadStatus = {
|
|
984
|
+
name: "Nomad",
|
|
985
|
+
installed: nomad.ok,
|
|
986
|
+
running: nomadRunning,
|
|
987
|
+
version: nomad.version.split("\n")[0],
|
|
988
|
+
path: nomad.path || NOMAD_BIN,
|
|
989
|
+
};
|
|
990
|
+
let openclaw = checkCommand("openclaw");
|
|
991
|
+
if (!openclaw.ok) {
|
|
992
|
+
const localBin = join(OPENCLAW_BIN_DIR, "openclaw");
|
|
993
|
+
if (existsSync(localBin)) {
|
|
994
|
+
let ver = "installed";
|
|
995
|
+
try {
|
|
996
|
+
const pkg = join(OPENCLAW_MODULES, "openclaw", "package.json");
|
|
997
|
+
if (existsSync(pkg)) {
|
|
998
|
+
const pkgJson = JSON.parse(readFileSync(pkg, "utf-8"));
|
|
999
|
+
ver = pkgJson.version || "installed";
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
catch { }
|
|
1003
|
+
openclaw = { ok: true, version: ver, path: localBin };
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
const runtimeImageReady = checkDockerImageExists();
|
|
1007
|
+
// If the Docker image already exists, treat openclaw as installed even when
|
|
1008
|
+
// the local npm package is absent — the image is all that's needed to run instances.
|
|
1009
|
+
if (!openclaw.ok && runtimeImageReady) {
|
|
1010
|
+
const imageTag = resolveDockerImageTag();
|
|
1011
|
+
const tagVersion = imageTag.split(":").pop() || "local";
|
|
1012
|
+
openclaw = { ok: true, version: tagVersion, path: imageTag };
|
|
1013
|
+
}
|
|
1014
|
+
const openclawStatus = {
|
|
1015
|
+
name: "OpenClaw",
|
|
1016
|
+
installed: openclaw.ok,
|
|
1017
|
+
running: false,
|
|
1018
|
+
version: openclaw.version.split("\n")[0],
|
|
1019
|
+
path: openclaw.path,
|
|
1020
|
+
};
|
|
1021
|
+
const runtimes = await buildRuntimesStatus();
|
|
1022
|
+
const ready = nodeStatus.installed
|
|
1023
|
+
&& !nodeStatus.needsUpgrade
|
|
1024
|
+
&& dockerStatus.installed
|
|
1025
|
+
&& dockerStatus.running
|
|
1026
|
+
&& nomadStatus.installed
|
|
1027
|
+
&& nomadStatus.running
|
|
1028
|
+
&& requiredRuntimesReady(runtimes);
|
|
1029
|
+
const providerConfigured = hasLlmProviderSetup();
|
|
1030
|
+
// Read-only check: report whether cgroup memory needs fixing (actual fix is in POST install routes)
|
|
1031
|
+
let needsReboot;
|
|
1032
|
+
if (dockerStatus.installed && !isCgroupMemoryEnabled()) {
|
|
1033
|
+
needsReboot = true;
|
|
1034
|
+
}
|
|
1035
|
+
const runningTasks = getRunningTasks();
|
|
1036
|
+
return { node: nodeStatus, docker: dockerStatus, nomad: nomadStatus, openclaw: openclawStatus, ready, providerConfigured, runtimeImageReady, hasSudo: checkSudo(), needsReboot, runningTasks: runningTasks.length ? runningTasks : undefined, runtimes };
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Build the per-runtime install status block by iterating over every
|
|
1040
|
+
* registered integration. A new integration that implements `getInstallStatus()`
|
|
1041
|
+
* automatically appears here — no change required in this function.
|
|
1042
|
+
*
|
|
1043
|
+
* Each probe is bounded by a short timeout so a hung integration (slow docker
|
|
1044
|
+
* socket, DNS stall) cannot block the `/api/setup/status` endpoint. This
|
|
1045
|
+
* mirrors the pattern used by `services/integrations/catalog.ts` so the
|
|
1046
|
+
* Setup wizard and `/api/integrations` see a consistent view.
|
|
1047
|
+
*/
|
|
1048
|
+
const BUILD_RUNTIMES_PROBE_TIMEOUT_MS = 2500;
|
|
1049
|
+
function requiredRuntimesReady(runtimes) {
|
|
1050
|
+
const required = listRegisteredIntegrations().filter((integration) => integration.manifest?.requiredForSetupReady === true);
|
|
1051
|
+
if (required.length === 0)
|
|
1052
|
+
return false;
|
|
1053
|
+
return required.every((integration) => {
|
|
1054
|
+
const status = runtimes?.[integration.kind];
|
|
1055
|
+
return status?.installed === true && status.imageReady === true;
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
async function buildRuntimesStatus() {
|
|
1059
|
+
const integrations = listRegisteredIntegrations().filter((a) => typeof a.getInstallStatus === "function");
|
|
1060
|
+
const probes = integrations.map(async (integration) => {
|
|
1061
|
+
let timer;
|
|
1062
|
+
try {
|
|
1063
|
+
const status = await Promise.race([
|
|
1064
|
+
Promise.resolve(integration.getInstallStatus()),
|
|
1065
|
+
new Promise((_, reject) => {
|
|
1066
|
+
timer = setTimeout(() => reject(new Error("install-status probe timed out")), BUILD_RUNTIMES_PROBE_TIMEOUT_MS);
|
|
1067
|
+
}),
|
|
1068
|
+
]);
|
|
1069
|
+
if (!status)
|
|
1070
|
+
return null;
|
|
1071
|
+
const sync = status;
|
|
1072
|
+
return [integration.kind, { ...sync, required: integration.manifest?.requiredForSetupReady === true }];
|
|
1073
|
+
}
|
|
1074
|
+
catch {
|
|
1075
|
+
return null;
|
|
1076
|
+
}
|
|
1077
|
+
finally {
|
|
1078
|
+
if (timer)
|
|
1079
|
+
clearTimeout(timer);
|
|
1080
|
+
}
|
|
1081
|
+
});
|
|
1082
|
+
const out = {};
|
|
1083
|
+
for (const result of await Promise.all(probes)) {
|
|
1084
|
+
if (result)
|
|
1085
|
+
out[result[0]] = result[1];
|
|
1086
|
+
}
|
|
1087
|
+
return Object.keys(out).length ? out : undefined;
|
|
1088
|
+
}
|
|
1089
|
+
// ── Upgrade Node.js ────────────────────────────────────────────────
|
|
1090
|
+
export async function upgradeNode(targetMajor = 22) {
|
|
1091
|
+
try {
|
|
1092
|
+
const current = process.version; // e.g. "v20.20.1"
|
|
1093
|
+
const currentMajor = parseInt(current.replace("v", "").split(".")[0], 10);
|
|
1094
|
+
if (currentMajor >= targetMajor) {
|
|
1095
|
+
return { ok: true, message: `Node.js ${current} already meets requirement (>= ${targetMajor})` };
|
|
1096
|
+
}
|
|
1097
|
+
const task = createTask("node-upgrade");
|
|
1098
|
+
emitTask(task, { type: "progress", message: `升级 Node.js: ${current} → v${targetMajor}`, progress: 0 });
|
|
1099
|
+
if (process.platform === "darwin") {
|
|
1100
|
+
try {
|
|
1101
|
+
execSync("brew --version", { timeout: 5000 });
|
|
1102
|
+
}
|
|
1103
|
+
catch {
|
|
1104
|
+
emitTask(task, { type: "error", message: "未找到 Homebrew" });
|
|
1105
|
+
task.status = "error";
|
|
1106
|
+
return { ok: false, message: "Homebrew not found", error: "Install Homebrew first: https://brew.sh", taskId: task.id };
|
|
1107
|
+
}
|
|
1108
|
+
emitTask(task, { type: "progress", message: `安装 Node.js ${targetMajor} (brew)...`, progress: 20 });
|
|
1109
|
+
const installResult = await spawnWithTask(task, "brew", ["install", `node@${targetMajor}`], { timeout: 300000 });
|
|
1110
|
+
if (!installResult.ok) {
|
|
1111
|
+
emitTask(task, { type: "error", message: "Node.js 安装失败" });
|
|
1112
|
+
task.status = "error";
|
|
1113
|
+
return { ok: false, message: "Node.js install failed", error: installResult.output, taskId: task.id };
|
|
1114
|
+
}
|
|
1115
|
+
emitTask(task, { type: "progress", message: "链接 Node.js...", progress: 80 });
|
|
1116
|
+
try {
|
|
1117
|
+
execSync(`brew link --overwrite --force node@${targetMajor}`, { timeout: 30000 });
|
|
1118
|
+
}
|
|
1119
|
+
catch { }
|
|
1120
|
+
}
|
|
1121
|
+
else {
|
|
1122
|
+
// Use NodeSource setup script
|
|
1123
|
+
emitTask(task, { type: "log", message: "下载 NodeSource 安装脚本..." });
|
|
1124
|
+
emitTask(task, { type: "progress", message: "配置软件源...", progress: 10 });
|
|
1125
|
+
// Use a unique temp path to prevent TOCTOU race: a fixed name like
|
|
1126
|
+
// /tmp/nodesource_setup_22.sh could be replaced by another process
|
|
1127
|
+
// between download (curl) and execution (sudo bash).
|
|
1128
|
+
const nsScriptPath = join(tmpdir(), `nodesource_setup_${targetMajor}_${randomBytes(8).toString("hex")}.sh`);
|
|
1129
|
+
const dlResult = await spawnWithTask(task, "curl", ["-fsSL", `https://deb.nodesource.com/setup_${targetMajor}.x`, "-o", nsScriptPath], { timeout: 60000 });
|
|
1130
|
+
if (!dlResult.ok) {
|
|
1131
|
+
emitTask(task, { type: "error", message: "NodeSource 脚本下载失败" });
|
|
1132
|
+
task.status = "error";
|
|
1133
|
+
return { ok: false, message: "NodeSource script download failed", error: dlResult.output, taskId: task.id };
|
|
1134
|
+
}
|
|
1135
|
+
const setupResult = await spawnWithTask(task, "sudo", ["-E", "bash", nsScriptPath], { timeout: 120000 });
|
|
1136
|
+
if (!setupResult.ok) {
|
|
1137
|
+
emitTask(task, { type: "error", message: "NodeSource 配置失败" });
|
|
1138
|
+
task.status = "error";
|
|
1139
|
+
return { ok: false, message: "Node.js source setup failed", error: setupResult.output, taskId: task.id };
|
|
1140
|
+
}
|
|
1141
|
+
emitTask(task, { type: "progress", message: "安装 Node.js...", progress: 50 });
|
|
1142
|
+
const installResult = await spawnWithTask(task, "sudo", ["apt-get", "install", "-y", "nodejs"], { timeout: 300000 });
|
|
1143
|
+
if (!installResult.ok) {
|
|
1144
|
+
emitTask(task, { type: "error", message: "Node.js 安装失败" });
|
|
1145
|
+
task.status = "error";
|
|
1146
|
+
return { ok: false, message: "Node.js install failed", error: installResult.output, taskId: task.id };
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
const newVersion = execSync("node --version", { encoding: "utf-8", timeout: 5000 }).trim();
|
|
1150
|
+
emitTask(task, { type: "done", message: `Node.js 升级完成: ${newVersion}`, progress: 100 });
|
|
1151
|
+
task.status = "done";
|
|
1152
|
+
return { ok: true, message: `Node.js upgraded to ${newVersion}. 请重启 JishuShell 以使用新版本。`, taskId: task.id };
|
|
1153
|
+
}
|
|
1154
|
+
catch (e) {
|
|
1155
|
+
return { ok: false, message: "Node.js upgrade failed", error: e.message };
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
// ── Install Docker (async with progress) ───────────────────────────
|
|
1159
|
+
async function installDockerWithTask(task) {
|
|
1160
|
+
try {
|
|
1161
|
+
emitTask(task, { type: "progress", message: "开始安装 Docker...", progress: 0 });
|
|
1162
|
+
// macOS short-circuit: the rest of this function targets apt/get.docker.com,
|
|
1163
|
+
// both of which silently no-op or hard-fail on Darwin. Docker on macOS is
|
|
1164
|
+
// provided by Colima (auto-installed by the bash installer at
|
|
1165
|
+
// install/jishu-install.sh) — not by this code path. Refuse early with a
|
|
1166
|
+
// clear pointer so the user doesn't see a confusing apt error.
|
|
1167
|
+
if (process.platform === "darwin") {
|
|
1168
|
+
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。";
|
|
1169
|
+
emitTask(task, { type: "error", message: msg });
|
|
1170
|
+
task.status = "error";
|
|
1171
|
+
return { ok: false, message: msg, error: "darwin-not-supported-via-this-path" };
|
|
1172
|
+
}
|
|
1173
|
+
const user = execSync("whoami", { encoding: "utf-8", timeout: 5000 }).trim();
|
|
1174
|
+
// ── Try the get.docker.com convenience script ─────────────────
|
|
1175
|
+
emitTask(task, { type: "log", message: "下载 Docker 安装脚本..." });
|
|
1176
|
+
emitTask(task, { type: "progress", message: "下载中...", progress: 5 });
|
|
1177
|
+
// Unique temp path prevents TOCTOU: another user can't swap a fixed /tmp/get-docker.sh
|
|
1178
|
+
// between our download and the sudo sh invocation.
|
|
1179
|
+
const dockerScriptPath = join(tmpdir(), `get-docker_${randomBytes(8).toString("hex")}.sh`);
|
|
1180
|
+
const dlResult = await spawnWithTask(task, "curl", ["-fsSL", "https://get.docker.com", "-o", dockerScriptPath], {
|
|
1181
|
+
timeout: 60000,
|
|
1182
|
+
});
|
|
1183
|
+
let scriptOk = false;
|
|
1184
|
+
if (dlResult.ok) {
|
|
1185
|
+
emitTask(task, { type: "log", message: "运行 Docker 安装脚本..." });
|
|
1186
|
+
const scriptResult = await spawnWithTask(task, "sudo", ["sh", dockerScriptPath], {
|
|
1187
|
+
timeout: 600000,
|
|
1188
|
+
progressParser: (line) => {
|
|
1189
|
+
if (line.includes("Executing docker install script"))
|
|
1190
|
+
return 10;
|
|
1191
|
+
if (line.includes("apt-get update"))
|
|
1192
|
+
return 20;
|
|
1193
|
+
if (line.includes("Installing packages"))
|
|
1194
|
+
return 40;
|
|
1195
|
+
if (line.includes("docker-ce"))
|
|
1196
|
+
return 60;
|
|
1197
|
+
if (line.includes("containerd"))
|
|
1198
|
+
return 70;
|
|
1199
|
+
if (line.includes("Created symlink"))
|
|
1200
|
+
return 85;
|
|
1201
|
+
return null;
|
|
1202
|
+
},
|
|
1203
|
+
});
|
|
1204
|
+
scriptOk = scriptResult.ok;
|
|
1205
|
+
if (!scriptOk) {
|
|
1206
|
+
emitTask(task, { type: "log", message: "便捷脚本安装失败,尝试 apt 仓库方式..." });
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
else {
|
|
1210
|
+
emitTask(task, { type: "log", message: "下载便捷脚本失败,尝试 apt 仓库方式..." });
|
|
1211
|
+
}
|
|
1212
|
+
// ── Fallback: apt-repo manual install (handles Debian trixie pre-releases) ─
|
|
1213
|
+
if (!scriptOk) {
|
|
1214
|
+
emitTask(task, { type: "progress", message: "通过 apt 仓库安装 Docker...", progress: 15 });
|
|
1215
|
+
// Detect system ID + codename; downgrade trixie/sid to bookworm.
|
|
1216
|
+
let codename = "bookworm";
|
|
1217
|
+
let repoOs = "debian";
|
|
1218
|
+
try {
|
|
1219
|
+
const osRelease = readFileSync("/etc/os-release", "utf-8");
|
|
1220
|
+
const codenameMatch = osRelease.match(/^VERSION_CODENAME=(.+)$/m);
|
|
1221
|
+
const distroId = (osRelease.match(/^ID=(.+)$/m)?.[1] || "debian").trim();
|
|
1222
|
+
const raw = (codenameMatch?.[1] || "").trim();
|
|
1223
|
+
repoOs = (distroId === "ubuntu") ? "ubuntu" : "debian";
|
|
1224
|
+
// Debian testing/unstable (trixie, sid) → use the previous stable repo.
|
|
1225
|
+
const unstableCodenames = new Set(["trixie", "forky", "sid", "unstable", "testing"]);
|
|
1226
|
+
const ubuntuFallback = "noble";
|
|
1227
|
+
if (distroId === "ubuntu" && unstableCodenames.has(raw)) {
|
|
1228
|
+
codename = ubuntuFallback;
|
|
1229
|
+
}
|
|
1230
|
+
else if (unstableCodenames.has(raw)) {
|
|
1231
|
+
codename = "bookworm";
|
|
1232
|
+
}
|
|
1233
|
+
else {
|
|
1234
|
+
codename = raw || "bookworm";
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
catch { }
|
|
1238
|
+
emitTask(task, { type: "log", message: `使用仓库 codename: ${codename} (OS: ${repoOs})` });
|
|
1239
|
+
const aptSteps = [
|
|
1240
|
+
["sudo", ["apt-get", "update", "-qq"]],
|
|
1241
|
+
["sudo", ["apt-get", "install", "-y", "-qq", "ca-certificates", "curl", "gnupg"]],
|
|
1242
|
+
["sudo", ["install", "-m", "0755", "-d", "/etc/apt/keyrings"]],
|
|
1243
|
+
["sudo", ["bash", "-c",
|
|
1244
|
+
`curl -fsSL https://download.docker.com/linux/${repoOs}/gpg | gpg --dearmor --yes -o /etc/apt/keyrings/docker.gpg && chmod a+r /etc/apt/keyrings/docker.gpg`
|
|
1245
|
+
]],
|
|
1246
|
+
["sudo", ["bash", "-c",
|
|
1247
|
+
`echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/${repoOs} ${codename} stable" > /etc/apt/sources.list.d/docker.list`
|
|
1248
|
+
]],
|
|
1249
|
+
["sudo", ["apt-get", "update", "-qq"]],
|
|
1250
|
+
["sudo", ["apt-get", "install", "-y", "-qq",
|
|
1251
|
+
"docker-ce", "docker-ce-cli", "containerd.io", "docker-buildx-plugin", "docker-compose-plugin"
|
|
1252
|
+
]],
|
|
1253
|
+
];
|
|
1254
|
+
const progressSteps = [5, 15, 20, 30, 35, 45, 80];
|
|
1255
|
+
for (let i = 0; i < aptSteps.length; i++) {
|
|
1256
|
+
const [cmd, args] = aptSteps[i];
|
|
1257
|
+
emitTask(task, { type: "progress", message: `${cmd} ${args[0]}...`, progress: progressSteps[i] });
|
|
1258
|
+
const r = await spawnWithTask(task, cmd, args, { timeout: 300000 });
|
|
1259
|
+
if (!r.ok) {
|
|
1260
|
+
emitTask(task, { type: "error", message: `apt 安装失败 (步骤 ${i + 1}): ${args.join(" ")}` });
|
|
1261
|
+
task.status = "error";
|
|
1262
|
+
return { ok: false, message: "Docker apt installation failed", error: r.output, taskId: task.id };
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
// ── Wrap up ────────────────────────────────────────────────────
|
|
1267
|
+
emitTask(task, { type: "progress", message: "配置用户权限...", progress: 90 });
|
|
1268
|
+
try {
|
|
1269
|
+
execFileSync("sudo", ["groupadd", "docker"], { timeout: 10000 });
|
|
1270
|
+
}
|
|
1271
|
+
catch { }
|
|
1272
|
+
try {
|
|
1273
|
+
execFileSync("sudo", ["usermod", "-aG", "docker", user], { timeout: 10000 });
|
|
1274
|
+
}
|
|
1275
|
+
catch { }
|
|
1276
|
+
emitTask(task, { type: "log", message: "注意: Docker 用户组变更可能需要重新登录才能生效" });
|
|
1277
|
+
emitTask(task, { type: "progress", message: "启动 Docker 服务...", progress: 93 });
|
|
1278
|
+
try {
|
|
1279
|
+
execSync("sudo systemctl enable --now docker", { timeout: 30000 });
|
|
1280
|
+
}
|
|
1281
|
+
catch { }
|
|
1282
|
+
// Enable cgroup memory for Docker stats (RPi default has it disabled)
|
|
1283
|
+
const needsReboot = ensureCgroupMemory();
|
|
1284
|
+
if (needsReboot) {
|
|
1285
|
+
emitTask(task, { type: "log", message: "已启用内存 cgroup(需要重启生效,Docker 内存统计才会正常)" });
|
|
1286
|
+
}
|
|
1287
|
+
const version = getDockerVersionLine(10000);
|
|
1288
|
+
// Verify the daemon via `sudo docker` (needed when the current user is not in the docker group yet).
|
|
1289
|
+
const daemonOk = canAccessDockerDaemon(10000);
|
|
1290
|
+
if (daemonOk) {
|
|
1291
|
+
const doneMsg = needsReboot
|
|
1292
|
+
? `Docker 安装完成: ${version}(需要重启以启用内存统计)`
|
|
1293
|
+
: `Docker 安装完成: ${version}`;
|
|
1294
|
+
emitTask(task, { type: "done", message: doneMsg, progress: 100 });
|
|
1295
|
+
task.status = "done";
|
|
1296
|
+
return { ok: true, message: `Docker installed: ${version}`, needsReboot, taskId: task.id };
|
|
1297
|
+
}
|
|
1298
|
+
else {
|
|
1299
|
+
emitTask(task, { type: "error", message: `Docker 已安装但 daemon 未运行: ${version}` });
|
|
1300
|
+
task.status = "error";
|
|
1301
|
+
return { ok: false, message: `Docker installed but daemon not accessible`, error: "Try: sudo systemctl start docker", taskId: task.id };
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
catch (e) {
|
|
1305
|
+
return { ok: false, message: "Docker installation failed", error: e.message };
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
/** Ensure current user is in docker group and service is enabled (idempotent) */
|
|
1309
|
+
function ensureDockerPostInstall() {
|
|
1310
|
+
try {
|
|
1311
|
+
const user = execSync("whoami", { encoding: "utf-8", timeout: 5000 }).trim();
|
|
1312
|
+
try {
|
|
1313
|
+
execFileSync("sudo", ["groupadd", "docker"], { timeout: 5000, stdio: "pipe" });
|
|
1314
|
+
}
|
|
1315
|
+
catch { }
|
|
1316
|
+
try {
|
|
1317
|
+
execFileSync("sudo", ["usermod", "-aG", "docker", user], { timeout: 5000 });
|
|
1318
|
+
}
|
|
1319
|
+
catch { }
|
|
1320
|
+
try {
|
|
1321
|
+
execFileSync("sudo", ["systemctl", "enable", "--now", "docker"], { timeout: 10000 });
|
|
1322
|
+
}
|
|
1323
|
+
catch { }
|
|
1324
|
+
}
|
|
1325
|
+
catch { }
|
|
1326
|
+
}
|
|
1327
|
+
export async function installDocker() {
|
|
1328
|
+
if (canAccessDockerDaemon(5000)) {
|
|
1329
|
+
ensureDockerPostInstall();
|
|
1330
|
+
const needsReboot = ensureCgroupMemory();
|
|
1331
|
+
return { ok: true, message: "Docker already installed", needsReboot };
|
|
1332
|
+
}
|
|
1333
|
+
const task = createTask("docker");
|
|
1334
|
+
return installDockerWithTask(task);
|
|
1335
|
+
}
|
|
1336
|
+
export function startInstallDocker() {
|
|
1337
|
+
if (canAccessDockerDaemon(5000)) {
|
|
1338
|
+
ensureDockerPostInstall();
|
|
1339
|
+
ensureCgroupMemory();
|
|
1340
|
+
return { ok: true, message: "Docker already installed" };
|
|
1341
|
+
}
|
|
1342
|
+
const task = createTask("docker");
|
|
1343
|
+
void installDockerWithTask(task).catch((err) => {
|
|
1344
|
+
emitTask(task, { type: "error", message: `Docker 安装失败: ${err?.message || err}` });
|
|
1345
|
+
task.status = "error";
|
|
1346
|
+
});
|
|
1347
|
+
return { ok: true, message: "Docker install started", taskId: task.id };
|
|
1348
|
+
}
|
|
1349
|
+
// ── Install Nomad (async with progress) ────────────────────────────
|
|
1350
|
+
function getNomadDownloadUrl() {
|
|
1351
|
+
const arch = process.arch === "arm64" ? "arm64" : "amd64";
|
|
1352
|
+
const os = process.platform === "linux" ? "linux" : "darwin";
|
|
1353
|
+
return `https://releases.hashicorp.com/nomad/${NOMAD_VERSION}/nomad_${NOMAD_VERSION}_${os}_${arch}.zip`;
|
|
1354
|
+
}
|
|
1355
|
+
/**
|
|
1356
|
+
* Signal nomad agents by exact process name (pgrep -x nomad) to avoid the
|
|
1357
|
+
* classic pkill -f self-match bug: a command line like "pkill -f 'nomad agent'"
|
|
1358
|
+
* literally contains the pattern and pkill kills itself before reaching the
|
|
1359
|
+
* real nomad process. pgrep's own comm is "pgrep" (not "nomad") so -x nomad
|
|
1360
|
+
* cannot self-match. Unprivileged kill is tried first; sudo -n as a fallback
|
|
1361
|
+
* if the running nomad is owned by root (1.6.5 User=root unit).
|
|
1362
|
+
*/
|
|
1363
|
+
function killNomadByProcName() {
|
|
1364
|
+
const collect = () => {
|
|
1365
|
+
try {
|
|
1366
|
+
const out = execSync("pgrep -x nomad 2>/dev/null || true", { encoding: "utf-8" }).trim();
|
|
1367
|
+
return out.split("\n").filter(Boolean);
|
|
1368
|
+
}
|
|
1369
|
+
catch {
|
|
1370
|
+
return [];
|
|
1371
|
+
}
|
|
1372
|
+
};
|
|
1373
|
+
const sendSignal = (sig, pids) => {
|
|
1374
|
+
if (pids.length === 0)
|
|
1375
|
+
return;
|
|
1376
|
+
try {
|
|
1377
|
+
execFileSync("sudo", ["-n", "kill", `-${sig}`, ...pids], { timeout: 5000, stdio: "pipe" });
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
catch { }
|
|
1381
|
+
try {
|
|
1382
|
+
execSync(`kill -${sig} ${pids.join(" ")} 2>/dev/null || true`, { timeout: 5000 });
|
|
1383
|
+
}
|
|
1384
|
+
catch { }
|
|
1385
|
+
};
|
|
1386
|
+
let pids = collect();
|
|
1387
|
+
sendSignal("TERM", pids);
|
|
1388
|
+
if (pids.length > 0) {
|
|
1389
|
+
// Short grace period, then SIGKILL any survivors.
|
|
1390
|
+
execSync("sleep 2");
|
|
1391
|
+
pids = collect();
|
|
1392
|
+
sendSignal("KILL", pids);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
/** Compare two "a.b.c" semver strings; returns a > b. */
|
|
1396
|
+
function isNomadVersionGreater(a, b) {
|
|
1397
|
+
const parse = (v) => v.replace(/^v/, "").split(".").map(n => parseInt(n, 10) || 0);
|
|
1398
|
+
const [aMaj, aMin, aPat] = parse(a);
|
|
1399
|
+
const [bMaj, bMin, bPat] = parse(b);
|
|
1400
|
+
if (aMaj !== bMaj)
|
|
1401
|
+
return aMaj > bMaj;
|
|
1402
|
+
if (aMin !== bMin)
|
|
1403
|
+
return aMin > bMin;
|
|
1404
|
+
return aPat > bPat;
|
|
1405
|
+
}
|
|
1406
|
+
/**
|
|
1407
|
+
* Auto-migrate from a higher Nomad version (e.g. 1.11.3 BSL) back to the
|
|
1408
|
+
* jishushell target (1.6.5 MPL). Called when installNomad detects a local
|
|
1409
|
+
* binary whose semver is > NOMAD_VERSION. Destructive to Nomad's raft state
|
|
1410
|
+
* (schema is not backward compatible) but preserves instance configs under
|
|
1411
|
+
* ~/.jishushell/instances/*. A single tar.gz snapshot of the old data_dir
|
|
1412
|
+
* is kept under ~/.jishushell/nomad/backups/ for forensic inspection.
|
|
1413
|
+
*
|
|
1414
|
+
* Safe-first: the new binary is downloaded and verified BEFORE any existing
|
|
1415
|
+
* state is touched. If any stage 1 step fails, state is untouched.
|
|
1416
|
+
*
|
|
1417
|
+
* Throws on failure so the caller's outer catch reports the error.
|
|
1418
|
+
*/
|
|
1419
|
+
async function migrateNomadToTarget(currentVersion) {
|
|
1420
|
+
console.log(`[nomad] Auto-migrating v${currentVersion} → v${NOMAD_VERSION} (BSL → MPL)`);
|
|
1421
|
+
console.log("[nomad] Raft state is not backward-compatible; allocation history will be reset.");
|
|
1422
|
+
console.log("[nomad] Instance configs under ~/.jishushell/instances/ are preserved.");
|
|
1423
|
+
// ── Stage 1: download + verify new binary into a staging dir ─────────
|
|
1424
|
+
const stageDir = mkdtempSync(join(tmpdir(), "nomad-migrate-"));
|
|
1425
|
+
let backupFile = "";
|
|
1426
|
+
try {
|
|
1427
|
+
const stagedBin = join(stageDir, "nomad");
|
|
1428
|
+
const zipPath = join(stageDir, "nomad.zip");
|
|
1429
|
+
const url = getNomadDownloadUrl();
|
|
1430
|
+
const arch = process.arch === "arm64" ? "arm64" : "amd64";
|
|
1431
|
+
const os = process.platform === "linux" ? "linux" : "darwin";
|
|
1432
|
+
const sumsUrl = `https://releases.hashicorp.com/nomad/${NOMAD_VERSION}/nomad_${NOMAD_VERSION}_SHA256SUMS`;
|
|
1433
|
+
const sumsPath = join(stageDir, "SHA256SUMS");
|
|
1434
|
+
console.log(`[nomad] Staging v${NOMAD_VERSION} (${os}/${arch})...`);
|
|
1435
|
+
execFileSync("curl", ["-fsSL", url, "-o", zipPath], { timeout: 300000, stdio: "pipe" });
|
|
1436
|
+
execFileSync("curl", ["-fsSL", sumsUrl, "-o", sumsPath], { timeout: 30000, stdio: "pipe" });
|
|
1437
|
+
const sums = readFileSync(sumsPath, "utf-8");
|
|
1438
|
+
const sumLine = sums.split("\n").find(l => l.includes(`nomad_${NOMAD_VERSION}_${os}_${arch}.zip`));
|
|
1439
|
+
if (!sumLine)
|
|
1440
|
+
throw new Error(`No checksum entry for nomad_${NOMAD_VERSION}_${os}_${arch}.zip`);
|
|
1441
|
+
const expected = sumLine.split(/\s+/)[0];
|
|
1442
|
+
// Match the bash installer: prefer sha256sum (GNU coreutils, Linux),
|
|
1443
|
+
// fall back to shasum -a 256 (BSD, macOS default — sha256sum is not
|
|
1444
|
+
// preinstalled there). Without this, triggering auto-migration from
|
|
1445
|
+
// the WebUI / Node path on macOS would fail even though the shell
|
|
1446
|
+
// installer works fine.
|
|
1447
|
+
let actual;
|
|
1448
|
+
try {
|
|
1449
|
+
actual = execSync(`sha256sum "${zipPath}" | awk '{print $1}'`, { encoding: "utf-8" }).trim();
|
|
1450
|
+
}
|
|
1451
|
+
catch {
|
|
1452
|
+
actual = execSync(`shasum -a 256 "${zipPath}" | awk '{print $1}'`, { encoding: "utf-8" }).trim();
|
|
1453
|
+
}
|
|
1454
|
+
if (expected !== actual) {
|
|
1455
|
+
throw new Error(`Nomad checksum mismatch: expected ${expected}, got ${actual}`);
|
|
1456
|
+
}
|
|
1457
|
+
console.log("[nomad] Checksum verified");
|
|
1458
|
+
execFileSync("unzip", ["-o", zipPath, "-d", stageDir], { timeout: 30000 });
|
|
1459
|
+
chmodSync(stagedBin, 0o755);
|
|
1460
|
+
const stagedVersionLine = execFileSync(stagedBin, ["version"], { encoding: "utf-8", timeout: 5000 }).trim().split("\n")[0];
|
|
1461
|
+
if (!stagedVersionLine.includes(`v${NOMAD_VERSION}`)) {
|
|
1462
|
+
throw new Error(`Staged binary reports "${stagedVersionLine}", expected v${NOMAD_VERSION}`);
|
|
1463
|
+
}
|
|
1464
|
+
console.log(`[nomad] Staged ${stagedVersionLine}`);
|
|
1465
|
+
// ── Stage 2: destructive state changes begin ───────────────────────
|
|
1466
|
+
console.log("[nomad] Stopping services...");
|
|
1467
|
+
try {
|
|
1468
|
+
execFileSync("sudo", ["-n", "systemctl", "stop", "jishushell"], { timeout: 15000, stdio: "pipe" });
|
|
1469
|
+
}
|
|
1470
|
+
catch { }
|
|
1471
|
+
try {
|
|
1472
|
+
execFileSync("sudo", ["-n", "systemctl", "stop", "nomad"], { timeout: 15000, stdio: "pipe" });
|
|
1473
|
+
}
|
|
1474
|
+
catch { }
|
|
1475
|
+
// pkill -f 'nomad agent' matches pkill's own cmdline and self-terminates
|
|
1476
|
+
// before reaching the real nomad process. Use pgrep -x nomad (exact proc
|
|
1477
|
+
// name match; pgrep's comm is "pgrep") to avoid the self-match bug.
|
|
1478
|
+
killNomadByProcName();
|
|
1479
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
1480
|
+
// ── Stage 3: tar backup (single snapshot, overwrite any previous) ──
|
|
1481
|
+
const backupDir = join(NOMAD_CONFIG_DIR, "backups");
|
|
1482
|
+
if (existsSync(NOMAD_DATA_DIR)) {
|
|
1483
|
+
try {
|
|
1484
|
+
ensureDirHost(backupDir);
|
|
1485
|
+
}
|
|
1486
|
+
catch { }
|
|
1487
|
+
const ts = new Date().toISOString().replace(/[-:]/g, "").replace(/\..+/, "").replace("T", "-");
|
|
1488
|
+
const candidate = join(backupDir, `data-${ts}.tar.gz`);
|
|
1489
|
+
console.log(`[nomad] Backing up raft state → ${candidate}`);
|
|
1490
|
+
try {
|
|
1491
|
+
execSync(`tar czf "${candidate}" -C "${NOMAD_CONFIG_DIR}" data 2>/dev/null`, { timeout: 120000 });
|
|
1492
|
+
backupFile = candidate;
|
|
1493
|
+
// Keep only the most recent snapshot
|
|
1494
|
+
try {
|
|
1495
|
+
const list = execSync(`ls -t "${backupDir}"/data-*.tar.gz 2>/dev/null | tail -n +2 || true`, { encoding: "utf-8" }).trim();
|
|
1496
|
+
for (const old of list.split("\n").filter(Boolean)) {
|
|
1497
|
+
try {
|
|
1498
|
+
unlinkSync(old);
|
|
1499
|
+
}
|
|
1500
|
+
catch { }
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
catch { }
|
|
1504
|
+
}
|
|
1505
|
+
catch {
|
|
1506
|
+
console.warn("[nomad] Backup tar failed — continuing (raft state will still be wiped)");
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
// ── Stage 4: wipe raft state + env files (schema incompatible) ─────
|
|
1510
|
+
try {
|
|
1511
|
+
execFileSync("sudo", ["-n", "rm", "-rf", NOMAD_DATA_DIR], { timeout: 15000, stdio: "pipe" });
|
|
1512
|
+
}
|
|
1513
|
+
catch {
|
|
1514
|
+
try {
|
|
1515
|
+
rmSync(NOMAD_DATA_DIR, { recursive: true, force: true });
|
|
1516
|
+
}
|
|
1517
|
+
catch { }
|
|
1518
|
+
}
|
|
1519
|
+
try {
|
|
1520
|
+
unlinkSync(join(JISHUSHELL_HOME, "nomad.env"));
|
|
1521
|
+
}
|
|
1522
|
+
catch { }
|
|
1523
|
+
try {
|
|
1524
|
+
execFileSync("sudo", ["-n", "rm", "-f", "/etc/jishushell/nomad.env"], { timeout: 5000, stdio: "pipe" });
|
|
1525
|
+
}
|
|
1526
|
+
catch { }
|
|
1527
|
+
// ── Stage 5: orphaned gateway containers ───────────────────────────
|
|
1528
|
+
// Panel normally has docker group via jishushell.service SupplementaryGroups,
|
|
1529
|
+
// but postinstall may run this helper from a shell where the invoking
|
|
1530
|
+
// user is not in docker group. Probe first, fall back to sudo docker.
|
|
1531
|
+
try {
|
|
1532
|
+
let dockerCmd = "docker";
|
|
1533
|
+
try {
|
|
1534
|
+
execSync("docker ps >/dev/null 2>&1", { timeout: 5000 });
|
|
1535
|
+
}
|
|
1536
|
+
catch {
|
|
1537
|
+
dockerCmd = "sudo -n docker";
|
|
1538
|
+
}
|
|
1539
|
+
const names = execSync(`${dockerCmd} ps -a --format '{{.Names}}' 2>/dev/null | grep '^gateway-' || true`, { encoding: "utf-8" }).trim();
|
|
1540
|
+
if (names) {
|
|
1541
|
+
const rows = names.split("\n").filter(Boolean);
|
|
1542
|
+
for (const name of rows) {
|
|
1543
|
+
try {
|
|
1544
|
+
execSync(`${dockerCmd} rm -f "${name}" 2>/dev/null`, { timeout: 10000 });
|
|
1545
|
+
}
|
|
1546
|
+
catch { }
|
|
1547
|
+
}
|
|
1548
|
+
console.log(`[nomad] Removed ${rows.length} orphaned gateway container(s)`);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
catch { }
|
|
1552
|
+
// ── Stage 6: swap binary into place (atomic via temp name + rename)
|
|
1553
|
+
ensureDirHost(BIN_DIR);
|
|
1554
|
+
const destTmp = `${NOMAD_BIN}.tmp.${process.pid}`;
|
|
1555
|
+
copyFileSync(stagedBin, destTmp);
|
|
1556
|
+
chmodSync(destTmp, 0o755);
|
|
1557
|
+
renameSync(destTmp, NOMAD_BIN);
|
|
1558
|
+
console.log(`[nomad] Migrated to v${NOMAD_VERSION}`);
|
|
1559
|
+
if (backupFile)
|
|
1560
|
+
console.log(`[nomad] Backup (forensic, not self-recovery): ${backupFile}`);
|
|
1561
|
+
console.log("[nomad] JishuShell will re-bootstrap ACL and resubmit jobs on next start.");
|
|
1562
|
+
}
|
|
1563
|
+
catch (err) {
|
|
1564
|
+
if (backupFile) {
|
|
1565
|
+
console.error(`[nomad] Migration failed — backup preserved at ${backupFile}`);
|
|
1566
|
+
}
|
|
1567
|
+
throw err;
|
|
1568
|
+
}
|
|
1569
|
+
finally {
|
|
1570
|
+
try {
|
|
1571
|
+
rmSync(stageDir, { recursive: true, force: true });
|
|
1572
|
+
}
|
|
1573
|
+
catch { }
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
export async function installNomad() {
|
|
1577
|
+
try {
|
|
1578
|
+
if (existsSync(NOMAD_BIN)) {
|
|
1579
|
+
// Boundary check 1: is it a regular file?
|
|
1580
|
+
try {
|
|
1581
|
+
const stat = (await import("fs")).statSync(NOMAD_BIN);
|
|
1582
|
+
if (!stat.isFile()) {
|
|
1583
|
+
try {
|
|
1584
|
+
unlinkSync(NOMAD_BIN);
|
|
1585
|
+
}
|
|
1586
|
+
catch { }
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
catch { }
|
|
1590
|
+
if (existsSync(NOMAD_BIN)) {
|
|
1591
|
+
// Boundary check 2: is it executable?
|
|
1592
|
+
try {
|
|
1593
|
+
chmodSync(NOMAD_BIN, 0o755);
|
|
1594
|
+
}
|
|
1595
|
+
catch { }
|
|
1596
|
+
// Boundary check 3: does it actually run?
|
|
1597
|
+
let versionLine = "";
|
|
1598
|
+
try {
|
|
1599
|
+
versionLine = execSync(`"${NOMAD_BIN}" version`, { encoding: "utf-8", timeout: 5000 }).trim().split("\n")[0];
|
|
1600
|
+
}
|
|
1601
|
+
catch {
|
|
1602
|
+
// Binary is corrupt or wrong arch — remove and reinstall
|
|
1603
|
+
try {
|
|
1604
|
+
unlinkSync(NOMAD_BIN);
|
|
1605
|
+
}
|
|
1606
|
+
catch { }
|
|
1607
|
+
}
|
|
1608
|
+
if (versionLine) {
|
|
1609
|
+
const match = versionLine.match(/v(\d+\.\d+\.\d+)/);
|
|
1610
|
+
const currentVersion = match ? match[1] : "";
|
|
1611
|
+
let migrated = false;
|
|
1612
|
+
if (currentVersion && isNomadVersionGreater(currentVersion, NOMAD_VERSION)) {
|
|
1613
|
+
// Current > target — auto-migrate (nomad 1.11.3 BSL → 1.6.5 MPL).
|
|
1614
|
+
// Migration failure is a hard stop: the old state has been
|
|
1615
|
+
// partially mutated (or about to be), returning falls through
|
|
1616
|
+
// to the reinstall path which would make a bad situation worse.
|
|
1617
|
+
try {
|
|
1618
|
+
await migrateNomadToTarget(currentVersion);
|
|
1619
|
+
}
|
|
1620
|
+
catch (migErr) {
|
|
1621
|
+
return {
|
|
1622
|
+
ok: false,
|
|
1623
|
+
message: "Nomad auto-migration failed",
|
|
1624
|
+
error: migErr?.message || String(migErr),
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
migrated = true;
|
|
1628
|
+
versionLine = execSync(`"${NOMAD_BIN}" version`, { encoding: "utf-8", timeout: 5000 }).trim().split("\n")[0];
|
|
1629
|
+
}
|
|
1630
|
+
// Always (re)write config so the installed nomad.hcl stays up-to-date
|
|
1631
|
+
// with the current defaults (e.g. raw_exec plugin, acl, limits).
|
|
1632
|
+
writeNomadConfig();
|
|
1633
|
+
// Ensure Nomad is started even if already installed
|
|
1634
|
+
if (!isPortListening(4646)) {
|
|
1635
|
+
try {
|
|
1636
|
+
installNomadSystemd();
|
|
1637
|
+
}
|
|
1638
|
+
catch { }
|
|
1639
|
+
if (!isPortListening(4646)) {
|
|
1640
|
+
await startNomad();
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
return migrated
|
|
1644
|
+
? { ok: true, message: `Nomad migrated to ${versionLine}` }
|
|
1645
|
+
: { ok: true, message: `Nomad already installed: ${versionLine}` };
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
// Fallback: if NOMAD_BIN still doesn't exist, check for a system-installed Nomad
|
|
1650
|
+
// (e.g. installed via apt/dnf) and create a symlink so the systemd service path resolves.
|
|
1651
|
+
if (!existsSync(NOMAD_BIN)) {
|
|
1652
|
+
try {
|
|
1653
|
+
const systemNomad = execSync("which nomad 2>/dev/null", { encoding: "utf-8" }).trim();
|
|
1654
|
+
if (systemNomad && existsSync(systemNomad)) {
|
|
1655
|
+
ensureDirHost(BIN_DIR);
|
|
1656
|
+
symlinkSync(systemNomad, NOMAD_BIN);
|
|
1657
|
+
const version = execSync(`${NOMAD_BIN} version`, { encoding: "utf-8", timeout: 5000 }).trim();
|
|
1658
|
+
console.log(`[nomad] Linked system nomad ${systemNomad} → ${NOMAD_BIN}`);
|
|
1659
|
+
writeNomadConfig();
|
|
1660
|
+
return { ok: true, message: `Nomad linked from system: ${version.split("\n")[0]}` };
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
catch { /* system nomad not found — proceed to download */ }
|
|
1664
|
+
}
|
|
1665
|
+
const task = createTask("nomad");
|
|
1666
|
+
ensureDirHost(BIN_DIR);
|
|
1667
|
+
const url = getNomadDownloadUrl();
|
|
1668
|
+
const zipPath = join(BIN_DIR, "nomad.zip");
|
|
1669
|
+
emitTask(task, { type: "progress", message: "下载 Nomad...", progress: 0 });
|
|
1670
|
+
const result = await spawnWithTask(task, "curl", ["-fL", "--progress-bar", url, "-o", zipPath], {
|
|
1671
|
+
timeout: 300000,
|
|
1672
|
+
progressParser: curlProgressParser,
|
|
1673
|
+
});
|
|
1674
|
+
if (!result.ok) {
|
|
1675
|
+
emitTask(task, { type: "error", message: "Nomad 下载失败" });
|
|
1676
|
+
task.status = "error";
|
|
1677
|
+
return { ok: false, message: "Nomad download failed", error: result.output, taskId: task.id };
|
|
1678
|
+
}
|
|
1679
|
+
emitTask(task, { type: "progress", message: "解压 Nomad...", progress: 80 });
|
|
1680
|
+
try {
|
|
1681
|
+
execSync("which unzip", { stdio: "ignore" });
|
|
1682
|
+
}
|
|
1683
|
+
catch {
|
|
1684
|
+
emitTask(task, { type: "log", message: "未找到 unzip,尝试安装..." });
|
|
1685
|
+
try {
|
|
1686
|
+
if (process.platform === "linux") {
|
|
1687
|
+
execSync("sudo apt-get update -qq && sudo apt-get install -y -qq unzip", { timeout: 60000 });
|
|
1688
|
+
}
|
|
1689
|
+
else if (process.platform === "darwin") {
|
|
1690
|
+
execSync("brew install unzip", { timeout: 60000 });
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
catch (e) {
|
|
1694
|
+
emitTask(task, { type: "error", message: "安装 unzip 失败,请手动安装后重试" });
|
|
1695
|
+
throw e;
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
execFileSync("unzip", ["-o", zipPath, "-d", BIN_DIR], { timeout: 30000 });
|
|
1699
|
+
chmodSync(NOMAD_BIN, 0o755);
|
|
1700
|
+
try {
|
|
1701
|
+
unlinkSync(zipPath);
|
|
1702
|
+
}
|
|
1703
|
+
catch { }
|
|
1704
|
+
const version = execFileSync(NOMAD_BIN, ["version"], { encoding: "utf-8", timeout: 5000 }).trim();
|
|
1705
|
+
emitTask(task, { type: "done", message: `Nomad 安装完成: ${version}`, progress: 100 });
|
|
1706
|
+
task.status = "done";
|
|
1707
|
+
return { ok: true, message: `Nomad installed: ${version}`, taskId: task.id };
|
|
1708
|
+
}
|
|
1709
|
+
catch (e) {
|
|
1710
|
+
return { ok: false, message: "Nomad installation failed", error: e.message };
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
// ── Configure & start Nomad ────────────────────────────────────────
|
|
1714
|
+
/** Fix ~/.jishushell dirs if they were accidentally created as root (e.g. from an old sudo install). */
|
|
1715
|
+
function fixNomadDirOwnership() {
|
|
1716
|
+
try {
|
|
1717
|
+
const { statSync } = require("fs");
|
|
1718
|
+
const stat = statSync(NOMAD_CONFIG_DIR);
|
|
1719
|
+
if (stat.uid === 0) {
|
|
1720
|
+
// Directory is owned by root — try to repair with sudo -n (non-interactive)
|
|
1721
|
+
const currentUser = process.env.USER || process.env.LOGNAME || "";
|
|
1722
|
+
if (currentUser) {
|
|
1723
|
+
try {
|
|
1724
|
+
execFileSync("sudo", ["-n", "chown", "-R", `${currentUser}:${currentUser}`, JISHUSHELL_HOME], { timeout: 10000, stdio: "pipe" });
|
|
1725
|
+
}
|
|
1726
|
+
catch { /* sudo not available or no NOPASSWD — ignore */ }
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
catch { /* stat failed — dir doesn't exist yet, no action needed */ }
|
|
1731
|
+
}
|
|
1732
|
+
export function buildExpectedNomadConfigContent() {
|
|
1733
|
+
const platform = osPlatform();
|
|
1734
|
+
const loopbackIface = platform === "darwin" ? "lo0" : "lo";
|
|
1735
|
+
const externalIface = detectNomadExternalInterface(loopbackIface);
|
|
1736
|
+
const externalHostNetworkBlock = externalIface
|
|
1737
|
+
? `
|
|
1738
|
+
host_network "external" {
|
|
1739
|
+
interface = "${externalIface}"
|
|
1740
|
+
}
|
|
1741
|
+
`
|
|
1742
|
+
: "";
|
|
1743
|
+
// Detect docker0 bridge CIDR so multi-container apps (weknora etc.) can
|
|
1744
|
+
// declare `host_network: docker_bridge` on their ports and have Nomad
|
|
1745
|
+
// publish them on the docker0 IP — which is what `host.docker.internal`
|
|
1746
|
+
// resolves to inside peer containers. Hardcoding 172.17.0.0/16 would
|
|
1747
|
+
// break setups where docker daemon.json overrides `bip` /
|
|
1748
|
+
// `default-address-pools`. Fall back to the Docker default if detection
|
|
1749
|
+
// fails (e.g. docker not yet installed at panel install time; doctor
|
|
1750
|
+
// will regenerate later).
|
|
1751
|
+
const dockerBridgeCidr = detectDockerBridgeCidr() || "172.17.0.0/16";
|
|
1752
|
+
const dockerBridgeHostNetworkBlock = `
|
|
1753
|
+
host_network "docker_bridge" {
|
|
1754
|
+
cidr = "${dockerBridgeCidr}"
|
|
1755
|
+
}
|
|
1756
|
+
`;
|
|
1757
|
+
// Apple Silicon CPU fingerprint workaround. Nomad 1.6.x on darwin/arm64
|
|
1758
|
+
// reads `hw.tbfrequency` (timebase, e.g. 24 MHz on M-series) as the CPU
|
|
1759
|
+
// clock, so a 14-core M4 Max reports as 48 MHz total compute and every
|
|
1760
|
+
// job that asks for CPU>48 gets rejected with "Resources exhausted on
|
|
1761
|
+
// dimension cpu". Override with a conservative estimate (cores × 2500
|
|
1762
|
+
// MHz) — M-series base clock is at least 2.5 GHz, so this never
|
|
1763
|
+
// overcommits relative to actual silicon. Intel Macs fingerprint
|
|
1764
|
+
// correctly and don't need the override.
|
|
1765
|
+
const cpuTotalComputeBlock = platform === "darwin" && osArch() === "arm64"
|
|
1766
|
+
? ` cpu_total_compute = ${osCpus().length * 2500} # Apple Silicon workaround: Nomad mis-fingerprints CPU freq via tbfrequency\n`
|
|
1767
|
+
: "";
|
|
1768
|
+
// macOS docker driver endpoint. macOS users run Docker via Colima
|
|
1769
|
+
// (managed by this panel under `~/.jishushell/colima/jishushell/`) or
|
|
1770
|
+
// Docker Desktop (`~/.docker/run/docker.sock`); neither exposes
|
|
1771
|
+
// `/var/run/docker.sock` out of the box. Resolve the correct host once
|
|
1772
|
+
// and bake it into the HCL so Nomad doesn't fall back to the default
|
|
1773
|
+
// `unix:///var/run/docker.sock` and fail. Linux keeps the default
|
|
1774
|
+
// (empty endpoint = Docker's standard /var/run/docker.sock).
|
|
1775
|
+
const dockerHost = platform === "darwin"
|
|
1776
|
+
? resolveDockerHost({ jishuHome: JISHUSHELL_HOME, colimaProfile: COLIMA_PROFILE })
|
|
1777
|
+
: undefined;
|
|
1778
|
+
const dockerEndpointLine = dockerHost ? `\n endpoint = "${dockerHost}"` : "";
|
|
1779
|
+
const config = `
|
|
1780
|
+
data_dir = "${NOMAD_DATA_DIR}"
|
|
1781
|
+
|
|
1782
|
+
bind_addr = "127.0.0.1"
|
|
1783
|
+
|
|
1784
|
+
leave_on_terminate = false
|
|
1785
|
+
|
|
1786
|
+
advertise {
|
|
1787
|
+
http = "127.0.0.1"
|
|
1788
|
+
rpc = "127.0.0.1"
|
|
1789
|
+
serf = "127.0.0.1"
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
server {
|
|
1793
|
+
enabled = true
|
|
1794
|
+
bootstrap_expect = 1
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
client {
|
|
1798
|
+
${cpuTotalComputeBlock} enabled = true
|
|
1799
|
+
servers = ["127.0.0.1:4647"]
|
|
1800
|
+
network_interface = "${loopbackIface}"
|
|
1801
|
+
alloc_dir = "${NOMAD_ALLOC_DIR}"
|
|
1802
|
+
${externalHostNetworkBlock}${dockerBridgeHostNetworkBlock}
|
|
1803
|
+
|
|
1804
|
+
# drain_on_shutdown intentionally omitted: on single-node Pi there is
|
|
1805
|
+
# nowhere to drain workloads to, and draining on every systemctl restart
|
|
1806
|
+
# would kill every running OpenClaw instance. Without this block Nomad
|
|
1807
|
+
# leaves allocations running across client restarts — the docker driver
|
|
1808
|
+
# re-attaches to the existing containers on startup.
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
plugin "docker" {
|
|
1812
|
+
config {${dockerEndpointLine}
|
|
1813
|
+
gc {
|
|
1814
|
+
image = false
|
|
1815
|
+
}
|
|
1816
|
+
volumes {
|
|
1817
|
+
enabled = true
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
plugin "raw_exec" {
|
|
1823
|
+
config {
|
|
1824
|
+
enabled = true
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
acl {
|
|
1829
|
+
enabled = true
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
limits {
|
|
1833
|
+
http_max_conns_per_client = 0
|
|
1834
|
+
}
|
|
1835
|
+
`;
|
|
1836
|
+
return config;
|
|
1837
|
+
}
|
|
1838
|
+
function writeNomadConfig() {
|
|
1839
|
+
fixNomadDirOwnership();
|
|
1840
|
+
ensureDirHost(NOMAD_CONFIG_DIR);
|
|
1841
|
+
ensureDirContainer(NOMAD_DATA_DIR);
|
|
1842
|
+
ensureDirContainer(NOMAD_ALLOC_DIR);
|
|
1843
|
+
writeConfigFile(join(NOMAD_CONFIG_DIR, "nomad.hcl"), buildExpectedNomadConfigContent());
|
|
1844
|
+
}
|
|
1845
|
+
function detectDockerBridgeCidr() {
|
|
1846
|
+
// `docker network inspect bridge` returns the CIDR for docker0. The exact
|
|
1847
|
+
// value depends on /etc/docker/daemon.json `bip` / `default-address-pools`
|
|
1848
|
+
// — Docker's default is 172.17.0.0/16 but isn't guaranteed. We need the
|
|
1849
|
+
// real value because Nomad uses `host_network "docker_bridge" { cidr = …}`
|
|
1850
|
+
// to pick which interface to publish a port on for specs that declare
|
|
1851
|
+
// `host_network: docker_bridge`. Wrong CIDR → port silently published on
|
|
1852
|
+
// wrong interface → cross-task host.docker.internal calls get refused.
|
|
1853
|
+
try {
|
|
1854
|
+
const raw = execFileSync("docker", ["network", "inspect", "bridge", "--format", "{{(index .IPAM.Config 0).Subnet}}"], { encoding: "utf8", timeout: 3000, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
1855
|
+
return /^\d{1,3}(\.\d{1,3}){3}\/\d{1,2}$/.test(raw) ? raw : "";
|
|
1856
|
+
}
|
|
1857
|
+
catch {
|
|
1858
|
+
return "";
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
function detectNomadExternalInterface(loopbackIface) {
|
|
1862
|
+
try {
|
|
1863
|
+
if (osPlatform() === "darwin") {
|
|
1864
|
+
const route = execFileSync("route", ["-n", "get", "default"], {
|
|
1865
|
+
encoding: "utf8",
|
|
1866
|
+
timeout: 3000,
|
|
1867
|
+
});
|
|
1868
|
+
const match = route.match(/interface:\s*(\S+)/);
|
|
1869
|
+
const iface = match?.[1]?.trim() ?? "";
|
|
1870
|
+
return iface && iface !== loopbackIface ? iface : "";
|
|
1871
|
+
}
|
|
1872
|
+
const route = execFileSync("ip", ["route", "show", "default"], {
|
|
1873
|
+
encoding: "utf8",
|
|
1874
|
+
timeout: 3000,
|
|
1875
|
+
});
|
|
1876
|
+
const match = route.match(/\bdev\s+(\S+)/);
|
|
1877
|
+
const iface = match?.[1]?.trim() ?? "";
|
|
1878
|
+
return iface && iface !== loopbackIface ? iface : "";
|
|
1879
|
+
}
|
|
1880
|
+
catch {
|
|
1881
|
+
return "";
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
export function loadNomadToken() {
|
|
1885
|
+
if (process.env.NOMAD_TOKEN)
|
|
1886
|
+
return;
|
|
1887
|
+
const candidates = [
|
|
1888
|
+
join(JISHUSHELL_HOME, "nomad.env"),
|
|
1889
|
+
"/etc/jishushell/nomad.env",
|
|
1890
|
+
];
|
|
1891
|
+
for (const envFile of candidates) {
|
|
1892
|
+
if (!existsSync(envFile))
|
|
1893
|
+
continue;
|
|
1894
|
+
try {
|
|
1895
|
+
const match = readFileSync(envFile, "utf-8").match(/^NOMAD_TOKEN=(.+)$/m);
|
|
1896
|
+
if (match) {
|
|
1897
|
+
process.env.NOMAD_TOKEN = match[1].trim();
|
|
1898
|
+
return;
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
catch {
|
|
1902
|
+
// no read permission, try next candidate
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
const legacy = getCoreConfig().nomad_token;
|
|
1906
|
+
if (legacy)
|
|
1907
|
+
process.env.NOMAD_TOKEN = legacy;
|
|
1908
|
+
}
|
|
1909
|
+
/** Verify the current NOMAD_TOKEN is accepted by the Nomad API. Returns false if it is missing or rejected (HTTP 403/401). */
|
|
1910
|
+
async function isNomadTokenValid() {
|
|
1911
|
+
const token = process.env.NOMAD_TOKEN || getCoreConfig().nomad_token;
|
|
1912
|
+
if (!token)
|
|
1913
|
+
return false;
|
|
1914
|
+
try {
|
|
1915
|
+
const resp = await fetch("http://127.0.0.1:4646/v1/acl/token/self", {
|
|
1916
|
+
headers: { "X-Nomad-Token": token },
|
|
1917
|
+
signal: AbortSignal.timeout(5000),
|
|
1918
|
+
});
|
|
1919
|
+
return resp.status === 200;
|
|
1920
|
+
}
|
|
1921
|
+
catch {
|
|
1922
|
+
return false;
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
function nomadApiHeaders(extra = {}) {
|
|
1926
|
+
const token = process.env.NOMAD_TOKEN || getCoreConfig().nomad_token;
|
|
1927
|
+
return token ? { "X-Nomad-Token": token, ...extra } : extra;
|
|
1928
|
+
}
|
|
1929
|
+
export async function ensureNomadMemoryOversubscriptionEnabled() {
|
|
1930
|
+
try {
|
|
1931
|
+
const resp = await fetch("http://127.0.0.1:4646/v1/operator/scheduler/configuration", {
|
|
1932
|
+
headers: nomadApiHeaders(),
|
|
1933
|
+
signal: AbortSignal.timeout(5000),
|
|
1934
|
+
});
|
|
1935
|
+
if (resp.status !== 200)
|
|
1936
|
+
return;
|
|
1937
|
+
const payload = await resp.json();
|
|
1938
|
+
const scheduler = payload?.SchedulerConfig;
|
|
1939
|
+
if (!scheduler || typeof scheduler !== "object")
|
|
1940
|
+
return;
|
|
1941
|
+
if (scheduler.MemoryOversubscriptionEnabled === true)
|
|
1942
|
+
return;
|
|
1943
|
+
const putResp = await fetch("http://127.0.0.1:4646/v1/operator/scheduler/configuration", {
|
|
1944
|
+
method: "PUT",
|
|
1945
|
+
headers: nomadApiHeaders({ "Content-Type": "application/json" }),
|
|
1946
|
+
body: JSON.stringify({ ...scheduler, MemoryOversubscriptionEnabled: true }),
|
|
1947
|
+
signal: AbortSignal.timeout(5000),
|
|
1948
|
+
});
|
|
1949
|
+
if (putResp.status === 200) {
|
|
1950
|
+
console.log("[nomad] Enabled memory oversubscription so MemoryMaxMB becomes an actual task limit.");
|
|
1951
|
+
}
|
|
1952
|
+
else {
|
|
1953
|
+
console.warn(`[nomad] Failed to enable memory oversubscription: HTTP ${putResp.status}`);
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
catch (e) {
|
|
1957
|
+
console.warn("[nomad] Failed to enable memory oversubscription:", e.message);
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
/** Shared post-start sequence: load token, bootstrap ACL, enable oversubscription, enable node. */
|
|
1961
|
+
async function finalizeNomadStartup() {
|
|
1962
|
+
loadNomadToken();
|
|
1963
|
+
await bootstrapNomadACL();
|
|
1964
|
+
await ensureNomadMemoryOversubscriptionEnabled();
|
|
1965
|
+
enableNomadNodeEligibility();
|
|
1966
|
+
}
|
|
1967
|
+
async function bootstrapNomadACL() {
|
|
1968
|
+
// If a token is already loaded, verify it's still valid before trusting it.
|
|
1969
|
+
if (process.env.NOMAD_TOKEN || getCoreConfig().nomad_token) {
|
|
1970
|
+
if (await isNomadTokenValid())
|
|
1971
|
+
return;
|
|
1972
|
+
console.log("[nomad] Stored ACL token is invalid — re-bootstrapping...");
|
|
1973
|
+
delete process.env.NOMAD_TOKEN;
|
|
1974
|
+
}
|
|
1975
|
+
const nomadPath = existsSync(NOMAD_BIN) ? NOMAD_BIN : "nomad";
|
|
1976
|
+
const doBootstrap = async () => {
|
|
1977
|
+
try {
|
|
1978
|
+
const raw = execFileSync(nomadPath, ["acl", "bootstrap", "-json"], {
|
|
1979
|
+
timeout: 10000,
|
|
1980
|
+
encoding: "utf-8",
|
|
1981
|
+
env: { ...process.env, NOMAD_ADDR: "http://127.0.0.1:4646" },
|
|
1982
|
+
});
|
|
1983
|
+
return JSON.parse(raw).SecretID ?? null;
|
|
1984
|
+
}
|
|
1985
|
+
catch (e) {
|
|
1986
|
+
// Return the raw stderr/stdout so caller can inspect it
|
|
1987
|
+
throw e;
|
|
1988
|
+
}
|
|
1989
|
+
};
|
|
1990
|
+
const saveToken = (token) => {
|
|
1991
|
+
const envFile = join(JISHUSHELL_HOME, "nomad.env");
|
|
1992
|
+
const envContent = `NOMAD_TOKEN=${token}\n`;
|
|
1993
|
+
writeSecretFile(envFile, envContent);
|
|
1994
|
+
try {
|
|
1995
|
+
execFileSync("sudo", ["-n", "mkdir", "-p", "/etc/jishushell"], { timeout: 5000, stdio: "pipe" });
|
|
1996
|
+
sudoCopySystemFile(envContent, "/etc/jishushell/nomad.env", "600");
|
|
1997
|
+
}
|
|
1998
|
+
catch (copyErr) {
|
|
1999
|
+
console.warn("[nomad] Could not sync token to /etc/jishushell/nomad.env:", copyErr.message);
|
|
2000
|
+
}
|
|
2001
|
+
process.env.NOMAD_TOKEN = token;
|
|
2002
|
+
console.log("[nomad] ACL token saved to", envFile);
|
|
2003
|
+
};
|
|
2004
|
+
try {
|
|
2005
|
+
const token = await doBootstrap();
|
|
2006
|
+
if (token)
|
|
2007
|
+
saveToken(token);
|
|
2008
|
+
return;
|
|
2009
|
+
}
|
|
2010
|
+
catch (e) {
|
|
2011
|
+
const msg = (e.stderr || e.stdout || e.message || "").toString();
|
|
2012
|
+
// "ACL bootstrap already done" — extract reset index and perform a reset
|
|
2013
|
+
const resetMatch = msg.match(/reset index[:\s]+(\d+)/i);
|
|
2014
|
+
if (!resetMatch) {
|
|
2015
|
+
if (!msg.includes("bootstrap"))
|
|
2016
|
+
console.warn("[nomad] ACL bootstrap warning:", msg);
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
const resetIndex = resetMatch[1];
|
|
2020
|
+
console.log(`[nomad] Bootstrap already done (reset index: ${resetIndex}). Performing ACL bootstrap reset...`);
|
|
2021
|
+
// Write the reset trigger file (Nomad reads this on startup to allow re-bootstrap).
|
|
2022
|
+
// NOMAD_DATA_DIR/server is owned by root because nomad.service runs as User=root
|
|
2023
|
+
// (docker driver on 1.6.5 requires euid==0). The panel runs as a non-root user, so
|
|
2024
|
+
// plain writeConfigFile would fail with EACCES — route through `sudo tee` instead.
|
|
2025
|
+
const resetFile = join(NOMAD_DATA_DIR, "server", "acl-bootstrap-reset");
|
|
2026
|
+
try {
|
|
2027
|
+
writeConfigFile(resetFile, resetIndex);
|
|
2028
|
+
}
|
|
2029
|
+
catch (writeErr) {
|
|
2030
|
+
try {
|
|
2031
|
+
execFileSync("sudo", ["-n", "mkdir", "-p", dirname(resetFile)], { timeout: 5000, stdio: "pipe" });
|
|
2032
|
+
execFileSync("sudo", ["-n", "tee", resetFile], {
|
|
2033
|
+
timeout: 5000,
|
|
2034
|
+
input: resetIndex,
|
|
2035
|
+
stdio: ["pipe", "ignore", "pipe"],
|
|
2036
|
+
});
|
|
2037
|
+
}
|
|
2038
|
+
catch (sudoErr) {
|
|
2039
|
+
console.warn("[nomad] Could not write acl-bootstrap-reset file:", sudoErr.message || writeErr.message);
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
// Restart Nomad so it picks up the reset file
|
|
2044
|
+
try {
|
|
2045
|
+
execFileSync("sudo", ["-n", "systemctl", "restart", "nomad"], { timeout: 15000, stdio: "pipe" });
|
|
2046
|
+
}
|
|
2047
|
+
catch {
|
|
2048
|
+
// No passwordless sudo — try pkill/re-spawn path (best effort)
|
|
2049
|
+
try {
|
|
2050
|
+
execFileSync("sudo", ["-n", "pkill", "-TERM", "-f", "nomad agent"], { stdio: "pipe" });
|
|
2051
|
+
}
|
|
2052
|
+
catch {
|
|
2053
|
+
try {
|
|
2054
|
+
execFileSync("pkill", ["-TERM", "-f", "nomad agent"], { stdio: "pipe" });
|
|
2055
|
+
}
|
|
2056
|
+
catch { }
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
// Wait for Nomad to come back
|
|
2060
|
+
for (let i = 0; i < 20; i++) {
|
|
2061
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
2062
|
+
if (isPortListening(4646))
|
|
2063
|
+
break;
|
|
2064
|
+
}
|
|
2065
|
+
// Bootstrap again — reset file is consumed by Nomad on first start after restart
|
|
2066
|
+
try {
|
|
2067
|
+
const token = await doBootstrap();
|
|
2068
|
+
if (token)
|
|
2069
|
+
saveToken(token);
|
|
2070
|
+
}
|
|
2071
|
+
catch (e2) {
|
|
2072
|
+
console.warn("[nomad] ACL bootstrap after reset failed:", (e2.stderr || e2.message || "").toString().split("\n")[0]);
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
function enableNomadNodeEligibility() {
|
|
2077
|
+
const nomadPath = existsSync(NOMAD_BIN) ? NOMAD_BIN : "nomad";
|
|
2078
|
+
try {
|
|
2079
|
+
const raw = execSync(`${nomadPath} node status -self -json`, {
|
|
2080
|
+
timeout: 5000,
|
|
2081
|
+
encoding: "utf-8",
|
|
2082
|
+
env: { ...process.env, NOMAD_ADDR: "http://127.0.0.1:4646" },
|
|
2083
|
+
});
|
|
2084
|
+
const nodeId = JSON.parse(raw).ID;
|
|
2085
|
+
if (!nodeId)
|
|
2086
|
+
return;
|
|
2087
|
+
execSync(`${nomadPath} node eligibility -enable ${nodeId}`, {
|
|
2088
|
+
timeout: 5000,
|
|
2089
|
+
env: { ...process.env, NOMAD_ADDR: "http://127.0.0.1:4646" },
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
catch {
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
export async function startNomad() {
|
|
2096
|
+
try {
|
|
2097
|
+
const configPath = join(NOMAD_CONFIG_DIR, "nomad.hcl");
|
|
2098
|
+
const readConfig = () => {
|
|
2099
|
+
try {
|
|
2100
|
+
return existsSync(configPath) ? readFileSync(configPath, "utf-8") : "";
|
|
2101
|
+
}
|
|
2102
|
+
catch {
|
|
2103
|
+
return "";
|
|
2104
|
+
}
|
|
2105
|
+
};
|
|
2106
|
+
const oldConfig = readConfig();
|
|
2107
|
+
writeNomadConfig();
|
|
2108
|
+
const newConfig = readConfig();
|
|
2109
|
+
const drifted = oldConfig.length > 0 && newConfig.length > 0 && oldConfig !== newConfig;
|
|
2110
|
+
if (isPortListening(4646)) {
|
|
2111
|
+
// Nomad already running, but if the rewritten config differs (typically
|
|
2112
|
+
// because detectNomadExternalInterface() now resolves to a different
|
|
2113
|
+
// interface — e.g. older install missing the host_network block, or
|
|
2114
|
+
// network interface renamed across reboots), reload nomad so allocations
|
|
2115
|
+
// bind to the right host network on next placement.
|
|
2116
|
+
if (drifted) {
|
|
2117
|
+
try {
|
|
2118
|
+
execFileSync("sudo", ["-n", "systemctl", "restart", "nomad"], { stdio: "pipe", timeout: 15000 });
|
|
2119
|
+
for (let i = 0; i < 15; i++) {
|
|
2120
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
2121
|
+
if (isPortListening(4646)) {
|
|
2122
|
+
await finalizeNomadStartup();
|
|
2123
|
+
return { ok: true, message: "Nomad restarted (config reconciled)" };
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
return { ok: false, message: "Nomad restart timed out after config reconcile", error: "Port 4646 not listening after 15s" };
|
|
2127
|
+
}
|
|
2128
|
+
catch (e) {
|
|
2129
|
+
console.warn(`[nomad] config drift detected but restart failed (${e.message}); existing nomad continues with stale config`);
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
await finalizeNomadStartup();
|
|
2133
|
+
return { ok: true, message: "Nomad already running" };
|
|
2134
|
+
}
|
|
2135
|
+
// If systemd manages nomad, delegate to systemctl to avoid spawning a competing process
|
|
2136
|
+
const isSystemdManaged = (() => {
|
|
2137
|
+
try {
|
|
2138
|
+
const result = execFileSync("systemctl", ["is-enabled", "nomad"], { stdio: "pipe", timeout: 3000 });
|
|
2139
|
+
return result.toString().trim() === "enabled";
|
|
2140
|
+
}
|
|
2141
|
+
catch {
|
|
2142
|
+
return false;
|
|
2143
|
+
}
|
|
2144
|
+
})();
|
|
2145
|
+
if (isSystemdManaged) {
|
|
2146
|
+
try {
|
|
2147
|
+
execFileSync("sudo", ["-n", "systemctl", "start", "nomad"], { stdio: "pipe", timeout: 10000 });
|
|
2148
|
+
}
|
|
2149
|
+
catch {
|
|
2150
|
+
// sudo -n failed (no passwordless sudo); service may start on its own
|
|
2151
|
+
}
|
|
2152
|
+
for (let i = 0; i < 15; i++) {
|
|
2153
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
2154
|
+
if (isPortListening(4646)) {
|
|
2155
|
+
await finalizeNomadStartup();
|
|
2156
|
+
return { ok: true, message: "Nomad started via systemd" };
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
return { ok: false, message: "Nomad start timed out", error: "Port 4646 not listening after 15s" };
|
|
2160
|
+
}
|
|
2161
|
+
if (process.platform === "darwin") {
|
|
2162
|
+
const launchdPlist = join(process.env.HOME || dirname(JISHUSHELL_HOME), "Library/LaunchAgents/com.jishushell.nomad.plist");
|
|
2163
|
+
if (existsSync(launchdPlist)) {
|
|
2164
|
+
for (let i = 0; i < 30; i++) {
|
|
2165
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
2166
|
+
if (isPortListening(4646)) {
|
|
2167
|
+
await finalizeNomadStartup();
|
|
2168
|
+
return { ok: true, message: "Nomad started" };
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
return { ok: false, message: "Nomad start timed out", error: "Port 4646 not listening after 30s" };
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
const nomadPath = existsSync(NOMAD_BIN) ? NOMAD_BIN : "nomad";
|
|
2175
|
+
try {
|
|
2176
|
+
execFileSync(nomadPath, ["version"], { timeout: 5000, stdio: "pipe" });
|
|
2177
|
+
}
|
|
2178
|
+
catch {
|
|
2179
|
+
return { ok: false, message: "Nomad not installed", error: "Install Nomad first" };
|
|
2180
|
+
}
|
|
2181
|
+
const logPath = join(NOMAD_CONFIG_DIR, "nomad.log");
|
|
2182
|
+
// Use spawn with detached to avoid execSync shell injection and ensure proper daemonization
|
|
2183
|
+
try {
|
|
2184
|
+
const { openSync, closeSync } = await import("fs");
|
|
2185
|
+
const logFd = openSync(logPath, "a");
|
|
2186
|
+
// Run as current (non-root) user — Nomad only binds to 127.0.0.1:4646/4647/4648
|
|
2187
|
+
// (non-privileged ports) and all data dirs live under ~/.jishushell/, so sudo is not needed.
|
|
2188
|
+
const cmd = nomadPath;
|
|
2189
|
+
const args = ["agent", `-config=${configPath}`];
|
|
2190
|
+
const child = nodeSpawn(cmd, args, {
|
|
2191
|
+
detached: true,
|
|
2192
|
+
stdio: ["ignore", logFd, logFd],
|
|
2193
|
+
});
|
|
2194
|
+
child.unref();
|
|
2195
|
+
closeSync(logFd);
|
|
2196
|
+
}
|
|
2197
|
+
catch (e) {
|
|
2198
|
+
return { ok: false, message: "Failed to start Nomad", error: `launch failed: ${e.message}. Check sudo/permissions.` };
|
|
2199
|
+
}
|
|
2200
|
+
for (let i = 0; i < 15; i++) {
|
|
2201
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
2202
|
+
if (isPortListening(4646)) {
|
|
2203
|
+
await finalizeNomadStartup();
|
|
2204
|
+
return { ok: true, message: "Nomad started" };
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
return { ok: false, message: "Nomad start timed out", error: "Port 4646 not listening after 15s" };
|
|
2208
|
+
}
|
|
2209
|
+
catch (e) {
|
|
2210
|
+
return { ok: false, message: "Failed to start Nomad", error: e.message };
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
export async function stopNomad() {
|
|
2214
|
+
try {
|
|
2215
|
+
if (!isPortListening(4646))
|
|
2216
|
+
return { ok: true, message: "Nomad not running" };
|
|
2217
|
+
// SIGTERM: graceful shutdown — Nomad flushes state and detaches from
|
|
2218
|
+
// running allocs without killing them (drain_on_shutdown is deliberately
|
|
2219
|
+
// not configured, so the docker containers keep running and will be
|
|
2220
|
+
// re-attached when Nomad comes back).
|
|
2221
|
+
// Use killNomadByProcName (pgrep -x) to avoid pkill -f self-matching.
|
|
2222
|
+
killNomadByProcName();
|
|
2223
|
+
// Wait up to 10s for the process to exit. No drain means shutdown is
|
|
2224
|
+
// near-instant — most of this budget is slack for slow disks on Pi.
|
|
2225
|
+
for (let i = 0; i < 10; i++) {
|
|
2226
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
2227
|
+
if (!isPortListening(4646))
|
|
2228
|
+
return { ok: true, message: "Nomad stopped" };
|
|
2229
|
+
}
|
|
2230
|
+
return { ok: true, message: "Nomad stop requested" };
|
|
2231
|
+
}
|
|
2232
|
+
catch (e) {
|
|
2233
|
+
return { ok: false, message: "Failed to stop Nomad", error: e.message };
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
// ── Install Nomad as systemd service ───────────────────────────────
|
|
2237
|
+
export function installNomadSystemd() {
|
|
2238
|
+
try {
|
|
2239
|
+
const nomadPath = resolveNomadExecutable();
|
|
2240
|
+
const configPath = join(NOMAD_CONFIG_DIR, "nomad.hcl");
|
|
2241
|
+
writeNomadConfig();
|
|
2242
|
+
if (process.platform === "darwin") {
|
|
2243
|
+
// macOS is Apple Silicon only — fail fast (mirrors the bash uname guard).
|
|
2244
|
+
const guard = appleSiliconGuard();
|
|
2245
|
+
if (!guard.ok)
|
|
2246
|
+
return { ok: false, message: guard.message };
|
|
2247
|
+
const laDir = join(process.env.HOME || dirname(JISHUSHELL_HOME), "Library/LaunchAgents");
|
|
2248
|
+
// Always use JishuShell's private Colima socket — hardcoded, not
|
|
2249
|
+
// runtime-detected. Colima may not be running yet when the plist is
|
|
2250
|
+
// written; runtime fallback would pick the wrong socket (Docker
|
|
2251
|
+
// Desktop or /var/run/docker.sock). Mirrors the bash _COLIMA_SOCKET use.
|
|
2252
|
+
const colimaSocket = managedColimaSocketPath(JISHUSHELL_HOME, COLIMA_PROFILE);
|
|
2253
|
+
const colimaDockerHost = managedColimaDockerHost(JISHUSHELL_HOME, COLIMA_PROFILE);
|
|
2254
|
+
// ── 1. Colima self-retrying launchd agent ───────────────────────
|
|
2255
|
+
// colima binary: prefer PATH, fall back to both Homebrew prefixes
|
|
2256
|
+
// (mirrors the bash COLIMA_BIN resolution).
|
|
2257
|
+
const colimaBin = resolveColimaExecutable();
|
|
2258
|
+
const colimaWrapperPath = join(BIN_DIR, "colima-launchd-wrapper.sh");
|
|
2259
|
+
writeExecutableFile(colimaWrapperPath, buildColimaWrapperScript({
|
|
2260
|
+
colimaHome: _COLIMA_DIR,
|
|
2261
|
+
profile: COLIMA_PROFILE,
|
|
2262
|
+
socket: colimaSocket,
|
|
2263
|
+
colimaBin,
|
|
2264
|
+
}));
|
|
2265
|
+
const colimaPlistPath = join(laDir, "com.jishushell.colima.plist");
|
|
2266
|
+
writeConfigFile(colimaPlistPath, buildColimaPlist({
|
|
2267
|
+
wrapperPath: colimaWrapperPath,
|
|
2268
|
+
colimaHome: _COLIMA_DIR,
|
|
2269
|
+
logPath: join(_COLIMA_DIR, "colima-launchd.log"),
|
|
2270
|
+
}));
|
|
2271
|
+
try {
|
|
2272
|
+
execSync(`launchctl unload "${colimaPlistPath}" 2>/dev/null`);
|
|
2273
|
+
}
|
|
2274
|
+
catch { }
|
|
2275
|
+
try {
|
|
2276
|
+
execSync(`launchctl load -w "${colimaPlistPath}"`, { timeout: 15000 });
|
|
2277
|
+
}
|
|
2278
|
+
catch { }
|
|
2279
|
+
// ── 2. Nomad gated behind a docker-readiness wrapper ────────────
|
|
2280
|
+
const logPath = join(NOMAD_CONFIG_DIR, "nomad.log");
|
|
2281
|
+
const nomadWrapperPath = join(BIN_DIR, "nomad-launchd-wrapper.sh");
|
|
2282
|
+
writeExecutableFile(nomadWrapperPath, buildNomadWaitWrapper({
|
|
2283
|
+
dockerHost: colimaDockerHost,
|
|
2284
|
+
nomadBin: nomadPath,
|
|
2285
|
+
configPath,
|
|
2286
|
+
}));
|
|
2287
|
+
const plistContent = buildNomadPlist({
|
|
2288
|
+
wrapperPath: nomadWrapperPath,
|
|
2289
|
+
dockerHost: colimaDockerHost,
|
|
2290
|
+
logPath,
|
|
2291
|
+
});
|
|
2292
|
+
const plistPath = join(laDir, "com.jishushell.nomad.plist");
|
|
2293
|
+
writeConfigFile(plistPath, plistContent);
|
|
2294
|
+
try {
|
|
2295
|
+
execSync(`launchctl unload "${plistPath}" 2>/dev/null`);
|
|
2296
|
+
}
|
|
2297
|
+
catch { }
|
|
2298
|
+
execSync(`launchctl load -w "${plistPath}"`, { timeout: 15000 });
|
|
2299
|
+
return { ok: true, message: "Nomad launchd agent installed and started (Colima gated)" };
|
|
2300
|
+
}
|
|
2301
|
+
// Nomad 1.6.5's docker driver fingerprint requires euid==0 — PR #18197 lifted
|
|
2302
|
+
// that restriction only in 1.7+, and we intentionally stay on the 1.6 MPL line.
|
|
2303
|
+
// The core server stays as the installing user via a separate jishushell.service unit;
|
|
2304
|
+
// it talks to this agent over HTTP, so no files under ~/.jishushell/nomad/data/
|
|
2305
|
+
// are read directly by the core process.
|
|
2306
|
+
const serviceContent = buildNomadSystemdUnit({ nomadPath, configPath });
|
|
2307
|
+
const servicePath = SYSTEMD_NOMAD_SERVICE_PATH;
|
|
2308
|
+
execFileSync("sudo", ["mkdir", "-p", "/etc/jishushell"], { timeout: 5000 });
|
|
2309
|
+
sudoCopySystemFile(serviceContent, servicePath, "644");
|
|
2310
|
+
execFileSync("sudo", ["systemctl", "daemon-reload"], { timeout: 5000 });
|
|
2311
|
+
execFileSync("sudo", ["systemctl", "enable", "--now", "nomad"], { timeout: 15000 });
|
|
2312
|
+
// Also restart if already running so it picks up the new User= directive
|
|
2313
|
+
try {
|
|
2314
|
+
execFileSync("sudo", ["systemctl", "restart", "nomad"], { timeout: 15000 });
|
|
2315
|
+
}
|
|
2316
|
+
catch { }
|
|
2317
|
+
return { ok: true, message: "Nomad systemd service installed and started" };
|
|
2318
|
+
}
|
|
2319
|
+
catch (e) {
|
|
2320
|
+
return { ok: false, message: "Failed to install Nomad systemd service", error: e.message };
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
// ── Install JishuShell as systemd service ─────────────────────────
|
|
2324
|
+
export function installJishushellSystemd(port, startNow = false) {
|
|
2325
|
+
try {
|
|
2326
|
+
const resolvedPort = port || _serverPort;
|
|
2327
|
+
persistCoreServicePort(resolvedPort);
|
|
2328
|
+
const serviceUser = process.platform === "darwin" ? undefined : resolveServiceUser();
|
|
2329
|
+
// Use the node binary that is currently running this process — avoids
|
|
2330
|
+
// "which node" failing when PATH doesn't include nvm's bin dir.
|
|
2331
|
+
const nodeBin = process.execPath;
|
|
2332
|
+
const cliBin = resolveCoreCliBin();
|
|
2333
|
+
// The real owner home: prefer JISHUSHELL_HOME's parent so we don't rely on $HOME.
|
|
2334
|
+
const realHome = resolveRealHome();
|
|
2335
|
+
const coreStartWrapperPath = join(BIN_DIR, "jishushell-core-start");
|
|
2336
|
+
const coreCliWrapperPath = join(BIN_DIR, "jishushell");
|
|
2337
|
+
removeSymlinkIfPresent(coreCliWrapperPath);
|
|
2338
|
+
writeManagedExecutableFile(coreStartWrapperPath, buildCoreStartWrapperContent({
|
|
2339
|
+
nodeBin,
|
|
2340
|
+
cliBin,
|
|
2341
|
+
realHome,
|
|
2342
|
+
port: resolvedPort,
|
|
2343
|
+
}), serviceUser);
|
|
2344
|
+
writeManagedExecutableFile(coreCliWrapperPath, buildCoreCliWrapperContent({
|
|
2345
|
+
nodeBin,
|
|
2346
|
+
cliBin,
|
|
2347
|
+
realHome,
|
|
2348
|
+
port: resolvedPort,
|
|
2349
|
+
}), serviceUser);
|
|
2350
|
+
chownToUser(JISHUSHELL_HOME, serviceUser, true);
|
|
2351
|
+
if (process.platform === "darwin") {
|
|
2352
|
+
// macOS is Apple Silicon only — fail fast (mirrors the bash uname guard).
|
|
2353
|
+
const guard = appleSiliconGuard();
|
|
2354
|
+
if (!guard.ok)
|
|
2355
|
+
return { ok: false, message: guard.message };
|
|
2356
|
+
const logPath = join(JISHUSHELL_HOME, "jishushell-core.log");
|
|
2357
|
+
// 60s docker-readiness gate before core starts; on timeout it starts anyway.
|
|
2358
|
+
const gateWrapperPath = join(BIN_DIR, "core-launchd-wrapper.sh");
|
|
2359
|
+
writeExecutableFile(gateWrapperPath, buildCoreGateWrapper({
|
|
2360
|
+
socket: managedColimaSocketPath(JISHUSHELL_HOME, COLIMA_PROFILE),
|
|
2361
|
+
coreStartWrapper: coreStartWrapperPath,
|
|
2362
|
+
}));
|
|
2363
|
+
const plistContent = buildCorePlist({
|
|
2364
|
+
gateWrapperPath,
|
|
2365
|
+
logPath,
|
|
2366
|
+
});
|
|
2367
|
+
// Symmetric with installNomadSystemd()'s laDir (realHome === dirname(JISHUSHELL_HOME),
|
|
2368
|
+
// so process.env.HOME || dirname(JISHUSHELL_HOME) is provably the same path).
|
|
2369
|
+
const laDir = resolveLaunchAgentsDir(realHome);
|
|
2370
|
+
const plistPath = join(laDir, "com.jishushell.core.plist");
|
|
2371
|
+
writeConfigFile(plistPath, plistContent);
|
|
2372
|
+
if (startNow || !isPortListening(resolvedPort)) {
|
|
2373
|
+
try {
|
|
2374
|
+
execSync(`launchctl unload "${plistPath}" 2>/dev/null`);
|
|
2375
|
+
}
|
|
2376
|
+
catch { }
|
|
2377
|
+
execSync(`launchctl load -w "${plistPath}"`, { timeout: 15000 });
|
|
2378
|
+
if (!waitForListeningPort(resolvedPort, 30)) {
|
|
2379
|
+
return {
|
|
2380
|
+
ok: false,
|
|
2381
|
+
message: "JishuShell core launchd agent installed, but the core port is not ready yet",
|
|
2382
|
+
};
|
|
2383
|
+
}
|
|
2384
|
+
return { ok: true, message: "JishuShell core launchd agent installed and started (port " + resolvedPort + ")" };
|
|
2385
|
+
}
|
|
2386
|
+
return { ok: true, message: "JishuShell core launchd agent installed and enabled (port " + resolvedPort + ")" };
|
|
2387
|
+
}
|
|
2388
|
+
const currentUser = serviceUser ?? resolveServiceUser();
|
|
2389
|
+
const serviceContent = buildJishushellCoreSystemdUnit({
|
|
2390
|
+
currentUser,
|
|
2391
|
+
realHome,
|
|
2392
|
+
coreStartWrapperPath,
|
|
2393
|
+
});
|
|
2394
|
+
execFileSync("sudo", ["mkdir", "-p", "/etc/jishushell"], { timeout: 5000 });
|
|
2395
|
+
sudoCopySystemFile(serviceContent, SYSTEMD_CORE_SERVICE_PATH, "644");
|
|
2396
|
+
execFileSync("sudo", ["systemctl", "daemon-reload"], { timeout: 5000 });
|
|
2397
|
+
execFileSync("sudo", ["systemctl", "enable", "jishushell"], { timeout: 15000, stdio: "pipe" });
|
|
2398
|
+
if (!startNow) {
|
|
2399
|
+
return { ok: true, message: "JishuShell core systemd service installed and enabled (port " + resolvedPort + ")" };
|
|
2400
|
+
}
|
|
2401
|
+
execFileSync("sudo", ["systemctl", "restart", "jishushell"], { timeout: 15000 });
|
|
2402
|
+
if (!waitForListeningPort(resolvedPort, 30)) {
|
|
2403
|
+
return {
|
|
2404
|
+
ok: false,
|
|
2405
|
+
message: "JishuShell core systemd service installed, but the core port is not ready yet",
|
|
2406
|
+
};
|
|
2407
|
+
}
|
|
2408
|
+
return { ok: true, message: "JishuShell core systemd service installed and started (port " + resolvedPort + ")" };
|
|
2409
|
+
}
|
|
2410
|
+
catch (e) {
|
|
2411
|
+
return { ok: false, message: "Failed to install JishuShell systemd service", error: e.message };
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
export function installJishushellPanelSystemd(corePort, panelPort = DEFAULT_PANEL_PORT, startNow = true) {
|
|
2415
|
+
try {
|
|
2416
|
+
const resolvedCorePort = corePort || _serverPort;
|
|
2417
|
+
const serviceUser = process.platform === "darwin" ? undefined : resolveServiceUser();
|
|
2418
|
+
const nodeBin = process.execPath;
|
|
2419
|
+
const panelBin = resolveBundledPanelServerBin();
|
|
2420
|
+
const realHome = resolveRealHome();
|
|
2421
|
+
const panelStartWrapperPath = join(BIN_DIR, "jishushell-panel-start");
|
|
2422
|
+
writeManagedExecutableFile(panelStartWrapperPath, buildPanelStartWrapperContent({
|
|
2423
|
+
nodeBin,
|
|
2424
|
+
panelBin,
|
|
2425
|
+
realHome,
|
|
2426
|
+
corePort: resolvedCorePort,
|
|
2427
|
+
panelPort,
|
|
2428
|
+
}), serviceUser);
|
|
2429
|
+
if (process.platform === "darwin") {
|
|
2430
|
+
const guard = appleSiliconGuard();
|
|
2431
|
+
if (!guard.ok)
|
|
2432
|
+
return { ok: false, message: guard.message };
|
|
2433
|
+
const laDir = resolveLaunchAgentsDir(realHome);
|
|
2434
|
+
const plistPath = join(laDir, "com.jishushell.panel.plist");
|
|
2435
|
+
writeConfigFile(plistPath, buildPanelPlist({
|
|
2436
|
+
panelWrapperPath: panelStartWrapperPath,
|
|
2437
|
+
logPath: join(JISHUSHELL_HOME, "jishushell-panel.log"),
|
|
2438
|
+
home: realHome,
|
|
2439
|
+
jishuHome: JISHUSHELL_HOME,
|
|
2440
|
+
corePort: resolvedCorePort,
|
|
2441
|
+
panelPort,
|
|
2442
|
+
}));
|
|
2443
|
+
if (!waitForListeningPort(resolvedCorePort, 30)) {
|
|
2444
|
+
return {
|
|
2445
|
+
ok: false,
|
|
2446
|
+
message: "JishuShell Panel launchd agent was written but not started because the core port is not ready",
|
|
2447
|
+
};
|
|
2448
|
+
}
|
|
2449
|
+
try {
|
|
2450
|
+
execSync(`launchctl unload "${plistPath}" 2>/dev/null`);
|
|
2451
|
+
}
|
|
2452
|
+
catch { }
|
|
2453
|
+
execSync(`launchctl load -w "${plistPath}"`, { timeout: 15000 });
|
|
2454
|
+
if (!waitForListeningPort(panelPort, 15)) {
|
|
2455
|
+
return {
|
|
2456
|
+
ok: false,
|
|
2457
|
+
message: "JishuShell Panel launchd agent started, but the panel port is not ready yet",
|
|
2458
|
+
};
|
|
2459
|
+
}
|
|
2460
|
+
return { ok: true, message: "JishuShell Panel launchd agent installed and started (port " + panelPort + ")" };
|
|
2461
|
+
}
|
|
2462
|
+
const currentUser = serviceUser ?? resolveServiceUser();
|
|
2463
|
+
const serviceContent = buildJishushellPanelSystemdUnit({
|
|
2464
|
+
currentUser,
|
|
2465
|
+
realHome,
|
|
2466
|
+
panelStartWrapperPath,
|
|
2467
|
+
corePort: resolvedCorePort,
|
|
2468
|
+
panelPort,
|
|
2469
|
+
});
|
|
2470
|
+
execFileSync("sudo", ["mkdir", "-p", "/etc/jishushell"], { timeout: 5000 });
|
|
2471
|
+
sudoCopySystemFile(serviceContent, SYSTEMD_PANEL_SERVICE_PATH, "644");
|
|
2472
|
+
execFileSync("sudo", ["systemctl", "daemon-reload"], { timeout: 5000 });
|
|
2473
|
+
execFileSync("sudo", ["systemctl", "enable", "jishushell-panel"], { timeout: 15000, stdio: "pipe" });
|
|
2474
|
+
if (!startNow) {
|
|
2475
|
+
return { ok: true, message: "JishuShell Panel systemd service installed and enabled (port " + panelPort + ")" };
|
|
2476
|
+
}
|
|
2477
|
+
if (!waitForListeningPort(resolvedCorePort, 30)) {
|
|
2478
|
+
return {
|
|
2479
|
+
ok: false,
|
|
2480
|
+
message: "JishuShell Panel systemd service was enabled but not started because the core port is not ready",
|
|
2481
|
+
};
|
|
2482
|
+
}
|
|
2483
|
+
execFileSync("sudo", ["systemctl", "restart", "jishushell-panel"], { timeout: 15000 });
|
|
2484
|
+
if (!waitForListeningPort(panelPort, 15)) {
|
|
2485
|
+
return {
|
|
2486
|
+
ok: false,
|
|
2487
|
+
message: "JishuShell Panel systemd service started, but the panel port is not ready yet",
|
|
2488
|
+
};
|
|
2489
|
+
}
|
|
2490
|
+
return { ok: true, message: "JishuShell Panel systemd service installed and started (port " + panelPort + ")" };
|
|
2491
|
+
}
|
|
2492
|
+
catch (e) {
|
|
2493
|
+
return { ok: false, message: "Failed to install JishuShell Panel systemd service", error: e.message };
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
// ── Install OpenClaw (async with progress) ─────────────────────────
|
|
2497
|
+
/**
|
|
2498
|
+
* Install OpenClaw runtime — thin dispatch wrapper.
|
|
2499
|
+
*
|
|
2500
|
+
* The heavy lifting lives in `OpenClawIntegration.installRuntime()`.
|
|
2501
|
+
* This wrapper is kept only for back-compat with the existing
|
|
2502
|
+
* `routes/setup.ts` endpoint and the CLI installer.
|
|
2503
|
+
*/
|
|
2504
|
+
export async function installOpenclaw(version = "latest") {
|
|
2505
|
+
const integration = getIntegration("openclaw");
|
|
2506
|
+
if (typeof integration.installRuntime !== "function") {
|
|
2507
|
+
return { ok: false, message: "OpenClawIntegration.installRuntime is not implemented" };
|
|
2508
|
+
}
|
|
2509
|
+
return integration.installRuntime({ version });
|
|
2510
|
+
}
|
|
2511
|
+
// ── Hermes install ─────────────────────────────────────────────────
|
|
2512
|
+
//
|
|
2513
|
+
// Hermes constants (HERMES_DEFAULT_IMAGE, HERMES_RUNTIME_DIR,
|
|
2514
|
+
// HERMES_SHIM_FILENAME) and the shim template resolver have moved to
|
|
2515
|
+
// `src/config.ts`. setup-manager no longer defines them.
|
|
2516
|
+
// The HermesIntegration imports them directly from config.ts.
|
|
2517
|
+
/**
|
|
2518
|
+
* Capture the immutable digest of a locally-present image so HermesIntegration
|
|
2519
|
+
* can pin via digest rather than the mutable tag. Returns undefined if the
|
|
2520
|
+
* image has no RepoDigests (e.g. locally-built image) — caller may still
|
|
2521
|
+
* proceed using the tag alone.
|
|
2522
|
+
*/
|
|
2523
|
+
export function captureImageDigest(imageRef) {
|
|
2524
|
+
try {
|
|
2525
|
+
const raw = execFileSync("docker", [
|
|
2526
|
+
"image",
|
|
2527
|
+
"inspect",
|
|
2528
|
+
"--format",
|
|
2529
|
+
"{{range .RepoDigests}}{{.}}{{\"\\n\"}}{{end}}",
|
|
2530
|
+
imageRef,
|
|
2531
|
+
], { encoding: "utf-8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"] });
|
|
2532
|
+
const first = raw.split("\n").map((l) => l.trim()).find(Boolean);
|
|
2533
|
+
return first || undefined;
|
|
2534
|
+
}
|
|
2535
|
+
catch {
|
|
2536
|
+
return undefined;
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
/**
|
|
2540
|
+
* Install Hermes runtime — thin dispatch wrapper.
|
|
2541
|
+
*
|
|
2542
|
+
* Heavy lifting lives in `HermesIntegration.installRuntime()`.
|
|
2543
|
+
*/
|
|
2544
|
+
export async function installHermes() {
|
|
2545
|
+
const integration = getIntegration("hermes");
|
|
2546
|
+
if (typeof integration.installRuntime !== "function") {
|
|
2547
|
+
return { ok: false, message: "HermesIntegration.installRuntime is not implemented" };
|
|
2548
|
+
}
|
|
2549
|
+
return integration.installRuntime();
|
|
2550
|
+
}
|
|
2551
|
+
/**
|
|
2552
|
+
* Non-blocking variant — returns a task id immediately. Implementation
|
|
2553
|
+
* dispatches via `HermesIntegration.startInstallRuntime()`.
|
|
2554
|
+
*/
|
|
2555
|
+
export function startInstallHermes() {
|
|
2556
|
+
const integration = getIntegration("hermes");
|
|
2557
|
+
if (typeof integration.startInstallRuntime !== "function") {
|
|
2558
|
+
return { ok: false, message: "HermesIntegration.startInstallRuntime is not implemented" };
|
|
2559
|
+
}
|
|
2560
|
+
return integration.startInstallRuntime();
|
|
2561
|
+
}
|
|
2562
|
+
/** Helper for setup status — true when Hermes has a persisted runtime install record. */
|
|
2563
|
+
export function isHermesInstalled() {
|
|
2564
|
+
const entry = getIntegrationRuntimeEntry("hermes");
|
|
2565
|
+
return !!entry && !!entry.defaultImage;
|
|
2566
|
+
}
|
|
2567
|
+
export function resolveDockerInvocation() {
|
|
2568
|
+
try {
|
|
2569
|
+
execFileSync("docker", ["info"], { timeout: 5000, stdio: "ignore" });
|
|
2570
|
+
return { cmd: "docker", argsPrefix: [] };
|
|
2571
|
+
}
|
|
2572
|
+
catch { }
|
|
2573
|
+
try {
|
|
2574
|
+
execFileSync("sudo", ["-n", "docker", "info"], { timeout: 5000, stdio: "ignore" });
|
|
2575
|
+
return { cmd: "sudo", argsPrefix: ["-n", "docker"] };
|
|
2576
|
+
}
|
|
2577
|
+
catch { }
|
|
2578
|
+
return { cmd: "docker", argsPrefix: [] };
|
|
2579
|
+
}
|
|
2580
|
+
function checkDockerImageExists() {
|
|
2581
|
+
const tag = resolveDockerImageTag();
|
|
2582
|
+
try {
|
|
2583
|
+
const invocation = resolveDockerInvocation();
|
|
2584
|
+
execFileSync(invocation.cmd, [...invocation.argsPrefix, "image", "inspect", tag], { timeout: 5000, stdio: "ignore" });
|
|
2585
|
+
return true;
|
|
2586
|
+
}
|
|
2587
|
+
catch {
|
|
2588
|
+
// Fallback scan: list all local images and try to find any known runtime image.
|
|
2589
|
+
// This handles two scenarios:
|
|
2590
|
+
// 1. core.json was wiped (e.g. after `jishushell reset`) and the pinned version
|
|
2591
|
+
// tag is no longer stored, causing getOpenclawDockerImage() to return the default
|
|
2592
|
+
// `:latest` tag which may have been removed locally during the first-run migration.
|
|
2593
|
+
// 2. The environment uses a locally built runtime image (e.g. jishushell-hermes:latest)
|
|
2594
|
+
// that differs from the registry default.
|
|
2595
|
+
// When a candidate is found we restore core.json (self-heal) so the fast path works next time.
|
|
2596
|
+
try {
|
|
2597
|
+
const invocation = resolveDockerInvocation();
|
|
2598
|
+
const out = execFileSync(invocation.cmd, [...invocation.argsPrefix, "images", "--format", "{{.Repository}}:{{.Tag}}"], { encoding: "utf8", timeout: 5000 });
|
|
2599
|
+
const lines = out.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
2600
|
+
// 1. Same repository as DEFAULT_OPENCLAW_DOCKER_IMAGE (e.g. after pinned-tag migration).
|
|
2601
|
+
const defaultImage = DEFAULT_OPENCLAW_DOCKER_IMAGE;
|
|
2602
|
+
const repoColonIdx = defaultImage.lastIndexOf(":");
|
|
2603
|
+
const repoSlashIdx = defaultImage.lastIndexOf("/");
|
|
2604
|
+
if (repoColonIdx > repoSlashIdx) {
|
|
2605
|
+
const repo = defaultImage.slice(0, repoColonIdx);
|
|
2606
|
+
const repoPrefix = repo + ":";
|
|
2607
|
+
const found = lines.find((l) => l.startsWith(repoPrefix) && !l.endsWith(":<none>") && !l.endsWith(":none"));
|
|
2608
|
+
if (found) {
|
|
2609
|
+
setOpenclawDockerImage(found);
|
|
2610
|
+
return true;
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
// 2. Backward compat: older locally-built jishushell-openclaw:* image names.
|
|
2614
|
+
// These use the same slim base architecture as ghcr.io/x-aijishu/openclaw-runtime:*
|
|
2615
|
+
// and are fully compatible. Self-heal core.json so the tag stored there becomes
|
|
2616
|
+
// the concrete existing tag, preventing repeated DEFAULT-migration side effects.
|
|
2617
|
+
const legacyFound = lines.find((l) => /^jishushell-openclaw:[^\s]+/.test(l) && !l.endsWith(":<none>") && !l.endsWith(":none"));
|
|
2618
|
+
if (legacyFound) {
|
|
2619
|
+
setOpenclawDockerImage(legacyFound);
|
|
2620
|
+
return true;
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
catch { }
|
|
2624
|
+
return false;
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
// The stable tag for the JishuShell base image. Reads core.json — framework-level.
|
|
2628
|
+
function resolveDockerImageTag() {
|
|
2629
|
+
return getOpenclawDockerImage();
|
|
2630
|
+
}
|
|
2631
|
+
// ── Docker image build dispatch wrappers ───────────────────────────────
|
|
2632
|
+
//
|
|
2633
|
+
// All OpenClaw-specific image build logic now lives in
|
|
2634
|
+
// `OpenClawIntegration.buildRuntimeImage()`. These
|
|
2635
|
+
// exports remain as thin wrappers so `routes/setup.ts` and
|
|
2636
|
+
// `install.ts` callers don't need to learn integration dispatch.
|
|
2637
|
+
/** Blocking: pull or build the OpenClaw docker image. */
|
|
2638
|
+
export async function buildSlimOpenclawImage(tag) {
|
|
2639
|
+
const integration = getIntegration("openclaw");
|
|
2640
|
+
if (typeof integration.buildRuntimeImage !== "function") {
|
|
2641
|
+
return { ok: false, message: "OpenClawIntegration.buildRuntimeImage is not implemented" };
|
|
2642
|
+
}
|
|
2643
|
+
return integration.buildRuntimeImage({ tag });
|
|
2644
|
+
}
|
|
2645
|
+
/** Non-blocking: returns immediately with a task id for SSE polling. */
|
|
2646
|
+
export function startBuildSlimOpenclawImage(tag) {
|
|
2647
|
+
const integration = getIntegration("openclaw");
|
|
2648
|
+
if (typeof integration.startBuildRuntimeImage !== "function") {
|
|
2649
|
+
return { ok: false, message: "OpenClawIntegration.startBuildRuntimeImage is not implemented" };
|
|
2650
|
+
}
|
|
2651
|
+
return integration.startBuildRuntimeImage({ tag });
|
|
2652
|
+
}
|
|
2653
|
+
/** @deprecated Use buildSlimOpenclawImage instead */
|
|
2654
|
+
export async function buildCustomOpenclawImage(tag) {
|
|
2655
|
+
return buildSlimOpenclawImage(tag);
|
|
2656
|
+
}
|
|
2657
|
+
/** @deprecated Use startBuildSlimOpenclawImage instead */
|
|
2658
|
+
export function startBuildCustomOpenclawImage(tag) {
|
|
2659
|
+
return startBuildSlimOpenclawImage(tag);
|
|
2660
|
+
}
|
|
2661
|
+
export async function runFullSetup(options = {}) {
|
|
2662
|
+
const steps = [];
|
|
2663
|
+
let allOk = true;
|
|
2664
|
+
const defaults = {
|
|
2665
|
+
installNomad: true,
|
|
2666
|
+
buildDockerImage: true,
|
|
2667
|
+
...options,
|
|
2668
|
+
};
|
|
2669
|
+
// Check Docker by daemon accessibility (docker info), not just binary existence
|
|
2670
|
+
const dockerReady = canAccessDockerDaemon(10000);
|
|
2671
|
+
if (!dockerReady) {
|
|
2672
|
+
steps.push({ step: "docker", status: "running", message: "Installing Docker..." });
|
|
2673
|
+
const result = await installDocker();
|
|
2674
|
+
steps[steps.length - 1].status = result.ok ? "done" : "error";
|
|
2675
|
+
steps[steps.length - 1].message = result.message;
|
|
2676
|
+
if (!result.ok)
|
|
2677
|
+
allOk = false;
|
|
2678
|
+
}
|
|
2679
|
+
if (defaults.installNomad) {
|
|
2680
|
+
steps.push({ step: "nomad", status: "running", message: "Installing Nomad..." });
|
|
2681
|
+
const result = await installNomad();
|
|
2682
|
+
steps[steps.length - 1].status = result.ok ? "done" : "error";
|
|
2683
|
+
steps[steps.length - 1].message = result.message;
|
|
2684
|
+
if (!result.ok)
|
|
2685
|
+
allOk = false;
|
|
2686
|
+
if (result.ok) {
|
|
2687
|
+
steps.push({ step: "nomad-systemd", status: "running", message: "Configuring Nomad auto-start..." });
|
|
2688
|
+
const sysResult = installNomadSystemd();
|
|
2689
|
+
steps[steps.length - 1].status = sysResult.ok ? "done" : "error";
|
|
2690
|
+
steps[steps.length - 1].message = sysResult.message;
|
|
2691
|
+
if (!sysResult.ok) {
|
|
2692
|
+
// Fallback to manual start if systemd fails
|
|
2693
|
+
steps.push({ step: "nomad-start", status: "running", message: "Starting Nomad..." });
|
|
2694
|
+
const startResult = await startNomad();
|
|
2695
|
+
steps[steps.length - 1].status = startResult.ok ? "done" : "error";
|
|
2696
|
+
steps[steps.length - 1].message = startResult.message;
|
|
2697
|
+
if (!startResult.ok)
|
|
2698
|
+
allOk = false;
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
// First-time setup prepares only required App workload images. Merely
|
|
2703
|
+
// registering an optional Integration must not download its App image: the
|
|
2704
|
+
// user installs optional Apps explicitly through Panel or CLI.
|
|
2705
|
+
if (defaults.buildDockerImage) {
|
|
2706
|
+
// Restart Nomad so it re-detects Docker driver after Docker was installed
|
|
2707
|
+
try {
|
|
2708
|
+
execSync("sudo systemctl restart nomad 2>/dev/null || true", { timeout: 15000 });
|
|
2709
|
+
for (let i = 0; i < 10; i++) {
|
|
2710
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
2711
|
+
if (isPortListening(4646))
|
|
2712
|
+
break;
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
catch { }
|
|
2716
|
+
for (const integration of listRegisteredIntegrations().filter((item) => item.manifest?.requiredForSetupReady === true)) {
|
|
2717
|
+
const stepName = `runtime-${integration.kind}`;
|
|
2718
|
+
// Prefer buildRuntimeImage (pure docker image prep) over
|
|
2719
|
+
// installRuntime (legacy host-mode npm install). Integrations that only
|
|
2720
|
+
// implement one will use whichever is present; integrations with neither
|
|
2721
|
+
// are skipped silently.
|
|
2722
|
+
const runner = integration.buildRuntimeImage
|
|
2723
|
+
? integration.buildRuntimeImage.bind(integration)
|
|
2724
|
+
: integration.installRuntime
|
|
2725
|
+
? integration.installRuntime.bind(integration)
|
|
2726
|
+
: null;
|
|
2727
|
+
if (!runner)
|
|
2728
|
+
continue;
|
|
2729
|
+
steps.push({
|
|
2730
|
+
step: stepName,
|
|
2731
|
+
status: "running",
|
|
2732
|
+
message: `Preparing ${integration.kind} runtime…`,
|
|
2733
|
+
});
|
|
2734
|
+
try {
|
|
2735
|
+
const imgResult = (await runner());
|
|
2736
|
+
const okStep = !!imgResult.ok;
|
|
2737
|
+
steps[steps.length - 1].status = okStep ? "done" : "error";
|
|
2738
|
+
steps[steps.length - 1].message = imgResult.message;
|
|
2739
|
+
if (!okStep)
|
|
2740
|
+
allOk = false;
|
|
2741
|
+
}
|
|
2742
|
+
catch (e) {
|
|
2743
|
+
const msg = e?.message || "runtime prepare failed";
|
|
2744
|
+
steps[steps.length - 1].status = "error";
|
|
2745
|
+
steps[steps.length - 1].message = msg;
|
|
2746
|
+
allOk = false;
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
if (isPortListening(4646)) {
|
|
2751
|
+
await finalizeNomadStartup();
|
|
2752
|
+
}
|
|
2753
|
+
// Only mark setup as complete if all critical steps succeeded
|
|
2754
|
+
if (!allOk) {
|
|
2755
|
+
return { ok: false, steps };
|
|
2756
|
+
}
|
|
2757
|
+
const config = getCoreConfig();
|
|
2758
|
+
config.service_manager = "nomad";
|
|
2759
|
+
config.nomad_driver = "docker";
|
|
2760
|
+
saveCoreConfig(config);
|
|
2761
|
+
// Install jishushell systemd service (best-effort — does not block setup completion)
|
|
2762
|
+
steps.push({ step: "jishushell-systemd", status: "running", message: "Configuring JishuShell auto-start..." });
|
|
2763
|
+
const jsResult = installJishushellSystemd();
|
|
2764
|
+
steps[steps.length - 1].status = jsResult.ok ? "done" : "error";
|
|
2765
|
+
steps[steps.length - 1].message = jsResult.message;
|
|
2766
|
+
return { ok: allOk, steps };
|
|
2767
|
+
}
|
|
2768
|
+
//# sourceMappingURL=setup-manager.js.map
|