210 lines
9.9 KiB
Bash
Executable File
210 lines
9.9 KiB
Bash
Executable File
#!/bin/bash
|
|
# Exit immediately on error, treat unset variables as errors, propagate pipe failures.
|
|
set -euo pipefail
|
|
# Load shared log/warn/skip helpers from the installer library.
|
|
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
|
|
|
log "Installing mail stack (isync, msmtp, notmuch, alot, w3m)..."
|
|
# isync/mbsync: IMAP sync daemon; msmtp: SMTP sender; notmuch: fast mail indexer;
|
|
# alot: terminal mail UI built on notmuch; w3m: renders HTML mail parts as plain text.
|
|
sudo pacman -S --noconfirm --needed isync msmtp notmuch alot w3m
|
|
|
|
# ── Credentials ───────────────────────────────────────────────────────────────
|
|
# Collect all account details interactively before writing any config files,
|
|
# so the user can review/abort cleanly before any files are touched.
|
|
read -rp "Full name : " FULL_NAME
|
|
read -rp "Email address : " EMAIL
|
|
read -rp "IMAP host (e.g. mail.example.com) : " IMAP_HOST
|
|
# Default IMAP port to 993 (IMAPS/TLS); user may override for non-standard setups.
|
|
read -rp "IMAP port [993] : " IMAP_PORT; IMAP_PORT="${IMAP_PORT:-993}"
|
|
# Default IMAP username to the email address, which most providers require.
|
|
read -rp "IMAP username [$EMAIL] : " IMAP_USER; IMAP_USER="${IMAP_USER:-$EMAIL}"
|
|
# -s suppresses echo so the password is not visible; trailing `echo` restores the newline.
|
|
read -rsp "IMAP password : " IMAP_PASS; echo
|
|
read -rp "SMTP host (e.g. mail.example.com) : " SMTP_HOST
|
|
# Default SMTP port to 587 (STARTTLS submission); use 465 for implicit TLS.
|
|
read -rp "SMTP port [587] : " SMTP_PORT; SMTP_PORT="${SMTP_PORT:-587}"
|
|
|
|
# Root of the local Maildir tree; mbsync creates per-folder subdirectories here.
|
|
MAILDIR="$HOME/Mail"
|
|
mkdir -p "$MAILDIR"
|
|
|
|
# ── mbsync ────────────────────────────────────────────────────────────────────
|
|
log "Writing ~/.mbsyncrc..."
|
|
# Heredoc writes the full mbsync config in one shot using the variables collected
|
|
# above. Overwriting on each run means re-running the script updates credentials.
|
|
cat > ~/.mbsyncrc << EOF
|
|
IMAPAccount main
|
|
Host $IMAP_HOST
|
|
Port $IMAP_PORT
|
|
User $IMAP_USER
|
|
Pass $IMAP_PASS
|
|
SSLType IMAPS
|
|
# Trust the system CA bundle rather than pinning a specific server certificate.
|
|
CertificateFile /etc/ssl/certs/ca-certificates.crt
|
|
|
|
IMAPStore main-remote
|
|
Account main
|
|
|
|
MaildirStore main-local
|
|
# SubFolders Verbatim: mirror the IMAP folder hierarchy exactly as subdirectories.
|
|
SubFolders Verbatim
|
|
Path $MAILDIR/
|
|
Inbox $MAILDIR/INBOX
|
|
|
|
Channel main
|
|
Far :main-remote:
|
|
Near :main-local:
|
|
# Sync all IMAP folders; replace * with a list to restrict to specific folders.
|
|
Patterns *
|
|
# Create Both: automatically create missing folders on either side.
|
|
Create Both
|
|
# SyncState *: store .mbsyncstate alongside each Maildir folder for portability.
|
|
SyncState *
|
|
# Expunge Both: propagate deletions bidirectionally so remote deletes appear locally.
|
|
Expunge Both
|
|
EOF
|
|
# 600 permissions keep the plaintext password private from other local users.
|
|
chmod 600 ~/.mbsyncrc
|
|
|
|
# ── msmtp ─────────────────────────────────────────────────────────────────────
|
|
log "Writing ~/.msmtprc..."
|
|
# msmtp acts as a drop-in sendmail replacement; alot invokes it via the
|
|
# sendmail_command field in the alot account block written further below.
|
|
cat > ~/.msmtprc << EOF
|
|
defaults
|
|
tls on
|
|
tls_trust_file /etc/ssl/certs/ca-certificates.crt
|
|
# Log every sent message with a timestamp to aid debugging delivery failures.
|
|
logfile ~/.msmtp.log
|
|
|
|
account main
|
|
host $SMTP_HOST
|
|
port $SMTP_PORT
|
|
auth on
|
|
user $IMAP_USER
|
|
password $IMAP_PASS
|
|
from $EMAIL
|
|
|
|
# Make "main" the account used when msmtp is called without an explicit -a flag.
|
|
account default : main
|
|
EOF
|
|
# Same 600 restriction as mbsyncrc — this file contains a plaintext SMTP password.
|
|
chmod 600 ~/.msmtprc
|
|
|
|
# ── notmuch ───────────────────────────────────────────────────────────────────
|
|
log "Configuring notmuch..."
|
|
# notmuch config set writes to ~/.notmuch-config; each call is idempotent
|
|
# (overwrites the existing key) so re-running the script is safe.
|
|
notmuch config set user.name "$FULL_NAME"
|
|
notmuch config set user.email "$EMAIL"
|
|
# Point notmuch at the Maildir root so `notmuch new` scans the right location.
|
|
notmuch config set database.path "$MAILDIR"
|
|
# Synchronise notmuch tags with Maildir flags (Seen, Replied, etc.) bidirectionally.
|
|
notmuch config set maildir.synchronize_flags true
|
|
# Tag every newly indexed message as unread and inbox by default.
|
|
notmuch config set new.tags "unread;inbox"
|
|
|
|
# post-new hook: tag sent mail, remove inbox from trash
|
|
# notmuch executes this script automatically after `notmuch new` indexes messages.
|
|
mkdir -p "$MAILDIR/.notmuch/hooks"
|
|
cat > "$MAILDIR/.notmuch/hooks/post-new" << 'EOF'
|
|
#!/bin/bash
|
|
# Apply folder-based tags and strip inbox/unread from non-inbox folders.
|
|
notmuch tag +sent -inbox -- folder:Sent
|
|
notmuch tag +trash -inbox -unread -- folder:Trash
|
|
notmuch tag +draft -inbox -- folder:Drafts
|
|
notmuch tag +spam -inbox -unread -- folder:Spam folder:Junk
|
|
EOF
|
|
# The hook must be executable or notmuch will silently skip it.
|
|
chmod +x "$MAILDIR/.notmuch/hooks/post-new"
|
|
|
|
# ── alot ──────────────────────────────────────────────────────────────────────
|
|
# The bindings section lives in ~/Dotfiles/alot/config (symlinked by shell-setup.sh).
|
|
# Write only the account block, which contains machine-specific paths/identity.
|
|
log "Writing account details into ~/Dotfiles/alot/config..."
|
|
ALOT_CFG="$HOME/Dotfiles/alot/config"
|
|
# Use an inline Python script to splice the [accounts] block into the existing
|
|
# config file. sed struggles with multi-line replacements; Python's re.sub with
|
|
# re.DOTALL handles the entire block atomically and overwrites any prior values.
|
|
# Replace the [[main]] account block in-place (sed removes old block, cat appends new one)
|
|
python3 - "$ALOT_CFG" "$FULL_NAME" "$EMAIL" "$MAILDIR" << 'PYEOF'
|
|
import sys, re
|
|
path, name, email, maildir = sys.argv[1:]
|
|
block = f"""[accounts]
|
|
[[main]]
|
|
realname = {name}
|
|
address = {email}
|
|
sendmail_command = msmtp -a main
|
|
sent_box = maildir://{maildir}/Sent
|
|
draft_box = maildir://{maildir}/Drafts
|
|
"""
|
|
with open(path) as f:
|
|
text = f.read()
|
|
# Replace from [accounts] up to the next top-level section or end-of-file,
|
|
# ensuring any previous account block is cleanly overwritten on re-runs.
|
|
text = re.sub(r'\[accounts\].*?(?=\n\[|\Z)', block, text, flags=re.DOTALL)
|
|
with open(path, 'w') as f:
|
|
f.write(text)
|
|
PYEOF
|
|
|
|
# ── mailcap (HTML email rendering via w3m) ────────────────────────────────────
|
|
log "Writing ~/.mailcap..."
|
|
# grep -qxF checks for an exact full-line match to avoid appending duplicates
|
|
# on repeated runs; the || only appends the entry when it is absent.
|
|
# w3m -dump converts HTML to plain text; copiousoutput tells mail clients
|
|
# to page the result rather than try to display it inline.
|
|
grep -qxF "text/html; w3m -dump -o document_charset=%{charset} '%s'; nametemplate=%s.html; copiousoutput" ~/.mailcap 2>/dev/null \
|
|
|| echo "text/html; w3m -dump -o document_charset=%{charset} '%s'; nametemplate=%s.html; copiousoutput" >> ~/.mailcap
|
|
|
|
# ── systemd timer for periodic sync ───────────────────────────────────────────
|
|
log "Installing mbsync systemd user timer (every 5 min)..."
|
|
# User units live under ~/.config/systemd/user/; --user scope requires no root
|
|
# and units only run while the user session is active.
|
|
mkdir -p ~/.config/systemd/user
|
|
|
|
# Type=oneshot: systemd marks the service done as soon as ExecStart exits,
|
|
# appropriate for a batch sync command that runs and terminates.
|
|
# After=network-online.target: prevents sync attempts before a route is available.
|
|
cat > ~/.config/systemd/user/mbsync.service << EOF
|
|
[Unit]
|
|
Description=Sync mail with mbsync
|
|
After=network-online.target
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=/usr/bin/mbsync -a
|
|
# Re-index immediately after each sync so alot reflects new messages right away.
|
|
ExecStartPost=/usr/bin/notmuch new
|
|
EOF
|
|
|
|
# OnBootSec=2min: delay the first run by 2 min to avoid startup congestion.
|
|
# OnUnitActiveSec=5min: repeat 5 min after the previous run finishes.
|
|
cat > ~/.config/systemd/user/mbsync.timer << EOF
|
|
[Unit]
|
|
Description=Run mbsync every 5 minutes
|
|
|
|
[Timer]
|
|
OnBootSec=2min
|
|
OnUnitActiveSec=5min
|
|
Unit=mbsync.service
|
|
|
|
[Install]
|
|
WantedBy=timers.target
|
|
EOF
|
|
|
|
# Reload the user manager so it picks up the newly written unit files.
|
|
systemctl --user daemon-reload
|
|
# enable makes the timer survive reboots; --now also starts it in the current session.
|
|
systemctl --user enable --now mbsync.timer
|
|
|
|
# ── initial sync ──────────────────────────────────────────────────────────────
|
|
log "Running initial mail sync..."
|
|
# -a syncs all configured channels; may take several minutes on a large mailbox.
|
|
mbsync -a
|
|
# Index the freshly downloaded messages so alot can display them immediately.
|
|
notmuch new
|
|
|
|
log "Mail setup complete. Syncs automatically every 5 min via systemd timer."
|
|
log "Open alot with: alot | Check timer: systemctl --user status mbsync.timer"
|