KINDLE HOME

Infrastruttura Self-Hosted: Il Design dei Moduli Deep

Come organizzare un'infrastruttura personale come se fosse un sistema software ben progettato, applicando il principio dei deep modules di Ousterhout a server, monitoring, backup e automazione.


Prologo: Perché Abbandonare il Managed

C'era una volta Cloudways. Un pannello di controllo pulito, deploy con un click, SSL che si rinnova da solo, staging con un bottone. Pagavi cento euro al mese e non ci pensavi. Poi un giorno ti ritrovi con un agente AI nel terminale che sa fare SSH, gestire DNS, configurare nginx, scrivere cron job e monitorare processi. E ti chiedi: perché pago qualcuno per fare cose che il mio assistente fa meglio e gratis?

La risposta è che non dovresti. Ma la migrazione da managed a self-hosted non è un interruttore che si accende: è un processo che dura mesi, durante il quale costruisci pezzo per pezzo un'infrastruttura che prima non esisteva. E se non stai attento, ti ritrovi con un Frankenstein di server, servizi, cron job e script sparsi che nessuno capisce più — nemmeno tu.

Questo documento è il risultato di una sessione di design il cui scopo era mettere ordine nel caos. Non aggiungere roba nuova: capire quella che c'è, darle una struttura, e decidere cosa manca, cosa è di troppo, e cosa sta nel posto sbagliato.

* * *

Capitolo 1: Il Principio dei Moduli Deep

John Ousterhout, nel suo libro "A Philosophy of Software Design", introduce il concetto di deep module: un componente che ha un'interfaccia stretta (pochi parametri, facile da usare) e una grande complessità nascosta dentro. Il contrario di uno shallow module, che ha un'interfaccia complicata quasi quanto la sua implementazione — tipo quei wrapper che richiedono dieci parametri per fare una cosa sola.

L'intuizione è che lo stesso principio si applica all'infrastruttura. Se qualcuno ti chiede "come fai i backup?", la risposta dovrebbe essere "Restor" — non "dipende, per i WordPress uso rsync verso b1, per i Laravel c'è un cron che..., per i database faccio un dump che finisce su R2 tramite...". Se la risposta è la seconda, il tuo modulo è shallow: l'interfaccia (spiegarti come funziona) è complicata quasi quanto l'implementazione.

L'altro concetto correlato è il grey box: non devi sapere come funziona dentro per usarlo. Se per mandare un backup a qualcuno devi sapere che usa Restic 0.18 con un wrapper Python che chiama R2 via S3 API, il modulo non è abbastanza grey. Devi solo dire "backuppa questa app" e il modulo fa il resto.

* * *

Capitolo 2: I Cinque Moduli

Dopo quindici domande di grill — quel processo in cui l'AI ti chiede cose finché non capisce davvero cosa hai in testa — sono emersi cinque moduli deep. Non sei, non quattro. Cinque. E la cosa bella è che la struttura non è stata imposta dall'alto: è emersa dalla conversazione, eliminando le sovrapposizioni e separando i moduli veri dalla colla.

Compute: Dove Girano le Cose

Compute è il modulo più fondamentale, quello da cui dipendono tutti gli altri. Ma attenzione: Compute non è "i server". Compute è "i ruoli". La distinzione è cruciale perché oggi un ruolo corrisponde a un server fisico, ma domani potrebbe essere un cluster di tre macchine senza che nulla cambi nell'interfaccia.

I ruoli sono tre. Sudo è il cervello — il control plane dove girano il brain, le API, l'automazione. Oggi è Efesto, un Hetzner CPX62 con 32GB di RAM. Watch è il monitoring indipendente — e la parola chiave è "indipendente". Se Sudo muore, Watch deve continuare a vedere che è morto. Per questo è su un server separato, Heimdall. App è tutto il resto: server che ospitano applicativi di qualsiasi tipo.

