refactor(apps): make app tools + helpers fully self-contained per app

Each app now carries everything under containers/<app>/: Tools-tab actions in
tools/ (declaration <app>.tools.json + function <app>_<tool_id>.sh) and logic
helpers in scripts/ (e.g. <app>_auth.sh). The container scan live-sources every
.sh under the app (maxdepth 3, prunes only resources/) and webui_tools.sh
auto-merges the .tools.json, so an app is a true drop-in — no central edit, no
array regen.

- Empty the central webui_tools.sh heredoc; all 34 tools across 11 apps now
  come from per-app declarations (verified byte-identical to the old output).
- Retire the orphaned mattermost tool scripts to scripts/unused (there is no
  containers/mattermost; its install fn already lived in unused).
- Update the dispatch comment/error path, the auth-adapter doc, and
  DEVELOPMENT.md to the new convention.
- Regenerate static arrays (files_app.sh no longer lists app/containers/*).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
librelad 2026-05-25 22:45:33 +01:00
parent 7204be3aff
commit 898068a390
75 changed files with 642 additions and 422 deletions

View File

@ -0,0 +1,106 @@
{
"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
}
]
}
]
}

View File

@ -0,0 +1,21 @@
{
"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"
]
}
]
}
]
}

View File

@ -0,0 +1,82 @@
{
"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
}
]
}
]
}

View File

@ -0,0 +1,105 @@
{
"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
}
]
}
]
}

View File

@ -0,0 +1,11 @@
{
"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": []
}
]
}

View File

@ -0,0 +1,75 @@
{
"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
}
]
}
]
}

View File

@ -0,0 +1,177 @@
{
"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
}
]
}
]
}

View File

@ -0,0 +1,11 @@
{
"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": []
}
]
}

View File

@ -0,0 +1,24 @@
{
"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"
}
]
}
]
}

View File

@ -170,15 +170,17 @@ is a dev-only escape hatch.
## Conventions
- **Versioning:** semver in `VERSION`. Bump before building; `latest.json` carries it.
- **App tools are self-contained** (preferred): put the declaration in
`containers/<app>/tools/<app>.tools.json` and each function in
- **An app is a self-contained drop-in.** Everything for an app lives under
`containers/<app>/`:
- **Tools tab actions:** declare them in `containers/<app>/tools/<app>.tools.json`
(`{ "tools": [ … ] }`) and put each function beside it at
`containers/<app>/tools/<app>_<tool_id>.sh` (function `app<App><PascalToolId>`).
Both are picked up automatically — the container scan live-sources the `.sh`, and
`webui_tools.sh` auto-merges the `.tools.json`. No central edits, no array regen →
the app is a true drop-in. (The legacy central style — an entry in the
`webui_tools.sh` heredoc + the function under `scripts/app/containers/<app>/`
still works; both coexist, so apps migrate one at a time. Only *declared tools*
move; shared logic helpers like `<app>_auth.sh` stay in `scripts/app/`.)
- **Logic helpers** (anything that isn't a Tools-tab action — e.g. the auth
adapter, post-install fixups): `containers/<app>/scripts/<app>_*.sh`.
- The container scan **live-sources** every `.sh` under `containers/<app>/`
(`tools/` and `scripts/`), and `webui_tools.sh` **auto-merges** the
`.tools.json`. So dropping the folder onto an install is all it takes — no
central edits, no array regen.
- **New runtime script?** Add it under `scripts/<area>/…` and run
`scripts/source/files/generate_arrays.sh run` so it's sourced (build/standalone
tooling under `scripts/release` and `scripts/system` is intentionally excluded).

View File

@ -5,7 +5,7 @@
# Each app declares an auth profile in its config:
# CFG_<APP>_AUTH_PROFILE = single_password | user_password | multi_user | none
#
# And implements adapter functions in scripts/app/containers/<app>/<app>_auth.sh:
# And implements adapter functions in containers/<app>/scripts/<app>_auth.sh:
# authAdapter_<app>_setPassword "$user" "$password"
# authAdapter_<app>_createUser "$user" "$password" "$email" "$isAdmin"
# authAdapter_<app>_listUsers

View File

@ -17,11 +17,11 @@ dockerAppRunTool()
# One-file-per-tool convention: tool id `<verb>_<noun>` resolves to
# function `app<AppName><PascalCaseToolId>`. Each tool lives at
# scripts/app/containers/<app>/<app>_<tool_id>.sh.
# containers/<app>/tools/<app>_<tool_id>.sh (live-sourced by the container scan).
#
# Examples:
# gluetun + refresh_providers → appGluetunRefreshProviders
# mattermost + reset_password → appMattermostResetPassword
# nextcloud + reset_password → appNextcloudResetPassword
local app_name_ucfirst="$(tr '[:lower:]' '[:upper:]' <<< ${app_name:0:1})${app_name:1}"
local tool_pascal=""
@ -35,7 +35,7 @@ dockerAppRunTool()
local toolFuncName="app${app_name_ucfirst}${tool_pascal}"
if ! declare -f "$toolFuncName" >/dev/null 2>&1; then
isError "App '$app_name' has no tool '$tool_name' (missing function $toolFuncName — expected in scripts/app/containers/$app_name/$app_name_${tool_name}.sh)."
isError "App '$app_name' has no tool '$tool_name' (missing function $toolFuncName — expected in containers/$app_name/tools/${app_name}_${tool_name}.sh)."
return 1
fi

View File

@ -10,66 +10,5 @@ app_scripts=(
"app/app_status.sh"
"app/app_update_specifics.sh"
"app/auth_adapter.sh"
"app/containers/adguard/adguard_auth.sh"
"app/containers/bookstack/bookstack_auth.sh"
"app/containers/bookstack/bookstack_create_account.sh"
"app/containers/bookstack/bookstack_delete_user.sh"
"app/containers/bookstack/bookstack_list_users.sh"
"app/containers/bookstack/bookstack_reset_password.sh"
"app/containers/bookstack/bookstack_set_admin.sh"
"app/containers/crowdsec/crowdsec_alerts_list.sh"
"app/containers/crowdsec/crowdsec_console_disable.sh"
"app/containers/crowdsec/crowdsec_console_enroll.sh"
"app/containers/crowdsec/crowdsec_decisions_list.sh"
"app/containers/crowdsec/crowdsec_fix_priority.sh"
"app/containers/crowdsec/crowdsec_hub_update.sh"
"app/containers/crowdsec/crowdsec_metrics.sh"
"app/containers/crowdsec/crowdsec_status.sh"
"app/containers/crowdsec/crowdsec_unban.sh"
"app/containers/crowdsec/crowdsec_update.sh"
"app/containers/crowdsec/crowdsec_verify_firewall.sh"
"app/containers/dashy/dashy_manage_shortcuts.sh"
"app/containers/dashy/dashy_update_conf.sh"
"app/containers/focalboard/focalboard_auth.sh"
"app/containers/focalboard/focalboard_create_account.sh"
"app/containers/focalboard/focalboard_delete_user.sh"
"app/containers/focalboard/focalboard_list_users.sh"
"app/containers/focalboard/focalboard_reset_password.sh"
"app/containers/focalboard/focalboard_set_admin.sh"
"app/containers/gitea/gitea_auth.sh"
"app/containers/gitea/gitea_create_account.sh"
"app/containers/gitea/gitea_delete_user.sh"
"app/containers/gitea/gitea_list_users.sh"
"app/containers/gitea/gitea_reset_password.sh"
"app/containers/gitea/gitea_set_admin.sh"
"app/containers/gluetun/gluetun_recreate_routed.sh"
"app/containers/gluetun/gluetun_refresh_providers.sh"
"app/containers/invidious/invidious_auth.sh"
"app/containers/invidious/invidious_create_account.sh"
"app/containers/invidious/invidious_delete_user.sh"
"app/containers/invidious/invidious_list_users.sh"
"app/containers/invidious/invidious_reset_password.sh"
"app/containers/invidious/invidious_set_admin.sh"
"app/containers/mattermost/mattermost_auth.sh"
"app/containers/mattermost/mattermost_create_account.sh"
"app/containers/mattermost/mattermost_delete_user.sh"
"app/containers/mattermost/mattermost_list_users.sh"
"app/containers/mattermost/mattermost_reset_password.sh"
"app/containers/mattermost/mattermost_set_admin.sh"
"app/containers/nextcloud/nextcloud_add_trusted_domain.sh"
"app/containers/nextcloud/nextcloud_auth.sh"
"app/containers/nextcloud/nextcloud_create_account.sh"
"app/containers/nextcloud/nextcloud_delete_user.sh"
"app/containers/nextcloud/nextcloud_list_users.sh"
"app/containers/nextcloud/nextcloud_rescan_files.sh"
"app/containers/nextcloud/nextcloud_reset_password.sh"
"app/containers/nextcloud/nextcloud_set_admin.sh"
"app/containers/nextcloud/nextcloud_system_status.sh"
"app/containers/nextcloud/nextcloud_tail_logs.sh"
"app/containers/nextcloud/nextcloud_toggle_maintenance.sh"
"app/containers/owncloud/owncloud_setup_config.sh"
"app/containers/pihole/pihole_apply_dns_updater.sh"
"app/containers/traefik/traefik_auth.sh"
"app/containers/traefik/traefik_reset_password.sh"
)

View File

@ -3,10 +3,13 @@
# 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.
# 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.
#
@ -49,355 +52,19 @@ webuiGenerateAppsToolsConfig() {
runFileOp mkdir -p "$(dirname "$output_file")"
# Heredoc carries the JSON literal verbatim — much easier to edit
# than escaping nested quotes through jq -n.
# 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": {
"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 }
]
}
]
}
}
"apps": {}
}
JSON
# Merge per-app tool declarations so a DROPPED-IN app (e.g. from LibrePortal-Infra)
# registers its own Tools tab actions without editing this file. Each app may ship
# containers/<app>/<app>.tools.json = { "tools": [ … ] } (same schema as above);
# it sets .apps[<app>]. Core apps declared in the heredoc need no such file.
# 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