#!/bin/bash # Pre-flight checker for app installs. # # Each app declares its prerequisites in its .config as # CFG__REQUIRES="" # where 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_ 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$" }