librelad 2d24a764a8 refactor(storage): route elevation tests and the WebUI tree through paths.sh
Two mechanical sweeps, no behaviour change on a single-root install.

The 14 `[[ "$p" == "$containers_dir"* ]]` prefix tests that decide
manager-vs-container-user elevation become pathIsContainerData, so a file
on a second storage root is no longer misclassified as manager-owned —
which would have written it with the wrong owner and failed later, far
from the cause. The 65 references to the WebUI's own tree become
webuiDir(), which is pinned to the primary root by design.

Two traps found while doing it:

run_privileged.sh is sourced directly by init.sh without paths.sh, so it
needs a fallback. Defining one named pathIsContainerData was wrong:
generate_function_manifest.sh indexes top-level definitions, and the
resulting autoload stub would have shadowed the real multi-root
implementation with the primary-only fallback — silently classifying
every file on a second disk as manager-owned, which is exactly the bug
this sweep exists to prevent. Renamed to _runCfgIsContainerPath, which
delegates when the real one is loaded.

setup_lock.sh built its path in a top-level assignment, so it was
evaluated at source time and needed the file flagged eager. Made it a
function instead: the path resolves on call, and the file drops off
LP_EAGER_FILES entirely.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 04:04:19 +01:00

92 lines
4.2 KiB
Bash

#!/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/<app>/tools/<app>.tools.json (a { "tools": [ … ] }
# object — auto-merged below; no edit to this file needed).
# 2. Drop the one-function file beside it at
# containers/<app>/tools/<app>_<tool_id>.sh with
# `app<App><PascalCase(toolId)>()` 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: "<unique within the app>", # 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="$(webuiDir)/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/<app>/tools/<app>.tools.json = { "tools": [ … ] };
# it sets .apps[<app>]. 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")")")" # …/<app>/tools/<x>.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))."
}