jishushell 0.4.30 → 0.5.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/Dockerfile.hermes-slim +2 -5
  2. package/apps/anythingllm-container.yaml +287 -0
  3. package/apps/browserless-chromium-container.yaml +18 -6
  4. package/apps/filebrowser-container.yaml +164 -0
  5. package/apps/ollama-binary.yaml +44 -0
  6. package/apps/ollama-with-hollama-binary.yaml +45 -1
  7. package/apps/openclaw-binary.yaml +8 -0
  8. package/apps/openclaw-container.yaml +9 -1
  9. package/apps/openclaw-with-searxng-container.yaml +4 -0
  10. package/apps/searxng-container.yaml +5 -4
  11. package/apps/weknora-container.yaml +471 -0
  12. package/dist/cli/doctor.js +144 -16
  13. package/dist/cli/doctor.js.map +1 -1
  14. package/dist/cli/panel.js.map +1 -1
  15. package/dist/config.d.ts +19 -0
  16. package/dist/config.js +99 -1
  17. package/dist/config.js.map +1 -1
  18. package/dist/install.js +4 -4
  19. package/dist/install.js.map +1 -1
  20. package/dist/routes/auth.js +2 -2
  21. package/dist/routes/auth.js.map +1 -1
  22. package/dist/routes/backup.js +64 -11
  23. package/dist/routes/backup.js.map +1 -1
  24. package/dist/routes/external-mounts.d.ts +17 -0
  25. package/dist/routes/external-mounts.js +73 -0
  26. package/dist/routes/external-mounts.js.map +1 -0
  27. package/dist/routes/file-mounts.d.ts +13 -0
  28. package/dist/routes/file-mounts.js +90 -0
  29. package/dist/routes/file-mounts.js.map +1 -0
  30. package/dist/routes/files-organize.d.ts +28 -0
  31. package/dist/routes/files-organize.js +167 -0
  32. package/dist/routes/files-organize.js.map +1 -0
  33. package/dist/routes/files.d.ts +31 -0
  34. package/dist/routes/files.js +321 -0
  35. package/dist/routes/files.js.map +1 -0
  36. package/dist/routes/instances.js +87 -12
  37. package/dist/routes/instances.js.map +1 -1
  38. package/dist/routes/internal.d.ts +2 -0
  39. package/dist/routes/internal.js +59 -0
  40. package/dist/routes/internal.js.map +1 -0
  41. package/dist/routes/llm.js +29 -0
  42. package/dist/routes/llm.js.map +1 -1
  43. package/dist/routes/setup.js +9 -9
  44. package/dist/routes/setup.js.map +1 -1
  45. package/dist/routes/system.js +1 -1
  46. package/dist/routes/system.js.map +1 -1
  47. package/dist/routes/webdav.d.ts +17 -0
  48. package/dist/routes/webdav.js +114 -0
  49. package/dist/routes/webdav.js.map +1 -0
  50. package/dist/server.js +358 -6
  51. package/dist/server.js.map +1 -1
  52. package/dist/services/agent-apps/catalog.d.ts +3 -0
  53. package/dist/services/agent-apps/catalog.js +40 -13
  54. package/dist/services/agent-apps/catalog.js.map +1 -1
  55. package/dist/services/agent-apps/installers/shell-script.d.ts +1 -1
  56. package/dist/services/agent-apps/installers/shell-script.js +19 -2
  57. package/dist/services/agent-apps/installers/shell-script.js.map +1 -1
  58. package/dist/services/agent-apps/types.d.ts +3 -0
  59. package/dist/services/app/app-compiler.d.ts +1 -1
  60. package/dist/services/app/app-compiler.js +5 -5
  61. package/dist/services/app/app-compiler.js.map +1 -1
  62. package/dist/services/app/app-manager.d.ts +9 -0
  63. package/dist/services/app/app-manager.js +248 -43
  64. package/dist/services/app/app-manager.js.map +1 -1
  65. package/dist/services/app/custom-manager.js.map +1 -1
  66. package/dist/services/app/hermes-agent-manager.js +1 -0
  67. package/dist/services/app/hermes-agent-manager.js.map +1 -1
  68. package/dist/services/app/ollama-manager.js +1 -1
  69. package/dist/services/app/ollama-manager.js.map +1 -1
  70. package/dist/services/app/openclaw-manager.js +37 -5
  71. package/dist/services/app/openclaw-manager.js.map +1 -1
  72. package/dist/services/app/platform-transform.d.ts +32 -0
  73. package/dist/services/app/platform-transform.js +65 -0
  74. package/dist/services/app/platform-transform.js.map +1 -0
  75. package/dist/services/app-passwords.d.ts +61 -0
  76. package/dist/services/app-passwords.js +173 -0
  77. package/dist/services/app-passwords.js.map +1 -0
  78. package/dist/services/backup-manager.d.ts +11 -0
  79. package/dist/services/backup-manager.js +220 -8
  80. package/dist/services/backup-manager.js.map +1 -1
  81. package/dist/services/capability-endpoint-validator.js +26 -7
  82. package/dist/services/capability-endpoint-validator.js.map +1 -1
  83. package/dist/services/connection-apply.d.ts +2 -0
  84. package/dist/services/connection-apply.js +55 -1
  85. package/dist/services/connection-apply.js.map +1 -1
  86. package/dist/services/connection-resolver.js +1 -1
  87. package/dist/services/connection-resolver.js.map +1 -1
  88. package/dist/services/connection-transactor.d.ts +2 -0
  89. package/dist/services/connection-transactor.js +12 -2
  90. package/dist/services/connection-transactor.js.map +1 -1
  91. package/dist/services/external-mounts.d.ts +40 -0
  92. package/dist/services/external-mounts.js +187 -0
  93. package/dist/services/external-mounts.js.map +1 -0
  94. package/dist/services/files-manager.d.ts +252 -0
  95. package/dist/services/files-manager.js +1075 -0
  96. package/dist/services/files-manager.js.map +1 -0
  97. package/dist/services/files-mounts.d.ts +42 -0
  98. package/dist/services/files-mounts.js +207 -0
  99. package/dist/services/files-mounts.js.map +1 -0
  100. package/dist/services/instance-manager.js +90 -32
  101. package/dist/services/instance-manager.js.map +1 -1
  102. package/dist/services/llm-proxy/index.d.ts +28 -0
  103. package/dist/services/llm-proxy/index.js +76 -3
  104. package/dist/services/llm-proxy/index.js.map +1 -1
  105. package/dist/services/llm-proxy/ssrf.js +6 -2
  106. package/dist/services/llm-proxy/ssrf.js.map +1 -1
  107. package/dist/services/llm-proxy/validate-key.d.ts +41 -0
  108. package/dist/services/llm-proxy/validate-key.js +672 -0
  109. package/dist/services/llm-proxy/validate-key.js.map +1 -0
  110. package/dist/services/macos-launchd.d.ts +89 -0
  111. package/dist/services/macos-launchd.js +273 -0
  112. package/dist/services/macos-launchd.js.map +1 -0
  113. package/dist/services/nomad-manager.d.ts +11 -0
  114. package/dist/services/nomad-manager.js +343 -98
  115. package/dist/services/nomad-manager.js.map +1 -1
  116. package/dist/services/organize/applier.d.ts +46 -0
  117. package/dist/services/organize/applier.js +218 -0
  118. package/dist/services/organize/applier.js.map +1 -0
  119. package/dist/services/organize/rules.d.ts +57 -0
  120. package/dist/services/organize/rules.js +286 -0
  121. package/dist/services/organize/rules.js.map +1 -0
  122. package/dist/services/organize/scanner.d.ts +50 -0
  123. package/dist/services/organize/scanner.js +366 -0
  124. package/dist/services/organize/scanner.js.map +1 -0
  125. package/dist/services/organize/store.d.ts +14 -0
  126. package/dist/services/organize/store.js +82 -0
  127. package/dist/services/organize/store.js.map +1 -0
  128. package/dist/services/panel-manager.js +40 -11
  129. package/dist/services/panel-manager.js.map +1 -1
  130. package/dist/services/process-manager.js +3 -2
  131. package/dist/services/process-manager.js.map +1 -1
  132. package/dist/services/runtime/adapters/custom.js +56 -0
  133. package/dist/services/runtime/adapters/custom.js.map +1 -1
  134. package/dist/services/runtime/adapters/hermes.d.ts +4 -3
  135. package/dist/services/runtime/adapters/hermes.js +166 -64
  136. package/dist/services/runtime/adapters/hermes.js.map +1 -1
  137. package/dist/services/runtime/adapters/openclaw-routes.d.ts +8 -2
  138. package/dist/services/runtime/adapters/openclaw-routes.js +68 -0
  139. package/dist/services/runtime/adapters/openclaw-routes.js.map +1 -1
  140. package/dist/services/runtime/adapters/openclaw.d.ts +118 -0
  141. package/dist/services/runtime/adapters/openclaw.js +1459 -49
  142. package/dist/services/runtime/adapters/openclaw.js.map +1 -1
  143. package/dist/services/runtime/instance.d.ts +1 -1
  144. package/dist/services/runtime/instance.js +1 -1
  145. package/dist/services/runtime/instance.js.map +1 -1
  146. package/dist/services/runtime/mcp-shims/anythingllm-shim.d.ts +46 -0
  147. package/dist/services/runtime/mcp-shims/anythingllm-shim.js +281 -0
  148. package/dist/services/runtime/mcp-shims/anythingllm-shim.js.map +1 -0
  149. package/dist/services/runtime/mcp-shims/drive-shim.d.ts +54 -0
  150. package/dist/services/runtime/mcp-shims/drive-shim.js +489 -0
  151. package/dist/services/runtime/mcp-shims/drive-shim.js.map +1 -0
  152. package/dist/services/runtime/types.d.ts +31 -0
  153. package/dist/services/setup-manager.js +190 -68
  154. package/dist/services/setup-manager.js.map +1 -1
  155. package/dist/services/suggestions.js.map +1 -1
  156. package/dist/services/update-manager.js +32 -14
  157. package/dist/services/update-manager.js.map +1 -1
  158. package/dist/services/webdav/server.d.ts +24 -0
  159. package/dist/services/webdav/server.js +420 -0
  160. package/dist/services/webdav/server.js.map +1 -0
  161. package/dist/services/webdav/xml-builder.d.ts +73 -0
  162. package/dist/services/webdav/xml-builder.js +156 -0
  163. package/dist/services/webdav/xml-builder.js.map +1 -0
  164. package/dist/services/workspace-builder.d.ts +29 -0
  165. package/dist/services/workspace-builder.js +188 -0
  166. package/dist/services/workspace-builder.js.map +1 -0
  167. package/dist/types.d.ts +61 -0
  168. package/dist/utils/path-locks.d.ts +30 -0
  169. package/dist/utils/path-locks.js +63 -0
  170. package/dist/utils/path-locks.js.map +1 -0
  171. package/dist/utils/path-safety.d.ts +41 -0
  172. package/dist/utils/path-safety.js +119 -0
  173. package/dist/utils/path-safety.js.map +1 -0
  174. package/dist/utils/safe-write.d.ts +24 -0
  175. package/dist/utils/safe-write.js +82 -0
  176. package/dist/utils/safe-write.js.map +1 -0
  177. package/install/jishu-install.sh +247 -35
  178. package/install/jishu-uninstall.sh +45 -5
  179. package/package.json +20 -2
  180. package/public/assets/ApiKeyField-CvyAOcJS.js +1 -0
  181. package/public/assets/Dashboard-AuJESBlJ.js +1 -0
  182. package/public/assets/{HermesChatPanel-_GHoklgo.js → HermesChatPanel-CByPREwb.js} +1 -1
  183. package/public/assets/HermesConfigForm-DRda8FKX.js +4 -0
  184. package/public/assets/InitPassword-ka4wNpM5.js +1 -0
  185. package/public/assets/InstanceDetail-Cg1nS8HX.js +92 -0
  186. package/public/assets/Login-aPajuQzf.js +1 -0
  187. package/public/assets/NewInstance-Dd1ebNIx.js +1 -0
  188. package/public/assets/ProviderRecommendations-DFmADQ7V.js +1 -0
  189. package/public/assets/Settings-BYQnbLYL.js +1 -0
  190. package/public/assets/Setup-D05lwDOV.js +1 -0
  191. package/public/assets/WeixinLoginPanel-D89kdhP4.js +9 -0
  192. package/public/assets/index-HSXCsceK.css +1 -0
  193. package/public/assets/index-bnBu0nlQ.js +19 -0
  194. package/public/assets/registry-C_qeFTkZ.js +2 -0
  195. package/public/assets/usePolling-Bn93fe7M.js +1 -0
  196. package/public/assets/{vendor-i18n-ucpM0OR0.js → vendor-i18n-flxcMVeP.js} +2 -2
  197. package/public/assets/{vendor-react-Bk1hRGiY.js → vendor-react-ZC5T_huj.js} +7 -7
  198. package/public/index.html +4 -4
  199. package/scripts/check-app-spec.mjs +18 -4
  200. package/scripts/check-colima-launchd.mjs +230 -0
  201. package/scripts/check-new-file-tests.mjs +230 -0
  202. package/scripts/check-quarantine-expiry.mjs +105 -0
  203. package/scripts/perf/README.md +49 -0
  204. package/scripts/perf/auth.js +99 -0
  205. package/scripts/perf/config.js +63 -0
  206. package/scripts/perf/instances.js +143 -0
  207. package/scripts/perf/proxy.js +96 -0
  208. package/scripts/smoke/files-w1.sh +142 -0
  209. package/scripts/smoke-backend.mjs +122 -0
  210. package/scripts/smoke-post-publish.mjs +346 -0
  211. package/public/assets/Dashboard-rkWp-CXd.js +0 -1
  212. package/public/assets/HermesConfigForm-anDnwUp_.js +0 -4
  213. package/public/assets/InitPassword-ZU9_-hDr.js +0 -1
  214. package/public/assets/InstanceDetail-CN0FH1aw.js +0 -92
  215. package/public/assets/Login-BItXqYAJ.js +0 -1
  216. package/public/assets/NewInstance-BousE6kY.js +0 -1
  217. package/public/assets/ProviderRecommendations-DFYj7Fb6.js +0 -1
  218. package/public/assets/Settings-Bttc6QmM.js +0 -1
  219. package/public/assets/Setup-Bsxx1zgj.js +0 -1
  220. package/public/assets/WeixinLoginPanel-DPZpAKgO.js +0 -9
  221. package/public/assets/index-8xZy1z5k.css +0 -1
  222. package/public/assets/index-Dw3HhUYE.js +0 -19
  223. package/public/assets/input-paste-CrNVAyOy.js +0 -1
  224. package/public/assets/providers-DtNXh9JD.js +0 -1
  225. package/public/assets/registry-5s2UB6is.js +0 -2
  226. package/public/assets/usePolling-Do5Erqm_.js +0 -1
