refactor(config): updater knobs -> configs/webui/webui_updater; fix config heal/reconcile gaps

Move the WebUI-updater settings out of general_terminal into their own
advanced webui-category file (webui_logs precedent): new
configs/webui/webui_updater holds CFG_UPDATER_SCAN_INTERVAL and the
migrated CFG_HOTFIX_AUTO, listed in webui/.category.

The move only reaches existing installs if the config convergence
machinery works, and three pieces of it silently didn't:

- checkConfigFilesMissingFiles walked a stale hardcoded category list
  ('general features network' — features doesn't exist; webui/backup/
  security never healed). Derive the categories from the template tree
  instead, and heal .category metadata too: copy it when absent and
  merge missing SUBCATEGORY_ORDER entries when present, so healed files
  actually appear in the WebUI Config editor. core_categories removed.
- Option reconciliation never touched ANY nested config file: configs_dir
  carries a trailing slash, so rel stripping missed ('configs//'), the
  template lookup failed, and reconcileConfigFile early-returned for
  every file. Strip the slash before matching.
- reconcileConfigFile's AUTO_DELETE=false branch read a never-populated
  live_line array, losing the dropped keys it promised to keep. Populate
  it alongside live_value.

Also exclude *.bak from config sourcing (reconciliation writes <file>.bak
next to live configs — now that it runs, sourcing backups would resurrect
deleted keys), and add 'libreportal config check' as a non-interactive
front door to the converge pass (was only reachable via install flows and
the interactive menu).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
librelad 2026-06-12 22:33:23 +01:00
parent 5b437c0c52
commit 7c28007779
10 changed files with 86 additions and 31 deletions

View File

@ -2,8 +2,6 @@
# Terminal - System utilities and advanced settings **ADVANCED** # Terminal - System utilities and advanced settings **ADVANCED**
# ================================================================================ # ================================================================================
CFG_UPDATER_CHECK=60 # Update Check Interval - Hours between system update checks CFG_UPDATER_CHECK=60 # Update Check Interval - Hours between system update checks
CFG_HOTFIX_AUTO=security-breakage # Hotfix Auto-Apply - Which signed hotfix severities apply automatically on the update check [security-breakage|all|off]
CFG_UPDATER_SCAN_INTERVAL=30 # App Scan Interval - Minutes between automatic app update/CVE/improvement scans (0 disables)
CFG_SWAPFILE_SIZE=2G # Swap File Size - Size of swap file for memory management CFG_SWAPFILE_SIZE=2G # Swap File Size - Size of swap file for memory management
CFG_GENERATED_PASS_LENGTH=14 # Password Length - Length for auto generated passwords CFG_GENERATED_PASS_LENGTH=14 # Password Length - Length for auto generated passwords
CFG_GENERATED_USER_LENGTH=8 # Username Length - Length for auto generated usernames CFG_GENERATED_USER_LENGTH=8 # Username Length - Length for auto generated usernames

View File

@ -2,4 +2,4 @@ TITLE=WebUI
DESCRIPTION=Web interface settings and preferences DESCRIPTION=Web interface settings and preferences
ICON=webui ICON=webui
ORDER=2 ORDER=2
SUBCATEGORY_ORDER=webui_logins,webui_logs SUBCATEGORY_ORDER=webui_logins,webui_logs,webui_updater

View File

@ -0,0 +1,5 @@
# ================================================================================
# WebUI Updater - Automatic app update, CVE & improvement scanning **ADVANCED**
# ================================================================================
CFG_UPDATER_SCAN_INTERVAL=30 # App Scan Interval - Minutes between automatic app update/CVE/improvement scans. 0 disables.
CFG_HOTFIX_AUTO=security-breakage # Hotfix Auto-Apply - Which signed hotfix severities apply automatically on the update check [security-breakage|all|off]

View File

