Skip to content

Commit b60e975

Browse files
committed
hermes-workspace: deploy as the default MiOS chat frontend
Operator directive 2026-05-11: 'forget open webui for now -- Ollama >> hermes agent >> hermes-workspace app is the front-end' Chain: mios-ollama (LLM models, GPU on the dev VM) -> mios-hermes (OpenAI-compatible gateway, port 8642) -> mios-hermes-workspace (web frontend, port 3030 -- the URL the operator opens in their browser) Files added: * etc/containers/systemd/mios-hermes-workspace.container -- Quadlet pulling ghcr.io/outsourc-e/hermes-workspace:latest. Wires HERMES_API_URL to the local mios-hermes; HERMES_API_TOKEN pulls the shared API_SERVER_KEY from /etc/mios/hermes/api.env so the same secret authenticates the gateway and the workspace. PORT=3030 overrides the image's PORT=3000 default to match mios.toml [ports].hermes_workspace (which collides with mios-forge otherwise). COOKIE_SECURE=0 keeps plain-HTTP login working without a reverse- proxy / TLS layer. * usr/lib/systemd/system/mios-hermes-workspace-firstboot.service + usr/libexec/mios/mios-hermes-workspace-firstboot.sh -- generates the workspace's session password (HERMES_PASSWORD) into /etc/mios/hermes-workspace/workspace.env on first boot. The upstream image makes HERMES_PASSWORD REQUIRED whenever the bind isn't pure loopback (we bind 0.0.0.0:3030), so without this the container exits immediately on first start. Rotate by deleting the file + restarting the firstboot service. mios.toml changes: * [ports] -- hermes_workspace = 3030 (canonical), webui moves to 3031 (legacy; Quadlet disabled in [quadlets.enable]). * [image.sidecars] -- hermes_workspace = ghcr.io/outsourc-e/ hermes-workspace:latest. * [quadlets.enable] -- mios-hermes-workspace = true, mios-hermes = true (explicit), mios-webui = false. Open WebUI stays in-tree but doesn't autostart by default; operators wanting it can flip the flag in their layered toml.
1 parent 9a8181d commit b60e975

4 files changed

Lines changed: 144 additions & 12 deletions

