# Déploiement et exploitation

Cible : VPS existant du client, CloudPanel + MySQL 8, PHP 8.3.

## 1. Créer le site dans CloudPanel

1. **Sites → Add Site → Create a PHP Site**
   - Domaine : celui retenu pour l'application
   - PHP version : **8.3**
   - Site User : par exemple `tournees`
2. **Databases → Add Database** : base `tournees`, utilisateur dédié, mot de
   passe fort. Ne pas réutiliser le compte `root`.
3. **SSL/TLS → Let's Encrypt** : certificat obligatoire. L'application est une
   PWA, et l'installation sur l'écran d'accueil comme le service worker exigent
   HTTPS.

### Racine du site

CloudPanel sert par défaut le dossier du site. Laravel doit être servi depuis
`public/`. Dans **Site Settings → Vhost**, faire pointer `root` sur :

```
/home/tournees/htdocs/<domaine>/public
```

## 2. Déposer le code

```bash
cd /home/tournees/htdocs/<domaine>
git clone <dépôt> .
composer install --no-dev --optimize-autoloader
npm ci && npm run build
cp .env.example .env
php artisan key:generate
```

### Variables à renseigner dans `.env`

```dotenv
APP_ENV=production
APP_DEBUG=false
APP_URL=https://<domaine>
APP_TIMEZONE=Europe/Paris

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_DATABASE=tournees
DB_USERNAME=tournees
DB_PASSWORD=<mot de passe>

QUEUE_CONNECTION=database
SESSION_DRIVER=database
CACHE_STORE=database

# Extraction des feuilles de route
ANTHROPIC_API_KEY=sk-ant-...
ANTHROPIC_MODEL=claude-opus-5

# Géocodage (aucune clé nécessaire)
GEOCODER_BASE_URL=https://api-adresse.data.gouv.fr

# Itinéraire : remplacer par l'instance auto-hébergée (voir §6)
OSRM_BASE_URL=https://router.project-osrm.org

# Identité portée sur les bons de livraison et de collecte signés
ORG_NAME="Ressourcerie du Calaisis"
ORG_ADDRESS="..."
ORG_POSTAL_CODE=62100
ORG_CITY=Calais
ORG_PHONE="03 21 ..."
ORG_SIRET=...

# Fin de la journée de travail : au-delà, les tournées du jour déjà
# terminées sortent des écrans de l'équipage.
TOURS_END_OF_DAY=16:30

# Durée de conservation des données personnelles, en mois
RGPD_RETENTION_MONTHS=24
```

### Initialiser la base et les caches

```bash
php artisan migrate --force
php artisan db:seed --force        # crée les trois comptes de démarrage
php artisan config:cache
php artisan route:cache
php artisan view:cache
```

> Se connecter immédiatement avec le compte administrateur
> (`admin@exemple.fr` / `motdepasse`), créer les comptes nominatifs réels dans
> **Comptes**, puis supprimer les comptes `@exemple.fr`. Il doit rester au
> moins un administrateur actif — l'application refuse de supprimer ou de
> rétrograder le dernier.

Sur une base déjà en service (mise à jour depuis une version antérieure aux
rôles), aucun compte n'est administrateur. Pour en désigner un :

```bash
php artisan tournees:admin responsable@exemple.fr
```

La commande promeut le compte existant, ou le crée s'il n'existe pas.

### Droits sur les fichiers

```bash
chown -R tournees:tournees storage bootstrap/cache
chmod -R ug+rw storage bootstrap/cache
```

Les scans de feuilles de route sont écrits dans `storage/app/private/` : ils ne
sont **pas** accessibles depuis le web, et c'est voulu — ils contiennent des
données personnelles.

## 3. Worker de file d'attente

La numérisation d'une feuille de route prend plusieurs dizaines de secondes ;
elle est traitée hors requête HTTP. Sans worker, les imports restent bloqués sur
« numérisation en cours ».

Créer `/etc/systemd/system/tournees-worker.service` :

```ini
[Unit]
Description=File d'attente - suivi de tournees
After=network.target

[Service]
User=tournees
Group=tournees
Restart=always
RestartSec=5
WorkingDirectory=/home/tournees/htdocs/<domaine>
ExecStart=/usr/bin/php8.3 artisan queue:work --queue=default --sleep=3 --tries=2 --timeout=300 --max-time=3600

[Install]
WantedBy=multi-user.target
```