@@ -178,11 +178,8 @@ RUN mkdir -p /opt/data && \
178
178
  COPY templates/hermes-entrypoint.sh /usr/local/bin/jishushell-hermes-entry.sh
179
179
  RUN chmod 0755 /usr/local/bin/jishushell-hermes-entry.sh
180
180
 
181
- # Advertise the protocol contract this image satisfies. The panel reads
182
- # this label via `docker inspect` before launching an instance; a panel
183
- # that only knows protocol version N refuses to start an image that
184
- # declares N+1, which is how we gate rolling upgrades without silent
185
- # behavioral drift.
181
+ # Retained as build metadata for operators and registry inspection. Runtime
182
+ # readiness is determined from the official ghcr.io RepoDigest, not this label.
186
183
  LABEL runtime.protocol.version="1"
187
184
 
188
185
  # Zombie reaping is handled by Nomad's `init: true` flag (docker-init
@@ -0,0 +1,287 @@
1
+ id: anythingllm-container
2
+ name: AnythingLLM
3
+ version: "1.0.0"
4
+ jishushell:
5
+ min_version: "0.5.15"
6
+ description: "Mintplex Labs 开源 RAG 引擎:单容器 + 内置 LanceDB 向量库 + 内置 Xenova ONNX embedder(默认零外部依赖)+ OpenAI 兼容 chat API;通过 anythingllm-shim 暴露 kb_search 工具给 OpenClaw agent"
7
+ singleInstance: true
8
+
9
+ # 拓扑(单容器,无 sidecar)
10
+ # anythingllm (port 3001) ── host :18097 ── 用户浏览器 / shim API 调
11
+ # └─ /app/server/storage (LanceDB + sqlite + 上传文件 + Xenova embedder ONNX 模型)
12
+ #
13
+ # 端到端调用链(agent 模式 / 路 2):
14
+ # Feishu 用户 → claw1 (MiniMax) → 决定调 kb_search → mcporter call kb.kb_search
15
+ # → anythingllm-shim → POST http://host.docker.internal:18097/api/v1/openai/chat/completions
16
+ # → AnythingLLM workspace 自动 RAG 检索 + 拼 prompt → AnythingLLM 上游 LLM
17
+ # → 答案 + citation → 回 claw1 → Feishu 用户
18
+ #
19
+ # 关键裁剪 / 配置:
20
+ # - SERVER_PORT 3001(AnythingLLM 默认 unified port)
21
+ # - STORAGE_DIR 落到 bind-mounted /app/server/storage,模型/向量/文件全持久化
22
+ # - DISABLE_TELEMETRY=true 不发数据回 Mintplex
23
+ # - 默认 embedder 内置(Xenova/all-MiniLM-L6-v2 ONNX)—— 不配 EMBEDDING_ENGINE 走 native
24
+ # - LLM provider 不预设;用户首启时 setup wizard 或 API 配,或者让 shim 通过 API 预填
25
+ #
26
+ # 端口选择:18097 在 panel 18xxx 段,避开 18088 filebrowser / 18092-18096 weknora 段
27
+ # (虽然 weknora 已卸) / 18099 fastembed-bge (已删) / 18790 openclaw gateway。
28
+ #
29
+ # host_network: docker_bridge 让 anythingllm-shim 容器(实际跑在 claw1 gateway
30
+ # 容器内)经 host.docker.internal:18097 调 AnythingLLM(跟之前 fastembed-bge
31
+ # 同套路;nomad.hcl 已经声明 docker_bridge host_network)。同时用户浏览器
32
+ # 走 LAN IP 10.188.5.25:18097 直接访问 UI(双地址 docker-proxy 自动处理)。
33
+
34
+ tasks:
35
+ - name: anythingllm
36
+ role: service
37
+ runtime: container
38
+ image: "mintplexlabs/anythingllm:latest"
39
+ # AnythingLLM 镜像 Dockerfile 用 `USER anythingllm` 设了非 root user (uid 1000),
40
+ # bind-mount /app/server/storage 给 panel-user (uid 1000) 拥有 → 容器侧
41
+ # uid 1000 (anythingllm user) 能写。"host" 解析到 1000:1000 跟镜像默认对上,
42
+ # 不需要 cap_add,干净。
43
+ user: "host"
44
+ env:
45
+ # 监听端口(unified backend + frontend)
46
+ SERVER_PORT: "3001"
47
+ # 数据持久化目录。镜像默认就是这个,显式声明便于看 spec 时一目了然。
48
+ STORAGE_DIR: "/app/server/storage"
49
+ # 关 telemetry,本地用不需要给 Mintplex 上报。
50
+ DISABLE_TELEMETRY: "true"
51
+ # 关闭强制 setup wizard 的 onboarding 锁(首启可直接调 API 创建 workspace
52
+ # 而不必先点完 UI 向导)。
53
+ DISABLE_SETUP_GUIDED_TOUR: "true"
54
+ TZ: "Asia/Shanghai"
55
+ resources:
56
+ # AnythingLLM 实际跑 ~400-700MB RSS:Node.js backend + Xenova ONNX embedder
57
+ # + LanceDB (mmap)。给 1024Mi 留 buffer。
58
+ cpu: "1500m"
59
+ memory: "1024Mi"
60
+ ports:
61
+ - name: http
62
+ port: 18097
63
+ container_port: 3001
64
+ visibility: external
65
+ # 不指定 host_network → 默认 0.0.0.0 publish:LAN(用户浏览器)和
66
+ # docker0 (172.17.0.1,shim 经 host.docker.internal 调) 全覆盖。
67
+ # 之前 docker_bridge 只绑 172.17.0.1,UI 不可达。
68
+ volumes:
69
+ # 单一数据卷涵盖 sqlite + LanceDB + workspace docs + embedder ONNX cache。
70
+ # 跟其它 app 数据目录保持隔离习惯。
71
+ - source: "~/.jishushell/apps/${app_id}/data"
72
+ target: "/app/server/storage"
73
+ health:
74
+ http:
75
+ # AnythingLLM 健康检查 endpoint —— /api/ping 在 backend 起来后即 200。
76
+ # 首启要 Node 启动 ~5s + 初始化 sqlite + 加载 ONNX embedder(首次解压 ~150MB
77
+ # ONNX 模型到 /app/server/storage/models/Xenova/ ~30s on Pi)。
78
+ path: /api/ping
79
+ port: 3001
80
+ interval: "15s"
81
+ timeout: "5s"
82
+ retries: 6
83
+ start_period: "180s"
84
+
85
+ provides:
86
+ # 通用 capability:让其它 agent / shim `requires: knowledge` 时
87
+ # panel 注入 ANYTHINGLLM_BASE_URL。命名遵循 `<category>-<name>` 约定
88
+ # (见 scripts/check-app-spec.mjs 的 categoryOf 分类规则)。
89
+ - capability: "knowledge-anythingllm"
90
+ task: "anythingllm"
91
+ port: 18097
92
+ path: "/api/v1"
93
+ protocol: "http"
94
+ visibility: "external"
95
+ description: "AnythingLLM OpenAI 兼容 chat-with-rag endpoint (/api/v1/openai/chat/completions)"
96
+
97
+ # Web UI capability:通过 panel `/apps/anythingllm/*` 反代 或者用户直接访问
98
+ # LAN IP:18097。AnythingLLM 默认 SPA 不绑 sub-path → 走端口直连(同 weknora 套路)。
99
+ - capability: "anythingllm-ui"
100
+ task: "anythingllm"
101
+ port: 18097
102
+ path: "/"
103
+ protocol: "http"
104
+ visibility: "external"
105
+ description: "AnythingLLM Web UI"
106
+
107
+ lifecycle:
108
+ install:
109
+ # ~1 GB 压缩镜像,~2GB 解压。给 30 min idle timeout 兜 Pi 慢网(同 weknora-app 同等级)。
110
+ # idle_timeout_ms 字段最近修过,现在真透传到 docker pull 的 stall 检测。
111
+ - downloadImage: "mintplexlabs/anythingllm:latest"
112
+ timeout_ms: 1800000
113
+ idle_timeout_ms: 1800000
114
+ - mkdir: "~/.jishushell/apps/${app_id}/data"
115
+
116
+ pre_start:
117
+ # 幂等:start/restart 都过这条路径,目录被误删可自愈。
118
+ - mkdir: "~/.jishushell/apps/${app_id}/data"
119
+
120
+ post_start:
121
+ # 首启自动布线,幂等可重跑:
122
+ # 1) 等 /api/ping 200(首启要解压 ~150MB Xenova ONNX,Pi 上 ~60-120s)
123
+ # 2) 从 panel 拿默认 LLM provider 凭据(GET /api/internal/default-provider)
124
+ # panel 端 default_provider 没设 → ready:false → 跳过 LLM 自配,
125
+ # 留给用户在 UI 里手动选
126
+ # 3) ready:true 时把 generic-openai provider 灌进去(无视用户上游真协议;
127
+ # panel proxy 反正只暴露 OpenAI 兼容口子)
128
+ # 4) 生成 AnythingLLM dev API key(POST /api/system/generate-api-key,
129
+ # 单用户模式无 auth),落 credentials.json
130
+ # 5) 建默认 workspace "default"(已存在则忽略,AnythingLLM 自身的 unique 约束)
131
+ #
132
+ # 关键设计:整个脚本必须幂等。每次 start/restart 都重跑,但:
133
+ # - update-env 重复 POST = 同样的环境值再写一遍,OK
134
+ # - generate-api-key 重复调 = 多生成几把 key(不丢失旧的,但旧的也能用,
135
+ # credentials.json 只保留最新一把,shim 端拿这把就行)
136
+ # - workspace/new 重复调 = AnythingLLM 返回 409 / 重复,被 || true 吞掉
137
+ #
138
+ # 失败处理:任何一步失败都 set -e 兜,post_start 整体失败 → app 状态停留
139
+ # 在 "installed" 但 currentTaskStatus=error,用户在 UI 上看得到,重启再跑。
140
+ # 全部 JSON 解析 / 文件写入走 `node -e` —— node 是 panel 装机后**唯一**
141
+ # 必定存在的运行时(jishushell 本身跑在 node 上)。jq 在 Pi/Debian 默认不装;
142
+ # python3 在 Alpine slim / 极简 Linux 上不一定有。用 node 才能保证第三方
143
+ # 用户初装即可用。
144
+ - run: |
145
+ set -e
146
+ BASE_URL="http://${JISHUSHELL_LAN_HOST:-127.0.0.1}:18097"
147
+ CRED_DIR="$HOME/.jishushell/apps/anythingllm-container"
148
+ CRED_FILE="$CRED_DIR/credentials.json"
149
+ mkdir -p "$CRED_DIR"
150
+
151
+ # jget <dot.path> — read JSON from stdin, print field at path, empty on miss
152
+ jget() {
153
+ node -e 'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{try{let v=JSON.parse(s);for(const k of process.argv[1].split("."))v=v?.[k];process.stdout.write(v==null?"":String(v));}catch{process.stdout.write("")}})' "$1"
154
+ }
155
+
156
+ echo "[post_start] waiting for AnythingLLM /api/ping ..."
157
+ for i in $(seq 1 60); do
158
+ code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 5 "$BASE_URL/api/ping" || echo 000)
159
+ if [ "$code" = "200" ]; then
160
+ echo "[post_start] /api/ping is 200 (after ${i}x2s)"
161
+ break
162
+ fi
163
+ sleep 2
164
+ done
165
+ # final check; if still down, post_start fails fast
166
+ curl -sf --max-time 5 "$BASE_URL/api/ping" >/dev/null
167
+
168
+ echo "[post_start] fetching panel default provider ..."
169
+ DP_JSON=$(curl -sf --max-time 8 \
170
+ -H "X-Jishushell-Internal-Token: $JISHUSHELL_INTERNAL_TOKEN" \
171
+ "$JISHUSHELL_PANEL_URL/api/internal/default-provider" || echo '{"ready":false,"reason":"panel_unreachable"}')
172
+ READY=$(printf '%s' "$DP_JSON" | jget ready)
173
+
174
+ if [ "$READY" = "true" ]; then
175
+ MODEL=$(printf '%s' "$DP_JSON" | jget modelId)
176
+ # W1 直连上游:用户上游 baseUrl + decrypted apiKey 直接给 AnythingLLM。
177
+ # 牺牲了 panel proxy 的 rate-limit / circuit-break / usage logging —
178
+ # W2 加 app-scoped proxy key 后切回经 proxy。
179
+ BASE=$(printf '%s' "$DP_JSON" | jget baseUrl)
180
+ KEY=$(printf '%s' "$DP_JSON" | jget apiKey)
181
+ echo "[post_start] configuring LLM provider: $MODEL @ $BASE"
182
+ # Build update-env body in node so apiKey / baseUrl / modelId can contain
183
+ # `"`, `\`, newlines, or any other JSON-significant chars without breaking
184
+ # the shell-interpolated payload (review finding F5).
185
+ UPDATE_BODY=$(BASE="$BASE" KEY="$KEY" MODEL="$MODEL" node -e '
186
+ process.stdout.write(JSON.stringify({
187
+ LLMProvider: "generic-openai",
188
+ GenericOpenAiBasePath: process.env.BASE,
189
+ GenericOpenAiKey: process.env.KEY,
190
+ GenericOpenAiModelPref: process.env.MODEL,
191
+ GenericOpenAiMaxTokens: "4096",
192
+ }));
193
+ ')
194
+ # Tolerate 401/403 — AnythingLLM ships single-user no-auth by default,
195
+ # but a user can opt-in to password protection via UI. validatedRequest
196
+ # then mandates a JWT we don't have, and there's no admin-bootstrap
197
+ # endpoint we can call here. Log the actionable next step and continue
198
+ # so the script stays idempotent (review finding F1).
199
+ UPDATE_HTTP=$(curl -s -o /tmp/anyllm-upd.txt -w '%{http_code}' --max-time 10 \
200
+ -X POST "$BASE_URL/api/system/update-env" \
201
+ -H "Content-Type: application/json" \
202
+ --data "$UPDATE_BODY" || echo 000)
203
+ case "$UPDATE_HTTP" in
204
+ 2*) echo "[post_start] LLM provider configured" ;;
205
+ 401|403)
206
+ echo "[post_start] WARN: AnythingLLM /api/system/update-env returned HTTP $UPDATE_HTTP — looks like a password is set; configure LLM in the UI then restart"
207
+ ;;
208
+ *)
209
+ echo "[post_start] WARN: update-env HTTP $UPDATE_HTTP — leaving LLM unconfigured"
210
+ [ -s /tmp/anyllm-upd.txt ] && head -c 400 /tmp/anyllm-upd.txt
211
+ ;;
212
+ esac
213
+ else
214
+ REASON=$(printf '%s' "$DP_JSON" | jget reason)
215
+ echo "[post_start] skip LLM auto-config (reason=${REASON:-unknown}); user will pick in UI"
216
+ fi
217
+
218
+ # 复用已存在的 dev API key(generate-api-key 增量、不可撤销,
219
+ # 每次重启重生只会堆 key;credentials.json 保稳定)。
220
+ if [ -f "$CRED_FILE" ]; then
221
+ KEY_JSON=$(CRED_FILE="$CRED_FILE" node -e 'try{const j=JSON.parse(require("fs").readFileSync(process.env.CRED_FILE,"utf-8"));process.stdout.write(String(j.apiKey||""))}catch{process.stdout.write("")}')
222
+ else
223
+ KEY_JSON=""
224
+ fi
225
+ if [ -n "$KEY_JSON" ]; then
226
+ echo "[post_start] reusing existing dev API key from $CRED_FILE"
227
+ else
228
+ echo "[post_start] generating new dev API key ..."
229
+ # Same 401 tolerance as update-env: if the user enabled password
230
+ # protection, generate-api-key needs a JWT. Surface the next step
231
+ # and exit cleanly so OpenClaw adapters fall back to "knowledge
232
+ # unconfigured" instead of leaving post_start in a permanent error.
233
+ GEN_HTTP=$(curl -s -o /tmp/anyllm-gen.json -w '%{http_code}' --max-time 8 \
234
+ -X POST "$BASE_URL/api/system/generate-api-key" || echo 000)
235
+ case "$GEN_HTTP" in
236
+ 2*)
237
+ KEY_JSON=$(jget apiKey.secret < /tmp/anyllm-gen.json)
238
+ if [ -z "$KEY_JSON" ]; then
239
+ echo "[post_start] WARN: generate-api-key returned 2xx but apiKey.secret missing — skipping kb wiring"
240
+ head -c 400 /tmp/anyllm-gen.json; echo
241
+ fi
242
+ ;;
243
+ 401|403)
244
+ echo "[post_start] WARN: generate-api-key HTTP $GEN_HTTP — AnythingLLM is password-protected. Generate a dev API key in the UI (Settings → API Keys) and write it to $CRED_FILE, then restart. Skipping kb wiring for this start."
245
+ ;;
246
+ *)
247
+ echo "[post_start] WARN: generate-api-key HTTP $GEN_HTTP — skipping kb wiring"
248
+ [ -s /tmp/anyllm-gen.json ] && head -c 400 /tmp/anyllm-gen.json
249
+ ;;
250
+ esac
251
+ fi
252
+
253
+ if [ -z "$KEY_JSON" ]; then
254
+ echo "[post_start] no API key available; skipping workspace seed + credentials write (post_start still considered success)"
255
+ echo "[post_start] DONE"
256
+ exit 0
257
+ fi
258
+
259
+ echo "[post_start] ensuring default workspace ..."
260
+ WS_HTTP=$(curl -s -o /tmp/anyllm-ws.json -w '%{http_code}' --max-time 10 \
261
+ -X POST "$BASE_URL/api/v1/workspace/new" \
262
+ -H "Authorization: Bearer $KEY_JSON" \
263
+ -H "Content-Type: application/json" \
264
+ -d '{"name":"default"}')
265
+ echo "[post_start] workspace POST → HTTP $WS_HTTP"
266
+ case "$WS_HTTP" in
267
+ 2*) : ;;
268
+ 4*) echo " (likely already exists; ignoring)" ;;
269
+ *) echo "[post_start] FAIL: workspace creation HTTP $WS_HTTP"; cat /tmp/anyllm-ws.json; exit 1 ;;
270
+ esac
271
+
272
+ # 写 credentials.json — 用 node 避免 shell quote 注入风险。
273
+ APIKEY="$KEY_JSON" BASEURL="$BASE_URL" CREDFILE="$CRED_FILE" node -e '
274
+ const fs = require("fs");
275
+ fs.writeFileSync(process.env.CREDFILE, JSON.stringify({
276
+ apiKey: process.env.APIKEY,
277
+ workspace: "default",
278
+ baseUrl: process.env.BASEURL,
279
+ }, null, 2));
280
+ fs.chmodSync(process.env.CREDFILE, 0o600);
281
+ '
282
+ echo "[post_start] credentials.json written → $CRED_FILE"
283
+ echo "[post_start] DONE"
284
+ timeout_ms: 300000
285
+
286
+ uninstall:
287
+ - deleteImage: "mintplexlabs/anythingllm:latest"
@@ -10,6 +10,11 @@ tasks:
10
10
  - name: browserless
