diff --git a/scripts/config/core/variables/config_scan_variables.sh b/scripts/config/core/variables/config_scan_variables.sh index a45ac65..944dfe3 100755 --- a/scripts/config/core/variables/config_scan_variables.sh +++ b/scripts/config/core/variables/config_scan_variables.sh @@ -34,6 +34,53 @@ _reconcileSplitValueComment() fi } +# Restore trailing columns a port descriptor has LOST, without touching any +# value the live config already states. +# +# CFG__PORT_ is a positional, pipe-separated row whose last three +# columns (url_path, subdomain, recommended) are optional. The port editor used +# to serialise only ten of them, so saving any port on an app silently dropped +# that app's Traefik subdomain and the router quietly fell back to the app-name +# default. Navidrome lost "music" and Speedtest lost "speedtest" exactly that +# way, and nothing said so — the app kept working on the wrong hostname. +# +# The writer is fixed, but installs already carrying the damage would keep it +# forever: reconcile preserves the user's value, and a truncated row IS the +# user's value as far as it can tell. So top it up from the template, appending +# only the columns the live row does not reach. Everything the live row states +# wins, including a deliberately blanked one, so a user who cleared a subdomain +# does not get it handed back. +# +# Echoes the value to use. +_reconcilePortColumns() +{ + local key="$1" live_v="$2" tmpl_v="$3" + [[ "$key" =~ _PORT_[0-9]+$ ]] || { printf '%s' "$live_v"; return 0; } + + # Values are stored quoted; compare and rebuild on the bare text. + local lq="" l="$live_v" t="$tmpl_v" + if [[ "$l" == \"*\" ]]; then lq='"'; l="${l%\"}"; l="${l#\"}"; fi + if [[ "$t" == \"*\" ]]; then t="${t%\"}"; t="${t#\"}"; fi + [[ -n "$l" && -n "$t" ]] || { printf '%s' "$live_v"; return 0; } + + local -a L T + IFS='|' read -ra L <<< "$l" + IFS='|' read -ra T <<< "$t" + (( ${#L[@]} < ${#T[@]} )) || { printf '%s' "$live_v"; return 0; } + + # Count BEFORE extending, or the message reports the post-merge width. + local was=${#L[@]} + local k + for (( k=${#L[@]}; k<${#T[@]}; k++ )); do L+=("${T[k]}"); done + local rebuilt; rebuilt="$(IFS='|'; printf '%s' "${L[*]}")" + # To STDERR. This function returns its result on stdout through a command + # substitution, so anything isNotice prints there lands INSIDE the config + # value — which is exactly what happened the first time, writing the notice + # text into navidrome's descriptor. + isNotice "Restored port descriptor $key from $was to ${#L[@]} columns (a truncated row had dropped its trailing column(s))." >&2 + printf '%s%s%s' "$lq" "$rebuilt" "$lq" +} + reconcileConfigFile() { local live="$1" template="$2" @@ -66,10 +113,14 @@ reconcileConfigFile() if [[ -n "${live_value[$key]+x}" ]]; then # Keep the user's value, take the template's comment (so # title/description/options/markers stay in sync with the repo). + # One exception, and it only ever ADDS: a port descriptor that + # has lost its trailing columns is topped up from the template. + local kept="${live_value[$key]}" + kept="$(_reconcilePortColumns "$key" "$kept" "$tmpl_value")" if [[ -n "$tmpl_comment" ]]; then - merged="${key}=${live_value[$key]} ${tmpl_comment}" + merged="${key}=${kept} ${tmpl_comment}" else - merged="${key}=${live_value[$key]}" + merged="${key}=${kept}" fi printf '%s\n' "$merged" >> "$tmp" emitted["$key"]=1