docs : corrige le déploiement prod (Docker) et documente les variables d'env mail

- README : section Variables d'environnement (ENCRYPTION_KEY, LOCK_DSN) + section Déploiement passée au flow Docker (deploy.sh)
- mail-cron-setup : sépare dev (make, php-lesstime-fpm) et prod (lesstime-app, docker compose exec), cron prod réel
- infra/prod/.env.example : ajoute ENCRYPTION_KEY et LOCK_DSN (manquaient, requis pour la sync mail)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthieu
2026-05-20 17:37:17 +02:00
parent 2bffff9b83
commit 5da165f739
3 changed files with 128 additions and 75 deletions

View File

@@ -45,6 +45,10 @@ make install
L'application est accessible sur **http://localhost:8082**. L'application est accessible sur **http://localhost:8082**.
Les valeurs par défaut du `.env` committé suffisent pour démarrer en local. Pour la prod
(et pour activer la messagerie), surcharger les variables sensibles dans `.env.local`
voir « Variables d'environnement » ci-dessous.
### Comptes de test (fixtures) ### Comptes de test (fixtures)
| Utilisateur | Mot de passe | Rôle | Détails | | Utilisateur | Mot de passe | Rôle | Détails |
@@ -56,6 +60,25 @@ L'application est accessible sur **http://localhost:8082**.
| `client-liot` | `client` | ROLE_CLIENT | Client LIOT (projet SIRH) | | `client-liot` | `client` | ROLE_CLIENT | Client LIOT (projet SIRH) |
| `client-acme` | `client` | ROLE_CLIENT | Client ACME (projet CRM) | | `client-acme` | `client` | ROLE_CLIENT | Client ACME (projet CRM) |
## Variables d'environnement
Les variables sont définies dans `.env` (committé, valeurs par défaut pour le dev) et
peuvent être surchargées dans `.env.local` (jamais committé). En prod, elles vont dans le
`.env` du serveur (`/var/www/lesstime/.env`, voir `infra/prod/.env.example`).
| Variable | Rôle | Défaut dev | À fixer en prod |
|----------|------|-----------|-----------------|
| `APP_SECRET` | Secret Symfony | placeholder | ✅ (hex 32) |
| `JWT_PASSPHRASE` | Passphrase des clés JWT | placeholder | ✅ |
| `DATABASE_URL` | Connexion PostgreSQL | container `db` | ✅ (`host.docker.internal`) |
| `CORS_ALLOW_ORIGIN` | Origines CORS autorisées | localhost | ✅ (domaine prod) |
| **`ENCRYPTION_KEY`** | **Clé hex 32 bytes chiffrant les credentials IMAP/SMTP (feature mail)** | placeholder | ✅ — doit rester **stable**, sinon les credentials mail stockés deviennent illisibles |
| **`LOCK_DSN`** | **Store de verrous Symfony pour la sync mail (anti-chevauchement)** | `flock` | `flock` suffit |
> **Messagerie** : `ENCRYPTION_KEY` et `LOCK_DSN` sont introduites par l'intégration mail.
> Détails de config et cron de synchronisation : `docs/mail-integration.md` et `docs/mail-cron-setup.md`.
> Générer une clé : `php -r "echo bin2hex(random_bytes(32));"`.
## Commandes ## Commandes
### Docker ### Docker
@@ -218,13 +241,19 @@ docker exec -u www-data php-lesstime-fpm php bin/console app:generate-api-token
## Déploiement ## Déploiement
1. Déployer le code sur le serveur La prod tourne en **Docker** : l'image est buildée par la CI Gitea sur push de tag `v*`
2. `composer install --no-dev --optimize-autoloader` (`gitea.malio.fr/malio-dev/lesstime:<tag>`), puis déployée par le script `deploy.sh` sur
3. `php bin/console doctrine:migrations:migrate --no-interaction` le serveur (dossier `/var/www/lesstime`, container `lesstime-app`).
4. `php bin/console cache:clear --env=prod`
5. `cd frontend && npm install && npm run build:dist` ```bash
6. `docker restart nginx-lesstime` # Sur le serveur, depuis /var/www/lesstime
7. Ouvrir le port 8082 sur le firewall (LAN uniquement) sudo ./deploy.sh # déploie la dernière image (latest)
sudo ./deploy.sh v0.4.2 # déploie une version précise
```
Le script active la maintenance, pull l'image, redémarre le container, lance les migrations
et vide le cache. Guide complet (première installation, BDD, Nginx, JWT, rollback) :
**`doc/deployment-docker.md`**.
## Licence ## Licence

View File

