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>
This commit is contained in:
parent
6def0f6186
commit
2fab98c0a8
2 changed files with 140 additions and 0 deletions
80
infra/postfix/mta_setup.sh
Normal file
80
infra/postfix/mta_setup.sh
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
TS=$(date +%s)
|
||||
echo "=== backups ==="
|
||||
cp /etc/postfix/master.cf /etc/postfix/master.cf.bak.$TS
|
||||
cp /etc/postfix/main.cf /etc/postfix/main.cf.bak.$TS
|
||||
cp /etc/postfix/transport /etc/postfix/transport.bak.$TS
|
||||
echo "backed up (.$TS)"
|
||||
|
||||
echo "=== master.cf: yahooslow bind .90 + add out02..out20 ==="
|
||||
python3 - <<'PY'
|
||||
mc = open('/etc/postfix/master.cf').read()
|
||||
if 'smtp_bind_address=207.174.124.90' not in mc:
|
||||
out=[]; iny=False
|
||||
for ln in mc.split('\n'):
|
||||
out.append(ln)
|
||||
if ln.startswith('yahooslow '): iny=True
|
||||
elif iny and 'smtp_destination_recipient_limit=1' in ln:
|
||||
out.append(' -o smtp_bind_address=207.174.124.90')
|
||||
out.append(' -o smtp_helo_name=mta01.performancewest.net')
|
||||
iny=False
|
||||
mc='\n'.join(out)
|
||||
if 'out02 unix' not in mc:
|
||||
b=['','# === Warmup rotation pool (.91-.109 / mta02-mta20) ===']
|
||||
for i in range(2,21):
|
||||
octet=89+i; name='out%02d'%i; helo='mta%02d.performancewest.net'%i
|
||||
b += ['%s unix - - n - - smtp'%name,
|
||||
' -o smtp_bind_address=207.174.124.%d'%octet,
|
||||
' -o smtp_helo_name=%s'%helo,
|
||||
' -o syslog_name=postfix/%s'%name,
|
||||
' -o smtp_destination_concurrency_limit=2',
|
||||
' -o smtp_destination_rate_delay=2s',
|
||||
' -o smtp_destination_recipient_limit=2']
|
||||
mc=mc.rstrip('\n')+'\n'+'\n'.join(b)+'\n'
|
||||
open('/etc/postfix/master.cf','w').write(mc)
|
||||
print(' master.cf updated')
|
||||
PY
|
||||
|
||||
echo "=== transport: yahoo/aol/yahoo-ISPs -> yahooslow; gmail/outlook removed (rotate) ==="
|
||||
cat > /etc/postfix/transport <<'EOF'
|
||||
# Yahoo/AOL + Yahoo-hosted ISPs -> dedicated .90 trickle (yahooslow). Everything
|
||||
# else falls through transport_maps to the randmap rotation pool.
|
||||
yahoo.com yahooslow:
|
||||
ymail.com yahooslow:
|
||||
rocketmail.com yahooslow:
|
||||
aol.com yahooslow:
|
||||
att.net yahooslow:
|
||||
sbcglobal.net yahooslow:
|
||||
bellsouth.net yahooslow:
|
||||
verizon.net yahooslow:
|
||||
frontier.com yahooslow:
|
||||
frontiernet.net yahooslow:
|
||||
EOF
|
||||
/usr/sbin/postmap /etc/postfix/transport
|
||||
echo " transport rebuilt"
|
||||
|
||||
echo "=== install warmup scheduler + stamp start + set phase-1 pool ==="
|
||||
cp /tmp/pw-mta-warmup.sh /usr/local/bin/pw-mta-warmup
|
||||
chmod +x /usr/local/bin/pw-mta-warmup
|
||||
/usr/local/bin/pw-mta-warmup --start
|
||||
|
||||
echo "=== postfix check (gate) ==="
|
||||
if /usr/sbin/postfix check 2>&1; then
|
||||
echo "CHECK OK"; /usr/sbin/postfix reload && echo "RELOADED"
|
||||
else
|
||||
echo "CHECK FAILED — rolling back"
|
||||
cp /etc/postfix/master.cf.bak.$TS /etc/postfix/master.cf
|
||||
cp /etc/postfix/main.cf.bak.$TS /etc/postfix/main.cf
|
||||
cp /etc/postfix/transport.bak.$TS /etc/postfix/transport
|
||||
/usr/sbin/postmap /etc/postfix/transport
|
||||
/usr/sbin/postfix reload
|
||||
echo "ROLLED BACK"; exit 1
|
||||
fi
|
||||
|
||||
echo "=== cron (daily warmup advance) ==="
|
||||
echo '17 7 * * * root /usr/local/bin/pw-mta-warmup >> /var/log/pw-mta-warmup.log 2>&1' > /etc/cron.d/pw-mta-warmup
|
||||
echo "=== final transport_maps ==="
|
||||
/usr/sbin/postconf -h transport_maps
|
||||
echo "=== out transport count ==="
|
||||
grep -cE "^out[0-9]+ unix" /etc/postfix/master.cf
|
||||
60
infra/postfix/pw-mta-warmup.sh
Executable file
60
infra/postfix/pw-mta-warmup.sh
Executable file
|
|
@ -0,0 +1,60 @@
|
|||
#!/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
|
||||
Loading…
Add table
Add a link
Reference in a new issue