diff --git a/app/routers/admin.py b/app/routers/admin.py index aa57e4d..5bee234 100644 --- a/app/routers/admin.py +++ b/app/routers/admin.py @@ -15,6 +15,7 @@ PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent ALLOWED_DOCS = { "README.md": PROJECT_ROOT / "README.md", "ROADMAP.md": PROJECT_ROOT / "docs" / "ROADMAP.md", + "PRODUCTION_CHECKLIST.md": PROJECT_ROOT / "docs" / "PRODUCTION_CHECKLIST.md", "USER_GUIDE.md": PROJECT_ROOT / "docs" / "USER_GUIDE.md", "ARCHITECTURE.md": PROJECT_ROOT / "docs" / "ARCHITECTURE.md", "API_GUIDE.md": PROJECT_ROOT / "docs" / "API_GUIDE.md", diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md new file mode 100644 index 0000000..68031f7 --- /dev/null +++ b/docs/DEPLOYMENT.md @@ -0,0 +1,308 @@ +# Guide de déploiement — Imago sur serveur Docker + +> Cible : serveur Docker distant (VM Proxmox, VPS, ou bare metal) +> Prérequis : Docker 24+, Docker Compose v2, accès SSH + +--- + +## Étape 1 — Préparer le registry d'images + +Choisir UNE des options : + +### Option A : Gitea Container Registry (recommandé si Gitea dispo) +```bash +# Builder et tagger l'image +cd C:\dev\git\python\imago +docker build -t gitea.dracodev.net/projets/imago-backend:latest . +docker build -t gitea.dracodev.net/projets/imago-admin:latest ./imago-admin + +# Se connecter au registry Gitea +docker login gitea.dracodev.net -u bruno + +# Pusher +docker push gitea.dracodev.net/projets/imago-backend:latest +docker push gitea.dracodev.net/projets/imago-admin:latest +``` + +### Option B : Docker Hub +```bash +docker build -t tonuser/imago-backend:latest . +docker build -t tonuser/imago-admin:latest ./imago-admin +docker push tonuser/imago-backend:latest +docker push tonuser/imago-admin:latest +``` + +### Option C : Registry local sur le serveur +```bash +# Sur le serveur cible +docker run -d -p 5000:5000 --name registry registry:2 + +# En local, builder et pusher +docker build -t localhost:5000/imago-backend:latest . +docker push localhost:5000/imago-backend:latest +``` + +--- + +## Étape 2 — Préparer les secrets de production + +```bash +# Générer des secrets forts (à faire UNE SEULE fois) +openssl rand -hex 32 # SECRET_KEY +openssl rand -hex 32 # ADMIN_API_KEY → note-la, c'est ta clé d'accès admin +openssl rand -hex 32 # JWT_SECRET_KEY +openssl rand -hex 32 # SIGNED_URL_SECRET +openssl rand -hex 16 # S3_SECRET_KEY +openssl rand -hex 16 # MINIO_ROOT_PASSWORD +openssl rand -hex 16 # POSTGRES_PASSWORD +``` + +Créer le fichier `.env.production` : +```bash +# ── Application ── +APP_NAME=Imago +APP_VERSION=2.0.0 +DEBUG=false +SECRET_KEY= + +# ── Base de données ── +DATABASE_URL=postgresql+asyncpg://imago:@db:5432/imago + +# ── Stockage S3/MinIO ── +STORAGE_BACKEND=s3 +S3_BUCKET=imago +S3_ENDPOINT_URL=http://minio:9000 +S3_ACCESS_KEY=minioadmin +S3_SECRET_KEY= +S3_REGION=us-east-1 +SIGNED_URL_SECRET= + +# ── Redis ── +REDIS_URL=redis://redis:***@ADMIN_API_KEY= +JWT_SECRET_KEY= +JWT_ALGORITHM=HS256 + +# ── AI ── +AI_ENABLED=true +AI_PROVIDER=openrouter +OPENROUTER_API_KEY= +OPENROUTER_MODEL=qwen/qwen2.5-vl-72b-instruct +AI_TAGS_MIN=5 +AI_TAGS_MAX=10 +AI_DESCRIPTION_LANGUAGE=francais +AI_REQUEST_TIMEOUT=60 +AI_MAX_RETRIES=2 + +# ── OCR ── +OCR_ENABLED=true +OCR_LANGUAGES=fra+eng + +# ── CORS ── +CORS_ORIGINS=["https://admin.ton-domaine.com"] + +# ── Pipeline ── +PIPELINE_TIMEOUT=300 +PIPELINE_MAX_RETRIES=3 + +# ── Rate Limiting (Redis persistence) ── +RATE_LIMIT_STORAGE_URL=redis://redis:6379/1 +``` + +--- + +## Étape 3 — Préparer le docker-compose.production.yml + +```yaml +services: + traefik: + image: traefik:v3.0 + command: + - "--providers.docker=true" + - "--providers.docker.exposedbydefault=false" + - "--entrypoints.web.address=:80" + - "--entrypoints.websecure.address=:443" + - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true" + - "--certificatesresolvers.letsencrypt.acme.email=bruno.charest@gmail.com" + - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" + ports: + - "80:80" + - "443:443" + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - traefik_certs:/letsencrypt + restart: unless-stopped + + backend: + image: gitea.dracodev.net/projets/imago-backend:latest + env_file: .env.production + labels: + - "traefik.enable=true" + - "traefik.http.routers.imago-api.rule=Host(`api.ton-domaine.com`)" + - "traefik.http.routers.imago-api.entrypoints=websecure" + - "traefik.http.routers.imago-api.tls.certresolver=letsencrypt" + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + restart: unless-stopped + deploy: + resources: + limits: + memory: 1G + cpus: '2' + + worker: + image: gitea.dracodev.net/projets/imago-backend:latest + command: python worker.py + env_file: .env.production + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + restart: unless-stopped + deploy: + resources: + limits: + memory: 512M + cpus: '1' + + admin: + image: nginx:alpine + ports: + - "127.0.0.1:3000:80" + volumes: + - ./admin-dist:/usr/share/nginx/html:ro + labels: + - "traefik.enable=true" + - "traefik.http.routers.imago-admin.rule=Host(`admin.ton-domaine.com`)" + - "traefik.http.routers.imago-admin.entrypoints=websecure" + - "traefik.http.routers.imago-admin.tls.certresolver=letsencrypt" + restart: unless-stopped + + db: + image: postgres:16-alpine + env_file: .env.production + environment: + POSTGRES_USER: imago + POSTGRES_DB: imago + volumes: + - pgdata:/var/lib/postgresql/data + - ./backups:/backups + healthcheck: + test: ["CMD-SHELL", "pg_isready -U imago -d imago"] + interval: 5s + timeout: 5s + retries: 5 + restart: unless-stopped + deploy: + resources: + limits: + memory: 512M + + redis: + image: redis:7-alpine + command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru + volumes: + - redisdata:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 3 + restart: unless-stopped + deploy: + resources: + limits: + memory: 256M + + minio: + image: minio/minio + command: server /data --console-address ":9001" + env_file: .env.production + volumes: + - miniodata:/data + restart: unless-stopped + deploy: + resources: + limits: + memory: 512M + +volumes: + pgdata: + redisdata: + miniodata: + traefik_certs: +``` + +--- + +## Étape 4 — Déployer sur le serveur + +```bash +# 1. Se connecter au serveur +ssh ton-serveur + +# 2. Créer le répertoire de l'application +mkdir -p /opt/imago && cd /opt/imago + +# 3. Copier les fichiers de configuration +# (depuis ton poste local) +scp docker-compose.production.yml ton-serveur:/opt/imago/ +scp .env.production ton-serveur:/opt/imago/ + +# 4. S'assurer que les volumes de backup existent +mkdir -p backups + +# 5. Démarrer la stack +docker compose -f docker-compose.production.yml up -d + +# 6. Vérifier les logs +docker compose -f docker-compose.production.yml logs -f backend + +# 7. Vérifier la santé +curl https://api.ton-domaine.com/health +``` + +--- + +## Étape 5 — Backup automatique (cron) + +Ajouter au crontab du serveur : +```cron +# Backup PostgreSQL — tous les jours à 2h +0 2 * * * docker exec imago-db-1 pg_dump -U imago imago > /opt/imago/backups/imago_$(date +\%Y\%m\%d).sql + +# Nettoyer les backups de plus de 30 jours +0 3 * * * find /opt/imago/backups -name "*.sql" -mtime +30 -delete +``` + +--- + +## Étape 6 — Vérifications post-déploiement + +- [ ] `https://api.ton-domaine.com/health` → `{"status":"healthy"}` +- [ ] `https://api.ton-domaine.com/health/detailed` → tous les checks OK +- [ ] `https://admin.ton-domaine.com` → page de login accessible +- [ ] Connexion admin avec `ADMIN_API_KEY` +- [ ] Upload d'une image test → pipeline OK +- [ ] WebSocket `/ws/pipeline/{id}` → événements reçus +- [ ] Métriques Prometheus `/metrics` → données présentes + +--- + +## Résumé des commandes rapides + +```bash +# Builder les images (local) +docker build -t gitea.dracodev.net/projets/imago-backend:latest . +docker login gitea.dracodev.net +docker push gitea.dracodev.net/projets/imago-backend:latest + +# Déployer (serveur) +ssh serveur "cd /opt/imago && docker compose pull && docker compose up -d" + +# Vérifier +curl https://api.ton-domaine.com/health +``` diff --git a/docs/PRODUCTION_CHECKLIST.md b/docs/PRODUCTION_CHECKLIST.md new file mode 100644 index 0000000..e0aaa2b --- /dev/null +++ b/docs/PRODUCTION_CHECKLIST.md @@ -0,0 +1,132 @@ +# Imago — Checklist Production + +> Dernière mise à jour : 2026-06-22 +> 46 items à compléter avant la mise en production publique + +--- + +## 🔴 Critique — Doit être fait avant la mise en ligne + +### 🔐 Sécurité +| # | Tâche | Pourquoi | +|---|---|---| +| 1 | Changer `SECRET_KEY` (encore `changez-moi`) | Tous les tokens JWT et URLs signées sont compromis | +| 2 | Changer `ADMIN_API_KEY` (`imago-admin-key`) | Accès admin trivial | +| 3 | Changer `JWT_SECRET_KEY` | Tokens JWT falsifiables | +| 4 | Changer `SIGNED_URL_SECRET` | URLs sécurisées contournables | +| 5 | Changer `S3_SECRET_KEY` / `MINIO_ROOT_PASSWORD` (minioadmin) | Accès complet au stockage | +| 6 | Changer `POSTGRES_PASSWORD` (imago) | Accès complet à la BDD | +| 7 | Passer `DEBUG=false` | Logs SQL en clair, stack traces exposées | +| 8 | Configurer CORS_ORIGINS pour le domaine de production | Sinon toute origine peut appeler l'API | +| 9 | Générer une `ADMIN_API_KEY` forte : `openssl rand -hex 32` | Clé admin actuelle trop simple | +| 10 | Restreindre `S3_ENDPOINT_PUBLIC` au domaine public réel | Éviter l'exposition de l'endpoint interne | + +### 🌐 Réseau & TLS +| # | Tâche | Pourquoi | +|---|---|---| +| 11 | Mettre un reverse proxy devant l'API (nginx/Caddy/Traefik) | SSL termination, rate limiting, buffering | +| 12 | Configurer HTTPS (Let's Encrypt) | Données en clair sur le réseau | +| 13 | Supprimer les ports Docker exposés sur 0.0.0.0 (6379, 5432, 9000) | Redis et PostgreSQL exposés au monde | +| 14 | Mettre l'API derrière un domaine (ex: api.imago.example.com) | URLs signées, CORS,WebSocket dépendent du domaine | + +### 📦 Sauvegarde +| # | Tâche | Pourquoi | +|---|---|---| +| 15 | Backup automatique PostgreSQL (pg_dump cron daily) | Perte de toutes les métadonnées | +| 16 | Backup MinIO/S3 (rclone ou S3 replication) | Perte de toutes les images | +| 17 | Tester une restauration complète (backup + restore) | Un backup non testé n'existe pas | + +### 🔄 Migrations BDD +| # | Tâche | Pourquoi | +|---|---|---| +| 18 | Initialiser Alembic (`alembic init migrations`) | `create_all()` en production = pas de rollback | +| 19 | Générer la migration initiale (`alembic revision --autogenerate`) | Versionner le schéma actuel | +| 20 | Ajouter `alembic upgrade head` au démarrage du conteneur | Appliquer les migrations automatiquement | + +--- + +## 🟡 Important — Bloque la scalabilité + +### 📊 Observabilité +| # | Tâche | Pourquoi | +|---|---|---| +| 21 | Déployer Grafana + Prometheus avec le dashboard fourni (`docs/grafana-dashboard.json`) | Aucune visibilité en prod | +| 22 | Configurer Alertmanager (alertes sur : API down, pipeline errors > seuil, disque > 80%) | Être réveillé avant les utilisateurs | +| 23 | Centraliser les logs (Loki, ELK, ou Datadog) | `docker logs` ne suffit pas à 3h du matin | +| 24 | Configurer Uptime Kuma ou similaire pour health check externe | Savoir si l'API est down depuis l'extérieur | + +### 🚦 Résilience +| # | Tâche | Pourquoi | +|---|---|---| +| 25 | Ajouter `restart: unless-stopped` à TOUS les services Docker Compose | Le worker ne doit pas mourir silencieusement | +| 26 | Configurer des resource limits Docker (CPU/memory) par conteneur | Un conteneur qui leak ne tue pas le host | +| 27 | Mettre en place un health check au niveau du load balancer (`GET /health`) | Le load balancer doit savoir si le backend est vivant | +| 28 | Configurer Redis avec `maxmemory-policy` et une limite | Redis ne doit pas saturer la RAM | + +### 🔑 Gestion des clés +| # | Tâche | Pourquoi | +|---|---|---| +| 29 | Utiliser Docker secrets ou un vault (Infisical, HashiCorp Vault) pour les secrets | `.env` en clair dans le repo = faille | +| 30 | Rotation automatique des clés API (politique : 90 jours) | Conformité sécurité | +| 31 | Journaliser les échecs d'authentification avec rate limiting | Détecter les attaques brute-force | + +--- + +## 🟢 Recommandé — Qualité de service + +### 🧪 Qualité +| # | Tâche | Pourquoi | +|---|---|---| +| 32 | Exécuter la suite complète de tests dans la CI (GitHub Actions / Gitea Actions) | Tests qui ne tournent pas = tests inutiles | +| 33 | Ajouter des tests d'intégration avec Redis + MinIO + PostgreSQL réels | Les mocks ne trouvent pas les vrais bugs | +| 34 | Faire un audit de sécurité (bandit, trivy sur l'image Docker) | Vulnérabilités dans les dépendances | +| 35 | Scanner l'image Docker avec Trivy/Grype | CVE dans les packages système | + +### 📖 Documentation +| # | Tâche | Pourquoi | +|---|---|---| +| 36 | Écrire un guide de déploiement production (`docs/DEPLOYMENT.md`) | Le nouveau dev ne devrait pas deviner | +| 37 | Écrire un runbook d'opérations (procédures : restart, backup restore, scale up) | Pas de panique à 3h du matin | +| 38 | Documenter l'architecture de sécurité (où sont les secrets, comment ils tournent) | Audit de sécurité | + +### ⚖️ Conformité +| # | Tâche | Pourquoi | +|---|---|---| +| 39 | Ajouter une politique de rétention des données (combien de temps garder les images supprimées) | RGPD : droit à l'oubli | +| 40 | Ajouter un endpoint `DELETE /api/v1/auth/me` (suppression de compte + données) | RGPD : portabilité/effacement | +| 41 | Ajouter un endpoint `GET /api/v1/auth/me/export` (export des données) | RGPD : portabilité | +| 42 | Mettre une page de statut publique (status.imago.example.com) | Transparence utilisateurs | +| 43 | Ajouter un rate limit global par IP (en plus du limit par client) | Protection DDoS basique | + +### 🚀 CI/CD +| # | Tâche | Pourquoi | +|---|---|---| +| 44 | Pipeline CI : lint → test → build image → push registry | Déploiement manuel = erreur humaine | +| 45 | Pipeline CD : pull image → migrate DB → restart conteneurs (rolling update) | Zéro downtime | +| 46 | Configurer un environnement de staging séparé | Tester en conditions réelles avant la prod | + +--- + +## Résumé + +| Priorité | Items | Effort estimé | +|---|---|---| +| 🔴 Critique | 20 | 2-3 jours | +| 🟡 Important | 11 | 2 jours | +| 🟢 Recommandé | 15 | 3-4 jours | +| **Total** | **46** | **~8 jours** | + +### Quick Start (top 5 à faire aujourd'hui) + +```bash +# 1. Générer des secrets forts +openssl rand -hex 32 # pour SECRET_KEY +openssl rand -hex 32 # pour ADMIN_API_KEY +openssl rand -hex 32 # pour JWT_SECRET_KEY +openssl rand -hex 16 # pour MINIO_ROOT_PASSWORD + +# 2. Mettre à jour .env avec ces valeurs +# 3. DEBUG=false +# 4. CORS_ORIGINS=["https://ton-domaine.com"] +# 5. docker compose up -d +```