Vai al contenuto

Migrazione dei box dalla 3.2.x alla nuova versione

Analisi del 29/10/2026 sul codice di mdm_installation: origin/main (3.2.9, commit 531e6552) e origin/stage (3.9.0, commit d02dbaee). Nessuna prova ancora fatta su un box 3.2.x (ticket MDM-081).

Ricontrollo del 30/10/2026 su origin/stage al commit ebde226c: i 7 commit successivi (correzioni portate da main, anti-manomissione, audio) non toccano migrazioni, updater, update.yml, compose né build tag. A quel commit l'import e il ponte non erano iniziati. Aggiornamento del 02/11/2026: l'import è ora committato in db444987; il ponte resta da realizzare. infra/environments/prod/.env.build_tag indica ancora 3.2.8/3.3.0. Le correzioni di main sono riportate (dettaglio).

Verifica del 02/11/2026

Riferimenti remoti controllati con git ls-remote origin refs/heads/main refs/heads/stage: main = 531e6552, stage = db444987. Nessuna modifica ai branch remoti, ai segreti AWS o ai servizi dei box durante queste verifiche.

Controllo Esito osservato
Frigate .168 internal_server:3.2.9, wrapper 3.4.0, Frigate 0.17.1 healthy; checkout 531e6552, sola modifica locale al .env del box; lettore execpipe.sh presente
Stato Frigate /status e /api/v1/detection/status HTTP 200; box, detector e wrapper online, una telecamera, inserito (disarm.status = false, wrapper status = true)
Isolamento Frigate HTTP/MQTT verso il dev, LOGSERVERBASEURL disabilitato. Il webhook alarm.php non punta a localhost né a un'interfaccia assegnata al box: Lorenzo conferma il 02/11 che è un ricevitore di laboratorio isolato; nessun allarme sintetico inviato in questa verifica
GANZ .165 internal_server:3.9.0-stage, checkout host ancora d02dbaee; API HTTP 200, una telecamera, detector online e disinserito. Il checkout host non prova quale codice sia stato copiato nel container
Isolamento GANZ HTTP/MQTT verso il dev; invio dei log verso BSQ non disabilitato. Da verificare/isolare prima di prove che producono eventi
Test locali dell'import uv run pytest tests/test_app/test_core/test_db/test_legacy_import.py tests/test_app/test_core/test_db/test_migration_manager.py -q: 17 passed. ruff check e ruff format --check sui due moduli di import e sul relativo test: superati
Import su backup reale GANZ Copia privata e temporanea del backup stage-cutover-20261028T151536Z/installation-original, sul solo box di laboratorio; sorgente aperta in sola lettura e database copiato con SQLite backup API. Migrazioni candidate di db444987 montate nell'immagine stage già presente, senza avviare il servizio
Stato nel backup GANZ La tabella arm_state esiste ma è vuota. Senza stato acquisito e con rete disabilitata l'import si ferma con LegacyImportError. La presenza della tabella non basta per scegliere il percorso della migrazione
Import GANZ con stato acquisito Letto lo stato dal detector configurato mediante GET, poi fornito quel booleano al lettore del processo di verifica. Migrazione effettuata su copia in container --network none: schema 7, configurazione, impostazioni, I/O e stato disinserito conservati; JSON originali e copie uguali, SQLite integro prima/dopo, seconda esecuzione senza cambiamenti di configurazione o stato. Nel backup ci sono 0 log: il conteggio resta 0, non è una prova su log esistenti. Un campo segnalato come non importato, da verificare prima di dichiarare la migrazione completa
Pannello → midserver → box Prova manuale di Lorenzo del 02/11 su Frigate: versione 3.2.9, inizialmente inserito, telecamera presente; disinserimento e reinserimento riusciti, nessun nuovo errore relativo ai due comandi. La persistenza dopo refresh non è riportata separatamente. Le prove di aggiornamento A1/A2 sotto restano evidenze del 30/10
Live Frigate dal pannello Non riuscito: il pannello segnala che il nome della telecamera manca nella configurazione degli stream MDM. Controllo SSH in sola lettura: webrtc_streams contiene 0 elementi, CAM_1 assente, coerente con la configurazione iniziale del laboratorio. La presenza della telecamera nel detector non crea lo stream MDM. Il codice origin/main:app/services/mqtt/handlers/web_rtc_handler.py cerca il nome in webrtc_streams prima di avviare FFmpeg: questa prova non verifica RTSP, codec o OME. Prima di aggiungere lo stream e riprovare, risolvere/verificare l'uscita SRT della 3.2.9, ancora scritta nel codice verso OME di produzione (185.31.66.185:9999, M15). Nessuno stream o servizio modificato
Ponte, cambio Compose, ripristino automatico Non implementati né provati da questa verifica; MDM-081 resta aperto

Preparazione del live Frigate dev (02/11/2026)

