#!/usr/bin/env bash # # Stalwart Post-Installation Configuration Script # Configures admin password, domains, and users via REST API # set -euo pipefail # Source utility functions SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${SCRIPT_DIR}/utils.sh" # Configuration readonly STALWART_PATH="${STALWART_INSTALL_PATH:-/opt/stalwart}" readonly API_URL="http://localhost:8080/api" readonly MAX_RETRIES=60 readonly RETRY_DELAY=2 # Read the generated password from installation DEFAULT_ADMIN_PASSWORD="changeme" if [ -f "${STALWART_PATH}/.init_password" ]; then DEFAULT_ADMIN_PASSWORD=$(cat "${STALWART_PATH}/.init_password" | tr -d '\n\r') log_info "Using generated password from installation" # Clean up the password file for security rm -f "${STALWART_PATH}/.init_password" else log_warning "Password file not found, using default password" fi readonly DEFAULT_ADMIN_PASSWORD # Environment variables (passed from action.yml) ADMIN_PASSWORD="${STALWART_ADMIN_PASSWORD:-}" DOMAINS_JSON="${STALWART_DOMAINS:-}" USERS_JSON="${STALWART_USERS:-}" # Main configuration function main() { log_info "Starting Stalwart post-installation configuration" # Wait for Stalwart API to be ready if ! wait_for_stalwart_api; then log_error "Stalwart API failed to become ready within timeout" return 1 fi # Authenticate with default password first log_info "Authenticating with Stalwart API..." local auth_token if ! auth_token=$(authenticate "$DEFAULT_ADMIN_PASSWORD"); then log_error "Failed to authenticate with default admin password" log_info "This might mean Stalwart has already been configured" # Try with provided password if different if [ -n "$ADMIN_PASSWORD" ] && [ "$ADMIN_PASSWORD" != "$DEFAULT_ADMIN_PASSWORD" ]; then log_info "Trying with provided admin password..." if ! auth_token=$(authenticate "$ADMIN_PASSWORD"); then log_error "Failed to authenticate with provided password" return 1 fi else return 1 fi fi log_success "Authentication successful" # Update admin password if provided and different from default if [ -n "$ADMIN_PASSWORD" ] && [ "$ADMIN_PASSWORD" != "$DEFAULT_ADMIN_PASSWORD" ]; then log_info "Updating admin password..." if update_admin_password "$auth_token" "$ADMIN_PASSWORD"; then log_success "Admin password updated successfully" # Re-authenticate with new password if ! auth_token=$(authenticate "$ADMIN_PASSWORD"); then log_error "Failed to re-authenticate with new password" return 1 fi else log_warning "Failed to update admin password, continuing with default" fi else log_info "No admin password provided, keeping default (changeme)" log_warning "⚠️ Remember to change the default password!" fi # Create domains if provided if [ -n "$DOMAINS_JSON" ]; then log_info "Creating domains..." if validate_json "$DOMAINS_JSON"; then create_domains "$auth_token" "$DOMAINS_JSON" else log_error "Invalid domains JSON format" return 1 fi else log_info "No domains specified, skipping domain creation" fi # Create users if provided if [ -n "$USERS_JSON" ]; then log_info "Creating users..." if validate_json "$USERS_JSON"; then create_users "$auth_token" "$USERS_JSON" else log_error "Invalid users JSON format" return 1 fi else log_info "No users specified, skipping user creation" fi log_success "Stalwart configuration completed successfully!" return 0 } # Wait for Stalwart API to be ready wait_for_stalwart_api() { log_info "Waiting for Stalwart API to be ready (timeout: ${MAX_RETRIES}s)..." local attempt=0 while [ $attempt -lt $MAX_RETRIES ]; do # Try to access the API login endpoint if curl -sf -m 5 "${API_URL%/api}/login" >/dev/null 2>&1; then log_success "Stalwart API is ready" return 0 fi attempt=$((attempt + 1)) if [ $attempt -lt $MAX_RETRIES ]; then sleep $RETRY_DELAY fi done return 1 } # Authenticate with Stalwart API # Args: $1 = password # Returns: JWT token on stdout authenticate() { local password="$1" local response local token local http_code # Stalwart uses Basic Authentication response=$(curl -s -w "\n%{http_code}" -X GET "${API_URL}/authenticate" \ -u "admin:${password}" 2>&1) http_code=$(echo "$response" | tail -n 1) response=$(echo "$response" | sed '$d') if [ "$http_code" != "200" ]; then log_error "Authentication failed with HTTP $http_code" log_error "Response: $response" return 1 fi token=$(echo "$response" | jq -r '.data // empty' 2>/dev/null) if [ -z "$token" ]; then log_error "No token in response" log_error "Response: $response" return 1 fi echo "$token" return 0 } # Update admin password # Args: $1 = auth token, $2 = new password update_admin_password() { local token="$1" local new_password="$2" local response response=$(curl -sf -X PUT "${API_URL}/account/admin" \ -H "Authorization: Bearer ${token}" \ -H "Content-Type: application/json" \ -d "{\"password\":\"${new_password}\"}" 2>&1) || { return 1 } return 0 } # Create domains from JSON array # Args: $1 = auth token, $2 = domains JSON array create_domains() { local token="$1" local domains_json="$2" local domain_count domain_count=$(echo "$domains_json" | jq 'length' 2>/dev/null) if [ -z "$domain_count" ] || [ "$domain_count" = "0" ]; then log_warning "No domains found in JSON array" return 0 fi log_info "Creating $domain_count domain(s)..." local index=0 local created=0 local failed=0 while [ $index -lt "$domain_count" ]; do local domain domain=$(echo "$domains_json" | jq -c ".[$index]" 2>/dev/null) if [ -z "$domain" ] || [ "$domain" = "null" ]; then log_warning "Skipping invalid domain at index $index" failed=$((failed + 1)) index=$((index + 1)) continue fi local domain_name domain_name=$(echo "$domain" | jq -r '.name // empty' 2>/dev/null) if [ -z "$domain_name" ]; then log_warning "Skipping domain without 'name' field at index $index" failed=$((failed + 1)) index=$((index + 1)) continue fi # Create domain via API if curl -sf -X POST "${API_URL}/domain" \ -H "Authorization: Bearer ${token}" \ -H "Content-Type: application/json" \ -d "$domain" >/dev/null 2>&1; then log_success "✓ Created domain: $domain_name" created=$((created + 1)) else log_warning "✗ Failed to create domain: $domain_name" failed=$((failed + 1)) fi index=$((index + 1)) done log_info "Domain creation summary: $created created, $failed failed" return 0 } # Create users from JSON array # Args: $1 = auth token, $2 = users JSON array create_users() { local token="$1" local users_json="$2" local user_count user_count=$(echo "$users_json" | jq 'length' 2>/dev/null) if [ -z "$user_count" ] || [ "$user_count" = "0" ]; then log_warning "No users found in JSON array" return 0 fi log_info "Creating $user_count user(s)..." local index=0 local created=0 local failed=0 while [ $index -lt "$user_count" ]; do local user user=$(echo "$users_json" | jq -c ".[$index]" 2>/dev/null) if [ -z "$user" ] || [ "$user" = "null" ]; then log_warning "Skipping invalid user at index $index" failed=$((failed + 1)) index=$((index + 1)) continue fi local email email=$(echo "$user" | jq -r '.email // empty' 2>/dev/null) if [ -z "$email" ]; then log_warning "Skipping user without 'email' field at index $index" failed=$((failed + 1)) index=$((index + 1)) continue fi # Build API payload (name, password, description, quota) local payload payload=$(echo "$user" | jq '{ name: .email, password: .password, description: (.name // .email), quota: (.quota // 1073741824) }' 2>/dev/null) # Create user via API if curl -sf -X POST "${API_URL}/account" \ -H "Authorization: Bearer ${token}" \ -H "Content-Type: application/json" \ -d "$payload" >/dev/null 2>&1; then log_success "✓ Created user: $email" created=$((created + 1)) else log_warning "✗ Failed to create user: $email" failed=$((failed + 1)) fi index=$((index + 1)) done log_info "User creation summary: $created created, $failed failed" return 0 } # Execute main function main "$@"