File tree

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# /etc/containers/systemd/mios-hermes-workspace.container
2+
# 'MiOS' Hermes Workspace -- web frontend for the Hermes Agent stack.
3+
#
4+
# Operator directive 2026-05-11: "forget open webui for now -- Ollama
5+
# >> hermes agent >> hermes-workspace app is the front-end".
6+
#
7+
# Chain: mios-ollama (LLM models) -> mios-hermes (OpenAI-compatible
8+
# gateway) -> mios-hermes-workspace (web frontend the operator opens
9+
# in their browser at http://localhost:3030/).
10+
#
11+
# Upstream: https://github.com/outsourc-e/hermes-workspace
12+
# Image: ghcr.io/outsourc-e/hermes-workspace:latest
13+
#
14+
# Auth: HERMES_API_TOKEN MUST equal API_SERVER_KEY in /etc/mios/hermes/
15+
# api.env (the same shared secret mios-hermes itself uses + that
16+
# mios-hermes-firstboot.service generates). HERMES_PASSWORD is the
17+
# workspace's session login -- generated by mios-hermes-workspace-
18+
# firstboot.service into /etc/mios/hermes-workspace/workspace.env on
19+
# first boot.
20+
21+
[Unit]
22+
Description='MiOS' Hermes Workspace (web frontend for Hermes Agent)
23+
After=network-online.target mios-hermes.service
24+
Wants=network-online.target mios-hermes.service
25+
ConditionPathExists=/etc/mios/hermes/api.env
26+
27+
[Container]
28+
# Image / Port placeholders -- resolved at image build time by
29+
# automation/15-render-quadlets.sh from mios.toml [image.sidecars].
30+
# hermes_workspace + [ports].hermes_workspace.
31+
Image=${MIOS_HERMES_WORKSPACE_IMAGE:-ghcr.io/outsourc-e/hermes-workspace:latest}
32+
ContainerName=mios-hermes-workspace
33+
Network=${MIOS_QUADLET_NETWORK:-mios.network}
34+
Network=ai-net.network
35+
PublishPort=${MIOS_PORT_HERMES_WORKSPACE:-3030}:${MIOS_PORT_HERMES_WORKSPACE:-3030}
36+
AutoUpdate=registry
37+
38+
# Wire the workspace to the locally-running Hermes Agent. HERMES_API_
39+
# TOKEN MUST match API_SERVER_KEY in /etc/mios/hermes/api.env -- which
40+
# mios-hermes-firstboot.service generates on first boot. EnvironmentFile=
41+
# auto-pulls the matching value via env-var substitution below.
42+
EnvironmentFile=/etc/mios/hermes/api.env
43+
EnvironmentFile=/etc/mios/hermes-workspace/workspace.env
44+
Environment=HERMES_API_URL=http://mios-hermes:${MIOS_PORT_HERMES:-8642}
45+
Environment=HERMES_API_TOKEN=${API_SERVER_KEY}
46+
# Bind the workspace on the operator-facing port directly. The upstream
47+
# image defaults PORT=3000; we override to 3030 (mios.toml [ports].
48+
# hermes_workspace) to keep the canonical 3030 surface previously
49+
# occupied by Open WebUI. Containers running with the Network=host
50+
# drop-in on the dev VM read this same env.
51+
Environment=PORT=${MIOS_PORT_HERMES_WORKSPACE:-3030}
52+
# Browsers drop Secure cookies over plain HTTP -- the workspace
53+
# defaults NODE_ENV=production which sets Secure on session cookies.
54+
# Disable explicitly so the operator's first-time login works without
55+
# a reverse proxy / TLS layer in front. Operators fronting the
56+
# workspace with HTTPS can flip this back to 1 via /etc/mios/hermes-
57+
# workspace/workspace.env.
58+
Environment=COOKIE_SECURE=0
59+
60+
Label=org.opencontainers.image.title=mios-hermes-workspace
61+
Label=org.opencontainers.image.url=http://localhost:${MIOS_PORT_HERMES_WORKSPACE:-3030}/
62+
Label=org.opencontainers.image.documentation=https://github.com/outsourc-e/hermes-workspace
63+
Label=io.podman_desktop.openInBrowser=http://localhost:${MIOS_PORT_HERMES_WORKSPACE:-3030}/
64+
65+
[Service]
66+
Restart=on-failure
67+
RestartSec=10s
68+
TimeoutStartSec=600s
69+
Delegate=yes
70+
71+
[Install]
72+
WantedBy=multi-user.target default.target
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# /usr/lib/systemd/system/mios-hermes-workspace-firstboot.service
2+
# Generate the workspace login password on first boot.
3+
[Unit]
4+
Description='MiOS' Hermes Workspace first-boot password generation
5+
ConditionPathExists=!/etc/mios/hermes-workspace/workspace.env
6+
Before=mios-hermes-workspace.service
7+
After=mios-hermes-firstboot.service
8+
9+
[Service]
10+
Type=oneshot
11+
RemainAfterExit=yes
12+
ExecStart=/usr/libexec/mios/mios-hermes-workspace-firstboot.sh
13+
14+
[Install]
15+
WantedBy=multi-user.target
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/usr/bin/env bash
2+
# /usr/libexec/mios/mios-hermes-workspace-firstboot.sh
3+
#
4+
# Generate the Hermes Workspace session password on first boot. The
5+
# upstream image binds 0.0.0.0:3030 (non-loopback), which makes
6+
# HERMES_PASSWORD REQUIRED -- without it the container exits with a
7+
# clear "session password missing" error.
8+
#
9+
# Sentinel: /etc/mios/hermes-workspace/workspace.env -- delete the
10+
# file + restart this unit to rotate the password.
11+
set -euo pipefail
12+
13+
DEST=/etc/mios/hermes-workspace/workspace.env
14+
install -d -m 0755 /etc/mios/hermes-workspace
15+
16+
if [[ -s "$DEST" ]] && grep -q '^HERMES_PASSWORD=' "$DEST" 2>/dev/null; then
17+
echo "[hermes-workspace-firstboot] $DEST already populated; nothing to do"
18+
exit 0
19+
fi
20+
21+
# 24 random bytes -> 48 hex chars. Strong enough for a session
22+
# password; rotate by deleting the file.
23+
PW=$(openssl rand -hex 24 2>/dev/null || \
24+
head -c 24 /dev/urandom | od -An -tx1 | tr -d ' \n')
25+
26+
cat > "$DEST" <<EOF
27+
# /etc/mios/hermes-workspace/workspace.env
28+
# Generated by mios-hermes-workspace-firstboot.service on first boot.
29+
# Read by both mios-hermes-workspace (login authentication) and any
30+
# operator tooling that needs the workspace login password.
31+
# Rotate by deleting this file + restarting mios-hermes-workspace-
32+
# firstboot.service.
33+
34+
HERMES_PASSWORD=$PW
35+
EOF
36+
chmod 0640 "$DEST"
37+
chown root:root "$DEST"
38+
39+
echo "[hermes-workspace-firstboot] wrote $DEST (rotate: rm + systemctl restart mios-hermes-workspace-firstboot.service)"

