# Guide de déploiement — Groupe Méditation ## Debian Bookworm + NGINX + PostgreSQL Testé sur Debian 12 (Bookworm) avec un utilisateur `ansible` ayant accès sudo. Principe : on utilise ce que Debian fournit. Pas de dépôt externe. --- ## 0. Prérequis système ```bash # Définir un mot de passe pour l'utilisateur ansible (indispensable pour récupération console) sudo passwd ansible sudo apt update && sudo apt upgrade -y sudo apt install -y curl ca-certificates gnupg ``` --- ## 1. Installer PostgreSQL On installe le PostgreSQL fourni par Debian Bookworm (version 15) : ```bash sudo apt install -y postgresql postgresql-client sudo systemctl enable postgresql sudo systemctl start postgresql ``` Créer l'utilisateur et la base. **Choisissez votre mot de passe maintenant** — il sera réutilisé à l'étape 6 : ```bash sudo -u postgres psql << 'SQL' CREATE USER grpmed WITH PASSWORD 'votre_mot_de_passe_ici'; CREATE DATABASE groupe_meditation OWNER grpmed; GRANT ALL PRIVILEGES ON DATABASE groupe_meditation TO grpmed; \c groupe_meditation GRANT ALL ON SCHEMA public TO grpmed; SQL ``` Vérifier la connexion. **Important : utiliser `127.0.0.1`, pas `localhost`** (asyncpg résout `localhost` en IPv6 `::1` sur Debian Bookworm, tandis que PostgreSQL n'écoute que sur `127.0.0.1`) : ```bash PGPASSWORD='votre_mot_de_passe_ici' psql -U grpmed -h 127.0.0.1 -d groupe_meditation -c "SELECT 1;" ``` --- ## 2. Installer Node.js On installe le Node.js fourni par Debian Bookworm (version 18) : ```bash sudo apt install -y nodejs npm ``` Vérifier : ```bash node --version npm --version ``` --- ## 3. Installer Python et les dépendances système ```bash sudo apt install -y python3 python3-pip python3-venv # Dépendances système pour WeasyPrint (rapports PDF) sudo apt install -y libpango-1.0-0 libpangocairo-1.0-0 \ libgdk-pixbuf2.0-0 libffi-dev libcairo2 ``` --- ## 4. Installer NGINX ```bash sudo apt install -y nginx sudo systemctl enable nginx sudo systemctl start nginx ``` --- ## 5. Déployer le code source ```bash # Copier l'archive sur le serveur, puis : cd ~ unzip groupe-meditation-v12.zip # Backend sudo mkdir -p /opt/groupe-meditation sudo cp -r ~/groupe-meditation/backend/ /opt/groupe-meditation/backend/ sudo cp -r ~/groupe-meditation/deploy/ /opt/groupe-meditation/deploy/ # Environnement virtuel Python cd /opt/groupe-meditation/backend sudo python3 -m venv venv sudo /opt/groupe-meditation/backend/venv/bin/pip install -r requirements.txt ``` --- ## 6. Configurer le backend Générer le secret JWT : ```bash openssl rand -base64 48 ``` Créer le `.env` : ```bash cd /opt/groupe-meditation/backend sudo cp .env.example .env sudo nano .env ``` Modifier `.env` avec le **même mot de passe** que l'étape 1. **⚠️ Utiliser `127.0.0.1`, jamais `localhost`** dans `DATABASE_URL` : ``` DATABASE_URL=postgresql+asyncpg://grpmed:votre_mot_de_passe_ici@127.0.0.1:5432/groupe_meditation JWT_SECRET=valeur_generee_ci-dessus CORS_ORIGINS=["https://groupe-meditation.87-16.org"] INVITATION_EXPIRE_HOURS=48 ``` **Permissions du `.env` — obligatoire** (le service tourne en `www-data`) : ```bash sudo chown www-data:www-data /opt/groupe-meditation/backend/.env sudo chmod 640 /opt/groupe-meditation/backend/.env ``` --- ## 7. Initialiser la base de données **Le seed doit être lancé depuis le dossier `backend/`** pour que le fichier `.env` soit trouvé : ```bash cd /opt/groupe-meditation/backend /opt/groupe-meditation/backend/venv/bin/python ../deploy/seed.py ``` Si le `.env` n'est pas trouvé, le script utilisera les valeurs par défaut de `config.py` (qui ont un autre mot de passe) et la connexion échouera. Le script crée **interactivement** : - Les tables (23+) - Les 10 postes prédéfinis - La littérature de base (5 titres) - Le premier groupe et le premier membre - Un code d'invitation valide 7 jours --- ## 7b. Mise à jour d'une base existante (v11 → v12) Si la base existe déjà, exécuter ces commandes SQL **avant** de relancer le service : ```bash PGPASSWORD='votre_mot_de_passe_ici' psql -U grpmed -h 127.0.0.1 -d groupe_meditation << 'SQL' -- ══ Membre : courriel obligatoire, nom optionnel, téléphone obligatoire ══ -- Remplir les courriels manquants avant de rendre NOT NULL UPDATE membres SET courriel = CONCAT(LOWER(prenom), '_', LEFT(id::text, 4), '@temp.local') WHERE courriel IS NULL OR courriel = ''; UPDATE membres SET telephone = '000-000-0000' WHERE telephone IS NULL OR telephone = ''; ALTER TABLE membres ALTER COLUMN courriel SET NOT NULL; ALTER TABLE membres ALTER COLUMN telephone SET NOT NULL; ALTER TABLE membres ALTER COLUMN nom DROP NOT NULL; -- Contrainte d'unicité courriel+groupe ALTER TABLE membres ADD CONSTRAINT uq_courriel_groupe UNIQUE (courriel, groupe_id); -- ══ Tâches ══ CREATE TABLE IF NOT EXISTS taches ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), nom VARCHAR(200) NOT NULL, informations TEXT, ordre_affichage INTEGER DEFAULT 99, cree_le TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS postes_taches ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), poste_id UUID NOT NULL REFERENCES postes(id), tache_id UUID NOT NULL REFERENCES taches(id), cree_le TIMESTAMPTZ DEFAULT NOW() ); -- ══ Poste : champ informations ══ ALTER TABLE postes ADD COLUMN IF NOT EXISTS informations TEXT; -- ══ Dépenses : preuve, methode nullable ══ ALTER TABLE depenses ADD COLUMN IF NOT EXISTS preuve_nom VARCHAR(255); ALTER TABLE depenses ALTER COLUMN methode DROP NOT NULL; -- ══ Candidatures : type, nomination, retrait ══ -- Ajouter les nouvelles valeurs au type ENUM DO $$ BEGIN ALTER TYPE statut_candidature ADD VALUE IF NOT EXISTS 'retiree'; EXCEPTION WHEN duplicate_object THEN NULL; END $$; DO $$ BEGIN CREATE TYPE type_candidature AS ENUM ('candidature', 'nomination'); EXCEPTION WHEN duplicate_object THEN NULL; END $$; ALTER TABLE candidatures ADD COLUMN IF NOT EXISTS type VARCHAR(20) DEFAULT 'candidature'; ALTER TABLE candidatures ADD COLUMN IF NOT EXISTS nomme_par UUID REFERENCES membres(id); ALTER TABLE candidatures ADD COLUMN IF NOT EXISTS acceptee_par_membre BOOLEAN; ALTER TABLE candidatures ADD COLUMN IF NOT EXISTS proposition_id UUID REFERENCES propositions(id); -- ══ Envoi contributions : résolution, engagement ══ ALTER TABLE envois_contributions ADD COLUMN IF NOT EXISTS resolution_id UUID REFERENCES propositions(id); ALTER TABLE envois_contributions ADD COLUMN IF NOT EXISTS engagee BOOLEAN DEFAULT false; ALTER TABLE envois_contributions ADD COLUMN IF NOT EXISTS debitee BOOLEAN DEFAULT false; -- ══ Propositions : type et lien candidature ══ ALTER TABLE propositions ADD COLUMN IF NOT EXISTS type_proposition VARCHAR(20) DEFAULT 'generale'; ALTER TABLE propositions ADD COLUMN IF NOT EXISTS candidature_id UUID REFERENCES candidatures(id); -- ══ Répertoire des preuves ══ -- Créer le répertoire manuellement : -- sudo mkdir -p /opt/groupe-meditation/uploads/preuves -- sudo chown www-data:www-data /opt/groupe-meditation/uploads/preuves SQL ``` Ensuite créer le répertoire des preuves et relancer : ```bash sudo mkdir -p /opt/groupe-meditation/uploads/preuves sudo chown www-data:www-data /opt/groupe-meditation/uploads/preuves sudo systemctl restart groupe-meditation ``` --- ## 8. Créer le service systemd ```bash sudo cp /opt/groupe-meditation/deploy/groupe-meditation.service /etc/systemd/system/ sudo chown -R www-data:www-data /opt/groupe-meditation sudo systemctl daemon-reload sudo systemctl enable groupe-meditation sudo systemctl start groupe-meditation ``` Vérifier : ```bash sudo systemctl status groupe-meditation curl -s http://127.0.0.1:8000/api/health ``` Doit retourner `{"status":"ok","app":"Groupe Méditation","version":"0.1.0"}` --- ## 9. Construire et déployer le frontend ```bash cd ~/groupe-meditation/frontend npm install npm run build sudo mkdir -p /var/www/groupe-meditation sudo cp -r dist/* /var/www/groupe-meditation/ sudo chown -R www-data:www-data /var/www/groupe-meditation ``` --- ## 10. Configurer NGINX NGINX fait tout : fichiers statiques du frontend + reverse proxy vers FastAPI. ```bash sudo cp ~/groupe-meditation/deploy/nginx-groupe-meditation.conf \ /etc/nginx/sites-available/groupe-meditation sudo ln -sf /etc/nginx/sites-available/groupe-meditation /etc/nginx/sites-enabled/ sudo rm -f /etc/nginx/sites-enabled/default sudo nginx -t sudo systemctl reload nginx ``` Tester en HTTP : ```bash curl -s http://groupe-meditation.87-16.org/api/health ``` --- ## 11. Certificat TLS (Let's Encrypt) ```bash sudo apt install -y certbot python3-certbot-nginx sudo certbot --nginx -d groupe-meditation.87-16.org ``` Certbot modifie automatiquement la config NGINX pour ajouter HTTPS et la redirection 80→443. --- ## 12. Sauvegarde automatique ```bash sudo cp ~/groupe-meditation/deploy/backup.sh /etc/cron.daily/groupe-meditation-backup sudo chmod +x /etc/cron.daily/groupe-meditation-backup ``` --- ## 13. Vérification finale ```bash # API curl -s https://groupe-meditation.87-16.org/api/health | python3 -m json.tool # Documentation OpenAPI # https://groupe-meditation.87-16.org/api/docs # Frontend # https://groupe-meditation.87-16.org ``` --- ## Mise à jour de l'application ```bash # 1. Copier les nouveaux fichiers cd ~ unzip groupe-meditation-vXX.zip # 2. Backend sudo cp -r ~/groupe-meditation/backend/ /opt/groupe-meditation/backend/ cd /opt/groupe-meditation/backend sudo venv/bin/pip install -r requirements.txt # 3. Migrations SQL si nécessaire (voir section 7b) # 4. Permissions sudo chown -R www-data:www-data /opt/groupe-meditation sudo chown www-data:www-data /opt/groupe-meditation/backend/.env sudo chmod 640 /opt/groupe-meditation/backend/.env # 5. Relancer le service sudo systemctl restart groupe-meditation # 6. Frontend cd ~/groupe-meditation/frontend npm install && npm run build sudo cp -r dist/* /var/www/groupe-meditation/ ``` --- ## Dépannage | Problème | Commande / Solution | |----------|---------------------| | API ne démarre pas | `sudo journalctl -u groupe-meditation -f` | | Workers bloqués au démarrage | `DATABASE_URL` doit utiliser `127.0.0.1` (pas `localhost`) | | `InvalidPasswordError` au seed | Lancer depuis `backend/`, vérifier `.env` lisible | | `InvalidPasswordError` à l'API | `sudo chown www-data:www-data .env && sudo chmod 640 .env` | | NGINX 502 Bad Gateway | `sudo systemctl status groupe-meditation` — Uvicorn démarré ? | | NGINX 504 Gateway Timeout | Worker bloqué DB — vérifier `127.0.0.1` dans `DATABASE_URL` | | WeasyPrint échoue | `sudo apt install libpango-1.0-0 libpangocairo-1.0-0` | | `seed.py ImportError` | Vérifier les noms de classes dans `models/` | | Console inaccessible | `sudo passwd ansible` — à faire en tout début | | `type "..." does not exist` après ALTER | Relancer le service pour que `create_all` crée les types manquants | | Nouvelle colonne absente | Exécuter les ALTER TABLE de la section 7b | --- **Hébergé au Québec · 100 % logiciel libre · Zéro traçage**