#!/bin/bash # ============================================================================================== # ================================= CONFIGURATION LOADER ======================================= # ============================================================================================== # # PURPOSE # ───────────────────────────────────────────────────────────────────────────── # Single entry point for all configuration sourcing across the ecosystem. # Every script sources this file instead of sourcing conf files directly. # # ============================================================================================== # OPERATIONAL MODEL # ============================================================================================== # # Sourcing order (order is load-bearing): # 1. Detects OS platform → PLATFORM=unraid|truenas|unknown; exports SCRIPTS_DIR # 2. Sources master.conf — shared config: hostnames, thresholds, toggles, profiles, job lists # 3. Auto-discovers and sources all host*.conf files present in Configurations/ # Each host conf extends the shared profile arrays and adds host-specific credentials # 4. Sources common.sh — shared functions: detect_hosts, logging, notifications, etc. # 5. Sources Plugin//adapter.sh — platform_*() functions for OS-specific ops # # USAGE IN SCRIPTS # Scripts in subdirectories (Rsync/, Docker_Essentials/ etc.): # SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # source "$SCRIPT_DIR/../load_config.sh" # # Scripts in the repo root: # SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # source "$SCRIPT_DIR/load_config.sh" # # SPARSE CHECKOUT NOTE # Sparse checkout controls which host*.conf files each server receives. # HOST1 only pulls Configurations/host1.conf — never HOST2's credentials. # HOST2 only pulls Configurations/host2.conf — never HOST1's credentials. # This loader sources whatever conf files ARE present — sparse checkout handles the rest. # # ============================================================================================== # OPERATIONAL SAFEGUARDS # ============================================================================================== # # No Root, No Lock, No detect_hosts on Load — Deliberate # Sourced by every script in the ecosystem, including read-only ones. A root check here # would gate all of them, a lock would be taken on every source, and auto-calling # detect_hosts() would exit the caller on an unknown hostname before it could handle that # itself. The executable scripts own those gates. Do not add them here. # # Fatal on Missing common.sh # Aborts loudly if common.sh is absent. Every downstream script assumes log(), error(), # acquire_lock() and detect_hosts() exist; continuing without them would produce # command-not-found errors scattered through unrelated scripts instead of one clear cause. # # Adapter Load Is Optional # A missing Plugin/$PLATFORM/adapter.sh is tolerated so the ecosystem can run before the # plugin directory exists — bootstrap and early install paths depend on that. # # Load Order Is Enforced, Not Incidental # confs before common.sh before adapter. common.sh needs HOST* to already be set, and the # adapter needs common.sh's output helpers. Reordering breaks both silently. # # Partner Confs Are Cache-Only # Partner host*.conf files load from the tmpfs RAM cache, never from disk. Sparse checkout # means this host has no partner conf in the repo, and reading a stale on-disk copy would # resurrect credentials the partner has since rotated. # # ============================================================================================== # CONFIGURATION # ============================================================================================== # # This file is the thing that loads configuration, so it consumes almost none itself. The one # input it must find on its own: # # /boot/config/plugins/varaverk/varaverk.cfg # SCRIPTS_DIR the authoritative install path — everything else derives from it # PLATFORM selects which Plugin//adapter.sh gets sourced # # It then defines the paths the rest of the ecosystem builds on: # # CONF_RAM_CACHE_DIR /tmp/.cache/vv/d — tmpfs partner conf cache, cleared each reboot # ARR_CACHE_DIR /tmp/arr_cache — tmpfs, restored from DATA_DIR on demand # # Everything else (STATE_DIR, DATA_DIR, thresholds, credentials) comes out of master.conf and # host*.conf, which this file sources rather than defines. # # ============================================================================================== # RUNTIME MODES # ============================================================================================== # # None — sourced, never executed. Every script begins with: # # SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # source "$SCRIPT_DIR/../load_config.sh" # # It takes no arguments and honours no flags. parse_args() arrives via common.sh and is # called by the script itself afterwards. # # ============================================================================================== # DESIGN PRINCIPLES # ============================================================================================== # # Zero-Script Expansion # Without this loader, adding a new server required updating every script to # source the new host conf. With this loader: create host3.conf in the repo, # commit and push — all servers auto-discover it on next git pull. No script # changes required. # # ============================================================================================== # ━━━ Locate config root ━━━ # load_config.sh always lives in the repo root. # Scripts call it from subdirectories using ../ — resolve to the actual root. LOAD_CONFIG_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # ━━━ Platform detection ━━━ # PLATFORM drives the adapter layer — Plugin//adapter.sh # Each platform adapter provides the same function API; scripts stay OS-agnostic. if [[ -f /etc/unraid-version ]]; then PLATFORM="unraid" elif [[ -f /etc/truenas ]]; then PLATFORM="truenas" else PLATFORM="unknown" fi export PLATFORM SCRIPTS_DIR="$LOAD_CONFIG_DIR" # ━━━ Source shared config ━━━ # master.conf must be sourced first — it declares the shared PROFILE_* arrays # that Host confs extend. Sourcing host confs before master.conf would fail. if [[ ! -f "$LOAD_CONFIG_DIR/Configurations/master.conf" ]]; then echo "[FATAL] master.conf not found at $LOAD_CONFIG_DIR/Configurations/master.conf" >&2 echo "[FATAL] Check TARGET_DIR and git pull status" >&2 exit 1 fi source "$LOAD_CONFIG_DIR/Configurations/master.conf" # ━━━ Auto-discover and source all host*.conf files ━━━ # Sorted for consistent load order — HOST1 before HOST2 before HOST3 etc. # Each host conf extends the shared PROFILE_* arrays and adds host-specific vars. # Missing files are silently skipped — sparse checkout intentionally withholds some. # At least one host conf must be present or the ecosystem has no identity to work with. _host_confs_loaded=0 declare -A _disk_conf_basenames=() # Use a sorted array glob — avoids word-splitting on paths with spaces while IFS= read -r _conf; do [[ -f "$_conf" ]] || continue source "$_conf" (( _host_confs_loaded++ )) _disk_conf_basenames["$(basename "$_conf")"]=1 [[ "${ENABLE_LOGGING:-false}" == "true" ]] && \ echo "[LOG] Loaded host config: $(basename "$_conf")" >&2 done < <(printf '%s\n' "$LOAD_CONFIG_DIR/Configurations"/host*.conf 2>/dev/null | sort) if [[ "$_host_confs_loaded" -eq 0 ]]; then echo "[FATAL] No host*.conf files found in $LOAD_CONFIG_DIR" >&2 echo "[FATAL] At least one host conf required — check git pull and sparse checkout" >&2 exit 1 fi # ━━━ Source partner confs from /tmp cache ━━━ # conf_sync.sh pulls partner host*.conf files into /tmp/.cache/vv/d/ # on array start and after any conf save. Sourcing them here makes partner vars # (HOST2_*, HOST3_*, …) available without committing credentials to the git repo # or violating sparse checkout — partner confs live in RAM only, cleared on reboot. # Confs already loaded from disk are skipped — disk copy is authoritative. _VV_CONF_CACHE="/tmp/.cache/vv/d" if [[ -d "$_VV_CONF_CACHE" ]]; then while IFS= read -r _conf; do [[ -f "$_conf" ]] || continue _conf_base="$(basename "$_conf")" # Skip if already sourced from disk [[ -n "${_disk_conf_basenames[$_conf_base]:-}" ]] && continue source "$_conf" [[ "${ENABLE_LOGGING:-false}" == "true" ]] && \ echo "[LOG] Loaded cached partner config: $_conf_base" >&2 done < <(printf '%s\n' "$_VV_CONF_CACHE"/host*.conf 2>/dev/null | sort) fi unset _VV_CONF_CACHE _conf_base _disk_conf_basenames # ━━━ Source shared functions ━━━ # common.sh sourced last — it defines detect_hosts(), which needs HOST* vars to be set. # It does NOT call it. Each script calls detect_hosts() itself, so MY_ID and REMOTE_ID # are unset until it does — anything building HOST*-prefixed variable names must come after. if [[ ! -f "$LOAD_CONFIG_DIR/common.sh" ]]; then echo "[FATAL] common.sh not found at $LOAD_CONFIG_DIR/common.sh" >&2 exit 1 fi source "$LOAD_CONFIG_DIR/common.sh" # ━━━ Source platform adapter ━━━ # Provides platform_*() functions used by common.sh and scripts. # Guard lets the ecosystem run before Plugin//adapter.sh exists. _adapter="$LOAD_CONFIG_DIR/Plugin/$PLATFORM/adapter.sh" [[ -f "$_adapter" ]] && source "$_adapter" # ━━━ Derived path constants ━━━ # Centralised here so every script that sources load_config.sh has them without # re-deriving from SCRIPTS_DIR or hardcoding /var/log or /tmp paths inline. CONF_DIR="${SCRIPTS_DIR}/Configurations" LOG_DIR="/var/log/varaverk" VV_CACHE_DIR="/tmp/vv_cache" CONF_RAM_CACHE_DIR="/tmp/.cache/vv/d" # tmpfs — cleared every reboot, repopulated by conf_sync.sh ARR_CACHE_DIR="/tmp/arr_cache" # tmpfs — cleared every reboot, restored from DATA_DIR backup by arr_cache_age_seconds() export CONF_DIR LOG_DIR VV_CACHE_DIR CONF_RAM_CACHE_DIR ARR_CACHE_DIR # ━━━ Cleanup ━━━ unset _conf _host_confs_loaded _adapter LOAD_CONFIG_DIR