More semantically accurate: contains SQLite files, schema, fixtures, test data. Updated all references in code, scripts, docs.
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:
storage/,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 ./storage/test.db posterg:/var/www/html/storage/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 ./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:
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 ~ /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
# 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
# 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 servestarts 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:
📊 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
// 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 storage/test.db to ../storage/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
- Create
PATHS_UPDATE.md- document all PHP file path changes needed - Create
public/router.php- for cleaner dev URLs - Update all
require/includestatements to use relative paths - Test migration on staging/local first
- Schedule production deployment during low-traffic period