Skip to content

Commit 6750332

Browse files
committed
build: switch to Go-based multi-stage build and improve
- Use golang:1.26-trixie builder instead of debian:sid - Build proton-bridge from source via version argument/envelopment - Add support for PTY tools (dtach, abduco, reptyr) for interactive sessions - Introduce manage and attach commands for bridge CLI sessions - Improve daemon startup with port readiness checks - Add HEALTHCHECK and configurable CMD/ENTRYPOINT - Harden entrypoint with strict bash flags and better error handling - Install additional runtime deps (libfido2, procps) and optional PTY tools
1 parent e02b51a commit 6750332

2 files changed

Lines changed: 218 additions & 45 deletions

File tree

build/Dockerfile

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,52 @@
1-
# The build image could be golang, but it currently does not support riscv64. Only debian:sid does, at the time of writing.
2-
FROM debian:sid-slim AS build
1+
### The Deb install is just a repack of the official ProtonMail Bridge deb package with less dependencies.
2+
### I recommend you don't use this. It's here for legacy reasons.
3+
4+
FROM golang:1.26-trixie AS build
35

46
ARG version
7+
ENV version=${version}
8+
59

6-
# Install dependencies
7-
RUN apt-get update && apt-get install -y golang build-essential libsecret-1-dev
10+
RUN apt-get update && apt-get install -y build-essential libsecret-1-dev libfido2-dev libcbor-dev
811

912
# Build
1013
ADD https://github.com/ProtonMail/proton-bridge.git#${version} /build/
1114
WORKDIR /build/
1215
RUN make build-nogui vault-editor
1316

14-
FROM debian:sid-slim
17+
# -----------------------------------------------------------------------------
18+
19+
FROM debian:trixie-slim
1520
LABEL maintainer="Simon Felding <sife@adm.ku.dk>"
1621

22+
# Select PTY tool for manage/attach commands: dtach (default), abduco, reptyr
23+
ARG PTY_TOOL=dtach
24+
ENV PTY_TOOL=${PTY_TOOL}
25+
1726
EXPOSE 25/tcp
1827
EXPOSE 143/tcp
1928

