Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: embed PHP apps into the FrankenPHP binary #324

Merged
merged 1 commit into from
Dec 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
!testdata/*.php
!testdata/*.txt
!build-static.sh
!embed/*
2 changes: 1 addition & 1 deletion .github/workflows/docker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ jobs:
-
name: Build
id: build
uses: docker/bake-action@v3
uses: docker/bake-action@v4
with:
pull: true
load: ${{!fromJson(needs.prepare.outputs.push)}}
Expand Down
13 changes: 11 additions & 2 deletions .github/workflows/static.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,30 @@ jobs:
uses: docker/setup-buildx-action@v3
with:
version: latest
-
name: Login to DockerHub
if: ${{toJson(startsWith(github.ref, 'refs/tags/') || (github.ref == 'refs/heads/main' && github.event_name != 'pull_request'))}}
uses: docker/login-action@v3
with:
username: ${{secrets.REGISTRY_USERNAME}}
password: ${{secrets.REGISTRY_PASSWORD}}
-
name: Build
id: build
uses: docker/bake-action@v3
uses: docker/bake-action@v4
with:
pull: true
load: true
push: ${{toJson(startsWith(github.ref, 'refs/tags/') || (github.ref == 'refs/heads/main' && github.event_name != 'pull_request'))}}
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_type == 'tag' && github.ref_name || github.sha}}
LATEST: '1' # TODO: unset this variable when releasing the first stable version
SHA: ${{github.sha}}
VERSION: ${{github.ref_type == 'tag' && github.ref_name || github.sha}}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
name: Copy binary
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ WORKDIR /go/src/app
COPY --link *.* ./
COPY --link caddy caddy
COPY --link C-Thread-Pool C-Thread-Pool
COPY --link embed embed
COPY --link internal internal
COPY --link testdata testdata

Expand Down
1 change: 1 addition & 0 deletions alpine.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ WORKDIR /go/src/app
COPY --link *.* ./
COPY --link caddy caddy
COPY --link C-Thread-Pool C-Thread-Pool
COPY --link embed embed
COPY --link internal internal
COPY --link testdata testdata

Expand Down
74 changes: 48 additions & 26 deletions build-static.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#!/bin/sh

set -o errexit
set -o xtrace

if ! type "git" > /dev/null; then
echo "The \"git\" command must be installed."
Expand Down Expand Up @@ -48,37 +47,47 @@ fi

bin="frankenphp-$os-$arch"

mkdir -p dist/
cd dist/
if [ "$CLEAN" ]; then
rm -Rf dist/
go clean -cache
fi

if [ -d "static-php-cli/" ]; then
cd static-php-cli/
git pull
# Build libphp if ncessary
if [ -f "dist/static-php-cli/buildroot/lib/libphp.a" ]; then
cd dist/static-php-cli
else
git clone --depth 1 https://github.com/crazywhalecc/static-php-cli
cd static-php-cli/
fi
mkdir -p dist/
cd dist/

composer install --no-dev -a
if [ -d "static-php-cli/" ]; then
cd static-php-cli/
git pull
else
git clone --depth 1 https://github.com/crazywhalecc/static-php-cli
cd static-php-cli/
fi

if type "brew" > /dev/null; then
packages="composer"
if [ "$RELEASE" ]; then
packages="$packages gh"
fi

if type "brew" > /dev/null; then
packages="composer"
if [ "$RELEASE" ]; then
packages="$packages gh"
brew install --formula --quiet "$packages"
fi

brew install --formula --quiet "$packages"
fi
composer install --no-dev -a

if [ "$os" = "linux" ]; then
extraOpts="--disable-opcache-jit"
fi

if [ "$os" = "linux" ]; then
extraOpts="--disable-opcache-jit"
./bin/spc doctor
./bin/spc fetch --with-php="$PHP_VERSION" --for-extensions="$PHP_EXTENSIONS"
# shellcheck disable=SC2086
./bin/spc build --enable-zts --build-embed $extraOpts "$PHP_EXTENSIONS" --with-libs="$PHP_EXTENSION_LIBS"
fi

./bin/spc doctor
./bin/spc fetch --with-php="$PHP_VERSION" --for-extensions="$PHP_EXTENSIONS"
# shellcheck disable=SC2086
./bin/spc build --enable-zts --build-embed $extraOpts "$PHP_EXTENSIONS" --with-libs="$PHP_EXTENSION_LIBS"
CGO_CFLAGS="-DFRANKENPHP_VERSION=$FRANKENPHP_VERSION $(./buildroot/bin/php-config --includes | sed s#-I/#-I"$PWD"/buildroot/#g)"
export CGO_CFLAGS

Expand All @@ -92,15 +101,28 @@ export CGO_LDFLAGS
LIBPHP_VERSION="$(./buildroot/bin/php-config --version)"
export LIBPHP_VERSION

cd ../../caddy/frankenphp/
cd ../..

# Embed PHP app, if any
if [ -d "$EMBED" ]; then
mv embed embed.bak
cp -R "$EMBED" embed
fi

cd caddy/frankenphp/
go env
go build -buildmode=pie -tags "cgo netgo osusergo static_build" -ldflags "-linkmode=external -extldflags -static-pie -w -s -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP $FRANKENPHP_VERSION PHP $LIBPHP_VERSION Caddy'" -o "../../dist/$bin"
cd ../..

if [ -d "$EMBED" ]; then
rm -Rf embed
mv embed.bak embed
fi

cd ../../dist/
"./$bin" version
"dist/$bin" version

if [ "$RELEASE" ]; then
gh release upload "$FRANKENPHP_VERSION" "$bin" --repo dunglas/frankenphp --clobber
gh release upload "$FRANKENPHP_VERSION" "dist/$bin" --repo dunglas/frankenphp --clobber
fi

if [ "$CURRENT_REF" ]; then
Expand Down
34 changes: 30 additions & 4 deletions caddy/caddy.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"encoding/json"
"errors"
"net/http"
"path/filepath"
"strconv"

"github.com/caddyserver/caddy/v2"
Expand All @@ -20,6 +21,8 @@ import (
"go.uber.org/zap"
)

const defaultDocumentRoot = "public"

func init() {
caddy.RegisterModule(FrankenPHPApp{})
caddy.RegisterModule(FrankenPHPModule{})
Expand Down Expand Up @@ -93,8 +96,6 @@ func (f *FrankenPHPApp) Start() error {
}
}

logger.Info("FrankenPHP started 🐘", zap.String("php_version", frankenphp.Version().Version))

return nil
}

Expand Down Expand Up @@ -169,6 +170,10 @@ func (f *FrankenPHPApp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
if wc.FileName == "" {
return errors.New(`The "file" argument must be specified`)
}

if frankenphp.EmbeddedAppPath != "" && filepath.IsLocal(wc.FileName) {
wc.FileName = filepath.Join(frankenphp.EmbeddedAppPath, wc.FileName)
}
}

f.Workers = append(f.Workers, wc)
Expand All @@ -193,7 +198,7 @@ func parseGlobalOption(d *caddyfile.Dispenser, _ interface{}) (interface{}, erro
}

type FrankenPHPModule struct {
// Root sets the root folder to the site. Default: `root` directive.
// Root sets the root folder to the site. Default: `root` directive, or the path of the public directory of the embed app it exists.
Root string `json:"root,omitempty"`
// SplitPath sets the substrings for splitting the URI into two parts. The first matching substring will be used to split the "path info" from the path. The first piece is suffixed with the matching substring and will be assumed as the actual resource (CGI script) name. The second piece will be set to PATH_INFO for the CGI script to use. Default: `.php`.
SplitPath []string `json:"split_path,omitempty"`
Expand All @@ -217,8 +222,18 @@ func (f *FrankenPHPModule) Provision(ctx caddy.Context) error {
f.logger = ctx.Logger(f)

if f.Root == "" {
f.Root = "{http.vars.root}"
if frankenphp.EmbeddedAppPath == "" {
f.Root = "{http.vars.root}"
} else {
f.Root = filepath.Join(frankenphp.EmbeddedAppPath, defaultDocumentRoot)
f.ResolveRootSymlink = false
}
} else {
if frankenphp.EmbeddedAppPath != "" && filepath.IsLocal(f.Root) {
f.Root = filepath.Join(frankenphp.EmbeddedAppPath, f.Root)
}
}

if len(f.SplitPath) == 0 {
f.SplitPath = []string{".php"}
}
Expand Down Expand Up @@ -425,6 +440,17 @@ func parsePhpServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
// unmarshaler can read it from the start
dispenser.Reset()

if frankenphp.EmbeddedAppPath != "" {
if phpsrv.Root == "" {
phpsrv.Root = filepath.Join(frankenphp.EmbeddedAppPath, defaultDocumentRoot)
fsrv.Root = phpsrv.Root
phpsrv.ResolveRootSymlink = false
} else if filepath.IsLocal(fsrv.Root) {
phpsrv.Root = filepath.Join(frankenphp.EmbeddedAppPath, phpsrv.Root)
fsrv.Root = phpsrv.Root
}
}

// set up a route list that we'll append to
routes := caddyhttp.RouteList{}

Expand Down
10 changes: 10 additions & 0 deletions caddy/php-server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"log"
"net/http"
"path/filepath"
"strconv"
"time"

Expand All @@ -15,6 +16,7 @@ import (
"github.com/caddyserver/caddy/v2/modules/caddyhttp/fileserver"
"github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
"github.com/caddyserver/certmagic"
"github.com/dunglas/frankenphp"
"go.uber.org/zap"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -60,6 +62,14 @@ func cmdPHPServer(fs caddycmd.Flags) (int, error) {
debug := fs.Bool("debug")
compress := !fs.Bool("no-compress")

if frankenphp.EmbeddedAppPath != "" {
if root == "" {
root = filepath.Join(frankenphp.EmbeddedAppPath, defaultDocumentRoot)
} else if filepath.IsLocal(root) {
root = filepath.Join(frankenphp.EmbeddedAppPath, root)
}
}

const indexFile = "index.php"
extensions := []string{"php"}
tryFiles := []string{"{http.request.uri.path}", "{http.request.uri.path}/" + indexFile, indexFile}
Expand Down
17 changes: 14 additions & 3 deletions docs/static.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ docker buildx bake --load --set static-builder.args.PHP_EXTENSIONS=opcache,pdo_s
# ...
```

See [the list of supported extensions](https://static-php.dev/en/guide/extensions.html).

To add libraries enabling additional functionality to the extensions you've enabled, you can pass use the `PHP_EXTENSION_LIBS` Docker ARG:

```console
Expand All @@ -43,6 +41,8 @@ docker buildx bake \
static-builder
```

See also: [customizing the build](#customizing-the-build)

### GitHub Token

If you hit the GitHub API rate limit, set a GitHub Personal Access Token in an environment variable named `GITHUB_TOKEN`:
Expand All @@ -64,4 +64,15 @@ cd frankenphp

Note: this script also works on Linux (and probably on other Unixes), and is used internally by the Docker based static builder we provide.

See [the list of supported extensions](https://static-php.dev/en/guide/extensions.html).
## Customizing The Build

The following environment variables can be passed to `docker build` and to the `build-static.sh`
script to customize the static build:

* `FRANKENPHP_VERSION`: the version of FrankenPHP to use
* `PHP_VERSION`: the version of PHP to use
* `PHP_EXTENSIONS`: the PHP extensions to build ([list of supported extensions](https://static-php.dev/en/guide/extensions.html))
* `PHP_EXTENSION_LIBS`: extra libraries to build that add extra features to the extensions
* `EMBED`: path of the PHP application to embed in the binary
* `CLEAN`: when set, libphp and all its dependencies are built from scratch (no cache)
* `RELEASE`: (maintainers only) when set, the resulting binary will be uploaded on GitHub
Loading
Loading