# BACKUP.md

Ce qu'il faut sauvegarder, à quelle fréquence, et — surtout — comment vérifier
que la sauvegarde sert à quelque chose.

## 1. Ce qui doit être sauvegardé

| Élément | Chemin | Volume | Reconstructible ? |
|---|---|---|---|
| **Base de données** | MariaDB `USER_portraits` | quelques Mo | ❌ **non** |
| **Photos originales** | `storage/app/private/photos/` | plusieurs Go | ❌ **non** |
| **Configuration** | `.env` | 2 Ko | ❌ **non** (clé d'application) |
| Dérivés web | `storage/app/private/derived/` | ~15 % des originaux | ✅ régénérables |
| Fichiers d'import | `storage/app/private/imports/` | quelques Mo | ✅ supprimés après usage |
| Code source | `app/` `config/` `resources/` … | quelques Mo | ✅ dépôt Git |
| Dépendances | `vendor/` `node_modules/` | ~500 Mo | ✅ `composer install` |
| Journaux | `storage/logs/` | variable | ✅ |
| Caches | `bootstrap/cache/` `storage/framework/` | variable | ✅ |

**Trois choses seulement sont irremplaçables** : la base, les originaux, et le
`.env`.

> ⚠️ **Le `.env` contient `APP_KEY`.** Sans elle, les codes d'accès des
> portails et les sessions deviennent illisibles. Conservez-en une copie
> **ailleurs que sur le serveur** — un gestionnaire de mots de passe convient.

> Les dérivés (`derived/`) ne méritent pas d'être sauvegardés : ils se
> régénèrent depuis les originaux, et ils représentent du volume pour rien.
> Voir §5 pour la commande de régénération.

## 2. Base de données

### Sauvegarde quotidienne

```bash
#!/usr/bin/env bash
# ~/scripts/backup-db.sh
set -euo pipefail

DEST="$HOME/backups/db"
KEEP=30
mkdir -p "$DEST"

FILE="$DEST/portraits-$(date +%F-%H%M).sql.gz"

mysqldump \
    --single-transaction --quick --default-character-set=utf8mb4 \
    --user="$DB_USER" --password="$DB_PASS" "$DB_NAME" \
    | gzip -9 > "$FILE"

# Une sauvegarde vide vaut pire que pas de sauvegarde : elle rassure à tort.
[ -s "$FILE" ] || { echo "Sauvegarde vide : $FILE" >&2; exit 1; }

find "$DEST" -name 'portraits-*.sql.gz' -mtime "+$KEEP" -delete
echo "OK — $(du -h "$FILE" | cut -f1)"
```

`--single-transaction` produit un instantané cohérent **sans verrouiller les
tables** : indispensable pour ne pas bloquer les photographes en plein
shooting.

Cron N0C, chaque nuit :

```
15 2 * * * /bin/bash /home/USER/scripts/backup-db.sh >> /home/USER/backups/db.log 2>&1
```

### Restauration

```bash
gunzip -c ~/backups/db/portraits-2026-09-12-0215.sql.gz \
    | mysql --default-character-set=utf8mb4 -u USER -p USER_portraits
```

## 3. Photos

Les originaux ne changent jamais après leur dépôt : une synchronisation
incrémentale suffit et reste peu coûteuse.

### Vers N0C Storage (S3), recommandé

```bash
rclone sync ~/portraits/storage/app/private/photos n0c:portraits-backup/photos \
    --transfers 4 --checkers 8 --log-file ~/backups/photos.log
```

Hebdomadaire, ou après chaque shooting :

```
30 3 * * 0 /usr/bin/rclone sync … >> /home/USER/backups/photos.log 2>&1
```

### En local, sur le poste

```bash
rsync -avz --delete \
    USER@serveur:~/portraits/storage/app/private/photos/ \
    ~/Sauvegardes/portraits-photos/
```

> `--delete` répercute les suppressions, **y compris les purges RGPD** — c'est
> voulu : conserver dans une sauvegarde des portraits qu'on s'est engagé à
> effacer viderait la purge de son sens.

## 4. Configuration

```bash
cp ~/portraits/.env ~/backups/env-$(date +%F).txt
chmod 600 ~/backups/env-*.txt
```

Puis copiez-la hors du serveur.

## 5. Reconstruction de ce qui n'est pas sauvegardé

```bash
composer install --no-dev --optimize-autoloader   # vendor/
php artisan optimize:clear                        # caches
```

Les dérivés web se régénèrent depuis les originaux :

```bash
php artisan tinker --execute '
use App\Models\Photo;
use App\Services\ImageDerivativeService;
$service = app(ImageDerivativeService::class);
Photo::whereNull("thumb_path")->orWhereNull("preview_path")
    ->each(fn ($photo) => $service->generate($photo));
'
```

## 6. Vérifier que la sauvegarde fonctionne

**Une sauvegarde jamais restaurée n'est pas une sauvegarde.** À faire au moins
une fois après l'installation, puis tous les six mois.

```bash
# 1. Base de test
mysql -e "CREATE DATABASE USER_restore_test CHARACTER SET utf8mb4;"

# 2. Restauration de la dernière sauvegarde
gunzip -c ~/backups/db/$(ls -t ~/backups/db | head -1) \
    | mysql -u USER -p USER_restore_test

# 3. Contrôles de cohérence
mysql USER_restore_test -e "
    SELECT 'shootings', COUNT(*) FROM shootings
    UNION SELECT 'participants', COUNT(*) FROM participants
    UNION SELECT 'photos', COUNT(*) FROM photos
    UNION SELECT 'événements', COUNT(*) FROM shooting_events;"

# 4. Ménage
mysql -e "DROP DATABASE USER_restore_test;"
```

Vérifiez ensuite qu'un fichier pris au hasard dans la sauvegarde photos
correspond bien à une ligne de `photos.original_path`.

## 7. Calendrier

| Quoi | Fréquence | Rétention |
|---|---|---|
| Base de données | quotidienne, 02 h 15 | 30 jours |
| Photos | hebdomadaire + après chaque shooting | miroir |
| `.env` | à chaque modification | permanente, hors serveur |
| **Test de restauration** | **semestriel** | — |

## 8. Avant un shooting important

Le moment où une perte coûterait le plus cher :

```bash
bash ~/scripts/backup-db.sh
rclone sync ~/portraits/storage/app/private/photos n0c:portraits-backup/photos
php artisan app:doctor
df -h ~   # espace disque suffisant pour la journée ?
```

## 9. Reprise après incident total

1. Recréer le compte d'hébergement et la base.
2. `git clone` du dépôt, puis `composer install --no-dev`.
3. Restaurer le `.env` (§4) — **c'est `APP_KEY` qui conditionne tout le reste**.
4. Restaurer la base (§2).
5. Restaurer les photos (§3).
6. `php artisan optimize:clear && php artisan config:cache`.
7. Régénérer les dérivés manquants (§5).
8. `php artisan app:doctor`.
9. Recréer la tâche cron (cf. [DEPLOYMENT.md §4](DEPLOYMENT.md)).