Dopo l'esito manuale dei comandi, Lorenzo autorizza la preparazione del live sul solo box .168:

  • Salvati JSON originale, copia coerente SQLite, sorgente originale dell'handler e variante dev in /home/bsqsecurity/backup/live-dev-20261102T103058Z/ sul box, directory privata. Nessuna credenziale copiata nella documentazione.
  • Aggiunto webrtc_streams con nome CAM_1 e lo stesso ingresso RTSP già usato dalla telecamera Frigate. Frigate non ha un restream CAM_1 configurato in go2rtc: il live apre quindi una connessione aggiuntiva alla sorgente NVR, senza cambiare il detector o i suoi parametri.
  • Nella sola copia dell'handler dentro il container internal_server sostituita l'uscita SRT fissa di produzione con srt://170.10.0.107:9999?mode=caller&streamid=live/app/<token>. Nessuna modifica a main, alle immagini ECR, al Compose o al server di produzione. È una modifica locale di laboratorio: la versione dichiarata resta 3.2.9, ma l'handler runtime differisce dall'immagine; un restart la conserva, una ricreazione del container la perde. La configurazione JSON è invece persistente sul volume host. Non considerare questo container una baseline 3.2.9 intatta per la prova del ponte.
  • Riavviato il solo internal_server. Controlli successivi: API HTTP 200, box/detector/wrapper online, una telecamera, box e wrapper inseriti; Frigate healthy e data di avvio invariata (2026-10-30T16:27:34Z). Compilazione AST dell'handler riuscita; esecuzione isolata del solo costruttore del comando FFmpeg con URL/token fittizi conferma l'uscita dev e l'assenza del vecchio URL SRT di produzione.
  • Il signaling configurato nel pannello è wss://mdm.dev.ome.bsqdev.uk; il dominio risolve a 185.31.66.187. Dal box, https://mdm.dev.ome.bsqdev.uk risponde HTTP 404 alla radice, così come il listener diretto 170.10.0.107:3333: prova di raggiungibilità HTTP, non prova di handshake WebRTC. Il Compose dev espone SRT su 9999/UDP; il precedente tentativo TCP su 9999 non ne misura la disponibilità.
  • Riproduzione video confermata da Lorenzo nel pannello dev il 02/11: «il video compare». Il percorso sorgente NVR → box → SRT OME dev → player del pannello è quindi riuscito in questa prova. Non misurati tempo di avvio, durata, latenza o audio; nessuna conclusione generale sulla compatibilità H.265 di altri browser. Controllo SSH successivo: API HTTP 200, box e detector online, box e wrapper ancora inseriti, Frigate healthy con avvio invariato (2026-10-30T16:27:34Z); 1 connessione live ancora attiva al controllo. La chiusura e il rilascio della connessione restano da verificare dopo che Lorenzo chiude il player.

Ripristino della sola preparazione live: conservare gli eventuali cambiamenti intervenuti nel frattempo; ripristinare config.json dal backup e il sorgente originale nell'handler del container, quindi riavviare solo internal_server e verificare stato inserito e detector. Non ripristinare l'intero database per annullare queste due modifiche: la copia SQLite serve come baseline, non è stata modificata per configurare il live.

Uscita dal player, prova successiva del 02/11: Lorenzo cambia pagina e segnala che nel player non c'è un pulsante di chiusura. Due letture successive del box mostrano ancora 1 stream attivo; la seconda conferma anche 1 processo FFmpeg con uscita SRT. Il cambio pagina non ha quindi arrestato la pubblicazione in questa prova. Box/detector online, impianto inserito e Frigate healthy senza riavvio. Nel sorgente locale del pannello, live_stream_player.page.dart non invia lo stop quando si esce; l'azione esplicita Stop stream (icona quadrata) è nella lista dei live attivi, web_rtc_streams.dart, e chiama LiveStream.stop. Da provare manualmente quell'azione per completare la verifica dell'arresto; non è stato terminato alcun processo via SSH.

Stop esplicito, prova successiva del 02/11: Lorenzo preme Stop stream e conferma l'assenza di errori nel pannello. Il box riporta 0 live attivi, ma due controlli successivi rilevano ancora 1 processo FFmpeg SRT, non zombie (sleeping), con destinazione OME dev. Quindi la risposta di successo e il contatore a zero non provano la terminazione del processo. Il codice 3.2.9 remove_connection invia process.terminate() e rimuove subito la voce dal registro, senza wait() né fallback kill(); anche app/services/livestream/live_stream_service.py della candidata usa terminate() senza attesa: da correggere e provare prima di chiudere l'accettazione video della nuova versione. Non attribuita una causa specifica alla mancata uscita di FFmpeg.

Pulizia della sola prova: riconfermati eseguibile e destinazione del PID residuo prima di terminarlo con SIGKILL. Controllo finale: 0 processi FFmpeg SRT, 0 live attivi, API HTTP 200, detector online, impianto ancora inserito, Frigate healthy con avvio invariato. Nessun riavvio di servizio o modifica del detector per questa pulizia. Lo stop dal pannello resta parzialmente riuscito, richiedendo intervento sul processo residuo.

