This commit is contained in:
c41ms0n 2026-03-27 00:44:23 +07:00 committed by GitHub
commit aab2506dac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 211 additions and 38 deletions

View File

@ -1,33 +1,52 @@
# The build image could be golang, but it currently does not support riscv64. Only debian:sid does, at the time of writing. ### The Deb install is just a repack of the official ProtonMail Bridge deb package with less dependencies.
FROM debian:sid-slim AS build ### I recommend you don't use this. It's here for legacy reasons.
FROM golang:1.26-trixie AS build
ARG version ARG version
ENV version=${version}
# Install dependencies
RUN apt-get update && apt-get install -y golang build-essential libsecret-1-dev RUN apt-get update && apt-get install -y build-essential libsecret-1-dev libfido2-dev libcbor-dev
# Build # Build
ADD https://github.com/ProtonMail/proton-bridge.git#${version} /build/ ADD https://github.com/ProtonMail/proton-bridge.git#${version} /build/
WORKDIR /build/ WORKDIR /build/
RUN make build-nogui vault-editor RUN make build-nogui vault-editor
FROM debian:sid-slim # -----------------------------------------------------------------------------
FROM debian:trixie-slim
LABEL maintainer="Simon Felding <sife@adm.ku.dk>" LABEL maintainer="Simon Felding <sife@adm.ku.dk>"
# Select PTY tool for manage/attach commands: dtach (default), abduco, reptyr
ARG PTY_TOOL=dtach
ENV PTY_TOOL=${PTY_TOOL}
EXPOSE 25/tcp EXPOSE 25/tcp
EXPOSE 143/tcp EXPOSE 143/tcp
# Install dependencies and protonmail bridge WORKDIR /protonmail
RUN apt-get update \
&& apt-get install -y --no-install-recommends socat pass libsecret-1-0 ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Copy bash scripts
COPY gpgparams entrypoint.sh /protonmail/ COPY gpgparams entrypoint.sh /protonmail/
# Copy protonmail # Copy protonmail
COPY --from=build /build/bridge /protonmail/ COPY --from=build /build/bridge /protonmail/
COPY --from=build /build/proton-bridge /protonmail/ COPY --from=build /build/proton-bridge /protonmail/
COPY --from=build /build/vault-editor /protonmail/ COPY --from=build /build/vault-editor /protonmail/
ENTRYPOINT ["bash", "/protonmail/entrypoint.sh"] RUN apt-get update \
&& apt-get install -y --no-install-recommends \
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 /var/lib/apt/lists/*
HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=120s \
CMD /bin/bash -c "true < /dev/tcp/localhost/25"
ENTRYPOINT ["/protonmail/entrypoint.sh"]
CMD ["run"]

View File

@ -1,35 +1,189 @@
#!/bin/bash #!/bin/bash
set -ex set -euo pipefail
# Initialize PTY_TOOL="${PTY_TOOL:-dtach}"
if [[ $1 == init ]]; then BRIDGE_SOCK=/protonmail/bridge.sock
BRIDGE_PID_FILE=/protonmail/bridge.pid
# Initialize pass # Clean stale gpg-agent sockets left from a previous run
rm -f /root/.gnupg/S.gpg-agent* 2>/dev/null || true
# --- PTY helpers (only used by: init, manage, attach) ---
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 &>/dev/null & echo $! > "${BRIDGE_PID_FILE}" ;;
esac
}
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
}
detach_hint() {
case "${PTY_TOOL}" in
dtach|abduco) echo "Ctrl+\\" ;;
reptyr) echo "Ctrl+C" ;;
esac
}
# 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
}
# --- Commands ---
CMD="${1:-run}"
case "${CMD}" in
init)
# One-time setup: generate GPG key, init password store, interactive login.
# Run as: docker run -it <image> init
gpg --generate-key --batch /protonmail/gpgparams gpg --generate-key --batch /protonmail/gpgparams
pass init pass-key pass init pass-key
exec /protonmail/proton-bridge --cli
;;
# Kill the other instance as only one can be running at a time. manage)
# This allows users to run entrypoint init inside a running conainter # Open an interactive --cli session for account management (add/remove accounts etc).
# which is useful in a k8s environment. # Run as: docker run -it --rm -v <data-volume> <image> manage
# || true to make sure this would not fail in case there is no running instance. # NOTE: Stop the running daemon container first to avoid port/lock conflicts.
pkill protonmail-bridge || true CONTAINER_ID=$(hostname)
echo " Starting management session... [PTY_TOOL=${PTY_TOOL}]"
pty_start /protonmail/proton-bridge --cli
# Login # Wait for the session socket/pid to appear before printing attach instructions
/protonmail/proton-bridge --cli $@ wait_for_session 10
else echo " Management session ready."
echo " Attach: docker exec -it ${CONTAINER_ID} /protonmail/entrypoint.sh attach"
echo " Detach: $(detach_hint)"
# socat will make the conn appear to come from 127.0.0.1 # Block so the container stays alive for `docker exec attach`.
# ProtonMail Bridge currently expects that. # If stdin is a tty (docker run -it), jump straight into the session.
# It also allows us to bind to the real ports :) if [[ -t 0 ]]; then
socat TCP-LISTEN:25,fork TCP:127.0.0.1:1025 & pty_attach
socat TCP-LISTEN:143,fork TCP:127.0.0.1:1143 & else
# No tty: wait until the bridge session disappears then exit cleanly.
while true; do
case "${PTY_TOOL}" in
dtach|abduco) [[ -S "${BRIDGE_SOCK}" ]] || break ;;
reptyr) kill -0 "$(cat "${BRIDGE_PID_FILE}" 2>/dev/null)" 2>/dev/null || break ;;
esac
sleep 2
done
fi
;;
# Start protonmail attach)
# Fake a terminal, so it does not quit because of EOF... # Reattach to a running manage session.
rm -f faketty case "${PTY_TOOL}" in
mkfifo faketty dtach|abduco)
cat faketty | /protonmail/proton-bridge --cli $@ 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
;;
fi 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 <image> init"
echo ""
echo " Manage accounts (stop daemon first):"
echo " docker run -it --rm -v <data-volume> <image> 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 "========================================"
# Start bridge in background so we can wait for it to bind its ports
# before socat begins accepting connections.
/protonmail/proton-bridge --noninteractive &
BRIDGE_PID=$!
# Wait for bridge to open its local SMTP and IMAP ports (up to 60s)
echo " Waiting for bridge ports 1025/1143..."
for port in 1025 1143; do
elapsed=0
until socat -u OPEN:/dev/null TCP:127.0.0.1:${port} 2>/dev/null; do
sleep 1
(( elapsed++ )) || true
if [[ "${elapsed}" -ge 60 ]]; then
echo "ERROR: bridge port ${port} did not open within 60s." >&2
kill "${BRIDGE_PID}" 2>/dev/null || true
exit 1
fi
done
echo " Port ${port} ready."
done
# socat forwards standard ports to bridge's localhost-only listener ports.
# retry=30,interval=2 handles transient bridge restarts without dropping connections.
socat TCP-LISTEN:25,fork,reuseaddr TCP:127.0.0.1:1025,nodelay,retry=30,interval=2 &
SOCAT_SMTP_PID=$!
socat TCP-LISTEN:143,fork,reuseaddr TCP:127.0.0.1:1143,nodelay,retry=30,interval=2 &
SOCAT_IMAP_PID=$!
# Verify both socat processes started
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
kill "${BRIDGE_PID}" 2>/dev/null || true
exit 1
fi
done
# Wait on bridge; if it exits, bring down socat too.
wait "${BRIDGE_PID}"
kill "${SOCAT_SMTP_PID}" "${SOCAT_IMAP_PID}" 2>/dev/null || true
;;
*)
echo "Usage: entrypoint.sh [init|manage|attach|run]" >&2
exit 1
;;
esac