E qui arriva la parte interessante: le app hanno due proprietà ortogonali. La categoria dice che tipo di applicativo è — brain, client, web, vault. L'ambiente dice se è staging o production. E l'ambiente è una proprietà dell'app, non del server. Un singolo server App può ospitare tre siti in production e uno staging per un test. Questo evita la tentazione di avere "server di staging" e "server di produzione" come ruoli separati, che nella pratica non funziona: non accendi un server da sei euro al mese solo per testare un plugin WordPress.

Quello che funziona è avere uno staging pool: un server App economico dove atterrano per default tutti i cloni temporanei. Non è un ruolo, è una convenzione. Il server è sempre App, ma per abitudine ci metti solo staging.

Observability: Sapere Se Funziona Tutto

Observability risponde a una sola domanda: "funziona tutto?". Per farlo usa quattro layer, e ognuno copre un aspetto diverso.

UptimeRobot è il watchdog esterno. Il suo unico scopo è monitorare che il monitoring stesso sia vivo. Se Heimdall muore, UptimeRobot è l'unico che se ne accorge perché gira fuori dalla tua infrastruttura. È il guardian del guardian.

Uptime Kuma è il monitoring HTTP primario, con quasi cento monitor organizzati in tier (Gold, Silver, Carbon). Ma non basta che un sito risponda 200 OK: serve il keyword monitoring, cioè verificare che la pagina contenga effettivamente il contenuto atteso. Un WordPress che risponde 200 ma mostra "Error establishing database connection" non è un sito funzionante.

GlitchTip è il terzo layer, dedicato agli errori applicativi. Mentre Kuma guarda dall'esterno ("il sito risponde?"), GlitchTip guarda dall'interno ("l'app ha crashato?"). Compatibile con Sentry, raccoglie eccezioni da tutti i progetti Laravel e WordPress.

Beszel chiude il cerchio con le metriche server: CPU, RAM, disco. Non è monitoring applicativo, è monitoring infrastrutturale. Se un server è al 95% di disco, vuoi saperlo prima che i servizi inizino a fallire.

Tutto converge su one.giobi.com — il cockpit. Ma one non è un modulo: è un'interfaccia. La colla che mostra lo stato di Observability, lo stato di Persistence, e permette di triggerare Automation. Non contiene logica propria.

L'evoluzione più interessante è il canary: un sito, canary.giobi.com, il cui unico scopo nella vita è morire ogni mattina alle otto. Se muore e non ricevi l'alert, il monitoring è rotto. È un dead man's switch al contrario: non "se non mi faccio vivo sono morto", ma "se non vedi che sono morto, i tuoi occhi sono rotti".

Persistence: Backup, Restore, e Duplicazione

Persistence è il modulo che ha assorbito più responsabilità durante il design, perché il deploy ci è finito dentro. L'intuizione è che duplicare un applicativo (per farne uno staging) e ripristinare un backup sono la stessa operazione vista da angolazioni diverse. In entrambi i casi prendi un'app, la copi, e la metti da qualche altra parte. Restor fa già il settanta percento di questo lavoro.

L'interfaccia del modulo è brutalmente semplice: "backuppa questa app", "ripristina questa app", "duplica questa app su questo dominio". Dentro c'è un detector che capisce che tipo di app è (WordPress, Laravel, statico), un engine basato su Restic per i backup incrementali, storage diversificato su R2 e OVH Cloud, e la logica di restore/clone che gestisce database, file, configurazione, e dominio.

Vaultwarden vive qui come app/vault — è un applicativo che gira su un server App, il cui scopo è custodire segreti. Non serve un ruolo server dedicato per un container Docker.

La vista sullo stato dei backup passa da one.giobi.com/apps: un semaforo verde/rosso che ti dice "tutti i backup sono a posto" o "questo è rotto" senza che tu debba scavare nei log.

Delivery: Mettere Roba Online

Delivery è il modulo più semplice, e questa è una virtù. Mettere online qualcosa significa tre cose: un vhost nginx sul server, un record DNS su Cloudflare con il proxy arancione acceso, e basta. HTTPS c'è perché Cloudflare lo fornisce istantaneamente.