11
11
  role: service
12
12
  runtime: container
13
+ # rebrowser 镜像在 Chromium 源码层面 patch 了 CDP Runtime.Enable 泄露问题,
14
+ # 是目前对抗自动化检测最彻底的方案(适用于小红书/淘宝等强风控场景)。
15
+ # 如遇 ghcr.io/browserless/chromium 仍被风控,可切换为:
16
+ # image: "ghcr.io/browserless/chromium:rebrowser"
17
+ # (rebrowser 分支由 browserless 官方维护,与 latest 保持同步)
13
18
  image: "ghcr.io/browserless/chromium:latest"
14
19
  env:
15
20
  HOST: "0.0.0.0"
@@ -18,13 +23,19 @@ tasks:
18
23
  CONCURRENT: "5"
19
24
  QUEUED: "10"
20
25
  TIMEOUT: "120000"
21
- # 防反爬虫配置
22
- DEFAULT_USER_AGENT: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
23
- FUNCTION_ENABLE_INCOGNITO: "true"
26
+ # 反风控配置
27
+ # STEALTH=true: 启用 puppeteer-extra-plugin-stealth,注入多项 evasion 补丁:
28
+ # 修复 navigator.plugins/languages、WebGL vendor、canvas 指纹、chrome 对象等
29
+ STEALTH: "true"
30
+ # UA 不指定版本号,由 Chromium 自行上报实际版本,避免版本不一致被检测
31
+ DEFAULT_USER_AGENT: ""
32
+ FUNCTION_ENABLE_INCOGNITO: "false"
24
33
  NAVIGATION_TIMEOUT: "30000"
