436 lines
10 KiB
Markdown
436 lines
10 KiB
Markdown
# MQTT Broker Mosquitto on Debian 13
|
|
|
|
Panduan operasional untuk menyiapkan broker MQTT awal platform QRIS Soundbox di Debian 13 dengan subdomain `broker.bizone.id`.
|
|
|
|
Keputusan arsitektur terkait:
|
|
- `D-026`: broker MQTT sungguhan ditunda sampai infrastruktur siap; simulator/outbox tetap dipakai selama transisi.
|
|
- `D-027`: broker awal memakai Mosquitto, dengan kontrak topic dan adapter backend tetap migration-ready ke EMQX/managed MQTT.
|
|
|
|
## Target Setup
|
|
|
|
- Broker: Eclipse Mosquitto.
|
|
- Domain: `broker.bizone.id`.
|
|
- MQTT TLS publik: `8883/tcp`.
|
|
- MQTT non-TLS pilot: `1883/tcp`, default disarankan local-only; boleh dibuka publik sementara jika firmware device belum support SSL/TLS.
|
|
- TLS: Let's Encrypt.
|
|
- Auth: username/password.
|
|
- Authorization: ACL topic per user/device.
|
|
- Anonymous access: disabled.
|
|
|
|
## DNS dan Paket
|
|
|
|
Pastikan DNS `broker.bizone.id` sudah mengarah ke public IP server.
|
|
|
|
```bash
|
|
dig +short broker.bizone.id
|
|
curl -4 ifconfig.me
|
|
```
|
|
|
|
Install paket:
|
|
|
|
```bash
|
|
sudo apt update
|
|
sudo apt install -y mosquitto mosquitto-clients certbot ufw
|
|
sudo systemctl enable --now mosquitto
|
|
```
|
|
|
|
## Firewall
|
|
|
|
```bash
|
|
sudo ufw default deny incoming
|
|
sudo ufw default allow outgoing
|
|
|
|
sudo ufw allow OpenSSH
|
|
sudo ufw allow 80/tcp
|
|
sudo ufw allow 8883/tcp
|
|
|
|
sudo ufw enable
|
|
sudo ufw status verbose
|
|
```
|
|
|
|
Default paling aman: jangan buka `1883/tcp` ke internet. Listener `1883` cukup untuk localhost/internal test.
|
|
|
|
Jika firmware device belum support SSL/TLS dan harus memakai MQTT non-SSL, port `1883/tcp` boleh dibuka untuk pilot dengan risiko credential MQTT lewat clear-text. Pastikan:
|
|
|
|
- `allow_anonymous false`;
|
|
- password kuat dan unik;
|
|
- ACL aktif;
|
|
- tidak memakai credential admin/backend untuk device;
|
|
- segera pindah ke `8883` setelah firmware support TLS.
|
|
|
|
Untuk pilot public non-TLS:
|
|
|
|
```bash
|
|
sudo ufw allow 1883/tcp
|
|
sudo ufw status verbose
|
|
```
|
|
|
|
Jika sumber IP device bisa diprediksi, batasi firewall:
|
|
|
|
```bash
|
|
sudo ufw allow from <DEVICE_OR_NAT_PUBLIC_IP> to any port 1883 proto tcp
|
|
```
|
|
|
|
## Sertifikat TLS
|
|
|
|
Ambil sertifikat Let's Encrypt:
|
|
|
|
```bash
|
|
sudo certbot certonly --standalone -d broker.bizone.id
|
|
```
|
|
|
|
Copy sertifikat ke lokasi yang bisa dibaca Mosquitto:
|
|
|
|
```bash
|
|
sudo install -d -o root -g mosquitto -m 750 /etc/mosquitto/certs
|
|
|
|
sudo install -o root -g mosquitto -m 640 \
|
|
/etc/letsencrypt/live/broker.bizone.id/fullchain.pem \
|
|
/etc/mosquitto/certs/fullchain.pem
|
|
|
|
sudo install -o root -g mosquitto -m 640 \
|
|
/etc/letsencrypt/live/broker.bizone.id/privkey.pem \
|
|
/etc/mosquitto/certs/privkey.pem
|
|
```
|
|
|
|
Buat renewal hook:
|
|
|
|
```bash
|
|
sudo nano /etc/letsencrypt/renewal-hooks/deploy/mosquitto-cert-copy.sh
|
|
```
|
|
|
|
Isi:
|
|
|
|
```bash
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
DOMAIN="broker.bizone.id"
|
|
|
|
install -o root -g mosquitto -m 640 \
|
|
"/etc/letsencrypt/live/${DOMAIN}/fullchain.pem" \
|
|
/etc/mosquitto/certs/fullchain.pem
|
|
|
|
install -o root -g mosquitto -m 640 \
|
|
"/etc/letsencrypt/live/${DOMAIN}/privkey.pem" \
|
|
/etc/mosquitto/certs/privkey.pem
|
|
|
|
systemctl reload mosquitto
|
|
```
|
|
|
|
Aktifkan:
|
|
|
|
```bash
|
|
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/mosquitto-cert-copy.sh
|
|
```
|
|
|
|
## User dan Password
|
|
|
|
Buat user backend dan device test:
|
|
|
|
```bash
|
|
sudo mosquitto_passwd -c /etc/mosquitto/passwd qris-backend
|
|
sudo mosquitto_passwd /etc/mosquitto/passwd DEVICE_UUID_FROM_PLATFORM
|
|
sudo chown root:mosquitto /etc/mosquitto/passwd
|
|
sudo chmod 640 /etc/mosquitto/passwd
|
|
```
|
|
|
|
Rekomendasi:
|
|
- `qris-backend` dipakai backend platform.
|
|
- Username device memakai `device_id` UUID dari platform, supaya ACL `pattern` bisa mengikat topic `devices/{deviceId}/...` ke user/device.
|
|
|
|
## ACL Topic
|
|
|
|
Buat file ACL:
|
|
|
|
```bash
|
|
sudo nano /etc/mosquitto/acl
|
|
```
|
|
|
|
Isi awal:
|
|
|
|
```conf
|
|
user qris-backend
|
|
topic readwrite devices/#
|
|
topic readwrite soundbox/#
|
|
|
|
pattern write devices/%u/uplink/#
|
|
pattern read devices/%u/downlink/#
|
|
pattern write devices/%u/heartbeat
|
|
pattern read soundbox/%u/down
|
|
```
|
|
|
|
Untuk firmware QF100 sample saat ini, config server mengembalikan topic berbasis serial number:
|
|
|
|
```text
|
|
soundbox/{dev-sn}/down
|
|
```
|
|
|
|
Jika masih memakai user MQTT bersama `qris-backend` untuk pilot, rule `topic readwrite soundbox/#` wajib ada. Jika nanti per-device credential memakai username sama dengan `dev-sn`, rule `pattern read soundbox/%u/down` bisa dipakai untuk membatasi tiap device hanya membaca topic miliknya sendiri.
|
|
|
|
Permission:
|
|
|
|
```bash
|
|
sudo chown root:mosquitto /etc/mosquitto/acl
|
|
sudo chmod 640 /etc/mosquitto/acl
|
|
```
|
|
|
|
## Konfigurasi Mosquitto
|
|
|
|
Buat file:
|
|
|
|
```bash
|
|
sudo nano /etc/mosquitto/conf.d/qris.conf
|
|
```
|
|
|
|
Isi minimal:
|
|
|
|
```conf
|
|
per_listener_settings true
|
|
|
|
listener 8883 0.0.0.0
|
|
protocol mqtt
|
|
allow_anonymous false
|
|
password_file /etc/mosquitto/passwd
|
|
acl_file /etc/mosquitto/acl
|
|
certfile /etc/mosquitto/certs/fullchain.pem
|
|
keyfile /etc/mosquitto/certs/privkey.pem
|
|
|
|
listener 1883 127.0.0.1
|
|
protocol mqtt
|
|
allow_anonymous false
|
|
password_file /etc/mosquitto/passwd
|
|
acl_file /etc/mosquitto/acl
|
|
```
|
|
|
|
Untuk device yang belum support SSL/TLS dan harus connect dari internet, ubah listener `1883` menjadi publik:
|
|
|
|
```conf
|
|
listener 1883 0.0.0.0
|
|
protocol mqtt
|
|
allow_anonymous false
|
|
password_file /etc/mosquitto/passwd
|
|
acl_file /etc/mosquitto/acl
|
|
```
|
|
|
|
Jangan jalankan dua listener `1883` sekaligus. Pilih salah satu:
|
|
|
|
- `listener 1883 127.0.0.1` untuk local-only;
|
|
- `listener 1883 0.0.0.0` untuk pilot public non-TLS.
|
|
|
|
Catatan Debian:
|
|
- Jangan set ulang `persistence`, `persistence_location`, `log_dest`, atau `log_type` di `conf.d/qris.conf` jika sudah ada di `/etc/mosquitto/mosquitto.conf`.
|
|
- Jika muncul error `Duplicate persistence_location value`, hapus `persistence` dan `persistence_location` dari `qris.conf`.
|
|
- Jika muncul error `Duplicate "log_dest file" value`, hapus blok logging dari `qris.conf`.
|
|
|
|
Test config:
|
|
|
|
```bash
|
|
sudo mosquitto -c /etc/mosquitto/mosquitto.conf -v
|
|
```
|
|
|
|
Jika tidak ada error, tekan `Ctrl+C`, lalu restart service:
|
|
|
|
```bash
|
|
sudo systemctl restart mosquitto
|
|
sudo systemctl status mosquitto --no-pager
|
|
```
|
|
|
|
Pastikan listener terbuka:
|
|
|
|
```bash
|
|
sudo ss -lntp | grep mosquitto
|
|
```
|
|
|
|
Expected:
|
|
- `0.0.0.0:8883`
|
|
- `127.0.0.1:1883` untuk local-only, atau `0.0.0.0:1883` untuk pilot public non-TLS.
|
|
|
|
## Test Publish Subscribe
|
|
|
|
Terminal 1, subscribe sebagai backend:
|
|
|
|
```bash
|
|
mosquitto_sub \
|
|
-h broker.bizone.id \
|
|
-p 8883 \
|
|
-u qris-backend \
|
|
-P 'PASSWORD_BACKEND' \
|
|
-t 'devices/DEVICE_UUID_FROM_PLATFORM/uplink/#' \
|
|
-v
|
|
```
|
|
|
|
Terminal 2, publish sebagai device:
|
|
|
|
```bash
|
|
mosquitto_pub \
|
|
-h broker.bizone.id \
|
|
-p 8883 \
|
|
-u DEVICE_UUID_FROM_PLATFORM \
|
|
-P 'PASSWORD_DEVICE' \
|
|
-t 'devices/DEVICE_UUID_FROM_PLATFORM/uplink/dynamic-qr/request' \
|
|
-m '{"request_id":"test-001","amount":10000}'
|
|
```
|
|
|
|
Test ACL negatif:
|
|
|
|
```bash
|
|
mosquitto_pub \
|
|
-h broker.bizone.id \
|
|
-p 8883 \
|
|
-u DEVICE_UUID_FROM_PLATFORM \
|
|
-P 'PASSWORD_DEVICE' \
|
|
-t 'devices/OTHER_DEVICE_UUID/uplink/dynamic-qr/request' \
|
|
-m '{}'
|
|
```
|
|
|
|
Pesan ke topic device lain harus ditolak atau tidak sampai ke subscriber.
|
|
|
|
## Test Non-TLS 1883
|
|
|
|
Jika port `1883` dibuka untuk device non-SSL, test tanpa parameter TLS:
|
|
|
|
Terminal 1, subscribe sebagai backend:
|
|
|
|
```bash
|
|
mosquitto_sub \
|
|
-h broker.bizone.id \
|
|
-p 1883 \
|
|
-u qris-backend \
|
|
-P 'PASSWORD_BACKEND' \
|
|
-t 'devices/DEVICE_UUID_FROM_PLATFORM/uplink/#' \
|
|
-v
|
|
```
|
|
|
|
Terminal 2, publish sebagai device:
|
|
|
|
```bash
|
|
mosquitto_pub \
|
|
-h broker.bizone.id \
|
|
-p 1883 \
|
|
-u DEVICE_UUID_FROM_PLATFORM \
|
|
-P 'PASSWORD_DEVICE' \
|
|
-t 'devices/DEVICE_UUID_FROM_PLATFORM/uplink/dynamic-qr/request' \
|
|
-m '{"request_id":"test-1883","amount":10000}'
|
|
```
|
|
|
|
Jika device memakai config server `/speaker/dev-config`, set app server agar response MQTT ke device memakai port 1883:
|
|
|
|
```env
|
|
QF100_MQTT_BROKER_HOST=broker.bizone.id
|
|
QF100_MQTT_BROKER_PORT=1883
|
|
QF100_MQTT_USERNAME=qris-backend
|
|
QF100_MQTT_PASSWORD=...
|
|
```
|
|
|
|
Response config device akan mengirim topic:
|
|
|
|
```json
|
|
{
|
|
"mqtt": {
|
|
"client-id": "soundbox-DEVICE_SN",
|
|
"subscribe-topic": "soundbox/DEVICE_SN/down",
|
|
"publish-topic": "soundbox/DEVICE_SN/up"
|
|
}
|
|
}
|
|
```
|
|
|
|
Jika firmware tidak bisa resolve domain, isi `QF100_MQTT_BROKER_HOST` dengan IP broker:
|
|
|
|
```bash
|
|
dig +short broker.bizone.id
|
|
```
|
|
|
|
## Monitoring
|
|
|
|
```bash
|
|
sudo journalctl -u mosquitto -f
|
|
sudo tail -f /var/log/mosquitto/mosquitto.log
|
|
sudo ss -lntp | grep mosquitto
|
|
```
|
|
|
|
Test renewal TLS:
|
|
|
|
```bash
|
|
sudo certbot renew --dry-run
|
|
```
|
|
|
|
## Environment Backend Nanti
|
|
|
|
Saat adapter broker sungguhan dipasang ke platform:
|
|
|
|
```env
|
|
MQTT_PUBLISH_MODE=broker
|
|
MQTT_BROKER_URL=mqtts://broker.bizone.id:8883
|
|
MQTT_USERNAME=qris-backend
|
|
MQTT_PASSWORD=...
|
|
MQTT_CLIENT_ID=qris-platform-backend
|
|
MQTT_CONNECT_TIMEOUT_MS=5000
|
|
MQTT_TLS=true
|
|
```
|
|
|
|
Backend sebaiknya tetap memakai TLS `8883`. Untuk device non-SSL, cukup ubah env khusus response config device:
|
|
|
|
```env
|
|
QF100_MQTT_BROKER_HOST=broker.bizone.id
|
|
QF100_MQTT_BROKER_PORT=1883
|
|
```
|
|
|
|
Topic kontrak yang harus dipertahankan:
|
|
|
|
```text
|
|
devices/{deviceId}/uplink/dynamic-qr/request
|
|
devices/{deviceId}/downlink/dynamic-qr/response
|
|
devices/{deviceId}/downlink/payment/success
|
|
devices/{deviceId}/downlink/config/push
|
|
devices/{deviceId}/uplink/config/ack
|
|
devices/{deviceId}/heartbeat
|
|
soundbox/{dev-sn}/down
|
|
soundbox/{dev-sn}/up
|
|
```
|
|
|
|
## Provisioning Credential Device
|
|
|
|
Credential MQTT device dibuat dari aplikasi, lalu password satu kali tersebut dimasukkan ke Mosquitto. Jalankan backend platform lebih dulu, lalu rotate credential:
|
|
|
|
```bash
|
|
ADMIN_TOKEN=admin-dev-token \
|
|
npm run mqtt:provision-device -- \
|
|
--base-url http://127.0.0.1:3000 \
|
|
--device-id DEVICE_UUID_FROM_PLATFORM
|
|
```
|
|
|
|
Output berisi:
|
|
- `mqtt_username`: sama dengan `device_id`.
|
|
- `mqtt_password`: secret satu kali untuk dimasukkan ke broker dan device.
|
|
- `mosquitto_commands`: perintah yang bisa dijalankan di server broker.
|
|
|
|
Jika script dijalankan langsung di host broker:
|
|
|
|
```bash
|
|
ADMIN_TOKEN=admin-dev-token \
|
|
npm run mqtt:provision-device -- \
|
|
--base-url http://127.0.0.1:3000 \
|
|
--device-id DEVICE_UUID_FROM_PLATFORM \
|
|
--apply-local
|
|
```
|
|
|
|
Setelah password dimasukkan, reload Mosquitto:
|
|
|
|
```bash
|
|
sudo systemctl reload mosquitto
|
|
```
|
|
|
|
Catatan penting:
|
|
- Platform hanya menyimpan fingerprint secret, bukan plaintext password.
|
|
- Jika password hilang, lakukan rotate ulang dan update device + broker.
|
|
- ACL `pattern` bergantung pada username yang sama dengan `device_id`; jangan mengganti username device menjadi `device_code` kecuali kontrak topic/ACL ikut diubah.
|
|
|
|
## Migration Ready Notes
|
|
|
|
- Jangan mengunci business logic di fitur Mosquitto-specific.
|
|
- Credential device tetap dikelola domain platform.
|
|
- Topic tetap `devices/{deviceId}/...`.
|
|
- `mqtt_messages` tetap menjadi outbox/trace awal di aplikasi.
|
|
- Benchmark sebelum milestone 20k, 50k, dan 100k connected device.
|