1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
|
#!/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."
|