25
- # 减少自动化检测特征
26
- DEFAULT_LAUNCH_ARGS: "--disable-blink-features=AutomationControlled --disable-infobars --window-size=1920,1080 --start-maximized"
27
- DEFAULT_HEADLESS: "new"
34
+ # --disable-blink-features=AutomationControlled: 隐藏 navigator.webdriver
35
+ # headed 模式(不加 --headless):小红书/百度会检测 headless 特征,
36
+ # browserless 容器内置 Xvfb,直接使用 headed 模式即可
37
+ DEFAULT_LAUNCH_ARGS: "--disable-blink-features=AutomationControlled --disable-infobars --window-size=1920,1080 --start-maximized --no-first-run --no-default-browser-check"
38
+ DEFAULT_HEADLESS: "false"
28
39
  # 持久化用户数据(cookies/localStorage/session),登录后会话保持
29
40
  DATA_DIR: "/tmp/browserless-data"
30
41
  volumes:
@@ -53,6 +64,7 @@ provides:
53
64
  protocol: "http"
54
65
  description: "Browserless Debug Viewer,可在实例页中嵌入浏览器画面"
55
66
  - capability: "browserless-api"
67
+ task: "browserless"
56
68
  port: 3000
57
69
  path: "/"
58
70
  protocol: "http"