20-
# Install dependencies and protonmail bridge
21-
RUN apt-get update \
22-
&& apt-get install -y --no-install-recommends socat pass libsecret-1-0 ca-certificates \
23-
&& rm -rf /var/lib/apt/lists/*
29+
WORKDIR /protonmail
2430

25-
# Copy bash scripts
2631
COPY gpgparams entrypoint.sh /protonmail/
27-
2832
# Copy protonmail
2933
COPY --from=build /build/bridge /protonmail/
3034
COPY --from=build /build/proton-bridge /protonmail/
3135
COPY --from=build /build/vault-editor /protonmail/
3236

33-
ENTRYPOINT ["bash", "/protonmail/entrypoint.sh"]
37+
RUN apt-get update \
38+
&& apt-get install -y --no-install-recommends \
39+
socat pass libsecret-1-0 libfido2-1 ca-certificates procps \
40+
&& case "${PTY_TOOL}" in \
41+
dtach) apt-get install -y --no-install-recommends dtach ;; \
42+
abduco) apt-get install -y --no-install-recommends abduco ;; \
43+
reptyr) apt-get install -y --no-install-recommends reptyr ;; \
44+
esac \
45+
&& chmod +x /protonmail/entrypoint.sh \
46+
&& rm -rf /var/lib/apt/lists/*
47+
48+
HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=120s \
49+
CMD /bin/bash -c "true < /dev/tcp/localhost/25"
50+
51+
ENTRYPOINT ["/protonmail/entrypoint.sh"]
52+
CMD ["run"]

build/entrypoint.sh

Lines changed: 187 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,189 @@
11
#!/bin/bash
22

3-
set -ex
4-
5-
# Initialize
6-
if [[ $1 == init ]]; then
7-
8-
# Initialize pass
9-
gpg --generate-key --batch /protonmail/gpgparams
10-
pass init pass-key
11-
12-
# Kill the other instance as only one can be running at a time.
13-
# This allows users to run entrypoint init inside a running conainter
14-
# which is useful in a k8s environment.
15-
# || true to make sure this would not fail in case there is no running instance.
16-
pkill protonmail-bridge || true
17-
18-
# Login
19-
/protonmail/proton-bridge --cli $@
20-
21-
else
22-
23-
# socat will make the conn appear to come from 127.0.0.1
24-
# ProtonMail Bridge currently expects that.
25-
# It also allows us to bind to the real ports :)
26-
socat TCP-LISTEN:25,fork TCP:127.0.0.1:1025 &
27-
socat TCP-LISTEN:143,fork TCP:127.0.0.1:1143 &
28-
29-
# Start protonmail
30-
# Fake a terminal, so it does not quit because of EOF...
31-
rm -f faketty
32-
mkfifo faketty
33-
cat faketty | /protonmail/proton-bridge --cli $@
34-
35-
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+
# Clean stale gpg-agent sockets left from a previous run
10+
rm -f /root/.gnupg/S.gpg-agent* 2>/dev/null || true
11+
12+
# --- PTY helpers (only used by: init, manage, attach) ---
13+
14+
pty_start() {
15+
case "${PTY_TOOL}" in
16+
dtach) dtach -n "${BRIDGE_SOCK}" "$@" ;;
17+
abduco) abduco -n bridge "$@" ;;
18+
# reptyr re-attaches existing PIDs; use nohup+setsid to launch headlessly instead
19+
reptyr) setsid "$@" </dev/null &>/dev/null & echo $! > "${BRIDGE_PID_FILE}" ;;
20+
esac
21+
}
22+
23+
pty_attach() {
24+
case "${PTY_TOOL}" in
25+
dtach) exec dtach -a "${BRIDGE_SOCK}" -e '^\' ;;
26+
abduco) exec abduco -a bridge ;;
27+
reptyr) exec reptyr "$(cat "${BRIDGE_PID_FILE}")" ;;
28+
esac
29+
}
30+
31+
detach_hint() {
32+
case "${PTY_TOOL}" in
33+
dtach|abduco) echo "Ctrl+\\" ;;
34+
reptyr) echo "Ctrl+C" ;;
35+
esac
36+
}
37+
38+
# Wait up to $1 seconds for the bridge socket (or PID file) to appear
39+
wait_for_session() {
40+
local timeout="${1:-10}"
41+
local elapsed=0
42+
while [[ "${elapsed}" -lt "${timeout}" ]]; do
43+
case "${PTY_TOOL}" in
44+
dtach|abduco) [[ -S "${BRIDGE_SOCK}" ]] && return 0 ;;
45+
reptyr) [[ -f "${BRIDGE_PID_FILE}" ]] && return 0 ;;
46+
esac
47+
sleep 1
48+
(( elapsed++ )) || true
49+
done
50+
echo "ERROR: bridge session did not start within ${timeout}s." >&2
51+
return 1
52+
}
53+
54+
# --- Commands ---
55+
56+
CMD="${1:-run}"
57+
58+
case "${CMD}" in
59+
60+
init)
61+
# One-time setup: generate GPG key, init password store, interactive login.
62+
# Run as: docker run -it <image> init
63+
gpg --generate-key --batch /protonmail/gpgparams
64+
pass init pass-key
65+
exec /protonmail/proton-bridge --cli
66+
;;
67+
68+
manage)
69+
# Open an interactive --cli session for account management (add/remove accounts etc).
70+
# Run as: docker run -it --rm -v <data-volume> <image> manage
71+
# NOTE: Stop the running daemon container first to avoid port/lock conflicts.
72+
CONTAINER_ID=$(hostname)
73+
echo " Starting management session... [PTY_TOOL=${PTY_TOOL}]"
74+
pty_start /protonmail/proton-bridge --cli
75+
76+
# Wait for the session socket/pid to appear before printing attach instructions
77+
wait_for_session 10
78+
79+
echo " Management session ready."
80+
echo " Attach: docker exec -it ${CONTAINER_ID} /protonmail/entrypoint.sh attach"
81+
echo " Detach: $(detach_hint)"
82+
83+
# Block so the container stays alive for `docker exec attach`.
84+
# If stdin is a tty (docker run -it), jump straight into the session.
85+
if [[ -t 0 ]]; then
86+
pty_attach
87+
else
88+
# No tty: wait until the bridge session disappears then exit cleanly.
89+
while true; do
90+
case "${PTY_TOOL}" in
91+
dtach|abduco) [[ -S "${BRIDGE_SOCK}" ]] || break ;;
92+
reptyr) kill -0 "$(cat "${BRIDGE_PID_FILE}" 2>/dev/null)" 2>/dev/null || break ;;
93+
esac
94+
sleep 2
95+
done
96+
fi
97+
;;
98+
99+
attach)
100+
# Reattach to a running manage session.
101+
case "${PTY_TOOL}" in
102+
dtach|abduco)
103+
if [[ ! -S "${BRIDGE_SOCK}" ]]; then
104+
echo "ERROR: No active session found (${BRIDGE_SOCK} does not exist)." >&2
105+
echo " Start one first: docker exec -it \$(hostname) /protonmail/entrypoint.sh manage" >&2
106+
exit 1
107+
fi
108+
;;
109+
reptyr)
110+
if [[ ! -f "${BRIDGE_PID_FILE}" ]]; then
111+
echo "ERROR: No active session found (${BRIDGE_PID_FILE} does not exist)." >&2
112+
echo " Start one first: docker exec -it \$(hostname) /protonmail/entrypoint.sh manage" >&2
113+
exit 1
114+
fi
115+
;;
116+
esac
117+
pty_attach
118+
;;
119+
120+
run)
121+
# Daemon mode: --noninteractive runs headless, output goes directly to docker logs.
122+
CONTAINER_ID=$(hostname)
123+
echo "========================================"
124+
echo " ProtonMail Bridge daemon starting..."
125+
echo " Container: ${CONTAINER_ID}"
126+
echo ""
127+
echo " Available commands:"
128+
echo " First-time setup:"
129+
echo " docker run -it <image> init"
130+
echo ""
131+
echo " Manage accounts (stop daemon first):"
132+
echo " docker run -it --rm -v <data-volume> <image> manage"
133+
echo ""
134+
echo " Attach to a running manage session:"
135+
echo " docker exec -it ${CONTAINER_ID} /protonmail/entrypoint.sh attach"
136+
echo ""
137+
echo " View logs:"
138+
echo " docker logs -f ${CONTAINER_ID}"
139+
echo "========================================"
140+
141+
# Start bridge in background so we can wait for it to bind its ports
142+
# before socat begins accepting connections.
143+
/protonmail/proton-bridge --noninteractive &
144+
BRIDGE_PID=$!
145+
146+
# Wait for bridge to open its local SMTP and IMAP ports (up to 60s)
147+
echo " Waiting for bridge ports 1025/1143..."
148+
for port in 1025 1143; do
149+
elapsed=0
150+
until socat -u OPEN:/dev/null TCP:127.0.0.1:${port} 2>/dev/null; do
151+
sleep 1
152+
(( elapsed++ )) || true
153+
if [[ "${elapsed}" -ge 60 ]]; then
154+
echo "ERROR: bridge port ${port} did not open within 60s." >&2
155+
kill "${BRIDGE_PID}" 2>/dev/null || true
156+
exit 1
157+
fi
158+
done
159+
echo " Port ${port} ready."
160+
done
161+
162+
# socat forwards standard ports to bridge's localhost-only listener ports.
163+
# retry=30,interval=2 handles transient bridge restarts without dropping connections.
164+
socat TCP-LISTEN:25,fork,reuseaddr TCP:127.0.0.1:1025,nodelay,retry=30,interval=2 &
165+
SOCAT_SMTP_PID=$!
166+
socat TCP-LISTEN:143,fork,reuseaddr TCP:127.0.0.1:1143,nodelay,retry=30,interval=2 &
167+
SOCAT_IMAP_PID=$!
168+
169+
# Verify both socat processes started
170+
sleep 1
171+
for pid in "${SOCAT_SMTP_PID}" "${SOCAT_IMAP_PID}"; do
172+
if ! kill -0 "${pid}" 2>/dev/null; then
173+
echo "ERROR: socat port-forward (pid ${pid}) failed to start." >&2
174+
kill "${BRIDGE_PID}" 2>/dev/null || true
175+
exit 1
176+
fi
177+
done
178+
179+
# Wait on bridge; if it exits, bring down socat too.
180+
wait "${BRIDGE_PID}"
181+
kill "${SOCAT_SMTP_PID}" "${SOCAT_IMAP_PID}" 2>/dev/null || true
182+
;;
183+
184+
*)
185+
echo "Usage: entrypoint.sh [init|manage|attach|run]" >&2
186+
exit 1
187+
;;
188+
189+
esac

0 commit comments

Comments
 (0)