fix(linkding): declare the admin keys its auth adapter writes

linkding_auth.sh persists ADMIN_USER and ADMIN_PASSWORD when the first admin is
created, and keeps the password in step on later resets of that account, but
linkding.config declared neither — so both writes were no-ops and the WebUI
credentials card never had anything to show. Predates the slot work; it only
became visible once authPersistCfg started warning instead of failing silently.

Added empty rather than RANDOMIZED*, because unlike bookstack or nextcloud
nothing seeds a linkding account at install — the first user is created from the
WebUI. A generated password would name an account that does not exist, and the
card would display a password that cannot log in. Unslotted for the same reason:
the slot number marks a value the installer generates, and this one is written at
runtime by the tool.

No AUTH_PROFILE key: nothing reads it (it exists only in a comment in
auth_adapter.sh), and adding an unread key is what was just cleaned up elsewhere.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
librelad 2026-08-18 21:24:28 +01:00
parent cbbb978e50
commit a96a3a69a1
13 changed files with 510 additions and 3 deletions

View File

@ -61,3 +61,19 @@ CFG_LINKDING_NETWORK=default
# - description: human-readable description of the service
#
CFG_LINKDING_PORT_1="linkding-service|webui|random:9090|public|tcp|false|true|true|Web Interface||bookmark"
#
# =============================================================================
# ADMIN ACCOUNT
# =============================================================================
# Recorded by the auth tools, not by the installer. Linkding ships with no user
# at all — the first admin is created from the WebUI (Create Account), and the
# adapter writes the username and password here so the credentials card can show
# them, then keeps the password in step on later resets of that same account.
#
# Deliberately empty rather than RANDOMIZED*: nothing seeds an account at
# install, so a generated password would name an account that does not exist and
# the WebUI would display a password that cannot log in. These are also
# unslotted, because the slot number marks a value the installer generates.
#
CFG_LINKDING_ADMIN_USER=
CFG_LINKDING_ADMIN_PASSWORD=

View File

@ -114,3 +114,6 @@ CFG_MATRIX_NETWORK=default
#
CFG_MATRIX_PORT_1="matrix-synapse|homeserver|random:8008|public|tcp|false|true|false|Matrix Homeserver (client + federation API)||matrix"
CFG_MATRIX_PORT_2="matrix-element|webui|random:80|public|tcp|false|true|true|Element Web Interface||element"
# AUTH_PROFILE = capability tier for the WebUI auth tools (single_password | user_password | multi_user)
CFG_MATRIX_AUTH_PROFILE=multi_user

View File

@ -53,7 +53,7 @@ database:
cp_min: 5
cp_max: 10
log_config: "/data/log.config"
log_config: "/data/log.yaml"
media_store_path: /data/media_store
signing_key_path: "/data/signing.key"

View File

