feat: enhance Docker setup and startup script for Hermes WebUI#1686
feat: enhance Docker setup and startup script for Hermes WebUI#1686binhpt310 wants to merge 1 commit into
Conversation
- Added git and xz-utils to Dockerfile for improved build capabilities. - Updated docker-compose.yml to specify build context and Dockerfile location. - Enhanced start.sh to handle environment variable parsing more robustly and set Python executable path.
ReviewReading the diff at 🔴 Blocker: syntax error in the regression test file
ROUTES_PY = (REPO_ROOT / "api" / "routes.py").read_text(encoding="utf-8")
AUTH_PY=*** / "api" / "auth.py").read_text(encoding="utf-8")That looks like an editor/tool artefact from a redacted diff — the test file simply will not parse. AUTH_PY = (REPO_ROOT / "api" / "auth.py").read_text(encoding="utf-8")…which is then asserted against at line 107 ( 🟡 AuthenticationThe endpoint goes through What concerns me more is the default-no-auth case: a fresh WebUI install with no password set has
Note: the polled 🟢 Implementation looks good
def _cpu_percent() -> float:
start = _read_proc_stat_cpu()
time.sleep(_CPU_SAMPLE_SECONDS) # 0.05s
end = _read_proc_stat_cpu()
return _cpu_delta_percent(start, end)50ms blocking sleep on every poll is fine in practice (BaseHTTPServer is threaded), but worth noting this will serialize behind the GIL alongside other GETs. At 5s polling per client × small instance, totally negligible.
The frontend at One small nit: Action items
Once the test file parses I'd be happy with this. Closes #693 — the feature itself is well-scoped and matches the feature request shape. |
|
Thanks for the patch. Three of the symptoms you describe are real ( CI is red on this branch
The guard at def test_start_sh_and_bootstrap_equivalent_env_loading(self):
start_sh = (REPO_ROOT / "start.sh").read_text(encoding="utf-8")
...
assert "source" in start_sh and ".env" in start_sh, (
"start.sh should still source .env (regression guard)"
)Your replacement loop never says set -a
source <(grep -vE "^[[:space:]]*(export[[:space:]]+)?(UID|GID|EUID|EGID|PPID|PID)=" "${REPO_ROOT}/.env")
set +aSame effect as your filter, half the lines, no test churn. Compose context change is breakingThe PR rewrites build:
context: ..
dockerfile: hermes-webui/Dockerfileand the Dockerfile then does The canonical user flow is clone the repo, cd in, then bring up the stack. With this PR applied, that breaks: For two-container layouts, The /app/venv early-exit in start.sh bypasses docker_init.bashif [[ -f "/.within_container" && -x "/app/venv/bin/python" ]]; then
export HERMES_WEBUI_PYTHON="/app/venv/bin/python"
exec "/app/venv/bin/python" "${REPO_ROOT}/bootstrap.py" --no-browser "$@"
fi
Also: the Dockerfile pre-bakes Pieces that look correct and should be split out
RecommendationSplit this into two surgical PRs:
The pre-bake-agent-source plus change-compose-context piece needs a separate design conversation. One question on the |
|
Thanks @binhpt310 — your fixes for the We need to defer this PR from the v0.51.5 release pass because the Dockerfile + docker-compose.yml shape it introduces makes the build context-dependent on a sibling repo: # docker-compose.yml:
build:
context: ..
dockerfile: hermes-webui/DockerfileThis means the build now requires a parent directory containing both …will hit There are a few ways forward — pick whichever fits best: Option A — Build arg (smallest change): ARG WITH_AGENT_SOURCE=0
RUN if [ "$WITH_AGENT_SOURCE" = "1" ]; then \
mkdir -p /opt/hermes && \
cp -r /tmp/hermes-agent-desktop/hermes-agent /opt/hermes/; \
fi
Option B — Vendor a download helper: Option C — Drop the agent-pre-bake and keep the rest: The other improvements in this PR are all valuable on their own — the A secondary nit (not blocking): the start.sh re-execs as Happy to absorb a v2 with Option A or C as soon as it lands. The infrastructure half of this is unambiguously good. |
4 PRs (1 surface addition, 3 fixes): - nesquena#1688 VPS resource health Insights panel (@Michaelyklam, closes nesquena#693) - nesquena#1709 preserve scroll on stream completion (@Michaelyklam, closes nesquena#1690) - nesquena#1711 hide rename tooltip on folders (@nesquena-hermes, closes nesquena#1710) - nesquena#1712 guard localStorage.setItem against QuotaExceededError (@24601) Tests: 4504 → 4527 (+23). Opus: SHIP, 6/6 verification clean. Held back: nesquena#1686 (Docker enhance) — Opus flagged sibling-repo dep that breaks standalone clones. Left open for follow-up. Co-authored-by: Michael Lam <Michaelyklam1@gmail.com> Co-authored-by: 24601 <noreply@github.com>
Closing — operational hardening salvaged into focused follow-up; structural Docker changes deferredThanks @binhpt310 — closing this out cleanly with a salvage map. What shipsThe three operational-hardening fixes from this PR were the unambiguously net-new and shippable cluster, so I extracted them into a focused follow-up: PR #1969. That PR ships:
11 new regression tests in
What was deferredTwo larger structural changes from this PR don't ship in the follow-up: A. Pre-baking hermes-agent source into the image# Dockerfile (your version, lines ~80-100):
COPY hermes-agent-desktop/hermes-agent /opt/hermes/
COPY --chown=hermeswebuitoo:hermeswebuitoo hermes-webui/ /apptoo/# docker-compose.yml (your version):
build:
context: ..
dockerfile: hermes-webui/DockerfileThis requires a parent directory with both …would hit If you'd like to retry, the cleanest shape is a build arg with the sibling-repo COPY gated behind it: ARG WITH_AGENT_SOURCE=0
RUN if [ "$WITH_AGENT_SOURCE" = "1" ]; then \
mkdir -p /opt/hermes && \
cp -r /tmp/hermes-agent-desktop/hermes-agent /opt/hermes/; \
fi
ENV HERMES_WEBUI_AGENT_DIR=/opt/hermes…with Happy to review a follow-up PR shaped like that. B. Pre-installing Node.js 22 LTS system-wideThe motivation (avoid
Worth evaluating separately from the three operational fixes that just shipped. If you want to refile this with one of those shapes, happy to review. Why split rather than fix in-placeYour three operational fixes are independent — they don't depend on the agent prebake or Node install. Salvaging them now means:
Thanks again — these are exactly the kind of operational fixes that catch real user-visible bugs. Looking forward to the next round. |
… apt deps) Three independent operational hardening fixes salvaged from PR #1686 (@binhpt310) after the parent PR was deferred over a separate sibling-repo build-context concern unrelated to these fixes: 1. start.sh's .env loader now filters readonly bash vars (UID, GID, EUID, EGID, PPID) before `source`-ing. docker-compose.yml's macOS instructions document `echo "UID=$(id -u)" >> .env` to set host UID/GID for bind-mount permission fixing — that .env was crashing start.sh with `UID: readonly variable` when `set -a; source ...; set +a` tried to assign to those names. Replaced with `source <(grep -vE '^[[:space:]]*(export[[:space:]]+)?(UID|GID|EUID|EGID|PPID)=' "${REPO_ROOT}/.env")`. The bootstrap regression guard at tests/test_bootstrap_dotenv.py:181 still passes — both `source` and `.env` are still on the modified line. 2. start.sh now defensively re-execs as the unprivileged hermeswebui user when invoked as root. Fires only when EUID==0 AND a hermeswebui user actually exists AND sudo is on PATH — so it's a no-op on host machines without the container user setup. The production image's entrypoint (docker_init.bash) already drops to hermeswebui before invoking start.sh, so this is a no-op on the canonical container path; it only matters for `sudo ./start.sh` or accidental root shells inside the container during interactive debugging. 3. Dockerfile installs xz-utils + git apt packages. xz-utils is required to decompress .tar.xz archives (e.g. Node.js distribution tarballs); git is needed for `git describe` (powers WEBUI_VERSION resolution at api/updates.py:_detect_webui_version) and any clone-based agent install path. Both are tiny apt packages on top of python:3.12-slim with no measurable image-size impact. What's NOT in this commit (deferred from #1686): - Pre-baking hermes-agent source into the image via `COPY hermes-agent-desktop/hermes-agent /opt/hermes/` plus a build-context flip to `..`. Requires a sibling-repo layout that breaks the canonical `git clone hermes-webui && cd hermes-webui && docker compose build` flow. The right shape is a build arg gating the COPY behind --build-arg WITH_AGENT_SOURCE=1; left to a separate PR. - Pre-installing Node.js 22 LTS system-wide. Real motivation but worth evaluating the fix shape (full Node bake vs. opt-in vs. layer cache) separately from these three operational fixes. Tests: tests/test_docker_env_readonly_vars.py — 11 tests (4 source-grep on the start.sh filter pattern + 5 behavioral that actually run bash against synthetic .env files containing readonly vars + 2 Dockerfile package-presence tests). All 11 pass. Behavioral tests skip if bash is not on PATH. Full suite: 5028 → 5036 passing (+8 net new after pytest collection counted some behavioral tests under skip), 0 regressions, 147.84s. Closes the operational-hardening portion of #1686. Co-authored-by: binhpt310 <binhpt310@users.noreply.github.com>
fix(docker): salvage operational hardening from #1686 — .env readonly-var parser + xz-utils/git apt deps + root re-exec
Problem
Running
./start.shinside the Docker container causes two failures:xz: Cannot exec: No such file or directory—xz-utilsmissingnpm installhangs indefinitely during agent auto-install.envUID/GID variables are readonly in bashSolution
xz-utilsandgitto DockerfileHERMES_WEBUI_AGENT_DIRto skip network installsstart.shto safely parse.env(skip readonly vars)start.shto avoid permission issuesdocker-compose.ymlbuild context to include agent sourceTesting
docker compose build --no-cache docker compose up -d docker exec -it hermes-webui-hermes-webui-1 /apptoo/start.sh