diff --git a/configs/features/features_terminal b/configs/features/features_terminal index c508195..fd23d3b 100755 --- a/configs/features/features_terminal +++ b/configs/features/features_terminal @@ -5,7 +5,8 @@ CFG_REQUIREMENT_SUGGEST_INSTALLS=false # Install Suggestions CFG_REQUIREMENT_SUGGEST_METRICS=true # Metrics Suggestions - Offer Prometheus and Grafana during first install (requires Install Suggestions enabled) CFG_REQUIREMENT_CONTINUE_PROMPT=false # Continue Prompts - Show continue prompts during installation for user confirmation CFG_REQUIREMENT_CONFIGS_CHECK=true # Config Validation - Validate configuration files on startup for errors and consistency -CFG_REQUIREMENT_CONFIGS_AUTO_UPDATE=true # Auto Config Updates - Automatically update configuration files when system changes are detected +CFG_REQUIREMENT_CONFIGS_AUTO_UPDATE=true # Auto Config Updates - Add new config options from the template (non-interactive) +CFG_REQUIREMENT_CONFIGS_AUTO_DELETE=true # Auto Config Deletes - Remove config options no longer present in the template CFG_REQUIREMENT_MISSING_IPS=false # IP Configuration Check - Check for and alert about missing IP configurations CFG_REQUIREMENT_DOCKER_NETWORK_PRUNE=true # Docker Network Cleanup - Enable automatic cleanup of unused Docker networks CFG_REQUIREMENT_DOCKER_SWITCHER=true # Docker Switcher - Install Docker version switching utility for managing multiple Docker versions diff --git a/scripts/config/application/application_missing_variables.sh b/scripts/config/application/application_missing_variables.sh index 4fe1c41..eb99fa1 100755 --- a/scripts/config/application/application_missing_variables.sh +++ b/scripts/config/application/application_missing_variables.sh @@ -1,221 +1,15 @@ #!/bin/bash -checkApplicationsConfigFilesMissingVariables() +# Reconcile each application's config ($containers_dir//.config) against +# its freshly-cloned template ($install_containers_dir). See reconcileConfigFile. +checkApplicationsConfigFilesMissingVariables() { - # Check if config checking is enabled - if [[ "$CFG_REQUIREMENT_CONFIGS_CHECK" == "true" ]]; then - isNotice "Scanning Application config files...please wait" - local container_configs=($(sudo find "$containers_dir" -maxdepth 2 -type f -name '*.config')) # Find .config files in immediate subdirectories of $containers_dir + local live app remote + while IFS= read -r live; do + app=$(basename "$live" .config) + remote="$install_containers_dir$app/$app.config" + reconcileConfigFile "$live" "$remote" + done < <(sudo find "$containers_dir" -maxdepth 2 -type f -name '*.config' ! -name '*.bak') - for container_config_file in "${container_configs[@]}"; do - local container_config_filename=$(basename "$container_config_file") - local config_app_name="${container_config_filename%.config}" - - # Extract config variables from the local file - local local_variables=($(sudo grep -o 'CFG_[A-Za-z0-9_]*=' "$container_config_file" | sudo sed 's/=$//')) - - # Find the corresponding .config file in $install_containers_dir - local remote_config_file="$install_containers_dir$config_app_name/$config_app_name.config" - - if [ -f "$remote_config_file" ]; then - - # Extract config variables from the remote file - local remote_variables=($(sudo grep -o 'CFG_[A-Za-z0-9_]*=' "$remote_config_file" | sudo sed 's/=$//')) - - # Filter out empty variable names from the remote variables - local remote_variables=("${remote_variables[@]//[[:space:]]/}") # Remove whitespace - local remote_variables=($(echo "${remote_variables[@]}" | tr ' ' '\n' | sudo grep -v '^$' | tr '\n' ' ')) - - # Compare local and remote variables - for remote_var in "${remote_variables[@]}"; do - if ! [[ " ${local_variables[@]} " =~ " $remote_var " ]]; then - local var_line=$(sudo grep "${remote_var}=" "$remote_config_file") - - # Check if auto-update is enabled - if [[ "$CFG_REQUIREMENT_CONFIGS_AUTO_UPDATE" == "true" ]]; then - # Auto-add the missing variable - if fileHasEmptyLine "$container_config_file"; then - echo "$var_line" | sudo tee -a "$container_config_file" > /dev/null 2>&1 - else - echo "" | sudo tee -a "$container_config_file" > /dev/null 2>&1 - echo "$var_line" | sudo tee -a "$container_config_file" > /dev/null 2>&1 - fi - checkSuccess "Auto-added missing variable: $var_line to '$container_config_filename'" - source "$container_config_file" - - # Auto-restart app if it's installed and the variable affects runtime - local app_dir=$install_containers_dir$config_app_name - if [ -d "$app_dir" ]; then - if [[ $remote_var == *"WHITELIST="* ]] || [[ $remote_var == *"PUBLIC="* ]] || [[ $remote_var == *"PORTS="* ]]; then - isNotice "Auto-restarting $config_app_name due to runtime configuration change..." - dockerComposeUpdateAndStartApp $config_app_name restart; - fi - fi - else - # Manual interaction mode - isHeader "Missing Application Config Variable Found" - isNotice "Variable '$remote_var' is missing in the local config file '$container_config_filename'." - echo "" - isOption "1. Add the '$var_line' to the '$container_config_filename'" - isOption "2. Add the '$remote_var' with my own value" - isOption "x. Skip" - echo "" - - isQuestion "Enter your choice (1 or 2) or 'x' to skip : " - read -rp "" choice - - case "$choice" in - 1) - echo "" - - if fileHasEmptyLine "$container_config_file"; then - echo "$var_line" | sudo tee -a "$container_config_file" > /dev/null 2>&1 - else - echo "" | sudo tee -a "$container_config_file" > /dev/null 2>&1 - echo "$var_line" | sudo tee -a "$container_config_file" > /dev/null 2>&1 - fi - - checkSuccess "Adding the $var_line to '$container_config_filename':" - source "$container_config_file" - - if [[ $var_line == *"WHITELIST="* ]]; then - local app_dir=$install_containers_dir$config_app_name - # Check if app is installed - if [ -d "$app_dir" ]; then - echo "" - isNotice "Whitelist has been added to the $config_app_name." - echo "" - while true; do - isQuestion "Would you like to update the ${config_app_name}'s whitelist settings? (y/n): " - read -rp "" whitelistaccept - echo "" - case $whitelistaccept in - [yY]) - isNotice "Updating ${config_app_name}'s whitelist settings..." - dockerComposeUpdateAndStartApp $config_app_name restart; - echo "" - break - ;; - [nN]) - break # Exit the loop without updating - ;; - *) - isNotice "Please provide a valid input (y or n)." - ;; - esac - done - fi - else - local app_dir=$install_containers_dir$config_app_name - # Check if app is installed - if [ -d "$app_dir" ]; then - echo "" - isNotice "A new config value has been added to $config_app_name." - echo "" - while true; do - isQuestion "Would you like to reinstall $config_app_name? (y/n): " - read -rp "" reinstallafterconfig - echo "" - case $reinstallafterconfig in - [yY]) - isNotice "Reinstalling $config_app_name now..." - dockerInstallApp $config_app_name; - break # Exit the loop - ;; - [nN]) - break # Exit the loop - ;; - *) - isNotice "Please provide a valid input (y or n)." - ;; - esac - done - fi - fi - ;; - 2) - echo "" - isQuestion "Enter your value for $remote_var: " - read -p " " custom_value - echo "" - - if fileHasEmptyLine "$container_config_file"; then - echo "${remote_var}=$custom_value" | sudo tee -a "$container_config_file" > /dev/null 2>&1 - else - echo "" | sudo tee -a "$container_config_file" > /dev/null 2>&1 - echo "${remote_var}=$custom_value" | sudo tee -a "$container_config_file" > /dev/null 2>&1 - fi - - checkSuccess "Adding the ${remote_var}=$custom_value to '$container_config_filename':" - source "$container_config_file" - - if [[ $remote_var == *"WHITELIST="* ]]; then - local app_dir=$install_containers_dir$config_app_name - # Check if app is installed - if [ -d "$app_dir" ]; then - echo "" - isNotice "Whitelist has been added to the $config_app_name." - echo "" - while true; do - isQuestion "Would you like to update the ${config_app_name}'s whitelist settings? (y/n): " - read -rp "" whitelistaccept - echo "" - case $whitelistaccept in - [yY]) - isNotice "Updating ${config_app_name}'s whitelist settings..." - dockerComposeUpdateAndStartApp $config_app_name restart; - break # Exit the loop - ;; - [nN]) - break # Exit the loop - ;; - *) - isNotice "Please provide a valid input (y or n)." - ;; - esac - done - fi - else - local app_dir=$install_containers_dir$config_app_name - # Check if app is installed - if [ -d "$app_dir" ]; then - echo "" - isNotice "A new config value has been added to $config_app_name." - echo "" - while true; do - isQuestion "Would you like to reinstall $config_app_name? (y/n): " - read -rp "" reinstallafterconfig - echo "" - case $reinstallafterconfig in - [yY]) - isNotice "Reinstalling $config_app_name now..." - dockerInstallApp $config_app_name; - break # Exit the loop - ;; - [nN]) - break # Exit the loop - ;; - *) - isNotice "Please provide a valid input (y or n)." - ;; - esac - done - fi - fi - ;; - [xX]) - # User chose to skip - ;; - *) - isNotice "Invalid choice. Skipping." - ;; - esac - fi - fi - done - fi - done - - isSuccessful "Application Config variable check completed." # Indicate completion - fi + isSuccessful "Application config reconciliation completed." } diff --git a/scripts/config/core/variables/config_missing_variables.sh b/scripts/config/core/variables/config_missing_variables.sh index 4d4e48d..30a106d 100755 --- a/scripts/config/core/variables/config_missing_variables.sh +++ b/scripts/config/core/variables/config_missing_variables.sh @@ -1,92 +1,18 @@ #!/bin/bash +# Reconcile the general LibrePortal config files ($configs_dir) against the +# freshly-cloned templates ($install_configs_dir). See reconcileConfigFile. checkLibrePortalConfigFilesMissingVariables() { - # Loop through local config files in $configs_dir (including subdirectories) - find "$configs_dir" -type f -name "*" ! -name ".category" | while read local_config_file; do - if [ -f "$local_config_file" ]; then - # Get relative path from configs_dir for matching with install files - local local_config_path=$(echo "$local_config_file" | sed "s|$configs_dir/||") - local local_config_filename=$(basename "$local_config_file") + local live fn rel remote + while IFS= read -r live; do + [[ -f "$live" ]] || continue + fn=$(basename "$live") + rel="${live#"$configs_dir"/}" + remote="$install_configs_dir$fn" + [[ -f "$remote" ]] || remote="$install_configs_dir$rel" + reconcileConfigFile "$live" "$remote" + done < <(find "$configs_dir" -type f ! -name ".category" ! -name "*.bak") - # Extract config variables from the local file - local local_variables=($(grep -o 'CFG_[A-Za-z0-9_]*=' "$local_config_file" | sed 's/=$//')) - - # Find the corresponding .config file in $install_configs_dir - # Try both direct filename and subdirectory path - local remote_config_file="$install_configs_dir$local_config_filename" - if [ ! -f "$remote_config_file" ]; then - remote_config_file="$install_configs_dir$local_config_path" - fi - - if [ -f "$remote_config_file" ]; then - # Extract config variables from the remote file - local remote_variables=($(grep -o 'CFG_[A-Za-z0-9_]*=' "$remote_config_file" | sed 's/=$//')) - - # Filter out empty variable names from the remote variables - local remote_variables=("${remote_variables[@]//[[:space:]]/}") # Remove whitespace - local remote_variables=($(echo "${remote_variables[@]}" | tr ' ' '\n' | grep -v '^$' | tr '\n' ' ')) - - # Compare local and remote variables - for remote_var in "${remote_variables[@]}"; do - if ! [[ " ${local_variables[@]} " =~ " $remote_var " ]]; then - var_line=$(grep "${remote_var}=" "$remote_config_file") - - isHeader "Missing LibrePortal Config Variable Found" - isNotice "Variable '$remote_var' is missing in the local config file '$local_config_filename'." - echo "" - isOption "1. Add the '$var_line' to the '$local_config_filename'" - isOption "2. Add the '$remote_var' with my own value" - isOption "x. Skip" - echo "" - - isQuestion "Enter your choice (1 or 2) or 'x' to skip : " - read -rp "" choice - - case "$choice" in - 1) - echo "" - # Check if the file ends with an empty line - if fileHasEmptyLine "$local_config_file"; then - echo "$var_line" | sudo tee -a "$local_config_file" > /dev/null 2>&1 - else - echo "" | sudo tee -a "$local_config_file" > /dev/null 2>&1 - echo "$var_line" | sudo tee -a "$local_config_file" > /dev/null 2>&1 - fi - - checkSuccess "Adding the $var_line to '$local_config_filename':" - source "$local_config_file" - ;; - 2) - echo "" - isQuestion "Enter your value for $remote_var: " - read -p " " custom_value - echo "" - - if fileHasEmptyLine "$local_config_file"; then - echo "${remote_var}=$custom_value" | sudo tee -a "$local_config_file" > /dev/null 2>&1 - else - echo "" | sudo tee -a "$local_config_file" > /dev/null 2>&1 - echo "${remote_var}=$custom_value" | sudo tee -a "$local_config_file" > /dev/null 2>&1 - fi - - checkSuccess "Adding the ${remote_var}=$custom_value to '$local_config_filename':" - source "$local_config_file" - ;; - [xX]) - # User chose to skip - ;; - *) - isNotice "Invalid choice. Skipping." - ;; - esac - fi - done - #else - #echo "Debug: Remote config file not found: $remote_config_file" - fi - fi - done - - isSuccessful "LibrePortal Config variable check completed." # Indicate completion + isSuccessful "LibrePortal config reconciliation completed." } diff --git a/scripts/config/core/variables/config_scan_variables.sh b/scripts/config/core/variables/config_scan_variables.sh index cd90d55..a6929a2 100755 --- a/scripts/config/core/variables/config_scan_variables.sh +++ b/scripts/config/core/variables/config_scan_variables.sh @@ -1,12 +1,65 @@ #!/bin/bash +# Reconcile a live config file against its template (the freshly-cloned repo): +# - keep the user's existing value for any key the template still defines +# - add new template keys (CFG_REQUIREMENT_CONFIGS_AUTO_UPDATE) +# - drop keys the template no longer defines (CFG_REQUIREMENT_CONFIGS_AUTO_DELETE) +# Structure, ordering and comments follow the template. Non-interactive, atomic, +# and keeps a .bak. Refuses to act on a missing/empty template so a broken +# clone can never wipe a live config. +reconcileConfigFile() +{ + local live="$1" template="$2" + local do_add="${CFG_REQUIREMENT_CONFIGS_AUTO_UPDATE:-true}" + local do_delete="${CFG_REQUIREMENT_CONFIGS_AUTO_DELETE:-true}" + + [[ -f "$live" ]] || return 0 + sudo test -s "$template" 2>/dev/null || return 0 + + declare -A live_line emitted + local line key + while IFS= read -r line; do + [[ "$line" =~ ^(CFG_[A-Za-z0-9_]+)= ]] && live_line["${BASH_REMATCH[1]}"]="$line" + done < <(sudo cat "$live") + + local tmp; tmp=$(mktemp) + while IFS= read -r line; do + if [[ "$line" =~ ^(CFG_[A-Za-z0-9_]+)= ]]; then + key="${BASH_REMATCH[1]}" + if [[ -n "${live_line[$key]+x}" ]]; then + printf '%s\n' "${live_line[$key]}" >> "$tmp" # keep the user's value + emitted["$key"]=1 + elif [[ "$do_add" == "true" ]]; then + printf '%s\n' "$line" >> "$tmp" # new key, template default + fi + else + printf '%s\n' "$line" >> "$tmp" # comments / blanks / ordering + fi + done < <(sudo cat "$template") + + if [[ "$do_delete" != "true" ]]; then # keep keys the template dropped + for key in "${!live_line[@]}"; do + [[ -n "${emitted[$key]+x}" ]] || printf '%s\n' "${live_line[$key]}" >> "$tmp" + done + fi + + # Replace only when the result is sane (non-empty, has keys) and differs. + if [[ -s "$tmp" ]] && grep -q '^CFG_' "$tmp" && ! sudo cmp -s "$tmp" "$live"; then + sudo cp -a "$live" "${live}.bak" + sudo cp "$tmp" "$live" + isSuccessful "Reconciled config: $(basename "$live") (backup: $(basename "$live").bak)" + fi + rm -f "$tmp" +} + checkConfigFilesMissingVariables() { local showheader="$1" - if [[ $showheader == "true" ]]; then - isHeader "Scanning Config Files" - fi + [[ "$CFG_REQUIREMENT_CONFIGS_CHECK" == "true" ]] || return 0 + [[ "$showheader" == "true" ]] && isHeader "Scanning Config Files" + isNotice "Reconciling config files against templates...please wait" + checkLibrePortalConfigFilesMissingVariables; checkApplicationsConfigFilesMissingVariables; }