# Deploy pre-produzione — upgrade Symfony 4 → 5.4

Questo runbook descrive il deploy in **pre-produzione** dell'applicativo aggiornato a
**Symfony 5.4**, partendo da un cloud attualmente su **Symfony 4**.

Pacchetto: **tarball autonomo del codice** (solo file committati; build composer/npm
eseguita sul server). Strategia schema DB: **migrazioni Doctrine mirate** (preferito) con
**backup obbligatorio** (il DB pre-prod ha dati reali). `doctrine:schema:update --force` (dalla
5.8.0 di nuovo funzionante, con `schema:validate` pulito) è usabile come anteprima/verifica ma
**mai con `--complete`** (no DROP): fa il diff di *tutte* le entità, quindi va lanciato solo con
`schema:validate` OK e dopo aver controllato `--dump-sql` (vedi §8).

---

## 1. Costruire il pacchetto (in locale)

```bash
# dalla root del repo, sul branch da rilasciare (es. feat/gallery-dashboard-perf)
./deploy/build-package.sh
# -> produce dist/c4rgocloud-preprod-<data>-<commit>.tar.gz
```

Il tarball **non contiene**: `.git`, `vendor/`, `node_modules/`, `public/build`,
`public/bundles`, `.env.local`/segreti, `var/`, dump SQL. Vengono ricostruiti sul server.

Trasferisci sul server:
```bash
scp dist/c4rgocloud-preprod-*.tar.gz utente@cloud-preprod:/srv/releases/
```

### Deploy incrementale (delta) — solo le ultime modifiche

Dopo un primo deploy completo, per spedire **solo i file cambiati** rispetto a un
release gia' deployato (evita di ritrasferire centinaia di MB):

```bash
# base = commit/tag del pacchetto gia' deployato (es. il commit del tarball completo)
./deploy/build-package.sh --since <commit_base>
# -> dist/c4rgocloud-preprod-delta-<data>-<base>_to_<head>.tar.gz  (+ .APPLY.txt)
```

Sul server, dentro la dir del release gia' presente:
```bash
tar -xzf c4rgocloud-preprod-delta-*.tar.gz -C /srv/releases/<release-dir>/
```
Leggi il file `.APPLY.txt` accluso: elenca i file aggiornati, eventuali file da
rimuovere a mano, e se servono passi extra (composer/cache/assets/npm). Per
modifiche di sole **doc/versione** non serve nulla.

## 2. Prerequisiti sul server (UNA TANTUM, prima del primo deploy 5.4)

- **PHP 8.1**: l'app gira **dentro il container** `php` (Dockerfile: PHP 8.1 + intl,
  gd, imagick, pdo_mysql, apcu, opcache, bcmath, xsl). Il PHP di sistema del cloud
  (probabilmente 7.x per SF4) **non viene usato**. Assicurati che Docker sia presente.
- **`.env.local`** con i segreti di pre-prod (NON è nel tarball). Deve contenere almeno:
  ```dotenv
  APP_ENV=staging        # oppure prod
  APP_DEBUG=0            # MAI 1 in pre-prod
  APP_SECRET=...
  DATABASE_URL="mysql://user:pass@host:3306/dbname?serverVersion=..."
  MAILER_DSN=...
  MAILER_REPORT_DSN=...
  # se usate: OKTA_*, OKTA_SSL_VERIFY=true, SITE_ONLINE, server_path, ecc.
  ```
- **Chiavi JWT** (lexik, gitignored): se usate le API,
  `docker-compose exec php php bin/console lexik:jwt:generate-keypair`.
- **Backup del DB** raggiungibile (mysqldump o snapshot del provider).

## 3. Deploy

```bash
cd /srv/releases
tar -xzf c4rgocloud-preprod-<...>.tar.gz
cd c4rgocloud-preprod-<...>/
cp /percorso/condiviso/.env.local .            # i segreti di pre-prod
# (se usi volumi/cartella fissa per i container, sposta/symlinka questa dir come fa il tuo setup)

./deploy/deploy-preprod.sh
```

Lo script esegue, con conferme nei punti critici:
1. pre-controlli (`.env.local`, JWT, `APP_DEBUG=0`);
2. `docker-compose build php` + `up -d` (PHP 8.1);
3. `composer install --no-dev --optimize-autoloader`;
4. **pulizia cache SF4** (`rm -rf var/cache/*`) + `cache:clear` + `cache:warmup` (prod);
5. `assets:install` (ripubblica gli asset dei bundle, **scheduler dukecity** incluso);
6. `npm ci && npm run build`;
7. **backup DB** (gate: conferma di averlo fatto);
8. **schema**: vedi nota sotto — di norma **migrazione mirata** (`doctrine:migrations:execute '<Version…>' --up`); `--force` solo se `doctrine:schema:validate` è pulito e l'anteprima `--dump-sql` non contiene DROP;
9. permessi `var/`;
10. smoke check.