```bash
systemctl daemon-reload
systemctl enable --now tournees-worker
systemctl status tournees-worker
```

Le `--timeout=300` doit rester supérieur au `timeout` du job d'extraction
(300 s dans `app/Jobs/ExtractRoadmap.php`).

## 4. Tâche planifiée

Une seule entrée cron, qui déclenche l'ordonnanceur Laravel (aujourd'hui : la
purge RGPD nocturne).

```bash
crontab -u tournees -e
```

```cron
* * * * * cd /home/tournees/htdocs/<domaine> && /usr/bin/php8.3 artisan schedule:run >> /dev/null 2>&1
```

## 5. Taille des fichiers envoyés

Les photos de feuilles de route font couramment 5 à 12 Mo. Dans **Site Settings
→ PHP Settings** :

```ini
upload_max_filesize = 24M
post_max_size = 24M
max_execution_time = 120
```

L'application plafonne l'envoi à 20 Mo côté validation.

## 6. Itinéraire : passer OSRM en auto-hébergé

L'instance publique `router.project-osrm.org` est une **démo** : pas de
garantie de disponibilité, et son usage en production est déconseillé par le
projet. Elle convient pour une recette, pas pour l'exploitation.

Sur le VPS, avec Docker :

```bash
mkdir -p /srv/osrm && cd /srv/osrm
wget https://download.geofabrik.de/europe/france/nord-pas-de-calais-latest.osm.pbf
docker run -t -v /srv/osrm:/data osrm/osrm-backend osrm-extract -p /opt/car.lua /data/nord-pas-de-calais-latest.osm.pbf
docker run -t -v /srv/osrm:/data osrm/osrm-backend osrm-partition /data/nord-pas-de-calais-latest.osrm
docker run -t -v /srv/osrm:/data osrm/osrm-backend osrm-customize /data/nord-pas-de-calais-latest.osrm
docker run -d --restart always --name osrm -p 5000:5000 -v /srv/osrm:/data \
  osrm/osrm-backend osrm-routed --algorithm mld /data/nord-pas-de-calais-latest.osrm
```

Puis dans `.env` :

```dotenv
OSRM_BASE_URL=http://127.0.0.1:5000
```

Compter environ 2 Go de RAM pendant la préparation et ~1 Go à l'exécution pour
la région Nord-Pas-de-Calais. Si OSRM est injoignable, l'application ne tombe
pas : elle ordonne les arrêts à vol d'oiseau et le signale dans les journaux.

## 7. Sauvegardes

À sauvegarder :

| Quoi | Où | Fréquence |
|---|---|---|
| Base de données | `mysqldump` de la base `tournees` | quotidienne |
| Scans des feuilles de route | `storage/app/private/feuilles-de-route/` | quotidienne |
| Photos prises sur le terrain | `storage/app/private/arrets/` | quotidienne |
| Signatures et bons signés | `storage/app/private/signatures/`, `bons/` | quotidienne |
| `.env` | hors serveur, chiffré | à chaque modification |

Exemple de dump quotidien :

```bash
mysqldump --single-transaction -u tournees -p tournees | gzip > /home/backups/tournees-$(date +\%F).sql.gz
```

Les sauvegardes contiennent des données personnelles : les chiffrer et leur
appliquer la même durée de conservation que la base.

## 8. Mise à jour

```bash
cd /home/tournees/htdocs/<domaine>
php artisan down
git pull
composer install --no-dev --optimize-autoloader
npm ci && npm run build
php artisan migrate --force
php artisan config:cache && php artisan route:cache && php artisan view:cache
systemctl restart tournees-worker
php artisan up
```

Le worker **doit** être redémarré après chaque déploiement : il garde en mémoire
l'ancien code.

## 9. RGPD

Les feuilles de route contiennent le nom et l'adresse des demandeurs.

- **Hébergement** : VPS du client, en France. Le géocodage passe par l'API
  Adresse (service public français). L'extraction envoie l'image de la feuille
  de route à l'API Claude — c'est le seul transfert vers un tiers, à porter au
  registre des traitements et à couvrir par un contrat de sous-traitance.
