# DEPLOYMENT.md — PlanetHoster (N0C / World)

Procédure complète de mise en production. À suivre dans l'ordre la première
fois ; les mises à jour suivantes se résument à `./deploy.sh`.

> Les points marqués **⚠ à vérifier** dépendent de réglages propres à votre
> compte : contrôlez-les dans le panneau N0C avant de commencer.

---

## 1. Prérequis à vérifier dans le panneau N0C

| Élément | Attendu | Où |
|---|---|---|
| Version de PHP | **8.3 ou 8.4** | N0C → PHP |
| Extensions | `pdo_mysql` `mbstring` `openssl` `tokenizer` `xml` `ctype` `json` `fileinfo` `curl` `zip` `phar` + `gd` **ou** `imagick` | N0C → PHP → Extensions |
| Extensions recommandées | `intl` `exif` `bcmath` `opcache` | idem |
| `memory_limit` | **512 M** (256 M minimum) | N0C → PHP → Options |
| `upload_max_filesize` | **32 M** | idem |
| `post_max_size` | **≥ upload_max_filesize** | idem |
| `max_execution_time` | **120** | idem |
| Base de données | MariaDB, jeu `utf8mb4` | N0C → Bases de données |
| SSH | activé, clé publique déposée | MG → Accès SSH |

**`phar` est indispensable** pour installer Composer. S'il est désactivé,
l'installation échoue avec un message peu explicite.

---

## 2. Arborescence

Le point décisif : **le document root pointe sur `public/`, pas sur la racine
de l'application.** N0C permet de le régler par domaine — aucun bidouillage de
`.htaccess` n'est nécessaire.

```
/home/USER/
├── portraits/              ← application (hors document root)
│   ├── app/  bootstrap/  config/  database/  resources/  routes/  vendor/
│   ├── storage/
│   │   ├── app/private/photos/     ← originaux
│   │   ├── app/private/derived/    ← aperçus et vignettes
│   │   ├── app/private/imports/    ← tableurs en cours d'import
│   │   └── logs/
│   ├── public/             ← DOCUMENT ROOT du sous-domaine
│   └── .env                ← jamais accessible par le web
```

Dans **N0C → Domaines → portrait.mondomaine.fr** :

```
ROOT DIRECTORY : /home/USER/portraits/public
```

**⚠ à vérifier** : après enregistrement, `https://portrait.mondomaine.fr/.env`
doit répondre **404**. S'il affiche le fichier, le document root est mal réglé —
arrêtez tout et corrigez avant d'aller plus loin.

---

## 3. Installation initiale

### 3.1 Composer

Il n'est pas préinstallé sur N0C.

```bash
mkdir -p ~/.local/bin
curl -sS https://getcomposer.org/installer | /opt/alt/php83/usr/bin/php -- \
    --install-dir=$HOME/.local/bin --filename=composer
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
```

### 3.2 Code et dépendances

```bash
cd ~ && git clone <votre-dépôt> portraits && cd portraits

/opt/alt/php83/usr/bin/php -d memory_limit=-1 ~/.local/bin/composer install \
    --no-dev --prefer-dist --optimize-autoloader
```

> **Compilez les assets sur votre poste**, pas sur le serveur : `npm ci` crée
> plus de 20 000 fichiers et consomme inutilement vos inodes. Versionnez
> `public/build/` ou transférez-le par SFTP.

### 3.3 Configuration

```bash
cp .env.example .env
/opt/alt/php83/usr/bin/php artisan key:generate
```

Réglages **obligatoires** dans `.env` :

```dotenv
APP_ENV=production
APP_DEBUG=false
APP_URL=https://portrait.mondomaine.fr      # sans barre oblique finale
APP_TIMEZONE=Europe/Paris
APP_LOCALE=fr

DB_CONNECTION=mysql
DB_HOST=localhost
DB_DATABASE=USER_portraits
DB_USERNAME=USER_portraits
DB_PASSWORD=•••

SESSION_DRIVER=database
CACHE_STORE=database
QUEUE_CONNECTION=database

MAIL_MAILER=smtp
MAIL_HOST=mail.mondomaine.fr
MAIL_PORT=587
MAIL_USERNAME=portraits@mondomaine.fr
MAIL_PASSWORD=•••
MAIL_FROM_ADDRESS=portraits@mondomaine.fr

PORTAL_LOG_SALT=•••                          # chaîne aléatoire, 32+ caractères
DEFAULT_RETENTION_DAYS=180
PHOTOS_MAX_UPLOAD_KB=25600
```

> ⚠️ **`APP_URL` doit correspondre *exactement* à l'adresse réellement
> visitée.** Les URLs signées du portail participant couvrent l'hôte : un
> `http` au lieu de `https`, ou un `www` de trop, fait répondre **403 à toutes
> les images du portail** alors que le reste de l'application fonctionne
> normalement. Symptôme parfaitement déroutant si l'on n'y pense pas.

### 3.4 Base de données et permissions

```bash
php artisan migrate --force
php artisan app:create-admin        # demande nom, e-mail et mot de passe

chmod -R 755 storage bootstrap/cache
```

> ⚠️ **N'exécutez jamais `db:seed` en production.** Le seeder de démonstration
> crée trois comptes photographes dont le mot de passe figure en clair dans le
> dépôt. `app:create-admin` demande un mot de passe choisi, qui n'apparaît ni à
> l'écran ni dans l'historique du shell.

### 3.5 Optimisations et contrôle

```bash
php artisan config:cache && php artisan route:cache
php artisan view:cache   && php artisan event:cache

php artisan app:doctor
```