@@ -0,0 +1,164 @@
1
+ id: filebrowser-container
2
+ name: File Browser
3
+ version: "1.0.0"
4
+ jishushell:
5
+ min_version: "0.5.15"
6
+ description: "通过浏览器管理本地文件的 Web UI"
7
+ singleInstance: true
8
+
9
+ tasks:
10
+ - name: filebrowser
11
+ role: service
12
+ runtime: container
13
+ image: "filebrowser/filebrowser:latest"
14
+ # Run as the panel user's uid:gid so files written via Filebrowser are
15
+ # owned by the same user that owns ~/.jishushell/files and the drive_*
16
+ # HTTP path. Mirrors openwebui's "user: host". Avoids the PUID/PGID
17
+ # entrypoint dance from the :s6 variant — uid/gid is set once at job
18
+ # build time.
19
+ user: "host"
20
+ args:
21
+ # Filebrowser CLI flag precedence is Flags > Env > Config file > DB.
22
+ # The image's init.sh seeds /config/settings.json from baked-in
23
+ # defaults (baseURL=""), which would otherwise mask the DB value —
24
+ # so we re-assert baseURL/root/etc here at runtime. auth.method is
25
+ # not exposed as a runtime flag and lives in the DB only (seeded
26
+ # during lifecycle/install below).
27
+ - "--root=/srv"
28
+ - "--database=/database/filebrowser.db"
29
+ - "--address=0.0.0.0"
30
+ - "--port=8080"
31
+ - "--baseURL=/apps/filebrowser"
32
+ resources:
33
+ cpu: "200m"
34
+ memory: "128Mi"
35
+ ports:
36
+ - name: http
37
+ # Host port 18088 is in the panel-managed 18xxx range (OpenClaw
38
+ # gateway uses 18790). Picked outside common defaults so it
39
+ # never collides with SearXNG (8080), OpenWebUI (3000), or the
40
+ # panel itself (8090). Container internal port stays at 8080
41
+ # — Filebrowser's natural default and what `--port=8080` in
42
+ # args wires it to.
43
+ port: 18088
44
+ container_port: 8080
45
+ visibility: external
46
+ volumes:
47
+ # The user's file tree — POSIX source of truth, shared with the panel
48
+ # drive_* substrate (src/services/files-manager.ts) and the drive-shim
49
+ # MCP path. Both sides operate on the same inodes.
50
+ - source: "~/.jishushell/files"
51
+ target: "/srv"
52
+ # SQLite settings DB + the auto-seeded admin user record.
53
+ - source: "~/.jishushell/apps/${app_id}/database"
54
+ target: "/database"
55
+ # Filebrowser image's init.sh writes /config/settings.json from defaults
56
+ # on first run; bind-mounting an app-owned dir lets us persist any
57
+ # branding tweaks without baking them into the image.
58
+ - source: "~/.jishushell/apps/${app_id}/config"
59
+ target: "/config"
60
+ health:
61
+ http:
62
+ # `--baseURL=/apps/filebrowser` is set in the DB by lifecycle/install
63
+ # below; the SPA serves from that prefix. Root / redirects 302 →
64
+ # /apps/filebrowser/ which answers HEAD with 200.
65
+ path: /apps/filebrowser/
66
+ port: 8080
67
+ interval: "15s"
68
+ timeout: "5s"
69
+ retries: 6
70
+ start_period: "20s"
71
+
72
+ provides:
73
+ # Canonical capability used by the panel's reverse-proxy resolver to mount
74
+ # Filebrowser at /apps/filebrowser/*. No `auth:` block — Filebrowser
75
+ # itself runs in noauth mode and authentication is enforced one layer
76
+ # higher, in src/server.ts middleware, via the panel JWT cookie.
77
+ - capability: "filebrowser-ui"
78
+ task: "filebrowser"
79
+ port: 18088
80
+ path: "/"
81
+ embedded: "proxy"
82
+ protocol: "http"
83
+ visibility: "external"
84
+ description: "Filebrowser Web UI;panel 反代到 /apps/filebrowser/*"
85
+ # Web-* convention (matches web-openwebui / web-searxng) so adapters that
86
+ # iterate "web-*" capabilities pick it up for dashboard tiles.
87
+ - capability: "web-filebrowser"
88
+ task: "filebrowser"
89
+ port: 18088
90
+ path: "/"
91
+ protocol: "http"
92
+ visibility: "external"
93
+ description: "Filebrowser Web UI(web-* convention 别名)"
94
+ # files-* convention: satisfies `requires: files` on consumer specs (OpenClaw
95
+ # etc.). The `files` category is UI-only — binding injects FILES_BROWSER_URL
96
+ # into the agent env so it can surface /apps/filebrowser/ to users in chat.
97
+ # Does NOT gate any API calls; Filebrowser itself runs noauth and is guarded
98
+ # by the panel JWT reverse-proxy layer.
99
+ - capability: "files-filebrowser"
100
+ task: "filebrowser"
101
+ port: 18088
102
+ path: "/apps/filebrowser"
103
+ protocol: "http"
104
+ visibility: "external"
105
+ description: "Filebrowser 文件管理 UI;满足 agent requires:files 绑定"
106
+
107
+ lifecycle:
108
+ install:
109
+ - downloadImage: "filebrowser/filebrowser:latest"
110
+ - mkdir: "~/.jishushell/apps/${app_id}/database"
111
+ - mkdir: "~/.jishushell/apps/${app_id}/config"
112
+ # Seed the SQLite DB with three things Filebrowser needs in noauth mode:
113
+ # 1. `config init --auth.method=noauth` — creates DB with noauth scheme
114
+ # 2. `config set --baseURL=/apps/filebrowser` — bakes the sub-path so the
115
+ # Vue SPA hash router resolves assets under the panel reverse proxy
116
+ # 3. `users add admin <random-pw> --perm.admin` — Filebrowser's noauth
117
+ # mode still requires a user record; POST /api/login mints a token
118
+ # for this user without checking the password. The password is
119
+ # throwaway (placeholder for the ≥12-char rule) and never used at
120
+ # runtime since the panel JWT layer guards the reverse proxy.
121
+ # Idempotent: skipped when DB already exists. Mirrors searxng's heredoc
122
+ # settings.yml generation pattern.
123
+ - run: |
124
+ DB_DIR="$HOME/.jishushell/apps/${app_id}/database"
125
+ CONFIG_DIR="$HOME/.jishushell/apps/${app_id}/config"
126
+ DB_FILE="$DB_DIR/filebrowser.db"
127
+ if [ ! -f "$DB_FILE" ]; then
128
+ docker run --rm \
129
+ --user "$(id -u):$(id -g)" \
130
+ -v "$DB_DIR:/database" \
131
+ -v "$CONFIG_DIR:/config" \
132
+ filebrowser/filebrowser:latest \
133
+ config init \
134
+ --database=/database/filebrowser.db \
135
+ --auth.method=noauth >/dev/null
136
+ docker run --rm \
137
+ --user "$(id -u):$(id -g)" \
138
+ -v "$DB_DIR:/database" \
139
+ -v "$CONFIG_DIR:/config" \
140
+ filebrowser/filebrowser:latest \
141
+ config set \
142
+ --database=/database/filebrowser.db \
143
+ --auth.method=noauth \
144
+ --baseURL=/apps/filebrowser \
145
+ --address=0.0.0.0 \
146
+ --port=8080 >/dev/null
147
+ # Password is throwaway: noauth mode never checks it, but Filebrowser
148
+ # enforces a 12-char minimum at create time. Generate a 32-char random
149
+ # to satisfy the rule without leaving a guessable trail.
150
+ PLACEHOLDER_PW="$(head -c 48 /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | head -c 32)"
151
+ docker run --rm \
152
+ --user "$(id -u):$(id -g)" \
153
+ -v "$DB_DIR:/database" \
154
+ -v "$CONFIG_DIR:/config" \
155
+ filebrowser/filebrowser:latest \
156
+ users add admin "$PLACEHOLDER_PW" \
157
+ --database=/database/filebrowser.db \
158
+ --perm.admin >/dev/null
159
+ fi
160
+ pre_start:
161
+ - mkdir: "~/.jishushell/apps/${app_id}/database"
162
+ - mkdir: "~/.jishushell/apps/${app_id}/config"
163
+ uninstall:
164
+ - deleteImage: "filebrowser/filebrowser:latest"
@@ -106,6 +106,28 @@ lifecycle:
106
106
  owner_marker="$HOME/.jishushell/.ollama-installed-by-jishushell-${app_id}";
