Files
computer-playbook/roles/web-app-stalwart/files/test/verify_migration.sh

145 lines
5.5 KiB
Bash

#!/usr/bin/env bash
# Mailu → Stalwart migration, end to end against the deployed stack, entirely inside the
# DiD host: mail is delivered into the legacy Mailu, the import script moves it into
# Stalwart, and the same messages are then read back out of Stalwart over IMAP.
# No redeploy is involved — the cutover is a docker-level data move.
# nocheck: raw-docker — storage assertions read the maildir volume via the container wrapper
set -euo pipefail
: "${STALWART_MIGRATION_E2E:?missing STALWART_MIGRATION_E2E}"
if [[ "${STALWART_MIGRATION_E2E}" != "true" ]]; then
echo "SKIP: web-app-mailu is not co-deployed; nothing to migrate."
exit 0
fi
: "${PYTHON_BIN:?missing PYTHON_BIN}"
: "${MIGRATION_SCRIPT:?missing MIGRATION_SCRIPT}"
: "${ADMIN_EMAIL:?missing ADMIN_EMAIL}"
: "${ADMIN_IMAP_PASSWORD:?missing ADMIN_IMAP_PASSWORD}"
: "${BIBER_EMAIL:?missing BIBER_EMAIL}"
: "${BIBER_IMAP_PASSWORD:?missing BIBER_IMAP_PASSWORD}"
: "${MAILU_MAILDIR_VOLUME:?missing MAILU_MAILDIR_VOLUME}"
: "${MAILU_SMTP_CONTAINER:?missing MAILU_SMTP_CONTAINER}"
: "${MAIL_DOMAIN:?missing MAIL_DOMAIN}"
RUN_ID="$(date +%s)"
SUBJ_A="infinito-mig-A-${RUN_ID}"
SUBJ_B="infinito-mig-B-${RUN_ID}"
SUBJ_C="infinito-mig-C-${RUN_ID}"
SUBJ_D="infinito-mig-D-${RUN_ID}"
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
WORKDIR="$(mktemp -d)"
cleanup() { rm -rf "${WORKDIR}"; }
trap cleanup EXIT
compose_mail() {
local from="$1" rcpt="$2" subject="$3" in_reply_to="${4:-}"
{
printf 'From: %s\r\nTo: %s\r\nSubject: %s\r\n' "${from}" "${rcpt}" "${subject}"
printf 'Message-ID: <%s@%s>\r\nDate: %s\r\n' "${subject}" "${MAIL_DOMAIN}" "$(date -R)"
if [[ -n "${in_reply_to}" ]]; then
printf 'In-Reply-To: <%s@%s>\r\n' "${in_reply_to}" "${MAIL_DOMAIN}"
fi
printf '\r\n%s\r\n' "migration e2e body ${subject}"
} >"${WORKDIR}/mail.eml"
}
send_smtp() {
local host="$1" from="$2" rcpt="$3" subject="$4" attempt
for attempt in 1 2 3 4 5 6; do
if "${PYTHON_BIN}" "${SCRIPT_DIR}/probe_mail.py" send \
"${host}" "${from}" "${rcpt}" "${WORKDIR}/mail.eml"; then
echo "sent: ${subject} (${from} -> ${rcpt} via ${host})"
return 0
fi
echo "retry ${attempt}: submission of ${subject} to ${host} failed; waiting 10s"
sleep 10
done
echo "FAIL: could not submit ${subject} to ${host}" >&2
return 1
}
wait_stored_in_maildir() {
local subject="$1" mountpoint="$2" attempt
for attempt in $(seq 1 30); do
if grep -rqF "Subject: ${subject}" "${mountpoint}" 2>/dev/null; then
echo "stored: ${subject} under ${mountpoint}"
return 0
fi
sleep 5
done
echo "FAIL: ${subject} never appeared under ${mountpoint}" >&2
return 1
}
wait_in_imap() {
local user="$1" password="$2" subject="$3" attempt
for attempt in $(seq 1 30); do
if "${PYTHON_BIN}" "${SCRIPT_DIR}/probe_mail.py" find \
127.0.0.1 "${user}" "${password}" "${subject}"; then
echo "found: ${subject} in ${user}'s mailbox"
return 0
fi
sleep 5
done
echo "FAIL: ${subject} not found over IMAP for ${user}" >&2
return 1
}
# The migration is a pure docker-level move: read the legacy maildir volume, APPEND over
# IMAP. No redeploy is involved, so the harness invokes the very same script (and argv
# shape) that tasks/12_import_mailu.yml runs on a real cutover.
run_migration() {
local maildir="$1"
"${PYTHON_BIN}" -c \
'import json,sys; json.dump({sys.argv[1]: sys.argv[2], sys.argv[3]: sys.argv[4]}, open(sys.argv[5], "w"))' \
"${ADMIN_EMAIL}" "${ADMIN_IMAP_PASSWORD}" \
"${BIBER_EMAIL}" "${BIBER_IMAP_PASSWORD}" \
"${WORKDIR}/accounts.json"
chmod 600 "${WORKDIR}/accounts.json"
"${PYTHON_BIN}" "${MIGRATION_SCRIPT}" \
--maildir-root "${maildir}" \
--imap-host 127.0.0.1 --imap-port 993 --imap-insecure \
--accounts-file "${WORKDIR}/accounts.json"
rm -f "${WORKDIR}/accounts.json"
}
echo "=== [1/4] Deliver mail into the legacy Mailu (A: biber->admin, B: admin's reply) ==="
MAILU_SMTP_IP="$(container inspect --type container -f '{{ range .NetworkSettings.Networks }}{{ .IPAddress }} {{ end }}' "${MAILU_SMTP_CONTAINER}" | awk '{print $1}')"
case "${MAILU_SMTP_IP}" in
[0-9]*.[0-9]*.[0-9]*.[0-9]*) ;;
*)
echo "FAIL: ${MAILU_SMTP_CONTAINER} has no IPv4 address (got '${MAILU_SMTP_IP}'); the legacy Mailu stack is not running" >&2
exit 1
;;
esac
echo "mailu smtp at ${MAILU_SMTP_IP}"
compose_mail "${BIBER_EMAIL}" "${ADMIN_EMAIL}" "${SUBJ_A}"
send_smtp "${MAILU_SMTP_IP}" "${BIBER_EMAIL}" "${ADMIN_EMAIL}" "${SUBJ_A}"
compose_mail "${ADMIN_EMAIL}" "${BIBER_EMAIL}" "${SUBJ_B}" "${SUBJ_A}"
send_smtp "${MAILU_SMTP_IP}" "${ADMIN_EMAIL}" "${BIBER_EMAIL}" "${SUBJ_B}"
echo "=== [2/4] Confirm the messages are stored in Mailu's maildir ==="
MAILDIR_MOUNT="$(container volume inspect --format '{{ .Mountpoint }}' "${MAILU_MAILDIR_VOLUME}")"
wait_stored_in_maildir "${SUBJ_A}" "${MAILDIR_MOUNT}/${ADMIN_EMAIL}"
wait_stored_in_maildir "${SUBJ_B}" "${MAILDIR_MOUNT}/${BIBER_EMAIL}"
echo "=== [3/4] Migrate: run the import script against the deployed stack ==="
run_migration "${MAILDIR_MOUNT}"
echo "=== [4/4] Continuity in Stalwart, then live flow (C: biber->admin, D: admin's reply) ==="
wait_in_imap "${ADMIN_EMAIL}" "${ADMIN_IMAP_PASSWORD}" "${SUBJ_A}"
wait_in_imap "${BIBER_EMAIL}" "${BIBER_IMAP_PASSWORD}" "${SUBJ_B}"
compose_mail "${BIBER_EMAIL}" "${ADMIN_EMAIL}" "${SUBJ_C}"
send_smtp "127.0.0.1" "${BIBER_EMAIL}" "${ADMIN_EMAIL}" "${SUBJ_C}"
compose_mail "${ADMIN_EMAIL}" "${BIBER_EMAIL}" "${SUBJ_D}" "${SUBJ_C}"
send_smtp "127.0.0.1" "${ADMIN_EMAIL}" "${BIBER_EMAIL}" "${SUBJ_D}"
wait_in_imap "${ADMIN_EMAIL}" "${ADMIN_IMAP_PASSWORD}" "${SUBJ_C}"
wait_in_imap "${BIBER_EMAIL}" "${BIBER_IMAP_PASSWORD}" "${SUBJ_D}"
echo "MIGRATION STATE MACHINE COMPLETE"