diff --git a/DOCS/ARCHITECTURE_NOTES.md b/DOCS/ARCHITECTURE_NOTES.md new file mode 100644 index 0000000..c32df3f --- /dev/null +++ b/DOCS/ARCHITECTURE_NOTES.md @@ -0,0 +1,110 @@ +# SecuBox Architecture Notes + +**Version:** 1.0.0 +**Last Updated:** 2025-12-28 +**Status:** Active + +These notes summarize the repository structure, conventions, and supporting tooling gathered from `README.md`, `DOCS/QUICK-START.md`, `DOCS/DEVELOPMENT-GUIDELINES.md`, `DOCS/CODEX.md`, `DOCS/DOCUMENTATION-INDEX.md`, the various module READMEs, and the `secubox-tools/` scripts. Use this as a rapid orientation before extending the platform. + +--- + +## Repository Layout (high level) + +- **LuCI modules (`luci-app-*`)** – Each app bundles a LuCI frontend (JS under `htdocs/luci-static`), RPCD backend (shell scripts in `root/usr/libexec/rpcd/`), UCI defaults (`root/etc/config/...`), ACL/menu descriptors, and module-specific README (see e.g. `luci-app-netdata-dashboard/README.md`). Naming conventions follow the “menu path = view path” and “RPC object name = file name” rules emphasized in `DOCS/QUICK-START.md`. +- **Themes (`luci-theme-secubox`)** – Provides the shared SecuBox design tokens (`sh-*`, `sb-*` classes, palette, typography). All new UI should import `secubox-theme/secubox-theme.css` and leverage the CSS variables described in `DOCS/DEVELOPMENT-GUIDELINES.md`. +- **Core orchestration apps** + - `luci-app-secubox`: global hub UI. + - `luci-app-system-hub`: system health/remote assistance. + - `luci-app-network-modes`: prebuilt router/sniffer modes (bridges, AP, relay, etc.). + - `luci-app-vhost-manager`: reverse-proxy/vhost configuration (existing baseline for future work). +- **Tooling (`secubox-tools/`)** – Bash/POSIX scripts for validation, building, deployment, permission repair, etc. The README documents workflows such as `validate-modules.sh`, `local-build.sh`, `fix-permissions.sh`, and `deploy-*.sh`. +- **Automation & Docs** + - `DOCS/` + `docs/`: mirrored, versioned documentation tree (design system, prompts, module templates, validation, permissions, etc.). + - `EXAMPLES/` and `templates/`: snippets and scaffolding. + - CI workflows live in `.github/workflows/` (referenced from README badges). + +--- + +## Coding & Packaging Standards + +Summarized from `DOCS/QUICK-START.md`, `DOCS/DEVELOPMENT-GUIDELINES.md`, and `DOCS/CODEX.md`: + +1. **RPCD/ubus** + - RPC script filename **must** equal the ubus object (e.g., `root/usr/libexec/rpcd/luci.netdata-dashboard` ⇒ `object: 'luci.netdata-dashboard'`). + - RPC scripts are executable (755) while JS/CSS assets are 644. + - ACL and menu JSON entries shipped under `root/usr/share/rpcd/acl.d/` and `root/usr/share/luci/menu.d/`. +2. **LuCI Views** + - Menu `path` mirrors `htdocs/luci-static/resources/view/...` location. + - Use the SecuBox design system (`sh-*`/`sb-*` classes, Inter + JetBrains Mono, gradients). Avoid inline styles; rely on variables defined in `system-hub/common.css` or `secubox-theme`. +3. **UCI-first configuration** + - Every feature stores runtime state in `/etc/config/`. + - CLI/scripts mutate config via `uci set/commit` and services watch UCI for changes. +4. **Service supervision** + - Prefer `procd` init scripts (`/etc/init.d/`) for daemons; ensure restart semantics and `ENABLE` flags. +5. **Validation workflow** + - Run `./secubox-tools/validate-modules.sh` before PRs/releases. It checks permissions, RPC naming, LuCI assets, etc. + - Use `./secubox-tools/local-build.sh build ` for SDK builds; `fix-permissions.sh` to normalize deployments. + +--- + +## Networking & Firewall Conventions + +Derived from `luci-app-network-modes`, `luci-app-client-guardian`, and docs: + +- Network modes (router/sniffer/AP) are implemented via UCI (`/etc/config/network`, `/etc/config/wireless`) with helper scripts ensuring safe defaults and backups before applying. +- Firewall zones follow OpenWrt defaults (lan/wan) with additional zones per feature (IoT, Guest, Quarantine). Changes should add backup/rollback paths and never remove LAN management access. +- Reverse proxy/vhost management currently relies on `luci-app-vhost-manager`; new work should extend its UCI schema rather than inventing a parallel config. + +--- + +## Storage & Runtime Expectations + +- Target platform: OpenWrt 24.10.x (and preview 25.12 RC) on ARM64 per `README.md`. Flash is limited; prefer `/srv` or external storage for large artifacts (Docker/LXC images). +- Overlay space must be <90% before deploying; quick-start docs include SSH check commands. +- `/tmp` is tmpfs; don’t store persistent state there. Use `/srv/` (Zigbee2MQTT), `/var/lib/`, or configurable data roots. + +--- + +## secubox-tools Highlights + +- `validate-modules.sh` – structural lint, permissions, ubus paths. +- `local-build.sh` – OpenWrt SDK automation for building/testing packages (supports multiple arches). +- `fix-permissions.sh`, `deploy-*.sh` – remote deployment helpers with backup/restore. +- These scripts are POSIX shell friendly and should be leveraged (or extended) for new installers/diagnostics. + +--- + +## Existing Modules & Patterns + +- **Monitoring dashboards** (Netdata, Netifyd, CrowdSec, WireGuard, Traffic Shaper, Bandwidth Manager) provide consistent patterns for: + - RPC APIs returning JSON for LuCI views. + - JS views using `view.extend`, `poll` auto-refresh, and stat cards built with design system classes. +- **Security & NAC modules** (Client Guardian, Auth Guardian) showcase how to integrate with firewall/zones, handle ACL, and present complex forms in LuCI. +- **Network orchestration** (Network Modes, System Hub) demonstrates multi-step wizards, health checks, and service controls. +- **Theme & Navigation** (luci-theme-secubox, luci-app-secubox) define tab components, cascade helpers, and global CSS/JS assets. + +Use these modules as references when building new “apps” to ensure consistent UX, RPC layout, and packaging. + +--- + +## Documentation Requirements + +- Every new or edited `.md` must follow the metadata header and versioning rules defined in `DOCS/DOCUMENTATION-INDEX.md`. +- Cross-link relevant guides (Quick Start, Dev Guidelines, Codex) when introducing new features. +- For platform-level additions (App Store, Vhost manager, DMZ mode, etc.), update both `DOCS/` and `docs/` to keep mkdocs + markdown parity. + +--- + +## Future Work Context + +`TODO-ANALYSE.md` highlights ongoing documentation and automation initiatives: +- Standardize version headers across documentation. +- Add “See Also” cross-links. +- Maintain an archive folder for legacy docs. +- Build new testing/performance/security guides. + +Keep these in mind when touching docs or adding new features so we converge toward the roadmap. + +--- + +This document will evolve alongside the App Store, Docker/LXC frameworks, and new wizard/profile systems. Update it whenever repository architecture or workflows change significantly. diff --git a/DOCS/DOCUMENTATION-INDEX.md b/DOCS/DOCUMENTATION-INDEX.md index 3823b75..31ef35b 100644 --- a/DOCS/DOCUMENTATION-INDEX.md +++ b/DOCS/DOCUMENTATION-INDEX.md @@ -206,7 +206,16 @@ Follow this template when creating or revising documentation: --- -### 4. Tools & Scripts Documentation +### 4. Embedded Deployment Guides + +#### **embedded/docker-zigbee2mqtt.md** 🔌 +*Deploy Zigbee2MQTT via Docker on SecuBox (ARM64).* + +Pointer: see `docs/embedded/docker-zigbee2mqtt.md` for the canonical version. + +--- + +### 5. Tools & Scripts Documentation #### **secubox-tools/README.md** 🔧 *Documentation for validation and build tools* @@ -225,7 +234,7 @@ Follow this template when creating or revising documentation: --- -### 5. Live Demo & Examples +### 6. Live Demo & Examples #### **Live Demo Website** 🌐 *Production demo of all modules* diff --git a/DOCS/embedded/docker-zigbee2mqtt.md b/DOCS/embedded/docker-zigbee2mqtt.md new file mode 100644 index 0000000..bd1945f --- /dev/null +++ b/DOCS/embedded/docker-zigbee2mqtt.md @@ -0,0 +1,7 @@ +# Docker Zigbee2MQTT on OpenWrt ARM64 + +**Version:** 1.0.0 +**Last Updated:** 2025-12-28 +**Status:** Active + +See `docs/embedded/docker-zigbee2mqtt.md` for the full guide. This mirrored copy is maintained for MkDocs builds. Keep both files in sync when editing. diff --git a/docs/ARCHITECTURE_NOTES.md b/docs/ARCHITECTURE_NOTES.md new file mode 100644 index 0000000..5d4ea83 --- /dev/null +++ b/docs/ARCHITECTURE_NOTES.md @@ -0,0 +1,7 @@ +# SecuBox Architecture Notes + +**Version:** 1.0.0 +**Last Updated:** 2025-12-28 +**Status:** Active + +This file mirrors the architecture summary maintained in `DOCS/ARCHITECTURE_NOTES.md`. See that copy for the canonical version. diff --git a/docs/documentation-index.md b/docs/documentation-index.md index 2a80668..359fabb 100644 --- a/docs/documentation-index.md +++ b/docs/documentation-index.md @@ -206,7 +206,24 @@ Follow this template when creating or revising documentation: --- -### 4. Tools & Scripts Documentation +### 4. Embedded Deployment Guides + +#### **embedded/docker-zigbee2mqtt.md** 🔌 +*Deploy Zigbee2MQTT via Docker on SecuBox (ARM64).* + +**Contents:** +- Prerequisite checklist (storage, cgroups, USB coordinator, Docker packages) +- Usage of `zigbee2mqttctl` (install/check/update/status/logs) +- UCI configuration reference (`/etc/config/zigbee2mqtt`) +- Troubleshooting + rollback/uninstall steps + +**When to use:** Setting up Zigbee2MQTT before the LuCI UI is available. + +**Size:** Short (~100 lines) + +--- + +### 5. Tools & Scripts Documentation #### **secubox-tools/README.md** 🔧 *Documentation for validation and build tools* @@ -225,7 +242,7 @@ Follow this template when creating or revising documentation: --- -### 5. Live Demo & Examples +### 6. Live Demo & Examples #### **Live Demo Website** 🌐 *Production demo of all modules* diff --git a/docs/embedded/docker-zigbee2mqtt.md b/docs/embedded/docker-zigbee2mqtt.md new file mode 100644 index 0000000..6e6af74 --- /dev/null +++ b/docs/embedded/docker-zigbee2mqtt.md @@ -0,0 +1,115 @@ +# Docker Zigbee2MQTT on OpenWrt ARM64 + +**Version:** 1.0.0 +**Last Updated:** 2025-12-28 +**Status:** Active + +This guide explains how to deploy Zigbee2MQTT on SecuBox (OpenWrt ARM64) using the new `secubox-app-zigbee2mqtt` package. The workflow follows upstream guidance from zigbee2mqtt.io while respecting OpenWrt storage constraints, UCI configuration, and procd supervision. + +--- + +## Prerequisites + +- OpenWrt 24.10.x (or newer) ARM64 build with SecuBox feeds. +- Internet connectivity to fetch Docker images. +- USB coordinator exposed as `/dev/ttyACM0` (e.g., Sonoff Zigbee 3.0 dongle). Load `kmod-usb-acm`. +- At least 200 MB free on overlay or external storage mounted at `/srv`. +- Cgroups v1/v2 enabled (verify `/sys/fs/cgroup` exists). Run `scripts/diagnose.sh` for sanity checks. + +--- + +## Installation Steps + +1. **Install the package:** + ```sh + opkg update + opkg install secubox-app-zigbee2mqtt + ``` + +2. **Bootstrap prerequisites (Docker, storage, config):** + ```sh + zigbee2mqttctl install + ``` + This command will: + - verify kernel modules, cgroups, USB device, and storage + - install `dockerd`, `docker`, `containerd`, and `kmod-usb-acm` + - create `/srv/zigbee2mqtt/data` with `configuration.yaml` + - pull `ghcr.io/koenkk/zigbee2mqtt:latest` + - enable `/etc/init.d/zigbee2mqtt` + +3. **Configure UCI (optional adjustments):** + ```sh + uci set zigbee2mqtt.main.serial_port='/dev/ttyACM0' + uci set zigbee2mqtt.main.mqtt_host='mqtt://192.168.8.10:1883' + uci set zigbee2mqtt.main.mqtt_username='secubox' + uci set zigbee2mqtt.main.mqtt_password='secret' + uci commit zigbee2mqtt + ``` + +4. **Start the service:** + ```sh + /etc/init.d/zigbee2mqtt start + ``` + The procd service executes `docker run` in foreground mode and respawns automatically. + +5. **Check logs and status:** + ```sh + zigbee2mqttctl status + zigbee2mqttctl logs -f + ``` + +6. **Upgrade to the latest image:** + ```sh + zigbee2mqttctl update + ``` + This pulls the latest container and restarts the service if enabled. + +--- + +## Files & Services + +| Path | Purpose | +|------|---------| +| `/etc/config/zigbee2mqtt` | UCI configuration (serial port, MQTT, base topic, frontend port, data path). | +| `/etc/init.d/zigbee2mqtt` | procd wrapper that invokes `zigbee2mqttctl service-run`. | +| `/usr/sbin/zigbee2mqttctl` | Management CLI for install/check/update/status/logs. | +| `/srv/zigbee2mqtt/data` | Persistent Zigbee2MQTT state (configuration.yaml, database). | + +--- + +## Troubleshooting + +| Symptom | Fix | +|---------|-----| +| `zigbee2mqttctl install` reports missing cgroups | Ensure `/sys/fs/cgroup` exists and cgroups are enabled in kernel config. | +| `/dev/ttyACM0` not found | Load `kmod-usb-acm` and reconnect the Zigbee dongle; verify with `ls -l /dev/ttyACM*`. | +| Docker fails to start due to low space | Move `/srv/zigbee2mqtt` to an external drive or free overlay space (`df -h`). | +| MQTT authentication errors | Update `mqtt_username`/`mqtt_password` via UCI and restart service. | +| Port conflict on 8080 | Change `frontend_port` in UCI, then `/etc/init.d/zigbee2mqtt restart`. | + +Run `scripts/diagnose.sh` for aggregated checks (storage, cgroups, firewall sanity). Use `scripts/smoke_test.sh` to quickly start/stop the Zigbee2MQTT service and verify Docker state. + +--- + +## Rollback / Uninstall + +1. Stop and disable the service: + ```sh + /etc/init.d/zigbee2mqtt stop + /etc/init.d/zigbee2mqtt disable + ``` +2. Remove the container image (optional): + ```sh + docker rm -f secbx-zigbee2mqtt 2>/dev/null + docker rmi ghcr.io/koenkk/zigbee2mqtt:latest + ``` +3. Remove package: + ```sh + opkg remove secubox-app-zigbee2mqtt + ``` +4. Clean data directory if no longer needed: + ```sh + rm -rf /srv/zigbee2mqtt + ``` + +Reinstall later by reinstalling the package and rerunning `zigbee2mqttctl install`. diff --git a/scripts/diagnose.sh b/scripts/diagnose.sh new file mode 100755 index 0000000..2e1d758 --- /dev/null +++ b/scripts/diagnose.sh @@ -0,0 +1,85 @@ +#!/bin/sh +# +# SecuBox diagnostic helper. +# Checks overlay storage, cgroups availability, serial adapters, +# and basic firewall sanity for LAN/WAN zones. + +set -eu + +err() { printf '[ERROR] %s\n' "$*" >&2; } +warn() { printf '[WARN] %s\n' "$*" >&2; } +info() { printf '[INFO] %s\n' "$*"; } + +check_storage() { + local mountpoint="/overlay" + local free + free=$(df -Pm "$mountpoint" | awk 'NR==2 {print $4}') + if [ -z "$free" ]; then + err "Unable to read storage usage for $mountpoint" + return 1 + fi + info "Overlay free space: ${free}MB" + if [ "$free" -lt 100 ]; then + warn "Overlay has less than 100MB free. Consider cleaning before installing apps." + fi +} + +check_cgroups() { + if [ ! -d /sys/fs/cgroup ]; then + err "cgroups are not mounted at /sys/fs/cgroup" + return 1 + fi + if ! mount | grep -q 'cgroup'; then + warn "cgroups filesystem present but not mounted. Docker/LXC will fail." + else + info "cgroups mount detected." + fi +} + +check_serial() { + if ls /dev/ttyACM* >/dev/null 2>&1; then + info "USB ACM device(s): $(ls /dev/ttyACM* 2>/dev/null | tr '\n' ' ')" + else + warn "No /dev/ttyACM* device detected. Plug Zigbee coordinator or load kmod-usb-acm." + fi +} + +check_firewall() { + if ! command -v uci >/dev/null 2>&1; then + warn "uci not found; skipping firewall checks." + return + fi + local lan_idx + lan_idx=$(uci show firewall | grep -n "name='lan'" | head -n1 | cut -d: -f1 || true) + if [ -z "$lan_idx" ]; then + warn "Firewall LAN zone missing." + else + info "Firewall LAN zone detected." + fi + local wan_idx + wan_idx=$(uci show firewall | grep -n "name='wan'" | head -n1 | cut -d: -f1 || true) + if [ -z "$wan_idx" ]; then + warn "Firewall WAN zone missing." + else + info "Firewall WAN zone detected." + fi +} + +check_docker() { + if command -v docker >/dev/null 2>&1; then + info "Docker CLI available: $(docker --version 2>/dev/null)" + else + warn "Docker CLI not found. Install dockerd/docker packages before deploying containers." + fi +} + +main() { + check_storage + check_cgroups + check_serial + check_firewall + check_docker + info "Diagnostics complete." +} + +main "$@" diff --git a/scripts/smoke_test.sh b/scripts/smoke_test.sh new file mode 100755 index 0000000..0904d0b --- /dev/null +++ b/scripts/smoke_test.sh @@ -0,0 +1,65 @@ +#!/bin/sh +# +# Basic smoke tests for SecuBox apps. +# - Verifies zigbee2mqtt service start/stop. +# - Checks Docker container health. +# - Optionally exercises MQTT pub/sub if mosquitto-clients installed. + +set -eu + +log() { printf '[SMOKE] %s\n' "$*"; } +err() { printf '[ERROR] %s\n' "$*" >&2; } + +ensure_service() { + local svc="$1" + if [ ! -x "/etc/init.d/$svc" ]; then + err "Service $svc not installed." + return 1 + fi +} + +check_zigbee2mqtt() { + if ! ensure_service zigbee2mqtt; then + return + fi + log "Starting zigbee2mqtt..." + /etc/init.d/zigbee2mqtt start || return 1 + sleep 3 + if docker ps --filter "name=secbx-zigbee2mqtt" --format '{{.Names}}' | grep -q secbx-zigbee2mqtt; then + log "Zigbee2MQTT container is running." + else + err "Zigbee2MQTT container failed to start." + fi + log "Stopping zigbee2mqtt..." + /etc/init.d/zigbee2mqtt stop || true +} + +check_mqtt_pubsub() { + if ! command -v mosquitto_pub >/dev/null 2>&1; then + log "mosquitto-clients not installed; skipping MQTT smoke test." + return + fi + local topic="secbx/smoke/test" + log "Publishing MQTT test message on $topic..." + mosquitto_pub -t "$topic" -m "smoke-test" >/dev/null 2>&1 || err "Failed to publish test message." +} + +check_ports() { + local port + for port in 8080; do + if netstat -tln 2>/dev/null | grep -q ":$port "; then + log "Port $port listening." + else + log "Port $port not listening (may be expected if zigbee2mqtt is stopped)." + fi + done +} + +main() { + check_zigbee2mqtt + check_mqtt_pubsub + check_ports + log "Smoke tests completed." +} + +main "$@" diff --git a/secubox-app-zigbee2mqtt/Makefile b/secubox-app-zigbee2mqtt/Makefile new file mode 100644 index 0000000..e60e1c8 --- /dev/null +++ b/secubox-app-zigbee2mqtt/Makefile @@ -0,0 +1,42 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=secubox-app-zigbee2mqtt +PKG_RELEASE:=1 +PKG_VERSION:=1.0.0 +PKG_MAINTAINER:=CyberMind Studio +PKG_LICENSE:=Apache-2.0 + +include $(INCLUDE_DIR)/package.mk + +define Package/secubox-app-zigbee2mqtt + SECTION:=utils + CATEGORY:=Utilities + SUBMENU:=SecuBox Apps + TITLE:=SecuBox Zigbee2MQTT docker app + DEPENDS:=+uci +libuci +kmod-usb-acm +bash? +dockerd +docker +containerd +endef + +define Package/secubox-app-zigbee2mqtt/description +Installer, configuration, and service manager for running Zigbee2MQTT +inside Docker on SecuBox-powered OpenWrt systems. +endef + +define Package/secubox-app-zigbee2mqtt/conffiles +/etc/config/zigbee2mqtt +endef + +define Build/Compile +endef + +define Package/secubox-app-zigbee2mqtt/install + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./files/etc/config/zigbee2mqtt $(1)/etc/config/zigbee2mqtt + + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./files/etc/init.d/zigbee2mqtt $(1)/etc/init.d/zigbee2mqtt + + $(INSTALL_DIR) $(1)/usr/sbin + $(INSTALL_BIN) ./files/usr/sbin/zigbee2mqttctl $(1)/usr/sbin/zigbee2mqttctl +endef + +$(eval $(call BuildPackage,secubox-app-zigbee2mqtt)) diff --git a/secubox-app-zigbee2mqtt/files/etc/config/zigbee2mqtt b/secubox-app-zigbee2mqtt/files/etc/config/zigbee2mqtt new file mode 100644 index 0000000..1bfd773 --- /dev/null +++ b/secubox-app-zigbee2mqtt/files/etc/config/zigbee2mqtt @@ -0,0 +1,12 @@ +config zigbee2mqtt 'main' + option enabled '0' + option image 'ghcr.io/koenkk/zigbee2mqtt:latest' + option serial_port '/dev/ttyACM0' + option mqtt_host 'mqtt://127.0.0.1:1883' + option mqtt_username '' + option mqtt_password '' + option base_topic 'zigbee2mqtt' + option frontend_port '8080' + option channel '11' + option data_path '/srv/zigbee2mqtt' + option timezone 'UTC' diff --git a/secubox-app-zigbee2mqtt/files/etc/init.d/zigbee2mqtt b/secubox-app-zigbee2mqtt/files/etc/init.d/zigbee2mqtt new file mode 100644 index 0000000..63b37f3 --- /dev/null +++ b/secubox-app-zigbee2mqtt/files/etc/init.d/zigbee2mqtt @@ -0,0 +1,23 @@ +#!/bin/sh /etc/rc.common + +START=95 +STOP=10 +USE_PROCD=1 + +SERVICE_BIN="/usr/sbin/zigbee2mqttctl" + +start_service() { + procd_open_instance + procd_set_param command "$SERVICE_BIN" service-run + procd_set_param respawn 2000 5 5 + procd_close_instance +} + +stop_service() { + "$SERVICE_BIN" service-stop >/dev/null 2>&1 +} + +restart_service() { + stop_service + start_service +} diff --git a/secubox-app-zigbee2mqtt/files/usr/sbin/zigbee2mqttctl b/secubox-app-zigbee2mqtt/files/usr/sbin/zigbee2mqttctl new file mode 100644 index 0000000..7607081 --- /dev/null +++ b/secubox-app-zigbee2mqtt/files/usr/sbin/zigbee2mqttctl @@ -0,0 +1,208 @@ +#!/bin/sh +# +# SecuBox Zigbee2MQTT manager +# Handles prerequisite checks, Docker installation, container lifecycle, +# and configuration generation using UCI. + +CONFIG="zigbee2mqtt" +CONTAINER="secbx-zigbee2mqtt" +OPKG_UPDATED=0 + +usage() { + cat <<'EOF' +Usage: zigbee2mqttctl + +Commands: + install Run prerequisite checks, install Docker packages, prepare data dir + check Validate kernel modules, cgroups, storage, and serial access + update Pull the latest Zigbee2MQTT image and restart the service + status Show container and service status + logs Show Docker logs (pass -f to follow) + service-run Internal: invoked by procd to run the container + service-stop Stop the running container + help Show this message +EOF +} + +require_root() { + if [ "$(id -u)" -ne 0 ]; then + echo "This command requires root privileges." >&2 + exit 1 + fi +} + +uci_get() { + uci -q get "${CONFIG}.main.$1" +} + +defaults() { + serial_port="$(uci_get serial_port || echo /dev/ttyACM0)" + mqtt_host="$(uci_get mqtt_host || echo mqtt://127.0.0.1:1883)" + mqtt_user="$(uci_get mqtt_username || printf '')" + mqtt_pass="$(uci_get mqtt_password || printf '')" + base_topic="$(uci_get base_topic || echo zigbee2mqtt)" + frontend_port="$(uci_get frontend_port || echo 8080)" + channel="$(uci_get channel || echo 11)" + image="$(uci_get image || echo ghcr.io/koenkk/zigbee2mqtt:latest)" + data_path="$(uci_get data_path || echo /srv/zigbee2mqtt)" + timezone="$(uci_get timezone || echo UTC)" +} + +ensure_dir() { + local path="$1" + [ -d "$path" ] || mkdir -p "$path" +} + +ensure_packages() { + local pkgs="$*" + require_root + for pkg in $pkgs; do + if ! opkg status "$pkg" >/dev/null 2>&1; then + if [ "$OPKG_UPDATED" -eq 0 ]; then + opkg update || return 1 + OPKG_UPDATED=1 + fi + opkg install "$pkg" || return 1 + fi + done + return 0 +} + +check_storage() { + local target="$1" + local free_kb + free_kb=$(df -Pk "${target:-/overlay}" | awk 'NR==2 {print $4}') + [ -z "$free_kb" ] && free_kb=0 + if [ "$free_kb" -lt 102400 ]; then + echo "[WARN] Less than 100MB free on ${target:-overlay}. Docker images may fail." >&2 + fi +} + +check_cgroups() { + if [ ! -d /sys/fs/cgroup ]; then + echo "[ERROR] /sys/fs/cgroup missing. Enable cgroups in the kernel." >&2 + return 1 + fi + return 0 +} + +check_serial() { + local port="$1" + if [ ! -c "$port" ]; then + echo "[WARN] Serial device $port not found. Plug the Zigbee coordinator first." >&2 + fi +} + +check_prereqs() { + defaults + check_storage "$data_path" + check_cgroups || return 1 + check_serial "$serial_port" + ensure_packages kmod-usb-acm || return 1 + return 0 +} + +ensure_docker() { + ensure_packages containerd docker dockerd || return 1 + /etc/init.d/dockerd enable >/dev/null 2>&1 + /etc/init.d/dockerd start >/dev/null 2>&1 +} + +generate_configuration() { + defaults + ensure_dir "$data_path/data" + cat > "$data_path/data/configuration.yaml" </dev/null 2>&1 || true + docker rm "$CONTAINER" >/dev/null 2>&1 || true +} + +cmd_install() { + require_root + check_prereqs || exit 1 + ensure_docker || exit 1 + ensure_dir "$data_path" + generate_configuration + pull_image + /etc/init.d/zigbee2mqtt enable + echo "Zigbee2MQTT prerequisites installed. Enable with: /etc/init.d/zigbee2mqtt start" +} + +cmd_check() { + check_prereqs + echo "Prerequisite check completed." +} + +cmd_update() { + require_root + defaults + pull_image || exit 1 + if /etc/init.d/zigbee2mqtt enabled >/dev/null 2>&1; then + /etc/init.d/zigbee2mqtt restart + else + echo "Image updated. Restart manually to apply." + fi +} + +cmd_status() { + docker ps -a --filter "name=$CONTAINER" +} + +cmd_logs() { + docker logs "$@" "$CONTAINER" +} + +cmd_service_run() { + require_root + check_prereqs || exit 1 + ensure_docker || exit 1 + generate_configuration + stop_container + defaults + exec docker run --rm \ + --name "$CONTAINER" \ + --device "$serial_port" \ + -p "${frontend_port}:8080" \ + -v "$data_path/data:/app/data" \ + -e TZ="$timezone" \ + "$image" +} + +cmd_service_stop() { + require_root + stop_container +} + +case "$1" in + install) shift; cmd_install "$@";; + check) shift; cmd_check "$@";; + update) shift; cmd_update "$@";; + status) shift; cmd_status "$@";; + logs) shift; cmd_logs "$@";; + service-run) shift; cmd_service_run "$@";; + service-stop) shift; cmd_service_stop "$@";; + help|--help|-h|"") usage;; + *) echo "Unknown command: $1" >&2; usage >&2; exit 1;; +esac