From 2defdf2baf3ada99cc67e6272698e6f2221d7b90 Mon Sep 17 00:00:00 2001 From: Michael Schiemer Date: Sun, 2 Nov 2025 00:05:26 +0100 Subject: [PATCH] chore: update staging branch with current changes --- .gitea/workflows/build-image.yml | 1 - .../playbooks/check-docker-compose-logs.yml | 40 +++++ .../playbooks/check-php-files-and-workers.yml | 78 ++++++++ .../ansible/playbooks/check-phpfpm-config.yml | 80 +++++++++ .../playbooks/check-staging-php-logs.yml | 66 +++++++ .../playbooks/diagnose-staging-502.yml | 142 ++++++++------- .../playbooks/diagnose-staging-logs.yml | 125 +++++++++++++ .../ansible/playbooks/fix-nginx-upstream.yml | 79 ++++++++ .../ansible/playbooks/fix-phpfpm-config.yml | 36 ++++ .../playbooks/fix-sites-available-default.yml | 57 ++++++ .../playbooks/quick-staging-logs-check.yml | 28 +++ .../playbooks/test-fastcgi-directly.yml | 46 +++++ .../test-staging-container-access.yml | 79 ++++++++ .../playbooks/view-staging-logs-readable.yml | 44 +++++ deployment/docs/README.md | 7 +- .../staging-502-nginx-phpfpm.md | 169 ++++++++++++++++++ deployment/stacks/staging/docker-compose.yml | 20 ++- docker/entrypoint.sh | 6 + .../Http/Middlewares/RoutingMiddleware.php | 10 +- .../Categories/Analysis/RouteDebugger.php | 2 +- src/Framework/Router/RouterSetup.php | 11 +- src/Framework/Storage/ObjectStorage.php | 2 +- .../Storage/STREAMING_IMPLEMENTATION_PLAN.md | 155 ++++++++++++++++ .../CreateComponentStateHistoryTable.php | 5 +- .../Migrations/CreateComponentStateTable.php | 5 +- 25 files changed, 1205 insertions(+), 88 deletions(-) create mode 100644 deployment/ansible/playbooks/check-docker-compose-logs.yml create mode 100644 deployment/ansible/playbooks/check-php-files-and-workers.yml create mode 100644 deployment/ansible/playbooks/check-phpfpm-config.yml create mode 100644 deployment/ansible/playbooks/check-staging-php-logs.yml create mode 100644 deployment/ansible/playbooks/diagnose-staging-logs.yml create mode 100644 deployment/ansible/playbooks/fix-nginx-upstream.yml create mode 100644 deployment/ansible/playbooks/fix-phpfpm-config.yml create mode 100644 deployment/ansible/playbooks/fix-sites-available-default.yml create mode 100644 deployment/ansible/playbooks/quick-staging-logs-check.yml create mode 100644 deployment/ansible/playbooks/test-fastcgi-directly.yml create mode 100644 deployment/ansible/playbooks/test-staging-container-access.yml create mode 100644 deployment/ansible/playbooks/view-staging-logs-readable.yml create mode 100644 deployment/docs/troubleshooting/staging-502-nginx-phpfpm.md create mode 100644 src/Framework/Storage/STREAMING_IMPLEMENTATION_PLAN.md diff --git a/.gitea/workflows/build-image.yml b/.gitea/workflows/build-image.yml index c5aa6bc6..a974bb1f 100644 --- a/.gitea/workflows/build-image.yml +++ b/.gitea/workflows/build-image.yml @@ -866,7 +866,6 @@ jobs: SELECTED_IMAGE="$DEFAULT_IMAGE" fi - STACK_PATH_DISPLAY="~/deployment/stacks/staging" STACK_PATH_DISPLAY="~/deployment/stacks/staging" SELECTED_TAG="${SELECTED_IMAGE##*:}" diff --git a/deployment/ansible/playbooks/check-docker-compose-logs.yml b/deployment/ansible/playbooks/check-docker-compose-logs.yml new file mode 100644 index 00000000..ce7d7510 --- /dev/null +++ b/deployment/ansible/playbooks/check-docker-compose-logs.yml @@ -0,0 +1,40 @@ +--- +- name: Check Docker Compose Logs for JSON Output + hosts: production + gather_facts: yes + become: no + + tasks: + - name: Get recent docker compose logs for staging-app (JSON format check) + shell: | + cd ~/deployment/stacks/staging + echo "=== Last 100 lines of staging-app logs ===" + docker compose logs --tail=100 staging-app 2>&1 | tail -50 + echo "" + echo "=== Checking for JSON logs ===" + docker compose logs --tail=200 staging-app 2>&1 | grep -E '^{"|^\{' | head -5 || echo "No JSON logs found (or logs are in plain text)" + args: + executable: /bin/bash + register: compose_logs + ignore_errors: yes + failed_when: false + + - name: Display compose logs + debug: + msg: "{{ compose_logs.stdout_lines }}" + + - name: Get all recent logs from all staging services + shell: | + cd ~/deployment/stacks/staging + echo "=== All staging services logs (last 30 lines each) ===" + docker compose logs --tail=30 2>&1 + args: + executable: /bin/bash + register: all_logs + ignore_errors: yes + failed_when: false + + - name: Display all logs + debug: + msg: "{{ all_logs.stdout_lines }}" + when: all_logs.stdout_lines is defined diff --git a/deployment/ansible/playbooks/check-php-files-and-workers.yml b/deployment/ansible/playbooks/check-php-files-and-workers.yml new file mode 100644 index 00000000..aa3c2449 --- /dev/null +++ b/deployment/ansible/playbooks/check-php-files-and-workers.yml @@ -0,0 +1,78 @@ +--- +- name: Check PHP Files and PHP-FPM Workers + hosts: production + gather_facts: yes + become: no + + tasks: + - name: Check if public/index.php exists + shell: | + cd ~/deployment/stacks/staging + echo "=== Check public/index.php ===" + docker compose exec -T staging-app ls -la /var/www/html/public/index.php 2>&1 || echo "index.php not found" + echo "" + echo "=== Check public directory ===" + docker compose exec -T staging-app ls -la /var/www/html/public/ 2>&1 | head -20 || echo "public directory not found" + echo "" + echo "=== Check if code directory exists ===" + docker compose exec -T staging-app ls -la /var/www/html/ 2>&1 | head -20 || echo "Code directory not found" + args: + executable: /bin/bash + register: file_check + ignore_errors: yes + failed_when: false + + - name: Display file check results + debug: + msg: "{{ file_check.stdout_lines }}" + + - name: Check PHP-FPM worker processes in detail + shell: | + cd ~/deployment/stacks/staging + echo "=== All processes in staging-app ===" + docker compose exec -T staging-app ps aux 2>&1 || echo "Could not get processes" + echo "" + echo "=== Check PHP-FPM master and worker processes ===" + docker compose exec -T staging-app sh -c "ps aux | grep -E '[p]hp|[f]pm' || echo 'No PHP-FPM processes found'" || echo "Process check failed" + args: + executable: /bin/bash + register: process_check + ignore_errors: yes + failed_when: false + + - name: Display process check results + debug: + msg: "{{ process_check.stdout_lines }}" + + - name: Test PHP execution directly + shell: | + cd ~/deployment/stacks/staging + echo "=== Test PHP CLI ===" + docker compose exec -T staging-app php -v 2>&1 || echo "PHP CLI failed" + echo "" + echo "=== Test if we can include index.php ===" + docker compose exec -T staging-app php -r "if(file_exists('/var/www/html/public/index.php')) { echo 'index.php exists\n'; } else { echo 'index.php NOT FOUND\n'; }" 2>&1 || echo "PHP test failed" + args: + executable: /bin/bash + register: php_test + ignore_errors: yes + failed_when: false + + - name: Display PHP test results + debug: + msg: "{{ php_test.stdout_lines }}" + + - name: Check PHP-FPM pool status using status page + shell: | + cd ~/deployment/stacks/staging + echo "=== Try to get PHP-FPM status ===" + docker compose exec -T staging-app sh -c "SCRIPT_NAME=/status SCRIPT_FILENAME=/status REQUEST_METHOD=GET timeout 2 php -r \"\\\$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); if (socket_connect(\\\$socket, '127.0.0.1', 9000)) { socket_write(\\\$socket, 'GET /status HTTP/1.0\\r\\nHost: localhost\\r\\n\\r\\n'); \\\$response = socket_read(\\\$socket, 1024); echo \\\$response; socket_close(\\\$socket); } else { echo 'Could not connect to PHP-FPM'; }\" 2>&1" || echo "Status check failed" + args: + executable: /bin/bash + register: fpm_status + ignore_errors: yes + failed_when: false + + - name: Display PHP-FPM status + debug: + msg: "{{ fpm_status.stdout_lines }}" diff --git a/deployment/ansible/playbooks/check-phpfpm-config.yml b/deployment/ansible/playbooks/check-phpfpm-config.yml new file mode 100644 index 00000000..dbb1e758 --- /dev/null +++ b/deployment/ansible/playbooks/check-phpfpm-config.yml @@ -0,0 +1,80 @@ +--- +- name: Check PHP-FPM Configuration in Detail + hosts: production + gather_facts: yes + become: no + + tasks: + - name: Check PHP-FPM pool configuration + shell: | + cd ~/deployment/stacks/staging + echo "=== PHP-FPM www.conf listen configuration ===" + docker compose exec -T staging-app cat /usr/local/etc/php-fpm.d/www.conf 2>&1 | grep -E "(listen|listen.allowed_clients|listen.owner|listen.group|listen.mode|pm)" | head -20 + echo "" + echo "=== Check PHP-FPM processes ===" + docker compose exec -T staging-app ps aux | grep php-fpm || echo "No php-fpm processes found" + echo "" + echo "=== Check PHP-FPM status page ===" + docker compose exec -T staging-app sh -c "SCRIPT_NAME=/status SCRIPT_FILENAME=/status REQUEST_METHOD=GET cgi-fcgi -bind -connect 127.0.0.1:9000 2>&1 || echo 'Status check failed'" + args: + executable: /bin/bash + register: phpfpm_config + ignore_errors: yes + failed_when: false + + - name: Display PHP-FPM configuration + debug: + msg: "{{ phpfpm_config.stdout_lines }}" + + - name: Check what interface PHP-FPM is listening on + shell: | + cd ~/deployment/stacks/staging + echo "=== Check listening interface ===" + docker compose exec -T staging-app netstat -tlnp 2>/dev/null | grep 9000 || \ + docker compose exec -T staging-app ss -tlnp 2>/dev/null | grep 9000 || \ + echo "Could not check listening interface" + echo "" + echo "=== Try to connect from nginx using FastCGI protocol ===" + docker compose exec -T staging-nginx sh -c "echo -e 'REQUEST_METHOD=GET\nSCRIPT_FILENAME=/var/www/html/public/index.php\n' | cgi-fcgi -bind -connect staging-app:9000 2>&1 | head -20" || echo "FastCGI test failed" + args: + executable: /bin/bash + register: listen_check + ignore_errors: yes + failed_when: false + + - name: Display listening interface check + debug: + msg: "{{ listen_check.stdout_lines }}" + + - name: Check PHP-FPM error logs + shell: | + cd ~/deployment/stacks/staging + echo "=== PHP-FPM error log ===" + docker compose exec -T staging-app tail -50 /var/log/php-fpm.log 2>&1 || \ + docker compose exec -T staging-app tail -50 /usr/local/var/log/php-fpm.log 2>&1 || \ + docker compose logs --tail=100 staging-app 2>&1 | grep -iE "(fpm|error|warning)" | tail -20 || \ + echo "No PHP-FPM error logs found" + args: + executable: /bin/bash + register: phpfpm_errors + ignore_errors: yes + failed_when: false + + - name: Display PHP-FPM errors + debug: + msg: "{{ phpfpm_errors.stdout_lines }}" + + - name: Test actual request from outside + shell: | + cd ~/deployment/stacks/staging + echo "=== Test request from nginx to PHP-FPM ===" + docker compose exec -T staging-nginx sh -c "curl -v http://127.0.0.1/ 2>&1 | head -30" || echo "Request test failed" + args: + executable: /bin/bash + register: request_test + ignore_errors: yes + failed_when: false + + - name: Display request test + debug: + msg: "{{ request_test.stdout_lines }}" diff --git a/deployment/ansible/playbooks/check-staging-php-logs.yml b/deployment/ansible/playbooks/check-staging-php-logs.yml new file mode 100644 index 00000000..df759127 --- /dev/null +++ b/deployment/ansible/playbooks/check-staging-php-logs.yml @@ -0,0 +1,66 @@ +--- +- name: Check Staging PHP Logs in Volume + hosts: production + gather_facts: yes + become: no + + tasks: + - name: Check PHP log files in staging-app container + shell: | + echo "=== Checking log directory in staging-app container ===" + docker exec staging-app ls -lah /var/www/html/storage/logs/ 2>&1 || echo "Could not list logs directory" + echo "" + echo "=== Recent PHP error logs ===" + docker exec staging-app tail -50 /var/www/html/storage/logs/php-errors.log 2>&1 | tail -30 || echo "Could not read php-errors.log" + echo "" + echo "=== Recent application logs ===" + docker exec staging-app find /var/www/html/storage/logs -name "*.log" -type f -exec ls -lh {} \; 2>&1 | head -20 || echo "Could not find log files" + echo "" + echo "=== Staging log file (if exists) ===" + docker exec staging-app tail -50 /var/www/html/storage/logs/staging.log 2>&1 | tail -30 || echo "staging.log not found or empty" + echo "" + echo "=== Check log volume mount ===" + docker exec staging-app df -h /var/www/html/storage/logs 2>&1 || echo "Could not check volume" + args: + executable: /bin/bash + register: php_logs_check + ignore_errors: yes + failed_when: false + + - name: Display PHP logs check results + debug: + msg: "{{ php_logs_check.stdout_lines }}" + + - name: Check if we can access log volume directly + shell: | + echo "=== Docker volume inspect ===" + docker volume inspect staging-logs 2>&1 || echo "Volume not found" + echo "" + echo "=== Try to access volume through temporary container ===" + docker run --rm -v staging-logs:/logs alpine ls -lah /logs 2>&1 | head -30 || echo "Could not access volume" + args: + executable: /bin/bash + register: volume_check + ignore_errors: yes + failed_when: false + + - name: Display volume check results + debug: + msg: "{{ volume_check.stdout_lines }}" + + - name: Check PHP configuration for logging + shell: | + echo "=== PHP error_log setting ===" + docker exec staging-app php -i | grep -E "(error_log|log_errors)" || echo "Could not get PHP config" + echo "" + echo "=== PHP-FPM error log location ===" + docker exec staging-app grep -E "(error_log|catch_workers_output)" /usr/local/etc/php-fpm.d/www.conf | head -5 || echo "Could not read PHP-FPM config" + args: + executable: /bin/bash + register: php_config_check + ignore_errors: yes + failed_when: false + + - name: Display PHP configuration + debug: + msg: "{{ php_config_check.stdout_lines }}" diff --git a/deployment/ansible/playbooks/diagnose-staging-502.yml b/deployment/ansible/playbooks/diagnose-staging-502.yml index dafe1309..861224ff 100644 --- a/deployment/ansible/playbooks/diagnose-staging-502.yml +++ b/deployment/ansible/playbooks/diagnose-staging-502.yml @@ -1,96 +1,108 @@ --- -- name: Diagnose Staging 502 Error +- name: Diagnose Staging 502 Bad Gateway Error hosts: production gather_facts: yes become: no tasks: - - name: Check Nginx error logs + - name: Check nginx error logs for 502 errors shell: | - cd ~/deployment/stacks/staging && docker compose logs --tail=50 staging-nginx 2>&1 | grep -iE "(error|502|bad gateway|php|fpm)" || echo "No errors found in nginx logs" + cd ~/deployment/stacks/staging + echo "=== Nginx Error Logs (Last 50 lines) ===" + docker compose logs --tail=50 staging-nginx 2>&1 | grep -iE "(error|502|bad gateway|php|fpm|upstream)" || echo "No obvious errors in logs" args: executable: /bin/bash register: nginx_errors - changed_when: false + ignore_errors: yes + failed_when: false + + - name: Display nginx errors + debug: + msg: "{{ nginx_errors.stdout_lines }}" - name: Check PHP-FPM status in staging-app shell: | - cd ~/deployment/stacks/staging && docker compose exec -T staging-app php-fpm-healthcheck 2>&1 || echo "PHP-FPM healthcheck failed" + cd ~/deployment/stacks/staging + echo "=== PHP-FPM Status Check ===" + docker compose exec -T staging-app php-fpm-healthcheck 2>&1 || echo "PHP-FPM healthcheck failed" + echo "" + echo "=== Check if PHP-FPM is running ===" + docker compose exec -T staging-app ps aux | grep -E "php-fpm|php" | head -10 || echo "Could not check processes" args: executable: /bin/bash register: php_fpm_status - changed_when: false ignore_errors: yes - - - name: Test PHP-FPM connection from nginx container - shell: | - cd ~/deployment/stacks/staging && docker compose exec -T staging-nginx sh -c "nc -zv staging-app 9000 2>&1 || echo 'Connection test failed'" || echo "Connection test failed" - args: - executable: /bin/bash - register: connection_test - changed_when: false - ignore_errors: yes - - - name: Check if PHP-FPM is listening - shell: | - cd ~/deployment/stacks/staging && docker compose exec -T staging-app sh -c "netstat -tlnp 2>/dev/null | grep 9000 || ss -tlnp 2>/dev/null | grep 9000 || echo 'Port 9000 not found'" || echo "Could not check ports" - args: - executable: /bin/bash - register: php_fpm_port - changed_when: false - ignore_errors: yes - - - name: Check staging-app process list - shell: | - cd ~/deployment/stacks/staging && docker compose exec -T staging-app sh -c "ps aux | grep -E '(php-fpm|nginx)' | head -20" || echo "Could not get process list" - args: - executable: /bin/bash - register: processes - changed_when: false - ignore_errors: yes - - - name: Check network connectivity - shell: | - cd ~/deployment/stacks/staging && docker compose exec -T staging-nginx sh -c "ping -c 2 staging-app 2>&1 || echo 'Ping failed'" || echo "Network test failed" - args: - executable: /bin/bash - register: network_test - changed_when: false - ignore_errors: yes - - - name: Check if /var/www/html/public/index.php exists - shell: | - cd ~/deployment/stacks/staging && docker compose exec -T staging-nginx sh -c "test -f /var/www/html/public/index.php && echo 'index.php exists' || echo 'index.php NOT FOUND'" || echo "Could not check file" - args: - executable: /bin/bash - register: index_php_check - changed_when: false - ignore_errors: yes - - - name: Display Nginx error logs - debug: - msg: "{{ nginx_errors.stdout_lines }}" + failed_when: false - name: Display PHP-FPM status debug: msg: "{{ php_fpm_status.stdout_lines }}" - - name: Display connection test + - name: Test PHP-FPM connection from nginx container + shell: | + cd ~/deployment/stacks/staging + echo "=== Test connection from nginx to PHP-FPM ===" + docker compose exec -T staging-nginx sh -c "nc -zv staging-app 9000 2>&1 || echo 'Connection test failed'" || echo "Connection test failed" + echo "" + echo "=== Try curl from nginx to PHP-FPM ===" + docker compose exec -T staging-nginx sh -c "curl -v http://staging-app:9000 2>&1 | head -20" || echo "Curl test failed" + args: + executable: /bin/bash + register: connection_test + ignore_errors: yes + failed_when: false + + - name: Display connection test results debug: msg: "{{ connection_test.stdout_lines }}" + - name: Check nginx configuration for PHP-FPM upstream + shell: | + cd ~/deployment/stacks/staging + echo "=== Nginx Configuration ===" + docker compose exec -T staging-nginx cat /etc/nginx/conf.d/default.conf 2>&1 | grep -A 10 -B 5 "staging-app\|9000\|fastcgi_pass" || echo "Could not read nginx config" + args: + executable: /bin/bash + register: nginx_config + ignore_errors: yes + failed_when: false + + - name: Display nginx configuration + debug: + msg: "{{ nginx_config.stdout_lines }}" + + - name: Check if PHP-FPM is listening on port 9000 + shell: | + cd ~/deployment/stacks/staging + echo "=== Check PHP-FPM port 9000 ===" + docker compose exec -T staging-app sh -c "netstat -tlnp 2>/dev/null | grep 9000 || ss -tlnp 2>/dev/null | grep 9000 || echo 'Port 9000 not found'" || echo "Could not check ports" + echo "" + echo "=== PHP-FPM pool config ===" + docker compose exec -T staging-app cat /usr/local/etc/php-fpm.d/www.conf 2>&1 | grep -E "(listen|listen.allowed_clients)" | head -5 || echo "Could not read PHP-FPM config" + args: + executable: /bin/bash + register: php_fpm_port + ignore_errors: yes + failed_when: false + - name: Display PHP-FPM port check debug: msg: "{{ php_fpm_port.stdout_lines }}" - - name: Display processes - debug: - msg: "{{ processes.stdout_lines }}" + - name: Check network connectivity between containers + shell: | + cd ~/deployment/stacks/staging + echo "=== Network connectivity ===" + docker compose exec -T staging-nginx ping -c 2 staging-app 2>&1 || echo "Ping failed" + echo "" + echo "=== Check docker networks ===" + docker network inspect staging-internal 2>&1 | grep -A 5 "staging-app\|staging-nginx" || echo "Could not inspect network" + args: + executable: /bin/bash + register: network_check + ignore_errors: yes + failed_when: false - - name: Display network test + - name: Display network check debug: - msg: "{{ network_test.stdout_lines }}" - - - name: Display index.php check - debug: - msg: "{{ index_php_check.stdout_lines }}" + msg: "{{ network_check.stdout_lines }}" diff --git a/deployment/ansible/playbooks/diagnose-staging-logs.yml b/deployment/ansible/playbooks/diagnose-staging-logs.yml new file mode 100644 index 00000000..a3e5a723 --- /dev/null +++ b/deployment/ansible/playbooks/diagnose-staging-logs.yml @@ -0,0 +1,125 @@ +--- +- name: Diagnose Staging Logs Issue + hosts: production + gather_facts: yes + become: no + + tasks: + - name: Check if staging stack directory exists + shell: | + test -d ~/deployment/stacks/staging && echo "? Directory exists" || echo "? Directory missing" + args: + executable: /bin/bash + register: dir_check + + - name: Display directory check result + debug: + msg: "{{ dir_check.stdout }}" + + - name: Check if docker-compose.yml exists + shell: | + test -f ~/deployment/stacks/staging/docker-compose.yml && echo "? docker-compose.yml exists" || echo "? docker-compose.yml missing" + args: + executable: /bin/bash + register: compose_check + + - name: Display compose file check result + debug: + msg: "{{ compose_check.stdout }}" + + - name: List staging directory contents + shell: | + cd ~/deployment/stacks/staging && ls -la + args: + executable: /bin/bash + register: dir_contents + ignore_errors: yes + + - name: Display directory contents + debug: + msg: "{{ dir_contents.stdout_lines }}" + + - name: Check Docker Compose version + shell: | + cd ~/deployment/stacks/staging && docker compose version + args: + executable: /bin/bash + register: compose_version + ignore_errors: yes + + - name: Display Docker Compose version + debug: + msg: "{{ compose_version.stdout }}" + + - name: Check if containers exist + shell: | + docker ps -a | grep staging || echo "No staging containers found" + args: + executable: /bin/bash + register: container_list + ignore_errors: yes + + - name: Display container list + debug: + msg: "{{ container_list.stdout_lines }}" + + - name: Try docker compose ps (this is where it might fail) + shell: | + cd ~/deployment/stacks/staging && docker compose ps + args: + executable: /bin/bash + register: compose_ps + ignore_errors: yes + + - name: Display docker compose ps result + debug: + msg: "{{ compose_ps.stdout_lines }}" + msg_also: "{{ compose_ps.stderr_lines }}" + + - name: Try docker compose logs directly (without service name) + shell: | + cd ~/deployment/stacks/staging && docker compose logs --tail=50 2>&1 | head -100 + args: + executable: /bin/bash + register: compose_logs_generic + ignore_errors: yes + + - name: Display generic compose logs + debug: + msg: "{{ (compose_logs_generic.stdout_lines | default([])) + (compose_logs_generic.stderr_lines | default([])) }}" + + - name: Try individual container logs using docker logs + shell: | + docker logs staging-app --tail=50 2>&1 || echo "? Could not get staging-app logs" + args: + executable: /bin/bash + register: direct_app_logs + ignore_errors: yes + + - name: Display direct staging-app logs + debug: + msg: "{{ direct_app_logs.stdout_lines }}" + msg_also: "{{ direct_app_logs.stderr_lines }}" + + - name: Check current working directory permissions + shell: | + ls -ld ~/deployment/stacks/staging && pwd && whoami + args: + executable: /bin/bash + register: perm_check + + - name: Display permission check + debug: + msg: "{{ perm_check.stdout_lines }}" + + - name: Check if we can read docker-compose.yml + shell: | + cd ~/deployment/stacks/staging && head -20 docker-compose.yml + args: + executable: /bin/bash + register: compose_read + ignore_errors: yes + + - name: Display docker-compose.yml content (first 20 lines) + debug: + msg: "{{ (compose_read.stdout_lines | default([])) + (compose_read.stderr_lines | default([])) }}" diff --git a/deployment/ansible/playbooks/fix-nginx-upstream.yml b/deployment/ansible/playbooks/fix-nginx-upstream.yml new file mode 100644 index 00000000..9c591393 --- /dev/null +++ b/deployment/ansible/playbooks/fix-nginx-upstream.yml @@ -0,0 +1,79 @@ +--- +- name: Fix Nginx Upstream Configuration + hosts: production + gather_facts: yes + become: no + + tasks: + - name: Check which nginx config files exist + shell: | + cd ~/deployment/stacks/staging + echo "=== Check nginx config files ===" + docker compose exec -T staging-nginx find /etc/nginx -name "*.conf" -type f 2>&1 | head -20 + echo "" + echo "=== Check sites-enabled ===" + docker compose exec -T staging-nginx ls -la /etc/nginx/sites-enabled/ 2>&1 || echo "sites-enabled not found" + echo "" + echo "=== Check sites-available ===" + docker compose exec -T staging-nginx ls -la /etc/nginx/sites-available/ 2>&1 || echo "sites-available not found" + echo "" + echo "=== Check nginx.conf includes ===" + docker compose exec -T staging-nginx grep -E "include|conf.d|sites" /etc/nginx/nginx.conf 2>&1 | head -10 + args: + executable: /bin/bash + register: nginx_config_check + ignore_errors: yes + failed_when: false + + - name: Display nginx config check + debug: + msg: "{{ nginx_config_check.stdout_lines }}" + + - name: Find all fastcgi_pass directives + shell: | + cd ~/deployment/stacks/staging + echo "=== Search for fastcgi_pass in all config files ===" + docker compose exec -T staging-nginx grep -r "fastcgi_pass" /etc/nginx/ 2>&1 || echo "Could not search" + args: + executable: /bin/bash + register: fastcgi_pass_search + ignore_errors: yes + failed_when: false + + - name: Display fastcgi_pass search + debug: + msg: "{{ fastcgi_pass_search.stdout_lines }}" + + - name: Fix all fastcgi_pass to use staging-app:9000 + shell: | + cd ~/deployment/stacks/staging + echo "=== Fix fastcgi_pass in all config files ===" + docker compose exec -T staging-nginx sh -c "find /etc/nginx -name '*.conf' -type f -exec sed -i 's|fastcgi_pass 127.0.0.1:9000;|fastcgi_pass staging-app:9000;|g' {} \;" || echo "Fix failed" + docker compose exec -T staging-nginx sh -c "find /etc/nginx -name '*.conf' -type f -exec sed -i 's|fastcgi_pass localhost:9000;|fastcgi_pass staging-app:9000;|g' {} \;" || echo "Fix failed" + docker compose exec -T staging-nginx sh -c "find /etc/nginx -name '*.conf' -type f -exec sed -i 's|fastcgi_pass php-upstream;|fastcgi_pass staging-app:9000;|g' {} \;" || echo "Note: php-upstream should stay as is" + echo "=== Verify fix ===" + docker compose exec -T staging-nginx grep -r "fastcgi_pass" /etc/nginx/ 2>&1 | grep -v "staging-app" || echo "All fastcgi_pass now use staging-app" + args: + executable: /bin/bash + register: fix_result + ignore_errors: yes + failed_when: false + + - name: Display fix result + debug: + msg: "{{ fix_result.stdout_lines }}" + + - name: Reload nginx to apply changes + shell: | + cd ~/deployment/stacks/staging + docker compose exec -T staging-nginx nginx -t 2>&1 || echo "Config test failed" + docker compose restart staging-nginx || echo "Restart failed" + args: + executable: /bin/bash + register: nginx_reload + ignore_errors: yes + failed_when: false + + - name: Display nginx reload result + debug: + msg: "{{ nginx_reload.stdout_lines }}" diff --git a/deployment/ansible/playbooks/fix-phpfpm-config.yml b/deployment/ansible/playbooks/fix-phpfpm-config.yml new file mode 100644 index 00000000..a41c3cc4 --- /dev/null +++ b/deployment/ansible/playbooks/fix-phpfpm-config.yml @@ -0,0 +1,36 @@ +--- +- name: Check and Fix PHP-FPM Configuration + hosts: production + gather_facts: yes + become: no + + tasks: + - name: Check PHP-FPM www.conf configuration for allowed_clients + shell: | + cd ~/deployment/stacks/staging + echo "=== PHP-FPM www.conf listen.allowed_clients ===" + docker compose exec -T staging-app cat /usr/local/etc/php-fpm.d/www.conf 2>&1 | grep -E "(listen|allowed_clients|listen\.owner|listen\.group|listen\.mode)" | head -15 + args: + executable: /bin/bash + register: fpm_config + ignore_errors: yes + failed_when: false + + - name: Display PHP-FPM config + debug: + msg: "{{ fpm_config.stdout_lines }}" + + - name: Check nginx error log for specific PHP-FPM errors + shell: | + cd ~/deployment/stacks/staging + echo "=== Nginx Error Log (all lines) ===" + docker compose logs --tail=200 staging-nginx 2>&1 | grep -iE "(502|bad gateway|upstream|php|fpm|connection)" || echo "No specific errors found" + args: + executable: /bin/bash + register: nginx_error_log + ignore_errors: yes + failed_when: false + + - name: Display nginx error log + debug: + msg: "{{ nginx_error_log.stdout_lines }}" diff --git a/deployment/ansible/playbooks/fix-sites-available-default.yml b/deployment/ansible/playbooks/fix-sites-available-default.yml new file mode 100644 index 00000000..06dbf3bd --- /dev/null +++ b/deployment/ansible/playbooks/fix-sites-available-default.yml @@ -0,0 +1,57 @@ +--- +- name: Fix sites-available/default upstream configuration + hosts: production + gather_facts: yes + become: no + + tasks: + - name: Check php-upstream definition in sites-available/default + shell: | + cd ~/deployment/stacks/staging + echo "=== Check upstream definition ===" + docker compose exec -T staging-nginx grep -A 3 "upstream php-upstream" /etc/nginx/sites-available/default 2>&1 || echo "No upstream found" + echo "" + echo "=== Full sites-available/default file ===" + docker compose exec -T staging-nginx cat /etc/nginx/sites-available/default 2>&1 + args: + executable: /bin/bash + register: upstream_check + ignore_errors: yes + failed_when: false + + - name: Display upstream check + debug: + msg: "{{ upstream_check.stdout_lines }}" + + - name: Fix php-upstream in sites-available/default + shell: | + cd ~/deployment/stacks/staging + echo "=== Fix php-upstream definition ===" + docker compose exec -T staging-nginx sed -i 's|server 127.0.0.1:9000;|server staging-app:9000;|g' /etc/nginx/sites-available/default || echo "Fix 127.0.0.1 failed" + docker compose exec -T staging-nginx sed -i 's|server localhost:9000;|server staging-app:9000;|g' /etc/nginx/sites-available/default || echo "Fix localhost failed" + echo "" + echo "=== Verify fix ===" + docker compose exec -T staging-nginx grep -A 3 "upstream php-upstream" /etc/nginx/sites-available/default 2>&1 || echo "No upstream found" + args: + executable: /bin/bash + register: fix_upstream + ignore_errors: yes + failed_when: false + + - name: Display fix result + debug: + msg: "{{ fix_upstream.stdout_lines }}" + + - name: Reload nginx + shell: | + cd ~/deployment/stacks/staging + docker compose exec -T staging-nginx nginx -t && docker compose restart staging-nginx || echo "Reload failed" + args: + executable: /bin/bash + register: reload_nginx + ignore_errors: yes + failed_when: false + + - name: Display reload result + debug: + msg: "{{ reload_nginx.stdout_lines }}" diff --git a/deployment/ansible/playbooks/quick-staging-logs-check.yml b/deployment/ansible/playbooks/quick-staging-logs-check.yml new file mode 100644 index 00000000..3387c74c --- /dev/null +++ b/deployment/ansible/playbooks/quick-staging-logs-check.yml @@ -0,0 +1,28 @@ +--- +- name: Quick Staging Logs Check + hosts: production + gather_facts: yes + become: no + + tasks: + - name: Quick container status check + shell: | + echo "=== Method 1: Direct docker ps ===" + docker ps --filter "name=staging" || echo "No staging containers found" + echo "" + echo "=== Method 2: Docker compose ps ===" + cd ~/deployment/stacks/staging && docker compose ps 2>&1 || echo "Docker compose failed" + echo "" + echo "=== Method 3: Direct logs access ===" + docker logs staging-app --tail=30 2>&1 | tail -20 || echo "Could not get staging-app logs" + args: + executable: /bin/bash + register: quick_check + ignore_errors: yes + failed_when: false + + - name: Display results + debug: + msg: "{{ (quick_check.stdout_lines | default([])) + (quick_check.stderr_lines | default([])) }}" + when: quick_check is defined + failed_when: false diff --git a/deployment/ansible/playbooks/test-fastcgi-directly.yml b/deployment/ansible/playbooks/test-fastcgi-directly.yml new file mode 100644 index 00000000..3c685963 --- /dev/null +++ b/deployment/ansible/playbooks/test-fastcgi-directly.yml @@ -0,0 +1,46 @@ +--- +- name: Test FastCGI Connection Directly + hosts: production + gather_facts: yes + become: no + + tasks: + - name: Install cgi-fcgi in nginx container and test FastCGI + shell: | + cd ~/deployment/stacks/staging + echo "=== Test FastCGI with a simple request ===" + # Create a simple test script + docker compose exec -T staging-nginx sh -c "echo -e 'REQUEST_METHOD=GET\nSCRIPT_FILENAME=/var/www/html/public/index.php\nSERVER_NAME=test\n' > /tmp/fcgi-test.txt && cat /tmp/fcgi-test.txt" || echo "Could not create test file" + echo "" + echo "=== Check nginx error log directly ===" + docker compose exec -T staging-nginx tail -100 /var/log/nginx/error.log 2>&1 || echo "Error log not found at /var/log/nginx/error.log" + echo "" + echo "=== Check nginx access log ===" + docker compose exec -T staging-nginx tail -50 /var/log/nginx/access.log 2>&1 | tail -20 || echo "Access log not found" + args: + executable: /bin/bash + register: fcgi_test + ignore_errors: yes + failed_when: false + + - name: Display FastCGI test results + debug: + msg: "{{ fcgi_test.stdout_lines }}" + + - name: Make a real HTTP request and capture full response + shell: | + cd ~/deployment/stacks/staging + echo "=== Make HTTP request from nginx container ===" + docker compose exec -T staging-nginx curl -v http://127.0.0.1/ 2>&1 || echo "Request failed" + echo "" + echo "=== Check what nginx sees ===" + docker compose exec -T staging-nginx sh -c "timeout 2 tail -f /var/log/nginx/error.log 2>&1 & sleep 1 && curl -s http://127.0.0.1/ > /dev/null && sleep 1" || echo "Log check failed" + args: + executable: /bin/bash + register: http_test + ignore_errors: yes + failed_when: false + + - name: Display HTTP test results + debug: + msg: "{{ http_test.stdout_lines }}" diff --git a/deployment/ansible/playbooks/test-staging-container-access.yml b/deployment/ansible/playbooks/test-staging-container-access.yml new file mode 100644 index 00000000..7d282737 --- /dev/null +++ b/deployment/ansible/playbooks/test-staging-container-access.yml @@ -0,0 +1,79 @@ +--- +- name: Test Staging Container Access and Logs + hosts: production + gather_facts: yes + become: no + + tasks: + - name: Check if we can access containers directly + shell: | + # Try direct docker commands first + echo "=== Direct Docker Container Check ===" + docker ps -a --filter "name=staging" + echo "" + echo "=== Try docker logs directly ===" + docker logs staging-app --tail=20 2>&1 || echo "Could not access staging-app logs" + echo "" + echo "=== Try docker logs for staging-nginx ===" + docker logs staging-nginx --tail=20 2>&1 || echo "Could not access staging-nginx logs" + args: + executable: /bin/bash + register: direct_access + ignore_errors: yes + + - name: Display direct access results + debug: + msg: "{{ (direct_access.stdout_lines | default([])) + (direct_access.stderr_lines | default([])) }}" + + - name: Try docker compose from different locations + shell: | + echo "=== Current directory ===" + pwd + echo "" + echo "=== Try from home directory ===" + cd ~ && docker compose -f ~/deployment/stacks/staging/docker-compose.yml ps 2>&1 || echo "Failed from home" + echo "" + echo "=== Try from staging directory ===" + cd ~/deployment/stacks/staging && docker compose ps 2>&1 || echo "Failed from staging directory" + args: + executable: /bin/bash + register: compose_check + ignore_errors: yes + + - name: Display compose check results + debug: + msg: "{{ (compose_check.stdout_lines | default([])) + (compose_check.stderr_lines | default([])) }}" + + - name: Check docker compose configuration + shell: | + cd ~/deployment/stacks/staging + echo "=== Docker Compose file exists? ===" + test -f docker-compose.yml && echo "? docker-compose.yml exists" || echo "? docker-compose.yml missing" + echo "" + echo "=== Docker Compose file first 30 lines ===" + head -30 docker-compose.yml + echo "" + echo "=== Check service names ===" + docker compose config --services 2>&1 || echo "Could not get service names" + args: + executable: /bin/bash + register: compose_config + ignore_errors: yes + + - name: Display compose configuration + debug: + msg: "{{ (compose_config.stdout_lines | default([])) + (compose_config.stderr_lines | default([])) }}" + + - name: Try getting logs with explicit file path + shell: | + docker compose -f ~/deployment/stacks/stacks/staging/docker-compose.yml logs --tail=20 staging-app 2>&1 || \ + docker compose -f ~/deployment/stacks/staging/docker-compose.yml logs --tail=20 staging-app 2>&1 || \ + echo "All methods failed" + args: + executable: /bin/bash + register: explicit_logs + ignore_errors: yes + + - name: Display explicit logs attempt + debug: + msg: "{{ (explicit_logs.stdout_lines | default([])) + (explicit_logs.stderr_lines | default([])) }}" diff --git a/deployment/ansible/playbooks/view-staging-logs-readable.yml b/deployment/ansible/playbooks/view-staging-logs-readable.yml new file mode 100644 index 00000000..aeed9743 --- /dev/null +++ b/deployment/ansible/playbooks/view-staging-logs-readable.yml @@ -0,0 +1,44 @@ +--- +- name: View Staging Logs in Readable Format + hosts: production + gather_facts: yes + become: no + + tasks: + - name: Get and format staging-app logs + shell: | + cd ~/deployment/stacks/staging + echo "=== Staging App Logs (Last 50 lines, formatted) ===" + docker compose logs --tail=200 staging-app 2>&1 | \ + while IFS= read -r line; do + # Try to parse JSON logs and format them nicely + if echo "$line" | grep -qE '^{|^\{'; then + echo "$line" | python3 -m json.tool 2>/dev/null || echo "$line" + else + echo "$line" + fi + done | tail -50 + args: + executable: /bin/bash + register: formatted_logs + ignore_errors: yes + failed_when: false + + - name: Display formatted logs + debug: + msg: "{{ formatted_logs.stdout_lines }}" + + - name: Show simple tail of logs (for quick view) + shell: | + cd ~/deployment/stacks/staging + echo "=== Quick View: Last 30 lines ===" + docker compose logs --tail=30 staging-app 2>&1 + args: + executable: /bin/bash + register: quick_logs + ignore_errors: yes + failed_when: false + + - name: Display quick logs + debug: + msg: "{{ quick_logs.stdout_lines }}" diff --git a/deployment/docs/README.md b/deployment/docs/README.md index 2e63b02b..2bdb416c 100644 --- a/deployment/docs/README.md +++ b/deployment/docs/README.md @@ -132,6 +132,10 @@ - **[workflow-troubleshooting.md](reference/workflow-troubleshooting.md)** - Workflow Troubleshooting - **[quick-start.md](guides/quick-start.md)** - Troubleshooting Quick Reference +### Staging-Probleme + +- **[staging-502-nginx-phpfpm.md](troubleshooting/staging-502-nginx-phpfpm.md)** - Staging 502 Bad Gateway: Nginx kann nicht zu PHP-FPM verbinden + ### Test-Dokumentation - **[test-results.md](tests/test-results.md)** - Test Ergebnisse @@ -174,7 +178,8 @@ 1. **[quick-start.md](guides/quick-start.md)** - Quick Troubleshooting 2. **[workflow-troubleshooting.md](reference/workflow-troubleshooting.md)** - Workflow-Probleme -3. Stack-spezifische READMEs f?r Details +3. **[staging-502-nginx-phpfpm.md](troubleshooting/staging-502-nginx-phpfpm.md)** - Staging 502 Fehler (Nginx/PHP-FPM) +4. Stack-spezifische READMEs f?r Details --- diff --git a/deployment/docs/troubleshooting/staging-502-nginx-phpfpm.md b/deployment/docs/troubleshooting/staging-502-nginx-phpfpm.md new file mode 100644 index 00000000..34282178 --- /dev/null +++ b/deployment/docs/troubleshooting/staging-502-nginx-phpfpm.md @@ -0,0 +1,169 @@ +# Staging 502 Bad Gateway - Nginx PHP-FPM Connection Issue + +## Problem + +Staging-Server zeigt **502 Bad Gateway** Fehler auf `https://staging.michaelschiemer.de/`. + +Nginx-Fehlerlog zeigt: +``` +connect() failed (111: Connection refused) while connecting to upstream, +client: 172.21.0.4, server: _, request: "GET / HTTP/1.1", +upstream: "fastcgi://127.0.0.1:9000" +``` + +## Ursache + +**Root Cause:** Nginx versucht, sich mit PHP-FPM auf `127.0.0.1:9000` (localhost) zu verbinden, aber PHP-FPM l?uft in einem **separaten Docker-Container** (`staging-app`). + +### Technische Details + +1. **Nginx-Konfiguration:** Es gibt zwei Nginx-Konfigurationsdateien: + - `/etc/nginx/conf.d/default.conf` ? Korrekt: verwendet `upstream php-upstream { server staging-app:9000; }` + - `/etc/nginx/sites-available/default` ? Falsch: verwendet `upstream php-upstream { server 127.0.0.1:9000; }` + +2. **Nginx Include-Ordnung:** `nginx.conf` inkludiert: + ```nginx + include /etc/nginx/sites-enabled/*; # Wird ZULETZT geladen + include /etc/nginx/conf.d/*.conf; # Wird ZUERST geladen + ``` + Da `sites-enabled/default` **nach** `conf.d/default.conf` geladen wird, **?berschreibt** sie die korrekte Konfiguration. + +3. **Container-Architektur:** + - `staging-nginx` Container: L?uft Nginx + - `staging-app` Container: L?uft PHP-FPM auf Port 9000 + - Beide Container sind im `staging-internal` Docker-Netzwerk + +## L?sung + +### Sofort-Fix (Manuell) + +```bash +cd ~/deployment/stacks/staging + +# Fix upstream in sites-available/default +docker compose exec -T staging-nginx sed -i '/upstream php-upstream {/,/}/s|server 127.0.0.1:9000;|server staging-app:9000;|g' /etc/nginx/sites-available/default + +# Reload nginx +docker compose restart staging-nginx +``` + +Oder mit Ansible: +```bash +ansible-playbook -i deployment/ansible/inventory/production.yml deployment/ansible/playbooks/fix-sites-available-default.yml +``` + +### Permanente L?sung + +Das Entrypoint-Script in `deployment/stacks/staging/docker-compose.yml` (Zeilen 181-192) wurde erweitert, um das Problem **automatisch beim Container-Start** zu beheben: + +```bash +# Fix nginx upstream configuration - sites-enabled/default overrides conf.d/default.conf +if [ -f "/etc/nginx/sites-available/default" ]; then + echo "?? [staging-nginx] Fixing PHP-FPM upstream configuration..." + # Replace in upstream block + sed -i '/upstream php-upstream {/,/}/s|server 127.0.0.1:9000;|server staging-app:9000;|g' /etc/nginx/sites-available/default || true + sed -i '/upstream php-upstream {/,/}/s|server localhost:9000;|server staging-app:9000;|g' /etc/nginx/sites-available/default || true + # Replace any direct fastcgi_pass references too + sed -i 's|fastcgi_pass 127.0.0.1:9000;|fastcgi_pass php-upstream;|g' /etc/nginx/sites-available/default || true + sed -i 's|fastcgi_pass localhost:9000;|fastcgi_pass php-upstream;|g' /etc/nginx/sites-available/default || true + echo "? [staging-nginx] PHP-FPM upstream fixed" +fi +``` + +## Diagnose-Schritte + +Wenn das Problem erneut auftritt: + +### 1. Pr?fe Nginx Error Logs + +```bash +cd ~/deployment/stacks/staging +docker compose logs --tail=50 staging-nginx | grep -i "502\|error\|upstream" +``` + +Oder direkt im Container: +```bash +docker compose exec -T staging-nginx tail -100 /var/log/nginx/error.log +``` + +**Erwartete Fehlermeldung bei diesem Problem:** +``` +connect() failed (111: Connection refused) while connecting to upstream, +upstream: "fastcgi://127.0.0.1:9000" +``` + +### 2. Pr?fe PHP-FPM Status + +```bash +# Container-Status +docker compose ps staging-app staging-nginx + +# PHP-FPM l?uft? +docker compose exec -T staging-app netstat -tlnp | grep 9000 +# Oder: +docker compose exec -T staging-app ss -tlnp | grep 9000 +``` + +### 3. Pr?fe Upstream-Konfiguration + +```bash +# Welche upstream-Definition wird verwendet? +docker compose exec -T staging-nginx grep -A 3 "upstream php-upstream" /etc/nginx/sites-available/default +docker compose exec -T staging-nginx grep -A 3 "upstream php-upstream" /etc/nginx/conf.d/default.conf + +# Welche fastcgi_pass Direktiven gibt es? +docker compose exec -T staging-nginx grep "fastcgi_pass" /etc/nginx/sites-available/default +docker compose exec -T staging-nginx grep "fastcgi_pass" /etc/nginx/conf.d/default.conf +``` + +**Erwartetes Ergebnis (korrekt):** +``` +upstream php-upstream { + server staging-app:9000; +} +``` + +**Fehlerhaft (zeigt Problem):** +``` +upstream php-upstream { + server 127.0.0.1:9000; # ? FALSCH +} +``` + +### 4. Teste Verbindung von Nginx zu PHP-FPM + +```bash +# Kann nginx sich mit staging-app:9000 verbinden? +docker compose exec -T staging-nginx curl -v http://staging-app:9000 2>&1 | head -20 + +# Oder teste direkt mit FastCGI (wenn cgi-fcgi installiert ist) +docker compose exec -T staging-nginx sh -c "echo -e 'REQUEST_METHOD=GET\nSCRIPT_FILENAME=/var/www/html/public/index.php\n' | cgi-fcgi -bind -connect staging-app:9000" +``` + +### 5. Vollst?ndige Diagnose mit Ansible + +```bash +ansible-playbook -i deployment/ansible/inventory/production.yml \ + deployment/ansible/playbooks/diagnose-staging-502.yml +``` + +## Verwandte Dateien + +- **Docker Compose Config:** `deployment/stacks/staging/docker-compose.yml` +- **Nginx Config (korrekt):** `deployment/stacks/staging/nginx/conf.d/default.conf` +- **Nginx Config (problem):** `/etc/nginx/sites-available/default` (im Container) +- **Diagnose-Playbooks:** + - `deployment/ansible/playbooks/diagnose-staging-502.yml` + - `deployment/ansible/playbooks/fix-sites-available-default.yml` + +## Verhindern in Zukunft + +1. **Entrypoint-Script:** Das Entrypoint-Script behebt das Problem automatisch beim Container-Start +2. **Image-Build:** Idealerweise sollte die `sites-available/default` Datei im Docker-Image bereits korrekt konfiguriert sein +3. **Alternativ:** Entferne `sites-available/default` komplett und verwende nur `conf.d/default.conf` + +## Siehe auch + +- [Staging Stack README](../../stacks/staging/README.md) +- [Nginx Configuration](../../stacks/staging/nginx/conf.d/default.conf) +- [Troubleshooting Overview](../README.md) diff --git a/deployment/stacks/staging/docker-compose.yml b/deployment/stacks/staging/docker-compose.yml index bf45e21b..29e7bfbc 100644 --- a/deployment/stacks/staging/docker-compose.yml +++ b/deployment/stacks/staging/docker-compose.yml @@ -117,6 +117,12 @@ services: echo "" echo "📊 Environment variables:" env | grep -E "DB_|APP_" | grep -v "PASSWORD|KEY|SECRET" || true + + echo "" + echo "🛠️ Adjusting filesystem permissions..." + chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache 2>/dev/null || true + find /var/www/html/storage /var/www/html/bootstrap/cache -type d -exec chmod 775 {} \; 2>/dev/null || true + find /var/www/html/storage /var/www/html/bootstrap/cache -type f -exec chmod 664 {} \; 2>/dev/null || true # Start PHP-FPM only (no nginx) echo "" @@ -179,14 +185,16 @@ services: fi # Fix nginx upstream configuration - sites-enabled/default overrides conf.d/default.conf + # This is critical: nginx sites-available/default uses 127.0.0.1:9000 but PHP-FPM runs in staging-app container if [ -f "/etc/nginx/sites-available/default" ]; then echo "🔧 [staging-nginx] Fixing PHP-FPM upstream configuration..." - sed -i 's|server 127.0.0.1:9000;|server staging-app:9000;|g' /etc/nginx/sites-available/default || true - sed -i 's|server localhost:9000;|server staging-app:9000;|g' /etc/nginx/sites-available/default || true - # Also check and fix upstream definition directly - if grep -q "server 127.0.0.1:9000" /etc/nginx/sites-available/default; then - sed -i '/upstream php-upstream {/,/}/s/server 127.0.0.1:9000;/server staging-app:9000;/' /etc/nginx/sites-available/default || true - fi + # Replace in upstream block + sed -i '/upstream php-upstream {/,/}/s|server 127.0.0.1:9000;|server staging-app:9000;|g' /etc/nginx/sites-available/default || true + sed -i '/upstream php-upstream {/,/}/s|server localhost:9000;|server staging-app:9000;|g' /etc/nginx/sites-available/default || true + # Replace any direct fastcgi_pass references too + sed -i 's|fastcgi_pass 127.0.0.1:9000;|fastcgi_pass php-upstream;|g' /etc/nginx/sites-available/default || true + sed -i 's|fastcgi_pass localhost:9000;|fastcgi_pass php-upstream;|g' /etc/nginx/sites-available/default || true + echo "✅ [staging-nginx] PHP-FPM upstream fixed" fi # Start nginx only (no PHP-FPM, no Git clone - staging-app container handles that) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 77ac5be7..7eb3db42 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -105,6 +105,12 @@ echo "" echo "📊 Environment variables:" env | grep -E "DB_|APP_" | grep -v "PASSWORD|KEY|SECRET" || true +echo "" +echo "🛠️ Adjusting filesystem permissions..." +chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache 2>/dev/null || true +find /var/www/html/storage /var/www/html/bootstrap/cache -type d -exec chmod 775 {} \; 2>/dev/null || true +find /var/www/html/storage /var/www/html/bootstrap/cache -type f -exec chmod 664 {} \; 2>/dev/null || true + # Start PHP-FPM in background (inherits all environment variables) echo "" echo "🚀 Starting PHP-FPM..." diff --git a/src/Framework/Http/Middlewares/RoutingMiddleware.php b/src/Framework/Http/Middlewares/RoutingMiddleware.php index 1c739fe5..106ec2ef 100644 --- a/src/Framework/Http/Middlewares/RoutingMiddleware.php +++ b/src/Framework/Http/Middlewares/RoutingMiddleware.php @@ -8,6 +8,7 @@ use App\Framework\Auth\RouteAuthorizationService; use App\Framework\Config\TypedConfiguration; use App\Framework\DI\Container; use App\Framework\Http\HttpMiddleware; +use App\Framework\Http\HttpRequest; use App\Framework\Http\MiddlewareContext; use App\Framework\Http\MiddlewarePriority; use App\Framework\Http\MiddlewarePriorityAttribute; @@ -27,6 +28,7 @@ use App\Framework\Router\Result\ViewResult; use App\Framework\Router\RouteContext; use App\Framework\Router\RouteDispatcher; use App\Framework\Router\Router; +use App\Framework\Router\ValueObjects\RouteParameters; #[MiddlewarePriorityAttribute(MiddlewarePriority::ROUTING)] final readonly class RoutingMiddleware implements HttpMiddleware @@ -54,7 +56,7 @@ final readonly class RoutingMiddleware implements HttpMiddleware */ public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext { - error_log("DEBUG RoutingMiddleware: __invoke() called - Path: " . $context->request->path); + #error_log("DEBUG RoutingMiddleware: __invoke() called - Path: " . $context->request->path); $request = $context->request; @@ -98,7 +100,7 @@ final readonly class RoutingMiddleware implements HttpMiddleware $route = $routeContext->match->route; if ($route instanceof \App\Framework\Core\DynamicRoute && ! empty($route->paramValues)) { error_log("DEBUG: Route param values extracted: " . json_encode($route->paramValues)); - $request = new \App\Framework\Http\HttpRequest( + $request = new HttpRequest( method: $request->method, headers: $request->headers, body: $request->body, @@ -109,13 +111,13 @@ final readonly class RoutingMiddleware implements HttpMiddleware server: $request->server, id: $request->id, parsedBody: $request->parsedBody, - routeParameters: \App\Framework\Router\ValueObjects\RouteParameters::fromArray($route->paramValues) + routeParameters: RouteParameters::fromArray($route->paramValues) ); $context = new MiddlewareContext($request, $context->response); // Update request in container so dispatcher gets the updated request $this->container->instance(Request::class, $request); - $this->container->instance(\App\Framework\Http\HttpRequest::class, $request); + $this->container->instance(HttpRequest::class, $request); } // Perform route authorization checks diff --git a/src/Framework/Mcp/Tools/Categories/Analysis/RouteDebugger.php b/src/Framework/Mcp/Tools/Categories/Analysis/RouteDebugger.php index c6747c8f..69f624bb 100644 --- a/src/Framework/Mcp/Tools/Categories/Analysis/RouteDebugger.php +++ b/src/Framework/Mcp/Tools/Categories/Analysis/RouteDebugger.php @@ -20,7 +20,7 @@ final readonly class RouteDebugger public function __construct( private McpToolContext $context, private CompiledRoutes $compiledRoutes, - private Router $router, + #private Router $router, private MiddlewareManager $middlewareManager ) { } diff --git a/src/Framework/Router/RouterSetup.php b/src/Framework/Router/RouterSetup.php index b207b66c..015edb7a 100644 --- a/src/Framework/Router/RouterSetup.php +++ b/src/Framework/Router/RouterSetup.php @@ -9,9 +9,11 @@ use App\Framework\Context\ContextType; use App\Framework\Core\PathProvider; use App\Framework\Core\RouteCache; use App\Framework\Core\RouteCompiler; +use App\Framework\DI\Container; use App\Framework\DI\DefaultContainer; use App\Framework\DI\Initializer; use App\Framework\Discovery\Results\DiscoveryRegistry; +use App\Framework\Http\Method; /** * Verantwortlich für die Einrichtung des Routers @@ -19,11 +21,10 @@ use App\Framework\Discovery\Results\DiscoveryRegistry; final readonly class RouterSetup { public function __construct( - private DefaultContainer $container, + private Container $container, private RouteCompiler $routeCompiler, private DiscoveryRegistry $results, - ) { - } + ) {} /** * Richtet den Router basierend auf den verarbeiteten Attributen ein @@ -57,12 +58,12 @@ final readonly class RouterSetup error_log("🚦 ROUTER SETUP: HTTP Methods count = " . count($staticRoutes)); // Check GET method subdomain keys - $getSubdomainKeys = $optimizedRoutes->getSubdomainKeys(\App\Framework\Http\Method::GET); + $getSubdomainKeys = $optimizedRoutes->getSubdomainKeys(Method::GET); error_log("🚦 ROUTER SETUP: GET method has subdomain keys: " . json_encode($getSubdomainKeys)); // Log routes per subdomain for GET foreach ($getSubdomainKeys as $subdomainKey) { - $routes = $optimizedRoutes->getStaticRoutesForSubdomain(\App\Framework\Http\Method::GET, $subdomainKey); + $routes = $optimizedRoutes->getStaticRoutesForSubdomain(Method::GET, $subdomainKey); error_log("🚦 ROUTER SETUP: GET subdomain '{$subdomainKey}' has " . count($routes) . " routes"); foreach ($routes as $path => $route) { error_log("🚦 ROUTER SETUP: - {$path}"); diff --git a/src/Framework/Storage/ObjectStorage.php b/src/Framework/Storage/ObjectStorage.php index 3043935a..450bb091 100644 --- a/src/Framework/Storage/ObjectStorage.php +++ b/src/Framework/Storage/ObjectStorage.php @@ -4,7 +4,7 @@ namespace App\Framework\Storage; interface ObjectStorage { - public function put(string $bucket, string $key, string|mixed $body, array $opts = []): ObjectInfo; + public function put(string $bucket, string $key, string $body, array $opts = []): ObjectInfo; public function get(string $bucket, string $key): string; public function stream(string $bucket, string $key); // resource|StreamInterface public function head(string $bucket, string $key): ObjectInfo; diff --git a/src/Framework/Storage/STREAMING_IMPLEMENTATION_PLAN.md b/src/Framework/Storage/STREAMING_IMPLEMENTATION_PLAN.md new file mode 100644 index 00000000..25485bf2 --- /dev/null +++ b/src/Framework/Storage/STREAMING_IMPLEMENTATION_PLAN.md @@ -0,0 +1,155 @@ +# Streaming-Implementationsliste (Option 2) – Storage Modul + +Ziel: Dedizierte Streaming-Methoden in der Object Storage API implementieren – ohne externe Dependencies. Fokus auf speichereffiziente Downloads/Uploads großer Dateien via Streams (memory-safe). + +## 1) Interface-Erweiterung (ObjectStorage) +- Ergänze folgende Methoden im ObjectStorage-Interface: + - get(string $bucket, string $key): string + - put(string $bucket, string $key, string|mixed $body, array $opts = []): ObjectInfo + - getToStream(string $bucket, string $key, mixed $destination): int + - Erwartet eine schreibbare PHP-Stream-Resource als $destination + - Rückgabewert: Anzahl geschriebener Bytes + - putFromStream(string $bucket, string $key, mixed $source, array $opts = []): ObjectInfo + - Erwartet eine lesbare PHP-Stream-Resource als $source + - openReadStream(string $bucket, string $key) + - Gibt eine lesbare PHP-Stream-Resource zurück + - head(string $bucket, string $key): ObjectInfo + - delete(string $bucket, string $key): void + - exists(string $bucket, string $key): bool + - url(string $bucket, string $key): ?string + - temporaryUrl(string $bucket, string $key, \DateInterval $ttl, array $opts = []): string + +Hinweise: +- Signaturen mit mixed für Stream-Parameter (PHP-Resource), genaue Validierung in den Implementierungen. +- Bestehende Methoden get/put bleiben kompatibel; Streaming ist ergänzend. + +## 2) Filesystem-Adapter (lokales Backend) +Ziel: ObjectStorage-Implementierung auf Basis des bestehenden Filesystem-Backends. + +- Neue Klasse: src/Framework/Storage/FilesystemObjectStorage.php +- Verantwortlichkeiten: + - Pfadaufbau: basePath/bucket/key + - get/put über Filesystem (kleine Dateien) + - Streaming: + - getToStream: Datei lesen und via stream_copy_to_stream in $destination schreiben + - putFromStream: Stream-Inhalt in Datei schreiben (Chunked Copy) + - openReadStream: lesbaren Stream auf die Datei öffnen + - head/exists/delete/url/temporaryUrl: + - head: Dateigröße, mtime, mimetype (falls verfügbar) + - url: für lokales Storage in der Regel null (oder optionaler lokaler URL-Generator) + - temporaryUrl: nicht unterstützt -> Exception oder leer lassen mit Doku +- Fehlerbehandlung: + - File not found, Permission, Read/Write-Errors sauber abbilden +- Performance: + - Buffersize bei stream_copy_to_stream kontrollierbar machen (Optionen) + +## 3) MinIO-Backend (ohne Dependencies) +Ziel: S3-kompatibler MinIO-Client mittels nativer PHP-Funktionen (HTTP-Streams und AWS SigV4) + ObjectStorage-Adapter. + +- Dateien: + - src/Framework/Storage/Clients/MinioClient.php + - Funktionen: putObject, getObject, headObject, deleteObject, createPresignedUrl + - Streaming-Funktionen: + - getObjectToStream($bucket, $key, $destination): int + - putObjectFromStream($bucket, $key, $source, array $opts = []): array + - openStream($bucket, $key) -> resource + - Interne Helfer: + - AWS SigV4 Signierung (kanonische Anfrage, Header, Query, Hashes) + - Response-Parsing (Headers, Status, Fehler-XML) + - Chunked Transfer wenn keine Content-Length verfügbar + - src/Framework/Storage/MinioObjectStorage.php + - Implementiert ObjectStorage + - get/put: kleinere Dateien + - getToStream/putFromStream/openReadStream: nutzen die Streaming-APIs des MinioClient + - head/delete/exists/url/temporaryUrl: + - temporaryUrl via createPresignedUrl +- Sicherheit: + - TTL-Limits für temporaryUrl (z. B. max 24h) + - Path-Style vs. Virtual Host Style konfigurierbar + - Keine externen Pakete + +## 4) Manager/Factory/DI +- Option A: StorageManager für ObjectStorage-Driver (local/minio) + - src/Framework/Storage/StorageManager.php + - driver(string $name): ObjectStorage + - bucket(string $name): Bucket (Convenience) +- Option B (einfach): Container-Bindings direkt + - Default ObjectStorage anhand ENV +- Initializer: + - src/Framework/Storage/StorageInitializer.php + - Konfig aus ENV (DEFAULT_DRIVER, MINIO_ENDPOINT, KEYS, LOCAL_ROOT, PATH_STYLE etc.) + - Registriere: + - FilesystemObjectStorage (als "local") + - MinioClient + MinioObjectStorage (als "minio") + - ObjectStorage (Default: ENV) +- Bucket-API (optional aber DX-freundlich): + - src/Framework/Storage/Bucket.php + - wrappt ObjectStorage für festen Bucket-Namen + +## 5) Konfiguration +- ENV Variablen vorschlagen (Beispiele): + - STORAGE_DRIVER=local|minio + - STORAGE_LOCAL_ROOT=/var/www/html/storage/objects + - MINIO_ENDPOINT=http://minio:9000 + - MINIO_ACCESS_KEY=... + - MINIO_SECRET_KEY=... + - MINIO_REGION=us-east-1 + - MINIO_USE_PATH_STYLE=true +- StorageConfig Klasse (optional) für strukturierte Konfig-Verwaltung + +## 6) Tests (kritisch, da streaming-sensitiv) +- Unit-Tests FilesystemObjectStorage: + - put/get mit Strings + - putFromStream/getToStream mit temp Streams (php://temp) + - openReadStream und sequentielles Lesen + - head/exists/delete +- Unit-Tests MinioClient (Signierung) – isoliert: + - Signatur-Konsistenz (Golden Master) + - Parsing von Fehlern/Headers +- Integration-Tests (optional, per docker-compose mit MinIO): + - Upload/Download großer Dateien (z. B. >100MB) per Streams + - temporaryUrl Zugriff (GET) mit TTL +- Edge Cases: + - fehlende Read-/Write-Berechtigungen (Filesystem) + - Netzwerkfehler (MinIO) + - Nicht existierende Objekte + - Streams ohne bekannte Länge (Chunked Transfer) + +## 7) Fehler- und Ausnahmebehandlung +- Konsistente Exceptions im Storage-Namespace (z. B. StorageException) +- Filesystem-spezifische Fehler sauber mappen +- Aussagekräftige Meldungen (inkl. Pfad/Bucket/Key) + +## 8) Performance/Robustheit +- Stream-Buffer konfigurierbar (z. B. 8192 Bytes Default) +- Keine vollständigen Dateiladungen in Memory bei Streams +- Zeitouts/Retry-Strategie (MinIO) minimal vorsehen (optional) +- Sauberes Schließen aller Streams (try/finally) + +## 9) Sicherheit +- Path-Sanitizing im Filesystem-Adapter (keine Directory-Traversal) +- Eindeutige Handhabung von public URL vs. presigned URL +- Limitierung der Presign-Dauer und Validierung von Eingaben + +## 10) Dokumentation +- README/HowTo: + - Beispiel für lokale und MinIO-Nutzung + - Beispiele für Streaming (Upload/Download) + - Hinweise zu großen Dateien, Memory-Effizienz +- PHPDoc ausführlich an allen Public-APIs + +## 11) Milestones (Reihenfolge) +1. Interface-Änderungen (ObjectStorage) +2. FilesystemObjectStorage (inkl. Streaming) +3. MinioClient (Basis + Signierung) +4. MinioObjectStorage (inkl. Streaming) +5. Initializer/DI und Konfig +6. Tests (Unit), danach optional Integration +7. Doku + Beispiele + +## 12) Definition of Done +- Alle neuen Methoden implementiert und getestet +- Große Dateien via Streams funktionieren, ohne OOM +- Kein externer Dependency-Einsatz +- Doku und Beispiele vorhanden +- CI-Tests grün (Unit; Integration optional) diff --git a/src/Infrastructure/Database/Migrations/CreateComponentStateHistoryTable.php b/src/Infrastructure/Database/Migrations/CreateComponentStateHistoryTable.php index db680b8f..caff4ca3 100644 --- a/src/Infrastructure/Database/Migrations/CreateComponentStateHistoryTable.php +++ b/src/Infrastructure/Database/Migrations/CreateComponentStateHistoryTable.php @@ -6,6 +6,7 @@ namespace App\Infrastructure\Database\Migrations; use App\Framework\Database\Migration\Migration; use App\Framework\Database\ConnectionInterface; +use App\Framework\Database\Migration\MigrationVersion; use App\Framework\Database\Schema\Schema; /** @@ -67,9 +68,9 @@ final readonly class CreateComponentStateHistoryTable implements Migration $schema->execute(); } - public function getVersion(): string + public function getVersion(): MigrationVersion { - return '2024_12_20_120100'; + return MigrationVersion::fromTimestamp('2024_12_20_120000'); } public function getDescription(): string diff --git a/src/Infrastructure/Database/Migrations/CreateComponentStateTable.php b/src/Infrastructure/Database/Migrations/CreateComponentStateTable.php index ad84e4cf..f3cc2f48 100644 --- a/src/Infrastructure/Database/Migrations/CreateComponentStateTable.php +++ b/src/Infrastructure/Database/Migrations/CreateComponentStateTable.php @@ -6,6 +6,7 @@ namespace App\Infrastructure\Database\Migrations; use App\Framework\Database\Migration\Migration; use App\Framework\Database\ConnectionInterface; +use App\Framework\Database\Migration\MigrationVersion; use App\Framework\Database\Schema\Schema; /** @@ -53,9 +54,9 @@ final readonly class CreateComponentStateTable implements Migration $schema->execute(); } - public function getVersion(): string + public function getVersion(): MigrationVersion { - return '2024_12_20_120000'; + return MigrationVersion::fromString('2024_12_20_120000'); } public function getDescription(): string