fix(servers): support IPv6 server addresses everywhere

Store server IPs in one form (no brackets, short lower-case IPv6) and
write each address in the form its context needs:

- URLs and host:port (Sentinel, database URLs, SFTP backup download,
  DNS servers, SMTP, webhook base URLs, links, health checks) get
  brackets through formatHostForUrl().
- Traefik Host() rules use the address without brackets, so IPv6
  domains no longer return 404; saved labels are fixed at deploy.
  Caddy keeps the brackets.
- SSH keeps the plain address.
- Hetzner gives an IPv6 /64 network; IPv6-only servers now get the
  server address (prefix::1) instead of the unusable prefix::64.
- sslip labels, registry image names with an IPv6 host, the API domain
  list, allowlist matching and duplicate checks handle IPv6.

A migration normalizes stored IPv6 server addresses, repairs Hetzner
servers saved as ::64 that are not reachable, and adds brackets to
saved Sentinel URLs.
This commit is contained in:
Andras Bacsai
2026-10-09 13:50:28 +02:00
parent 17957fbb4c
commit f39c884cc7
65 changed files with 1458 additions and 107 deletions
+2 -2
View File
@@ -330,7 +330,7 @@ class ServerSetting extends Model
return match (true) {
filled($fqdn) => $fqdn,
filled($publicIpv4) => 'http://'.$publicIpv4.':8000',
filled($publicIpv6) => 'http://'.$publicIpv6.':8000',
filled($publicIpv6) => 'http://'.formatHostForUrl($publicIpv6).':8000',
default => null,
};
}
@@ -392,7 +392,7 @@ class ServerSetting extends Model
}
$request = request();
$host = strtolower($request->getHost());
$host = normalizeIpAddress(strtolower($request->getHost()));
if (
$host === 'localhost' ||