Questa è stata una delle scoperte più importanti del grill: la parola "SSL" in questo contesto non significa certificati server-side, certbot, propagazione, Let's Encrypt. Significa Cloudflare proxy. Il browser vede il lucchetto, l'utente è contento, il server dietro può anche parlare HTTP puro. Per i siti clienti in produzione si può evolvere verso SSL strict (certificato anche sul server), ma non c'è fretta e non è il default.

La regola non negoziabile è: HTTPS sempre, dominio sempre. Mai IP:porta esposti, nemmeno per lo staging, nemmeno per un test di cinque minuti. Per questo esistono i domini utility: staging.re, app.ga, i mille sottodomini di giobi.com. Sono lì apposta per dare un nome a qualsiasi cosa in trenta secondi.

Automation: Gli Attori Nascosti

Automation è tutto ciò che succede senza intervento umano. Il motore unico è n8n: workflow visuali, dashboard dove vedi cosa gira, log di esecuzione. La regola è ferrea: se è un'automazione, la vedi su n8n. Se non la vedi su n8n, non esiste. Il crontab di sistema deve essere vuoto — o al massimo contenere il Laravel scheduler e logrotate, cose che per natura devono stare a livello di sistema operativo.

Playbook vive accanto a n8n come il cervello decisionale: le rules of engagement che decidono "quando succede X, fai Y". Ma le esecuzioni passano per n8n o per API, non per script Python sparsi in /tmp. La separazione è netta: Playbook decide, n8n esegue.

* * *

Capitolo 3: Le Interfacce — La Colla Che Tiene Insieme

Tra i moduli deep ci sono strati sottili di colla. Non contengono logica propria, collegano e espongono.

one.giobi.com è il cockpit. Mostra lo stato di Observability (Kuma, GlitchTip), lo stato di Persistence (Restor, backup), e permette di triggerare Automation (playbook, azioni manuali). È il posto dove guardi e da cui agisci, ma non è lui a fare le cose.

api.giobi.com è il punto di accesso programmatico. Le skill dell'agente, i workflow n8n, le integrazioni esterne — tutti parlano con i moduli attraverso questa API. Restor ha i suoi endpoint, il database ha i suoi, il canary ha i suoi.

Tailscale è la rete overlay che collega tutti i server in una mesh privata. Non è un modulo e non è colla nel senso applicativo: è il tessuto connettivo a livello di rete. Ogni server ha un IP Tailscale, ogni servizio interno comunica attraverso questa rete.

* * *

Capitolo 4: I Vincoli Non Negoziabili

Ogni design ha delle linee rosse che non si attraversano. Queste sono emerse durante il grill come principi su cui non si scende a compromessi.

Watch deve essere indipendente da Sudo. Se Efesto prende fuoco, Heimdall deve continuare a funzionare e a mandare alert. Questa è la ragione per cui il monitoring è su un server separato e non sarà mai consolidato. Il costo di sei euro al mese per un server dedicato al monitoring è il prezzo di dormire tranquilli.

HTTPS e dominio sempre, ovunque, per qualsiasi cosa. Lo staging di un test che dura cinque minuti ha un dominio e HTTPS. La ragione non è la sicurezza (anche se aiuta): è che accedere via IP:porta è una rottura di coglioni e crea problemi con i cookie, i redirect, i certificati misti. Meglio spendere trenta secondi su Cloudflare che mezz'ora a debuggare perché il browser si lamenta.

n8n è il motore unico di automazione. Non perché sia perfetto, ma perché avere automazioni sparse tra crontab, script Python, e workflow n8n è il modo più veloce per dimenticarsi cosa gira e dove. Se domani devi capire "cosa succede automaticamente alle due di notte?", apri n8n e lo vedi. Se la risposta è "devi anche guardare il crontab di Efesto, e forse c'è uno script in /home/giobi/scripts/...", hai perso.

