Skip to content

Commit 3f73176

Browse files
committed
feat(pantheon): add support for root-level WordPress installations
Adds configurable DOCROOT variable for Pantheon projects to support legacy sites with WordPress at application root (no subdirectory). Modern sites with web/ subdirectory remain fully supported. Changes include: - Interactive docroot prompt in project-configure - Path handling in all commands (project-init, project-wp, theme-activate) - Nginx template with {{DOCROOT}} placeholder replacement - Updated install.yaml for wp-config and mu-plugin path detection - Comprehensive documentation with troubleshooting guidance - New integration test for root-level WordPress validation Backward compatible - existing projects continue working without changes.
1 parent 6a4823a commit 3f73176

File tree

12 files changed

+209
-22
lines changed

12 files changed

+209
-22
lines changed

CLAUDE.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,11 @@ The add-on uses a **modular command approach** where `project-init` orchestrates
6868
## Hosting Provider Support
6969

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

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

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,11 @@ ddev project-init
6767

6868
## 🌐 Hosting Provider Support
6969

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

7676
## 📋 Installation Options
7777

commands/host/project-configure

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,14 @@ case "$HOST_PROVIDER" in
134134
echo "--------------------------"
135135
prompt_input "Site machine name (e.g., my-site)" "${HOSTING_SITE}" HOSTING_SITE
136136
prompt_input "Default environment (dev/test/live)" "${HOSTING_ENV:-dev}" HOSTING_ENV
137+
prompt_input "Document root directory (web for modern sites, or leave empty for root/legacy sites)" "${DOCROOT:-web}" DOCROOT
138+
139+
# Normalize empty/dot to empty string for root-level WordPress
140+
if [ "$DOCROOT" = "." ]; then
141+
DOCROOT=""
142+
fi
143+
144+
echo "💡 Document root configured: ${DOCROOT:-[root/application root]}"
137145
;;
138146
"wpengine")
139147
echo "⚡ WPEngine Configuration:"
@@ -313,6 +321,7 @@ case "$HOST_PROVIDER" in
313321
"pantheon")
314322
write_config_var "HOSTING_SITE" "$HOSTING_SITE"
315323
write_config_var "HOSTING_ENV" "$HOSTING_ENV"
324+
write_config_var "DOCROOT" "$DOCROOT"
316325
;;
317326
"wpengine")
318327
write_config_var "HOSTING_SITE" "$HOSTING_SITE"
@@ -463,13 +472,22 @@ if [ -n "$PROXY_URL" ]; then
463472
esac
464473

465474
# Update the nginx configuration with actual values
475+
# Replace {{DOCROOT}} placeholder with actual docroot value
476+
# Handle empty docroot by removing trailing slash
477+
if [ -z "$DOCROOT" ]; then
478+
NGINX_ROOT="/var/www/html"
479+
else
480+
NGINX_ROOT="/var/www/html/$DOCROOT"
481+
fi
482+
466483
sed -i.bak \
467484
-e "s|HOSTING_ENV-HOSTING_SITE.HOSTING_DOMAIN|$PROXY_HOST|g" \
468-
-e "s|/var/www/html/web|/var/www/html/$DOCROOT|g" \
485+
-e "s|/var/www/html/{{DOCROOT}}|$NGINX_ROOT|g" \
486+
-e "s|/var/www/html/web|$NGINX_ROOT|g" \
469487
".ddev/nginx_full/nginx-site.conf"
470488
rm -f ".ddev/nginx_full/nginx-site.conf.bak"
471489

472-
echo " ✅ Nginx proxy configuration updated (docroot: $DOCROOT, proxy: $PROXY_HOST)"
490+
echo " ✅ Nginx proxy configuration updated (docroot: ${DOCROOT:-[root]}, proxy: $PROXY_HOST)"
473491
else
474492
echo " ⚠️ .ddev/nginx_full/nginx-site.conf not found - proxy configuration not updated"
475493
fi

commands/host/project-init

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,21 @@ refresh_command='ddev db-refresh'
3333

