From 01cb1026a4adc02843c26b0396af313e9334d8a4 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Mar 2026 01:55:40 +0000 Subject: [PATCH 1/3] Add research plan for headless VNC removal Documents the approach for removing VNC/GUI components and running protonmail-bridge with --noninteractive flag using the existing DEB package. https://claude.ai/code/session_0134TRgraVFXq3TiYmLYanRQ --- plan.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 plan.md diff --git a/plan.md b/plan.md new file mode 100644 index 0000000..8db7f52 --- /dev/null +++ b/plan.md @@ -0,0 +1,49 @@ +# Plan: Remove VNC/GUI, Run Bridge Headless + +## Approach +Strip all VNC/GUI services and run `protonmail-bridge --noninteractive` with the existing DEB package. Keep minimal X11/Qt libs since the DEB binary links against them (it just won't open a window with `--noninteractive`). + +## Changes + +### 1. Dockerfile (`Dockerfile`) +- **Remove packages**: `xvfb`, `x11vnc`, `fluxbox`, `stalonetray`, `novnc`, `websockify`, `python3-gi` +- **Keep packages**: `dbus`, `dbus-x11`, `gnome-keyring`, `gir1.2-secret-1`, `gnupg`, `pass`, `ca-certificates`, `curl`, `runit`, and all the `lib*` X11/Qt/OpenGL packages (the DEB binary dynamically links them — removing them would crash the binary even in noninteractive mode) +- **Remove**: `COPY novnc.html /novnc.html` +- **Remove service symlinks**: `xvfb`, `fluxbox`, `stalonetray`, `x11vnc`, `websockify` +- **Remove**: `ENV DISPLAY=:99` +- **Change EXPOSE**: `6080 8080` → `8080` + +### 2. Bridge service (`sv/bridge/run`) +- Remove the Xvfb wait loop (`while [ ! -e /tmp/.X99-lock ]`) +- Add `--noninteractive` flag to `protonmail-bridge` +- Keep dbus sourcing and lock cleanup + +### 3. Entrypoint (`entrypoint.sh`) +- No changes needed — GPG, pass, and dbus setup are all still required + +### 4. Docker Compose (`docker-compose.yml`) +- Remove port mapping `6080:6080` + +### 5. Delete files +- `novnc.html` +- `sv/xvfb/run` +- `sv/fluxbox/run` +- `sv/stalonetray/run` +- `sv/x11vnc/run` +- `sv/websockify/run` + +### 6. Update CLAUDE.md and README.md +- Remove references to noVNC, VNC, port 6080, xvfb, fluxbox, stalonetray +- Document new login flow: `docker exec -it mayl protonmail-bridge --cli` for initial account setup + +## Initial Account Login (post-change) +Users will run: +```bash +docker exec -it mayl protonmail-bridge --cli +``` +Then use the `login` command to authenticate. This is a one-time step; credentials persist in the `bridge-pass` and `bridge-config` volumes. + +## Risk +The DEB binary links against Qt/X11 libs. Running with `--noninteractive` *should* skip GUI init, but if it doesn't, we'd need to either: +- Add back a minimal Xvfb (fallback) +- Switch to building from source with `make build-nogui` From b8d508be932c3516d8352424de13ff95de2f92b9 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Mar 2026 06:59:47 +0000 Subject: [PATCH 2/3] Remove VNC/GUI stack, run protonmail-bridge headless Strip all VNC and GUI components (xvfb, fluxbox, stalonetray, x11vnc, websockify, novnc) and run protonmail-bridge with --noninteractive flag. Removed packages: xvfb, x11vnc, fluxbox, stalonetray, novnc, websockify, python3-gi. Kept Qt/X11 runtime libs since the DEB binary links against them. Initial Proton account login is now done via: docker exec -it mayl protonmail-bridge --cli https://claude.ai/code/session_0134TRgraVFXq3TiYmLYanRQ --- CLAUDE.md | 20 +++++++------------- Dockerfile | 17 +---------------- README.md | 33 ++++++++++++++++----------------- docker-compose.yml | 1 - novnc.html | 31 ------------------------------- sv/bridge/run | 3 +-- sv/fluxbox/run | 4 ---- sv/stalonetray/run | 3 --- sv/websockify/run | 8 -------- sv/x11vnc/run | 3 --- sv/xvfb/run | 3 --- 11 files changed, 25 insertions(+), 101 deletions(-) delete mode 100644 novnc.html delete mode 100644 sv/fluxbox/run delete mode 100644 sv/stalonetray/run delete mode 100644 sv/websockify/run delete mode 100644 sv/x11vnc/run delete mode 100644 sv/xvfb/run diff --git a/CLAUDE.md b/CLAUDE.md index 906f633..5e0d7ed 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,7 +2,7 @@ ## Project Overview -mayl is an email-sending HTTP API backed by protonmail-bridge, running in a single Docker container. The container runs both protonmail-bridge (SMTP on localhost:1025) and the mayl Rust API server (HTTP on port 8080). noVNC on port 6080 provides browser access to the bridge GUI for Proton account login. All processes are supervised by runit (PID 1). +mayl is an email-sending HTTP API backed by protonmail-bridge, running in a single Docker container. The container runs both protonmail-bridge in headless/noninteractive mode (SMTP on localhost:1025) and the mayl Rust API server (HTTP on port 8080). All processes are supervised by runit (PID 1). Emails can be sent synchronously or queued for background delivery. All sent emails are optionally archived in SQLite with automatic old-row culling. Domain-based token authentication controls who can send. @@ -42,14 +42,8 @@ Rust edition 2024 requires a recent stable toolchain. ├── Dockerfile # Multi-stage: trixie runtime + rust builder + final ├── docker-compose.yml # Single service, 5 volumes ├── entrypoint.sh # One-time init (GPG, pass, dbus) then exec runsvdir -├── novnc.html # Minimal noVNC client page ├── sv/ # runit service directories -│ ├── xvfb/run # Virtual X display -│ ├── fluxbox/run # Window manager -│ ├── stalonetray/run # System tray for bridge icon -│ ├── x11vnc/run # VNC server -│ ├── websockify/run # noVNC WebSocket proxy -│ ├── bridge/run # protonmail-bridge (with lock cleanup) +│ ├── bridge/run # protonmail-bridge --noninteractive (with lock cleanup) │ └── mayl/run # mayl API server ├── src/ │ └── main.rs # Entire application (~970 lines) @@ -106,7 +100,7 @@ Access serialized via `tokio::sync::Mutex`. ## Process Supervision -runit (`runsvdir`) runs as PID 1. The entrypoint does one-time setup (GPG key generation, pass init, D-Bus) then `exec runsvdir /etc/service`. Each service in `sv/` has a `run` script; runit auto-restarts any that exit. Services that depend on X wait for `/tmp/.X99-lock`. The bridge service cleans stale lock files before each start. +runit (`runsvdir`) runs as PID 1. The entrypoint does one-time setup (GPG key generation, pass init, D-Bus) then `exec runsvdir /etc/service`. Each service in `sv/` has a `run` script; runit auto-restarts any that exit. The bridge service cleans stale lock files before each start. Initial Proton account login is done via `docker exec -it mayl protonmail-bridge --cli`. ## Key Design Decisions @@ -117,7 +111,7 @@ runit (`runsvdir`) runs as PID 1. The entrypoint does one-time setup (GPG key ge - **`dangerous_accept_invalid_certs(true)`:** Bridge uses self-signed TLS. Must use `TlsParameters::builder().dangerous_accept_invalid_certs(true)` then `AsyncSmtpTransport::builder_dangerous()` with `Tls::Required(tls_params)`. - **Domain token auth:** `POST /domains` creates a domain + UUID token. `POST /email` validates the Bearer token matches the `from` domain. - **Background workers:** `queue_worker` and `archive_culler` run as `tokio::spawn` tasks. -- **Custom noVNC page:** Minimal `novnc.html` using noVNC core `RFB` module directly (the packaged `vnc.html` UI is broken in trixie). +- **Headless bridge:** Bridge runs with `--noninteractive`. Initial login via `docker exec -it mayl protonmail-bridge --cli`. ## Testing @@ -142,6 +136,6 @@ All tests use in-memory SQLite. No SMTP or Docker required. - Rust 2024 edition: `std::env::remove_var` is unsafe. Tests avoid it. - maud 0.27 required for axum 0.8 compatibility (0.26 uses axum-core 0.4, needs 0.5). - Bridge Dockerfile: use `apt-get install -y /tmp/bridge.deb` (NOT `dpkg -i || apt-get -yf` which removes the package). -- Bridge runs with GUI (no `--noninteractive`) so it's visible/usable in noVNC. -- Stale X lock (`/tmp/.X99-lock`) and bridge locks must be cleaned on service start to survive container stop/start cycles. -- Base image is `debian:trixie-slim` (not bookworm) because `stalonetray` and OpenGL libs needed by bridge-gui are only in trixie. +- Bridge runs with `--noninteractive` (headless). Initial account login via `docker exec -it mayl protonmail-bridge --cli`. +- Bridge lock files must be cleaned on service start to survive container stop/start cycles. +- Base image is `debian:trixie-slim` (not bookworm) because OpenGL/Qt libs needed by bridge are only in trixie. diff --git a/Dockerfile b/Dockerfile index dbeb9b6..ca8f95b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,14 +11,7 @@ RUN apt-get update && apt-get install -y \ pass \ dbus \ dbus-x11 \ - xvfb \ - x11vnc \ - fluxbox \ - stalonetray \ - novnc \ - websockify \ gnome-keyring \ - python3-gi \ gir1.2-secret-1 \ libegl1 \ libgl1 \ @@ -65,21 +58,13 @@ FROM runtime COPY --from=builder /app/target/release/mayl /usr/local/bin/mayl COPY entrypoint.sh /entrypoint.sh -COPY novnc.html /novnc.html COPY sv/ /etc/sv/ RUN chmod +x /entrypoint.sh \ && chmod +x /etc/sv/*/run \ - && ln -s /etc/sv/xvfb /etc/service/xvfb \ - && ln -s /etc/sv/fluxbox /etc/service/fluxbox \ - && ln -s /etc/sv/stalonetray /etc/service/stalonetray \ - && ln -s /etc/sv/x11vnc /etc/service/x11vnc \ - && ln -s /etc/sv/websockify /etc/service/websockify \ && ln -s /etc/sv/bridge /etc/service/bridge \ && ln -s /etc/sv/mayl /etc/service/mayl -ENV DISPLAY=:99 - -EXPOSE 6080 8080 +EXPOSE 8080 VOLUME ["/root/.config/protonmail", "/root/.local/share/protonmail", "/root/.gnupg", "/root/.password-store", "/data"] diff --git a/README.md b/README.md index 2aac5f4..e9ea23c 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ token authentication controls who can send. │ ┌─────────────────────────────────┐ │ │ │ protonmail-bridge │ │ │ │ localhost:1025 (SMTP) │ │ - │ │ Xvfb + Fluxbox + noVNC :6080 │ │ + │ │ --noninteractive (headless) │ │ │ └──────────────┬──────────────────┘ │ │ │ SMTP │ │ ┌──────────────▼──────────────────┐ │ @@ -29,13 +29,12 @@ token authentication controls who can send. │ │ │ runit (PID 1) supervises all services │ └──────────────────────────────────────┘ - :8080 :6080 - HTTP API VNC (browser) + :8080 + HTTP API ``` -A single container runs both **protonmail-bridge** and the **mayl** API. -Bridge provides SMTP on localhost:1025; mayl connects to it directly. noVNC -on port 6080 lets you log in to your Proton account through a browser. +A single container runs both **protonmail-bridge** (headless) and the **mayl** API. +Bridge provides SMTP on localhost:1025; mayl connects to it directly. [runit](https://smarden.org/runit/) supervises all processes, handling signal forwarding, automatic restarts, and clean shutdown. @@ -47,11 +46,17 @@ forwarding, automatic restarts, and clean shutdown. docker compose up -d --build ``` -### 2. Log in to Protonmail Bridge via VNC +### 2. Log in to Protonmail Bridge -Open [http://localhost:6080](http://localhost:6080) in your browser. You will -see a desktop with the Protonmail Bridge GUI. Sign in with your Proton account -credentials and note the SMTP username and password that Bridge generates. +Run the bridge CLI to log in to your Proton account: + +```bash +docker exec -it mayl protonmail-bridge --cli +``` + +Use the `login` command to authenticate with your Proton credentials. Note the +SMTP username and password that Bridge generates. This is a one-time step; +credentials persist across container restarts via Docker volumes. ### 3. Configure SMTP credentials @@ -234,12 +239,7 @@ runit automatically restarts any service that exits. | Service | Description | |--------------|-------------| -| `xvfb` | Virtual X display (:99) | -| `fluxbox` | Window manager | -| `stalonetray`| System tray (for bridge icon) | -| `x11vnc` | VNC server on :5900 | -| `websockify` | WebSocket proxy (noVNC on :6080) | -| `bridge` | protonmail-bridge with GUI | +| `bridge` | protonmail-bridge (headless, `--noninteractive`) | | `mayl` | mayl HTTP API | The entrypoint script runs one-time init (GPG key, pass, D-Bus) then @@ -250,7 +250,6 @@ The entrypoint script runs one-time init (GPG key, pass, D-Bus) then | Port | Service | |--------|------------------| | `8080` | mayl HTTP API | -| `6080` | noVNC (browser) | ## Volumes diff --git a/docker-compose.yml b/docker-compose.yml index 8f7872a..f57db6f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,6 @@ services: container_name: mayl ports: - "8080:8080" - - "6080:6080" environment: - MAYL_SMTP_HOST=localhost - MAYL_SMTP_PORT=1025 diff --git a/novnc.html b/novnc.html deleted file mode 100644 index bf37efd..0000000 --- a/novnc.html +++ /dev/null @@ -1,31 +0,0 @@ - - - -mayl - VNC - - - - -
connecting...
-
- - - diff --git a/sv/bridge/run b/sv/bridge/run index e1681cc..4e93b8b 100644 --- a/sv/bridge/run +++ b/sv/bridge/run @@ -1,5 +1,4 @@ #!/bin/sh -while [ ! -e /tmp/.X99-lock ]; do sleep 0.2; done # Clean up stale lock files from previous runs rm -f /root/.cache/protonmail/bridge-v3/bridge-v3-gui.lock @@ -8,4 +7,4 @@ rm -f /root/.cache/protonmail/bridge-v3/bridge-v3.lock # Source dbus session address written by entrypoint [ -f /run/dbus-session-env ] && . /run/dbus-session-env -exec protonmail-bridge +exec protonmail-bridge --noninteractive diff --git a/sv/fluxbox/run b/sv/fluxbox/run deleted file mode 100644 index 6953785..0000000 --- a/sv/fluxbox/run +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -# Wait for X -while [ ! -e /tmp/.X99-lock ]; do sleep 0.2; done -exec fluxbox diff --git a/sv/stalonetray/run b/sv/stalonetray/run deleted file mode 100644 index a13cd89..0000000 --- a/sv/stalonetray/run +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -while [ ! -e /tmp/.X99-lock ]; do sleep 0.2; done -exec stalonetray --geometry 5x1+0+0 --icon-size 24 --background "#2a2a2a" --grow-gravity E diff --git a/sv/websockify/run b/sv/websockify/run deleted file mode 100644 index 421da3b..0000000 --- a/sv/websockify/run +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -# Wait for VNC -while ! bash -c 'echo > /dev/tcp/localhost/5900' 2>/dev/null; do sleep 0.5; done - -# noVNC has no index.html, create a redirect to vnc.html -cp /novnc.html /usr/share/novnc/index.html - -exec websockify --web /usr/share/novnc 6080 localhost:5900 diff --git a/sv/x11vnc/run b/sv/x11vnc/run deleted file mode 100644 index 03ecc1c..0000000 --- a/sv/x11vnc/run +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -while [ ! -e /tmp/.X99-lock ]; do sleep 0.2; done -exec x11vnc -display :99 -forever -nopw -rfbport 5900 -shared diff --git a/sv/xvfb/run b/sv/xvfb/run deleted file mode 100644 index 73738b6..0000000 --- a/sv/xvfb/run +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -rm -f /tmp/.X99-lock -exec Xvfb :99 -screen 0 1024x768x24 From 4d9022d4c7de16e46fd954c866e461872e39e7f9 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Mar 2026 06:59:56 +0000 Subject: [PATCH 3/3] Remove plan.md after implementation complete https://claude.ai/code/session_0134TRgraVFXq3TiYmLYanRQ --- plan.md | 49 ------------------------------------------------- 1 file changed, 49 deletions(-) delete mode 100644 plan.md diff --git a/plan.md b/plan.md deleted file mode 100644 index 8db7f52..0000000 --- a/plan.md +++ /dev/null @@ -1,49 +0,0 @@ -# Plan: Remove VNC/GUI, Run Bridge Headless - -## Approach -Strip all VNC/GUI services and run `protonmail-bridge --noninteractive` with the existing DEB package. Keep minimal X11/Qt libs since the DEB binary links against them (it just won't open a window with `--noninteractive`). - -## Changes - -### 1. Dockerfile (`Dockerfile`) -- **Remove packages**: `xvfb`, `x11vnc`, `fluxbox`, `stalonetray`, `novnc`, `websockify`, `python3-gi` -- **Keep packages**: `dbus`, `dbus-x11`, `gnome-keyring`, `gir1.2-secret-1`, `gnupg`, `pass`, `ca-certificates`, `curl`, `runit`, and all the `lib*` X11/Qt/OpenGL packages (the DEB binary dynamically links them — removing them would crash the binary even in noninteractive mode) -- **Remove**: `COPY novnc.html /novnc.html` -- **Remove service symlinks**: `xvfb`, `fluxbox`, `stalonetray`, `x11vnc`, `websockify` -- **Remove**: `ENV DISPLAY=:99` -- **Change EXPOSE**: `6080 8080` → `8080` - -### 2. Bridge service (`sv/bridge/run`) -- Remove the Xvfb wait loop (`while [ ! -e /tmp/.X99-lock ]`) -- Add `--noninteractive` flag to `protonmail-bridge` -- Keep dbus sourcing and lock cleanup - -### 3. Entrypoint (`entrypoint.sh`) -- No changes needed — GPG, pass, and dbus setup are all still required - -### 4. Docker Compose (`docker-compose.yml`) -- Remove port mapping `6080:6080` - -### 5. Delete files -- `novnc.html` -- `sv/xvfb/run` -- `sv/fluxbox/run` -- `sv/stalonetray/run` -- `sv/x11vnc/run` -- `sv/websockify/run` - -### 6. Update CLAUDE.md and README.md -- Remove references to noVNC, VNC, port 6080, xvfb, fluxbox, stalonetray -- Document new login flow: `docker exec -it mayl protonmail-bridge --cli` for initial account setup - -## Initial Account Login (post-change) -Users will run: -```bash -docker exec -it mayl protonmail-bridge --cli -``` -Then use the `login` command to authenticate. This is a one-time step; credentials persist in the `bridge-pass` and `bridge-config` volumes. - -## Risk -The DEB binary links against Qt/X11 libs. Running with `--noninteractive` *should* skip GUI init, but if it doesn't, we'd need to either: -- Add back a minimal Xvfb (fallback) -- Switch to building from source with `make build-nogui`