From 3610d02f059743d924bcda64dcf0ffe839bd904e Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 11 Jan 2024 16:46:56 -0800 Subject: [PATCH] Add initial "NGINX Unit" based image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Unit configuration I've included here is based on the example provided by the Unit documentation (https://unit.nginx.org/howto/wordpress/), although I did spend time understanding it before blindly trusting it. 😄 The biggest 😬 here (IMO) is that WordPress itself does not know about Unit yet, so the permalink handling is a bit off -- I've added a hack in `wp-config-docker.php` (specifically in the Unit images only) which works around it by teaching WordPress that Unit is effectively NGINX (which does work and is accurate enough for what WordPress needs), but that should probably be an issue or PR to the WordPress community instead. --- Dockerfile.template | 32 +++- apply-templates.sh | 8 + beta/php8.1/apache/docker-entrypoint.sh | 10 +- beta/php8.1/fpm-alpine/docker-entrypoint.sh | 10 +- beta/php8.1/fpm/docker-entrypoint.sh | 10 +- beta/php8.2/apache/docker-entrypoint.sh | 10 +- beta/php8.2/fpm-alpine/docker-entrypoint.sh | 10 +- beta/php8.2/fpm/docker-entrypoint.sh | 10 +- beta/php8.2/unit/Dockerfile | 165 ++++++++++++++++++ beta/php8.2/unit/docker-entrypoint.sh | 107 ++++++++++++ beta/php8.2/unit/unit.json | 45 +++++ beta/php8.2/unit/wp-config-docker.php | 133 ++++++++++++++ beta/php8.3/apache/docker-entrypoint.sh | 10 +- beta/php8.3/fpm-alpine/docker-entrypoint.sh | 10 +- beta/php8.3/fpm/docker-entrypoint.sh | 10 +- docker-entrypoint.sh | 10 +- latest/php8.1/apache/docker-entrypoint.sh | 10 +- latest/php8.1/fpm-alpine/docker-entrypoint.sh | 10 +- latest/php8.1/fpm/docker-entrypoint.sh | 10 +- latest/php8.2/apache/docker-entrypoint.sh | 10 +- latest/php8.2/fpm-alpine/docker-entrypoint.sh | 10 +- latest/php8.2/fpm/docker-entrypoint.sh | 10 +- latest/php8.2/unit/Dockerfile | 165 ++++++++++++++++++ latest/php8.2/unit/docker-entrypoint.sh | 107 ++++++++++++ latest/php8.2/unit/unit.json | 45 +++++ latest/php8.2/unit/wp-config-docker.php | 133 ++++++++++++++ latest/php8.3/apache/docker-entrypoint.sh | 10 +- latest/php8.3/fpm-alpine/docker-entrypoint.sh | 10 +- latest/php8.3/fpm/docker-entrypoint.sh | 10 +- unit.json | 45 +++++ versions.json | 18 +- versions.sh | 37 +++- 32 files changed, 1205 insertions(+), 25 deletions(-) create mode 100644 beta/php8.2/unit/Dockerfile create mode 100755 beta/php8.2/unit/docker-entrypoint.sh create mode 100644 beta/php8.2/unit/unit.json create mode 100644 beta/php8.2/unit/wp-config-docker.php create mode 100644 latest/php8.2/unit/Dockerfile create mode 100755 latest/php8.2/unit/docker-entrypoint.sh create mode 100644 latest/php8.2/unit/unit.json create mode 100644 latest/php8.2/unit/wp-config-docker.php create mode 100644 unit.json diff --git a/Dockerfile.template b/Dockerfile.template index 2296928274..8af48cf052 100644 --- a/Dockerfile.template +++ b/Dockerfile.template @@ -2,7 +2,11 @@ def is_alpine: env.variant | index("alpine") -}} +{{ if env.variant == "unit" then ( -}} +FROM unit:{{ .unit.version }}-php{{ env.phpVersion }} +{{ ) else ( -}} FROM php:{{ env.phpVersion }}-{{ env.variant }} +{{ ) end -}} {{ if env.version != "cli" then ( -}} # persistent dependencies @@ -252,12 +256,38 @@ VOLUME /var/www/html {{ if env.version != "cli" then ( -}} COPY --chown=www-data:www-data wp-config-docker.php /usr/src/wordpress/ +{{ if env.variant == "unit" then ( -}} + +# NGINX Unit specific bits +WORKDIR /var/www/html +RUN set -eux; \ +# save Unit's entrypoint so we can successfully/correctly configure Unit at runtime + sed -e 's/docker-entrypoint/unit-entrypoint/g' /usr/local/bin/docker-entrypoint.sh > /usr/local/bin/unit-entrypoint.sh; \ + grep -F '/unit-entrypoint.d/' /usr/local/bin/unit-entrypoint.sh; \ + chmod +x /usr/local/bin/unit-entrypoint.sh; \ + \ + { \ + echo; \ + echo '// WordPress does not support NGINX Unit (yet), so we have to do a little hackery to let it know that Unit supports nice permalinks (and prevent it from insisting on injecting "/index.php" in all the permalink options) -- the simplest way is teaching it to recognize Unit as if it were NGINX (which is only semi-true, but true enough for these purposes)'; \ + echo '$is_nginx = $is_nginx || str_starts_with($_SERVER["SERVER_SOFTWARE"], "Unit/");'; \ + echo '// see also https://github.com/WordPress/WordPress/blob/39f7f558d91afdd2f3afc7f3b049a6a800cd3f80/wp-includes/vars.php#L127'; \ + } >> /usr/src/wordpress/wp-config-docker.php +COPY unit.json /unit-entrypoint.d/wordpress.json + +{{ ) else "" end -}} {{ ) else "" end -}} COPY docker-entrypoint.sh /usr/local/bin/ ENTRYPOINT ["docker-entrypoint.sh"] {{ if env.version != "cli" then ( -}} -CMD {{ [ if env.variant == "apache" then "apache2-foreground" else "php-fpm" end ] | @json }} +CMD {{ + { + apache: [ "apache2-foreground" ], + # https://github.com/nginx/unit/blob/fb33ec86a3b6ca6a844dfa6980bb9e083094abec/pkg/docker/Dockerfile.php8.2#L89 + unit: [ "unitd", "--no-daemon", "--control", "unix:/var/run/control.unit.sock" ], + }[env.variant] // [ "php-fpm" ] + | @json +}} {{ ) else ( -}} USER www-data CMD ["wp", "shell"] diff --git a/apply-templates.sh b/apply-templates.sh index 99ac8785a4..af1a34443b 100755 --- a/apply-templates.sh +++ b/apply-templates.sh @@ -43,6 +43,10 @@ for version; do for variant in "${variants[@]}"; do export variant + if [ "$variant" = 'unit' ] && jq -e '.[env.version][env.variant].phpVersions | index(env.phpVersion) | not' versions.json > /dev/null; then + continue + fi + dir="$version/php$phpVersion/$variant" mkdir -p "$dir" @@ -58,6 +62,10 @@ for version; do else cp -a docker-entrypoint.sh wp-config-docker.php "$dir/" fi + + if [ "$variant" = 'unit' ]; then + cp -a unit.json "$dir/" + fi done done done diff --git a/beta/php8.1/apache/docker-entrypoint.sh b/beta/php8.1/apache/docker-entrypoint.sh index 1034f1dec1..4843caa7d3 100755 --- a/beta/php8.1/apache/docker-entrypoint.sh +++ b/beta/php8.1/apache/docker-entrypoint.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -Eeuo pipefail -if [[ "$1" == apache2* ]] || [ "$1" = 'php-fpm' ]; then +if [[ "$1" == apache2* ]] || [ "$1" = 'php-fpm' ] || [[ "$1" == unit* ]]; then uid="$(id -u)" gid="$(id -g)" if [ "$uid" = '0' ]; then @@ -15,6 +15,10 @@ if [[ "$1" == apache2* ]] || [ "$1" = 'php-fpm' ]; then user="${user#$pound}" group="${group#$pound}" ;; + unit*) + user='unit' + group='unit' + ;; *) # php-fpm user='www-data' group='www-data' @@ -94,6 +98,10 @@ if [[ "$1" == apache2* ]] || [ "$1" = 'php-fpm' ]; then fi done fi + + if [[ "$1" == unit* ]] && [ "$1" != 'unit-entrypoint.sh' ]; then + set -- unit-entrypoint.sh "$@" + fi fi exec "$@" diff --git a/beta/php8.1/fpm-alpine/docker-entrypoint.sh b/beta/php8.1/fpm-alpine/docker-entrypoint.sh index 1034f1dec1..4843caa7d3 100755 --- a/beta/php8.1/fpm-alpine/docker-entrypoint.sh +++ b/beta/php8.1/fpm-alpine/docker-entrypoint.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -Eeuo pipefail -if [[ "$1" == apache2* ]] || [ "$1" = 'php-fpm' ]; then +if [[ "$1" == apache2* ]] || [ "$1" = 'php-fpm' ] || [[ "$1" == unit* ]]; then uid="$(id -u)" gid="$(id -g)" if [ "$uid" = '0' ]; then @@ -15,6 +15,10 @@ if [[ "$1" == apache2* ]] || [ "$1" = 'php-fpm' ]; then user="${user#$pound}" group="${group#$pound}" ;; + unit*) + user='unit' + group='unit' + ;; *) # php-fpm user='www-data' group='www-data' @@ -94,6 +98,10 @@ if [[ "$1" == apache2* ]] || [ "$1" = 'php-fpm' ]; then fi done fi + + if [[ "$1" == unit* ]] && [ "$1" != 'unit-entrypoint.sh' ]; then + set -- unit-entrypoint.sh "$@" + fi fi exec "$@" diff --git a/beta/php8.1/fpm/docker-entrypoint.sh b/beta/php8.1/fpm/docker-entrypoint.sh index 1034f1dec1..4843caa7d3 100755 --- a/beta/php8.1/fpm/docker-entrypoint.sh +++ b/beta/php8.1/fpm/docker-entrypoint.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -Eeuo pipefail -if [[ "$1" == apache2* ]] || [ "$1" = 'php-fpm' ]; then +if [[ "$1" == apache2* ]] || [ "$1" = 'php-fpm' ] || [[ "$1" == unit* ]]; then uid="$(id -u)" gid="$(id -g)" if [ "$uid" = '0' ]; then @@ -15,6 +15,10 @@ if [[ "$1" == apache2* ]] || [ "$1" = 'php-fpm' ]; then user="${user#$pound}" group="${group#$pound}" ;; + unit*) + user='unit' + group='unit' + ;; *) # php-fpm user='www-data' group='www-data' @@ -94,6 +98,10 @@ if [[ "$1" == apache2* ]] || [ "$1" = 'php-fpm' ]; then fi done fi + + if [[ "$1" == unit* ]] && [ "$1" != 'unit-entrypoint.sh' ]; then + set -- unit-entrypoint.sh "$@" + fi fi exec "$@" diff --git a/beta/php8.2/apache/docker-entrypoint.sh b/beta/php8.2/apache/docker-entrypoint.sh index 1034f1dec1..4843caa7d3 100755 --- a/beta/php8.2/apache/docker-entrypoint.sh +++ b/beta/php8.2/apache/docker-entrypoint.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -Eeuo pipefail -if [[ "$1" == apache2* ]] || [ "$1" = 'php-fpm' ]; then +if [[ "$1" == apache2* ]] || [ "$1" = 'php-fpm' ] || [[ "$1" == unit* ]]; then uid="$(id -u)" gid="$(id -g)" if [ "$uid" = '0' ]; then @@ -15,6 +15,10 @@ if [[ "$1" == apache2* ]] || [ "$1" = 'php-fpm' ]; then user="${user#$pound}" group="${group#$pound}" ;; + unit*) + user='unit' + group='unit' + ;; *) # php-fpm user='www-data' group='www-data' @@ -94,6 +98,10 @@ if [[ "$1" == apache2* ]] || [ "$1" = 'php-fpm' ]; then fi done fi + + if [[ "$1" == unit* ]] && [ "$1" != 'unit-entrypoint.sh' ]; then + set -- unit-entrypoint.sh "$@" + fi fi exec "$@" diff --git a/beta/php8.2/fpm-alpine/docker-entrypoint.sh b/beta/php8.2/fpm-alpine/docker-entrypoint.sh index 1034f1dec1..4843caa7d3 100755 --- a/beta/php8.2/fpm-alpine/docker-entrypoint.sh +++ b/beta/php8.2/fpm-alpine/docker-entrypoint.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -Eeuo pipefail -if [[ "$1" == apache2* ]] || [ "$1" = 'php-fpm' ]; then +if [[ "$1" == apache2* ]] || [ "$1" = 'php-fpm' ] || [[ "$1" == unit* ]]; then uid="$(id -u)" gid="$(id -g)" if [ "$uid" = '0' ]; then @@ -15,6 +15,10 @@ if [[ "$1" == apache2* ]] || [ "$1" = 'php-fpm' ]; then user="${user#$pound}" group="${group#$pound}" ;; + unit*) + user='unit' + group='unit' + ;; *) # php-fpm user='www-data' group='www-data' @@ -94,6 +98,10 @@ if [[ "$1" == apache2* ]] || [ "$1" = 'php-fpm' ]; then fi done fi + + if [[ "$1" == unit* ]] && [ "$1" != 'unit-entrypoint.sh' ]; then + set -- unit-entrypoint.sh "$@" + fi fi exec "$@" diff --git a/beta/php8.2/fpm/docker-entrypoint.sh b/beta/php8.2/fpm/docker-entrypoint.sh index 1034f1dec1..4843caa7d3 100755 --- a/beta/php8.2/fpm/docker-entrypoint.sh +++ b/beta/php8.2/fpm/docker-entrypoint.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -Eeuo pipefail -if [[ "$1" == apache2* ]] || [ "$1" = 'php-fpm' ]; then +if [[ "$1" == apache2* ]] || [ "$1" = 'php-fpm' ] || [[ "$1" == unit* ]]; then uid="$(id -u)" gid="$(id -g)" if [ "$uid" = '0' ]; then @@ -15,6 +15,10 @@ if [[ "$1" == apache2* ]] || [ "$1" = 'php-fpm' ]; then user="${user#$pound}" group="${group#$pound}" ;; + unit*) + user='unit' + group='unit' + ;; *) # php-fpm user='www-data' group='www-data' @@ -94,6 +98,10 @@ if [[ "$1" == apache2* ]] || [ "$1" = 'php-fpm' ]; then fi done fi + + if [[ "$1" == unit* ]] && [ "$1" != 'unit-entrypoint.sh' ]; then + set -- unit-entrypoint.sh "$@" + fi fi exec "$@" diff --git a/beta/php8.2/unit/Dockerfile b/beta/php8.2/unit/Dockerfile new file mode 100644 index 0000000000..549810639c --- /dev/null +++ b/beta/php8.2/unit/Dockerfile @@ -0,0 +1,165 @@ +# +# NOTE: THIS DOCKERFILE IS GENERATED VIA "apply-templates.sh" +# +# PLEASE DO NOT EDIT IT DIRECTLY. +# + +FROM unit:1.31.1-php8.2 + +# persistent dependencies +RUN set -eux; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ +# Ghostscript is required for rendering PDF previews + ghostscript \ + ; \ + rm -rf /var/lib/apt/lists/* + +# install the PHP extensions we need (https://make.wordpress.org/hosting/handbook/handbook/server-environment/#php-extensions) +RUN set -ex; \ + \ + savedAptMark="$(apt-mark showmanual)"; \ + \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + libfreetype6-dev \ + libicu-dev \ + libjpeg-dev \ + libmagickwand-dev \ + libpng-dev \ + libwebp-dev \ + libzip-dev \ + ; \ + \ + docker-php-ext-configure gd \ + --with-freetype \ + --with-jpeg \ + --with-webp \ + ; \ + docker-php-ext-install -j "$(nproc)" \ + bcmath \ + exif \ + gd \ + intl \ + mysqli \ + zip \ + ; \ +# https://pecl.php.net/package/imagick + pecl install imagick-3.6.0; \ + docker-php-ext-enable imagick; \ + rm -r /tmp/pear; \ + \ +# some misbehaving extensions end up outputting to stdout 🙈 (https://github.com/docker-library/wordpress/issues/669#issuecomment-993945967) + out="$(php -r 'exit(0);')"; \ + [ -z "$out" ]; \ + err="$(php -r 'exit(0);' 3>&1 1>&2 2>&3)"; \ + [ -z "$err" ]; \ + \ + extDir="$(php -r 'echo ini_get("extension_dir");')"; \ + [ -d "$extDir" ]; \ +# reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies + apt-mark auto '.*' > /dev/null; \ + apt-mark manual $savedAptMark; \ + ldd "$extDir"/*.so \ + | awk '/=>/ { so = $(NF-1); if (index(so, "/usr/local/") == 1) { next }; gsub("^/(usr/)?", "", so); print so }' \ + | sort -u \ + | xargs -r dpkg-query --search \ + | cut -d: -f1 \ + | sort -u \ + | xargs -rt apt-mark manual; \ + \ + apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ + rm -rf /var/lib/apt/lists/*; \ + \ + ! { ldd "$extDir"/*.so | grep 'not found'; }; \ +# check for output like "PHP Warning: PHP Startup: Unable to load dynamic library 'foo' (tried: ...) + err="$(php --version 3>&1 1>&2 2>&3)"; \ + [ -z "$err" ] + +# set recommended PHP.ini settings +# see https://secure.php.net/manual/en/opcache.installation.php +RUN set -eux; \ + docker-php-ext-enable opcache; \ + { \ + echo 'opcache.memory_consumption=128'; \ + echo 'opcache.interned_strings_buffer=8'; \ + echo 'opcache.max_accelerated_files=4000'; \ + echo 'opcache.revalidate_freq=2'; \ + } > /usr/local/etc/php/conf.d/opcache-recommended.ini +# https://wordpress.org/support/article/editing-wp-config-php/#configure-error-logging +RUN { \ +# https://www.php.net/manual/en/errorfunc.constants.php +# https://github.com/docker-library/wordpress/issues/420#issuecomment-517839670 + echo 'error_reporting = E_ERROR | E_WARNING | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING | E_RECOVERABLE_ERROR'; \ + echo 'display_errors = Off'; \ + echo 'display_startup_errors = Off'; \ + echo 'log_errors = On'; \ + echo 'error_log = /dev/stderr'; \ + echo 'log_errors_max_len = 1024'; \ + echo 'ignore_repeated_errors = On'; \ + echo 'ignore_repeated_source = Off'; \ + echo 'html_errors = Off'; \ + } > /usr/local/etc/php/conf.d/error-logging.ini + +RUN set -eux; \ + version='6.4.2'; \ + sha1='d1aedbfea77b243b09e0ab05b100b782497406dd'; \ + \ + curl -o wordpress.tar.gz -fL "https://wordpress.org/wordpress-$version.tar.gz"; \ + echo "$sha1 *wordpress.tar.gz" | sha1sum -c -; \ + \ +# upstream tarballs include ./wordpress/ so this gives us /usr/src/wordpress + tar -xzf wordpress.tar.gz -C /usr/src/; \ + rm wordpress.tar.gz; \ + \ +# https://wordpress.org/support/article/htaccess/ + [ ! -e /usr/src/wordpress/.htaccess ]; \ + { \ + echo '# BEGIN WordPress'; \ + echo ''; \ + echo 'RewriteEngine On'; \ + echo 'RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]'; \ + echo 'RewriteBase /'; \ + echo 'RewriteRule ^index\.php$ - [L]'; \ + echo 'RewriteCond %{REQUEST_FILENAME} !-f'; \ + echo 'RewriteCond %{REQUEST_FILENAME} !-d'; \ + echo 'RewriteRule . /index.php [L]'; \ + echo ''; \ + echo '# END WordPress'; \ + } > /usr/src/wordpress/.htaccess; \ + \ + chown -R www-data:www-data /usr/src/wordpress; \ +# pre-create wp-content (and single-level children) for folks who want to bind-mount themes, etc so permissions are pre-created properly instead of root:root +# wp-content/cache: https://github.com/docker-library/wordpress/issues/534#issuecomment-705733507 + mkdir wp-content; \ + for dir in /usr/src/wordpress/wp-content/*/ cache; do \ + dir="$(basename "${dir%/}")"; \ + mkdir "wp-content/$dir"; \ + done; \ + chown -R www-data:www-data wp-content; \ + chmod -R 1777 wp-content + +VOLUME /var/www/html + +COPY --chown=www-data:www-data wp-config-docker.php /usr/src/wordpress/ + +# NGINX Unit specific bits +WORKDIR /var/www/html +RUN set -eux; \ +# save Unit's entrypoint so we can successfully/correctly configure Unit at runtime + sed -e 's/docker-entrypoint/unit-entrypoint/g' /usr/local/bin/docker-entrypoint.sh > /usr/local/bin/unit-entrypoint.sh; \ + grep -F '/unit-entrypoint.d/' /usr/local/bin/unit-entrypoint.sh; \ + chmod +x /usr/local/bin/unit-entrypoint.sh; \ + \ + { \ + echo; \ + echo '// WordPress does not support NGINX Unit (yet), so we have to do a little hackery to let it know that Unit supports nice permalinks (and prevent it from insisting on injecting "/index.php" in all the permalink options) -- the simplest way is teaching it to recognize Unit as if it were NGINX (which is only semi-true, but true enough for these purposes)'; \ + echo '$is_nginx = $is_nginx || str_starts_with($_SERVER["SERVER_SOFTWARE"], "Unit/");'; \ + echo '// see also https://github.com/WordPress/WordPress/blob/39f7f558d91afdd2f3afc7f3b049a6a800cd3f80/wp-includes/vars.php#L127'; \ + } >> /usr/src/wordpress/wp-config-docker.php +COPY unit.json /unit-entrypoint.d/wordpress.json + +COPY docker-entrypoint.sh /usr/local/bin/ + +ENTRYPOINT ["docker-entrypoint.sh"] +CMD ["unitd","--no-daemon","--control","unix:/var/run/control.unit.sock"] diff --git a/beta/php8.2/unit/docker-entrypoint.sh b/beta/php8.2/unit/docker-entrypoint.sh new file mode 100755 index 0000000000..4843caa7d3 --- /dev/null +++ b/beta/php8.2/unit/docker-entrypoint.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +if [[ "$1" == apache2* ]] || [ "$1" = 'php-fpm' ] || [[ "$1" == unit* ]]; then + uid="$(id -u)" + gid="$(id -g)" + if [ "$uid" = '0' ]; then + case "$1" in + apache2*) + user="${APACHE_RUN_USER:-www-data}" + group="${APACHE_RUN_GROUP:-www-data}" + + # strip off any '#' symbol ('#1000' is valid syntax for Apache) + pound='#' + user="${user#$pound}" + group="${group#$pound}" + ;; + unit*) + user='unit' + group='unit' + ;; + *) # php-fpm + user='www-data' + group='www-data' + ;; + esac + else + user="$uid" + group="$gid" + fi + + if [ ! -e index.php ] && [ ! -e wp-includes/version.php ]; then + # if the directory exists and WordPress doesn't appear to be installed AND the permissions of it are root:root, let's chown it (likely a Docker-created directory) + if [ "$uid" = '0' ] && [ "$(stat -c '%u:%g' .)" = '0:0' ]; then + chown "$user:$group" . + fi + + echo >&2 "WordPress not found in $PWD - copying now..." + if [ -n "$(find -mindepth 1 -maxdepth 1 -not -name wp-content)" ]; then + echo >&2 "WARNING: $PWD is not empty! (copying anyhow)" + fi + sourceTarArgs=( + --create + --file - + --directory /usr/src/wordpress + --owner "$user" --group "$group" + ) + targetTarArgs=( + --extract + --file - + ) + if [ "$uid" != '0' ]; then + # avoid "tar: .: Cannot utime: Operation not permitted" and "tar: .: Cannot change mode to rwxr-xr-x: Operation not permitted" + targetTarArgs+=( --no-overwrite-dir ) + fi + # loop over "pluggable" content in the source, and if it already exists in the destination, skip it + # https://github.com/docker-library/wordpress/issues/506 ("wp-content" persisted, "akismet" updated, WordPress container restarted/recreated, "akismet" downgraded) + for contentPath in \ + /usr/src/wordpress/.htaccess \ + /usr/src/wordpress/wp-content/*/*/ \ + ; do + contentPath="${contentPath%/}" + [ -e "$contentPath" ] || continue + contentPath="${contentPath#/usr/src/wordpress/}" # "wp-content/plugins/akismet", etc. + if [ -e "$PWD/$contentPath" ]; then + echo >&2 "WARNING: '$PWD/$contentPath' exists! (not copying the WordPress version)" + sourceTarArgs+=( --exclude "./$contentPath" ) + fi + done + tar "${sourceTarArgs[@]}" . | tar "${targetTarArgs[@]}" + echo >&2 "Complete! WordPress has been successfully copied to $PWD" + fi + + wpEnvs=( "${!WORDPRESS_@}" ) + if [ ! -s wp-config.php ] && [ "${#wpEnvs[@]}" -gt 0 ]; then + for wpConfigDocker in \ + wp-config-docker.php \ + /usr/src/wordpress/wp-config-docker.php \ + ; do + if [ -s "$wpConfigDocker" ]; then + echo >&2 "No 'wp-config.php' found in $PWD, but 'WORDPRESS_...' variables supplied; copying '$wpConfigDocker' (${wpEnvs[*]})" + # using "awk" to replace all instances of "put your unique phrase here" with a properly unique string (for AUTH_KEY and friends to have safe defaults if they aren't specified with environment variables) + awk ' + /put your unique phrase here/ { + cmd = "head -c1m /dev/urandom | sha1sum | cut -d\\ -f1" + cmd | getline str + close(cmd) + gsub("put your unique phrase here", str) + } + { print } + ' "$wpConfigDocker" > wp-config.php + if [ "$uid" = '0' ]; then + # attempt to ensure that wp-config.php is owned by the run user + # could be on a filesystem that doesn't allow chown (like some NFS setups) + chown "$user:$group" wp-config.php || true + fi + break + fi + done + fi + + if [[ "$1" == unit* ]] && [ "$1" != 'unit-entrypoint.sh' ]; then + set -- unit-entrypoint.sh "$@" + fi +fi + +exec "$@" diff --git a/beta/php8.2/unit/unit.json b/beta/php8.2/unit/unit.json new file mode 100644 index 0000000000..c58a5cb111 --- /dev/null +++ b/beta/php8.2/unit/unit.json @@ -0,0 +1,45 @@ +{ + "listeners": { + "*:80": { + "pass": "routes" + } + }, + + "routes": [ + { + "match": { + "uri": [ + "*.php", + "*.php/*", + "/wp-admin/" + ] + }, + "action": { + "pass": "applications/wordpress/direct" + } + }, + { + "action": { + "share": "/var/www/html$uri", + "fallback": { + "pass": "applications/wordpress/index" + } + } + } + ], + + "applications": { + "wordpress": { + "type": "php", + "targets": { + "direct": { + "root": "/var/www/html/" + }, + "index": { + "root": "/var/www/html/", + "script": "index.php" + } + } + } + } +} diff --git a/beta/php8.2/unit/wp-config-docker.php b/beta/php8.2/unit/wp-config-docker.php new file mode 100644 index 0000000000..9cd3babf01 --- /dev/null +++ b/beta/php8.2/unit/wp-config-docker.php @@ -0,0 +1,133 @@ +&1 1>&2 2>&3)"; \ + [ -z "$err" ]; \ + \ + extDir="$(php -r 'echo ini_get("extension_dir");')"; \ + [ -d "$extDir" ]; \ +# reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies + apt-mark auto '.*' > /dev/null; \ + apt-mark manual $savedAptMark; \ + ldd "$extDir"/*.so \ + | awk '/=>/ { so = $(NF-1); if (index(so, "/usr/local/") == 1) { next }; gsub("^/(usr/)?", "", so); print so }' \ + | sort -u \ + | xargs -r dpkg-query --search \ + | cut -d: -f1 \ + | sort -u \ + | xargs -rt apt-mark manual; \ + \ + apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ + rm -rf /var/lib/apt/lists/*; \ + \ + ! { ldd "$extDir"/*.so | grep 'not found'; }; \ +# check for output like "PHP Warning: PHP Startup: Unable to load dynamic library 'foo' (tried: ...) + err="$(php --version 3>&1 1>&2 2>&3)"; \ + [ -z "$err" ] + +# set recommended PHP.ini settings +# see https://secure.php.net/manual/en/opcache.installation.php +RUN set -eux; \ + docker-php-ext-enable opcache; \ + { \ + echo 'opcache.memory_consumption=128'; \ + echo 'opcache.interned_strings_buffer=8'; \ + echo 'opcache.max_accelerated_files=4000'; \ + echo 'opcache.revalidate_freq=2'; \ + } > /usr/local/etc/php/conf.d/opcache-recommended.ini +# https://wordpress.org/support/article/editing-wp-config-php/#configure-error-logging +RUN { \ +# https://www.php.net/manual/en/errorfunc.constants.php +# https://github.com/docker-library/wordpress/issues/420#issuecomment-517839670 + echo 'error_reporting = E_ERROR | E_WARNING | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING | E_RECOVERABLE_ERROR'; \ + echo 'display_errors = Off'; \ + echo 'display_startup_errors = Off'; \ + echo 'log_errors = On'; \ + echo 'error_log = /dev/stderr'; \ + echo 'log_errors_max_len = 1024'; \ + echo 'ignore_repeated_errors = On'; \ + echo 'ignore_repeated_source = Off'; \ + echo 'html_errors = Off'; \ + } > /usr/local/etc/php/conf.d/error-logging.ini + +RUN set -eux; \ + version='6.4.2'; \ + sha1='d1aedbfea77b243b09e0ab05b100b782497406dd'; \ + \ + curl -o wordpress.tar.gz -fL "https://wordpress.org/wordpress-$version.tar.gz"; \ + echo "$sha1 *wordpress.tar.gz" | sha1sum -c -; \ + \ +# upstream tarballs include ./wordpress/ so this gives us /usr/src/wordpress + tar -xzf wordpress.tar.gz -C /usr/src/; \ + rm wordpress.tar.gz; \ + \ +# https://wordpress.org/support/article/htaccess/ + [ ! -e /usr/src/wordpress/.htaccess ]; \ + { \ + echo '# BEGIN WordPress'; \ + echo ''; \ + echo 'RewriteEngine On'; \ + echo 'RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]'; \ + echo 'RewriteBase /'; \ + echo 'RewriteRule ^index\.php$ - [L]'; \ + echo 'RewriteCond %{REQUEST_FILENAME} !-f'; \ + echo 'RewriteCond %{REQUEST_FILENAME} !-d'; \ + echo 'RewriteRule . /index.php [L]'; \ + echo ''; \ + echo '# END WordPress'; \ + } > /usr/src/wordpress/.htaccess; \ + \ + chown -R www-data:www-data /usr/src/wordpress; \ +# pre-create wp-content (and single-level children) for folks who want to bind-mount themes, etc so permissions are pre-created properly instead of root:root +# wp-content/cache: https://github.com/docker-library/wordpress/issues/534#issuecomment-705733507 + mkdir wp-content; \ + for dir in /usr/src/wordpress/wp-content/*/ cache; do \ + dir="$(basename "${dir%/}")"; \ + mkdir "wp-content/$dir"; \ + done; \ + chown -R www-data:www-data wp-content; \ + chmod -R 1777 wp-content + +VOLUME /var/www/html + +COPY --chown=www-data:www-data wp-config-docker.php /usr/src/wordpress/ + +# NGINX Unit specific bits +WORKDIR /var/www/html +RUN set -eux; \ +# save Unit's entrypoint so we can successfully/correctly configure Unit at runtime + sed -e 's/docker-entrypoint/unit-entrypoint/g' /usr/local/bin/docker-entrypoint.sh > /usr/local/bin/unit-entrypoint.sh; \ + grep -F '/unit-entrypoint.d/' /usr/local/bin/unit-entrypoint.sh; \ + chmod +x /usr/local/bin/unit-entrypoint.sh; \ + \ + { \ + echo; \ + echo '// WordPress does not support NGINX Unit (yet), so we have to do a little hackery to let it know that Unit supports nice permalinks (and prevent it from insisting on injecting "/index.php" in all the permalink options) -- the simplest way is teaching it to recognize Unit as if it were NGINX (which is only semi-true, but true enough for these purposes)'; \ + echo '$is_nginx = $is_nginx || str_starts_with($_SERVER["SERVER_SOFTWARE"], "Unit/");'; \ + echo '// see also https://github.com/WordPress/WordPress/blob/39f7f558d91afdd2f3afc7f3b049a6a800cd3f80/wp-includes/vars.php#L127'; \ + } >> /usr/src/wordpress/wp-config-docker.php +COPY unit.json /unit-entrypoint.d/wordpress.json + +COPY docker-entrypoint.sh /usr/local/bin/ + +ENTRYPOINT ["docker-entrypoint.sh"] +CMD ["unitd","--no-daemon","--control","unix:/var/run/control.unit.sock"] diff --git a/latest/php8.2/unit/docker-entrypoint.sh b/latest/php8.2/unit/docker-entrypoint.sh new file mode 100755 index 0000000000..4843caa7d3 --- /dev/null +++ b/latest/php8.2/unit/docker-entrypoint.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +if [[ "$1" == apache2* ]] || [ "$1" = 'php-fpm' ] || [[ "$1" == unit* ]]; then + uid="$(id -u)" + gid="$(id -g)" + if [ "$uid" = '0' ]; then + case "$1" in + apache2*) + user="${APACHE_RUN_USER:-www-data}" + group="${APACHE_RUN_GROUP:-www-data}" + + # strip off any '#' symbol ('#1000' is valid syntax for Apache) + pound='#' + user="${user#$pound}" + group="${group#$pound}" + ;; + unit*) + user='unit' + group='unit' + ;; + *) # php-fpm + user='www-data' + group='www-data' + ;; + esac + else + user="$uid" + group="$gid" + fi + + if [ ! -e index.php ] && [ ! -e wp-includes/version.php ]; then + # if the directory exists and WordPress doesn't appear to be installed AND the permissions of it are root:root, let's chown it (likely a Docker-created directory) + if [ "$uid" = '0' ] && [ "$(stat -c '%u:%g' .)" = '0:0' ]; then + chown "$user:$group" . + fi + + echo >&2 "WordPress not found in $PWD - copying now..." + if [ -n "$(find -mindepth 1 -maxdepth 1 -not -name wp-content)" ]; then + echo >&2 "WARNING: $PWD is not empty! (copying anyhow)" + fi + sourceTarArgs=( + --create + --file - + --directory /usr/src/wordpress + --owner "$user" --group "$group" + ) + targetTarArgs=( + --extract + --file - + ) + if [ "$uid" != '0' ]; then + # avoid "tar: .: Cannot utime: Operation not permitted" and "tar: .: Cannot change mode to rwxr-xr-x: Operation not permitted" + targetTarArgs+=( --no-overwrite-dir ) + fi + # loop over "pluggable" content in the source, and if it already exists in the destination, skip it + # https://github.com/docker-library/wordpress/issues/506 ("wp-content" persisted, "akismet" updated, WordPress container restarted/recreated, "akismet" downgraded) + for contentPath in \ + /usr/src/wordpress/.htaccess \ + /usr/src/wordpress/wp-content/*/*/ \ + ; do + contentPath="${contentPath%/}" + [ -e "$contentPath" ] || continue + contentPath="${contentPath#/usr/src/wordpress/}" # "wp-content/plugins/akismet", etc. + if [ -e "$PWD/$contentPath" ]; then + echo >&2 "WARNING: '$PWD/$contentPath' exists! (not copying the WordPress version)" + sourceTarArgs+=( --exclude "./$contentPath" ) + fi + done + tar "${sourceTarArgs[@]}" . | tar "${targetTarArgs[@]}" + echo >&2 "Complete! WordPress has been successfully copied to $PWD" + fi + + wpEnvs=( "${!WORDPRESS_@}" ) + if [ ! -s wp-config.php ] && [ "${#wpEnvs[@]}" -gt 0 ]; then + for wpConfigDocker in \ + wp-config-docker.php \ + /usr/src/wordpress/wp-config-docker.php \ + ; do + if [ -s "$wpConfigDocker" ]; then + echo >&2 "No 'wp-config.php' found in $PWD, but 'WORDPRESS_...' variables supplied; copying '$wpConfigDocker' (${wpEnvs[*]})" + # using "awk" to replace all instances of "put your unique phrase here" with a properly unique string (for AUTH_KEY and friends to have safe defaults if they aren't specified with environment variables) + awk ' + /put your unique phrase here/ { + cmd = "head -c1m /dev/urandom | sha1sum | cut -d\\ -f1" + cmd | getline str + close(cmd) + gsub("put your unique phrase here", str) + } + { print } + ' "$wpConfigDocker" > wp-config.php + if [ "$uid" = '0' ]; then + # attempt to ensure that wp-config.php is owned by the run user + # could be on a filesystem that doesn't allow chown (like some NFS setups) + chown "$user:$group" wp-config.php || true + fi + break + fi + done + fi + + if [[ "$1" == unit* ]] && [ "$1" != 'unit-entrypoint.sh' ]; then + set -- unit-entrypoint.sh "$@" + fi +fi + +exec "$@" diff --git a/latest/php8.2/unit/unit.json b/latest/php8.2/unit/unit.json new file mode 100644 index 0000000000..c58a5cb111 --- /dev/null +++ b/latest/php8.2/unit/unit.json @@ -0,0 +1,45 @@ +{ + "listeners": { + "*:80": { + "pass": "routes" + } + }, + + "routes": [ + { + "match": { + "uri": [ + "*.php", + "*.php/*", + "/wp-admin/" + ] + }, + "action": { + "pass": "applications/wordpress/direct" + } + }, + { + "action": { + "share": "/var/www/html$uri", + "fallback": { + "pass": "applications/wordpress/index" + } + } + } + ], + + "applications": { + "wordpress": { + "type": "php", + "targets": { + "direct": { + "root": "/var/www/html/" + }, + "index": { + "root": "/var/www/html/", + "script": "index.php" + } + } + } + } +} diff --git a/latest/php8.2/unit/wp-config-docker.php b/latest/php8.2/unit/wp-config-docker.php new file mode 100644 index 0000000000..9cd3babf01 --- /dev/null +++ b/latest/php8.2/unit/wp-config-docker.php @@ -0,0 +1,133 @@ +[0-9]+([.][0-9]+)*)-php(?[0-9]+[.][0-9]+)$") + | .split = (.version | split(".") | map(tonumber? // .)) # pre-parse version numbers (for filtering and sorting) + | select(.split[0] == 1) # filter down to just 1.x versions (attempt to avoid major breakage) + ] + # sort the list in descending version sort order + | unique_by([ .split, .php ]) + | reverse + | ( + # find the highest and least specific version number (2 preferred over 1 over 2.3 over 2.3.4) + map(.version) + | unique_by(indices(".") | length) + | .[0] // error("no suitable unit version found") + ) as $version + | { + version: $version, + phpVersions: map( + select(.version == $version) + | .php + ), + } + ' +)" + for version in "${versions[@]}"; do export version @@ -55,7 +84,7 @@ for version in "${versions[@]}"; do export fullVersion json="$( - jq <<<"$json" -c --argjson doc "$doc" ' + jq <<<"$json" -c --argjson doc "$doc" --argjson unit "$unit" ' .[env.version] = { version: env.fullVersion, phpVersions: [ "8.1", "8.2", "8.3" ], @@ -63,10 +92,12 @@ for version in "${versions[@]}"; do if env.version == "cli" then [ "alpine" ] else - [ "apache", "fpm", "fpm-alpine" ] + [ "apache", "fpm", "fpm-alpine", "unit" ] end ), - } + $doc + } + if env.version == "cli" then {} else { + unit: $unit, + } end + $doc ' )" done