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
+17 -4
View File
@@ -154,12 +154,22 @@ function vv_resolve_tailscale_ip(string $hostname): string {
$h = strtolower($hostname);
$ip = trim(shell_exec('tailscale ip -4 ' . escapeshellarg($h) . ' 2>/dev/null') ?: '');
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') ?: '';
$matches = [];
foreach (explode("\n", $out) as $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 {
@@ -174,12 +184,15 @@ function vv_detect_host(): string {
// 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
// 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) {
$candidates = [];
foreach ($m[1] as $i => $key) {
$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';
}