librelad 315c528306 refactor(webui): silence per-file touch/chown noise in data generators
The WebUI data snapshots (locations.json, dashboard.json, snapshots_*.json,
etc.) are regenerated on every wizard/config change. Each file emitted two
extra success lines via createTouch — "Touching <file>" and "Updating
<file> with <user> ownership" — which spammed the output around the genuinely
useful "... JSON regenerated" line.

Add an optional "silent" flag to createTouch (third arg; default keeps the
existing loud behaviour for interactive install flows) and pass it from every
WebUI data generator/task. Touch + chown still run; only the logging is
suppressed for these background regenerations.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-23 12:40:32 +01:00

429 lines
21 KiB
Bash
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# Single source of truth for the Tools tab. The frontend reads
# /data/apps/generated/apps-tools.json — this generator emits it.
#
# To add a tool:
# 1. Add an entry under that app's "tools" array below.
# 2. Drop a one-function file at scripts/app/containers/<app>/<app>_<tool_id>.sh
# with `app<App><PascalCase(toolId)>()` defined inside.
# 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="${containers_dir}libreportal/frontend/data/apps/generated/apps-tools.json"
local tmp="${output_file}.tmp.$$"
mkdir -p "$(dirname "$output_file")"
# Heredoc carries the JSON literal verbatim — much easier to edit
# than escaping nested quotes through jq -n.
cat > "$tmp" <<'JSON'
{
"apps": {
"adguard": {
"tools": [
{
"id": "reset_password",
"label": "Reset Admin Password",
"description": "Reset the AdGuard Home admin password. Leave the field blank to generate a random one — the new password is saved to the config so you can see it after.",
"icon": "🔑",
"fields": [
{ "name": "password", "label": "New password (leave blank to generate)", "type": "password", "placeholder": "Leave blank for random" }
]
},
{
"id": "apply_dns_updater",
"label": "Apply DNS Updater",
"description": "Rewrite this server's /etc/resolv.conf to use AdGuard as its DNS resolver right now. Same action that runs automatically when the global DNS Updater requirement is enabled.",
"icon": "🌐",
"fields": []
}
]
},
"pihole": {
"tools": [
{
"id": "apply_dns_updater",
"label": "Apply DNS Updater",
"description": "Rewrite this server's /etc/resolv.conf to use Pi-hole as its DNS resolver right now. Same action that runs automatically when the global DNS Updater requirement is enabled.",
"icon": "🌐",
"fields": []
}
]
},
"dashy": {
"tools": [
{
"id": "manage_shortcuts",
"label": "Manage Shortcuts",
"description": "Pick which service URLs appear as shortcuts on the Dashy dashboard.",
"icon": "🧩",
"fields": [
{
"name": "selected",
"label": "URLs to show on the dashboard",
"type": "app_urls_multi",
"prefillFromCfgKey": "CFG_DASHY_SHORTCUTS",
"excludeApps": ["dashy"]
}
]
}
]
},
"bookstack": {
"tools": [
{
"id": "reset_password",
"category": "users",
"label": "Reset User Password",
"description": "Reset an existing Bookstack user's password. Leave the password field blank to generate a random one — it is shown in the task log.",
"icon": "🔑",
"fields": [
{ "name": "email", "label": "User email", "type": "text", "placeholder": "user@example.com", "required": true },
{ "name": "password", "label": "New password (leave blank to generate)", "type": "password", "placeholder": "Leave blank for random" }
]
},
{
"id": "create_account",
"category": "users",
"label": "Create User Account",
"description": "Create a new Bookstack user. Tick \"Make admin\" to grant full admin rights; otherwise the new user gets the default registration role. Leave the password blank to generate a random one.",
"icon": "👤",
"fields": [
{ "name": "email", "label": "Email", "type": "text", "placeholder": "user@example.com", "required": true },
{ "name": "name", "label": "Display name", "type": "text", "required": true },
{ "name": "password", "label": "Password (leave blank to generate)", "type": "password", "placeholder": "Leave blank for random" },
{ "name": "admin", "label": "Make admin", "type": "checkbox", "default": false }
]
},
{ "id": "list_users", "category": "users", "label": "List Users", "description": "Show every Bookstack user with their roles.", "icon": "📋", "fields": [] },
{
"id": "delete_user", "category": "users", "label": "Delete User Account",
"description": "Permanently delete a user account.", "icon": "🗑",
"destructive": true, "confirm": "This cannot be undone.",
"fields": [
{ "name": "email", "label": "User email", "type": "text", "required": true }
]
},
{
"id": "set_admin", "category": "users", "label": "Set Admin Status",
"description": "Promote a user to admin or demote them to a normal user.", "icon": "👑",
"fields": [
{ "name": "email", "label": "User email", "type": "text", "required": true },
{ "name": "admin", "label": "Make admin", "type": "checkbox", "default": false }
]
}
]
},
"focalboard": {
"tools": [
{
"id": "reset_password",
"category": "users",
"label": "Reset User Password",
"description": "Reset an existing Focalboard user's password.",
"icon": "🔑",
"fields": [
{ "name": "username", "label": "Username", "type": "text", "required": true },
{ "name": "password", "label": "New password (leave blank to generate)", "type": "password", "placeholder": "Leave blank for random" }
]
},
{
"id": "create_account",
"category": "users",
"label": "Create User Account",
"description": "Create a new Focalboard user. Leave password blank to generate one.",
"icon": "👤",
"fields": [
{ "name": "username", "label": "Username", "type": "text", "required": true },
{ "name": "email", "label": "Email", "type": "text", "required": true },
{ "name": "password", "label": "Password (leave blank to generate)", "type": "password" },
{ "name": "admin", "label": "Make admin", "type": "checkbox", "default": false }
]
},
{
"id": "list_users",
"category": "users",
"label": "List Users",
"description": "Show every Focalboard user account.",
"icon": "📋",
"fields": []
},
{
"id": "delete_user", "category": "users", "label": "Delete User Account",
"description": "Permanently delete a user account.", "icon": "🗑",
"destructive": true, "confirm": "This cannot be undone.",
"fields": [
{ "name": "username", "label": "Username", "type": "text", "required": true }
]
}
]
},
"mattermost": {
"tools": [
{
"id": "reset_password", "category": "users", "label": "Reset User Password",
"description": "Reset a Mattermost user's password via mmctl.", "icon": "🔑",
"fields": [
{ "name": "username", "label": "Username or Email", "type": "text", "required": true },
{ "name": "password", "label": "New password (leave blank to generate)", "type": "password" }
]
},
{
"id": "create_account", "category": "users", "label": "Create User Account",
"description": "Create a new Mattermost user via mmctl.", "icon": "👤",
"fields": [
{ "name": "username", "label": "Username", "type": "text", "required": true },
{ "name": "email", "label": "Email", "type": "text", "required": true },
{ "name": "password", "label": "Password (leave blank to generate)", "type": "password" },
{ "name": "admin", "label": "Make system admin", "type": "checkbox", "default": false }
]
},
{ "id": "list_users", "category": "users", "label": "List Users", "description": "Show every Mattermost user account.", "icon": "📋", "fields": [] },
{
"id": "delete_user", "category": "users", "label": "Delete User Account",
"description": "Permanently delete a user account.", "icon": "🗑",
"destructive": true, "confirm": "This cannot be undone.",
"fields": [
{ "name": "username", "label": "Username/Email", "type": "text", "required": true }
]
},
{
"id": "set_admin", "category": "users", "label": "Set Admin Status",
"description": "Promote a user to admin or demote them to a normal user.", "icon": "👑",
"fields": [
{ "name": "username", "label": "Username/Email", "type": "text", "required": true },
{ "name": "admin", "label": "Make admin", "type": "checkbox", "default": false }
]
}
]
},
"invidious": {
"tools": [
{
"id": "reset_password", "category": "users", "label": "Reset User Password",
"description": "Reset an Invidious user's password (Postgres bcrypt update).", "icon": "🔑",
"fields": [
{ "name": "email", "label": "Email", "type": "text", "required": true },
{ "name": "password", "label": "New password (leave blank to generate)", "type": "password" }
]
},
{
"id": "create_account", "category": "users", "label": "Create User Account",
"description": "Create a new Invidious user (Invidious uses email as username).", "icon": "👤",
"fields": [
{ "name": "email", "label": "Email", "type": "text", "required": true },
{ "name": "password", "label": "Password (leave blank to generate)", "type": "password" },
{ "name": "admin", "label": "Make admin", "type": "checkbox", "default": false }
]
},
{ "id": "list_users", "category": "users", "label": "List Users", "description": "Show every Invidious user account.", "icon": "📋", "fields": [] },
{
"id": "delete_user", "category": "users", "label": "Delete User Account",
"description": "Permanently delete a user account.", "icon": "🗑",
"destructive": true, "confirm": "This cannot be undone.",
"fields": [
{ "name": "email", "label": "Email", "type": "text", "required": true }
]
}
]
},
"gitea": {
"tools": [
{
"id": "reset_password",
"category": "users",
"label": "Reset User Password",
"description": "Reset a Gitea user's password using the built-in `gitea admin user change-password` CLI. Leave the password blank to generate a random one.",
"icon": "🔑",
"fields": [
{ "name": "username", "label": "Username", "type": "text", "required": true },
{ "name": "password", "label": "New password (leave blank to generate)", "type": "password", "placeholder": "Leave blank for random" }
]
},
{
"id": "create_account",
"category": "users",
"label": "Create User Account",
"description": "Create a new Gitea user via the built-in `gitea admin user create` CLI. Tick \"Make admin\" for full admin rights. Leave the password blank to generate a random one. The account starts ready to log in (no forced password change).",
"icon": "👤",
"fields": [
{ "name": "username", "label": "Username", "type": "text", "required": true },
{ "name": "email", "label": "Email", "type": "text", "placeholder": "user@example.com", "required": true },
{ "name": "password", "label": "Password (leave blank to generate)", "type": "password", "placeholder": "Leave blank for random" },
{ "name": "admin", "label": "Make admin", "type": "checkbox", "default": false }
]
},
{ "id": "list_users", "category": "users", "label": "List Users", "description": "Show every Gitea user account.", "icon": "📋", "fields": [] },
{
"id": "delete_user", "category": "users", "label": "Delete User Account",
"description": "Permanently delete a user account.", "icon": "🗑",
"destructive": true, "confirm": "This cannot be undone.",
"fields": [
{ "name": "username", "label": "Username", "type": "text", "required": true }
]
},
{
"id": "set_admin", "category": "users", "label": "Set Admin Status",
"description": "Promote a user to admin or demote them to a normal user.", "icon": "👑",
"fields": [
{ "name": "username", "label": "Username", "type": "text", "required": true },
{ "name": "admin", "label": "Make admin", "type": "checkbox", "default": false }
]
}
]
},
"gluetun": {
"tools": [
{
"id": "refresh_providers",
"label": "Refresh VPN Providers",
"description": "Re-scan VPN providers and country lists, regenerating the snapshot used by the country picker.",
"icon": "🔄",
"fields": []
}
]
},
"traefik": {
"tools": [
{
"id": "reset_password",
"label": "Reset Dashboard Login",
"description": "Reset the Traefik dashboard credentials (CFG_TRAEFIK_USER / CFG_TRAEFIK_PASS) and regenerate the htpasswd entry in protectionauth.yml. Leave fields blank to keep the current username and/or generate a new random password.",
"icon": "🔑",
"fields": [
{ "name": "username", "label": "Username (leave blank to keep current)", "type": "text", "placeholder": "admin" },
{ "name": "password", "label": "New password (leave blank to generate)", "type": "password", "placeholder": "Leave blank for random" }
]
}
]
},
"nextcloud": {
"tools": [
{
"id": "reset_password",
"category": "users",
"label": "Reset User Password",
"description": "Reset an existing Nextcloud user's password. Leave the password field blank to generate a random one — it is shown in the task log.",
"icon": "🔑",
"fields": [
{ "name": "username", "label": "Username", "type": "text", "placeholder": "alice", "required": true },
{ "name": "password", "label": "New password (leave blank to generate)", "type": "password", "placeholder": "Leave blank for random" }
]
},
{
"id": "create_account",
"category": "users",
"label": "Create User Account",
"description": "Create a new Nextcloud user. Tick \"Make admin\" to add them to the admin group. Leave the password blank to generate a random one.",
"icon": "👤",
"fields": [
{ "name": "username", "label": "Username", "type": "text", "placeholder": "alice", "required": true },
{ "name": "display_name", "label": "Display name", "type": "text", "placeholder": "Alice Smith" },
{ "name": "password", "label": "Password (leave blank to generate)", "type": "password", "placeholder": "Leave blank for random" },
{ "name": "admin", "label": "Make admin", "type": "checkbox", "default": false }
]
},
{ "id": "list_users", "category": "users", "label": "List Users", "description": "Show every Nextcloud user with their display name and admin flag.", "icon": "📋", "fields": [] },
{
"id": "delete_user", "category": "users", "label": "Delete User Account",
"description": "Permanently delete a user and all their files.", "icon": "🗑",
"destructive": true, "confirm": "This cannot be undone. The user's files will be removed.",
"fields": [
{ "name": "username", "label": "Username", "type": "text", "required": true }
]
},
{
"id": "set_admin", "category": "users", "label": "Set Admin Status",
"description": "Add a user to (or remove from) the admin group.", "icon": "👑",
"fields": [
{ "name": "username", "label": "Username", "type": "text", "required": true },
{ "name": "admin", "label": "Make admin", "type": "checkbox", "default": false }
]
},
{
"id": "toggle_maintenance", "category": "maintenance", "label": "Toggle Maintenance Mode",
"description": "Lock all users out and show a maintenance notice — required before running upgrades or repairs from the CLI.", "icon": "🚧",
"fields": [
{ "name": "enable", "label": "Enable maintenance mode", "type": "checkbox", "default": true }
]
},
{
"id": "rescan_files", "category": "maintenance", "label": "Rescan Files",
"description": "Re-index Nextcloud's file metadata. Run this after files were added or removed on disk outside Nextcloud (rsync, restore, manual copy). Leave the username blank to scan every user.", "icon": "🔄",
"fields": [
{ "name": "username", "label": "Username (blank = all users)", "type": "text", "placeholder": "blank for all" }
]
},
{
"id": "add_trusted_domain", "category": "system", "label": "Add Trusted Domain",
"description": "Append a hostname to Nextcloud's trusted_domains list so requests to that host are accepted.", "icon": "🌐",
"fields": [
{ "name": "domain", "label": "Domain", "type": "text", "placeholder": "cloud.example.org", "required": true }
]
},
{
"id": "system_status", "category": "system", "label": "System Status",
"description": "Show Nextcloud's version, install state, and maintenance flag.", "icon": "",
"fields": []
},
{
"id": "tail_logs", "category": "system", "label": "Tail Logs",
"description": "Show the most recent lines of nextcloud.log.", "icon": "📜",
"fields": [
{ "name": "lines", "label": "Lines", "type": "number", "default": 100, "min": 10, "max": 1000 }
]
}
]
}
}
}
JSON
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
mv "$tmp" "$output_file"
createTouch "$output_file" "$docker_install_user" "silent"
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))."
}