@@ -3,44 +3,24 @@
## Vue d'ensemble ## Vue d'ensemble
La synchronisation IMAP est déclenchée par un cron OS toutes les 10 minutes. La synchronisation IMAP est déclenchée par un cron OS toutes les 10 minutes.
Elle appelle la commande Symfony `app:mail:sync` qui s'exécute dans le container PHP. Elle appelle la commande Symfony `app:mail:sync` qui s'exécute **dans le container PHP**.
Un Symfony Lock (`mail.sync`, TTL 10 min, store `flock` via `LOCK_DSN=flock`) empêche Un Symfony Lock (`mail.sync`, TTL 10 min, store `flock` via `LOCK_DSN=flock`) empêche
les runs de se chevaucher si une sync prend plus de 10 min. les runs de se chevaucher si une sync prend plus de 10 min.
> **Dev vs prod** — en dev le container s'appelle `php-lesstime-fpm` et on passe par `make`.
> En **production** le container s'appelle `lesstime-app` (service `app` du `docker-compose.yml`
> dans `/var/www/lesstime`), il n'y a **pas de `make`** : tout passe par `docker compose` / `docker exec`.
## Prérequis ## Prérequis
- Container `php-lesstime-fpm` démarré (`make start`) - `MailConfiguration.enabled = true` (configurable depuis l'admin — onglet « Mail »)
- `MailConfiguration.enabled = true` (configurable depuis l'admin — Phase 7) - `ENCRYPTION_KEY` (clé hex 32 bytes) défini dans l'environnement :
- `ENCRYPTION_KEY` défini dans `infra/dev/.env.docker.local` (ou production env) - **dev** : `infra/dev/.env.docker.local`
- **prod** : `/var/www/lesstime/.env`
## Installation du cron - Container démarré :
- **dev** : `make start` (container `php-lesstime-fpm`)
Sur la **machine hôte** (pas dans le container) : - **prod** : déployé via `sudo ./deploy.sh` (container `lesstime-app`)
```bash
crontab -e
```
Ajouter la ligne suivante (adapter le chemin) :
```cron
*/10 * * * * cd /home/r-dev/malio-dev/Lesstime && make mail-sync >> /var/log/lesstime-mail-sync.log 2>&1
```
Ou directement via `docker exec` (sans dépendance à `make`) :
```cron
*/10 * * * * docker exec php-lesstime-fpm php bin/console app:mail:sync >> /var/log/lesstime-mail-sync.log 2>&1
```
### Avec un utilisateur système dédié
Si le cron est configuré pour un utilisateur système spécifique (ex: `www-data` ou `deploy`) :
```bash
sudo crontab -u deploy -e
```
## Variables d'environnement nécessaires ## Variables d'environnement nécessaires
@@ -49,63 +29,100 @@ sudo crontab -u deploy -e
| `ENCRYPTION_KEY` | Clé hex 32 bytes pour déchiffrer le password IMAP | `$(php -r "echo bin2hex(random_bytes(32));")` | | `ENCRYPTION_KEY` | Clé hex 32 bytes pour déchiffrer le password IMAP | `$(php -r "echo bin2hex(random_bytes(32));")` |
| `LOCK_DSN` | DSN du store de verrous Symfony | `flock` (défaut, fichier local) | | `LOCK_DSN` | DSN du store de verrous Symfony | `flock` (défaut, fichier local) |
La clé doit être la même que celle utilisée pour chiffrer le password lors de la configuration. La clé `ENCRYPTION_KEY` doit être **identique** à celle utilisée pour chiffrer le password
lors de la configuration depuis l'admin. Si elle change, les credentials stockés deviennent illisibles.
## Checklist setup production ---
1. [ ] Définir `ENCRYPTION_KEY` dans les variables d'environnement production ## Dev
2. [ ] Créer le compte mail dédié (ex: `lesstime@votre-domaine.fr`) chez OVH
3. [ ] Accéder à `/admin` → onglet "Mail" → renseigner les credentials IMAP/SMTP
4. [ ] Cliquer "Tester la connexion" → vérifier le succès
5. [ ] Cocher "Activer la synchronisation" → Enregistrer
6. [ ] Installer le cron OS (voir section "Installation du cron")
7. [ ] Vérifier les logs après la première sync : `make logs-dev` (chercher `mail.sync`)
## Commandes utiles ### Lancer une sync à la main
```bash ```bash
# Sync complète (toutes les boîtes) make mail-sync # sync complète (toutes les boîtes)
make mail-sync make mail-sync FOLDER=INBOX # un seul dossier (doit déjà exister en base)
make mail-sync DRYRUN=1 # simulation (dry-run, pas d'écriture BDD)
```
# Sync d'un seul dossier (le dossier doit déjà exister en base) Ou directement dans le container :
make mail-sync FOLDER=INBOX
# Simulation (dry-run, pas d'écriture BDD) ```bash
make mail-sync DRYRUN=1
# Directement dans le container
docker exec php-lesstime-fpm php bin/console app:mail:sync docker exec php-lesstime-fpm php bin/console app:mail:sync
docker exec php-lesstime-fpm php bin/console app:mail:sync --folder=INBOX docker exec php-lesstime-fpm php bin/console app:mail:sync --folder=INBOX
docker exec php-lesstime-fpm php bin/console app:mail:sync --dry-run docker exec php-lesstime-fpm php bin/console app:mail:sync --dry-run
``` ```
## Logs ### Logs (dev)
Les logs Symfony sont dans `var/log/dev.log` (ou `prod.log` en production).
Suivre les logs en temps réel :
```bash ```bash
make logs-dev make logs-dev # tail -f var/log/dev.log
``` ```
Les messages loggés par `MailSyncService` sont préfixés `mail.sync`. Les messages loggés par `MailSyncService` sont préfixés `mail.sync`.
---
## Production
En prod, l'app tourne dans le container `lesstime-app` déployé par `sudo ./deploy.sh`
(dossier `/var/www/lesstime`). La commande s'exécute en tant que `www-data` (uid 33),
comme les migrations lancées par `deploy.sh`.
### Lancer une sync à la main
Depuis `/var/www/lesstime` :
```bash
sudo docker compose exec -T -u www-data app php bin/console app:mail:sync
sudo docker compose exec -T -u www-data app php bin/console app:mail:sync --folder=INBOX
sudo docker compose exec -T -u www-data app php bin/console app:mail:sync --dry-run
```
### Installer le cron
Sur la **machine hôte** (pas dans le container). Comme `docker` requiert `sudo` en prod,
installer le cron sous root :
```bash
sudo crontab -e
```
Ajouter :
```cron
*/10 * * * * cd /var/www/lesstime && docker compose exec -T -u www-data app php bin/console app:mail:sync >> /var/log/lesstime-mail-sync.log 2>&1
```
> Le crontab de root exécute déjà les commandes en root → pas de `sudo` à l'intérieur de la ligne cron.
> La commande est **idempotente** (UIDs uniques en base) : la relancer ne duplique pas les données.
### Logs (prod)
```bash
cd /var/www/lesstime
docker compose logs -f --tail=100 app # logs container
docker compose exec app cat var/log/prod.log # log Symfony (volume lesstime_logs)
```
### Checklist setup production
1. [ ] Définir `ENCRYPTION_KEY` (hex 32 bytes) et `LOCK_DSN=flock` dans `/var/www/lesstime/.env`
2. [ ] Créer le compte mail dédié (ex: `lesstime@votre-domaine.fr`) chez OVH
3. [ ] Accéder à `/admin` → onglet « Mail » → renseigner les credentials IMAP/SMTP
4. [ ] Cliquer « Tester la connexion » → vérifier le succès
5. [ ] Cocher « Activer la synchronisation » → Enregistrer
6. [ ] Lancer une sync manuelle pour valider (commande ci-dessus)
7. [ ] Installer le cron OS (voir « Installer le cron »)
8. [ ] Vérifier les logs après la première sync (`docker compose logs -f app`, chercher `mail.sync`)
---
## Sécurité ## Sécurité
- Le password IMAP est **toujours stocké chiffré** (libsodium secretbox) - Le password IMAP est **toujours stocké chiffré** (libsodium secretbox)
- Les corps de mails, passwords et pièces jointes ne sont **jamais loggés** - Les corps de mails, passwords et pièces jointes ne sont **jamais loggés**
- Le lock `flock` évite les runs parallèles (fichier dans `/tmp/sf.mail.sync.<hash>.lock`) - Le lock `flock` évite les runs parallèles (fichier dans `/tmp/sf.mail.sync.<hash>.lock`)
## Rappels sécurité
- La page `/mail` et tous les endpoints `/api/mail/*` sont refusés aux `ROLE_CLIENT` exclusifs - La page `/mail` et tous les endpoints `/api/mail/*` sont refusés aux `ROLE_CLIENT` exclusifs
- Le sidebar "Messagerie" est masqué pour les utilisateurs ROLE_CLIENT sans ROLE_USER - Le sidebar « Messagerie » est masqué pour les utilisateurs `ROLE_CLIENT` sans `ROLE_USER`
- Le password IMAP est chiffré via libsodium secretbox avant stockage (jamais en clair en base) - Les corps de mails sont sanitisés via DOMPurify avant affichage (`frontend/utils/sanitizeMailHtml.ts`)
- Les corps de mails sont sanitisés via DOMPurify avant affichage (voir `frontend/utils/sanitizeMailHtml.ts`) - Les pixels de tracking distants sont remplacés par un placeholder
- Les pixels tracking distants sont remplacés par un placeholder
- Aucun body mail, password ou contenu de pièce jointe n'est loggé
## Production
En production, préférer un cron système ou un job scheduler (Kubernetes CronJob, ECS Scheduled Task, etc.).
La commande est idempotente : relancer plusieurs fois ne duplique pas les données (UIDs uniques en base).

View File

@@ -15,6 +15,13 @@ JWT_COOKIE_SAMESITE=lax
JWT_TOKEN_TTL=86400 JWT_TOKEN_TTL=86400
JWT_COOKIE_TTL=86400 JWT_COOKIE_TTL=86400
# Mail (intégration IMAP/SMTP)
# Clé hex 32 bytes chiffrant les credentials mail stockés en base.
# Générer : php -r "echo bin2hex(random_bytes(32));" — doit rester STABLE.
ENCRYPTION_KEY=change-me
# Store de verrous Symfony pour la sync mail (anti-chevauchement du cron).
LOCK_DSN=flock
# CORS # CORS
CORS_ALLOW_ORIGIN='^https?://project\.malio-dev\.fr$' CORS_ALLOW_ORIGIN='^https?://project\.malio-dev\.fr$'