107
107
  legacy_owner_marker="$HOME/.jishushell/apps/${app_id}/.ollama-installed-by-jishushell";
108
108
  system_install_marker="$HOME/.jishushell/.ollama-system-install-${app_id}";
109
+ if [ "$(uname -s)" = "Linux" ] && ! command -v zstd >/dev/null 2>&1; then
110
+ echo "未检测到 zstd;Ollama v0.24+ 安装包仅提供 .tar.zst,需先安装 zstd..." >&2;
111
+ pm_rc=1;
112
+ if command -v apt-get >/dev/null 2>&1; then
113
+ DEBIAN_FRONTEND=noninteractive apt-get update -qq || true;
114
+ DEBIAN_FRONTEND=noninteractive apt-get install -y zstd && pm_rc=0;
115
+ elif command -v dnf >/dev/null 2>&1; then
116
+ dnf install -y zstd && pm_rc=0;
117
+ elif command -v yum >/dev/null 2>&1; then
118
+ yum install -y zstd && pm_rc=0;
119
+ elif command -v apk >/dev/null 2>&1; then
120
+ apk add --no-cache zstd && pm_rc=0;
121
+ elif command -v pacman >/dev/null 2>&1; then
122
+ pacman -Sy --noconfirm zstd && pm_rc=0;
123
+ else
124
+ echo "ERROR: 未找到受支持的包管理器(apt/dnf/yum/apk/pacman)来安装 zstd" >&2;
125
+ fi;
126
+ if [ "$pm_rc" != "0" ] || ! command -v zstd >/dev/null 2>&1; then
127
+ echo "ERROR: 自动安装 zstd 失败;请手动安装后重试。" >&2;
128
+ exit 1;
129
+ fi;
130
+ fi;
109
131
  for BINDIR in /usr/local/bin /usr/bin /bin; do
