# Lab di test — canale MQTT scanner ⇆ cloud

Come portare il canale MQTT su una macchina di test in LAN **restando identici alla
produzione**, in modo che il passaggio test → prod cambi *una riga sola*.

Riferimento produzione: [DEPLOY-baremetal.md](DEPLOY-baremetal.md).

## Il trucco: stesso nome, SAN in piu'

Il bridge del device valida il certificato del broker (`bridge_insecure false`): se
l'indirizzo a cui si collega non compare nelle SAN del certificato, la connessione
cade. Quindi il **cert del broker di test viene emesso con due SAN**:

```
DNS:my.c4rgo.cloud , IP:<ip-macchina-di-test>
```

Cosi' lo stesso identico certificato/CA/ACL/utenti vale in entrambi gli ambienti e
l'unico delta sul device e' la riga `address` del bridge.

> Verificato empiricamente su mosquitto 2.0.21: con `IP:` nelle SAN il bridge in TLS
> verso un indirizzo IP valida correttamente; senza, fallisce. Non serve (e non va
> usato) `bridge_insecure true`.

### Perche' NON usare `/etc/hosts` sul Raspberry
Mappare `my.c4rgo.cloud` all'IP di test in `/etc/hosts` sembra piu' elegante, ma
dirotta **tutto** il traffico verso quel nome, non solo l'MQTT: registrazione
(`c4rgo-activate` → `POST /en/api/v1/reg`), upload log `c4rgo-mon` e il polling della
coda comandi legacy `comandi_exec` finirebbero sulla macchina di test, che sulla 443
non risponde. Meglio l'IP esplicito nel bridge.

## Delta test ↔ produzione (tutto il resto e' identico)

| | test | produzione |
|---|---|---|
| `address` nel bridge del device | `<ip-test>:8883` | `my.c4rgo.cloud:8883` |
| SAN del cert broker | `DNS:my.c4rgo.cloud,IP:<ip-test>` | `DNS:my.c4rgo.cloud` |
| `SCANNER_MQTT_PORT` in `.env.local` | `1883`, o altra se occupata | `1883` |
| password broker dei device | diverse (ambienti separati) | diverse |

CA, ACL, schema dei topic, utente `r_server`, unit systemd, comando
`app:scanner:listener`, colonna `mqtt_device_id`: **uguali**.

## 0. Prerequisito — chi occupa gia' la 1883?

Il worker usa un listener locale in chiaro sulla 1883. Su una macchina di sviluppo
quella porta e' spesso gia' presa (es. il broker docker del vecchio canale SIM7600).
Sulla macchina di test:

```bash
sudo ss -lntp | grep -E ':(1883|8883)\b'
docker ps --format '{{.Names}}\t{{.Ports}}' 2>/dev/null | grep 1883
```

- Nessun risultato → procedi con i default.
- Porta occupata → installa con `LOCAL_PORT=1884` e metti `SCANNER_MQTT_PORT=1884`
  in `.env.local`. La 8883 (quella che vedono i device) resta invariata.

### Convivenza con un broker mosquitto gia' presente

Se in `conf.d/` c'e' gia' un file (tipico su una macchina di sviluppo: il broker del
vecchio canale SIM7600), i due config finiscono nello **stesso processo mosquitto** e
condividono lo spazio dei topic. Due conseguenze da controllare.

**Ordine di caricamento.** I file di `conf.d/` si leggono in ordine alfabetico:
`c4rgo-scanner.conf` viene prima di `default.conf`. Il nostro file attiva
`per_listener_settings true`, che e' globale e cambia la semantica di tutto cio' che
segue. Se il config preesistente mette `allow_anonymous`/`password_file` **prima**
della sua riga `listener`, quelle opzioni non sono piu' globali: si applicano
all'ultimo listener definito, cioe' al *nostro* listener locale. Guasto silenzioso —
il broker parte, ma il worker si autentica contro il file di password sbagliato.
Verifica che il config preesistente dichiari il suo `listener` **come prima riga**:

```conf
listener 1883          # <- deve venire prima
allow_anonymous false
password_file /etc/mosquitto/passwd
```

**Isolamento dei topic.** Un listener preesistente con `password_file` ma senza
`acl_file` diventa una porta di servizio su tutti i topic degli scanner: chi ha una
credenziale legacy puo' fare `subscribe c4rgo/#` e *inviare comandi ai device*,
scavalcando l'ACL per-device. In lab e' tollerabile; in produzione non deve esistere
un secondo listener senza ACL. Se vuoi chiuderlo anche in lab, restringi
l'esposizione invece di aggiungere un `acl_file` (che e' default-deny e romperebbe il
canale legacy se non conosci i suoi topic):

```bash
sudo ufw delete allow 1883/tcp
sudo ufw allow from <lan>/24 to any port 1883 proto tcp
```

### Firewall

`ufw` con default `deny (incoming)` scarta i SYN invece di rifiutarli: una 8883
bloccata e una 8883 senza listener sono indistinguibili da fuori (entrambe vanno in
timeout). Apri la porta e ricontrolla prima di sospettare il broker:

```bash
sudo ufw allow 8883/tcp comment 'MQTT TLS scanner c4rgo'
sudo ss -lntp | grep -E ':(8883|1884)\b'      # conferma che i listener ci siano
```