L'ambiente è una proprietà dell'app, non del server. Questo sembra un dettaglio tecnico ma è una decisione architetturale fondamentale: significa che non hai bisogno di "server staging" e "server production" come categorie separate. Un server è un server, le app che ci girano sopra hanno il loro ambiente. Questo semplifica enormemente la gestione e permette flessibilità.

* * *

Epilogo: Cosa Manca

Un design non è completo finché non sai cosa non hai ancora deciso. Le decisioni aperte sono dodici, e vanno dalla posizione esatta di Vaultwarden (che nessuno sa più con certezza su quale server giri) al censimento dei domini utility, dalla migrazione dei crontab residui a n8n fino al canary automatico quotidiano.

La più importante è probabilmente la creazione di un domain.md per il brain root: un file che cristallizzi queste regole operative in modo che ogni sessione futura dell'agente le conosca senza doverle rinegoziare. Perché il design è bello, ma se la prossima volta l'AI ti propone certbot per lo staging, il design non è servito a niente.

Il moonshot resta il page change detection: non solo "il sito risponde", ma "il sito è visivamente uguale a ieri". Se un aggiornamento plugin rompe il layout della homepage, oggi non lo sai finché qualcuno non se ne accorge. Ma questo è futuro, e il futuro può aspettare. Intanto, i cinque moduli reggono.

* * *

Capitolo 5: L'Avvocato del Diavolo

Un design che non viene attaccato non è un design: è un wishful thinking. Così ci siamo tolti il cappello del progettista e abbiamo messo quello del rompicoglioni. Sei punti deboli, analizzati uno per uno, per capire se la struttura regge o se ci stiamo raccontando storie.

Il Peccato Originale: Efesto Fa Troppo

Sudo è il cervello, abbiamo detto. Ma un cervello che è anche il cuore, i polmoni e il fegato ha un problema ovvio: se smette di battere, muore tutto. Efesto oggi ospita il brain, le API, n8n, WAHA, Restor, Playbook, i cron residui, Vaultwarden. Watch (Heimdall) vede che Efesto è morto — fantastico — ma non può fare niente per riportarlo in vita. E nel frattempo l'automazione è ferma, i backup non partono, le comunicazioni sono mute, le API non rispondono, e perfino il password manager è irraggiungibile.

Per un one-man-shop il costo di un failover attivo non si giustifica. Ma quello che manca è almeno un documento: "se Efesto muore, ecco come ricostruisco tutto da zero in quattro ore". Quel documento non esiste, e finché non esiste il disaster recovery è un concetto teorico, non una procedura.

Restor Come Modulo Unico: Ambizione e Rischio

Abbiamo deciso che Restor fa backup, restore, e deploy (duplicazione). Il settanta percento c'è, ha detto Giobi. Ma quel trenta percento mancante è la parte più insidiosa: il provisioning. Quando duplichi un'app su un nuovo server devi creare il vhost nginx, creare il database (MySQL? SQLite? Niente?), configurare i permessi, assegnare il dominio. Ogni server App ha la sua configurazione specifica.

Il rischio è che Restor diventi un modulo shallow travestito da deep: l'interfaccia resta stretta ("duplica questa app"), ma dentro cresce un albero di if/else per ogni combinazione di stack e server. Il detector — il componente che capisce automaticamente che tipo di app è — è la risposta giusta al problema, ma serve anche un contratto esplicito per i server target: "per essere destinazione di un deploy, un server App deve esporre queste capability".

n8n: Il Motore Senza Cintura di Sicurezza

Se n8n è il motore unico dell'automazione, e n8n è un container Docker su Efesto, cosa succede se il container crasha alle due di notte? Il backup notturno non parte. Il canary delle otto non si attiva. I workflow di integrazione saltano. E nessuno se ne accorge perché — eccolo il punto — chi monitora il motore dell'automazione?

