new-site/infra/postfix/pw-mta-warmup.sh
justin 2fab98c0a8 postfix: multi-IP warmup sending pool (20 IPs, gradual rotation)
- 20 IPs (.90-.109 / mta01-mta20) with FCrDNS + SPF in HestiaCP
- .90 (mta01) dedicated Yahoo/AOL recovery IP (yahooslow, 20s trickle)
- .91-.109 (out02-out20) rotation pool via transport_maps randmap
- pw-mta-warmup: cron-driven scheduler grows the active rotation pool
  3 -> 5 -> 8 -> 12 -> 16 -> 19 IPs over ~25 days
- mta_setup.sh: idempotent installer (backups + postfix-check-gated reload)

New IPs verified clean on Spamhaus/Barracuda/SpamCop/SORBS.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 19:03:30 -05:00

60 lines
2.2 KiB
Bash
Executable file

#!/bin/bash
# Postfix IP-warmup scheduler.
#
# Gradually expands the outbound sending-IP rotation pool over a warmup window,
# so we don't blast 20 brand-new IPs at consumer ISPs on day one (which looks
# like snowshoe spam and torches reputation).
#
# IP layout:
# .90 / mta01 -> dedicated Yahoo/AOL recovery IP (transport: yahooslow)
# .91-.109 / mta02-mta20 -> rotation pool (transports: out02..out20)
#
# transport_maps = hash:/etc/postfix/transport, randmap:{<active pool>}
# - hash routes yahoo/aol -> yahooslow (the dedicated .90 trickle)
# - randmap round-robins the source IP across the *active* rotation pool
#
# This script (run daily by cron) recomputes the active pool from the warmup
# start date and reloads Postfix only when the pool changes. It never shrinks.
#
# Run once with `--start` to stamp the start date.
set -euo pipefail
STATE=/etc/postfix/pw-warmup-start
MAINCF=/etc/postfix/main.cf
POSTCONF=/usr/sbin/postconf
POSTFIX=/usr/sbin/postfix
# Rotation transports (.91-.109). .90 is Yahoo-dedicated, not in rotation.
ALL=(out02 out03 out04 out05 out06 out07 out08 out09 out10 out11 \
out12 out13 out14 out15 out16 out17 out18 out19 out20)
if [ "${1:-}" = "--start" ]; then
date +%s | sudo tee "$STATE" >/dev/null
echo "warmup start stamped: $(date)"
fi
[ -f "$STATE" ] || date +%s | sudo tee "$STATE" >/dev/null
START=$(cat "$STATE"); NOW=$(date +%s); DAYS=$(( (NOW - START) / 86400 ))
# Warmup schedule: day-since-start -> active rotation IPs
if [ "$DAYS" -le 3 ]; then N=3
elif [ "$DAYS" -le 7 ]; then N=5
elif [ "$DAYS" -le 11 ]; then N=8
elif [ "$DAYS" -le 17 ]; then N=12
elif [ "$DAYS" -le 24 ]; then N=16
else N=19; fi
POOL=""
for ((i=0; i<N; i++)); do POOL="${POOL}${ALL[$i]}:,"; done
POOL="${POOL%,}"
NEWVAL="hash:/etc/postfix/transport, randmap:{${POOL}}"
CUR=$($POSTCONF -h transport_maps 2>/dev/null || echo "")
if [ "$CUR" != "$NEWVAL" ]; then
sudo $POSTCONF -e "transport_maps=${NEWVAL}"
sudo $POSTFIX reload >/dev/null 2>&1 || true
logger -t pw-warmup "day $DAYS -> $N rotation IPs active"
echo "$(date '+%F %T') warmup: day=$DAYS active_rotation_ips=$N"
else
echo "$(date '+%F %T') warmup: day=$DAYS active_rotation_ips=$N (no change)"
fi