Category: Self-hosting

  • La resurrezione di monitoring: come ho salvato i dati da una microSD corrotta

    La resurrezione di monitoring: come ho salvato i dati da una microSD corrotta

    Una mattina di qualche settimana fa, mentre prendevo il caffè, mi accorgo che la dashboard Grafana non risponde. Stessa cosa per la web UI di Observium e per il logger centrale. Tre servizi giù insieme: non è coincidenza, è un host giù. Quell’host, nel mio homelab, è un Raspberry Pi 5 8GB in case dedicato che chiamo “monitoring” (qui lo chiamo rpi-monitoring) e che fa girare diversi servizi:

    Snipe-IT per asset management

    Observium Community come NMS che polla SNMP gli altri host

    InfluxDB v2 come time series database delle metriche

    rsyslog centrale che riceve log dalla LAN

    MariaDB condiviso fra Snipe-IT e Observium

    Tutto girava su una microSD 64 GB classe A1 generica. Dopo circa un anno di servizio la scheda si è bloccata: kernel pieno di mmc0: Card stuck being busy e poi EXT4-fs error a cascata. Ecco come ho recuperato i dati e ricostruito il servizio.

    Perché la microSD è morta

    Una microSD generica entry-level è progettata per fotografia e video: scritture sequenziali grosse, poche cancellazioni. Quello che le ho fatto fare era l’esatto contrario: SQL relazionale di Snipe-IT, polling SNMP Observium ogni cinque minuti su dieci host con scrittura MariaDB + RRD, ingestion InfluxDB v2 di metriche ogni dieci secondi, rsyslog centrale, journald del Pi, log rotation di Apache e nginx.

    Il TBW sopportabile da una microSD entry-level è qualche TB. Con quel carico ci si arriva in 8-12 mesi. Errore mio: non aver migrato a SSD via USB 3.0 prima.

    Step 1: bitwise dump con ddrescue

    La prima cosa che si fa con un dispositivo morente, prima di provare a recuperare file, è un dump bitwise dell’intera scheda. ddrescue è lo strumento giusto: legge sequenzialmente, prova a recuperare i settori illeggibili con strategie successive e mantiene un logfile delle aree da riprovare. Su un Raspberry Pi di servizio con uno slot USB ho infilato la microSD malata tramite un adattatore e ho lanciato:

    
    sudo apt install gddrescue
    sudo ddrescue -d -r 3 /dev/sda /mnt/ssd/monitoring.img /mnt/ssd/monitoring.log
    

    -d salta la cache del kernel e legge in direct I/O, -r 3 riprova ogni settore tre volte. Il dump è andato avanti per quasi cinque ore. Risultato: 62,8 GB su 64 GB recuperati, 1,2 GB irrecuperabili distribuiti soprattutto nell’area dove stavano i datafile MariaDB di un mese. Già un buon punto di partenza.

    Step 2: montare l’immagine read-only

    Con l’immagine in mano, l’ho associata a un loop device read-only per non rischiare di scriverci sopra:

    
    sudo losetup -r -fP /mnt/ssd/monitoring.img
    sudo losetup -a
    

    Il losetup -a mostra il device /dev/loop0 con partizioni loop0p1 (boot) e loop0p2 (rootfs). Ho montato la rootfs:

    
    sudo mkdir -p /mnt/recover
    sudo mount -o ro /dev/loop0p2 /mnt/recover
    

    Filesystem montato pulito, con qualche file segnato come corrotto in dmesg ma navigabile.

    Step 3: estrazione dati per servizio

    Ho copiato selettivamente i datadir dei servizi importanti su un SSD USB di lavoro:

    
    sudo rsync -aH --info=progress2 /mnt/recover/var/lib/mysql /mnt/ssd/recover/
    sudo rsync -aH --info=progress2 /mnt/recover/var/lib/influxdb /mnt/ssd/recover/
    sudo rsync -aH --info=progress2 /mnt/recover/var/lib/observium /mnt/ssd/recover/
    sudo rsync -aH --info=progress2 /mnt/recover/etc /mnt/ssd/recover/
    

    Per MariaDB il datafile più recente era nell’area corrotta. Ho recuperato il mysqldump notturno dal NAS, datato 7 ore prima del crash: perdita accettabile.

    Step 4: ricostruzione su nuovo hardware

    Ho montato un Raspberry Pi 5 8GB nuovo, microSD A2 ad alta affidabilità solo per OS, SSD USB 3.0 da 480 GB per /var/lib/observium, /var/lib/influxdb, /var/lib/mysql e /var/lib/observium/rrd tramite bind mount in fstab:

    
    /mnt/ssd/observium  /var/lib/observium  none  bind  0  0
    /mnt/ssd/influxdb   /var/lib/influxdb   none  bind  0  0
    /mnt/ssd/mysql      /var/lib/mysql      none  bind  0  0
    /mnt/ssd/rrd        /var/lib/observium/rrd  none  bind  0  0
    

    Ripristino del dump SQL, copia dei datadir InfluxDB e Observium dal SSD di recupero, riavvio servizi. Snipe-IT è tornato online con sette ore di asset persi (un paio di check-out ricostruibili a memoria). Observium ha ripreso polling con storia RRD intatta. InfluxDB ha ripreso con un buco di un paio d’ore.

    Un caso reale dentro il caso

    Mentre chiudevo il restore, intorno alle 22:30, ho notato che Observium non discoverava due switch aggiunti la settimana prima. La config era persa nel buco MariaDB? No: avevano cambiato community string dopo una rotazione che mi ero dimenticato di propagare. Credenziali corrette, switch tornati visibili. La documentazione delle credenziali deve vivere fuori dall’host che stai ripristinando, sempre.

    Cosa funziona bene

    ddrescue fa una cosa e la fa benissimo. Il logfile permette di riprovare incrementalmente sui settori illeggibili senza rifare l’intera immagine. Avere backup esterni (mysqldump notturno su NAS, snapshot config di /etc su altra macchina) ha trasformato un disastro potenziale in un fastidio di mezza giornata.

    Limiti

    L’unico vero limite era la microSD stessa: classe A1 generica per workload database-heavy è stato un errore di partenza. Le microSD A2 industrial sono molto più resistenti, ma per workload veri serve SSD via USB 3.0 o M.2 HAT.

    In pratica

    è la storia di scelte hardware sbagliate compensate da una disciplina di backup ragionevole. Oggi configuro SSD via USB 3.0 da subito su qualsiasi Pi che ospiti database o ingestion log, e tengo backup esterni con cadenza coerente con il valore dei dati. Una microSD corrotta non è più un disastro, è una procedura.


    Immagine generata con Cloudflare Workers AI / SDXL Base 1.

  • Troubleshooting connettività in un homelab eterogeneo

    Troubleshooting connettività in un homelab eterogeneo

    In un homelab che mescola Raspberry Pi, mini PC con Linux, un Mac, qualche IoT scemo e uno switch managed, prima o poi capita: “un host non risponde più, non capisco perché”. E sotto la frustrazione c’è quasi sempre la stessa cosa: una catena di ipotesi non verificate. Il modo per uscirne in tempi ragionevoli è una checklist mentale che parte dal basso e sale.

    Racconto una sessione di troubleshooting recente e i comandi che uso, ne approfitto per fissare il metodo per la prossima volta.

    L’incidente

    Un sabato pomeriggio, verso le 16:00, mi accorgo che un Raspberry Pi del homelab (lo chiamo rpi-monitoring) non risponde più al ping dal portatile. Dalle metriche Grafana risulta off dalla rete da circa un’ora. Niente alert sul telefono perché avevo silenziato Telegram nel pomeriggio. Ma il fatto è lì: l’host non c’è più.

    Step 1: dove sta il problema?

    La prima domanda non è “perché non risponde”, è “da quale layer in giù la cosa funziona”. Vado dal portatile in giù:

    
    ping -c 4 rpi-monitoring
    

    Risposta: Name or service not known. Quindi il DNS non risolve. Il primo strato già rotto. Provo a interrogare direttamente il resolver interno:

    
    dig +short rpi-monitoring @203.0.113.1
    

    Risposta vuota. Il DNS interno non ha più il record. Sospetto un DHCP-DNS che non ha rinnovato la lease. Provo per IP statico, perché ricordo che ha un fallback:

    
    ping -c 4 203.0.113.42
    

    Anche qui timeout. Il problema non è DNS, è che proprio non raggiungo l’host in IP.

    Step 2: layer 2 o layer 3?

    Ipotesi: il device non è connesso alla LAN. Lo verifico dallo switch managed (accesso via Tailscale dato che è raggiungibile sulla management interface):

    
    ssh admin@switch-managed
    show interfaces ethernet port5 status
    

    Port5 down. Cavo non collegato, NIC del device giù, o cavo rotto. Vado fisicamente al rack: cavo collegato sia sullo switch che sul Pi, ma spia LAN sul Pi spenta. Tolgo l’alimentatore e lo riattacco. La spia POWER resta spenta. Provo un altro alimentatore: idem. Sospetto SBC morto.

    Step 3: c’è alimentazione?

    Tiro fuori un multimetro economico, misuro la tensione del cavo USB-C dell’alimentatore: 5,15 V, dentro spec. Il problema non è l’alimentatore. Provo a infilare la microSD del Pi morto in un altro Raspberry Pi 5 di backup: avvio in 30 secondi, dmesg pulito. Il problema è hardware sul Pi originale. Probabilmente il regolatore di tensione lato SBC è andato.

    A questo punto la troubleshooting di rete è finita: non c’era niente da fare via comandi. Il device è hardware-dead, non network-dead. Ma se avessi saltato i primi step e fossi corso a sostituire l’host senza verificare DNS, switch e fisico, avrei potuto perdere un’ora a configurare un Pi nuovo per scoprire poi che lo switch aveva la porta down per un altro motivo.

    Comandi che uso spesso

    Senza ordine preciso, la cassetta degli attrezzi:

    
    ip link show
    ip addr show
    ip route show
    ss -tunlp
    arp -an
    ping -c 4 -W 2 host
    traceroute -n host
    mtr -n -c 10 host
    dig +short host @resolver
    nslookup host resolver
    nmap -sn 203.0.113.0/24
    tcpdump -i eth0 -nn host 203.0.113.42
    ethtool eth0
    sudo nft list ruleset
    

    Su Mac molti hanno equivalenti con flag diversi (ping con -W in millisecondi, traceroute senza -n per default).

    Un caso reale collaterale

    Stessa settimana, un altro incidente: latenza folle (centinaia di ms) e pacchetti duplicati ogni ping verso un host in LAN. Sintomo classico di ARP duplicato su VLAN. arp -an dal portatile mostrava due MAC diversi per lo stesso IP. Un Pi vecchio, smesso da mesi e tirato fuori per un test, era stato ricollegato con IP statico identico a uno già attivo. L’avevo dimenticato. Spegnimento del Pi vecchio, arp -d 203.0.113.42 per pulire la cache, latenza tornata a valori normali in dieci secondi. Anche quel caso si è risolto in cinque minuti perché ho seguito la checklist invece di indovinare.

    Cosa funziona bene

    Avere strumenti standard (ping, dig, mtr, tcpdump, ip) installati ovunque, su ogni Pi e mini PC. Niente tool esotici, niente script personalizzati. Funzionano dappertutto, e quando arriva il problema in mezzo alla notte non devi ricordarti come si lancia un binario custom.

    Limiti

    Il troubleshooting di rete su Wi-Fi è sempre più frustrante: client che migrano fra AP, mesh che fanno scelte opache, banda 2.4/5/6 GHz che cambia comportamento. Per quello tengo separati i client Wi-Fi dai server, e i server stanno tutti su ethernet. Niente Wi-Fi nei percorsi critici.

    In pratica

    Il metodo è più importante degli strumenti. Partire dal layer 1 (cavo, alimentazione, link) e salire (layer 2 MAC/switch, layer 3 IP, layer 4 porte, layer 7 applicazione) evita di girare a vuoto. Quasi tutti i problemi si risolvono nei primi tre layer, e il tempo speso a verificare l’ovvio si recupera ampiamente non andando dietro alle ipotesi sbagliate.


    Immagine generata con ComfyUI Mac M1 / RealVisXL V5 Lightning.

  • Asset management per il tuo homelab con Snipe-IT

    Asset management per il tuo homelab con Snipe-IT

    Il mio homelab è cresciuto a strati: prima un Raspberry Pi, poi due, poi un mini PC, poi un piccolo server ARM su un tier gratuito di un provider cloud, qualche switch managed, una decina di cavi etichettati male. A un certo punto, in un assemblaggio domenicale, ho passato venti minuti a cercare un cavetto da console seriale che ero sicuro di avere “in quella scatola lì”. Ho aperto tre scatole, non l’ho trovato, e ho ordinato il quarto in vita mia. Quella sera ho deciso di mettere ordine.

    Snipe-IT è un asset management open source pensato per piccole aziende, ma scalato bene anche per chi ha un homelab di media complessità. Lo uso da un anno e mezzo per tracciare hardware, licenze, accessori e dove ho messo cosa.

    Installazione

    Lo faccio girare su un Raspberry Pi 5 8GB con Debian Bookworm, in container Docker. La compose minima:

    
    mkdir -p ~/snipeit && cd ~/snipeit
    curl -fsSL https://raw.githubusercontent.com/snipe/snipe-it/master/docker-compose.yml -o docker-compose.yml
    

    Edito il file .env per APP_URL, password DB e SMTP, poi:

    
    docker compose pull
    docker compose up -d
    

    Snipe-IT richiede MariaDB (incluso nel compose) e PHP 8.x. Il primo bootstrap inizializza il database ed emette la chiave APP_KEY: salvarla altrove subito, perché senza non si decifra il backup del database.

    Davanti metto un reverse proxy con Caddy o nginx e certificato Let’s Encrypt automatico. L’interfaccia parla HTTPS sul mio dominio interno, mai esposta su internet pubblica.

    Modello dati che uso

    Snipe-IT distingue Assets (oggetti seriali tipo un Raspberry Pi), Accessories (cose contate ma non singolarmente tracciate tipo cavi USB-C), Consumables (cose che si esauriscono tipo viti M3), Licenses (chiavi software con expiry), Components (RAM, dischi che sostituisco). Le categorie che ho creato:

    SBC: Raspberry Pi, Orange Pi, NanoPi

    Mini PC: NUC e simili

    Network gear: switch, AP Wi-Fi, router

    Storage: dischi USB, microSD etichettate, SSD da rotazione

    Cavi e adattatori: contati, non seriali

    Console: cavi seriali, programmer JTAG, alimentatori speciali

    Ogni asset seriale ha tag fisico stampato (DYMO 12mm) con asset tag univoco, modello, e tag categoria. Il tag fisico è la chiave per arrivare al record digitale in pochi secondi.

    Import bulk da CSV

    Quando ho fatto il primo censimento, l’inserimento manuale di trenta asset era già troppo. Ho preparato un CSV e usato l’importer:

    
    docker compose exec -T app php artisan snipeit:import --type=asset /tmp/assets.csv
    

    Il CSV deve avere intestazioni che mappano i campi (Asset Tag, Serial, Model Name, Category, Location, Status). Per i custom field ho aggiunto colonne mac_address e firmware_version che ho creato dal pannello.

    Un caso reale

    Un giovedì pomeriggio, intorno alle 15:00, un cliente mi ha chiesto la documentazione di un audit hardware su un suo lab di test. Volevo presentare lista, posizione, data acquisto e stato di garanzia per una ventina di macchine. Avevo registrato tutto su Snipe-IT nei mesi precedenti, comprese le scansioni delle fatture allegate come file ai singoli asset. In quindici minuti ho generato l’export CSV via API:

    
    curl -H "Authorization: Bearer TOKEN" \
      "https://snipeit.example.lan/api/v1/hardware?limit=100&offset=0" \
      -o assets.json
    

    E ho consegnato il foglio pulito. Senza Snipe-IT sarebbe stata una giornata di archeologia fra ricevute. Risparmio puro che paga da solo il costo di mantenere il servizio attivo.

    Cosa funziona bene

    La gestione checkout/checkin è preziosa: quando presto un cavo console a un cliente o porto un Pi a casa di un familiare per ricostruire un disastro, lo segno come “checked out” con data e nota. Tornato il pezzo, lo checko di nuovo in casa. Mai più “ma dove l’avevo lasciato?”. L’API REST è completa, l’ho integrata con uno script che, quando installo un nuovo host, registra automaticamente serial number e MAC.

    Limiti

    Snipe-IT richiede una manutenzione minima per restare utile: se smetti di aggiornarlo quando aggiungi o rimuovi hardware, in tre mesi diventa rumore. Il primo censimento è il lavoro più ingrato, conviene farlo a step (categorie principali prima, dettagli dopo) e accettare che sarà imperfetto per un po’.

    In pratica

    è una di quelle infrastrutture che paga in tranquillità più che in tempo risparmiato giornaliero. Tenere allineata la realtà fisica al database costa qualche minuto a settimana e ti restituisce ore quando arriva il momento del troubleshooting, dell’audit o del trasloco. Per chi è abituato a “tanto me lo ricordo”, il salto a Snipe-IT è quasi rieducativo.


    Immagine generata con ComfyUI Mac M1 / RealVisXL V5 Lightning.

  • Sincronizzazione file decentralizzata con Syncthing

    Sincronizzazione file decentralizzata con Syncthing

    Sincronizzare file fra macchine diverse senza affidarsi a un cloud di terzi è una di quelle abitudini che mi hanno cambiato il modo di lavorare. Sulla scrivania ho un portatile Linux, in salotto un Mac mini dedicato al multimedia, in un angolo un paio di Raspberry Pi e su un provider cloud un piccolo server ARM su tier gratuito. Tenere allineata la cartella ~/Documenti/note/ fra tutti questi posti, senza che nessuno legga i miei file, è il caso d’uso perfetto per Syncthing.

    Installare

    Su Debian e derivate, incluso Raspberry Pi OS, il pacchetto è nel repo ufficiale. Sul portatile uso fish come shell, sui server bash di default:

    
    sudo apt update
    sudo apt install syncthing
    

    Su Mac via Homebrew:

    
    brew install syncthing
    brew services start syncthing
    

    Sui sistemi headless lo abilito come servizio utente, sostituendo USERNAME con il mio utente di servizio:

    
    sudo systemctl enable --now [email protected]
    

    Il demone parte su 127.0.0.1:8384 per la web UI e su TCP/UDP 22000 per la sincronizzazione vera e propria. Se il server è remoto, faccio tunnel SSH per arrivare alla UI invece di esporla in rete:

    
    ssh -L 8384:127.0.0.1:8384 utente@host-remoto
    

    E apro http://127.0.0.1:8384 in locale. Esporre la web UI direttamente su internet è una pessima idea: l’autenticazione di default è basilare e da li si gestisce l’intera tailnet di file.

    Aggiungere un dispositivo

    Ogni nodo ha un Device ID lungo, generato alla prima esecuzione. Dalla web UI lo copio, sull’altro nodo apro “Add Remote Device” e lo incollo. Quando entrambi si vedono, posso condividere cartelle. Tre accortezze che applico sempre:

    1. Folder Type “Send & Receive” sulla macchina principale, “Receive Only” sui Raspberry Pi: un errore su un Pi non torna a sporcarmi il master.

    2. Versioning “Staggered” con retention a 30 giorni: salva snapshot a intervalli crescenti e mi permette di recuperare un file modificato per sbaglio anche tre settimane dopo.

    3. Ignore patterns per .DS_Store, .git/, node_modules/ e cartelle di cache: senza, Syncthing si mangia banda dietro a robaccia.

    Topologia e routing

    Per ridurre il traffico relay e tenere la sincronizzazione veloce, configuro un nodo come “Introducer” e abilito il discovery globale solo dove serve. Sui server in cloud con IP pubblico aggiungo l’hint statico nel device address: invece di dynamic metto tcp://203.0.113.10:22000. In questo modo il NAT traversal è immediato e non serve passare dai relay pubblici Syncthing.

    Le porte 22000/tcp e 22000/udp sui server vanno aperte nel security group o sull’host firewall:

    
    sudo ufw allow 22000/tcp
    sudo ufw allow 22000/udp
    

    Un caso reale

    Una mattina, intorno alle 9:30, stavo lavorando su un Raspberry Pi della stanza-studio quando ho dovuto spostarmi al portatile per un ticket urgente. Apro il laptop e in venti secondi la cartella note è già aggiornata con quello che avevo scritto sul Pi. Niente upload manuali, niente “lascia perdere, lo finisco poi”. Quattro ore dopo, al rientro, ho notato che un service di un host periferico aveva ricreato per errore qualche file dentro node_modules in una cartella condivisa per sbaglio: la Receive Only di quel nodo ha bloccato la propagazione, ho potuto fare cleanup centralmente senza rincorrere copie sporche su tre macchine.

    Cosa funziona bene

    La filosofia peer-to-peer mi piace: i file restano miei, cifrati in transito, e non c’è nessun servizio centrale che vede i metadati. Lo Staggered Versioning mi ha salvato almeno tre volte da rm sbagliati. La UI è chiara, lo stato di ogni cartella è leggibile a colpo d’occhio.

    Limiti

    Per cartelle con tantissimi file piccoli (un cache di build, una libreria con migliaia di assets) Syncthing fatica e il CPU sul Raspberry Pi schizza. Per quei casi continuo a usare rsync puntuale o uno share NFS. Il primo handshake fra device dietro NAT a volte richiede un paio di minuti, soprattutto se la rete del provider cloud blocca certi flussi UDP.

    In pratica

    Syncthing è diventato il livello di trasporto file dell’homelab: copre la mia esigenza di “stessi file, ovunque”, senza che nessuno si metta in mezzo. Lo accoppio con backup a freddo per il disaster recovery e con un object storage S3-compatibile per il versioning storico, ma per il quotidiano è lui che fa il lavoro.


    Immagine generata con ComfyUI Mac M1 / RealVisXL V5 Lightning.