diff --git a/packages/secubox-meshtastic/conf/mosquitto-secubox-meshtastic.conf b/packages/secubox-meshtastic/conf/mosquitto-secubox-meshtastic.conf new file mode 100644 index 00000000..436896a7 --- /dev/null +++ b/packages/secubox-meshtastic/conf/mosquitto-secubox-meshtastic.conf @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: LicenseRef-CMSD-1.0 +# Copyright (c) 2026 CyberMind — Gérald Kerma +# +# SecuBox-Deb :: secubox-meshtastic :: private "shared_grid" broker. +# +# Installed as /etc/mosquitto/conf.d/secubox-meshtastic.conf. This listener +# federates the "shared_grid" Meshtastic channel(s) between SecuBox nodes +# over the encrypted MirrorNet WireGuard mesh ONLY — it is bound to this +# node's mesh IP (10.10.0.1), never to 0.0.0.0/:: (see +# conf/meshtastic.toml.example: [shared_grid] broker = "10.10.0.1:1883"). +# The "on_grid" bridge (public/community MQTT, e.g. mqtt.example.org) is a +# separate, independent client connection made BY this daemon and is not +# affected by this listener. +# +# This file ships the topology only. Mosquitto itself is NOT started by the +# secubox-meshtastic package (it's a shared system daemon other modules may +# also use) — the operator must: +# 1. apt install mosquitto mosquitto-clients +# 2. provision credentials: mosquitto_passwd -c /etc/mosquitto/secubox-meshtastic.passwd +# 3. write /etc/mosquitto/secubox-meshtastic.acl (topic ACLs per node/user) +# 4. systemctl enable --now mosquitto + +# Replace the default any-interface listener for this purpose: bind ONLY to +# the mesh address, on the standard MQTT port. +listener 1883 10.10.0.1 + +# No anonymous publish/subscribe on the mesh listener. +allow_anonymous false + +# Password file — created by the operator (step 2 above). Left commented so +# a fresh install fails closed (mosquitto refuses to start rather than +# silently allowing anonymous access) until the operator provisions it. +#password_file /etc/mosquitto/secubox-meshtastic.passwd + +# Optional per-topic ACL (recommended once multiple nodes/users share the +# grid) — created by the operator (step 3 above). +#acl_file /etc/mosquitto/secubox-meshtastic.acl diff --git a/packages/secubox-meshtastic/debian/secubox-meshtasticd.service b/packages/secubox-meshtastic/debian/secubox-meshtasticd.service new file mode 100644 index 00000000..ed0a5c37 --- /dev/null +++ b/packages/secubox-meshtastic/debian/secubox-meshtasticd.service @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: LicenseRef-CMSD-1.0 +# Copyright (c) 2026 CyberMind — Gérald Kerma +# Installed as /etc/systemd/system/secubox-meshtasticd.service — own socket, +# own service (never served by the aggregator: a radio/serial-attached daemon +# must not depend on something it may later restart, and the aggregator loop +# must not block on serial I/O). +[Unit] +Description=SecuBox Meshtastic — LoRa mesh node daemon (radio/cache/passive/bridge) +After=network.target secubox-core.service +Wants=secubox-core.service + +[Service] +Type=simple +User=secubox-meshtastic +Group=secubox-meshtastic +# Serial device access (/dev/ttyUSB*, /dev/ttyACM*) is granted via the +# dialout group PLUS the explicit DeviceAllow rules below (belt+braces: +# some udev rulesets restrict ttyUSB/ttyACM perms to group dialout only, +# others rely on the cgroup device controller). +SupplementaryGroups=dialout +WorkingDirectory=/usr/lib/secubox/meshtastic +ExecStart=/usr/sbin/secubox-meshtasticd +Restart=on-failure +RestartSec=5 +UMask=0007 + +# NB (ref #896): do NOT set `RuntimeDirectory=secubox` here. Any unit that +# declares RuntimeDirectory=secubox has systemd chown the SHARED /run/secubox +# directory to *this unit's* user on every (re)start — which breaks every +# other secubox-* service's socket bind the next time this unit restarts. +# /run/secubox is provided 1777 root:root by tmpfiles (secubox-core), i.e. +# world-writable + sticky, so this daemon creates its own +# /run/secubox/meshtastic.sock there directly, with no RuntimeDirectory at +# all. If a private scratch dir is ever needed, use +# `RuntimeDirectory=secubox-meshtastic` (this unit's OWN dir) — never +# `=secubox`. +NoNewPrivileges=yes + +ProtectSystem=strict +ProtectHome=yes +PrivateTmp=yes +ReadWritePaths=/run/secubox /var/cache/secubox/meshtastic /var/log/secubox/meshtastic + +# Serial radio access — PrivateDevices=yes would hide /dev/ttyUSB*/ttyACM* +# entirely, so it is deliberately NOT set here. +DeviceAllow=char-ttyUSB rw +DeviceAllow=char-ttyACM rw + +[Install] +WantedBy=multi-user.target diff --git a/packages/secubox-meshtastic/menu.d/71-meshtastic.json b/packages/secubox-meshtastic/menu.d/71-meshtastic.json new file mode 100644 index 00000000..8a0b4b19 --- /dev/null +++ b/packages/secubox-meshtastic/menu.d/71-meshtastic.json @@ -0,0 +1,9 @@ +{ + "id": "meshtastic", + "name": "Meshtastic", + "category": "mesh", + "icon": "📡", + "path": "/meshtastic/", + "order": 71, + "description": "Meshtastic LoRa mesh node — radio status, channels and grid bridging" +} diff --git a/packages/secubox-meshtastic/nginx/meshtastic.conf b/packages/secubox-meshtastic/nginx/meshtastic.conf new file mode 100644 index 00000000..743d993f --- /dev/null +++ b/packages/secubox-meshtastic/nginx/meshtastic.conf @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: LicenseRef-CMSD-1.0 +# Copyright (c) 2026 CyberMind — Gérald Kerma +# /etc/nginx/secubox-routes.d/meshtastic.conf — installé par secubox-meshtastic. +# +# Ce répertoire est celui que le vhost admin inclut réellement +# (`include /etc/nginx/secubox-routes.d/*.conf;`). + +# Panel (statique, servi par le vhost admin). +location /meshtastic/ { + alias /usr/share/secubox/www/meshtastic/; + try_files $uri $uri/ /meshtastic/index.html; +} + +# API — socket DÉDIÉE, jamais l'aggregator : le daemon radio/série ne doit +# pas dépendre de ce qu'il est susceptible de redémarrer, et la boucle de +# l'aggregator ne doit pas bloquer sur de l'I/O série (cf. api/daemon.py). +# +# PAS de `:/` en fin de proxy_pass : l'app enregistre le chemin COMPLET +# (@app.get("/api/v1/meshtastic/...")), donc le préfixe doit être préservé. +# Le stripper renverrait un chemin tronqué à une app qui ne connaît que +# /api/v1/meshtastic/... → 404. +location /api/v1/meshtastic/ { + proxy_pass http://unix:/run/secubox/meshtastic.sock; + include /etc/nginx/snippets/secubox-proxy.conf; +} diff --git a/packages/secubox-meshtastic/systemd/secubox-meshtasticd.service b/packages/secubox-meshtastic/systemd/secubox-meshtasticd.service new file mode 100644 index 00000000..ed0a5c37 --- /dev/null +++ b/packages/secubox-meshtastic/systemd/secubox-meshtasticd.service @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: LicenseRef-CMSD-1.0 +# Copyright (c) 2026 CyberMind — Gérald Kerma +# Installed as /etc/systemd/system/secubox-meshtasticd.service — own socket, +# own service (never served by the aggregator: a radio/serial-attached daemon +# must not depend on something it may later restart, and the aggregator loop +# must not block on serial I/O). +[Unit] +Description=SecuBox Meshtastic — LoRa mesh node daemon (radio/cache/passive/bridge) +After=network.target secubox-core.service +Wants=secubox-core.service + +[Service] +Type=simple +User=secubox-meshtastic +Group=secubox-meshtastic +# Serial device access (/dev/ttyUSB*, /dev/ttyACM*) is granted via the +# dialout group PLUS the explicit DeviceAllow rules below (belt+braces: +# some udev rulesets restrict ttyUSB/ttyACM perms to group dialout only, +# others rely on the cgroup device controller). +SupplementaryGroups=dialout +WorkingDirectory=/usr/lib/secubox/meshtastic +ExecStart=/usr/sbin/secubox-meshtasticd +Restart=on-failure +RestartSec=5 +UMask=0007 + +# NB (ref #896): do NOT set `RuntimeDirectory=secubox` here. Any unit that +# declares RuntimeDirectory=secubox has systemd chown the SHARED /run/secubox +# directory to *this unit's* user on every (re)start — which breaks every +# other secubox-* service's socket bind the next time this unit restarts. +# /run/secubox is provided 1777 root:root by tmpfiles (secubox-core), i.e. +# world-writable + sticky, so this daemon creates its own +# /run/secubox/meshtastic.sock there directly, with no RuntimeDirectory at +# all. If a private scratch dir is ever needed, use +# `RuntimeDirectory=secubox-meshtastic` (this unit's OWN dir) — never +# `=secubox`. +NoNewPrivileges=yes + +ProtectSystem=strict +ProtectHome=yes +PrivateTmp=yes +ReadWritePaths=/run/secubox /var/cache/secubox/meshtastic /var/log/secubox/meshtastic + +# Serial radio access — PrivateDevices=yes would hide /dev/ttyUSB*/ttyACM* +# entirely, so it is deliberately NOT set here. +DeviceAllow=char-ttyUSB rw +DeviceAllow=char-ttyACM rw + +[Install] +WantedBy=multi-user.target