`app:doctor` doit terminer sans aucune ligne rouge. Il vérifie la version de
PHP, les extensions, la base, les disques, les limites d'upload, la file
d'attente, et — en production — `APP_DEBUG`, `APP_URL` et le cache de
configuration.

---

## 4. Tâche planifiée

Une **seule** entrée cron suffit : Laravel orchestre le reste.

**N0C → Crons → Ajouter**, toutes les minutes :

```
/opt/alt/php83/usr/bin/php /home/USER/portraits/artisan schedule:run >/dev/null 2>&1
```

La redirection est importante : sans elle, PlanetHoster vous envoie un e-mail
à chaque exécution — 1 440 par jour.

Elle déclenche :

| Quand | Tâche |
|---|---|
| chaque minute | `queue:work --stop-when-empty --max-time=50` |
| 00 h 10 | transitions de statut, calcul des dates de purge |
| toutes les 15 min (12 h–23 h) | récapitulatifs de fin de journée |
| 03 h 00 | suppression des imports abandonnés |
| 03 h 30 | purge RGPD |

> **Aucun processus permanent.** `--stop-when-empty` est délibéré : un
> `queue:work` en démon serait tué par l'hébergeur, et la file cesserait
> d'avancer sans alerte. Contrôlez `php artisan schedule:list` après
> installation.

---

## 5. Mises à jour

```bash
cd ~/portraits && ./deploy.sh
```

Le script met en maintenance, récupère le code, installe les dépendances,
migre, reconstruit les caches, lance `app:doctor` et ressort de maintenance —
même en cas d'échec, grâce à un `trap`.

Variables utiles :

```bash
PHP_BIN=/opt/alt/php84/usr/bin/php ./deploy.sh   # autre version de PHP
BUILD_ASSETS=1 ./deploy.sh                       # si Node est disponible
DEPLOY_BRANCH=recette ./deploy.sh
```

---

## 6. SSL

N0C → SSL → activer **Let's Encrypt** sur le sous-domaine, puis forcer HTTPS.
L'application force elle-même le schéma `https` en production
(`AppServiceProvider::configureUrls`), mais la redirection au niveau du serveur
évite un aller-retour en clair.

---

## 7. Points de vigilance propres à cet hébergement

### 7.1 Limites LVE

N0C repose sur CloudLinux : dépasser les quotas CPU ou mémoire provoque un
**HTTP 508**, pas une erreur applicative. Si vous en voyez, regardez d'abord
les ressources, pas le code.

Le polling de l'écran shooting a été dimensionné pour cela : **3 requêtes
indexées toutes les 8 secondes et par photographe**, soit ~18 secondes de CPU
cumulées par heure à cinq photographes.

### 7.2 Volume disque

Un séminaire de 500 personnes avec 3 photos chacune, en JPEG de 4 Mo, occupe
**environ 6 Go** — dérivés compris. Le plan Standard offre 10 Go.

Trois leviers, dans cet ordre :

1. `PHOTOS_RECOMPRESS=true` — divise l'occupation par trois environ (bord long
   4 000 px, qualité 92). Désactivé par défaut : on ne dégrade pas le fichier
   du photographe sans décision explicite.
2. La purge RGPD automatique libère l'espace des shootings échus.
3. Au-delà, basculez `photos_original` vers **N0C Storage (S3)** : seule la
   configuration du disque change, aucune ligne de code ni migration de données
   en base (le disque est stocké par photo).

### 7.3 Inodes

**⚠ à vérifier** : le quota d'inodes n'est pas publié par PlanetHoster.
`vendor/` représente à lui seul ~12 000 fichiers. N'installez pas
`node_modules` sur le serveur.

### 7.4 Imagick

S'il est disponible, activez-le : les vignettes sont générées plus vite et avec
beaucoup moins de mémoire. À défaut, GD suffit, mais montez `memory_limit` à
512 M. `app:doctor` indique lequel est utilisé.

---

## 8. Vérification après déploiement

À faire dans l'ordre, une seule fois, sur le site en production :

- [ ] `https://…/.env` renvoie **404**
- [ ] `php artisan app:doctor` : aucune ligne rouge
- [ ] Connexion administrateur, mot de passe changé
- [ ] Création d'un shooting de test
- [ ] Import d'un fichier de 3 lignes
- [ ] Marquage d'une prise de vue depuis un **téléphone**
- [ ] Dépôt d'une photo, vignette visible
- [ ] Publication, puis ouverture du portail participant **depuis un autre
      réseau** (4G, navigation privée)
- [ ] Téléchargement du portrait
- [ ] `https://…/p/<uuid>/preview` **sans signature** renvoie **403**
- [ ] Export PDF du rapport
- [ ] `php artisan schedule:list` affiche les cinq tâches
- [ ] Une première sauvegarde a été exécutée et **restaurée à blanc**
      (cf. [BACKUP.md](BACKUP.md))

---

## 9. Dépannage

| Symptôme | Cause la plus probable |
|---|---|
| 500 à l'accueil | `php artisan config:cache` non relancé après modification du `.env` |
| Page blanche | `storage/` non inscriptible — `chmod -R 755 storage` |
| **Toutes** les images du portail en 403 | `APP_URL` ne correspond pas à l'adresse visitée (§3.3) |
| Vignettes absentes | ni GD ni Imagick — voir `app:doctor` |
| 508 | quotas LVE dépassés (§7.1) |
| Import qui expire | `max_execution_time` trop bas, ou fichier > 5 000 lignes |
| Aucun e-mail | identifiants SMTP, ou file d'attente non vidée — `php artisan queue:work --stop-when-empty` à la main |
| Accents cassés dans un export | ouverture du CSV par double-clic sous Windows ; passez par *Données → Importer* |
