From 89896568b18318415150b01f115a260455c811ea Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Wed, 4 Feb 2026 21:32:17 +0100 Subject: [PATCH] feat(domoticz): Add LuCI dashboard with MQTT auto-bridge and Zigbee2MQTT integration New luci-app-domoticz package with RPCD handler (12 methods), LuCI overview (status, IoT integration, MQTT, HAProxy, mesh, logs), and full service lifecycle. Enhanced domoticzctl with configure-mqtt (auto Mosquitto+Z2M bridge), configure-haproxy, backup/restore, mesh-register, and uninstall commands. UCI extended with mqtt/network/mesh sections. Catalog updated with LuCI package and IoT tags. MirrorNetworking strategic document noted in planning files. Co-Authored-By: Claude Opus 4.5 --- .claude/HISTORY.md | 1 + .claude/TODO.md | 22 + .claude/WIP.md | 24 +- package/secubox/luci-app-domoticz/Makefile | 29 ++ package/secubox/luci-app-domoticz/README.md | 57 +++ .../resources/view/domoticz/overview.js | 456 ++++++++++++++++++ .../root/usr/libexec/rpcd/luci.domoticz | 237 +++++++++ .../share/luci/menu.d/luci-app-domoticz.json | 14 + .../share/rpcd/acl.d/luci-app-domoticz.json | 24 + package/secubox/secubox-app-domoticz/Makefile | 2 +- .../secubox/secubox-app-domoticz/README.md | 65 ++- .../files/etc/config/domoticz | 15 + .../files/usr/sbin/domoticzctl | 260 +++++++++- .../root/usr/share/secubox/catalog.json | 36 +- 14 files changed, 1198 insertions(+), 44 deletions(-) create mode 100644 package/secubox/luci-app-domoticz/Makefile create mode 100644 package/secubox/luci-app-domoticz/README.md create mode 100644 package/secubox/luci-app-domoticz/htdocs/luci-static/resources/view/domoticz/overview.js create mode 100644 package/secubox/luci-app-domoticz/root/usr/libexec/rpcd/luci.domoticz create mode 100644 package/secubox/luci-app-domoticz/root/usr/share/luci/menu.d/luci-app-domoticz.json create mode 100644 package/secubox/luci-app-domoticz/root/usr/share/rpcd/acl.d/luci-app-domoticz.json diff --git a/.claude/HISTORY.md b/.claude/HISTORY.md index 73d7d45b..f80f8743 100644 --- a/.claude/HISTORY.md +++ b/.claude/HISTORY.md @@ -136,3 +136,4 @@ _Last updated: 2026-02-04_ - `zigbee2mqtt`: Direct `/dev/ttyUSB0` passthrough (socat TCP bridge fails ASH protocol), adapter `ezsp`→`ember` (z2m 2.x), `ZIGBEE2MQTT_DATA` env var, `mosquitto-nossl` dependency. - `smbfs`: New SMB/CIFS remote mount manager package — UCI config, `smbfsctl` CLI (add/remove/mount/umount/test/status), auto-mount init script, credentials storage, Jellyfin+Lyrion integration, catalog entry. - `jellyfin`: KISS READMEs for both backend and LuCI packages. + - `domoticz`: New `luci-app-domoticz` LuCI dashboard with IoT integration status (Mosquitto, Zigbee2MQTT, MQTT bridge), service lifecycle, HAProxy, mesh P2P, logs. `domoticzctl` enhanced with `configure-mqtt` (auto Mosquitto+Z2M bridge), `configure-haproxy`, `backup/restore`, `mesh-register`, `uninstall`. UCI extended with mqtt/network/mesh sections. Catalog updated. diff --git a/.claude/TODO.md b/.claude/TODO.md index 978b0c49..d8cb5c04 100644 --- a/.claude/TODO.md +++ b/.claude/TODO.md @@ -105,3 +105,25 @@ _Last updated: 2026-02-04_ - Decentralized package distribution across mesh nodes. - Compatible with existing bonus-feed and secubox-feed infrastructure. - Torrent-style swarming for large IPK downloads across mesh peers. + +17. **MirrorNetworking Stack** (ref: `SecuBox_MirrorNetworking_Paradigm_Reversal.html`) + - EnigmaBox paradigm reversal: zero central authority, each box is the network. + - Dual transport: WireGuard (tier 1, known peers) + Yggdrasil (tier 2, discovery/extended mesh, optional). + - New packages roadmap: + - `secubox-mirrornet` (v0.19): Core mesh orchestration, gossip protocol, peer management. + - `secubox-identity` (v0.19): did:plc generation, key rotation, trust scoring. + - `secubox-p2p-intel` (v0.19): IoC signed gossip, threat intelligence sharing. + - `luci-app-secubox-mirror` (v0.19): Dashboard for peers, trust, services, comms. + - `secubox-voip` (v1.0): Asterisk micro-PBX, SIP/SRTP direct over WireGuard mesh. + - `secubox-matrix` (v1.0): Conduit Matrix server (Rust, ~15MB RAM), federation on mesh. + - `secubox-factory` (v1.0): Auto-provisioning new box via mesh P2P. + - `yggdrasil-secubox` (v1.1+): Yggdrasil overlay + meshname DNS. + - Mirror concepts: Threat Intel sharing, AI Inference distribution, Reputation scoring, Config & Updates P2P. + - Communication: VoIP E2E (Asterisk/SRTP, no exit server), Matrix E2EE, optional mesh email. + - ANSSI CSPN: Zero central authority = verifiable sovereignty. + - Crowdfunding target: 2027. + +18. **Tor Shield / opkg Bug** (deferred) + - opkg downloads fail (`wget returned 4`) when Tor Shield is active. + - Direct `wget` to full URL works — likely DNS/routing interference. + - Investigate: opkg proxy settings, Tor split-routing exclusions for package repos. diff --git a/.claude/WIP.md b/.claude/WIP.md index 97675f9f..155ab04e 100644 --- a/.claude/WIP.md +++ b/.claude/WIP.md @@ -23,18 +23,28 @@ - `SecuBox_LocalAI_Strategic_Analysis.html` — AI Management Layer roadmap (LocalAI 3.9 + LocalAGI + MCP). - `SecuBox_AI_Gateway_Hybrid_Architecture.html` — Hybrid Local/Cloud architecture (LiteLLM + Data Classifier + multi-provider). +- `SecuBox_MirrorNetworking_Paradigm_Reversal.html` — EnigmaBox autopsy → MirrorNet zero-central-authority architecture. Dual transport (WireGuard + Yggdrasil), VoIP E2E (Asterisk), Matrix/Conduit messaging, did:plc identity, P2P gossip threat intel, Mirror concepts (Threat Intel, AI Inference, Reputation, Config & Updates). New packages: secubox-mirrornet (v0.19), secubox-identity (v0.19), secubox-voip (v1.0), secubox-matrix (v1.0), secubox-p2p-intel (v0.19), yggdrasil-secubox (v1.1+), luci-app-secubox-mirror (v0.19). Crowdfunding target: 2027. + +- **Domoticz IoT Integration** + Status: DONE (2026-02-04) + Notes: `luci-app-domoticz` created with RPCD handler, LuCI overview (status, MQTT, Z2M, HAProxy, mesh, logs). + `domoticzctl` enhanced with `configure-mqtt`, `configure-haproxy`, `backup/restore`, `mesh-register`, `uninstall`. + UCI config extended with mqtt, network, mesh sections. Catalog updated with LuCI package and IoT tags. ## Next Up -1. **Domoticz IoT Integration** — LuCI app, MQTT auto-bridge, zigbee2mqtt integration, P2P mesh. -2. **Metablogizer Upload Fixes** — Investigate failed uploads. -3. **App Store P2P Emancipation** — Remote P2P/torrent endpoint, generative IPK distribution. -4. Port chip header layout to client-guardian, auth-guardian. -5. Rebuild bonus feed with all 2026-02-04 changes. -6. Commit uncommitted working tree changes (bonus-feed IPKs, glancesctl, zigbee2mqttctl, smbfs, jellyfin READMEs). +1. **Metablogizer Upload Fixes** — Investigate failed uploads. +2. **App Store P2P Emancipation** — Remote P2P/torrent endpoint, generative IPK distribution. +3. Port chip header layout to client-guardian, auth-guardian. +4. Rebuild bonus feed with all 2026-02-04 changes. +5. Commit uncommitted working tree changes. + +## Known Bugs (Deferred) + +- **Tor Shield / opkg conflict**: opkg downloads fail (`wget returned 4`) when Tor Shield is active. Direct `wget` to full URL works. Likely DNS/routing interference from Tor split-routing. To be fixed later. ## Blockers / Risks - No automated regression tests for LuCI views; manual verification required after each SCP deploy. - Glances + Zigbee2MQTT + SMB/CIFS source changes uncommitted in working tree. -- Strategic AI documents noted but not yet implemented (v0.18+ roadmap). +- Strategic AI + MirrorNetworking documents noted but not yet implemented (v0.18+ roadmap). diff --git a/package/secubox/luci-app-domoticz/Makefile b/package/secubox/luci-app-domoticz/Makefile new file mode 100644 index 00000000..2d26d806 --- /dev/null +++ b/package/secubox/luci-app-domoticz/Makefile @@ -0,0 +1,29 @@ +include $(TOPDIR)/rules.mk + +LUCI_TITLE:=LuCI Domoticz Home Automation Configuration +LUCI_DEPENDS:=+secubox-app-domoticz +LUCI_PKGARCH:=all + +PKG_NAME:=luci-app-domoticz +PKG_VERSION:=1.0.0 +PKG_RELEASE:=1 +PKG_MAINTAINER:=CyberMind Studio +PKG_LICENSE:=Apache-2.0 + +include $(TOPDIR)/feeds/luci/luci.mk + +define Package/luci-app-domoticz/install + $(INSTALL_DIR) $(1)/usr/share/luci/menu.d + $(INSTALL_DATA) ./root/usr/share/luci/menu.d/luci-app-domoticz.json $(1)/usr/share/luci/menu.d/ + + $(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d + $(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/luci-app-domoticz.json $(1)/usr/share/rpcd/acl.d/ + + $(INSTALL_DIR) $(1)/www/luci-static/resources/view/domoticz + $(INSTALL_DATA) ./htdocs/luci-static/resources/view/domoticz/*.js $(1)/www/luci-static/resources/view/domoticz/ + + $(INSTALL_DIR) $(1)/usr/libexec/rpcd + $(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.domoticz $(1)/usr/libexec/rpcd/ +endef + +$(eval $(call BuildPackage,luci-app-domoticz)) diff --git a/package/secubox/luci-app-domoticz/README.md b/package/secubox/luci-app-domoticz/README.md new file mode 100644 index 00000000..a225174f --- /dev/null +++ b/package/secubox/luci-app-domoticz/README.md @@ -0,0 +1,57 @@ +# luci-app-domoticz + +LuCI web interface for managing the Domoticz home automation platform on SecuBox. + +## Installation + +```bash +opkg install luci-app-domoticz +``` + +Requires `secubox-app-domoticz` (installed as dependency). + +## Features + +- **Service Status**: Container status, Docker availability, disk usage, USB devices +- **IoT Integration**: Mosquitto broker status, Zigbee2MQTT status, MQTT bridge configuration +- **MQTT Auto-Setup**: One-click Mosquitto installation and broker configuration +- **Network**: HAProxy reverse proxy integration, WAN access control, domain configuration +- **Mesh P2P**: Register Domoticz in the SecuBox P2P mesh for multi-node discovery +- **Actions**: Install, start, stop, restart, update, backup, uninstall +- **Logs**: Live container log viewer + +## RPCD Methods + +| Method | Params | Description | +|--------|--------|-------------| +| `status` | — | Container, MQTT, Z2M, HAProxy, mesh status | +| `start` | — | Start Domoticz service | +| `stop` | — | Stop Domoticz service | +| `restart` | — | Restart Domoticz service | +| `install` | — | Pull Docker image and configure | +| `uninstall` | — | Remove container (preserves data) | +| `update` | — | Pull latest image and restart | +| `configure_mqtt` | — | Auto-configure Mosquitto and MQTT bridge | +| `configure_haproxy` | — | Register HAProxy vhost | +| `backup` | — | Create data backup | +| `restore` | path | Restore from backup file | +| `logs` | lines | Fetch container logs | + +## Menu Location + +Services > Domoticz + +## Files + +- `/usr/libexec/rpcd/luci.domoticz` — RPCD handler +- `/usr/share/rpcd/acl.d/luci-app-domoticz.json` — ACL permissions +- `/usr/share/luci/menu.d/luci-app-domoticz.json` — Menu entry +- `/www/luci-static/resources/view/domoticz/overview.js` — LuCI view + +## Dependencies + +- `secubox-app-domoticz` + +## License + +Apache-2.0 diff --git a/package/secubox/luci-app-domoticz/htdocs/luci-static/resources/view/domoticz/overview.js b/package/secubox/luci-app-domoticz/htdocs/luci-static/resources/view/domoticz/overview.js new file mode 100644 index 00000000..3da807d9 --- /dev/null +++ b/package/secubox/luci-app-domoticz/htdocs/luci-static/resources/view/domoticz/overview.js @@ -0,0 +1,456 @@ +'use strict'; +'require view'; +'require form'; +'require uci'; +'require rpc'; +'require ui'; + +var callStatus = rpc.declare({ + object: 'luci.domoticz', + method: 'status', + expect: {} +}); + +var callStart = rpc.declare({ + object: 'luci.domoticz', + method: 'start', + expect: {} +}); + +var callStop = rpc.declare({ + object: 'luci.domoticz', + method: 'stop', + expect: {} +}); + +var callRestart = rpc.declare({ + object: 'luci.domoticz', + method: 'restart', + expect: {} +}); + +var callInstall = rpc.declare({ + object: 'luci.domoticz', + method: 'install', + expect: {} +}); + +var callUninstall = rpc.declare({ + object: 'luci.domoticz', + method: 'uninstall', + expect: {} +}); + +var callUpdate = rpc.declare({ + object: 'luci.domoticz', + method: 'update', + expect: {} +}); + +var callConfigureMqtt = rpc.declare({ + object: 'luci.domoticz', + method: 'configure_mqtt', + expect: {} +}); + +var callConfigureHaproxy = rpc.declare({ + object: 'luci.domoticz', + method: 'configure_haproxy', + expect: {} +}); + +var callBackup = rpc.declare({ + object: 'luci.domoticz', + method: 'backup', + expect: {} +}); + +var callLogs = rpc.declare({ + object: 'luci.domoticz', + method: 'logs', + params: ['lines'], + expect: {} +}); + +function statusColor(val) { + if (val === 'running' || val === 'configured') return '#27ae60'; + if (val === 'stopped' || val === 'pending') return '#e74c3c'; + return '#8892b0'; +} + +function statusLabel(val) { + if (val === 'running') return 'Running'; + if (val === 'stopped') return 'Stopped'; + if (val === 'not_installed') return 'Not Installed'; + if (val === 'configured') return 'Configured'; + if (val === 'pending') return 'Pending'; + if (val === 'disabled') return 'Disabled'; + return val || 'Unknown'; +} + +return view.extend({ + load: function() { + return Promise.all([ + uci.load('domoticz'), + callStatus() + ]); + }, + + render: function(data) { + var status = data[1] || {}; + var m, s, o; + + m = new form.Map('domoticz', _('Domoticz Home Automation'), + _('Open-source home automation platform with IoT device management, MQTT bridge, and Zigbee integration.')); + + /* ---- Service Status ---- */ + s = m.section(form.NamedSection, 'main', 'domoticz', _('Service Status')); + s.anonymous = true; + + o = s.option(form.DummyValue, '_status', _('Status')); + o.rawhtml = true; + o.cfgvalue = function() { + var cs = status.container_status || 'unknown'; + var color = statusColor(cs); + var label = statusLabel(cs); + var html = '' + label + ''; + if (cs === 'running' && status.container_uptime) + html += ' (' + status.container_uptime + ')'; + return html; + }; + + o = s.option(form.DummyValue, '_docker', _('Docker')); + o.rawhtml = true; + o.cfgvalue = function() { + return status.docker_available + ? 'Available' + : 'Not available'; + }; + + o = s.option(form.DummyValue, '_info', _('Details')); + o.rawhtml = true; + o.cfgvalue = function() { + var port = status.port || 8080; + var html = ''; + html += ''; + html += ''; + html += ''; + html += ''; + if (status.disk_usage) + html += ''; + if (status.usb_devices && status.usb_devices.length > 0) + html += ''; + html += '
Image:' + (status.image || '-') + '
Port:' + port + '
Data:' + (status.data_path || '-') + '
Domain:' + (status.domain || '-') + '
Disk:' + status.disk_usage + '
USB:' + status.usb_devices.join(', ') + '
'; + return html; + }; + + /* ---- IoT Integration Status ---- */ + o = s.option(form.DummyValue, '_iot', _('IoT Integration')); + o.rawhtml = true; + o.cfgvalue = function() { + var html = ''; + + // Mosquitto + var mc = statusColor(status.mosquitto_status); + html += ''; + html += ''; + + // Zigbee2MQTT + var zc = statusColor(status.z2m_status); + var zl = statusLabel(status.z2m_status); + if (status.z2m_status === 'running' && status.z2m_port) + zl += ' (port ' + status.z2m_port + ')'; + html += ''; + html += ''; + + // MQTT bridge + var be = status.mqtt_enabled ? '#27ae60' : '#8892b0'; + var bl = status.mqtt_enabled + ? 'Enabled (' + (status.mqtt_broker || '127.0.0.1') + ':' + (status.mqtt_broker_port || 1883) + ')' + : 'Disabled'; + html += ''; + html += ''; + + html += '
Mosquitto:' + statusLabel(status.mosquitto_status) + '
Zigbee2MQTT:' + zl + '
MQTT Bridge:' + bl + '
'; + return html; + }; + + /* ---- Network Integration ---- */ + o = s.option(form.DummyValue, '_integrations', _('Network')); + o.rawhtml = true; + o.cfgvalue = function() { + var html = ''; + + var hc = '#8892b0', hl = 'Disabled'; + if (status.haproxy_status === 'configured') { + hc = '#27ae60'; hl = 'Configured (' + (status.domain || '') + ')'; + } else if (status.haproxy_status === 'pending') { + hc = '#f39c12'; hl = 'Enabled (not yet configured)'; + } + html += ''; + + var meshc = status.mesh_enabled ? '#27ae60' : '#8892b0'; + var meshl = status.mesh_enabled ? 'Enabled' : 'Disabled'; + html += ''; + + var fc = status.firewall_wan ? '#27ae60' : '#8892b0'; + var fl = status.firewall_wan ? 'WAN access on port ' + (status.port || 8080) : 'LAN only'; + html += ''; + + html += '
HAProxy:' + hl + '
Mesh P2P:' + meshl + '
Firewall:' + fl + '
'; + return html; + }; + + /* ---- Action Buttons ---- */ + var cs = status.container_status || 'not_installed'; + + if (cs === 'not_installed') { + o = s.option(form.Button, '_install', _('Install')); + o.inputtitle = _('Install Domoticz'); + o.inputstyle = 'apply'; + o.onclick = function() { + ui.showModal(_('Installing...'), [ + E('p', { 'class': 'spinning' }, _('Pulling Docker image and configuring...')) + ]); + return callInstall().then(function(res) { + ui.hideModal(); + if (res && res.success) { + ui.addNotification(null, E('p', {}, _('Domoticz installed successfully.')), 'info'); + } else { + ui.addNotification(null, E('p', {}, _('Installation failed: ') + (res.output || 'Unknown error')), 'danger'); + } + window.location.href = window.location.pathname + '?' + Date.now(); + }); + }; + } else { + if (cs === 'stopped') { + o = s.option(form.Button, '_start', _('Start')); + o.inputtitle = _('Start'); + o.inputstyle = 'apply'; + o.onclick = function() { + return callStart().then(function() { + window.location.href = window.location.pathname + '?' + Date.now(); + }); + }; + } + + if (cs === 'running') { + o = s.option(form.Button, '_stop', _('Stop')); + o.inputtitle = _('Stop'); + o.inputstyle = 'remove'; + o.onclick = function() { + return callStop().then(function() { + window.location.href = window.location.pathname + '?' + Date.now(); + }); + }; + + o = s.option(form.Button, '_restart', _('Restart')); + o.inputtitle = _('Restart'); + o.inputstyle = 'reload'; + o.onclick = function() { + return callRestart().then(function() { + window.location.href = window.location.pathname + '?' + Date.now(); + }); + }; + + o = s.option(form.Button, '_webui', _('Web UI')); + o.inputtitle = _('Open Domoticz UI'); + o.inputstyle = 'action'; + o.onclick = function() { + var port = status.port || 8080; + window.open('http://' + window.location.hostname + ':' + port, '_blank'); + }; + } + + o = s.option(form.Button, '_update', _('Update')); + o.inputtitle = _('Pull Latest Image'); + o.inputstyle = 'action'; + o.onclick = function() { + ui.showModal(_('Updating...'), [ + E('p', { 'class': 'spinning' }, _('Pulling latest Docker image and restarting...')) + ]); + return callUpdate().then(function(res) { + ui.hideModal(); + if (res && res.success) { + ui.addNotification(null, E('p', {}, _('Domoticz updated successfully.')), 'info'); + } else { + ui.addNotification(null, E('p', {}, _('Update failed: ') + (res.output || 'Unknown error')), 'danger'); + } + window.location.href = window.location.pathname + '?' + Date.now(); + }); + }; + + o = s.option(form.Button, '_backup', _('Backup')); + o.inputtitle = _('Create Backup'); + o.inputstyle = 'action'; + o.onclick = function() { + return callBackup().then(function(res) { + if (res && res.success) { + ui.addNotification(null, E('p', {}, _('Backup created: ') + (res.path || '')), 'info'); + } else { + ui.addNotification(null, E('p', {}, _('Backup failed: ') + (res.output || 'Unknown error')), 'danger'); + } + }); + }; + + o = s.option(form.Button, '_uninstall', _('Uninstall')); + o.inputtitle = _('Uninstall'); + o.inputstyle = 'remove'; + o.onclick = function() { + if (!confirm(_('Are you sure you want to uninstall Domoticz? Data will be preserved.'))) + return; + ui.showModal(_('Uninstalling...'), [ + E('p', { 'class': 'spinning' }, _('Removing container and integrations...')) + ]); + return callUninstall().then(function(res) { + ui.hideModal(); + if (res && res.success) { + ui.addNotification(null, E('p', {}, _('Domoticz uninstalled.')), 'info'); + } else { + ui.addNotification(null, E('p', {}, _('Uninstall failed: ') + (res.output || 'Unknown error')), 'danger'); + } + window.location.href = window.location.pathname + '?' + Date.now(); + }); + }; + } + + /* ---- Configuration Section ---- */ + s = m.section(form.NamedSection, 'main', 'domoticz', _('Configuration')); + s.anonymous = true; + + o = s.option(form.Flag, 'enabled', _('Enabled'), + _('Enable the Domoticz service.')); + o.rmempty = false; + + o = s.option(form.Value, 'port', _('Port'), + _('HTTP port for the Domoticz web interface.')); + o.datatype = 'port'; + o.placeholder = '8080'; + + o = s.option(form.Value, 'image', _('Docker Image'), + _('Docker image to use.')); + o.placeholder = 'domoticz/domoticz:latest'; + + o = s.option(form.Value, 'data_path', _('Data Path'), + _('Path for Domoticz config and database.')); + o.placeholder = '/srv/domoticz'; + + o = s.option(form.Value, 'devices_path', _('Devices Path'), + _('Path for USB device passthrough into container.')); + o.placeholder = '/srv/devices'; + + o = s.option(form.Value, 'timezone', _('Timezone')); + o.placeholder = 'Europe/Paris'; + + /* ---- MQTT & Zigbee Section ---- */ + s = m.section(form.NamedSection, 'mqtt', 'domoticz', _('MQTT & Zigbee')); + s.anonymous = true; + + o = s.option(form.Flag, 'enabled', _('MQTT Bridge'), + _('Auto-configure Domoticz MQTT connection to the local Mosquitto broker.')); + o.rmempty = false; + + o = s.option(form.Value, 'broker', _('MQTT Broker'), + _('Mosquitto broker address.')); + o.placeholder = '127.0.0.1'; + o.depends('enabled', '1'); + + o = s.option(form.Value, 'broker_port', _('MQTT Port'), + _('Mosquitto broker port.')); + o.datatype = 'port'; + o.placeholder = '1883'; + o.depends('enabled', '1'); + + o = s.option(form.Value, 'topic_prefix', _('Domoticz Topic'), + _('MQTT topic prefix for Domoticz messages.')); + o.placeholder = 'domoticz'; + o.depends('enabled', '1'); + + o = s.option(form.Value, 'z2m_topic', _('Zigbee2MQTT Topic'), + _('MQTT topic where Zigbee2MQTT publishes device data.')); + o.placeholder = 'zigbee2mqtt'; + o.depends('enabled', '1'); + + o = s.option(form.Button, '_setup_mqtt', _('Auto-Configure')); + o.inputtitle = _('Setup MQTT Bridge'); + o.inputstyle = 'apply'; + o.depends('enabled', '1'); + o.onclick = function() { + ui.showModal(_('Configuring MQTT...'), [ + E('p', { 'class': 'spinning' }, _('Setting up Mosquitto broker and Domoticz MQTT connection...')) + ]); + return callConfigureMqtt().then(function(res) { + ui.hideModal(); + if (res && res.success) { + ui.addNotification(null, E('p', {}, _('MQTT bridge configured successfully.')), 'info'); + } else { + ui.addNotification(null, E('p', {}, _('MQTT setup failed: ') + (res.output || 'Unknown error')), 'danger'); + } + window.location.href = window.location.pathname + '?' + Date.now(); + }); + }; + + /* ---- Network & Domain Section ---- */ + s = m.section(form.NamedSection, 'network', 'domoticz', _('Network & Domain')); + s.anonymous = true; + + o = s.option(form.Value, 'domain', _('Domain'), + _('Domain name for accessing Domoticz via HAProxy reverse proxy.')); + o.placeholder = 'domoticz.secubox.local'; + + o = s.option(form.Flag, 'haproxy', _('HAProxy Integration'), + _('Register Domoticz as an HAProxy vhost for reverse proxy access.')); + o.rmempty = false; + + o = s.option(form.Flag, 'firewall_wan', _('WAN Access'), + _('Allow direct WAN access to the Domoticz port.')); + o.rmempty = false; + + o = s.option(form.Button, '_apply_haproxy', _('Apply HAProxy')); + o.inputtitle = _('Configure HAProxy Now'); + o.inputstyle = 'action'; + o.depends('haproxy', '1'); + o.onclick = function() { + return callConfigureHaproxy().then(function(res) { + if (res && res.success) { + ui.addNotification(null, E('p', {}, _('HAProxy configured successfully.')), 'info'); + } else { + ui.addNotification(null, E('p', {}, _('HAProxy configuration failed: ') + (res.output || 'Unknown error')), 'danger'); + } + }); + }; + + /* ---- Mesh P2P Section ---- */ + s = m.section(form.NamedSection, 'mesh', 'domoticz', _('Mesh P2P')); + s.anonymous = true; + + o = s.option(form.Flag, 'enabled', _('Mesh Integration'), + _('Register Domoticz with the SecuBox P2P mesh network for discovery by other nodes.')); + o.rmempty = false; + + /* ---- Logs Section ---- */ + s = m.section(form.NamedSection, 'main', 'domoticz', _('Logs')); + s.anonymous = true; + + o = s.option(form.DummyValue, '_logs', ' '); + o.rawhtml = true; + o.cfgvalue = function() { + return '
Click "Fetch Logs" to view container output.
'; + }; + + o = s.option(form.Button, '_fetch_logs', _('Fetch Logs')); + o.inputtitle = _('Fetch Logs'); + o.inputstyle = 'action'; + o.onclick = function() { + var logsDiv = document.getElementById('domoticz-logs'); + if (logsDiv) logsDiv.textContent = 'Loading...'; + return callLogs(50).then(function(res) { + if (logsDiv) logsDiv.textContent = (res && res.logs) ? res.logs : 'No logs available.'; + }); + }; + + return m.render(); + } +}); diff --git a/package/secubox/luci-app-domoticz/root/usr/libexec/rpcd/luci.domoticz b/package/secubox/luci-app-domoticz/root/usr/libexec/rpcd/luci.domoticz new file mode 100644 index 00000000..1b8678bf --- /dev/null +++ b/package/secubox/luci-app-domoticz/root/usr/libexec/rpcd/luci.domoticz @@ -0,0 +1,237 @@ +#!/bin/sh + +. /lib/functions.sh +. /usr/share/libubox/jshn.sh + +CONTAINER="secbx-domoticz" +CONFIG="domoticz" + +case "$1" in + list) + echo '{"status":{},"start":{},"stop":{},"restart":{},"install":{},"uninstall":{},"update":{},"configure_mqtt":{},"configure_haproxy":{},"backup":{},"restore":{"path":"str"},"logs":{"lines":"int"}}' + ;; + call) + case "$2" in + status) + json_init + + enabled=$(uci -q get ${CONFIG}.main.enabled) + image=$(uci -q get ${CONFIG}.main.image) + port=$(uci -q get ${CONFIG}.main.port) + data_path=$(uci -q get ${CONFIG}.main.data_path) + devices_path=$(uci -q get ${CONFIG}.main.devices_path) + timezone=$(uci -q get ${CONFIG}.main.timezone) + + # MQTT config + mqtt_enabled=$(uci -q get ${CONFIG}.mqtt.enabled) + mqtt_broker=$(uci -q get ${CONFIG}.mqtt.broker) + mqtt_broker_port=$(uci -q get ${CONFIG}.mqtt.broker_port) + mqtt_topic_prefix=$(uci -q get ${CONFIG}.mqtt.topic_prefix) + mqtt_z2m_topic=$(uci -q get ${CONFIG}.mqtt.z2m_topic) + + # Network/domain config + domain=$(uci -q get ${CONFIG}.network.domain) + haproxy=$(uci -q get ${CONFIG}.network.haproxy) + firewall_wan=$(uci -q get ${CONFIG}.network.firewall_wan) + + # Mesh config + mesh_enabled=$(uci -q get ${CONFIG}.mesh.enabled) + + json_add_boolean "enabled" ${enabled:-0} + json_add_string "image" "${image:-domoticz/domoticz:latest}" + json_add_int "port" ${port:-8080} + json_add_string "data_path" "${data_path:-/srv/domoticz}" + json_add_string "devices_path" "${devices_path:-/srv/devices}" + json_add_string "timezone" "${timezone:-UTC}" + + json_add_boolean "mqtt_enabled" ${mqtt_enabled:-0} + json_add_string "mqtt_broker" "${mqtt_broker:-127.0.0.1}" + json_add_int "mqtt_broker_port" ${mqtt_broker_port:-1883} + json_add_string "mqtt_topic_prefix" "${mqtt_topic_prefix:-domoticz}" + json_add_string "mqtt_z2m_topic" "${mqtt_z2m_topic:-zigbee2mqtt}" + + json_add_string "domain" "${domain:-domoticz.secubox.local}" + json_add_boolean "haproxy" ${haproxy:-0} + json_add_boolean "firewall_wan" ${firewall_wan:-0} + json_add_boolean "mesh_enabled" ${mesh_enabled:-0} + + # Docker availability + if command -v docker >/dev/null 2>&1; then + json_add_boolean "docker_available" 1 + else + json_add_boolean "docker_available" 0 + fi + + # Container status + if docker ps --filter "name=$CONTAINER" --format '{{.Status}}' 2>/dev/null | grep -q .; then + json_add_string "container_status" "running" + uptime=$(docker ps --filter "name=$CONTAINER" --format '{{.Status}}' 2>/dev/null) + json_add_string "container_uptime" "$uptime" + elif docker ps -a --filter "name=$CONTAINER" --format '{{.Status}}' 2>/dev/null | grep -q .; then + json_add_string "container_status" "stopped" + json_add_string "container_uptime" "" + else + json_add_string "container_status" "not_installed" + json_add_string "container_uptime" "" + fi + + # Mosquitto broker status + if pgrep mosquitto >/dev/null 2>&1; then + json_add_string "mosquitto_status" "running" + elif command -v mosquitto >/dev/null 2>&1; then + json_add_string "mosquitto_status" "stopped" + else + json_add_string "mosquitto_status" "not_installed" + fi + + # Zigbee2MQTT status + z2m_running=0 + if [ -f /srv/zigbee2mqtt/alpine/rootfs/run.pid ]; then + z2m_running=1 + elif pgrep -f zigbee2mqtt >/dev/null 2>&1; then + z2m_running=1 + fi + if [ "$z2m_running" = "1" ]; then + json_add_string "z2m_status" "running" + z2m_port=$(uci -q get zigbee2mqtt.main.frontend_port) + json_add_int "z2m_port" ${z2m_port:-8099} + elif [ -f /etc/config/zigbee2mqtt ]; then + json_add_string "z2m_status" "stopped" + json_add_int "z2m_port" 0 + else + json_add_string "z2m_status" "not_installed" + json_add_int "z2m_port" 0 + fi + + # HAProxy vhost status + if [ "${haproxy:-0}" = "1" ]; then + vhost_exists=$(uci show haproxy 2>/dev/null | grep "\.domain='${domain:-domoticz.secubox.local}'" | head -1) + if [ -n "$vhost_exists" ]; then + json_add_string "haproxy_status" "configured" + else + json_add_string "haproxy_status" "pending" + fi + else + json_add_string "haproxy_status" "disabled" + fi + + # Disk usage + dp="${data_path:-/srv/domoticz}" + if [ -d "$dp" ]; then + disk_usage=$(du -sh "$dp" 2>/dev/null | cut -f1) + json_add_string "disk_usage" "${disk_usage:-0}" + else + json_add_string "disk_usage" "" + fi + + # USB devices + json_add_array "usb_devices" + for dev in /dev/ttyUSB* /dev/ttyACM*; do + [ -e "$dev" ] && json_add_string "" "$dev" + done + json_close_array + + json_dump + ;; + + start) + /etc/init.d/domoticz start >/dev/null 2>&1 + echo '{"success":true}' + ;; + + stop) + /etc/init.d/domoticz stop >/dev/null 2>&1 + echo '{"success":true}' + ;; + + restart) + /etc/init.d/domoticz restart >/dev/null 2>&1 + echo '{"success":true}' + ;; + + install) + output=$(/usr/sbin/domoticzctl install 2>&1) + code=$? + json_init + json_add_boolean "success" $((code == 0)) + json_add_string "output" "$output" + json_dump + ;; + + uninstall) + output=$(/usr/sbin/domoticzctl uninstall 2>&1) + code=$? + json_init + json_add_boolean "success" $((code == 0)) + json_add_string "output" "$output" + json_dump + ;; + + update) + output=$(/usr/sbin/domoticzctl update 2>&1) + code=$? + json_init + json_add_boolean "success" $((code == 0)) + json_add_string "output" "$output" + json_dump + ;; + + configure_mqtt) + output=$(/usr/sbin/domoticzctl configure-mqtt 2>&1) + code=$? + json_init + json_add_boolean "success" $((code == 0)) + json_add_string "output" "$output" + json_dump + ;; + + configure_haproxy) + output=$(/usr/sbin/domoticzctl configure-haproxy 2>&1) + code=$? + json_init + json_add_boolean "success" $((code == 0)) + json_add_string "output" "$output" + json_dump + ;; + + backup) + backup_file="/tmp/domoticz-backup-$(date +%Y%m%d-%H%M%S).tar.gz" + output=$(/usr/sbin/domoticzctl backup "$backup_file" 2>&1) + code=$? + json_init + json_add_boolean "success" $((code == 0)) + json_add_string "path" "$backup_file" + json_add_string "output" "$output" + json_dump + ;; + + restore) + read -r input + path=$(echo "$input" | jsonfilter -e '@.path' 2>/dev/null) + if [ -z "$path" ]; then + echo '{"success":false,"output":"No backup path specified"}' + else + output=$(/usr/sbin/domoticzctl restore "$path" 2>&1) + code=$? + json_init + json_add_boolean "success" $((code == 0)) + json_add_string "output" "$output" + json_dump + fi + ;; + + logs) + read -r input + lines=$(echo "$input" | jsonfilter -e '@.lines' 2>/dev/null) + [ -z "$lines" ] && lines=50 + + logs=$(docker logs --tail "$lines" "$CONTAINER" 2>&1 | tail -100) + json_init + json_add_string "logs" "$logs" + json_dump + ;; + esac + ;; +esac + +exit 0 diff --git a/package/secubox/luci-app-domoticz/root/usr/share/luci/menu.d/luci-app-domoticz.json b/package/secubox/luci-app-domoticz/root/usr/share/luci/menu.d/luci-app-domoticz.json new file mode 100644 index 00000000..22358d8c --- /dev/null +++ b/package/secubox/luci-app-domoticz/root/usr/share/luci/menu.d/luci-app-domoticz.json @@ -0,0 +1,14 @@ +{ + "admin/services/domoticz": { + "title": "Domoticz", + "order": 65, + "action": { + "type": "view", + "path": "domoticz/overview" + }, + "depends": { + "acl": ["luci-app-domoticz"], + "uci": {"domoticz": true} + } + } +} diff --git a/package/secubox/luci-app-domoticz/root/usr/share/rpcd/acl.d/luci-app-domoticz.json b/package/secubox/luci-app-domoticz/root/usr/share/rpcd/acl.d/luci-app-domoticz.json new file mode 100644 index 00000000..b728173a --- /dev/null +++ b/package/secubox/luci-app-domoticz/root/usr/share/rpcd/acl.d/luci-app-domoticz.json @@ -0,0 +1,24 @@ +{ + "luci-app-domoticz": { + "description": "Grant access to Domoticz home automation configuration", + "read": { + "file": { + "/etc/config/domoticz": ["read"] + }, + "ubus": { + "file": ["read", "stat"], + "luci.domoticz": ["*"] + }, + "uci": ["domoticz"] + }, + "write": { + "file": { + "/etc/config/domoticz": ["write"] + }, + "ubus": { + "luci.domoticz": ["*"] + }, + "uci": ["domoticz"] + } + } +} diff --git a/package/secubox/secubox-app-domoticz/Makefile b/package/secubox/secubox-app-domoticz/Makefile index ce88bf02..6e6aa41a 100644 --- a/package/secubox/secubox-app-domoticz/Makefile +++ b/package/secubox/secubox-app-domoticz/Makefile @@ -1,7 +1,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=secubox-app-domoticz -PKG_RELEASE:=2 +PKG_RELEASE:=3 PKG_VERSION:=1.0.0 PKG_ARCH:=all PKG_MAINTAINER:=CyberMind Studio diff --git a/package/secubox/secubox-app-domoticz/README.md b/package/secubox/secubox-app-domoticz/README.md index ff669457..0008ef33 100644 --- a/package/secubox/secubox-app-domoticz/README.md +++ b/package/secubox/secubox-app-domoticz/README.md @@ -1,11 +1,13 @@ # SecuBox Domoticz -Home automation platform running in Docker on SecuBox-powered OpenWrt systems. +Home automation platform running in Docker with MQTT bridge, Zigbee2MQTT integration, and P2P mesh support. ## Installation ```sh opkg install secubox-app-domoticz +domoticzctl install +/etc/init.d/domoticz start ``` ## Configuration @@ -15,33 +17,70 @@ UCI config file: `/etc/config/domoticz` ``` config domoticz 'main' option enabled '0' + option image 'domoticz/domoticz:latest' + option data_path '/srv/domoticz' + option devices_path '/srv/devices' option port '8080' + option timezone 'UTC' + +config domoticz 'mqtt' + option enabled '0' + option broker '127.0.0.1' + option broker_port '1883' + option topic_prefix 'domoticz' + option z2m_topic 'zigbee2mqtt' + +config domoticz 'network' + option domain 'domoticz.secubox.local' + option haproxy '0' + option firewall_wan '0' + +config domoticz 'mesh' + option enabled '0' ``` ## Usage ```sh -# Start / stop the service -/etc/init.d/domoticz start -/etc/init.d/domoticz stop - -# Controller CLI -domoticzctl status -domoticzctl install -domoticzctl remove +domoticzctl install # Pull image, install prerequisites +domoticzctl uninstall # Remove container (data preserved) +domoticzctl update # Pull latest image, restart +domoticzctl status # Show container status +domoticzctl logs [-f] # Container logs +domoticzctl configure-mqtt # Auto-setup Mosquitto + MQTT bridge +domoticzctl configure-haproxy # Register HAProxy vhost +domoticzctl backup [path] # Backup data +domoticzctl restore # Restore from backup +domoticzctl mesh-register # Register in P2P mesh ``` +## MQTT Bridge + +The `configure-mqtt` command auto-configures: +1. Installs `mosquitto-nossl` if not present +2. Configures Mosquitto listener on port 1883 +3. Detects Zigbee2MQTT broker settings for compatibility +4. Stores MQTT config in UCI for persistence + +After setup, add MQTT hardware in Domoticz UI: Setup > Hardware > MQTT Client Gateway. + +## Zigbee Integration + +When `secubox-app-zigbee2mqtt` is installed: +- Both services share the same Mosquitto broker +- Zigbee devices publish on the `zigbee2mqtt/#` topic +- Domoticz subscribes via MQTT Client Gateway hardware + ## Files - `/etc/config/domoticz` -- UCI configuration -- `/etc/init.d/domoticz` -- init script +- `/etc/init.d/domoticz` -- init script (procd) - `/usr/sbin/domoticzctl` -- controller CLI ## Dependencies -- `dockerd` -- `docker` -- `containerd` +- `dockerd`, `docker`, `containerd` +- Optional: `mosquitto-nossl`, `secubox-app-zigbee2mqtt` ## License diff --git a/package/secubox/secubox-app-domoticz/files/etc/config/domoticz b/package/secubox/secubox-app-domoticz/files/etc/config/domoticz index 2d133de9..b8ec6cca 100644 --- a/package/secubox/secubox-app-domoticz/files/etc/config/domoticz +++ b/package/secubox/secubox-app-domoticz/files/etc/config/domoticz @@ -5,3 +5,18 @@ config domoticz 'main' option devices_path '/srv/devices' option port '8080' option timezone 'UTC' + +config domoticz 'mqtt' + option enabled '0' + option broker '127.0.0.1' + option broker_port '1883' + option topic_prefix 'domoticz' + option z2m_topic 'zigbee2mqtt' + +config domoticz 'network' + option domain 'domoticz.secubox.local' + option haproxy '0' + option firewall_wan '0' + +config domoticz 'mesh' + option enabled '0' diff --git a/package/secubox/secubox-app-domoticz/files/usr/sbin/domoticzctl b/package/secubox/secubox-app-domoticz/files/usr/sbin/domoticzctl index 6fdba715..276e7e04 100644 --- a/package/secubox/secubox-app-domoticz/files/usr/sbin/domoticzctl +++ b/package/secubox/secubox-app-domoticz/files/usr/sbin/domoticzctl @@ -1,5 +1,5 @@ #!/bin/sh -# SecuBox Domoticz manager +# SecuBox Domoticz manager — IoT home automation with MQTT/Zigbee integration CONFIG="domoticz" CONTAINER="secbx-domoticz" @@ -10,19 +10,28 @@ usage() { Usage: domoticzctl Commands: - install Install prerequisites, prepare directories, pull image - check Run prerequisite checks - update Pull new image and restart - status Show container status - logs Show container logs (use -f to follow) - service-run Internal: run container via procd - service-stop Stop container + install Install prerequisites, prepare directories, pull image + uninstall Remove container (preserves data) + check Run prerequisite checks + update Pull new image and restart + status Show container status + logs [-f] Show container logs (use -f to follow) + configure-mqtt Auto-configure Mosquitto broker and Domoticz MQTT bridge + configure-haproxy Register as HAProxy vhost for reverse proxy + backup [path] Backup Domoticz data + restore Restore Domoticz from backup + mesh-register Register Domoticz in P2P mesh service catalog + service-run Internal: run container via procd + service-stop Stop container USAGE } require_root() { [ "$(id -u)" -eq 0 ]; } -uci_get() { uci -q get ${CONFIG}.main.$1; } +uci_get() { + local section="${2:-main}" + uci -q get ${CONFIG}.${section}.$1 +} defaults() { image="$(uci_get image || echo domoticz/domoticz:latest)" @@ -36,7 +45,7 @@ ensure_dir() { [ -d "$1" ] || mkdir -p "$1"; } ensure_packages() { for pkg in "$@"; do - if ! opkg status "$pkg" >/dev/null 2>&1; then + if ! opkg status "$pkg" 2>/dev/null | grep -q "Status:.*installed"; then if [ "$OPKG_UPDATED" -eq 0 ]; then opkg update || return 1 OPKG_UPDATED=1 @@ -57,49 +66,268 @@ check_prereqs() { pull_image() { defaults; docker pull "$image"; } -stop_container() { docker stop "$CONTAINER" >/dev/null 2>&1 || true; docker rm "$CONTAINER" >/dev/null 2>&1 || true; } +stop_container() { + docker stop "$CONTAINER" >/dev/null 2>&1 || true + docker rm "$CONTAINER" >/dev/null 2>&1 || true +} cmd_install() { - require_root || { echo Root required >&2; exit 1; } + require_root || { echo "Root required" >&2; exit 1; } check_prereqs || exit 1 ensure_dir "$data_path/config" pull_image || exit 1 uci set ${CONFIG}.main.enabled='1' uci commit ${CONFIG} /etc/init.d/domoticz enable - echo "Domoticz prerequisites installed. Start with /etc/init.d/domoticz start" + echo "Domoticz installed. Start with /etc/init.d/domoticz start" +} + +cmd_uninstall() { + require_root || { echo "Root required" >&2; exit 1; } + /etc/init.d/domoticz stop >/dev/null 2>&1 + stop_container + defaults + docker rmi "$image" >/dev/null 2>&1 || true + uci set ${CONFIG}.main.enabled='0' + uci commit ${CONFIG} + /etc/init.d/domoticz disable >/dev/null 2>&1 + echo "Domoticz uninstalled. Data preserved at ${data_path}." } cmd_check() { check_prereqs; echo "Prerequisite check completed."; } cmd_update() { - require_root || { echo Root required >&2; exit 1; } + require_root || { echo "Root required" >&2; exit 1; } pull_image || exit 1 /etc/init.d/domoticz restart + echo "Domoticz updated and restarted." } cmd_status() { docker ps -a --filter "name=$CONTAINER"; } cmd_logs() { docker logs "$@" "$CONTAINER"; } +cmd_configure_mqtt() { + require_root || { echo "Root required" >&2; exit 1; } + + local broker=$(uci_get broker mqtt) + local broker_port=$(uci_get broker_port mqtt) + local topic_prefix=$(uci_get topic_prefix mqtt) + local z2m_topic=$(uci_get z2m_topic mqtt) + + broker="${broker:-127.0.0.1}" + broker_port="${broker_port:-1883}" + topic_prefix="${topic_prefix:-domoticz}" + z2m_topic="${z2m_topic:-zigbee2mqtt}" + + # Ensure Mosquitto is installed and running + if ! command -v mosquitto >/dev/null 2>&1; then + echo "Installing mosquitto-nossl..." + ensure_packages mosquitto-nossl mosquitto-client-nossl || { + echo "[ERROR] Failed to install Mosquitto" >&2 + return 1 + } + fi + + # Configure Mosquitto listener + local mosquitto_conf="/etc/mosquitto/mosquitto.conf" + if [ -f "$mosquitto_conf" ]; then + # Ensure listener on configured port + if ! grep -q "^listener ${broker_port}" "$mosquitto_conf" 2>/dev/null; then + echo "" >> "$mosquitto_conf" + echo "# Auto-configured by domoticzctl" >> "$mosquitto_conf" + echo "listener ${broker_port}" >> "$mosquitto_conf" + echo "allow_anonymous true" >> "$mosquitto_conf" + fi + fi + + # Start Mosquitto + /etc/init.d/mosquitto enable >/dev/null 2>&1 + /etc/init.d/mosquitto start >/dev/null 2>&1 + + # Verify broker is listening + local retries=0 + while [ $retries -lt 5 ]; do + if netstat -tln 2>/dev/null | grep -q ":${broker_port} "; then + break + fi + retries=$((retries + 1)) + sleep 1 + done + + if ! netstat -tln 2>/dev/null | grep -q ":${broker_port} "; then + echo "[WARN] Mosquitto may not be listening on port ${broker_port}" >&2 + fi + + # Check zigbee2mqtt MQTT settings + if [ -f /etc/config/zigbee2mqtt ]; then + local z2m_mqtt_uri=$(uci -q get zigbee2mqtt.main.mqtt_host) + local z2m_base=$(uci -q get zigbee2mqtt.main.base_topic) + + # z2m stores full URI (mqtt://host:port) — extract host and port + local z2m_host=$(echo "$z2m_mqtt_uri" | sed 's|^mqtt[s]*://||' | cut -d: -f1) + local z2m_port_conf=$(echo "$z2m_mqtt_uri" | sed 's|^mqtt[s]*://||' | cut -d: -f2) + z2m_host="${z2m_host:-127.0.0.1}" + z2m_port_conf="${z2m_port_conf:-1883}" + + if [ "$z2m_host" = "$broker" ] && [ "$z2m_port_conf" = "$broker_port" ]; then + echo "Zigbee2MQTT shares same broker (${broker}:${broker_port})." + echo "z2m topic: ${z2m_base:-zigbee2mqtt}" + else + echo "[INFO] Zigbee2MQTT uses broker ${z2m_host}:${z2m_port_conf}" + echo "[INFO] Domoticz will connect to ${broker}:${broker_port}" + echo "[INFO] Ensure both use the same Mosquitto broker for device bridging." + fi + else + echo "[INFO] Zigbee2MQTT not installed — MQTT bridge will work with other MQTT publishers." + fi + + # Update UCI MQTT settings + uci set ${CONFIG}.mqtt.enabled='1' + uci set ${CONFIG}.mqtt.broker="$broker" + uci set ${CONFIG}.mqtt.broker_port="$broker_port" + uci set ${CONFIG}.mqtt.topic_prefix="$topic_prefix" + uci set ${CONFIG}.mqtt.z2m_topic="$z2m_topic" + uci commit ${CONFIG} + + # Note: Domoticz MQTT configuration happens inside the Domoticz web UI + # (Hardware > MQTT Client Gateway). We configure the broker and topic, + # but the user must add the MQTT hardware in Domoticz UI. + echo "" + echo "MQTT bridge configured:" + echo " Broker: ${broker}:${broker_port}" + echo " Domoticz: topic_prefix=${topic_prefix}" + echo " Z2M: topic=${z2m_topic}" + echo "" + echo "Next step: In Domoticz UI (Setup > Hardware), add:" + echo " Type: MQTT Client Gateway with LAN interface" + echo " Remote Address: ${broker}" + echo " Port: ${broker_port}" + echo " Topic prefix: ${topic_prefix}" +} + +cmd_configure_haproxy() { + require_root || { echo "Root required" >&2; exit 1; } + + local domain=$(uci_get domain network) + local port_val=$(uci_get port) + domain="${domain:-domoticz.secubox.local}" + port_val="${port_val:-8080}" + + # Use haproxyctl if available + if command -v haproxyctl >/dev/null 2>&1; then + haproxyctl add-vhost "$domain" "127.0.0.1:${port_val}" 2>&1 + local code=$? + if [ $code -eq 0 ]; then + uci set ${CONFIG}.network.haproxy='1' + uci commit ${CONFIG} + echo "HAProxy vhost configured for ${domain} -> 127.0.0.1:${port_val}" + else + echo "[ERROR] haproxyctl add-vhost failed" >&2 + return 1 + fi + else + echo "[ERROR] haproxyctl not found — install secubox-app-haproxy first" >&2 + return 1 + fi +} + +cmd_backup() { + require_root || { echo "Root required" >&2; exit 1; } + defaults + local backup_path="${1:-/tmp/domoticz-backup-$(date +%Y%m%d-%H%M%S).tar.gz}" + + if [ ! -d "$data_path/config" ]; then + echo "[ERROR] No data to backup at ${data_path}/config" >&2 + return 1 + fi + + tar czf "$backup_path" -C "$data_path" config 2>&1 + local code=$? + if [ $code -eq 0 ]; then + echo "Backup created: ${backup_path}" + else + echo "[ERROR] Backup failed" >&2 + return 1 + fi +} + +cmd_restore() { + require_root || { echo "Root required" >&2; exit 1; } + defaults + local backup_path="$1" + + if [ -z "$backup_path" ] || [ ! -f "$backup_path" ]; then + echo "[ERROR] Backup file not found: ${backup_path}" >&2 + return 1 + fi + + # Stop service before restore + /etc/init.d/domoticz stop >/dev/null 2>&1 + stop_container + + ensure_dir "$data_path" + tar xzf "$backup_path" -C "$data_path" 2>&1 + local code=$? + if [ $code -eq 0 ]; then + echo "Restored from ${backup_path}" + echo "Restart Domoticz with: /etc/init.d/domoticz start" + else + echo "[ERROR] Restore failed" >&2 + return 1 + fi +} + +cmd_mesh_register() { + require_root || { echo "Root required" >&2; exit 1; } + defaults + + if command -v secubox-p2p >/dev/null 2>&1; then + secubox-p2p register-service domoticz "$port" 2>&1 + uci set ${CONFIG}.mesh.enabled='1' + uci commit ${CONFIG} + echo "Domoticz registered in P2P mesh on port ${port}" + else + echo "[ERROR] secubox-p2p not found — install secubox-p2p first" >&2 + return 1 + fi +} + cmd_service_run() { - require_root || { echo Root required >&2; exit 1; } + require_root || { echo "Root required" >&2; exit 1; } check_prereqs || exit 1 defaults stop_container + local docker_args="--name $CONTAINER -p ${port}:8080 -v $data_path/config:/config" [ -d "$devices_path" ] && docker_args="$docker_args -v $devices_path:/devices" + + # If MQTT enabled, pass broker info to container network + local mqtt_en=$(uci_get enabled mqtt) + if [ "${mqtt_en:-0}" = "1" ]; then + docker_args="$docker_args --add-host=mqtt-broker:$(uci_get broker mqtt || echo 127.0.0.1)" + fi + exec docker run --rm $docker_args -e TZ="$timezone" "$image" } -cmd_service_stop() { require_root || { echo Root required >&2; exit 1; }; stop_container; } +cmd_service_stop() { + require_root || { echo "Root required" >&2; exit 1; } + stop_container +} case "${1:-}" in install) shift; cmd_install "$@" ;; + uninstall) shift; cmd_uninstall "$@" ;; check) shift; cmd_check "$@" ;; update) shift; cmd_update "$@" ;; status) shift; cmd_status "$@" ;; logs) shift; cmd_logs "$@" ;; + configure-mqtt) shift; cmd_configure_mqtt "$@" ;; + configure-haproxy) shift; cmd_configure_haproxy "$@" ;; + backup) shift; cmd_backup "$@" ;; + restore) shift; cmd_restore "$@" ;; + mesh-register) shift; cmd_mesh_register "$@" ;; service-run) shift; cmd_service_run "$@" ;; service-stop) shift; cmd_service_stop "$@" ;; help|--help|-h|'') usage ;; diff --git a/package/secubox/secubox-core/root/usr/share/secubox/catalog.json b/package/secubox/secubox-core/root/usr/share/secubox/catalog.json index a292d905..e463e4e9 100644 --- a/package/secubox/secubox-core/root/usr/share/secubox/catalog.json +++ b/package/secubox/secubox-core/root/usr/share/secubox/catalog.json @@ -1523,7 +1523,7 @@ "version": "1.0.0", "category": "iot", "runtime": "docker", - "description": "Home automation system with support for various devices and protocols", + "description": "Home automation system with MQTT bridge, Zigbee2MQTT integration, and IoT device management", "author": "CyberMind.fr", "license": "GPL-3.0", "url": "https://www.domoticz.com/", @@ -1532,34 +1532,56 @@ "home-automation", "iot", "smart-home", - "docker" + "docker", + "mqtt", + "zigbee", + "mesh" ], "packages": { "required": [ "secubox-app-domoticz", + "luci-app-domoticz", "docker", "dockerd" + ], + "optional": [ + "mosquitto-nossl", + "secubox-app-zigbee2mqtt" ] }, "capabilities": [ "home-automation", "iot", - "smart-home" + "smart-home", + "mqtt-bridge", + "zigbee", + "mesh-service" ], "requirements": { "min_ram_mb": 256, "min_storage_mb": 100 }, "status": "stable", - "pkg_version": "1.0.0-2", + "pkg_version": "1.0.0-3", "app_version": "1.0.0", "changelog": { + "1.0.0-3": { + "date": "2026-02-04", + "changes": [ + "LuCI dashboard with IoT integration status", + "MQTT auto-bridge for Mosquitto and Zigbee2MQTT", + "HAProxy reverse proxy integration", + "P2P mesh service registration", + "Backup and restore support", + "USB device passthrough visibility" + ] + }, "1.0.0": { "date": "2026-01-04", "changes": [ - "Added support for new devices", - "Improved automation rules", - "Enhanced device discovery" + "Initial Docker-based deployment", + "domoticzctl CLI manager", + "UCI configuration" ] } },