Skip to content
Open
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
4 changes: 3 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,11 @@ The add-on uses a **modular command approach** where `project-init` orchestrates
## Hosting Provider Support

### Pantheon
- **Recommended Docroot**: `web` (set during `ddev config`)
- **Recommended Docroot**: `web` (recommended for modern sites) or root/empty (legacy sites)
- **Environments**: dev, test, live, multidev
- **Authentication**: Terminus machine token
- **Database**: Automated backup management with age detection
- **Note**: Root-level WordPress (no webroot subdirectory) is fully supported for older Pantheon sites

### WPEngine
- **Recommended Docroot**: `public` (set during `ddev config`)
Expand Down Expand Up @@ -110,6 +111,7 @@ Variables are stored in multiple locations:
#### Pantheon Configuration
- `HOSTING_SITE`: Pantheon site machine name
- `HOSTING_ENV`: Default environment for database pulls (dev/test/live)
- `DOCROOT`: Document root directory (`web` for modern sites, empty string for root-level WordPress on legacy sites)
- `MIGRATE_DB_SOURCE`: Source site for migrations (optional)
- `MIGRATE_DB_ENV`: Source environment for migrations (optional)

Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,11 @@ ddev project-init

## 🌐 Hosting Provider Support

| Provider | Authentication | Features |
|----------|---------------|----------|
| **[Pantheon](https://kanopi.github.io/ddev-kanopi-wp/providers/pantheon/)** | Machine Token | Terminus integration, multidev support |
| **[WPEngine](https://kanopi.github.io/ddev-kanopi-wp/providers/wpengine/)** | SSH Key (local config) | Nightly backup utilization |
| **[Kinsta](https://kanopi.github.io/ddev-kanopi-wp/providers/kinsta/)** | SSH Key | Direct server access |
| Provider | Authentication | Features | Docroot |
|----------|---------------|----------|---------|
| **[Pantheon](https://kanopi.github.io/ddev-kanopi-wp/providers/pantheon/)** | Machine Token | Terminus integration, multidev support | `web` (recommended) or root (legacy) |
| **[WPEngine](https://kanopi.github.io/ddev-kanopi-wp/providers/wpengine/)** | SSH Key (local config) | Nightly backup utilization | Configurable |
| **[Kinsta](https://kanopi.github.io/ddev-kanopi-wp/providers/kinsta/)** | SSH Key | Direct server access | `public` |

## 📋 Installation Options

Expand Down
22 changes: 20 additions & 2 deletions commands/host/project-configure
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,14 @@ case "$HOST_PROVIDER" in
echo "--------------------------"
prompt_input "Site machine name (e.g., my-site)" "${HOSTING_SITE}" HOSTING_SITE
prompt_input "Default environment (dev/test/live)" "${HOSTING_ENV:-dev}" HOSTING_ENV
prompt_input "Document root directory (web for modern sites, or leave empty for root/legacy sites)" "${DOCROOT:-web}" DOCROOT

# Normalize empty/dot to empty string for root-level WordPress
if [ "$DOCROOT" = "." ]; then
DOCROOT=""
fi

echo "💡 Document root configured: ${DOCROOT:-[root/application root]}"
;;
"wpengine")
echo "⚡ WPEngine Configuration:"
Expand Down Expand Up @@ -313,6 +321,7 @@ case "$HOST_PROVIDER" in
"pantheon")
write_config_var "HOSTING_SITE" "$HOSTING_SITE"
write_config_var "HOSTING_ENV" "$HOSTING_ENV"
write_config_var "DOCROOT" "$DOCROOT"
;;
"wpengine")
write_config_var "HOSTING_SITE" "$HOSTING_SITE"
Expand Down Expand Up @@ -463,13 +472,22 @@ if [ -n "$PROXY_URL" ]; then
esac

# Update the nginx configuration with actual values
# Replace {{DOCROOT}} placeholder with actual docroot value
# Handle empty docroot by removing trailing slash
if [ -z "$DOCROOT" ]; then
NGINX_ROOT="/var/www/html"
else
NGINX_ROOT="/var/www/html/$DOCROOT"
fi

sed -i.bak \
-e "s|HOSTING_ENV-HOSTING_SITE.HOSTING_DOMAIN|$PROXY_HOST|g" \
-e "s|/var/www/html/web|/var/www/html/$DOCROOT|g" \
-e "s|/var/www/html/{{DOCROOT}}|$NGINX_ROOT|g" \
-e "s|/var/www/html/web|$NGINX_ROOT|g" \
".ddev/nginx_full/nginx-site.conf"
rm -f ".ddev/nginx_full/nginx-site.conf.bak"

echo " ✅ Nginx proxy configuration updated (docroot: $DOCROOT, proxy: $PROXY_HOST)"
echo " ✅ Nginx proxy configuration updated (docroot: ${DOCROOT:-[root]}, proxy: $PROXY_HOST)"
else
echo " ⚠️ .ddev/nginx_full/nginx-site.conf not found - proxy configuration not updated"
fi
Expand Down
13 changes: 11 additions & 2 deletions commands/host/project-init
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,21 @@ refresh_command='ddev db-refresh'

# Handle Pantheon mu-plugin conflicts early (before any wp-cli commands)
echo -e "\n${construction} ${yellow} Checking for Pantheon mu-plugin conflicts...${NC} ${construction}\n"
PANTHEON_LOADER="web/wp-content/mu-plugins/pantheon-mu-loader.php"

# Determine the correct path based on whether docroot is configured
# Check DOCROOT from config.yaml (empty string means root)
if [ -z "${DOCROOT}" ] || [ "${DOCROOT}" = "." ]; then
MU_PLUGINS_PATH="wp-content/mu-plugins"
else
MU_PLUGINS_PATH="${DOCROOT}/wp-content/mu-plugins"
fi

PANTHEON_LOADER="${MU_PLUGINS_PATH}/pantheon-mu-loader.php"
if [ -f "$PANTHEON_LOADER" ]; then
# Check if the loader tries to load pantheon-mu-plugin
if grep -q "pantheon-mu-plugin/pantheon.php" "$PANTHEON_LOADER"; then
# Check if the actual plugin directory exists
if [ ! -d "web/wp-content/mu-plugins/pantheon-mu-plugin" ]; then
if [ ! -d "${MU_PLUGINS_PATH}/pantheon-mu-plugin" ]; then
echo "📝 Found Pantheon mu-plugin loader but plugin directory missing"
echo " Disabling to prevent PHP fatal errors in DDEV environment..."
mv "$PANTHEON_LOADER" "${PANTHEON_LOADER}.disabled"
Expand Down
10 changes: 9 additions & 1 deletion commands/host/project-wp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,15 @@ load_kanopi_config
# Determine docroot based on hosting provider
case "${HOSTING_PROVIDER}" in
"pantheon")
DOCROOT="web"
# Handle application root case (. or empty)
if [ "$DOCROOT" = "." ] || [ -z "$DOCROOT" ]; then
DOCROOT=""
DOCROOT_PATH="/var/www/html"
echo "Using application root (no subfolder) for Pantheon"
else
DOCROOT_PATH="/var/www/html/${DOCROOT}"
echo "Using docroot: $DOCROOT for hosting provider: $HOSTING_PROVIDER"
fi
;;
"wpengine")
# Use configured DOCROOT from environment, fallback to 'web'
Expand Down
7 changes: 6 additions & 1 deletion commands/web/theme-activate
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ load_kanopi_config
# Determine docroot based on hosting provider
case "${HOSTING_PROVIDER}" in
"pantheon")
DOCROOT_PATH="/var/www/html/web"
# Handle application root case (. or empty)
if [ "$DOCROOT" = "." ] || [ -z "$DOCROOT" ]; then
DOCROOT_PATH="/var/www/html"
else
DOCROOT_PATH="/var/www/html/${DOCROOT}"
fi
;;
"wpengine")
# Use configured DOCROOT from environment, fallback to 'web'
Expand Down
66 changes: 66 additions & 0 deletions docs/providers/pantheon.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Full integration with Pantheon hosting including Terminus CLI, automated backup
### Required Variables
- `HOSTING_SITE` - Pantheon site machine name
- `HOSTING_ENV` - Default environment for database pulls (dev/test/live)
- `DOCROOT` - Document root directory (`web` recommended for modern sites, empty for root-level WordPress on legacy sites)
- `MIGRATE_DB_SOURCE` - Source project for migrations (optional)
- `MIGRATE_DB_ENV` - Source environment for migrations (optional)

