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 <cursoragent@cursor.com>
This commit is contained in:
Bartok
2026-05-31 22:32:55 -05:00
committed by GitHub
parent 740fb28d02
commit a60bff282e
2 changed files with 60 additions and 1 deletions

View File

@ -73,7 +73,17 @@ RUN set -eu; \
tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz; \ tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz; \
tar -C / -Jxpf /tmp/s6-overlay-arch.tar.xz; \ tar -C / -Jxpf /tmp/s6-overlay-arch.tar.xz; \
tar -C / -Jxpf /tmp/s6-overlay-symlinks-noarch.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 # Non-root user for runtime; UID can be overridden via HERMES_UID at runtime
RUN useradd -u 10000 -m -d /opt/data hermes RUN useradd -u 10000 -m -d /opt/data hermes

View File

@ -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."
)