#!/usr/bin/env bash # SPDX-License-Identifier: GPL-3.0-or-later # cmail-setup-finish.sh — finish Proton Mail Bridge setup after Bridge # first-run. Idempotent; safe to re-run after a Bridge cert rotation. # # Pre-reqs (the script aborts if any are missing): # - protonmail-bridge installed (archsetup handles it) # - You have run 'protonmail-bridge --cli', logged in, and quit at least once # (the script looks for state at ~/.config/protonmail/bridge-v3/) # - dotfiles stowed (~/.config/.cmailpass.gpg present) # # Not a pre-req, but checked and warned about: cmail-action on PATH. rulesets' # `make install` links it, and session start runs that, so on a machine that # runs agent sessions it arrives without anyone asking. On one that doesn't, # it needs the command by hand. The script never invokes it either way. # # What it does: # 1. Decrypts ~/.config/.cmailpass.gpg → ~/.config/.cmailpass (mode 0600) # 2. Copies Bridge's self-signed cert → ~/.config/protonbridge.pem # 3. Removes the leftover ~/.config/autostart/Proton Mail Bridge.desktop # stub (it double-launches Bridge alongside the systemd user service # and throws an "orphan instance" dialog every login) # 4. Installs a wait-for-dns drop-in so Bridge doesn't spam # name-resolution errors during the early-boot DNS race # 5. Enables + starts the protonmail-bridge user service # 6. Verifies Bridge is listening on 127.0.0.1:1143 / :1025 # # It no longer installs cmail-action. That moved to rulesets # (claude-templates/bin/), whose `make install` owns the symlink. set -euo pipefail err() { printf 'error: %s\n' "$*" >&2; exit 1; } warn() { printf 'warning: %s\n' "$*" >&2; } info() { printf '==> %s\n' "$*"; } ok() { printf ' %s\n' "$*"; } # Decrypt $1 to $2 with 0600 from the moment of creation. gpg writes its output # at the process umask (often 0644), so a bare decrypt leaves the plaintext # world-readable until the chmod on the next line. The 0077 umask subshell closes # that window; the chmod stays to tighten a looser file left by an earlier run. decrypt_to_secure() { ( umask 077; gpg --quiet --yes --decrypt --output "$2" "$1" ) chmod 600 "$2" } # 1. Pre-reqs command -v protonmail-bridge >/dev/null 2>&1 \ || err "protonmail-bridge not found in PATH — install via archsetup first" bridge_state="$HOME/.config/protonmail/bridge-v3" [ -d "$bridge_state" ] \ || err "Bridge has no state at $bridge_state — run 'protonmail-bridge --cli' and log in first" # cmail-action is no longer this script's to install. It lives in rulesets at # claude-templates/bin/, and rulesets' `make install` links everything there # into ~/.local/bin. Session start runs that, so on a machine that runs agent # sessions the symlink arrives on its own; on one that doesn't, it needs the # command below. # # A warning rather than an abort, because this script never invokes the tool. # Its job is to leave Bridge working, and it can finish that whether or not a # mail client has been linked yet. Aborting here would make Bridge setup # depend on rulesets being cloned and installed first, an ordering neither # repo otherwise needs, and would strand a fresh machine with Bridge ready and # the script refusing to configure it. command -v cmail-action >/dev/null 2>&1 \ || warn "cmail-action not on PATH — run 'make -C ~/code/rulesets install' before sending mail" cmailpass_enc="$HOME/.config/.cmailpass.gpg" [ -f "$cmailpass_enc" ] \ || err "$cmailpass_enc not found — ensure dotfiles are stowed" # 2. Decrypt cmailpass info "decrypting $cmailpass_enc" cmailpass_plain="$HOME/.config/.cmailpass" decrypt_to_secure "$cmailpass_enc" "$cmailpass_plain" ok "wrote $cmailpass_plain (mode 0600)" # 3. Bridge cert info "exporting Bridge cert" cert_src="$(find "$bridge_state" -name 'cert.pem' -print -quit 2>/dev/null)" [ -n "$cert_src" ] || err "no cert.pem found under $bridge_state — Bridge state is incomplete" cert_dst="$HOME/.config/protonbridge.pem" cp "$cert_src" "$cert_dst" ok "copied $cert_src → $cert_dst" # 4. Remove leftover XDG autostart stub # The systemd --user service is the canonical launcher. The autostart .desktop # starts a second Bridge instance that can't get the lock and pops up an # "orphan instance" dialog every login. info "removing orphan autostart launcher (if present)" autostart_stub="$HOME/.config/autostart/Proton Mail Bridge.desktop" if [ -f "$autostart_stub" ]; then rm "$autostart_stub" ok "removed $autostart_stub" else ok "no autostart stub present" fi # 5. Install wait-for-dns drop-in # User-instance systemd doesn't carry network-online.target / nss-lookup.target, # so the packaged unit's After=network.target doesn't imply DNS readiness. # Bridge starts before the resolver is up and its first API calls all fail # until DNS comes online a few seconds later. The drop-in waits (bounded 30s) # for resolution before ExecStart. The leading '-' on ExecStartPre makes it # non-fatal, so an offline boot still starts the unit. info "installing wait-for-dns drop-in" dropin_dir="$HOME/.config/systemd/user/protonmail-bridge.service.d" dropin_file="$dropin_dir/wait-for-dns.conf" mkdir -p "$dropin_dir" cat > "$dropin_file" <<'EOF' [Service] ExecStartPre=-/bin/sh -c 'for i in $(seq 1 30); do getent hosts mail-api.proton.me >/dev/null 2>&1 && exit 0; sleep 1; done' EOF ok "wrote $dropin_file" systemctl --user daemon-reload ok "reloaded systemd user units" # 6. Enable + start systemd user service info "enabling protonmail-bridge user service" was_active=0 systemctl --user is-active --quiet protonmail-bridge.service && was_active=1 systemctl --user enable --now protonmail-bridge if [ "$was_active" = "1" ]; then systemctl --user restart protonmail-bridge ok "service active (restarted to pick up drop-in)" else ok "service active" fi # 7. Verify info "verifying Bridge is listening" listening="$(ss -ltn 2>/dev/null || true)" missing="" echo "$listening" | grep -q '127\.0\.0\.1:1143' || missing="$missing 1143 (IMAP)" echo "$listening" | grep -q '127\.0\.0\.1:1025' || missing="$missing 1025 (SMTP)" if [ -z "$missing" ]; then ok "127.0.0.1:1143 + :1025 LISTEN" else error_status="$(systemctl --user status protonmail-bridge --no-pager --lines=10 2>&1 || true)" printf '%s\n' "$error_status" >&2 err "Bridge isn't listening on:${missing}" fi echo echo "cmail setup complete." echo "Next: 'mbsync cmail && mu index' for the first sync."