Skip to content

Commit 5ebc2d1

Browse files
committed
chore(deb): sync entrypoint and Dockerfile from build/
- Replace legacy entrypoint with the current build/ version (PTY_TOOL support, init/manage/attach/run commands, SIGTERM trap, port-readiness liveness check, abduco session detection, <container> placeholder in error messages); only difference is binary name protonmail-bridge instead of /protonmail/proton-bridge - Add ARG/ENV PTY_TOOL and conditional apt install of dtach/abduco/reptyr - Add HEALTHCHECK on 127.0.0.1 for all four ports (25, 143, 1025, 1143) - Switch from CMD bash ... to ENTRYPOINT + CMD ["run"]
1 parent 51cabd4 commit 5ebc2d1

2 files changed

Lines changed: 261 additions & 48 deletions

File tree

deb/Dockerfile

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ RUN bash /install.sh
1212
FROM debian:sid-slim
1313
LABEL maintainer="Simon Felding <sife@adm.ku.dk>"
1414

15+
# Select PTY tool for manage/attach commands: dtach (default), abduco, reptyr
16+
ARG PTY_TOOL=dtach
17+
ENV PTY_TOOL=${PTY_TOOL}
18+
1519
EXPOSE 25/tcp
1620
EXPOSE 143/tcp
1721

@@ -23,6 +27,17 @@ COPY --from=build /protonmail.deb /tmp/protonmail.deb
2327

