From be3c160a85fa8d3a59df897d8809843c7d0e021a Mon Sep 17 00:00:00 2001 From: HexLab98 Date: Sat, 18 Jul 2026 14:37:40 +0700 Subject: [PATCH] fix(docker): strip tini -g flags in legacy entrypoint shim MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A plain /usr/bin/tini → /init symlink forwarded tini's -g into s6-overlay's rc.init as the container CMD, causing boot loops after image updates that preserve old entrypoints (#66679). --- Dockerfile | 24 ++++++------ docker/tini-shim.sh | 89 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 11 deletions(-) create mode 100755 docker/tini-shim.sh diff --git a/Dockerfile b/Dockerfile index 6803adc2e..388056faa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -73,17 +73,19 @@ 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; \ - # #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 + rm /tmp/s6-overlay-*.tar.xz /tmp/s6-overlay.sha256 + +# #34192 / #66679: backward-compat shim for orchestration templates that +# still reference the legacy /usr/bin/tini entrypoint (Hostinger's +# 'Hermes WebUI' catalog, NAS compose projects that preserve an old +# entrypoint on image update, etc.). A plain symlink to /init made the +# path exist, but forwarded tini flags like `-g` into s6-overlay's +# rc.init as the container CMD (`rc.init: 91: -g: not found`) and +# boot-looped any `restart: unless-stopped` deploy. The shim strips the +# tini CLI surface, then exec's /init + main-wrapper — see +# docker/tini-shim.sh. Safe to drop once the affected catalogs are +# updated. +COPY --chmod=0755 docker/tini-shim.sh /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/docker/tini-shim.sh b/docker/tini-shim.sh new file mode 100755 index 000000000..573737b03 --- /dev/null +++ b/docker/tini-shim.sh @@ -0,0 +1,89 @@ +#!/bin/sh +# shellcheck shell=sh +# /usr/bin/tini — compatibility shim for legacy orchestration templates. +# +# Background +# ---------- +# The published image used to ship real `tini` as PID 1. After the +# s6-overlay migration, PID 1 is `/init`. Downstream catalogs (Hostinger +# Hermes WebUI, NAS "update" flows that preserve an old entrypoint, etc.) +# still pin entrypoints like: +# +# ["/usr/bin/tini", "-g", "--"] +# ["/usr/bin/tini", "-g", "--", "gateway", "run"] +# +# A plain `ln -sf /init /usr/bin/tini` (#34192) made the binary exist, but +# forwarded tini's own flags into s6-overlay. `/init` then handed `-g` to +# `rc.init` as the container CMD, which fails with: +# +# /run/s6/basedir/scripts/rc.init: 91: -g: not found +# +# …and with `restart: unless-stopped` the container boot-loops forever +# (#66679). +# +# This shim strips the tini CLI surface, then exec's `/init` with the +# image's main-wrapper so privilege drop + arg routing still apply. +# +# Tini flags we discard (see krallin/tini --help): +# -g / --kill-process-group, -s / --subreaper, -w, -v (repeatable), +# -h / --help, -l, --version, -p SIGNAL, -e EXIT_CODE, and `--`. +# s6-overlay already reaps zombies and forwards signals as PID 1; the +# flag semantics are intentionally no-ops here. +# +# Test hook: HERMES_TINI_SHIM_TARGET overrides the `/init` path (and +# HERMES_TINI_SHIM_WRAPPER the main-wrapper path) so unit tests can +# record argv without a real s6 tree. + +set -e + +INIT_TARGET="${HERMES_TINI_SHIM_TARGET:-/init}" +WRAPPER="${HERMES_TINI_SHIM_WRAPPER:-/opt/hermes/docker/main-wrapper.sh}" + +while [ "$#" -gt 0 ]; do + case "$1" in + --) + shift + break + ;; + -g|--kill-process-group|-s|--subreaper|-w|-l|-h|--help|--version) + shift + ;; + -v) + # tini allows repeated -v; drop them all. + shift + ;; + -p|-e) + # These take a mandatory argument. + shift + if [ "$#" -gt 0 ]; then + shift + fi + ;; + -*) + # Unknown short/long option from an older/newer tini — drop + # rather than forwarding into /init (would recreate #66679). + shift + ;; + *) + break + ;; + esac +done + +# No program left (entrypoint was just `tini -g --`) → same as the +# image's default ENTRYPOINT + empty CMD. +if [ "$#" -eq 0 ]; then + exec "$INIT_TARGET" "$WRAPPER" +fi + +# Caller already supplied the wrapper or /init — don't double-wrap. +case "$1" in + /init|"$INIT_TARGET") + exec "$@" + ;; + "$WRAPPER"|*/main-wrapper.sh) + exec "$INIT_TARGET" "$@" + ;; +esac + +exec "$INIT_TARGET" "$WRAPPER" "$@"