Lo script verifica_import_legacy.py verifica una copia sacrificabile montata in /verification/config, con DATABASE_URL=sqlite:////verification/config/installation.db, nell'immagine dell'installazione e con le migrazioni candidate montate in /app/app/core/db/migrations in sola lettura. Avviare solo lo script con /app/.venv/bin/python, senza run.py, Docker socket o rete; PYTHONPATH=/app. Se manca uno stato salvato, CAPTURED_DETECTOR_DISARMED=true|false deve contenere il risultato di una lettura reale precedente, mai un valore scelto per far passare la prova. Il verificatore sostituisce solo il lettore del detector nel proprio processo isolato. Non è un comando di migrazione operativa. Output limitato a booleani e conteggi; i file di prova sono stati rimossi al termine e non trasferiti nel repository.

Sequenza per completare la migrazione in laboratorio

  1. Confermare l'isolamento di ogni uscita del GANZ, compresi log BSQ e notifiche; salvare baseline e backup coerente, stato reale del detector e digest delle immagini.
  2. Ripetere dal pannello dev un comando su ciascun box, verificandone l'effetto reale e la successiva sincronizzazione; su Frigate provare disinserimento e reinserimento, lasciandolo infine nello stato iniziale. Provare snapshot, live ed evento solo dopo aver verificato le rispettive destinazioni.
  3. Preparare il ponte di MDM-081 e una candidata isolata dal vero main. Acquisire lo stato del detector prima del fermo anche se la tabella arm_state esiste ma non contiene righe. Conservare snapshot coerente di SQLite, JSON, configurazioni detector/wrapper, progetto Compose e digest precedenti prima della prima scrittura della nuova versione.
  4. Provare import su copia anche con log esistenti e verificare singolarmente i campi non importati; poi provare il passaggio reale con gli indirizzi dev, senza modificare il segreto AWS di produzione.
  5. Verificare versione/digest, identità, configurazioni, stato reale del detector, telecamere e comando di ritorno dal pannello. Ripetere con un errore intenzionale della candidata e dimostrare il ritorno automatico a codice, database e container precedenti. Misurare separatamente download e fermo.
  6. Ripetere per i formati di partenza previsti in MDM-081 e verificare un secondo aggiornamento con il nuovo updater. Solo dopo questi esiti valutare la release di produzione: nessun merge nel vero main durante le prove.

Regola (D22)

  • La linea stage è sviluppo: gira solo sui box di prova installati come Stage (per esempio MDMTESTGANZ1, collegato al dev).
  • Il codice di stage arriva ai clienti solo dopo essere stato unito in main e rilasciato con le immagini di produzione.
  • I box dei clienti restano sempre sul branch main, nell'ambiente prod, e si aggiornano con l'update normale dal pannello. Nessun box dei clienti va portato su stage, nemmeno temporaneamente.

Il codice della 3.9 segue già questa logica: l'ambiente prod corrisponde al branch main e al file infra/environments/prod/.env.build_tag, l'ambiente stage al branch stage (dev_scripts/commands/compose_config.py).

Risposta breve

Dopo l'unione in main, l'update di un box 3.2.x da solo non basta. Il download del codice funziona, ma l'avvio no. In più la nuova versione non legge la configurazione della 3.2.x. Servono, dentro la release unita in main (senza modificare la 3.2.x):

  1. l'import delle configurazioni della 3.2.x al primo avvio;
  2. un ponte che permetta all'update della 3.2.x di avviare la nuova versione, che poi completa da sola il passaggio alla nuova struttura.

Come funziona l'update oggi

  • Il pulsante non aggiorna di uno step e non sceglie una versione. Il pannello invia solo la lista dei box a POST /installation/update (mdm_web_panel/lib/features/installation/data_source/installation.dts.dart); il midserver inoltra update_system (mdm_mid_server/app/features/installation/data/repo_impl/installation.py). Il box sceglie il branch dal proprio ambiente, scarica la testa del branch e usa i tag indicati nel file di build. Esempio: oggi un box production 3.1.9 può arrivare direttamente alla 3.2.9 di main, come nella prova A1; non passa prima per 3.2.1 e 3.2.2. Le migrazioni applicative pendenti sono un meccanismo separato e vengono eseguite all'avvio.
  • "Ultima" significa tag configurati in quel branch, non il tag numericamente maggiore di ECR e non la linea stage per un box production/prod. Se il branch punta a immagini vecchie, il pulsante usa quelle. La notifica di successo del pannello non prova che i container siano stati aggiornati: vedere il problema della pipe sotto.
  • Il pannello manda il comando di update. Il box (host_manager.py) legge update.yml dalla versione che sta girando e scrive il comando sulla named pipe verso l'host.
  • 3.2.x: con envirment = production usa il branch main e il file .env.build_tag. Esegue dev_scripts/updater.py: git pull origin main, login ECR, poi docker compose --env-file .env.build_tag -f docker-compose.yml --profile <detector> --profile production pull e up -d --remove-orphans.
  • 3.9: uv run mdm update <detector> <ambiente>: git pull del branch dell'ambiente, sottomoduli (external/infra-lib), login ECR, poi infra-cli. Questo legge il segreto AWS mdm/installation/<ambiente> e avvia infra/compose/docker-compose.yml con il progetto Compose installation.

