Per cambiare un qualunque limite: modifica il numero in `lib/config.ts` e ricostruisci. Il valore è condiviso tra interfaccia (contatore / `maxLength`), validazione server e check di upload — un solo punto di verità.
Per cambiare un qualunque limite: modifica il numero in `lib/config.ts` e ricostruisci. Il valore è condiviso tra interfaccia (contatore / `maxLength`), validazione server e check di upload — un solo punto di verità.
@@ -275,7 +276,7 @@ Copiare via questa cartella = backup completo. Sostituirla = ripristino completo
---
---
## Stato zero, rilasci e aggiornamenti del codice
## Stato zero e compatibilità tra versioni
### Cosa costituisce lo "stato zero"
### Cosa costituisce lo "stato zero"
Lo stato applicativo (i dati) è **solo** il contenuto di `data/`: `cards.txt`, `portals.txt`, `uploads/`, `fonts/`. Tutto il resto — codice sorgente, `node_modules`, e gli asset di default in `public/` (es. `hero-bg.jpg`, `logo.png`) — **non** fa parte dello stato dati: arriva con il rilascio del software. "Azzerare" il CPC significa quindi sostituire `data/` con uno stato noto.
Lo stato applicativo (i dati) è **solo** il contenuto di `data/`: `cards.txt`, `portals.txt`, `uploads/`, `fonts/`. Tutto il resto — codice sorgente, `node_modules`, e gli asset di default in `public/` (es. `hero-bg.jpg`, `logo.png`) — **non** fa parte dello stato dati: arriva con il rilascio del software. "Azzerare" il CPC significa quindi sostituire `data/` con uno stato noto.
@@ -285,8 +286,8 @@ Lo stato applicativo (i dati) è **solo** il contenuto di `data/`: `cards.txt`,
- **Aggiornamento del codice**: si sostituisce tutto **tranne** `data/`. I contenuti restano al loro posto.
- **Aggiornamento del codice**: si sostituisce tutto **tranne** `data/`. I contenuti restano al loro posto.
- Da CLI un reset conservativo è: `mv data data.old && <estrai-l-archivio-dei-contenuti>`. È l'equivalente del `tar zxf` citato dal QA — vedi nota su ZIP vs tar nella sezione [Backup](#backup-e-ripristino).
- Da CLI un reset conservativo è: `mv data data.old && <estrai-l-archivio-dei-contenuti>`. È l'equivalente del `tar zxf` citato dal QA — vedi nota su ZIP vs tar nella sezione [Backup](#backup-e-ripristino).
### Compatibilità dei contenuti dopo un update del codice
I contenuti salvati da versioni precedenti continuano a funzionare: in lettura vengono adattati al volo (es. il vecchio `extraImages: string[]` viene convertito in `extraMedia: MediaItem[]` in [`lib/db.ts`](lib/db.ts)). **Convenzione per future modifiche di schema:** aggiungere il branch di migrazione nella lettura (`getCards`/`getPortals`), senza mai rcompattare i dati legacy in scrittura senza un fallback in lettura — così un archivio di contenuti vecchio resta sempre ripristinabile.
### Compatibilità dei contenuti tra versioni
I contenuti salvati da versioni precedenti continuano a funzionare: in lettura vengono adattati al volo (es. il vecchio `extraImages: string[]` viene convertito in `extraMedia: MediaItem[]` in [`lib/db.ts`](lib/db.ts)). **Convenzione per future modifiche di schema:** aggiungere il branch di migrazione nella lettura (`getCards`/`getPortals`), senza mai ricompattare i dati legacy in scrittura senza un fallback in lettura — così un archivio di contenuti vecchio resta sempre ripristinabile. La procedura operativa di aggiornamento è descritta in [Aggiornamento](#aggiornamento).
### Far accompagnare lo stato zero ai rilasci
### Far accompagnare lo stato zero ai rilasci
Per avere uno stato di partenza noto su ogni macchina nuova, includere nel pacchetto di rilascio un `factory/preset.zip` curato (vedi [Factory Preset](#factory-preset-developer)). Su una macchina nuova, il ripristino di quel preset porta allo stato zero ufficiale.
Per avere uno stato di partenza noto su ogni macchina nuova, includere nel pacchetto di rilascio un `factory/preset.zip` curato (vedi [Factory Preset](#factory-preset-developer)). Su una macchina nuova, il ripristino di quel preset porta allo stato zero ufficiale.
@@ -298,8 +299,8 @@ Per avere uno stato di partenza noto su ogni macchina nuova, includere nel pacch
Disponibile dall'admin in **Settings → Backup & Restore**.
Disponibile dall'admin in **Settings → Backup & Restore**.
### Dall'interfaccia
### Dall'interfaccia
- **⬇ Save backup (ZIP)** — scarica `interceptor-backup-YYYYMMDD-hhmmss.zip` con card, configurazione, media e font (esclude i file temporanei).
- **⤴ Restore from ZIP** — carica uno zip; dopo conferma sovrascrive lo stato attuale e ricarica la pagina. La cartella `data/` precedente viene conservata come `data.bak-<timestamp>/` come rete di sicurezza.
- **Save backup (ZIP)** — scarica `interceptor-backup-YYYYMMDD-hhmmss.zip` con card, configurazione, media e font (esclude i file temporanei).
- **Restore from ZIP** — carica uno zip; dopo conferma sovrascrive lo stato attuale e ricarica la pagina. La cartella `data/` precedente viene conservata come `data.bak-<timestamp>/` come rete di sicurezza.
### Da riga di comando (Linux)
### Da riga di comando (Linux)
@@ -324,7 +325,7 @@ Lo zip così prodotto è caricabile direttamente dal pulsante "Restore from ZIP"
## Factory Preset (developer)
## Factory Preset (developer)
Stato "di fabbrica" ripristinabile con un click. Pensato per preparare preset standard (es. banner + icona + una card di redirect) da distribuire a più macchine MajorNet.
Stato "di fabbrica" ripristinabile con un click. Pensato per preparare preset standard (es. banner + icona + una card di redirect) da distribuire a più MajorNet.
**La sezione è sempre visibile in admin.** Il bottone "Factory Reset" e lo stato del preset corrente sono mostrati sempre (è un'operazione lecita per qualunque amministratore). Il bottone "Save as Factory Preset (dev)" — che riscrive il preset di fabbrica — è invece **gated** da `FACTORY_PRESET_SAVE_ENABLED = true` in `lib/config.ts` (default `false`): tienilo attivo solo sulla macchina di sviluppo dove prepari/aggiorni il preset. Gli endpoint API restano comunque attivi a prescindere dal flag.
**La sezione è sempre visibile in admin.** Il bottone "Factory Reset" e lo stato del preset corrente sono mostrati sempre (è un'operazione lecita per qualunque amministratore). Il bottone "Save as Factory Preset (dev)" — che riscrive il preset di fabbrica — è invece **gated** da `FACTORY_PRESET_SAVE_ENABLED = true` in `lib/config.ts` (default `false`): tienilo attivo solo sulla macchina di sviluppo dove prepari/aggiorni il preset. Gli endpoint API restano comunque attivi a prescindere dal flag.
@@ -587,18 +588,18 @@ Apri il file vhost (quello con i segnaposto `@@@REDIRECT@@@`, `@@@DIRECTORY@@@`,
L'amministratore è identificato dal **`preferred_username` Keycloak**, controllato nei `<Location>` Apache. Per gestire più amministratori:
L'amministratore è identificato dal **`preferred_username` Keycloak**, controllato nei `<Location>` Apache. Per gestire più amministratori:
1. **In Keycloak** (Admin Console → Users): crea/verifica gli utenti con gli username che vuoi promuovere ad admin (es. `admin`, `alice`, `bob`). Imposta le credenziali normalmente.
1. **In Keycloak** (Admin Console → Users): crea/verifica gli utenti con gli username che vuoi promuovere ad admin (es. `admin`, `pollutri`, `russi`). Imposta le credenziali normalmente.
2. **In Apache** (in tutti i blocchi `<Location "/cards/admin">`, `<Location "/cards/api/...">`): usa un blocco **`<RequireAny>`** con **una riga `Require claim` per ogni username**:
2. **In Apache** (in tutti i blocchi `<Location "/cards/admin">`, `<Location "/cards/api/...">`): usa un blocco **`<RequireAny>`** con **una riga `Require claim` per ogni username**:
> ⚠ **Non usare la forma compatta `Require claim preferred_username:admin alice bob`** sulla stessa riga: su alcune versioni di `mod_auth_openidc` (incluse quelle in uso sui server MajorNet) il parser dei valori space-separated è buggy e finisce per accettare solo il primo username. Il blocco `<RequireAny>` con una riga `Require claim` per username delega la OR-logic ad Apache (`mod_authz_core`) e funziona affidabilmente. Stesso schema anche dentro `<LimitExcept GET HEAD>`.
> ⚠ **Non usare la forma compatta `Require claim preferred_username:admin pollutri russi`** sulla stessa riga: su alcune versioni di `mod_auth_openidc` (incluse quelle in uso sui server MajorNet) il parser dei valori space-separated è buggy e finisce per accettare solo il primo username. Il blocco `<RequireAny>` con una riga `Require claim` per username delega la OR-logic ad Apache (`mod_authz_core`) e funziona affidabilmente. Stesso schema anche dentro `<LimitExcept GET HEAD>`.
**Rimuovere un admin**: togli la riga `Require claim preferred_username:<username>` corrispondente da `<RequireAny>` (e ricarica Apache), oppure disabilita l'utente direttamente in Keycloak.
**Rimuovere un admin**: togli la riga `Require claim preferred_username:<username>` corrispondente da `<RequireAny>` (e ricarica Apache), oppure disabilita l'utente direttamente in Keycloak.