Skip to content
Merged
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
7 changes: 7 additions & 0 deletions THIRD_PARTY_LICENSES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,17 @@ The `proton-bridge` image distributes binaries built from:
- Adapted bootstrap/containerization ideas from `shenxn/protonmail-bridge-docker` (GPL-3.0)
- Additional referenced implementation patterns from `VideoCurio/ProtonMailBridgeDocker` (GPL-3.0)

The bundled `proton-bridge-exporter` additionally vendors the Bridge gRPC client
stubs (`images/proton-bridge/exporter/bridgepb/`, GPL-3.0, taken verbatim from
`ProtonMail/proton-bridge` with only the Go package name changed) and links Go
libraries under their own permissive licences (gRPC and Prometheus client
under Apache-2.0; `google.golang.org/protobuf` under BSD-3-Clause).

Relevant local files:

- `images/proton-bridge/LICENSE` (full GPL-3.0 license text)
- `images/proton-bridge/NOTICE` (attribution and provenance)
- `images/proton-bridge/exporter/README.md` (exporter provenance and stub regeneration)

Upstream sources:

Expand Down
15 changes: 15 additions & 0 deletions images/proton-bridge/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ RUN --mount=type=cache,target=/go/pkg/mod \
set -eux; \
make build-nogui vault-editor

# Build the Prometheus exporter that scrapes the bridge gRPC frontend.
FROM golang:alpine3.24 AS exporter
WORKDIR /exporter
COPY exporter/go.mod exporter/go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod go mod download
COPY exporter/ ./
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 go build -o /proton-bridge-exporter .

# Working stage image
FROM alpine:3.24

Expand All @@ -55,11 +65,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 @@ -88,6 +100,7 @@ RUN set -eux; \
# Copy executables made during previous stage
WORKDIR /usr/bin/
COPY --from=build /build/proton-bridge/bridge /build/proton-bridge/proton-bridge /build/proton-bridge/vault-editor /usr/bin/
COPY --from=exporter /proton-bridge-exporter /usr/bin/proton-bridge-exporter

# Install needed scripts and files
WORKDIR /app/
Expand All @@ -105,6 +118,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/exporter/run /app/services/exporter/run
COPY --chmod=0755 services/exporter/finish /app/services/exporter/finish
COPY --from=build /build/BRIDGE_VERSION /app/VERSION
RUN chown -R bridge:bridge /app

Expand Down
17 changes: 15 additions & 2 deletions images/proton-bridge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ Headless Proton Mail Bridge image with `s6`-supervised services.

The container runs these long-lived services under `s6`:

- `bridge` (`/usr/bin/bridge --noninteractive` by default)
- `bridge` (`/usr/bin/bridge --grpc` by default; serves mail and the gRPC machine API)
- `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}`)
- `exporter` (Prometheus metrics on `${CONTAINER_METRICS_PORT}`, scraping the bridge gRPC API)

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 All @@ -33,9 +35,20 @@ Optional runtime tuning:
- `BRIDGE_EXIT_ZERO_STOPS_CONTAINER` (default `true`)
- `BRIDGE_GPG_AGENT_WAIT_SECONDS` (default `30`)
- `GPG_AGENT_LAUNCH_MAX_FAILURES` (default `10`)
- `BRIDGE_MODE` (`noninteractive` or `cli`, default `noninteractive`)
- `BRIDGE_MODE` (`grpc`, `noninteractive`, or `cli`, default `grpc`)
- `SOCAT_DEBUG` (`true` enables verbose socat logs, default `false`)

## Metrics

The `exporter` service exposes Prometheus metrics on `${CONTAINER_METRICS_PORT}`
(default `9154`, path `/metrics`), including per-account login state and
dropped-auth counters scraped from the bridge gRPC API. This requires the
default `BRIDGE_MODE=grpc`; under `noninteractive`/`cli` the gRPC API is absent
and the exporter reports `proton_bridge_up 0`.

See [`exporter/README.md`](exporter/README.md) for the full metric list and the
stub-regeneration step required on each upstream version bump.

## Login

Use Bridge CLI mode for first-time account login.
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
61 changes: 61 additions & 0 deletions images/proton-bridge/exporter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# proton-bridge-exporter

A small Prometheus exporter that scrapes the headless Proton Bridge **gRPC
frontend** (`bridge --grpc`) and exposes process- and account-level metrics,
so dropped account authentication can be alerted on.

## How it connects

Bridge writes a `grpcServerConfig.json` to its settings directory
(`~/.config/protonmail/bridge-v3/`) containing a generated TLS certificate, an
auth token, and a unix socket path. The exporter reads that file, dials the
socket, pins the (self-signed, CN `127.0.0.1`) certificate, and presents the
token in a `server-token` per-RPC header — exactly as the desktop GUI does.

It then polls `GetUserList` / `Version` on an interval and subscribes to
`RunEventStream` for live disconnect / bad-event / IMAP-login-failure events.

## Metrics

| Metric | Type | Notes |
| --- | --- | --- |
| `proton_bridge_up` | gauge | 1 if the gRPC service is reachable |
| `proton_bridge_info{version}` | gauge | bridge version, value 1 |
| `proton_bridge_accounts_total` | gauge | number of known accounts |
| `proton_bridge_account_connected{account}` | gauge | 1 = logged in / connected |
| `proton_bridge_account_state{account,state}` | gauge | 1 for the active state (`connected`/`locked`/`signed_out`) |
| `proton_bridge_account_used_bytes{account}` | gauge | mail storage used |
| `proton_bridge_account_total_bytes{account}` | gauge | mail storage quota |
| `proton_bridge_internet_connected` | gauge | bridge's internet status |
| `proton_bridge_user_disconnected_total{account}` | counter | dropped-auth events |
| `proton_bridge_user_bad_event_total{account}` | counter | sync error events |
| `proton_bridge_imap_login_failed_total{account}` | counter | failed IMAP client logins |
| `proton_bridge_exporter_scrape_errors_total` | counter | exporter-side gRPC errors |

Account/event series only appear once at least one account is logged in or the
relevant event has fired.

Configuration via env: `CONTAINER_METRICS_PORT` (listen port, default `9154`)
and `BRIDGE_GRPC_CONFIG` (override the config path).

## gRPC stubs (`bridgepb/`) and upstream coupling

`bridgepb/bridge.pb.go` and `bridgepb/bridge_grpc.pb.go` are **vendored verbatim**
from `ProtonMail/proton-bridge` (`internal/frontend/grpc/`, GPL-3.0), with only
the Go `package` declaration renamed to `bridgepb`. `bridgepb/bridge.proto` is
kept alongside for provenance.

The gRPC API is Bridge's internal GUI contract, so it can change between
releases. **On each upstream version bump, re-vendor these files** from the
matching tag:

```sh
tag=v3.25.0
for f in bridge.pb.go bridge_grpc.pb.go bridge.proto; do
curl -fsSL "https://raw.githubusercontent.com/ProtonMail/proton-bridge/${tag}/internal/frontend/grpc/${f}" \
-o "bridgepb/${f}"
done
sed -i 's/^package grpc$/package bridgepb/' bridgepb/bridge.pb.go bridgepb/bridge_grpc.pb.go
```

Then `go build ./...` and adjust `main.go` if the schema changed.
Loading
Loading