mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 19:01:54 +00:00
145 lines
5.5 KiB
Bash
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"
|