Entrambi gli updater contengono una chiave AWS scritta nel codice (rischio già noto, T6.5/T10.6).

Cosa cambia tra le due versioni

3.2.x nuova versione (oggi stage)
Configurazione file JSON in app/config/configuration_files/: config.json, setting.json, pin.json, .env (UUID durante la registrazione), .applied_migrations.json database SQLite installation.db nella stessa cartella: installation_config, feature_settings, io_settings, alarm_schedule, notification_config, health_config, cluster_settings, schema_versions
Stato inserito/disinserito tabella arm_state (disarmed) tabella system_state (JSON system_state)
Log tabella logs logs con la colonna is_synced in più (migrazione 002)
Compose docker-compose.yml nella radice, progetto con il nome della cartella infra/compose/docker-compose.yml, progetto installation, avviato da infra-cli
Variabili d'ambiente .env.build_tag nella radice infra/environments/<ambiente>/.env.build_tag più .infra.generated.env dal segreto AWS
Nome dell'ambiente production (anche come profilo Compose) prod
Strumenti sull'host python3, aws, docker in più uv e il sottomodulo external/infra-lib (infra-lib, scaricato da CodeCommit; ogni avvio con infra-cli legge il segreto AWS)
Container internal_server, frigate, frigate_wrapper, mosquitto stessi nomi, tranne installation-mosquitto

Restano dove sono: configurazione e registrazioni di Frigate (frigate_wrapper/frigate/config e storage), configurazione del wrapper (frigate_wrapper/config/configuration_files), immagini e video in app/logs/logs_assets. Il compose nuovo monta gli stessi percorsi.

Corrispondenza dei campi

3.2.x Nuova versione Nota
config.json → mac installation_config.mac è il codice dell'impianto: va conservato
config.json → envirment (production/stage) installation_config.environment (prod/stage) cambiano il nome e il valore
config.json → detector, webrtc_streams, privacy_mode stessi nomi in installation_config stessa struttura
config.json → configured (scritto alla registrazione; manca nel file di esempio) installation_config.configured deve risultare true per un box già in servizio
config.json → gpio_usb io_settings.gpio_usb spostato
config.json → sound_url io_settings.horns (struttura nuova) da mappare o riconfigurare
setting.json → enable_webRtc, enable_screen_shot feature_settings.enable_webrtc, enable_screenshot nomi cambiati
setting.json → check_camera_damage, fire_detection, monitoring_delay, monitoring_ganz_alarm_in, clean_disk stessi nomi in feature_settings
arm_state.disarmed system_state (ARMED/DISARMED) critico
pin.json, gpio_usb nessuno: la scheda GPIO su USB ("PIN") è stata tolta dalla linea stage non usato da nessuno dei 29 box del censimento; l'import lo segnala se gpio_usb è valorizzato (dettaglio)

Versioni più vecchie e altri casi del parco

Dal censimento del 28/10/2026: 28 box rispondono (3.2.9 → 15, 3.2.2 → 2, 3.2.1 → 2, 3.1.9 → 10, di cui 4 GANZ). Confronto fatto sui commit in cui .env.build_tag passa a ciascuna versione (3.1.9: 31764c60, 3.2.1: 4eaf7092, 3.2.2: 3fe09496, 3.2.9: cdb7acd1).

Uguale dalla 3.1.9 alla 3.2.9: update.yml, dev_scripts/updater.py, host_manager.py, app_config.py, app_setting.py e le 15 migrazioni JSON della configurazione. Il compose differisce solo per le variabili MQTT del wrapper. Quindi l'update si comporta allo stesso modo su tutte queste versioni e il formato di config.json/setting.json è lo stesso.

Cosa cambia:

Versione Stato inserito/disinserito Wrapper Frigate
3.1.9 nel detector: wrapper Frigate o GANZ (get_disarm_status), più una copia in memoria (DISARMSTATUS) 2.0.1, Flask
3.2.1, 3.2.2 come la 3.1.9 2.1.3, Flask
3.2.9 tabella arm_state in installation.db (dal 03/08/2026) 3.3.0 nel commit della 3.2.9, 3.4.0 dal commit successivo 91591ec9; FastAPI