3434
# Handle Pantheon mu-plugin conflicts early (before any wp-cli commands)
3535
echo -e "\n${construction} ${yellow} Checking for Pantheon mu-plugin conflicts...${NC} ${construction}\n"
36-
PANTHEON_LOADER="web/wp-content/mu-plugins/pantheon-mu-loader.php"
36+
37+
# Determine the correct path based on whether docroot is configured
38+
# Check DOCROOT from config.yaml (empty string means root)
39+
if [ -z "${DOCROOT}" ] || [ "${DOCROOT}" = "." ]; then
40+
MU_PLUGINS_PATH="wp-content/mu-plugins"
41+
else
42+
MU_PLUGINS_PATH="${DOCROOT}/wp-content/mu-plugins"
43+
fi
44+
45+
PANTHEON_LOADER="${MU_PLUGINS_PATH}/pantheon-mu-loader.php"
3746
if [ -f "$PANTHEON_LOADER" ]; then
3847
# Check if the loader tries to load pantheon-mu-plugin
3948
if grep -q "pantheon-mu-plugin/pantheon.php" "$PANTHEON_LOADER"; then
4049
# Check if the actual plugin directory exists
41-
if [ ! -d "web/wp-content/mu-plugins/pantheon-mu-plugin" ]; then
50+
if [ ! -d "${MU_PLUGINS_PATH}/pantheon-mu-plugin" ]; then
4251
echo "📝 Found Pantheon mu-plugin loader but plugin directory missing"
4352
echo " Disabling to prevent PHP fatal errors in DDEV environment..."
4453
mv "$PANTHEON_LOADER" "${PANTHEON_LOADER}.disabled"

commands/host/project-wp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,15 @@ load_kanopi_config
2121
# Determine docroot based on hosting provider
2222
case "${HOSTING_PROVIDER}" in
2323
"pantheon")
24-
DOCROOT="web"
24+
# Handle application root case (. or empty)
25+
if [ "$DOCROOT" = "." ] || [ -z "$DOCROOT" ]; then
26+
DOCROOT=""
27+
DOCROOT_PATH="/var/www/html"
28+
echo "Using application root (no subfolder) for Pantheon"
29+
else
30+
DOCROOT_PATH="/var/www/html/${DOCROOT}"
31+
echo "Using docroot: $DOCROOT for hosting provider: $HOSTING_PROVIDER"
32+
fi
2533
;;
2634
"wpengine")
2735
# Use configured DOCROOT from environment, fallback to 'web'

commands/web/theme-activate

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ load_kanopi_config
1515
# Determine docroot based on hosting provider
1616
case "${HOSTING_PROVIDER}" in
1717
"pantheon")
18-
DOCROOT_PATH="/var/www/html/web"
18+
# Handle application root case (. or empty)
19+
if [ "$DOCROOT" = "." ] || [ -z "$DOCROOT" ]; then
20+
DOCROOT_PATH="/var/www/html"
21+
else
22+
DOCROOT_PATH="/var/www/html/${DOCROOT}"
23+
fi
1924
;;
2025
"wpengine")
2126
# Use configured DOCROOT from environment, fallback to 'web'

docs/providers/pantheon.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Full integration with Pantheon hosting including Terminus CLI, automated backup
77
### Required Variables
88
- `HOSTING_SITE` - Pantheon site machine name
99
- `HOSTING_ENV` - Default environment for database pulls (dev/test/live)
10+
- `DOCROOT` - Document root directory (`web` recommended for modern sites, empty for root-level WordPress on legacy sites)
1011
- `MIGRATE_DB_SOURCE` - Source project for migrations (optional)
1112
- `MIGRATE_DB_ENV` - Source environment for migrations (optional)
1213

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

