From 13b6b60ac4fd50cef2d7074be2776cde0bbe8cec Mon Sep 17 00:00:00 2001 From: tomwalder Date: Wed, 19 Feb 2025 16:02:54 +0000 Subject: [PATCH 1/3] WIP FrankenPHP support --- Dockerfile | 56 +++++--- README.md | 44 +++--- manifest/etc/caddy/Caddyfile | 80 +++++++++++ manifest/runphp-foundation/admin/admin.php | 8 +- .../runphp-development-500-core.ini | 15 ++ .../runphp-development-700-opcache.ini | 127 ++++++++++++++++ .../runphp-development-900-xdebug.ini | 12 ++ .../php-conf.d/runphp-production-500-misc.ini | 15 ++ .../runphp-production-700-opcache.ini | 136 ++++++++++++++++++ .../runphp-foundation/src/Google/Metadata.php | 6 +- .../src/Google/ReportedErrorHandler.php | 22 +-- manifest/runphp/index.php | 6 +- 12 files changed, 476 insertions(+), 51 deletions(-) create mode 100644 manifest/etc/caddy/Caddyfile create mode 100644 manifest/runphp-foundation/etc/development/php-conf.d/runphp-development-500-core.ini create mode 100644 manifest/runphp-foundation/etc/development/php-conf.d/runphp-development-700-opcache.ini create mode 100644 manifest/runphp-foundation/etc/development/php-conf.d/runphp-development-900-xdebug.ini create mode 100644 manifest/runphp-foundation/etc/production/php-conf.d/runphp-production-500-misc.ini create mode 100644 manifest/runphp-foundation/etc/production/php-conf.d/runphp-production-700-opcache.ini diff --git a/Dockerfile b/Dockerfile index c129ef6..7d6cc6a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,23 +1,52 @@ ARG TAG_NAME="dev-master" -ARG BUILD_PHP_VER="8.4.2" -ARG BUILD_FOUNDATION_SUFFIX="v0.20.0" +ARG BUILD_FRANKENPHP_VER="1.4.2" +ARG BUILD_PHP_VER="8.4.3" +ARG BUILD_FOUNDATION_SUFFIX="v0.22.0" ################################################################################################################ -FROM fluentthinking/runphp-foundation:${BUILD_PHP_VER}-${BUILD_FOUNDATION_SUFFIX} +FROM fluentthinking/runphp-foundation:${BUILD_FOUNDATION_SUFFIX}-frankenphp${BUILD_FRANKENPHP_VER}-php${BUILD_PHP_VER} ARG TAG_NAME +# Let's get up to date +RUN apt-get update && apt-get -y upgrade + +# Setup the XHprof output dir, install additional libs +ENV XHPROF_OUTPUT="/tmp/xhprof" +# chown www-data:www-data /tmp/xhprof && \ +RUN mkdir -p /tmp/xhprof && \ + apt-get install -y graphviz && \ + apt-get clean + + + # Install our code, then switch from foundation to our runphp site COPY ./manifest / -RUN a2dissite 001-runphp-foundation && a2ensite 002-runphp && a2disconf other-vhosts-access-log +#RUN a2dissite 001-runphp-foundation && a2ensite 002-runphp && a2disconf other-vhosts-access-log + + +# TODO log format update \ +# NOT for access logs (Cloud Run will take care of that) +# BUT for "other" logs (e.g. startup logs/messages) +# https://caddy.community/t/google-cloud-structured-logging-format/12678/8 + + +# Install profile viewer +RUN curl https://github.com/thinkfluent/xhprof/archive/master.tar.gz --silent --location --output /tmp/xhprof.tgz && \ + tar xfz /tmp/xhprof.tgz -C /tmp/ && \ + mv /tmp/xhprof-master/* /runphp-foundation/admin/xhprof && \ + rm -rf /tmp/xhprof.tgz # So we can handle signals properly (Cloud Run will send a SIGTERM) # Re-map SIGTERM to SIGWINCH for graceful apache shutdown # https://cloud.google.com/blog/topics/developers-practitioners/graceful-shutdowns-cloud-run-deep-dive -ENTRYPOINT ["/usr/bin/dumb-init", "--rewrite", "15:28", "docker-runphp-entrypoint"] +# ENTRYPOINT ["/usr/bin/dumb-init", "--rewrite", "15:28", "docker-runphp-entrypoint"] + +# Use our custom entrypoint (which does some dev vs prod config changes & extracts Google Cloud context) +ENTRYPOINT ["docker-runphp-entrypoint"] -# Run apache on startup -CMD [ "apache2-foreground" ] +# Run frankenphp on startup +CMD ["--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"] # Do some baseline setup for runphp ENV RUNPHP_MODE="production" @@ -31,18 +60,7 @@ ENV RUNPHP_PRELOAD_STRATEGY="src" # PHP Preloading - "include" or "compile" ENV RUNPHP_PRELOAD_ACTION="include" -# Setup the XHprof output dir, install additional libs -ENV XHPROF_OUTPUT="/tmp/xhprof" -RUN mkdir -p /tmp/xhprof && \ - chown www-data:www-data /tmp/xhprof && \ - apt-get install -y graphviz && \ - apt-get clean - -# Install profile viewer -RUN curl https://github.com/thinkfluent/xhprof/archive/master.tar.gz --silent --location --output /tmp/xhprof.tgz && \ - tar xfz /tmp/xhprof.tgz -C /tmp/ && \ - mv /tmp/xhprof-master/* /runphp-foundation/admin/xhprof && \ - rm -rf /tmp/xhprof.tgz +EXPOSE 8080 # Optional # ENV RUNPHP_SKIP_COMPOSER_EXTENSION_CHECKS="true" diff --git a/README.md b/README.md index b46d9b6..99717e7 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,22 @@ The `thinkfluent/runphp` toolkit enables rapid application development and serve Docker images can be found here: https://hub.docker.com/r/fluentthinking/runphp -| PHP Version | Latest Image | -|-------------|------------------------------------------------| -| PHP 8.4.2 | `fluentthinking/runphp:8.4.2-v0.21.0` `latest` | -| PHP 8.3.15 | `fluentthinking/runphp:8.3.15-v0.21.0` | -| PHP 8.2.27 | `fluentthinking/runphp:8.2.27-v0.21.0` | -| PHP 8.1.31 | `fluentthinking/runphp:8.1.31-v0.21.0` | -| PHP 8.0.30 | `fluentthinking/runphp:8.0.30-v0.21.0` | -| PHP 7.4.33 | `fluentthinking/runphp:7.4.33-v0.21.0` | + +| PHP Version | Latest Image | +|-------------|----------------------------------------------------------| +| PHP 8.4.4 | `fluentthinking/runphp:v0.23.0-frankenphp1.4.3-php8.4.4` `frankenphp-latest` | +| PHP 8.3.17 | `fluentthinking/runphp:v0.23.0-frankenphp1.4.3-php8.3.17` | +| PHP 8.2.27 | `fluentthinking/runphp:v0.23.0-frankenphp1.4.3-php8.2.27` | + + +| PHP Version | Latest Image | +|------------|-------------------------------------| +| PHP 8.4.4 | `fluentthinking/runphp:8.4.4-v0.23.0` `latest` | +| PHP 8.3.17 | `fluentthinking/runphp:8.3.17-v0.23.0` | +| PHP 8.2.27 | `fluentthinking/runphp:8.2.27-v0.23.0` | +| PHP 8.1.31 | `fluentthinking/runphp:8.1.31-v0.23.0` | +| PHP 8.0.30 | `fluentthinking/runphp:8.0.30-v0.23.0` | +| PHP 7.4.33 | `fluentthinking/runphp:7.4.33-v0.23.0` | #### Some Benefits of Cloud Run with runphp @@ -24,15 +32,14 @@ Docker images can be found here: https://hub.docker.com/r/fluentthinking/runphp ### The Vanilla Build -This should start a local instance of the default runphp image. +Start a local instance of the default runphp image. + +Localhost - http://localhost:8080 http://localhost:8080/_runphp +With OrbStack (HTTPS proxy built in) - https://runphp.orb.local https://runphp.orb.local/_runphp ```bash -docker run --rm -e "RUNPHP_MODE=development" -e "PORT=80" -p 8080:80 fluentthinking/runphp:latest +docker run --rm --name runphp -e "RUNPHP_MODE=development" -p 8080:8080 runphp:frankendev ``` -You should be able to access the default home page and admin interfaces as follows: -* http://localhost:8080 -* http://localhost:8080/_runphp - ### Building Your Own Application You will most likely need a `Dockerfile`; here is a simple example, which uses the baked in apache configs. @@ -70,6 +77,7 @@ gcloud run deploy \ --image=eu.gcr.io//myapp:latest \ --platform managed \ --allow-unauthenticated \ + --use-http2 \ --set-env-vars "RUNPHP_MODE=development" \ --region europe-west1 \ --project @@ -173,8 +181,8 @@ If you need to build your own base images (this repo)... ```bash docker build \ --platform linux/amd64 \ - --build-arg TAG_NAME=dev \ - --build-arg BUILD_PHP_VER=2 \ - --build-arg BUILD_FOUNDATION_SUFFIX=v0.20.0 \ - -t runphp:dev . + --build-arg TAG_NAME=frankendev \ + --build-arg BUILD_PHP_VER=8.4.2 \ + --build-arg BUILD_FOUNDATION_SUFFIX=v0.21.0-frankenphp \ + -t runphp:frankendev . ``` \ No newline at end of file diff --git a/manifest/etc/caddy/Caddyfile b/manifest/etc/caddy/Caddyfile new file mode 100644 index 0000000..f7ae06b --- /dev/null +++ b/manifest/etc/caddy/Caddyfile @@ -0,0 +1,80 @@ +{ + {$CADDY_GLOBAL_OPTIONS} + + # For Cloud Run, we do NOT terminate TLS, but want to offer end-to-end HTTP/2 cleartext + # https://cloud.google.com/run/docs/configuring/http2 + servers { + protocols h1 h2c + } + + http_port {$PORT:8080} + + # Disable any HTTPS stuff for Cloud Run + auto_https off + auto_https disable_redirects + + frankenphp { + #worker /path/to/your/worker.php + {$FRANKENPHP_CONFIG} + } +} + +{$CADDY_EXTRA_CONFIG} + +# We are not terminating TLS, so include the "http://" prefix +# A blank server name will match all requests, which we want to Cloud Run and named services, custom domains, etc. +http://{$SERVER_NAME:}:{$PORT:8080} { + #log { + # # Redact the authorization query parameter that can be set by Mercure + # format filter { + # request>uri query { + # replace authorization REDACTED + # } + # } + #} + + # Application public source + root * {$RUNPHP_DOC_ROOT} + + # Compression - Zstandard, brotli, gzip + encode zstd br gzip + + # Uncomment the following lines to enable Mercure and Vulcain modules + #mercure { + # # Transport to use (default to Bolt) + # transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db} + # # Publisher JWT key + # publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG} + # # Subscriber JWT key + # subscriber_jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_SUBSCRIBER_JWT_ALG} + # # Allow anonymous subscribers (double-check that it's what you want) + # anonymous + # # Enable the subscription API (double-check that it's what you want) + # subscriptions + # # Extra directives + # {$MERCURE_EXTRA_DIRECTIVES} + #} + #vulcain + + {$CADDY_SERVER_EXTRA_DIRECTIVES} + + # We need to customise the `php_server` directive (difference index) + route { + # Add trailing slash for directory requests + @canonicalPath { + file {path}/index.php + not path */ + } + redir @canonicalPath {path}/ 308 + # If the requested file does not exist, try index files + @indexFiles file { + try_files {path} {path}/{$RUNPHP_INDEX_FILE} {$RUNPHP_INDEX_FILE} + split_path .php + } + rewrite @indexFiles {http.matchers.file.relative} + # FrankenPHP! + @phpFiles path *.php + php @phpFiles + file_server + } +} diff --git a/manifest/runphp-foundation/admin/admin.php b/manifest/runphp-foundation/admin/admin.php index 08743f9..5ebb3ce 100644 --- a/manifest/runphp-foundation/admin/admin.php +++ b/manifest/runphp-foundation/admin/admin.php @@ -71,7 +71,7 @@ mode: getMode(); ?> foundation: getFoundationVersion(); ?> runphp: getVersion(); ?> - PHP: env()['PHP_VERSION']; ?> +
diff --git a/manifest/runphp-foundation/etc/development/php-conf.d/runphp-development-500-core.ini b/manifest/runphp-foundation/etc/development/php-conf.d/runphp-development-500-core.ini new file mode 100644 index 0000000..7ca44f4 --- /dev/null +++ b/manifest/runphp-foundation/etc/development/php-conf.d/runphp-development-500-core.ini @@ -0,0 +1,15 @@ +; Do not show the PHP version +expose_php = On + +; Logs to STDERR please +error_log = /dev/stderr +log_errors = 1 + +; Production logging +error_reporting = E_ALL +display_errors = 1 +display_startup_errors = 1 + +; Production assert behaviour +zend.assertions = 1 ; generate and execute code (development mode) +assert.exception = 1 ; throw an exception when assertion fails. diff --git a/manifest/runphp-foundation/etc/development/php-conf.d/runphp-development-700-opcache.ini b/manifest/runphp-foundation/etc/development/php-conf.d/runphp-development-700-opcache.ini new file mode 100644 index 0000000..747ea6f --- /dev/null +++ b/manifest/runphp-foundation/etc/development/php-conf.d/runphp-development-700-opcache.ini @@ -0,0 +1,127 @@ +[opcache] +; Determines if Zend OPCache is enabled +opcache.enable=1 + +; Determines if Zend OPCache is enabled for the CLI version of PHP +opcache.enable_cli=0 + +; The OPcache shared memory storage size. +opcache.memory_consumption=128 + +; The amount of memory for interned strings in Mbytes. +opcache.interned_strings_buffer=8 + +; The maximum number of keys (scripts) in the OPcache hash table. +; Only numbers between 200 and 100000 are allowed. +opcache.max_accelerated_files=10000 + +; The maximum percentage of "wasted" memory until a restart is scheduled. +;opcache.max_wasted_percentage=5 + +; When this directive is enabled, the OPcache appends the current working +; directory to the script key, thus eliminating possible collisions between +; files with the same name (basename). Disabling the directive improves +; performance, but may break existing applications. +;opcache.use_cwd=1 + +; When disabled, you must reset the OPcache manually or restart the +; webserver for changes to the filesystem to take effect. +opcache.validate_timestamps=1 + +; How often (in seconds) to check file timestamps for changes to the shared +; memory storage allocation. ("1" means validate once per second, but only +; once per request. "0" means always validate) +opcache.revalidate_freq=0 + +; Enables or disables file search in include_path optimization +;opcache.revalidate_path=0 + +; If disabled, all PHPDoc comments are dropped from the code to reduce the +; size of the optimized code. +opcache.save_comments=1 + +; If enabled, a fast shutdown sequence is used for the accelerated code +; Depending on the used Memory Manager this may cause some incompatibilities. +opcache.fast_shutdown=1 + +; Allow file existence override (file_exists, etc.) performance feature. +opcache.enable_file_override=1 + +; A bitmask, where each bit enables or disables the appropriate OPcache +; passes +;opcache.optimization_level=0xffffffff + +;opcache.inherited_hack=1 +;opcache.dups_fix=0 + +; The location of the OPcache blacklist file (wildcards allowed). +; Each OPcache blacklist file is a text file that holds the names of files +; that should not be accelerated. The file format is to add each filename +; to a new line. The filename may be a full path or just a file prefix +; (i.e., /var/www/x blacklists all the files and directories in /var/www +; that start with 'x'). Line starting with a ; are ignored (comments). +;opcache.blacklist_filename= + +; Allows exclusion of large files from being cached. By default all files +; are cached. +;opcache.max_file_size=0 + +; Check the cache checksum each N requests. +; The default value of "0" means that the checks are disabled. +;opcache.consistency_checks=0 + +; How long to wait (in seconds) for a scheduled restart to begin if the cache +; is not being accessed. +;opcache.force_restart_timeout=180 + +; OPcache error_log file name. Empty string assumes "stderr". +;opcache.error_log= + +; All OPcache errors go to the Web server log. +; By default, only fatal errors (level 0) or errors (level 1) are logged. +; You can also enable warnings (level 2), info messages (level 3) or +; debug messages (level 4). +;opcache.log_verbosity_level=1 + +; Preferred Shared Memory back-end. Leave empty and let the system decide. +;opcache.preferred_memory_model= + +; Protect the shared memory from unexpected writing during script execution. +; Useful for internal debugging only. +;opcache.protect_memory=0 + +; Allows calling OPcache API functions only from PHP scripts which path is +; started from specified string. The default "" means no restriction +;opcache.restrict_api= + +; Mapping base of shared memory segments (for Windows only). All the PHP +; processes have to map shared memory into the same address space. This +; directive allows to manually fix the "Unable to reattach to base address" +; errors. +;opcache.mmap_base= + +; Enables and sets the second level cache directory. +; It should improve performance when SHM memory is full, at server restart or +; SHM reset. The default "" disables file based caching. +;opcache.file_cache= + +; Enables or disables opcode caching in shared memory. +;opcache.file_cache_only=0 + +; Enables or disables checksum validation when script loaded from file cache. +;opcache.file_cache_consistency_checks=1 + +; Implies opcache.file_cache_only=1 for a certain process that failed to +; reattach to the shared memory (for Windows only). Explicitly enabled file +; cache is required. +;opcache.file_cache_fallback=1 + +; Enables or disables copying of PHP code (text segment) into HUGE PAGES. +; This should improve performance, but requires appropriate OS configuration. +;opcache.huge_code_pages=1 + +; Validate cached file permissions. +;opcache.validate_permission=0 + +; Prevent name collisions in chroot'ed environment. +;opcache.validate_root=0 \ No newline at end of file diff --git a/manifest/runphp-foundation/etc/development/php-conf.d/runphp-development-900-xdebug.ini b/manifest/runphp-foundation/etc/development/php-conf.d/runphp-development-900-xdebug.ini new file mode 100644 index 0000000..f9d0770 --- /dev/null +++ b/manifest/runphp-foundation/etc/development/php-conf.d/runphp-development-900-xdebug.ini @@ -0,0 +1,12 @@ +zend_extension=xdebug.so + +;xdebug.remote_autostart=0 +;xdebug.remote_enable=1 +;xdebug.default_enable=0 +;xdebug.remote_host=host.docker.internal +;xdebug.remote_host=192.168.254.1 +;xdebug.remote_port=9000 +;xdebug.remote_connect_back=0 +;xdebug.profiler_enable=0 +;xdebug.remote_log="/tmp/xdebug.log" +;xdebug.idekey = PHPSTORM \ No newline at end of file diff --git a/manifest/runphp-foundation/etc/production/php-conf.d/runphp-production-500-misc.ini b/manifest/runphp-foundation/etc/production/php-conf.d/runphp-production-500-misc.ini new file mode 100644 index 0000000..2719db0 --- /dev/null +++ b/manifest/runphp-foundation/etc/production/php-conf.d/runphp-production-500-misc.ini @@ -0,0 +1,15 @@ +; Do not show the PHP version +expose_php = Off + +; Logs to STDERR please +error_log = /dev/stderr +log_errors = 1 + +; Production logging +error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT +display_errors = 0 +display_startup_errors = 0 + +; Production assert behaviour +zend.assertions = -1 ; -1: do not generate code (production mode) +assert.exception = 0 ; do not throw exceptions when assertions fail diff --git a/manifest/runphp-foundation/etc/production/php-conf.d/runphp-production-700-opcache.ini b/manifest/runphp-foundation/etc/production/php-conf.d/runphp-production-700-opcache.ini new file mode 100644 index 0000000..9628b75 --- /dev/null +++ b/manifest/runphp-foundation/etc/production/php-conf.d/runphp-production-700-opcache.ini @@ -0,0 +1,136 @@ +[opcache] +; Determines if Zend OPCache is enabled +opcache.enable=1 + +; Determines if Zend OPCache is enabled for the CLI version of PHP +opcache.enable_cli=0 + +; The OPcache shared memory storage size. +opcache.memory_consumption=128 + +; The amount of memory for interned strings in Mbytes. +opcache.interned_strings_buffer=8 + +; The maximum number of keys (scripts) in the OPcache hash table. +; Only numbers between 200 and 100000 are allowed. +opcache.max_accelerated_files=10000 + +; The maximum percentage of "wasted" memory until a restart is scheduled. +;opcache.max_wasted_percentage=5 + +; When this directive is enabled, the OPcache appends the current working +; directory to the script key, thus eliminating possible collisions between +; files with the same name (basename). Disabling the directive improves +; performance, but may break existing applications. +;opcache.use_cwd=1 + +; When disabled, you must reset the OPcache manually or restart the +; webserver for changes to the filesystem to take effect. +; +; Disabled in production, as files will not change +opcache.validate_timestamps=0 + +; How often (in seconds) to check file timestamps for changes to the shared +; memory storage allocation. ("1" means validate once per second, but only +; once per request. "0" means always validate) +; +; Ignored when "opcache.validate_timestamps" is disabled (production) +;opcache.revalidate_freq=60 + +; Enables or disables file search in include_path optimization +;opcache.revalidate_path=0 + +; If disabled, all PHPDoc comments are dropped from the code to reduce the +; size of the optimized code. +opcache.save_comments=1 + +; If enabled, a fast shutdown sequence is used for the accelerated code +; Depending on the used Memory Manager this may cause some incompatibilities. +opcache.fast_shutdown=1 + +; Allow file existence override (file_exists, etc.) performance feature. +; This may increase performance in applications that check the existence +; and readability of PHP scripts, but risks returning stale data +; if "opcache.validate_timestamps" is disabled (which it is in production) +; +; Deemed low risk in production because files will not change +opcache.enable_file_override=1 + +; A bitmask, where each bit enables or disables the appropriate OPcache +; passes +;opcache.optimization_level=0xffffffff + +;opcache.inherited_hack=1 +;opcache.dups_fix=0 + +; The location of the OPcache blacklist file (wildcards allowed). +; Each OPcache blacklist file is a text file that holds the names of files +; that should not be accelerated. The file format is to add each filename +; to a new line. The filename may be a full path or just a file prefix +; (i.e., /var/www/x blacklists all the files and directories in /var/www +; that start with 'x'). Line starting with a ; are ignored (comments). +;opcache.blacklist_filename= + +; Allows exclusion of large files from being cached. By default all files +; are cached. +;opcache.max_file_size=0 + +; Check the cache checksum each N requests. +; The default value of "0" means that the checks are disabled. +;opcache.consistency_checks=0 + +; How long to wait (in seconds) for a scheduled restart to begin if the cache +; is not being accessed. +;opcache.force_restart_timeout=180 + +; OPcache error_log file name. Empty string assumes "stderr". +;opcache.error_log= + +; All OPcache errors go to the Web server log. +; By default, only fatal errors (level 0) or errors (level 1) are logged. +; You can also enable warnings (level 2), info messages (level 3) or +; debug messages (level 4). +;opcache.log_verbosity_level=1 + +; Preferred Shared Memory back-end. Leave empty and let the system decide. +;opcache.preferred_memory_model= + +; Protect the shared memory from unexpected writing during script execution. +; Useful for internal debugging only. +;opcache.protect_memory=0 + +; Allows calling OPcache API functions only from PHP scripts which path is +; started from specified string. The default "" means no restriction +;opcache.restrict_api= + +; Mapping base of shared memory segments (for Windows only). All the PHP +; processes have to map shared memory into the same address space. This +; directive allows to manually fix the "Unable to reattach to base address" +; errors. +;opcache.mmap_base= + +; Enables and sets the second level cache directory. +; It should improve performance when SHM memory is full, at server restart or +; SHM reset. The default "" disables file based caching. +;opcache.file_cache= + +; Enables or disables opcode caching in shared memory. +;opcache.file_cache_only=0 + +; Enables or disables checksum validation when script loaded from file cache. +;opcache.file_cache_consistency_checks=1 + +; Implies opcache.file_cache_only=1 for a certain process that failed to +; reattach to the shared memory (for Windows only). Explicitly enabled file +; cache is required. +;opcache.file_cache_fallback=1 + +; Enables or disables copying of PHP code (text segment) into HUGE PAGES. +; This should improve performance, but requires appropriate OS configuration. +;opcache.huge_code_pages=1 + +; Validate cached file permissions. +;opcache.validate_permission=0 + +; Prevent name collisions in chroot'ed environment. +;opcache.validate_root=0 \ No newline at end of file diff --git a/manifest/runphp-foundation/src/Google/Metadata.php b/manifest/runphp-foundation/src/Google/Metadata.php index 56b2998..e4f4d8e 100644 --- a/manifest/runphp-foundation/src/Google/Metadata.php +++ b/manifest/runphp-foundation/src/Google/Metadata.php @@ -1,5 +1,7 @@ handleError( E_RECOVERABLE_ERROR, @@ -56,7 +58,7 @@ public function handleException(\Throwable $obj_thrown) * The following types cannot be caught by set_error_handler(), so deal with them on shutdown * https://www.php.net/manual/en/function.set-error-handler */ - public function handleShutdown() + public function handleShutdown(): void { $arr_error = error_get_last(); $int_types = E_ERROR | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING; @@ -80,10 +82,10 @@ public function handleShutdown() public function handleError( int $int_errno, string $str_error, - string $str_file = null, - int $int_line = null, + ?string $str_file = null, + ?int $int_line = null, array $arr_context = [] - ) { + ): void { // Respect current error_reporting() level if (0 == ($int_errno & error_reporting())) { return; @@ -93,12 +95,14 @@ public function handleError( E_USER_WARNING => 'WARNING', E_NOTICE => 'NOTICE', E_USER_NOTICE => 'NOTICE', - E_STRICT => 'INFO', E_DEPRECATED => 'INFO', E_USER_DEPRECATED => 'INFO', E_USER_ERROR => 'ERROR', E_RECOVERABLE_ERROR => 'ERROR', ]; + if (PHP_VERSION_ID < 80400) { + $arr_error_map[2048] = 'INFO'; // E_STRICT + } static $arr_env = []; if(empty($arr_env)) { $arr_env = Runtime::get()->env(); @@ -151,7 +155,7 @@ public function handleError( * @param array|null $arr_trace * @return string */ - protected function getFunctionNameForReport(array $arr_trace = null): string + protected function getFunctionNameForReport(?array $arr_trace = null): string { if (null === $arr_trace) { return ''; diff --git a/manifest/runphp/index.php b/manifest/runphp/index.php index 8749f3f..52ec3e5 100644 --- a/manifest/runphp/index.php +++ b/manifest/runphp/index.php @@ -25,9 +25,13 @@
From ae9d334ff699a45cf916667d7f7911371f9bb262 Mon Sep 17 00:00:00 2001 From: tomwalder Date: Sun, 21 Sep 2025 16:43:17 +0100 Subject: [PATCH 2/3] Move to single stage FrankenPHP build, general tidy --- .editorconfig | 2 + Dockerfile | 51 +++--- README.md | 35 +++-- .../apache2/conf-available/000-logging.conf | 20 --- .../apache2/sites-available/002-runphp.conf | 18 --- .../apache2/sites-available/003-xhprof.conf | 4 - manifest/etc/caddy/Caddyfile | 147 ++++++++++-------- manifest/etc/caddy/README.md | 4 + manifest/runphp-foundation/admin/admin.php | 12 +- .../runphp-development-500-core.ini | 6 +- .../runphp-foundation/runtime/prepend.php | 13 +- .../src/Google/ReportedErrorHandler.php | 46 ++++-- manifest/runphp-foundation/src/Runtime.php | 8 +- manifest/runphp/index.php | 2 +- .../usr/local/bin/docker-runphp-entrypoint | 10 +- .../usr/local/bin/runphp-set-mode-development | 4 +- 16 files changed, 199 insertions(+), 183 deletions(-) create mode 100644 .editorconfig delete mode 100644 manifest/etc/apache2/conf-available/000-logging.conf delete mode 100644 manifest/etc/apache2/sites-available/002-runphp.conf delete mode 100644 manifest/etc/apache2/sites-available/003-xhprof.conf create mode 100644 manifest/etc/caddy/README.md diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..09cf0b7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,2 @@ +[Caddyfile] +indent_style = tab \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 7d6cc6a..f5483d4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,31 @@ +# Default build versions +ARG BUILD_FRANKENPHP_VER="1.9.1" +ARG BUILD_PHP_VER="8.4.12" ARG TAG_NAME="dev-master" -ARG BUILD_FRANKENPHP_VER="1.4.2" -ARG BUILD_PHP_VER="8.4.3" -ARG BUILD_FOUNDATION_SUFFIX="v0.22.0" +################################################################################################################ +FROM dunglas/frankenphp:${BUILD_FRANKENPHP_VER}-php${BUILD_PHP_VER} as foundation +# Extensions we'd like to add by default +ARG PHP_EXT_ESSENTIAL="bcmath opcache mysqli pdo_mysql bz2 soap sockets zip gd intl yaml apcu protobuf memcached redis xhprof grpc" +ARG TAG_NAME + +# Workaround for noisy pecl E_STRICT +RUN sed -i '1 s/^.*$/ \ --region europe-west1 \ --project ``` +`--use-http2` is recommended for FrankenPHP based images ## Components runphp has the following key areas of concern: diff --git a/manifest/etc/apache2/conf-available/000-logging.conf b/manifest/etc/apache2/conf-available/000-logging.conf deleted file mode 100644 index 856805b..0000000 --- a/manifest/etc/apache2/conf-available/000-logging.conf +++ /dev/null @@ -1,20 +0,0 @@ -# Custom Log Formats -LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\" latency_ms %{ms}T php_mem \"%{php_mem_peak}n\" php_mem_mb \"%{php_mem_peak_mb}n\"" nongoogle -LogFormat "{ \"timestamp\": \"%{%Y-%m-%dT%H:%M:%S%z}t\", \"severity\": \"%>s\", \"latency\": %{ms}T, \"phpMemory\": \"%{php_mem_peak}n\", \"httpRequest\": { \"requestMethod\" :\"%m\", \"requestUrl\": \"%U%q\", \"status\": %>s, \"responseSize\": %B, \"userAgent\": \"%{User-Agent}i\", \"remoteIp\": \"%a\", \"referrer\": \"%{Referer}i\", \"latency\": \"%{ms}T\", \"protocol\": \"%{X-Forwarded-Proto}i\" }}" stackdriverhttp -LogFormat "{ \"timestamp\": \"%{%Y-%m-%dT%H:%M:%S%z}t\", \"severity\": \"INFO\", \"latency\": %{ms}T, \"phpMemory\": \"%{php_mem_peak}n\", \"phpMemoryMb\": \"%{php_mem_peak_mb}n\", \"logging.googleapis.com/trace\": \"%{gcp_trace_context}n\" }" phpmemlatency - -# In containers, logs go to STDOUT/STDERR -ErrorLog /dev/stderr - -# Here, we use different logging formats for Google Cloud & local -SetEnvIfExpr "env('RUNPHP_APACHE_STACKDRIVER_LOGS') == 'yes'" stackdriverlogs - -# When running in Cloud Run, "HTTP/access" logs are produced by GCP/load balacner, so we only log _additional_ data -CustomLog /dev/stdout phpmemlatency env=stackdriverlogs -#CustomLog /dev/stdout stackdriverhttp env=stackdriverlogs - -# And for non-GCP environments (e.g. local dev) we push out a combined apache log, plus some additional fields -CustomLog /dev/stdout nongoogle env=!stackdriverlogs - -# This is regular apache logging, no context awareness -# CustomLog /dev/stdout combined \ No newline at end of file diff --git a/manifest/etc/apache2/sites-available/002-runphp.conf b/manifest/etc/apache2/sites-available/002-runphp.conf deleted file mode 100644 index e2053f1..0000000 --- a/manifest/etc/apache2/sites-available/002-runphp.conf +++ /dev/null @@ -1,18 +0,0 @@ -ServerName 127.0.0.1 - -# Our default VHOST config - - DocumentRoot ${RUNPHP_DOC_ROOT} - - - Require all granted - DirectoryIndex ${RUNPHP_INDEX_FILE} - RewriteEngine on - # Uncomment the following line to allow /server-status requests - # RewriteCond %{REQUEST_URI} !=/server-status - # If the file or directory exists, no not rewrite - RewriteCond %{REQUEST_FILENAME} !-f - RewriteCond %{REQUEST_FILENAME} !-d - RewriteRule . ${RUNPHP_INDEX_FILE} [L] - - diff --git a/manifest/etc/apache2/sites-available/003-xhprof.conf b/manifest/etc/apache2/sites-available/003-xhprof.conf deleted file mode 100644 index f7a161e..0000000 --- a/manifest/etc/apache2/sites-available/003-xhprof.conf +++ /dev/null @@ -1,4 +0,0 @@ -Alias "/xhprof" "/runphp-foundation/admin/xhprof/xhprof_html" - - Require all granted - \ No newline at end of file diff --git a/manifest/etc/caddy/Caddyfile b/manifest/etc/caddy/Caddyfile index f7ae06b..a3e3d04 100644 --- a/manifest/etc/caddy/Caddyfile +++ b/manifest/etc/caddy/Caddyfile @@ -1,80 +1,101 @@ { - {$CADDY_GLOBAL_OPTIONS} + {$CADDY_GLOBAL_OPTIONS} - # For Cloud Run, we do NOT terminate TLS, but want to offer end-to-end HTTP/2 cleartext - # https://cloud.google.com/run/docs/configuring/http2 - servers { - protocols h1 h2c - } + # Disable any HTTPS stuff for Cloud Run + auto_https off - http_port {$PORT:8080} + # Turn off admin endpoint in production + admin off + persist_config off - # Disable any HTTPS stuff for Cloud Run - auto_https off - auto_https disable_redirects + # For Cloud Run, we do NOT terminate TLS, but want to offer end-to-end HTTP/2 cleartext + # https://cloud.google.com/run/docs/configuring/http2 + servers { + protocols h1 h2c + } - frankenphp { - #worker /path/to/your/worker.php - {$FRANKENPHP_CONFIG} - } + http_port {$PORT:8080} + + frankenphp { + #worker /path/to/your/worker.php + {$FRANKENPHP_CONFIG} + } + + log default { + format json { + message_key message + level_key severity + time_key timestamp + name_key channel + caller_key function + stacktrace_key stack_trace + time_format iso8601 + duration_format "ms" + # GCP seems to ingest these OK i.e. WARN seems OK, not WARNING + level_format "upper" + } + } } {$CADDY_EXTRA_CONFIG} -# We are not terminating TLS, so include the "http://" prefix +# We are not terminating TLS on Cloud Run, so include the "http://" prefix # A blank server name will match all requests, which we want to Cloud Run and named services, custom domains, etc. http://{$SERVER_NAME:}:{$PORT:8080} { - #log { - # # Redact the authorization query parameter that can be set by Mercure - # format filter { - # request>uri query { - # replace authorization REDACTED - # } - # } - #} + #log { + # # Redact the authorization query parameter that can be set by Mercure + # format filter { + # request>uri query { + # replace authorization REDACTED + # } + # } + #} + + # Application public source + root * {$RUNPHP_DOC_ROOT} - # Application public source - root * {$RUNPHP_DOC_ROOT} + # Compression - Zstandard, brotli, gzip + encode zstd br gzip - # Compression - Zstandard, brotli, gzip - encode zstd br gzip + # Uncomment the following lines to enable Mercure and Vulcain modules + #mercure { + # # Transport to use (default to Bolt) + # transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db} + # # Publisher JWT key + # publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG} + # # Subscriber JWT key + # subscriber_jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_SUBSCRIBER_JWT_ALG} + # # Allow anonymous subscribers (double-check that it's what you want) + # anonymous + # # Enable the subscription API (double-check that it's what you want) + # subscriptions + # # Extra directives + # {$MERCURE_EXTRA_DIRECTIVES} + #} + #vulcain - # Uncomment the following lines to enable Mercure and Vulcain modules - #mercure { - # # Transport to use (default to Bolt) - # transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db} - # # Publisher JWT key - # publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG} - # # Subscriber JWT key - # subscriber_jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_SUBSCRIBER_JWT_ALG} - # # Allow anonymous subscribers (double-check that it's what you want) - # anonymous - # # Enable the subscription API (double-check that it's what you want) - # subscriptions - # # Extra directives - # {$MERCURE_EXTRA_DIRECTIVES} - #} - #vulcain + {$CADDY_SERVER_EXTRA_DIRECTIVES} - {$CADDY_SERVER_EXTRA_DIRECTIVES} + # We need to customise the `php_server` directive (variant try_files) + route { + # Optional XHProf config must be inside the route directive to ensure priority + {$RUNPHP_XHPROF_CADDY_CONFIG} - # We need to customise the `php_server` directive (difference index) - route { - # Add trailing slash for directory requests - @canonicalPath { - file {path}/index.php - not path */ - } - redir @canonicalPath {path}/ 308 - # If the requested file does not exist, try index files - @indexFiles file { - try_files {path} {path}/{$RUNPHP_INDEX_FILE} {$RUNPHP_INDEX_FILE} - split_path .php - } - rewrite @indexFiles {http.matchers.file.relative} - # FrankenPHP! - @phpFiles path *.php - php @phpFiles - file_server - } + # Add trailing slash for directory requests + @canonicalPath { + file {path}/index.php + not path */ + } + redir @canonicalPath {path}/ 308 + # If the requested file does not exist, try index files + @indexFiles file { + try_files {path} {path}/{$RUNPHP_INDEX_FILE} {$RUNPHP_INDEX_FILE} + split_path .php + } + rewrite @indexFiles {http.matchers.file.relative} + # FrankenPHP! + @phpFiles path *.php + php @phpFiles + file_server + } } diff --git a/manifest/etc/caddy/README.md b/manifest/etc/caddy/README.md new file mode 100644 index 0000000..6a5fb0d --- /dev/null +++ b/manifest/etc/caddy/README.md @@ -0,0 +1,4 @@ +# Caddyfiles + +TODO - move to `Caddyfile.development` and `Caddyfile.production`. Only some of the production Cloud Run settings are +useful in a development environment (for example, disabling Auto HTTPS). diff --git a/manifest/runphp-foundation/admin/admin.php b/manifest/runphp-foundation/admin/admin.php index 5ebb3ce..743ec06 100644 --- a/manifest/runphp-foundation/admin/admin.php +++ b/manifest/runphp-foundation/admin/admin.php @@ -64,13 +64,13 @@

thinkfluent/runphp

-

Serverless PHP Toolkit for Google Cloud Run

+

Serverless FrankenPHP for Google Cloud Run

project: location: mode: getMode(); ?> - foundation: getFoundationVersion(); ?> runphp: getVersion(); ?> + sapi:
@@ -126,7 +126,11 @@
Xdebug, Profiling

In dev mode, we enable Xdebug for you. Control this via the RUNPHP_MODE environment variable.

Performance profiling with XHProf

- Show Profiles + shouldProfile()) { ?> + Show Profiles + + Disabled. Enable with env: =yes +
@@ -138,7 +142,7 @@
PHP Extensions
-

runphp comes with a bunch of common PHP extensions compiled. Some are enabled by default, and some you either have to turn on yourself - or let our Composer extension detection handle this for you.

+

runphp comes with a bunch of common PHP extensions compiled. Some (especially Google-friendly ones, like grpc and protobuf are enabled by default.

Enabled

diff --git a/manifest/runphp-foundation/etc/development/php-conf.d/runphp-development-500-core.ini b/manifest/runphp-foundation/etc/development/php-conf.d/runphp-development-500-core.ini index 7ca44f4..6b3cc7e 100644 --- a/manifest/runphp-foundation/etc/development/php-conf.d/runphp-development-500-core.ini +++ b/manifest/runphp-foundation/etc/development/php-conf.d/runphp-development-500-core.ini @@ -2,7 +2,11 @@ expose_php = On ; Logs to STDERR please -error_log = /dev/stderr +; error_log = /dev/stderr + +error_log = syslog + + log_errors = 1 ; Production logging diff --git a/manifest/runphp-foundation/runtime/prepend.php b/manifest/runphp-foundation/runtime/prepend.php index 70079e9..c68d1cb 100644 --- a/manifest/runphp-foundation/runtime/prepend.php +++ b/manifest/runphp-foundation/runtime/prepend.php @@ -6,7 +6,6 @@ * - Verify we've disabled Xdebug in production * - Check for (and execute) admin requests * - Enable profiling - * - Send PHP memory usage to apache (for inclusion in logs) * * @author Tom Walder */ @@ -32,18 +31,8 @@ // CLI mode, limit web server behaviours if ('cli' !== PHP_SAPI) { - // Memory & trace data to apache - if (function_exists('apache_note')) { - register_shutdown_function(function () { - $int_peak = memory_get_peak_usage(true); - apache_note('php_mem_peak', $int_peak); - apache_note('php_mem_peak_mb', sprintf('%.2f', $int_peak / 1024 / 1024)); - apache_note('gcp_trace_context', Runtime::get()->getTraceContext()); - }); - } - // In DEV mode, if this is a request for an admin page, load, run & exit. - if ('/_runphp' === substr($_SERVER['REQUEST_URI'] ?? '', 0, 8) && $obj_runtime->allowAdmin()) { + if (str_starts_with($_SERVER['REQUEST_URI'] ?? '', '/_runphp') && $obj_runtime->allowAdmin()) { require_once __DIR__ . '/../admin/admin.php'; exit(); } diff --git a/manifest/runphp-foundation/src/Google/ReportedErrorHandler.php b/manifest/runphp-foundation/src/Google/ReportedErrorHandler.php index d0b0649..6ef9e57 100644 --- a/manifest/runphp-foundation/src/Google/ReportedErrorHandler.php +++ b/manifest/runphp-foundation/src/Google/ReportedErrorHandler.php @@ -24,13 +24,31 @@ public function register(): void { register_shutdown_function([$this, 'handleShutdown']); set_error_handler([$this, 'handleError'], E_ALL); - set_exception_handler([$this, 'handleException']); + set_exception_handler([$this, 'uncaughtException']); } /** - * Handle an 'Exception' - * - * Produced message string must start with PHP (Notice|Parse error|Fatal error|Warning) + * Handle an uncaught Exception + */ + public function uncaughtException(\Throwable $obj_thrown): void + { + $this->handleError( + E_ERROR, + sprintf("Uncaught %s: %s", get_class($obj_thrown), $obj_thrown->getMessage()), + $obj_thrown->getFile(), + $obj_thrown->getLine(), + [ + 'function' => $this->getFunctionNameForReport($obj_thrown->getTrace()), + 'exception' => $obj_thrown, + ], + // And now a stack trace that will be parsed by Google + // Must be prefixed with "PHP (Notice|Parse error|Fatal error|Warning): " + 'PHP Fatal error: Uncaught ' . (string) $obj_thrown + ); + } + + /** + * Handle an Exception (convenience method) * * @param \Throwable $obj_thrown */ @@ -38,17 +56,16 @@ public function handleException(\Throwable $obj_thrown): void { $this->handleError( E_RECOVERABLE_ERROR, - sprintf( - "PHP Warning: %s\nStack trace:\n%s", - (string) $obj_thrown, - $obj_thrown->getTraceAsString() - ), + sprintf("%s: %s", get_class($obj_thrown), $obj_thrown->getMessage()), $obj_thrown->getFile(), $obj_thrown->getLine(), [ 'function' => $this->getFunctionNameForReport($obj_thrown->getTrace()), 'exception' => $obj_thrown, - ] + ], + // And now a stack trace that will be parsed by Google + // Must be prefixed with "PHP (Notice|Parse error|Fatal error|Warning): " + 'PHP Warning: ' . (string) $obj_thrown ); } @@ -84,7 +101,8 @@ public function handleError( string $str_error, ?string $str_file = null, ?int $int_line = null, - array $arr_context = [] + array $arr_context = [], + ?string $str_stack_trace = null ): void { // Respect current error_reporting() level if (0 == ($int_errno & error_reporting())) { @@ -99,6 +117,7 @@ public function handleError( E_USER_DEPRECATED => 'INFO', E_USER_ERROR => 'ERROR', E_RECOVERABLE_ERROR => 'ERROR', + E_ERROR => 'ERROR', ]; if (PHP_VERSION_ID < 80400) { $arr_error_map[2048] = 'INFO'; // E_STRICT @@ -115,7 +134,7 @@ public function handleError( '@type' => 'type.googleapis.com/google.devtools.clouderrorreporting.v1beta1.ReportedErrorEvent', "eventTime" => (new \DateTimeImmutable('now', new \DateTimeZone('UTC')))->format(self::DTM_FORMAT), "severity" => $str_severity, - "serviceContext" => (object)[ + "serviceContext" => [ "service" => $arr_env['K_SERVICE'] ?? 'unknown', "version" => $arr_env['K_REVISION'] ?? 'unknown', ], @@ -142,6 +161,9 @@ public function handleError( } catch (\Throwable $obj_thrown) { // swallow } + if (!empty($str_stack_trace)) { + $arr_payload['stack_trace'] = $str_stack_trace; + } file_put_contents('php://stderr', json_encode($arr_payload) . PHP_EOL); } diff --git a/manifest/runphp-foundation/src/Runtime.php b/manifest/runphp-foundation/src/Runtime.php index 364b262..15121de 100644 --- a/manifest/runphp-foundation/src/Runtime.php +++ b/manifest/runphp-foundation/src/Runtime.php @@ -5,11 +5,11 @@ class Runtime { - public const + public const string MODE_DEV = 'development', MODE_PROD = 'production'; - public const + public const string ENV_MODE = 'RUNPHP_MODE', ENV_VERSION = 'RUNPHP_VERSION', ENV_FOUNDATION_VERSION = 'RUNPHP_FOUNDATION_VERSION', @@ -20,11 +20,11 @@ class Runtime ENV_TRACE_HINT = 'RUNPHP_TRACE_CONTEXT_HINT', ENV_TRACE_PROJECT = 'RUNPHP_TRACE_PROJECT'; - public const + public const string ENV_TRUE = 'true', ENV_YES = 'yes'; - public const + public const string SERVER_TRACE_CONTEXT_HEADER = 'HTTP_X_CLOUD_TRACE_CONTEXT'; /** diff --git a/manifest/runphp/index.php b/manifest/runphp/index.php index 52ec3e5..975a45d 100644 --- a/manifest/runphp/index.php +++ b/manifest/runphp/index.php @@ -13,7 +13,7 @@

thinkfluent/runphp

-

Serverless PHP Toolkit for Google Cloud Run

+

Serverless FrankenPHP for Google Cloud Run