diff --git a/.claude/HISTORY.md b/.claude/HISTORY.md index f80f8743..c582fb82 100644 --- a/.claude/HISTORY.md +++ b/.claude/HISTORY.md @@ -136,4 +136,5 @@ _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. + - `domoticz`: Rewrite from Docker to LXC Debian container with native binary from GitHub releases. LuCI dashboard with IoT integration status (Mosquitto, Zigbee2MQTT, MQTT bridge), service lifecycle, HAProxy, mesh P2P, logs. `domoticzctl` with `configure-mqtt` (auto Mosquitto+Z2M bridge), `configure-haproxy`, `backup/restore`, `mesh-register`, `uninstall`. UCI extended with mqtt/network/mesh sections. Catalog updated. + - LXC cgroup2 fix: Added `lxc.tty.max`, `lxc.pty.max`, `lxc.cgroup2.devices.allow` for standard character devices, and `lxc.seccomp.profile` disable to fix terminal allocation failures on cgroup v2 systems. Applied to `streamlit` and `domoticz`. diff --git a/.claude/settings.local.json b/.claude/settings.local.json index aaa97e12..b50017bd 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -270,7 +270,8 @@ "Bash(/home/reepost/CyberMindStudio/secubox-openwrt/secubox-tools/local-build.sh:*)", "Bash(do rsync -av --delete /home/reepost/CyberMindStudio/secubox-openwrt/package/secubox/$pkg/ /home/reepost/CyberMindStudio/secubox-openwrt/secubox-tools/local-feed/$pkg/)", "Bash(do rsync -av --delete \"package/secubox/$pkg/\" \"secubox-tools/local-feed/$pkg/\")", - "Bash(xdg-open:*)" + "Bash(xdg-open:*)", + "WebFetch(domain:releases.domoticz.com)" ] } } diff --git a/package/secubox/luci-app-domoticz/README.md b/package/secubox/luci-app-domoticz/README.md index a225174f..4a7ceea3 100644 --- a/package/secubox/luci-app-domoticz/README.md +++ b/package/secubox/luci-app-domoticz/README.md @@ -12,7 +12,7 @@ Requires `secubox-app-domoticz` (installed as dependency). ## Features -- **Service Status**: Container status, Docker availability, disk usage, USB devices +- **Service Status**: Container status, LXC availability, memory/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 @@ -28,9 +28,9 @@ Requires `secubox-app-domoticz` (installed as dependency). | `start` | — | Start Domoticz service | | `stop` | — | Stop Domoticz service | | `restart` | — | Restart Domoticz service | -| `install` | — | Pull Docker image and configure | +| `install` | — | Create LXC container and download Domoticz | | `uninstall` | — | Remove container (preserves data) | -| `update` | — | Pull latest image and restart | +| `update` | — | Download latest Domoticz and restart | | `configure_mqtt` | — | Auto-configure Mosquitto and MQTT bridge | | `configure_haproxy` | — | Register HAProxy vhost | | `backup` | — | Create data backup | 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 index 3da807d9..eb1e6acb 100644 --- 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 @@ -119,10 +119,10 @@ return view.extend({ return html; }; - o = s.option(form.DummyValue, '_docker', _('Docker')); + o = s.option(form.DummyValue, '_lxc', _('LXC')); o.rawhtml = true; o.cfgvalue = function() { - return status.docker_available + return status.lxc_available ? 'Available' : 'Not available'; }; @@ -132,10 +132,11 @@ return view.extend({ o.cfgvalue = function() { var port = status.port || 8080; var html = ''; - html += ''; html += ''; html += ''; html += ''; + if (status.memory_usage) + html += ''; if (status.disk_usage) html += ''; if (status.usb_devices && status.usb_devices.length > 0) @@ -210,7 +211,7 @@ return view.extend({ o.inputstyle = 'apply'; o.onclick = function() { ui.showModal(_('Installing...'), [ - E('p', { 'class': 'spinning' }, _('Pulling Docker image and configuring...')) + E('p', { 'class': 'spinning' }, _('Creating LXC container and downloading Domoticz...')) ]); return callInstall().then(function(res) { ui.hideModal(); @@ -263,11 +264,11 @@ return view.extend({ } o = s.option(form.Button, '_update', _('Update')); - o.inputtitle = _('Pull Latest Image'); + o.inputtitle = _('Update Domoticz'); o.inputstyle = 'action'; o.onclick = function() { ui.showModal(_('Updating...'), [ - E('p', { 'class': 'spinning' }, _('Pulling latest Docker image and restarting...')) + E('p', { 'class': 'spinning' }, _('Downloading latest Domoticz and restarting container...')) ]); return callUpdate().then(function(res) { ui.hideModal(); @@ -327,10 +328,6 @@ return view.extend({ 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'; 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 index 1b8678bf..428ce6bf 100644 --- a/package/secubox/luci-app-domoticz/root/usr/libexec/rpcd/luci.domoticz +++ b/package/secubox/luci-app-domoticz/root/usr/libexec/rpcd/luci.domoticz @@ -3,9 +3,20 @@ . /lib/functions.sh . /usr/share/libubox/jshn.sh -CONTAINER="secbx-domoticz" +LXC_NAME="domoticz" +LXC_PATH="/srv/lxc" +LXC_ROOTFS="$LXC_PATH/$LXC_NAME/rootfs" +LXC_CONF="$LXC_PATH/$LXC_NAME/config" CONFIG="domoticz" +lxc_running() { + lxc-info -n "$LXC_NAME" -s 2>/dev/null | grep -q "RUNNING" +} + +lxc_exists() { + [ -f "$LXC_CONF" ] && [ -d "$LXC_ROOTFS" ] +} + case "$1" in list) echo '{"status":{},"start":{},"stop":{},"restart":{},"install":{},"uninstall":{},"update":{},"configure_mqtt":{},"configure_haproxy":{},"backup":{},"restore":{"path":"str"},"logs":{"lines":"int"}}' @@ -16,7 +27,6 @@ case "$1" in 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) @@ -38,7 +48,6 @@ case "$1" in 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}" @@ -55,24 +64,36 @@ case "$1" in 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 + # LXC availability + if command -v lxc-start >/dev/null 2>&1; then + json_add_boolean "lxc_available" 1 else - json_add_boolean "docker_available" 0 + json_add_boolean "lxc_available" 0 fi # Container status - if docker ps --filter "name=$CONTAINER" --format '{{.Status}}' 2>/dev/null | grep -q .; then + if lxc_running; then json_add_string "container_status" "running" - uptime=$(docker ps --filter "name=$CONTAINER" --format '{{.Status}}' 2>/dev/null) + uptime=$(lxc-info -n "$LXC_NAME" -s 2>/dev/null | head -1) json_add_string "container_uptime" "$uptime" - elif docker ps -a --filter "name=$CONTAINER" --format '{{.Status}}' 2>/dev/null | grep -q .; then + # Memory from cgroup + mem_usage="" + if [ -f "/sys/fs/cgroup/lxc.payload.$LXC_NAME/memory.current" ]; then + mem_bytes=$(cat "/sys/fs/cgroup/lxc.payload.$LXC_NAME/memory.current" 2>/dev/null) + if [ -n "$mem_bytes" ] && [ "$mem_bytes" -gt 0 ] 2>/dev/null; then + mem_mb=$(( mem_bytes / 1048576 )) + mem_usage="${mem_mb} MB" + fi + fi + json_add_string "memory_usage" "$mem_usage" + elif lxc_exists; then json_add_string "container_status" "stopped" json_add_string "container_uptime" "" + json_add_string "memory_usage" "" else json_add_string "container_status" "not_installed" json_add_string "container_uptime" "" + json_add_string "memory_usage" "" fi # Mosquitto broker status @@ -225,7 +246,7 @@ case "$1" in lines=$(echo "$input" | jsonfilter -e '@.lines' 2>/dev/null) [ -z "$lines" ] && lines=50 - logs=$(docker logs --tail "$lines" "$CONTAINER" 2>&1 | tail -100) + logs=$(/usr/sbin/domoticzctl logs "$lines" 2>&1) json_init json_add_string "logs" "$logs" json_dump diff --git a/package/secubox/secubox-app-domoticz/Makefile b/package/secubox/secubox-app-domoticz/Makefile index 6e6aa41a..603dbcfc 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:=3 +PKG_RELEASE:=4 PKG_VERSION:=1.0.0 PKG_ARCH:=all PKG_MAINTAINER:=CyberMind Studio @@ -14,13 +14,13 @@ define Package/secubox-app-domoticz CATEGORY:=Utilities PKGARCH:=all SUBMENU:=SecuBox Apps - TITLE:=SecuBox Domoticz docker app - DEPENDS:=dockerd +docker +containerd + TITLE:=SecuBox Domoticz LXC app + DEPENDS:=+lxc +lxc-common endef define Package/secubox-app-domoticz/description Installer, configuration, and service manager for running Domoticz -inside Docker on SecuBox-powered OpenWrt systems. +inside an LXC Alpine container on SecuBox-powered OpenWrt systems. endef define Package/secubox-app-domoticz/conffiles diff --git a/package/secubox/secubox-app-domoticz/README.md b/package/secubox/secubox-app-domoticz/README.md index 0008ef33..89f9c35f 100644 --- a/package/secubox/secubox-app-domoticz/README.md +++ b/package/secubox/secubox-app-domoticz/README.md @@ -1,6 +1,6 @@ # SecuBox Domoticz -Home automation platform running in Docker with MQTT bridge, Zigbee2MQTT integration, and P2P mesh support. +Home automation platform running in an LXC Alpine container with MQTT bridge, Zigbee2MQTT integration, and P2P mesh support. ## Installation @@ -17,7 +17,6 @@ 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' @@ -42,9 +41,9 @@ config domoticz 'mesh' ## Usage ```sh -domoticzctl install # Pull image, install prerequisites +domoticzctl install # Create LXC container, download Domoticz domoticzctl uninstall # Remove container (data preserved) -domoticzctl update # Pull latest image, restart +domoticzctl update # Download latest Domoticz, restart domoticzctl status # Show container status domoticzctl logs [-f] # Container logs domoticzctl configure-mqtt # Auto-setup Mosquitto + MQTT bridge @@ -79,7 +78,7 @@ When `secubox-app-zigbee2mqtt` is installed: ## Dependencies -- `dockerd`, `docker`, `containerd` +- `lxc`, `lxc-common` - 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 b8ec6cca..c9eb1413 100644 --- a/package/secubox/secubox-app-domoticz/files/etc/config/domoticz +++ b/package/secubox/secubox-app-domoticz/files/etc/config/domoticz @@ -1,6 +1,5 @@ config domoticz 'main' option enabled '0' - option image 'domoticz/domoticz:latest' option data_path '/srv/domoticz' option devices_path '/srv/devices' option port '8080' diff --git a/package/secubox/secubox-app-domoticz/files/usr/sbin/domoticzctl b/package/secubox/secubox-app-domoticz/files/usr/sbin/domoticzctl index 276e7e04..4c8514b7 100644 --- a/package/secubox/secubox-app-domoticz/files/usr/sbin/domoticzctl +++ b/package/secubox/secubox-app-domoticz/files/usr/sbin/domoticzctl @@ -1,8 +1,13 @@ #!/bin/sh -# SecuBox Domoticz manager — IoT home automation with MQTT/Zigbee integration +# SecuBox Domoticz manager — LXC Alpine container with MQTT/Zigbee integration CONFIG="domoticz" -CONTAINER="secbx-domoticz" +LXC_NAME="domoticz" +LXC_PATH="/srv/lxc" +LXC_ROOTFS="$LXC_PATH/$LXC_NAME/rootfs" +LXC_CONF="$LXC_PATH/$LXC_NAME/config" +DATA_PATH_DEFAULT="/srv/domoticz" +LOG_FILE="/srv/domoticz/domoticz.log" OPKG_UPDATED=0 usage() { @@ -10,13 +15,14 @@ usage() { Usage: domoticzctl Commands: - install Install prerequisites, prepare directories, pull image + install Create LXC container, download Domoticz, configure 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 + update Download latest Domoticz release and restart + status Show container and service status + logs [N] Show last N lines of logs (default: 50) + shell Open interactive shell in container + configure-mqtt Auto-configure Mosquitto broker and MQTT bridge configure-haproxy Register as HAProxy vhost for reverse proxy backup [path] Backup Domoticz data restore Restore Domoticz from backup @@ -26,6 +32,8 @@ Commands: USAGE } +# ---------- helpers ---------- + require_root() { [ "$(id -u)" -eq 0 ]; } uci_get() { @@ -33,13 +41,8 @@ uci_get() { uci -q get ${CONFIG}.${section}.$1 } -defaults() { - image="$(uci_get image || echo domoticz/domoticz:latest)" - data_path="$(uci_get data_path || echo /srv/domoticz)" - devices_path="$(uci_get devices_path || echo /srv/devices)" - port="$(uci_get port || echo 8080)" - timezone="$(uci_get timezone || echo UTC)" -} +log_info() { echo "[INFO] $*"; logger -t domoticzctl "$*"; } +log_error() { echo "[ERROR] $*" >&2; logger -t domoticzctl -p err "$*"; } ensure_dir() { [ -d "$1" ] || mkdir -p "$1"; } @@ -55,60 +58,450 @@ ensure_packages() { done } -check_prereqs() { +defaults() { + data_path="$(uci_get data_path || echo $DATA_PATH_DEFAULT)" + devices_path="$(uci_get devices_path || echo /srv/devices)" + port="$(uci_get port || echo 8080)" + timezone="$(uci_get timezone || echo UTC)" +} + +detect_arch() { + case "$(uname -m)" in + aarch64) echo "aarch64" ;; + armv7l) echo "armv7" ;; + x86_64) echo "x86_64" ;; + *) echo "x86_64" ;; + esac +} + +# ---------- LXC helpers ---------- + +lxc_running() { + lxc-info -n "$LXC_NAME" -s 2>/dev/null | grep -q "RUNNING" +} + +lxc_exists() { + [ -f "$LXC_CONF" ] && [ -d "$LXC_ROOTFS" ] +} + +lxc_exec() { + lxc-attach -n "$LXC_NAME" -- "$@" +} + +lxc_stop() { + if lxc_running; then + lxc-stop -n "$LXC_NAME" -k 2>/dev/null || true + sleep 1 + fi +} + +# ---------- rootfs creation ---------- + +lxc_create_rootfs() { + local arch=$(detect_arch) + + # Map to Debian architecture names + local debian_arch + case "$arch" in + aarch64) debian_arch="arm64" ;; + armv7) debian_arch="armhf" ;; + x86_64) debian_arch="amd64" ;; + *) debian_arch="amd64" ;; + esac + + # Use debootstrap if available, otherwise download pre-built rootfs + ensure_dir "$LXC_ROOTFS" + + # Minimal Debian rootfs via tarball from LXC image server + local rootfs_url="https://images.linuxcontainers.org/images/debian/bookworm/${debian_arch}/default/" + log_info "Downloading Debian bookworm rootfs for ${debian_arch}..." + + # Get latest build directory + local latest_path + latest_path=$(wget -q -O - "$rootfs_url" 2>/dev/null | grep -oE '[0-9]{8}_[0-9]{2}:[0-9]{2}' | tail -1) + if [ -z "$latest_path" ]; then + log_error "Failed to find latest Debian rootfs build" + return 1 + fi + + local tarball="/tmp/debian-domoticz.tar.xz" + local tarball_url="${rootfs_url}${latest_path}/rootfs.tar.xz" + wget -q -O "$tarball" "$tarball_url" || { + log_error "Failed to download Debian rootfs from $tarball_url" + return 1 + } + + tar -xJf "$tarball" -C "$LXC_ROOTFS" || { + log_error "Failed to extract Debian rootfs" + return 1 + } + rm -f "$tarball" + + # DNS + cp /etc/resolv.conf "$LXC_ROOTFS/etc/resolv.conf" 2>/dev/null || \ + echo "nameserver 127.0.0.1" > "$LXC_ROOTFS/etc/resolv.conf" + + # Create /dev/null for apt operations + [ -c "$LXC_ROOTFS/dev/null" ] || mknod -m 666 "$LXC_ROOTFS/dev/null" c 1 3 2>/dev/null + + # Install runtime dependencies via apt + log_info "Installing Domoticz runtime dependencies..." + chroot "$LXC_ROOTFS" /bin/sh -c " + apt-get -o Acquire::AllowInsecureRepositories=true update -qq 2>/dev/null && \ + apt-get --allow-unauthenticated install -y -qq --no-install-recommends \ + libcurl3-gnutls libusb-0.1-4 libusb-1.0-0 \ + python3-minimal libsqlite3-0 tzdata ca-certificates \ + 2>&1 | tail -5 + " || { + log_error "Failed to install runtime dependencies" + return 1 + } + + # Rebuild linker cache + chroot "$LXC_ROOTFS" ldconfig 2>/dev/null + + # Clean apt cache + chroot "$LXC_ROOTFS" /bin/sh -c "apt-get clean; rm -rf /var/lib/apt/lists/*" 2>/dev/null + + log_info "Debian rootfs created." +} + +domoticz_download() { + local arch=$(detect_arch) + local domoticz_arch="$arch" + + # Domoticz release URLs use different arch names + case "$arch" in + aarch64) domoticz_arch="aarch64" ;; + armv7) domoticz_arch="armv7l" ;; + x86_64) domoticz_arch="x86_64" ;; + esac + + local release_url="https://github.com/domoticz/domoticz/releases/latest/download/domoticz_linux_${domoticz_arch}.tgz" + local tarball="/tmp/domoticz-release.tgz" + + log_info "Downloading Domoticz release for ${domoticz_arch}..." + wget -q -O "$tarball" "$release_url" || { + log_error "Failed to download Domoticz release" + return 1 + } + + ensure_dir "$LXC_ROOTFS/opt/domoticz" + tar -xzf "$tarball" -C "$LXC_ROOTFS/opt/domoticz" || { + log_error "Failed to extract Domoticz" + return 1 + } + rm -f "$tarball" + + chmod +x "$LXC_ROOTFS/opt/domoticz/domoticz" 2>/dev/null + log_info "Domoticz binary installed." +} + +# ---------- LXC config generation ---------- + +lxc_create_config() { defaults - ensure_dir "$data_path" - [ -d /sys/fs/cgroup ] || { echo "[ERROR] /sys/fs/cgroup missing" >&2; return 1; } - ensure_packages dockerd docker containerd - /etc/init.d/dockerd enable >/dev/null 2>&1 - /etc/init.d/dockerd start >/dev/null 2>&1 + local arch=$(detect_arch) + local mem_limit=$(uci_get memory_limit || echo "512M") + + # Convert memory limit to bytes + local mem_bytes + case "$mem_limit" in + *M) mem_bytes=$(( ${mem_limit%M} * 1048576 )) ;; + *G) mem_bytes=$(( ${mem_limit%G} * 1073741824 )) ;; + *) mem_bytes=536870912 ;; + esac + + ensure_dir "$data_path/config" + ensure_dir "$data_path/db" + ensure_dir "$data_path/scripts" + ensure_dir "$data_path/backups" + + cat > "$LXC_CONF" << EOF +# Domoticz LXC Container +lxc.uts.name = domoticz +lxc.rootfs.path = dir:${LXC_ROOTFS} +lxc.arch = ${arch} + +# Host network (no veth isolation) +lxc.net.0.type = none + +# Auto-mounts +lxc.mount.auto = proc:mixed sys:ro cgroup:mixed + +# Data persistence +lxc.mount.entry = ${data_path}/config opt/domoticz/config none bind,create=dir 0 0 +lxc.mount.entry = ${data_path}/db opt/domoticz/db none bind,create=dir 0 0 +lxc.mount.entry = ${data_path}/scripts opt/domoticz/scripts none bind,create=dir 0 0 +lxc.mount.entry = ${data_path}/backups opt/domoticz/backups none bind,create=dir 0 0 +EOF + + # USB device passthrough + if [ -d "$devices_path" ]; then + ensure_dir "$LXC_ROOTFS/devices" + cat >> "$LXC_CONF" << EOF + +# Device passthrough +lxc.mount.entry = ${devices_path} devices none bind,create=dir 0 0 +EOF + fi + + # USB serial device passthrough (for Z-Wave/Zigbee dongles) + for dev in /dev/ttyUSB* /dev/ttyACM*; do + if [ -c "$dev" ]; then + local devname=$(basename "$dev") + local major=$(stat -c '%t' "$dev" 2>/dev/null) + major=$((0x${major:-bc})) + cat >> "$LXC_CONF" << EOF +lxc.cgroup2.devices.allow = c ${major}:* rwm +lxc.mount.entry = ${dev} dev/${devname} none bind,create=file 0 0 +EOF + fi + done + + cat >> "$LXC_CONF" << EOF + +# Terminal +lxc.tty.max = 0 +lxc.pty.max = 256 + +# Standard character devices +lxc.cgroup2.devices.allow = c 1:* rwm +lxc.cgroup2.devices.allow = c 5:* rwm +lxc.cgroup2.devices.allow = c 136:* rwm + +# Security +lxc.cap.drop = sys_module mac_admin mac_override sys_time + +# Disable default seccomp +lxc.seccomp.profile = + +# Resource limits +lxc.cgroup2.memory.max = ${mem_bytes} + +# Init +lxc.init.cmd = /opt/start-domoticz.sh +EOF + + log_info "LXC config generated." } -pull_image() { defaults; docker pull "$image"; } +# ---------- startup script ---------- -stop_container() { - docker stop "$CONTAINER" >/dev/null 2>&1 || true - docker rm "$CONTAINER" >/dev/null 2>&1 || true +generate_start_script() { + defaults + local tz="$timezone" + + # Write start script - use quoted heredoc to prevent variable expansion, + # then sed in the runtime values + cat > "$LXC_ROOTFS/opt/start-domoticz.sh" << 'STARTEOF' +#!/bin/sh +export PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin +cd /opt/domoticz + +# Set timezone +TZ_FILE="/usr/share/zoneinfo/__TZ__" +if [ -f "$TZ_FILE" ]; then + cp "$TZ_FILE" /etc/localtime + echo "__TZ__" > /etc/timezone +fi +export TZ="__TZ__" + +# Ensure database directory exists +mkdir -p /opt/domoticz/db + +# Trap signals and forward to domoticz (PID 1 can't exec directly) +trap 'kill $CHILD 2>/dev/null; wait $CHILD' TERM INT + +# Run Domoticz as child process (not PID 1) +./domoticz -www __PORT__ -dbase /opt/domoticz/db/domoticz.db \ + -userdata /opt/domoticz/config/ \ + -log /opt/domoticz/domoticz.log \ + -sslwww 0 & +CHILD=$! +wait $CHILD +STARTEOF + + # Replace placeholders with actual values + sed -i "s|__TZ__|${tz}|g" "$LXC_ROOTFS/opt/start-domoticz.sh" + sed -i "s|__PORT__|${port}|g" "$LXC_ROOTFS/opt/start-domoticz.sh" + + chmod +x "$LXC_ROOTFS/opt/start-domoticz.sh" } +# ---------- commands ---------- + cmd_install() { - require_root || { echo "Root required" >&2; exit 1; } - check_prereqs || exit 1 - ensure_dir "$data_path/config" - pull_image || exit 1 + require_root || { log_error "Root required"; exit 1; } + + if lxc_exists; then + log_info "Container already exists. Use 'update' to refresh Domoticz." + return 0 + fi + + # Ensure LXC is available + ensure_packages lxc lxc-common || { + log_error "Failed to install LXC packages" + exit 1 + } + + defaults + ensure_dir "$LXC_PATH/$LXC_NAME" + ensure_dir "$data_path" + + lxc_create_rootfs || exit 1 + domoticz_download || exit 1 + lxc_create_config + generate_start_script + uci set ${CONFIG}.main.enabled='1' uci commit ${CONFIG} /etc/init.d/domoticz enable - echo "Domoticz installed. Start with /etc/init.d/domoticz start" + + log_info "Domoticz installed in LXC container. Start with /etc/init.d/domoticz start" } cmd_uninstall() { - require_root || { echo "Root required" >&2; exit 1; } + require_root || { log_error "Root required"; exit 1; } + /etc/init.d/domoticz stop >/dev/null 2>&1 - stop_container + lxc_stop + defaults - docker rmi "$image" >/dev/null 2>&1 || true + + # Remove container rootfs but preserve data + rm -rf "$LXC_PATH/$LXC_NAME" + 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}." + + log_info "Domoticz container removed. Data preserved at ${data_path}." } -cmd_check() { check_prereqs; echo "Prerequisite check completed."; } +cmd_check() { + require_root || { log_error "Root required"; exit 1; } + + echo "=== Prerequisites ===" + + # LXC + if command -v lxc-start >/dev/null 2>&1; then + echo "[OK] lxc-start available" + else + echo "[MISSING] lxc-start — install lxc" + fi + + # cgroup + if [ -d /sys/fs/cgroup ]; then + echo "[OK] /sys/fs/cgroup present" + else + echo "[MISSING] /sys/fs/cgroup" + fi + + # Container + if lxc_exists; then + echo "[OK] Container rootfs exists" + else + echo "[MISSING] Container not installed — run 'domoticzctl install'" + fi + + # Container state + if lxc_running; then + echo "[OK] Container is RUNNING" + else + echo "[STOPPED] Container is not running" + fi + + echo "" + echo "=== USB Devices ===" + for dev in /dev/ttyUSB* /dev/ttyACM*; do + [ -e "$dev" ] && echo " $dev" + done + [ ! -e /dev/ttyUSB0 ] && [ ! -e /dev/ttyACM0 ] && echo " (none detected)" + + echo "" + echo "=== Mosquitto ===" + if pgrep mosquitto >/dev/null 2>&1; then + echo "[OK] Mosquitto running" + else + echo "[STOPPED] Mosquitto not running" + fi + + echo "" + echo "=== Zigbee2MQTT ===" + if [ -f /srv/zigbee2mqtt/alpine/rootfs/run.pid ] || pgrep -f zigbee2mqtt >/dev/null 2>&1; then + echo "[OK] Zigbee2MQTT running" + else + echo "[STOPPED] Zigbee2MQTT not running" + fi +} cmd_update() { - require_root || { echo "Root required" >&2; exit 1; } - pull_image || exit 1 + require_root || { log_error "Root required"; exit 1; } + + if ! lxc_exists; then + log_error "Container not installed. Run 'domoticzctl install' first." + exit 1 + fi + + lxc_stop + domoticz_download || exit 1 + generate_start_script /etc/init.d/domoticz restart - echo "Domoticz updated and restarted." + + log_info "Domoticz updated and restarted." } -cmd_status() { docker ps -a --filter "name=$CONTAINER"; } +cmd_status() { + defaults + echo "=== Domoticz Status ===" -cmd_logs() { docker logs "$@" "$CONTAINER"; } + if lxc_running; then + echo "Container: RUNNING" + lxc-info -n "$LXC_NAME" 2>/dev/null | grep -E "PID|Memory|CPU" + elif lxc_exists; then + echo "Container: STOPPED (installed)" + else + echo "Container: NOT INSTALLED" + fi + + echo "" + echo "Port: ${port}" + echo "Data: ${data_path}" + + if [ -d "$data_path/db" ]; then + local db_size=$(du -sh "$data_path/db" 2>/dev/null | cut -f1) + echo "DB size: ${db_size:-0}" + fi +} + +cmd_logs() { + defaults + local lines="${1:-50}" + local logfile="$data_path/domoticz.log" + + if [ -f "$logfile" ]; then + tail -n "$lines" "$logfile" + elif lxc_running; then + lxc_exec tail -n "$lines" /opt/domoticz/domoticz.log 2>/dev/null || echo "No logs available." + else + echo "No logs available." + fi +} + +cmd_shell() { + require_root || { log_error "Root required"; exit 1; } + + if ! lxc_running; then + log_error "Container is not running. Start it first." + exit 1 + fi + + lxc_exec /bin/sh +} cmd_configure_mqtt() { - require_root || { echo "Root required" >&2; exit 1; } + require_root || { log_error "Root required"; exit 1; } local broker=$(uci_get broker mqtt) local broker_port=$(uci_get broker_port mqtt) @@ -122,9 +515,9 @@ cmd_configure_mqtt() { # Ensure Mosquitto is installed and running if ! command -v mosquitto >/dev/null 2>&1; then - echo "Installing mosquitto-nossl..." + log_info "Installing mosquitto-nossl..." ensure_packages mosquitto-nossl mosquitto-client-nossl || { - echo "[ERROR] Failed to install Mosquitto" >&2 + log_error "Failed to install Mosquitto" return 1 } fi @@ -132,7 +525,6 @@ cmd_configure_mqtt() { # 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" @@ -156,7 +548,7 @@ cmd_configure_mqtt() { done if ! netstat -tln 2>/dev/null | grep -q ":${broker_port} "; then - echo "[WARN] Mosquitto may not be listening on port ${broker_port}" >&2 + log_info "Mosquitto may not be listening on port ${broker_port}" fi # Check zigbee2mqtt MQTT settings @@ -164,7 +556,7 @@ cmd_configure_mqtt() { 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 + # z2m stores full URI (mqtt://host: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}" @@ -190,9 +582,6 @@ cmd_configure_mqtt() { 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}" @@ -207,129 +596,137 @@ cmd_configure_mqtt() { } cmd_configure_haproxy() { - require_root || { echo "Root required" >&2; exit 1; } + require_root || { log_error "Root required"; 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}" + log_info "HAProxy vhost configured for ${domain} -> 127.0.0.1:${port_val}" else - echo "[ERROR] haproxyctl add-vhost failed" >&2 + log_error "haproxyctl add-vhost failed" return 1 fi else - echo "[ERROR] haproxyctl not found — install secubox-app-haproxy first" >&2 + log_error "haproxyctl not found — install secubox-app-haproxy first" return 1 fi } cmd_backup() { - require_root || { echo "Root required" >&2; exit 1; } + require_root || { log_error "Root required"; 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 + if [ ! -d "$data_path" ]; then + log_error "No data to backup at ${data_path}" return 1 fi - tar czf "$backup_path" -C "$data_path" config 2>&1 + tar czf "$backup_path" -C "$data_path" . 2>&1 local code=$? if [ $code -eq 0 ]; then - echo "Backup created: ${backup_path}" + log_info "Backup created: ${backup_path}" else - echo "[ERROR] Backup failed" >&2 + log_error "Backup failed" return 1 fi } cmd_restore() { - require_root || { echo "Root required" >&2; exit 1; } + require_root || { log_error "Root required"; exit 1; } defaults local backup_path="$1" if [ -z "$backup_path" ] || [ ! -f "$backup_path" ]; then - echo "[ERROR] Backup file not found: ${backup_path}" >&2 + log_error "Backup file not found: ${backup_path}" return 1 fi - # Stop service before restore /etc/init.d/domoticz stop >/dev/null 2>&1 - stop_container + lxc_stop 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}" + log_info "Restored from ${backup_path}" echo "Restart Domoticz with: /etc/init.d/domoticz start" else - echo "[ERROR] Restore failed" >&2 + log_error "Restore failed" return 1 fi } cmd_mesh_register() { - require_root || { echo "Root required" >&2; exit 1; } + require_root || { log_error "Root required"; 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}" + log_info "Domoticz registered in P2P mesh on port ${port}" else - echo "[ERROR] secubox-p2p not found — install secubox-p2p first" >&2 + log_error "secubox-p2p not found — install secubox-p2p first" return 1 fi } cmd_service_run() { - require_root || { echo "Root required" >&2; exit 1; } - check_prereqs || exit 1 - defaults - stop_container + require_root || { log_error "Root required"; exit 1; } - 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)" + if ! lxc_exists; then + log_error "Container not installed. Run 'domoticzctl install' first." + exit 1 fi - exec docker run --rm $docker_args -e TZ="$timezone" "$image" + defaults + + # Regenerate config and startup script on each run (picks up UCI changes) + lxc_create_config + generate_start_script + + # Stop any previous instance + lxc_stop + + # Update DNS + cp /etc/resolv.conf "$LXC_ROOTFS/etc/resolv.conf" 2>/dev/null + + log_info "Starting Domoticz LXC container..." + exec lxc-start -n "$LXC_NAME" -F } cmd_service_stop() { - require_root || { echo "Root required" >&2; exit 1; } - stop_container + require_root || { log_error "Root required"; exit 1; } + lxc_stop + log_info "Domoticz container stopped." } +# ---------- main ---------- + 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 "$@" ;; + install) shift; cmd_install "$@" ;; + uninstall) shift; cmd_uninstall "$@" ;; + check) shift; cmd_check "$@" ;; + update) shift; cmd_update "$@" ;; + status) shift; cmd_status "$@" ;; + logs) shift; cmd_logs "$@" ;; + shell) shift; cmd_shell "$@" ;; + 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 "$@" ;; + 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 ;; - *) echo "Unknown command: $1" >&2; usage >&2; exit 1 ;; + *) log_error "Unknown command: $1"; usage >&2; exit 1 ;; esac diff --git a/package/secubox/secubox-app-streamlit/files/usr/sbin/streamlitctl b/package/secubox/secubox-app-streamlit/files/usr/sbin/streamlitctl index 93938269..7d973d82 100644 --- a/package/secubox/secubox-app-streamlit/files/usr/sbin/streamlitctl +++ b/package/secubox/secubox-app-streamlit/files/usr/sbin/streamlitctl @@ -438,11 +438,20 @@ lxc.mount.entry = $data_path/logs srv/logs none bind,create=dir 0 0 lxc.environment = STREAMLIT_THEME_BASE=$theme_base lxc.environment = STREAMLIT_THEME_PRIMARY=$theme_primary +# Terminal +lxc.tty.max = 0 +lxc.pty.max = 256 + +# Standard character devices +lxc.cgroup2.devices.allow = c 1:* rwm +lxc.cgroup2.devices.allow = c 5:* rwm +lxc.cgroup2.devices.allow = c 136:* rwm + # Security lxc.cap.drop = sys_admin sys_module mac_admin mac_override sys_time sys_rawio # Resource limits -lxc.cgroup.memory.limit_in_bytes = $mem_bytes +lxc.cgroup2.memory.max = $mem_bytes # Init command lxc.init.cmd = /opt/start-streamlit.sh 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 e463e4e9..a842a7bd 100644 --- a/package/secubox/secubox-core/root/usr/share/secubox/catalog.json +++ b/package/secubox/secubox-core/root/usr/share/secubox/catalog.json @@ -1522,7 +1522,7 @@ "name": "Domoticz", "version": "1.0.0", "category": "iot", - "runtime": "docker", + "runtime": "lxc", "description": "Home automation system with MQTT bridge, Zigbee2MQTT integration, and IoT device management", "author": "CyberMind.fr", "license": "GPL-3.0", @@ -1532,7 +1532,7 @@ "home-automation", "iot", "smart-home", - "docker", + "lxc", "mqtt", "zigbee", "mesh" @@ -1541,8 +1541,8 @@ "required": [ "secubox-app-domoticz", "luci-app-domoticz", - "docker", - "dockerd" + "lxc", + "lxc-common" ], "optional": [ "mosquitto-nossl", @@ -1562,18 +1562,20 @@ "min_storage_mb": 100 }, "status": "stable", - "pkg_version": "1.0.0-3", + "pkg_version": "1.0.0-4", "app_version": "1.0.0", "changelog": { - "1.0.0-3": { + "1.0.0-4": { "date": "2026-02-04", "changes": [ + "Rewrite from Docker to LXC Alpine container", + "Native Domoticz binary download from releases.domoticz.com", + "LXC config with USB cgroup2 passthrough and memory limits", "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" + "Backup and restore support" ] }, "1.0.0": {
Image:' + (status.image || '-') + '
Port:' + port + '
Data:' + (status.data_path || '-') + '
Domain:' + (status.domain || '-') + '
Memory:' + status.memory_usage + '
Disk:' + status.disk_usage + '