diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a566dcf..e4c67dd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3'] + php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/runtime/Makefile b/runtime/Makefile index de27ba4..96c303a 100644 --- a/runtime/Makefile +++ b/runtime/Makefile @@ -1,7 +1,7 @@ SHELL := /bin/bash .PHONY: build build-images publish -build: build-php-72.zip build-php-73.zip build-php-74.zip build-php-80.zip build-php-81.zip build-php-82.zip build-php-83.zip +build: build-php-72.zip build-php-73.zip build-php-74.zip build-php-80.zip build-php-81.zip build-php-82.zip build-php-83.zip build-php-84.zip build-php%.zip: publish-images PHP_VERSION=$$(echo $@ | cut -d'.' -f 1 | cut -d'-' -f 2,3); \ @@ -25,7 +25,8 @@ publish-images-%: cd ../php-80 ; depot build -t $$RUNTIME_REPO:php-80 . --load --push --build-arg BASE_IMAGE=base --platform=$$DOCKER_PLATFORM --build-arg DOCKER_PLATFORM=$$DOCKER_PLATFORM --build-arg CPU_ARCHITECTURE=$$CPU_ARCHITECTURE; \ cd ../php-81 ; depot build -t $$RUNTIME_REPO:php-81 . --load --push --build-arg BASE_IMAGE=base --platform=$$DOCKER_PLATFORM --build-arg DOCKER_PLATFORM=$$DOCKER_PLATFORM --build-arg CPU_ARCHITECTURE=$$CPU_ARCHITECTURE; \ cd ../php-82 ; depot build -t $$RUNTIME_REPO:php-82 . --load --push --build-arg BASE_IMAGE=base --platform=$$DOCKER_PLATFORM --build-arg DOCKER_PLATFORM=$$DOCKER_PLATFORM --build-arg CPU_ARCHITECTURE=$$CPU_ARCHITECTURE; \ - cd ../php-83 ; depot build -t $$RUNTIME_REPO:php-83 . --load --push --build-arg BASE_IMAGE=base --platform=$$DOCKER_PLATFORM --build-arg DOCKER_PLATFORM=$$DOCKER_PLATFORM --build-arg CPU_ARCHITECTURE=$$CPU_ARCHITECTURE + cd ../php-83 ; depot build -t $$RUNTIME_REPO:php-83 . --load --push --build-arg BASE_IMAGE=base --platform=$$DOCKER_PLATFORM --build-arg DOCKER_PLATFORM=$$DOCKER_PLATFORM --build-arg CPU_ARCHITECTURE=$$CPU_ARCHITECTURE; \ + cd ../php-84 ; depot build -t $$RUNTIME_REPO:php-84 . --load --push --build-arg BASE_IMAGE=base --platform=$$DOCKER_PLATFORM --build-arg DOCKER_PLATFORM=$$DOCKER_PLATFORM --build-arg CPU_ARCHITECTURE=$$CPU_ARCHITECTURE publish-images: publish-images-linux_amd64-x86_64 publish-images-linux_arm64-arm64 @@ -44,7 +45,8 @@ publish-dev-images-%: cd ../php-80 ; depot build -t $$RUNTIME_REPO:php-80 . --load --push --build-arg BASE_IMAGE=base-dev --platform=$$DOCKER_PLATFORM --build-arg DOCKER_PLATFORM=$$DOCKER_PLATFORM --build-arg CPU_ARCHITECTURE=$$CPU_ARCHITECTURE; \ cd ../php-81 ; depot build -t $$RUNTIME_REPO:php-81 . --load --push --build-arg BASE_IMAGE=base-dev --platform=$$DOCKER_PLATFORM --build-arg DOCKER_PLATFORM=$$DOCKER_PLATFORM --build-arg CPU_ARCHITECTURE=$$CPU_ARCHITECTURE; \ cd ../php-82 ; depot build -t $$RUNTIME_REPO:php-82 . --load --push --build-arg BASE_IMAGE=base-dev --platform=$$DOCKER_PLATFORM --build-arg DOCKER_PLATFORM=$$DOCKER_PLATFORM --build-arg CPU_ARCHITECTURE=$$CPU_ARCHITECTURE; \ - cd ../php-83 ; depot build -t $$RUNTIME_REPO:php-83 . --load --push --build-arg BASE_IMAGE=base-dev --platform=$$DOCKER_PLATFORM --build-arg DOCKER_PLATFORM=$$DOCKER_PLATFORM --build-arg CPU_ARCHITECTURE=$$CPU_ARCHITECTURE + cd ../php-83 ; depot build -t $$RUNTIME_REPO:php-83 . --load --push --build-arg BASE_IMAGE=base-dev --platform=$$DOCKER_PLATFORM --build-arg DOCKER_PLATFORM=$$DOCKER_PLATFORM --build-arg CPU_ARCHITECTURE=$$CPU_ARCHITECTURE; \ + cd ../php-84 ; depot build -t $$RUNTIME_REPO:php-84 . --load --push --build-arg BASE_IMAGE=base-dev --platform=$$DOCKER_PLATFORM --build-arg DOCKER_PLATFORM=$$DOCKER_PLATFORM --build-arg CPU_ARCHITECTURE=$$CPU_ARCHITECTURE publish-dev-images: publish-dev-images-linux_amd64-x86_64 publish-dev-images-linux_arm64-arm64 diff --git a/runtime/base/Dockerfile b/runtime/base/Dockerfile index d3a89cc..7641627 100644 --- a/runtime/base/Dockerfile +++ b/runtime/base/Dockerfile @@ -77,6 +77,31 @@ RUN mkdir -p ${BUILD_DIR} \ ${INSTALL_DIR}/sbin \ ${INSTALL_DIR}/share +############################################################################### +# ZLIB Build +# We compile a newer version because Lambda uses an old version (1.2.7) that +# has a security vulnerability (CVE-2022-37434). +# See https://github.com/brefphp/aws-lambda-layers/pull/110 +# Can be removed once Lambda updates their version. +# https://github.com/madler/zlib/releases +ENV VERSION_ZLIB=1.3.1 +ENV ZLIB_BUILD_DIR=${BUILD_DIR}/zlib +RUN set -xe; \ + mkdir -p ${ZLIB_BUILD_DIR}; \ + curl -Ls https://github.com/madler/zlib/releases/download/v${VERSION_ZLIB}/zlib-${VERSION_ZLIB}.tar.gz \ + | tar xzC ${ZLIB_BUILD_DIR} --strip-components=1 +WORKDIR ${ZLIB_BUILD_DIR}/ +RUN set -xe; \ + make distclean \ + && CFLAGS="" \ + CPPFLAGS="-I${INSTALL_DIR}/include -I/usr/include" \ + LDFLAGS="-L${INSTALL_DIR}/lib64 -L${INSTALL_DIR}/lib" \ + ./configure \ + --prefix=${INSTALL_DIR} +RUN set -xe; \ + make install \ + && rm ${INSTALL_DIR}/lib/libz.a + ############################################################################### # OPENSSL Build # https://github.com/openssl/openssl/releases diff --git a/runtime/layers.json b/runtime/layers.json index 81c7045..1519dc4 100644 --- a/runtime/layers.json +++ b/runtime/layers.json @@ -6,11 +6,13 @@ "arm-php-81": "Ymir PHP 8.1 runtime (64-bit ARM Architecture)", "arm-php-82": "Ymir PHP 8.2 runtime (64-bit ARM Architecture)", "arm-php-83": "Ymir PHP 8.3 runtime (64-bit ARM Architecture)", + "arm-php-84": "Ymir PHP 8.4 runtime (64-bit ARM Architecture)", "php-72": "Ymir PHP 7.2 runtime (64-bit x86 Architecture)", "php-73": "Ymir PHP 7.3 runtime (64-bit x86 Architecture)", "php-74": "Ymir PHP 7.4 runtime (64-bit x86 Architecture)", "php-80": "Ymir PHP 8.0 runtime (64-bit x86 Architecture)", "php-81": "Ymir PHP 8.1 runtime (64-bit x86 Architecture)", "php-82": "Ymir PHP 8.2 runtime (64-bit x86 Architecture)", - "php-83": "Ymir PHP 8.3 runtime (64-bit x86 Architecture)" + "php-83": "Ymir PHP 8.3 runtime (64-bit x86 Architecture)", + "php-84": "Ymir PHP 8.4 runtime (64-bit x86 Architecture)" } diff --git a/runtime/php-84/Dockerfile b/runtime/php-84/Dockerfile new file mode 100644 index 0000000..36dc1f4 --- /dev/null +++ b/runtime/php-84/Dockerfile @@ -0,0 +1,165 @@ +# Base Image to use for build. +ARG BASE_IMAGE=base +# CPU Architecture. Can be "x86_64" or "arm64" +ARG CPU_ARCHITECTURE +# Platform used to build the image. Can be "linux/amd64" or "linux/arm64". +ARG DOCKER_PLATFORM + +FROM --platform=${DOCKER_PLATFORM} ymirapp/${BASE_IMAGE}:${CPU_ARCHITECTURE} as php-build + +############################################################################### +# Oniguruma +# This library is not packaged in PHP since PHP 7.4. +# See https://github.com/php/php-src/blob/43dc7da8e3719d3e89bd8ec15ebb13f997bbbaa9/UPGRADING#L578-L581 +# We do not install the system version because I didn't manage to make it work... +# Ideally we shouldn't compile it ourselves. +# https://github.com/kkos/oniguruma/releases +# Needed by: +# - php mbstring +ENV VERSION_ONIG=6.9.9 +ENV ONIG_BUILD_DIR=${BUILD_DIR}/oniguruma +RUN set -xe; \ + mkdir -p ${ONIG_BUILD_DIR}; \ + curl -Ls https://github.com/kkos/oniguruma/releases/download/v${VERSION_ONIG}/onig-${VERSION_ONIG}.tar.gz \ + | tar xzC ${ONIG_BUILD_DIR} --strip-components=1 +WORKDIR ${ONIG_BUILD_DIR}/ +RUN set -xe; \ + ./configure --prefix=${INSTALL_DIR}; \ + make -j $(nproc); \ + make install + + +ENV VERSION_PHP=8.4.1 + + +ENV PHP_BUILD_DIR=${BUILD_DIR}/php +RUN set -xe; \ + mkdir -p ${PHP_BUILD_DIR}; \ + # Download and upack the source code + # --location will follow redirects + # --silent will hide the progress, but also the errors: we restore error messages with --show-error + # --fail makes sure that curl returns an error instead of fetching the 404 page + curl --location --silent --show-error --fail https://www.php.net/get/php-${VERSION_PHP}.tar.gz/from/this/mirror \ + | tar xzC ${PHP_BUILD_DIR} --strip-components=1 +# Move into the unpackaged code directory +WORKDIR ${PHP_BUILD_DIR}/ + +# Configure the build +# -fstack-protector-strong : Be paranoid about stack overflows +# -fpic : Make PHP's main executable position-independent (improves ASLR security mechanism, and has no performance impact on x86_64) +# -fpie : Support Address Space Layout Randomization (see -fpic) +# -O3 : Optimize for fastest binaries possible. +# -I : Add the path to the list of directories to be searched for header files during preprocessing. +# --enable-option-checking=fatal: make sure invalid --configure-flags are fatal errors instead of just warnings +# --enable-ftp: because ftp_ssl_connect() needs ftp to be compiled statically (see https://github.com/docker-library/php/issues/236) +# --enable-mbstring: because otherwise there's no way to get pecl to use it properly (see https://github.com/docker-library/php/issues/195) +# --with-zlib and --with-zlib-dir: See https://stackoverflow.com/a/42978649/245552 +# --with-pear: necessary for `pecl` to work (to install PHP extensions) +# +RUN set -xe \ + && ./buildconf --force \ + && CFLAGS="-fstack-protector-strong -fpic -fpie -O3 -I${INSTALL_DIR}/include -I/usr/include -ffunction-sections -fdata-sections" \ + CPPFLAGS="-fstack-protector-strong -fpic -fpie -O3 -I${INSTALL_DIR}/include -I/usr/include -ffunction-sections -fdata-sections" \ + LDFLAGS="-L${INSTALL_DIR}/lib64 -L${INSTALL_DIR}/lib -Wl,-O1 -Wl,--strip-all -Wl,--hash-style=both -pie" \ + ./configure \ + --prefix=${INSTALL_DIR} \ + --enable-option-checking=fatal \ + --enable-sockets \ + --with-config-file-path=${INSTALL_DIR}/etc/php \ + --with-config-file-scan-dir=${INSTALL_DIR}/etc/php/conf.d:/var/task/php/conf.d \ + --enable-fpm \ + --disable-cgi \ + --enable-cli \ + --disable-phpdbg \ + --with-sodium \ + --with-readline \ + --with-openssl \ + --with-zlib \ + --with-curl \ + --enable-exif \ + --enable-ftp \ + --with-gettext \ + --enable-mbstring \ + --with-pdo-mysql=shared,mysqlnd \ + --with-mysqli \ + --enable-pcntl \ + --with-zip \ + --enable-bcmath \ + --enable-intl=shared \ + --enable-soap \ + --with-xsl=${INSTALL_DIR} \ + --with-pear +RUN make -j $(nproc) +# Run `make install` and override PEAR's PHAR URL because pear.php.net is down +RUN set -xe; \ + make install PEAR_INSTALLER_URL='https://github.com/pear/pearweb_phars/raw/master/install-pear-nozlib.phar'; \ + { find ${INSTALL_DIR}/bin ${INSTALL_DIR}/sbin -type f -perm +0111 -exec strip --strip-all '{}' + || true; }; \ + make clean; \ + cp php.ini-production ${INSTALL_DIR}/etc/php/php.ini + +# Install extensions using pecl +RUN pecl install APCu +RUN pecl install igbinary +RUN pecl install msgpack +RUN pecl install zstd + +# Build extensions +WORKDIR ${IMAGICK_BUILD_DIR} +RUN set -xe; \ + pecl download imagick-${VERSION_IMAGICK_EXTENSION}; \ + tar xzf imagick-${VERSION_IMAGICK_EXTENSION}.tgz +WORKDIR ${IMAGICK_BUILD_DIR}/imagick-${VERSION_IMAGICK_EXTENSION} +RUN set -xe; \ + phpize; \ + ./configure --with-imagick=${INSTALL_DIR}; \ + make -j $(nproc); \ + make install; + +RUN set -xe; \ + mkdir -p ${RELAY_BUILD_DIR}; \ + RELAY_CPU_ARCHITECTURE=$(arch | sed -e 's/arm64/aarch64/;s/amd64\|x86_64/x86-64/') && \ + curl -L "https://builds.r2.relay.so/v${VERSION_RELAY_EXTENSION}/relay-v${VERSION_RELAY_EXTENSION}-php8.4-centos7-${RELAY_CPU_ARCHITECTURE}.tar.gz" \ + | tar xzC ${RELAY_BUILD_DIR} --strip-components=1 +WORKDIR ${RELAY_BUILD_DIR}/ +RUN cp relay.ini ${INSTALL_DIR}/etc/php/conf.d/50-relay.ini; \ + cp relay-pkg.so ${INSTALL_DIR}/lib/php/extensions/no-debug-non-zts-20230831/relay.so; \ + sed -i "s/00000000-0000-0000-0000-000000000000/$(cat /proc/sys/kernel/random/uuid)/" ${INSTALL_DIR}/lib/php/extensions/no-debug-non-zts-20230831/relay.so; + +# Install Composer +RUN curl -sS https://getcomposer.org/installer | ${INSTALL_DIR}/bin/php -- --install-dir=${INSTALL_DIR}/bin/ --filename=composer + +# Symlink all our binaries into /opt/bin so that Lambda sees them in the path. +RUN mkdir -p /opt/bin \ + && cd /opt/bin \ + && ln -s ../ymir/bin/* . \ + && ln -s ../ymir/sbin/* . + +# Remove extra files to make the layers as slim as possible +COPY clean.sh /tmp +RUN /tmp/clean.sh && rm /tmp/clean.sh + +# Copy config files +COPY php.ini ${INSTALL_DIR}/etc/php/conf.d +COPY php-fpm.conf ${INSTALL_DIR}/etc/php-fpm.d + +# Build PHP runtime +RUN git clone https://github.com/ymirapp/php-runtime.git /tmp/runtime-build \ + && cd /tmp/runtime-build \ + && git checkout tags/v1.12.2 \ + && cd /opt \ + && cp -R /tmp/runtime-build/composer.json /tmp/runtime-build/composer.lock /tmp/runtime-build/runtime/bootstrap /tmp/runtime-build/runtime/runtime.php /tmp/runtime-build/src /tmp/runtime-build/templates ./ \ + && chmod 0555 /opt/bootstrap /opt/runtime.php \ + && composer install --no-dev + +# Now we start back from a clean image. +# We get rid of everything that is unnecessary (build tools, source code, and anything else +# that might have created intermediate layers for docker) by copying online the /opt directory. +FROM --platform=${DOCKER_PLATFORM} public.ecr.aws/lambda/provided:al2-${CPU_ARCHITECTURE} +ENV PATH="/opt/bin:${PATH}" \ + LD_LIBRARY_PATH="/opt/ymir/lib64:/opt/ymir/lib" + +# Copy everything we built above into the same dir on the base AmazonLinux container. +COPY --from=php-build /opt /opt + +# Needed for building the layer +COPY --from=php-build /usr/lib64 /usr/lib64 diff --git a/runtime/php-84/clean.sh b/runtime/php-84/clean.sh new file mode 100755 index 0000000..61c81da --- /dev/null +++ b/runtime/php-84/clean.sh @@ -0,0 +1,45 @@ +# ------------------------------------------ +# This script cleans extra files from /opt +# to keep the layers as small as possible. +# ------------------------------------------ + +# Stop on error +set -e +# Treat unset variables and parameters as an error. +set -u + +# Strip all the unneeded symbols from shared libraries to reduce size. +find /opt/ymir -type f -name "*.so*" -exec strip --strip-unneeded {} \; +find /opt/ymir -type f -name "*.a"|xargs rm +find /opt/ymir -type f -name "*.la"|xargs rm +find /opt/ymir -type f -name "*.dist"|xargs rm +find /opt/ymir -type f -executable -exec sh -c "file -i '{}' | grep -q 'x-executable; charset=binary'" \; -print|xargs strip --strip-all + +# Cleanup all the binaries we don't want. +find /opt/ymir/sbin -mindepth 1 -maxdepth 1 ! -name "composer" ! -name "php" ! -name "php-fpm" -exec rm {} \+ +find /opt/ymir/bin -mindepth 1 -maxdepth 1 ! -name "composer" ! -name "php" ! -name "php-fpm" -exec rm {} \+ +find /opt/bin -mindepth 1 -maxdepth 1 ! -name "composer" ! -name "php" ! -name "php-fpm" -exec rm {} \+ + +# Cleanup all the files we don't want either +# We do not support running pear functions in Lambda +rm -rf /opt/ymir/lib/php/PEAR +rm -rf /opt/ymir/share +rm -rf /opt/ymir/include +rm -rf /opt/ymir/{lib,lib64}/pkgconfig +rm -rf /opt/ymir/{lib,lib64}/cmake +rm -rf /opt/ymir/lib/xml2Conf.sh +find /opt/ymir/lib/php -mindepth 1 -maxdepth 1 -type d -a ! -name "extensions" -exec rm -rf {} \; +find /opt/ymir/lib/php -mindepth 1 -maxdepth 1 -type f -exec rm -rf {} \; +rm -rf /opt/ymir/lib/php/test +rm -rf /opt/ymir/lib/php/doc +rm -rf /opt/ymir/lib/php/docs +rm -rf /opt/ymir/tests +rm -rf /opt/ymir/doc +rm -rf /opt/ymir/docs +rm -rf /opt/ymir/man +rm -rf /opt/ymir/php +rm -rf /opt/ymir/www +rm -rf /opt/ymir/cfg +rm -rf /opt/ymir/libexec +rm -rf /opt/ymir/var +rm -rf /opt/ymir/data diff --git a/runtime/php-84/php-fpm.conf b/runtime/php-84/php-fpm.conf new file mode 100644 index 0000000..60a2403 --- /dev/null +++ b/runtime/php-84/php-fpm.conf @@ -0,0 +1,21 @@ +; Logging anywhere on disk doesn't make sense on lambda since instances are ephemeral +error_log = /dev/null +pid = /tmp/.ymir/php-fpm.pid +; Log above warning because PHP-FPM logs useless notices +; We must comment this flag else uncaught exceptions/fatal errors are not reported in the logs! +; TODO: report that to the PHP bug tracker +;log_level = 'warning' + +[default] +pm = static +; We only need one child because a lambda can process only one request at a time +pm.max_children = 1 +listen = /tmp/.ymir/php-fpm.sock +; Allows PHP processes to access the lambda's environment variables +clear_env = no +; Forward stderr of PHP processes to stderr of PHP-FPM (so that it can be sent to cloudwatch) +catch_workers_output = yes +; New PHP 7.3 option that disables a verbose log prefix +decorate_workers_output = no +; Limit the number of core dump logs to 1 to avoid filling up the /tmp disk +rlimit_core = 1 diff --git a/runtime/php-84/php.ini b/runtime/php-84/php.ini new file mode 100644 index 0000000..0edc8e5 --- /dev/null +++ b/runtime/php-84/php.ini @@ -0,0 +1,50 @@ +; Do not display errors in production because with PHP-FPM that means +; errors will be output in the HTTP response +display_errors=0 + +; Since PHP 7.4 the default value is E_ALL +; We override it to set the recommended configuration value for production. +; See https://github.com/php/php-src/blob/d91abf76e01a3c39424e8192ad049f473f900936/php.ini-production#L463 +error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT + +memory_limit=3008M + +opcache.enable=1 +opcache.enable_cli=1 + +; Skip this check to save a bit +opcache.validate_permission=0 + +; The code is readonly on lambdas so it never changes +opcache.validate_timestamps=0 + +; Set sane values, modern PHP applications have higher needs than opcache's defaults +; See https://tideways.com/profiler/blog/fine-tune-your-opcache-configuration-to-avoid-caching-suprises +opcache.memory_consumption=128 +opcache.max_accelerated_files=10000 +opcache.max_wasted_percentage=10 + +extension=apcu.so +extension=igbinary.so +extension=imagick.so +extension=intl.so +extension=msgpack.so +extension=pdo_mysql.so +extension=zstd.so +zend_extension=opcache.so + +; Disable the header "X-Powered-By" exposing the installed PHP version +expose_php=0 + +; This directive determines which super global arrays are registered when PHP +; starts up. G,P,C,E & S are abbreviations for the following respective super +; globals: GET, POST, COOKIE, ENV and SERVER. +; We explicitly populate all variables else ENV is not populated by default. +variables_order="EGPCS" + +; The lambda environment is not compatible with fastcgi_finish_request +disable_functions=fastcgi_finish_request + +; API Gateway has a timeout of 29 seconds. Setting this to 28 will give PHP some +; time to properly finish up its resources and flush logs to CloudWatch. +max_execution_time=28