Skip to content
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
50 changes: 28 additions & 22 deletions docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,30 +30,36 @@ fi
# Default WordPress URL if not provided
WORDPRESS_SOURCE_URL=${WORDPRESS_SOURCE_URL:-"https://wordpress.org/latest.zip"}

# Function to download and install WordPress
# Function to install WordPress: from the core baked into the image when
# present (fast, offline), otherwise downloaded from WORDPRESS_SOURCE_URL.
install_wordpress() {
echo "WordPress not found. Downloading and installing from: $WORDPRESS_SOURCE_URL"
wget -O wordpress.zip "$WORDPRESS_SOURCE_URL"

# Create a temporary directory for extraction
TEMP_DIR="/tmp/wordpress"
mkdir -p "$TEMP_DIR"
unzip wordpress.zip -d "$TEMP_DIR"

# Find WordPress files
WP_ROOT=$(find "$TEMP_DIR" -name wp-config-sample.php -exec dirname {} \; | head -n 1)

if [ -z "$WP_ROOT" ]; then
echo "Error: WordPress files not found in the downloaded archive."
exit 1
if [ -f /usr/src/wordpress/wp-includes/version.php ]; then
echo "Installing bundled WordPress core from /usr/src/wordpress"
cp -r /usr/src/wordpress/. /var/www/html/
chmod -R u+w /var/www/html
else
echo "WordPress not found. Downloading and installing from: $WORDPRESS_SOURCE_URL"
wget -O wordpress.zip "$WORDPRESS_SOURCE_URL"

# Create a temporary directory for extraction
TEMP_DIR="/tmp/wordpress"
mkdir -p "$TEMP_DIR"
unzip wordpress.zip -d "$TEMP_DIR"

# Find WordPress files
WP_ROOT=$(find "$TEMP_DIR" -name wp-config-sample.php -exec dirname {} \; | head -n 1)

if [ -z "$WP_ROOT" ]; then
echo "Error: WordPress files not found in the downloaded archive."
exit 1
fi

# Move WordPress files to the correct location
mv "$WP_ROOT"/* /var/www/html/

# Clean up
rm -rf "$TEMP_DIR" wordpress.zip
fi

# Move WordPress files to the correct location
mv "$WP_ROOT"/* /var/www/html/

# Clean up
rm -rf "$TEMP_DIR" wordpress.zip
# chown -R nobody:nobody /var/www/html

# Import database if WORDPRESS_DB_URL is set
if [ -n "${WORDPRESS_DB_URL:-}" ]; then
Expand Down
18 changes: 18 additions & 0 deletions modules/containers.nix
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,22 @@
d1DriverSrc ? null,
# Package set providing the Rust toolchain for the native extensions.
rustPkgs ? pkgs,
# WordPress core is baked into the image (at /usr/src/wordpress) so cold
# starts don't re-download it, and so the exact static-asset set is known
# (the Cloudflare Worker serves those files from Worker Assets — keep this
# version in sync with wordpress-cloudflare's WORDPRESS_VERSION).
wordpressVersion ? "7.0.1",
wordpressHash ? "sha256-vkzmfQpcj/qYT26PXi+V2ji/F5tKJhk0zZJ9QkHwQoY=",
}:
let
phpBuild = import ../lib/php.nix { inherit pkgs php; };

# WordPress core, extracted (fetchzip strips the leading "wordpress/" dir).
wordpressCore = pkgs.fetchzip {
url = "https://wordpress.org/wordpress-${wordpressVersion}.zip";
hash = wordpressHash;
};

phpExtensions =
if d1DriverSrc == null then
null
Expand Down Expand Up @@ -94,6 +106,12 @@ pkgs.dockerTools.buildLayeredImage {
mkdir -p var/www/html
cp ${../conf/wp-config.php} wp-config.php

# Bake WordPress core into the image. The entrypoint installs it into the
# docroot on first boot (no runtime download).
mkdir -p usr/src
cp -r ${wordpressCore} usr/src/wordpress
chmod -R u+w usr/src/wordpress

# The APCu persistent object cache drop-in. The entrypoint installs it
# as wp-content/object-cache.php unless WORDPRESS_OBJECT_CACHE=none.
cp ${../conf/object-cache.php} object-cache.php
Expand Down
118 changes: 118 additions & 0 deletions mu-plugins/cloudflare-page-cache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php
/*
Plugin Name: Cloudflare Page Cache Helper
Plugin URI: https://avu.nu/
Description: Signals page cacheability to the Cloudflare Worker edge cache and purges it (by cache-version bump) when content changes.
Version: 1.0
Author: Avunu LLC
Author URI: https://avu.nu/
*/

/*
* This works with the edge full-page cache in the Cloudflare Worker
* (wordpress-cloudflare/src/cache.js). The Worker caches anonymous HTML
* pages by default; this mu-plugin:
*
* 1. Marks responses that must NOT be cached for anonymous visitors
* (search, previews, logged-in) with the "X-WP-Cacheable: 0" header,
* and marks cacheable pages "1" with a friendly Cache-Control.
* 2. On content changes, calls the Worker's /__cache/purge endpoint,
* which bumps a global cache version — invalidating every cached page
* at once — authenticated with the shared CACHE_PURGE_SECRET.
*/

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

/**
* Whether the current front-end request may be cached for anonymous visitors.
*
* @return bool
*/
function wp_cf_cache_is_cacheable() {
if ( is_user_logged_in() ) {
return false;
}
if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || 'GET' !== $_SERVER['REQUEST_METHOD'] ) {
return false;
}
// Dynamic or personalized views.
if ( is_admin() || is_preview() || is_search() || is_customize_preview() ) {
return false;
}
if ( function_exists( 'is_404' ) && is_404() ) {
// 404s are not cached by the Worker (non-200); mark explicitly anyway.
return false;
}
return true;
}

