#!/bin/bash sourceScanFiles() { local load_type="$1" local file_pattern # Specific LibrePortal config files if [ "$load_type" = "libreportal_configs" ]; then # Load new subdirectory config files only (no more old structure fallback) local folder_dir="$configs_dir" # Load new subdirectory config files with exclusions while IFS= read -r -d '' file; do if [ -f "$file" ]; then local filename=$(basename "$file") local should_load=true # Skip .category files and excluded files if [[ "$file" =~ \.category$ ]] || [[ "$filename" == "app_categories" ]]; then should_load=false fi # A file in a SUBDIRECTORY is only sourced when that directory is # a declared config category (carries .category) — the same # contract commandReloadConfigs enforces in the CLI wrapper. # # Sourcing means EXECUTING, so an unmarked directory used as # ordinary storage turned its contents into a script. That is not # hypothetical: storageIndexSet caches an app -> root TSV at # configs/storage/app_locations, no .category alongside it. Every # line there is ``, which bash reads as a command # and its argument. Harmless while no slug matched a real # executable — and a fork bomb the moment the row was for the app # named `libreportal`, because that IS the CLI: sourcing ran # `libreportal /libreportal-containers`, which re-entered this # scan, which sourced the file again, one process pair per level # until the host died of OOM. Every CLI invocation on the box, # including the task processor's own, detonated it. # # Depth-1 files (directly in configs/) keep loading as before — # only the category dirs gained a marker requirement. if [ "$should_load" = true ]; then local parent_dir="${file%/*}" if [[ "$parent_dir" != "${folder_dir%/}" && ! -f "$parent_dir/.category" ]]; then should_load=false fi fi if [ "$should_load" = true ]; then source "$file" # echo "$load_type NEW FILE $file" fi fi done < <(find "$folder_dir" -maxdepth 2 -type f ! -name "*.category" ! -name "config_*" ! -name ".*" ! -name "*.bak" -print0) # Per-location backup configs live nested at depth 3 # (configs/backup/locations//location.config) — source them via # the dedicated loader so CFG_BACKUP_LOC__* vars are populated. if declare -f sourceBackupLocations >/dev/null 2>&1; then sourceBackupLocations fi # Same shape for storage locations: configs/storage/locations// # location.config sits at depth 3, below this scan's reach, and is loaded # by its own dedicated walker. Note storage/ carries no .category on # purpose — nothing under it should EVER be sourced by the generic scan. if declare -f sourceStorageLocations >/dev/null 2>&1; then sourceStorageLocations fi # Specific for LibrePortal app container configs elif [ "$load_type" = "app_configs" ]; then local file_pattern="*.config" local folder_dir="$containers_dir" # Specific for LibrePortal app install scripts elif [ "$load_type" = "containers" ]; then local file_pattern="*.sh" local folder_dir="$install_containers_dir" else echo "Invalid load type: $load_type" fi # Scanning function for other types (not libreportal_configs). # app_configs live under /docker/containers (owned by the docker install user # and not list-readable by the manager), so enumerate them AS that user via # runFileOp; the manager still sources each (the .config files are o+r). The # 'containers' install templates are manager-owned, so a plain find suffices. # # Unreadable dirs are PRUNED, not descended into: an app's container-created # data dirs are owned by the container's (sub)uid and mode 0700 — e.g. # containers//postgres, uid 231141 under rootless — so even the docker # install user can't list them, and find would print a "Permission denied" # line per dir straight into the middle of every install's output. They never # hold a LibrePortal .config, so skipping them loses nothing; pruning (rather # than discarding stderr) keeps genuine find errors visible. if [ "$load_type" != "libreportal_configs" ]; then local scan_op="" [[ "$load_type" == "app_configs" ]] && scan_op="runFileOp" while IFS= read -r -d '' file; do if [ -f "$file" ]; then source "$file" # echo "$load_type FILE $file" fi done < <($scan_op find "$folder_dir" -maxdepth 3 \( -type d \( -name 'resources' -o ! -readable -o ! -executable \) -prune \) -o -type f -name "$file_pattern" -print0) fi # Load the categories from the file into an array if [ "$load_type" = "libreportal_configs" ]; then if [ -f "$configs_dir/app_categories" ]; then mapfile -t app_categories < $configs_dir/app_categories fi fi }