# Deployment & Dev Server Migration Guide Analysis of current `justfile` and required changes for the new directory structure. --- ## ๐Ÿ” Current Setup Analysis ### Current Dev Server (โŒ INCORRECT) ```bash # From justfile line 33 php -S 127.0.0.1:8000 ``` **Problems:** - Serves from project root (all files accessible via web) - Exposes sensitive files: `storage/`, `tests/`, `vendor/`, config files - Doesn't match production DocumentRoot configuration - Security risk: `.env`, database files, source code all accessible ### Current Deployment (โŒ INCORRECT) ```bash # From justfile lines 56-75 rsync -vur --progress \ --exclude 'vendor' --exclude 'tests' --exclude '*.db' ... \ ./ posterg:/var/www/html/ ``` **Problems:** - Deploys entire project to DocumentRoot - Relies on exclusions to hide sensitive files (error-prone) - Wrong structure: `/var/www/html/` should only contain public files - Private files (`src/`, `config/`) are still in DocumentRoot - Nginx serves from `/var/www/html/` exposing everything not excluded --- ## โœ… Required Changes ### 1. New Directory Structure on Server **Before (current):** ``` /var/www/html/ # DocumentRoot โŒ โ”œโ”€โ”€ index.php # Public โ”œโ”€โ”€ search.php # Public โ”œโ”€โ”€ admin/ # Public (protected by nginx) โ”œโ”€โ”€ assets/ # Public โ”œโ”€โ”€ inc/ # โŒ EXPOSED (config files!) โ”œโ”€โ”€ lib/ # โŒ EXPOSED (source code!) โ”œโ”€โ”€ database/ # โŒ EXPOSED (database files!) โ””โ”€โ”€ vendor/ # โŒ (excluded but in wrong place) ``` **After (recommended):** ``` /var/www/posterg/ # Application root (private) โ”œโ”€โ”€ public/ # DocumentRoot โœ… (only this exposed) โ”‚ โ”œโ”€โ”€ index.php โ”‚ โ”œโ”€โ”€ search.php โ”‚ โ”œโ”€โ”€ memoire.php โ”‚ โ”œโ”€โ”€ admin/ โ”‚ โ””โ”€โ”€ assets/ โ”œโ”€โ”€ src/ # โœ… PRIVATE โ”œโ”€โ”€ config/ # โœ… PRIVATE โ”œโ”€โ”€ database/ # โœ… PRIVATE โ”œโ”€โ”€ vendor/ # โœ… PRIVATE โ”œโ”€โ”€ var/ # โœ… PRIVATE (cache, logs) โ””โ”€โ”€ lib/ # โœ… PRIVATE ``` --- ## ๐Ÿ“ Updated Justfile ### Change 1: Dev Server **Current:** ```just [group('dev')] serve: @echo "๐Ÿš€ Starting Post-ERG development server" @echo "๐Ÿ“ Public site: http://localhost:8000" @echo "๐Ÿ“ Admin panel: http://localhost:8000/admin/" @php -S 127.0.0.1:8000 ``` **New:** ```just [group('dev')] serve: @echo "๐Ÿš€ Starting Post-ERG development server" @echo "========================================" @echo "" @echo "๐Ÿ“ Public site: http://localhost:8000" @echo "๐Ÿ“ Admin panel: http://localhost:8000/admin/" @echo "" @echo "๐Ÿ”’ Serving from public/ directory (matches production)" @echo "" @if [ -d "vendor/php-live-reload" ]; then \ echo "โœจ Live reload enabled - browser auto-refreshes on file save!"; \ else \ echo "๐Ÿ’ก Tip: Run 'just setup' to enable live reload"; \ fi @echo "" @echo "Press Ctrl+C to stop" @echo "" @php -S 127.0.0.1:8000 -t public/ # Alternative: If you need router script for URL rewriting [group('dev')] serve-router: @echo "๐Ÿš€ Starting with router script (for clean URLs)" @php -S 127.0.0.1:8000 -t public/ public/router.php ``` **Key change:** `-t public/` flag tells PHP server to use `public/` as DocumentRoot --- ### Change 2: Deployment Strategy **Current (โŒ):** ```just deploy: rsync -vur --progress \ --exclude 'vendor' --exclude 'tests' ... \ ./ posterg:/var/www/html/ ``` **New (โœ…) - Two-Step Deployment:** ```just [group('deploy')] deploy: @echo "๐Ÿ“ค Deploying Post-ERG site" @echo "==========================" @echo "" @echo "Step 1: Deploying application files..." # Deploy entire application to private directory rsync -vur --progress \ --exclude '.git*' \ --exclude '.jj' \ --exclude 'tests/' \ --exclude 'docs/' \ --exclude '*.md' \ --exclude 'justfile*' \ --exclude 'setup-dev.sh' \ --exclude 'migrate-structure.sh' \ --exclude 'database/test.db' \ --exclude 'database/backup_*' \ --exclude 'database/fixtures/' \ --exclude 'var/cache/*' \ --exclude 'var/logs/*' \ --exclude '.DS_Store' \ ./ posterg:/var/www/posterg/ @echo "" @echo "Step 2: Setting up directory structure..." # Create necessary directories on server ssh posterg "mkdir -p /var/www/posterg/var/{cache,logs,tmp} && \ chown -R www-data:posterg /var/www/posterg/var && \ chmod -R 775 /var/www/posterg/var" @echo "" @echo "Step 3: Setting permissions..." # Set correct ownership and permissions ssh posterg "cd /var/www/posterg && \ chown -R www-data:posterg . && \ find . -type d -exec chmod 755 {} \; && \ find . -type f -exec chmod 644 {} \; && \ chmod -R 775 var/ && \ chmod -R 775 database/ && \ chmod 660 database/*.db" @echo "" @echo "โœ… Deployment complete!" @echo "" @echo "๐Ÿ” Verify deployment:" @echo " โ€ข Public: https://posterg.erg.be/" @echo " โ€ข Admin: https://posterg.erg.be/admin/" @echo "" @echo "๐Ÿ“ Server structure:" @echo " โ€ข App root: /var/www/posterg/" @echo " โ€ข DocumentRoot: /var/www/posterg/public/" # Quick deploy - only public files (faster for frontend changes) [group('deploy')] deploy-public: @echo "โšก Quick deploy: public files only" rsync -vur --progress \ --delete \ ./public/ posterg:/var/www/posterg/public/ @echo "โœ… Public files updated" # Deploy only code (no assets) [group('deploy')] deploy-code: @echo "โšก Deploying PHP code only" rsync -vur --progress \ --include='**.php' \ --include='**/' \ --exclude='*' \ ./ posterg:/var/www/posterg/ @echo "โœ… Code updated" ``` --- ### Change 3: Database Deployment **Current:** ```just test-deploy: ssh posterg "mkdir -p /var/www/html/database" rsync -vur --progress ./storage/test.db posterg:/var/www/html/storage/test.db ``` **New:** ```just [group('deploy')] deploy-database: @echo "๐Ÿ“Š Deploying database..." @echo "โš ๏ธ This will overwrite the remote database!" @read -p "Continue? [y/N] " -n 1 -r; \ echo; \ if [[ $$REPLY =~ ^[Yy]$$ ]]; then \ ssh posterg "mkdir -p /var/www/posterg/database" && \ rsync -vur --progress ./storage/test.db posterg:/var/www/posterg/storage/ && \ ssh posterg "chown www-data:posterg /var/www/posterg/storage/test.db && \ chmod 660 /var/www/posterg/storage/test.db" && \ echo "โœ… Database deployed"; \ else \ echo "โŒ Cancelled"; \ fi # Backup remote database before deploying [group('deploy')] backup-remote-db: @echo "๐Ÿ’พ Backing up remote database..." @ssh posterg "sqlite3 /var/www/posterg/storage/test.db .dump" > database/remote_backup_$(date +%Y%m%d_%H%M%S).sql @echo "โœ… Remote database backed up locally" ``` --- ### Change 4: Updated Nginx Deployment **Current nginx configuration probably points to:** ```nginx root /var/www/html; ``` **Should change to:** ```nginx root /var/www/posterg/public; ``` **Updated recipe:** ```just [group('server')] deploy-nginx: @echo "๐Ÿ”ง Deploying nginx configuration..." @echo "" @echo "โš ๏ธ IMPORTANT: Nginx config must point to /var/www/posterg/public" @echo "" # Check if nginx config has correct DocumentRoot @if ! grep -q "/var/www/posterg/public" nginx/posterg.conf 2>/dev/null; then \ echo "โŒ ERROR: nginx/posterg.conf doesn't contain '/var/www/posterg/public'"; \ echo " Update DocumentRoot before deploying!"; \ exit 1; \ fi rsync -vur --progress ./nginx/posterg.conf posterg:/tmp/posterg.conf rsync -vur --progress ./nginx/deploy-production.sh posterg:/tmp/deploy-production.sh @echo "โœ… Files uploaded to /tmp/ on server" @echo "" @echo "Next steps on the server:" @echo " ssh posterg" @echo " sudo bash /tmp/deploy-production.sh" ``` --- ## ๐Ÿ”ง Nginx Configuration Changes ### Update `nginx/posterg.conf` **Find and replace:** ```nginx # OLD root /var/www/html; # NEW root /var/www/posterg/public; ``` **Complete example:** ```nginx server { listen 80; listen [::]:80; server_name posterg.erg.be; # Redirect to HTTPS return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name posterg.erg.be; # NEW DocumentRoot - only public directory root /var/www/posterg/public; index index.php index.html; # SSL configuration ssl_certificate /etc/letsencrypt/live/posterg.erg.be/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/posterg.erg.be/privkey.pem; # Logs access_log /var/log/nginx/posterg_access.log; error_log /var/log/nginx/posterg_error.log; # Security headers add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; # Deny access to sensitive files (should already be outside public/) location ~ /\. { deny all; } location ~ /storage/ { deny all; } # Admin area - basic auth location /admin/ { auth_basic "Admin Access"; auth_basic_user_file /etc/nginx/.htpasswd; location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/var/run/php/php8.4-fpm.sock; # IMPORTANT: Set correct SCRIPT_FILENAME fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } } # PHP files location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/var/run/php/php8.4-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } # Static files location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; } # Deny access to uploaded files execution location ~ ^/uploads/.*\.php$ { deny all; } } ``` --- ## ๐Ÿ”„ Migration Steps ### Step 1: Local Development ```bash # 1. Create new structure mkdir -p public/{admin,assets} mkdir -p src config var/{cache,logs,tmp} # 2. Move public files mv index.php search.php memoire.php public/ mv assets/* public/assets/ mv admin/* public/admin/ # 3. Move private files mv inc/* config/ # or src/ depending on content mv lib src/lib # 4. Update paths in PHP files (see PATHS_UPDATE.md below) # 5. Test local dev server just serve # Opens http://localhost:8000 # Verify that sensitive files return 404: # http://localhost:8000/storage/test.db โ†’ 404 # http://localhost:8000/config/ โ†’ 404 # http://localhost:8000/src/ โ†’ 404 ``` ### Step 2: Update Nginx Config ```bash # Edit nginx/posterg.conf # Change: root /var/www/html; # To: root /var/www/posterg/public; # Validate locally nginx -t -c nginx/posterg.conf ``` ### Step 3: Deploy ```bash # 1. Backup current production ssh posterg "tar -czf /tmp/posterg-backup-$(date +%Y%m%d).tar.gz /var/www/html" # 2. Create new directory structure ssh posterg "mkdir -p /var/www/posterg" # 3. Deploy application just deploy # 4. Deploy nginx config just deploy-nginx # 5. On server: activate new config ssh posterg sudo bash /tmp/deploy-production.sh # 6. Reload nginx sudo systemctl reload nginx # 7. Test just server-status ``` --- ## ๐Ÿงช Testing Checklist ### Local Testing - [ ] `just serve` starts on port 8000 - [ ] Public site loads: http://localhost:8000 - [ ] Admin loads: http://localhost:8000/admin/ - [ ] Assets load (CSS, images, JS) - [ ] Database files NOT accessible via browser - [ ] Config files NOT accessible via browser - [ ] Tests still pass: `just test` ### Production Testing - [ ] HTTPS works: https://posterg.erg.be/ - [ ] Admin login works - [ ] Search functionality works - [ ] Database connections work - [ ] File uploads work (if applicable) - [ ] Logs written to `/var/www/posterg/var/logs/` - [ ] Sensitive URLs return 404: - https://posterg.erg.be/storage/test.db - https://posterg.erg.be/config/ - https://posterg.erg.be/src/ - https://posterg.erg.be/vendor/ --- ## ๐Ÿ“Š Path Changes Summary | File Type | Current Path | New Path | |-----------|-------------|----------| | Public index | `/index.php` | `/public/index.php` | | Admin panel | `/admin/` | `/public/admin/` | | Assets | `/assets/` | `/public/assets/` | | Config | `/inc/` | `/config/` or `/src/` | | Libraries | `/lib/` | `/src/lib/` | | Database | `/storage/` | `/storage/` (stays) | | Vendor | `/vendor/` | `/vendor/` (stays) | | Tests | `/tests/` | `/tests/` (stays) | --- ## ๐Ÿ”’ Security Improvements ### Before - โŒ All files in DocumentRoot - โŒ Relies on nginx deny rules - โŒ One misconfiguration = full exposure - โŒ Database accessible if nginx fails ### After - โœ… Only `public/` in DocumentRoot - โœ… Physical separation of public/private - โœ… Nginx misconfiguration = site down (not exposed) - โœ… Database physically unreachable via web --- ## ๐Ÿ’ก Tips ### Router Script (for clean URLs) Create `public/router.php` for development: ```php