2428
RUN apt-get update \
2529
&& apt-get install -y --no-install-recommends /tmp/protonmail.deb socat pass libsecret-1-0 ca-certificates procps \
30+
&& case "${PTY_TOOL}" in \
31+
dtach) apt-get install -y --no-install-recommends dtach ;; \
32+
abduco) apt-get install -y --no-install-recommends abduco ;; \
33+
reptyr) apt-get install -y --no-install-recommends reptyr ;; \
34+
*) echo "Unsupported PTY_TOOL: ${PTY_TOOL}. Supported values are: dtach, abduco, reptyr." >&2; exit 1 ;; \
35+
esac \
36+
&& chmod +x /protonmail/entrypoint.sh \
2637
&& rm -rf /var/lib/apt/lists/*
2738

28-
CMD ["bash", "/protonmail/entrypoint.sh"]
39+
HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=120s \
40+
CMD /bin/bash -c "true < /dev/tcp/127.0.0.1/25 && true < /dev/tcp/127.0.0.1/143 && true < /dev/tcp/127.0.0.1/1025 && true < /dev/tcp/127.0.0.1/1143"
41+
42+
ENTRYPOINT ["/protonmail/entrypoint.sh"]
43+
CMD ["run"]

deb/entrypoint.sh

Lines changed: 245 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,247 @@
11
#!/bin/bash
22

3-
set -ex
4-
5-
# Initialize
6-
if [[ $1 == init ]]; then
7-
8-
# # Parse parameters
9-
# TFP="" # Default empty two factor passcode
10-
# shift # skip `init`
11-
# while [[ $# -gt 0 ]]; do
12-
# key="$1"
13-
# case $key in
14-
# -u|--username)
15-
# USERNAME="$2"
16-
# ;;
17-
# -p|--password)
18-
# PASSWORD="$2"
19-
# ;;
20-
# -t|--twofactor)
21-
# TWOFACTOR="$2"
22-
# ;;
23-
# esac
24-
# shift
25-
# shift
26-
# done
27-
28-
# Initialize pass
29-
gpg --generate-key --batch /protonmail/gpgparams
30-
pass init pass-key
31-
32-
# Login
33-
protonmail-bridge --cli
34-
35-
else
36-
37-
# socat will make the conn appear to come from 127.0.0.1
38-
# ProtonMail Bridge currently expects that.
39-
# It also allows us to bind to the real ports :)
40-
socat TCP-LISTEN:25,fork TCP:127.0.0.1:1025 &
41-
socat TCP-LISTEN:143,fork TCP:127.0.0.1:1143 &
42-
43-
# Start protonmail
44-
# Fake a terminal, so it does not quit because of EOF...
45-
rm -f faketty
46-
mkfifo faketty
47-
cat faketty | protonmail-bridge --cli
48-
49-
fi
3+
set -euo pipefail
4+
5+
PTY_TOOL="${PTY_TOOL:-dtach}"
6+
BRIDGE_SOCK=/protonmail/bridge.sock
7+
BRIDGE_PID_FILE=/protonmail/bridge.pid
8+
9+
# Validate PTY_TOOL early and ensure the selected binary is present.
10+
case "${PTY_TOOL}" in
11+
dtach|abduco|reptyr)
12+
if ! command -v "${PTY_TOOL}" &>/dev/null; then
13+
echo "ERROR: PTY_TOOL=${PTY_TOOL} but '${PTY_TOOL}' was not found in PATH." >&2
14+
exit 1
15+
fi
16+
;;
17+
*)
18+
echo "ERROR: PTY_TOOL=${PTY_TOOL} is not supported. Valid values: dtach, abduco, reptyr." >&2
19+
exit 1
20+
;;
21+
esac
22+
23+
# Clean stale gpg-agent sockets left from a previous run
24+
rm -f /root/.gnupg/S.gpg-agent* 2>/dev/null || true
25+
26+
# --- PTY helpers (only used by: init, manage, attach) ---
27+
28+
pty_start() {
29+
case "${PTY_TOOL}" in
30+
dtach) dtach -n "${BRIDGE_SOCK}" "$@" ;;
31+
abduco) abduco -n bridge "$@" ;;
32+
# reptyr re-attaches existing PIDs; use nohup+setsid to launch headlessly instead
33+
reptyr) setsid "$@" </dev/null &>/dev/null & echo $! > "${BRIDGE_PID_FILE}" ;;
34+
*) echo "ERROR: pty_start: unsupported PTY_TOOL=${PTY_TOOL}" >&2; exit 1 ;;
35+
esac
36+
}
37+
38+
pty_attach() {
39+
case "${PTY_TOOL}" in
40+
dtach) exec dtach -a "${BRIDGE_SOCK}" -e '^\' ;;
41+
abduco) exec abduco -a bridge ;;
42+
reptyr) exec reptyr "$(cat "${BRIDGE_PID_FILE}")" ;;
43+
*) echo "ERROR: pty_attach: unsupported PTY_TOOL=${PTY_TOOL}" >&2; exit 1 ;;
44+
esac
45+
}
46+
47+
detach_hint() {
48+
case "${PTY_TOOL}" in
49+
dtach|abduco) echo "Ctrl+\\" ;;
50+
reptyr) echo "Ctrl+C" ;;
51+
*) echo "(unknown)" ;;
52+
esac
53+
}
54+
55+
# True if the abduco session named 'bridge' is listed as running.
56+
abduco_session_alive() {
57+
abduco -l 2>/dev/null | grep -qw 'bridge'
58+
}
59+
60+
# Wait up to $1 seconds for the bridge session (socket or PID file) to appear.
61+
wait_for_session() {
62+
local timeout="${1:-10}"
63+
local elapsed=0
64+
while [[ "${elapsed}" -lt "${timeout}" ]]; do
65+
case "${PTY_TOOL}" in
66+
dtach) [[ -S "${BRIDGE_SOCK}" ]] && return 0 ;;
67+
abduco) abduco_session_alive && return 0 ;;
68+
reptyr) [[ -f "${BRIDGE_PID_FILE}" ]] && return 0 ;;
69+
*) echo "ERROR: wait_for_session: unsupported PTY_TOOL=${PTY_TOOL}" >&2; exit 1 ;;
70+
esac
71+
sleep 1
72+
(( elapsed++ )) || true
73+
done
74+
echo "ERROR: bridge session did not start within ${timeout}s." >&2
75+
return 1
76+
}
77+
78+
# --- Commands ---
79+
80+
CMD="${1:-run}"
81+
82+
case "${CMD}" in
83+
84+
init)
85+
# One-time setup: generate GPG key, init password store, interactive login.
86+
# Run as: docker run -it <image> init
87+
gpg --generate-key --batch /protonmail/gpgparams
88+
pass init pass-key
89+
exec protonmail-bridge --cli
90+
;;
91+
92+
manage)
93+
# Open an interactive --cli session for account management (add/remove accounts etc).
94+
# Run as: docker run -it --rm -v <data-volume> <image> manage
95+
# NOTE: Stop the running daemon container first to avoid port/lock conflicts.
96+
CONTAINER_ID=$(hostname)
97+
echo " Starting management session... [PTY_TOOL=${PTY_TOOL}]"
98+
pty_start protonmail-bridge --cli
99+
100+
# Wait for the session socket/pid to appear before printing attach instructions
101+
wait_for_session 10
102+
103+
echo " Management session ready."
104+
echo " Attach: docker exec -it ${CONTAINER_ID} /protonmail/entrypoint.sh attach"
105+
echo " Detach: $(detach_hint)"
106+
107+
# Block so the container stays alive for `docker exec attach`.
108+
# If stdin is a tty (docker run -it), jump straight into the session.
109+
if [[ -t 0 ]]; then
110+
pty_attach
111+
else
112+
# No tty: wait until the bridge session disappears then exit cleanly.
113+
while true; do
114+
case "${PTY_TOOL}" in
115+
dtach) [[ -S "${BRIDGE_SOCK}" ]] || break ;;
116+
abduco) abduco_session_alive || break ;;
117+
reptyr) kill -0 "$(cat "${BRIDGE_PID_FILE}" 2>/dev/null)" 2>/dev/null || break ;;
118+
*) echo "ERROR: manage loop: unsupported PTY_TOOL=${PTY_TOOL}" >&2; exit 1 ;;
119+
esac
120+
sleep 2
121+
done
122+
fi
123+
;;
124+
125+
attach)
126+
# Reattach to a running manage session.
127+
case "${PTY_TOOL}" in
128+
dtach)
129+
if [[ ! -S "${BRIDGE_SOCK}" ]]; then
130+
echo "ERROR: No active dtach session found (${BRIDGE_SOCK} does not exist)." >&2
131+
echo " Start one first: docker exec -it <container> /protonmail/entrypoint.sh manage" >&2
132+
exit 1
133+
fi
134+
;;
135+
abduco)
136+
if ! abduco_session_alive; then
137+
echo "ERROR: No active abduco session 'bridge' found." >&2
138+
echo " Start one first: docker exec -it <container> /protonmail/entrypoint.sh manage" >&2
139+
exit 1
140+
fi
141+
;;
142+
reptyr)
143+
if [[ ! -f "${BRIDGE_PID_FILE}" ]]; then
144+
echo "ERROR: No active session found (${BRIDGE_PID_FILE} does not exist)." >&2
145+
echo " Start one first: docker exec -it <container> /protonmail/entrypoint.sh manage" >&2
146+
exit 1
147+
fi
148+
;;
149+
*)
150+
echo "ERROR: attach: unsupported PTY_TOOL=${PTY_TOOL}" >&2
151+
exit 1
152+
;;
153+
esac
154+
pty_attach
155+
;;
156+
157+
run)
158+
# Daemon mode: --noninteractive runs headless, output goes directly to docker logs.
159+
CONTAINER_ID=$(hostname)
160+
161+
# Cleanup handler: forward SIGTERM/SIGINT and reap child processes cleanly.
162+
# As PID 1, without this Docker's SIGTERM would leave children running until
163+
# the kill timeout expires.
164+
BRIDGE_PID=
165+
SOCAT_SMTP_PID=
166+
SOCAT_IMAP_PID=
167+
_cleanup_done=0
168+
cleanup() {
169+
[[ "${_cleanup_done}" -eq 1 ]] && return
170+
_cleanup_done=1
171+
echo " Shutting down bridge and port-forwards..." >&2
172+
[[ -n "${BRIDGE_PID:-}" ]] && kill "${BRIDGE_PID}" 2>/dev/null || true
173+
[[ -n "${SOCAT_SMTP_PID:-}" ]] && kill "${SOCAT_SMTP_PID}" 2>/dev/null || true
174+
[[ -n "${SOCAT_IMAP_PID:-}" ]] && kill "${SOCAT_IMAP_PID}" 2>/dev/null || true
175+
wait 2>/dev/null || true
176+
}
177+
trap cleanup EXIT SIGTERM SIGINT
178+
179+
echo "========================================"
180+
echo " ProtonMail Bridge daemon starting..."
181+
echo " Container: ${CONTAINER_ID}"
182+
echo ""
183+
echo " Available commands:"
184+
echo " First-time setup:"
185+
echo " docker run -it <image> init"
186+
echo ""
187+
echo " Manage accounts (stop daemon first):"
188+
echo " docker run -it --rm -v <data-volume> <image> manage"
189+
echo ""
190+
echo " Attach to a running manage session:"
191+
echo " docker exec -it <container> /protonmail/entrypoint.sh attach"
192+
echo ""
193+
echo " View logs:"
194+
echo " docker logs -f ${CONTAINER_ID}"
195+
echo "========================================"
196+
197+
# Start bridge in background so we can wait for it to bind its ports
198+
# before socat begins accepting connections.
199+
protonmail-bridge --noninteractive &
200+
BRIDGE_PID=$!
201+
202+
# Wait for bridge to open its local SMTP and IMAP ports (up to 60s).
203+
# Abort immediately if the bridge process exits before the port is ready.
204+
echo " Waiting for bridge ports 1025/1143..."
205+
for port in 1025 1143; do
206+
elapsed=0
207+
until socat -u OPEN:/dev/null TCP:127.0.0.1:${port} 2>/dev/null; do
208+
if ! kill -0 "${BRIDGE_PID}" 2>/dev/null; then
209+
echo "ERROR: bridge process (pid ${BRIDGE_PID}) exited before port ${port} became ready." >&2
210+
exit 1
211+
fi
212+
sleep 1
213+
(( elapsed++ )) || true
214+
if [[ "${elapsed}" -ge 60 ]]; then
215+
echo "ERROR: bridge port ${port} did not open within 60s." >&2
216+
exit 1
217+
fi
218+
done
219+
echo " Port ${port} ready."
220+
done
221+
222+
# socat forwards standard ports to bridge's localhost-only listener ports.
223+
# retry=30,interval=2 handles transient bridge restarts without dropping connections.
224+
socat TCP-LISTEN:25,fork,reuseaddr TCP:127.0.0.1:1025,nodelay,retry=30,interval=2 &
225+
SOCAT_SMTP_PID=$!
226+
socat TCP-LISTEN:143,fork,reuseaddr TCP:127.0.0.1:1143,nodelay,retry=30,interval=2 &
227+
SOCAT_IMAP_PID=$!
228+
229+
# Verify both socat processes started
230+
sleep 1
231+
for pid in "${SOCAT_SMTP_PID}" "${SOCAT_IMAP_PID}"; do
232+
if ! kill -0 "${pid}" 2>/dev/null; then
233+
echo "ERROR: socat port-forward (pid ${pid}) failed to start." >&2
234+
exit 1
235+
fi
236+
done
237+
238+
# Wait on bridge; EXIT trap will bring down socat when bridge exits.
239+
wait "${BRIDGE_PID}"
240+
;;
241+
242+
*)
243+
echo "Usage: entrypoint.sh [init|manage|attach|run]" >&2
244+
exit 1
245+
;;
246+
247+
esac

0 commit comments

Comments
 (0)