34+
## WordPress Installation Location
35+
36+
Pantheon sites can have WordPress installed in different locations:
37+
38+
### Modern Sites (Recommended)
39+
- **Document root**: `web/`
40+
- **WordPress core**: `/web/` directory
41+
- **Configuration during setup**: Enter "web" when prompted for document root
42+
- **Best for**: New projects, Composer-managed WordPress, clean directory structure
43+
44+
### Legacy Sites (Alternative)
45+
- **Document root**: Root/application root
46+
- **WordPress core**: Root directory (no subdirectory)
47+
- **Configuration during setup**: Leave document root empty or press Enter when prompted
48+
- **Best for**: Older Pantheon sites, sites migrated from other hosts
49+
50+
### Configuration Examples
51+
52+
**Modern site with web/ subdirectory:**
53+
```bash
54+
ddev project-configure
55+
# When prompted: "Document root directory (web for modern sites, or leave empty for root/legacy sites) [web]:"
56+
# Enter: web (or press Enter to accept default)
57+
```
58+
59+
**Legacy site with root-level WordPress:**
60+
```bash
61+
ddev project-configure
62+
# When prompted: "Document root directory (web for modern sites, or leave empty for root/legacy sites) [web]:"
63+
# Enter: (press Enter without typing anything, or explicitly type empty string)
64+
```
65+
66+
### Troubleshooting 404 Errors
67+
68+
If you experience 404 errors after setup, verify your document root configuration:
69+
70+
1. **Check your Pantheon site structure**:
71+
- Does `wp-config.php` exist in the root directory or in a `web/` subdirectory?
72+
- Where are `wp-content/`, `wp-includes/`, and `wp-admin/` located?
73+
74+
2. **Verify DDEV configuration**:
75+
```bash
76+
grep '^docroot:' .ddev/config.yaml
77+
# Should show: docroot: web (for modern sites)
78+
# Or: docroot: "" (for root-level WordPress)
79+
```
80+
81+
3. **Re-run configuration if needed**:
82+
```bash
83+
ddev project-configure
84+
# Provide the correct document root
85+
ddev restart
86+
```
87+
88+
### Migrating Between Configurations
89+
90+
**Existing projects** with `web/` subdirectory continue working without changes. To explicitly update configuration:
91+
92+
```bash
93+
ddev project-configure
94+
# Re-enter your settings with correct document root
95+
ddev restart
96+
```
97+
3298
## Features
3399

34100
### Smart Database Refresh

