@@ -1773,6 +1773,7 @@ run_functional_tests() {
1773
1773
check_minio_storage
1774
1774
check_api_endpoints
1775
1775
check_websocket_endpoint
1776
+ check_websocket_connection_simulation
1776
1777
check_admin_frontend
1777
1778
check_collaboration_data
1778
1779
check_ai_service
@@ -1977,9 +1978,9 @@ check_container_errors() {
1977
1978
1978
1979
# Try to parse JSON logs for better formatting
1979
1980
if echo " $line " | grep -q ' "timestamp".*"level".*"message"' ; then
1980
- local timestamp=$( echo " $line " | grep -oP ' "timestamp":"[^"]*" ' | cut -d ' " ' -f4 | cut -c1-19)
1981
- local level=$( echo " $line " | grep -oP ' "level":"[^"]*" ' | cut -d ' " ' -f4 | tr ' [:lower:]' ' [:upper:]' )
1982
- local message=$( echo " $line " | grep -oP ' "message":"[^"]*" ' | cut -d ' " ' -f4 )
1981
+ local timestamp=$( echo " $line " | sed -n ' s/.* "timestamp":"\( [^"]*\)".*/\1/p ' | cut -c1-19)
1982
+ local level=$( echo " $line " | sed -n ' s/.* "level":"\( [^"]*\)".*/\1/p ' | tr ' [:lower:]' ' [:upper:]' )
1983
+ local message=$( echo " $line " | sed -n ' s/.* "message":"\( [^"]*\)".*/\1/p ' )
1983
1984
1984
1985
if [[ -n " $timestamp " && -n " $level " && -n " $message " ]]; then
1985
1986
print_error " [$timestamp ] $level : $message "
@@ -2369,6 +2370,340 @@ generate_recommendations() {
2369
2370
fi
2370
2371
}
2371
2372
2373
+ # ==================== ADDITIONAL DIAGNOSTICS ====================
2374
+
2375
+ check_plan_limits () {
2376
+ print_verbose " Checking self-hosted plan limits..."
2377
+
2378
+ local compose_cmd=$( get_compose_command)
2379
+
2380
+ # Get appflowy_cloud container ID
2381
+ local container_id=$( $compose_cmd ps -q appflowy_cloud 2> /dev/null)
2382
+ if [[ -z " $container_id " ]]; then
2383
+ print_verbose " AppFlowy Cloud container not found"
2384
+ return 0
2385
+ fi
2386
+
2387
+ # Parse logs for "Free plan limits" message
2388
+ local logs=$( docker logs --tail 200 " $container_id " 2>&1 )
2389
+ local plan_limits=$( echo " $logs " | grep -i " Free plan limits" | tail -1)
2390
+
2391
+ if [[ -n " $plan_limits " ]]; then
2392
+ # Extract max_users and max_guests from log line (BSD grep compatible)
2393
+ local max_users=$( echo " $plan_limits " | sed -n ' s/.*max_users:[[:space:]]*\([0-9][0-9]*\).*/\1/p' )
2394
+ local max_guests=$( echo " $plan_limits " | sed -n ' s/.*max_guests:[[:space:]]*\([0-9][0-9]*\).*/\1/p' )
2395
+
2396
+ # Fallback to "unknown" if extraction failed
2397
+ [[ -z " $max_users " ]] && max_users=" unknown"
2398
+ [[ -z " $max_guests " ]] && max_guests=" unknown"
2399
+
2400
+ if [[ " $max_users " != " unknown" && " $max_guests " != " unknown" ]]; then
2401
+ print_warning " Self-Hosted Plan Limits Detected:"
2402
+ print_warning " Max Users: $max_users "
2403
+ print_warning " Max Guests: $max_guests "
2404
+ echo " "
2405
+
2406
+ if [[ " $max_users " == " 1" ]]; then
2407
+ print_error " IMPORTANT: Self-hosted version has a 1-user limit"
2408
+ print_error " - Only ONE user account can log in (not including admin)"
2409
+ print_error " - Admin and user can use different emails"
2410
+ print_error " - Additional users will fail to authenticate"
2411
+ print_error " - This is a limitation of the self-hosted free plan"
2412
+ echo " "
2413
+ fi
2414
+
2415
+ # Try to count existing users (if database accessible)
2416
+ if $compose_cmd ps postgres & > /dev/null; then
2417
+ local user_count=$( $compose_cmd exec -T postgres psql -U postgres -d postgres -tAc " SELECT COUNT(*) FROM af_user WHERE deleted_at IS NULL;" 2> /dev/null || echo " unknown" )
2418
+
2419
+ if [[ " $user_count " != " unknown" && " $user_count " =~ ^[0-9]+$ ]]; then
2420
+ print_info " Current user count: $user_count "
2421
+
2422
+ if [[ " $max_users " != " unknown" && " $max_users " =~ ^[0-9]+$ ]]; then
2423
+ if [[ $user_count -ge $max_users ]]; then
2424
+ print_error " ⚠️ User limit reached! ($user_count /$max_users users)"
2425
+ print_error " - New users will not be able to log in"
2426
+ print_error " - Consider removing unused accounts"
2427
+ else
2428
+ print_success " Users: $user_count /$max_users (within limit)"
2429
+ fi
2430
+ fi
2431
+ fi
2432
+ fi
2433
+ else
2434
+ print_verbose " Could not parse plan limits from logs"
2435
+ fi
2436
+ else
2437
+ print_verbose " No plan limits found in logs (container may be using custom configuration)"
2438
+ fi
2439
+
2440
+ return 0
2441
+ }
2442
+
2443
+ check_admin_frontend_errors () {
2444
+ print_verbose " Checking Admin Frontend specific errors..."
2445
+
2446
+ local compose_cmd=$( get_compose_command)
2447
+
2448
+ # Get admin_frontend container ID
2449
+ local container_id=$( $compose_cmd ps -q admin_frontend 2> /dev/null)
2450
+ if [[ -z " $container_id " ]]; then
2451
+ print_verbose " Admin Frontend container not found"
2452
+ return 0
2453
+ fi
2454
+
2455
+ # Check container status
2456
+ local container_status=$( docker inspect --format=' {{.State.Status}}' " $container_id " 2> /dev/null)
2457
+ local restart_count=$( docker inspect --format=' {{.RestartCount}}' " $container_id " 2> /dev/null)
2458
+
2459
+ # Get logs
2460
+ local logs=$( docker logs --tail 300 " $container_id " 2>&1 )
2461
+
2462
+ # Check for specific error patterns
2463
+ local has_errors=false
2464
+
2465
+ # Pattern 1: EISDIR error (node_modules issue)
2466
+ if echo " $logs " | grep -q " EISDIR.*node_modules" ; then
2467
+ has_errors=true
2468
+ print_error " Admin Frontend: EISDIR error detected (node_modules issue)"
2469
+ local error_line=$( echo " $logs " | grep " EISDIR.*node_modules" | tail -1 | cut -c1-200)
2470
+ print_error " Error: $error_line "
2471
+ print_error " Cause: Likely a Docker volume or build cache issue"
2472
+ print_error " Fix: Try rebuilding the admin_frontend image:"
2473
+ print_error " docker compose build --no-cache admin_frontend"
2474
+ print_error " docker compose up -d admin_frontend"
2475
+ echo " "
2476
+ fi
2477
+
2478
+ # Pattern 2: Module resolution errors
2479
+ if echo " $logs " | grep -qiE " Cannot find module|Module not found|Error: Cannot resolve" ; then
2480
+ has_errors=true
2481
+ print_error " Admin Frontend: Module resolution error detected"
2482
+ local error_line=$( echo " $logs " | grep -iE " Cannot find module|Module not found|Error: Cannot resolve" | tail -1 | cut -c1-200)
2483
+ print_error " Error: $error_line "
2484
+ print_error " Fix: Rebuild with fresh dependencies:"
2485
+ print_error " docker compose build --no-cache admin_frontend"
2486
+ echo " "
2487
+ fi
2488
+
2489
+ # Pattern 3: Configuration injection failures
2490
+ if echo " $logs " | grep -qiE " Failed to inject configuration|Configuration error|APPFLOWY_BASE_URL.*undefined" ; then
2491
+ has_errors=true
2492
+ print_error " Admin Frontend: Configuration injection failed"
2493
+ local error_line=$( echo " $logs " | grep -iE " Failed to inject configuration|Configuration error" | tail -1 | cut -c1-200)
2494
+ print_error " Error: $error_line "
2495
+ print_error " Fix: Verify .env file has correct APPFLOWY_BASE_URL"
2496
+ print_error " Check: APPFLOWY_BASE_URL is set and matches your deployment URL"
2497
+ echo " "
2498
+ fi
2499
+
2500
+ # Pattern 4: Port binding errors
2501
+ if echo " $logs " | grep -qiE " EADDRINUSE.*3000|port.*already in use" ; then
2502
+ has_errors=true
2503
+ print_error " Admin Frontend: Port conflict detected"
2504
+ print_error " Error: Port 3000 already in use"
2505
+ print_error " Fix: Another service is using the port"
2506
+ print_error " Check: docker ps | grep 3000"
2507
+ echo " "
2508
+ fi
2509
+
2510
+ # Pattern 5: Bun/Node runtime errors
2511
+ if echo " $logs " | grep -qiE " bun.*error|node.*fatal|v8::" ; then
2512
+ has_errors=true
2513
+ local error_line=$( echo " $logs " | grep -iE " bun.*error|node.*fatal|v8::" | tail -1 | cut -c1-200)
2514
+ print_error " Admin Frontend: Runtime error"
2515
+ print_error " Error: $error_line "
2516
+ echo " "
2517
+ fi
2518
+
2519
+ # Check if container is constantly restarting
2520
+ if [[ " $restart_count " -gt 3 ]]; then
2521
+ print_error " Admin Frontend: Container restarting frequently (count: $restart_count )"
2522
+ print_error " Status: $container_status "
2523
+
2524
+ # Get the last startup attempt
2525
+ local startup_logs=$( echo " $logs " | tail -50)
2526
+ local last_error=$( echo " $startup_logs " | grep -iE " error|fatal|failed" | tail -3)
2527
+
2528
+ if [[ -n " $last_error " ]]; then
2529
+ print_error " Recent errors:"
2530
+ while IFS= read -r line; do
2531
+ local clean_line=$( echo " $line " | cut -c1-200)
2532
+ print_error " $clean_line "
2533
+ done <<< " $last_error"
2534
+ fi
2535
+ echo " "
2536
+ has_errors=true
2537
+ fi
2538
+
2539
+ if [[ " $has_errors " == " false" && " $container_status " == " running" ]]; then
2540
+ print_success " Admin Frontend: No specific startup errors detected"
2541
+ fi
2542
+
2543
+ return 0
2544
+ }
2545
+
2546
+ check_websocket_connection_simulation () {
2547
+ print_verbose " Simulating WebSocket connection for login hang detection..."
2548
+
2549
+ if ! load_env_vars; then
2550
+ print_warning " WebSocket Simulation: Cannot load .env configuration"
2551
+ return 0
2552
+ fi
2553
+
2554
+ local base_url=" ${APPFLOWY_BASE_URL} "
2555
+ local ws_url=" ${APPFLOWY_WS_BASE_URL:- ${APPFLOWY_WEBSOCKET_BASE_URL} } "
2556
+
2557
+ if [[ -z " $ws_url " ]]; then
2558
+ print_warning " WebSocket Simulation: WS URL not configured, skipping connection test"
2559
+ return 0
2560
+ fi
2561
+
2562
+ # Extract scheme and check for common misconfigurations
2563
+ local ws_scheme=$( extract_url_scheme " $ws_url " )
2564
+ local base_scheme=$( extract_url_scheme " $base_url " )
2565
+
2566
+ # Critical: Check for HTTPS + WS (not WSS) mismatch
2567
+ if [[ " $base_scheme " == " https" && " $ws_scheme " == " ws" ]]; then
2568
+ print_error " WebSocket Connection: CRITICAL MISCONFIGURATION DETECTED"
2569
+ print_error " Base URL: $base_url (HTTPS)"
2570
+ print_error " WebSocket URL: $ws_url (WS)"
2571
+ print_error " "
2572
+ print_error " ⚠️ This WILL cause login to hang at 100%!"
2573
+ print_error " Browsers block insecure WebSocket (ws://) from HTTPS pages"
2574
+ print_error " "
2575
+ print_error " Fix: Change WS_SCHEME=wss in .env file"
2576
+ print_error " Then: docker compose up -d"
2577
+ echo " "
2578
+ return 1
2579
+ fi
2580
+
2581
+ # Test WebSocket endpoint availability (without auth)
2582
+ local ws_test_url
2583
+ if [[ " $ws_url " == * " /ws/v2" ]]; then
2584
+ # For v2, test with a dummy workspace ID
2585
+ ws_test_url=" http://localhost/ws/v2/00000000-0000-0000-0000-000000000000"
2586
+ else
2587
+ # For v1 or other versions
2588
+ ws_test_url=" http://localhost${ws_url##* ${base_url} } "
2589
+ fi
2590
+
2591
+ # Try WebSocket upgrade request
2592
+ local response=$( curl -s -o /dev/null -w " %{http_code}" \
2593
+ -X GET " $ws_test_url " \
2594
+ -H " Upgrade: websocket" \
2595
+ -H " Connection: Upgrade" \
2596
+ -H " Sec-WebSocket-Version: 13" \
2597
+ -H " Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
2598
+ --max-time 5 2> /dev/null)
2599
+
2600
+ if [[ " $response " == " 401" || " $response " == " 403" ]]; then
2601
+ print_success " WebSocket Connection: Endpoint reachable (auth required - expected)"
2602
+ print_verbose " WebSocket upgrade request returned: $response "
2603
+ elif [[ " $response " == " 404" ]]; then
2604
+ print_error " WebSocket Connection: Endpoint not found (404)"
2605
+ print_error " This will cause login to hang after authentication"
2606
+ print_error " Fix: Verify nginx WebSocket routing configuration"
2607
+ echo " "
2608
+ return 1
2609
+ elif [[ " $response " == " 502" || " $response " == " 503" ]]; then
2610
+ print_error " WebSocket Connection: Backend unavailable ($response )"
2611
+ print_error " AppFlowy Cloud service may not be running properly"
2612
+ print_error " Check: docker compose ps appflowy_cloud"
2613
+ echo " "
2614
+ return 1
2615
+ elif [[ " $response " == " 000" ]]; then
2616
+ print_warning " WebSocket Connection: Connection timeout or refused"
2617
+ print_warning " This may indicate nginx or network issues"
2618
+ else
2619
+ print_verbose " WebSocket Connection: Received HTTP $response "
2620
+ fi
2621
+
2622
+ # Additional check: verify nginx WebSocket proxy configuration exists
2623
+ local compose_cmd=$( get_compose_command)
2624
+ local nginx_container=$( $compose_cmd ps -q nginx 2> /dev/null)
2625
+
2626
+ if [[ -n " $nginx_container " ]]; then
2627
+ local nginx_config=$( docker exec " $nginx_container " cat /etc/nginx/nginx.conf 2> /dev/null || echo " " )
2628
+
2629
+ if [[ -n " $nginx_config " ]]; then
2630
+ # Check for WebSocket upgrade headers
2631
+ if ! echo " $nginx_config " | grep -q " Upgrade.*\$ http_upgrade" ; then
2632
+ print_warning " WebSocket Connection: Nginx may be missing WebSocket upgrade headers"
2633
+ print_warning " This can cause connection failures after login"
2634
+ fi
2635
+ fi
2636
+ fi
2637
+
2638
+ return 0
2639
+ }
2640
+
2641
+ check_user_auth_flow () {
2642
+ print_verbose " Validating user authentication flow..."
2643
+
2644
+ if ! load_env_vars; then
2645
+ return 0
2646
+ fi
2647
+
2648
+ local compose_cmd=$( get_compose_command)
2649
+
2650
+ # Check if GoTrue is configured for auto-confirm (important for testing)
2651
+ local mailer_autoconfirm=" ${GOTRUE_MAILER_AUTOCONFIRM:- false} "
2652
+
2653
+ if [[ " $mailer_autoconfirm " == " false" ]]; then
2654
+ print_warning " User Auth: Email confirmation required (GOTRUE_MAILER_AUTOCONFIRM=false)"
2655
+ print_warning " - New users must confirm email before login"
2656
+ print_warning " - Check SMTP settings if emails not arriving"
2657
+ print_warning " - For testing, set GOTRUE_MAILER_AUTOCONFIRM=true"
2658
+ echo " "
2659
+ else
2660
+ print_success " User Auth: Auto-confirm enabled (good for testing)"
2661
+ fi
2662
+
2663
+ # Check for OTP configuration
2664
+ local enable_otp=" ${GOTRUE_EXTERNAL_PHONE_ENABLED:- false} "
2665
+ if [[ " $enable_otp " == " true" ]]; then
2666
+ print_info " User Auth: OTP/Phone authentication enabled"
2667
+ fi
2668
+
2669
+ # Verify GoTrue is accessible
2670
+ local gotrue_health=$( curl -s -o /dev/null -w " %{http_code}" " http://localhost/gotrue/health" --max-time 3 2> /dev/null)
2671
+
2672
+ if [[ " $gotrue_health " == " 200" ]]; then
2673
+ print_success " User Auth: GoTrue service healthy"
2674
+ elif [[ " $gotrue_health " == " 404" ]]; then
2675
+ print_error " User Auth: GoTrue health endpoint not found"
2676
+ print_error " This suggests nginx routing issues"
2677
+ print_error " Fix: Verify nginx.conf has /gotrue/* proxy configuration"
2678
+ echo " "
2679
+ else
2680
+ print_warning " User Auth: GoTrue health check returned: $gotrue_health "
2681
+ fi
2682
+
2683
+ # Check for common auth flow issues in logs
2684
+ local gotrue_container=$( $compose_cmd ps -q gotrue 2> /dev/null)
2685
+ if [[ -n " $gotrue_container " ]]; then
2686
+ local recent_logs=$( docker logs --tail 100 " $gotrue_container " 2>&1 )
2687
+
2688
+ # Look for email delivery issues
2689
+ if echo " $recent_logs " | grep -qiE " failed to send.*email|smtp.*error|mailer.*failed" ; then
2690
+ print_error " User Auth: Email delivery issues detected in GoTrue logs"
2691
+ print_error " Users may not receive confirmation emails"
2692
+ print_error " Check SMTP configuration in .env"
2693
+ echo " "
2694
+ fi
2695
+
2696
+ # Look for token/JWT issues
2697
+ if echo " $recent_logs " | grep -qiE " invalid.*token|jwt.*error|signature.*invalid" ; then
2698
+ print_error " User Auth: JWT/Token validation issues detected"
2699
+ print_error " Verify GOTRUE_JWT_SECRET matches across services"
2700
+ echo " "
2701
+ fi
2702
+ fi
2703
+
2704
+ return 0
2705
+ }
2706
+
2372
2707
# ==================== MAIN EXECUTION ====================
2373
2708
2374
2709
main () {
@@ -2414,12 +2749,14 @@ main() {
2414
2749
check_base_urls
2415
2750
check_scheme_consistency
2416
2751
check_gotrue_configuration
2752
+ check_user_auth_flow
2417
2753
check_admin_credentials
2418
2754
check_smtp_configuration
2419
2755
check_nginx_websocket_config
2420
2756
check_production_https_websocket
2421
2757
check_ssl_certificate
2422
2758
check_websocket_cors_headers
2759
+ check_plan_limits
2423
2760
2424
2761
echo " "
2425
2762
@@ -2452,6 +2789,7 @@ main() {
2452
2789
fi
2453
2790
2454
2791
check_admin_frontend_connectivity
2792
+ check_admin_frontend_errors
2455
2793
check_gotrue_auth_errors
2456
2794
check_container_errors
2457
2795
extract_container_crash_summary
0 commit comments