175 lines
7.7 KiB
Markdown
175 lines
7.7 KiB
Markdown
# Déploiement
|
|
|
|
Le pipeline [`.gitea/workflows/ci-cd.yml`](../.gitea/workflows/ci-cd.yml) (Gitea
|
|
Actions) se déclenche sur **chaque push** :
|
|
|
|
1. tests (backend : `ruff` + `pytest` + `alembic check` ; frontend :
|
|
`typecheck` + `lint` + `build` + `smoke`) ;
|
|
2. sur `main` seulement : build des images `backend` et `frontend`, poussées
|
|
sur le registre de conteneurs intégré à Gitea ;
|
|
3. copie de `deploy/docker-compose.prod.yml` sur le VPS par SSH ;
|
|
4. `docker compose pull`, migration Alembic, `up -d`.
|
|
|
|
Nécessite un runner Gitea Actions (`act_runner`, exécuteur Docker) activé sur
|
|
l'instance et attaché au dépôt.
|
|
|
|
Le VPS n'expose rien directement : un **Caddy déjà installé sur le
|
|
serveur** (service système, hors de ce dépôt) reçoit le trafic public sur
|
|
443 et reverse-proxy vers les deux conteneurs, qui n'écoutent que sur
|
|
`127.0.0.1`.
|
|
|
|
## Avant d'ouvrir au public — check-list sécurité
|
|
|
|
- `JWT_SECRET` : une vraie valeur générée (`openssl rand -hex 32`), jamais la
|
|
valeur par défaut de `.env.example`. Le backend refuse de démarrer en
|
|
`ENVIRONMENT=production` sinon (`app/config.py:check_production_secrets`).
|
|
- `EMAIL_ENABLED=true` avec un SMTP qui fonctionne (`docker compose run --rm
|
|
backend uv run python -m app.email.check <destinataire>` pour vérifier) —
|
|
sinon les liens magiques sont journalisés en clair au lieu d'être envoyés
|
|
(le backend avertit au démarrage tant que c'est le cas, voir plus bas).
|
|
- Édition créée via `python -m app.bootstrap` (étape 3), **jamais** via
|
|
`python -m app.seed` : ce dernier crée un compte de démonstration
|
|
(`coordination@lucioles.test`) avec un mot de passe versionné dans le
|
|
dépôt — un vrai identifiant de connexion COORDINATION public si la base de
|
|
démo devient la base de production.
|
|
- `CORS_ORIGINS` : sans objet ici tant que le frontend appelle l'API en
|
|
même origine via `/api/*` (voir le vhost Caddy, étape 5) — à ne renseigner
|
|
que si un autre client (domaine séparé, appli mobile) doit appeler l'API
|
|
directement.
|
|
- `/auth/login` et `/auth/magic-link` sont limités en tentatives par email
|
|
(5/min et 3/5 min) — voir `app/auth/rate_limit.py`. Limité par email et
|
|
non par IP (le VPS ne transmet pas encore l'IP réelle du client à travers
|
|
Caddy) : suffisant contre la force brute sur un compte connu, pas contre
|
|
une attaque distribuée sur de nombreux emails différents.
|
|
|
|
## Étape 0 — pousser le dépôt sur Gitea
|
|
|
|
```bash
|
|
# Créer d'abord un dépôt vide sur Gitea (sans README ni .gitignore), puis :
|
|
git remote add origin git@gitea.mon-domaine.fr:<namespace>/<projet>.git
|
|
git push -u origin main
|
|
```
|
|
|
|
## Secrets et variables à définir (Paramètres du dépôt → Actions)
|
|
|
|
Secrets (Paramètres → Actions → Secrets) :
|
|
|
|
| Secret | Description |
|
|
|-------------------|-------------------------------------------------------------------|
|
|
| `REGISTRY_TOKEN` | Jeton d'accès (scope `write:package`) pour pousser sur le registre de conteneurs de l'instance Gitea |
|
|
| `SSH_KEY` | Clé privée SSH (format OpenSSH) |
|
|
|
|
Variables (Paramètres → Actions → Variables) :
|
|
|
|
| Variable | Description |
|
|
|-----------------------|-------------------------------------------------------------------|
|
|
| `REGISTRY_HOST` | Hôte du registre (ex. `gitea.mon-domaine.fr`) |
|
|
| `REGISTRY_NAMESPACE` | Espace du registre (ex. `<organisation>/<depot>`) |
|
|
| `REGISTRY_USER` | Utilisateur associé à `REGISTRY_TOKEN` |
|
|
| `SSH_HOST` | Hôte du VPS |
|
|
| `SSH_USER` | Utilisateur SSH |
|
|
| `DEPLOY_PATH` | Dossier du projet sur le VPS (ex. `/opt/benevoles`) |
|
|
| `VITE_EDITION_ID` | UUID de l'édition de production — **à ajouter après** l'étape 3 |
|
|
|
|
La clé publique correspondant à `SSH_KEY` doit être dans le
|
|
`~/.ssh/authorized_keys` de `SSH_USER` sur le VPS.
|
|
|
|
## Étape 1 — préparation du VPS (une fois)
|
|
|
|
```bash
|
|
mkdir -p /opt/benevoles
|
|
cd /opt/benevoles
|
|
cat > .env <<'EOF'
|
|
POSTGRES_USER=benevoles
|
|
POSTGRES_PASSWORD=<mot de passe fort>
|
|
POSTGRES_DB=benevoles
|
|
JWT_SECRET=<secret fort ; openssl rand -hex 32>
|
|
PUBLIC_BASE_URL=https://benevoles.mon-domaine.fr
|
|
|
|
# Emails (récapitulatifs de planning + liens de connexion). Laisser
|
|
# EMAIL_ENABLED=false tant que le SMTP n'est pas prêt : les emails sont alors
|
|
# seulement journalisés — un jeton de lien magique en clair dans les logs du
|
|
# conteneur permet de se connecter à la place de n'importe qui, donc à
|
|
# repasser à true avant l'ouverture au public (le backend affiche un
|
|
# avertissement au démarrage tant que c'est le cas).
|
|
EMAIL_ENABLED=true
|
|
SMTP_HOST=smtp.mon-domaine.fr
|
|
SMTP_PORT=587
|
|
SMTP_USER=no-reply@mon-domaine.fr
|
|
SMTP_PASSWORD=<mot de passe SMTP>
|
|
SMTP_STARTTLS=true
|
|
SMTP_SSL=false
|
|
SMTP_FROM=Festival <no-reply@mon-domaine.fr>
|
|
EOF
|
|
```
|
|
|
|
Le pipeline dépose `docker-compose.prod.yml` dans ce dossier et l'utilise tel
|
|
quel — il n'y a rien d'autre à y copier.
|
|
|
|
## Étape 2 — premier déploiement (backend + base seuls)
|
|
|
|
Le frontend a besoin de l'UUID d'une édition pour être construit (voir plus
|
|
bas) : le tout premier déploiement se fait donc en deux temps.
|
|
|
|
1. Sur Gitea, lancer le pipeline sur `main` normalement (Push → Actions, ou
|
|
un simple commit). Le job `build-frontend` échouera ou produira une image
|
|
sans édition tant que `VITE_EDITION_ID` n'existe pas : c'est attendu, on
|
|
l'ignore pour l'instant.
|
|
2. Une fois `build-backend` et `deploy` passés, `db` et `backend` tournent
|
|
sur le VPS.
|
|
|
|
## Étape 3 — créer l'édition de production
|
|
|
|
```bash
|
|
ssh SSH_USER@SSH_HOST
|
|
cd /opt/benevoles
|
|
docker compose -f docker-compose.prod.yml run --rm backend \
|
|
uv run python -m app.bootstrap \
|
|
--name "Festival 2026" --start 2026-07-01 --end 2026-07-04 \
|
|
--email coordination@mon-domaine.fr --password <mot-de-passe-solide>
|
|
```
|
|
|
|
La commande affiche l'UUID de l'édition créée. Récupérer aussi celui-ci à
|
|
tout moment avec :
|
|
|
|
```bash
|
|
docker compose -f docker-compose.prod.yml exec -T db \
|
|
psql -U benevoles -tAc "select id from edition order by created_at desc limit 1"
|
|
```
|
|
|
|
## Étape 4 — brancher le frontend sur cette édition
|
|
|
|
Dans Gitea, ajouter la variable `VITE_EDITION_ID` (Paramètres → Actions →
|
|
Variables) avec cet UUID, puis relancer le pipeline sur `main` (bouton
|
|
*Re-run* sur le dernier run, ou un nouveau commit). Cette fois
|
|
`build-frontend` construit une image qui pointe sur la bonne édition, et
|
|
`deploy` démarre le service `frontend`.
|
|
|
|
`VITE_EDITION_ID` ne change plus ensuite : elle ne se touche à nouveau que
|
|
pour une nouvelle édition (festival de l'année suivante) qu'on voudrait
|
|
publier sur ce même domaine.
|
|
|
|
## Étape 5 — le vhost Caddy
|
|
|
|
Caddy tourne déjà sur ce serveur (service système) : ajouter le bloc
|
|
[`deploy/Caddyfile.benevoles`](./Caddyfile.benevoles) à son `Caddyfile` (ou au
|
|
fichier qu'il importe pour les sites), avec le vrai nom de domaine, puis :
|
|
|
|
```bash
|
|
sudo caddy validate --config /etc/caddy/Caddyfile
|
|
sudo systemctl reload caddy
|
|
```
|
|
|
|
Le DNS du domaine doit déjà pointer (enregistrement `A`/`AAAA`) vers ce VPS —
|
|
Caddy obtient et renouvelle seul le certificat HTTPS (Let's Encrypt) au
|
|
premier accès.
|
|
|
|
Vérifier ensuite que `PUBLIC_BASE_URL` dans le `.env` du VPS (étape 1)
|
|
correspond bien à `https://<ce domaine>` — c'est cette valeur qui est utilisée
|
|
dans les liens des emails envoyés aux bénévoles.
|
|
|
|
## Déploiements suivants
|
|
|
|
Un simple push sur `main` suffit : tests → build → déploiement → migration →
|
|
redémarrage, sans aucune intervention sur le VPS ni sur Caddy.
|