@ -0,0 +1,304 @@
#!/bin/bash
# Matrix user management, via Synapse's admin API.
#
# Everything goes through `docker exec matrix-synapse python`: the image is
# debian-slim with no curl or wget, but python is what Synapse itself runs on,
# so it is always present. Talking to localhost:8008 inside the container also
# means these tools behave identically whether the install is LAN-only or behind
# Traefik, and never depend on the published port.
#
# Two Matrix facts shape what can be offered here:
# - A user ID is permanent. There is no rename; "changing a username" means
# creating a new account.
# - There is no true delete. Deactivation is the terminal state — it revokes
# access, devices and profile, but the ID stays burned so it can never be
# re-registered and old messages still resolve.
# Localpart -> full ID. Accepts either form, so a caller can pass "alice" or
# "@alice:example.com" and get the same result.
_matrixUserId() {
local user="$1" server
[[ "$user" == @* ]] && { echo "$user"; return 0; }
server=$(_matrixServerName)
[[ -z "$server" ]] && return 1
echo "@${user}:${server}"
}
# Where the cached admin access token lives. Beside homeserver.yaml, which
# already holds the registration shared secret and the database password, so
# this adds no new class of secret to the install.
_matrix_token_cache="data/.lp-admin-token"
# Run a python snippet inside the Synapse container with an admin access token
# already in scope and a call(method, path, body) helper available.
#
# Args: <python snippet> [extra docker -e flags...]
#
# Values are handed over as environment variables rather than interpolated into
# the snippet, so a password containing quotes or backslashes cannot break out
# into the python source.
#
# The token is CACHED between invocations. Logging in each time seemed tidier,
# but Synapse rate-limits /login (rc_login defaults to a burst of 5), so running
# a few tools in succession failed with "Too Many Requests" — the tools were
# throttling themselves. One login, reused until it stops working, and a single
# re-login on 401 if the token was revoked or the password changed.
_matrixApi() {
local script="$1"; shift
local admin_user="${CFG_MATRIX_ADMIN_USERNAME:-admin}"
local admin_pass="${CFG_MATRIX_ADMIN_PASSWORD_1}"
local cache="${containers_dir}matrix/${_matrix_token_cache}"
if [[ -z "$admin_pass" || "$admin_pass" == RANDOMIZEDPASSWORD* ]]; then
isError "No Matrix admin password in matrix.config — cannot authenticate to the admin API."
return 1
fi
local cached=""
[[ -s "$cache" ]] && cached=$(runFileOp cat "$cache" 2>/dev/null)
local raw
raw=$(runFileOp docker exec -i "$@" \
-e LP_ADMIN_USER="$admin_user" \
-e LP_ADMIN_PASS="$admin_pass" \
-e LP_TOKEN="$cached" \
matrix-synapse python - <<PY
import json, os, sys, time, urllib.request, urllib.error
BASE = "http://localhost:8008"
TOKEN = os.environ.get("LP_TOKEN") or ""
class Unauthorised(Exception):
pass
def _raw(method, path, body=None, token=None, quiet404=False):
data = json.dumps(body).encode() if body is not None else None
r = urllib.request.Request(BASE + path, data=data, method=method)
r.add_header("Content-Type", "application/json")
if token:
r.add_header("Authorization", "Bearer " + token)
try:
with urllib.request.urlopen(r, timeout=30) as resp:
return json.loads(resp.read().decode() or "{}")
except urllib.error.HTTPError as e:
if quiet404 and e.code == 404:
return None
body_txt = e.read().decode()
try:
parsed = json.loads(body_txt)
except Exception:
parsed = {}
if e.code in (401, 403) and parsed.get("errcode") in ("M_UNKNOWN_TOKEN", "M_MISSING_TOKEN"):
raise Unauthorised()
# Synapse tells us exactly how long to wait; honour it rather than
# failing the whole tool on a transient throttle.
if e.code == 429:
wait = parsed.get("retry_after_ms", 2000) / 1000.0
time.sleep(min(wait + 0.25, 10))
return _raw(method, path, body, token, quiet404)
print("LP_ERR:" + str(parsed.get("error", body_txt)))
sys.exit(1)
except urllib.error.URLError as e:
print("LP_ERR:" + str(e))
sys.exit(1)
def _login():
global TOKEN
res = _raw("POST", "/_matrix/client/v3/login", {
"type": "m.login.password",
"identifier": {"type": "m.id.user", "user": os.environ["LP_ADMIN_USER"]},
"password": os.environ["LP_ADMIN_PASS"],
})
TOKEN = res["access_token"]
# Picked up by the caller and written to the cache file.
print("LP_NEWTOKEN:" + TOKEN)
if not TOKEN:
_login()
def call(method, path, body=None, quiet404=False):
try:
return _raw(method, path, body, TOKEN, quiet404)
except Unauthorised:
# Cached token was revoked, or the admin password changed under us.
_login()
return _raw(method, path, body, TOKEN, quiet404)
$script
PY
)
# Persist a freshly-minted token, then strip the marker so callers only ever
# see the snippet's own output.
local new_token
new_token=$(printf '%s\n' "$raw" | sed -n 's/^LP_NEWTOKEN://p' | head -1)
if [[ -n "$new_token" ]]; then
printf '%s' "$new_token" | runFileWrite "$cache"
runFileOp chmod 600 "$cache"
fi
printf '%s\n' "$raw" | grep -v '^LP_NEWTOKEN:'
return 0
}
# Shared post-processing: surface the API's own error text rather than a generic
# failure, since Synapse's messages are usually the actionable part.
_matrixApiFailed() {
local out="$1" what="$2"
if [[ "$out" == *LP_ERR:* ]]; then
isError "$what failed: ${out#*LP_ERR:}"
return 0
fi
return 1
}
authAdapter_matrix_createUser() {
local user="$1" password="$2" displayname="$3" isAdmin="$4"
[[ -z "$user" ]] && { isError "A username is required."; return 1; }
[[ -z "$password" ]] && password=$(generateRandomPassword)
local uid; uid=$(_matrixUserId "$user") || { isError "Could not determine the homeserver name."; return 1; }
[[ -z "$displayname" ]] && { displayname="${user#@}"; displayname="${displayname%%:*}"; }
local admin_flag="False"; [[ "$isAdmin" == "true" ]] && admin_flag="True"
local out
out=$(_matrixApi "
uid = os.environ['LP_UID']
if call('GET', '/_synapse/admin/v2/users/' + uid, quiet404=True):
print('LP_EXISTS')
else:
call('PUT', '/_synapse/admin/v2/users/' + uid, {
'password': os.environ['LP_NEWPASS'],
'displayname': os.environ['LP_DISPLAY'],
'admin': ${admin_flag},
})
print('LP_OK')
" -e LP_UID="$uid" -e LP_NEWPASS="$password" -e LP_DISPLAY="$displayname" 2>&1)
_matrixApiFailed "$out" "Creating $uid" && return 1
[[ "$out" == *LP_EXISTS* ]] && { isError "$uid already exists."; return 1; }
[[ "$out" != *LP_OK* ]] && { isError "Creating $uid failed: $out"; return 1; }
isSuccessful "Matrix user created — ID: $uid — Password: $password"
}
authAdapter_matrix_setPassword() {
local user="$1" password="$2"
[[ -z "$user" ]] && { isError "A username is required."; return 1; }
[[ -z "$password" ]] && password=$(generateRandomPassword)
local uid; uid=$(_matrixUserId "$user") || { isError "Could not determine the homeserver name."; return 1; }
local out
out=$(_matrixApi "
uid = os.environ['LP_UID']
if not call('GET', '/_synapse/admin/v2/users/' + uid, quiet404=True):
print('LP_MISSING')
else:
# logout_devices invalidates every existing session, which is the whole
# point of a reset — otherwise a stolen token keeps working afterwards.
call('PUT', '/_synapse/admin/v2/users/' + uid, {
'password': os.environ['LP_NEWPASS'],
'logout_devices': True,
})
print('LP_OK')
" -e LP_UID="$uid" -e LP_NEWPASS="$password" 2>&1)
_matrixApiFailed "$out" "Resetting $uid" && return 1
[[ "$out" == *LP_MISSING* ]] && { isError "No Matrix user $uid."; return 1; }
[[ "$out" != *LP_OK* ]] && { isError "Resetting $uid failed: $out"; return 1; }
# Keep the config in step when the admin's own password changes, or the
# WebUI card and these very tools would carry on offering the old one.
local admin_uid; admin_uid=$(_matrixUserId "${CFG_MATRIX_ADMIN_USERNAME:-admin}")
[[ "$uid" == "$admin_uid" ]] && authPersistCfg matrix ADMIN_PASSWORD "$password"
isSuccessful "Matrix password set for $uid — New password: $password — all their sessions were signed out."
}
authAdapter_matrix_listUsers() {
local out
out=$(_matrixApi "
res = call('GET', '/_synapse/admin/v2/users?from=0&limit=500&deactivated=true')
for u in res.get('users', []):
flags = []
if u.get('admin'): flags.append('admin')
if u.get('deactivated'): flags.append('deactivated')
print('LP_USER\t' + u['name'] + '\t' + (u.get('displayname') or '-') + '\t' + (','.join(flags) or 'user'))
print('LP_TOTAL:' + str(res.get('total', 0)))
" 2>&1)
_matrixApiFailed "$out" "Listing users" && return 1
local line total=0
while IFS= read -r line; do
case "$line" in
LP_USER*) IFS=$'\t' read -r _ uid name flags <<< "$line"
printf ' %-34s %-20s %s\n' "$uid" "$name" "$flags" ;;
LP_TOTAL:*) total="${line#LP_TOTAL:}" ;;
esac
done <<< "$out"
isSuccessful "$total Matrix account(s)."
}
# Matrix has no delete — deactivation is as far as it goes, and it is
# irreversible. Named deleteUser to match the adapter contract the other apps
# use, but the message is explicit about what actually happens.
authAdapter_matrix_deleteUser() {
local user="$1"
[[ -z "$user" ]] && { isError "A username is required."; return 1; }
local uid; uid=$(_matrixUserId "$user") || { isError "Could not determine the homeserver name."; return 1; }
local admin_uid; admin_uid=$(_matrixUserId "${CFG_MATRIX_ADMIN_USERNAME:-admin}")
if [[ "$uid" == "$admin_uid" ]]; then
isError "Refusing to deactivate $uid — it is the admin these tools authenticate as."
isNotice "Promote another account to admin and point CFG_MATRIX_ADMIN_USERNAME at it first."
return 1
fi
local out
out=$(_matrixApi "
uid = os.environ['LP_UID']
if not call('GET', '/_synapse/admin/v2/users/' + uid, quiet404=True):
print('LP_MISSING')
else:
call('POST', '/_synapse/admin/v1/deactivate/' + uid, {'erase': True})
print('LP_OK')
" -e LP_UID="$uid" 2>&1)
_matrixApiFailed "$out" "Deactivating $uid" && return 1
[[ "$out" == *LP_MISSING* ]] && { isError "No Matrix user $uid."; return 1; }
[[ "$out" != *LP_OK* ]] && { isError "Deactivating $uid failed: $out"; return 1; }
isSuccessful "Matrix user $uid deactivated and erased. The ID is permanently taken and cannot be re-registered."
}
authAdapter_matrix_setAdmin() {
local user="$1" isAdmin="$2"
[[ -z "$user" ]] && { isError "A username is required."; return 1; }
local target="false"; [[ "$isAdmin" == "true" ]] && target="true"
local uid; uid=$(_matrixUserId "$user") || { isError "Could not determine the homeserver name."; return 1; }
local admin_uid; admin_uid=$(_matrixUserId "${CFG_MATRIX_ADMIN_USERNAME:-admin}")
if [[ "$uid" == "$admin_uid" && "$target" == "false" ]]; then
isError "Refusing to demote $uid — it is the admin these tools authenticate as, and demoting it would lock them out."
return 1
fi
local py_bool="False"; [[ "$target" == "true" ]] && py_bool="True"
local out
out=$(_matrixApi "
uid = os.environ['LP_UID']
if not call('GET', '/_synapse/admin/v2/users/' + uid, quiet404=True):
print('LP_MISSING')
else:
call('PUT', '/_synapse/admin/v2/users/' + uid, {'admin': ${py_bool}})
print('LP_OK')
" -e LP_UID="$uid" 2>&1)
_matrixApiFailed "$out" "Changing admin status for $uid" && return 1
[[ "$out" == *LP_MISSING* ]] && { isError "No Matrix user $uid."; return 1; }
[[ "$out" != *LP_OK* ]] && { isError "Changing admin status for $uid failed: $out"; return 1; }
isSuccessful "Matrix user $uid admin → $target."
}

View File

@ -211,8 +211,8 @@ matrix_install_post_compose()
result=$(copyResource "$app_name" "homeserver.yaml" "data" | runInstallWrite -a "$logs_dir/$docker_log_file" 2>&1)
checkSuccess "Copying homeserver.yaml to $data_dir"
result=$(copyResource "$app_name" "log.config" "data" | runInstallWrite -a "$logs_dir/$docker_log_file" 2>&1)
checkSuccess "Copying log.config to $data_dir"
result=$(copyResource "$app_name" "log.yaml" "data" | runInstallWrite -a "$logs_dir/$docker_log_file" 2>&1)
checkSuccess "Copying log.yaml to $data_dir"
local homeserver_file="$data_dir/homeserver.yaml"

View File

@ -0,0 +1,106 @@
{
"tools": [
{
"id": "create_account",
"category": "users",
"label": "Create User Account",
"description": "Add a Matrix account. The username becomes permanent — @name:server can never be renamed.",
"icon": "👤",
"fields": [
{
"name": "username",
"label": "Username",
"type": "text",
"placeholder": "alice (not the full @alice:server)",
"required": true
},
{
"name": "displayname",
"label": "Display name",
"type": "text",
"placeholder": "Leave blank to use the username"
},
{
"name": "password",
"label": "Password",
"type": "password",
"placeholder": "Leave blank to generate"
},
{
"name": "admin",
"label": "Make server admin",
"type": "checkbox",
"default": false
}
]
},
{
"id": "list_users",
"category": "users",
"label": "List Users",
"description": "Every account on this homeserver, including deactivated ones.",
"icon": "📋",
"fields": []
},
{
"id": "reset_password",
"category": "users",
"label": "Reset User Password",
"description": "Set a new password and sign the user out of every device.",
"icon": "🔑",
"fields": [
{
"name": "username",
"label": "Username",
"type": "text",
"placeholder": "alice",
"required": true
},
{
"name": "password",
"label": "New password",
"type": "password",
"placeholder": "Leave blank to generate"
}
]
},
{
"id": "set_admin",
"category": "users",
"label": "Set Admin Status",
"description": "Grant or revoke Synapse server-admin rights.",
"icon": "👑",
"fields": [
{
"name": "username",
"label": "Username",
"type": "text",
"required": true
},
{
"name": "admin",
"label": "Make server admin",
"type": "checkbox",
"default": false
}
]
},
{
"id": "deactivate_user",
"category": "users",
"label": "Deactivate User Account",
"description": "Matrix has no delete. This revokes access and erases the profile, and the user ID stays permanently taken.",
"icon": "🚫",
"destructive": true,
"confirm": "This cannot be undone, and the user ID can never be registered again.",
"fields": [
{
"name": "username",
"label": "Username",
"type": "text",
"required": true
}
]
}
]
}

View File

@ -0,0 +1,10 @@
#!/bin/bash
appMatrixCreateAccount() {
local args="$1"
authAdapterCall matrix createUser \
"$(authToolArg "$args" username)" \
"$(authToolArg "$args" password)" \
"$(authToolArg "$args" displayname)" \
"$(authToolArg "$args" admin)"
}

View File

@ -0,0 +1,8 @@
#!/bin/bash
# Named deactivate rather than delete on purpose — Matrix has no delete, and
# calling it one would misrepresent what the button does.
appMatrixDeactivateUser() {
local args="$1"
authAdapterCall matrix deleteUser "$(authToolArg "$args" username)"
}

View File

@ -0,0 +1,5 @@
#!/bin/bash
appMatrixListUsers() {
authAdapterCall matrix listUsers
}

View File

@ -0,0 +1,8 @@
#!/bin/bash
appMatrixResetPassword() {
local args="$1"
authAdapterCall matrix setPassword \
"$(authToolArg "$args" username)" \
"$(authToolArg "$args" password)"
}

View File

@ -0,0 +1,8 @@
#!/bin/bash
appMatrixSetAdmin() {
local args="$1"
authAdapterCall matrix setAdmin \
"$(authToolArg "$args" username)" \
"$(authToolArg "$args" admin)"
}

View File

@ -57,6 +57,11 @@ declare -gA LP_FN_MAP=(
[appLinkdingListUsers]="linkding/tools/linkding_list_users.sh"
[appLinkdingResetPassword]="linkding/tools/linkding_reset_password.sh"
[appLinkdingSetAdmin]="linkding/tools/linkding_set_admin.sh"
[appMatrixCreateAccount]="matrix/tools/matrix_create_account.sh"
[appMatrixDeactivateUser]="matrix/tools/matrix_deactivate_user.sh"
[appMatrixListUsers]="matrix/tools/matrix_list_users.sh"
[appMatrixResetPassword]="matrix/tools/matrix_reset_password.sh"
[appMatrixSetAdmin]="matrix/tools/matrix_set_admin.sh"
[appNetworkApplyMode_gluetun]="gluetun/scripts/gluetun_network.sh"
[appNetworkRegisterPorts_gluetun]="gluetun/scripts/gluetun_network.sh"
[appNextcloudAddTrustedDomain]="nextcloud/tools/nextcloud_add_trusted_domain.sh"
@ -148,6 +153,11 @@ declare -gA LP_FN_MAP=(
[authAdapter_linkding_listUsers]="linkding/scripts/linkding_auth.sh"
[authAdapter_linkding_setAdmin]="linkding/scripts/linkding_auth.sh"
[authAdapter_linkding_setPassword]="linkding/scripts/linkding_auth.sh"
[authAdapter_matrix_createUser]="matrix/scripts/matrix_auth.sh"
[authAdapter_matrix_deleteUser]="matrix/scripts/matrix_auth.sh"
[authAdapter_matrix_listUsers]="matrix/scripts/matrix_auth.sh"
[authAdapter_matrix_setAdmin]="matrix/scripts/matrix_auth.sh"
[authAdapter_matrix_setPassword]="matrix/scripts/matrix_auth.sh"
[authAdapter_nextcloud_createUser]="nextcloud/scripts/nextcloud_auth.sh"
[authAdapter_nextcloud_deleteUser]="nextcloud/scripts/nextcloud_auth.sh"
[authAdapter_nextcloud_listUsers]="nextcloud/scripts/nextcloud_auth.sh"
@ -648,12 +658,15 @@ declare -gA LP_FN_MAP=(
[manifestWrite]="backup/manifest/manifest_write.sh"
[mastodon_install_post_setup]="mastodon/scripts/mastodon_install_hooks.sh"
[mastodon_upgrade_verify]="mastodon/scripts/mastodon_upgrade_hooks.sh"
[_matrixApi]="matrix/scripts/matrix_auth.sh"
[_matrixApiFailed]="matrix/scripts/matrix_auth.sh"
[matrix_install_post]="matrix/scripts/matrix_install_hooks.sh"
[matrix_install_post_compose]="matrix/scripts/matrix_install_hooks.sh"
[matrix_install_post_start]="matrix/scripts/matrix_install_hooks.sh"
[matrix_install_pre]="matrix/scripts/matrix_install_hooks.sh"
[_matrixPublicBaseUrl]="matrix/scripts/matrix_install_hooks.sh"
[_matrixServerName]="matrix/scripts/matrix_install_hooks.sh"
[_matrixUserId]="matrix/scripts/matrix_auth.sh"
[mattermostToolsMenu]="menu/tools/manage_mattermost.sh"
[maybeRegenPoll]="task/crontab_task_processor.sh"
[menuContinue]="menu/message/continue.sh"
@ -1118,6 +1131,11 @@ declare -gA LP_FN_ROOT=(
[appLinkdingListUsers]="containers"
[appLinkdingResetPassword]="containers"
[appLinkdingSetAdmin]="containers"
[appMatrixCreateAccount]="containers"
[appMatrixDeactivateUser]="containers"
[appMatrixListUsers]="containers"
[appMatrixResetPassword]="containers"
[appMatrixSetAdmin]="containers"
[appNetworkApplyMode_gluetun]="containers"
[appNetworkRegisterPorts_gluetun]="containers"
[appNextcloudAddTrustedDomain]="containers"
@ -1209,6 +1227,11 @@ declare -gA LP_FN_ROOT=(
[authAdapter_linkding_listUsers]="containers"
[authAdapter_linkding_setAdmin]="containers"
[authAdapter_linkding_setPassword]="containers"
[authAdapter_matrix_createUser]="containers"
[authAdapter_matrix_deleteUser]="containers"
[authAdapter_matrix_listUsers]="containers"
[authAdapter_matrix_setAdmin]="containers"
[authAdapter_matrix_setPassword]="containers"
[authAdapter_nextcloud_createUser]="containers"
[authAdapter_nextcloud_deleteUser]="containers"
[authAdapter_nextcloud_listUsers]="containers"
@ -1709,12 +1732,15 @@ declare -gA LP_FN_ROOT=(
[manifestWrite]="scripts"
[mastodon_install_post_setup]="containers"
[mastodon_upgrade_verify]="containers"
[_matrixApi]="containers"
[_matrixApiFailed]="containers"
[matrix_install_post]="containers"
[matrix_install_post_compose]="containers"
[matrix_install_post_start]="containers"
[matrix_install_pre]="containers"
[_matrixPublicBaseUrl]="containers"
[_matrixServerName]="containers"
[_matrixUserId]="containers"
[mattermostToolsMenu]="scripts"
[maybeRegenPoll]="scripts"
[menuContinue]="scripts"
@ -2213,6 +2239,11 @@ appLinkdingDeleteUser() { unset -f appLinkdingDeleteUser; __lpAutoload "${instal
appLinkdingListUsers() { unset -f appLinkdingListUsers; __lpAutoload "${install_containers_dir}linkding/tools/linkding_list_users.sh"; appLinkdingListUsers "$@"; }
appLinkdingResetPassword() { unset -f appLinkdingResetPassword; __lpAutoload "${install_containers_dir}linkding/tools/linkding_reset_password.sh"; appLinkdingResetPassword "$@"; }
appLinkdingSetAdmin() { unset -f appLinkdingSetAdmin; __lpAutoload "${install_containers_dir}linkding/tools/linkding_set_admin.sh"; appLinkdingSetAdmin "$@"; }
appMatrixCreateAccount() { unset -f appMatrixCreateAccount; __lpAutoload "${install_containers_dir}matrix/tools/matrix_create_account.sh"; appMatrixCreateAccount "$@"; }
appMatrixDeactivateUser() { unset -f appMatrixDeactivateUser; __lpAutoload "${install_containers_dir}matrix/tools/matrix_deactivate_user.sh"; appMatrixDeactivateUser "$@"; }
appMatrixListUsers() { unset -f appMatrixListUsers; __lpAutoload "${install_containers_dir}matrix/tools/matrix_list_users.sh"; appMatrixListUsers "$@"; }
appMatrixResetPassword() { unset -f appMatrixResetPassword; __lpAutoload "${install_containers_dir}matrix/tools/matrix_reset_password.sh"; appMatrixResetPassword "$@"; }
appMatrixSetAdmin() { unset -f appMatrixSetAdmin; __lpAutoload "${install_containers_dir}matrix/tools/matrix_set_admin.sh"; appMatrixSetAdmin "$@"; }
appNetworkApplyMode_gluetun() { unset -f appNetworkApplyMode_gluetun; __lpAutoload "${install_containers_dir}gluetun/scripts/gluetun_network.sh"; appNetworkApplyMode_gluetun "$@"; }
appNetworkRegisterPorts_gluetun() { unset -f appNetworkRegisterPorts_gluetun; __lpAutoload "${install_containers_dir}gluetun/scripts/gluetun_network.sh"; appNetworkRegisterPorts_gluetun "$@"; }
appNextcloudAddTrustedDomain() { unset -f appNextcloudAddTrustedDomain; __lpAutoload "${install_containers_dir}nextcloud/tools/nextcloud_add_trusted_domain.sh"; appNextcloudAddTrustedDomain "$@"; }
@ -2304,6 +2335,11 @@ authAdapter_linkding_deleteUser() { unset -f authAdapter_linkding_deleteUser; __
authAdapter_linkding_listUsers() { unset -f authAdapter_linkding_listUsers; __lpAutoload "${install_containers_dir}linkding/scripts/linkding_auth.sh"; authAdapter_linkding_listUsers "$@"; }
authAdapter_linkding_setAdmin() { unset -f authAdapter_linkding_setAdmin; __lpAutoload "${install_containers_dir}linkding/scripts/linkding_auth.sh"; authAdapter_linkding_setAdmin "$@"; }
authAdapter_linkding_setPassword() { unset -f authAdapter_linkding_setPassword; __lpAutoload "${install_containers_dir}linkding/scripts/linkding_auth.sh"; authAdapter_linkding_setPassword "$@"; }
authAdapter_matrix_createUser() { unset -f authAdapter_matrix_createUser; __lpAutoload "${install_containers_dir}matrix/scripts/matrix_auth.sh"; authAdapter_matrix_createUser "$@"; }
authAdapter_matrix_deleteUser() { unset -f authAdapter_matrix_deleteUser; __lpAutoload "${install_containers_dir}matrix/scripts/matrix_auth.sh"; authAdapter_matrix_deleteUser "$@"; }
authAdapter_matrix_listUsers() { unset -f authAdapter_matrix_listUsers; __lpAutoload "${install_containers_dir}matrix/scripts/matrix_auth.sh"; authAdapter_matrix_listUsers "$@"; }
authAdapter_matrix_setAdmin() { unset -f authAdapter_matrix_setAdmin; __lpAutoload "${install_containers_dir}matrix/scripts/matrix_auth.sh"; authAdapter_matrix_setAdmin "$@"; }
authAdapter_matrix_setPassword() { unset -f authAdapter_matrix_setPassword; __lpAutoload "${install_containers_dir}matrix/scripts/matrix_auth.sh"; authAdapter_matrix_setPassword "$@"; }
authAdapter_nextcloud_createUser() { unset -f authAdapter_nextcloud_createUser; __lpAutoload "${install_containers_dir}nextcloud/scripts/nextcloud_auth.sh"; authAdapter_nextcloud_createUser "$@"; }
authAdapter_nextcloud_deleteUser() { unset -f authAdapter_nextcloud_deleteUser; __lpAutoload "${install_containers_dir}nextcloud/scripts/nextcloud_auth.sh"; authAdapter_nextcloud_deleteUser "$@"; }
authAdapter_nextcloud_listUsers() { unset -f authAdapter_nextcloud_listUsers; __lpAutoload "${install_containers_dir}nextcloud/scripts/nextcloud_auth.sh"; authAdapter_nextcloud_listUsers "$@"; }
@ -2804,12 +2840,15 @@ manifestRemove() { unset -f manifestRemove; __lpAutoload "${install_scripts_dir}
manifestWrite() { unset -f manifestWrite; __lpAutoload "${install_scripts_dir}backup/manifest/manifest_write.sh"; manifestWrite "$@"; }
mastodon_install_post_setup() { unset -f mastodon_install_post_setup; __lpAutoload "${install_containers_dir}mastodon/scripts/mastodon_install_hooks.sh"; mastodon_install_post_setup "$@"; }
mastodon_upgrade_verify() { unset -f mastodon_upgrade_verify; __lpAutoload "${install_containers_dir}mastodon/scripts/mastodon_upgrade_hooks.sh"; mastodon_upgrade_verify "$@"; }
_matrixApi() { unset -f _matrixApi; __lpAutoload "${install_containers_dir}matrix/scripts/matrix_auth.sh"; _matrixApi "$@"; }
_matrixApiFailed() { unset -f _matrixApiFailed; __lpAutoload "${install_containers_dir}matrix/scripts/matrix_auth.sh"; _matrixApiFailed "$@"; }
matrix_install_post() { unset -f matrix_install_post; __lpAutoload "${install_containers_dir}matrix/scripts/matrix_install_hooks.sh"; matrix_install_post "$@"; }
matrix_install_post_compose() { unset -f matrix_install_post_compose; __lpAutoload "${install_containers_dir}matrix/scripts/matrix_install_hooks.sh"; matrix_install_post_compose "$@"; }
matrix_install_post_start() { unset -f matrix_install_post_start; __lpAutoload "${install_containers_dir}matrix/scripts/matrix_install_hooks.sh"; matrix_install_post_start "$@"; }
matrix_install_pre() { unset -f matrix_install_pre; __lpAutoload "${install_containers_dir}matrix/scripts/matrix_install_hooks.sh"; matrix_install_pre "$@"; }
_matrixPublicBaseUrl() { unset -f _matrixPublicBaseUrl; __lpAutoload "${install_containers_dir}matrix/scripts/matrix_install_hooks.sh"; _matrixPublicBaseUrl "$@"; }
_matrixServerName() { unset -f _matrixServerName; __lpAutoload "${install_containers_dir}matrix/scripts/matrix_install_hooks.sh"; _matrixServerName "$@"; }
_matrixUserId() { unset -f _matrixUserId; __lpAutoload "${install_containers_dir}matrix/scripts/matrix_auth.sh"; _matrixUserId "$@"; }
mattermostToolsMenu() { unset -f mattermostToolsMenu; __lpAutoload "${install_scripts_dir}menu/tools/manage_mattermost.sh"; mattermostToolsMenu "$@"; }
maybeRegenPoll() { unset -f maybeRegenPoll; __lpAutoload "${install_scripts_dir}task/crontab_task_processor.sh"; maybeRegenPoll "$@"; }
menuContinue() { unset -f menuContinue; __lpAutoload "${install_scripts_dir}menu/message/continue.sh"; menuContinue "$@"; }