@@ -12,8 +12,26 @@ yellow() { echo -e "\033[33m$1\033[0m"; }
1212blue () { echo -e " \033[34m$1 \033[0m" ; }
1313
1414PROJECT_NAME=" ${1:- wordpress-dev} "
15+
16+ # Validate PROJECT_NAME (allow letters, digits, hyphens, underscores only)
17+ if ! echo " $PROJECT_NAME " | grep -Eq ' ^[A-Za-z0-9_-]+$' ; then
18+ red " ❌ Invalid project name: '$PROJECT_NAME '"
19+ echo " Use only letters, numbers, hyphens, and underscores."
20+ exit 1
21+ fi
22+
1523echo " $( blue " 🚀 Creating WordPress Development Environment: $PROJECT_NAME " ) "
1624
25+ # Cleanup on interrupt before .env is written
26+ SETUP_COMPLETE=0
27+ cleanup_on_exit () {
28+ if [ " $SETUP_COMPLETE " -eq 0 ] && [ -n " ${PROJECT_DIR:- } " ] && [ -d " $PROJECT_DIR " ] && [ ! -f " $PROJECT_DIR /.env" ]; then
29+ red " ⚠️ Setup interrupted — removing partial project directory: $PROJECT_DIR "
30+ rm -rf " $PROJECT_DIR "
31+ fi
32+ }
33+ trap cleanup_on_exit EXIT INT TERM
34+
1735# Check dependencies
1836check_dependencies () {
1937 local missing_deps=()
@@ -30,6 +48,10 @@ check_dependencies() {
3048 missing_deps+=(" make" )
3149 fi
3250
51+ if ! command -v openssl > /dev/null 2>&1 ; then
52+ missing_deps+=(" openssl" )
53+ fi
54+
3355 if [ ${# missing_deps[@]} -gt 0 ]; then
3456 red " ❌ Missing required dependencies: ${missing_deps[*]} "
3557 echo " Please install the missing dependencies and try again."
@@ -54,7 +76,8 @@ check_dependencies
5476detect_user_ids
5577
5678mkdir -p " $PROJECT_NAME "
57- cd " $PROJECT_NAME "
79+ PROJECT_DIR=" $( cd " $PROJECT_NAME " && pwd) "
80+ cd " $PROJECT_DIR "
5881
5982# ###########################################
6083# .env + .env.example
110133
111134# Always provide an example for teammates
112135cp -f .env .env.example
113- # Remove sensitive data from example
114- sed -i.bak -e ' s/password=wordpress_secure_[a-f0-9]*/password=your_secure_password_here/g' -e ' s/password=root_secure_[a-f0-9]*/password=your_secure_root_password_here/g' .env.example && rm -f .env.example.bak
136+ # Remove sensitive data from example (key-based replacement: works regardless of value pattern)
137+ sed -i.bak \
138+ -e ' s|^\(DB_PASSWORD=\).*|\1your_secure_password_here|' \
139+ -e ' s|^\(DB_ROOT_PASSWORD=\).*|\1your_secure_root_password_here|' \
140+ .env.example && rm -f .env.example.bak
115141
116142# ###########################################
117143# docker-compose.yml (persistent wpcli + exec)
@@ -207,7 +233,6 @@ services:
207233 PMA_HOST: db
208234 PMA_USER: ${DB_USER:-wordpress}
209235 PMA_PASSWORD: ${DB_PASSWORD:-wordpress}
210- PMA_ARBITRARY: 1
211236 UPLOAD_LIMIT: ${PMA_UPLOAD_LIMIT:-128M}
212237 MEMORY_LIMIT: 256M
213238 MAX_EXECUTION_TIME: 300
@@ -220,7 +245,7 @@ services:
220245
221246 # Persistent wpcli container (no more "Creating 2/2" spam)
222247 wpcli:
223- image: wordpress:cli-php8.4
248+ image: " wordpress:cli-php${PHP_VERSION:-8.4}"
224249 working_dir: /var/www/html
225250 command: tail -f /dev/null
226251 environment:
@@ -251,7 +276,6 @@ services:
251276
252277 mailpit:
253278 image: axllent/mailpit:latest
254- profiles: ["dev"]
255279 ports:
256280 - "${MAILPIT_HTTP_PORT:-8025}:8025"
257281 - "${MAILPIT_SMTP_PORT:-1025}:1025"
@@ -277,7 +301,11 @@ DOCKER_EOF
277301cat > Makefile << 'MAKEFILE_EOF '
278302# WordPress Development Environment
279303.DEFAULT_GOAL := help
280- .PHONY: help up down install clean shell db-shell logs plugin theme backup restore db-import list-backups fix-permissions wait-db wp sr cron-run prune-backups phpinfo test health restart plugin-repo plugin-clone plugin-list theme-repo theme-clone
304+ .PHONY: \
305+ help up down install clean shell db-shell logs \
306+ plugin theme backup restore db-import list-backups fix-permissions \
307+ wait-db wp sr cron-run phpinfo test health restart \
308+ plugin-repo plugin-clone plugin-list theme-repo theme-clone
281309
282310# Stop "make[1]: on entre/quitte le répertoire ..." messages
283311MAKEFLAGS += --no-print-directory
@@ -335,11 +363,12 @@ health: ## Check service health
335363 @echo "Database connection test:"
336364 @$(DB_EXEC) mariadb -u"$${DB_USER:-wordpress}" -p"$${DB_PASSWORD:-wordpress}" -e "SELECT 'DB OK' as status, VERSION() as version;" "$${DB_NAME:-wordpress}" 2>/dev/null || echo "❌ Database connection failed"
337365
338- install: up wait-db ## Download, configure and install WordPress
366+ install: up ## Download, configure and install WordPress
367+ @if [ -f wordpress/wp-config.php ]; then echo "⚠️ WordPress already installed (wordpress/wp-config.php exists)."; echo " Run 'make clean' first to reinstall from scratch."; exit 1; fi
339368 @echo "📦 Installing WordPress..."
340- @$(RUN_WP) core download --path=/var/www/html --skip-content --force
369+ @$(RUN_WP) core download --path=/var/www/html --skip-content
341370 @echo "🔧 Creating wp-config.php..."
342- @$(RUN_WP) config create --dbname="$${DB_NAME:-wordpress}" --dbuser="$${DB_USER:-wordpress}" --dbpass="$${DB_PASSWORD:-wordpress}" --dbhost=db --skip-check --force
371+ @$(RUN_WP) config create --dbname="$${DB_NAME:-wordpress}" --dbuser="$${DB_USER:-wordpress}" --dbpass="$${DB_PASSWORD:-wordpress}" --dbhost=db --skip-check
343372 @$(RUN_WP) config set WP_DEBUG "$${WP_DEBUG:-true}" --raw
344373 @$(RUN_WP) config set WP_DEBUG_LOG "$${WP_DEBUG_LOG:-true}" --raw
345374 @$(RUN_WP) config set WP_DEBUG_DISPLAY "$${WP_DEBUG_DISPLAY:-false}" --raw
@@ -349,9 +378,9 @@ install: up wait-db ## Download, configure and install WordPress
349378 @ADMIN_PASS="$$(openssl rand -base64 16 | tr -d '=' | head -c 16)"; ADMIN_EMAIL="admin@$$(echo $${WP_URL:-http://localhost:8080} | sed 's|https\?://||' | sed 's|:.*||').local"; $(RUN_WP) core install --url="$${WP_URL:-http://localhost:8080}" --title="$${PROJECT_NAME:-WordPress Dev} Site" --admin_user=admin --admin_password="$$ADMIN_PASS" --admin_email="$$ADMIN_EMAIL" --skip-email; echo ""; echo "✅ WordPress installed successfully!"; echo "🌐 URL: $${WP_URL:-http://localhost:8080}"; echo "👤 Username: admin"; echo "🔑 Password: $$ADMIN_PASS"; echo "📧 Email: $$ADMIN_EMAIL"
350379 @$(MAKE) fix-permissions
351380
352- clean: ## Reset everything (removes all data!)
381+ clean: ## Reset everything (removes all data!) — pass FORCE=1 for non-interactive
353382 @echo "⚠️ This will delete ALL data including database and uploads!"
354- @read -p "Are you sure? (y/N): " confirm; if [ "$$confirm" = "y" ] || [ "$$confirm" = "Y" ]; then $(DOCKER_COMPOSE) down -v; rm -rf wordpress plugins themes uploads .docker 2>/dev/null || true; echo "✅ Environment reset!"; else echo "❌ Operation cancelled"; fi
383+ @if [ -n "$(FORCE)" ]; then confirm=y; else read -p "Are you sure? (y/N): " confirm; fi ; if [ "$$confirm" = "y" ] || [ "$$confirm" = "Y" ]; then $(DOCKER_COMPOSE) down -v; rm -rf wordpress plugins themes uploads .docker 2>/dev/null || true; echo "✅ Environment reset!"; else echo "❌ Operation cancelled"; fi
355384
356385shell: ## Access WordPress container shell
357386 @$(DOCKER_COMPOSE) exec wordpress bash
@@ -365,8 +394,10 @@ logs: ## Show logs (usage: make logs [service=wordpress])
365394fix-permissions: ## Fix file permissions for development
366395 @echo "🔧 Fixing permissions..."
367396 @mkdir -p wordpress plugins themes uploads backups .docker/mysql-logs
368- @chmod -R 0777 wordpress plugins themes uploads backups 2>/dev/null || true
369- @$(DOCKER_COMPOSE) exec -T wordpress bash -lc 'install -d -m 0777 /var/www/html/wp-content/{plugins,themes,uploads} 2>/dev/null || true; chmod -R 0777 /var/www/html 2>/dev/null || true'
397+ @chmod 0700 backups 2>/dev/null || true
398+ @find wordpress plugins themes uploads -type d -exec chmod 0775 {} + 2>/dev/null || true
399+ @find wordpress plugins themes uploads -type f -exec chmod 0664 {} + 2>/dev/null || true
400+ @$(DOCKER_COMPOSE) exec -T wordpress bash -lc 'install -d -m 0775 /var/www/html/wp-content/plugins /var/www/html/wp-content/themes /var/www/html/wp-content/uploads 2>/dev/null || true; find /var/www/html -type d -exec chmod 0775 {} + 2>/dev/null || true; find /var/www/html -type f -exec chmod 0664 {} + 2>/dev/null || true'
370401 @echo "✅ Permissions fixed"
371402
372403plugin: fix-permissions ## Create plugin (usage: make plugin name=my-plugin)
@@ -543,7 +574,7 @@ html_errors = On
543574
544575; Session and timezone
545576session.gc_maxlifetime = 3600
546- date.timezone = Europe/Paris
577+ ; date.timezone is set via TZ env var from .env (passed by docker-compose)
547578
548579; OPcache for development
549580opcache.enable = 1
@@ -571,25 +602,26 @@ cat > .gitignore << 'GITIGNORE_EOF'
571602wordpress/
572603uploads/
573604
574- # Plugins (each should have their own repo)
575- plugins/
576- # Alternative: ignore all but keep structure
577- # plugins/*
578- # !plugins/.gitkeep
605+ # Plugins (each should have their own repo — preserve directory via .gitkeep)
606+ plugins/*
607+ !plugins/.gitkeep
579608
580- # Themes (if using separate repos for themes too)
581- themes/
582- # Alternative: ignore all but keep structure
583- # themes/*
584- # !themes/.gitkeep
609+ # Themes (each should have their own repo — preserve directory via .gitkeep)
610+ themes/*
611+ !themes/.gitkeep
585612
586613# Database backups (contain sensitive data)
587- backups/*.sql
588- backups/*.sql.gz
614+ backups/
589615
590616# Environment and secrets
591617.env
592618
619+ # Generated helper scripts (regenerated by setup.sh)
620+ backup.sh
621+ restore.sh
622+ health-check.sh
623+ plugin-status.sh
624+
593625# System files
594626.DS_Store
595627Thumbs.db
@@ -603,10 +635,11 @@ Thumbs.db
603635*~
604636
605637# Docker volumes and temp
606- .docker/mysql-logs/*.log
638+ .docker/
607639
608640# Backup temp files
609641*.tmp
642+ *.sql.gz.tmp
610643GITIGNORE_EOF
611644
612645# ###########################################
@@ -810,7 +843,6 @@ docker compose exec -T db sh -lc '
810843 echo "✅ Backup created: $FILE";
811844'
812845BACKUP_EOF
813- chmod +x backup.sh
814846
815847# ###########################################
816848# restore.sh (optional helper; uses /backups in container)
@@ -827,39 +859,41 @@ if [ $# -eq 0 ]; then
827859fi
828860
829861FILE="$1"
830- if [ -f "backups/$FILE" ]; then
831- PATH_IN="/backups/$FILE"
832- elif [ -f "$FILE" ]; then
833- PATH_IN="$FILE"
834- else
835- echo "❌ File not found: $FILE"
836- exit 1
837- fi
838-
839- docker compose exec -T db sh -lc "
840- set -e;
841- [ -f '$PATH_IN' ] || { echo '❌ Not found in container: $PATH_IN'; exit 1; }
842- echo '📦 Restoring from $PATH_IN...';
843- if echo '$PATH_IN' | grep -q '\.gz; then
844- gunzip -c '$PATH_IN' | mariadb -u"\$MYSQL_USER" -p"\$MYSQL_PASSWORD" "\$MYSQL_DATABASE";
845- else
846- mariadb -u"\$MYSQL_USER" -p"\$MYSQL_PASSWORD" "\$MYSQL_DATABASE" < '$PATH_IN';
847- fi
848- echo '✅ Restore done.'
849- "
862+ case "$FILE" in
863+ /backups/*) PATH_IN="$FILE" ;;
864+ /*) PATH_IN="$FILE" ;;
865+ backups/*) PATH_IN="/backups/${FILE#backups/}" ;;
866+ *) PATH_IN="/backups/$FILE" ;;
867+ esac
868+
869+ export PATH_IN
870+ docker compose exec -T -e PATH_IN db sh -lc '
871+ set -e
872+ [ -f "$PATH_IN" ] || { echo "❌ Backup file not found in container: $PATH_IN"; exit 1; }
873+ echo "📦 Restoring from $PATH_IN..."
874+ case "$PATH_IN" in
875+ *.gz)
876+ gunzip -c "$PATH_IN" | mariadb -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE"
877+ ;;
878+ *)
879+ mariadb -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE" < "$PATH_IN"
880+ ;;
881+ esac
882+ echo "✅ Database restored from $PATH_IN"
883+ '
850884RESTORE_EOF
851- chmod +x restore.sh
852885
853886# ###########################################
854887# Health check script
855888# ###########################################
856889cat > health-check.sh << 'HEALTH_EOF '
857890#!/bin/bash
858891set -euo pipefail
859- red() { echo -e "␛[31m$1␛[0m"; }
860- green() { echo -e "␛[32m$1␛[0m"; }
861- yellow() { echo -e "␛[33m$1␛[0m"; }
862- blue() { echo -e "␛[34m$1␛[0m"; }
892+ ESC=$(printf '\033')
893+ red() { printf '%s[31m%s%s[0m\n' "$ESC" "$1" "$ESC"; }
894+ green() { printf '%s[32m%s%s[0m\n' "$ESC" "$1" "$ESC"; }
895+ yellow() { printf '%s[33m%s%s[0m\n' "$ESC" "$1" "$ESC"; }
896+ blue() { printf '%s[34m%s%s[0m\n' "$ESC" "$1" "$ESC"; }
863897
864898[ -f .env ] && source .env || { red "❌ .env file not found"; exit 1; }
865899
@@ -969,7 +1003,6 @@ else
9691003 exit 1
9701004fi
9711005HEALTH_EOF
972- chmod +x health-check.sh
9731006
9741007# ###########################################
9751008# Plugin development helper scripts
@@ -1062,7 +1095,6 @@ else
10621095 echo "WordPress container not running"
10631096fi
10641097PLUGIN_STATUS_EOF
1065- chmod +x plugin-status.sh
10661098
10671099# ###########################################
10681100# Final output + folder perms
@@ -1072,11 +1104,30 @@ mkdir -p plugins themes uploads backups .docker/mysql-logs wordpress
10721104touch plugins/.gitkeep themes/.gitkeep
10731105
10741106# Dev-friendly perms so both www-data and wpcli can write
1075- chmod -R 0777 wordpress plugins themes uploads backups 2> /dev/null || true
1107+ find wordpress plugins themes uploads -type d -exec chmod 0775 {} + 2> /dev/null || true
1108+ find wordpress plugins themes uploads -type f -exec chmod 0664 {} + 2> /dev/null || true
1109+ chmod 0700 backups 2> /dev/null || true
10761110
10771111# Set proper permissions for scripts
10781112chmod +x backup.sh restore.sh health-check.sh plugin-status.sh
10791113
1114+ # Validate generated docker-compose.yml syntax
1115+ if command -v docker > /dev/null 2>&1 ; then
1116+ if docker compose version > /dev/null 2>&1 ; then
1117+ if ! docker compose config > /dev/null 2>&1 ; then
1118+ red " ❌ Generated docker-compose.yml is invalid"
1119+ exit 1
1120+ fi
1121+ elif command -v docker-compose > /dev/null 2>&1 ; then
1122+ if ! docker-compose config > /dev/null 2>&1 ; then
1123+ red " ❌ Generated docker-compose.yml is invalid"
1124+ exit 1
1125+ fi
1126+ fi
1127+ fi
1128+
1129+ SETUP_COMPLETE=1
1130+
10801131echo " "
10811132green " ✅ Enhanced WordPress Development Environment created with Plugin Development Workflow!"
10821133echo " "
0 commit comments