@bitseek/hermes-webui 0.1.0-beta.0

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 (233) hide show
  1. package/README.md +213 -0
  2. package/bin/hermes-webui.mjs +588 -0
  3. package/package.json +25 -0
  4. package/scripts/sync-vendor.mjs +74 -0
  5. package/templates/launchd/com.bitseek.hermes-webui.plist +21 -0
  6. package/templates/systemd/hermes-webui.service +13 -0
  7. package/templates/windows/hermes-webui-task.ps1 +3 -0
  8. package/vendor/agent-frontend-shell/.bitseek-source.json +6 -0
  9. package/vendor/agent-frontend-shell/.dockerignore +7 -0
  10. package/vendor/agent-frontend-shell/.env.docker.example +89 -0
  11. package/vendor/agent-frontend-shell/.env.example +34 -0
  12. package/vendor/agent-frontend-shell/.github/FUNDING.yml +3 -0
  13. package/vendor/agent-frontend-shell/.github/workflows/browser-smoke.yml +42 -0
  14. package/vendor/agent-frontend-shell/.github/workflows/docker-smoke.yml +233 -0
  15. package/vendor/agent-frontend-shell/.github/workflows/native-windows-startup.yml +132 -0
  16. package/vendor/agent-frontend-shell/.github/workflows/release.yml +57 -0
  17. package/vendor/agent-frontend-shell/.github/workflows/tests.yml +88 -0
  18. package/vendor/agent-frontend-shell/.vscode/launch.json +59 -0
  19. package/vendor/agent-frontend-shell/.vscode/settings.json +13 -0
  20. package/vendor/agent-frontend-shell/AGENTS.md +80 -0
  21. package/vendor/agent-frontend-shell/ARCHITECTURE.md +1658 -0
  22. package/vendor/agent-frontend-shell/BUGS.md +52 -0
  23. package/vendor/agent-frontend-shell/CHANGELOG.md +7295 -0
  24. package/vendor/agent-frontend-shell/CONTRIBUTING.md +205 -0
  25. package/vendor/agent-frontend-shell/CONTRIBUTORS.md +107 -0
  26. package/vendor/agent-frontend-shell/DESIGN.md +173 -0
  27. package/vendor/agent-frontend-shell/Dockerfile +91 -0
  28. package/vendor/agent-frontend-shell/LICENSE +21 -0
  29. package/vendor/agent-frontend-shell/README-CUSTOM.md +76 -0
  30. package/vendor/agent-frontend-shell/README.md +705 -0
  31. package/vendor/agent-frontend-shell/ROADMAP.md +351 -0
  32. package/vendor/agent-frontend-shell/SPRINTS.md +147 -0
  33. package/vendor/agent-frontend-shell/TESTING.md +1932 -0
  34. package/vendor/agent-frontend-shell/THEMES.md +170 -0
  35. package/vendor/agent-frontend-shell/api/__init__.py +1 -0
  36. package/vendor/agent-frontend-shell/api/agent_health.py +392 -0
  37. package/vendor/agent-frontend-shell/api/agent_sessions.py +782 -0
  38. package/vendor/agent-frontend-shell/api/auth.py +592 -0
  39. package/vendor/agent-frontend-shell/api/background.py +87 -0
  40. package/vendor/agent-frontend-shell/api/clarify.py +238 -0
  41. package/vendor/agent-frontend-shell/api/commands.py +124 -0
  42. package/vendor/agent-frontend-shell/api/compression_anchor.py +134 -0
  43. package/vendor/agent-frontend-shell/api/config.py +5178 -0
  44. package/vendor/agent-frontend-shell/api/dashboard_probe.py +255 -0
  45. package/vendor/agent-frontend-shell/api/extensions.py +253 -0
  46. package/vendor/agent-frontend-shell/api/gateway_chat.py +435 -0
  47. package/vendor/agent-frontend-shell/api/gateway_watcher.py +230 -0
  48. package/vendor/agent-frontend-shell/api/goals.py +608 -0
  49. package/vendor/agent-frontend-shell/api/helpers.py +474 -0
  50. package/vendor/agent-frontend-shell/api/kanban_bridge.py +1255 -0
  51. package/vendor/agent-frontend-shell/api/metering.py +194 -0
  52. package/vendor/agent-frontend-shell/api/models.py +4210 -0
  53. package/vendor/agent-frontend-shell/api/oauth.py +770 -0
  54. package/vendor/agent-frontend-shell/api/onboarding.py +1046 -0
  55. package/vendor/agent-frontend-shell/api/passkeys.py +365 -0
  56. package/vendor/agent-frontend-shell/api/profiles.py +1499 -0
  57. package/vendor/agent-frontend-shell/api/providers.py +2175 -0
  58. package/vendor/agent-frontend-shell/api/request_diagnostics.py +160 -0
  59. package/vendor/agent-frontend-shell/api/rollback.py +320 -0
  60. package/vendor/agent-frontend-shell/api/routes.py +13990 -0
  61. package/vendor/agent-frontend-shell/api/run_journal.py +284 -0
  62. package/vendor/agent-frontend-shell/api/runner_client.py +156 -0
  63. package/vendor/agent-frontend-shell/api/runtime_adapter.py +431 -0
  64. package/vendor/agent-frontend-shell/api/session_discoverability.py +640 -0
  65. package/vendor/agent-frontend-shell/api/session_events.py +45 -0
  66. package/vendor/agent-frontend-shell/api/session_lifecycle.py +208 -0
  67. package/vendor/agent-frontend-shell/api/session_ops.py +207 -0
  68. package/vendor/agent-frontend-shell/api/session_recovery.py +655 -0
  69. package/vendor/agent-frontend-shell/api/skill_usage.py +32 -0
  70. package/vendor/agent-frontend-shell/api/startup.py +128 -0
  71. package/vendor/agent-frontend-shell/api/state_sync.py +187 -0
  72. package/vendor/agent-frontend-shell/api/streaming.py +7048 -0
  73. package/vendor/agent-frontend-shell/api/system_health.py +167 -0
  74. package/vendor/agent-frontend-shell/api/terminal.py +410 -0
  75. package/vendor/agent-frontend-shell/api/turn_journal.py +214 -0
  76. package/vendor/agent-frontend-shell/api/updates.py +1261 -0
  77. package/vendor/agent-frontend-shell/api/upload.py +322 -0
  78. package/vendor/agent-frontend-shell/api/usage.py +26 -0
  79. package/vendor/agent-frontend-shell/api/workspace.py +867 -0
  80. package/vendor/agent-frontend-shell/api/workspace_git.py +1261 -0
  81. package/vendor/agent-frontend-shell/api/worktrees.py +357 -0
  82. package/vendor/agent-frontend-shell/bootstrap.py +492 -0
  83. package/vendor/agent-frontend-shell/ctl.sh +427 -0
  84. package/vendor/agent-frontend-shell/docker-compose.custom.yml +26 -0
  85. package/vendor/agent-frontend-shell/docker-compose.three-container.yml +168 -0
  86. package/vendor/agent-frontend-shell/docker-compose.two-container.yml +147 -0
  87. package/vendor/agent-frontend-shell/docker-compose.yml +57 -0
  88. package/vendor/agent-frontend-shell/docker_init.bash +459 -0
  89. package/vendor/agent-frontend-shell/docs/CONTRACTS.md +207 -0
  90. package/vendor/agent-frontend-shell/docs/EXTENSIONS.md +212 -0
  91. package/vendor/agent-frontend-shell/docs/ISSUES.md +23 -0
  92. package/vendor/agent-frontend-shell/docs/UIUX-GUIDE.md +196 -0
  93. package/vendor/agent-frontend-shell/docs/advanced-chat-setup.md +83 -0
  94. package/vendor/agent-frontend-shell/docs/docker.md +337 -0
  95. package/vendor/agent-frontend-shell/docs/onboarding-agent-checklist.md +207 -0
  96. package/vendor/agent-frontend-shell/docs/onboarding.md +202 -0
  97. package/vendor/agent-frontend-shell/docs/remote-access.md +75 -0
  98. package/vendor/agent-frontend-shell/docs/rfcs/README.md +53 -0
  99. package/vendor/agent-frontend-shell/docs/rfcs/agent-source-boundary.md +70 -0
  100. package/vendor/agent-frontend-shell/docs/rfcs/canonical-session-resolution.md +124 -0
  101. package/vendor/agent-frontend-shell/docs/rfcs/hermes-run-adapter-contract.md +1079 -0
  102. package/vendor/agent-frontend-shell/docs/rfcs/turn-journal.md +195 -0
  103. package/vendor/agent-frontend-shell/docs/rfcs/webui-run-state-consistency-contract.md +157 -0
  104. package/vendor/agent-frontend-shell/docs/supervisor.md +280 -0
  105. package/vendor/agent-frontend-shell/docs/troubleshooting.md +132 -0
  106. package/vendor/agent-frontend-shell/docs/ui-ux/index.html +863 -0
  107. package/vendor/agent-frontend-shell/docs/ui-ux/two-stage-proposal.html +768 -0
  108. package/vendor/agent-frontend-shell/docs/why-hermes.md +489 -0
  109. package/vendor/agent-frontend-shell/docs/workspace-git.md +92 -0
  110. package/vendor/agent-frontend-shell/docs/wsl-autostart.md +126 -0
  111. package/vendor/agent-frontend-shell/eslint.runtime-guard.config.mjs +35 -0
  112. package/vendor/agent-frontend-shell/extensions/bitseek-design-system.md +330 -0
  113. package/vendor/agent-frontend-shell/extensions/branding/assets/apple-touch-icon.png +0 -0
  114. package/vendor/agent-frontend-shell/extensions/branding/assets/empty-logo.svg +739 -0
  115. package/vendor/agent-frontend-shell/extensions/branding/assets/favicon-192.png +0 -0
  116. package/vendor/agent-frontend-shell/extensions/branding/assets/favicon-32.png +0 -0
  117. package/vendor/agent-frontend-shell/extensions/branding/assets/favicon-512.png +0 -0
  118. package/vendor/agent-frontend-shell/extensions/branding/assets/favicon-512.svg +745 -0
  119. package/vendor/agent-frontend-shell/extensions/branding/assets/favicon.ico +0 -0
  120. package/vendor/agent-frontend-shell/extensions/branding/assets/favicon.svg +745 -0
  121. package/vendor/agent-frontend-shell/extensions/branding/assets/titlebar-icon-v2.svg +751 -0
  122. package/vendor/agent-frontend-shell/extensions/branding/assets/titlebar-icon-v3.svg +739 -0
  123. package/vendor/agent-frontend-shell/extensions/branding/assets/titlebar-icon.svg +745 -0
  124. package/vendor/agent-frontend-shell/extensions/branding/branding.js +112 -0
  125. package/vendor/agent-frontend-shell/extensions/branding/config.json +14 -0
  126. package/vendor/agent-frontend-shell/extensions/branding/manifest.json +53 -0
  127. package/vendor/agent-frontend-shell/extensions/index.js +67 -0
  128. package/vendor/agent-frontend-shell/extensions/loader/hermes-loader.js +77 -0
  129. package/vendor/agent-frontend-shell/extensions/manifest.json +16 -0
  130. package/vendor/agent-frontend-shell/extensions/pages/ai-teammates/page.css +333 -0
  131. package/vendor/agent-frontend-shell/extensions/pages/ai-teammates/page.js +487 -0
  132. package/vendor/agent-frontend-shell/extensions/pages/manifest.json +6 -0
  133. package/vendor/agent-frontend-shell/extensions/pages/registry.css +56 -0
  134. package/vendor/agent-frontend-shell/extensions/pages/registry.js +302 -0
  135. package/vendor/agent-frontend-shell/extensions/themes/bitseek/index.css +93 -0
  136. package/vendor/agent-frontend-shell/extensions/themes/bitseek/index.js +98 -0
  137. package/vendor/agent-frontend-shell/install.sh +63 -0
  138. package/vendor/agent-frontend-shell/mcp_server.py +567 -0
  139. package/vendor/agent-frontend-shell/package.json +12 -0
  140. package/vendor/agent-frontend-shell/pyproject.toml +56 -0
  141. package/vendor/agent-frontend-shell/pytest.ini +3 -0
  142. package/vendor/agent-frontend-shell/requirements.txt +5 -0
  143. package/vendor/agent-frontend-shell/server.py +624 -0
  144. package/vendor/agent-frontend-shell/start.ps1 +210 -0
  145. package/vendor/agent-frontend-shell/start.sh +65 -0
  146. package/vendor/agent-frontend-shell/static/apple-touch-icon.png +0 -0
  147. package/vendor/agent-frontend-shell/static/boot.js +1990 -0
  148. package/vendor/agent-frontend-shell/static/commands.js +1402 -0
  149. package/vendor/agent-frontend-shell/static/favicon-192.png +0 -0
  150. package/vendor/agent-frontend-shell/static/favicon-32.png +0 -0
  151. package/vendor/agent-frontend-shell/static/favicon-512.png +0 -0
  152. package/vendor/agent-frontend-shell/static/favicon-512.svg +18 -0
  153. package/vendor/agent-frontend-shell/static/favicon.ico +0 -0
  154. package/vendor/agent-frontend-shell/static/favicon.svg +20 -0
  155. package/vendor/agent-frontend-shell/static/i18n.js +15389 -0
  156. package/vendor/agent-frontend-shell/static/icons.js +92 -0
  157. package/vendor/agent-frontend-shell/static/index.html +1506 -0
  158. package/vendor/agent-frontend-shell/static/login.js +177 -0
  159. package/vendor/agent-frontend-shell/static/manifest.json +53 -0
  160. package/vendor/agent-frontend-shell/static/messages.js +3521 -0
  161. package/vendor/agent-frontend-shell/static/onboarding.js +800 -0
  162. package/vendor/agent-frontend-shell/static/panels.js +7995 -0
  163. package/vendor/agent-frontend-shell/static/pwa-startup.js +83 -0
  164. package/vendor/agent-frontend-shell/static/sessions.js +5165 -0
  165. package/vendor/agent-frontend-shell/static/style.css +4774 -0
  166. package/vendor/agent-frontend-shell/static/sw.js +173 -0
  167. package/vendor/agent-frontend-shell/static/terminal.js +632 -0
  168. package/vendor/agent-frontend-shell/static/ui.js +8997 -0
  169. package/vendor/agent-frontend-shell/static/vendor/js-yaml/4.1.0/js-yaml.min.js +2 -0
  170. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_AMS-Regular.ttf +0 -0
  171. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_AMS-Regular.woff +0 -0
  172. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  173. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  174. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  175. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  176. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  177. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  178. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  179. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  180. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  181. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  182. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  183. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  184. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  185. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Bold.ttf +0 -0
  186. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Bold.woff +0 -0
  187. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Bold.woff2 +0 -0
  188. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  189. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  190. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  191. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Italic.ttf +0 -0
  192. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Italic.woff +0 -0
  193. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Italic.woff2 +0 -0
  194. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Regular.ttf +0 -0
  195. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Regular.woff +0 -0
  196. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Regular.woff2 +0 -0
  197. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  198. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  199. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  200. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-Italic.ttf +0 -0
  201. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-Italic.woff +0 -0
  202. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-Italic.woff2 +0 -0
  203. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  204. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  205. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  206. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  207. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  208. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  209. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  210. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  211. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  212. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Script-Regular.ttf +0 -0
  213. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Script-Regular.woff +0 -0
  214. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Script-Regular.woff2 +0 -0
  215. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size1-Regular.ttf +0 -0
  216. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size1-Regular.woff +0 -0
  217. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  218. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size2-Regular.ttf +0 -0
  219. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size2-Regular.woff +0 -0
  220. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  221. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size3-Regular.ttf +0 -0
  222. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size3-Regular.woff +0 -0
  223. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  224. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size4-Regular.ttf +0 -0
  225. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size4-Regular.woff +0 -0
  226. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  227. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  228. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  229. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  230. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/katex.min.css +1 -0
  231. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/katex.min.js +1 -0
  232. package/vendor/agent-frontend-shell/static/vendor/smd.min.js +29 -0
  233. package/vendor/agent-frontend-shell/static/workspace.js +680 -0
