A free, open, self-hosted app platform (GNU AGPLv3): one-click app deploys, Traefik reverse proxy with automatic SSL, rootless Docker support, gluetun VPN routing, and a web dashboard to manage it all. Free & open forever to self-host; optional paid hosted services fund it. See PROMISE.md. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: librelad <librelad@digitalangels.vip>
120 lines
3.9 KiB
Bash
120 lines
3.9 KiB
Bash
#!/bin/bash
|
|
|
|
# Pre-flight checker for app installs.
|
|
#
|
|
# Each app declares its prerequisites in its .config as
|
|
# CFG_<APP>_REQUIRES="<csv>"
|
|
# where <csv> is a comma-separated list of requirement keys (see the
|
|
# `case` arms below for the full set). Examples:
|
|
# CFG_AUTHELIA_REQUIRES="domain,traefik"
|
|
# CFG_BOOKSTACK_REQUIRES="domain,traefik"
|
|
#
|
|
# Call this as the first step inside an app's install lifecycle:
|
|
#
|
|
# if ! appInstallCheckRequirements "$app_name" "$CFG_AUTHELIA_REQUIRES"; then
|
|
# return 1
|
|
# fi
|
|
#
|
|
# Returns 0 if all requirements are met, 1 if any are missing. On
|
|
# failure, prints one line per missing requirement so the user can
|
|
# resolve them in order.
|
|
appInstallCheckRequirements()
|
|
{
|
|
local app_name="$1"
|
|
local reqs_csv="$2"
|
|
|
|
[[ -z "$reqs_csv" ]] && return 0
|
|
|
|
local missing=()
|
|
local req
|
|
local IFS=','
|
|
for req in $reqs_csv; do
|
|
# Trim whitespace.
|
|
req="${req#"${req%%[![:space:]]*}"}"
|
|
req="${req%"${req##*[![:space:]]}"}"
|
|
[[ -z "$req" ]] && continue
|
|
|
|
case "$req" in
|
|
"domain")
|
|
if ! _appReqHasDomain; then
|
|
missing+=("Set at least one CFG_DOMAIN_N (General → Network) before installing $app_name.")
|
|
fi
|
|
;;
|
|
"traefik")
|
|
if ! _appReqServiceInstalled "traefik"; then
|
|
missing+=("Install Traefik first — $app_name needs a reverse proxy to publish itself.")
|
|
fi
|
|
;;
|
|
"gluetun")
|
|
if ! _appReqServiceInstalled "gluetun"; then
|
|
missing+=("Install Gluetun first — $app_name expects a VPN gateway to route through.")
|
|
fi
|
|
;;
|
|
"authelia")
|
|
if ! _appReqServiceInstalled "authelia"; then
|
|
missing+=("Install Authelia first — $app_name's auth integration depends on it.")
|
|
fi
|
|
;;
|
|
"headscale")
|
|
if ! _appReqServiceInstalled "headscale"; then
|
|
missing+=("Install Headscale first.")
|
|
fi
|
|
;;
|
|
"prometheus")
|
|
if ! _appReqServiceInstalled "prometheus"; then
|
|
missing+=("Install Prometheus first — $app_name has nothing to query without it.")
|
|
fi
|
|
;;
|
|
"mail")
|
|
if [[ "$CFG_MAIL_ENABLED" != "true" ]]; then
|
|
missing+=("Configure global mail (CFG_MAIL_ENABLED=true under General → Mail) first.")
|
|
fi
|
|
;;
|
|
*)
|
|
isNotice "Unknown requirement '$req' declared by $app_name — ignoring."
|
|
;;
|
|
esac
|
|
done
|
|
unset IFS
|
|
|
|
if [[ ${#missing[@]} -gt 0 ]]; then
|
|
isError "Cannot install $app_name — prerequisites are not met:"
|
|
local m
|
|
for m in "${missing[@]}"; do
|
|
isError " • $m"
|
|
done
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# True if any CFG_DOMAIN_<n> is set to a non-empty value.
|
|
_appReqHasDomain()
|
|
{
|
|
local i var val
|
|
for i in 1 2 3 4 5 6 7 8 9; do
|
|
var="CFG_DOMAIN_$i"
|
|
val="${!var}"
|
|
[[ -n "$val" ]] && return 0
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Thin wrapper so we can use either dockerCheckAppInstalled or whatever
|
|
# convenience helper a future refactor introduces, without rewriting the
|
|
# call sites in here.
|
|
_appReqServiceInstalled()
|
|
{
|
|
local svc="$1"
|
|
if declare -f checkServiceInstalled >/dev/null 2>&1; then
|
|
checkServiceInstalled "$svc" && return 0 || return 1
|
|
fi
|
|
if declare -f dockerCheckAppInstalled >/dev/null 2>&1; then
|
|
local status
|
|
status=$(dockerCheckAppInstalled "$svc" "docker" 2>/dev/null)
|
|
[[ "$status" == "installed" ]] && return 0 || return 1
|
|
fi
|
|
# Fallback: ask docker directly.
|
|
sudo docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "^${svc}-service$"
|
|
}
|