Make multi-instance actually install, and stop apps stealing each other's network rows

Instance install (bugs found by running one end to end):

- The cloned compose kept the TYPE's tag namespace
  (#LIBREPORTAL|BOOKSTACK_APP_KEY_1_TAG|...) while the config had been
  re-namespaced to CFG_<SLUG>_*, so tagsProcessorAppConfigValues matched
  nothing, the placeholders survived and the pre-start guard refused to
  launch. Rewrite the tag names and *_DATA tokens too — narrowly, so an
  app whose compose sets a real env var named after itself is untouched.
- Tools/hooks kept uppercase CFG_<TYPE>_ reads, so an instance
  provisioned itself from the type's config and ignored its own values.
- Cloned hooks were never loaded: both loaders run at startup, before the
  instance dir exists, so _appCallHook's `declare -F` found nothing and
  every <slug>_install_* hook silently no-opped — for bookstack that is
  the readiness probe and the admin bootstrap. Source the instance's own
  scripts in-process, then regen arrays + manifest for later runs.
- bookstack's hook hardcoded the container name after `docker exec -e ...`
  flags, where the rewriter can't see it, so an instance's admin bootstrap
  ran against the BASE app's container — including a tinker DELETE of a
  user. Target "$app_name" instead, and teach the rewriter the
  container="<type>" assignment form used by auth adapters.

network_resources uniqueness:

UNIQUE(resource_type, resource_value) is right for 'ip' and 'port' but the
port-tag writer stores descriptive rows in the same table with INSERT OR
REPLACE, so every install DELETED the matching row from whichever app held
it. traefik_managed and url_accessible are booleans, so the whole table
could only ever hold one row of each. Observed live: installing a second
bookstack took all four traefik_managed/url_accessible rows from stoat and
bookstack, and removing that instance took the stolen rows with it.

Replace it with a partial unique index scoped to ip/port, and migrate
existing databases in place (SQLite can't drop a constraint, so the table
is rebuilt inside a transaction). The migration is invoked from
portUpdateComposeTags, not just databaseCreateTables — the latter only
runs from startPreInstall, which a working install never re-runs.

Verified: two bookstacks now hold port_tag_internal=80, traefik_managed
and url_accessible simultaneously; duplicate host ports and IPs are still
rejected; instance installs, serves HTTP 200, provisions its own admin in
its own database, and removes cleanly with no orphan rows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
librelad 2026-08-18 23:15:21 +01:00
parent f9ec4cc986
commit ff25b08ee8
5 changed files with 155 additions and 18 deletions

View File

@ -46,17 +46,23 @@ bookstack_install_post_start()
fi fi
isSuccessful "Bookstack is online (HTTP ${bookstack_http_code})." isSuccessful "Bookstack is online (HTTP ${bookstack_http_code})."
# Target the container by $app_name, never the literal "bookstack": under
# multi-instance this hook is cloned for each instance, and a hardcoded name
# pointed every instance's admin bootstrap at the BASE app's container —
# provisioning (and, in the branch below, DELETING) users in the wrong
# database. instanceCreate's rewriter can't catch it either, since the
# container name here doesn't directly follow `docker exec`.
local bookstack_create_output local bookstack_create_output
bookstack_create_output=$(runFileOp docker exec \ bookstack_create_output=$(runFileOp docker exec \
-e EZ_BS_NEW_EMAIL="$bookstack_target_email" \ -e EZ_BS_NEW_EMAIL="$bookstack_target_email" \
-e EZ_BS_NEW_PASS="$bookstack_target_pass" \ -e EZ_BS_NEW_PASS="$bookstack_target_pass" \
bookstack sh -c 'cd /app/www && s6-setuidgid abc php artisan bookstack:create-admin --no-ansi --email="$EZ_BS_NEW_EMAIL" --name=Admin --password="$EZ_BS_NEW_PASS" 2>&1') "$app_name" sh -c 'cd /app/www && s6-setuidgid abc php artisan bookstack:create-admin --no-ansi --email="$EZ_BS_NEW_EMAIL" --name=Admin --password="$EZ_BS_NEW_PASS" 2>&1')
local bookstack_create_rc=$? local bookstack_create_rc=$?
if [[ $bookstack_create_rc -eq 0 ]]; then if [[ $bookstack_create_rc -eq 0 ]]; then
isSuccessful "Bookstack admin account created (email: $bookstack_target_email)." isSuccessful "Bookstack admin account created (email: $bookstack_target_email)."
if [[ "$bookstack_target_email" != "admin@admin.com" ]]; then if [[ "$bookstack_target_email" != "admin@admin.com" ]]; then
runFileOp docker exec -i bookstack php /app/www/artisan tinker --no-ansi >/dev/null 2>&1 <<'PHP' runFileOp docker exec -i "$app_name" php /app/www/artisan tinker --no-ansi >/dev/null 2>&1 <<'PHP'
$c = class_exists('\BookStack\Users\Models\User') ? '\BookStack\Users\Models\User' : '\BookStack\Auth\User'; $c = class_exists('\BookStack\Users\Models\User') ? '\BookStack\Users\Models\User' : '\BookStack\Auth\User';
optional($c::where('email', 'admin@admin.com')->first())->delete(); optional($c::where('email', 'admin@admin.com')->first())->delete();
PHP PHP

View File

@ -104,8 +104,7 @@ databaseCreateTables()
status TEXT DEFAULT 'active', status TEXT DEFAULT 'active',
created_date DATE DEFAULT CURRENT_DATE, created_date DATE DEFAULT CURRENT_DATE,
created_time TIME DEFAULT CURRENT_TIME, created_time TIME DEFAULT CURRENT_TIME,
UNIQUE(app_name, resource_type, service_name), UNIQUE(app_name, resource_type, service_name)
UNIQUE(resource_type, resource_value)
);") );")
checkSuccess "Creating unified network_resources table" checkSuccess "Creating unified network_resources table"
@ -121,7 +120,81 @@ databaseCreateTables()
local result; result=$(sqlite3 "$docker_dir/$db_file" "CREATE INDEX IF NOT EXISTS idx_network_resources_parent_service ON network_resources(parent_service);") local result; result=$(sqlite3 "$docker_dir/$db_file" "CREATE INDEX IF NOT EXISTS idx_network_resources_parent_service ON network_resources(parent_service);")
checkSuccess "Creating network resources parent service index" checkSuccess "Creating network resources parent service index"
fi fi
# Runs for fresh AND existing databases (this function is re-run on every
# startup), so an install created before the constraint was scoped is
# repaired in place.
databaseMigrateNetworkResourcesUnique
else else
echo "SQLite3 is not installed. Skipping table creation." echo "SQLite3 is not installed. Skipping table creation."
fi fi
} }
# Scope the global-uniqueness constraint on network_resources to the resources
# that are actually globally unique.
#
# The table shipped with UNIQUE(resource_type, resource_value). That is right for
# 'ip' and 'port' — no two apps may hold the same host port or container IP — but
# the port-tag writer stores descriptive rows in the same table and writes them
# with INSERT OR REPLACE, so each install DELETED the matching row from whichever
# app held it:
# port_tag_internal the CONTAINER-side port: every web app uses 80
# traefik_managed 'true'/'false' — two possible values, so the whole table
# url_accessible could only ever hold ONE row of each, for one app
# Consequences: the firewall rebuild's traefik_managed LEFT JOIN reads NULL for
# every app but the last one installed, and in rooted mode a stolen
# port_tag_internal makes ufw-docker fall back to the EXTERNAL port — the exact
# "cannot find the published port" failure firewall_rebuild_from_db.sh warns
# about. Rows already lost are not reconstructable here; each app repopulates its
# own on the next `libreportal app install <app>`.
#
# SQLite cannot drop a table constraint, so this rebuilds the table when the
# legacy constraint is still present, then enforces the narrow rule as a partial
# unique index. Idempotent: once migrated, the guard skips it.
databaseMigrateNetworkResourcesUnique()
{
local table_sql
table_sql=$(sqlite3 "$docker_dir/$db_file" "SELECT sql FROM sqlite_master WHERE type='table' AND name='network_resources';" 2>/dev/null)
[[ -n "$table_sql" ]] || return 0
if [[ "$table_sql" == *"UNIQUE(resource_type, resource_value)"* ]]; then
# Single transaction: either the whole swap lands or the old table stays.
local result; result=$(sqlite3 "$docker_dir/$db_file" "
PRAGMA foreign_keys=off;
BEGIN TRANSACTION;
CREATE TABLE network_resources_migrated (
id INTEGER PRIMARY KEY AUTOINCREMENT,
app_name TEXT NOT NULL,
resource_type TEXT NOT NULL,
resource_value TEXT NOT NULL,
service_name TEXT DEFAULT 'main',
parent_service TEXT DEFAULT NULL,
status TEXT DEFAULT 'active',
created_date DATE DEFAULT CURRENT_DATE,
created_time TIME DEFAULT CURRENT_TIME,
UNIQUE(app_name, resource_type, service_name)
);
INSERT INTO network_resources_migrated
SELECT id, app_name, resource_type, resource_value, service_name,
parent_service, status, created_date, created_time
FROM network_resources;
DROP TABLE network_resources;
ALTER TABLE network_resources_migrated RENAME TO network_resources;
CREATE INDEX IF NOT EXISTS idx_network_resources_app ON network_resources(app_name);
CREATE INDEX IF NOT EXISTS idx_network_resources_type ON network_resources(resource_type);
CREATE INDEX IF NOT EXISTS idx_network_resources_value ON network_resources(resource_value);
CREATE INDEX IF NOT EXISTS idx_network_resources_status ON network_resources(status);
CREATE INDEX IF NOT EXISTS idx_network_resources_parent_service ON network_resources(parent_service);
COMMIT;
" 2>&1)
if [[ -n "$result" ]]; then
isNotice "network_resources migration reported: $result"
fi
checkSuccess "Scoping network_resources uniqueness to ip/port"
fi
# The narrow rule, as a partial index — still blocks two apps claiming the
# same host port or IP, and lets the descriptive rows coexist. Silent: it is
# a no-op on every startup after the first.
local result; result=$(sqlite3 "$docker_dir/$db_file" "CREATE UNIQUE INDEX IF NOT EXISTS idx_network_resources_global_value ON network_resources(resource_type, resource_value) WHERE resource_type IN ('ip','port');" 2>/dev/null)
}

View File

@ -94,6 +94,19 @@ _instanceRewriteCompose() {
sed -i -E "s/(container_name:[[:space:]]*)${type}\b/\1${slug}/g" "$f" sed -i -E "s/(container_name:[[:space:]]*)${type}\b/\1${slug}/g" "$f"
# 3. The files-backup label's container ref (libreportal.backup.files: "<type>:/..."). # 3. The files-backup label's container ref (libreportal.backup.files: "<type>:/...").
sed -i -E "s/(libreportal\.backup\.files:[[:space:]]*\")${type}\b/\1${slug}/g" "$f" sed -i -E "s/(libreportal\.backup\.files:[[:space:]]*\")${type}\b/\1${slug}/g" "$f"
# 4. The per-app tag namespace. tagsProcessorAppConfigValues derives tag names
# mechanically from the config keys (CFG_<APP>_APP_KEY_1 -> the tag
# <APP>_APP_KEY_1_TAG), so a clone still carrying the TYPE's tag names has
# nothing to match its own CFG_<SLUG>_* vars: the placeholders survive and
# the pre-start guard refuses to launch the instance.
# Deliberately narrow — the tag name right after the #LIBREPORTAL| marker,
# and the *_DATA placeholder tokens. A blanket <TYPE>_ rewrite would also
# hit an app whose compose sets a real container env var named after itself
# (- <TYPE>_SECRET=...), renaming the variable the image reads.
local type_u="${type^^}" slug_u="${slug^^}"
type_u="${type_u//-/_}"; slug_u="${slug_u//-/_}"
sed -i -E "s/(#LIBREPORTAL\|)${type_u}_/\1${slug_u}_/g" "$f"
sed -i -E "s/\b${type_u}_([A-Z0-9_]*)_DATA\b/${slug_u}_\1_DATA/g" "$f"
} }
# Clone + prefix-rename the per-app tools/scripts so an instance's helpers target # Clone + prefix-rename the per-app tools/scripts so an instance's helpers target
@ -117,6 +130,17 @@ _instanceRewriteTools() {
sed -i -E "s/\b${type}_/${slug}_/g" "$f" sed -i -E "s/\b${type}_/${slug}_/g" "$f"
sed -i -E "s/(docker[[:space:]]+(exec|logs|restart|stop|start|inspect)[[:space:]]+)${type}\b/\1${slug}/g" "$f" sed -i -E "s/(docker[[:space:]]+(exec|logs|restart|stop|start|inspect)[[:space:]]+)${type}\b/\1${slug}/g" "$f"
sed -i -E "s/\b${type}\.config\b/${slug}.config/g" "$f" sed -i -E "s/\b${type}\.config\b/${slug}.config/g" "$f"
# Config reads are uppercase and so escape the lowercase rename above:
# an instance hook left reading CFG_<TYPE>_ADMIN_EMAIL would provision
# itself from the type's config (its own value silently ignored).
sed -i -E "s/\bCFG_${type^^}_/CFG_${slug^^}_/g" "$f"
# container="<type>" / container_name="<type>" holds the docker target
# for exec-based helpers (auth adapters, tools). The bare literal has
# no trailing underscore, so the rename above misses it and the clone
# would operate on the BASE app's container. Kept to these two
# assignment forms — a blanket bare-<type> rewrite would hit image
# names and prose.
sed -i -E "s/(\b(container|container_name)=\")${type}(\")/\1${slug}\3/g" "$f"
done done
done done
} }
@ -202,7 +226,27 @@ instanceCreate() {
isSuccessful "Instance template ready: $slug (instance of $type)" isSuccessful "Instance template ready: $slug (instance of $type)"
# 5. Hand off to the standard installer — from here it's just another app. # 5. Make the instance's freshly-cloned installers/hooks callable in THIS
# process. Both loaders ran at startup, before this dir existed: the eager
# scan (sourceScanFiles "containers") never saw it, and the lazy manifest
# has no stub for it. Without this, _appCallHook's `declare -F` finds
# nothing and every <slug>_install_* hook silently no-ops — for bookstack
# that is the readiness probe and the admin-account bootstrap, so the
# instance installs "successfully" with no usable login.
local _inst_f
while IFS= read -r -d '' _inst_f; do
source "$_inst_f"
done < <(find "$inst_dir" -maxdepth 2 -type d -name resources -prune -o -type f -name '*.sh' -print0 2>/dev/null)
# Persist that for later runs (and the WebUI): regenerate the file arrays +
# function manifest now that a new app dir exists. Best-effort — a stale
# manifest only affects lazy mode, and the in-process sourcing above already
# covers this install.
if declare -F lpRegenArrays >/dev/null 2>&1; then
lpRegenArrays force >/dev/null 2>&1 || true
fi
# 6. Hand off to the standard installer — from here it's just another app.
if ! declare -F dockerInstallApp >/dev/null 2>&1; then if ! declare -F dockerInstallApp >/dev/null 2>&1; then
isError "dockerInstallApp unavailable; instance template created but not installed." isError "dockerInstallApp unavailable; instance template created but not installed."
return 1 return 1

View File

@ -5,7 +5,18 @@ portUpdateComposeTags()
{ {
local app_name="$1" local app_name="$1"
local full_file_path="$2" local full_file_path="$2"
# This function is the only INSERT OR REPLACE writer into network_resources,
# so it is where the legacy UNIQUE(resource_type, resource_value) did its
# damage — each run silently stole other apps' port_tag_internal /
# traefik_managed / url_accessible rows. Repair the constraint here rather
# than only in databaseCreateTables: that runs from startPreInstall, which a
# working install never re-runs, so existing installs would keep corrupting
# themselves until a full reinstall. One guarded SELECT; a no-op once done.
if declare -F databaseMigrateNetworkResourcesUnique >/dev/null 2>&1; then
databaseMigrateNetworkResourcesUnique
fi
local port_tags_found=() local port_tags_found=()
local port_numbers=() local port_numbers=()

View File

@ -58,12 +58,12 @@ declare -gA LP_FN_MAP=(
[appLinkdingResetPassword]="linkding/tools/linkding_reset_password.sh" [appLinkdingResetPassword]="linkding/tools/linkding_reset_password.sh"
[appLinkdingSetAdmin]="linkding/tools/linkding_set_admin.sh" [appLinkdingSetAdmin]="linkding/tools/linkding_set_admin.sh"
[appMatrixCreateAccount]="matrix/tools/matrix_create_account.sh" [appMatrixCreateAccount]="matrix/tools/matrix_create_account.sh"
[appMatrixDeactivateUser]="matrix/tools/matrix_deactivate_user.sh" [appMatrixDeleteUser]="matrix/tools/matrix_delete_user.sh"
[appMatrixListUsers]="matrix/tools/matrix_list_users.sh" [appMatrixListUsers]="matrix/tools/matrix_list_users.sh"
[appMatrixResetPassword]="matrix/tools/matrix_reset_password.sh" [appMatrixResetPassword]="matrix/tools/matrix_reset_password.sh"
[appMatrixSetAdmin]="matrix/tools/matrix_set_admin.sh" [appMatrixSetAdmin]="matrix/tools/matrix_set_admin.sh"
[appMattermostCreateAccount]="mattermost/tools/mattermost_create_account.sh" [appMattermostCreateAccount]="mattermost/tools/mattermost_create_account.sh"
[appMattermostDeactivateUser]="mattermost/tools/mattermost_deactivate_user.sh" [appMattermostDeleteUser]="mattermost/tools/mattermost_delete_user.sh"
[appMattermostListUsers]="mattermost/tools/mattermost_list_users.sh" [appMattermostListUsers]="mattermost/tools/mattermost_list_users.sh"
[appMattermostResetPassword]="mattermost/tools/mattermost_reset_password.sh" [appMattermostResetPassword]="mattermost/tools/mattermost_reset_password.sh"
[appMattermostSetAdmin]="mattermost/tools/mattermost_set_admin.sh" [appMattermostSetAdmin]="mattermost/tools/mattermost_set_admin.sh"
@ -86,7 +86,7 @@ declare -gA LP_FN_MAP=(
[_appReqServiceInstalled]="checks/requirements/check_app_install.sh" [_appReqServiceInstalled]="checks/requirements/check_app_install.sh"
[_appReqServiceMsg]="checks/requirements/check_app_install.sh" [_appReqServiceMsg]="checks/requirements/check_app_install.sh"
[appRocketchatCreateAccount]="rocketchat/tools/rocketchat_create_account.sh" [appRocketchatCreateAccount]="rocketchat/tools/rocketchat_create_account.sh"
[appRocketchatDeactivateUser]="rocketchat/tools/rocketchat_deactivate_user.sh" [appRocketchatDeleteUser]="rocketchat/tools/rocketchat_delete_user.sh"
[appRocketchatEnableUser]="rocketchat/tools/rocketchat_enable_user.sh" [appRocketchatEnableUser]="rocketchat/tools/rocketchat_enable_user.sh"
[appRocketchatListUsers]="rocketchat/tools/rocketchat_list_users.sh" [appRocketchatListUsers]="rocketchat/tools/rocketchat_list_users.sh"
[appRocketchatResetPassword]="rocketchat/tools/rocketchat_reset_password.sh" [appRocketchatResetPassword]="rocketchat/tools/rocketchat_reset_password.sh"
@ -103,7 +103,7 @@ declare -gA LP_FN_MAP=(
[appStalwartSetMode]="stalwart/tools/stalwart_set_mode.sh" [appStalwartSetMode]="stalwart/tools/stalwart_set_mode.sh"
[appStalwartShowDns]="stalwart/tools/stalwart_show_dns.sh" [appStalwartShowDns]="stalwart/tools/stalwart_show_dns.sh"
[appStatus]="app/app_status.sh" [appStatus]="app/app_status.sh"
[appStoatDisableUser]="stoat/tools/stoat_disable_user.sh" [appStoatDeleteUser]="stoat/tools/stoat_delete_user.sh"
[appStoatEnableUser]="stoat/tools/stoat_enable_user.sh" [appStoatEnableUser]="stoat/tools/stoat_enable_user.sh"
[appStoatListUsers]="stoat/tools/stoat_list_users.sh" [appStoatListUsers]="stoat/tools/stoat_list_users.sh"
[appTraefikExtraMiddlewares_onlyoffice]="onlyoffice/scripts/onlyoffice_traefik.sh" [appTraefikExtraMiddlewares_onlyoffice]="onlyoffice/scripts/onlyoffice_traefik.sh"
@ -405,6 +405,7 @@ declare -gA LP_FN_MAP=(
[databaseListAllApps]="database/app/db_list_all_apps.sh" [databaseListAllApps]="database/app/db_list_all_apps.sh"
[databaseListInstalledApp]="database/app/db_list_installed_app.sh" [databaseListInstalledApp]="database/app/db_list_installed_app.sh"
[databaseListInstalledApps]="database/app/db_list_installed_apps.sh" [databaseListInstalledApps]="database/app/db_list_installed_apps.sh"
[databaseMigrateNetworkResourcesUnique]="database/tables/db_create_tables.sh"
[databaseOptionInsert]="database/insert/db_insert_option.sh" [databaseOptionInsert]="database/insert/db_insert_option.sh"
[databasePortOpenInsert]="database/insert/db_insert_port_open.sh" [databasePortOpenInsert]="database/insert/db_insert_port_open.sh"
[databasePortUsedInsert]="database/insert/db_insert_port_used.sh" [databasePortUsedInsert]="database/insert/db_insert_port_used.sh"
@ -1181,12 +1182,12 @@ declare -gA LP_FN_ROOT=(
[appLinkdingResetPassword]="containers" [appLinkdingResetPassword]="containers"
[appLinkdingSetAdmin]="containers" [appLinkdingSetAdmin]="containers"
[appMatrixCreateAccount]="containers" [appMatrixCreateAccount]="containers"
[appMatrixDeactivateUser]="containers" [appMatrixDeleteUser]="containers"
[appMatrixListUsers]="containers" [appMatrixListUsers]="containers"
[appMatrixResetPassword]="containers" [appMatrixResetPassword]="containers"
[appMatrixSetAdmin]="containers" [appMatrixSetAdmin]="containers"
[appMattermostCreateAccount]="containers" [appMattermostCreateAccount]="containers"
[appMattermostDeactivateUser]="containers" [appMattermostDeleteUser]="containers"
[appMattermostListUsers]="containers" [appMattermostListUsers]="containers"
[appMattermostResetPassword]="containers" [appMattermostResetPassword]="containers"
[appMattermostSetAdmin]="containers" [appMattermostSetAdmin]="containers"
@ -1209,7 +1210,7 @@ declare -gA LP_FN_ROOT=(
[_appReqServiceInstalled]="scripts" [_appReqServiceInstalled]="scripts"
[_appReqServiceMsg]="scripts" [_appReqServiceMsg]="scripts"
[appRocketchatCreateAccount]="containers" [appRocketchatCreateAccount]="containers"
[appRocketchatDeactivateUser]="containers" [appRocketchatDeleteUser]="containers"
[appRocketchatEnableUser]="containers" [appRocketchatEnableUser]="containers"
[appRocketchatListUsers]="containers" [appRocketchatListUsers]="containers"
[appRocketchatResetPassword]="containers" [appRocketchatResetPassword]="containers"
@ -1226,7 +1227,7 @@ declare -gA LP_FN_ROOT=(
[appStalwartSetMode]="containers" [appStalwartSetMode]="containers"
[appStalwartShowDns]="containers" [appStalwartShowDns]="containers"
[appStatus]="scripts" [appStatus]="scripts"
[appStoatDisableUser]="containers" [appStoatDeleteUser]="containers"
[appStoatEnableUser]="containers" [appStoatEnableUser]="containers"
[appStoatListUsers]="containers" [appStoatListUsers]="containers"
[appTraefikExtraMiddlewares_onlyoffice]="containers" [appTraefikExtraMiddlewares_onlyoffice]="containers"
@ -1528,6 +1529,7 @@ declare -gA LP_FN_ROOT=(
[databaseListAllApps]="scripts" [databaseListAllApps]="scripts"
[databaseListInstalledApp]="scripts" [databaseListInstalledApp]="scripts"
[databaseListInstalledApps]="scripts" [databaseListInstalledApps]="scripts"
[databaseMigrateNetworkResourcesUnique]="scripts"
[databaseOptionInsert]="scripts" [databaseOptionInsert]="scripts"
[databasePortOpenInsert]="scripts" [databasePortOpenInsert]="scripts"
[databasePortUsedInsert]="scripts" [databasePortUsedInsert]="scripts"
@ -2339,12 +2341,12 @@ appLinkdingListUsers() { unset -f appLinkdingListUsers; __lpAutoload "${install_
appLinkdingResetPassword() { unset -f appLinkdingResetPassword; __lpAutoload "${install_containers_dir}linkding/tools/linkding_reset_password.sh"; appLinkdingResetPassword "$@"; } 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 "$@"; } 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 "$@"; } 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 "$@"; } appMatrixDeleteUser() { unset -f appMatrixDeleteUser; __lpAutoload "${install_containers_dir}matrix/tools/matrix_delete_user.sh"; appMatrixDeleteUser "$@"; }
appMatrixListUsers() { unset -f appMatrixListUsers; __lpAutoload "${install_containers_dir}matrix/tools/matrix_list_users.sh"; appMatrixListUsers "$@"; } 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 "$@"; } 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 "$@"; } appMatrixSetAdmin() { unset -f appMatrixSetAdmin; __lpAutoload "${install_containers_dir}matrix/tools/matrix_set_admin.sh"; appMatrixSetAdmin "$@"; }
appMattermostCreateAccount() { unset -f appMattermostCreateAccount; __lpAutoload "${install_containers_dir}mattermost/tools/mattermost_create_account.sh"; appMattermostCreateAccount "$@"; } appMattermostCreateAccount() { unset -f appMattermostCreateAccount; __lpAutoload "${install_containers_dir}mattermost/tools/mattermost_create_account.sh"; appMattermostCreateAccount "$@"; }
appMattermostDeactivateUser() { unset -f appMattermostDeactivateUser; __lpAutoload "${install_containers_dir}mattermost/tools/mattermost_deactivate_user.sh"; appMattermostDeactivateUser "$@"; } appMattermostDeleteUser() { unset -f appMattermostDeleteUser; __lpAutoload "${install_containers_dir}mattermost/tools/mattermost_delete_user.sh"; appMattermostDeleteUser "$@"; }
appMattermostListUsers() { unset -f appMattermostListUsers; __lpAutoload "${install_containers_dir}mattermost/tools/mattermost_list_users.sh"; appMattermostListUsers "$@"; } appMattermostListUsers() { unset -f appMattermostListUsers; __lpAutoload "${install_containers_dir}mattermost/tools/mattermost_list_users.sh"; appMattermostListUsers "$@"; }
appMattermostResetPassword() { unset -f appMattermostResetPassword; __lpAutoload "${install_containers_dir}mattermost/tools/mattermost_reset_password.sh"; appMattermostResetPassword "$@"; } appMattermostResetPassword() { unset -f appMattermostResetPassword; __lpAutoload "${install_containers_dir}mattermost/tools/mattermost_reset_password.sh"; appMattermostResetPassword "$@"; }
appMattermostSetAdmin() { unset -f appMattermostSetAdmin; __lpAutoload "${install_containers_dir}mattermost/tools/mattermost_set_admin.sh"; appMattermostSetAdmin "$@"; } appMattermostSetAdmin() { unset -f appMattermostSetAdmin; __lpAutoload "${install_containers_dir}mattermost/tools/mattermost_set_admin.sh"; appMattermostSetAdmin "$@"; }
@ -2367,7 +2369,7 @@ _appReqHasDomain() { unset -f _appReqHasDomain; __lpAutoload "${install_scripts_
_appReqServiceInstalled() { unset -f _appReqServiceInstalled; __lpAutoload "${install_scripts_dir}checks/requirements/check_app_install.sh"; _appReqServiceInstalled "$@"; } _appReqServiceInstalled() { unset -f _appReqServiceInstalled; __lpAutoload "${install_scripts_dir}checks/requirements/check_app_install.sh"; _appReqServiceInstalled "$@"; }
_appReqServiceMsg() { unset -f _appReqServiceMsg; __lpAutoload "${install_scripts_dir}checks/requirements/check_app_install.sh"; _appReqServiceMsg "$@"; } _appReqServiceMsg() { unset -f _appReqServiceMsg; __lpAutoload "${install_scripts_dir}checks/requirements/check_app_install.sh"; _appReqServiceMsg "$@"; }
appRocketchatCreateAccount() { unset -f appRocketchatCreateAccount; __lpAutoload "${install_containers_dir}rocketchat/tools/rocketchat_create_account.sh"; appRocketchatCreateAccount "$@"; } appRocketchatCreateAccount() { unset -f appRocketchatCreateAccount; __lpAutoload "${install_containers_dir}rocketchat/tools/rocketchat_create_account.sh"; appRocketchatCreateAccount "$@"; }
appRocketchatDeactivateUser() { unset -f appRocketchatDeactivateUser; __lpAutoload "${install_containers_dir}rocketchat/tools/rocketchat_deactivate_user.sh"; appRocketchatDeactivateUser "$@"; } appRocketchatDeleteUser() { unset -f appRocketchatDeleteUser; __lpAutoload "${install_containers_dir}rocketchat/tools/rocketchat_delete_user.sh"; appRocketchatDeleteUser "$@"; }
appRocketchatEnableUser() { unset -f appRocketchatEnableUser; __lpAutoload "${install_containers_dir}rocketchat/tools/rocketchat_enable_user.sh"; appRocketchatEnableUser "$@"; } appRocketchatEnableUser() { unset -f appRocketchatEnableUser; __lpAutoload "${install_containers_dir}rocketchat/tools/rocketchat_enable_user.sh"; appRocketchatEnableUser "$@"; }
appRocketchatListUsers() { unset -f appRocketchatListUsers; __lpAutoload "${install_containers_dir}rocketchat/tools/rocketchat_list_users.sh"; appRocketchatListUsers "$@"; } appRocketchatListUsers() { unset -f appRocketchatListUsers; __lpAutoload "${install_containers_dir}rocketchat/tools/rocketchat_list_users.sh"; appRocketchatListUsers "$@"; }
appRocketchatResetPassword() { unset -f appRocketchatResetPassword; __lpAutoload "${install_containers_dir}rocketchat/tools/rocketchat_reset_password.sh"; appRocketchatResetPassword "$@"; } appRocketchatResetPassword() { unset -f appRocketchatResetPassword; __lpAutoload "${install_containers_dir}rocketchat/tools/rocketchat_reset_password.sh"; appRocketchatResetPassword "$@"; }
@ -2384,7 +2386,7 @@ appSetupComposeTags_wireguard() { unset -f appSetupComposeTags_wireguard; __lpAu
appStalwartSetMode() { unset -f appStalwartSetMode; __lpAutoload "${install_containers_dir}stalwart/tools/stalwart_set_mode.sh"; appStalwartSetMode "$@"; } appStalwartSetMode() { unset -f appStalwartSetMode; __lpAutoload "${install_containers_dir}stalwart/tools/stalwart_set_mode.sh"; appStalwartSetMode "$@"; }
appStalwartShowDns() { unset -f appStalwartShowDns; __lpAutoload "${install_containers_dir}stalwart/tools/stalwart_show_dns.sh"; appStalwartShowDns "$@"; } appStalwartShowDns() { unset -f appStalwartShowDns; __lpAutoload "${install_containers_dir}stalwart/tools/stalwart_show_dns.sh"; appStalwartShowDns "$@"; }
appStatus() { unset -f appStatus; __lpAutoload "${install_scripts_dir}app/app_status.sh"; appStatus "$@"; } appStatus() { unset -f appStatus; __lpAutoload "${install_scripts_dir}app/app_status.sh"; appStatus "$@"; }
appStoatDisableUser() { unset -f appStoatDisableUser; __lpAutoload "${install_containers_dir}stoat/tools/stoat_disable_user.sh"; appStoatDisableUser "$@"; } appStoatDeleteUser() { unset -f appStoatDeleteUser; __lpAutoload "${install_containers_dir}stoat/tools/stoat_delete_user.sh"; appStoatDeleteUser "$@"; }
appStoatEnableUser() { unset -f appStoatEnableUser; __lpAutoload "${install_containers_dir}stoat/tools/stoat_enable_user.sh"; appStoatEnableUser "$@"; } appStoatEnableUser() { unset -f appStoatEnableUser; __lpAutoload "${install_containers_dir}stoat/tools/stoat_enable_user.sh"; appStoatEnableUser "$@"; }
appStoatListUsers() { unset -f appStoatListUsers; __lpAutoload "${install_containers_dir}stoat/tools/stoat_list_users.sh"; appStoatListUsers "$@"; } appStoatListUsers() { unset -f appStoatListUsers; __lpAutoload "${install_containers_dir}stoat/tools/stoat_list_users.sh"; appStoatListUsers "$@"; }
appTraefikExtraMiddlewares_onlyoffice() { unset -f appTraefikExtraMiddlewares_onlyoffice; __lpAutoload "${install_containers_dir}onlyoffice/scripts/onlyoffice_traefik.sh"; appTraefikExtraMiddlewares_onlyoffice "$@"; } appTraefikExtraMiddlewares_onlyoffice() { unset -f appTraefikExtraMiddlewares_onlyoffice; __lpAutoload "${install_containers_dir}onlyoffice/scripts/onlyoffice_traefik.sh"; appTraefikExtraMiddlewares_onlyoffice "$@"; }
@ -2686,6 +2688,7 @@ databaseInstallApp() { unset -f databaseInstallApp; __lpAutoload "${install_scri
databaseListAllApps() { unset -f databaseListAllApps; __lpAutoload "${install_scripts_dir}database/app/db_list_all_apps.sh"; databaseListAllApps "$@"; } databaseListAllApps() { unset -f databaseListAllApps; __lpAutoload "${install_scripts_dir}database/app/db_list_all_apps.sh"; databaseListAllApps "$@"; }
databaseListInstalledApp() { unset -f databaseListInstalledApp; __lpAutoload "${install_scripts_dir}database/app/db_list_installed_app.sh"; databaseListInstalledApp "$@"; } databaseListInstalledApp() { unset -f databaseListInstalledApp; __lpAutoload "${install_scripts_dir}database/app/db_list_installed_app.sh"; databaseListInstalledApp "$@"; }
databaseListInstalledApps() { unset -f databaseListInstalledApps; __lpAutoload "${install_scripts_dir}database/app/db_list_installed_apps.sh"; databaseListInstalledApps "$@"; } databaseListInstalledApps() { unset -f databaseListInstalledApps; __lpAutoload "${install_scripts_dir}database/app/db_list_installed_apps.sh"; databaseListInstalledApps "$@"; }
databaseMigrateNetworkResourcesUnique() { unset -f databaseMigrateNetworkResourcesUnique; __lpAutoload "${install_scripts_dir}database/tables/db_create_tables.sh"; databaseMigrateNetworkResourcesUnique "$@"; }
databaseOptionInsert() { unset -f databaseOptionInsert; __lpAutoload "${install_scripts_dir}database/insert/db_insert_option.sh"; databaseOptionInsert "$@"; } databaseOptionInsert() { unset -f databaseOptionInsert; __lpAutoload "${install_scripts_dir}database/insert/db_insert_option.sh"; databaseOptionInsert "$@"; }
databasePortOpenInsert() { unset -f databasePortOpenInsert; __lpAutoload "${install_scripts_dir}database/insert/db_insert_port_open.sh"; databasePortOpenInsert "$@"; } databasePortOpenInsert() { unset -f databasePortOpenInsert; __lpAutoload "${install_scripts_dir}database/insert/db_insert_port_open.sh"; databasePortOpenInsert "$@"; }
databasePortUsedInsert() { unset -f databasePortUsedInsert; __lpAutoload "${install_scripts_dir}database/insert/db_insert_port_used.sh"; databasePortUsedInsert "$@"; } databasePortUsedInsert() { unset -f databasePortUsedInsert; __lpAutoload "${install_scripts_dir}database/insert/db_insert_port_used.sh"; databasePortUsedInsert "$@"; }