mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
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.
77 lines
3.1 KiB
PHP
77 lines
3.1 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Traefik matches the Host header of an IPv6 request against the address without brackets:
|
|
* Host(`[2a01:4f8::1]`) gives 404, Host(`2a01:4f8::1`) matches. Caddy needs the brackets.
|
|
*/
|
|
function traefikRuleLabels(array $domains, bool $forceHttps = false): array
|
|
{
|
|
return fqdnLabelsForTraefik(
|
|
uuid: 'testuuid',
|
|
domains: collect($domains),
|
|
is_force_https_enabled: $forceHttps,
|
|
onlyPort: 3000,
|
|
)->filter(fn (string $label) => str_contains($label, '.rule='))->values()->all();
|
|
}
|
|
|
|
it('writes IPv6 hosts without brackets in Traefik Host rules', function (string $domain, string $expectedRule) {
|
|
$rules = traefikRuleLabels([$domain]);
|
|
|
|
expect($rules)->not->toBeEmpty();
|
|
foreach ($rules as $rule) {
|
|
expect($rule)->toContain($expectedRule)->not->toContain('[');
|
|
}
|
|
})->with([
|
|
'http' => ['http://[2a01:4f8:c016:bd23::1]', 'Host(`2a01:4f8:c016:bd23::1`) && PathPrefix(`/`)'],
|
|
'https' => ['https://[2a01:4f8:c016:bd23::1]', 'Host(`2a01:4f8:c016:bd23::1`) && PathPrefix(`/`)'],
|
|
'with port and path' => ['http://[2a01:4f8::1]:8080/api', 'Host(`2a01:4f8::1`) && PathPrefix(`/api`)'],
|
|
]);
|
|
|
|
it('writes IPv6 hosts without brackets when https is forced', function () {
|
|
$rules = traefikRuleLabels(['https://[2a01:4f8::1]'], forceHttps: true);
|
|
|
|
expect($rules)->toHaveCount(2);
|
|
foreach ($rules as $rule) {
|
|
expect($rule)->toContain('Host(`2a01:4f8::1`)');
|
|
}
|
|
});
|
|
|
|
it('keeps IPv4 and hostname Traefik Host rules unchanged', function (string $domain, string $host) {
|
|
foreach (traefikRuleLabels([$domain]) as $rule) {
|
|
expect($rule)->toContain("Host(`{$host}`) && PathPrefix(`/`)");
|
|
}
|
|
})->with([
|
|
'hostname' => ['https://app.example.com', 'app.example.com'],
|
|
'IPv4' => ['http://192.0.2.10', '192.0.2.10'],
|
|
'sslip' => ['http://app.2a01-4f8--1.sslip.io', 'app.2a01-4f8--1.sslip.io'],
|
|
]);
|
|
|
|
it('keeps brackets for IPv6 hosts in Caddy labels', function () {
|
|
$labels = fqdnLabelsForCaddy(
|
|
network: 'testnetwork',
|
|
uuid: 'testuuid',
|
|
domains: collect(['http://[2a01:4f8::1]']),
|
|
onlyPort: 3000,
|
|
)->values()->all();
|
|
|
|
expect($labels)->toContain('caddy_0=http://[2a01:4f8::1]');
|
|
});
|
|
|
|
it('removes IPv6 brackets from Traefik Host rules in saved labels', function () {
|
|
$labels = collect([
|
|
'traefik.http.routers.http-0-app.rule=Host(`[2a01:4f8:c016:bd23::1]`) && PathPrefix(`/`)',
|
|
'traefik.http.routers.http-1-app.rule=Host(`app.example.com`) && PathPrefix(`/`)',
|
|
'traefik.http.routers.http-2-app.rule=Host(`188.245.12.182`)',
|
|
'traefik.http.routers.custom.rule=Host(`[not-an-ip]`)',
|
|
'caddy_0=http://[2a01:4f8:c016:bd23::1]',
|
|
]);
|
|
|
|
expect(traefikHostRulesWithoutIpv6Brackets($labels)->all())->toBe([
|
|
'traefik.http.routers.http-0-app.rule=Host(`2a01:4f8:c016:bd23::1`) && PathPrefix(`/`)',
|
|
'traefik.http.routers.http-1-app.rule=Host(`app.example.com`) && PathPrefix(`/`)',
|
|
'traefik.http.routers.http-2-app.rule=Host(`188.245.12.182`)',
|
|
'traefik.http.routers.custom.rule=Host(`[not-an-ip]`)',
|
|
'caddy_0=http://[2a01:4f8:c016:bd23::1]',
|
|
]);
|
|
});
|