## 1. CA sulla macchina di test

Serve la coppia **C4rgo-CA** (`ca.crt` + `ca.key`), la stessa che i device gia'
fidano. Dal Raspberry:

```bash
sudo cp /opt/.c4rgo/etc/certs/ca.crt /opt/.c4rgo/etc/certs/ca.key /tmp/ && sudo chown "$USER" /tmp/ca.*
scp /tmp/ca.crt /tmp/ca.key <utente>@<ip-test>:/tmp/
shred -u /tmp/ca.key            # non lasciarla in giro sul Raspberry
```

> `ca.key` firma i certificati di tutta la flotta: sulla macchina di test tienila
> `chmod 600` e cancellala appena il broker e' installato (lo script la copia in
> `/etc/mosquitto/certs/`, dove serve solo se rigeneri il cert).

## 2. Broker sulla macchina di test

```bash
cd <path-progetto>/deploy/mqtt-baremetal
sudo EXTRA_SAN=IP:<ip-test> ./install-broker-baremetal.sh /tmp/ca.crt /tmp/ca.key
#   ...e LOCAL_PORT=1884 in testa al comando se la 1883 era occupata
```

Controlla che le SAN siano quelle attese e che i listener siano su:

```bash
sudo openssl x509 -in /etc/mosquitto/certs/my.c4rgo.cloud.crt -noout -ext subjectAltName
sudo ss -lntp | grep mosquitto
```

Lo script stampa la password di `r_server`: **annotala**, serve al passo 4.

## 3. Utente broker per il device

```bash
sudo mosquitto_passwd -b /etc/mosquitto/c4rgo-scanner.passwd kiosk_36b94b28 '<password-device-test>'
sudo systemctl reload mosquitto
```

## 4. App di test — `.env.local`

Nel progetto c4rgocloud sulla macchina di test (la webapp resta sulla sua porta,
l'MQTT non la tocca):

```dotenv
SCANNER_MQTT_HOST=127.0.0.1
SCANNER_MQTT_PORT=1883          # oppure 1884 se hai usato LOCAL_PORT
SCANNER_MQTT_PASSWORD=<password r_server dal passo 2>
```

DB di test: assicurati che la macchina abbia la colonna e il mapping.

```bash
mysql -u<user> -p <db_test> < docker/mqtt/ensure-mqtt-schema.sql
mysql -u<user> -p <db_test> -e "UPDATE macchine SET mqtt_device_id='kiosk_36b94b28' WHERE id=131;"
```

## 5. Worker

In lab conviene tenerlo in primo piano, si legge tutto:

```bash
php bin/console cache:clear
php bin/console app:scanner:listener -vv
```

Quando il canale funziona, installa la unit systemd come in produzione — cosi'
provi anche quel pezzo.

## 6. Bridge sul Raspberry

> **mosquitto non ha commenti a fine riga.** Il `#` e' un commento solo a inizio
> riga. Su `remote_password` il parser prende tutto il resto della riga, quindi un
> commento in coda entra nella password e il bridge fallisce con
> `Connection Refused: not authorised` pur avendo credenziali corrette — mentre
> `mosquitto_sub` con le stesse credenziali funziona, il che manda fuori strada.
> Su `address` e sui booleani non si nota (viene usato solo il primo token).

```bash
sudo install -m 600 -o root -g root c4rgo-cloud-bridge.conf /etc/mosquitto/conf.d/
sudo systemctl restart mosquitto
mosquitto_sub -h 127.0.0.1 -p 1883 -t '$SYS/broker/connection/+/state' -C 1   # deve dare 1
```

> Un solo file bridge alla volta in `conf.d/`: due `connection` con lo stesso nome
> (o due bridge sugli stessi topic) confliggono. Per tornare in produzione si
> **modifica** questo file, non se ne aggiunge un secondo.

## 7. Verifica end-to-end

Dalla macchina di test, come `r_server` sul listener locale:

```bash
python3 tools/c4rgo_scanner_probe.py --host 127.0.0.1 --port 1883 --no-tls \
  --username r_server --password '<pwd>' --device-id kiosk_36b94b28 ping
python3 tools/c4rgo_scanner_probe.py --host 127.0.0.1 --port 1883 --no-tls \
  --username r_server --password '<pwd>' --device-id kiosk_36b94b28 status modem
```

Deve arrivare anche la telemetria spontanea (`c4rgo/kiosk_36b94b28/modem/info` ogni
60 s, retained):

```bash
mosquitto_sub -h 127.0.0.1 -p 1883 -u r_server -P '<pwd>' -t 'c4rgo/#' -v
```

## 8. Passaggio test → produzione

Sul Raspberry, in `/etc/mosquitto/conf.d/c4rgo-cloud-bridge.conf`:

```diff
-address 192.168.0.80:8883
+address my.c4rgo.cloud:8883
 ...
-remote_password <password-di-test>
+remote_password <password-di-produzione>
```

`systemctl restart mosquitto` e il device parla col cloud. Nient'altro cambia lato
device. Sul server di produzione si esegue lo stesso `install-broker-baremetal.sh`
**senza** `EXTRA_SAN`.
