{"id":149,"date":"2026-05-11T17:10:32","date_gmt":"2026-05-11T15:10:32","guid":{"rendered":"https:\/\/rpi.temporiti.net\/wordpress\/?p=149"},"modified":"2026-05-30T14:06:40","modified_gmt":"2026-05-30T12:06:40","slug":"la-risurrezione-di-monitoring-come-ho-salvato-i-dati-da-una-microsd-corrotta","status":"publish","type":"post","link":"https:\/\/rpi.temporiti.net\/wordpress\/?p=149","title":{"rendered":"La resurrezione di monitoring: come ho salvato i dati da una microSD corrotta"},"content":{"rendered":"<p>Una mattina di qualche settimana fa, mentre prendevo il caff\u00e8, mi accorgo che la dashboard Grafana non risponde. Stessa cosa per la web UI di Observium e per il logger centrale. Tre servizi gi\u00f9 insieme: non \u00e8 coincidenza, \u00e8 un host gi\u00f9. Quell&#8217;host, nel mio homelab, \u00e8 un Raspberry Pi 5 8GB in case dedicato che chiamo &#8220;monitoring&#8221; (qui lo chiamo <code>rpi-monitoring<\/code>) e che fa girare diversi servizi:<\/p>\n<p>&#8211; <strong>Snipe-IT<\/strong> per asset management<\/p>\n<p>&#8211; <strong>Observium<\/strong> Community come NMS che polla SNMP gli altri host<\/p>\n<p>&#8211; <strong>InfluxDB v2<\/strong> come time series database delle metriche<\/p>\n<p>&#8211; <strong>rsyslog<\/strong> centrale che riceve log dalla LAN<\/p>\n<p>&#8211; <strong>MariaDB<\/strong> condiviso fra Snipe-IT e Observium<\/p>\n<p>Tutto girava su una microSD 64 GB classe A1 generica. Dopo circa un anno di servizio la scheda si \u00e8 bloccata: kernel pieno di <code>mmc0: Card stuck being busy<\/code> e poi <code>EXT4-fs error<\/code> a cascata. Ecco come ho recuperato i dati e ricostruito il servizio.<\/p>\n<h2>Perch\u00e9 la microSD \u00e8 morta<\/h2>\n<p>Una microSD generica entry-level \u00e8 progettata per fotografia e video: scritture sequenziali grosse, poche cancellazioni. Quello che le ho fatto fare era l&#8217;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.<\/p>\n<p>Il TBW sopportabile da una microSD entry-level \u00e8 qualche TB. Con quel carico ci si arriva in 8-12 mesi. Errore mio: non aver migrato a SSD via USB 3.0 prima.<\/p>\n<h2>Step 1: bitwise dump con ddrescue<\/h2>\n<p>La prima cosa che si fa con un dispositivo morente, prima di provare a recuperare file, \u00e8 un dump bitwise dell&#8217;intera scheda. ddrescue \u00e8 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:<\/p>\n<pre><code class=\"language-bash\">\nsudo apt install gddrescue\nsudo ddrescue -d -r 3 \/dev\/sda \/mnt\/ssd\/monitoring.img \/mnt\/ssd\/monitoring.log\n<\/code><\/pre>\n<p><code>-d<\/code> salta la cache del kernel e legge in direct I\/O, <code>-r 3<\/code> riprova ogni settore tre volte. Il dump \u00e8 andato avanti per quasi cinque ore. Risultato: 62,8 GB su 64 GB recuperati, 1,2 GB irrecuperabili distribuiti soprattutto nell&#8217;area dove stavano i datafile MariaDB di un mese. Gi\u00e0 un buon punto di partenza.<\/p>\n<h2>Step 2: montare l&#8217;immagine read-only<\/h2>\n<p>Con l&#8217;immagine in mano, l&#8217;ho associata a un loop device read-only per non rischiare di scriverci sopra:<\/p>\n<pre><code class=\"language-bash\">\nsudo losetup -r -fP \/mnt\/ssd\/monitoring.img\nsudo losetup -a\n<\/code><\/pre>\n<p>Il <code>losetup -a<\/code> mostra il device <code>\/dev\/loop0<\/code> con partizioni <code>loop0p1<\/code> (boot) e <code>loop0p2<\/code> (rootfs). Ho montato la rootfs:<\/p>\n<pre><code class=\"language-bash\">\nsudo mkdir -p \/mnt\/recover\nsudo mount -o ro \/dev\/loop0p2 \/mnt\/recover\n<\/code><\/pre>\n<p>Filesystem montato pulito, con qualche file segnato come corrotto in dmesg ma navigabile.<\/p>\n<h2>Step 3: estrazione dati per servizio<\/h2>\n<p>Ho copiato selettivamente i datadir dei servizi importanti su un SSD USB di lavoro:<\/p>\n<pre><code class=\"language-bash\">\nsudo rsync -aH --info=progress2 \/mnt\/recover\/var\/lib\/mysql \/mnt\/ssd\/recover\/\nsudo rsync -aH --info=progress2 \/mnt\/recover\/var\/lib\/influxdb \/mnt\/ssd\/recover\/\nsudo rsync -aH --info=progress2 \/mnt\/recover\/var\/lib\/observium \/mnt\/ssd\/recover\/\nsudo rsync -aH --info=progress2 \/mnt\/recover\/etc \/mnt\/ssd\/recover\/\n<\/code><\/pre>\n<p>Per MariaDB il datafile pi\u00f9 recente era nell&#8217;area corrotta. Ho recuperato il <code>mysqldump<\/code> notturno dal NAS, datato 7 ore prima del crash: perdita accettabile.<\/p>\n<h2>Step 4: ricostruzione su nuovo hardware<\/h2>\n<p>Ho montato un Raspberry Pi 5 8GB nuovo, microSD A2 ad alta affidabilit\u00e0 solo per OS, SSD USB 3.0 da 480 GB per <code>\/var\/lib\/observium<\/code>, <code>\/var\/lib\/influxdb<\/code>, <code>\/var\/lib\/mysql<\/code> e <code>\/var\/lib\/observium\/rrd<\/code> tramite bind mount in fstab:<\/p>\n<pre><code class=\"language-plaintext\">\n\/mnt\/ssd\/observium  \/var\/lib\/observium  none  bind  0  0\n\/mnt\/ssd\/influxdb   \/var\/lib\/influxdb   none  bind  0  0\n\/mnt\/ssd\/mysql      \/var\/lib\/mysql      none  bind  0  0\n\/mnt\/ssd\/rrd        \/var\/lib\/observium\/rrd  none  bind  0  0\n<\/code><\/pre>\n<p>Ripristino del dump SQL, copia dei datadir InfluxDB e Observium dal SSD di recupero, riavvio servizi. Snipe-IT \u00e8 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&#8217;ore.<\/p>\n<h2>Un caso reale dentro il caso<\/h2>\n<p>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&#8217;host che stai ripristinando, sempre.<\/p>\n<h2>Cosa funziona bene<\/h2>\n<p>ddrescue fa una cosa e la fa benissimo. Il logfile permette di riprovare incrementalmente sui settori illeggibili senza rifare l&#8217;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.<\/p>\n<h2>Limiti<\/h2>\n<p>L&#8217;unico vero limite era la microSD stessa: classe A1 generica per workload database-heavy \u00e8 stato un errore di partenza. Le microSD A2 industrial sono molto pi\u00f9 resistenti, ma per workload veri serve SSD via USB 3.0 o M.2 HAT.<\/p>\n<h2>In pratica<\/h2>\n<p>\u00e8 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 \u00e8 pi\u00f9 un disastro, \u00e8 una procedura.<\/p>\n<hr>\n<blockquote>\n<p>Immagine generata con Cloudflare Workers AI \/ SDXL Base 1.<\/p>\n<\/blockquote>\n","protected":false},"excerpt":{"rendered":"<p>La microSD generica del mio pironman5 e morta dopo un anno di Snipe-IT, Observium, InfluxDB e rsyslog server. Ecco come ho recuperato i dati con ddrescue, fatto shrink offline della rootfs ext4 per adattarla a una Kingston piu piccola, e sistemato la race condition del boot con un drop-in systemd.<\/p>\n","protected":false},"author":1,"featured_media":187,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[12,8,11,9,10,14,13],"class_list":["post-149","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-self-hosting","tag-ddrescue","tag-homelab","tag-microsd","tag-raspberry-pi","tag-recovery","tag-snipe-it","tag-systemd"],"_links":{"self":[{"href":"https:\/\/rpi.temporiti.net\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/149","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rpi.temporiti.net\/wordpress\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rpi.temporiti.net\/wordpress\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rpi.temporiti.net\/wordpress\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rpi.temporiti.net\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=149"}],"version-history":[{"count":14,"href":"https:\/\/rpi.temporiti.net\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/149\/revisions"}],"predecessor-version":[{"id":386,"href":"https:\/\/rpi.temporiti.net\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/149\/revisions\/386"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rpi.temporiti.net\/wordpress\/index.php?rest_route=\/wp\/v2\/media\/187"}],"wp:attachment":[{"href":"https:\/\/rpi.temporiti.net\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=149"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rpi.temporiti.net\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=149"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rpi.temporiti.net\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=149"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}