LibrePortal/scripts/checks/requirements/check_app_install.sh
librelad 875a60f90f LibrePortal v0.1.0 — initial release
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>
2026-05-21 20:37:54 +01:00

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$"
}