diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f447192..6afc5d34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Docker startup no longer fails when a bind-mounted `~/.hermes/hermes-agent/.git/objects` tree contains read-only git object packs. The root init ownership pass now skips that git object subtree while still chowning the rest of `/home/hermeswebui`, so macOS Docker Desktop bind mounts can start WebUI without requiring writable ownership over agent git internals (fixes #2237). + ### Added - **PR #2099** by @dobby-d-elf — Adds an opt-in `Settings → Preferences → Fade text effect` toggle (off by default). When enabled, newly streamed output tokens are revealed through an adaptive playout buffer and animated with an opacity-only fade similar to ChatGPT and other frontier LLM apps. Implementation details: fade locked per stream to avoid mid-stream toggle rewind; reduced-motion users get non-animated text; live cursor hidden while fade is active; custom renderer on `streaming-markdown` parser wraps only newly-appended words; animated spans replace themselves with plain text on `animationend` (no long-lived wrapper buildup in long responses); unsafe streamed `href`/`src` values blocked in fade renderer `set_attr` path. Performance tuning: 200ms base fade duration scaling to 350ms for fast output, 16ms word stagger, 320ms done-drain wait cap, 160 wps visual cap, max 2-3 words/frame, brief pauses after sentence punctuation. Default-off means existing users see no change. 293-line regression test pinning the contract. diff --git a/docker_init.bash b/docker_init.bash index 00d521c9..247bc9f8 100644 --- a/docker_init.bash +++ b/docker_init.bash @@ -181,6 +181,15 @@ load_env() { fi } +chown_home_hermeswebui() { + # macOS Docker bind mounts can expose hermes-agent git object packs as + # read-only host files. The runtime only needs to read those existing objects; + # requiring chown on them makes startup fail before WebUI can run (#2237). + find /home/hermeswebui \ + -path "/home/hermeswebui/.hermes/hermes-agent/.git/objects" -prune \ + -o -exec chown -h "${WANTED_UID}:${WANTED_GID}" {} + +} + # The production image does not ship sudo. The entrypoint starts as root only # long enough to align the hermeswebui UID/GID with mounted volumes, prepare # root-owned paths, and then drop privileges for the server process. @@ -209,7 +218,7 @@ if [ "A${whoami}" == "Aroot" ]; then usermod -o -u "${WANTED_UID}" hermeswebui || error_exit "Failed to set UID of hermeswebui user" fi - chown -R "${WANTED_UID}:${WANTED_GID}" /home/hermeswebui || error_exit "Failed to set owner of /home/hermeswebui" + chown_home_hermeswebui || error_exit "Failed to set owner of /home/hermeswebui" echo ""; echo "-- Preparing /app for the hermeswebui runtime user" mkdir -p /app || error_exit "Failed to create /app directory" diff --git a/tests/test_issue2237_docker_chown_git_objects.py b/tests/test_issue2237_docker_chown_git_objects.py new file mode 100644 index 00000000..99a69829 --- /dev/null +++ b/tests/test_issue2237_docker_chown_git_objects.py @@ -0,0 +1,33 @@ +"""Regression coverage for #2237 Docker startup chown on git object packs.""" + +from pathlib import Path +import subprocess + + +REPO = Path(__file__).resolve().parents[1] +INIT_SCRIPT = (REPO / "docker_init.bash").read_text(encoding="utf-8") + + +def test_home_chown_skips_hermes_agent_git_objects(): + assert "chown_home_hermeswebui()" in INIT_SCRIPT + assert "/home/hermeswebui/.hermes/hermes-agent/.git/objects" in INIT_SCRIPT + assert "-prune" in INIT_SCRIPT + assert 'chown -h "${WANTED_UID}:${WANTED_GID}"' in INIT_SCRIPT + + +def test_root_init_uses_git_object_safe_chown_helper(): + root_start = INIT_SCRIPT.index('if [ "A${whoami}" == "Aroot" ]; then') + root_restart = INIT_SCRIPT.index("exec su", root_start) + root_section = INIT_SCRIPT[root_start:root_restart] + + assert "chown_home_hermeswebui || error_exit" in root_section + assert 'chown -R "${WANTED_UID}:${WANTED_GID}" /home/hermeswebui' not in root_section + + +def test_docker_init_bash_syntax_still_valid(): + result = subprocess.run( + ["bash", "-n", str(REPO / "docker_init.bash")], + capture_output=True, + text=True, + ) + assert result.returncode == 0, result.stderr