From 96ab4c2a14f39567df53b14696b5cc471dae0ad2 Mon Sep 17 00:00:00 2001 From: cytopia Date: Wed, 2 Feb 2022 18:51:26 +0100 Subject: [PATCH] Initial Release --- .dockerignore | 2 + .editorconfig | 32 ++++ .github/workflows/build.yml | 188 ++++++++++++++++++++++ .github/workflows/lint.yml | 39 +++++ .github/workflows/nightly.yml | 191 ++++++++++++++++++++++ .gitignore | 86 ++++++++++ Dockerfile | 289 ++++++++++++++++++++++++++++++++++ LICENSE | 21 +++ Makefile | 101 ++++++++++++ data/docker-php-entrypoint | 9 ++ data/docker-php-ext-configure | 69 ++++++++ data/docker-php-ext-enable | 114 ++++++++++++++ data/docker-php-ext-install | 122 ++++++++++++++ data/docker-php-source | 34 ++++ tests/get-modules.sh | 17 ++ tests/test.sh | 10 ++ 16 files changed, 1324 insertions(+) create mode 100644 .dockerignore create mode 100644 .editorconfig create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/nightly.yml create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 Makefile create mode 100755 data/docker-php-entrypoint create mode 100755 data/docker-php-ext-configure create mode 100755 data/docker-php-ext-enable create mode 100755 data/docker-php-ext-install create mode 100755 data/docker-php-source create mode 100755 tests/get-modules.sh create mode 100755 tests/test.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2162f14 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +**/*.md +.git diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c232cd6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,32 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# ------------------------------------------------------------------------------------------------- +# Default configuration +# ------------------------------------------------------------------------------------------------- +# top-most EditorConfig file +root = true + +# Default for all files +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_size = 4 +indent_style = tab + +# ------------------------------------------------------------------------------------------------- +# Git Repository +# ------------------------------------------------------------------------------------------------- +[*.yml] +indent_style = space +indent_size = 2 + +[Makefile] +indent_style = tab +indent_size = 4 + +[*.md] +indent_style = space +trim_trailing_whitespace = false +indent_size = 2 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..cb718a9 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,188 @@ +--- + +# ------------------------------------------------------------------------------------------------- +# Job Name +# ------------------------------------------------------------------------------------------------- +name: build + + +# ------------------------------------------------------------------------------------------------- +# When to run +# ------------------------------------------------------------------------------------------------- +on: + # Runs on Pull Requests + pull_request: + # Runs on Push + push: + + +# ------------------------------------------------------------------------------------------------- +# What to run +# ------------------------------------------------------------------------------------------------- +jobs: + build: + name: "[ PHP-${{ matrix.version }} ]" + runs-on: ubuntu-latest + strategy: + fail-fast: False + matrix: + version: + - '8.2' + steps: + + # ------------------------------------------------------------ + # Setup repository + # ------------------------------------------------------------ + - name: Checkout repository + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Set variables + id: vars + run: | + + # Retrieve git info (tags, etc) + git fetch --all + + # Branch, Tag or Commit + GIT_TYPE="$( \ + curl -sS https://raw.githubusercontent.com/cytopia/git-tools/master/git-info.sh \ + | sh \ + | grep '^GIT_TYPE' \ + | sed 's|.*=||g' \ + )" + # Branch name, Tag name or Commit Hash + GIT_SLUG="$( \ + curl -sS https://raw.githubusercontent.com/cytopia/git-tools/master/git-info.sh \ + | sh \ + | grep '^GIT_NAME' \ + | sed 's|.*=||g' \ + )" + # Docker Tag + if [ "${GIT_TYPE}" = "BRANCH" ] && [ "${GIT_SLUG}" = "master" ]; then + DOCKER_TAG="latest" + else + DOCKER_TAG="${GIT_SLUG}" + fi + + # Output + echo "GIT_TYPE=${GIT_TYPE}" + echo "GIT_SLUG=${GIT_SLUG}" + echo "DOCKER_TAG=${DOCKER_TAG}" + + # Export variable + # https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#environment-files + echo "GIT_TYPE=${GIT_TYPE}" >> ${GITHUB_ENV} + echo "GIT_SLUG=${GIT_SLUG}" >> ${GITHUB_ENV} + echo "DOCKER_TAG=${DOCKER_TAG}" >> ${GITHUB_ENV} + + + # ------------------------------------------------------------ + # Build + # ------------------------------------------------------------ + - name: Build + run: | + retry() { + for n in $(seq ${RETRIES}); do + echo "[${n}/${RETRIES}] ${*}"; + if eval "${*}"; then + echo "[SUCC] ${n}/${RETRIES}"; + return 0; + fi; + sleep ${PAUSE}; + echo "[FAIL] ${n}/${RETRIES}"; + done; + return 1; + } + retry make build + env: + RETRIES: 20 + PAUSE: 10 + + # ------------------------------------------------------------ + # Test + # ------------------------------------------------------------ + - name: Test Docker Image + run: | + retry() { + for n in $(seq ${RETRIES}); do + echo "[${n}/${RETRIES}] ${*}"; + if eval "${*}"; then + echo "[SUCC] ${n}/${RETRIES}"; + return 0; + fi; + sleep ${PAUSE}; + echo "[FAIL] ${n}/${RETRIES}"; + done; + return 1; + } + retry make test + env: + RETRIES: 20 + PAUSE: 10 + + - name: Test Repository Readme + run: | + retry() { + for n in $(seq ${RETRIES}); do + echo "[${n}/${RETRIES}] ${*}"; + if eval "${*}"; then + echo "[SUCC] ${n}/${RETRIES}"; + return 0; + fi; + sleep ${PAUSE}; + echo "[FAIL] ${n}/${RETRIES}"; + done; + return 1; + } + retry make update-readme + git diff --quiet || { echo "Build Changes"; git diff; git status; false; } + env: + RETRIES: 20 + PAUSE: 10 + + + # ------------------------------------------------------------ + # Deploy + # ------------------------------------------------------------ + - name: Publish images (only repo owner) + run: | + retry() { + for n in $(seq ${RETRIES}); do + echo "[${n}/${RETRIES}] ${*}"; + if eval "${*}"; then + echo "[SUCC] ${n}/${RETRIES}"; + return 0; + fi; + sleep ${PAUSE}; + echo "[FAIL] ${n}/${RETRIES}"; + done; + return 1; + } + + # Output + echo "GIT_TYPE=${GIT_TYPE}" + echo "GIT_SLUG=${GIT_SLUG}" + echo "DOCKER_TAG=${DOCKER_TAG}" + + # Tag image + retry make tag TAG=${DOCKER_TAG} + docker images + + # Login and Push + retry make login USER=${{ secrets.DOCKERHUB_USERNAME }} PASS=${{ secrets.DOCKERHUB_PASSWORD }} + retry make push TAG=${DOCKER_TAG} + + env: + RETRIES: 20 + PAUSE: 10 + # https://help.github.com/en/github/automating-your-workflow-with-github-actions/contexts-and-expression-syntax-for-github-actions#functions + if: github.event.pull_request.base.repo.id == github.event.pull_request.head.repo.id + && ( + (github.event_name == 'schedule' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/'))) + || + (github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/'))) + || + (github.event_name == 'push' && startsWith(github.ref, 'refs/heads/release-')) + ) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..57a401b --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,39 @@ +--- + +# ------------------------------------------------------------------------------------------------- +# Job Name +# ------------------------------------------------------------------------------------------------- +name: lint + + +# ------------------------------------------------------------------------------------------------- +# When to run +# ------------------------------------------------------------------------------------------------- +on: + # Runs on Pull Requests + pull_request: + + +# ------------------------------------------------------------------------------------------------- +# What to run +# ------------------------------------------------------------------------------------------------- +jobs: + lint: + name: "Lint" + runs-on: ubuntu-latest + steps: + # ------------------------------------------------------------ + # Setup repository + # ------------------------------------------------------------ + - name: Checkout repository + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + # ------------------------------------------------------------ + # Lint repository + # ------------------------------------------------------------ + - name: Lint workflow + id: vars + run: | + make lint-workflow diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 0000000..38979cc --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,191 @@ +--- + +# ------------------------------------------------------------------------------------------------- +# Job Name +# ------------------------------------------------------------------------------------------------- +name: nightly + + +# ------------------------------------------------------------------------------------------------- +# When to run +# ------------------------------------------------------------------------------------------------- +on: + # Runs daily + schedule: + - cron: '0 0 * * *' + + +# ------------------------------------------------------------------------------------------------- +# What to run +# ------------------------------------------------------------------------------------------------- +jobs: + nightly: + name: "[ PHP-${{ matrix.version }} ] (ref: ${{ matrix.refs }})" + runs-on: ubuntu-latest + strategy: + fail-fast: False + matrix: + version: + - '8.2' + refs: + - 'master' + - '0.1' + steps: + + # ------------------------------------------------------------ + # Setup repository + # ------------------------------------------------------------ + - name: Checkout repository + uses: actions/checkout@v2 + with: + fetch-depth: 0 + ref: ${{ matrix.refs }} + + - name: Set variables + id: vars + run: | + + # Retrieve git info (tags, etc) + git fetch --all + + # Branch, Tag or Commit + GIT_TYPE="$( \ + curl -sS https://raw.githubusercontent.com/cytopia/git-tools/master/git-info.sh \ + | sh \ + | grep '^GIT_TYPE' \ + | sed 's|.*=||g' \ + )" + # Branch name, Tag name or Commit Hash + GIT_SLUG="$( \ + curl -sS https://raw.githubusercontent.com/cytopia/git-tools/master/git-info.sh \ + | sh \ + | grep '^GIT_NAME' \ + | sed 's|.*=||g' \ + )" + # Docker Tag + if [ "${GIT_TYPE}" = "BRANCH" ] && [ "${GIT_SLUG}" = "master" ]; then + DOCKER_TAG="latest" + else + DOCKER_TAG="${GIT_SLUG}" + fi + + # Output + echo "GIT_TYPE=${GIT_TYPE}" + echo "GIT_SLUG=${GIT_SLUG}" + echo "DOCKER_TAG=${DOCKER_TAG}" + + # Export variable + # https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#environment-files + echo "GIT_TYPE=${GIT_TYPE}" >> ${GITHUB_ENV} + echo "GIT_SLUG=${GIT_SLUG}" >> ${GITHUB_ENV} + echo "DOCKER_TAG=${DOCKER_TAG}" >> ${GITHUB_ENV} + + + # ------------------------------------------------------------ + # Build + # ------------------------------------------------------------ + - name: Build + run: | + retry() { + for n in $(seq ${RETRIES}); do + echo "[${n}/${RETRIES}] ${*}"; + if eval "${*}"; then + echo "[SUCC] ${n}/${RETRIES}"; + return 0; + fi; + sleep ${PAUSE}; + echo "[FAIL] ${n}/${RETRIES}"; + done; + return 1; + } + retry make build + env: + RETRIES: 20 + PAUSE: 10 + + # ------------------------------------------------------------ + # Test + # ------------------------------------------------------------ + - name: Test Docker Image + run: | + retry() { + for n in $(seq ${RETRIES}); do + echo "[${n}/${RETRIES}] ${*}"; + if eval "${*}"; then + echo "[SUCC] ${n}/${RETRIES}"; + return 0; + fi; + sleep ${PAUSE}; + echo "[FAIL] ${n}/${RETRIES}"; + done; + return 1; + } + retry make test + env: + RETRIES: 20 + PAUSE: 10 + + - name: Test Repository Readme + run: | + retry() { + for n in $(seq ${RETRIES}); do + echo "[${n}/${RETRIES}] ${*}"; + if eval "${*}"; then + echo "[SUCC] ${n}/${RETRIES}"; + return 0; + fi; + sleep ${PAUSE}; + echo "[FAIL] ${n}/${RETRIES}"; + done; + return 1; + } + retry make update-readme + git diff --quiet || { echo "Build Changes"; git diff; git status; false; } + env: + RETRIES: 20 + PAUSE: 10 + + + # ------------------------------------------------------------ + # Deploy + # ------------------------------------------------------------ + - name: Publish images (only repo owner) + run: | + retry() { + for n in $(seq ${RETRIES}); do + echo "[${n}/${RETRIES}] ${*}"; + if eval "${*}"; then + echo "[SUCC] ${n}/${RETRIES}"; + return 0; + fi; + sleep ${PAUSE}; + echo "[FAIL] ${n}/${RETRIES}"; + done; + return 1; + } + + # Output + echo "GIT_TYPE=${GIT_TYPE}" + echo "GIT_SLUG=${GIT_SLUG}" + echo "DOCKER_TAG=${DOCKER_TAG}" + + # Tag image + retry make tag TAG=${DOCKER_TAG} + docker images + + # Login and Push + retry make login USER=${{ secrets.DOCKERHUB_USERNAME }} PASS=${{ secrets.DOCKERHUB_PASSWORD }} + retry make push TAG=${DOCKER_TAG} + + env: + RETRIES: 20 + PAUSE: 10 + # https://help.github.com/en/github/automating-your-workflow-with-github-actions/contexts-and-expression-syntax-for-github-actions#functions + if: github.event.pull_request.base.repo.id == github.event.pull_request.head.repo.id + && ( + (github.event_name == 'schedule' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/'))) + || + (github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/'))) + || + (github.event_name == 'push' && startsWith(github.ref, 'refs/heads/release-')) + ) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cc3a8c2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,86 @@ +# Note: +# To effectively apply the changes you will have +# to re-index the git index (if there are already +# commited files) +# +# $ git rm -r --cached . +# $ git add . +# $ git commit -m ".gitignore index rebuild" +# + + +###################################### +# CUSTOM +###################################### + + +###################################### +# GENERIC +###################################### + +###### std ###### +.lock +*.log + +###### patches/diffs ###### +*.patch +*.diff +*.orig +*.rej + + +###################################### +# Operating Systems +###################################### + +###### OSX ###### +._* +.DS* +.Spotlight-V100 +.Trashes + +###### Windows ###### +Thumbs.db +ehthumbs.db +Desktop.ini +$RECYCLE.BIN/ +*.lnk +*.shortcut + +###################################### +# Editors +###################################### + +###### Sublime ###### +*.sublime-workspace +*.sublime-project + +###### Eclipse ###### +.classpath +.buildpath +.project +.settings/ + +###### Netbeans ###### +/nbproject/ + +###### Intellij IDE ###### +.idea/ +.idea_modules/ + +###### vim ###### +*.swp +*.swo +*.swn +*.swm +*~ + +###### TextMate ###### +.tm_properties +*.tmproj + +###### BBEdit ###### +*.bbprojectd +*.bbproject + +.vagrant diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..82d3ccc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,289 @@ +# +# NOTE: THIS DOCKERFILE IS GENERATED VIA "apply-templates.sh" +# +# PLEASE DO NOT EDIT IT DIRECTLY. +# + +FROM debian:bullseye-slim + +# prevent Debian's PHP packages from being installed +# https://github.com/docker-library/php/pull/542 +RUN set -eux; \ + { \ + echo 'Package: php*'; \ + echo 'Pin: release *'; \ + echo 'Pin-Priority: -1'; \ + } > /etc/apt/preferences.d/no-debian-php + +# dependencies required for running "phpize" +# (see persistent deps below) +ENV PHPIZE_DEPS \ + autoconf \ + dpkg-dev \ + file \ + g++ \ + gcc \ + libc-dev \ + make \ + pkg-config \ + re2c + +# persistent / runtime deps +RUN set -eux; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + $PHPIZE_DEPS \ + ca-certificates \ + curl \ + xz-utils \ + ; \ + rm -rf /var/lib/apt/lists/* + +ENV PHP_INI_DIR /usr/local/etc/php +RUN set -eux; \ + mkdir -p "$PHP_INI_DIR/conf.d"; \ +# allow running as an arbitrary user (https://github.com/docker-library/php/issues/743) + [ ! -d /var/www/html ]; \ + mkdir -p /var/www/html; \ + chown www-data:www-data /var/www/html; \ + chmod 777 /var/www/html + +ENV PHP_EXTRA_CONFIGURE_ARGS --enable-fpm --with-fpm-user=www-data --with-fpm-group=www-data --disable-cgi + +# Apply stack smash protection to functions using local buffers and alloca() +# Make PHP's main executable position-independent (improves ASLR security mechanism, and has no performance impact on x86_64) +# Enable optimization (-O2) +# Enable linker optimization (this sorts the hash buckets to improve cache locality, and is non-default) +# https://github.com/docker-library/php/issues/272 +# -D_LARGEFILE_SOURCE and -D_FILE_OFFSET_BITS=64 (https://www.php.net/manual/en/intro.filesystem.php) +ENV PHP_CFLAGS="-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64" +ENV PHP_CPPFLAGS="$PHP_CFLAGS" +ENV PHP_LDFLAGS="-Wl,-O1 -pie" + +#ENV GPG_KEYS 1729F83938DA44E27BA0F4D3DBDB397470D12172 BFDDD28642824F8118EF77909B67A5C12229118F + +#ENV PHP_VERSION 8.0.0 +#ENV PHP_URL="https://www.php.net/distributions/php-8.0.0.tar.xz" PHP_ASC_URL="https://www.php.net/distributions/php-8.0.0.tar.xz.asc" +#ENV PHP_SHA256="b5278b3eef584f0c075d15666da4e952fa3859ee509d6b0cc2ed13df13f65ebb" + +RUN set -eux; \ + \ + savedAptMark="$(apt-mark showmanual)"; \ + apt-get update; \ + apt-get install -y --no-install-recommends gnupg dirmngr git; \ + rm -rf /var/lib/apt/lists/*; \ + \ + mkdir -p /usr/src; \ + cd /usr/src; \ + \ + #curl -fsSL -o php.tar.xz "$PHP_URL"; \ + #\ +# if [ -n "$PHP_SHA256" ]; then \ +# echo "$PHP_SHA256 *php.tar.xz" | sha256sum -c -; \ +# fi; \ +# \ +# if [ -n "$PHP_ASC_URL" ]; then \ + # curl -fsSL -o php.tar.xz.asc "$PHP_ASC_URL"; \ +# export GNUPGHOME="$(mktemp -d)"; \ +# for key in $GPG_KEYS; do \ + # gpg --batch --keyserver ha.pool.sks-keyservers.net --recv-keys "$key"; \ +# done; \ +# gpg --batch --verify php.tar.xz.asc php.tar.xz; \ + # gpgconf --kill all; \ +# rm -rf "$GNUPGHOME"; \ +# fi; \ + git clone https://github.com/php/php-src php; \ + cd php; \ + # git checkout "$( \ + # git for-each-ref --format='%(refname)' refs/tags \ + # | grep -E 'refs/tags/php-8\.1[.0-9]+$' \ + # | sed 's|.*tags/||g' \ + # | sort -V \ + # | tail -1 \ + # )"; \ + ./buildconf --force; \ + rm -rf .git; \ + cd /usr/src; \ + tar -cJf php.tar.xz php; \ + rm -rf php; \ + \ + apt-mark auto '.*' > /dev/null; \ + apt-mark manual $savedAptMark > /dev/null; \ + apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false + +COPY data/docker-php-source /usr/local/bin/ + +RUN set -eux; \ + \ + savedAptMark="$(apt-mark showmanual)"; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + bison \ + libargon2-dev \ + libcurl4-openssl-dev \ + libedit-dev \ + libffi-dev \ + libonig-dev \ + libreadline-dev \ + libsodium-dev \ + libsqlite3-dev \ + libssl-dev \ + libxml2-dev \ + zlib1g-dev \ + ${PHP_EXTRA_BUILD_DEPS:-} \ + ; \ + rm -rf /var/lib/apt/lists/*; \ + \ + export \ + CFLAGS="$PHP_CFLAGS" \ + CPPFLAGS="$PHP_CPPFLAGS" \ + LDFLAGS="$PHP_LDFLAGS" \ + ; \ + docker-php-source extract; \ + cd /usr/src/php; \ + gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; \ + debMultiarch="$(dpkg-architecture --query DEB_BUILD_MULTIARCH)"; \ +# https://bugs.php.net/bug.php?id=74125 + if [ ! -d /usr/include/curl ]; then \ + ln -sT "/usr/include/$debMultiarch/curl" /usr/local/include/curl; \ + fi; \ + ./configure \ + --build="$gnuArch" \ + --with-config-file-path="$PHP_INI_DIR" \ + --with-config-file-scan-dir="$PHP_INI_DIR/conf.d" \ + \ +# make sure invalid --configure-flags are fatal errors instead of just warnings + --enable-option-checking=fatal \ + \ +# https://github.com/docker-library/php/issues/439 + --with-mhash \ + \ +# https://github.com/docker-library/php/issues/822 + --with-pic \ + \ +# --enable-ftp is included here because ftp_ssl_connect() needs ftp to be compiled statically (see https://github.com/docker-library/php/issues/236) + --enable-ftp \ +# --enable-mbstring is included here because otherwise there's no way to get pecl to use it properly (see https://github.com/docker-library/php/issues/195) + --enable-mbstring \ +# --enable-mysqlnd is included here because it's harder to compile after the fact than extensions are (since it's a plugin for several extensions, not an extension in itself) + --enable-mysqlnd \ +# https://wiki.php.net/rfc/argon2_password_hash (7.2+) + --with-password-argon2 \ +# https://wiki.php.net/rfc/libsodium + --with-sodium=shared \ +# always build against system sqlite3 (https://github.com/php/php-src/commit/6083a387a81dbbd66d6316a3a12a63f06d5f7109) + --with-pdo-sqlite=/usr \ + --with-sqlite3=/usr \ + # Build FFI into it + --with-ffi \ + \ + --with-curl \ + --with-libedit \ + --with-openssl \ + --with-readline \ + --with-zlib \ + \ +# in PHP 7.4+, the pecl/pear installers are officially deprecated (requiring an explicit "--with-pear") + --with-pear \ + \ +# bundled pcre does not support JIT on s390x +# https://manpages.debian.org/stretch/libpcre3-dev/pcrejit.3.en.html#AVAILABILITY_OF_JIT_SUPPORT + $(test "$gnuArch" = 's390x-linux-gnu' && echo '--without-pcre-jit') \ + --with-libdir="lib/$debMultiarch" \ + \ + ${PHP_EXTRA_CONFIGURE_ARGS:-} \ + ; \ + make -j "$(nproc)"; \ + find -type f -name '*.a' -delete; \ + make install; \ + find /usr/local/bin /usr/local/sbin -type f -executable -exec strip --strip-all '{}' + || true; \ + make clean; \ + \ +# https://github.com/docker-library/php/issues/692 (copy default example "php.ini" files somewhere easily discoverable) + cp -v php.ini-* "$PHP_INI_DIR/"; \ + \ + cd /; \ + docker-php-source delete; \ + \ +# reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies + apt-mark auto '.*' > /dev/null; \ + [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; \ + find /usr/local -type f -executable -exec ldd '{}' ';' \ + | awk '/=>/ { print $(NF-1) }' \ + | sort -u \ + | xargs -r dpkg-query --search \ + | cut -d: -f1 \ + | sort -u \ + | xargs -r apt-mark manual \ + ; \ + apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ + rm -rf /var/lib/apt/lists/*; \ + \ +# update pecl channel definitions https://github.com/docker-library/php/issues/443 + pecl update-channels; \ + rm -rf /tmp/pear ~/.pearrc; \ + \ +# smoke test + php --version + +COPY data/docker-php-ext-* data/docker-php-entrypoint /usr/local/bin/ + +# sodium was built as a shared module (so that it can be replaced later if so desired), so let's enable it too (https://github.com/docker-library/php/issues/598) +RUN docker-php-ext-enable sodium + +ENTRYPOINT ["docker-php-entrypoint"] +WORKDIR /var/www/html + +RUN set -eux; \ + cd /usr/local/etc; \ + if [ -d php-fpm.d ]; then \ + # for some reason, upstream's php-fpm.conf.default has "include=NONE/etc/php-fpm.d/*.conf" + sed 's!=NONE/!=!g' php-fpm.conf.default | tee php-fpm.conf > /dev/null; \ + cp php-fpm.d/www.conf.default php-fpm.d/www.conf; \ + else \ + # PHP 5.x doesn't use "include=" by default, so we'll create our own simple config that mimics PHP 7+ for consistency + mkdir php-fpm.d; \ + cp php-fpm.conf.default php-fpm.d/www.conf; \ + { \ + echo '[global]'; \ + echo 'include=etc/php-fpm.d/*.conf'; \ + } | tee php-fpm.conf; \ + fi; \ + { \ + echo '[global]'; \ + echo 'error_log = /proc/self/fd/2'; \ + echo; echo '; https://github.com/docker-library/php/pull/725#issuecomment-443540114'; echo 'log_limit = 8192'; \ + echo; \ + echo '[www]'; \ + echo '; if we send this to /proc/self/fd/1, it never appears'; \ + echo 'access.log = /proc/self/fd/2'; \ + echo; \ + echo 'clear_env = no'; \ + echo; \ + echo '; Ensure worker stdout and stderr are sent to the main error log.'; \ + echo 'catch_workers_output = yes'; \ + echo 'decorate_workers_output = no'; \ + } | tee php-fpm.d/docker.conf; \ + { \ + echo '[global]'; \ + echo 'daemonize = no'; \ + echo; \ + echo '[www]'; \ + echo 'listen = 9000'; \ + } | tee php-fpm.d/zz-docker.conf + +# Override stop signal to stop process gracefully +# https://github.com/php/php-src/blob/17baa87faddc2550def3ae7314236826bc1b1398/sapi/fpm/php-fpm.8.in#L163 +STOPSIGNAL SIGQUIT + +### +### Verify +### +RUN set -x \ + && php -v | grep -oE 'PHP\s[.0-9]+' | grep -oE '[.0-9]+' | grep '^8.2' \ + && /usr/local/sbin/php-fpm --test \ + && PHP_ERROR="$( php -v 2>&1 1>/dev/null )" \ + && if [ -n "${PHP_ERROR}" ]; then echo "${PHP_ERROR}"; false; fi + +EXPOSE 9000 +CMD ["php-fpm"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ffc4669 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 cytopia + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4fe773b --- /dev/null +++ b/Makefile @@ -0,0 +1,101 @@ +ifneq (,) +.error This Makefile requires GNU Make. +endif + + +# ------------------------------------------------------------------------------------------------- +# Docker configuration +# ------------------------------------------------------------------------------------------------- + +.PHONY: build rebuild test tag pull login push enter + +DIR = . +FILE = Dockerfile +IMAGE = devilbox/php-fpm-8.2 +TAG = latest + + +# ------------------------------------------------------------------------------------------------- +# Default Target +# ------------------------------------------------------------------------------------------------- + +help: + @echo "lint Lint project files and repository" + @echo "build Build Docker image" + @echo "rebuild Build Docker image without cache" + @echo "test Test built Docker image" + @echo "update-readme Update README.md with PHP modules" + @echo "tag [TAG=...] Retag Docker image" + @echo "login USER=... PASS=... Login to Docker hub" + @echo "push [TAG=...] Push Docker image to Docker hub" + + +# ------------------------------------------------------------------------------------------------- +# Lint Targets +# ------------------------------------------------------------------------------------------------- +# +lint: lint-workflow + +lint-workflow: + @\ + GIT_CURR_MAJOR="$$( git tag | sort -V | tail -1 | sed 's|\.[0-9]*$$||g' )"; \ + GIT_CURR_MINOR="$$( git tag | sort -V | tail -1 | sed 's|^[0-9]*\.||g' )"; \ + GIT_NEXT_TAG="$${GIT_CURR_MAJOR}.$$(( GIT_CURR_MINOR + 1 ))"; \ + if ! grep 'refs:' -A 100 .github/workflows/nightly.yml \ + | grep " - '$${GIT_NEXT_TAG}'" >/dev/null; then \ + echo "[ERR] New Tag required in .github/workflows/nightly.yml: $${GIT_NEXT_TAG}"; \ + exit 1; \ + else \ + echo "[OK] Git Tag present in .github/workflows/nightly.yml: $${GIT_NEXT_TAG}"; \ + fi + + +# ------------------------------------------------------------------------------------------------- +# Build Targets +# ------------------------------------------------------------------------------------------------- + +build: + docker build -t $(IMAGE) -f $(DIR)/$(FILE) $(DIR) + +rebuild: pull-base-image + docker build --no-cache -t $(IMAGE) -f $(DIR)/$(FILE) $(DIR) + + +# ------------------------------------------------------------------------------------------------- +# Test Targets +# ------------------------------------------------------------------------------------------------- + +test: + ./tests/test.sh $(IMAGE) + +update-readme: + cat "./README.md" \ + | perl -00 -pe "s/.*/\n$$(./tests/get-modules.sh)\n/s" \ + > "./README.md.tmp" + yes | mv -f "./README.md.tmp" "./README.md" + + +# ------------------------------------------------------------------------------------------------- +# Deploy Targets +# ------------------------------------------------------------------------------------------------- + +tag: + docker tag $(IMAGE) $(IMAGE):$(TAG) + +login: + yes | docker login --username $(USER) --password $(PASS) + +push: + @$(MAKE) tag TAG=$(TAG) + docker push $(IMAGE):$(TAG) + + +# ------------------------------------------------------------------------------------------------- +# Helper Targets +# ------------------------------------------------------------------------------------------------- + +enter: + docker run --rm --name $(subst /,-,$(IMAGE)) -it --entrypoint=bash $(ARG) $(IMAGE) + +pull-base-image: + @docker pull $(shell grep FROM Dockerfile | sed 's/^FROM\s*//g';) diff --git a/data/docker-php-entrypoint b/data/docker-php-entrypoint new file mode 100755 index 0000000..86343d8 --- /dev/null +++ b/data/docker-php-entrypoint @@ -0,0 +1,9 @@ +#!/bin/sh +set -e + +# first arg is `-f` or `--some-option` +if [ "${1#-}" != "$1" ]; then + set -- php-fpm "$@" +fi + +exec "$@" diff --git a/data/docker-php-ext-configure b/data/docker-php-ext-configure new file mode 100755 index 0000000..9e949e1 --- /dev/null +++ b/data/docker-php-ext-configure @@ -0,0 +1,69 @@ +#!/bin/sh +set -e + +# prefer user supplied CFLAGS, but default to our PHP_CFLAGS +: ${CFLAGS:=$PHP_CFLAGS} +: ${CPPFLAGS:=$PHP_CPPFLAGS} +: ${LDFLAGS:=$PHP_LDFLAGS} +export CFLAGS CPPFLAGS LDFLAGS + +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 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 + +pm='unknown' +if [ -e /lib/apk/db/installed ]; then + pm='apk' +fi + +if [ "$pm" = 'apk' ]; then + if \ + [ -n "$PHPIZE_DEPS" ] \ + && ! apk info --installed .phpize-deps > /dev/null \ + && ! apk info --installed .phpize-deps-configure > /dev/null \ + ; then + apk add --no-cache --virtual .phpize-deps-configure $PHPIZE_DEPS + fi +fi + +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/data/docker-php-ext-enable b/data/docker-php-ext-enable new file mode 100755 index 0000000..a2aa88e --- /dev/null +++ b/data/docker-php-ext-enable @@ -0,0 +1,114 @@ +#!/bin/sh +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 + +pm='unknown' +if [ -e /lib/apk/db/installed ]; then + pm='apk' +fi + +apkDel= +if [ "$pm" = 'apk' ]; then + if \ + [ -n "$PHPIZE_DEPS" ] \ + && ! apk info --installed .phpize-deps > /dev/null \ + && ! apk info --installed .phpize-deps-configure > /dev/null \ + ; then + apk add --no-cache --virtual '.docker-php-ext-enable-deps' binutils + apkDel='.docker-php-ext-enable-deps' + fi +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="/usr/local/etc/php/conf.d/${iniName:-"docker-php-ext-$ext.ini"}" + if ! grep -q "$line" "$ini" 2>/dev/null; then + echo "$line" >> "$ini" + fi +done + +if [ "$pm" = 'apk' ] && [ -n "$apkDel" ]; then + apk del $apkDel +fi diff --git a/data/docker-php-ext-install b/data/docker-php-ext-install new file mode 100755 index 0000000..d75d6d7 --- /dev/null +++ b/data/docker-php-ext-install @@ -0,0 +1,122 @@ +#!/bin/sh +set -e + +# prefer user supplied CFLAGS, but default to our PHP_CFLAGS +: ${CFLAGS:=$PHP_CFLAGS} +: ${CPPFLAGS:=$PHP_CPPFLAGS} +: ${LDFLAGS:=$PHP_LDFLAGS} +export CFLAGS CPPFLAGS LDFLAGS + +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 + +pm='unknown' +if [ -e /lib/apk/db/installed ]; then + pm='apk' +fi + +apkDel= +if [ "$pm" = 'apk' ]; then + if [ -n "$PHPIZE_DEPS" ]; then + if apk info --installed .phpize-deps-configure > /dev/null; then + apkDel='.phpize-deps-configure' + elif ! apk info --installed .phpize-deps > /dev/null; then + apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS + apkDel='.phpize-deps' + fi + fi +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 [ "$pm" = 'apk' ] && [ -n "$apkDel" ]; then + apk del $apkDel +fi + +if [ -e /usr/src/php/.docker-delete-me ]; then + docker-php-source delete +fi diff --git a/data/docker-php-source b/data/docker-php-source new file mode 100755 index 0000000..9033d24 --- /dev/null +++ b/data/docker-php-source @@ -0,0 +1,34 @@ +#!/bin/sh +set -e + +dir=/usr/src/php + +usage() { + echo "usage: $0 COMMAND" + echo + echo "Manage php source tarball lifecycle." + echo + echo "Commands:" + echo " extract extract php source tarball into directory $dir if not already done." + echo " delete delete extracted php source located into $dir if not already done." + echo +} + +case "$1" in + extract) + mkdir -p "$dir" + if [ ! -f "$dir/.docker-extracted" ]; then + tar -Jxf /usr/src/php.tar.xz -C "$dir" --strip-components=1 + touch "$dir/.docker-extracted" + fi + ;; + + delete) + rm -rf "$dir" + ;; + + *) + usage + exit 1 + ;; +esac diff --git a/tests/get-modules.sh b/tests/get-modules.sh new file mode 100755 index 0000000..7c48273 --- /dev/null +++ b/tests/get-modules.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +MODULES="$( docker run --rm -t --entrypoint=php devilbox/php-fpm-8.2 -m \ + | grep -vE '(^\[)|(^\s*$)' \ + | sort -u -f +)" + +echo "| Module | Built-in |" +echo "|--------------|-----------|" +echo "${MODULES}" | while read line; do + line="$( echo "${line}" | sed 's/\r//g' | xargs )" + printf "| %-12s | ✔ |\n" "${line}" +done diff --git a/tests/test.sh b/tests/test.sh new file mode 100755 index 0000000..2c4d3d7 --- /dev/null +++ b/tests/test.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +IMAGE="${1}" + +docker run --rm --entrypoint=php "${IMAGE}" -v | grep -E '^PHP 8\.2' +docker run --rm --entrypoint=php-fpm "${IMAGE}" -v | grep -E '^PHP 8\.2'