Versioni del wrapper lette da .env.build_tag nei commit di ogni versione (correzione del 30/10: prima qui c'era 3.0.6 per la 3.2.1/3.2.2, che in ECR esiste solo come 3.0.6-stage). Quale wrapper giri davvero su ogni box si sa solo con docker ps (MDM-012).

Il file di configurazione del wrapper (config/configuration_files/config.json, lista web_hooks) ha la stessa struttura di base nelle due generazioni; i campi interni vanno confrontati in MDM-080. Lo stato di rilevamento di Frigate è nella configurazione di Frigate, che resta al suo posto.

Altri casi del censimento:

Caso Impianti Cosa fare
Box 3.1.9–3.2.9 che rispondono 28 update dal pannello con la nuova release (soluzione sotto), dopo aver verificato con SSH che la versione in esecuzione sia quella dichiarata (MDM-012)
Linux con SSH ma API 5005 non raggiungibile 5 prima capire versione reale e stato con SSH (MDM-012, MDM-013); se sono sotto la 3.1.9 o modificati a mano, reinstallazione guidata con import della configurazione
Windows (probabile GANZ del sistema vecchio) 8 non sono box MDM: nessun update possibile, serve una nuova installazione (T10.3)
Solo altre porte o nessuna risposta 26 riconciliare con l'inventario (MDM-011): dismessi → pulizia (T9.3); attivi ma irraggiungibili → intervento sul posto
Impianti con più box — ogni box (master e slave <MAC>-01) si aggiorna da solo; lo slave si raggiunge dal proxy del master

Versioni sotto la 3.1.9 non risultano tra i box che rispondono. Se ne emergono dal controllo SSH, si valutano caso per caso: se hanno lo stesso update.yml, valgono le regole della 3.1.9.

Percorso dentro la linea main (3.1.9 → 3.2.2 → 3.2.9)

Analisi del 30/10/2026 sul codice di mdm_installation (origin/main, 531e6552) e mdm_frigate_wrapper (2.0.1 = 847a0f8, 3.4.0 = 87c4f4e), più i tag in ECR (solo lettura). Nessuna prova ancora fatta su un box: le prove A1–A3 di macchine-di-test.md servono a confermarla.

Risposta breve: l'update esiste ed è lo stesso su tutte queste versioni. Una migrazione dedicata non serve, perché configurazione e stato restano dove sono. Va però provato, e ci sono tre punti da verificare.

Non ci sono passaggi intermedi. L'update fa sempre git pull origin main, quindi un box 3.1.9 va direttamente alla testa di main: .env.build_tag con INTERNAL_BUILD_TAG=3.2.9 e WRAPPER_BUILD_TAG=3.4.0. Non si passa dalla 3.2.2, e la 3.2.2 non si può scegliere come destinazione.

Cosa resta compatibile, verificato nel codice:

Parte Perché funziona
Updater update.yml, dev_scripts/updater.py e host_manager.py uguali dalla 3.1.9 alla testa di main
Compose stessi servizi e percorsi; il wrapper riceve in più le variabili MQTT_*, con valori predefiniti uguali al broker locale; Frigate 0.17.1 in tutte le versioni
Configurazione del box config.json/setting.json con lo stesso formato e le stesse 15 migrazioni JSON
.env del box (indirizzi del midserver) file uguale dalla 3.1.9 alla testa: il git pull non lo tocca
Database la 3.2.9 apre lo stesso installation.db; logs ha le stesse colonne e arm_state viene creata vuota
Stato inserito/disinserito, GANZ resta nel GANZ; la 3.2.9 lo salva in arm_state solo al primo inserimento o disinserimento successivo
Stato inserito/disinserito, Frigate il wrapper 2.0.1 lo scrive in config.yaml di Frigate (detect.enabled), che resta al suo posto. Il 3.4.0 non trova ancora uno stato salvato (detection.armed nella sua configurazione) e quindi non riapplica niente; Frigate riparte da config.yaml, cioè nello stato di prima
Stato all'avvio della 3.2.9 nella testa di main (app/services/object_detector/arm_state.py) arm_state è solo una copia dell'ultimo stato letto dal detector, scritta a ogni cambio: nessun ripristino all'avvio (la funzione restore_disarm_state del commit cdb7acd1 è stata tolta dopo). Lo stato lo tiene il wrapper 3.4.0
Configurazione del wrapper file di esempio identico tra 2.0.1 e 3.4.0; stesso percorso montato
MQTT del wrapper il 2.0.1 legge già gli eventi di Frigate dal broker locale (172.17.0.1:1883, utente test); il 3.4.0 usa lo stesso broker (localhost:1883 in rete host) e lo stesso utente

Da verificare:

  1. Il tag 3.2.9 è stato sovrascritto. In ECR l'immagine installation:3.2.9 è stata pubblicata il 19/10/2026 alle 10:02, tre minuti dopo il commit 531e6552 ("stream audio") e oltre due mesi dopo la 3.2.9 del 03/08. I box aggiornati prima del 19/10 hanno quindi un'altra immagine con lo stesso tag, e un box aggiornato oggi riceve anche la modifica audio del live (-map 0:a?, AAC), che è in [Unreleased] nel changelog. L'immagine originale del 03/08 è ancora in ECR, senza tag: sha256:bd38a99e…. È quella del backup di MDMTESTGANZ1 (mdm-local-rollback:3.2.9-20261028t151536z, verificato il 30/10). Quella del 19/10 è sha256:4661ae33…. I tag non sono immutabili e la versione dichiarata non distingue le due immagini: va confrontato il digest sui box (MDM-012). Per le prove in laboratorio: un update dal pannello scarica oggi l'immagine del 19/10; per riprodurre un box aggiornato prima del 19/10 si avvia la 3.2.9 per digest.
  2. Inserimento e disinserimento dopo il passaggio al wrapper 3.x. Dalla 3.2.0 il wrapper non riscrive più config.yaml e non riavvia Frigate: manda detect/set via MQTT e aspetta la conferma (DETECTION_CONFIRM_TIMEOUT_SECONDS, 2 s). Serve che Frigate accetti i comandi dal broker locale. Sui 15 box 3.2.9 succede già; sui 3.1.9–3.2.2 non è mai stato usato. La sezione mqtt di config.yaml è scritta a mano o dal pannello sul box, non da un repository.
  3. Wrapper sui box 3.2.9. Dipende da quando sono stati aggiornati: 3.3.0 se prima di 91591ec9, 3.4.0 dopo. Con il 3.3.0 lo stato di Frigate non sopravvive a un riavvio di Frigate. Da leggere con docker ps (MDM-012).

  4. L'update può dichiarare successo senza aggiornare niente (osservato il 30/10/2026 sulla prova A1). Il box riceve update_system, scrive il comando sulla named pipe e, se sull'host il lettore execpipe.sh non è in esecuzione, dopo il timeout scrive [Error] Timeout: Host scanner is not listening on the pipe. (host_commander.py) ma risponde comunque updated successfully: il pannello mostra Installation update triggered successfully e il box resta sulla versione di prima. Il lettore è avviato da cron (@reboot): se manca la riga in crontab, o il processo è morto, gli update dal pannello non hanno effetto. Dopo ogni update va controllata la versione reale (docker ps, MDM-012); in campo conviene verificare prima che execpipe.sh sia in esecuzione.

Cosa provare (prove A1–A3): partire da un box configurato e inserito, poi update dal pannello. Dopo l'update controllare: - versione e tag delle immagini con docker ps; - stesso mac e stessa configurazione; - stesso stato in /status; - un disinserimento e un reinserimento dal pannello, con Frigate che conferma; - un allarme di prova che arriva al dev; - un riavvio di Frigate, dopo il quale lo stato deve restare quello impostato (wrapper 3.4.0).

Misurare anche il tempo di fermo.

Esito della prova A1 (30/10/2026)

Box MDMTESTFRIGATE1 sulla .168 (macchine-di-test.md): 3.1.9, wrapper 2.0.1, Frigate 0.17.1 con Coral USB, una telecamera, inserito, collegato al midserver dev (v3). Update lanciato dal pannello dev.

Controllo Esito
Comando dal pannello arrivato (q_security/installation_topic, update_system). Al primo tentativo il lettore della pipe non girava: nessun effetto ma risposta di successo (punto 4 sopra). Dopo un riavvio, con il lettore avviato da cron, l'update è partito
Codice git pull origin main da 31764c60 a 531e6552 senza conflitti; il .env modificato in locale è rimasto com'era
Immagini installation:3.2.9 = sha256:4661ae33… (quella del 19/10), frigate_wrapper:3.4.0 = sha256:66f4ae00…; in più parte installation-dsniff-1, perché l'updater usa anche il profilo production, che l'installer non usa
Configurazione config.json, setting.json e il codice impianto invariati
Stato inserito invariato: config.yaml con detect.enabled: true, wrapper 3.4.0 {"status": true}, /status della 3.2.9 con disarm.status: false; arm_state creata e scritta alla prima lettura (disarmed = 0); nel wrapper nessuno stato salvato, quindi il reconciler non interviene
Frigate non riavviato: il rilevamento non si è mai fermato
Fermo API del box non raggiungibile per circa 8 s (ultimo 200 alle 14:01:26, di nuovo 200 alle 14:01:34, campionamento ogni 2 s); stato del wrapper non leggibile per un campione. Download delle immagini (circa 300 MB per la 3.2.9) fatto prima, senza fermo
Disinserimento dal pannello dopo l'update riuscito: wrapper 3.4.0 via MQTT, detection.armed: false salvato, arm_state aggiornata

Esito della prova A2 (30/10/2026)

Stesso box, ripristinato dall'istantanea della 3.1.9 inserita e portato alla 3.2.2 (3fe09496, wrapper 2.1.3 Flask) con un fast-forward di main, poi riavviato perché cron rilanciasse il lettore della pipe. Update lanciato dal pannello dev, che mostrava la 3.2.2.

Controllo Esito
Comando e pipe eseguito al primo tentativo
Codice e immagini 3fe09496 → 531e6552; installation:3.2.9 sha256:4661ae33…, frigate_wrapper:3.4.0 sha256:66f4ae00…, più dsniff
Configurazione invariata
Stato inserito invariato (wrapper 3.4.0 {"status": true}, /status disarm.status: false, config.yaml true, arm_state disarmed = 0, nessuno stato salvato nel wrapper)
Frigate non riavviato
Fermo API del box circa 8 s (ultimo 200 alle 14:31:32, di nuovo 200 alle 14:31:42, campionamento ogni 2 s); stato del wrapper non leggibile per due campioni. Le immagini erano già sul box dalla prova A1

Conclusione A1 e A2: l'update dentro la linea main porta un box Frigate 3.1.9 o 3.2.2 alla 3.2.9 con wrapper 3.4.0 senza perdere configurazione né stato inserito, con circa 8 s di fermo delle API e senza fermare il rilevamento. Restano da provare: un allarme vero dopo l'update, il percorso GANZ (A3).

Trovato: finestra di rilevamento attivo su un impianto disinserito dopo un riavvio di Frigate. Il wrapper 3.4.0 disinserisce solo a runtime (detect/set) e lascia config.yaml con detect.enabled: true. Al riavvio Frigate riparte dal file, quindi inserito, finché il reconciler non riapplica lo stato salvato. Misurato con Frigate riavviato a mano: inserito da +5 s a +13 s dal riavvio (circa 8 s); il controllo gira ogni DETECTION_RECONCILE_INTERVAL_SECONDS (30 s), quindi il caso peggiore è circa 30 s. In quella finestra un impianto disinserito rileva e può generare allarmi. Il caso opposto (inserito a runtime, config.yaml con false) lascia l'impianto non protetto per lo stesso tempo. Possibili correzioni nel wrapper: riapplicare lo stato salvato appena Frigate risponde, prima del primo ciclo; oppure scrivere anche config.yaml. Non verificato se in quella finestra gli eventi arrivano davvero al box e alla centrale.

Cosa succederebbe con l'update dopo l'unione in main

Si suppone che stage sia unito in main con un merge, così che il vecchio main resti un antenato del nuovo.

  1. git pull origin main: funziona. Il box è su un commit del vecchio main e il nuovo main lo contiene, quindi il pull avanza senza conflitti (salvo file modificati a mano sul box).
  2. L'avvio fallisce. L'updater della 3.2.x è già in memoria e continua con i passi vecchi:
  3. cerca docker-compose.yml e .env.build_tag nella radice, che nella nuova versione non ci sono;
  4. usa il profilo production, mentre la nuova versione usa prod;
  5. non installa uv, non scarica il sottomodulo e non legge il segreto AWS.

Il box resterebbe con il codice nuovo sul disco e i container vecchi in esecuzione: funziona ancora, ma a un riavvio o a un update successivo il risultato non è prevedibile. 3. Anche avviando a mano la nuova versione: - il progetto Compose si chiama installation e i nomi dei container sono gli stessi: se lo stack vecchio non viene spento prima, l'avvio fallisce per nomi già in uso; - la nuova versione non trova schema_versions e la migrazione iniziale scrive i valori di default (InitialMigration_001): mac vuoto, configured falso, detector di esempio, nessuno stream e stato disinserito. Il box si registrerebbe di nuovo sul midserver come un box nuovo. I file JSON restano sul disco ma non vengono letti. 4. Immagini di produzione vecchie: in stage il file infra/environments/prod/.env.build_tag indica ancora INTERNAL_BUILD_TAG=3.2.8 e WRAPPER_BUILD_TAG=3.3.0. Al rilascio va aggiornato con i tag della nuova release, altrimenti il compose nuovo avvierebbe le immagini vecchie.

La prima versione della migrazione iniziale (febbraio 2026) leggeva config.json e setting.json. La lettura è stata tolta il 27/02/2026 (commit 902c533f); la funzione _load_json_config è rimasta ma non è usata.

Cosa si conserva comunque: la 3.2.9 ha già installation.db nello stesso percorso (app/config/configuration_files/installation.db). La nuova versione lo apre, crea le tabelle che mancano e con la migrazione 002 aggiunge is_synced alla tabella logs: i log della 3.2.9 restano. La tabella arm_state resta nel file ma nessuno la legge più. La migrazione 003 (production → prod) agisce solo su una configurazione già presente nel database, quindi su un box 3.2.x non fa nulla.

Limiti dell'updater nuovo (mdm update)

L'updater della nuova versione (dev_scripts/commands/updater.py, chiamato da update.yml di stage) va bene da una nuova versione alla successiva, perché la configurazione è già nel database e le migrazioni partono all'avvio. Non basta per il passaggio dalla 3.2.x e oggi:

  • fa git pull, sottomoduli, login ECR e infra-cli run --operation <detector>-up, senza spegnere un progetto Compose diverso da installation;
  • non importa configurazioni e non controlla il risultato (versione, mac, stato inserito/disinserito, midserver);
  • non torna indietro se l'avvio fallisce: il box resta sul commit nuovo;
  • richiede uv in ~/.local/bin, che i box 3.2.x non hanno;
  • contiene la chiave AWS scritta nel codice.

Il commit b9dd8510 della stage ("fix: missing rollback") riguarda il rollback delle transazioni SQLite, non quello dell'update.

Soluzione ideale

Un box 3.2.x si aggiorna dal pannello come sempre e si ritrova sulla nuova versione di produzione, branch main e ambiente prod, con lo stesso codice impianto, la stessa configurazione, lo stesso stato inserito/disinserito e gli stessi log. Se qualcosa va storto torna da solo alla versione precedente. La 3.2.x non si modifica: tutto il necessario sta nella release unita in main.

  1. Import delle configurazioni nella nuova versione, valido per tutte le versioni dalla 3.1.9 in su: al primo avvio, se il database non ha configurazioni e nella cartella ci sono config.json/setting.json, la nuova versione li importa con la corrispondenza dei campi sopra. Imposta environment = prod e configured = true e conserva una copia dei file originali. Lo stesso import è disponibile come comando a secco (mdm db import-legacy).
  2. Stato inserito/disinserito: da arm_state se c'è (3.2.9). Altrimenti (3.1.9–3.2.2) lo chiede al detector, cioè al wrapper Frigate o al GANZ, prima di fermare lo stack vecchio. Se lo stato non si può leggere, il passaggio si ferma e torna alla versione precedente: meglio nessun update che un impianto disinserito senza saperlo.
  3. Wrapper: la nuova release porta il wrapper FastAPI; la sua configurazione (web_hooks) viene verificata e, se serve, convertita.

L'import supporta tutte le versioni dalla 3.1.9 come sicurezza. Se viene confermata la proposta P4 (portare prima tutti i box alla 3.2.9, T9.1), la migrazione parte sempre dalla 3.2.9; la decisione è aperta (A25). 2. Ponte per l'update della 3.2.x, cioè file nella radice del nuovo main usati una volta sola: - docker-compose.yml e .env.build_tag compatibili con l'updater vecchio, con il profilo production. Avviano la nuova immagine internal_server di produzione e gli stessi servizi di prima con gli stessi nomi, così --remove-orphans non spegne il rilevamento; - all'avvio la nuova versione riconosce di essere partita dal ponte, importa la configurazione (punto 1) e, attraverso la named pipe, esegue il passaggio alla struttura nuova: - installa uv se manca e scarica i sottomoduli; - spegne il progetto Compose vecchio; - avvia lo stack con infra-cli, ambiente prod; - controlli finali: stesso mac, stesso stato inserito/disinserito, telecamere, collegamento al midserver. Se uno fallisce, ripristina il commit e lo stack precedenti e lo segnala al midserver. 3. Release corretta: infra/environments/prod/.env.build_tag con i tag della nuova release; segreto AWS mdm/installation/prod corretto (M27). 4. Dopo il passaggio gli aggiornamenti usano l'update della nuova versione (mdm update, ambiente prod, branch main) e le migrazioni del database. I file del ponte si potranno togliere quando nessun box sarà più sulla 3.2.x.

Cosa dobbiamo fare

# Passo Perché Ticket
1 Riportare in stage le 42 correzioni di main (✅ i quattro comportamenti, committati entro il 30/10; restano /heartbeat e i test in laboratorio) non perdere correzioni già in produzione MDM-030
2 Import delle configurazioni 3.2.x nella nuova versione (automatico e a secco) — 🔄 committato in db444987; il 02/11 superati 17 test di import/gestore migrazioni e prova su copia del backup reale GANZ con stato acquisito dal detector (dettaglio sopra). Restano verifica dei campi non importati, compatibilità wrapper e log esistenti; PIN non più supportato senza, il box riparte non configurato e disinserito MDM-080
3 Chiarire pin.json e sound_url → horns due dati senza corrispondenza diretta MDM-080
4 Ponte per l'update della 3.2.x, con passaggio automatico alla struttura nuova e ripristino; controlli finali e ripristino anche in mdm update l'updater della 3.2.x non sa avviare la nuova versione, e mdm update non controlla né torna indietro MDM-081
5 Correggere il segreto AWS di produzione (M27) la nuova versione prende da lì gli indirizzi di MQTT, midserver e log MDM-031
6 Release candidata: unione di stage in main con un merge, tag di produzione in infra/environments/prod/.env.build_tag, matrice di compatibilità con il midserver i box ricevono solo versioni di produzione MDM-032
7 Prova in laboratorio con la release candidata, update dal pannello e ripristino, su: 3.1.9 GANZ, 3.1.9 o 3.2.2 Frigate (wrapper Flask), 3.2.9 Frigate; ognuno configurato e inserito coprire i tre formati di partenza presenti nel parco MDM-081
8 Censimento con SSH: versione realmente in esecuzione, modifiche a mano, stato dei 5 box senza API 5005 l'update parte dalla versione reale, non da quella dichiarata MDM-012, MDM-013
9 Riconciliare con l'inventario i 26 impianti che non rispondono e gli 8 Windows sapere quali aggiornare, quali reinstallare, quali dismettere MDM-011, T10.3, T9.3
10 Aggiornamento a ondate (T9.2) dopo il cambio di midserver; se P4 è confermata (A25) i box partono tutti dalla 3.2.9 (T9.1) con P4 una sola versione di partenza; senza, l'import copre già dalla 3.1.9 backlog

I passi 1–9 servono prima di toccare qualsiasi box dei clienti. Il passo 8 segue le regole di aggiornamento-box.md.

Nota per la prova del passo 7: il box di laboratorio deve essere in ambiente prod sul branch della release candidata, ma collegato al midserver dev. Come farlo senza usare il segreto di produzione va deciso nel ticket.