diff --git a/README.md b/README.md index a8064a0..3abd0a8 100644 --- a/README.md +++ b/README.md @@ -285,6 +285,14 @@ Stato "di fabbrica" ripristinabile con un click. Pensato per preparare preset st Il preset è un file fisso: **`factory/preset.zip`** alla radice del progetto (fuori da `data/`, quindi non viene toccato dai reset; fuori da `public/`, quindi non scaricabile via web). +### Bootstrap automatico all'avvio + +All'avvio del processo Next, un hook ([instrumentation.ts](instrumentation.ts) → [lib/bootstrap.ts](lib/bootstrap.ts)) controlla `data/cards.txt`: +- Se è **assente o `[]` (zero card)** **E** esiste `factory/preset.zip`, il preset viene estratto automaticamente sopra a `data/` (stessa logica del Factory Reset; la eventuale `data/` precedente viene rinominata `data.bak-/`). +- In tutti gli altri casi (cards.txt esiste con contenuto, o il preset manca) il bootstrap non fa nulla. + +Questo permette di committare nel repo un `factory/preset.zip` e avere ogni macchina appena clonata/distribuita che parte direttamente nello stato standard, senza interventi manuali in admin. Le righe `[bootstrap]` nei log del server segnalano se ha agito. + ### Dall'interfaccia - **Factory Reset** — sempre visibile. Ripristina tutto al preset (disabilitato se il preset non esiste). La `data/` precedente resta come `data.bak-/`. - **Save as Factory Preset (dev)** — visibile solo con `FACTORY_PRESET_SAVE_ENABLED = true`. Congela lo stato corrente in `factory/preset.zip`. diff --git a/factory/preset.zip b/factory/preset.zip new file mode 100644 index 0000000..c134371 Binary files /dev/null and b/factory/preset.zip differ diff --git a/instrumentation.ts b/instrumentation.ts new file mode 100644 index 0000000..4009c1d --- /dev/null +++ b/instrumentation.ts @@ -0,0 +1,15 @@ +// Hook eseguito una sola volta all'avvio del server Next. +// L'unico compito attuale è il bootstrap del factory preset quando data/ è vuoto. + +export async function register() { + // Esegui solo nel runtime Node (l'instrumentation può girare anche su Edge, + // dove fs/spawn non esistono). + if (process.env.NEXT_RUNTIME !== 'nodejs') return; + + const { bootstrapFromFactoryPresetIfEmpty } = await import('@/lib/bootstrap'); + try { + await bootstrapFromFactoryPresetIfEmpty(); + } catch (err) { + console.warn('[instrumentation] Bootstrap error:', err); + } +} diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts new file mode 100644 index 0000000..5fe1d9d --- /dev/null +++ b/lib/bootstrap.ts @@ -0,0 +1,44 @@ +// Bootstrap "stato di fabbrica" automatico all'avvio. +// +// Se all'avvio del server NON ci sono card in data/cards.txt (file mancante o `[]`) +// E esiste factory/preset.zip, il preset viene estratto sopra a data/. +// È quello che permette a una macchina appena clonata/distribuita di partire con +// lo stato standard senza interventi manuali. + +import { readFile, access } from 'node:fs/promises'; +import path from 'node:path'; +import { restoreFromZipFile } from './restore-zip'; + +const PROJECT_ROOT = process.cwd(); +const CARDS_FILE = path.join(PROJECT_ROOT, 'data', 'cards.txt'); +const PRESET_PATH = path.join(PROJECT_ROOT, 'factory', 'preset.zip'); + +async function hasCards(): Promise { + try { + const raw = await readFile(CARDS_FILE, 'utf-8'); + const parsed = JSON.parse(raw || '[]'); + return Array.isArray(parsed) && parsed.length > 0; + } catch { + return false; + } +} + +export async function bootstrapFromFactoryPresetIfEmpty(): Promise { + // Niente preset di fabbrica → niente da fare, sezione opzionale. + try { + await access(PRESET_PATH); + } catch { + return; + } + + // Stato non vuoto → l'admin ha già contenuti, non sovrascriviamo. + if (await hasCards()) return; + + console.log('[bootstrap] No cards found; restoring factory preset from factory/preset.zip'); + const result = await restoreFromZipFile(PRESET_PATH); + if (result.ok) { + console.log(`[bootstrap] Restored ${result.cards} cards, ${result.portals} portals from factory preset`); + } else { + console.warn(`[bootstrap] Factory restore failed: ${result.error}${result.detail ? ` (${result.detail})` : ''}`); + } +}