From be89ba866223209daa6c9611ed8fd02764c253e7 Mon Sep 17 00:00:00 2001 From: c41ms0n <193478517+c41ms0n@users.noreply.github.com> Date: Tue, 3 Mar 2026 10:02:03 +0000 Subject: [PATCH] refactor: replace faketty with headless --noninteractive daemon + PTY_TOOL switcher - Drop faketty named pipe and screen; daemon runs via exec protonmail-bridge --noninteractive so stdout/stderr reach docker logs natively and SIGTERM lands directly on the bridge - Add PTY_TOOL build ARG/ENV (dtach default, abduco, reptyr) for interactive sessions only - Split commands: init (first-time setup), run (daemon), manage (interactive CLI), attach (reattach) - PTY machinery isolated to manage/attach; restart loop and log forwarding hacks removed - Fix $1 unbound variable with ${1:-}, set -ex replaced with set -euo pipefail - chmod +x consolidated into install RUN layer; healthcheck -exc fixed to -c - CMD switched to exec form to ensure signals reach entrypoint directly --- deb/Dockerfile | 27 +++++-- deb/entrypoint.sh | 180 ++++++++++++++++++++++++++++++++++++---------- 2 files changed, 163 insertions(+), 44 deletions(-) diff --git a/deb/Dockerfile b/deb/Dockerfile index a0d8e2c..1ea6f71 100644 --- a/deb/Dockerfile +++ b/deb/Dockerfile @@ -9,20 +9,37 @@ RUN apt-get update && apt-get install -y wget binutils # Repack deb (removes unnecessary dependencies and produces /protonmail.deb) RUN bash /install.sh +# ----------------------------------------------------------------------------- + FROM debian:sid-slim LABEL maintainer="Simon Felding " +# Select PTY tool for manage/attach commands: dtach (default), abduco, reptyr +ARG PTY_TOOL=dtach +ENV PTY_TOOL=${PTY_TOOL} + EXPOSE 25/tcp EXPOSE 143/tcp WORKDIR /protonmail -# Copy bash scripts -COPY gpgparams entrypoint.sh PACKAGE /protonmail/ +# PACKAGE is only needed in the build stage; don't copy it into the final image +COPY gpgparams entrypoint.sh /protonmail/ COPY --from=build /protonmail.deb /tmp/protonmail.deb RUN apt-get update \ - && apt-get install -y --no-install-recommends /tmp/protonmail.deb socat pass libsecret-1-0 ca-certificates procps \ - && rm -rf /var/lib/apt/lists/* + && apt-get install -y --no-install-recommends \ + /tmp/protonmail.deb \ + socat pass libsecret-1-0 libfido2-1 ca-certificates procps \ + && case "${PTY_TOOL}" in \ + dtach) apt-get install -y --no-install-recommends dtach ;; \ + abduco) apt-get install -y --no-install-recommends abduco ;; \ + reptyr) apt-get install -y --no-install-recommends reptyr ;; \ + esac \ + && chmod +x /protonmail/entrypoint.sh \ + && rm -rf /tmp/protonmail.deb /var/lib/apt/lists/* -CMD ["bash", "/protonmail/entrypoint.sh"] +HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=120s \ + CMD /bin/bash -c "true < /dev/tcp/localhost/25" + +CMD ["/protonmail/entrypoint.sh", "run"] diff --git a/deb/entrypoint.sh b/deb/entrypoint.sh index 13637e5..2d2fa65 100644 --- a/deb/entrypoint.sh +++ b/deb/entrypoint.sh @@ -1,49 +1,151 @@ #!/bin/bash -set -ex +set -euo pipefail -# Initialize -if [[ $1 == init ]]; then +PTY_TOOL="${PTY_TOOL:-dtach}" +BRIDGE_SOCK=/protonmail/bridge.sock +BRIDGE_PID_FILE=/protonmail/bridge.pid - # # Parse parameters - # TFP="" # Default empty two factor passcode - # shift # skip `init` - # while [[ $# -gt 0 ]]; do - # key="$1" - # case $key in - # -u|--username) - # USERNAME="$2" - # ;; - # -p|--password) - # PASSWORD="$2" - # ;; - # -t|--twofactor) - # TWOFACTOR="$2" - # ;; - # esac - # shift - # shift - # done +# Clean stale gpg-agent sockets left from a previous run +rm -f /root/.gnupg/S.gpg-agent* 2>/dev/null || true - # Initialize pass - gpg --generate-key --batch /protonmail/gpgparams - pass init pass-key +# --- PTY helpers (only used by: init, manage, attach) --- - # Login - protonmail-bridge --cli +pty_start() { + case "${PTY_TOOL}" in + dtach) dtach -n "${BRIDGE_SOCK}" "$@" ;; + abduco) abduco -n bridge "$@" ;; + # reptyr re-attaches existing PIDs; use nohup+setsid to launch headlessly instead + reptyr) setsid "$@" /dev/null & echo $! > "${BRIDGE_PID_FILE}" ;; + esac +} -else +pty_attach() { + case "${PTY_TOOL}" in + dtach) exec dtach -a "${BRIDGE_SOCK}" -e '^\' ;; + abduco) exec abduco -a bridge ;; + reptyr) exec reptyr "$(cat "${BRIDGE_PID_FILE}")" ;; + esac +} - # socat will make the conn appear to come from 127.0.0.1 - # ProtonMail Bridge currently expects that. - # It also allows us to bind to the real ports :) - socat TCP-LISTEN:25,fork TCP:127.0.0.1:1025 & - socat TCP-LISTEN:143,fork TCP:127.0.0.1:1143 & +detach_hint() { + case "${PTY_TOOL}" in + dtach|abduco) echo "Ctrl+\\" ;; + reptyr) echo "Ctrl+C" ;; + esac +} - # Start protonmail - # Fake a terminal, so it does not quit because of EOF... - rm -f faketty - mkfifo faketty - cat faketty | protonmail-bridge --cli +# Wait up to $1 seconds for the bridge socket (or PID file) to appear +wait_for_session() { + local timeout="${1:-10}" + local elapsed=0 + while [[ "${elapsed}" -lt "${timeout}" ]]; do + case "${PTY_TOOL}" in + dtach|abduco) [[ -S "${BRIDGE_SOCK}" ]] && return 0 ;; + reptyr) [[ -f "${BRIDGE_PID_FILE}" ]] && return 0 ;; + esac + sleep 1 + (( elapsed++ )) || true + done + echo "ERROR: bridge session did not start within ${timeout}s." >&2 + return 1 +} -fi +# --- Commands --- + +CMD="${1:-run}" + +case "${CMD}" in + + init) + # One-time setup: generate GPG key, init password store, interactive login. + # Run as: docker run -it init + gpg --generate-key --batch /protonmail/gpgparams + pass init pass-key + exec protonmail-bridge --cli + ;; + + manage) + # Open an interactive --cli session for account management (add/remove accounts etc). + # Run as: docker run -it --rm -v manage + # NOTE: Stop the running daemon container first to avoid port/lock conflicts. + CONTAINER_ID=$(hostname) + echo " Starting management session... [PTY_TOOL=${PTY_TOOL}]" + pty_start protonmail-bridge --cli + + # Wait for the session socket/pid to appear before printing attach instructions + wait_for_session 10 + + echo " Management session ready." + echo " Attach: docker exec -it ${CONTAINER_ID} /protonmail/entrypoint.sh attach" + echo " Detach: $(detach_hint)" + ;; + + attach) + # Reattach to a running manage session. + case "${PTY_TOOL}" in + dtach|abduco) + if [[ ! -S "${BRIDGE_SOCK}" ]]; then + echo "ERROR: No active session found (${BRIDGE_SOCK} does not exist)." >&2 + echo " Start one first: docker exec -it \$(hostname) /protonmail/entrypoint.sh manage" >&2 + exit 1 + fi + ;; + reptyr) + if [[ ! -f "${BRIDGE_PID_FILE}" ]]; then + echo "ERROR: No active session found (${BRIDGE_PID_FILE} does not exist)." >&2 + echo " Start one first: docker exec -it \$(hostname) /protonmail/entrypoint.sh manage" >&2 + exit 1 + fi + ;; + esac + pty_attach + ;; + + run) + # Daemon mode: --noninteractive runs headless, output goes directly to docker logs. + CONTAINER_ID=$(hostname) + echo "========================================" + echo " ProtonMail Bridge daemon starting..." + echo " Container: ${CONTAINER_ID}" + echo "" + echo " Available commands:" + echo " First-time setup:" + echo " docker run -it init" + echo "" + echo " Manage accounts (stop daemon first):" + echo " docker run -it --rm -v manage" + echo "" + echo " Attach to a running manage session:" + echo " docker exec -it ${CONTAINER_ID} /protonmail/entrypoint.sh attach" + echo "" + echo " View logs:" + echo " docker logs -f ${CONTAINER_ID}" + echo "========================================" + + # socat forwards standard ports to bridge's localhost-only listener ports. + socat TCP-LISTEN:25,fork,reuseaddr TCP:127.0.0.1:1025,nodelay & + SOCAT_SMTP_PID=$! + socat TCP-LISTEN:143,fork,reuseaddr TCP:127.0.0.1:1143,nodelay & + SOCAT_IMAP_PID=$! + + # Verify both socat processes started successfully + sleep 1 + for pid in "${SOCAT_SMTP_PID}" "${SOCAT_IMAP_PID}"; do + if ! kill -0 "${pid}" 2>/dev/null; then + echo "ERROR: socat port-forward (pid ${pid}) failed to start." >&2 + exit 1 + fi + done + + # exec replaces the shell so the bridge becomes the waited-on process. + # docker stop sends SIGTERM directly to it. socat processes are reaped on exit. + exec protonmail-bridge --noninteractive + ;; + + *) + echo "Usage: entrypoint.sh [init|manage|attach|run]" >&2 + exit 1 + ;; + +esac