Provision a Stalwart group (team@example.org) with carol as a member before her first login, and assert the composer's From selector offers the group address. This confirms the group-membership scenario of #569 already works out of the box: Stalwart returns the group's send-as identity on the member's own account, so the app's normal single-account identity load surfaces it (identities.length > 1 -> the From <select> renders with team@). - stalwart: create the `team` Group in plan-accounts and add carol via User.memberGroupIds in the entrypoint (id resolved after apply, like DOMAIN_ID). carol, not alice/bob, so the sync specs stay unshared. - helpers: GROUP config, openComposer/composerFromOptions, and JmapClient accounts + sharedAccountNames (Identity/get needs the submission capability). - composer: add data-testid="composer-from" to the From <select> and its single-identity <span> fallback. Ref: https://github.com/bulwarkmail/webmail/issues/569
155 lines
5.9 KiB
Bash
Executable File
155 lines
5.9 KiB
Bash
Executable File
#!/bin/sh
|
|
# Declarative bootstrap for the integration-test Stalwart Mail Server.
|
|
#
|
|
# Phase 1 (first start only, marker absent):
|
|
# - Stalwart starts in bootstrap mode (no config.json -> HTTP on :8080).
|
|
# - plan-bootstrap.ndjson is applied via stalwart-cli. This writes
|
|
# config.json, initialises RocksDB and creates the default domain +
|
|
# admin account.
|
|
# - Stalwart is restarted in normal mode (config.json now exists).
|
|
# - plan-accounts.ndjson.tpl is materialised with the resolved DOMAIN_ID
|
|
# and the shared TEST_ACCOUNT_PASSWORD, then applied (test accounts +
|
|
# submission/IMAP listeners + cleartext auth for the dev lanes).
|
|
# - Stalwart is stopped and the marker is written.
|
|
#
|
|
# Phase 2 (regular start, marker present):
|
|
# - exec stalwart as PID 1.
|
|
#
|
|
# Adapted from examples/docker/stalwart for webmail<->Stalwart integration
|
|
# testing: no ticket/service accounts, no Sieve, a single shared password for
|
|
# the alice/bob/carol test mailboxes.
|
|
|
|
set -eu
|
|
|
|
# stalwart-cli caches its schema under $HOME/.cache/stalwart-cli. The stalwart
|
|
# user has no home, so redirect to /tmp.
|
|
export HOME=/tmp
|
|
|
|
DATA_DIR=/var/lib/stalwart
|
|
MARKER="${DATA_DIR}/.bootstrap-applied"
|
|
PLAN_DIR=/etc/stalwart-bootstrap
|
|
STALWART_BIN=/usr/local/bin/stalwart
|
|
STALWART_CLI=/usr/local/bin/stalwart-cli
|
|
STALWART_CFG=/etc/stalwart/config.json
|
|
LOCAL_URL=http://127.0.0.1:8080
|
|
|
|
log() { printf '[stalwart-bootstrap] %s\n' "$*" >&2; }
|
|
|
|
wait_for_http() {
|
|
for _ in $(seq 1 60); do
|
|
if curl -fsS -u "admin:${ADMIN_PASS}" "${LOCAL_URL}/jmap/session" >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
sleep 1
|
|
done
|
|
log "Stalwart HTTP on :8080 did not come up in time"
|
|
return 1
|
|
}
|
|
|
|
run_stalwart_bg() {
|
|
"${STALWART_BIN}" --config "${STALWART_CFG}" &
|
|
STALWART_PID=$!
|
|
}
|
|
|
|
stop_stalwart_bg() {
|
|
if [ -n "${STALWART_PID:-}" ]; then
|
|
kill -TERM "${STALWART_PID}" 2>/dev/null || true
|
|
wait "${STALWART_PID}" 2>/dev/null || true
|
|
STALWART_PID=
|
|
fi
|
|
}
|
|
|
|
if [ ! -f "${MARKER}" ]; then
|
|
: "${STALWART_RECOVERY_ADMIN:?must be set for first-run bootstrap}"
|
|
: "${TEST_ACCOUNT_PASSWORD:?must be set for first-run bootstrap}"
|
|
|
|
ADMIN_PASS=${STALWART_RECOVERY_ADMIN#*:}
|
|
|
|
log "Phase 1: starting Stalwart in bootstrap mode"
|
|
run_stalwart_bg
|
|
wait_for_http
|
|
|
|
log "Applying plan-bootstrap.ndjson"
|
|
STALWART_URL=${LOCAL_URL} \
|
|
STALWART_USER=admin \
|
|
STALWART_PASSWORD=${ADMIN_PASS} \
|
|
"${STALWART_CLI}" apply --file "${PLAN_DIR}/plan-bootstrap.ndjson" --quiet
|
|
|
|
log "Restarting Stalwart to leave bootstrap mode"
|
|
stop_stalwart_bg
|
|
run_stalwart_bg
|
|
wait_for_http
|
|
|
|
log "Resolving DOMAIN_ID for example.org"
|
|
DOMAIN_ID=$(STALWART_URL=${LOCAL_URL} STALWART_USER=admin STALWART_PASSWORD=${ADMIN_PASS} \
|
|
"${STALWART_CLI}" query Domain --json 2>/dev/null \
|
|
| head -1 \
|
|
| sed -E 's/.*"id":"([^"]+)".*/\1/')
|
|
if [ -z "${DOMAIN_ID}" ]; then
|
|
log "Could not resolve DOMAIN_ID after bootstrap"
|
|
stop_stalwart_bg
|
|
exit 1
|
|
fi
|
|
log "DOMAIN_ID=${DOMAIN_ID}"
|
|
|
|
# Materialise the account plan. gettext/envsubst is not in the base image,
|
|
# so substitute the two placeholders with sed. Passwords are escaped for the
|
|
# sed replacement (& and / are the only metacharacters that matter here).
|
|
PLAN_ACCOUNTS=/tmp/plan-accounts.ndjson
|
|
esc_pw=$(printf '%s' "${TEST_ACCOUNT_PASSWORD}" | sed -e 's/[&/\\]/\\&/g')
|
|
sed -e "s/\${DOMAIN_ID}/${DOMAIN_ID}/g" \
|
|
-e "s/\${TEST_ACCOUNT_PASSWORD}/${esc_pw}/g" \
|
|
"${PLAN_DIR}/plan-accounts.ndjson.tpl" > "${PLAN_ACCOUNTS}"
|
|
|
|
log "Applying plan-accounts.ndjson"
|
|
STALWART_URL=${LOCAL_URL} \
|
|
STALWART_USER=admin \
|
|
STALWART_PASSWORD=${ADMIN_PASS} \
|
|
"${STALWART_CLI}" apply --file "${PLAN_ACCOUNTS}" --quiet
|
|
rm -f "${PLAN_ACCOUNTS}"
|
|
|
|
# Make 'carol' a member of the 'team' group *before her first login*, so she
|
|
# already has the shared group mailbox (its folders show under "Shared") and
|
|
# the team@ send-as identity. This provisions the issue #569 scenario: the
|
|
# composer's From dropdown should then offer the group address. Membership is
|
|
# a set keyed by the group's server-assigned account id (see the User schema's
|
|
# memberGroupIds), so the ids are resolved here, mirroring DOMAIN_ID above.
|
|
# carol (not alice/bob) is used so the single-/multi-account sync specs, which
|
|
# drive alice and bob, keep a clean unshared environment.
|
|
q_account_id() {
|
|
STALWART_URL=${LOCAL_URL} STALWART_USER=admin STALWART_PASSWORD=${ADMIN_PASS} \
|
|
"${STALWART_CLI}" query Account --json 2>/dev/null \
|
|
| grep "\"emailAddress\":\"$1\"" \
|
|
| sed -E 's/.*"id":"([^"]+)".*/\1/'
|
|
}
|
|
CAROL_ID=$(q_account_id "carol@example.org")
|
|
TEAM_ID=$(q_account_id "team@example.org")
|
|
if [ -n "${CAROL_ID}" ] && [ -n "${TEAM_ID}" ]; then
|
|
log "Adding carol (${CAROL_ID}) to the team group (${TEAM_ID}) [issue #569]"
|
|
STALWART_URL=${LOCAL_URL} STALWART_USER=admin STALWART_PASSWORD=${ADMIN_PASS} \
|
|
"${STALWART_CLI}" update Account "${CAROL_ID}" \
|
|
--field "memberGroupIds={\"${TEAM_ID}\":true}" >/dev/null
|
|
else
|
|
log "WARNING: could not resolve account ids (carol='${CAROL_ID}' team='${TEAM_ID}'); skipping group membership"
|
|
fi
|
|
|
|
# Default inbound throttles (sender->recipient + sender-IP) otherwise trip
|
|
# 452 4.4.5 when a test blasts many messages. Stalwart re-seeds the defaults
|
|
# on every start when absent, so deleting is useless; disable them instead,
|
|
# which survives restarts.
|
|
for tid in $(STALWART_URL=${LOCAL_URL} STALWART_USER=admin STALWART_PASSWORD=${ADMIN_PASS} \
|
|
"${STALWART_CLI}" query MtaInboundThrottle --json 2>/dev/null \
|
|
| sed -E 's/.*"id":"([^"]+)".*/\1/'); do
|
|
log "Disabling MtaInboundThrottle ${tid}"
|
|
STALWART_URL=${LOCAL_URL} STALWART_USER=admin STALWART_PASSWORD=${ADMIN_PASS} \
|
|
"${STALWART_CLI}" update MtaInboundThrottle "${tid}" --field enable=false >/dev/null
|
|
done
|
|
|
|
log "Stopping bootstrap instance, marking complete"
|
|
stop_stalwart_bg
|
|
touch "${MARKER}"
|
|
fi
|
|
|
|
log "Starting Stalwart (final, foreground)"
|
|
exec "${STALWART_BIN}" --config "${STALWART_CFG}"
|