From 1f3007337d845a8b7fe29b85a5c91cc58e0064e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 12 Sep 2023 22:08:19 +0200 Subject: [PATCH] feat: create a static build of FrankenPHP (#198) * ci: create a static build of FrankenPHP * try to use alpine * path mapping * cache and fixes * fix * fix include path * fix include path * fix include path * switch to Docker * fix github token * cleanup * various improvements * macOS instructions * fix GHA * docs * feat: mac static builds * minor fix * fix wd * Apple silicon build * Revert "Apple silicon build" This reverts commit 7a2997e0920c2e490b2b4317feaaac01a89486b4. * add opcache * update builder * upgrade to PHP 8.2 * switch to upstream static-php-cli, add intl --- .github/workflows/docker.yml | 4 +- .github/workflows/static.yml | 97 ++++++++++++++++++++++++++++++++++++ .github/workflows/tests.yml | 2 +- docker-bake.hcl | 19 ++++++- docs/compile.md | 5 ++ docs/static.md | 64 ++++++++++++++++++++++++ frankenphp.go | 11 ++-- static-builder.Dockerfile | 79 +++++++++++++++++++++++++++++ 8 files changed, 274 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/static.yml create mode 100644 docs/static.md create mode 100644 static-builder.Dockerfile diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index b20fe4f..b2d5964 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -20,7 +20,7 @@ jobs: platforms: ${{ steps.matrix.outputs.platforms }} metadata: ${{ steps.matrix.outputs.metadata }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 @@ -57,7 +57,7 @@ jobs: - platform: linux/386 qemu: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up QEMU if: matrix.qemu diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml new file mode 100644 index 0000000..0fce2df --- /dev/null +++ b/.github/workflows/static.yml @@ -0,0 +1,97 @@ +name: Build binary releases +on: + pull_request: + branches: + - main + push: + branches: + - main + tags: + - v* + workflow_dispatch: + inputs: {} +jobs: + build-linux: + name: Build Linux x86_64 binary + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + with: + version: latest + + - name: Build + id: build + uses: docker/bake-action@v3 + with: + pull: true + load: true + targets: static-builder + set: | + *.cache-from=type=gha,scope=${{github.ref}}-static-builder + *.cache-from=type=gha,scope=refs/heads/main-static-builder + *.cache-to=type=gha,scope=${{github.ref}}-static-builder + env: + VERSION: ${{github.ref_name}} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Copy binary + run: docker cp $(docker create --name static-builder dunglas/frankenphp:static-builder):/go/src/app/caddy/frankenphp/frankenphp frankenphp ; docker rm static-builder + + - name: Upload binary + uses: actions/upload-artifact@v3 + with: + name: frankenphp-linux-x86_64-dev + path: frankenphp + + build-mac: + name: Build macOS binaries + runs-on: macos-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/checkout@v4 + with: + repository: crazywhalecc/static-php-cli + path: static-php-cli + + - uses: actions/setup-go@v4 + with: + go-version: '1.21' + + - name: Install static-php-cli dependencies + working-directory: static-php-cli/ + run: composer install --no-dev -a + + - name: Install missing system dependencies + run: ./bin/spc doctor --auto-fix + working-directory: static-php-cli/ + + - name: Fetch libraries sources + working-directory: static-php-cli/ + run: ./bin/spc fetch --with-php=8.2 -A + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Build static libphp + working-directory: static-php-cli/ + run: ./bin/spc build --enable-zts --build-embed --debug "bcmath,calendar,ctype,curl,dba,dom,exif,filter,fileinfo,gd,iconv,intl,mbstring,mbregex,mysqli,mysqlnd,opcache,openssl,pcntl,pdo,pdo_mysql,pdo_pgsql,pdo_sqlite,pgsql,phar,posix,readline,redis,session,simplexml,sockets,sqlite3,tokenizer,xml,xmlreader,xmlwriter,zip,zlib,apcu" + + - name: Set CGO flags + working-directory: static-php-cli/ + run: | + echo "CGO_CFLAGS=$(./buildroot/bin/php-config --includes | sed s#-I/#-I$PWD/buildroot/#g)" >> "$GITHUB_ENV" + echo "CGO_LDFLAGS=-framework CoreFoundation -framework SystemConfiguration $(./buildroot/bin/php-config --ldflags) $(./buildroot/bin/php-config --libs)" >> "$GITHUB_ENV" + + - name: Build FrankenPHP + working-directory: caddy/frankenphp/ + run: go build -buildmode=pie -tags "cgo netgo osusergo static_build" -ldflags "-linkmode=external -extldflags -static-pie -w -s" + + - name: Upload binary + uses: actions/upload-artifact@v3 + with: + name: frankenphp-mac-x86_64-dev + path: caddy/frankenphp/frankenphp diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c27afae..20bb1c1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: matrix: php-versions: ['8.2', '8.3'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: diff --git a/docker-bake.hcl b/docker-bake.hcl index dc5d2a7..b7c1fec 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -6,6 +6,10 @@ variable "VERSION" { default = "dev" } +variable "GO_VERSION" { + default = "1.21" +} + variable "SHA" {} variable "LATEST" { @@ -59,7 +63,7 @@ target "default" { } contexts = { php-base = "docker-image://php:${php-version}-zts-${os}" - golang-base = "docker-image://golang:1.21-${os}" + golang-base = "docker-image://golang:${GO_VERSION}-${os}" } dockerfile = os == "alpine" ? "alpine.Dockerfile" : "Dockerfile" context = "./" @@ -91,3 +95,16 @@ target "default" { FRANKENPHP_VERSION = VERSION } } + +target "static-builder" { + contexts = { + golang-base = "docker-image://golang:${GO_VERSION}-alpine" + } + dockerfile = "static-builder.Dockerfile" + context = "./" + tags = ["${IMAGE_NAME}:static-builder"] + args = { + FRANKENPHP_VERSION = VERSION + } + secret = ["id=github-token,env=GITHUB_TOKEN"] +} diff --git a/docs/compile.md b/docs/compile.md index 0b1f89c..3191e41 100644 --- a/docs/compile.md +++ b/docs/compile.md @@ -1,5 +1,10 @@ # Compile From Sources +This document explain how to create a FrankenPHP build that will load PHP as a dymanic library. +This is the recommended method. + +Alternatively, [creating static builds](static.md) is also possible. + ## Install PHP FrankenPHP is compatible with the PHP 8.2 and superior. diff --git a/docs/static.md b/docs/static.md new file mode 100644 index 0000000..c2d7eab --- /dev/null +++ b/docs/static.md @@ -0,0 +1,64 @@ +# Create a Static Build + +Instead of using a local installation of the PHP library, +it's possible to create a static build of FrankenPHP thanks to the great [static-php-cli project](https://github.com/crazywhalecc/static-php-cli) (despite its name, this project support all SAPIs, not only CLI). + +With this method, a single, portable, binary will contain the PHP interpreter, the Caddy web server and FrankenPHP! + +## Linux + +We provide a Docker image to build a Linux static binary: + +```console +docker buildx bake --load static-builder +docker cp $(docker create --name static-builder dunglas/frankenphp:static-builder):/go/src/app/caddy/frankenphp/frankenphp frankenphp ; docker rm static-builder +``` + +The resulting static binary is named `frankenphp` and is available in the current directory. + +If you want to build the static binary without Docker, take a look to the `static-builder.Dockerfile` file. + +### Custom Extensions + +By default, most popular PHP extensions are compiled. + +To reduce the size of the binary and to reduce the attack surface, you can choose the list of extensions to build using the `PHP_EXTENSIONS` Docker ARG. + +For instance, run the following command to only build the `opcache` extension: + +```console +docker buildx bake --load --set static-builder.args.PHP_EXTENSIONS=opcache,pdo_sqlite static-builder +# ... +``` + +See [the list of supported extensions](https://static-php-cli.zhamao.me/en/guide/extensions.html). + +### GitHub Token + +If you hit the GitHub API rate limit, set a GitHub Personal Access Token in an environment variable named `GITHUB_TOKEN`: + +```console +GITHUB_TOKEN="xxx" docker --load buildx bake static-builder +# ... +``` + +## macOS + +Run the following command to create a static binary for macOS: + +```console +git clone --depth=1 https://github.com/crazywhalecc/static-php-cli.git +cd static-php-cli +composer install --no-dev -a +./bin/spc doctor --auto-fix +./bin/spc fetch --with-php=8.2 -A +./bin/spc build --enable-zts --build-embed --debug "bcmath,calendar,ctype,curl,dba,dom,exif,filter,fileinfo,gd,iconv,intl,mbstring,mbregex,mysqli,mysqlnd,opcache,openssl,pcntl,pdo,pdo_mysql,pdo_pgsql,pdo_sqlite,pgsql,phar,posix,readline,redis,session,simplexml,sockets,sqlite3,tokenizer,xml,xmlreader,xmlwriter,zip,zlib,apcu" +export CGO_CFLAGS="$(./buildroot/bin/php-config --includes | sed s#-I/#-I$PWD/buildroot/#g)" +export CGO_LDFLAGS="-framework CoreFoundation -framework SystemConfiguration $(./buildroot/bin/php-config --ldflags) $(./buildroot/bin/php-config --libs)" + +git clone --depth=1 https://github.com/dunglas/frankenphp.git +cd frankenphp/caddy/frankenphp +go build -buildmode=pie -tags "cgo netgo osusergo static_build" -ldflags "-linkmode=external -extldflags -static-pie" +``` + +See [the list of supported extensions](https://static-php-cli.zhamao.me/en/guide/extensions.html). diff --git a/frankenphp.go b/frankenphp.go index 962a558..6e90c25 100644 --- a/frankenphp.go +++ b/frankenphp.go @@ -12,13 +12,18 @@ package frankenphp // Use PHP includes corresponding to your PHP installation by running: // // export CGO_CFLAGS=$(php-config --includes) +// export CGO_LDFLAGS=$(php-config --ldflags --libs) +// +// We also set these flags for hardening: https://github.com/docker-library/php/blob/master/8.2/bookworm/zts/Dockerfile#L57-L59 -// #cgo pkg-config: libxml-2.0 sqlite3 -// #cgo CFLAGS: -Wall -Werror +// #cgo darwin pkg-config: libxml-2.0 sqlite3 +// #cgo CFLAGS: -Wall -Werror -fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 // #cgo CFLAGS: -I/usr/local/include/php -I/usr/local/include/php/main -I/usr/local/include/php/TSRM -I/usr/local/include/php/Zend -I/usr/local/include/php/ext -I/usr/local/include/php/ext/date/lib // #cgo linux CFLAGS: -D_GNU_SOURCE +// #cgo CPPFLAGS: -fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 // #cgo darwin LDFLAGS: -L/opt/homebrew/opt/libiconv/lib -liconv -// #cgo LDFLAGS: -L/usr/local/lib -L/usr/lib -lphp -lresolv -ldl -lm -lutil +// #cgo linux LDFLAGS: -Wl,-O1 +// #cgo LDFLAGS: -pie -L/usr/local/lib -L/usr/lib -lphp -lresolv -ldl -lm -lutil // #include // #include // #include diff --git a/static-builder.Dockerfile b/static-builder.Dockerfile new file mode 100644 index 0000000..60f0b32 --- /dev/null +++ b/static-builder.Dockerfile @@ -0,0 +1,79 @@ +# syntax=docker/dockerfile:1 +FROM golang-base + +ARG FRANKENPHP_VERSION='dev' +ARG PHP_VERSION='8.2' +ARG PHP_EXTENSIONS='bcmath,calendar,ctype,curl,dba,dom,exif,filter,fileinfo,gd,iconv,intl,mbstring,mbregex,mysqli,mysqlnd,opcache,openssl,pcntl,pdo,pdo_mysql,pdo_pgsql,pdo_sqlite,pgsql,phar,posix,readline,redis,session,simplexml,sockets,sqlite3,tokenizer,xml,xmlreader,xmlwriter,zip,zlib,apcu' + +RUN apk update; \ + apk add --no-cache \ + autoconf \ + automake \ + bash \ + binutils \ + bison \ + build-base \ + cmake \ + curl \ + file \ + flex \ + g++ \ + gcc \ + git \ + jq \ + libgcc \ + libstdc++ \ + linux-headers \ + m4 \ + make \ + php82 \ + php82-common \ + php82-dom \ + php82-mbstring \ + php82-openssl \ + php82-pcntl \ + php82-phar \ + php82-posix \ + php82-tokenizer \ + php82-xml \ + php82-xmlwriter \ + pkgconfig \ + wget \ + xz ; \ + ln -sf /usr/bin/php82 /usr/bin/php + +# https://getcomposer.org/doc/03-cli.md#composer-allow-superuser +ENV COMPOSER_ALLOW_SUPERUSER=1 +ENV PATH="${PATH}:/root/.composer/vendor/bin" + +COPY --from=composer/composer:2-bin --link /composer /usr/bin/composer + +WORKDIR /static-php-cli + +RUN git clone --depth=1 https://github.com/crazywhalecc/static-php-cli . && \ + composer install --no-cache --no-dev --classmap-authoritative + +RUN --mount=type=secret,id=github-token GITHUB_TOKEN=$(cat /run/secrets/github-token) ./bin/spc download --with-php=$PHP_VERSION --all + +RUN ./bin/spc build --build-embed --enable-zts --debug "$PHP_EXTENSIONS" + +ENV PATH="/static-php-cli/buildroot/bin:/static-php-cli/buildroot/usr/bin:$PATH" + +WORKDIR /go/src/app + +COPY go.mod go.sum ./ +RUN go mod graph | awk '{if ($1 !~ "@") print $2}' | xargs go get + +RUN mkdir caddy && cd caddy +COPY caddy/go.mod caddy/go.sum ./caddy/ + +RUN cd caddy && go mod graph | awk '{if ($1 !~ "@") print $2}' | xargs go get + +COPY *.* ./ +COPY caddy caddy +COPY C-Thread-Pool C-Thread-Pool + +RUN cd caddy/frankenphp && \ + CGO_CFLAGS="$(/static-php-cli/buildroot/bin/php-config --includes | sed s#-I/#-I/static-php-cli/buildroot/#g)" \ + CGO_LDFLAGS="$(/static-php-cli/buildroot/bin/php-config --ldflags) $(/static-php-cli/buildroot/bin/php-config --libs | sed -e 's/-lgcc_s//g')" \ + go build -buildmode=pie -tags "cgo netgo osusergo static_build" -ldflags "-linkmode=external -extldflags -static-pie -s -w -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP $FRANKENPHP_VERSION Caddy'"