Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions images/proton-bridge/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,13 @@ ARG ENV_BRIDGE_HOST=127.0.0.1
# Change ENV_CONTAINER_SMTP_PORT only if you have a docker port conflict on host network namespace.
ARG ENV_CONTAINER_SMTP_PORT=1026
ARG ENV_CONTAINER_IMAP_PORT=1144
ARG ENV_CONTAINER_METRICS_PORT=9154
ENV PROTON_BRIDGE_SMTP_PORT=$ENV_BRIDGE_SMTP_PORT \
PROTON_BRIDGE_IMAP_PORT=$ENV_BRIDGE_IMAP_PORT \
PROTON_BRIDGE_HOST=$ENV_BRIDGE_HOST \
CONTAINER_SMTP_PORT=$ENV_CONTAINER_SMTP_PORT \
CONTAINER_IMAP_PORT=$ENV_CONTAINER_IMAP_PORT \
CONTAINER_METRICS_PORT=$ENV_CONTAINER_METRICS_PORT \
ENV_TARGET_PLATFORM=$TARGETPLATFORM

# Install dependencies
Expand Down Expand Up @@ -93,6 +95,8 @@ COPY --from=build /build/proton-bridge/bridge /build/proton-bridge/proton-bridge
WORKDIR /app/
COPY --chmod=0755 entrypoint.sh /app/entrypoint.sh
COPY --chmod=0755 healthcheck.sh /app/healthcheck.sh
COPY --chmod=0755 metrics/metrics.cgi /app/metrics/metrics.cgi
COPY --chmod=0755 metrics/serve-http.sh /app/metrics/serve-http.sh
COPY GPGparams.txt /app/GPGparams.txt
COPY LICENSE /app/licenses/LICENSE
COPY NOTICE /app/licenses/NOTICE
Expand All @@ -105,6 +109,8 @@ COPY --chmod=0755 services/socat-smtp/run /app/services/socat-smtp/run
COPY --chmod=0755 services/socat-smtp/finish /app/services/socat-smtp/finish
COPY --chmod=0755 services/socat-imap/run /app/services/socat-imap/run
COPY --chmod=0755 services/socat-imap/finish /app/services/socat-imap/finish
COPY --chmod=0755 services/metrics/run /app/services/metrics/run
COPY --chmod=0755 services/metrics/finish /app/services/metrics/finish
COPY --from=build /build/BRIDGE_VERSION /app/VERSION
RUN chown -R bridge:bridge /app

Expand Down
26 changes: 26 additions & 0 deletions images/proton-bridge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ The container runs these long-lived services under `s6`:
- `gpg-agent` (launched and monitored via `gpgconf`)
- SMTP forwarder (`socat` on `${CONTAINER_SMTP_PORT}` -> `${PROTON_BRIDGE_HOST}:${PROTON_BRIDGE_SMTP_PORT}`)
- IMAP forwarder (`socat` on `${CONTAINER_IMAP_PORT}` -> `${PROTON_BRIDGE_HOST}:${PROTON_BRIDGE_IMAP_PORT}`)
- metrics endpoint (`socat` listener on `${CONTAINER_METRICS_PORT}` serving `/cgi-bin/metrics`)

Bootstrap-only initialization in `entrypoint.sh`:

Expand All @@ -25,6 +26,7 @@ Required:
- `PROTON_BRIDGE_HOST`
- `CONTAINER_SMTP_PORT`
- `CONTAINER_IMAP_PORT`
- `CONTAINER_METRICS_PORT`

Optional runtime tuning:

Expand Down Expand Up @@ -83,6 +85,30 @@ The container `HEALTHCHECK` script validates:
- `gpg-agent` control socket responsiveness (`gpg-connect-agent /bye`)
- listening state for SMTP/IMAP container ports
- lightweight SMTP and IMAP banner-level handshake probes on local forwarded ports
- metrics endpoint HTTP readiness (`/metrics`)

## Metrics

The image now exposes Prometheus-formatted metrics from inside the container.

- listen port: `${CONTAINER_METRICS_PORT}` (default `9154`)
- scrape path: `/metrics` (`/cgi-bin/metrics` also supported)

Current metrics include:

- `proton_bridge_service_up{service=...}`
- `proton_bridge_service_restart_count{service=...}`
- `proton_bridge_service_start_time_seconds{service=...}`
- `proton_bridge_gpg_agent_up`
- `proton_bridge_port_listening{listener=...,port=...}`
- `proton_bridge_smtp_banner_probe_up`
- `proton_bridge_imap_banner_probe_up`
- `proton_bridge_pass_entry_count`

