jishushell 0.4.17 → 0.4.24-beta.2

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 (241) hide show
  1. package/Dockerfile.hermes-slim +193 -0
  2. package/apps/hermes-container.yaml +35 -0
  3. package/apps/ollama-binary.yaml +164 -0
  4. package/apps/ollama-cpu-container.yaml +37 -0
  5. package/apps/ollama-with-hollama-binary.yaml +159 -0
  6. package/apps/openclaw-binary.yaml +69 -0
  7. package/apps/openclaw-container.yaml +37 -0
  8. package/apps/openclaw-with-ollama-container.yaml +42 -0
  9. package/apps/openclaw-with-searxng-container.yaml +136 -0
  10. package/apps/openwebui-container.yaml +53 -0
  11. package/apps/playwright-container.yaml +120 -0
  12. package/apps/searxng-container.yaml +115 -0
  13. package/dist/auth.d.ts +1 -0
  14. package/dist/auth.js +15 -14
  15. package/dist/auth.js.map +1 -1
  16. package/dist/cli/app.d.ts +1 -0
  17. package/dist/cli/app.js +770 -52
  18. package/dist/cli/app.js.map +1 -1
  19. package/dist/cli/backup.d.ts +3 -0
  20. package/dist/cli/backup.js +434 -0
  21. package/dist/cli/backup.js.map +1 -0
  22. package/dist/cli/doctor.d.ts +1 -0
  23. package/dist/cli/doctor.js +61 -35
  24. package/dist/cli/doctor.js.map +1 -1
  25. package/dist/cli/job.d.ts +1 -0
  26. package/dist/cli/job.js +37 -99
  27. package/dist/cli/job.js.map +1 -1
  28. package/dist/cli/llm.d.ts +1 -0
  29. package/dist/cli/llm.js +20 -14
  30. package/dist/cli/llm.js.map +1 -1
  31. package/dist/cli/managed-list.d.ts +30 -0
  32. package/dist/cli/managed-list.js +129 -0
  33. package/dist/cli/managed-list.js.map +1 -0
  34. package/dist/cli/panel.d.ts +4 -3
  35. package/dist/cli/panel.js +94 -24
  36. package/dist/cli/panel.js.map +1 -1
  37. package/dist/cli/version.d.ts +1 -0
  38. package/dist/cli/version.js +12 -0
  39. package/dist/cli/version.js.map +1 -0
  40. package/dist/cli.js +47 -516
  41. package/dist/cli.js.map +1 -1
  42. package/dist/config.d.ts +68 -0
  43. package/dist/config.js +266 -12
  44. package/dist/config.js.map +1 -1
  45. package/dist/control.d.ts +10 -6
  46. package/dist/control.js +87 -6
  47. package/dist/control.js.map +1 -1
  48. package/dist/install.d.ts +16 -0
  49. package/dist/install.js +75 -26
  50. package/dist/install.js.map +1 -1
  51. package/dist/routes/agent-apps.d.ts +15 -0
  52. package/dist/routes/agent-apps.js +78 -0
  53. package/dist/routes/agent-apps.js.map +1 -0
  54. package/dist/routes/apps.js +186 -7
  55. package/dist/routes/apps.js.map +1 -1
  56. package/dist/routes/backup.js +3 -3
  57. package/dist/routes/backup.js.map +1 -1
  58. package/dist/routes/instances.d.ts +6 -0
  59. package/dist/routes/instances.js +862 -879
  60. package/dist/routes/instances.js.map +1 -1
  61. package/dist/routes/llm.js +9 -8
  62. package/dist/routes/llm.js.map +1 -1
  63. package/dist/routes/runtime.d.ts +15 -0
  64. package/dist/routes/runtime.js +69 -0
  65. package/dist/routes/runtime.js.map +1 -0
  66. package/dist/routes/setup.js +103 -8
  67. package/dist/routes/setup.js.map +1 -1
  68. package/dist/routes/system.js +25 -3
  69. package/dist/routes/system.js.map +1 -1
  70. package/dist/server.js +71 -7
  71. package/dist/server.js.map +1 -1
  72. package/dist/services/agent-apps/catalog.d.ts +30 -0
  73. package/dist/services/agent-apps/catalog.js +60 -0
  74. package/dist/services/agent-apps/catalog.js.map +1 -0
  75. package/dist/services/agent-apps/index.d.ts +36 -0
  76. package/dist/services/agent-apps/index.js +171 -0
  77. package/dist/services/agent-apps/index.js.map +1 -0
  78. package/dist/services/agent-apps/installers/adapter-probes.d.ts +49 -0
  79. package/dist/services/agent-apps/installers/adapter-probes.js +223 -0
  80. package/dist/services/agent-apps/installers/adapter-probes.js.map +1 -0
  81. package/dist/services/agent-apps/installers/adapter.d.ts +30 -0
  82. package/dist/services/agent-apps/installers/adapter.js +171 -0
  83. package/dist/services/agent-apps/installers/adapter.js.map +1 -0
  84. package/dist/services/agent-apps/installers/registry-probe.d.ts +38 -0
  85. package/dist/services/agent-apps/installers/registry-probe.js +183 -0
  86. package/dist/services/agent-apps/installers/registry-probe.js.map +1 -0
  87. package/dist/services/agent-apps/installers/shell-script.d.ts +47 -0
  88. package/dist/services/agent-apps/installers/shell-script.js +471 -0
  89. package/dist/services/agent-apps/installers/shell-script.js.map +1 -0
  90. package/dist/services/agent-apps/types.d.ts +125 -0
  91. package/dist/services/agent-apps/types.js +17 -0
  92. package/dist/services/agent-apps/types.js.map +1 -0
  93. package/dist/services/{app-compiler.d.ts → app/app-compiler.d.ts} +3 -3
  94. package/dist/services/{app-compiler.js → app/app-compiler.js} +10 -7
  95. package/dist/services/app/app-compiler.js.map +1 -0
  96. package/dist/services/app/app-manager.d.ts +142 -0
  97. package/dist/services/app/app-manager.js +2148 -0
  98. package/dist/services/app/app-manager.js.map +1 -0
  99. package/dist/services/app/custom-manager.d.ts +27 -0
  100. package/dist/services/app/custom-manager.js +285 -0
  101. package/dist/services/app/custom-manager.js.map +1 -0
  102. package/dist/services/app/hermes-agent-manager.d.ts +20 -0
  103. package/dist/services/app/hermes-agent-manager.js +289 -0
  104. package/dist/services/app/hermes-agent-manager.js.map +1 -0
  105. package/dist/services/app/id-normalizer.d.ts +27 -0
  106. package/dist/services/app/id-normalizer.js +77 -0
  107. package/dist/services/app/id-normalizer.js.map +1 -0
  108. package/dist/services/app/ollama-manager.d.ts +18 -0
  109. package/dist/services/app/ollama-manager.js +207 -0
  110. package/dist/services/app/ollama-manager.js.map +1 -0
  111. package/dist/services/app/openclaw-manager.d.ts +63 -0
  112. package/dist/services/app/openclaw-manager.js +1178 -0
  113. package/dist/services/app/openclaw-manager.js.map +1 -0
  114. package/dist/services/app/paths.d.ts +47 -0
  115. package/dist/services/app/paths.js +68 -0
  116. package/dist/services/app/paths.js.map +1 -0
  117. package/dist/services/app/registry.d.ts +17 -0
  118. package/dist/services/app/registry.js +31 -0
  119. package/dist/services/app/registry.js.map +1 -0
  120. package/dist/services/app/remote-spec.d.ts +14 -0
  121. package/dist/services/app/remote-spec.js +58 -0
  122. package/dist/services/app/remote-spec.js.map +1 -0
  123. package/dist/services/app/terminal-session-manager.d.ts +27 -0
  124. package/dist/services/app/terminal-session-manager.js +157 -0
  125. package/dist/services/app/terminal-session-manager.js.map +1 -0
  126. package/dist/services/app/types.d.ts +72 -0
  127. package/dist/services/app/types.js +16 -0
  128. package/dist/services/app/types.js.map +1 -0
  129. package/dist/services/backup-manager.js +60 -22
  130. package/dist/services/backup-manager.js.map +1 -1
  131. package/dist/services/instance-manager.d.ts +82 -39
  132. package/dist/services/instance-manager.js +575 -1142
  133. package/dist/services/instance-manager.js.map +1 -1
  134. package/dist/services/llm-proxy/circuit-breaker.js +10 -2
  135. package/dist/services/llm-proxy/circuit-breaker.js.map +1 -1
  136. package/dist/services/llm-proxy/index.d.ts +14 -1
  137. package/dist/services/llm-proxy/index.js +51 -6
  138. package/dist/services/llm-proxy/index.js.map +1 -1
  139. package/dist/services/nomad-manager.d.ts +260 -3
  140. package/dist/services/nomad-manager.js +2866 -449
  141. package/dist/services/nomad-manager.js.map +1 -1
  142. package/dist/services/panel-manager.d.ts +10 -0
  143. package/dist/services/panel-manager.js +97 -0
  144. package/dist/services/panel-manager.js.map +1 -1
  145. package/dist/services/plugin-installer.js +28 -2
  146. package/dist/services/plugin-installer.js.map +1 -1
  147. package/dist/services/process-manager.js +22 -0
  148. package/dist/services/process-manager.js.map +1 -1
  149. package/dist/services/runtime/adapters/custom.d.ts +20 -0
  150. package/dist/services/runtime/adapters/custom.js +90 -0
  151. package/dist/services/runtime/adapters/custom.js.map +1 -0
  152. package/dist/services/runtime/adapters/hermes.d.ts +174 -0
  153. package/dist/services/runtime/adapters/hermes.js +1316 -0
  154. package/dist/services/runtime/adapters/hermes.js.map +1 -0
  155. package/dist/services/runtime/adapters/openclaw-routes.d.ts +17 -0
  156. package/dist/services/runtime/adapters/openclaw-routes.js +946 -0
  157. package/dist/services/runtime/adapters/openclaw-routes.js.map +1 -0
  158. package/dist/services/runtime/adapters/openclaw.d.ts +188 -0
  159. package/dist/services/runtime/adapters/openclaw.js +2195 -0
  160. package/dist/services/runtime/adapters/openclaw.js.map +1 -0
  161. package/dist/services/runtime/errors.d.ts +28 -0
  162. package/dist/services/runtime/errors.js +31 -0
  163. package/dist/services/runtime/errors.js.map +1 -0
  164. package/dist/services/runtime/index.d.ts +34 -0
  165. package/dist/services/runtime/index.js +51 -0
  166. package/dist/services/runtime/index.js.map +1 -0
  167. package/dist/services/runtime/instance.d.ts +24 -0
  168. package/dist/services/runtime/instance.js +143 -0
  169. package/dist/services/runtime/instance.js.map +1 -0
  170. package/dist/services/runtime/migrations.d.ts +15 -0
  171. package/dist/services/runtime/migrations.js +25 -0
  172. package/dist/services/runtime/migrations.js.map +1 -0
  173. package/dist/services/runtime/registry.d.ts +13 -0
  174. package/dist/services/runtime/registry.js +32 -0
  175. package/dist/services/runtime/registry.js.map +1 -0
  176. package/dist/services/runtime/types.d.ts +545 -0
  177. package/dist/services/runtime/types.js +14 -0
  178. package/dist/services/runtime/types.js.map +1 -0
  179. package/dist/services/setup-manager.d.ts +70 -29
  180. package/dist/services/setup-manager.js +278 -597
  181. package/dist/services/setup-manager.js.map +1 -1
  182. package/dist/services/task-registry.d.ts +44 -0
  183. package/dist/services/task-registry.js +74 -0
  184. package/dist/services/task-registry.js.map +1 -0
  185. package/dist/services/telemetry/heartbeat.d.ts +6 -6
  186. package/dist/services/telemetry/heartbeat.js +29 -30
  187. package/dist/services/telemetry/heartbeat.js.map +1 -1
  188. package/dist/types.d.ts +164 -2
  189. package/dist/utils/docker-host.d.ts +15 -0
  190. package/dist/utils/docker-host.js +64 -0
  191. package/dist/utils/docker-host.js.map +1 -0
  192. package/install/jishu-install.sh +25 -2
  193. package/package.json +14 -4
  194. package/public/assets/Dashboard-rh9qpYRR.js +1 -0
  195. package/public/assets/HermesChatPanel-D6JI6lLY.js +1 -0
  196. package/public/assets/HermesConfigForm-DcbSemaj.js +4 -0
  197. package/public/assets/InitPassword-CFTKsED4.js +1 -0
  198. package/public/assets/InstanceDetail-BhNIKA6Z.js +91 -0
  199. package/public/assets/{Login-D1Bt-Lyk.js → Login-KB9qrtM0.js} +1 -1
  200. package/public/assets/NewInstance-CxkO8Hlq.js +1 -0
  201. package/public/assets/Settings-BVWJvOkU.js +1 -0
  202. package/public/assets/Setup-X-lzuaUT.js +1 -0
  203. package/public/assets/WeixinLoginPanel-gca0QTic.js +9 -0
  204. package/public/assets/index-C8B0cFJM.js +19 -0
  205. package/public/assets/index-CPhVFEsx.css +1 -0
  206. package/public/assets/input-paste-CrNVAyOy.js +1 -0
  207. package/public/assets/registry-fVUSujib.js +2 -0
  208. package/public/assets/{usePolling-CK0DfI4h.js → usePolling-Do5Erqm_.js} +1 -1
  209. package/public/assets/vendor-i18n-ucpM0OR0.js +9 -0
  210. package/public/assets/{vendor-react-B1-3Yrt-.js → vendor-react-Bk1hRGiY.js} +1 -1
  211. package/public/favicon.png +0 -0
  212. package/public/index.html +9 -4
  213. package/public/logos/hermes.png +0 -0
  214. package/public/logos/ollama.png +0 -0
  215. package/public/logos/openclaw.svg +60 -0
  216. package/scripts/build-hermes-image.sh +21 -0
  217. package/scripts/build-local.sh +54 -0
  218. package/scripts/check-adapter-isolation.ts +293 -0
  219. package/scripts/fixtures/instances/hermes-sample/instance.json +37 -0
  220. package/scripts/fixtures/instances/legacy-openclaw-sample/instance.json +7 -0
  221. package/scripts/smoke/hermes-bootstrap.sh +195 -0
  222. package/templates/hermes-entrypoint.sh +154 -0
  223. package/dist/cli/openclaw.d.ts +0 -12
  224. package/dist/cli/openclaw.js +0 -156
  225. package/dist/cli/openclaw.js.map +0 -1
  226. package/dist/services/app-compiler.js.map +0 -1
  227. package/dist/services/app-manager.d.ts +0 -17
  228. package/dist/services/app-manager.js +0 -168
  229. package/dist/services/app-manager.js.map +0 -1
  230. package/dist/services/job-manager.d.ts +0 -22
  231. package/dist/services/job-manager.js +0 -102
  232. package/dist/services/job-manager.js.map +0 -1
  233. package/public/assets/Dashboard-CQsp1Mr9.js +0 -1
  234. package/public/assets/InitPassword-BEC8SE4A.js +0 -1
  235. package/public/assets/InstanceDetail-B5wTgNEg.js +0 -17
  236. package/public/assets/NewInstance-GQzm3K9D.js +0 -1
  237. package/public/assets/Settings-ByjGlqhP.js +0 -1
  238. package/public/assets/Setup-cMF21Y-8.js +0 -1
  239. package/public/assets/index-B6qQP4mH.css +0 -1
  240. package/public/assets/index-BuTQtuNy.js +0 -16
  241. package/public/assets/vendor-i18n-CfW0RvgE.js +0 -9
