Files
xamxam/DEPLOYMENT_MIGRATION.md
Théophile Gervreau-Mercier e789c286de Refactor admin panel and add migration documentation
- Add comprehensive migration guides (DEPLOYMENT_MIGRATION.md, DIRECTORY_STRUCTURE.md, MIGRATION_CHECKLIST.md)
- Refactor admin panel: split add.php, create reusable header/footer
- Update styles: admin.css, common.css, main.css
- Improve public pages: index.php, memoire.php
- Reorganize database documentation into database/docs/
- Update .gitignore and justfile

This prepares for migration to public/ directory structure
2026-02-06 12:14:21 +01:00

15 KiB

Deployment & Dev Server Migration Guide

Analysis of current justfile and required changes for the new directory structure.


🔍 Current Setup Analysis

Current Dev Server ( INCORRECT)

# From justfile line 33
php -S 127.0.0.1:8000

Problems:

  • Serves from project root (all files accessible via web)
  • Exposes sensitive files: database/, tests/, vendor/, config files
  • Doesn't match production DocumentRoot configuration
  • Security risk: .env, database files, source code all accessible

Current Deployment ( INCORRECT)

# 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:

[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:

[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 ():

deploy:
    rsync -vur --progress \
        --exclude 'vendor' --exclude 'tests' ... \
        ./ posterg:/var/www/html/

New () - Two-Step Deployment:

[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:

test-deploy:
    ssh posterg "mkdir -p /var/www/html/database"
    rsync -vur --progress ./database/test.db posterg:/var/www/html/database/test.db

New:

[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 ./database/test.db posterg:/var/www/posterg/database/ && \
        ssh posterg "chown www-data:posterg /var/www/posterg/database/test.db && \
                     chmod 660 /var/www/posterg/database/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/database/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:

root /var/www/html;

Should change to:

root /var/www/posterg/public;

Updated recipe:

[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:

# OLD
root /var/www/html;

# NEW
root /var/www/posterg/public;

Complete example:

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 ~ /database/ {
        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

# 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/database/test.db  → 404
#   http://localhost:8000/config/          → 404
#   http://localhost:8000/src/             → 404

Step 2: Update Nginx Config

# 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

# 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


📊 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 /database/ /database/ (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
// Development router script
if (php_sapi_name() === 'cli-server') {
    $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
    
    // Serve static files directly
    if (file_exists(__DIR__ . $path) && is_file(__DIR__ . $path)) {
        return false;
    }
    
    // Route everything else to index.php
    require __DIR__ . '/index.php';
}

Environment Detection

In your code, detect environment:

<?php
// config/bootstrap.php
define('IS_DEV', php_sapi_name() === 'cli-server');
define('APP_ROOT', dirname(__DIR__)); // /var/www/posterg
define('PUBLIC_ROOT', APP_ROOT . '/public');

Use Relative Requires

// Before (brittle)
require_once '/var/www/html/inc/config.php';

// After (portable)
require_once __DIR__ . '/../config/app.php';
// or
require_once APP_ROOT . '/config/app.php';

🆘 Troubleshooting

Issue: 404 on all pages after deploy

Cause: Nginx DocumentRoot not updated
Fix: Check nginx config has root /var/www/posterg/public;

Issue: PHP includes fail

Cause: Hardcoded paths or wrong APP_ROOT
Fix: Use __DIR__ or define APP_ROOT constant

Issue: Database connection fails

Cause: Path to database file wrong
Fix: Update path from database/test.db to ../database/test.db (from public/)

Issue: Can't write to cache/logs

Cause: Wrong permissions on var/ directory
Fix: sudo chown -R www-data:posterg /var/www/posterg/var && chmod -R 775 /var/www/posterg/var


Next Steps

  1. Create PATHS_UPDATE.md - document all PHP file path changes needed
  2. Create public/router.php - for cleaner dev URLs
  3. Update all require/include statements to use relative paths
  4. Test migration on staging/local first
  5. Schedule production deployment during low-traffic period