#!/usr/bin/env bash # Local development only. One-time setup for the disposable Stalwart test # server from docker-compose.yml: completes the server's bootstrap wizard, # creates a real "devadmin" account (used by dev-token.sh/.ps1 — the # STALWART_RECOVERY_ADMIN account is break-glass only and always issues # fixed 1h OAuth tokens regardless of server config), and sets the default # OAuth access token lifetime to 3 hours. # # Idempotent: safe to re-run; does nothing if the server already left # bootstrap mode. Run this once after `npm run dev:server` on a fresh # volume (or after `docker compose down -v`). set -euo pipefail API_BASE_URL="${1:-http://localhost:8080}" RECOVERY_ACCOUNT="admin@example.org" RECOVERY_SECRET="c8321iEscHDy0GWV" DEV_DOMAIN="example.org" DEV_HOSTNAME="mail.example.org" DEVADMIN_NAME="devadmin" DEVADMIN_SECRET="DevAdminPass123!" DEFAULT_TOKEN_EXPIRY_MS=10800000 # 3 hours jmap() { curl -sf --compressed -u "$RECOVERY_ACCOUNT:$RECOVERY_SECRET" \ -X POST -H "Content-Type: application/json" -d "$1" "$API_BASE_URL/jmap/" } echo "Waiting for $API_BASE_URL to be reachable..." for _ in $(seq 1 30); do curl -sf -o /dev/null -u "$RECOVERY_ACCOUNT:$RECOVERY_SECRET" "$API_BASE_URL/jmap/session" && break sleep 1 done SESSION=$(curl -sf --compressed -u "$RECOVERY_ACCOUNT:$RECOVERY_SECRET" "$API_BASE_URL/jmap/session") ACCOUNT_ID=$(printf '%s' "$SESSION" | grep -o '"urn:stalwart:jmap":"[^"]*"' | cut -d'"' -f4) QUERY_RESULT=$(jmap "{\"using\":[\"urn:ietf:params:jmap:core\",\"urn:stalwart:jmap\"],\"methodCalls\":[[\"x:Domain/query\",{\"accountId\":\"$ACCOUNT_ID\"},\"0\"]]}") if ! printf '%s' "$QUERY_RESULT" | grep -q '"forbidden"'; then echo "Server already bootstrapped, skipping setup. (Use 'docker compose down -v && npm run dev:server' to start fresh.)" else echo "Completing server bootstrap (domain: $DEV_DOMAIN, no TLS certificate request)..." jmap "{\"using\":[\"urn:ietf:params:jmap:core\",\"urn:stalwart:jmap\"],\"methodCalls\":[[\"x:Bootstrap/set\",{\"accountId\":\"$ACCOUNT_ID\",\"update\":{\"singleton\":{\"defaultDomain\":\"$DEV_DOMAIN\",\"serverHostname\":\"$DEV_HOSTNAME\",\"requestTlsCertificate\":false}}},\"0\"]]}" > /dev/null echo "Restarting the container to apply bootstrap config (one-time only)..." docker compose restart stalwart > /dev/null for _ in $(seq 1 30); do curl -sf -o /dev/null -u "$RECOVERY_ACCOUNT:$RECOVERY_SECRET" "$API_BASE_URL/jmap/session" && break sleep 1 done DOMAIN_RESULT=$(jmap "{\"using\":[\"urn:ietf:params:jmap:core\",\"urn:stalwart:jmap\"],\"methodCalls\":[[\"x:Domain/query\",{\"accountId\":\"$ACCOUNT_ID\"},\"0\"]]}") DOMAIN_ID=$(printf '%s' "$DOMAIN_RESULT" | grep -o '"ids":\["[^"]*"' | cut -d'"' -f4) echo "Creating devadmin account ($DEVADMIN_NAME@$DEV_DOMAIN)..." CREATE_RESULT=$(jmap "{\"using\":[\"urn:ietf:params:jmap:core\",\"urn:stalwart:jmap\"],\"methodCalls\":[[\"x:Account/set\",{\"accountId\":\"$ACCOUNT_ID\",\"create\":{\"u1\":{\"@type\":\"User\",\"name\":\"$DEVADMIN_NAME\",\"domainId\":\"$DOMAIN_ID\",\"roles\":{\"@type\":\"Admin\"}}}},\"0\"]]}") DEVADMIN_ID=$(printf '%s' "$CREATE_RESULT" | grep -o '"id":"[^"]*"' | cut -d'"' -f4) jmap "{\"using\":[\"urn:ietf:params:jmap:core\",\"urn:stalwart:jmap\"],\"methodCalls\":[[\"x:Account/set\",{\"accountId\":\"$ACCOUNT_ID\",\"update\":{\"$DEVADMIN_ID\":{\"credentials\":{\"0\":{\"@type\":\"Password\",\"secret\":\"$DEVADMIN_SECRET\"}}}}},\"0\"]]}" > /dev/null echo "Setting default OAuth access token lifetime to 3 hours..." jmap "{\"using\":[\"urn:ietf:params:jmap:core\",\"urn:stalwart:jmap\"],\"methodCalls\":[[\"x:OidcProvider/set\",{\"accountId\":\"$ACCOUNT_ID\",\"update\":{\"singleton\":{\"accessTokenExpiry\":$DEFAULT_TOKEN_EXPIRY_MS}}},\"0\"]]}" > /dev/null jmap "{\"using\":[\"urn:ietf:params:jmap:core\",\"urn:stalwart:jmap\"],\"methodCalls\":[[\"x:Action/set\",{\"accountId\":\"$ACCOUNT_ID\",\"create\":{\"a1\":{\"@type\":\"ReloadSettings\"}}},\"0\"]]}" > /dev/null 2>&1 || true # Cosmetic only: Stalwart seeds a default "Stalwart Web Interface" / # stalwartlabs/webui entry for the x:Application record serving /admin # and /account (see Settings > Web Applications' "Active WebUI" card). # Point it at this fork so that card isn't misleading in local dev too. echo "Pointing the active WebUI's description at this fork..." APP_RESULT=$(jmap "{\"using\":[\"urn:ietf:params:jmap:core\",\"urn:stalwart:jmap\"],\"methodCalls\":[[\"x:Application/query\",{\"accountId\":\"$ACCOUNT_ID\"},\"0\"]]}") || true APP_ID=$(printf '%s' "$APP_RESULT" | grep -o '"ids":\["[^"]*"' | cut -d'"' -f4) if [ -n "$APP_ID" ]; then jmap "{\"using\":[\"urn:ietf:params:jmap:core\",\"urn:stalwart:jmap\"],\"methodCalls\":[[\"x:Application/set\",{\"accountId\":\"$ACCOUNT_ID\",\"update\":{\"$APP_ID\":{\"description\":\"Stalwart WebUI Fork\",\"resourceUrl\":\"https://github.com/LinkPhoenix/stalwart-webui-fork/releases/latest/download/webui.zip\"}}},\"0\"]]}" > /dev/null 2>&1 || true fi echo "Done. devadmin@$DEV_DOMAIN / $DEVADMIN_SECRET is ready — run scripts/dev-token.sh to get a token." fi