110
132
  echo "$PATH" | grep -q "$BINDIR" && break || continue;
111
133
  done;
@@ -117,6 +139,10 @@ lifecycle:
117
139
  ( rm -rf "$old_dir" >/dev/null 2>&1 || true ) &
118
140
  fi;
119
141
  curl -fsSL https://ollama.com/install.sh | sh;
142
+ if ! command -v ollama >/dev/null 2>&1; then
143
+ echo "ERROR: 安装脚本结束后仍未找到 ollama 命令;请检查网络、磁盘空间或 zstd 是否可用。" >&2;
144
+ exit 1;
145
+ fi;
120
146
  rm -f "$install_marker";
121
147
  rm -f "$legacy_owner_marker";
122
148
  rm -f "$system_install_marker";
@@ -138,6 +164,18 @@ lifecycle:
138
164
  : > "$owner_marker";
139
165
  fi
140
166
  ifFileExists: "~/.jishushell/apps/${app_id}/.ollama-install-needed"
167
+ - run: >-
168
+ if [ -e /usr/local/bin/ollama ]; then
169
+ exit 0;
170
+ fi;
171
+ if ! command -v ollama >/dev/null 2>&1; then
172
+ exit 0;
173
+ fi;
174
+ real_ollama="$(command -v ollama)";
175
+ echo "检测到 ollama 位于 $real_ollama 但 /usr/local/bin/ollama 不存在;建立兼容符号链接(用于 Apple Silicon brew 等场景)..." >&2;
176
+ mkdir -p /usr/local/bin;
177
+ ln -sf "$real_ollama" /usr/local/bin/ollama
178
+ sudo: true
141
179
  - run: >-