usr/share/mios/mios.toml

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1556,7 +1556,8 @@ cockpit_link = 19090 # mios-cockpit-link Podman Desktop discovery shim
15561556
ollama = 11434 # alternate local LLM backend
15571557
searxng = 8888 # local privacy-respecting metasearch
15581558
hermes = 8642 # Hermes-Agent OpenAI-compatible gateway
1559-
webui = 3030 # Open WebUI (port 3030 because mios-ai owns 8080)
1559+
hermes_workspace = 3030 # Hermes Workspace web frontend (default chat UI)
1560+
webui = 3031 # Open WebUI (legacy; mios-webui Quadlet disabled by default 2026-05-11)
15601561
k3s_api = 6443 # K3s Kubernetes API
15611562
guacamole_web = 8090 # mios-guacamole browser desktop
15621563
ceph_dashboard = 8443 # Ceph dashboard
@@ -1582,6 +1583,8 @@ hermes_version = "latest"
15821583
hermes = "docker.io/nousresearch/hermes-agent:latest"
15831584
webui_version = "latest"
15841585
webui = "docker.io/openwebui/open-webui:latest"
1586+
hermes_workspace_version = "latest"
1587+
hermes_workspace = "ghcr.io/outsourc-e/hermes-workspace:latest"
15851588
ollama_version = "latest"
15861589
ollama = "docker.io/ollama/ollama:latest"
15871590
guacamole_version = "latest"
@@ -2167,17 +2170,20 @@ features = ["ai", "virtualization", "k3s"]
21672170
# force-disable a service even when it would otherwise run.
21682171
# ----------------------------------------------------------------------------
21692172
[quadlets.enable]
2170-
mios-ai = true
2171-
mios-ceph = true
2172-
mios-k3s = true
2173-
mios-forge = true
2174-
mios-cockpit-link = true # surfaces host Cockpit (9090) in Podman Desktop UI
2175-
crowdsec-dashboard = true
2176-
ollama = true
2177-
cloudws-guacamole = true
2178-
cloudws-pxe-hub = true
2179-
guacamole-postgres = true
2180-
guacd = true
2173+
mios-ai = true
2174+
mios-ceph = true
2175+
mios-k3s = true
2176+
mios-forge = true
2177+
mios-cockpit-link = true # surfaces host Cockpit (9090) in Podman Desktop UI
2178+
mios-hermes = true
2179+
mios-hermes-workspace = true # default chat frontend (Ollama -> Hermes -> Workspace)
2180+
mios-webui = false # disabled 2026-05-11: hermes-workspace is the default chat UI
2181+
crowdsec-dashboard = true
2182+
ollama = true
2183+
cloudws-guacamole = true
2184+
cloudws-pxe-hub = true
2185+
guacamole-postgres = true
2186+
guacd = true
21812187

21822188
# ----------------------------------------------------------------------------
21832189
# [env] -- free-form environment-variable additions. Keys must match POSIX

0 commit comments

Comments
 (0)