@ -14,6 +14,16 @@ cliHandleConfigCommands()
configUpdateBatch "$encoded_pairs" configUpdateBatch "$encoded_pairs"
;; ;;
"check")
# Converge live configs on the install templates: copy any missing
# files / categories (and surface them to the WebUI Config editor
# via .category), then reconcile the options inside each file —
# add new keys, drop removed ones, refresh comments, keep the
# user's values. Read-and-converge; safe to run any time.
checkConfigFilesMissingFiles
checkConfigFilesMissingVariables true
;;
"") "")
cliShowConfigHelp cliShowConfigHelp
;; ;;

View File

@ -5,6 +5,9 @@ cliShowConfigHelp()
echo "" echo ""
echo "Available Config Commands:" echo "Available Config Commands:"
echo "" echo ""
echo " libreportal config check - Converge live configs on the install templates:"
echo " copy missing files, then reconcile options in each"
echo " (add new keys, drop removed ones, keep your values)"
echo " libreportal config update '<encoded>' - Apply a batch of CFG_KEY=VALUE pairs" echo " libreportal config update '<encoded>' - Apply a batch of CFG_KEY=VALUE pairs"
echo " <encoded> is pipe-separated; literal '|' in" echo " <encoded> is pipe-separated; literal '|' in"
echo " values must be URL-encoded as %7C." echo " values must be URL-encoded as %7C."

View File

@ -11,31 +11,66 @@ checkConfigFilesMissingFiles()
local missing_files_count=0 local missing_files_count=0
local found_files_count=0 local found_files_count=0
# Check for core config categories in the new structure # Walk every category the install templates define (configs/<category>/),
for category in "${core_categories[@]}"; do # derived from the template tree itself — so a new category, or one a
local install_category_dir="$install_configs_dir/$category" # hardcoded list would miss (webui/backup/security were), heals onto
local target_category_dir="$configs_dir/$category" # existing installs automatically.
local install_category_dir category target_category_dir
if [ -d "$install_category_dir" ]; then for install_category_dir in "$install_configs_dir"*/; do
# Create target category directory if it doesn't exist [ -d "$install_category_dir" ] || continue
if [ ! -d "$target_category_dir" ]; then category="$(basename "$install_category_dir")"
runInstallOp mkdir -p "$target_category_dir" target_category_dir="${configs_dir%/}/$category"
fi
# Create target category directory if it doesn't exist
# Check each file in the install category directory if [ ! -d "$target_category_dir" ]; then
for install_file in "$install_category_dir"/*; do runInstallOp mkdir -p "$target_category_dir"
if [ -f "$install_file" ] && [[ ! "$install_file" =~ \.category$ ]]; then fi
local filename=$(basename "$install_file")
local target_file="$target_category_dir/$filename" # Check each file in the install category directory
local install_file
if [ ! -f "$target_file" ]; then for install_file in "$install_category_dir"*; do
# Copy missing file from install directory if [ -f "$install_file" ] && [[ ! "$install_file" =~ \.category$ ]]; then
runInstallOp cp "$install_file" "$target_file" local filename=$(basename "$install_file")
((missing_files_count++)) local target_file="$target_category_dir/$filename"
fi
((found_files_count++)) if [ ! -f "$target_file" ]; then
# Copy missing file from install directory
runInstallOp cp "$install_file" "$target_file"
((missing_files_count++))
fi fi
done ((found_files_count++))
fi
done
# Heal the category's .category metadata too: copy it when absent (a
# brand-new category is invisible to the WebUI Config editor without
# it), and when present merge in any template SUBCATEGORY_ORDER entries
# it lacks — the editor only renders files listed there, so a healed
# config file would otherwise ship hidden. User-kept order and any
# extra local entries are preserved; new entries append.
local tmpl_cat="${install_category_dir}.category"
local live_cat="$target_category_dir/.category"
if [ -f "$tmpl_cat" ]; then
if [ ! -f "$live_cat" ]; then
runInstallOp cp "$tmpl_cat" "$live_cat"
else
local tmpl_order live_order merged entry _entries
tmpl_order="$(grep -m1 '^SUBCATEGORY_ORDER=' "$tmpl_cat" 2>/dev/null | cut -d= -f2-)"
live_order="$(runInstallOp grep -m1 '^SUBCATEGORY_ORDER=' "$live_cat" 2>/dev/null | cut -d= -f2-)"
merged="$live_order"
IFS=',' read -ra _entries <<< "$tmpl_order"
for entry in "${_entries[@]}"; do
[ -n "$entry" ] || continue
[[ ",$merged," == *",$entry,"* ]] || merged="${merged:+$merged,}$entry"
done
if [ -n "$merged" ] && [ "$merged" != "$live_order" ]; then
if runInstallOp grep -q '^SUBCATEGORY_ORDER=' "$live_cat" 2>/dev/null; then
runInstallOp sed -i "s/^SUBCATEGORY_ORDER=.*/SUBCATEGORY_ORDER=$merged/" "$live_cat"
else
printf 'SUBCATEGORY_ORDER=%s\n' "$merged" | runInstallOp tee -a "$live_cat" >/dev/null
fi
fi
fi
fi fi
done done