Limitations:

- Proton Bridge does not currently expose a stable machine API for per-account sync status in this image mode.
- You can scrape transport/process readiness today; account-level sync state likely requires either upstream Bridge support or a dedicated log/CLI parser sidecar.

## Build Supply Chain

Expand Down
3 changes: 2 additions & 1 deletion images/proton-bridge/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ for required_var in \
PROTON_BRIDGE_IMAP_PORT \
PROTON_BRIDGE_HOST \
CONTAINER_SMTP_PORT \
CONTAINER_IMAP_PORT
CONTAINER_IMAP_PORT \
CONTAINER_METRICS_PORT
do
require_env "${required_var}"
done
Expand Down
6 changes: 5 additions & 1 deletion images/proton-bridge/healthcheck.sh
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
#!/usr/bin/env sh
set -eu

for service in bridge gpg-agent socat-smtp socat-imap; do
for service in bridge gpg-agent socat-smtp socat-imap metrics; do
s6-svstat "/app/services/${service}" | grep -q '^up '
done

gpg-connect-agent /bye >/dev/null 2>&1

netstat -ltn 2>/dev/null | grep -q "[.:]${CONTAINER_SMTP_PORT}[[:space:]]"
netstat -ltn 2>/dev/null | grep -q "[.:]${CONTAINER_IMAP_PORT}[[:space:]]"
netstat -ltn 2>/dev/null | grep -q "[.:]${CONTAINER_METRICS_PORT}[[:space:]]"

printf 'QUIT\r\n' | nc -w 3 127.0.0.1 "${CONTAINER_SMTP_PORT}" | grep -Eq '^220 '
printf 'a1 LOGOUT\r\n' | nc -w 3 127.0.0.1 "${CONTAINER_IMAP_PORT}" | grep -Eq '^\* (OK|PREAUTH|BYE) '
metrics_resp="$(printf 'GET /metrics HTTP/1.0\r\nHost: localhost\r\n\r\n' \
| nc -w 3 127.0.0.1 "${CONTAINER_METRICS_PORT}")"
printf '%s\n' "${metrics_resp}" | grep -Eq '^HTTP/[0-9.]+ 200 '
137 changes: 137 additions & 0 deletions images/proton-bridge/metrics/metrics.cgi
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
#!/bin/sh
set -eu

state_dir="/tmp/proton-bridge-s6"

metric_bool() {
name="$1"
value="$2"
printf '%s %s\n' "${name}" "${value}"
}

service_up() {
service="$1"
if s6-svstat "/app/services/${service}" 2>/dev/null | grep -q '^up '; then
value=1
else
value=0
fi
printf 'proton_bridge_service_up{service="%s"} %s\n' "${service}" "${value}"
}

service_restart_count() {
service="$1"
state_file="${state_dir}/${service}.state"
count=0
if [ -f "${state_file}" ]; then
count="$(cat "${state_file}" 2>/dev/null || echo 0)"
fi
printf 'proton_bridge_service_restart_count{service="%s"} %s\n' "${service}" "${count}"
}

service_start_time() {
service="$1"
start_file="${state_dir}/${service}.start"
if [ -f "${start_file}" ]; then
started_at="$(cat "${start_file}" 2>/dev/null || echo 0)"
else
started_at=0
fi
printf 'proton_bridge_service_start_time_seconds{service="%s"} %s\n' "${service}" "${started_at}"
}

port_listening() {
name="$1"
port="$2"
if netstat -ltn 2>/dev/null | grep -q "[.:]${port}[[:space:]]"; then
value=1
else
value=0
fi
printf 'proton_bridge_port_listening{listener="%s",port="%s"} %s\n' "${name}" "${port}" "${value}"
}

smtp_probe_up() {
if printf 'QUIT\r\n' | nc -w 3 127.0.0.1 "${CONTAINER_SMTP_PORT}" 2>/dev/null | grep -Eq '^220 '; then
value=1
else
value=0
fi
metric_bool proton_bridge_smtp_banner_probe_up "${value}"
}

imap_probe_up() {
if printf 'a1 LOGOUT\r\n' | nc -w 3 127.0.0.1 "${CONTAINER_IMAP_PORT}" 2>/dev/null | grep -Eq '^\* (OK|PREAUTH|BYE) '; then
value=1
else
value=0
fi
metric_bool proton_bridge_imap_banner_probe_up "${value}"
}

