From b4c7c84d50cf65799b1e6319832b7a08e75c1a4e Mon Sep 17 00:00:00 2001 From: Kevin Shenk Date: Thu, 2 Jul 2026 16:14:03 -0400 Subject: [PATCH] Add Cloudflare D1 WordPress image variants Add wordpress-d1-php83/php84 image variants that bundle the SQLite Database Integration plugin with its Cloudflare D1 backend, the D1 db.php drop-in, and the native wp_mysql_parser and wp_d1_client Rust extensions, built from source against this flake's ZTS PHP. - The driver source is a git+https flake input (the project's .gitattributes marks /packages as export-ignore, which would exclude it from GitHub tarball downloads). - The Rust toolchain comes from a separate, fresh nixpkgs input; the main pin's cargo is too old for the extensions' lockfiles. - Extensions are loaded via PHP_INI_SCAN_DIR in the image environment (FrankenPHP's embedded PHP does not inherit the CLI wrapper's compiled-in scan directory, and appending them to the PHP buildEnv would create an evaluation cycle). - The entrypoint installs the plugin and, when WP_D1_PROXY_URL is set, the D1 database drop-in into the docroot. Verified: a full WordPress install onto a real local D1 database (wrangler dev) from this image, over the native client extension. Co-Authored-By: Claude Fable 5 --- docker-entrypoint.sh | 16 +++++++++++ flake.lock | 37 ++++++++++++++++++++++++- flake.nix | 34 ++++++++++++++++++++++- lib/php-extensions.nix | 62 ++++++++++++++++++++++++++++++++++++++++++ modules/containers.nix | 36 ++++++++++++++++++++++++ 5 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 lib/php-extensions.nix diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index bdcb030..8690a41 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -106,5 +106,21 @@ mkdir -p /var/www/html/wp-content/mu-plugins cp -a /mu-plugins/. /var/www/html/wp-content/mu-plugins/ chmod 755 /var/www/html/wp-content/mu-plugins +# Install the SQLite Database Integration plugin and the Cloudflare D1 +# database drop-in when the image bundles them and a proxy is configured. +if [ -d /wordpress-plugins/sqlite-database-integration ]; then + echo "Installing the SQLite Database Integration plugin" + rm -rf /var/www/html/wp-content/plugins/sqlite-database-integration + mkdir -p /var/www/html/wp-content/plugins + cp -a /wordpress-plugins/sqlite-database-integration /var/www/html/wp-content/plugins/ + + if [ -n "${WP_D1_PROXY_URL:-}" ]; then + echo "Installing the Cloudflare D1 database drop-in (wp-content/db.php)" + cp /var/www/html/wp-content/plugins/sqlite-database-integration/wp-includes/database/d1/db.copy \ + /var/www/html/wp-content/db.php + chmod 644 /var/www/html/wp-content/db.php + fi +fi + # Execute the main command exec "$@" \ No newline at end of file diff --git a/flake.lock b/flake.lock index 1b26b9a..3db61c4 100644 --- a/flake.lock +++ b/flake.lock @@ -34,10 +34,45 @@ "type": "github" } }, + "nixpkgs-rust": { + "locked": { + "lastModified": 1782723713, + "narHash": "sha256-oPXCU/SSUokcGaJREHibG1CBX3+s/W7orDWQOZDsEeQ=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b5aa0fbd538984f6e3d201be0005b4463d8b09f8", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, "root": { "inputs": { "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs", + "nixpkgs-rust": "nixpkgs-rust", + "sqlite-database-integration": "sqlite-database-integration" + } + }, + "sqlite-database-integration": { + "flake": false, + "locked": { + "lastModified": 1783023221, + "narHash": "sha256-u2M91EbwxMb87mhnaNR3gVMngXieYby3JvaQ+TjNqck=", + "ref": "d1-support", + "rev": "cf541890783c7eb93162fa611d7a674435c7c794", + "revCount": 906, + "type": "git", + "url": "https://github.com/Avunu/sqlite-database-integration" + }, + "original": { + "ref": "d1-support", + "type": "git", + "url": "https://github.com/Avunu/sqlite-database-integration" } }, "systems": { diff --git a/flake.nix b/flake.nix index 88d33ab..7d1c38e 100644 --- a/flake.nix +++ b/flake.nix @@ -4,10 +4,30 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; flake-utils.url = "github:numtide/flake-utils"; + + # A fresh nixpkgs used only for the Rust toolchain building the native + # PHP extensions (their dependency trees need a newer cargo than the + # main pin provides). + nixpkgs-rust.url = "github:NixOS/nixpkgs/nixos-unstable"; + + # The SQLite Database Integration project with the Cloudflare D1 backend. + # Fetched over git (not the GitHub tarball API): the project's + # .gitattributes marks /packages as export-ignore for WordPress.org + # release exports, which would exclude it from archive downloads. + sqlite-database-integration = { + url = "git+https://github.com/Avunu/sqlite-database-integration?ref=d1-support"; + flake = false; + }; }; outputs = - { self, nixpkgs, flake-utils }: + { + self, + nixpkgs, + flake-utils, + nixpkgs-rust, + sqlite-database-integration, + }: { # Deploy WordPress directly on NixOS. See readme.md for usage. nixosModules.default = import ./modules/nixos.nix; @@ -25,12 +45,24 @@ let pkgs = nixpkgs.legacyPackages.${system}; mkImage = php: imageName: import ./modules/containers.nix { inherit pkgs php imageName; }; + mkD1Image = + php: imageName: + import ./modules/containers.nix { + inherit pkgs php imageName; + d1DriverSrc = sqlite-database-integration; + rustPkgs = nixpkgs-rust.legacyPackages.${system}; + }; in { packages = { wordpress-php82 = mkImage pkgs.php82 "wordpress-php82"; wordpress-php83 = mkImage pkgs.php83 "wordpress-php83"; wordpress-php84 = mkImage pkgs.php84 "wordpress-php84"; + # Cloudflare D1 variants: bundle the SQLite Database Integration + # plugin, the D1 db.php drop-in, and the native wp_mysql_parser + + # wp_d1_client extensions. Configure with WP_D1_PROXY_URL. + wordpress-d1-php83 = mkD1Image pkgs.php83 "wordpress-d1-php83"; + wordpress-d1-php84 = mkD1Image pkgs.php84 "wordpress-d1-php84"; default = self.packages.${system}.wordpress-php83; }; } diff --git a/lib/php-extensions.nix b/lib/php-extensions.nix new file mode 100644 index 0000000..aa1ce49 --- /dev/null +++ b/lib/php-extensions.nix @@ -0,0 +1,62 @@ +# Native PHP extensions of the SQLite Database Integration project, built +# from source against the exact PHP this flake ships (ZTS, FrankenPHP-ready). +# +# - wp_mysql_parser: accelerates the MySQL lexer/parser of the SQLite +# driver (~15x parser speedup over pure PHP). +# - wp_d1_client: a native HTTP client for the Cloudflare D1 proxy +# protocol, holding a connection pool that persists across requests. +# +# Returns an attrset with the two extension derivations and `iniDir`, a +# directory with an .ini file loading both — point PHP_INI_SCAN_DIR at it +# (alongside the PHP buildEnv's own lib directory). +# +# mkPhpExtensions { pkgs; php = phpBuild; src = sqlite-database-integration; } +{ + pkgs, + php, + src, + # The Rust toolchain can come from a newer package set than the PHP build. + rustPkgs ? pkgs, +}: +let + mkExtension = + { + pname, + dir, + }: + rustPkgs.rustPlatform.buildRustPackage { + inherit pname; + version = "0.1.0"; + src = "${src}/packages/${dir}"; + cargoLock.lockFile = "${src}/packages/${dir}/Cargo.lock"; + + # ext-php-rs generates bindings against the PHP headers at build time. + nativeBuildInputs = [ rustPkgs.rustPlatform.bindgenHook ]; + env = { + PHP_CONFIG = "${php.unwrapped.dev}/bin/php-config"; + PHP = "${php.unwrapped}/bin/php"; + }; + + # The crates' tests require a live PHP runtime; extension correctness + # is verified by the driver test suites instead. + doCheck = false; + }; + + wp-mysql-parser = mkExtension { + pname = "wp_mysql_parser"; + dir = "php-ext-wp-mysql-parser"; + }; + + wp-d1-client = mkExtension { + pname = "wp_d1_client"; + dir = "php-ext-wp-d1-client"; + }; +in +{ + inherit wp-mysql-parser wp-d1-client; + + iniDir = pkgs.writeTextDir "wp-native-extensions.ini" '' + extension=${wp-mysql-parser}/lib/libwp_mysql_parser.so + extension=${wp-d1-client}/lib/libwp_d1_client.so + ''; +} diff --git a/modules/containers.nix b/modules/containers.nix index 5fdf7bf..24f23f0 100644 --- a/modules/containers.nix +++ b/modules/containers.nix @@ -9,10 +9,33 @@ pkgs, php, imageName, + # Source of the SQLite Database Integration project (with the D1 backend). + # When set, the image bundles the plugin, the D1 db.php drop-in, and the + # native wp_mysql_parser + wp_d1_client extensions. + d1DriverSrc ? null, + # Package set providing the Rust toolchain for the native extensions. + rustPkgs ? pkgs, }: let phpBuild = import ../lib/php.nix { inherit pkgs php; }; + phpExtensions = + if d1DriverSrc == null then + null + else + import ../lib/php-extensions.nix { + inherit pkgs rustPkgs; + php = phpBuild; + src = d1DriverSrc; + }; + + # The PHP ini scan path: the buildEnv's own configuration, plus the native + # extensions when enabled. FrankenPHP's embedded PHP does not inherit the + # CLI wrapper's compiled-in scan directory, so it is set explicitly. + phpIniScanDir = pkgs.lib.concatStringsSep ":" ( + [ "${phpBuild}/lib" ] ++ pkgs.lib.optional (phpExtensions != null) "${phpExtensions.iniDir}" + ); + wp-cli = pkgs.wp-cli.override { php = phpBuild; }; @@ -42,6 +65,9 @@ pkgs.dockerTools.buildLayeredImage { ]; config = { + Env = [ + "PHP_INI_SCAN_DIR=${phpIniScanDir}" + ]; Entrypoint = [ "${pkgs.busybox}/bin/sh" "${pkgs.lib.getExe docker-entrypoint}" @@ -71,6 +97,16 @@ pkgs.dockerTools.buildLayeredImage { # copy must-use plugins mkdir mu-plugins cp -r ${../mu-plugins}/. mu-plugins/ +${ + pkgs.lib.optionalString (d1DriverSrc != null) '' + # Bundle the SQLite Database Integration plugin with the D1 backend. + # The entrypoint installs it into the docroot when WP_D1_PROXY_URL is set. + # -L dereferences the plugin's wp-includes/database symlink. + mkdir -p wordpress-plugins + cp -rL ${d1DriverSrc}/packages/plugin-sqlite-database-integration wordpress-plugins/sqlite-database-integration + chmod -R u+w wordpress-plugins +'' +} # Symlink CA certificates ln -s ${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt etc/ssl/certs/ca-certificates.crt