secubox-openwrt/package/secubox/secubox-app/files/usr/sbin/secubox-app

343 lines
7.9 KiB
Bash
Executable File

#!/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 <command> [arguments]
Commands:
list Show available app manifests
show <app-id> Display manifest details
install <app-id> Install required packages + run install action
remove <app-id> Remove packages listed in manifest
status <app-id> Show install state and run status action if defined
update <app-id> 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 <<EOF
$(manifest_files | sort)
EOF
}
show_manifest() {
require_jsonfilter
local file=$(manifest_path "$1")
cat "$file"
}
install_plugin() {
require_jsonfilter
ensure_pkg_mgr
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
pkg_install "$pkg"
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
ensure_pkg_mgr
local file=$(manifest_path "$1")
local pkgs pkg
pkgs=$(manifest_packages "$file")
for pkg in $pkgs; do
pkg_remove "$pkg"
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
ensure_pkg_mgr
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"
pkg_update_once
pkg_upgrade "$pkg" || true
done
fi
}
validate_manifests_cmd() {
require_jsonfilter
local failed=0 passed=0 file
while IFS= read -r file; do
[ -f "$file" ] || continue
if validate_manifest_file "$file"; then
passed=$((passed + 1))
else
failed=$((failed + 1))
fi
done <<EOF
$(manifest_files)
EOF
info "Validated $passed manifest(s)"
if [ "$failed" -gt 0 ]; then
err "$failed manifest(s) failed validation"
return 1
fi
return 0
}
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" ;;
validate) shift; if ! validate_manifests_cmd; then exit 1; fi ;;
help|--help|-h|'') usage ;;
*) err "Unknown command: $1"; usage; exit 1 ;;
esac