Fix admin CSS not loading and quirks mode issues

Fixed multiple issues in admin panel:

1. CSS path: modern-normalize.css → modern-normalize.min.css
   (File is actually named .min.css)

2. Icon path: assets/icon.svg → /assets/admin_favicon.svg
   (Was relative, now absolute; correct filename)

3. Navigation: /admin/list.php → /admin/
   (list.php was renamed to index.php)

4. Short PHP tags: <? → <?php
   (Better compatibility, some servers don't enable short_open_tag)

5. Quirks mode warning was due to CSS not loading, not DOCTYPE
   (DOCTYPE was already present)

Files modified:
- public/admin/inc/head.php (main fixes)
- public/admin/index.php (short tags)
- public/admin/add.php (short tags)
- public/admin/import.php (short tags)

Need to redeploy for production: just deploy
This commit is contained in:
Théophile Gervreau-Mercier
2026-02-06 12:14:26 +01:00
parent e789c286de
commit 4bbbc58e24
44 changed files with 1850 additions and 377 deletions

View File

@@ -0,0 +1,571 @@
# 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: `database/`, `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 ./database/test.db posterg:/var/www/html/database/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 ./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:**
```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 ~ /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
```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/database/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/database/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 | `/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
<?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
<?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
```php
// 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