feat: add Hermes WebUI provisioning and English Dragon HD Omni voices
Rebuild & Deploy / rebuild (push) Successful in 2m17s

This commit is contained in:
snowspeeder committed 2026-10-02 16:07:24 -04:00
1 parent 47cc9c88a0
commit 6b8d55fc92
59 files changed
+1175 -70

No files matched your search

+74
View File
@@ -79,6 +79,80 @@ For colocated FastAPI integration use `Depends(control.current_user)` and
this application's `app.state.accounts`/settings. Separate services use the
secret-authenticated internal endpoints instead.
## Hermes LAN access and migration
User containers use Docker **host networking** for outbound access to the same
LAN resources as Unraid. The control service must also run with host networking
(as in the deployment script). Each Hermes API binds **127.0.0.1 only**, on its
own persistent port; no LAN-facing API publication or Docker port bindings.
`HERMES_API_PORT_RANGE` defaults to `20000-29999`. Reserve this TCP range for
Hermes. Allocation skips ports already bound on the host and assignments are
stored in the protected `api-ports.json` under `HOPE_HERMES_DATA_DIR`. Run only
one control process against this runtime root. Assignments survive restarts and
account deletion alongside retained data; changing the range affects only new
assignments. An unrelated process taking an assigned port is an operator error;
provisioning fails rather than silently changing the account endpoint.
Deploy/restart the updated control service to migrate existing accounts:
startup schedules **all accounts**, including ready ones. Provisioning replaces
owned legacy bridge containers with host-networked containers, retaining the
account name, API key, home, workspace and non-listener configuration. Only the
API host/port settings in the persisted YAML are updated. Owned legacy networks
are removed. Expect brief per-account downtime; failed startup is retriable with
the retained state. The normal reconcile loop also rejects legacy containers as
not ready and schedules provisioning. This is not a live deployment receipt.
**Security tradeoff:** host networking removes per-user network namespace
isolation. Containers can reach host-loopback services and other account API
ports (authentication is still required). Loopback-only binding prevents direct
LAN access to the Hermes API, but is not isolation from other host-networked
containers. File separation, nonroot execution, capability drops and resource
limits remain. This controls the Hermes API listener, **not arbitrary tool
processes**: terminal/browser tools can open their own LAN-facing unprivileged
ports. Strict outbound-only enforcement needs a separate host ingress firewall
policy; host networking alone cannot guarantee that. Host networking does not
bypass LAN ACLs or configure mDNS/DNS.
## Per-account Hermes WebUI
Build `hermes-webui.Dockerfile` as `hope-hermes-webui:62e4d7b`. This packages the
pinned nesquena/hermes-webui revision with the existing pinned Hermes image and
uses `bootstrap.py --foreground --no-browser --skip-agent-install` as a supervised
second service. Both gateway and WebUI exit/restart together on service failure.
Enable in the protected control runtime environment:
```dotenv
HOPE_WEBUI_ENABLED=true
HERMES_IMAGE=hope-hermes-webui:62e4d7b
HERMES_WEBUI_HOST=192.168.86.2
HERMES_WEBUI_PORT_RANGE=30000-30999
```
Each user gets a persistent port recorded in `webui-ports.json`, a separate random
browser password in `<runtime-root>/<full-account-id>/webui-password` (0600), and
private WebUI state and self-signed TLS certificate under the retained home.
User-selected WebUI passwords have **no minimum length**; only blank/whitespace
and multiline values are rejected to keep authentication enabled. After changing
the protected password file, recreate that account container to update its env.
Passwords are **not** Hope login passwords. An operator can retrieve the intended
account's password over trusted SSH; never print all credentials or Docker envs.
Certificates have the configured LAN IP SAN; clients must trust the self-signed
certificate. Only WebUI binds the configured LAN IP; gateway APIs remain loopback.
No router forwarding/public internet access is configured. No firewall changes
are needed if the allocated LAN listener is already reachable.
Browser chat uses that account's authenticated gateway and runs API, preserving
its execution boundary/concurrency limit. Both API readiness and **HTTPS** WebUI
health must pass. Separate cookie names prevent collisions between ports, but
ports on the same IP are not a cookie isolation boundary: for stronger isolation,
use per-account hostnames. WebUI grants its authenticated owner powerful access
to account files/configuration/tools. Source/dependency installers are disabled
at runtime. Recreating an existing container preserves state, keys, API ports,
WebUI ports and browser passwords. CI builds the pinned WebUI image for future
accounts; keep the above environment enabled across control redeployments.
## Provisioner interface
`create_app(settings: Settings, provisioner=...)` supports offline injection.