QUA-001, volet dialecte.
`postgresql://` ne veut pas dire "le pilote installe" : SQLAlchemy y lit
psycopg2 et importe ce module a la creation du moteur. requirements.txt
epingle psycopg 3 (`psycopg[binary]`) et pas psycopg2. Une installation
propre demarree sur cette URL leve donc
ModuleNotFoundError: No module named 'psycopg2'
avant la premiere requete. Verifie dans le .venv du depot, et c est
exactement la forme que Render distribue -- celle que docs/deployment.md et
docs/database-restore.md donnaient en exemple.
normalise_database_url() nomme le pilote quand l URL n en nomme pas.
`postgres://` (alias hérite, abandonne par SQLAlchemy en 1.4) est traite de
meme. Une URL qui nomme deja son pilote est laissee telle quelle, y compris
`postgresql+psycopg2://` : un environnement qui a psycopg2 garde le choix.
La normalisation a lieu apres l application de la configuration passee en
argument, pour couvrir aussi les appels de test. backup.py n avait pas
besoin d etre touche : il retirait deja le suffixe +pilote.
Documentation alignee sur les trois fichiers qui donnaient l exemple, dont
docs/deployment.md qui proposait sqlite:/// pour DATABASE_URL alors que
create_app refuse de demarrer sans PostgreSQL.
Reste de QUA-001, dit franchement
- les trois paquets parasites (dotenv, login, discord) ne sont plus dans
requirements.txt : deja retires. psycopg est deja epingle.
- la consolidation vers des groupes de dependances n est PAS faite. Le
deploiement est un miroir de fichiers lftp sans etape de construction ;
les groupes PEP 735 demandent pip >= 25.1 sur une machine dont on ne
peut pas verifier la version d ici. A revoir avec OPS-011.
14 tests, dont trois qui prouvent que l echec est reel et non theorique.
Co-Authored-By: Claude Opus 5 <[email protected]>
164 lines
4.7 KiB
Markdown
164 lines
4.7 KiB
Markdown
# Team Tryouts - Production Deployment Guide (Windows)
|
|
|
|
This document outlines the secure production deployment for the Team Tryouts
|
|
Flask application on Windows with Nginx reverse proxy.
|
|
|
|
## Architecture Overview
|
|
|
|
```
|
|
Internet → Nginx (HTTPS:443) → Waitress (127.0.0.1:5000) → Flask App
|
|
```
|
|
|
|
- **Nginx**: TLS termination, static file serving, rate limiting, security headers
|
|
- **Waitress**: Production WSGI server with multiple worker threads
|
|
- **Flask**: Application logic (never exposed directly to the internet)
|
|
|
|
## Prerequisites
|
|
|
|
1. **Python 3.12+** installed
|
|
2. **Nginx for Windows** downloaded from [nginx.org](https://nginx.org/en/download.html)
|
|
3. **SSL Certificate** (Let's Encrypt via certbot or commercial provider)
|
|
4. **Windows Firewall** configured properly
|
|
|
|
## Step 1: Install Dependencies
|
|
|
|
```powershell
|
|
# Install Python packages
|
|
pip install -r requirements.txt
|
|
|
|
# Create required directories
|
|
mkdir logs
|
|
mkdir backups
|
|
```
|
|
|
|
## Step 2: Configure Environment Variables
|
|
|
|
Create a `.env` file in the project root:
|
|
|
|
```env
|
|
# Security (REQUIRED - generate with: python -c "import secrets; print(secrets.token_hex(32))")
|
|
SECRET_KEY=<your-generated-64-char-hex-key>
|
|
|
|
# Database — PostgreSQL. create_app() refuses to start without this.
|
|
# Either form works; the driver is named for you if you leave it out.
|
|
DATABASE_URL=postgresql://user:password@host:5432/dbname
|
|
|
|
# Security settings
|
|
SESSION_COOKIE_SECURE=true
|
|
FORCE_HTTPS=true
|
|
FLASK_DEBUG=false
|
|
|
|
# CORS (set to your actual domain in production)
|
|
CORS_ALLOWED_ORIGINS=https://yourdomain.com
|
|
|
|
# Discord bot (optional)
|
|
DISCORD_BOT_TOKEN=
|
|
DISCORD_WEBHOOK_URL=
|
|
|
|
# Backup settings
|
|
BACKUP_DIR=./backups
|
|
BACKUP_RETENTION_DAYS=30
|
|
```
|
|
|
|
**Important**: Never commit `.env` to version control.
|
|
|
|
## Step 3: Configure Nginx
|
|
|
|
1. Copy `nginx.conf` to your Nginx installation directory (e.g., `C:\nginx\conf\`)
|
|
2. Place SSL certificate files:
|
|
- `C:\nginx\certs\fullchain.pem`
|
|
- `C:\nginx\certs\privkey.pem`
|
|
3. Start Nginx: `C:\nginx\nginx.exe`
|
|
|
|
### Obtaining SSL Certificates
|
|
|
|
Using Let's Encrypt with certbot (recommended):
|
|
|
|
```powershell
|
|
# Using certbot on Windows
|
|
certbot certonly --standalone -d yourdomain.com
|
|
```
|
|
|
|
Or use your hosting provider's SSL certificate.
|
|
|
|
## Step 4: Start the Application
|
|
|
|
```powershell
|
|
# Production start
|
|
python wsgi.py
|
|
|
|
# Or with custom port/threads
|
|
$env:PORT=5000
|
|
$env:WAITRESS_THREADS=5
|
|
python wsgi.py
|
|
```
|
|
|
|
## Step 5: Configure Windows Firewall
|
|
|
|
```powershell
|
|
# Allow only necessary ports
|
|
New-NetFirewallRule -DisplayName "Nginx HTTPS" -Direction Inbound -Protocol TCP -LocalPort 443 -Action Allow
|
|
New-NetFirewallRule -DisplayName "Nginx HTTP Redirect" -Direction Inbound -Protocol TCP -LocalPort 80 -Action Allow
|
|
|
|
# Block direct access to Waitress port (5000) from outside
|
|
New-NetFirewallRule -DisplayName "Block Waitress External" -Direction Inbound -Protocol TCP -LocalPort 5000 -Action Block
|
|
```
|
|
|
|
## Step 6: Set Up Automated Backups
|
|
|
|
Create a scheduled task for daily backups:
|
|
|
|
```powershell
|
|
# Run backup script daily at 2:00 AM
|
|
$Action = New-ScheduledTaskAction -Execute "python" -Argument "backup.py" -WorkingDirectory "C:\path\to\team-tryouts"
|
|
$Trigger = New-ScheduledTaskTrigger -Daily -At 2:00AM
|
|
Register-ScheduledTask -TaskName "TeamTryoutsBackup" -Action $Action -Trigger $Trigger
|
|
```
|
|
|
|
## Step 7: Run Security Validation
|
|
|
|
Before going live:
|
|
|
|
```powershell
|
|
# Run the security scanner
|
|
python security_scan.py --url https://yourdomain.com
|
|
```
|
|
|
|
All checks must pass before deployment.
|
|
|
|
## Security Checklist
|
|
|
|
- [ ] `.env` is not committed to repository
|
|
- [ ] `SECRET_KEY` is strong (64+ hex characters, randomly generated)
|
|
- [ ] `FLASK_DEBUG` is set to `false`
|
|
- [ ] Nginx is running with HTTPS (port 443)
|
|
- [ ] HTTP (port 80) redirects to HTTPS
|
|
- [ ] TLS 1.2+ configured (TLS 1.0/1.1 disabled)
|
|
- [ ] HSTS header is present (`max-age=31536000; includeSubDomains; preload`)
|
|
- [ ] CSP header is configured with no `unsafe-eval`
|
|
- [ ] `server_tokens` is `off` in Nginx
|
|
- [ ] File uploads restricted to PDF only
|
|
- [ ] File uploads limited to 16MB
|
|
- [ ] Database backups scheduled daily
|
|
- [ ] Firewall rules applied (only 80/443 open)
|
|
- [ ] Application is not running as administrator
|
|
- [ ] Logs directory exists and is writable
|
|
- [ ] Health check endpoint returns 200
|
|
|
|
## Monitoring
|
|
|
|
- **Application logs**: `./logs/app.log`
|
|
- **Error logs**: `./logs/errors.log`
|
|
- **Auth logs**: `./logs/auth.log`
|
|
- **Nginx access logs**: `C:\nginx\logs\access.log`
|
|
- **Nginx error logs**: `C:\nginx\logs\error.log`
|
|
|
|
Monitor these logs regularly for suspicious activity.
|
|
|
|
## Maintenance
|
|
|
|
- Run `pip-audit` weekly to check for vulnerabilities
|
|
- Run `python security_scan.py` after any configuration changes
|
|
- Test backup restoration quarterly
|
|
- Review and rotate `SECRET_KEY` if compromised
|
|
- Keep Python and system packages updated |