@@ -0,0 +1,289 @@
1
+ /**
2
+ * Hermes Agent App-type Manager
3
+ *
4
+ * Encapsulates all logic specific to the "hermes-agent" app type:
5
+ * - Instance directory layout (hermes-home/data)
6
+ * - Instance creation
7
+ * - Pre-start validation
8
+ *
9
+ * - Nomad task builder (buildNomadTaskHermes) and Nomad secrets writer
10
+ */
11
+ import { execFileSync } from "child_process";
12
+ import { randomBytes } from "crypto";
13
+ import { chownSync, existsSync, statSync } from "fs";
14
+ import { join } from "path";
15
+ import { INSTANCES_DIR } from "../../config.js";
16
+ import { safeWriteJson } from "../../utils/safe-json.js";
17
+ import { ensureDirContainer } from "../../utils/fs.js";
18
+ import { writeSecretFile } from "../../utils/fs.js";
19
+ import { compileTaskRuntime } from "./app-compiler.js";
20
+ import { getInstance, getInstanceDir, instanceMetaPath, resolveServiceUser, defaultGatewayPort, releasePort, parseEnvFile, getInstanceRuntime, getRuntimeEnv, } from "../instance-manager.js";
21
+ // ── Constants ─────────────────────────────────────────────────────────────────
22
+ const HERMES_HOME_DIRNAME = "hermes-home";
23
+ const HERMES_ENV_FILENAME = "hermes.env";
24
+ const DEFAULT_HERMES_IMAGE = "jishushell-hermes:latest";
25
+ // ── Path helpers ──────────────────────────────────────────────────────────────
26
+ export function defaultHermesHome(instanceId) {
27
+ return join(getInstanceDir(instanceId), HERMES_HOME_DIRNAME);
28
+ }
29
+ export function defaultHermesEnvFile(instanceId) {
30
+ return join(getInstanceDir(instanceId), HERMES_ENV_FILENAME);
31
+ }
32
+ export function getHermesHome(instanceId) {
33
+ const meta = getInstance(instanceId);
34
+ return meta?.hermes_home || defaultHermesHome(instanceId);
35
+ }
36
+ export function getHermesEnvFile(instanceId) {
37
+ const meta = getInstance(instanceId);
38
+ return meta?.hermes_env_file || defaultHermesEnvFile(instanceId);
39
+ }
40
+ // ── Pre-start validation ──────────────────────────────────────────────────────
41
+ export async function prepareStartHermes(instanceId) {
42
+ const { DOCKER_IMAGE_RE, MAX_DOCKER_IMAGE_NAME_LEN } = await import("../nomad-manager.js");
43
+ const { getNomadDriver } = await import("../../config.js");
44
+ const hermesHome = getHermesHome(instanceId);
45
+ ensureDirContainer(join(hermesHome, "data"));
46
+ if (getNomadDriver() === "docker") {
47
+ const inst = getInstance(instanceId);
48
+ const image = inst?.runtime?.image || DEFAULT_HERMES_IMAGE;
49
+ if (!DOCKER_IMAGE_RE.test(image) || image.length > MAX_DOCKER_IMAGE_NAME_LEN) {
50
+ return { ok: false, error: `Invalid Docker image name: "${image}"` };
51
+ }
52
+ try {
53
+ execFileSync("docker", ["image", "inspect", image], { timeout: 10000, stdio: "ignore" });
54
+ }
55
+ catch {
56
+ console.log(`[hermes] Docker image ${image} not found, starting background pull...`);
57
+ try {
58
+ const { exec: execAsync } = await import("child_process");
59
+ const { promisify } = await import("util");
60
+ promisify(execAsync)(`docker pull ${image}`, { timeout: 0 }).catch((e) => {
61
+ console.warn(`[hermes] Background pull of ${image} failed: ${e.message}`);
62
+ });
63
+ return {
64
+ ok: false,
65
+ error: `Docker image ${image} not found locally. Pull started in background — retry in a few minutes.`,
66
+ };
67
+ }
68
+ catch (e) {
69
+ return { ok: false, error: `Docker image ${image} not available: ${e.message}` };
70
+ }
71
+ }
72
+ }
73
+ return { ok: true };
74
+ }
75
+ // ── Instance creation ─────────────────────────────────────────────────────────
76
+ export async function createHermesInstance(instanceId, name, description, options) {
77
+ const { appSpec } = options;
78
+ const d = getInstanceDir(instanceId);
79
+ if (existsSync(d))
80
+ throw new Error(`Instance '${instanceId}' already exists`);
81
+ const hermesHome = defaultHermesHome(instanceId);
82
+ const hermesEnvFile = defaultHermesEnvFile(instanceId);
83
+ ensureDirContainer(d);
84
+ try {
85
+ const parentGid = statSync(INSTANCES_DIR).gid;
86
+ chownSync(d, -1, parentGid);
87
+ }
88
+ catch { /* non-root without CAP_CHOWN */ }
89
+ ensureDirContainer(hermesHome);
90
+ ensureDirContainer(join(hermesHome, "data"));
91
+ const hermesApiKey = randomBytes(32).toString("hex");
92
+ writeSecretFile(hermesEnvFile, `HERMES_API_KEY=${hermesApiKey}\n`);
93
+ const serviceTask = appSpec?.tasks.find((t) => t.role === "service");
94
+ const compiled = serviceTask ? compileTaskRuntime(serviceTask, instanceId) : null;
95
+ const image = compiled?.image || serviceTask?.image || DEFAULT_HERMES_IMAGE;
96
+ const resources = compiled?.resources || { CPU: 1500, MemoryMB: 3072 };
97
+ const webuiHostPort = await defaultGatewayPort(instanceId);
98
+ let allocatedPort = webuiHostPort;
99
+ try {
100
+ const taskEnv = { ...(serviceTask?.env || {}) };
101
+ delete taskEnv.HERMES_API_KEY;
102
+ // Wire Open WebUI to JishuShell LLM proxy so the Hermes instance can
103
+ // use the panel's configured LLM provider out of the box.
104
+ const { getPanelConfig } = await import("../../config.js");
105
+ const panelPort = getPanelConfig().port || 8090;
106
+ const proxyBaseUrl = `http://host.docker.internal:${panelPort}/jsproxy/v1`;
107
+ const runtime = {
108
+ command: null,
109
+ args: [],
110
+ cwd: "/data",
111
+ user: "root",
112
+ env_files: [hermesEnvFile],
113
+ env: {
114
+ OPENCLAW_GATEWAY_PORT: String(webuiHostPort),
115
+ HERMES_WEBUI_PORT: String(webuiHostPort),
116
+ HERMES_HOME: "/data/.hermes",
117
+ DATA_DIR: "/data/open-webui",
118
+ PORT: "8080",
119
+ OPENAI_API_BASE_URL: proxyBaseUrl,
120
+ OPENAI_API_KEY: hermesApiKey,
121
+ ...taskEnv,
122
+ },
123
+ image,
124
+ resources,
125
+ };
126
+ const meta = {
127
+ id: instanceId,
128
+ name,
129
+ description,
130
+ hermes_home: hermesHome,
131
+ hermes_env_file: hermesEnvFile,
132
+ app_type: "hermes-agent",
133
+ runtime,
134
+ created_at: new Date().toISOString(),
135
+ ...(appSpec ? { app_id: appSpec.app_id ?? appSpec.id } : {}),
136
+ };
137
+ safeWriteJson(instanceMetaPath(instanceId), meta);
138
+ // Generate LLM proxy token and write it to hermes.env so that
139
+ // Open WebUI inside the container can authenticate to the proxy.
140
+ try {
141
+ const { ensureInstanceProxyToken } = await import("../llm-proxy/index.js");
142
+ const proxyToken = ensureInstanceProxyToken(instanceId);
143
+ // Update runtime env to pass proxy token as OPENAI_API_KEY
144
+ runtime.env.OPENAI_API_KEY = proxyToken;
145
+ safeWriteJson(instanceMetaPath(instanceId), meta);
146
+ }
147
+ catch (e) {
148
+ console.warn(`[hermes] Proxy bootstrap for ${instanceId} deferred: ${e.message}`);
149
+ }
150
+ const svcUser = resolveServiceUser();
151
+ if (svcUser) {
152
+ try {
153
+ execFileSync("chown", ["-R", `${svcUser.uid}:${svcUser.gid}`, d], { timeout: 10_000 });
154
+ execFileSync("chown", ["-R", `${svcUser.uid}:${svcUser.gid}`, hermesHome], { timeout: 10_000 });
155
+ }
156
+ catch (e) {
157
+ console.warn(`[hermes] chown for ${instanceId} failed:`, e.message);
158
+ }
159
+ }
160
+ return meta;
161
+ }
162
+ finally {
163
+ if (allocatedPort != null)
164
+ releasePort(allocatedPort);
165
+ }
166
+ }
167
+ import { DEFAULT_RESOURCES, DEFAULT_ENV, MAX_CPU_MHZ, MAX_MEMORY_MB, VALID_USER_RE, normalizeDockerResources, nomadGet, nomadPut, jobId, } from "../nomad-manager.js";
168
+ function buildHermesRuntime(instanceId) {
169
+ const runtime = getInstanceRuntime(instanceId);
170
+ const hermesHome = getHermesHome(instanceId);
171
+ if (runtime.user && !VALID_USER_RE.test(runtime.user)) {
172
+ throw new Error(`Invalid runtime user: ${runtime.user}`);
173
+ }
174
+ const env = { ...DEFAULT_ENV };
175
+ Object.assign(env, getRuntimeEnv(instanceId));
176
+ delete env.HERMES_API_KEY;
177
+ env.HERMES_HOME = hermesHome;
178
+ const resources = { ...DEFAULT_RESOURCES, CPU: 1500, MemoryMB: 3072 };
179
+ for (const [key, value] of Object.entries(runtime.resources || {})) {
180
+ if (value != null)
181
+ resources[key] = Number(value);
182
+ }
183
+ resources.CPU = Math.max(1, Math.min(resources.CPU, MAX_CPU_MHZ));
184
+ resources.MemoryMB = Math.max(1, Math.min(resources.MemoryMB, MAX_MEMORY_MB));
185
+ return {
186
+ command: runtime.command || null,
187
+ args: Array.isArray(runtime.args) ? runtime.args.map(String) : [],
188
+ user: runtime.user || "root",
189
+ cwd: runtime.cwd || "/data",
190
+ env,
191
+ resources,
192
+ image: runtime.image ?? null,
193
+ };
194
+ }
195
+ export function buildNomadTaskHermes(instanceId, runtime, safeJobId) {
196
+ const hermesHome = getHermesHome(instanceId);
197
+ const image = runtime.image || DEFAULT_HERMES_IMAGE;
198
+ const webuiHostPort = parseInt(runtime.env?.HERMES_WEBUI_PORT || runtime.env?.OPENCLAW_GATEWAY_PORT || "8080", 10);
199
+ const containerEnv = { ...runtime.env };
200
+ containerEnv.PORT = "8080";
201
+ delete containerEnv.HERMES_API_KEY;
202
+ const normalizedResources = normalizeDockerResources(instanceId, {
203
+ ...runtime,
204
+ resources: runtime.resources || { CPU: 1500, MemoryMB: 3072 },
205
+ });
206
+ return {
207
+ Name: "gateway",
208
+ Driver: "docker",
209
+ User: "0:0",
210
+ Config: {
211
+ image,
212
+ force_pull: false,
213
+ volumes: [`${hermesHome}/data:/data:rw`],
214
+ extra_hosts: ["host.docker.internal:host-gateway"],
215
+ pids_limit: 1024,
216
+ mounts: [
217
+ { type: "tmpfs", target: "/tmp", tmpfs_options: { size: 536870912 } },
218
+ { type: "tmpfs", target: "/var/run", tmpfs_options: { size: 52428800 } },
219
+ ],
220
+ },
221
+ Env: containerEnv,
222
+ Resources: {
223
+ ...normalizedResources,
224
+ Networks: [{
225
+ ReservedPorts: [{ Label: "gateway", Value: webuiHostPort, To: 8080 }],
226
+ }],
227
+ },
228
+ LogConfig: { MaxFiles: 3, MaxFileSizeMB: 20 },
229
+ Templates: [{
230
+ DestPath: "secrets/hermes.env",
231
+ Envvars: true,
232
+ EmbeddedTmpl: [
233
+ `{{ if nomadVarExists "nomad/jobs/${safeJobId}/hermes/gateway" }}`,
234
+ `HERMES_API_KEY={{ with nomadVar "nomad/jobs/${safeJobId}/hermes/gateway" }}{{ .HERMES_API_KEY }}{{ end }}`,
235
+ `{{ end }}`,
236
+ ].join("\n"),
237
+ ChangeMode: "restart",
238
+ }],
239
+ };
240
+ }
241
+ export async function writeNomadSecretsHermes(instanceId) {
242
+ const jid = jobId(instanceId);
243
+ const ns = "default";
244
+ const varPath = `nomad/jobs/${jid}/hermes/gateway`;
245
+ const encodedPath = encodeURIComponent(varPath);
246
+ const hermesEnvFile = getHermesEnvFile(instanceId);
247
+ const env = parseEnvFile(hermesEnvFile);
248
+ const apiKey = env.HERMES_API_KEY || "";
249
+ if (!apiKey)
250
+ return;
251
+ const items = { HERMES_API_KEY: apiKey };
252
+ const MAX_ATTEMPTS = 3;
253
+ for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
254
+ let cas = 0;
255
+ try {
256
+ const existing = await nomadGet(`/v1/var/${encodedPath}?namespace=${ns}`);
257
+ if (existing.ok) {
258
+ const data = await existing.json();
259
+ cas = data.ModifyIndex || 0;
260
+ }
261
+ }
262
+ catch { /* variable may not exist yet */ }
263
+ const resp = await nomadPut(`/v1/var/${encodedPath}?cas=${cas}&namespace=${ns}`, {
264
+ Namespace: ns,
265
+ Path: varPath,
266
+ Items: items,
267
+ });
268
+ if (resp.ok)
269
+ return;
270
+ const text = await resp.text();
271
+ if (resp.status === 409 && attempt < MAX_ATTEMPTS - 1) {
272
+ await new Promise(r => setTimeout(r, 100 * Math.pow(2, attempt)));
273
+ continue;
274
+ }
275
+ throw new Error(`Failed to write Nomad Variables for ${instanceId}` +
276
+ ` (attempt ${attempt + 1}/${MAX_ATTEMPTS}): HTTP ${resp.status} ${text}`);
277
+ }
278
+ }
279
+ // ── AppTypeManager export ──────────────────────────────────────────────────
280
+ export const hermesAgentManager = {
281
+ appType: "hermes-agent",
282
+ createInstance: createHermesInstance,
283
+ prepareStart: prepareStartHermes,
284
+ writeNomadSecrets: writeNomadSecretsHermes,
285
+ buildNomadTask: buildNomadTaskHermes,
286
+ nomadTaskGroupName: () => "hermes",
287
+ buildRuntime: buildHermesRuntime,
288
+ };
289
+ //# sourceMappingURL=hermes-agent-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hermes-agent-manager.js","sourceRoot":"","sources":["../../../src/services/app/hermes-agent-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Instance ID normalizer for V2 creation flows.
3
+ *
4
+ * Computes the canonical instance ID at creation time, enforcing:
5
+ * - Singleton apps use their AppSpec `id` verbatim.
6
+ * - Tier-1 agents (openclaw / hermes) are prefixed with their kind.
7
+ * - Custom (non-singleton) apps are prefixed with the spec's canonical `id`.
8
+ * - Prefix application is idempotent: existing prefix is not doubled.
9
+ * - Reserved-prefix protection: custom apps cannot produce an ID that starts
10
+ * with `openclaw-` or `hermes-`.
11
+ */
12
+ /** Runtime adapter key used to decide ID prefix and dispatch logic. */
13
+ export type InstanceKind = "openclaw" | "hermes" | "custom";
14
+ /**
15
+ * Compute the canonical instance id at creation time.
16
+ *
17
+ * @param userInput Raw string from the user (name / short id).
18
+ * @param kind Runtime adapter key: `"openclaw"`, `"hermes"`, or `"custom"`.
19
+ * @param spec Optional AppSpec — required when `kind === "custom"`.
20
+ *
21
+ * @throws {Error} If `userInput` reduces to an empty string after kebab conversion.
22
+ * @throws {Error} If the resolved id (custom kind) starts with a reserved prefix.
23
+ */
24
+ export declare function normalizeInstanceId(userInput: string, kind: InstanceKind, spec?: {
25
+ id: string;
26
+ singleInstance?: boolean;
27
+ }): string;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Instance ID normalizer for V2 creation flows.
3
+ *
4
+ * Computes the canonical instance ID at creation time, enforcing:
5
+ * - Singleton apps use their AppSpec `id` verbatim.
6
+ * - Tier-1 agents (openclaw / hermes) are prefixed with their kind.
7
+ * - Custom (non-singleton) apps are prefixed with the spec's canonical `id`.
8
+ * - Prefix application is idempotent: existing prefix is not doubled.
9
+ * - Reserved-prefix protection: custom apps cannot produce an ID that starts
10
+ * with `openclaw-` or `hermes-`.
11
+ */
12
+ /** Reserved Tier-1 prefixes that custom apps must not produce. */
13
+ const RESERVED_PREFIXES = ["openclaw-", "hermes-"];
14
+ /**
15
+ * Convert an arbitrary string to kebab-case:
16
+ * - Lowercase all characters.
17
+ * - Replace any run of non-alphanumeric characters with a single `-`.
18
+ * - Strip leading and trailing `-`.
19
+ */
20
+ function toKebab(s) {
21
+ return s
22
+ .toLowerCase()
23
+ .replace(/[^a-z0-9]+/g, "-")
24
+ .replace(/^-+|-+$/g, "");
25
+ }
26
+ /**
27
+ * Compute the canonical instance id at creation time.
28
+ *
29
+ * @param userInput Raw string from the user (name / short id).
30
+ * @param kind Runtime adapter key: `"openclaw"`, `"hermes"`, or `"custom"`.
31
+ * @param spec Optional AppSpec — required when `kind === "custom"`.
32
+ *
33
+ * @throws {Error} If `userInput` reduces to an empty string after kebab conversion.
34
+ * @throws {Error} If the resolved id (custom kind) starts with a reserved prefix.
35
+ */
36
+ export function normalizeInstanceId(userInput, kind, spec) {
37
+ // Singleton apps always use the canonical spec id regardless of user input.
38
+ if (spec?.singleInstance) {
39
+ return spec.id;
40
+ }
41
+ const userPart = toKebab(userInput);
42
+ if (userPart === "") {
43
+ throw new Error(`invalid instance id: user input "${userInput}" produces an empty string after normalization`);
44
+ }
45
+ // Reserved-prefix protection runs first for custom kind — catches the
46
+ // "user tried to smuggle a Tier-1 id via custom" case even if the route
47
+ // didn't supply an AppSpec yet.
48
+ if (kind === "custom") {
49
+ for (const reserved of RESERVED_PREFIXES) {
50
+ if (userPart.startsWith(reserved)) {
51
+ throw new Error(`reserved prefix "${reserved}": custom app id cannot start with it`);
52
+ }
53
+ }
54
+ if (!spec) {
55
+ throw new Error("custom kind requires an AppSpec: route must supply app_spec_yaml or app_spec_ref");
56
+ }
57
+ }
58
+ // Determine the prefix: Tier-1 agents use their kind; custom apps use spec.id.
59
+ const prefix = kind === "custom" ? spec.id : kind;
60
+ // Idempotent: skip prefixing if userPart already starts with `<prefix>-` or
61
+ // equals `<prefix>` exactly.
62
+ const normalized = userPart.startsWith(`${prefix}-`) || userPart === prefix
63
+ ? userPart
64
+ : `${prefix}-${userPart}`;
65
+ // Final reserved-prefix guard on the computed id: catches the case where the
66
+ // spec's own id starts with a Tier-1 prefix (would only happen if an AppSpec
67
+ // author did something adversarial).
68
+ if (kind === "custom") {
69
+ for (const reserved of RESERVED_PREFIXES) {
70
+ if (normalized.startsWith(reserved)) {
71
+ throw new Error(`reserved prefix "${reserved}": custom app id cannot start with it`);
72
+ }
73
+ }
74
+ }
75
+ return normalized;
76
+ }
77
+ //# sourceMappingURL=id-normalizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id-normalizer.js","sourceRoot":"","sources":["../../../src/services/app/id-normalizer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,kEAAkE;AAClE,MAAM,iBAAiB,GAAG,CAAC,WAAW,EAAE,SAAS,CAAU,CAAC;AAE5D;;;;;GAKG;AACH,SAAS,OAAO,CAAC,CAAS;IACxB,OAAO,CAAC;SACL,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;AAC7B,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CACjC,SAAiB,EACjB,IAAkB,EAClB,IAA+C;IAE/C,4EAA4E;IAC5E,IAAI,IAAI,EAAE,cAAc,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC,EAAE,CAAC;IACjB,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAEpC,IAAI,QAAQ,KAAK,EAAE,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CACb,oCAAoC,SAAS,gDAAgD,CAC9F,CAAC;IACJ,CAAC;IAED,sEAAsE;IACtE,wEAAwE;IACxE,gCAAgC;IAChC,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,KAAK,MAAM,QAAQ,IAAI,iBAAiB,EAAE,CAAC;YACzC,IAAI,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAClC,MAAM,IAAI,KAAK,CACb,oBAAoB,QAAQ,uCAAuC,CACpE,CAAC;YACJ,CAAC;QACH,CAAC;QACD,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CACb,kFAAkF,CACnF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,+EAA+E;IAC/E,MAAM,MAAM,GAAG,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAEnD,4EAA4E;IAC5E,6BAA6B;IAC7B,MAAM,UAAU,GACd,QAAQ,CAAC,UAAU,CAAC,GAAG,MAAM,GAAG,CAAC,IAAI,QAAQ,KAAK,MAAM;QACtD,CAAC,CAAC,QAAQ;QACV,CAAC,CAAC,GAAG,MAAM,IAAI,QAAQ,EAAE,CAAC;IAE9B,6EAA6E;IAC7E,6EAA6E;IAC7E,qCAAqC;IACrC,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,KAAK,MAAM,QAAQ,IAAI,iBAAiB,EAAE,CAAC;YACzC,IAAI,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACpC,MAAM,IAAI,KAAK,CACb,oBAAoB,QAAQ,uCAAuC,CACpE,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Ollama App-type Manager
3
+ *
4
+ * Encapsulates all logic specific to the "ollama" app type:
5
+ * - Instance directory layout (ollama-home/models)
6
+ * - Instance creation
7
+ * - Pre-start validation
8
+ *
9
+ * Ollama runs as a plain Docker container with a mounted model directory.
10
+ * No IM channels, no OpenClaw config, no Nomad Variables needed.
11
+ */
12
+ import type { AppTypeManager, CreateInstanceOptions, InstanceMeta, StartPreparation } from "./types.js";
13
+ export declare function defaultOllamaHome(instanceId: string): string;
14
+ export declare function getOllamaHome(instanceId: string): string;
15
+ export declare function prepareStartOllama(instanceId: string): Promise<StartPreparation>;
16
+ export declare function createOllamaInstance(instanceId: string, name: string, description: string, options: CreateInstanceOptions): Promise<InstanceMeta>;
17
+ export declare function buildNomadTaskOllama(instanceId: string, runtime: Record<string, any>, _safeJobId: string): Record<string, any>;
18
+ export declare const ollamaManager: AppTypeManager;
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Ollama App-type Manager
3
+ *
4
+ * Encapsulates all logic specific to the "ollama" app type:
5
+ * - Instance directory layout (ollama-home/models)
6
+ * - Instance creation
7
+ * - Pre-start validation
8
+ *
9
+ * Ollama runs as a plain Docker container with a mounted model directory.
10
+ * No IM channels, no OpenClaw config, no Nomad Variables needed.
11
+ */
12
+ import { execFileSync } from "child_process";
13
+ import { chownSync, existsSync, statSync } from "fs";
14
+ import { join } from "path";
15
+ import { INSTANCES_DIR, getInstanceDir, instanceMetaPath } from "../../config.js";
16
+ import { safeWriteJson } from "../../utils/safe-json.js";
17
+ import { ensureDirContainer } from "../../utils/fs.js";
18
+ import { compileTaskRuntime } from "./app-compiler.js";
19
+ import { resolveServiceUser, defaultGatewayPort, releasePort, getInstanceRuntime, getRuntimeEnv, getGatewayPort, } from "../instance-manager.js";
20
+ // ── Constants ─────────────────────────────────────────────────────────────────
21
+ const OLLAMA_HOME_DIRNAME = "ollama-home";
22
+ const DEFAULT_OLLAMA_IMAGE = "ollama/ollama:latest";
23
+ // ── Path helpers ──────────────────────────────────────────────────────────────
24
+ export function defaultOllamaHome(instanceId) {
25
+ return join(getInstanceDir(instanceId), OLLAMA_HOME_DIRNAME);
26
+ }
27
+ export function getOllamaHome(instanceId) {
28
+ return defaultOllamaHome(instanceId);
29
+ }
30
+ // ── Pre-start validation ──────────────────────────────────────────────────────
31
+ export async function prepareStartOllama(instanceId) {
32
+ const { DOCKER_IMAGE_RE, MAX_DOCKER_IMAGE_NAME_LEN } = await import("../nomad-manager.js");
33
+ const { getNomadDriver } = await import("../../config.js");
34
+ if (getNomadDriver() === "docker") {
35
+ const { getInstance } = await import("../instance-manager.js");
36
+ const inst = getInstance(instanceId);
37
+ const image = inst?.runtime?.image || DEFAULT_OLLAMA_IMAGE;
38
+ if (!DOCKER_IMAGE_RE.test(image) || image.length > MAX_DOCKER_IMAGE_NAME_LEN) {
39
+ return { ok: false, error: `Invalid Docker image name: "${image}"` };
40
+ }
41
+ try {
42
+ execFileSync("docker", ["image", "inspect", image], { timeout: 10000, stdio: "ignore" });
43
+ }
44
+ catch {
45
+ console.log(`[ollama] Docker image ${image} not found, starting background pull...`);
46
+ try {
47
+ const { exec: execAsync } = await import("child_process");
48
+ const { promisify } = await import("util");
49
+ promisify(execAsync)(`docker pull ${image}`, { timeout: 0 }).catch((e) => {
50
+ console.warn(`[ollama] Background pull of ${image} failed: ${e.message}`);
51
+ });
52
+ return {
53
+ ok: false,
54
+ error: `Docker image ${image} not found locally. Pull started in background — retry in a few minutes.`,
55
+ };
56
+ }
57
+ catch (e) {
58
+ return { ok: false, error: `Docker image ${image} not available: ${e.message}` };
59
+ }
60
+ }
61
+ }
62
+ return { ok: true };
63
+ }
64
+ // ── Instance creation ─────────────────────────────────────────────────────────
65
+ export async function createOllamaInstance(instanceId, name, description, options) {
66
+ const { appSpec } = options;
67
+ const d = getInstanceDir(instanceId);
68
+ if (existsSync(d))
69
+ throw new Error(`Instance '${instanceId}' already exists`);
70
+ const ollamaHome = defaultOllamaHome(instanceId);
71
+ ensureDirContainer(d);
72
+ try {
73
+ const parentGid = statSync(INSTANCES_DIR).gid;
74
+ chownSync(d, -1, parentGid);
75
+ }
76
+ catch { /* non-root */ }
77
+ ensureDirContainer(ollamaHome);
78
+ ensureDirContainer(join(ollamaHome, "models"));
79
+ const serviceTask = appSpec?.tasks.find((t) => t.role === "service");
80
+ const compiled = serviceTask ? compileTaskRuntime(serviceTask, instanceId) : null;
81
+ const image = compiled?.image || serviceTask?.image || DEFAULT_OLLAMA_IMAGE;
82
+ const resources = compiled?.resources || { CPU: 2000, MemoryMB: 4096 };
83
+ // Ollama exposes port 11434 by default; we still use the gateway port pool
84
+ // so we don't allocate a fixed port that clashes with other instances.
85
+ const hostPort = await defaultGatewayPort(instanceId);
86
+ let allocatedPort = hostPort;
87
+ try {
88
+ const taskEnv = { ...(serviceTask?.env || {}) };
89
+ const runtime = {
90
+ command: null,
91
+ args: [],
92
+ cwd: "/root",
93
+ user: "root",
94
+ env_files: [],
95
+ env: {
96
+ // Use OPENCLAW_GATEWAY_PORT for consistency with the port helpers
97
+ OPENCLAW_GATEWAY_PORT: String(hostPort),
98
+ OLLAMA_HOST: `0.0.0.0:11434`,
99
+ OLLAMA_MODELS: "/models",
100
+ ...taskEnv,
101
+ },
102
+ image,
103
+ resources,
104
+ };
105
+ const meta = {
106
+ id: instanceId,
107
+ name,
108
+ description,
109
+ ollama_home: ollamaHome,
110
+ app_type: "ollama",
111
+ runtime,
112
+ created_at: new Date().toISOString(),
113
+ ...(appSpec ? { app_id: appSpec.app_id ?? appSpec.id } : {}),
114
+ };
115
+ safeWriteJson(instanceMetaPath(instanceId), meta);
116
+ const svcUser = resolveServiceUser();
117
+ if (svcUser) {
118
+ try {
119
+ execFileSync("chown", ["-R", `${svcUser.uid}:${svcUser.gid}`, d], { timeout: 10_000 });
120
+ execFileSync("chown", ["-R", `${svcUser.uid}:${svcUser.gid}`, ollamaHome], { timeout: 10_000 });
121
+ }
122
+ catch (e) {
123
+ console.warn(`[ollama] chown for ${instanceId} failed:`, e.message);
124
+ }
125
+ }
126
+ return meta;
127
+ }
128
+ finally {
129
+ if (allocatedPort != null)
130
+ releasePort(allocatedPort);
131
+ }
132
+ }
133
+ // ── Nomad task builder for Ollama ──────────────────────────────────────────
134
+ import { DEFAULT_RESOURCES, DEFAULT_ENV, MAX_CPU_MHZ, MAX_MEMORY_MB, VALID_USER_RE, normalizeDockerResources, } from "../nomad-manager.js";
135
+ function buildOllamaRuntime(instanceId) {
136
+ const runtime = getInstanceRuntime(instanceId);
137
+ const ollamaHome = getOllamaHome(instanceId);
138
+ if (runtime.user && !VALID_USER_RE.test(runtime.user)) {
139
+ throw new Error(`Invalid runtime user: ${runtime.user}`);
140
+ }
141
+ const env = { ...DEFAULT_ENV };
142
+ Object.assign(env, getRuntimeEnv(instanceId));
143
+ env.OLLAMA_MODELS = "/models";
144
+ const resources = { ...DEFAULT_RESOURCES, CPU: 2000, MemoryMB: 4096 };
145
+ for (const [key, value] of Object.entries(runtime.resources || {})) {
146
+ if (value != null)
147
+ resources[key] = Number(value);
148
+ }
149
+ resources.CPU = Math.max(1, Math.min(resources.CPU, MAX_CPU_MHZ));
150
+ resources.MemoryMB = Math.max(1, Math.min(resources.MemoryMB, MAX_MEMORY_MB));
151
+ return {
152
+ command: runtime.command || null,
153
+ args: Array.isArray(runtime.args) ? runtime.args.map(String) : [],
154
+ user: runtime.user || "root",
155
+ cwd: runtime.cwd || "/root",
156
+ env,
157
+ resources,
158
+ image: runtime.image ?? null,
159
+ };
160
+ }
161
+ export function buildNomadTaskOllama(instanceId, runtime, _safeJobId) {
162
+ const ollamaHome = getOllamaHome(instanceId);
163
+ const image = runtime.image || DEFAULT_OLLAMA_IMAGE;
164
+ const hostPort = getGatewayPort(instanceId);
165
+ const containerEnv = { ...runtime.env };
166
+ containerEnv.OLLAMA_HOST = "0.0.0.0:11434";
167
+ containerEnv.OLLAMA_MODELS = "/models";
168
+ const normalizedResources = normalizeDockerResources(instanceId, runtime);
169
+ return {
170
+ Name: "gateway",
171
+ Driver: "docker",
172
+ User: "0:0",
173
+ Config: {
174
+ image,
175
+ force_pull: false,
176
+ volumes: [
177
+ `${ollamaHome}:/root/.ollama:rw`,
178
+ `${join(ollamaHome, "models")}:/models:rw`,
179
+ ],
180
+ extra_hosts: ["host.docker.internal:host-gateway"],
181
+ pids_limit: 512,
182
+ mounts: [
183
+ { type: "tmpfs", target: "/tmp", tmpfs_options: { size: 536870912 } },
184
+ ],
185
+ },
186
+ Env: containerEnv,
187
+ Resources: {
188
+ ...normalizedResources,
189
+ Networks: [{
190
+ ReservedPorts: [{ Label: "gateway", Value: hostPort, To: 11434 }],
191
+ }],
192
+ },
193
+ LogConfig: { MaxFiles: 3, MaxFileSizeMB: 10 },
194
+ Templates: [],
195
+ };
196
+ }
197
+ // ── AppTypeManager export ──────────────────────────────────────────────────
198
+ export const ollamaManager = {
199
+ appType: "ollama",
200
+ createInstance: createOllamaInstance,
201
+ prepareStart: prepareStartOllama,
202
+ writeNomadSecrets: async () => { },
203
+ buildNomadTask: buildNomadTaskOllama,
204
+ nomadTaskGroupName: () => "ollama",
205
+ buildRuntime: buildOllamaRuntime,
206
+ };
207
+ //# sourceMappingURL=ollama-manager.js.map