142
180
  service_active_marker="$HOME/.jishushell/apps/${app_id}/.ollama-service-active";
143
181
  rm -f "$service_active_marker";
@@ -198,4 +236,10 @@ lifecycle:
198
236
  rm -f "$owner_marker" "$legacy_owner_marker" "$system_install_marker" "$uninstall_marker"
199
237
  sudo: true
200
238
  ifFileExists: "~/.jishushell/.ollama-uninstall-needed-${app_id}"
239
+ - run: >-
240
+ if [ -L /usr/local/bin/ollama ]; then
241
+ echo "清理由 JishuShell 建立的兼容性符号链接 /usr/local/bin/ollama" >&2;
242
+ rm -f /usr/local/bin/ollama;
243
+ fi
244
+ sudo: true
201
245
 
@@ -102,6 +102,28 @@ lifecycle:
102
102
  owner_marker="$HOME/.jishushell/.ollama-installed-by-jishushell-${app_id}";
103
103
  legacy_owner_marker="$HOME/.jishushell/apps/${app_id}/.ollama-installed-by-jishushell";
104
104
  system_install_marker="$HOME/.jishushell/.ollama-system-install-${app_id}";
105
+ if [ "$(uname -s)" = "Linux" ] && ! command -v zstd >/dev/null 2>&1; then
106
+ echo "未检测到 zstd;Ollama v0.24+ 安装包仅提供 .tar.zst,需先安装 zstd..." >&2;
107
+ pm_rc=1;
108
+ if command -v apt-get >/dev/null 2>&1; then
109
+ DEBIAN_FRONTEND=noninteractive apt-get update -qq || true;
110
+ DEBIAN_FRONTEND=noninteractive apt-get install -y zstd && pm_rc=0;
111
+ elif command -v dnf >/dev/null 2>&1; then
112
+ dnf install -y zstd && pm_rc=0;
113
+ elif command -v yum >/dev/null 2>&1; then
114
+ yum install -y zstd && pm_rc=0;
115
+ elif command -v apk >/dev/null 2>&1; then
116
+ apk add --no-cache zstd && pm_rc=0;
117
+ elif command -v pacman >/dev/null 2>&1; then
118
+ pacman -Sy --noconfirm zstd && pm_rc=0;
119
+ else
120
+ echo "ERROR: 未找到受支持的包管理器(apt/dnf/yum/apk/pacman)来安装 zstd" >&2;
121
+ fi;
122
+ if [ "$pm_rc" != "0" ] || ! command -v zstd >/dev/null 2>&1; then
123
+ echo "ERROR: 自动安装 zstd 失败;请手动安装后重试。" >&2;
124
+ exit 1;
125
+ fi;
126
+ fi;
105
127
  for BINDIR in /usr/local/bin /usr/bin /bin; do
106
128
  echo "$PATH" | grep -q "$BINDIR" && break || continue;
107
129
  done;
@@ -113,6 +135,10 @@ lifecycle:
113
135
  ( rm -rf "$old_dir" >/dev/null 2>&1 || true ) &
114
136
  fi;
115
137
  curl -fsSL https://ollama.com/install.sh | sh;
138
+ if ! command -v ollama >/dev/null 2>&1; then
139
+ echo "ERROR: 安装脚本结束后仍未找到 ollama 命令;请检查网络、磁盘空间或 zstd 是否可用。" >&2;
140
+ exit 1;
141
+ fi;
116
142
  rm -f "$install_marker";
117
143
  rm -f "$legacy_owner_marker";
118
144
  rm -f "$system_install_marker";
@@ -134,6 +160,18 @@ lifecycle:
134
160
  : > "$owner_marker";
135
161
  fi
136
162
  ifFileExists: "~/.jishushell/apps/${app_id}/.ollama-install-needed"
163
+ - run: >-
164
+ if [ -e /usr/local/bin/ollama ]; then
165
+ exit 0;
166
+ fi;
167
+ if ! command -v ollama >/dev/null 2>&1; then
168
+ exit 0;
169
+ fi;
170
+ real_ollama="$(command -v ollama)";
171
+ echo "检测到 ollama 位于 $real_ollama 但 /usr/local/bin/ollama 不存在;建立兼容符号链接(用于 Apple Silicon brew 等场景)..." >&2;
172
+ mkdir -p /usr/local/bin;
173
+ ln -sf "$real_ollama" /usr/local/bin/ollama
174
+ sudo: true
137
175
  - run: >-
138
176
  service_active_marker="$HOME/.jishushell/apps/${app_id}/.ollama-service-active";
139
177
  rm -f "$service_active_marker";
@@ -193,4 +231,10 @@ lifecycle:
193
231
  rm -rf /Applications/Ollama.app "$HOME/Applications/Ollama.app" 2>/dev/null || true;
194
232
  rm -f "$owner_marker" "$legacy_owner_marker" "$system_install_marker" "$uninstall_marker"
195
233
  sudo: true
196
- ifFileExists: "~/.jishushell/.ollama-uninstall-needed-${app_id}"
234
+ ifFileExists: "~/.jishushell/.ollama-uninstall-needed-${app_id}"
235
+ - run: >-
236
+ if [ -L /usr/local/bin/ollama ]; then
237
+ echo "清理由 JishuShell 建立的兼容性符号链接 /usr/local/bin/ollama" >&2;
238
+ rm -f /usr/local/bin/ollama;
239
+ fi
240
+ sudo: true