/**
* Emit the cacheability signal header for the current front-end response.
*/
function wp_cf_cache_send_headers() {
if ( headers_sent() ) {
return;
}
if ( wp_cf_cache_is_cacheable() ) {
header( 'X-WP-Cacheable: 1' );
// The Worker sets its own edge TTL; this is a friendly hint and
// keeps the response cache-consistent for any intermediary.
header( 'Cache-Control: public, max-age=0, s-maxage=300' );
} else {
header( 'X-WP-Cacheable: 0' );
}
}
// template_redirect runs on the front end after the query is parsed (so the
// conditional tags are available) and before output — and never for REST or
// admin requests, which the Worker bypasses anyway.
add_action( 'template_redirect', 'wp_cf_cache_send_headers' );

/**
* Purge the edge cache by bumping its version, once per request.
*/
function wp_cf_cache_purge() {
static $purged = false;
if ( $purged ) {
return;
}
$purged = true;

$secret = getenv( 'CACHE_PURGE_SECRET' );
if ( empty( $secret ) || ! defined( 'WP_HOME' ) || empty( WP_HOME ) ) {
return;
}

wp_remote_post(
rtrim( WP_HOME, '/' ) . '/__cache/purge',
array(
'headers' => array( 'X-WP-Cache-Secret' => $secret ),
'blocking' => false,
'timeout' => 2,
)
);
}

/*
* Purge on content and configuration changes. A version bump invalidates
* the whole cache, so there is no need to enumerate affected URLs.
*/
foreach (
array(
'save_post',
'deleted_post',
'trashed_post',
'untrashed_post',
'comment_post',
'edit_comment',
'wp_set_comment_status',
'switch_theme',
'customize_save_after',
'activated_plugin',
'deactivated_plugin',
'wp_update_nav_menu',
) as $wp_cf_cache_hook
) {
add_action( $wp_cf_cache_hook, 'wp_cf_cache_purge', 10, 0 );
}