Compare commits
2 Commits
b8e8c90498
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 3e5bf97f4c | |||
| cc3fffbda1 |
205
.github/workflows/test.yml
vendored
205
.github/workflows/test.yml
vendored
@@ -93,6 +93,7 @@ jobs:
|
|||||||
sudo apt-get install -y jq curl
|
sudo apt-get install -y jq curl
|
||||||
|
|
||||||
- name: Install with full configuration
|
- name: Install with full configuration
|
||||||
|
id: install
|
||||||
uses: ./
|
uses: ./
|
||||||
with:
|
with:
|
||||||
domains: |
|
domains: |
|
||||||
@@ -162,6 +163,154 @@ jobs:
|
|||||||
echo "::error::API not accessible after 60 seconds"
|
echo "::error::API not accessible after 60 seconds"
|
||||||
exit 1
|
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 ""
|
||||||
|
echo "=== Verifying Domains ==="
|
||||||
|
DOMAINS_RESPONSE=$(curl -s -u "admin:$ADMIN_PASSWORD" \
|
||||||
|
"http://localhost:8080/api/principal?types=domain&limit=100")
|
||||||
|
|
||||||
|
DOMAIN_COUNT=$(echo "$DOMAINS_RESPONSE" | jq '.data.total // 0')
|
||||||
|
echo "Total domains found: $DOMAIN_COUNT"
|
||||||
|
|
||||||
|
# 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 "::error::Domain test1.local not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if echo "$DOMAINS_RESPONSE" | jq -e '.data.items[] | select(.name == "test2.local")' >/dev/null; then
|
||||||
|
echo "✓ Domain test2.local exists"
|
||||||
|
else
|
||||||
|
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
|
- name: Show logs on failure
|
||||||
if: failure()
|
if: failure()
|
||||||
run: |
|
run: |
|
||||||
@@ -226,64 +375,11 @@ jobs:
|
|||||||
# Remove installation
|
# Remove installation
|
||||||
sudo rm -rf /opt/stalwart || true
|
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: ./
|
|
||||||
|
|
||||||
- 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
|
# Summary job
|
||||||
test-summary:
|
test-summary:
|
||||||
name: Test Summary
|
name: Test Summary
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [test-basic-install, test-full-config, test-ubuntu-versions]
|
needs: [test-basic-install, test-full-config]
|
||||||
if: always()
|
if: always()
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -293,7 +389,6 @@ jobs:
|
|||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "- Basic Install: ${{ needs.test-basic-install.result }}" >> $GITHUB_STEP_SUMMARY
|
echo "- Basic Install: ${{ needs.test-basic-install.result }}" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "- Full Config: ${{ needs.test-full-config.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
|
# Fail if any required test failed
|
||||||
if [ "${{ needs.test-basic-install.result }}" != "success" ] || \
|
if [ "${{ needs.test-basic-install.result }}" != "success" ] || \
|
||||||
|
|||||||
@@ -80,9 +80,4 @@ runs:
|
|||||||
run: |
|
run: |
|
||||||
HOSTNAME=$(hostname -f 2>/dev/null || echo "localhost")
|
HOSTNAME=$(hostname -f 2>/dev/null || echo "localhost")
|
||||||
echo "::notice::🎉 Stalwart Mail Server installation complete!"
|
echo "::notice::🎉 Stalwart Mail Server installation complete!"
|
||||||
echo "::notice::Web admin: http://$HOSTNAME:8080/login"
|
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
|
|
||||||
@@ -56,6 +56,10 @@ main() {
|
|||||||
log_info "📝 Generated admin password: ${current_password}"
|
log_info "📝 Generated admin password: ${current_password}"
|
||||||
log_warning "⚠️ Save this password securely - it won't be shown again!"
|
log_warning "⚠️ Save this password securely - it won't be shown again!"
|
||||||
|
|
||||||
|
# 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
|
# Create domains if provided
|
||||||
if [ -n "$DOMAINS_JSON" ]; then
|
if [ -n "$DOMAINS_JSON" ]; then
|
||||||
log_info "Creating domains..."
|
log_info "Creating domains..."
|
||||||
@@ -174,11 +178,30 @@ create_domains() {
|
|||||||
continue
|
continue
|
||||||
fi
|
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
|
# Create domain via API
|
||||||
if curl -sf -X POST "${API_URL}/domain" \
|
if curl -sf -X POST "${API_URL}/principal" \
|
||||||
-u "admin:${password}" \
|
-u "admin:${password}" \
|
||||||
-H "Content-Type: application/json" \
|
-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"
|
log_success "✓ Created domain: $domain_name"
|
||||||
created=$((created + 1))
|
created=$((created + 1))
|
||||||
else
|
else
|
||||||
@@ -234,17 +257,61 @@ create_users() {
|
|||||||
continue
|
continue
|
||||||
fi
|
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
|
local payload
|
||||||
payload=$(echo "$user" | jq '{
|
if [ -n "$hashed_password" ]; then
|
||||||
name: .email,
|
payload=$(echo "$user" | jq --arg email "$email" --arg pwd "$hashed_password" '{
|
||||||
password: .password,
|
type: "individual",
|
||||||
description: (.name // .email),
|
quota: (.quota // 0),
|
||||||
quota: (.quota // 1073741824)
|
name: $email,
|
||||||
}' 2>/dev/null)
|
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
|
# Create user via API
|
||||||
if curl -sf -X POST "${API_URL}/account" \
|
if curl -sf -X POST "${API_URL}/principal" \
|
||||||
-u "admin:${password}" \
|
-u "admin:${password}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "$payload" >/dev/null 2>&1; then
|
-d "$payload" >/dev/null 2>&1; then
|
||||||
|
|||||||
Reference in New Issue
Block a user