Compare commits
11 Commits
1f96a32ea8
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d00fd2ac4 | ||
|
|
238d3244b2 | ||
|
|
072c284ba5 | ||
|
|
f3c65de9da | ||
|
|
77ceaf6bb0 | ||
|
|
811a8c8f51 | ||
|
|
d98ec294e7 | ||
|
|
f5d29e3515 | ||
|
|
e506f11873 | ||
|
|
d004e8a145 | ||
|
|
8e386802ba |
28
README.md
28
README.md
@@ -31,12 +31,35 @@ The CI workflow is configured to build with the repository root as the Docker bu
|
|||||||
|
|
||||||
- Images use `/var/www/html` as the webroot. When you mount a host directory over that path the mount replaces the image contents, including ownership.
|
- Images use `/var/www/html` as the webroot. When you mount a host directory over that path the mount replaces the image contents, including ownership.
|
||||||
- Recommended safe options:
|
- Recommended safe options:
|
||||||
|
|
||||||
- Pre-chown host files to UID/GID 1000 before starting containers:
|
- Pre-chown host files to UID/GID 1000 before starting containers:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo chown -R 1000:1000 ./wp_root
|
sudo chown -R 1000:1000 ./wp_root
|
||||||
```
|
```
|
||||||
|
|
||||||
- Or enable the entrypoint-based fixup in php-fpm by setting `CHOWN_ON_START=1` for the `php-fpm` service (the entrypoint is guarded — it only runs when this env is explicitly enabled).
|
- Or enable the entrypoint-based fixup in php-fpm by setting `CHOWN_ON_START=1` for the `php-fpm` service (the entrypoint is guarded — it only runs when this env is explicitly enabled).
|
||||||
|
|
||||||
|
## Logging & debugging
|
||||||
|
|
||||||
|
- PHP-FPM streams master/worker logs plus PHP fatals to stderr, so `docker compose logs php-fpm` (or your platform equivalent) will always contain the messages you need for incident response.
|
||||||
|
- The auto-prepend bootstrap additionally installs shutdown/exception hooks that write uncaught throwables and fatal errors to stderr even if WordPress or a plugin tampers with `ini_set()`.
|
||||||
|
- When you need full stack traces in the browser, set `FORCE_DEBUG_ERRORS=1` on the `php-fpm` service. The bootstrap enables verbose output and emits a warning in the container logs reminding you to turn it back off (leaving it on in production leaks stack traces to clients).
|
||||||
|
- Remove or unset `FORCE_DEBUG_ERRORS` after troubleshooting so production responses stay clean.
|
||||||
|
|
||||||
|
## Health checks
|
||||||
|
|
||||||
|
- The nginx config exposes `/healthz`, which bypasses WordPress entirely and executes `/usr/local/share/auvem/health/healthcheck.php` inside PHP-FPM. The script verifies the `/var/www/html` mount and optionally performs a MySQL connection test when `WORDPRESS_DB_*` variables are present.
|
||||||
|
- HTTP 200 means the platform (nginx ↔ php-fpm ↔ database) is healthy. Any filesystem or database failures return HTTP 503 with a short JSON payload detailing which probe failed. This lets you point uptime monitors at `/healthz` while still separately watching the public homepage for regressions caused by client code.
|
||||||
|
- Example local probe: `curl -fsS http://localhost:8080/healthz | jq`.
|
||||||
|
|
||||||
|
## WP-CLI baked in
|
||||||
|
|
||||||
|
- The PHP-FPM image now ships with the official `wp` binary, so you can run administrative remediation tasks without installing extra tooling on target hosts.
|
||||||
|
- Typical usage: `docker compose exec php-fpm wp --allow-root user update admin --user_pass='NewPassword123!' --skip-email --path=/var/www/html`.
|
||||||
|
- The binary runs as root inside the container, so remember the `--allow-root` flag (or create an app-level user if you prefer tighter isolation).
|
||||||
|
- Automation hooks (e.g., your client portal) can wrap `wp` commands for password resets, cron runs, plugin inspections, etc., provided they bind-mount the site into `/var/www/html`.
|
||||||
|
|
||||||
## Local Testing & Development
|
## Local Testing & Development
|
||||||
|
|
||||||
Use the provided `docker-compose.yml` in the repo root for local development — it builds images from the repo (so shared files are available) and mounts `./wp_root` for site content.
|
Use the provided `docker-compose.yml` in the repo root for local development — it builds images from the repo (so shared files are available) and mounts `./wp_root` for site content.
|
||||||
@@ -47,7 +70,7 @@ Below is an example `docker-compose.yml` for production deployments that pulls i
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
db:
|
mariadb:
|
||||||
image: mariadb:latest
|
image: mariadb:latest
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
@@ -61,11 +84,14 @@ services:
|
|||||||
php-fpm:
|
php-fpm:
|
||||||
image: gitea.auvem.com/auvem/wordpress-docker/php-fpm:7.4-stable
|
image: gitea.auvem.com/auvem/wordpress-docker/php-fpm:7.4-stable
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- mariadb
|
||||||
volumes:
|
volumes:
|
||||||
- ./wp_root:/var/www/html:rw
|
- ./wp_root:/var/www/html:rw
|
||||||
|
|
||||||
nginx:
|
nginx:
|
||||||
image: gitea.auvem.com/auvem/wordpress-docker/nginx:latest
|
image: gitea.auvem.com/auvem/wordpress-docker/nginx:latest
|
||||||
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "80:80"
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ services:
|
|||||||
php-fpm:
|
php-fpm:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: docker/7.4/Dockerfile
|
dockerfile: php-fpm/7.4/Dockerfile
|
||||||
image: local/auvem-php:7.4
|
image: local/auvem-php:7.4
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -1,69 +1,81 @@
|
|||||||
|
error_log /dev/stderr warn;
|
||||||
|
access_log /dev/stdout;
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
listen [::]:80;
|
listen [::]:80;
|
||||||
|
|
||||||
|
# Set the server name to a catch all since this will be behind
|
||||||
|
# a reverse proxy or load balancer in most cases.
|
||||||
server_name _;
|
server_name _;
|
||||||
root /var/www/html;
|
|
||||||
index index.php index.html index.htm;
|
|
||||||
|
|
||||||
# Basic security headers (can be extended per-site)
|
root /var/www/html;
|
||||||
|
index index.php;
|
||||||
|
|
||||||
|
# Security Headers
|
||||||
|
# These provide a strong baseline against common web vulnerabilities.
|
||||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
add_header X-Content-Type-Options "nosniff" always;
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
add_header X-XSS-Protection "1; mode=block" always;
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
|
|
||||||
|
|
||||||
# Deny access to hidden files and directories
|
# Deny access to any files starting with a dot.
|
||||||
location ~ (^|/)[.] {
|
location ~ /\. {
|
||||||
deny all;
|
deny all;
|
||||||
access_log off;
|
|
||||||
log_not_found off;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Static files: long cache, immutable where appropriate
|
# Deny access to specific sensitive file types.
|
||||||
|
location ~* \.(?:bak|conf|dist|fla|in[ci]|log|psd|sh|sql|sw[op])$ {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cache static assets for a long time.
|
||||||
|
# The 'immutable' directive is great for versioned assets.
|
||||||
location ~* \.(?:css|js|gif|jpe?g|png|ico|svg|woff2?|ttf|eot)$ {
|
location ~* \.(?:css|js|gif|jpe?g|png|ico|svg|woff2?|ttf|eot)$ {
|
||||||
try_files $uri =404;
|
try_files $uri =404;
|
||||||
access_log off;
|
access_log off;
|
||||||
expires 30d;
|
expires 30d;
|
||||||
add_header Cache-Control "public, max-age=2592000, immutable";
|
add_header Cache-Control "public, immutable";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Handle favicon and robots.txt without logging.
|
||||||
location = /favicon.ico { access_log off; log_not_found off; }
|
location = /favicon.ico { access_log off; log_not_found off; }
|
||||||
location = /robots.txt { access_log off; log_not_found off; allow all; }
|
location = /robots.txt { access_log off; log_not_found off; }
|
||||||
|
|
||||||
# Main front controller; fall back to index.php
|
# Fleet readiness probe that bypasses WordPress and executes a bundled PHP script.
|
||||||
location / {
|
location = /healthz {
|
||||||
try_files $uri $uri/ /index.php$is_args$args;
|
access_log off;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME /usr/local/share/auvem/health/healthcheck.php;
|
||||||
|
fastcgi_param SCRIPT_NAME /healthz;
|
||||||
|
fastcgi_param PATH_INFO "";
|
||||||
|
fastcgi_pass php-fpm:9000;
|
||||||
}
|
}
|
||||||
|
|
||||||
# PHP-FPM handling; pass to php-fpm:9000 (docker service name)
|
# Main WordPress front-controller.
|
||||||
location ~ [^/]
|
# All non-file requests fall through to index.php.
|
||||||
\.php(/|$) {
|
location / {
|
||||||
# Prevent direct access to PHP files in uploads or other writable dirs if necessary
|
try_files $uri $uri/ /index.php?$args;
|
||||||
try_files $document_root$fastcgi_script_name =404;
|
}
|
||||||
|
|
||||||
|
# Pass PHP scripts to the PHP-FPM container.
|
||||||
|
location ~ \.php$ {
|
||||||
|
try_files $uri =404;
|
||||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||||
# Use TCP FPM backend service name. Matches the php-fpm image we built.
|
|
||||||
|
# Use the service name of your PHP-FPM container.
|
||||||
fastcgi_pass php-fpm:9000;
|
fastcgi_pass php-fpm:9000;
|
||||||
fastcgi_index index.php;
|
fastcgi_index index.php;
|
||||||
include fastcgi_params;
|
include fastcgi_params;
|
||||||
|
|
||||||
|
# Set required CGI parameters.
|
||||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||||
fastcgi_param HTTPS $https if_not_empty;
|
fastcgi_param HTTPS $https if_not_empty;
|
||||||
|
|
||||||
|
# Optimize buffers for potentially large WordPress headers.
|
||||||
|
fastcgi_buffers 16 16k;
|
||||||
|
fastcgi_buffer_size 32k;
|
||||||
fastcgi_read_timeout 300;
|
fastcgi_read_timeout 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Block access to .ht* files
|
|
||||||
location ~* /\.(?:ht|git) {
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Optional: small buffer for large headers (WordPress with many cookies/plugins)
|
|
||||||
fastcgi_buffers 16 16k;
|
|
||||||
fastcgi_buffer_size 32k;
|
|
||||||
|
|
||||||
# Prevent clickjacking on all responses
|
|
||||||
add_header X-Frame-Options "SAMEORIGIN";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Default server; allow override by mounting /etc/nginx/conf.d/default.conf
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ RUN set -eux; \
|
|||||||
# Install runtime dependencies
|
# Install runtime dependencies
|
||||||
apk add --no-cache \
|
apk add --no-cache \
|
||||||
bash \
|
bash \
|
||||||
|
curl \
|
||||||
freetype \
|
freetype \
|
||||||
libjpeg-turbo \
|
libjpeg-turbo \
|
||||||
libpng \
|
libpng \
|
||||||
@@ -67,6 +68,10 @@ RUN set -eux; \
|
|||||||
# Use production php.ini
|
# Use production php.ini
|
||||||
cp "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"; \
|
cp "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"; \
|
||||||
\
|
\
|
||||||
|
# Install WP-CLI for remedial administration
|
||||||
|
curl -fSL https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar -o /usr/local/bin/wp; \
|
||||||
|
chmod +x /usr/local/bin/wp; \
|
||||||
|
\
|
||||||
# Clean up build dependencies
|
# Clean up build dependencies
|
||||||
apk del .build-deps; \
|
apk del .build-deps; \
|
||||||
rm -rf /var/cache/apk/* /tmp/*
|
rm -rf /var/cache/apk/* /tmp/*
|
||||||
@@ -79,33 +84,26 @@ RUN addgroup -g 1000 app && \
|
|||||||
mkdir -p /var/run/php /run/php /var/log/php && \
|
mkdir -p /var/run/php /run/php /var/log/php && \
|
||||||
chown -R app:app /var/run/php /run/php /var/log/php
|
chown -R app:app /var/run/php /run/php /var/log/php
|
||||||
|
|
||||||
# Minimal security / production tuning for opcache and PHP
|
# Ship opinionated PHP configuration snippets from source control
|
||||||
RUN set -eux; \
|
COPY php-fpm/conf.d/ /usr/local/etc/php/conf.d/
|
||||||
{ \
|
|
||||||
echo 'opcache.enable=1'; \
|
# Copy the force-debug script (enablement is handled via conf.d/99-force-debug.ini)
|
||||||
echo 'opcache.memory_consumption=128'; \
|
COPY --chown=app:app shared/php-fpm/force-debug.php /usr/local/etc/php/force-debug.php
|
||||||
echo 'opcache.interned_strings_buffer=8'; \
|
|
||||||
echo 'opcache.max_accelerated_files=10000'; \
|
# Copy shared healthcheck assets
|
||||||
echo 'opcache.revalidate_freq=2'; \
|
RUN mkdir -p /usr/local/share/auvem/health
|
||||||
echo 'opcache.fast_shutdown=1'; \
|
COPY shared/php-fpm/healthcheck.php /usr/local/share/auvem/health/healthcheck.php
|
||||||
echo 'opcache.enable_file_override=0'; \
|
|
||||||
} > /usr/local/etc/php/conf.d/zz-opcache.ini; \
|
|
||||||
{ \
|
|
||||||
echo 'expose_php = Off'; \
|
|
||||||
echo 'display_errors = Off'; \
|
|
||||||
echo 'log_errors = On'; \
|
|
||||||
echo 'error_log = /proc/self/fd/2'; \
|
|
||||||
} > /usr/local/etc/php/conf.d/zz-hardening.ini
|
|
||||||
|
|
||||||
# Copy pool configuration from this directory
|
# Copy pool configuration from this directory
|
||||||
COPY --chown=app:app php-fpm/${BASE_VERSION}/www.conf /usr/local/etc/php-fpm.d/www.conf
|
COPY --chown=app:app php-fpm/${BASE_VERSION}/www.conf /usr/local/etc/php-fpm.d/www.conf
|
||||||
|
# Copy the global php-fpm configuration so logging defaults are predictable
|
||||||
|
COPY php-fpm/${BASE_VERSION}/php-fpm.conf /usr/local/etc/php-fpm.conf
|
||||||
|
|
||||||
# Copy entrypoint from shared path in repo root
|
# Copy entrypoint from shared path in repo root
|
||||||
COPY --chown=root:root shared/php-fpm/entrypoint.sh /usr/local/bin/entrypoint.sh
|
COPY --chown=root:root shared/php-fpm/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||||
RUN chmod 755 /usr/local/bin/entrypoint.sh
|
RUN chmod 755 /usr/local/bin/entrypoint.sh
|
||||||
|
|
||||||
WORKDIR /var/www/html
|
WORKDIR /var/www/html
|
||||||
USER app
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||||
CMD ["php-fpm"]
|
CMD ["php-fpm"]
|
||||||
|
|||||||
8
php-fpm/7.4/php-fpm.conf
Normal file
8
php-fpm/7.4/php-fpm.conf
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[global]
|
||||||
|
pid = /var/run/php/php-fpm.pid
|
||||||
|
error_log = /proc/self/fd/2
|
||||||
|
log_limit = 8192
|
||||||
|
log_buffering = no
|
||||||
|
daemonize = no
|
||||||
|
|
||||||
|
include = /usr/local/etc/php-fpm.d/*.conf
|
||||||
@@ -41,10 +41,17 @@ request_terminate_timeout = 300s
|
|||||||
request_slowlog_timeout = 5s
|
request_slowlog_timeout = 5s
|
||||||
slowlog = /var/log/php/www-slow.log
|
slowlog = /var/log/php/www-slow.log
|
||||||
|
|
||||||
|
; Force all PHP errors into the container log stream so kubernetes/docker can collect them.
|
||||||
|
php_admin_flag[log_errors] = on
|
||||||
|
php_admin_value[error_log] = /proc/self/fd/2
|
||||||
|
|
||||||
; Redirect worker stdout and stderr to the main error log.
|
; Redirect worker stdout and stderr to the main error log.
|
||||||
; This ensures that any `echo` or `var_dump` calls from workers are captured in the container logs.
|
; This ensures that any `echo` or `var_dump` calls from workers are captured in the container logs.
|
||||||
catch_workers_output = yes
|
catch_workers_output = yes
|
||||||
|
|
||||||
|
; Allow worker processes to write to stderr, which is essential for container logging.
|
||||||
|
decorate_workers_output = no
|
||||||
|
|
||||||
; We are logging errors to stderr in zz-hardening.ini, so we can disable the FPM access log
|
; We are logging errors to stderr in zz-hardening.ini, so we can disable the FPM access log
|
||||||
; to avoid redundant logging and improve performance. Nginx should handle access logging.
|
; to avoid redundant logging and improve performance. Nginx should handle access logging.
|
||||||
; access.log = /var/log/php/www-access.log
|
; access.log = /var/log/php/www-access.log
|
||||||
|
|||||||
7
php-fpm/conf.d/40-opcache.ini
Normal file
7
php-fpm/conf.d/40-opcache.ini
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
opcache.enable=1
|
||||||
|
opcache.memory_consumption=128
|
||||||
|
opcache.interned_strings_buffer=8
|
||||||
|
opcache.max_accelerated_files=10000
|
||||||
|
opcache.revalidate_freq=2
|
||||||
|
opcache.fast_shutdown=1
|
||||||
|
opcache.enable_file_override=0
|
||||||
5
php-fpm/conf.d/90-runtime.ini
Normal file
5
php-fpm/conf.d/90-runtime.ini
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
expose_php=Off
|
||||||
|
log_errors=On
|
||||||
|
error_log=/proc/self/fd/2
|
||||||
|
display_errors=Off
|
||||||
|
display_startup_errors=Off
|
||||||
1
php-fpm/conf.d/99-force-debug.ini
Normal file
1
php-fpm/conf.d/99-force-debug.ini
Normal file
@@ -0,0 +1 @@
|
|||||||
|
auto_prepend_file=/usr/local/etc/php/force-debug.php
|
||||||
@@ -1,12 +1,19 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -euo pipefail
|
set -eu
|
||||||
|
|
||||||
: ${CHOWN_ON_START:=}
|
PHP_ENTRYPOINT="/usr/local/bin/docker-php-entrypoint"
|
||||||
if [ "${CHOWN_ON_START}" = "1" ] || [ "${CHOWN_ON_START}" = "true" ]; then
|
if [ ! -x "${PHP_ENTRYPOINT}" ]; then
|
||||||
echo "[entrypoint] CHOWN_ON_START enabled — fixing ownership of /var/www/html"
|
echo "[entrypoint] Missing ${PHP_ENTRYPOINT}" >&2
|
||||||
if [ -d /var/www/html ]; then
|
exit 1
|
||||||
chown -R 1000:1000 /var/www/html || true
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exec "$@"
|
case "${CHOWN_ON_START:-}" in
|
||||||
|
1|true|TRUE|yes|on)
|
||||||
|
echo "[entrypoint] CHOWN_ON_START enabled — fixing ownership of /var/www/html"
|
||||||
|
if [ -d /var/www/html ]; then
|
||||||
|
chown -R 1000:1000 /var/www/html || true
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exec "${PHP_ENTRYPOINT}" "$@"
|
||||||
|
|||||||
80
shared/php-fpm/force-debug.php
Normal file
80
shared/php-fpm/force-debug.php
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
// force-debug.php
|
||||||
|
// Automatically prepended; guarantees fatals reach container logs and optionally surfaces verbose output.
|
||||||
|
|
||||||
|
$forceDebugRaw = getenv('FORCE_DEBUG_ERRORS');
|
||||||
|
$forceDebugEnabled = false;
|
||||||
|
if ($forceDebugRaw !== false) {
|
||||||
|
$normalized = strtolower(trim($forceDebugRaw));
|
||||||
|
$forceDebugEnabled = in_array($normalized, ['1', 'true', 'yes', 'on'], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure PHP always logs to stderr even if application code tampers with settings.
|
||||||
|
ini_set('log_errors', '1');
|
||||||
|
ini_set('error_log', '/proc/self/fd/2');
|
||||||
|
|
||||||
|
// Helper to safely write to container stderr without relying on php.ini.
|
||||||
|
$forceDebugLog = static function (string $message): void {
|
||||||
|
static $stderr = null;
|
||||||
|
if ($stderr === null) {
|
||||||
|
$stderr = @fopen('php://stderr', 'ab');
|
||||||
|
}
|
||||||
|
if ($stderr) {
|
||||||
|
@fwrite($stderr, $message . PHP_EOL);
|
||||||
|
} else {
|
||||||
|
// Fallback to PHP's error_log if stderr is unavailable for any reason.
|
||||||
|
error_log($message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Capture uncaught exceptions so they can never disappear silently.
|
||||||
|
set_exception_handler(static function (Throwable $throwable) use ($forceDebugLog, $forceDebugEnabled): void {
|
||||||
|
$message = sprintf(
|
||||||
|
'[force-debug] Uncaught %s: %s in %s:%d',
|
||||||
|
get_class($throwable),
|
||||||
|
$throwable->getMessage(),
|
||||||
|
$throwable->getFile(),
|
||||||
|
$throwable->getLine()
|
||||||
|
);
|
||||||
|
$forceDebugLog($message);
|
||||||
|
$forceDebugLog($throwable->getTraceAsString());
|
||||||
|
|
||||||
|
if ($forceDebugEnabled) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo '<pre>' . htmlspecialchars($message . "\n" . $throwable->getTraceAsString(), ENT_QUOTES, 'UTF-8') . '</pre>';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Capture fatal shutdown errors (E_ERROR, E_PARSE, etc.) and surface them to stderr.
|
||||||
|
register_shutdown_function(static function () use ($forceDebugLog, $forceDebugEnabled): void {
|
||||||
|
$error = error_get_last();
|
||||||
|
if ($error === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fatalTypes = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR];
|
||||||
|
if (!in_array($error['type'], $fatalTypes, true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$message = sprintf(
|
||||||
|
'[force-debug] Fatal error (%s): %s in %s:%d',
|
||||||
|
$error['type'],
|
||||||
|
$error['message'],
|
||||||
|
$error['file'],
|
||||||
|
$error['line']
|
||||||
|
);
|
||||||
|
$forceDebugLog($message);
|
||||||
|
|
||||||
|
if ($forceDebugEnabled) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo '<pre>' . htmlspecialchars($message, ENT_QUOTES, 'UTF-8') . '</pre>';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($forceDebugEnabled) {
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
ini_set('display_errors', '1');
|
||||||
|
ini_set('display_startup_errors', '1');
|
||||||
|
$forceDebugLog('[force-debug] FORCE_DEBUG_ERRORS is enabled — stack traces will be sent to clients. Disable this in production.');
|
||||||
|
}
|
||||||
94
shared/php-fpm/healthcheck.php
Normal file
94
shared/php-fpm/healthcheck.php
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
<?php
|
||||||
|
// healthcheck.php
|
||||||
|
// Lightweight readiness probe independent of WordPress code.
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||||
|
|
||||||
|
$response = [
|
||||||
|
'status' => 'ok',
|
||||||
|
'php_version' => PHP_VERSION,
|
||||||
|
'checks' => [],
|
||||||
|
];
|
||||||
|
$httpStatus = 200;
|
||||||
|
|
||||||
|
// Filesystem/webroot probe
|
||||||
|
$webroot = '/var/www/html';
|
||||||
|
$fsStatus = [
|
||||||
|
'name' => 'filesystem',
|
||||||
|
'status' => 'ok',
|
||||||
|
'detail' => 'Webroot mounted',
|
||||||
|
];
|
||||||
|
if (!is_dir($webroot)) {
|
||||||
|
$fsStatus['status'] = 'error';
|
||||||
|
$fsStatus['detail'] = 'Missing /var/www/html mount';
|
||||||
|
$httpStatus = 503;
|
||||||
|
} elseif (!is_readable($webroot)) {
|
||||||
|
$fsStatus['status'] = 'error';
|
||||||
|
$fsStatus['detail'] = 'Webroot not readable';
|
||||||
|
$httpStatus = 503;
|
||||||
|
}
|
||||||
|
$response['checks'][] = $fsStatus;
|
||||||
|
|
||||||
|
// Database probe (optional if env vars missing)
|
||||||
|
$requiredEnv = ['WORDPRESS_DB_HOST', 'WORDPRESS_DB_USER', 'WORDPRESS_DB_PASSWORD', 'WORDPRESS_DB_NAME'];
|
||||||
|
$dbConfig = [];
|
||||||
|
$missingEnv = [];
|
||||||
|
foreach ($requiredEnv as $var) {
|
||||||
|
$value = getenv($var);
|
||||||
|
if ($value === false || $value === '') {
|
||||||
|
$missingEnv[] = $var;
|
||||||
|
} else {
|
||||||
|
$dbConfig[$var] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$dbStatus = [
|
||||||
|
'name' => 'database',
|
||||||
|
'status' => 'skipped',
|
||||||
|
'detail' => 'Database env vars missing',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (empty($missingEnv)) {
|
||||||
|
[$host, $port] = (function (string $rawHost): array {
|
||||||
|
if (preg_match('/^\[(.+)\](?::(\d+))?$/', $rawHost, $matches)) {
|
||||||
|
return [$matches[1], isset($matches[2]) ? (int) $matches[2] : 3306];
|
||||||
|
}
|
||||||
|
if (substr_count($rawHost, ':') === 1 && strpos($rawHost, '::') === false) {
|
||||||
|
[$h, $p] = explode(':', $rawHost, 2);
|
||||||
|
return [$h, (int) $p];
|
||||||
|
}
|
||||||
|
return [$rawHost, 3306];
|
||||||
|
})($dbConfig['WORDPRESS_DB_HOST']);
|
||||||
|
|
||||||
|
$mysqli = @mysqli_init();
|
||||||
|
if ($mysqli !== false) {
|
||||||
|
@mysqli_options($mysqli, MYSQLI_OPT_CONNECT_TIMEOUT, 3);
|
||||||
|
$connected = @$mysqli->real_connect(
|
||||||
|
$host,
|
||||||
|
$dbConfig['WORDPRESS_DB_USER'],
|
||||||
|
$dbConfig['WORDPRESS_DB_PASSWORD'],
|
||||||
|
$dbConfig['WORDPRESS_DB_NAME'],
|
||||||
|
$port
|
||||||
|
);
|
||||||
|
if ($connected) {
|
||||||
|
$dbStatus['status'] = 'ok';
|
||||||
|
$dbStatus['detail'] = 'DB connection OK';
|
||||||
|
$mysqli->close();
|
||||||
|
} else {
|
||||||
|
$dbStatus['status'] = 'error';
|
||||||
|
$dbStatus['detail'] = sprintf('DB connect failed: %s', $mysqli->connect_error ?? 'unknown error');
|
||||||
|
$httpStatus = 503;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$dbStatus['status'] = 'error';
|
||||||
|
$dbStatus['detail'] = 'Unable to init mysqli';
|
||||||
|
$httpStatus = 503;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$dbStatus['detail'] = 'Missing env: ' . implode(', ', $missingEnv);
|
||||||
|
}
|
||||||
|
$response['checks'][] = $dbStatus;
|
||||||
|
|
||||||
|
http_response_code($httpStatus);
|
||||||
|
echo json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
|
||||||
Reference in New Issue
Block a user