From 45ee27a0d665202d7dfab6dc5b6d109f26c682b4 Mon Sep 17 00:00:00 2001 From: librelad Date: Thu, 20 Aug 2026 01:19:03 +0100 Subject: [PATCH] refactor(ports): nine columns is the floor; refuse the legacy layouts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The parser accepted five shapes. Three of them (9, 10, 11/12) differ only by trailing columns that have sane defaults, and those are worth keeping: 39 of the catalogue's descriptors stop at nine because they are non-Traefik ports — DNS, SMTP, WireGuard UDP — with no subdomain to state. A short row there is a complete row. The other two were different animals. The 8-column legacy layout has no login column and the 7-column one has no parent either, so they SHIFT every position rather than omitting a tail: whenever the length was misread, each field after the shift silently took its neighbour's value — a port's access type reading from its protocol, and so on. That is the same class of fault the word-splitting bug in this file just caused, and it is invisible when it happens. Nothing needs them. All 74 descriptors in the catalogue carry nine or more, as does every one on this install. So they are refused now, with a notice naming the offending key: a skipped port is visible, a mis-parsed one is not. Checked that skipping a row cannot misalign the parallel arrays — port_config_data and port_config_vars are appended before the branch, but neither is ever indexed alongside the others; the former is only tested for emptiness. Verified across every shape: 9, 10, 11 and 12 parse with the right defaults, a label containing spaces survives intact next to an empty trailing column, and both legacy layouts are refused rather than guessed. Co-Authored-By: Claude Opus 5 --- .../network/variables/variables_init_app.sh | 62 +++++++------------ 1 file changed, 24 insertions(+), 38 deletions(-) diff --git a/scripts/network/variables/variables_init_app.sh b/scripts/network/variables/variables_init_app.sh index 27be595..c5c1299 100755 --- a/scripts/network/variables/variables_init_app.sh +++ b/scripts/network/variables/variables_init_app.sh @@ -66,10 +66,16 @@ initializeAppVariables() port_config_data+=("$port_config_value") # 12-col: parent|name|ext:int|access|proto|login|traefik|webui|label|url_path|subdomain|recommended - # 10-col: parent|name|ext:int|access|proto|login|traefik|webui|label|url_path - # 9-col: parent|name|ext:int|access|proto|login|traefik|webui|label - # Legacy 8-col: parent|name|ext:int|access|proto|traefik|webui|label (login defaults to false) - # Legacy 7-col: name|ext:int|access|proto|traefik|webui|label (no parent, login defaults to false) + # 11-col: ... |subdomain (recommended defaults to the webui flag) + # 10-col: ... |url_path (subdomain empty -> app-name default) + # 9-col: parent|name|ext:int|access|proto|login|traefik|webui|label + # + # Nine is the floor. The trailing three are genuinely optional and + # have sane defaults, so a 9/10/11-column row is a complete row -- + # 39 of the catalogue's descriptors stop at nine because they are + # non-Traefik ports (DNS, SMTP, WireGuard UDP) with no subdomain to + # state. Anything shorter is not a shorter row, it is a DIFFERENT + # layout, and is refused below. # IFS-split, NOT ${v//|/ } with word-splitting. That idiom broke the # format in two ways at once: a label containing a space became # several fields ("Web Interface" -> label "Web", url_path @@ -105,40 +111,20 @@ initializeAppVariables() else port_recommendeds+=("${parts[7]}") fi - elif [[ ${#parts[@]} -ge 8 ]]; then - local external_port="${parts[2]%%:*}" - local internal_port="${parts[2]##*:}" - port_parent_services+=("${parts[0]}") - port_service_names+=("${parts[1]}") - port_data_tags+=("PORTS_TAG_$i") - port_external_ports+=("$external_port") - port_internal_ports+=("$internal_port") - port_access_types+=("${parts[3]}") - port_protocols+=("${parts[4]}") - port_traefik_managed+=("${parts[5]}") - port_url_accessibles+=("${parts[6]}") - port_login_requireds+=("false") - port_labels+=("${parts[7]}") - port_url_paths+=("") - port_subdomains+=("") - port_recommendeds+=("${parts[6]}") - elif [[ ${#parts[@]} -ge 7 ]]; then - local external_port="${parts[1]%%:*}" - local internal_port="${parts[1]##*:}" - port_parent_services+=("") - port_service_names+=("${parts[0]}") - port_data_tags+=("PORTS_TAG_$i") - port_external_ports+=("$external_port") - port_internal_ports+=("$internal_port") - port_access_types+=("${parts[2]}") - port_protocols+=("${parts[3]}") - port_traefik_managed+=("${parts[4]}") - port_url_accessibles+=("${parts[5]}") - port_login_requireds+=("false") - port_labels+=("${parts[6]}") - port_url_paths+=("") - port_subdomains+=("") - port_recommendeds+=("${parts[5]}") + else + # The 8- and 7-column legacy shapes used to be accepted here. + # They do not merely omit trailing fields -- they SHIFT every + # position (8-col has no login column, 7-col has no parent + # either), so whenever the length was misread every field after + # the shift silently took its neighbour's value. That is exactly + # the class of fault this parser was just fixed for, and nothing + # ships in those shapes any more: all 74 descriptors in the + # catalogue, and every one on a live install, carry nine or more. + # + # So refuse and say which row, rather than guess a layout and + # hand back a port whose access or protocol came from the wrong + # column. A skipped row is visible; a mis-parsed one is not. + isNotice "Port config $port_config_var has ${#parts[@]} column(s); at least 9 are required (parent|name|ext:int|access|proto|login|traefik|webui|label). Skipping this port." fi fi done