feat(config): regenerate config files from template (batch add + delete)
Replaces the slow, interactive per-variable scan with a deterministic reconcile: each live config is rebuilt from its (freshly-cloned) template — keeping the user's existing values, adding new template keys (CFG_REQUIREMENT_CONFIGS_AUTO_UPDATE), and dropping keys the template no longer defines (new CFG_REQUIREMENT_CONFIGS_AUTO_DELETE, default true). Structure/order/comments follow the template; non-interactive; atomic with a .bak; refuses to act on a missing/empty template so a broken clone can't wipe a config. Applies to both general and per-app configs. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
parent
d0b7b1a32f
commit
d3681163af
@ -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
|
||||
|
||||
@ -1,221 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
checkApplicationsConfigFilesMissingVariables()
|
||||
# Reconcile each application's config ($containers_dir/<app>/<app>.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."
|
||||
}
|
||||
|
||||
@ -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."
|
||||
}
|
||||
|
||||
@ -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 <file>.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;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user