Add unambiguous prefix-match fallback for NetBIOS-truncated hostnames

Unraid truncates the Server Name to 15 chars. Depending on which form
ends up in master.conf's HOST* value (the truncated OS hostname, or a
longer name matching what Tailscale independently registered for that
peer), either host-identity detection or Tailscale IP resolution could
fail — hit live on HOST2 in both directions this session.

- common.sh detect_hosts(): was case-sensitive exact match only, with no
  fallback and an exit 1 on failure — meaning every orchestrator/watchdog/
  rsync script would hard-fail on a truncated-hostname host, not just the
  web UI. Now case-insensitive, with a prefix-match fallback when the
  local hostname is exactly 15 chars.
- common.sh resolve_tailscale_ip(): already did a bare regex prefix match
  with zero ambiguity handling (pre-existing risk: e.g. server1/server10
  could collide). Replaced with an explicit unambiguous-only check.
- config.php vv_detect_host() / vv_resolve_tailscale_ip(): same treatment,
  kept as close a mirror of the bash logic as PHP allows.

All fallbacks require an EXACT prefix match (never fuzzy/percentage
similarity — considered and rejected, since names like server1/server2/
server3 would be dangerously similar under any generic similarity metric)
and require exactly one candidate to qualify; ambiguous matches are
treated as no match rather than guessed. Verified live against HOST1/
HOST2 in both master.conf configurations (short and long HOST2 value).
This commit is contained in:
Gmer4Lfe
2026-07-04 23:49:44 -04:00
parent abfbaa7f47
commit 084220692f
2 changed files with 49 additions and 8 deletions
+32 -4
View File
@@ -462,12 +462,29 @@ detect_hosts() {
for host_var in HOST1 HOST2 HOST3 HOST4 HOST5 HOST6 HOST7 HOST8; do
host_val="${!host_var:-}"
[[ -z "$host_val" ]] && continue
if [[ "$local_hostname" == "$host_val" ]]; then
if [[ "${local_hostname,,}" == "${host_val,,}" ]]; then
MY_ID="$host_var"
break
fi
done
# Unraid truncates the Server Name to 15 chars (NetBIOS limit). If the live hostname
# is at that exact limit, a configured HOST* value may be a longer, untruncated version
# (matching what Tailscale shows for this peer — resolve_tailscale_ip() keys off the same
# value). Only accept the match if it's unambiguous — exactly one HOST* may qualify.
if [[ -z "$MY_ID" && ${#local_hostname} -eq 15 ]]; then
local candidate="" candidate_count=0
for host_var in HOST1 HOST2 HOST3 HOST4 HOST5 HOST6 HOST7 HOST8; do
host_val="${!host_var:-}"
[[ -z "$host_val" ]] && continue
if [[ ${#host_val} -gt 15 && "${host_val,,}" == "${local_hostname,,}"* ]]; then
candidate="$host_var"
(( candidate_count++ ))
fi
done
[[ "$candidate_count" -eq 1 ]] && MY_ID="$candidate"
fi
if [[ -z "$MY_ID" ]]; then
error "Unknown host: $local_hostname"
error "Hostname must match a HOST* value in host*.conf"
@@ -780,9 +797,20 @@ resolve_tailscale_ip() {
local hostname="${1,,}"
local ip
ip=$(tailscale ip -4 "$hostname" 2>/dev/null)
[[ -z "$ip" ]] && \
ip=$(tailscale status 2>/dev/null | awk -v name="$hostname" '$2 ~ "^" name { print $1; exit }')
echo "$ip"
if [[ -n "$ip" ]]; then
echo "$ip"
return
fi
# Fallback: unambiguous prefix match against tailscale status (either direction) — handles
# Unraid's 15-char NetBIOS hostname truncation vs. a longer name recorded in master.conf.
# Only accept the match when exactly one peer could qualify; never guess between multiple
# candidates that happen to share a prefix (e.g. server1/server10).
local matches count
matches=$(tailscale status 2>/dev/null | awk -v name="$hostname" '
{ split(tolower($2), parts, "."); host = parts[1];
if (index(host, name) == 1 || index(name, host) == 1) print $1 }')
count=$(echo "$matches" | grep -c .)
[[ "$count" -eq 1 ]] && echo "$matches"
}
# Strips the "unraid-" prefix (case-insensitive) and title-cases what remains.