La risposta è ovvia una volta formulata: n8n deve essere un monitor Gold su Kuma. Intervallo di sessanta secondi, alert su Discord e Telegram. Se n8n non risponde, lo sai subito. Sembra banale, ma è il tipo di buco che si scopre solo quando è troppo tardi.

Cloudflare Come Collo di Bottiglia

Tutto il delivery passa da Cloudflare. Ogni dominio, ogni staging, ogni progetto temporaneo. Se Cloudflare ha un'outage globale — ed è successo, più di una volta — tutti i siti diventano irraggiungibili per il mondo esterno. Heimdall intanto vede tutto verde perché raggiunge i server via Tailscale, bypassando Cloudflare.

Non ci sono alternative realistiche: per il volume e il budget in gioco, Cloudflare è insostituibile. Ma va nella sezione dei rischi accettati, non ignorato. Un'outage Cloudflare è come un terremoto: non puoi prevenirlo, ma puoi sapere che esiste e avere un piano (anche se il piano è "aspetta che torni su").

Il Termine Mancante: Provisioning

Il vocabolario condiviso definisce "deploy" come duplicazione di applicativi. Ma manca un concetto adiacente: il provisioning. Preparare un server App da zero — installare nginx, PHP, fail2ban, Beszel agent, Tailscale, configurare il firewall. Oggi si fa a mano, ricostruendo a memoria ogni volta. Se domani arriva un nuovo cliente brain e devi spinnare un server, quanto tempo perdi a ricordarti tutti i passaggi?

Un playbook Ansible, uno script bash, o almeno una checklist — qualcosa che trasformi il provisioning da arte orale a procedura ripetibile. Non è urgente, ma è il tipo di debito tecnico che si paga tutto insieme nel momento peggiore.

Vaultwarden: Il Mistero Risolto (Male)

La domanda "su che server gira Vaultwarden?" durante il design aveva ricevuto un'alzata di spalle. Un DNS lookup ha rivelato la risposta: vault.giobi.com punta all'IP Tailscale di Efesto. Il password manager di tutta l'infrastruttura gira sullo stesso server che fa tutto il resto.

Questo significa che lo scenario "Efesto muore" include anche "perdi l'accesso a tutte le password". La replica su htz1 (se ancora attiva) potrebbe salvare la situazione, ma va verificata. Nel frattempo, è un altro motivo per cui quel documento di disaster recovery non è opzionale.

* * *

Capitolo 6: Il Verdetto

Il design regge. Non è una frase di cortesia: i cinque moduli sono davvero deep, le interfacce sono strette, la separazione è pulita. La tassonomia dei ruoli server (sudo, watch, app) è elegante nella sua semplicità, e la decisione di rendere l'ambiente una proprietà dell'app piuttosto che del server risolve un problema che avrebbe creato complessità crescente.

Le debolezze trovate dal Devil sono tutte operazionali, non architetturali. Manca documentazione di recovery, manca provisioning standardizzato, manca monitoring sul motore dell'automazione. Sono buchi nel "come" fai le cose, non nel "cosa" sono le cose. La differenza è importante: un'architettura sbagliata si rifà da zero, le procedure si scrivono.

I tre fix prioritari, in ordine: primo, un documento di disaster recovery per Efesto (perché senza quello tutto il resto è costruito sulla sabbia). Secondo, n8n monitorato come servizio Gold su Kuma (perché un'automazione che nessuno controlla non è automazione, è speranza). Terzo, verificare la replica Vaultwarden (perché perdere le password è il tipo di catastrofe da cui non ti riprendi facilmente).

Il resto — provisioning, censimento domini, migrazione crontab, canary automatico, page change detection — sono evoluzioni. Importanti, ma non urgenti. Il sistema funziona oggi con questi buchi. Ma ora almeno sappiamo dove sono, e sappiamo che sono buchi, non feature.

Un design non si giudica dalla sua eleganza ma dalla sua onestà. Un sistema che sa dove è fragile è più robusto di uno che crede di essere perfetto.
- FINE -
1