From a60bff282ef8bfe9b191966bff71b86d7e4b38c9 Mon Sep 17 00:00:00 2001 From: Bartok Date: Sun, 31 May 2026 22:32:55 -0500 Subject: [PATCH] fix(docker): add /usr/bin/tini compatibility shim for legacy wrappers (#34192) (#34382) #34192 reports Hostinger's 'Hermes WebUI' catalog crashes on startup with: /usr/bin/tini: No such file or directory The image moved from tini to s6-overlay as PID 1 (/init) earlier in 2026. Orchestration templates that still pin /usr/bin/tini as the entrypoint \u2014 like the Hostinger Hermes WebUI catalog \u2014 have no binary to exec and the container crashes immediately. Hermes has no control over the Hostinger catalog template, but we can make the image backward-compatible by symlinking /usr/bin/tini -> /init during the s6-overlay install step. External wrappers that exec /usr/bin/tini will land on the same s6-overlay reaper they would have landed on if they'd used the canonical /init entrypoint. The image's own ENTRYPOINT continues to be /init verbatim \u2014 the shim is purely for legacy external wrappers, not for the image's own runtime path. Once affected catalogs are updated, the symlink can be removed. Other issues #34192 raises that are NOT addressed by this PR: * Problem #2 (UID 1024 vs 10000 mismatch): already fixed by #33148 (S6_KEEP_ENV=1) and #32412 (with-contenv shebangs). The Hostinger template likely needs to update its env-var propagation. * Problem #3 (incompatible session formats): RFC for pluggable SessionDB is tracked in #23717. * Problem #4 (Telegram polling conflict): an operations problem on Hostinger's side, not in this codebase. This PR is scoped to the one issue that can be fixed inside Dockerfile: the missing /usr/bin/tini binary. Tests (3 in test_dockerfile_tini_compat_shim.py): - test_tini_compat_symlink_present Guard: the symlink line must exist in Dockerfile. - test_tini_compat_comment_explains_why The #34192 anchor comment must be present so future readers know why the shim is there (avoid accidental removal). - test_entrypoint_still_init_not_tini Sanity check: ENTRYPOINT remains /init (s6-overlay). The shim is only for external wrappers. Refs: #34192 Partial fix: addresses the immediate tini-binary crash. Catalog-side fixes still needed by Hostinger for the UID and session-format problems documented in the issue. Co-authored-by: Cursor --- Dockerfile | 12 +++++- tests/test_dockerfile_tini_compat_shim.py | 49 +++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 tests/test_dockerfile_tini_compat_shim.py diff --git a/Dockerfile b/Dockerfile index f1e04a3b6..660257d7c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -73,7 +73,17 @@ RUN set -eu; \ tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz; \ tar -C / -Jxpf /tmp/s6-overlay-arch.tar.xz; \ tar -C / -Jxpf /tmp/s6-overlay-symlinks-noarch.tar.xz; \ - rm /tmp/s6-overlay-*.tar.xz /tmp/s6-overlay.sha256 + rm /tmp/s6-overlay-*.tar.xz /tmp/s6-overlay.sha256; \ + # #34192: backward-compat shim for orchestration templates that still\ + # reference the legacy /usr/bin/tini entrypoint (e.g. Hostinger's\ + # 'Hermes WebUI' catalog). The image has moved to s6-overlay /init\ + # as PID 1 (see ENTRYPOINT below + the migration comment at the top\ + # of this file), but external wrappers pinned to /usr/bin/tini will\ + # crash with 'tini: No such file or directory' on startup. The shim\ + # symlinks /usr/bin/tini -> /init so legacy wrappers exec the right\ + # PID-1 reaper without behavior change for users on the current\ + # ENTRYPOINT. Safe to drop once the affected catalogs are updated.\ + ln -sf /init /usr/bin/tini # Non-root user for runtime; UID can be overridden via HERMES_UID at runtime RUN useradd -u 10000 -m -d /opt/data hermes diff --git a/tests/test_dockerfile_tini_compat_shim.py b/tests/test_dockerfile_tini_compat_shim.py new file mode 100644 index 000000000..e396c8625 --- /dev/null +++ b/tests/test_dockerfile_tini_compat_shim.py @@ -0,0 +1,49 @@ +"""Regression test for #34192 — Dockerfile must keep the tini compat shim +for orchestration templates that still reference /usr/bin/tini. + +This is a documentation-as-test guard: removing the shim is a real +choice, but it should be done deliberately (e.g. once Hostinger's +'Hermes WebUI' catalog updates to /init) and not by accident. +""" + +from __future__ import annotations + +from pathlib import Path + + +def _dockerfile_text() -> str: + return (Path(__file__).parent.parent / "Dockerfile").read_text(encoding="utf-8") + + +def test_tini_compat_symlink_present(): + """The /usr/bin/tini -> /init symlink line must exist for #34192.""" + df = _dockerfile_text() + assert "ln -sf /init /usr/bin/tini" in df, ( + "Dockerfile must keep the tini compat symlink (#34192). " + "Removing it breaks orchestration templates that still pin " + "/usr/bin/tini as the entrypoint (Hostinger 'Hermes WebUI' " + "catalog as of v0.14.x)." + ) + + +def test_tini_compat_comment_explains_why(): + """The symlink line is comment-anchored to #34192 so a future reader + knows why it exists. Removing the comment makes it look like dead + code worth deleting.""" + df = _dockerfile_text() + assert "#34192" in df, ( + "The Dockerfile tini compat shim must keep its #34192 anchor " + "comment so future maintainers know why the symlink is there." + ) + + +def test_entrypoint_still_init_not_tini(): + """Sanity check: the actual ENTRYPOINT is still /init (s6-overlay). + The shim is for legacy external wrappers, not for the image's own + runtime — that path must continue to use the canonical /init.""" + df = _dockerfile_text() + assert 'ENTRYPOINT [ "/init"' in df, ( + "Dockerfile ENTRYPOINT must remain /init (s6-overlay). The " + "tini shim is only for external wrappers that haven't been " + "updated yet." + )