- **Accès** : comptes nominatifs, trois rôles (chauffeur, manager,
  administrateur). Un chauffeur ne voit que ses propres tournées et ne peut
  pointer aucun autre arrêt ; un manager ne peut ni créer de compte, ni
  attribuer de rôle, ni toucher à un compte administrateur (vérifié par les
  tests). Les scans ne sont jamais servis publiquement. Chacun change son mot
  de passe depuis **Mon compte**, ce qui déconnecte ses autres appareils.
- **Conservation** : `RGPD_RETENTION_MONTHS` (24 mois par défaut, valeur à
  arrêter avec le client). La commande planifiée `tournees:purger` supprime
  chaque nuit les tournées, leurs arrêts et les scans au-delà de ce délai.

```bash
php artisan tournees:purger --simulation     # liste sans rien supprimer
php artisan tournees:purger --mois=12        # purge ponctuelle
```

## 10. Écran blanc sur un appareil : /diagnostic.html

Un service worker qui se trompe donne un écran blanc, sans message ni
recours, et il est **collant** : il survit au rechargement de la page.

`https://<domaine>/diagnostic.html` s'ouvre depuis l'appareil concerné et
affiche l'état réel — contexte sécurisé ou non, mode (navigateur ou
application installée), service worker enregistré, caches présents. Un
bouton supprime le service worker et tous les caches ; les pointages et
photos en attente d'envoi ne sont pas touchés.

Cette page est volontairement statique et exclue du cache : elle reste
joignable quand tout le reste est cassé.

**Rappel** : hors `localhost`, un service worker exige HTTPS **avec un
certificat reconnu par l'appareil**. Deux pièges en développement local :

- une adresse en `http://` : le service worker ne s'enregistre pas,
  l'application ne s'installe pas vraiment. Le site fonctionne, pointage
  hors-ligne compris (localStorage et IndexedDB n'exigent pas HTTPS) ;
- le **certificat auto-signé de Laragon** : reconnu sur le poste où
  Laragon a installé son autorité, inconnu de tout autre appareil. Dans un
  onglet on passe outre l'avertissement et le site s'affiche ; depuis une
  icône d'écran d'accueil, la fenêtre autonome ne peut pas afficher cet
  avertissement et **reste blanche**.

Pour tester depuis une tablette avant la mise en ligne : utiliser l'adresse
en `http://`, ou installer l'autorité Laragon (`C:/laragon/etc/ssl/`) sur
l'appareil. En production, Let's Encrypt règle la question.

## 11. Vérifications après mise en ligne

- [ ] `https://<domaine>` répond, certificat valide
- [ ] Connexion administrateur, création d'un compte manager et d'un compte
      chauffeur, puis suppression des comptes `@exemple.fr`
- [ ] Changement de mot de passe depuis **Mon compte** avec un des comptes créés
- [ ] Connexion manager puis chauffeur
- [ ] Import d'une vraie feuille de route → arrêts relevés en moins d'une minute
- [ ] Import d'un CSV (le modèle téléchargeable fait l'affaire) → arrêts créés
      immédiatement, accents et codes postaux intacts
- [ ] Validation d'une tournée → arrêts géocodés et ordonnés
- [ ] Sur un téléphone : « Ajouter à l'écran d'accueil », l'application s'ouvre
      en plein écran avec son icône
- [ ] Affecter une tournée à deux comptes : les deux la voient, et le nom de
      celui qui pointe apparaît au tableau de bord
- [ ] Joindre une photo depuis un téléphone, la retrouver dans le suivi manager
- [ ] Démarrer une tournée, déclarer un vidage, reprendre, clôturer — vérifier
      le compteur de rotations au tableau de bord
- [ ] Créer une tournée de livraison en numérisant plusieurs bons d'un coup :
      chacun doit devenir un arrêt
- [ ] Faire signer un bon sur le téléphone, ouvrir le PDF produit et contrôler
      l'entête (raison sociale, SIRET)
- [ ] Après TOURS_END_OF_DAY, vérifier qu'une tournée close a quitté l'écran du
      chauffeur et reste dans l'historique manager
- [ ] Vérifier que l’heure de passage correspond à l’heure réelle (fuseau
      Europe/Paris)
- [ ] Mode avion : pointer un arrêt ET prendre une photo, réactiver le réseau,
      vérifier que les deux remontent au tableau de bord
- [ ] `systemctl status tournees-worker` actif
- [ ] `php artisan tournees:purger --simulation` s'exécute sans erreur