gpg_agent_up() {
if gpg-connect-agent /bye >/dev/null 2>&1; then
value=1
else
value=0
fi
metric_bool proton_bridge_gpg_agent_up "${value}"
}

pass_entry_count() {
count=0
if [ -d "${HOME}/.password-store" ]; then
count="$(find "${HOME}/.password-store" -type f -name '*.gpg' 2>/dev/null | wc -l | tr -d ' ')"
fi
metric_bool proton_bridge_pass_entry_count "${count}"
}

print_metrics() {
printf '# HELP proton_bridge_service_up Whether an s6 service is currently up (1) or down (0).\n'
printf '# TYPE proton_bridge_service_up gauge\n'
service_up bridge
service_up gpg-agent
service_up socat-smtp
service_up socat-imap
service_up metrics

printf '# HELP proton_bridge_service_restart_count Number of rapid restarts seen by the s6 finish policy per service.\n'
printf '# TYPE proton_bridge_service_restart_count gauge\n'
service_restart_count bridge
service_restart_count gpg-agent
service_restart_count socat-smtp
service_restart_count socat-imap
service_restart_count metrics

printf '# HELP proton_bridge_service_start_time_seconds Last recorded service start time as Unix epoch seconds.\n'
printf '# TYPE proton_bridge_service_start_time_seconds gauge\n'
service_start_time bridge
service_start_time gpg-agent
service_start_time socat-smtp
service_start_time socat-imap
service_start_time metrics

printf '# HELP proton_bridge_gpg_agent_up Whether gpg-agent responds to gpg-connect-agent.\n'
printf '# TYPE proton_bridge_gpg_agent_up gauge\n'
gpg_agent_up

printf '# HELP proton_bridge_port_listening Whether the expected listener port is open inside the container.\n'
printf '# TYPE proton_bridge_port_listening gauge\n'
port_listening smtp "${CONTAINER_SMTP_PORT}"
port_listening imap "${CONTAINER_IMAP_PORT}"
port_listening metrics "${CONTAINER_METRICS_PORT}"

printf '# HELP proton_bridge_smtp_banner_probe_up Whether SMTP banner probe on localhost succeeds.\n'
printf '# TYPE proton_bridge_smtp_banner_probe_up gauge\n'
smtp_probe_up

printf '# HELP proton_bridge_imap_banner_probe_up Whether IMAP banner probe on localhost succeeds.\n'
printf '# TYPE proton_bridge_imap_banner_probe_up gauge\n'
imap_probe_up

printf '# HELP proton_bridge_pass_entry_count Number of pass entries in the mounted bridge password store.\n'
printf '# TYPE proton_bridge_pass_entry_count gauge\n'
pass_entry_count
}

print_metrics
29 changes: 29 additions & 0 deletions images/proton-bridge/metrics/serve-http.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/bin/sh
set -eu

method=""
path=""
protocol=""
IFS=' ' read -r method path protocol || true

while IFS= read -r header; do
case "${header}" in
''|$'\r')
break
;;
esac
done

if [ "${method}" = "GET" ] && { [ "${path}" = "/metrics" ] || [ "${path}" = "/cgi-bin/metrics" ]; }; then
printf 'HTTP/1.1 200 OK\r\n'
printf 'Content-Type: text/plain; version=0.0.4\r\n'
printf 'Connection: close\r\n'
printf '\r\n'
exec /app/metrics/metrics.cgi
fi

printf 'HTTP/1.1 404 Not Found\r\n'
printf 'Content-Type: text/plain\r\n'
printf 'Connection: close\r\n'
printf '\r\n'
printf 'not found\n'
4 changes: 4 additions & 0 deletions images/proton-bridge/services/metrics/finish
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
set -eu

exec /app/services/.s6-finish-policy.sh "metrics" "false" "${1:-0}" "${2:-0}"
10 changes: 10 additions & 0 deletions images/proton-bridge/services/metrics/run
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/sh
set -eu

state_dir="/tmp/proton-bridge-s6"
mkdir -p "${state_dir}"
date +%s > "${state_dir}/metrics.start"

exec socat \
TCP-LISTEN:"${CONTAINER_METRICS_PORT}",fork,reuseaddr \
SYSTEM:"/app/metrics/serve-http.sh"