Finishes the installApp refactor started in d941f59 (Wave A). Every app
whose <app>.sh was either pure boilerplate (Wave B) or boilerplate +
small custom logic (Wave C) now routes through the generic driver in
scripts/app/install/app_install.sh; bespoke logic moved to declarative
hooks in containers/<app>/scripts/<app>_install_hooks.sh.
Net: ~4,000 lines of duplicated 10-step sequence gone. From 31 per-app
.sh files (pre-Wave-A) down to 2 intentional keepers.
DELETED outright (pure boilerplate — driver replaces them identically):
jellyfin, mastodon, focalboard, ipinfo, speedtest, dashy, invidious,
nextcloud, ollama, vaultwarden, pihole
DELETED + hook-extracted (small bespoke step preserved in a hook):
bookstack, moneyapp, owncloud, trilium, searxng, gitea, headscale,
unbound, prometheus, grafana, gluetun, wireguard, jitsimeet, authelia,
traefik, adguard, onlyoffice
KEPT (intentional special cases):
crowdsec — host-app pattern (no docker compose, runs as apt+
systemd via installCrowdsecHost; uninstall/stop/
restart hooks already live in this file and are
invoked by dockerUninstall/Stop/RestartApp directly).
libreportal — WebUI bootstrap. Pre-compose image build + post-install
webuiLibrePortalUpdate + bootstrap-time suppression of
menuShowFinalMessages don't fit the generic flow.
Driver change — scripts/app/install/app_install.sh:
Moved monitoringToggleAppConfig "$app_name" "docker-compose.yml" from
the post-start integrations block into the install body at post-compose
(right after dockerComposeSetupFile, before docker-compose up). The
toggle edits the compose file on disk — running it after start meant
the container had already been brought up with the unmodified compose,
so the metrics endpoint wouldn't reflect CFG_<APP>_MONITORING until
the next restart. Matches the original ordering in every per-app .sh
that used to call it inline.
Hook surface (declare-f-gated, silent no-op when absent):
<slug>_install_pre before any install work
<slug>_install_post_setup after dockerConfigSetupToContainer
<slug>_install_post_compose after dockerComposeSetupFile (+ the
shared monitoring toggle on the compose)
<slug>_install_post_start after dockerComposeUpdateAndStartApp
<slug>_install_message_data echoes extra argv for menuShowFinalMessages
<slug>_install_post very last thing, after the final message
+ the existing _uninstall_pre/_post, _stop_post, _restart_post
Notable extractions:
bookstack — _install_post_start: probe :PORT_1/login until 200/302,
then `bookstack:create-admin` inside the container with
CFG_BOOKSTACK_ADMIN_{EMAIL,PASSWORD}; falls back to the
seeded admin@admin.com on timeout.
adguard — _install_post_start drives the wizard's HTTP API
(POST /control/install/configure) so the admin doesn't
click through five pages, then pins the admin bind back
to 0.0.0.0:3000 (matches the compose mapping) and health
checks. _install_message_data echoes user/password to
menuShowFinalMessages.
authelia — _install_pre requirements; _install_post_compose copies
configuration.yml + users_database.yml, substitutes
theme/domain/host, generates JWT/session/storage secrets,
toggles monitoring on configuration.yml; _install_post_start
argon2-hashes the admin password via the container, writes
users_database.yml, restarts; _install_post echoes creds.
traefik — _install_pre prompts for the LE email if CFG_TRAEFIK_EMAIL
is unset; _install_post_compose copies static + dynamic
configs, wires CFG_TRAEFIK_DASHBOARD_ACCESS (local-only /
domain-only / public), toggles monitoring on traefik.yml,
then traefikUpdateWhitelist + traefikSetupLoginCredentials.
wireguard — _install_pre host-conflict guard (/etc/wireguard/params);
_install_post_compose persists CFG_WIREGUARD_SUBNET,
resolves WG_HOST (domain+traefik → host_setup, else IP),
runs runAppCfg wireguard-ip-forward; _install_post_start
restarts after wg-easy installs its iptables rules.
jitsimeet — _install_post_setup downloads the tagged release zip from
GitHub; _install_post_compose mass-edits the .env and runs
gen-passwords.sh; _install_post_start rewrites nginx
default site to usedport1/2 + restart.
prometheus — _install_post_compose seeds prometheus.yml under
$containers_dir/prometheus/prometheus/; _install_post_start
sets 0777 on storage dirs so the container TSDB can write
regardless of host UID mapping.
grafana — _install_pre requirements; _install_post_start 0777 on
grafana_storage.
gluetun — _install_post_start refreshes the provider snapshot,
reattaches every routed app (the netns container ID is
stale after gluetun gets recreated), then prompts to
onboard any existing apps.
+ the smaller bookstack-shape extractions for owncloud (version scrape),
trilium / searxng (wait-for-first-boot-config), gitea (Prometheus
bearer token sync), headscale / unbound (config copy), moneyapp
(Auth.js AUTH_URL), onlyoffice (compose-resolved user/pass into the
final message).
Manifest + arrays regenerated. Verified end-to-end:
- bash -n on every hook file + the driver: clean
- Each hook file sources cleanly in a subshell, exposes only the
intended functions, flagged lazy-loadable (not eager)
- Smoke-stubbed install run for jellyfin (pure), nextcloud (pure),
bookstack (hooked), crowdsec (kept): correct dispatch in all cases —
deleted apps route to installApp, kept apps still hit their real
function
Signed-off-by: librelad <librelad@digitalangels.vip>
191 lines
7.7 KiB
Bash
191 lines
7.7 KiB
Bash
#!/bin/bash
|
|
|
|
# Generic per-app install/uninstall/start/stop/restart/edit driver.
|
|
#
|
|
# The 31 containers/<app>/<app>.sh files used to each define their own
|
|
# install<App>() with the SAME 10-step sequence. ~4,000 lines of duplicated
|
|
# boilerplate. This is the one place that sequence lives now; per-app
|
|
# customisation lands in declarative hooks in containers/<app>/tools/
|
|
# <app>_tools.sh (or wherever the app's tools.sh lives — auto-sourced).
|
|
#
|
|
# Dispatch is driven by the `$<slug>` global variable (set by dockerInstallApp
|
|
# in scripts/docker/app/functions/function_install_app.sh — `declare $app=i`).
|
|
# Same convention the per-app .sh files used; nothing changes for the caller.
|
|
# Actions are letters: c (config edit), u (uninstall), s (stop), r (restart),
|
|
# i (install), t (treated like c — legacy alias).
|
|
#
|
|
# Hook surface — all are `declare -f`-gated, silent no-op when absent:
|
|
#
|
|
# <slug>_install_pre before any install work
|
|
# <slug>_install_post_setup after dockerConfigSetupToContainer
|
|
# (install folder + .config exist; compose
|
|
# file not yet written)
|
|
# <slug>_install_post_compose after dockerComposeSetupFile
|
|
# (docker-compose.yml has been written +
|
|
# tag-substituted; container not yet up)
|
|
# <slug>_install_post_start after dockerComposeUpdateAndStartApp
|
|
# (container is up; the place for
|
|
# wait-for-ready + post-up API calls)
|
|
# <slug>_install_message_data echoes extra args for menuShowFinalMessages
|
|
# (typically credentials / URLs)
|
|
# <slug>_install_post very last thing, after the final message
|
|
#
|
|
# <slug>_uninstall_pre / _post around dockerUninstallApp
|
|
# <slug>_stop_post after dockerComposeDown
|
|
# <slug>_restart_post after dockerComposeRestart
|
|
#
|
|
# Hooks receive $app_name as $1 (and stay un-namespaced — they're already
|
|
# slug-prefixed). Return code is ignored unless they isError; the install
|
|
# continues regardless. Use that escape hatch for non-fatal app-specific
|
|
# refinements (rotate a key, patch a yaml after start, etc.).
|
|
|
|
_appCallHook()
|
|
{
|
|
local hook_name="$1"; shift
|
|
if declare -F "$hook_name" >/dev/null 2>&1; then
|
|
"$hook_name" "$@"
|
|
fi
|
|
}
|
|
|
|
# Standard "post-start integration" steps. Same for every app. Lives in a
|
|
# helper so the generic install body stays readable; safe for apps that
|
|
# don't tag for monitoring (the helpers no-op gracefully).
|
|
_appPostStartIntegrations()
|
|
{
|
|
local app_name="$1"
|
|
appUpdateSpecifics "$app_name"
|
|
setupHeadscale "$app_name"
|
|
databaseInstallApp "$app_name"
|
|
webuiContainerSetup "$app_name" install
|
|
|
|
# Scrape-target + dashboard re-gather. The compose-level toggle ran
|
|
# already (post-compose, so the running container reflects it).
|
|
# monitoringRefreshAll is self-correcting and no-ops when Prometheus
|
|
# / Grafana aren't installed.
|
|
if declare -F monitoringRefreshAll >/dev/null 2>&1; then
|
|
monitoringRefreshAll 2>/dev/null || true
|
|
fi
|
|
}
|
|
|
|
installApp()
|
|
{
|
|
local app_slug="$1"
|
|
local config_variables="$2"
|
|
|
|
# APP_NAME comes from the app's CFG_<APP>_APP_NAME (the user's chosen
|
|
# subdomain / install name). Fall back to the slug if unset.
|
|
local app_name_var="CFG_${app_slug^^}_APP_NAME"
|
|
local app_name="${!app_name_var:-$app_slug}"
|
|
|
|
# Dispatch flags live in the $<slug> global, e.g. linkding=i. Default to
|
|
# install if nothing set — installApp called directly without flag = install.
|
|
local actions="${!app_slug:-i}"
|
|
|
|
# Setup phase shared by every action (folder + variables).
|
|
if [[ "$actions" == *[cCtTuUsSrRiI]* ]]; then
|
|
dockerConfigSetupToContainer silent "$app_slug"
|
|
initializeAppVariables "$app_name"
|
|
fi
|
|
|
|
if [[ "$actions" == *[cCtT]* ]]; then
|
|
editAppConfig "$app_name"
|
|
fi
|
|
|
|
if [[ "$actions" == *[uU]* ]]; then
|
|
_appCallHook "${app_slug}_uninstall_pre" "$app_name"
|
|
dockerUninstallApp "$app_name"
|
|
_appCallHook "${app_slug}_uninstall_post" "$app_name"
|
|
fi
|
|
|
|
if [[ "$actions" == *[sS]* ]]; then
|
|
dockerComposeDown "$app_name"
|
|
_appCallHook "${app_slug}_stop_post" "$app_name"
|
|
fi
|
|
|
|
if [[ "$actions" == *[rR]* ]]; then
|
|
dockerComposeRestart "$app_name"
|
|
_appCallHook "${app_slug}_restart_post" "$app_name"
|
|
fi
|
|
|
|
if [[ "$actions" == *[iI]* ]]; then
|
|
isHeader "Install $app_name"
|
|
_appCallHook "${app_slug}_install_pre" "$app_name"
|
|
|
|
((menu_number++))
|
|
echo ""
|
|
echo "---- $menu_number. Setting up install folder and config for $app_name."
|
|
echo ""
|
|
dockerConfigSetupToContainer "loud" "$app_name" "install" "$config_variables"
|
|
isSuccessful "Install folders and Config files set up for $app_name."
|
|
_appCallHook "${app_slug}_install_post_setup" "$app_name"
|
|
|
|
((menu_number++))
|
|
echo ""
|
|
echo "---- $menu_number. Setting up the $app_name docker-compose.yml."
|
|
echo ""
|
|
dockerComposeSetupFile "$app_name"
|
|
|
|
# Compose-level monitoring toggle MUST run before docker-compose up
|
|
# — the compose file is the source of truth for the running
|
|
# container, so editing it post-start wouldn't take effect until
|
|
# the next restart. Idempotent + no-op for apps without a marker
|
|
# block; apps that toggle additional files (authelia config.yml,
|
|
# traefik traefik.yml, unbound unbound.conf …) call it again from
|
|
# their _install_post_compose hook.
|
|
if declare -F monitoringToggleAppConfig >/dev/null 2>&1; then
|
|
monitoringToggleAppConfig "$app_name" "docker-compose.yml" 2>/dev/null || true
|
|
fi
|
|
|
|
_appCallHook "${app_slug}_install_post_compose" "$app_name"
|
|
|
|
# Optional .env handling — apps that ship a .env in their template
|
|
# dir get it copied + tag-substituted. No-op for apps without one.
|
|
if [[ -f "${install_containers_dir}${app_slug}/.env" ]]; then
|
|
local result
|
|
result=$(copyResource "$app_name" ".env" "")
|
|
checkSuccess "Copying .env for $app_name"
|
|
configSetupFileWithData "$app_name" ".env"
|
|
fi
|
|
|
|
((menu_number++))
|
|
echo ""
|
|
echo "---- $menu_number. Updating file permissions before starting."
|
|
echo ""
|
|
fixPermissionsBeforeStart "$app_name"
|
|
|
|
((menu_number++))
|
|
echo ""
|
|
echo "---- $menu_number. Running docker-compose to install + start $app_name."
|
|
echo ""
|
|
dockerComposeUpdateAndStartApp "$app_name" install
|
|
_appCallHook "${app_slug}_install_post_start" "$app_name"
|
|
|
|
((menu_number++))
|
|
echo ""
|
|
echo "---- $menu_number. Running post-install integrations."
|
|
echo ""
|
|
_appPostStartIntegrations "$app_name"
|
|
|
|
((menu_number++))
|
|
echo ""
|
|
echo "---- $menu_number. You can find $app_name files at $containers_dir$app_name"
|
|
echo ""
|
|
|
|
# Final-message data — apps that want extra args (creds, URLs, etc.)
|
|
# printed in the menu output echo them from their hook. Word-split is
|
|
# intentional: each space-separated token becomes a positional arg.
|
|
local msg_data=""
|
|
if declare -F "${app_slug}_install_message_data" >/dev/null 2>&1; then
|
|
msg_data=$("${app_slug}_install_message_data" "$app_name")
|
|
fi
|
|
# shellcheck disable=SC2086 # intentional split — hook returns "u p" etc.
|
|
menuShowFinalMessages "$app_name" $msg_data
|
|
|
|
_appCallHook "${app_slug}_install_post" "$app_name"
|
|
menu_number=0
|
|
fi
|
|
|
|
# Reset the dispatch flag so a stale value doesn't trip a later call.
|
|
eval "$app_slug=n"
|
|
}
|