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 <noreply@anthropic.com>
This commit is contained in:
parent
79bb3c43f4
commit
89896568b1
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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).
|
||||
|
||||
29
package/secubox/luci-app-domoticz/Makefile
Normal file
29
package/secubox/luci-app-domoticz/Makefile
Normal file
@ -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 <contact@cybermind.fr>
|
||||
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))
|
||||
57
package/secubox/luci-app-domoticz/README.md
Normal file
57
package/secubox/luci-app-domoticz/README.md
Normal file
@ -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
|
||||
@ -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 = '<span style="color:' + color + '; font-weight:bold;">' + label + '</span>';
|
||||
if (cs === 'running' && status.container_uptime)
|
||||
html += ' <span style="color:#8892b0;">(' + status.container_uptime + ')</span>';
|
||||
return html;
|
||||
};
|
||||
|
||||
o = s.option(form.DummyValue, '_docker', _('Docker'));
|
||||
o.rawhtml = true;
|
||||
o.cfgvalue = function() {
|
||||
return status.docker_available
|
||||
? '<span style="color:#27ae60;">Available</span>'
|
||||
: '<span style="color:#e74c3c;">Not available</span>';
|
||||
};
|
||||
|
||||
o = s.option(form.DummyValue, '_info', _('Details'));
|
||||
o.rawhtml = true;
|
||||
o.cfgvalue = function() {
|
||||
var port = status.port || 8080;
|
||||
var html = '<table style="border-collapse:collapse;">';
|
||||
html += '<tr><td style="padding:2px 12px 2px 0;color:#8892b0;">Image:</td><td>' + (status.image || '-') + '</td></tr>';
|
||||
html += '<tr><td style="padding:2px 12px 2px 0;color:#8892b0;">Port:</td><td>' + port + '</td></tr>';
|
||||
html += '<tr><td style="padding:2px 12px 2px 0;color:#8892b0;">Data:</td><td>' + (status.data_path || '-') + '</td></tr>';
|
||||
html += '<tr><td style="padding:2px 12px 2px 0;color:#8892b0;">Domain:</td><td>' + (status.domain || '-') + '</td></tr>';
|
||||
if (status.disk_usage)
|
||||
html += '<tr><td style="padding:2px 12px 2px 0;color:#8892b0;">Disk:</td><td>' + status.disk_usage + '</td></tr>';
|
||||
if (status.usb_devices && status.usb_devices.length > 0)
|
||||
html += '<tr><td style="padding:2px 12px 2px 0;color:#8892b0;">USB:</td><td>' + status.usb_devices.join(', ') + '</td></tr>';
|
||||
html += '</table>';
|
||||
return html;
|
||||
};
|
||||
|
||||
/* ---- IoT Integration Status ---- */
|
||||
o = s.option(form.DummyValue, '_iot', _('IoT Integration'));
|
||||
o.rawhtml = true;
|
||||
o.cfgvalue = function() {
|
||||
var html = '<table style="border-collapse:collapse;">';
|
||||
|
||||
// Mosquitto
|
||||
var mc = statusColor(status.mosquitto_status);
|
||||
html += '<tr><td style="padding:2px 12px 2px 0;color:#8892b0;">Mosquitto:</td>';
|
||||
html += '<td style="color:' + mc + ';">' + statusLabel(status.mosquitto_status) + '</td></tr>';
|
||||
|
||||
// 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 += '<tr><td style="padding:2px 12px 2px 0;color:#8892b0;">Zigbee2MQTT:</td>';
|
||||
html += '<td style="color:' + zc + ';">' + zl + '</td></tr>';
|
||||
|
||||
// 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 += '<tr><td style="padding:2px 12px 2px 0;color:#8892b0;">MQTT Bridge:</td>';
|
||||
html += '<td style="color:' + be + ';">' + bl + '</td></tr>';
|
||||
|
||||
html += '</table>';
|
||||
return html;
|
||||
};
|
||||
|
||||
/* ---- Network Integration ---- */
|
||||
o = s.option(form.DummyValue, '_integrations', _('Network'));
|
||||
o.rawhtml = true;
|
||||
o.cfgvalue = function() {
|
||||
var html = '<table style="border-collapse:collapse;">';
|
||||
|
||||
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 += '<tr><td style="padding:2px 12px 2px 0;color:#8892b0;">HAProxy:</td><td style="color:' + hc + ';">' + hl + '</td></tr>';
|
||||
|
||||
var meshc = status.mesh_enabled ? '#27ae60' : '#8892b0';
|
||||
var meshl = status.mesh_enabled ? 'Enabled' : 'Disabled';
|
||||
html += '<tr><td style="padding:2px 12px 2px 0;color:#8892b0;">Mesh P2P:</td><td style="color:' + meshc + ';">' + meshl + '</td></tr>';
|
||||
|
||||
var fc = status.firewall_wan ? '#27ae60' : '#8892b0';
|
||||
var fl = status.firewall_wan ? 'WAN access on port ' + (status.port || 8080) : 'LAN only';
|
||||
html += '<tr><td style="padding:2px 12px 2px 0;color:#8892b0;">Firewall:</td><td style="color:' + fc + ';">' + fl + '</td></tr>';
|
||||
|
||||
html += '</table>';
|
||||
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 '<div id="domoticz-logs" style="background:#0a0a1a; color:#ccc; padding:8px; ' +
|
||||
'border-radius:4px; font-family:monospace; font-size:12px; max-height:300px; ' +
|
||||
'overflow-y:auto; white-space:pre-wrap; min-height:40px;">Click "Fetch Logs" to view container output.</div>';
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
@ -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
|
||||
@ -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}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 <contact@cybermind.fr>
|
||||
|
||||
@ -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 <path> # 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
|
||||
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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 <command>
|
||||
|
||||
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 <path> 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 ;;
|
||||
|
||||
@ -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"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user