Skip to content

Commit 9cb4eee

Browse files
committed
fix: harden setup.sh, patch security and reliability bugs
setup.sh: - restore.sh: rewrite gzip detection with case pattern (unterminated quote crashed restores; escapes opaque) - health-check.sh: emit real ANSI via printf, not Unicode ␛ glyph - .env.example: key-based sed replacement (lowercase pattern never matched DB_PASSWORD=, leaking secrets) - wpcli: image follows PHP_VERSION instead of pinning 8.4 - install: drop --force, add wp-config preflight guard (--force silently overwrote wp-config.php on re-run) - install: drop redundant wait-db dep (up already waits) - clean: support FORCE=1 for non-interactive runs - mailpit: remove dev profile so make up starts it - phpMyAdmin: drop PMA_ARBITRARY: 1 to shrink attack surface - permissions: 0775 dirs / 0664 files, backups/ 0700 (0777 exposed dumps to any host user) - PROJECT_NAME: reject anything outside [A-Za-z0-9_-] - deps: require openssl (used for password generation) - trap EXIT/INT/TERM: clean partial project dir on abort - php.ini: stop hardcoding date.timezone, defer to TZ - .gitignore: plugins/* + !plugins/.gitkeep pattern, backups/, generated helper scripts, *.sql.gz.tmp - Makefile: split .PHONY across lines, drop undefined prune-backups target - chmod +x: consolidate (was duplicated 4x) - validate generated docker-compose.yml before exit README: PHP badge 8.2 -> 8.4 to match default CONTRIBUTING.md: add (badge linked to missing file)
1 parent f662f3f commit 9cb4eee

3 files changed

Lines changed: 157 additions & 61 deletions

File tree

CONTRIBUTING.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Contributing to WP Quick Dev
2+
3+
Thanks for your interest in improving WP Quick Dev.
4+
5+
## Reporting Issues
6+
7+
- Search existing issues first to avoid duplicates.
8+
- Include: OS, Docker version, PHP version, exact command, full error output.
9+
- For setup failures, run with `bash -x ./setup.sh project-name` and attach the trace.
10+
11+
## Pull Requests
12+
13+
1. Fork the repo and create a feature branch from `main`.
14+
2. Keep changes focused — one logical change per PR.
15+
3. Run the setup end-to-end before submitting:
16+
```bash
17+
./setup.sh test-project
18+
cd test-project
19+
make up && make install
20+
make health
21+
make clean FORCE=1
22+
```
23+
4. Run `shellcheck setup.sh` and address findings where reasonable.
24+
5. Validate the generated docker-compose:
25+
```bash
26+
cd test-project && docker compose config >/dev/null
27+
```
28+
6. Update `README.md` if your change affects user-visible behavior.
29+
7. Write commits in present tense, imperative mood (e.g. `Fix mailpit profile activation`).
30+
31+
## Coding Standards
32+
33+
- Bash: `set -euo pipefail` at top of every script.
34+
- Quoted heredocs (`<< 'EOF'`) for generated config to prevent unintended expansion.
35+
- Unquoted heredocs only when variable substitution is intended.
36+
- No secrets in committed files; `.env.example` must contain placeholders only.
37+
- Permissions: prefer `0775`/`0664` over `0777` outside of last-resort fixes.
38+
39+
## Scope
40+
41+
WP Quick Dev is a development-only tool. PRs adding production-hardening features
42+
(TLS, secret managers, multi-host orchestration) are out of scope. Keep the tool
43+
small and fast.
44+
45+
## License
46+
47+
By contributing, you agree your contributions are licensed under the MIT License
48+
(see `LICENSE`).

README.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@
22

33
[![Docker](https://img.shields.io/badge/Docker-20.10+-blue?logo=docker&logoColor=white)](https://www.docker.com/)
44
[![WordPress](https://img.shields.io/badge/WordPress-6.0+-21759B?logo=wordpress&logoColor=white)](https://wordpress.org/)
5-
[![PHP](https://img.shields.io/badge/PHP-8.2-777BB4?logo=php&logoColor=white)](https://php.net/)
5+
[![PHP](https://img.shields.io/badge/PHP-8.4-777BB4?logo=php&logoColor=white)](https://php.net/)
66
[![MariaDB](https://img.shields.io/badge/MariaDB-11.0-003545?logo=mariadb&logoColor=white)](https://mariadb.org/)
77
[![License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
88
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)
99
[![Platform](https://img.shields.io/badge/Platform-Linux%20%7C%20macOS%20%7C%20Windows-lightgrey)](README.md)
10-
[![GitHub](https://img.shields.io/badge/GitHub-gl0bal01-181717?logo=github&logoColor=white)](https://github.com/gl0bal01)
1110

1211
> **Lightning-fast WordPress development environment for rapid plugin and theme prototyping**
1312
@@ -476,8 +475,6 @@ Contributions are welcome! To contribute:
476475

477476
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
478477

479-
---
480-
481478
**Happy Coding!** 🎉
482479

483480
*Built with ❤️ for the WordPress developer community*

setup.sh

Lines changed: 108 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,26 @@ yellow() { echo -e "\033[33m$1\033[0m"; }
1212
blue() { echo -e "\033[34m$1\033[0m"; }
1313

1414
PROJECT_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+
1523
echo "$(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
1836
check_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
5476
detect_user_ids
5577

5678
mkdir -p "$PROJECT_NAME"
57-
cd "$PROJECT_NAME"
79+
PROJECT_DIR="$(cd "$PROJECT_NAME" && pwd)"
80+
cd "$PROJECT_DIR"
5881

5982
############################################
6083
# .env + .env.example
@@ -110,8 +133,11 @@ fi
110133

111134
# Always provide an example for teammates
112135
cp -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
277301
cat > 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
283311
MAKEFLAGS += --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
356385
shell: ## Access WordPress container shell
357386
@$(DOCKER_COMPOSE) exec wordpress bash
@@ -365,8 +394,10 @@ logs: ## Show logs (usage: make logs [service=wordpress])
365394
fix-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
372403
plugin: fix-permissions ## Create plugin (usage: make plugin name=my-plugin)
@@ -543,7 +574,7 @@ html_errors = On
543574
544575
; Session and timezone
545576
session.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
549580
opcache.enable = 1
@@ -571,25 +602,26 @@ cat > .gitignore << 'GITIGNORE_EOF'
571602
wordpress/
572603
uploads/
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
595627
Thumbs.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
610643
GITIGNORE_EOF
611644

612645
############################################
@@ -810,7 +843,6 @@ docker compose exec -T db sh -lc '
810843
echo "✅ Backup created: $FILE";
811844
'
812845
BACKUP_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
827859
fi
828860
829861
FILE="$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+
'
850884
RESTORE_EOF
851-
chmod +x restore.sh
852885

853886
############################################
854887
# Health check script
855888
############################################
856889
cat > health-check.sh << 'HEALTH_EOF'
857890
#!/bin/bash
858891
set -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
9701004
fi
9711005
HEALTH_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"
10631096
fi
10641097
PLUGIN_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
10721104
touch 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
10781112
chmod +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+
10801131
echo ""
10811132
green "✅ Enhanced WordPress Development Environment created with Plugin Development Workflow!"
10821133
echo ""

0 commit comments

Comments
 (0)