#!/bin/bash # Single source of truth for the Tools tab. The frontend reads # /data/apps/generated/apps-tools.json — this generator emits it. # # Tools are self-contained per app. To add one: # 1. Declare it in containers//tools/.tools.json (a { "tools": [ … ] } # object — auto-merged below; no edit to this file needed). # 2. Drop the one-function file beside it at # containers//tools/_.sh with # `app()` defined inside (live-sourced by the # container scan). # 3. Re-run the WebUI updater (or call this function directly) to # regenerate apps-tools.json. # # Tool entry schema (matches what tools-manager.js expects): # { # id: "", # passed to dispatcher # label: "Reset User Password", # button + modal heading # description: "One-line summary", # shown on card + modal # icon: "🔑", # any emoji # destructive: false, # red button if true # confirm: "Are you sure?", # forces a modal even # # when fields is empty # fields: [] # see field schema below # } # # Field schema (each form input the modal collects): # { name, label, type: text|password|number|select|checkbox|textarea, # default, placeholder, required, options (for select), min, max } webuiGenerateAppsToolsConfig() { # The WebUI task service is a long-lived bash process that sources # this file once at startup. When the auto-updater rewrites this # script on disk (e.g. to add a new app's tools entry), the function # in memory stays stale until the service restarts — producing # apps-tools.json without the new entries. Re-source ourselves on # every call so heredoc edits take effect immediately, then dispatch # to the freshly-loaded version. The guard prevents infinite # recursion. # Skip the reload if the file is missing right now — happens briefly # during update.sh quick (find -delete + cp). Fall through to the # in-memory version (one cycle stale at worst). if [[ -z "$_WEBUI_TOOLS_RELOADED" && -f "${BASH_SOURCE[0]}" ]]; then local _WEBUI_TOOLS_RELOADED=1 source "${BASH_SOURCE[0]}" webuiGenerateAppsToolsConfig "$@" return $? fi local output_file="${containers_dir}libreportal/frontend/data/apps/generated/apps-tools.json" local tmp="$(mktemp)" runFileOp mkdir -p "$(dirname "$output_file")" # Start empty; every app declares its own tools (merged below). The heredoc is # kept as the seed so a central tool could be added here if ever needed. cat > "$tmp" <<'JSON' { "apps": {} } JSON # Merge per-app tool declarations so any app — core or DROPPED-IN (e.g. from # LibrePortal-Infra) — registers its own Tools tab actions without editing this # file. Each app ships containers//tools/.tools.json = { "tools": [ … ] }; # it sets .apps[]. The tool functions live beside it in the same tools/ # folder and are live-sourced by the container scan. if command -v jq >/dev/null 2>&1; then local _tj _app for _tj in "${install_containers_dir}"*/tools/*.tools.json; do [[ -f "$_tj" ]] || continue _app="$(basename "$(dirname "$(dirname "$_tj")")")" # …//tools/.tools.json if jq -e . "$_tj" >/dev/null 2>&1; then jq --arg app "$_app" --slurpfile t "$_tj" '.apps[$app] = $t[0]' "$tmp" > "$tmp.m" && mv "$tmp.m" "$tmp" else isNotice "Skipping invalid tools file: $_tj" fi done fi if command -v jq >/dev/null 2>&1; then if ! jq . "$tmp" >/dev/null 2>&1; then isNotice "Generated apps-tools.json failed JSON validation; keeping existing file." rm -f "$tmp" return 0 fi fi runFileWrite "$output_file" < "$tmp"; rm -f "$tmp" isSuccessful "Generated apps-tools.json ($(jq '[.apps[].tools | length] | add' "$output_file" 2>/dev/null || echo "?") tool(s) across $(jq '.apps | length' "$output_file" 2>/dev/null || echo "?") app(s))." }