Follow-up to 928e244, which stopped configs/ subdirectories being sourced without a .category marker. That closed the hole; this removes the thing that fell into it. storageIndexFile pointed at configs/storage/app_locations. The file's requirements are only "manager-owned" and "not on a removable disk" — configs/ satisfies both, which is why I put it there, and it was still wrong: that tree carries a third property the file violates. sourceScanFiles SOURCES what it finds under configs/, and sourcing means executing. The index is a TSV of "<slug><TAB><root>", which bash reads as a command and its argument. Harmless while no slug matched a real executable. The row for the app named `libreportal` armed it, because that IS the CLI on PATH: sourcing ran `libreportal /libreportal-containers`, which re-entered the scan, which sourced the file again — one process pair per level until the host OOMed and took the desktop session with it. It now lives at $system_dir/storage/app_locations, with a one-shot migration so an install that already has an index keeps knowing where its apps live rather than silently forgetting. libreportal-ownership reconciles the new directory, and scan_files.sh gained a note that configs/storage/ carries no .category on purpose. scripts/dev/lp-configs-guard-test covers both ends: the index never lands in configs/, a legacy one migrates, and a file of the exact detonating shape placed in an unmarked configs/ subdirectory is not executed while a marked category still loads. Also wires sourceStorageLocations into the config scan beside sourceBackupLocations — per-location configs sit at depth 3, below the generic scan, and need their own walker like the backup ones do. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
116 lines
5.5 KiB
Bash
Executable File
116 lines
5.5 KiB
Bash
Executable File
#!/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 `<slug><TAB><path>`, 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/<idx>/location.config) — source them via
|
|
# the dedicated loader so CFG_BACKUP_LOC_<idx>_* vars are populated.
|
|
if declare -f sourceBackupLocations >/dev/null 2>&1; then
|
|
sourceBackupLocations
|
|
fi
|
|
|
|
# Same shape for storage locations: configs/storage/locations/<id>/
|
|
# 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/<app>/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
|
|
}
|