install.yaml

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,21 @@ post_install_actions:
7575
done
7676
7777
if [ -n "$CONFIG_FILE" ]; then
78-
DOCROOT=$(grep '^docroot:' "$CONFIG_FILE" 2>/dev/null | cut -d: -f2 | tr -d ' "'\''' || echo "web")
78+
DOCROOT=$(grep '^docroot:' "$CONFIG_FILE" 2>/dev/null | cut -d: -f2 | tr -d ' "'\''')
79+
# If DOCROOT is empty or not found, default to empty string (root-level WordPress)
80+
if [ -z "$DOCROOT" ]; then
81+
DOCROOT=""
82+
fi
7983
else
8084
DOCROOT="web"
8185
fi
8286
83-
WP_CONFIG_PATH="../../${DOCROOT}/wp-config.php"
87+
# Handle empty docroot (root-level WordPress)
88+
if [ -z "$DOCROOT" ]; then
89+
WP_CONFIG_PATH="../../wp-config.php"
90+
else
91+
WP_CONFIG_PATH="../../${DOCROOT}/wp-config.php"
92+
fi
8493
8594
# Add ddev-managed settings to wp-config.php
8695
if [ -f "$WP_CONFIG_PATH" ]; then
@@ -125,18 +134,29 @@ post_install_actions:
125134
done
126135
127136
if [ -n "$CONFIG_FILE" ]; then
128-
DOCROOT=$(grep '^docroot:' "$CONFIG_FILE" 2>/dev/null | cut -d: -f2 | tr -d ' "'\''' || echo "web")
137+
DOCROOT=$(grep '^docroot:' "$CONFIG_FILE" 2>/dev/null | cut -d: -f2 | tr -d ' "'\''')
138+
# If DOCROOT is empty or not found, default to empty string (root-level WordPress)
139+
if [ -z "$DOCROOT" ]; then
140+
DOCROOT=""
141+
fi
129142
else
130143
DOCROOT="web"
131144
fi
132145
133-
PANTHEON_LOADER="../${DOCROOT}/wp-content/mu-plugins/pantheon-mu-loader.php"
146+
# Handle empty docroot (root-level WordPress)
147+
if [ -z "$DOCROOT" ]; then
148+
PANTHEON_LOADER="../wp-content/mu-plugins/pantheon-mu-loader.php"
149+
PANTHEON_PLUGIN_DIR="../wp-content/mu-plugins/pantheon-mu-plugin"
150+
else
151+
PANTHEON_LOADER="../${DOCROOT}/wp-content/mu-plugins/pantheon-mu-loader.php"
152+
PANTHEON_PLUGIN_DIR="../${DOCROOT}/wp-content/mu-plugins/pantheon-mu-plugin"
153+
fi
134154
135155
if [ -f "$PANTHEON_LOADER" ]; then
136156
# Check if the loader tries to load pantheon-mu-plugin
137157
if grep -q "pantheon-mu-plugin/pantheon.php" "$PANTHEON_LOADER"; then
138158
# Check if the actual plugin directory exists
139-
if [ ! -d "../${DOCROOT}/wp-content/mu-plugins/pantheon-mu-plugin" ]; then
159+
if [ ! -d "$PANTHEON_PLUGIN_DIR" ]; then
140160
echo "📝 Found Pantheon mu-plugin loader but plugin directory missing"
141161
echo " Disabling to prevent PHP fatal errors in DDEV environment..."
142162
mv "$PANTHEON_LOADER" "${PANTHEON_LOADER}.disabled"
@@ -260,14 +280,25 @@ removal_actions:
260280
done
261281
262282
if [ -n "$CONFIG_FILE" ]; then
263-
DOCROOT=$(grep '^docroot:' "$CONFIG_FILE" 2>/dev/null | cut -d: -f2 | tr -d ' "'\''' || echo "web")
283+
DOCROOT=$(grep '^docroot:' "$CONFIG_FILE" 2>/dev/null | cut -d: -f2 | tr -d ' "'\''')
284+
# If DOCROOT is empty or not found, default to empty string (root-level WordPress)
285+
if [ -z "$DOCROOT" ]; then
286+
DOCROOT=""
287+
fi
264288
else
265289
DOCROOT="web"
266290
fi
267291
268-
PANTHEON_LOADER_DISABLED="../${DOCROOT}/wp-content/mu-plugins/pantheon-mu-loader.php.disabled"
269-
if [ -f "$PANTHEON_LOADER_DISABLED" ]; then
292+
# Handle empty docroot (root-level WordPress)
293+
if [ -z "$DOCROOT" ]; then
294+
PANTHEON_LOADER_DISABLED="../wp-content/mu-plugins/pantheon-mu-loader.php.disabled"
295+
PANTHEON_LOADER="../wp-content/mu-plugins/pantheon-mu-loader.php"
296+
else
297+
PANTHEON_LOADER_DISABLED="../${DOCROOT}/wp-content/mu-plugins/pantheon-mu-loader.php.disabled"
270298
PANTHEON_LOADER="../${DOCROOT}/wp-content/mu-plugins/pantheon-mu-loader.php"
299+
fi
300+
301+
if [ -f "$PANTHEON_LOADER_DISABLED" ]; then
271302
mv "$PANTHEON_LOADER_DISABLED" "$PANTHEON_LOADER"
272303
echo "✅ Restored Pantheon mu-plugin loader (was disabled during add-on installation)"
273304
fi

nginx_full/nginx-site.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ server {
1212
listen 80 default_server;
1313
listen 443 ssl default_server;
1414

15-
root /var/www/html/web;
15+
root /var/www/html/{{DOCROOT}};
1616

1717
ssl_certificate /etc/ssl/certs/master.crt;
1818
ssl_certificate_key /etc/ssl/certs/master.key;

scripts/load-config.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ load_kanopi_config() {
3434
if [[ "${HOSTING_PROVIDER}" == "pantheon" ]]; then
3535
export HOSTING_SITE=${HOSTING_SITE:-''}
3636
export HOSTING_ENV=${HOSTING_ENV:-'dev'}
37+
export DOCROOT=${DOCROOT:-''} # Pantheon default is root, but can be configured to 'web'
3738
# Migration Configuration for Pantheon
3839
export MIGRATE_DB_SOURCE=${MIGRATE_DB_SOURCE:-''}
3940
export MIGRATE_DB_ENV=${MIGRATE_DB_ENV:-''}

0 commit comments

Comments
 (0)