#!/bin/sh # SecuBox Apps CLI # Lists/installs/removes plugin manifests declared under plugins/*/manifest.json set -eu SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) DEFAULT_PLUGINS_DIR="/usr/share/secubox/plugins" if [ -n "${SECUBOX_PLUGINS_DIR:-}" ]; then PLUGINS_DIR="$SECUBOX_PLUGINS_DIR" elif [ -d "$SCRIPT_DIR/../plugins" ]; then PLUGINS_DIR=$(cd "$SCRIPT_DIR/../plugins" && pwd) else PLUGINS_DIR="$DEFAULT_PLUGINS_DIR" fi PKG_MGR="" PKG_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 validate Verify manifest schema/metadata Environment: SECUBOX_PLUGINS_DIR Override manifest directory (default: /usr/share/secubox/plugins) USAGE } require_tool() { command -v "$1" >/dev/null 2>&1 || { err "Missing dependency: $1"; exit 1; } } require_jsonfilter() { require_tool jsonfilter; } ensure_pkg_mgr() { if [ -n "$PKG_MGR" ]; then return fi if command -v opkg >/dev/null 2>&1; then PKG_MGR="opkg" elif command -v apk >/dev/null 2>&1; then PKG_MGR="apk" else err "Missing dependency: require opkg or apk" exit 1 fi } pkg_update_once() { ensure_pkg_mgr [ "$PKG_UPDATED" -eq 1 ] && return case "$PKG_MGR" in opkg) opkg update >/dev/null ;; apk) apk update >/dev/null ;; esac PKG_UPDATED=1 } pkg_is_installed() { ensure_pkg_mgr case "$PKG_MGR" in opkg) opkg status "$1" >/dev/null 2>&1 ;; apk) apk info -e "$1" >/dev/null 2>&1 ;; esac } pkg_install() { local pkg="$1" if pkg_is_installed "$pkg"; then info "$pkg already installed" return fi info "Installing $pkg" pkg_update_once case "$PKG_MGR" in opkg) opkg install "$pkg" ;; apk) apk add "$pkg" ;; esac } pkg_remove() { local pkg="$1" if ! pkg_is_installed "$pkg"; then info "$pkg not installed" return fi info "Removing $pkg" case "$PKG_MGR" in opkg) opkg remove "$pkg" ;; apk) apk del "$pkg" ;; esac } pkg_upgrade() { case "$PKG_MGR" in opkg) opkg upgrade "$1" ;; apk) apk upgrade "$1" ;; esac } manifest_files() { if [ -d "$PLUGINS_DIR/catalog" ]; then for file in "$PLUGINS_DIR"/catalog/*.json; do [ -f "$file" ] || continue echo "$file" done fi for file in "$PLUGINS_DIR"/*/manifest.json; do [ -f "$file" ] || continue echo "$file" done } manifest_path() { local id="$1" local catalog="$PLUGINS_DIR/catalog/$id.json" local legacy="$PLUGINS_DIR/$id/manifest.json" if [ -f "$catalog" ]; then printf '%s' "$catalog" return 0 fi if [ -f "$legacy" ]; then printf '%s' "$legacy" return 0 fi err "Manifest not found for '$id'" exit 1 } manifest_field() { local file="$1"; shift cat "$file" | jsonfilter -e "$1" 2>/dev/null || true } manifest_packages() { local file="$1" cat "$file" | jsonfilter -e '@.packages[*]' 2>/dev/null || true } manifest_action() { local file="$1"; shift cat "$file" | jsonfilter -e "@.actions.$1" 2>/dev/null || true } manifest_runtime() { local file="$1" local value value=$(manifest_field "$file" '@.type') [ -n "$value" ] || value=$(manifest_field "$file" '@.runtime') printf '%s' "$value" } validate_manifest_file() { require_jsonfilter local file="$1" local id name runtime packages category id=$(manifest_field "$file" '@.id') name=$(manifest_field "$file" '@.name') category=$(manifest_field "$file" '@.category') runtime=$(manifest_runtime "$file") packages=$(manifest_packages "$file") if [ -z "$id" ] || [ -z "$name" ] || [ -z "$runtime" ]; then warn "Skipping manifest $file (missing id/name/runtime)" return 1 fi if [ -z "$packages" ]; then warn "Manifest $id has no packages defined" return 1 fi if [ -z "$category" ]; then warn "Manifest $id missing category" return 1 fi return 0 } 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 pkg_is_installed "$pkg"; 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 ensure_pkg_mgr local seen_ids="" printf '%-18s %-24s %-10s %-10s %-10s\n' "ID" "Name" "Runtime" "Category" "State" printf '%-18s %-24s %-10s %-10s %-10s\n' "--" "----" "-------" "--------" "-----" while IFS= read -r file; do [ -f "$file" ] || continue if ! validate_manifest_file "$file"; then continue fi local id name runtime state category id=$(manifest_field "$file" '@.id') name=$(manifest_field "$file" '@.name') runtime=$(manifest_runtime "$file") category=$(manifest_field "$file" '@.category') case " $seen_ids " in *" $id "*) continue ;; esac seen_ids="$seen_ids $id" state=$(plugin_state "$file") printf '%-18s %-24s %-10s %-10s %-10s\n' "$id" "${name:-Unknown}" "${runtime:-unknown}" "${category:-?}" "$state" done <