diff --git a/DOCS/DOCUMENTATION-INDEX.md b/DOCS/DOCUMENTATION-INDEX.md index 9d59d3b7..97f58e95 100644 --- a/DOCS/DOCUMENTATION-INDEX.md +++ b/DOCS/DOCUMENTATION-INDEX.md @@ -218,6 +218,11 @@ Pointer: see `docs/embedded/docker-zigbee2mqtt.md` for the canonical version. Pointer: see `docs/embedded/vhost-manager.md` for the canonical version. +#### **embedded/app-store.md** 🛒 +*Plugin manifest format and CLI for the SecuBox App Store.* + +Pointer: see `docs/embedded/app-store.md` for the canonical version. + --- ### 5. Tools & Scripts Documentation diff --git a/DOCS/embedded/app-store.md b/DOCS/embedded/app-store.md new file mode 100644 index 00000000..866f6c96 --- /dev/null +++ b/DOCS/embedded/app-store.md @@ -0,0 +1,95 @@ +# SecuBox App Store & Manifests + +**Version:** 1.0.0 +**Last Updated:** 2025-12-28 +**Status:** Active + +This guide outlines the initial “SecuBox Apps” registry format and the `secubox-app` CLI helper. It currently ships with a single manifest (Zigbee2MQTT), but the workflow scales to other Docker/LXC/native services such as Lyrion. + +--- + +## Manifest Layout (`plugins//manifest.json`) + +Each plugin folder contains a `manifest.json`. Example (Zigbee2MQTT): + +```json +{ + "id": "zigbee2mqtt", + "name": "Zigbee2MQTT", + "type": "docker", + "description": "Dockerized Zigbee gateway", + "packages": ["secubox-app-zigbee2mqtt", "luci-app-zigbee2mqtt"], + "ports": [{ "name": "frontend", "protocol": "http", "port": 8080 }], + "volumes": ["/srv/zigbee2mqtt"], + "network": { "default_mode": "lan", "dmz_supported": true }, + "wizard": { + "uci": "/etc/config/zigbee2mqtt", + "steps": ["serial_port", "mqtt_host", "credentials", "frontend_port"] + }, + "profiles": ["home", "lab"], + "actions": { + "install": "zigbee2mqttctl install", + "check": "zigbee2mqttctl check", + "update": "zigbee2mqttctl update", + "status": "/etc/init.d/zigbee2mqtt status" + } +} +``` + +**Required keys** + +| Key | Purpose | +|-----|---------| +| `id` | Unique identifier used by the CLI (`secubox-app install `). | +| `name` / `description` | Display metadata. | +| `type` | `docker`, `lxc`, or `native`. | +| `packages` | List of OpenWrt packages to install/remove. | +| `actions.install/update/check/status` | Optional shell commands executed after opkg operations. | + +**Optional keys** + +- `ports`: Document exposed services for the App Store UI. +- `volumes`: Persistent directories (e.g., `/srv/zigbee2mqtt`). +- `network`: Defaults + whether DMZ mode is supported. +- `wizard`: UCI file and logical steps for the future wizard UI. +- `profiles`: Tags to pre-load when applying OS-like profiles. + +--- + +## CLI Usage (`secubox-tools/secubox-app`) + +Copy or install `secubox-tools/secubox-app` on the router (ensure it’s executable). Commands: + +```bash +# List manifests +secubox-app list + +# Inspect raw manifest +secubox-app show zigbee2mqtt + +# Install packages + run install action +secubox-app install zigbee2mqtt + +# Run status command (if defined) +secubox-app status zigbee2mqtt + +# Update or remove +secubox-app update zigbee2mqtt +secubox-app remove zigbee2mqtt +``` + +Environment variables: +- `SECUBOX_PLUGINS_DIR`: override manifest directory (default `../plugins`). + +The CLI relies on `opkg` and `jsonfilter`, so run it on the router (or within the OpenWrt SDK). It is idempotent: reinstalling an already-installed app simply confirms package state and reruns optional install hooks. + +--- + +## Future Integration + +- LuCI App Store page will consume the same manifest directory to render cards, filters, and install buttons. +- Wizards will read the `wizard.steps` metadata to present guided forms. +- Profiles can bundle manifests with specific network modes (e.g., DMZ + Zigbee2MQTT + Lyrion). + +For now, Zigbee2MQTT demonstrates the format. Additional manifests should follow the same schema to ensure the CLI and future UIs remain consistent. + diff --git a/docs/documentation-index.md b/docs/documentation-index.md index e125e563..79716c3d 100644 --- a/docs/documentation-index.md +++ b/docs/documentation-index.md @@ -234,6 +234,19 @@ Follow this template when creating or revising documentation: **Size:** Short (~120 lines) +#### **embedded/app-store.md** 🛒 +*Plugin manifest format and SecuBox App Store CLI.* + +**Contents:** +- Manifest schema (id/type/packages/actions, wizard metadata) +- Example manifest for Zigbee2MQTT +- `secubox-app` CLI usage (list/show/install/status/update/remove) +- Notes on future LuCI App Store + profile integration + +**When to use:** Creating new app manifests or scripting installs via CLI. + +**Size:** Short (~120 lines) + --- ### 5. Tools & Scripts Documentation diff --git a/docs/embedded/app-store.md b/docs/embedded/app-store.md new file mode 100644 index 00000000..866f6c96 --- /dev/null +++ b/docs/embedded/app-store.md @@ -0,0 +1,95 @@ +# SecuBox App Store & Manifests + +**Version:** 1.0.0 +**Last Updated:** 2025-12-28 +**Status:** Active + +This guide outlines the initial “SecuBox Apps” registry format and the `secubox-app` CLI helper. It currently ships with a single manifest (Zigbee2MQTT), but the workflow scales to other Docker/LXC/native services such as Lyrion. + +--- + +## Manifest Layout (`plugins//manifest.json`) + +Each plugin folder contains a `manifest.json`. Example (Zigbee2MQTT): + +```json +{ + "id": "zigbee2mqtt", + "name": "Zigbee2MQTT", + "type": "docker", + "description": "Dockerized Zigbee gateway", + "packages": ["secubox-app-zigbee2mqtt", "luci-app-zigbee2mqtt"], + "ports": [{ "name": "frontend", "protocol": "http", "port": 8080 }], + "volumes": ["/srv/zigbee2mqtt"], + "network": { "default_mode": "lan", "dmz_supported": true }, + "wizard": { + "uci": "/etc/config/zigbee2mqtt", + "steps": ["serial_port", "mqtt_host", "credentials", "frontend_port"] + }, + "profiles": ["home", "lab"], + "actions": { + "install": "zigbee2mqttctl install", + "check": "zigbee2mqttctl check", + "update": "zigbee2mqttctl update", + "status": "/etc/init.d/zigbee2mqtt status" + } +} +``` + +**Required keys** + +| Key | Purpose | +|-----|---------| +| `id` | Unique identifier used by the CLI (`secubox-app install `). | +| `name` / `description` | Display metadata. | +| `type` | `docker`, `lxc`, or `native`. | +| `packages` | List of OpenWrt packages to install/remove. | +| `actions.install/update/check/status` | Optional shell commands executed after opkg operations. | + +**Optional keys** + +- `ports`: Document exposed services for the App Store UI. +- `volumes`: Persistent directories (e.g., `/srv/zigbee2mqtt`). +- `network`: Defaults + whether DMZ mode is supported. +- `wizard`: UCI file and logical steps for the future wizard UI. +- `profiles`: Tags to pre-load when applying OS-like profiles. + +--- + +## CLI Usage (`secubox-tools/secubox-app`) + +Copy or install `secubox-tools/secubox-app` on the router (ensure it’s executable). Commands: + +```bash +# List manifests +secubox-app list + +# Inspect raw manifest +secubox-app show zigbee2mqtt + +# Install packages + run install action +secubox-app install zigbee2mqtt + +# Run status command (if defined) +secubox-app status zigbee2mqtt + +# Update or remove +secubox-app update zigbee2mqtt +secubox-app remove zigbee2mqtt +``` + +Environment variables: +- `SECUBOX_PLUGINS_DIR`: override manifest directory (default `../plugins`). + +The CLI relies on `opkg` and `jsonfilter`, so run it on the router (or within the OpenWrt SDK). It is idempotent: reinstalling an already-installed app simply confirms package state and reruns optional install hooks. + +--- + +## Future Integration + +- LuCI App Store page will consume the same manifest directory to render cards, filters, and install buttons. +- Wizards will read the `wizard.steps` metadata to present guided forms. +- Profiles can bundle manifests with specific network modes (e.g., DMZ + Zigbee2MQTT + Lyrion). + +For now, Zigbee2MQTT demonstrates the format. Additional manifests should follow the same schema to ensure the CLI and future UIs remain consistent. + diff --git a/plugins/zigbee2mqtt/manifest.json b/plugins/zigbee2mqtt/manifest.json new file mode 100644 index 00000000..4708c166 --- /dev/null +++ b/plugins/zigbee2mqtt/manifest.json @@ -0,0 +1,36 @@ +{ + "id": "zigbee2mqtt", + "name": "Zigbee2MQTT", + "description": "Dockerized Zigbee gateway (secubox-app-zigbee2mqtt + LuCI UI).", + "type": "docker", + "version": "1.0.0", + "source": "https://www.zigbee2mqtt.io/", + "packages": [ + "secubox-app-zigbee2mqtt", + "luci-app-zigbee2mqtt" + ], + "ports": [ + { "name": "frontend", "protocol": "http", "port": 8080, "expose": true } + ], + "volumes": ["/srv/zigbee2mqtt"], + "network": { + "default_mode": "lan", + "dmz_supported": true + ], + "wizard": { + "uci": "/etc/config/zigbee2mqtt", + "steps": [ + "serial_port", + "mqtt_host", + "credentials", + "frontend_port" + ] + }, + "profiles": ["home", "lab"], + "actions": { + "install": "zigbee2mqttctl install", + "check": "zigbee2mqttctl check", + "update": "zigbee2mqttctl update", + "status": "/etc/init.d/zigbee2mqtt status" + } +} diff --git a/secubox-tools/README.md b/secubox-tools/README.md index 226093a4..e96ef579 100644 --- a/secubox-tools/README.md +++ b/secubox-tools/README.md @@ -240,6 +240,27 @@ git push # validation runs automatically ./secubox-tools/install-git-hooks.sh ``` +### Apps & Plugin Registry + +#### secubox-app + +CLI helper for the nascent SecuBox App Store. It reads `plugins/*/manifest.json`, installs/removes the packages listed there, and runs optional shell actions (`install`, `check`, `update`, `status`) defined in the manifest. + +```bash +# List manifests and install state +secubox-app list + +# Install Zigbee2MQTT (packages + zigbee2mqttctl install) +secubox-app install zigbee2mqtt + +# Show manifest or run status/update +secubox-app show zigbee2mqtt +secubox-app status zigbee2mqtt +secubox-app update zigbee2mqtt +``` + +Environment: set `SECUBOX_PLUGINS_DIR` to override the manifest directory (default `../plugins`). Requires `opkg` and `jsonfilter`, so run it on an OpenWrt system (or within the SDK chroot). + ### Maintenance Tools #### secubox-repair.sh diff --git a/secubox-tools/secubox-app b/secubox-tools/secubox-app new file mode 100755 index 00000000..9cb4a5ed --- /dev/null +++ b/secubox-tools/secubox-app @@ -0,0 +1,210 @@ +#!/bin/sh +# SecuBox Apps CLI +# Lists/installs/removes plugin manifests declared under plugins/*/manifest.json + +set -eu + +PLUGINS_DIR="${SECUBOX_PLUGINS_DIR:-$(cd "$(dirname "$0")/../plugins" && pwd)}" +OPKG_UPDATED=0 + +info() { printf '[INFO] %s\n' "$*"; } +warn() { printf '[WARN] %s\n' "$*" >&2; } +err() { printf '[ERROR] %s\n' "$*" >&2; } + +usage() { + cat <<'USAGE' +SecuBox Apps CLI +Usage: secubox-app [arguments] + +Commands: + list Show available app manifests + show Display manifest details + install Install required packages + run install action + remove Remove packages listed in manifest + status Show install state and run status action if defined + update Run plugin update action or opkg upgrade + +Environment: + SECUBOX_PLUGINS_DIR Override plugin manifest directory (default: ../plugins) +USAGE +} + +require_tool() { + command -v "$1" >/dev/null 2>&1 || { err "Missing dependency: $1"; exit 1; } +} + +require_opkg() { require_tool opkg; } +require_jsonfilter() { require_tool jsonfilter; } + +manifest_path() { + local id="$1" + local file="$PLUGINS_DIR/$id/manifest.json" + [ -f "$file" ] || { err "Manifest not found for '$id' ($file)"; exit 1; } + printf '%s' "$file" +} + +manifest_field() { + local file="$1"; shift + jsonfilter -s "$file" -e "$1" 2>/dev/null || true +} + +manifest_packages() { + local file="$1" + jsonfilter -s "$file" -e '@.packages[*]' 2>/dev/null || true +} + +manifest_action() { + local file="$1"; shift + jsonfilter -s "$file" -e "@.actions.$1" 2>/dev/null || true +} + +ensure_opkg_updated() { + [ "$OPKG_UPDATED" -eq 1 ] && return + opkg update >/dev/null + OPKG_UPDATED=1 +} + +packages_installed() { + local all_installed=0 + local partial=0 + local pkg + for pkg in $1; do + if opkg status "$pkg" >/dev/null 2>&1; then + continue + else + all_installed=1 + fi + [ -n "$partial" ] + done +} + +plugin_state() { + local file="$1" + local pkgs pkg missing=0 installed=0 total=0 + pkgs=$(manifest_packages "$file") + for pkg in $pkgs; do + total=$((total+1)) + if opkg status "$pkg" >/dev/null 2>&1; then + installed=$((installed+1)) + else + missing=$((missing+1)) + fi + done + if [ "$total" -eq 0 ]; then + echo "n/a" + elif [ "$missing" -eq 0 ]; then + echo "installed" + elif [ "$installed" -eq 0 ]; then + echo "missing" + else + echo "partial" + fi +} + +list_plugins() { + require_jsonfilter + require_opkg + printf '%-16s %-22s %-10s %-10s\n' "ID" "Name" "Type" "State" + printf '%-16s %-22s %-10s %-10s\n' "--" "----" "----" "-----" + find "$PLUGINS_DIR" -mindepth 2 -maxdepth 2 -name manifest.json | sort | while read -r file; do + local id name type state + id=$(manifest_field "$file" '@.id') + name=$(manifest_field "$file" '@.name') + type=$(manifest_field "$file" '@.type') + state=$(plugin_state "$file") + printf '%-16s %-22s %-10s %-10s\n' "$id" "${name:-Unknown}" "${type:-?}" "$state" + done +} + +show_manifest() { + require_jsonfilter + local file=$(manifest_path "$1") + cat "$file" +} + +install_plugin() { + require_jsonfilter + require_opkg + local file=$(manifest_path "$1") + local pkgs pkg + pkgs=$(manifest_packages "$file") + if [ -z "$pkgs" ]; then + warn "Manifest has no packages; nothing to install." + else + for pkg in $pkgs; do + if opkg status "$pkg" >/dev/null 2>&1; then + info "$pkg already installed" + else + info "Installing $pkg" + ensure_opkg_updated + opkg install "$pkg" + fi + done + fi + local install_cmd + install_cmd=$(manifest_action "$file" install) + if [ -n "$install_cmd" ]; then + info "Running install action: $install_cmd" + sh -c "$install_cmd" + fi +} + +remove_plugin() { + require_jsonfilter + require_opkg + local file=$(manifest_path "$1") + local pkgs pkg + pkgs=$(manifest_packages "$file") + for pkg in $pkgs; do + if opkg status "$pkg" >/dev/null 2>&1; then + info "Removing $pkg" + opkg remove "$pkg" + else + info "$pkg not installed" + fi + done +} + +plugin_status_cmd() { + require_jsonfilter + local file=$(manifest_path "$1") + local status_cmd + status_cmd=$(manifest_action "$file" status) + local state + state=$(plugin_state "$file") + printf 'App: %s\nState: %s\n' "$1" "$state" + if [ -n "$status_cmd" ]; then + printf 'Status command output:\n' + sh -c "$status_cmd" || true + fi +} + +update_plugin() { + require_jsonfilter + require_opkg + local file=$(manifest_path "$1") + local update_cmd pkgs pkg + update_cmd=$(manifest_action "$file" update) + if [ -n "$update_cmd" ]; then + info "Running update action: $update_cmd" + sh -c "$update_cmd" + else + pkgs=$(manifest_packages "$file") + for pkg in $pkgs; do + info "Upgrading $pkg" + ensure_opkg_updated + opkg upgrade "$pkg" || true + done + fi +} + +case "${1:-}" in + list) shift; list_plugins ;; + show) shift; [ $# -ge 1 ] || { err "show requires an app id"; exit 1; }; show_manifest "$1" ;; + install) shift; [ $# -ge 1 ] || { err "install requires an app id"; exit 1; }; install_plugin "$1" ;; + remove) shift; [ $# -ge 1 ] || { err "remove requires an app id"; exit 1; }; remove_plugin "$1" ;; + status) shift; [ $# -ge 1 ] || { err "status requires an app id"; exit 1; }; plugin_status_cmd "$1" ;; + update) shift; [ $# -ge 1 ] || { err "update requires an app id"; exit 1; }; update_plugin "$1" ;; + help|--help|-h|'') usage ;; + *) err "Unknown command: $1"; usage; exit 1 ;; +esac