2026-04-16 07:40:34 +02:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
# Starts a Stalwart instance for local development and integration tests.
|
|
|
|
|
#
|
|
|
|
|
# By default it uses STALWART_PORT from the environment. When STALWART_PORT=0
|
|
|
|
|
# or STALWART_RANDOM_PORTS=1, three free random ports are chosen and written to
|
|
|
|
|
# STALWART_TMPDIR/ports.env for other scripts to source.
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
if [ "${STALWART_RANDOM_PORTS:-0}" = "1" ] || [ "${STALWART_PORT:-0}" = "0" ]; then
|
|
|
|
|
command -v python3 >/dev/null || {
|
|
|
|
|
echo "python3 not in PATH — cannot choose random Stalwart ports"
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
2026-05-07 07:35:27 +02:00
|
|
|
read -r STALWART_PORT STALWART_IMAP_PORT STALWART_SMTP_PORT STALWART_SIEVE_PORT < <(
|
2026-04-16 07:40:34 +02:00
|
|
|
python3 - <<'PY'
|
|
|
|
|
import socket
|
|
|
|
|
ports = []
|
2026-05-07 07:35:27 +02:00
|
|
|
for _ in range(4):
|
2026-04-16 07:40:34 +02:00
|
|
|
sock = socket.socket()
|
|
|
|
|
sock.bind(("127.0.0.1", 0))
|
|
|
|
|
ports.append(str(sock.getsockname()[1]))
|
|
|
|
|
sock.close()
|
|
|
|
|
print(" ".join(ports))
|
|
|
|
|
PY
|
|
|
|
|
)
|
|
|
|
|
else
|
|
|
|
|
: "${STALWART_PORT:?STALWART_PORT is not set — run this inside nix develop}"
|
|
|
|
|
STALWART_IMAP_PORT="${STALWART_IMAP_PORT:-$((STALWART_PORT + 1))}"
|
|
|
|
|
STALWART_SMTP_PORT="${STALWART_SMTP_PORT:-$((STALWART_PORT + 2))}"
|
2026-05-07 07:35:27 +02:00
|
|
|
STALWART_SIEVE_PORT="${STALWART_SIEVE_PORT:-$((STALWART_PORT + 3))}"
|
2026-04-16 07:40:34 +02:00
|
|
|
fi
|
|
|
|
|
|
2026-05-07 07:35:27 +02:00
|
|
|
export STALWART_PORT STALWART_IMAP_PORT STALWART_SMTP_PORT STALWART_SIEVE_PORT
|
2026-04-16 07:40:34 +02:00
|
|
|
export STALWART_URL="http://127.0.0.1:${STALWART_PORT}"
|
|
|
|
|
|
|
|
|
|
TMPDIR="${STALWART_TMPDIR:-/tmp/stalwart-dev-${STALWART_PORT}}"
|
|
|
|
|
mkdir -p "$TMPDIR"
|
|
|
|
|
|
2026-05-07 07:35:27 +02:00
|
|
|
for port in "$STALWART_PORT" "$STALWART_IMAP_PORT" "$STALWART_SMTP_PORT" "$STALWART_SIEVE_PORT"; do
|
2026-04-16 07:40:34 +02:00
|
|
|
ss -ltnH "sport = :$port" | grep -q . && {
|
|
|
|
|
echo "Stalwart port $port is already in use"
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
cat >"${TMPDIR}/ports.env" <<EOF
|
|
|
|
|
export STALWART_PORT=${STALWART_PORT}
|
|
|
|
|
export STALWART_IMAP_PORT=${STALWART_IMAP_PORT}
|
|
|
|
|
export STALWART_SMTP_PORT=${STALWART_SMTP_PORT}
|
2026-05-07 07:35:27 +02:00
|
|
|
export STALWART_SIEVE_PORT=${STALWART_SIEVE_PORT}
|
2026-04-16 07:40:34 +02:00
|
|
|
export STALWART_URL=${STALWART_URL}
|
|
|
|
|
EOF
|
|
|
|
|
|
2026-05-17 16:01:42 +02:00
|
|
|
# Find a container runtime
|
|
|
|
|
if command -v podman >/dev/null 2>&1; then
|
|
|
|
|
RUNTIME="podman"
|
|
|
|
|
elif command -v docker >/dev/null 2>&1; then
|
|
|
|
|
RUNTIME="docker"
|
|
|
|
|
else
|
|
|
|
|
echo "No container runtime (podman or docker) found" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
2026-05-07 07:35:27 +02:00
|
|
|
echo "Stalwart ports: JMAP=${STALWART_PORT} IMAP=${STALWART_IMAP_PORT} SMTP=${STALWART_SMTP_PORT} SIEVE=${STALWART_SIEVE_PORT}" >&2
|
2026-05-17 16:01:42 +02:00
|
|
|
echo "Stalwart is running in a container (${RUNTIME}). Press Ctrl+C to stop." >&2
|
2026-04-16 07:40:34 +02:00
|
|
|
echo "Connection info written to ${TMPDIR}/ports.env" >&2
|
|
|
|
|
|
|
|
|
|
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
|
|
|
|
2026-05-17 16:01:42 +02:00
|
|
|
# Run Stalwart in container, mapping the random host ports to the fixed container ports.
|
|
|
|
|
# We mount the config.toml and use /tmp/stalwart for data (mapped to our local TMPDIR).
|
|
|
|
|
exec "${RUNTIME}" run --rm -i \
|
|
|
|
|
-p "${STALWART_PORT}:8080" \
|
|
|
|
|
-p "${STALWART_IMAP_PORT}:1430" \
|
|
|
|
|
-p "${STALWART_SMTP_PORT}:1025" \
|
|
|
|
|
-p "${STALWART_SIEVE_PORT}:4190" \
|
|
|
|
|
-v "${REPO_ROOT}/stalwart-dev/config.toml:/etc/stalwart/config.toml:ro" \
|
|
|
|
|
-v "${TMPDIR}:/tmp/stalwart:rw" \
|
|
|
|
|
docker.io/stalwartlabs/stalwart:v0.14.1 \
|
|
|
|
|
stalwart --config /etc/stalwart/config.toml
|