Compare commits
2 Commits
0014789f68
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 3e5bf97f4c | |||
| cc3fffbda1 |
308
.github/workflows/test.yml
vendored
308
.github/workflows/test.yml
vendored
@@ -78,72 +78,6 @@ jobs:
|
||||
echo "::error::Web admin not accessible after 60 seconds"
|
||||
exit 1
|
||||
|
||||
# Test installation with admin password
|
||||
test-with-password:
|
||||
name: Installation with Admin Password
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y jq curl
|
||||
|
||||
- name: Install with admin password
|
||||
uses: ./
|
||||
with:
|
||||
admin_password: 'TestPassword123!@#'
|
||||
|
||||
- name: Wait for service to fully start
|
||||
run: |
|
||||
echo "Waiting for Stalwart to fully initialize..."
|
||||
sleep 15
|
||||
|
||||
- name: Verify service is running
|
||||
run: |
|
||||
# Check process
|
||||
if ! pgrep -x stalwart >/dev/null; then
|
||||
echo "::error::Stalwart process is not running"
|
||||
echo "Process list:"
|
||||
ps aux | grep stalwart || true
|
||||
echo "PID file:"
|
||||
cat /var/run/stalwart.pid 2>/dev/null || echo "No PID file"
|
||||
echo "Logs:"
|
||||
tail -50 /opt/stalwart/logs/*.log 2>/dev/null || echo "No logs"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Stalwart is running"
|
||||
|
||||
- name: Test authentication with new password
|
||||
run: |
|
||||
# Wait for API to be ready
|
||||
echo "Waiting for Stalwart API..."
|
||||
for i in {1..30}; do
|
||||
if curl -sf http://localhost:8080/login >/dev/null 2>&1; then
|
||||
echo "✓ API is ready"
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Try to authenticate with new password
|
||||
RESPONSE=$(curl -sf -X POST http://localhost:8080/api/authenticate \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"login":"admin","password":"TestPassword123!@#"}')
|
||||
|
||||
TOKEN=$(echo "$RESPONSE" | jq -r '.token // empty')
|
||||
|
||||
if [ -z "$TOKEN" ]; then
|
||||
echo "::error::Failed to authenticate with new password"
|
||||
echo "Response: $RESPONSE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Successfully authenticated with new password"
|
||||
|
||||
# Test full configuration with domains and users
|
||||
test-full-config:
|
||||
name: Full Configuration (Domains + Users)
|
||||
@@ -159,9 +93,9 @@ jobs:
|
||||
sudo apt-get install -y jq curl
|
||||
|
||||
- name: Install with full configuration
|
||||
id: install
|
||||
uses: ./
|
||||
with:
|
||||
admin_password: 'AdminPass123!@#'
|
||||
domains: |
|
||||
[
|
||||
{
|
||||
@@ -215,61 +149,166 @@ jobs:
|
||||
fi
|
||||
echo "✓ Stalwart is running"
|
||||
|
||||
- name: Verify admin password was changed
|
||||
- name: Verify service is accessible
|
||||
run: |
|
||||
# Wait for API to be ready
|
||||
echo "Waiting for Stalwart API..."
|
||||
for i in {1..30}; do
|
||||
if curl -sf http://localhost:8080/login >/dev/null 2>&1; then
|
||||
echo "✓ API is ready"
|
||||
break
|
||||
exit 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
RESPONSE=$(curl -sf -X POST http://localhost:8080/api/authenticate \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"login":"admin","password":"AdminPass123!@#"}')
|
||||
|
||||
TOKEN=$(echo "$RESPONSE" | jq -r '.token // empty')
|
||||
|
||||
if [ -z "$TOKEN" ]; then
|
||||
echo "::error::Failed to authenticate with admin password"
|
||||
echo "Response: $RESPONSE"
|
||||
echo "::error::API not accessible after 60 seconds"
|
||||
exit 1
|
||||
|
||||
- name: Verify domains and users were created
|
||||
run: |
|
||||
echo "=== Reading Admin Password ==="
|
||||
if [ -f /tmp/stalwart_admin_password ]; then
|
||||
ADMIN_PASSWORD=$(cat /tmp/stalwart_admin_password)
|
||||
echo "✓ Admin password retrieved"
|
||||
else
|
||||
echo "::error::Admin password file not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Admin authentication successful"
|
||||
echo "TOKEN=$TOKEN" >> $GITHUB_ENV
|
||||
|
||||
- name: Verify domains were created
|
||||
run: |
|
||||
# List domains via API
|
||||
DOMAINS=$(curl -sf -X GET http://localhost:8080/api/domain \
|
||||
-H "Authorization: Bearer $TOKEN")
|
||||
echo ""
|
||||
echo "=== Verifying Domains ==="
|
||||
DOMAINS_RESPONSE=$(curl -s -u "admin:$ADMIN_PASSWORD" \
|
||||
"http://localhost:8080/api/principal?types=domain&limit=100")
|
||||
|
||||
echo "Domains response: $DOMAINS"
|
||||
DOMAIN_COUNT=$(echo "$DOMAINS_RESPONSE" | jq '.data.total // 0')
|
||||
echo "Total domains found: $DOMAIN_COUNT"
|
||||
|
||||
# Check if our test domains exist
|
||||
if echo "$DOMAINS" | jq -e '.[] | select(.name == "test1.local")' >/dev/null; then
|
||||
echo "✓ Domain test1.local found"
|
||||
# List domains
|
||||
echo "$DOMAINS_RESPONSE" | jq -r '.data.items[] | " - \(.name): \(.description // "No description")"'
|
||||
|
||||
# Verify specific domains exist
|
||||
if echo "$DOMAINS_RESPONSE" | jq -e '.data.items[] | select(.name == "test1.local")' >/dev/null; then
|
||||
echo "✓ Domain test1.local exists"
|
||||
else
|
||||
echo "::warning::Domain test1.local not found (API might use different structure)"
|
||||
echo "::error::Domain test1.local not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Verify users were created
|
||||
run: |
|
||||
# List accounts via API
|
||||
ACCOUNTS=$(curl -sf -X GET http://localhost:8080/api/account \
|
||||
-H "Authorization: Bearer $TOKEN")
|
||||
|
||||
echo "Accounts response: $ACCOUNTS"
|
||||
|
||||
# Check if users exist
|
||||
if echo "$ACCOUNTS" | jq -e '.[] | select(.name == "user1@test1.local")' >/dev/null; then
|
||||
echo "✓ User user1@test1.local found"
|
||||
if echo "$DOMAINS_RESPONSE" | jq -e '.data.items[] | select(.name == "test2.local")' >/dev/null; then
|
||||
echo "✓ Domain test2.local exists"
|
||||
else
|
||||
echo "::warning::User user1@test1.local not found (API might use different structure)"
|
||||
echo "::error::Domain test2.local not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Verifying Users ==="
|
||||
USERS_RESPONSE=$(curl -s -u "admin:$ADMIN_PASSWORD" \
|
||||
"http://localhost:8080/api/principal?types=individual&limit=100")
|
||||
|
||||
USER_COUNT=$(echo "$USERS_RESPONSE" | jq '.data.total // 0')
|
||||
echo "Total users found: $USER_COUNT"
|
||||
|
||||
# List users
|
||||
echo "$USERS_RESPONSE" | jq -r '.data.items[] | " - \(.name) (\(.emails[0])): roles=\(.roles)"'
|
||||
|
||||
# Verify we have at least the 3 users we created
|
||||
if [ "$USER_COUNT" -lt 3 ]; then
|
||||
echo "::error::Expected at least 3 users, found $USER_COUNT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify specific users exist and have correct roles
|
||||
for user_email in "user1@test1.local" "user2@test1.local" "admin@test2.local"; do
|
||||
USER_DATA=$(echo "$USERS_RESPONSE" | jq --arg email "$user_email" '.data.items[] | select(.emails[] == $email)')
|
||||
|
||||
if [ -z "$USER_DATA" ]; then
|
||||
echo "::error::User $user_email not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if user has "user" role
|
||||
HAS_USER_ROLE=$(echo "$USER_DATA" | jq '.roles | contains(["user"])')
|
||||
if [ "$HAS_USER_ROLE" = "true" ]; then
|
||||
echo "✓ User $user_email exists with 'user' role"
|
||||
else
|
||||
echo "::error::User $user_email exists but missing 'user' role"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "✓ All domains and users verified successfully"
|
||||
|
||||
- name: Verify unauthenticated JMAP access
|
||||
run: |
|
||||
echo "Testing unauthenticated JMAP endpoint..."
|
||||
|
||||
# Call without authentication (follow redirects with -L)
|
||||
HTTP_CODE=$(curl -s -L \
|
||||
-o /tmp/jmap_response_no_auth.json \
|
||||
-w "%{http_code}" \
|
||||
"http://localhost:8080/.well-known/jmap")
|
||||
|
||||
echo "HTTP Status Code: $HTTP_CODE"
|
||||
echo "JMAP Response:"
|
||||
cat /tmp/jmap_response_no_auth.json
|
||||
echo ""
|
||||
|
||||
# Check if request succeeded
|
||||
if [ "$HTTP_CODE" != "200" ]; then
|
||||
echo "::error::JMAP endpoint returned HTTP $HTTP_CODE for unauthenticated request"
|
||||
cat /tmp/jmap_response_no_auth.json || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify username is empty (no authentication)
|
||||
USERNAME=$(cat /tmp/jmap_response_no_auth.json | jq -r '.username // empty')
|
||||
|
||||
if [ -z "$USERNAME" ]; then
|
||||
echo "✓ Unauthenticated access returns empty username"
|
||||
else
|
||||
echo "::warning::Expected empty username for unauthenticated request, got '$USERNAME'"
|
||||
fi
|
||||
|
||||
- name: Verify created user can authenticate
|
||||
run: |
|
||||
echo "Testing user authentication via JMAP endpoint..."
|
||||
|
||||
# Test user1@test1.local authentication (follow redirects with -L)
|
||||
HTTP_CODE=$(curl -s -L \
|
||||
-o /tmp/jmap_response_auth.json \
|
||||
-w "%{http_code}" \
|
||||
-u "user1@test1.local:UserPass123!" \
|
||||
"http://localhost:8080/.well-known/jmap")
|
||||
|
||||
echo "HTTP Status Code: $HTTP_CODE"
|
||||
echo "JMAP Response:"
|
||||
cat /tmp/jmap_response_auth.json
|
||||
echo ""
|
||||
|
||||
# Check if request succeeded
|
||||
if [ "$HTTP_CODE" != "200" ]; then
|
||||
echo "::error::JMAP endpoint returned HTTP $HTTP_CODE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify username field contains our test user
|
||||
USERNAME=$(cat /tmp/jmap_response_auth.json | jq -r '.username // empty')
|
||||
|
||||
if [ "$USERNAME" = "user1@test1.local" ]; then
|
||||
echo "✓ User authentication successful: $USERNAME"
|
||||
else
|
||||
echo "::error::Expected username 'user1@test1.local', got '$USERNAME'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify accounts object is not empty (means user is authenticated)
|
||||
ACCOUNTS=$(cat /tmp/jmap_response_auth.json | jq '.accounts // {}')
|
||||
if [ "$ACCOUNTS" != "{}" ]; then
|
||||
echo "✓ User has active accounts"
|
||||
else
|
||||
echo "::error::User accounts are empty (authentication may have failed)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Show logs on failure
|
||||
@@ -311,7 +350,6 @@ jobs:
|
||||
continue-on-error: true
|
||||
uses: ./
|
||||
with:
|
||||
admin_password: 'TestPass123'
|
||||
domains: 'invalid json string'
|
||||
|
||||
- name: Verify invalid JSON was caught
|
||||
@@ -337,66 +375,11 @@ jobs:
|
||||
# Remove installation
|
||||
sudo rm -rf /opt/stalwart || true
|
||||
|
||||
# Test on different Ubuntu versions
|
||||
test-ubuntu-versions:
|
||||
name: Test on Ubuntu ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-20.04, ubuntu-22.04, ubuntu-24.04]
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y jq curl
|
||||
|
||||
- name: Install Stalwart
|
||||
uses: ./
|
||||
with:
|
||||
admin_password: 'TestPassword123'
|
||||
|
||||
- name: Wait for service to fully start
|
||||
run: |
|
||||
echo "Waiting for Stalwart to fully initialize..."
|
||||
sleep 15
|
||||
|
||||
- name: Verify installation
|
||||
run: |
|
||||
# Check if process is running
|
||||
if pgrep -x stalwart >/dev/null; then
|
||||
echo "✓ Stalwart running on ${{ matrix.os }}"
|
||||
else
|
||||
echo "::error::Stalwart not running on ${{ matrix.os }}"
|
||||
ps aux | grep stalwart || true
|
||||
tail -50 /opt/stalwart/logs/*.log 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Test API accessibility
|
||||
run: |
|
||||
# Wait for API to be ready
|
||||
echo "Waiting for API on ${{ matrix.os }}..."
|
||||
for i in {1..30}; do
|
||||
if curl -sf http://localhost:8080/login >/dev/null 2>&1; then
|
||||
echo "✓ API accessible on ${{ matrix.os }}"
|
||||
exit 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
echo "::error::API not accessible on ${{ matrix.os }}"
|
||||
tail -50 /opt/stalwart/logs/*.log 2>/dev/null || true
|
||||
exit 1
|
||||
|
||||
# Summary job
|
||||
test-summary:
|
||||
name: Test Summary
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test-basic-install, test-with-password, test-full-config, test-ubuntu-versions]
|
||||
needs: [test-basic-install, test-full-config]
|
||||
if: always()
|
||||
|
||||
steps:
|
||||
@@ -405,13 +388,10 @@ jobs:
|
||||
echo "## Test Results Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Basic Install: ${{ needs.test-basic-install.result }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- With Password: ${{ needs.test-with-password.result }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Full Config: ${{ needs.test-full-config.result }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Ubuntu Versions: ${{ needs.test-ubuntu-versions.result }}" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Fail if any required test failed
|
||||
if [ "${{ needs.test-basic-install.result }}" != "success" ] || \
|
||||
[ "${{ needs.test-with-password.result }}" != "success" ] || \
|
||||
[ "${{ needs.test-full-config.result }}" != "success" ]; then
|
||||
echo "::error::One or more tests failed"
|
||||
exit 1
|
||||
|
||||
19
action.yml
19
action.yml
@@ -7,11 +7,6 @@ branding:
|
||||
color: 'blue'
|
||||
|
||||
inputs:
|
||||
admin_password:
|
||||
description: 'Admin password for Stalwart web interface (use GitHub Secrets). If not provided, defaults to "changeme"'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
domains:
|
||||
description: 'JSON array of domains to create. Example: [{"name":"example.com","description":"Primary domain"}]'
|
||||
required: false
|
||||
@@ -59,17 +54,12 @@ runs:
|
||||
|
||||
- name: Configure Stalwart
|
||||
shell: bash
|
||||
if: ${{ inputs.admin_password != '' || inputs.domains != '' || inputs.users != '' }}
|
||||
if: ${{ inputs.domains != '' || inputs.users != '' }}
|
||||
env:
|
||||
STALWART_ADMIN_PASSWORD: ${{ inputs.admin_password }}
|
||||
STALWART_DOMAINS: ${{ inputs.domains }}
|
||||
STALWART_USERS: ${{ inputs.users }}
|
||||
STALWART_INSTALL_PATH: '/opt/stalwart'
|
||||
run: |
|
||||
# Mask sensitive data
|
||||
if [ -n "$STALWART_ADMIN_PASSWORD" ]; then
|
||||
echo "::add-mask::$STALWART_ADMIN_PASSWORD"
|
||||
fi
|
||||
|
||||
# Mask user passwords from JSON
|
||||
if [ -n "$STALWART_USERS" ]; then
|
||||
@@ -90,9 +80,4 @@ runs:
|
||||
run: |
|
||||
HOSTNAME=$(hostname -f 2>/dev/null || echo "localhost")
|
||||
echo "::notice::🎉 Stalwart Mail Server installation complete!"
|
||||
echo "::notice::Web admin: http://$HOSTNAME:8080/login"
|
||||
if [ -n "${{ inputs.admin_password }}" ]; then
|
||||
echo "::notice::Admin credentials configured via inputs"
|
||||
else
|
||||
echo "::notice::Default admin password: changeme (change this immediately!)"
|
||||
fi
|
||||
echo "::notice::Web admin: http://$HOSTNAME:8080/login"
|
||||
@@ -29,7 +29,6 @@ fi
|
||||
readonly DEFAULT_ADMIN_PASSWORD
|
||||
|
||||
# Environment variables (passed from action.yml)
|
||||
ADMIN_PASSWORD="${STALWART_ADMIN_PASSWORD:-}"
|
||||
DOMAINS_JSON="${STALWART_DOMAINS:-}"
|
||||
USERS_JSON="${STALWART_USERS:-}"
|
||||
|
||||
@@ -43,50 +42,29 @@ main() {
|
||||
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
|
||||
# Set current password (start with generated one)
|
||||
local current_password="$DEFAULT_ADMIN_PASSWORD"
|
||||
|
||||
# Test authentication with generated password
|
||||
log_info "Verifying API access with generated password..."
|
||||
if ! test_auth "$current_password"; then
|
||||
log_error "Failed to authenticate with generated password"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_success "Authentication successful"
|
||||
log_success "API authentication verified"
|
||||
log_info "📝 Generated admin password: ${current_password}"
|
||||
log_warning "⚠️ Save this password securely - it won't be shown again!"
|
||||
|
||||
# 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
|
||||
# Save admin password to temp file for testing/debugging (remove in production)
|
||||
echo "$current_password" > /tmp/stalwart_admin_password
|
||||
chmod 600 /tmp/stalwart_admin_password
|
||||
|
||||
# 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"
|
||||
create_domains "$current_password" "$DOMAINS_JSON"
|
||||
else
|
||||
log_error "Invalid domains JSON format"
|
||||
return 1
|
||||
@@ -99,7 +77,7 @@ main() {
|
||||
if [ -n "$USERS_JSON" ]; then
|
||||
log_info "Creating users..."
|
||||
if validate_json "$USERS_JSON"; then
|
||||
create_users "$auth_token" "$USERS_JSON"
|
||||
create_users "$current_password" "$USERS_JSON"
|
||||
else
|
||||
log_error "Invalid users JSON format"
|
||||
return 1
|
||||
@@ -133,62 +111,36 @@ wait_for_stalwart_api() {
|
||||
return 1
|
||||
}
|
||||
|
||||
# Authenticate with Stalwart API
|
||||
# Test authentication with Stalwart API using Basic Auth
|
||||
# Args: $1 = password
|
||||
# Returns: JWT token on stdout
|
||||
authenticate() {
|
||||
# Returns: 0 if auth works, 1 otherwise
|
||||
test_auth() {
|
||||
local password="$1"
|
||||
|
||||
local response
|
||||
local token
|
||||
local http_code
|
||||
local response
|
||||
|
||||
# Stalwart uses Basic Authentication
|
||||
response=$(curl -s -w "\n%{http_code}" -X GET "${API_URL}/authenticate" \
|
||||
-u "admin:${password}" 2>&1)
|
||||
# Test by querying domains endpoint (works on fresh and configured systems)
|
||||
response=$(curl -s -w "\n%{http_code}" \
|
||||
-u "admin:${password}" \
|
||||
"${API_URL}/principal?types=domain&limit=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"
|
||||
if [ "$http_code" = "200" ]; then
|
||||
return 0
|
||||
else
|
||||
log_error "Authentication test failed with HTTP $http_code"
|
||||
response=$(echo "$response" | sed '$d')
|
||||
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
|
||||
# Args: $1 = password, $2 = domains JSON array
|
||||
create_domains() {
|
||||
local token="$1"
|
||||
local password="$1"
|
||||
local domains_json="$2"
|
||||
|
||||
local domain_count
|
||||
@@ -226,11 +178,30 @@ create_domains() {
|
||||
continue
|
||||
fi
|
||||
|
||||
# Build API payload with required structure
|
||||
local payload
|
||||
payload=$(echo "$domain" | jq '{
|
||||
type: "domain",
|
||||
quota: (.quota // 0),
|
||||
name: .name,
|
||||
description: (.description // ""),
|
||||
secrets: [],
|
||||
emails: [],
|
||||
urls: [],
|
||||
memberOf: [],
|
||||
roles: [],
|
||||
lists: [],
|
||||
members: [],
|
||||
enabledPermissions: [],
|
||||
disabledPermissions: [],
|
||||
externalMembers: []
|
||||
}' 2>/dev/null)
|
||||
|
||||
# Create domain via API
|
||||
if curl -sf -X POST "${API_URL}/domain" \
|
||||
-H "Authorization: Bearer ${token}" \
|
||||
if curl -sf -X POST "${API_URL}/principal" \
|
||||
-u "admin:${password}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$domain" >/dev/null 2>&1; then
|
||||
-d "$payload" >/dev/null 2>&1; then
|
||||
log_success "✓ Created domain: $domain_name"
|
||||
created=$((created + 1))
|
||||
else
|
||||
@@ -246,9 +217,9 @@ create_domains() {
|
||||
}
|
||||
|
||||
# Create users from JSON array
|
||||
# Args: $1 = auth token, $2 = users JSON array
|
||||
# Args: $1 = password, $2 = users JSON array
|
||||
create_users() {
|
||||
local token="$1"
|
||||
local password="$1"
|
||||
local users_json="$2"
|
||||
|
||||
local user_count
|
||||
@@ -286,18 +257,62 @@ create_users() {
|
||||
continue
|
||||
fi
|
||||
|
||||
# Build API payload (name, password, description, quota)
|
||||
# Hash the password with SHA-512 (API requires hashed passwords in secrets array)
|
||||
local hashed_password=""
|
||||
local raw_password
|
||||
raw_password=$(echo "$user" | jq -r '.password // empty' 2>/dev/null)
|
||||
|
||||
if [ -n "$raw_password" ]; then
|
||||
if command -v mkpasswd >/dev/null 2>&1; then
|
||||
hashed_password=$(mkpasswd -m sha-512 "$raw_password")
|
||||
elif command -v openssl >/dev/null 2>&1; then
|
||||
hashed_password=$(openssl passwd -6 "$raw_password")
|
||||
else
|
||||
log_warning "Cannot hash password for $email - no mkpasswd or openssl found"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build API payload with required structure
|
||||
local payload
|
||||
payload=$(echo "$user" | jq '{
|
||||
name: .email,
|
||||
password: .password,
|
||||
description: (.name // .email),
|
||||
quota: (.quota // 1073741824)
|
||||
}' 2>/dev/null)
|
||||
if [ -n "$hashed_password" ]; then
|
||||
payload=$(echo "$user" | jq --arg email "$email" --arg pwd "$hashed_password" '{
|
||||
type: "individual",
|
||||
quota: (.quota // 0),
|
||||
name: $email,
|
||||
description: (.name // ""),
|
||||
secrets: [$pwd],
|
||||
emails: [$email],
|
||||
urls: [],
|
||||
memberOf: [],
|
||||
roles: ["user"],
|
||||
lists: [],
|
||||
members: [],
|
||||
enabledPermissions: [],
|
||||
disabledPermissions: [],
|
||||
externalMembers: []
|
||||
}' 2>/dev/null)
|
||||
else
|
||||
payload=$(echo "$user" | jq --arg email "$email" '{
|
||||
type: "individual",
|
||||
quota: (.quota // 0),
|
||||
name: $email,
|
||||
description: (.name // ""),
|
||||
secrets: [],
|
||||
emails: [$email],
|
||||
urls: [],
|
||||
memberOf: [],
|
||||
roles: ["user"],
|
||||
lists: [],
|
||||
members: [],
|
||||
enabledPermissions: [],
|
||||
disabledPermissions: [],
|
||||
externalMembers: []
|
||||
}' 2>/dev/null)
|
||||
fi
|
||||
|
||||
# Create user via API
|
||||
if curl -sf -X POST "${API_URL}/account" \
|
||||
-H "Authorization: Bearer ${token}" \
|
||||
if curl -sf -X POST "${API_URL}/principal" \
|
||||
-u "admin:${password}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$payload" >/dev/null 2>&1; then
|
||||
log_success "✓ Created user: $email"
|
||||
|
||||
@@ -158,6 +158,19 @@ main() {
|
||||
create_service_macos "$_dir"
|
||||
fi
|
||||
|
||||
# Wait for service to be responsive
|
||||
say "⏳ Waiting for Stalwart to start..."
|
||||
local _wait_attempts=0
|
||||
local _max_wait=30
|
||||
while [ $_wait_attempts -lt $_max_wait ]; do
|
||||
if curl -sf -m 2 "http://localhost:8080/login" >/dev/null 2>&1; then
|
||||
say "✓ Stalwart service is responding"
|
||||
break
|
||||
fi
|
||||
_wait_attempts=$((_wait_attempts + 1))
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Installation complete
|
||||
local _host=$(hostname -f 2>/dev/null || echo "localhost")
|
||||
say "✅ Installation complete! Stalwart service is running."
|
||||
|
||||
Reference in New Issue
Block a user