diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 94ae333..91a07c5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,15 +1,16 @@ name: "Build on main" on: + workflow_dispatch: push: branches: [ main ] - workflow_dispatch: + jobs: scheduler: name: Build Trigger runs-on: ubuntu-latest strategy: matrix: - version: [ 'buster', 'bullseye' ] + version: [ 'buster', 'bullseye', 'bookworm' ] steps: - name: Checkout uses: actions/checkout@v2 diff --git a/.github/workflows/schedule.yml b/.github/workflows/schedule.yml index d12ebf1..d195d45 100644 --- a/.github/workflows/schedule.yml +++ b/.github/workflows/schedule.yml @@ -2,7 +2,7 @@ name: "Build Scheduler" on: workflow_dispatch: schedule: - - cron: '35 1 * * 0' + - cron: '30 3 * * 1' jobs: scheduler: @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - version: [ 'buster', 'bullseye' ] + version: [ 'buster', 'bullseye', 'bookworm' ] steps: - name: Checkout uses: actions/checkout@v2 diff --git a/src/bookworm/src/Dockerfile b/src/bookworm/src/Dockerfile new file mode 100644 index 0000000..8115224 --- /dev/null +++ b/src/bookworm/src/Dockerfile @@ -0,0 +1,123 @@ +FROM openmage/debian:bookworm-latest + +COPY root / + +## configure default environment stuff and file permissions +RUN set -xe; \ + chmod 755 /usr/local/bin/{docker-php-source-prepare,docker-entrypoint,docker-fpm-healthcheck,docker-php-ext-configure,docker-php-ext-enable,docker-php-ext-disable,docker-php-ext-disable,docker-php-ext-install,docker-php-pecl-install,docker-php-source,phpgosu}; \ + mkdir /home/www-data; \ + chmod 711 /home/www-data; \ + chown www-data:www-data /home/www-data; \ + usermod -d /home/www-data www-data; \ + \ + ## block packages from being installed + { \ + echo 'Package: libjpeg*'; \ + echo 'Pin: release *'; \ + echo 'Pin-Priority: -1'; \ + } > /etc/apt/preferences.d/no-libjpeg; \ + { \ + echo 'Package: libtiff*'; \ + echo 'Pin: release *'; \ + echo 'Pin-Priority: -1'; \ + } > /etc/apt/preferences.d/no-libtiff; \ + { \ + echo 'Package: libwebp*'; \ + echo 'Pin: release *'; \ + echo 'Pin-Priority: -1'; \ + } > /etc/apt/preferences.d/no-libwebp; + +ARG IMAGICK_RUNTIME_REQUIREMENTS="libpng16-16 liblcms2-2 libgomp1 libltdl7 bzip2 gosu brotli" +ARG IMAGICK_RUNTIME_REQUIREMENTS_EXTRA="" +ARG IMAGICK_BUILD_REQUIREMENTS="curl cmake gcc libtool libedit-dev liblcms2-dev build-essential autoconf automake pkg-config libpng-dev libltdl-dev nasm" +ARG IMAGICK_BUILD_REQUIREMENTS_EXTRA="" +ARG IMAGICK_EXTRA_CONFIGURE_ARGS="" + +ARG MOZJPEG_EXTRA_CONFIGURE_ARGS="" +ARG MOZJPEG_VERSION="4.1.1" + +ARG TIFF_VERSION="4.6.0" +ARG TIFF_EXTRA_CONFIGURE_ARGS="" + +ARG WEBP_VERSION="1.4.0" +ARG WEBP_EXTRA_CONFIGURE_ARGS="" + +ARG IMAGICK_VERSION="7.1.1-36" +ARG IMAGICK_EXTRA_CONFIGURE_ARGS="" + +ARG OPENJPEG_VERSION="2.5.2" + +## configure imagick and the dependencies +RUN set -xe; \ + \ + /usr/local/bin/docker-install-requirements imagick; \ + ################################################ + ## install mozjpeg + ################################################ + mkdir -p /tmp/mozjpeg; \ + cd /tmp/mozjpeg; \ + docker-package-download -o mozjpeg.tar.gz -s https://codeload.github.com/mozilla/mozjpeg/tar.gz/v${MOZJPEG_VERSION}; \ + tar --strip 1 -xzf mozjpeg.tar.gz; \ + cmake -G"Unix Makefiles" -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=/usr/lib -DWITH_JPEG8=true; \ + make install prefix=/usr libdir=/usr/lib64 ; \ + ################################################ + ## install tiff + ################################################ + mkdir -p /tmp/tiff; \ + cd /tmp/tiff; \ + docker-package-download -o tiff.tar.gz -s http://download.osgeo.org/libtiff/tiff-${TIFF_VERSION}.tar.gz; \ + tar --strip 1 -xzf tiff.tar.gz; \ + ./configure \ + --prefix=/usr \ + ${TIFF_EXTRA_CONFIGURE_ARGS:-} \ + ; \ + make -j$(nproc); \ + make install; \ + ################################################ + ## install webp + ################################################ + mkdir -p /tmp/libwebp; \ + cd /tmp/libwebp; \ + docker-package-download -o libwebp.tar.gz -s https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-${WEBP_VERSION}.tar.gz; \ + tar --strip 1 -xzf libwebp.tar.gz; \ + ./configure \ + --prefix=/usr \ + ${WEBP_EXTRA_CONFIGURE_ARGS:-} \ + ; \ + make -j$(nproc); \ + make install; \ + ################################################ + ## install openjpeg + ################################################ + mkdir -p /tmp/openjpeg; \ + cd /tmp/openjpeg; \ + docker-package-download -o openjpeg.tar.gz -s https://codeload.github.com/uclouvain/openjpeg/tar.gz/v${OPENJPEG_VERSION}; \ + tar --strip 1 -xzf openjpeg.tar.gz; \ + mkdir build; \ + cd build; \ + cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr; \ + make -j$(nproc); \ + make install; \ + ################################################ + ## install imagick + ################################################ + mkdir -p /tmp/imagemagick; \ + cd /tmp/imagemagick; \ + docker-package-download -o imagemagick.tar.gz -s https://codeload.github.com/ImageMagick/ImageMagick/tar.gz/${IMAGICK_VERSION}; \ + tar --strip 1 -xzf imagemagick.tar.gz; \ + ./configure \ + --prefix=/usr \ + --with-webp \ + --without-perl \ + --without-x \ + --without-xml \ + --without-pango \ + --without-jbig \ + --without-wmf \ + --with-perl=no \ + --with-modules \ + ${IMAGICK_EXTRA_CONFIGURE_ARGS:-} \ + ; \ + make -j$(nproc); \ + make install; \ + /usr/local/bin/docker-layer-clean diff --git a/src/bookworm/src/root/etc/apt/preferences.d/debian_main_cmake b/src/bookworm/src/root/etc/apt/preferences.d/debian_main_cmake new file mode 100644 index 0000000..5f1672f --- /dev/null +++ b/src/bookworm/src/root/etc/apt/preferences.d/debian_main_cmake @@ -0,0 +1,3 @@ +Package: cmake* +Pin: release o=Debian,n=stretch,c=main +Pin-Priority: 600 \ No newline at end of file diff --git a/src/bookworm/src/root/etc/php-src b/src/bookworm/src/root/etc/php-src new file mode 100644 index 0000000..2459c65 --- /dev/null +++ b/src/bookworm/src/root/etc/php-src @@ -0,0 +1,2 @@ +PHP_URL="changeme" +PHP_SHA256="changeme" \ No newline at end of file diff --git a/src/bookworm/src/root/usr/local/bin/docker-entrypoint b/src/bookworm/src/root/usr/local/bin/docker-entrypoint new file mode 100644 index 0000000..a5bffc6 --- /dev/null +++ b/src/bookworm/src/root/usr/local/bin/docker-entrypoint @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +[ "${DEBUG}" = "true" ] && set -x + +if [ -z "${KUBERNETES_SERVICE_HOST}" ]; then + HOST_DOMAIN="host.docker.internal" + ping -q -c1 $HOST_DOMAIN > /dev/null 2>&1 + if [ $? -ne 0 ]; then + HOST_IP=$(ip route | awk 'NR==1 {print $3}') + echo -e "$HOST_IP\t$HOST_DOMAIN" >> /etc/hosts + fi +fi + +set -e + +if [ -n "${PHP_EXT_ENABLE}" ]; then + docker-php-ext-enable ${PHP_EXT_ENABLE} +fi + +DOCKER_UID=$(stat -c "%u" "${DOCUMENT_ROOT}") +DOCKER_GID=$(stat -c "%g" "${DOCUMENT_ROOT}") + +if [[ "${DOCKER_UID}" -ne "33" || "${DOCKER_GID}" -ne "33" ]] && [[ ! -f /root/.uid-gid-fixed && "${FIX_UID_GID}" = "true" ]]; then + CONFLICT_USER=$(getent passwd "${DOCKER_UID}" | cut -d: -f1) + CONFLICT_GROUP=$(getent group "${DOCKER_GID}" | cut -d: -f1) + echo "Docker: uid = ${DOCKER_UID}, gid = ${DOCKER_GID}" + echo "Conflict: user = ${CONFLICT_USER}, group = ${CONFLICT_GROUP}" + # Once we've established the ids and incumbent ids then we need to free them + # up (if necessary) and then make the change to www-data. + CONFLICT_OFFSET=$(( $RANDOM % 10000 + 1)) + [ ! -z "${CONFLICT_USER}" ] && usermod -u $(expr 50000 - "${CONFLICT_OFFSET}" - "${DOCKER_UID}") "${CONFLICT_USER}" + usermod -u "${DOCKER_UID}" www-data + [ ! -z "${CONFLICT_GROUP}" ] && groupmod -g $(expr 50000 - "${CONFLICT_OFFSET}" - "${DOCKER_GID}") "${CONFLICT_GROUP}" + groupmod -g "${DOCKER_GID}" www-data + touch /root/.uid-gid-fixed +fi + +if test -f "/usr/local/bin/docker-entrypoint-custom"; then +source "/usr/local/bin/docker-entrypoint-custom" +fi + +if [ "$1" = "/usr/local/bin/php" ] || [ "$1" = "php" ]; then + exec gosu "${DOCKER_UID}":"${DOCKER_GID}" "$@" +elif [ "$1" = "console" ]; then + set -- "${@:2}" + exec gosu "${DOCKER_UID}":"${DOCKER_GID}" "/bin/bash" "${@}" +elif [ "$1" = "/usr/local/bin/composer" ] || [ "$1" = "composer" ]; then + exec gosu "${DOCKER_UID}":"${DOCKER_GID}" "$@" +elif [ "$1" = "/usr/local/bin/magerun" ] || [ "$1" = "magerun" ]; then + exec gosu "${DOCKER_UID}":"${DOCKER_GID}" "$@" +elif [ "$1" = "/usr/local/bin/magerun2" ] || [ "$1" = "magerun2" ]; then + exec gosu "${DOCKER_UID}":"${DOCKER_GID}" "$@" +elif [ "$1" = "/usr/local/sbin/php-fpm" ] || [ "$1" = "php-fpm" ]; then + exec "$@" +elif [ "$1" = "/usr/sbin/cron" ] || [ "$1" = "cron" ]; then + if test -f "${CRONTAB_CONFIG}"; then + echo "Adding crontab in ${CRONTAB_CONFIG}" + /usr/bin/crontab -u www-data "${CRONTAB_CONFIG}" + fi +fi +exec "$@" diff --git a/src/bookworm/src/root/usr/local/bin/docker-fpm-healthcheck b/src/bookworm/src/root/usr/local/bin/docker-fpm-healthcheck new file mode 100644 index 0000000..b061795 --- /dev/null +++ b/src/bookworm/src/root/usr/local/bin/docker-fpm-healthcheck @@ -0,0 +1,138 @@ +#!/usr/bin/env bash +# vim: set filetype=sh : + +# Author: https://github.com/renatomefi +# The original code lives in https://github.com/renatomefi/php-fpm-healthcheck +# +# A POSIX compliant shell script to healthcheck PHP fpm status, can be used only for pinging the status page +# or check for specific metrics +# +# i.e.: ./php-fpm-healthcheck --verbose --active-processes=6 +# The script will fail in case the 'active processes' is bigger than 6. +# +# You can combine multiple options as well, the first one to fail will fail the healthcheck +# i.e.: ./php-fpm-healthcheck --listen-queue-len=10 --active-processes=6 +# +# Ping mode (exit 0 if php-fpm returned data): ./php-fpm-healthcheck +# +# Ping mode with data (outputs php-fpm status text): ./php-fpm-healthcheck -v +# +# Exit status codes: +# 2,9,111 - Couldn't connect to PHP fpm, is it running? +# 8 - Couldn't reach PHP fpm status page, have you configured it with `pm.status_path = /status`? +# 1 - A healthcheck condition has failed +# 3 - Invalid option given +# 4 - One or more required softwares are missing +# +# Available options: +# -v|--verbose +# +# Metric options, fails in case the CURRENT VALUE is bigger than the GIVEN VALUE +# --accepted-conn=n +# --listen-queue=n +# --max-listen-queue=n +# --idle-processes=n +# --active-processes=n +# --total-processes=n +# --max-active-processes=n +# --max-children-reached=n +# --slow-requests=n +# + +set -eu + +OPTIND=1 # Reset getopt in case it has been used previously in the shell + +# FastCGI variables +export REQUEST_METHOD="GET" +export SCRIPT_NAME="/status" +export SCRIPT_FILENAME="/status" +FCGI_CONNECT_DEFAULT="localhost:9000" + +# Required software +FCGI_CMD_PATH=$(command -v cgi-fcgi) || { >&2 echo "Make sure fcgi is installed (i.e. apk add --no-cache fcgi). Aborting."; exit 4; } +command -v sed 1> /dev/null || { >&2 echo "Make sure sed is installed (i.e. apk add --no-cache busybox). Aborting."; exit 4; } +command -v tail 1> /dev/null || { >&2 echo "Make sure tail is installed (i.e. apk add --no-cache busybox). Aborting."; exit 4; } +command -v grep 1> /dev/null || { >&2 echo "Make sure grep is installed (i.e. apk add --no-cache grep). Aborting."; exit 4; } + +# Get status from fastcgi connection +# $1 - cgi-fcgi connect argument +get_fpm_status() { + if test "$VERBOSE" = 1; then printf "Trying to connect to php-fpm via: %s\\n" "$1"; fi; + + # Since I cannot use pipefail I'll just split these in two commands + FPM_STATUS=$(env -i REQUEST_METHOD="$REQUEST_METHOD" SCRIPT_NAME="$SCRIPT_NAME" SCRIPT_FILENAME="$SCRIPT_FILENAME" "$FCGI_CMD_PATH" -bind -connect "$1" 2> /dev/null) + FPM_STATUS=$(echo "$FPM_STATUS" | tail +5) + + if test "$VERBOSE" = 1; then printf "php-fpm status output:\\n%s\\n" "$FPM_STATUS"; fi; + + if test "$FPM_STATUS" = "File not found."; then + >&2 printf "php-fpm status page non reachable\\n"; + exit 8; + fi; +} + +# $1 - fpm option +# $2 - expected value threshold +check_fpm_health_by() { + OPTION=$(echo "$1" | sed 's/--//g; s/-/ /g;') + VALUE_EXPECTED="$2"; + VALUE_ACTUAL=$(echo "$FPM_STATUS" | grep "^$OPTION" | cut -d: -f2 | sed 's/ //g') + + if test "$VERBOSE" = 1; then printf "'%s' value '%s' and expected is less than '%s'\\n" "$OPTION" "$VALUE_ACTUAL" "$VALUE_EXPECTED"; fi; + + if test "$VALUE_ACTUAL" -gt "$VALUE_EXPECTED"; then + >&2 printf "'%s' value '%s' is greater than expected '%s'\\n" "$OPTION" "$VALUE_ACTUAL" "$VALUE_EXPECTED"; + exit 1; + fi; +} + +PARAM_AMOUNT=0 + +# $1 - fpm option +# $2 - expected value threshold +check_later() { + # The POSIX sh way to check if it's an integer, also the output is supressed since it's polution + if ! test "$2" -eq "$2" 2> /dev/null; then + >&2 printf "'%s' option value must be an integer, '%s' given\\n" "$1" "$2"; exit 3; + fi + + PARAM_AMOUNT=$(( PARAM_AMOUNT + 1 )) + + eval "PARAM_TO_CHECK$PARAM_AMOUNT=$1" + eval "VALUE_TO_CHECK$PARAM_AMOUNT=$2" +} + +# From the PARAM_TO_CHECK/VALUE_TO_CHECK magic variables, do all the checks +check_fpm_health() { + j=1 + while [ $j -le $PARAM_AMOUNT ]; do + eval "CURRENT_PARAM=\$PARAM_TO_CHECK$j" + eval "CURRENT_VALUE=\$VALUE_TO_CHECK$j" + check_fpm_health_by "$CURRENT_PARAM" "$CURRENT_VALUE" + j=$(( j + 1 )) + done +} + +if ! GETOPT=$(getopt -o v --long verbose,accepted-conn:,listen-queue:,max-listen-queue:,listen-queue-len:,idle-processes:,active-processes:,total-processes:,max-active-processes:,max-children-reached:,slow-requests: -n 'php-fpm-healthcheck' -- "$@"); then + >&2 echo "Invalid options, terminating." ; exit 3 +fi; + +eval set -- "$GETOPT" + +FCGI_CONNECT="${FCGI_CONNECT:-$FCGI_CONNECT_DEFAULT}" + +VERBOSE=0 + +while test "$1"; do + case "$1" in + -v|--verbose ) VERBOSE=1; shift ;; + --) shift ; break ;; + * ) check_later "$1" "$2"; shift 2 ;; + esac +done + +FPM_STATUS=false + +get_fpm_status "$FCGI_CONNECT" +check_fpm_health diff --git a/src/bookworm/src/root/usr/local/bin/docker-php-ext-configure b/src/bookworm/src/root/usr/local/bin/docker-php-ext-configure new file mode 100644 index 0000000..011fdd7 --- /dev/null +++ b/src/bookworm/src/root/usr/local/bin/docker-php-ext-configure @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +set -e + +# prefer user supplied CFLAGS, but default to our PHP_CFLAGS +: "${CFLAGS:=$PHP_CFLAGS}" +: "${CPPFLAGS:=$PHP_CPPFLAGS}" +: "${LDFLAGS:=$PHP_LDFLAGS}" +: "${DEBIAN_FRONTEND:-noninteractive}" +export CFLAGS CPPFLAGS LDFLAGS DEBIAN_FRONTEND + +srcExists= +if [ -d /usr/src/php ]; then + srcExists=1 +fi +docker-php-source extract +if [ -z "$srcExists" ]; then + touch /usr/src/php/.docker-delete-me +fi + +cd "${PHP_SRC_DIR}/ext" + +usage() { + echo "usage: $0 ext-name [configure flags]" + echo " ie: $0 gd --with-jpeg-dir=/usr/local/something" + echo + echo 'Possible values for ext-name:' + find . \ + -mindepth 2 \ + -maxdepth 2 \ + -type f \ + -name 'config.m4' \ + | xargs -n1 dirname \ + | xargs -n1 basename \ + | sort \ + | xargs + echo + echo 'Some of the above modules are already compiled into PHP; please check' + echo 'the output of "php -i" to see which modules are already loaded.' +} + +ext="$1" +if [ -z "$ext" ] || [ ! -d "$ext" ]; then + usage >&2 + exit 1 +fi +shift + +if command -v dpkg-architecture > /dev/null; then + gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" + set -- --build="$gnuArch" "$@" +fi + +cd "$ext" +phpize +./configure "$@" diff --git a/src/bookworm/src/root/usr/local/bin/docker-php-ext-disable b/src/bookworm/src/root/usr/local/bin/docker-php-ext-disable new file mode 100644 index 0000000..05df630 --- /dev/null +++ b/src/bookworm/src/root/usr/local/bin/docker-php-ext-disable @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +set -e + +cd "$(php -r 'echo ini_get("extension_dir");')" + +usage() { + echo "usage: $0 [options] module-name [module-name ...]" + echo " ie: $0 gd mysqli" + echo " $0 pdo pdo_mysql" + echo " $0 --ini-name 0-apc.ini apcu apc" + echo + echo 'Possible values for module-name:' + echo $(find -maxdepth 1 -type f -name '*.so' -exec basename '{}' ';' | sort) +} + +opts="$(getopt -o 'h?' --long 'help,ini-name:' -- "$@" || { usage >&2 && false; })" +eval set -- "$opts" + +iniName= +while true; do + flag="$1" + shift + case "$flag" in + --help|-h|'-?') usage && exit 0 ;; + --ini-name) iniName="$1" && shift ;; + --) break ;; + *) + { + echo "error: unknown flag: $flag" + usage + } >&2 + exit 1 + ;; + esac +done + +modules= +for module; do + if [ -z "$module" ]; then + continue + fi + if [ -f "$module.so" ] && ! [ -f "$module" ]; then + # allow ".so" to be optional + module="$module.so" + fi + if ! [ -f "$module" ]; then + echo >&2 "error: $(readlink -f "$module") does not exist" + echo >&2 + usage >&2 + exit 1 + fi + modules="$modules $module" +done + +if [ -z "$modules" ]; then + usage >&2 + exit 1 +fi + +for module in $modules; do + if nm -g "$module" | grep -q ' zend_extension_entry$'; then + # https://wiki.php.net/internals/extensions#loading_zend_extensions + line="zend_extension=$(readlink -f "$module")" + else + line="extension=$module" + fi + + ext="$(basename "$module")" + ext="${ext%.*}" + if ! php -r 'exit(extension_loaded("'"$ext"'") ? 0 : 1);'; then + echo >&2 + echo >&2 "warning: $ext ($module) is already unloaded!" + echo >&2 + continue + fi + + ini="/usr/local/etc/php/conf.d/${iniName:-"docker-php-ext-$ext.ini"}" + if grep -q "$line" "$ini" 2>/dev/null; then + rm -rf "${ini}" + fi +done diff --git a/src/bookworm/src/root/usr/local/bin/docker-php-ext-enable b/src/bookworm/src/root/usr/local/bin/docker-php-ext-enable new file mode 100644 index 0000000..a7e2647 --- /dev/null +++ b/src/bookworm/src/root/usr/local/bin/docker-php-ext-enable @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +set -e + +extDir="$(php -r 'echo ini_get("extension_dir");')" +cd "$extDir" + +usage() { + echo "usage: $0 [options] module-name [module-name ...]" + echo " ie: $0 gd mysqli" + echo " $0 pdo pdo_mysql" + echo " $0 --ini-name 0-apc.ini apcu apc" + echo + echo 'Possible values for module-name:' + find -maxdepth 1 \ + -type f \ + -name '*.so' \ + -exec basename '{}' ';' \ + | sort \ + | xargs + echo + echo 'Some of the above modules are already compiled into PHP; please check' + echo 'the output of "php -i" to see which modules are already loaded.' +} + +opts="$(getopt -o 'h?' --long 'help,ini-name:' -- "$@" || { usage >&2 && false; })" +eval set -- "$opts" + +iniName= +while true; do + flag="$1" + shift + case "$flag" in + --help|-h|'-?') usage && exit 0 ;; + --ini-name) iniName="$1" && shift ;; + --) break ;; + *) + { + echo "error: unknown flag: $flag" + usage + } >&2 + exit 1 + ;; + esac +done + +modules= +for module; do + if [ -z "$module" ]; then + continue + fi + if [ -f "$module.so" ] && ! [ -f "$module" ]; then + # allow ".so" to be optional + module="$module.so" + fi + if ! [ -f "$module" ]; then + echo >&2 "error: '$module' does not exist" + echo >&2 + usage >&2 + exit 1 + fi + modules="$modules $module" +done + +if [ -z "$modules" ]; then + usage >&2 + exit 1 +fi + +for module in $modules; do + if readelf --wide --syms "$module" | grep -q ' zend_extension_entry$'; then + # https://wiki.php.net/internals/extensions#loading_zend_extensions + absModule="$(readlink -f "$module")" + line="zend_extension=$absModule" + else + line="extension=$module" + fi + + ext="$(basename "$module")" + ext="${ext%.*}" + if php -r 'exit(extension_loaded("'"$ext"'") ? 0 : 1);'; then + # this isn't perfect, but it's better than nothing + # (for example, 'opcache.so' presents inside PHP as 'Zend OPcache', not 'opcache') + # echo >&2 + # echo >&2 "warning: $ext ($module) is already loaded!" + # echo >&2 + continue + fi + + ini="$PHP_INI_DIR/conf.d/${iniName:-"docker-php-ext-$ext.ini"}" + if ! grep -q "$line" "$ini" 2>/dev/null; then + echo "$line" >> "$ini" + fi +done diff --git a/src/bookworm/src/root/usr/local/bin/docker-php-ext-install b/src/bookworm/src/root/usr/local/bin/docker-php-ext-install new file mode 100644 index 0000000..795b0f1 --- /dev/null +++ b/src/bookworm/src/root/usr/local/bin/docker-php-ext-install @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +set -e + +# prefer user supplied CFLAGS, but default to our PHP_CFLAGS +: "${CFLAGS:=$PHP_CFLAGS}" +: "${CPPFLAGS:=$PHP_CPPFLAGS}" +: "${LDFLAGS:=$PHP_LDFLAGS}" +: "${DEBIAN_FRONTEND:-noninteractive}" +export CFLAGS CPPFLAGS LDFLAGS DEBIAN_FRONTEND + +srcExists= +if [ -d /usr/src/php ]; then + srcExists=1 +fi +docker-php-source extract +if [ -z "$srcExists" ]; then + touch /usr/src/php/.docker-delete-me +fi + +cd /usr/src/php/ext + +usage() { + echo "usage: $0 [-jN] ext-name [ext-name ...]" + echo " ie: $0 gd mysqli" + echo " $0 pdo pdo_mysql" + echo " $0 -j5 gd mbstring mysqli pdo pdo_mysql shmop" + echo + echo 'if custom ./configure arguments are necessary, see docker-php-ext-configure' + echo + echo 'Possible values for ext-name:' + find . \ + -mindepth 2 \ + -maxdepth 2 \ + -type f \ + -name 'config.m4' \ + | xargs -n1 dirname \ + | xargs -n1 basename \ + | sort \ + | xargs + echo + echo 'Some of the above modules are already compiled into PHP; please check' + echo 'the output of "php -i" to see which modules are already loaded.' +} + +opts="$(getopt -o 'h?j:' --long 'help,jobs:' -- "$@" || { usage >&2 && false; })" +eval set -- "$opts" + +j=1 +while true; do + flag="$1" + shift + case "$flag" in + --help|-h|'-?') usage && exit 0 ;; + --jobs|-j) j="$1" && shift ;; + --) break ;; + *) + { + echo "error: unknown flag: $flag" + usage + } >&2 + exit 1 + ;; + esac +done + +exts= +for ext; do + if [ -z "$ext" ]; then + continue + fi + if [ ! -d "$ext" ]; then + echo >&2 "error: $PWD/$ext does not exist" + echo >&2 + usage >&2 + exit 1 + fi + exts="$exts $ext" +done + +if [ -z "$exts" ]; then + usage >&2 + exit 1 +fi + +popDir="$PWD" +for ext in $exts; do + cd "$ext" + [ -e Makefile ] || docker-php-ext-configure "$ext" + make -j"$j" + make -j"$j" install + find modules \ + -maxdepth 1 \ + -name '*.so' \ + -exec basename '{}' ';' \ + | xargs -r docker-php-ext-enable + make -j"$j" clean + cd "$popDir" +done + +if [ -e /usr/src/php/.docker-delete-me ]; then + docker-php-source delete +fi diff --git a/src/bookworm/src/root/usr/local/bin/docker-php-pecl-install b/src/bookworm/src/root/usr/local/bin/docker-php-pecl-install new file mode 100644 index 0000000..5ae61f9 --- /dev/null +++ b/src/bookworm/src/root/usr/local/bin/docker-php-pecl-install @@ -0,0 +1,81 @@ +#!/usr/bin/env bash + +set -o errexit # Exit script when a command exits with non-zero status +set -o errtrace # Exit on error inside any functions or sub-shells +set -o nounset # Exit script on use of an undefined variable +set -o pipefail # Return exit status of the last command in the pipe that failed + +declare -a pecl_build_ext=() + +readonly EX_OK=0 # Successful termination +readonly EX_UNKNOWN=1 # Unknown error occured + +usage() { + echo "usage: $0 [-jN] pecl-ext-name [pecl-ext-name ...]" + echo " ie: $0 xdebug imagick" + echo " $0 xdebug=2.6.1 imagick=3.4.3" + echo " $0 -j$(nproc) xdebug=2.6.1 imagick=3.4.3" + echo + echo + echo "Possible values for pecl-ext-name:" + echo $(pecl remote-list | tail -n +4 | awk '{print $1}' | sort | xargs) + echo + echo "Some of the above modules are maybe already compiled into PHP; please check" + echo "the output of "php -i" to see which modules are already loaded." +} + +opts="$(getopt -o 'h?j:' --long 'help,jobs:' -- "$@" || { usage >&2 && false; })" +eval set -- "$opts" + +j=1 +while true; do + flag="$1" + shift + case "$flag" in + --help|-h|'-?') usage && exit 0 ;; + --jobs|-j) j="$1" && shift ;; + --) break ;; + *) + { + echo "error: unknown flag: $flag" + usage + } >&2 + exit 1 + ;; + esac +done + +# prefer user supplied CFLAGS, but default to our PHP_CFLAGS +: "${CFLAGS:=$PHP_CFLAGS}"docker +: "${CPPFLAGS:=$PHP_CPPFLAGS}" +: "${LDFLAGS:=$PHP_LDFLAGS}" +: "${DEBIAN_FRONTEND:-noninteractive}" +export CFLAGS CPPFLAGS LDFLAGS DEBIAN_FRONTEND + +if [[ ! -d ${PHP_SRC_DIR} ]]; then + docker-install-requirements phpize + docker-php-source extract + touch ${PHP_SRC_DIR}/.docker-delete-me +fi + +for ext in "$@"; do + pecl_build_ext+=($ext) +done + +echo "${pecl_build_ext[@]}" +if [[ ${#pecl_build_ext[@]} -eq 0 ]]; then + usage >&2 + exit 1 +else + pecl channel-update pecl.php.net +fi + +for pecl_ext in ${pecl_build_ext[@]}; do + echo ${pecl_ext} + yes '' | pecl install -f ${pecl_ext} || EXIT_CODE=$? && true; + if [[ "${EXIT_CODE}" != "141" && "${EXIT_CODE}" != "0" ]]; then + exit "${EX_UNKNOWN}" + fi +done + +exit "${EX_OK}" diff --git a/src/bookworm/src/root/usr/local/bin/docker-php-source b/src/bookworm/src/root/usr/local/bin/docker-php-source new file mode 100644 index 0000000..59b3f3f --- /dev/null +++ b/src/bookworm/src/root/usr/local/bin/docker-php-source @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +set -o errexit # Exit script when a command exits with non-zero status +set -o errtrace # Exit on error inside any functions or sub-shells +set -o nounset # Exit script on use of an undefined variable +set -o pipefail # Return exit status of the last command in the pipe that failed + +readonly EX_OK=0 # Successful termination +readonly EX_UNKNOWN=1 # Unknown error occurred + +usage() { + echo "usage: $0 COMMAND" + echo + echo "Manage php source tarball lifecycle." + echo + echo "Commands:" + echo " extract extract php source tarball into directory ${PHP_SRC_DIR} if not already done." + echo " delete delete extracted php source located into ${PHP_SRC_DIR} if not already done." + echo +} + +case "${1:-}" in + extract) + mkdir -p "${PHP_SRC_DIR}" + if [[ ! -f "${PHP_SRC_DIR}/.docker-extracted" ]]; then + docker-package-download -o "${PHP_SRC_FILE}" "https://www.php.net/distributions/php-${PHP_VERSION}.tar.gz" + tar --strip-components=1 -C "${PHP_SRC_DIR}" -xzf "${PHP_SRC_FILE}" + touch "${PHP_SRC_DIR}/.docker-extracted" + fi + ;; + + delete) + rm -rf "${PHP_SRC_DIR}" + rm -rf "${PHP_SRC_FILE}" + rm -rf "${PHP_SRC_DIR}/.docker-extracted" + ;; + + *) + usage + exit ${EX_UNKNOWN} + ;; +esac diff --git a/src/bookworm/src/root/usr/local/bin/docker-php-source-prepare b/src/bookworm/src/root/usr/local/bin/docker-php-source-prepare new file mode 100644 index 0000000..7417165 --- /dev/null +++ b/src/bookworm/src/root/usr/local/bin/docker-php-source-prepare @@ -0,0 +1,112 @@ +#!/usr/bin/env bash + +set -o errexit # Exit script when a command exits with non-zero status +set -o errtrace # Exit on error inside any functions or sub-shells +set -o nounset # Exit script on use of an undefined variable +set -o pipefail # Return exit status of the last command in the pipe that failed +set -x + +readonly EX_OK=0 # Successful termination +readonly EX_UNKNOWN=1 # Unknown error occurred + + +display_error_message() { + local status=${1} + local exitcode=${2:-0} + + echo >&2 + echo " ! ERROR: ${status}" + echo >&2 + + if [[ ${exitcode} -ne 0 ]]; then + exit "${exitcode}" + fi +} + +# ------------------------------------------------------------------------------ +# Displays a notice +# +# Arguments: +# $* Notice message to display +# Returns: +# Exit code +# ------------------------------------------------------------------------------ +display_notice_message() { + local status=$* + + echo + echo "NOTICE: ${status}" + echo +} + +# ------------------------------------------------------------------------------ +# Displays a status message +# +# Arguments: +# $* Status message to display +# Returns: +# Exit code +# ------------------------------------------------------------------------------ +display_status_message() { + local status=$* + + echo "-----> ${status}" +} + +if [[ "${PHP_VERSION}" =~ ^([0-9]+).(([0-9]+)(.([0-9]+))?)(-(rc[0-9]+))?(-([0-9a-z_]+))?$ ]]; then + majorVersion=${BASH_REMATCH[1]} + minorVersion=${BASH_REMATCH[3]} + mainlineVersion="${BASH_REMATCH[1]}.${BASH_REMATCH[3]}" + patchVersion=${BASH_REMATCH[5]} + rcVersion=$(echo "${BASH_REMATCH[7]}" | tr '[a-z]' '[A-Z]') + type=$(echo "${BASH_REMATCH[9]}" | tr '[A-Z]' '[a-z]') + targetFolder="${majorVersion}.${minorVersion}" + sourceFolder="${majorVersion}.${minorVersion}" + echo "######################## current image ########################" + echo "Version: ${PHP_VERSION%%-*}" + echo "Major Version: ${majorVersion}" + echo "Minor Version: ${minorVersion}" + if [[ -z "${rcVersion}" ]]; then + phpApiUrl="https://secure.php.net/releases/index.php?json&max=100&version=${PHP_VERSION%%-*}" + ## recover / workaround dot maybe + phpApiJqExpr=' + (keys[] | select(startswith("'"${PHP_VERSION%%-*}"'"))) as $version + | [ $version, ( + .[$version].source[] + | select(.filename | endswith(".tar.gz")) + | + "https://secure.php.net/get/" + .filename + "/from/this/mirror", + .sha256 // "" + ) ] + ' + else + phpApiUrl='https://qa.php.net/api.php?type=qa-releases&format=json' + phpApiJqExpr=' + .releases[] + | select(.version | startswith("'"${PHP_VERSION%%-*}"'")) + | [ + .version, + .files.gz.path // "", + .files.gz.sha256 // "" + ] + ' + fi + IFS=$'\n' + releases=( $(curl -fsSL "${phpApiUrl}" | jq --raw-output "${phpApiJqExpr} | @sh" | sort -rV ) ) + unset IFS + if [ "${#releases[@]}" -eq 0 ]; then + display_error_message "Unable to determine available releases of ${PHP_VERSION}" "${EX_UNKNOWN}" + fi + eval "possi=( ${releases[0]} )" + phpDownloadFullVersion="${possi[0]}" + phpDownloadUrl="${possi[1]}" + phpDownloadSha256="${possi[2]}" + echo "PHP Download URL: ${phpDownloadUrl}" + echo "PHP Download Sha256: ${phpDownloadSha256}" + echo "PHP Download Version: ${phpDownloadFullVersion}" + sed -ri \ + -e 's/([\s]+)?PHP_URL="(.*)"/\1PHP_URL="'"${phpDownloadUrl//\//\\/}"'"/' \ + -e 's/([\s]+)?PHP_SHA256="([a-z0-9]+)"/\1PHP_SHA256="'"${phpDownloadSha256}"'"/' \ + /etc/php-src + +fi diff --git a/src/bookworm/src/root/usr/local/bin/phpgosu b/src/bookworm/src/root/usr/local/bin/phpgosu new file mode 100644 index 0000000..aacd7ec --- /dev/null +++ b/src/bookworm/src/root/usr/local/bin/phpgosu @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +DOCKER_UID=$(id -u) +if [ "${DOCKER_UID}" -eq "0" ]; then + exec /usr/sbin/gosu www-data:www-data php "${@}" +fi +php "${@}" diff --git a/src/bookworm/src/root/usr/local/etc/php-src b/src/bookworm/src/root/usr/local/etc/php-src new file mode 100644 index 0000000..2459c65 --- /dev/null +++ b/src/bookworm/src/root/usr/local/etc/php-src @@ -0,0 +1,2 @@ +PHP_URL="changeme" +PHP_SHA256="changeme" \ No newline at end of file diff --git a/src/bullseye/src/Dockerfile b/src/bullseye/src/Dockerfile index 138ab92..b89cd45 100644 --- a/src/bullseye/src/Dockerfile +++ b/src/bullseye/src/Dockerfile @@ -33,20 +33,20 @@ ARG IMAGICK_BUILD_REQUIREMENTS="curl cmake gcc libtool libedit-dev liblcms2-dev ARG IMAGICK_BUILD_REQUIREMENTS_EXTRA="" ARG IMAGICK_EXTRA_CONFIGURE_ARGS="" +ARG MOZJPEG_VERSION="4.1.1" ARG MOZJPEG_EXTRA_CONFIGURE_ARGS="" -ARG MOZJPEG_VERSION="4.0.3" +ARG TIFF_VERSION="4.6.0" ARG TIFF_EXTRA_CONFIGURE_ARGS="" -ARG TIFF_VERSION="4.3.0" -ARG WEBP_VERSION="1.2.2" +ARG WEBP_VERSION="1.4.0" ARG WEBP_EXTRA_CONFIGURE_ARGS="" -ARG OPENJPEG_VERSION="2.4.0" - -ARG IMAGICK_VERSION="7.1.0-25" +ARG IMAGICK_VERSION="7.1.1-36" ARG IMAGICK_EXTRA_CONFIGURE_ARGS="" +ARG OPENJPEG_VERSION="2.5.2" + ## configure imagick and the dependencies RUN set -xe; \ \ diff --git a/src/buster/src/Dockerfile b/src/buster/src/Dockerfile index 5b884e1..a02d2ac 100644 --- a/src/buster/src/Dockerfile +++ b/src/buster/src/Dockerfile @@ -34,20 +34,20 @@ ARG IMAGICK_BUILD_REQUIREMENTS="curl cmake gcc libtool libedit-dev liblcms2-dev ARG IMAGICK_BUILD_REQUIREMENTS_EXTRA="" ARG IMAGICK_EXTRA_CONFIGURE_ARGS="" +ARG MOZJPEG_VERSION="4.1.1" ARG MOZJPEG_EXTRA_CONFIGURE_ARGS="" -ARG MOZJPEG_VERSION="4.0.3" +ARG TIFF_VERSION="4.6.0" ARG TIFF_EXTRA_CONFIGURE_ARGS="" -ARG TIFF_VERSION="4.3.0" -ARG WEBP_VERSION="1.2.2" +ARG WEBP_VERSION="1.4.0" ARG WEBP_EXTRA_CONFIGURE_ARGS="" -ARG OPENJPEG_VERSION="2.4.0" - -ARG IMAGICK_VERSION="7.1.0-25" +ARG IMAGICK_VERSION="7.1.1-36" ARG IMAGICK_EXTRA_CONFIGURE_ARGS="" +ARG OPENJPEG_VERSION="2.5.2" + ## configure imagick and the dependencies RUN set -xe; \ \