> **Schema DB — migrazioni mirate vs `schema:update`.** Per le modifiche di schema usa la
> **migrazione dedicata** della release, eseguita singolarmente:
> ```bash
> docker-compose exec php php bin/console doctrine:migrations:execute '<DoctrineMigrations\Version…>' --up --no-interaction
> ```
> Scrivi le migrazioni in modo **rieseguibile** (`CREATE TABLE IF NOT EXISTS`, ADD condizionati
> a `information_schema`).
>
> ℹ️ **`doctrine:schema:update --force`**: in passato abortiva per mapping legacy disallineati
> dal DB reale (es. *“There is no column with name `nometermine` on table `traduzioni`”*) —
> poiché confronta *tutte* le metadata, un solo mapping rotto bloccava anche la creazione di
> tabelle nuove e scorrelate. Dalla release 5.8.0 i mapping sono allineati e
> `doctrine:schema:validate` è **pulito**, quindi il comando torna utilizzabile. Resta buona
> norma usarlo **solo** dopo che `doctrine:schema:validate` è OK e l'anteprima `--dump-sql`
> non mostra DROP (e **mai** con `--complete`). Se in futuro `schema:validate` torna a segnalare
> `[FAIL]`, preferisci di nuovo le migrazioni mirate finché non è risolto.

## 4. Differenze chiave SF4 → 5.4 da tenere a mente

- **Cache prod**: ora funziona (tutti i componenti Symfony riallineati a 5.4; in
  precedenza `symfony/cache` 6.x rompeva il warmup prod — vedi commit di re-pin).
- **Scheduler**: migrato da `jmose` a **`dukecity/command-scheduler-bundle`**.
  `assets:install` ripubblica `public/bundles/dukecitycommandscheduler/` (i template
  scheduler vi puntano). Senza questo, JS dello scheduler 404.
- **Editor HTML** (pagina admin *Template email*, campo corpo HTML): usa **TinyMCE
  community self-hosted** (GPL, nessuna API key/nag, funziona offline), versionato
  nel repo in `public/static/tinymce/`. Nessun comando di install: arriva col
  tarball. CKEditor 4 è stato dismesso perché i suoi release open-source non sono
  più scaricabili (il repo upstream serve solo build LTS commerciali con errore di
  licenza).
- **Security**: `password_hashers`, firewall `lazy`, `PUBLIC_ACCESS`. Nessuna azione
  di deploy, ma resta attivo il vecchio sistema authenticator (deprecazioni, non errori).
- **Front controller**: `public/index.php`/`bin/console` usano `ErrorHandler\Debug` e
  `loadEnv()` (carica `.env.local`).
- **FOSRest / rotte API**: le ex `@Rest\{Get,Post,...}` sono ora `@Route(methods=...)`;
  verifica un paio di endpoint API dopo il deploy.
- **DBAL 3**: `Statement::fetchAll()` → `executeQuery()->fetchAllAssociative()` (già nel codice).

## 5. Verifiche post-deploy

- `docker-compose exec php php bin/console about` → Symfony 5.4.x, env corretto, debug off.
- Home → 302 a `/<locale>/login` → pagina di login (200).
- Login + dashboard (`/secwelcome`): i numeri/contatori sono coerenti.
- Gallery: anteprime e lightbox ok.
- Scheduler (area admin): pagina e azioni ok (asset dukecity).
- Log: `docker-compose logs -f php` senza errori 500.

## 6. Rollback

- **Codice**: il deploy è per-release (cartella dedicata). Per tornare indietro,
  ripristina la dir del release precedente (o ri-deploya il tarball precedente) e
  `docker-compose up -d`.
- **DB**: ripristina il **backup** fatto al punto 7
  (`mysql ... < backup_db_<ts>.sql`). Per questo lo schema si applica **senza `--complete`**:
  riduce al minimo le modifiche e rende il rollback dello schema raramente necessario.
- Tieni il release precedente finché la pre-prod non è validata.

## 7. Note di sicurezza

- I segreti vivono **solo** in `.env.local` sul server (mai nel tarball né in git).
- `APP_DEBUG=0` in pre-prod (con debug on, profiler ed errori dettagliati sono esposti).
- `--complete` su `schema:update` è **disabilitato di proposito**: su dati reali può
  fare DROP. Se servono rimozioni di colonne/tabelle, falle a mano, riviste, dopo backup.