@@ -0,0 +1,147 @@
1
+ # Two-container Docker Compose: Hermes Agent + Hermes WebUI
2
+ #
3
+ # QUICK START:
4
+ # docker compose -f docker-compose.two-container.yml up -d
5
+ # Open http://localhost:8787
6
+ #
7
+ # This runs the agent and web UI in separate containers connected via shared
8
+ # Docker volumes. The WebUI installs the agent's Python dependencies from the
9
+ # shared agent source volume at startup.
10
+ #
11
+ # WHEN TO USE THIS:
12
+ # - You want isolation between the agent gateway and the WebUI
13
+ # - You're already running hermes-agent in its own container
14
+ # - You don't need the dashboard (use docker-compose.three-container.yml for that)
15
+ #
16
+ # WHEN NOT TO USE THIS:
17
+ # - You hit "Permission denied" trying to share an existing ~/.hermes directory
18
+ # → use docker-compose.yml (single-container) instead, OR
19
+ # → keep this file but switch to NAMED VOLUMES (the default) instead of bind mounts
20
+ # - You're on Podman 3.4 or older without keep-id namespace support
21
+ # → see https://github.com/sunnysktsang/hermes-suite for an all-in-one image
22
+ #
23
+ # KNOWN LIMITATION (#681): tools triggered from the WebUI run in the WebUI
24
+ # container, not the agent container. If you need git/node/etc. on the
25
+ # WebUI's filesystem, install them in the WebUI image — or use a single-
26
+ # container setup where everything lives in one place.
27
+ #
28
+ # NOTE ON VOLUMES:
29
+ # This file uses named Docker volumes (hermes-home, hermes-agent-src) which
30
+ # work out of the box on every Docker installation. If you prefer bind mounts
31
+ # to share an existing host directory:
32
+ #
33
+ # volumes:
34
+ # hermes-home:
35
+ # driver: local
36
+ # driver_opts:
37
+ # type: none
38
+ # o: bind
39
+ # device: /home/youruser/.hermes
40
+ #
41
+ # When using bind mounts, BOTH containers must mount the same host path,
42
+ # AND your host directory must be readable by UID 1000 (the default). Run:
43
+ # id -u && id -g
44
+ # to find your host UID/GID, then put them in a .env file:
45
+ # echo "UID=$(id -u)" >> .env
46
+ # echo "GID=$(id -g)" >> .env
47
+
48
+ services:
49
+ hermes-agent:
50
+ image: nousresearch/hermes-agent:latest
51
+ container_name: hermes-agent
52
+ command: gateway run
53
+ ports:
54
+ # Gateway API — exposed on localhost only.
55
+ # Other containers on hermes-net reach it via http://hermes-agent:8642.
56
+ # Remove 127.0.0.1: to expose on the host network (e.g. for remote clients).
57
+ - "127.0.0.1:8642:8642"
58
+ volumes:
59
+ # Persist config, state, sessions, skills, memory across restarts
60
+ - hermes-home:/home/hermes/.hermes
61
+ # Expose agent source so the WebUI can install dependencies from it
62
+ - hermes-agent-src:/opt/hermes
63
+ environment:
64
+ - HERMES_HOME=/home/hermes/.hermes
65
+ # Align UID/GID across containers sharing the hermes-home volume.
66
+ # Defaults to 1000 to match WANTED_UID/WANTED_GID in the webui service.
67
+ # The agent image's entrypoint already supports usermod remapping.
68
+ - HERMES_UID=${UID:-1000}
69
+ - HERMES_GID=${GID:-1000}
70
+ # Bind-mount permission handling for the agent — narrow set of overrides.
71
+ # NOTE: The agent's HERMES_HOME_MODE applies to the HERMES_HOME *directory*
72
+ # mode (default 0700) — NOT to credential files like the WebUI's variant.
73
+ # If you set this, you MUST keep the owner-execute bit so the agent can
74
+ # traverse its own home directory. 0640 BREAKS the agent (no x bit → no
75
+ # traversal). Use 0750 for group-traversable or 0701 for x-only.
76
+ # The agent's container detection (/.dockerenv) already auto-skips
77
+ # credential chmod inside Docker, so HERMES_SKIP_CHMOD is redundant here.
78
+ # - HERMES_HOME_MODE=0750
79
+ restart: unless-stopped
80
+ networks:
81
+ - hermes-net
82
+
83
+ hermes-webui:
84
+ image: ghcr.io/nesquena/hermes-webui:latest
85
+ container_name: hermes-webui
86
+ depends_on:
87
+ - hermes-agent
88
+ ports:
89
+ - "127.0.0.1:8787:8787"
90
+ volumes:
91
+ # Same hermes home as the agent — shares config, sessions, state
92
+ - hermes-home:/home/hermeswebui/.hermes
93
+ # Agent source mounted where docker_init.bash expects it.
94
+ # Mounted read-only — the WebUI only reads this volume to install
95
+ # the agent's Python dependencies at startup (`uv pip install`).
96
+ # Read-only enforces that defence-in-depth at the kernel layer.
97
+ - hermes-agent-src:/home/hermeswebui/.hermes/hermes-agent:ro
98
+ # Workspace directory — browse and edit files from the WebUI.
99
+ # Adapt the host path to your project directory.
100
+ # Override with: HERMES_WORKSPACE=/your/path docker compose up
101
+ # ${HOME} is used rather than `~` so the default resolves the same way
102
+ # across Linux, macOS, WSL2, and Docker Desktop on Windows.
103
+ - ${HERMES_WORKSPACE:-${HOME}/workspace}:/workspace
104
+ environment:
105
+ - HERMES_WEBUI_HOST=0.0.0.0
106
+ - HERMES_WEBUI_PORT=8787
107
+ - HERMES_WEBUI_STATE_DIR=/home/hermeswebui/.hermes/webui
108
+ # Match your host user's UID/GID for correct file permissions.
109
+ # In two-container setups the WebUI auto-detects UID/GID from the shared
110
+ # hermes-home volume, but you can override explicitly if needed (#668):
111
+ # Run `id -u` and `id -g` to find your values.
112
+ # On macOS, UIDs start at 501 — set these in a .env file:
113
+ # echo "UID=$(id -u)" >> .env && echo "GID=$(id -g)" >> .env
114
+ - WANTED_UID=${UID:-1000}
115
+ - WANTED_GID=${GID:-1000}
116
+ # Optional: set a password for remote access
117
+ # - HERMES_WEBUI_PASSWORD=your-secret-password
118
+ # Bind-mount permission handling for the WebUI (fixes #1389, #1399).
119
+ # NOTE: WebUI's HERMES_HOME_MODE is a credential-file threshold (allow
120
+ # group bits on .env/.signing_key/etc.), DIFFERENT from the agent's
121
+ # which applies to the HERMES_HOME directory itself. 0640 is correct
122
+ # for the WebUI; do NOT copy this value to the agent service block.
123
+ # - HERMES_SKIP_CHMOD=1
124
+ # - HERMES_HOME_MODE=0640
125
+ restart: unless-stopped
126
+ networks:
127
+ - hermes-net
128
+
129
+ networks:
130
+ hermes-net:
131
+ driver: bridge
132
+
133
+ volumes:
134
+ # IMPORTANT — upgrading the agent image:
135
+ # The `hermes-agent-src` volume is initialised from the agent image's
136
+ # `/opt/hermes` on first `up`, and Docker reuses the volume verbatim on
137
+ # later runs — even after `docker pull` of a newer agent image. After
138
+ # upgrading the agent image, run:
139
+ #
140
+ # docker compose down
141
+ # docker volume rm <project>_hermes-agent-src
142
+ # docker compose pull
143
+ # docker compose up -d
144
+ #
145
+ # The full procedure (and why) is documented in docs/docker.md.
146
+ hermes-home:
147
+ hermes-agent-src:
@@ -0,0 +1,57 @@
1
+ # Hermes WebUI — single-container Docker Compose
2
+ #
3
+ # QUICK START (most users):
4
+ # 1. (Optional) Copy .env.docker.example to .env and edit values
5
+ # 2. docker compose up -d
6
+ # 3. Open http://localhost:8787
7
+ #
8
+ # This is the simplest setup: one WebUI container that runs the agent in-process.
9
+ # The WebUI auto-detects host UID/GID from the mounted .hermes volume.
10
+ #
11
+ # For multi-container setups (separate agent + webui or agent+webui+dashboard),
12
+ # see docker-compose.two-container.yml or docker-compose.three-container.yml.
13
+
14
+ services:
15
+ hermes-webui:
16
+ build: .
17
+ ports:
18
+ # select only one; use 127.0.0.1 version to expose to localhost only
19
+ - "127.0.0.1:8787:8787"
20
+ # - "8787:8787"
21
+ volumes:
22
+ # Mount your Hermes home directory into the container.
23
+ # The default (${HOME}/.hermes) works on both macOS (/Users/<you>/.hermes)
24
+ # and Linux (/home/<you>/.hermes) — no change needed for standard installs.
25
+ # Only set HERMES_HOME explicitly if your .hermes lives somewhere non-standard.
26
+ # macOS note: set UID and GID below to match your user ID (run `id -u` and `id -g`).
27
+ - ${HERMES_HOME:-${HOME}/.hermes}:/home/hermeswebui/.hermes
28
+ # Your workspace directory shown on first launch (adapt if yours is different, the container will use the mounted /workspace)
29
+ - ${HERMES_WORKSPACE:-${HOME}/workspace}:/workspace
30
+ environment:
31
+ # Set to your host user ID: run `id -u` and `id -g` to find them.
32
+ # On macOS, UIDs start at 501 (not 1000), so set UID and GID in a .env file:
33
+ # echo "UID=$(id -u)" >> .env
34
+ # echo "GID=$(id -g)" >> .env
35
+ # Without this, the container may not be able to read your mounted files.
36
+ - WANTED_UID=${UID:-1000}
37
+ - WANTED_GID=${GID:-1000}
38
+ # Required: bind address and port
39
+ - HERMES_WEBUI_HOST=0.0.0.0
40
+ - HERMES_WEBUI_PORT=8787
41
+ # Where to store sessions, workspaces, and other state (default: ~/.hermes/webui)
42
+ - HERMES_WEBUI_STATE_DIR=/home/hermeswebui/.hermes/webui
43
+ # Default workspace directory shown on first launch
44
+ # - HERMES_WEBUI_DEFAULT_WORKSPACE=/workspace
45
+ # Optional: set a password for remote access
46
+ # - HERMES_WEBUI_PASSWORD=your-secret-password
47
+ #
48
+ # Bind-mount permission handling (fixes #1389, #1399):
49
+ # When you mount an EXISTING ~/.hermes directory (the common case),
50
+ # the WebUI's startup credential-permission fixer can clash with
51
+ # your host file modes (e.g. 0640 group-readable .env files).
52
+ # Set HERMES_SKIP_CHMOD=1 to bypass the fixer entirely, OR set
53
+ # HERMES_HOME_MODE=0640 to allow group bits while still stripping
54
+ # world-readable. Both are documented in api/startup.py.
55
+ # - HERMES_SKIP_CHMOD=1
56
+ # - HERMES_HOME_MODE=0640
57
+ restart: unless-stopped
@@ -0,0 +1,459 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ error_exit() {
6
+ echo -n "!! ERROR: "
7
+ echo $*
8
+ echo "!! Exiting script (ID: $$)"
9
+ exit 1
10
+ }
11
+
12
+ ok_exit() {
13
+ echo $*
14
+ echo "++ Exiting script (ID: $$)"
15
+ exit 0
16
+ }
17
+
18
+ ## Environment variables loaded when passing environment variables from user to user
19
+ # Ignore list: variables to ignore when loading environment variables from user to user
20
+ export ENV_IGNORELIST="HOME PWD USER SHLVL TERM OLDPWD SHELL _ SUDO_COMMAND HOSTNAME LOGNAME MAIL SUDO_GID SUDO_UID SUDO_USER CHECK_NV_CUDNN_VERSION VIRTUAL_ENV VIRTUAL_ENV_PROMPT ENV_IGNORELIST ENV_OBFUSCATE_PART"
21
+ # Obfuscate part: part of the key to obfuscate when loading environment variables from user to user, ex: HF_TOKEN, ...
22
+ export ENV_OBFUSCATE_PART="TOKEN API KEY"
23
+
24
+ # Check for ENV_IGNORELIST and ENV_OBFUSCATE_PART
25
+ if [ -z "${ENV_IGNORELIST+x}" ]; then error_exit "ENV_IGNORELIST not set"; fi
26
+ if [ -z "${ENV_OBFUSCATE_PART+x}" ]; then error_exit "ENV_OBFUSCATE_PART not set"; fi
27
+
28
+ # whoami fails under set -e if the UID has no /etc/passwd entry (k8s runAsUser).
29
+ whoami=$(whoami 2>/dev/null || echo "uid-$(id -u)")
30
+ script_dir=$(dirname $0)
31
+ script_name=$(basename $0)
32
+ echo ""; echo ""
33
+ echo "======================================"
34
+ echo "=================== Starting script (ID: $$)"
35
+ echo "== Running ${script_name} in ${script_dir} as ${whoami}"
36
+ script_fullname=$0
37
+ echo " - script_fullname: ${script_fullname}"
38
+ ignore_value="VALUE_TO_IGNORE"
39
+
40
+ # Keep init scratch files private to the container user that owns them.
41
+ umask 0077
42
+
43
+ write_privtmpfile() {
44
+ tmpfile=$1
45
+ if [ -z "${tmpfile}" ]; then error_exit "write_privtmpfile: missing argument"; fi
46
+ if [ -f "$tmpfile" ]; then rm -f "$tmpfile"; fi
47
+ printf '%s' "$2" > "$tmpfile"
48
+ chmod 600 "$tmpfile"
49
+ }
50
+
51
+ itdir=/tmp/hermeswebui_init
52
+ if [ ! -d "$itdir" ]; then mkdir -p "$itdir"; fi
53
+ chmod 700 "$itdir" || error_exit "Failed to secure $itdir"
54
+ if [ ! -d "$itdir" ]; then error_exit "Failed to create $itdir"; fi
55
+
56
+ # Set user and group id
57
+ # logic: if not set and file exists, use file value, else use default. Create file for persistence when the container is re-run
58
+ # reasoning: needed when using docker compose as the file will exist in the stopped container, and changing the value from environment variables or configuration file must be propagated from the root init phase to the hermeswebui runtime phase
59
+ it=$itdir/hermeswebui_user_uid
60
+ if [ -z "${WANTED_UID+x}" ]; then
61
+ if [ -f $it ]; then WANTED_UID=$(cat $it); fi
62
+ fi
63
+ # Auto-detect from mounted volumes if still unset (#569, #668).
64
+ # On macOS, host UIDs start at 501. Using the wrong UID means the container
65
+ # user cannot read the bind-mounted files, making the workspace appear empty.
66
+ # In two-container setups (hermes-agent + hermes-webui), the shared hermes-home
67
+ # volume may be owned by the agent container's UID — detect from there first.
68
+ if [ -z "${WANTED_UID+x}" ] || [ "${WANTED_UID}" = "1024" ]; then
69
+ # Priority 1: hermes-home shared volume — covers two-container Zeabur/Compose setups (#668)
70
+ for _probe_dir in "/home/hermeswebui/.hermes" "$HERMES_HOME" "/opt/data"; do
71
+ if [ -d "$_probe_dir" ]; then
72
+ _detected_uid=$(stat -c '%u' "$_probe_dir" 2>/dev/null || echo "")
73
+ if [ -n "$_detected_uid" ] && [ "$_detected_uid" != "0" ]; then
74
+ echo "-- Auto-detected UID: $_detected_uid (from $_probe_dir)"
75
+ WANTED_UID=$_detected_uid
76
+ break
77
+ fi
78
+ fi
79
+ done
80
+ fi
81
+ if [ -z "${WANTED_UID+x}" ] || [ "${WANTED_UID}" = "1024" ]; then
82
+ # Priority 2: /workspace bind-mount — the standard single-container mount point
83
+ if [ -d "/workspace" ]; then
84
+ _detected_uid=$(stat -c '%u' "/workspace" 2>/dev/null || echo "")
85
+ if [ -n "$_detected_uid" ] && [ "$_detected_uid" != "0" ]; then
86
+ echo "-- Auto-detected workspace UID: $_detected_uid (from /workspace)"
87
+ WANTED_UID=$_detected_uid
88
+ fi
89
+ fi
90
+ fi
91
+ WANTED_UID=${WANTED_UID:-1024}
92
+ write_privtmpfile $it "$WANTED_UID"
93
+ echo "-- WANTED_UID: \"${WANTED_UID}\""
94
+
95
+ it=$itdir/hermeswebui_user_gid
96
+ if [ -z "${WANTED_GID+x}" ]; then
97
+ if [ -f $it ]; then WANTED_GID=$(cat $it); fi
98
+ fi
99
+ # Auto-detect GID from mounted volumes to match (#569, #668)
100
+ if [ -z "${WANTED_GID+x}" ] || [ "${WANTED_GID}" = "1024" ]; then
101
+ # Priority 1: hermes-home shared volume
102
+ for _probe_dir in "/home/hermeswebui/.hermes" "$HERMES_HOME" "/opt/data"; do
103
+ if [ -d "$_probe_dir" ]; then
104
+ _detected_gid=$(stat -c '%g' "$_probe_dir" 2>/dev/null || echo "")
105
+ if [ -n "$_detected_gid" ] && [ "$_detected_gid" != "0" ]; then
106
+ echo "-- Auto-detected GID: $_detected_gid (from $_probe_dir)"
107
+ WANTED_GID=$_detected_gid
108
+ break
109
+ fi
110
+ fi
111
+ done
112
+ fi
113
+ if [ -z "${WANTED_GID+x}" ] || [ "${WANTED_GID}" = "1024" ]; then
114
+ # Priority 2: /workspace bind-mount
115
+ if [ -d "/workspace" ]; then
116
+ _detected_gid=$(stat -c '%g' "/workspace" 2>/dev/null || echo "")
117
+ if [ -n "$_detected_gid" ] && [ "$_detected_gid" != "0" ]; then
118
+ echo "-- Auto-detected workspace GID: $_detected_gid (from /workspace)"
119
+ WANTED_GID=$_detected_gid
120
+ fi
121
+ fi
122
+ fi
123
+ WANTED_GID=${WANTED_GID:-1024}
124
+ write_privtmpfile $it "$WANTED_GID"
125
+ echo "-- WANTED_GID: \"${WANTED_GID}\""
126
+
127
+ echo "== Most Environment variables set"
128
+
129
+ # Check user id and group id
130
+ new_gid=`id -g`
131
+ new_uid=`id -u`
132
+ echo "== user ($whoami)"
133
+ echo " uid: $new_uid / WANTED_UID: $WANTED_UID"
134
+ echo " gid: $new_gid / WANTED_GID: $WANTED_GID"
135
+
136
+ save_env() {
137
+ tosave=$1
138
+ echo "-- Saving environment variables to $tosave"
139
+ env | sort > "$tosave"
140
+ }
141
+
142
+ load_env() {
143
+ tocheck=$1
144
+ overwrite_if_different=$2
145
+ ignore_list="${ENV_IGNORELIST}"
146
+ obfuscate_part="${ENV_OBFUSCATE_PART}"
147
+ if [ -f "$tocheck" ]; then
148
+ echo "-- Loading environment variables from $tocheck (overwrite existing: $overwrite_if_different) (ignorelist: $ignore_list) (obfuscate: $obfuscate_part)"
149
+ while IFS='=' read -r key value; do
150
+ doit=false
151
+ # checking if the key is in the ignorelist
152
+ for i in $ignore_list; do
153
+ if [[ "A$key" == "A$i" ]]; then doit=ignore; break; fi
154
+ done
155
+ if [[ "A$doit" == "Aignore" ]]; then continue; fi
156
+ rvalue=$value
157
+ # checking if part of the key is in the obfuscate list
158
+ doobs=false
159
+ for i in $obfuscate_part; do
160
+ if [[ "A$key" == *"$i"* ]]; then doobs=obfuscate; break; fi
161
+ done
162
+ if [[ "A$doobs" == "Aobfuscate" ]]; then rvalue="**OBFUSCATED**"; fi
163
+
164
+ if [ -z "${!key}" ]; then
165
+ echo " ++ Setting environment variable $key [$rvalue]"
166
+ doit=true
167
+ elif [ "A$overwrite_if_different" == "Atrue" ]; then
168
+ cvalue="${!key}"
169
+ if [[ "A${doobs}" == "Aobfuscate" ]]; then cvalue="**OBFUSCATED**"; fi
170
+ if [[ "A${!key}" != "A${value}" ]]; then
171
+ echo " @@ Overwriting environment variable $key [$cvalue] -> [$rvalue]"
172
+ doit=true
173
+ else
174
+ echo " == Environment variable $key [$rvalue] already set and value is unchanged"
175
+ fi
176
+ fi
177
+ if [[ "A$doit" == "Atrue" ]]; then
178
+ export "$key=$value"
179
+ fi
180
+ done < "$tocheck"
181
+ fi
182
+ }
183
+
184
+ chown_home_hermeswebui() {
185
+ # macOS Docker bind mounts can expose hermes-agent git object packs as
186
+ # read-only host files. The runtime only needs to read those existing objects;
187
+ # requiring chown on them makes startup fail before WebUI can run (#2237).
188
+ #
189
+ # Multi-container compose (#2470) additionally mounts the entire
190
+ # hermes-agent-src volume read-only on the WebUI side because the WebUI only
191
+ # reads it for `uv pip install`. On a :ro mount, chown returns EROFS for any
192
+ # file inside the subtree, which would propagate to `set -e` and kill startup
193
+ # before the WebUI can run. Either way, the WebUI never writes to the agent
194
+ # source — prune the entire hermes-agent path from the chown walk so a
195
+ # read-only or partially-read-only mount doesn't break the rest of the home
196
+ # ownership alignment.
197
+ find /home/hermeswebui \
198
+ -path "/home/hermeswebui/.hermes/hermes-agent" -prune \
199
+ -o -exec chown -h "${WANTED_UID}:${WANTED_GID}" {} +
200
+ }
201
+
202
+ # The production image does not ship sudo. The entrypoint starts as root only
203
+ # long enough to align the hermeswebui UID/GID with mounted volumes, prepare
204
+ # root-owned paths, and then drop privileges for the server process.
205
+ if [ "A${whoami}" == "Aroot" ]; then
206
+ echo "-- Running as root for one-time container init; will switch to hermeswebui"
207
+
208
+ # We are altering the UID/GID of the hermeswebui user to the desired ones and restarting as that user
209
+ # using usermod for the already created hermeswebui user, knowing it is not already in use
210
+ # per usermod manual: "You must make certain that the named user is not executing any processes when this command is being executed"
211
+ # Guard for read-only root filesystem (podman with read_only=true, issue #1470).
212
+ _readonly_root=false
213
+ if ! sh -c 'test -w /etc/group && test -w /etc/passwd' 2>/dev/null; then
214
+ _readonly_root=true
215
+ echo " !! Detected read-only root filesystem — /etc/group or /etc/passwd is not writable"
216
+ fi
217
+ if [ "A${_readonly_root}" == "Atrue" ]; then
218
+ _current_hermeswebui_gid=$(id -g hermeswebui 2>/dev/null || echo "")
219
+ _current_hermeswebui_uid=$(id -u hermeswebui 2>/dev/null || echo "")
220
+ if [ "A${_current_hermeswebui_gid}" == "A${WANTED_GID}" ] && [ "A${_current_hermeswebui_uid}" == "A${WANTED_UID}" ]; then
221
+ echo " -- Skipping groupmod/usermod — hermeswebui already has UID ${WANTED_UID} GID ${WANTED_GID} and root fs is read-only"
222
+ else
223
+ error_exit "Cannot modify /etc/group or /etc/passwd (read-only root fs). Set UID=${_current_hermeswebui_uid} and GID=${_current_hermeswebui_gid} to match, or run without read_only=true. See issue #1470."
224
+ fi
225
+ else
226
+ groupmod -o -g "${WANTED_GID}" hermeswebui || error_exit "Failed to set GID of hermeswebui user"
227
+ usermod -o -u "${WANTED_UID}" hermeswebui || error_exit "Failed to set UID of hermeswebui user"
228
+ fi
229
+
230
+ chown_home_hermeswebui || error_exit "Failed to set owner of /home/hermeswebui"
231
+
232
+ echo ""; echo "-- Preparing /app for the hermeswebui runtime user"
233
+ mkdir -p /app || error_exit "Failed to create /app directory"
234
+ chown hermeswebui:hermeswebui /app || error_exit "Failed to set owner of /app to hermeswebui user"
235
+ rsync -av --chown=hermeswebui:hermeswebui /apptoo/ /app/ || error_exit "Failed to sync /apptoo to /app with correct ownership"
236
+
237
+ if [ -z "${HERMES_WEBUI_DEFAULT_WORKSPACE+x}" ]; then export HERMES_WEBUI_DEFAULT_WORKSPACE="/workspace"; fi
238
+ if [ ! -d "$HERMES_WEBUI_DEFAULT_WORKSPACE" ]; then
239
+ mkdir -p "$HERMES_WEBUI_DEFAULT_WORKSPACE" || error_exit "Failed to create default workspace at $HERMES_WEBUI_DEFAULT_WORKSPACE"
240
+ fi
241
+ if [ ! -d "$HERMES_WEBUI_DEFAULT_WORKSPACE" ]; then error_exit "HERMES_WEBUI_DEFAULT_WORKSPACE directory does not exist at $HERMES_WEBUI_DEFAULT_WORKSPACE"; fi
242
+ chown hermeswebui:hermeswebui "$HERMES_WEBUI_DEFAULT_WORKSPACE" 2>/dev/null || echo "!! WARNING: Could not chown $HERMES_WEBUI_DEFAULT_WORKSPACE (continuing)"
243
+
244
+ export UV_CACHE_DIR=${UV_CACHE_DIR:-/uv_cache}
245
+ mkdir -p "${UV_CACHE_DIR}" || error_exit "Failed to create ${UV_CACHE_DIR} directory"
246
+ chown hermeswebui:hermeswebui "${UV_CACHE_DIR}" || error_exit "Failed to set owner of ${UV_CACHE_DIR} to hermeswebui user"
247
+
248
+ chown -R "${WANTED_UID}:${WANTED_GID}" "$itdir" || error_exit "Failed to set owner of $itdir"
249
+ # Issue #2010 — Railway / user-namespaced runtimes: in-container UID 0 may map
250
+ # to a host UID outside the writable subuid range, so /tmp writes fail despite
251
+ # id -u == 0. Probe writability and fall back through $itdir → /app.
252
+ ENV_FILE="/tmp/hermeswebui_root_env.txt"
253
+ if ! ( : > "$ENV_FILE" ) 2>/dev/null; then
254
+ ENV_FILE="${itdir:-/tmp/hermeswebui_init}/hermeswebui_root_env.txt"
255
+ mkdir -p "$(dirname "$ENV_FILE")" 2>/dev/null
256
+ if ! ( : > "$ENV_FILE" ) 2>/dev/null; then
257
+ ENV_FILE="/app/.hermeswebui_root_env"
258
+ fi
259
+ echo " !! /tmp not writable by root — falling back to $ENV_FILE (user-namespaced runtime?)"
260
+ fi
261
+ save_env "$ENV_FILE"
262
+ chown "${WANTED_UID}:${WANTED_GID}" "$ENV_FILE" || error_exit "Failed to set owner of $ENV_FILE"
263
+ chmod 600 "$ENV_FILE" || error_exit "Failed to secure $ENV_FILE"
264
+ export _HW_ROOT_ENV_PATH="$ENV_FILE"
265
+
266
+ # restart the script as hermeswebui set with the correct UID/GID this time
267
+ echo "-- Restarting as hermeswebui user with UID ${WANTED_UID} GID ${WANTED_GID}"
268
+ exec su -s /bin/bash -c "exec \"${script_fullname}\"" hermeswebui || error_exit "subscript failed"
269
+ fi
270
+
271
+ # If we are here, the script is started as an unprivileged runtime user.
272
+ # Because the whoami value for the hermeswebui user can be any existing user, we cannot check against it;
273
+ # instead we check if the UID/GID are the expected ones.
274
+ if [ "$WANTED_GID" != "$new_gid" ]; then error_exit "hermeswebui MUST be running as UID ${WANTED_UID} GID ${WANTED_GID}, current UID ${new_uid} GID ${new_gid}"; fi
275
+ if [ "$WANTED_UID" != "$new_uid" ]; then error_exit "hermeswebui MUST be running as UID ${WANTED_UID} GID ${WANTED_GID}, current UID ${new_uid} GID ${new_gid}"; fi
276
+
277
+ ########## 'hermeswebui' specific section below
278
+
279
+ # We are therefore running as hermeswebui
280
+ echo ""; echo "== Running as hermeswebui"
281
+
282
+ # Load environment variables one by one if they do not exist from the root init phase
283
+ tmp_root_env="${_HW_ROOT_ENV_PATH:-/tmp/hermeswebui_root_env.txt}"
284
+ if [ -f $tmp_root_env ]; then
285
+ echo "-- Loading not already set environment variables from $tmp_root_env"
286
+ load_env $tmp_root_env true
287
+ fi
288
+
289
+ ##
290
+ if [ ! -f /app/server.py ] && [ -d /apptoo ]; then
291
+ echo ""; echo "-- Seeding /app from /apptoo (rootless startup)"
292
+ cp -a /apptoo/. /app/ || error_exit "Failed to seed /app from /apptoo (is /app writable by the runtime user?)"
293
+ fi
294
+
295
+ echo ""; echo "-- Verifying /app is writable by the hermeswebui runtime user"
296
+ if [ ! -d /app ]; then error_exit "/app directory does not exist"; fi
297
+ it=/app/.testfile; touch $it || error_exit "Failed to verify /app directory"
298
+ rm -f $it || error_exit "Failed to delete test file in /app"
299
+
300
+ ######## Environment variables (consume AFTER the load_env)
301
+
302
+ echo ""; echo "== Checking required environment variables for hermes-webui"
303
+
304
+ echo ""; echo "-- HERMES_WEBUI_STATE_DIR: Where to store sessions, workspaces, and other state (default: ~/.hermes/webui)"
305
+ if [ -z "${HERMES_WEBUI_STATE_DIR+x}" ]; then error_exit "HERMES_WEBUI_STATE_DIR not set"; fi;
306
+ echo "-- HERMES_WEBUI_STATE_DIR: $HERMES_WEBUI_STATE_DIR"
307
+ if [ ! -d "$HERMES_WEBUI_STATE_DIR" ]; then mkdir -p $HERMES_WEBUI_STATE_DIR || error_exit "Failed to create state directory at $HERMES_WEBUI_STATE_DIR"; fi
308
+ if [ ! -d "$HERMES_WEBUI_STATE_DIR" ]; then error_exit "HERMES_WEBUI_STATE_DIR directory does not exist at $HERMES_WEBUI_STATE_DIR"; fi
309
+ it="$HERMES_WEBUI_STATE_DIR/.testfile"; touch $it || error_exit "Failed to verify state directory at $HERMES_WEBUI_STATE_DIR"
310
+ rm -f $it || error_exit "Failed to delete test file in $HERMES_WEBUI_STATE_DIR"
311
+
312
+ echo ""; echo "-- HERMES_WEBUI_DEFAULT_WORKSPACE: Default workspace directory shown on first launch"
313
+ if [ -z "${HERMES_WEBUI_DEFAULT_WORKSPACE+x}" ]; then echo "HERMES_WEBUI_DEFAULT_WORKSPACE not set, setting to /workspace"; export HERMES_WEBUI_DEFAULT_WORKSPACE="/workspace"; fi;
314
+ echo "-- HERMES_WEBUI_DEFAULT_WORKSPACE: $HERMES_WEBUI_DEFAULT_WORKSPACE"
315
+ # The root init phase creates/chowns missing bind-mount directories before
316
+ # dropping privileges. After that, the runtime user only verifies access.
317
+ if [ ! -d "$HERMES_WEBUI_DEFAULT_WORKSPACE" ]; then
318
+ mkdir -p "$HERMES_WEBUI_DEFAULT_WORKSPACE" || error_exit "Failed to create default workspace at $HERMES_WEBUI_DEFAULT_WORKSPACE"
319
+ fi
320
+ if [ ! -d "$HERMES_WEBUI_DEFAULT_WORKSPACE" ]; then error_exit "HERMES_WEBUI_DEFAULT_WORKSPACE directory does not exist at $HERMES_WEBUI_DEFAULT_WORKSPACE"; fi
321
+ # Only write-test if the workspace is writable. Read-only bind-mounts (:ro)
322
+ # are valid — the workspace is used for browsing, not writing by the server.
323
+ if [ -w "$HERMES_WEBUI_DEFAULT_WORKSPACE" ]; then
324
+ it="$HERMES_WEBUI_DEFAULT_WORKSPACE/.testfile"; touch $it && rm -f $it || echo "!! WARNING: Could not write to $HERMES_WEBUI_DEFAULT_WORKSPACE (continuing)"
325
+ else
326
+ echo "-- HERMES_WEBUI_DEFAULT_WORKSPACE is read-only — skipping write check (read-only workspace is supported)"
327
+ fi
328
+
329
+ echo ""; echo "==================="
330
+ echo ""; echo "== Installing uv and creating a new virtual environment for hermes-webui"
331
+
332
+ export PATH="/home/hermeswebui/.local/bin/:$PATH"
333
+ if command -v uv &>/dev/null; then
334
+ echo "-- uv already installed ($(uv --version)), skipping download"
335
+ else
336
+ echo "-- uv not found, downloading..."
337
+ curl -LsSf https://astral.sh/uv/install.sh | sh || error_exit "Failed to install uv — check network connectivity"
338
+ fi
339
+ export UV_PROJECT_ENVIRONMENT=venv
340
+
341
+ export UV_CACHE_DIR=${UV_CACHE_DIR:-/uv_cache}
342
+ mkdir -p "${UV_CACHE_DIR}" || error_exit "Failed to create ${UV_CACHE_DIR} directory"
343
+ test -w "${UV_CACHE_DIR}" || error_exit "${UV_CACHE_DIR} is not writable by hermeswebui"
344
+
345
+ cd /app
346
+ if [ -f /app/venv/bin/python3 ]; then
347
+ echo ""; echo "== Existing virtual environment found — reusing (fast restart)"
348
+ else
349
+ echo ""; echo "== Creating new virtual environment"
350
+ uv venv venv
351
+ fi
352
+ export VIRTUAL_ENV=/app/venv
353
+ test -d /app/venv
354
+ test -f /app/venv/bin/activate
355
+
356
+ echo "";echo "== Activating hermes webui's virtual environment"
357
+ source /app/venv/bin/activate || error_exit "Failed to activate hermeswebui virtual environment"
358
+ test -x /app/venv/bin/python3
359
+
360
+ ensure_hindsight_client_docker_dependency() {
361
+ # Keep this outside the .deps_installed fast-restart guard so existing
362
+ # two-container Docker venvs self-heal after this dependency was added.
363
+ _hindsight_client_requirement="hindsight-client>=0.4.22"
364
+ echo ""; echo "== Checking Hindsight memory provider dependency"
365
+ if uv pip show hindsight-client >/dev/null 2>&1; then
366
+ echo "-- hindsight-client already installed"
367
+ else
368
+ echo "-- Installing ${_hindsight_client_requirement} for Hindsight memory provider support"
369
+ uv pip install "${_hindsight_client_requirement}" --trusted-host pypi.org --trusted-host files.pythonhosted.org || error_exit "Failed to install hindsight-client"
370
+ fi
371
+ }
372
+
373
+ if [ -f /app/venv/.deps_installed ]; then
374
+ echo ""; echo "== Dependencies already installed — skipping (fast restart)"
375
+ else
376
+ echo ""; echo "== Installing hermes-webui dependencies"
377
+ uv pip install -r requirements.txt --trusted-host pypi.org --trusted-host files.pythonhosted.org
378
+ uv pip install -U pip setuptools --trusted-host pypi.org --trusted-host files.pythonhosted.org
379
+ test -x /app/venv/bin/pip
380
+
381
+ echo ""; echo "== Adding hermes-agent's pyproject.toml base dependencies to the virtual environment"
382
+ _agent_paths=(
383
+ "/home/hermeswebui/.hermes/hermes-agent"
384
+ "/opt/hermes"
385
+ )
386
+ _agent_src=""
387
+ for _p in "${_agent_paths[@]}"; do
388
+ if [ -d "$_p" ] && [ -f "$_p/pyproject.toml" ]; then
389
+ _agent_src="$_p"
390
+ break
391
+ fi
392
+ done
393
+ if [ -n "$_agent_src" ]; then
394
+ if [ -w "$_agent_src" ]; then
395
+ echo ""
396
+ echo "!! WARNING: hermes-agent source mount is writable from the WebUI container."
397
+ echo "!! Path: $_agent_src"
398
+ echo "!! The multi-container compose defaults use a read-only mount for defence-in-depth."
399
+ echo "!! If this is not an intentional local development checkout, switch the WebUI"
400
+ echo "!! agent source volume/bind mount to read-only. See docs/rfcs/agent-source-boundary.md."
401
+ echo ""
402
+ fi
403
+ # The agent source can be mounted read-only (see docker-compose.two-container.yml
404
+ # / docker-compose.three-container.yml — the WebUI only reads this volume to
405
+ # install the agent's Python dependencies and never writes to it). setuptools'
406
+ # `egg_info` build step, however, touches `hermes_agent.egg-info/` inside the
407
+ # source tree even under PEP 517 build isolation, which `EROFS`-fails on a
408
+ # `:ro` mount and (under `set -e`) kills startup of every multi-container
409
+ # deploy. Stage the source into a writable tmpfs copy so the build can write
410
+ # its metadata side-by-side without touching the underlying mount.
411
+ #
412
+ # The copy excludes any pre-baked `*.egg-info` / `build` / `dist` artifacts
413
+ # to avoid the timestamp-update path setuptools takes when one is present,
414
+ # and `--reflink=auto` makes the copy near-free on overlay2/btrfs where
415
+ # supported. We rebuild on every container start (the agent source can
416
+ # change across volume re-init); cost is one rsync of ~10MB of Python source.
417
+ _stage_src="/tmp/hermes-agent-build"
418
+ rm -rf "$_stage_src"
419
+ mkdir -p "$_stage_src"
420
+ if command -v rsync >/dev/null 2>&1; then
421
+ rsync -a \
422
+ --exclude='*.egg-info' --exclude='build' --exclude='dist' \
423
+ --exclude='__pycache__' --exclude='.git' \
424
+ "$_agent_src"/ "$_stage_src"/ \
425
+ || error_exit "Failed to stage hermes-agent source to writable build dir"
426
+ else
427
+ # Fallback when rsync isn't in the image — straight cp -a, then drop
428
+ # the build artifacts that would trip setuptools.
429
+ cp -a "$_agent_src"/. "$_stage_src"/ \
430
+ || error_exit "Failed to copy hermes-agent source to writable build dir"
431
+ rm -rf "$_stage_src"/*.egg-info "$_stage_src"/build "$_stage_src"/dist 2>/dev/null || true
432
+ find "$_stage_src" -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
433
+ fi
434
+ uv pip install "$_stage_src[all]" --trusted-host pypi.org --trusted-host files.pythonhosted.org \
435
+ || error_exit "Failed to install hermes-agent's requirements"
436
+ rm -rf "$_stage_src"
437
+ else
438
+ echo ""
439
+ echo "!! WARNING: hermes-agent source not found."
440
+ echo "!! Looked in: ${_agent_paths[0]}"
441
+ echo "!! ${_agent_paths[1]}"
442
+ echo "!! The WebUI will start with reduced functionality (no model auto-detection,"
443
+ echo "!! no personality routing, no CLI session imports)."
444
+ echo "!! To fix: mount the agent source volume into the container:"
445
+ echo "!! -v /path/to/hermes-agent:/home/hermeswebui/.hermes/hermes-agent"
446
+ echo "!! Or see the two-container compose example:"
447
+ echo "!! https://github.com/nesquena/hermes-webui/blob/master/docker-compose.two-container.yml"
448
+ echo ""
449
+ fi
450
+ touch /app/venv/.deps_installed
451
+ fi
452
+
453
+ ensure_hindsight_client_docker_dependency
454
+
455
+ echo ""; echo "== Running hermes-webui"
456
+ cd /app; python server.py || error_exit "hermes-webui failed or exited with an error"
457
+
458
+ # we should never be here because the server should be running indefinitely, but if we are, we exit safely
459
+ ok_exit "Clean exit"