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:
@@ -154,12 +154,22 @@ function vv_resolve_tailscale_ip(string $hostname): string {
|
|||||||
$h = strtolower($hostname);
|
$h = strtolower($hostname);
|
||||||
$ip = trim(shell_exec('tailscale ip -4 ' . escapeshellarg($h) . ' 2>/dev/null') ?: '');
|
$ip = trim(shell_exec('tailscale ip -4 ' . escapeshellarg($h) . ' 2>/dev/null') ?: '');
|
||||||
if ($ip) return $ip;
|
if ($ip) return $ip;
|
||||||
|
|
||||||
|
// 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).
|
||||||
$out = shell_exec('tailscale status 2>/dev/null') ?: '';
|
$out = shell_exec('tailscale status 2>/dev/null') ?: '';
|
||||||
|
$matches = [];
|
||||||
foreach (explode("\n", $out) as $line) {
|
foreach (explode("\n", $out) as $line) {
|
||||||
$cols = preg_split('/\s+/', trim($line));
|
$cols = preg_split('/\s+/', trim($line));
|
||||||
if (isset($cols[1]) && stripos($cols[1], $h . '.') === 0) return $cols[0];
|
if (!isset($cols[1])) continue;
|
||||||
|
$peerHost = strtolower(explode('.', $cols[1])[0]);
|
||||||
|
if (str_starts_with($peerHost, $h) || str_starts_with($h, $peerHost)) {
|
||||||
|
$matches[] = $cols[0];
|
||||||
}
|
}
|
||||||
return '';
|
}
|
||||||
|
return count($matches) === 1 ? $matches[0] : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function vv_detect_host(): string {
|
function vv_detect_host(): string {
|
||||||
@@ -174,12 +184,15 @@ function vv_detect_host(): string {
|
|||||||
// Unraid truncates the Server Name to 15 chars (NetBIOS limit). If the live hostname
|
// Unraid truncates the Server Name to 15 chars (NetBIOS limit). If the live hostname
|
||||||
// is at that exact limit, the name configured in master.conf (matching what Tailscale
|
// is at that exact limit, the name configured in master.conf (matching what Tailscale
|
||||||
// shows for this peer — resolve_tailscale_ip() keys off the same value) may be a longer,
|
// shows for this peer — resolve_tailscale_ip() keys off the same value) may be a longer,
|
||||||
// untruncated version. Treat a same-prefix match as this host rather than 'unknown'.
|
// untruncated version. Only accept the match when it's unambiguous — exactly one
|
||||||
|
// configured host may qualify; never guess between multiple prefix candidates.
|
||||||
if (strlen($hostname) === 15) {
|
if (strlen($hostname) === 15) {
|
||||||
|
$candidates = [];
|
||||||
foreach ($m[1] as $i => $key) {
|
foreach ($m[1] as $i => $key) {
|
||||||
$configured = trim($m[2][$i]);
|
$configured = trim($m[2][$i]);
|
||||||
if (strlen($configured) > 15 && stripos($configured, $hostname) === 0) return strtolower($key);
|
if (strlen($configured) > 15 && stripos($configured, $hostname) === 0) $candidates[] = $key;
|
||||||
}
|
}
|
||||||
|
if (count($candidates) === 1) return strtolower($candidates[0]);
|
||||||
}
|
}
|
||||||
return 'unknown';
|
return 'unknown';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -462,12 +462,29 @@ detect_hosts() {
|
|||||||
for host_var in HOST1 HOST2 HOST3 HOST4 HOST5 HOST6 HOST7 HOST8; do
|
for host_var in HOST1 HOST2 HOST3 HOST4 HOST5 HOST6 HOST7 HOST8; do
|
||||||
host_val="${!host_var:-}"
|
host_val="${!host_var:-}"
|
||||||
[[ -z "$host_val" ]] && continue
|
[[ -z "$host_val" ]] && continue
|
||||||
if [[ "$local_hostname" == "$host_val" ]]; then
|
if [[ "${local_hostname,,}" == "${host_val,,}" ]]; then
|
||||||
MY_ID="$host_var"
|
MY_ID="$host_var"
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
done
|
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
|
if [[ -z "$MY_ID" ]]; then
|
||||||
error "Unknown host: $local_hostname"
|
error "Unknown host: $local_hostname"
|
||||||
error "Hostname must match a HOST* value in host*.conf"
|
error "Hostname must match a HOST* value in host*.conf"
|
||||||
@@ -780,9 +797,20 @@ resolve_tailscale_ip() {
|
|||||||
local hostname="${1,,}"
|
local hostname="${1,,}"
|
||||||
local ip
|
local ip
|
||||||
ip=$(tailscale ip -4 "$hostname" 2>/dev/null)
|
ip=$(tailscale ip -4 "$hostname" 2>/dev/null)
|
||||||
[[ -z "$ip" ]] && \
|
if [[ -n "$ip" ]]; then
|
||||||
ip=$(tailscale status 2>/dev/null | awk -v name="$hostname" '$2 ~ "^" name { print $1; exit }')
|
|
||||||
echo "$ip"
|
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.
|
# Strips the "unraid-" prefix (case-insensitive) and title-cases what remains.
|
||||||
|
|||||||
Reference in New Issue
Block a user