Files
coolify/tests/Unit/IpAddressFormattingTest.php
Andras Bacsai f39c884cc7 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.
2026-10-09 13:50:28 +02:00

43 lines
2.1 KiB
PHP

<?php
it('normalizes IP addresses to one stored form', function (string $input, string $expected) {
expect(normalizeIpAddress($input))->toBe($expected);
})->with([
'IPv4' => ['188.245.12.182', '188.245.12.182'],
'IPv4 with spaces' => [' 188.245.12.182 ', '188.245.12.182'],
'hostname' => ['server.example.com', 'server.example.com'],
'IPv6 short form' => ['2a01:4f8:c016:bd23::1', '2a01:4f8:c016:bd23::1'],
'IPv6 upper case with zeros' => ['2A01:04F8:C016:BD23:0000:0000:0000:0001', '2a01:4f8:c016:bd23::1'],
'IPv6 in brackets' => ['[2a01:4f8:c016:bd23::1]', '2a01:4f8:c016:bd23::1'],
'not an IP in brackets' => ['[not-an-ip]', '[not-an-ip]'],
]);
it('adds brackets only to IPv6 hosts for URLs', function (string $input, string $expected) {
expect(formatHostForUrl($input))->toBe($expected)
->and(formatHostForUrl(formatHostForUrl($input)))->toBe($expected);
})->with([
'IPv4' => ['188.245.12.182', '188.245.12.182'],
'hostname' => ['coolify.example.com', 'coolify.example.com'],
'IPv6' => ['2a01:4f8:c016:bd23::1', '[2a01:4f8:c016:bd23::1]'],
'IPv6 already in brackets' => ['[2a01:4f8:c016:bd23::1]', '[2a01:4f8:c016:bd23::1]'],
'IPv6 long form' => ['2a01:04f8:c016:bd23:0:0:0:1', '[2a01:4f8:c016:bd23::1]'],
]);
it('turns the Hetzner IPv6 network into the server address', function (?string $network, ?string $expected) {
expect(hetznerServerIpv6($network))->toBe($expected);
})->with([
'network' => ['2a01:4f8:c016:bd23::/64', '2a01:4f8:c016:bd23::1'],
'address without prefix length' => ['2a01:4f8:c016:bd23::1', '2a01:4f8:c016:bd23::1'],
'empty' => [null, null],
'not IPv6' => ['188.245.12.182', null],
]);
it('builds sslip host labels that are valid for every IPv6 address', function (string $ip, string $expected) {
expect(sslipHostLabel($ip))->toBe($expected);
})->with([
'IPv4' => ['188.245.12.182', '188.245.12.182'],
'IPv6' => ['2a01:4f8:c016:bd23::1', '2a01-4f8-c016-bd23--1'],
'IPv6 that ends with ::' => ['2a01:4f8:c016:bd23::', '2a01-4f8-c016-bd23--0'],
'IPv6 that starts with ::' => ['::1', '0--1'],
]);