View File

@ -8,7 +8,11 @@ checkLibrePortalConfigFilesMissingVariables()
while IFS= read -r live; do while IFS= read -r live; do
[[ -f "$live" ]] || continue [[ -f "$live" ]] || continue
fn=$(basename "$live") fn=$(basename "$live")
rel="${live#"$configs_dir"/}" # configs_dir carries a trailing slash (paths.sh) — strip it before
# appending one, or the prefix never matches ("configs//"), rel stays
# the absolute path, the template lookup misses, and every nested
# config file silently skips reconciliation.
rel="${live#"${configs_dir%/}"/}"
remote="$install_configs_dir$fn" remote="$install_configs_dir$fn"
[[ -f "$remote" ]] || remote="$install_configs_dir$rel" [[ -f "$remote" ]] || remote="$install_configs_dir$rel"
reconcileConfigFile "$live" "$remote" reconcileConfigFile "$live" "$remote"

View File

@ -41,13 +41,14 @@ reconcileConfigFile()
[[ -f "$live" ]] || return 0 [[ -f "$live" ]] || return 0
runInstallOp test -s "$template" 2>/dev/null || return 0 runInstallOp test -s "$template" 2>/dev/null || return 0
declare -A live_value emitted declare -A live_value live_line emitted
local line key local line key
while IFS= read -r line; do while IFS= read -r line; do
if [[ "$line" =~ ^(CFG_[A-Za-z0-9_]+)= ]]; then if [[ "$line" =~ ^(CFG_[A-Za-z0-9_]+)= ]]; then
key="${BASH_REMATCH[1]}" key="${BASH_REMATCH[1]}"
_reconcileSplitValueComment "$line" _reconcileSplitValueComment "$line"
live_value["$key"]="$_reconcile_value" live_value["$key"]="$_reconcile_value"
live_line["$key"]="$line"
fi fi
done < <(runInstallOp cat "$live") done < <(runInstallOp cat "$live")

View File

@ -26,7 +26,7 @@ sourceScanFiles()
# echo "$load_type NEW FILE $file" # echo "$load_type NEW FILE $file"
fi fi
fi fi
done < <(find "$folder_dir" -maxdepth 2 -type f ! -name "*.category" ! -name "config_*" ! -name ".*" -print0) 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 # Per-location backup configs live nested at depth 3
# (configs/backup/locations/<idx>/location.config) — source them via # (configs/backup/locations/<idx>/location.config) — source them via

View File

@ -54,7 +54,6 @@ run_file=run.txt
# Configs # Configs
update_done=false update_done=false
config_file_wireguard=config_wireguard config_file_wireguard=config_wireguard
core_categories=("general" "features" "network")
# Menu # Menu
menu_number=0 menu_number=0