Expand All @@ -27,8 +28,73 @@ Full integration with Pantheon hosting including Terminus CLI, automated backup
# Select Pantheon as provider
# Enter site machine name
# Choose default environment
# Configure document root (web or leave empty for root)
```

## WordPress Installation Location

Pantheon sites can have WordPress installed in different locations:

### Modern Sites (Recommended)
- **Document root**: `web/`
- **WordPress core**: `/web/` directory
- **Configuration during setup**: Enter "web" when prompted for document root
- **Best for**: New projects, Composer-managed WordPress, clean directory structure

### Legacy Sites (Alternative)
- **Document root**: Root/application root
- **WordPress core**: Root directory (no subdirectory)
- **Configuration during setup**: Leave document root empty or press Enter when prompted
- **Best for**: Older Pantheon sites, sites migrated from other hosts

### Configuration Examples

**Modern site with web/ subdirectory:**
```bash
ddev project-configure
# When prompted: "Document root directory (web for modern sites, or leave empty for root/legacy sites) [web]:"
# Enter: web (or press Enter to accept default)
```

**Legacy site with root-level WordPress:**
```bash
ddev project-configure
# When prompted: "Document root directory (web for modern sites, or leave empty for root/legacy sites) [web]:"
# Enter: (press Enter without typing anything, or explicitly type empty string)
```

### Troubleshooting 404 Errors

If you experience 404 errors after setup, verify your document root configuration:

1. **Check your Pantheon site structure**:
- Does `wp-config.php` exist in the root directory or in a `web/` subdirectory?
- Where are `wp-content/`, `wp-includes/`, and `wp-admin/` located?

2. **Verify DDEV configuration**:
```bash
grep '^docroot:' .ddev/config.yaml
# Should show: docroot: web (for modern sites)
# Or: docroot: "" (for root-level WordPress)
```

3. **Re-run configuration if needed**:
```bash
ddev project-configure
# Provide the correct document root
ddev restart
```

### Migrating Between Configurations

**Existing projects** with `web/` subdirectory continue working without changes. To explicitly update configuration:

```bash
ddev project-configure
# Re-enter your settings with correct document root
ddev restart
```

## Features

### Smart Database Refresh
Expand Down
47 changes: 39 additions & 8 deletions install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,21 @@ post_install_actions:
done

if [ -n "$CONFIG_FILE" ]; then
DOCROOT=$(grep '^docroot:' "$CONFIG_FILE" 2>/dev/null | cut -d: -f2 | tr -d ' "'\''' || echo "web")
DOCROOT=$(grep '^docroot:' "$CONFIG_FILE" 2>/dev/null | cut -d: -f2 | tr -d ' "'\''')
# If DOCROOT is empty or not found, default to empty string (root-level WordPress)
if [ -z "$DOCROOT" ]; then
DOCROOT=""
fi
else
DOCROOT="web"
fi

WP_CONFIG_PATH="../../${DOCROOT}/wp-config.php"
# Handle empty docroot (root-level WordPress)
if [ -z "$DOCROOT" ]; then
WP_CONFIG_PATH="../../wp-config.php"
else
WP_CONFIG_PATH="../../${DOCROOT}/wp-config.php"
fi

# Add ddev-managed settings to wp-config.php
if [ -f "$WP_CONFIG_PATH" ]; then
Expand Down Expand Up @@ -125,18 +134,29 @@ post_install_actions:
done

if [ -n "$CONFIG_FILE" ]; then
DOCROOT=$(grep '^docroot:' "$CONFIG_FILE" 2>/dev/null | cut -d: -f2 | tr -d ' "'\''' || echo "web")
DOCROOT=$(grep '^docroot:' "$CONFIG_FILE" 2>/dev/null | cut -d: -f2 | tr -d ' "'\''')
# If DOCROOT is empty or not found, default to empty string (root-level WordPress)
if [ -z "$DOCROOT" ]; then
DOCROOT=""
fi
else
DOCROOT="web"
fi

PANTHEON_LOADER="../${DOCROOT}/wp-content/mu-plugins/pantheon-mu-loader.php"
# Handle empty docroot (root-level WordPress)
if [ -z "$DOCROOT" ]; then
PANTHEON_LOADER="../wp-content/mu-plugins/pantheon-mu-loader.php"
PANTHEON_PLUGIN_DIR="../wp-content/mu-plugins/pantheon-mu-plugin"
else
PANTHEON_LOADER="../${DOCROOT}/wp-content/mu-plugins/pantheon-mu-loader.php"
PANTHEON_PLUGIN_DIR="../${DOCROOT}/wp-content/mu-plugins/pantheon-mu-plugin"
fi

if [ -f "$PANTHEON_LOADER" ]; then
# Check if the loader tries to load pantheon-mu-plugin
if grep -q "pantheon-mu-plugin/pantheon.php" "$PANTHEON_LOADER"; then
# Check if the actual plugin directory exists
if [ ! -d "../${DOCROOT}/wp-content/mu-plugins/pantheon-mu-plugin" ]; then
if [ ! -d "$PANTHEON_PLUGIN_DIR" ]; then
echo "📝 Found Pantheon mu-plugin loader but plugin directory missing"
echo " Disabling to prevent PHP fatal errors in DDEV environment..."
mv "$PANTHEON_LOADER" "${PANTHEON_LOADER}.disabled"
Expand Down Expand Up @@ -260,14 +280,25 @@ removal_actions:
done

if [ -n "$CONFIG_FILE" ]; then
DOCROOT=$(grep '^docroot:' "$CONFIG_FILE" 2>/dev/null | cut -d: -f2 | tr -d ' "'\''' || echo "web")
DOCROOT=$(grep '^docroot:' "$CONFIG_FILE" 2>/dev/null | cut -d: -f2 | tr -d ' "'\''')
# If DOCROOT is empty or not found, default to empty string (root-level WordPress)
if [ -z "$DOCROOT" ]; then
DOCROOT=""
fi
else
DOCROOT="web"
fi

PANTHEON_LOADER_DISABLED="../${DOCROOT}/wp-content/mu-plugins/pantheon-mu-loader.php.disabled"
if [ -f "$PANTHEON_LOADER_DISABLED" ]; then
# Handle empty docroot (root-level WordPress)
if [ -z "$DOCROOT" ]; then
PANTHEON_LOADER_DISABLED="../wp-content/mu-plugins/pantheon-mu-loader.php.disabled"
PANTHEON_LOADER="../wp-content/mu-plugins/pantheon-mu-loader.php"
else
PANTHEON_LOADER_DISABLED="../${DOCROOT}/wp-content/mu-plugins/pantheon-mu-loader.php.disabled"
PANTHEON_LOADER="../${DOCROOT}/wp-content/mu-plugins/pantheon-mu-loader.php"
fi

if [ -f "$PANTHEON_LOADER_DISABLED" ]; then
mv "$PANTHEON_LOADER_DISABLED" "$PANTHEON_LOADER"
echo "✅ Restored Pantheon mu-plugin loader (was disabled during add-on installation)"
fi
Expand Down
1 change: 1 addition & 0 deletions scripts/load-config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ load_kanopi_config() {
if [[ "${HOSTING_PROVIDER}" == "pantheon" ]]; then
export HOSTING_SITE=${HOSTING_SITE:-''}
export HOSTING_ENV=${HOSTING_ENV:-'dev'}
export DOCROOT=${DOCROOT:-''} # Pantheon default is root, but can be configured to 'web'
# Migration Configuration for Pantheon
export MIGRATE_DB_SOURCE=${MIGRATE_DB_SOURCE:-''}
export MIGRATE_DB_ENV=${MIGRATE_DB_ENV:-''}
Expand Down
7 changes: 6 additions & 1 deletion scripts/refresh-pantheon.sh
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,12 @@ DB_DUMP="/tmp/pantheon_backup.${SITE_ENV}.sql.gz"
terminus backup:get ${SITE_ENV} --element=database --to=${DB_DUMP}

echo -e "\nReset DB"
cd ${DDEV_DOCROOT}
# Stay in DDEV_APPROOT if DDEV_DOCROOT is empty (root-level WordPress)
if [ -n "${DDEV_DOCROOT}" ]; then
cd ${DDEV_APPROOT}/${DDEV_DOCROOT}
else
cd ${DDEV_APPROOT}
fi
wp db reset --yes --skip-plugins --skip-themes

echo -e "\nImport db"
Expand Down
42 changes: 42 additions & 0 deletions tests/test.bats
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,48 @@ EOF
ddev exec wp core version 2>/dev/null || echo "WP-CLI not available or WordPress not fully configured"
}

@test "pantheon root-level wordpress configuration" {
set -eu -o pipefail
cd $TESTDIR
# Configure with empty docroot for root-level WordPress (legacy Pantheon sites)
ddev config --project-name=$PROJNAME --project-type=wordpress --docroot="" --create-docroot

# Verify empty docroot in config
grep -q '^docroot: ""' .ddev/config.yaml || grep -q '^docroot:$' .ddev/config.yaml

# Install add-on with root-level WordPress
ddev add-on get $DIR

# Configure for Pantheon with root-level WordPress
export DDEV_NONINTERACTIVE=true
ddev config --web-environment-add="HOSTING_PROVIDER=pantheon"
ddev config --web-environment-add="HOSTING_SITE=test-site"
ddev config --web-environment-add="HOSTING_ENV=dev"
ddev config --web-environment-add="DOCROOT="

ddev start

# Verify nginx configuration uses correct root path
ddev exec "grep -q 'root /var/www/html;' /etc/nginx/sites-enabled/nginx-site.conf" || echo "Nginx root path should be /var/www/html for empty docroot"

# Verify commands handle empty DOCROOT correctly
ddev project-wp --help >/dev/null 2>&1
ddev theme-activate --help >/dev/null 2>&1

# Test mu-plugin handling with empty docroot
mkdir -p wp-content/mu-plugins
cat > wp-content/mu-plugins/pantheon-mu-loader.php << 'EOF'
<?php
$pantheon_mu_plugins = ['pantheon-mu-plugin/pantheon.php'];
foreach ( $pantheon_mu_plugins as $file ) {
require_once WPMU_PLUGIN_DIR . '/' . $file;
}
EOF

# Run project-init which should handle mu-plugins at correct path
ddev project-init --help >/dev/null 2>&1 || echo "project-init command exists"
}

@test "db-refresh error handling and exit status" {
set -eu -o pipefail
cd $TESTDIR
Expand Down