#!/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//_.sh # with `app()` 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: "", # 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" 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))." }