feat(email): wire listmonk-hc into deploy + dev override + hc ramp-cap
- deploy.sh/deploy-dev.sh: bring up listmonk-hc (upstream image, excluded from build); document the one-time listmonk_hc DB create + --install. - docker-compose.dev.override.yml: dev-only override (committed) that drops the prod host-port bindings and pins dev's own postgres volume (dev-pgdata) via compose !override tags. deploy-dev ships it as docker-compose.override.yml so syncing the canonical compose to the shared host no longer breaks dev's api-postgres (port :5432 clash + volume switch). Discovered + fixed while validating listmonk-hc on dev. - pw-hc-rampcap.sh: healthcare analogue of pw-listmonk-rampcap, ramps the listmonk_hc cap 100->1000/h off /etc/postfix/hc-warmup-start, fully independent of the trucking ramp/cap.
This commit is contained in:
parent
08d5132459
commit
90d8b94f3f
4 changed files with 82 additions and 5 deletions
14
deploy.sh
14
deploy.sh
|
|
@ -8,11 +8,17 @@
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
cd /opt/performancewest
|
cd /opt/performancewest
|
||||||
|
|
||||||
SERVICES="${@:-site api workers proxy-relay}"
|
SERVICES="${@:-site api workers proxy-relay listmonk-hc}"
|
||||||
|
|
||||||
# proxy-relay is an upstream image (no build context). Build everything else,
|
# proxy-relay and listmonk-hc are upstream images (no build context). Build
|
||||||
# but always include it in the `up` set so the healthcare proxy sidecar runs.
|
# everything else, but always include them in the `up` set so the healthcare
|
||||||
BUILD_SERVICES="$(echo "$SERVICES" | tr ' ' '\n' | grep -v '^proxy-relay$' | tr '\n' ' ')"
|
# proxy sidecar and the healthcare-stream Listmonk run.
|
||||||
|
# NB: listmonk-hc needs a one-time DB setup the first time it is deployed:
|
||||||
|
# docker compose exec api-postgres psql -U pw -d postgres -c 'CREATE DATABASE listmonk_hc OWNER pw;'
|
||||||
|
# docker compose run --rm --entrypoint /bin/sh listmonk-hc -c './listmonk --install --idempotent --yes --config /listmonk/config.toml'
|
||||||
|
# then configure its 3 SMTP servers (hc ports 2526/2527/2528). See
|
||||||
|
# docs/healthcare-email-stream-plan.md.
|
||||||
|
BUILD_SERVICES="$(echo "$SERVICES" | tr ' ' '\n' | grep -vE '^(proxy-relay|listmonk-hc)$' | tr '\n' ' ')"
|
||||||
|
|
||||||
echo "=== Pulling latest from git ==="
|
echo "=== Pulling latest from git ==="
|
||||||
git pull origin main
|
git pull origin main
|
||||||
|
|
|
||||||
17
docker-compose.dev.override.yml
Normal file
17
docker-compose.dev.override.yml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Dev-only overrides. Dev shares the host with prod and historically used its
|
||||||
|
# own postgres data volume (dev-pgdata) and no host-port bindings. The canonical
|
||||||
|
# (prod) compose uses api-pgdata + host ports; pin dev back to its own data and
|
||||||
|
# drop the clashing host ports. !override replaces base lists.
|
||||||
|
services:
|
||||||
|
api-postgres:
|
||||||
|
ports: !override []
|
||||||
|
volumes: !override
|
||||||
|
- dev-pgdata:/var/lib/postgresql/data
|
||||||
|
listmonk:
|
||||||
|
ports: !override []
|
||||||
|
listmonk-hc:
|
||||||
|
ports: !override []
|
||||||
|
volumes:
|
||||||
|
dev-pgdata:
|
||||||
|
external: true
|
||||||
|
name: performancewest-dev_dev-pgdata
|
||||||
46
infra/postfix/pw-hc-rampcap.sh
Normal file
46
infra/postfix/pw-hc-rampcap.sh
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Ramp the listmonk-hc hourly send cap in lockstep with the healthcare IP warmup.
|
||||||
|
#
|
||||||
|
# The HEALTHCARE HOT stream sends from fresh dedicated IPs (.107/.108/.109), so
|
||||||
|
# even though institutional B2B mail tolerates far more volume than consumer
|
||||||
|
# cold mail, we still warm these IPs before hitting the 10k/day ceiling. This is
|
||||||
|
# the hc analogue of /usr/local/bin/pw-listmonk-rampcap, driven off a SEPARATE
|
||||||
|
# warmup stamp (/etc/postfix/hc-warmup-start) and writing a SEPARATE Listmonk
|
||||||
|
# DB (listmonk_hc), so the trucking ramp/cap and the healthcare ramp/cap are
|
||||||
|
# fully independent.
|
||||||
|
#
|
||||||
|
# Steady-state target (institutional, 3 IPs):
|
||||||
|
# day 0-1 : ~1,000/day -> 100/h
|
||||||
|
# day 2-4 : ~3,000/day -> 300/h
|
||||||
|
# day 5-9 : ~6,000/day -> 600/h
|
||||||
|
# day 10+ : ~10,000/day -> 1000/h (the chosen ceiling)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
STATE=/etc/postfix/hc-warmup-start
|
||||||
|
COMPOSE_DIR=/opt/performancewest
|
||||||
|
DB=listmonk_hc
|
||||||
|
PGPASSWORD=${DB_PASSWORD:-pw_dev_2026}
|
||||||
|
|
||||||
|
[ -f "$STATE" ] || { echo "no hc warmup stamp ($STATE); run hc_stream_setup.sh first"; exit 1; }
|
||||||
|
START=$(cat "$STATE"); NOW=$(date +%s); DAYS=$(( (NOW - START) / 86400 ))
|
||||||
|
|
||||||
|
if [ "$DAYS" -le 1 ]; then RATE=100
|
||||||
|
elif [ "$DAYS" -le 4 ]; then RATE=300
|
||||||
|
elif [ "$DAYS" -le 9 ]; then RATE=600
|
||||||
|
else RATE=1000; fi
|
||||||
|
|
||||||
|
cd "$COMPOSE_DIR"
|
||||||
|
psql() { PGPASSWORD=$PGPASSWORD docker compose exec -T -e PGPASSWORD=$PGPASSWORD api-postgres \
|
||||||
|
psql -U pw -d "$DB" -tAc "$1"; }
|
||||||
|
|
||||||
|
CUR=$(psql "SELECT value FROM settings WHERE key='app.message_sliding_window_rate';" 2>/dev/null || echo "")
|
||||||
|
if [ "$CUR" != "$RATE" ]; then
|
||||||
|
psql "UPDATE settings SET value='$RATE' WHERE key='app.message_sliding_window_rate';
|
||||||
|
UPDATE settings SET value='\"1h\"' WHERE key='app.message_sliding_window_duration';
|
||||||
|
UPDATE settings SET value='true' WHERE key='app.message_sliding_window';" >/dev/null
|
||||||
|
docker compose restart listmonk-hc >/dev/null 2>&1 || true
|
||||||
|
logger -t pw-hc-rampcap "day $DAYS -> listmonk-hc cap ${RATE}/h (was ${CUR}/h)"
|
||||||
|
echo "$(date '+%F %T') hc-rampcap: day=$DAYS cap=${RATE}/h (changed from ${CUR}/h, listmonk-hc restarted)"
|
||||||
|
else
|
||||||
|
echo "$(date '+%F %T') hc-rampcap: day=$DAYS cap=${RATE}/h (no change)"
|
||||||
|
fi
|
||||||
|
|
@ -60,12 +60,20 @@ done
|
||||||
# and not overwritten.
|
# and not overwritten.
|
||||||
rsync -avz -e "$SSH" docker-compose.yml "$REMOTE:$DEV_DIR/docker-compose.yml"
|
rsync -avz -e "$SSH" docker-compose.yml "$REMOTE:$DEV_DIR/docker-compose.yml"
|
||||||
|
|
||||||
|
# Dev-only compose override: dev shares the host with prod, so it must NOT bind
|
||||||
|
# the prod host ports (5432/9100/9101/...) and must use its own postgres data
|
||||||
|
# volume (dev-pgdata). Shipped as docker-compose.override.yml (compose auto-loads
|
||||||
|
# it). Without this, syncing the canonical compose makes dev's api-postgres try
|
||||||
|
# to rebind prod's :5432 and switch volumes -> stack breakage. See
|
||||||
|
# docs/healthcare-email-stream-plan.md.
|
||||||
|
rsync -avz -e "$SSH" docker-compose.dev.override.yml "$REMOTE:$DEV_DIR/docker-compose.override.yml"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Rebuilding containers ==="
|
echo "=== Rebuilding containers ==="
|
||||||
|
|
||||||
# proxy-relay is an upstream image (no build context); `up --build` skips the
|
# proxy-relay is an upstream image (no build context); `up --build` skips the
|
||||||
# build for it but still (re)creates it from the compose definition.
|
# build for it but still (re)creates it from the compose definition.
|
||||||
$SSH "$REMOTE" "cd $DEV_DIR && sudo docker compose up -d --build api site workers proxy-relay"
|
$SSH "$REMOTE" "cd $DEV_DIR && sudo docker compose up -d --build api site workers proxy-relay listmonk-hc"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Deploy complete ==="
|
echo "=== Deploy complete ==="
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue