Merge branch 'dev' into feat/redirect-tables

This commit is contained in:
miloschwartz
2026-10-15 11:47:55 -04:00
175 changed files with 8650 additions and 1759 deletions
+2 -2
View File
@@ -297,7 +297,7 @@ export class TraefikConfigManager {
const secondsUntilExpiry =
localState.expiresAt - nowInSeconds;
const daysUntilExpiry = secondsUntilExpiry / (60 * 60 * 24);
if (daysUntilExpiry < 45) {
if (daysUntilExpiry < 15) {
logger.info(
`Fetching certificates due to upcoming expiry for ${domain} (${Math.round(daysUntilExpiry)} days remaining)`
);
@@ -314,7 +314,7 @@ export class TraefikConfigManager {
const nowInSeconds = Math.floor(Date.now() / 1000);
const secondsUntilExpiry = state.expiresAt - nowInSeconds;
const daysUntilExpiry = secondsUntilExpiry / (60 * 60 * 24);
if (daysUntilExpiry < 45) {
if (daysUntilExpiry < 15) {
logger.info(
`Fetching certificates due to upcoming expiry for wildcard cert ${certDomain} (${Math.round(daysUntilExpiry)} days remaining)`
);
+6
View File
@@ -42,6 +42,7 @@ import { buildWildcardTls } from "./certResolver";
import { buildCustomHeadersMiddleware } from "./headersMiddleware";
import {
buildHttpLoadBalancerServers,
buildLoadBalancingStrategy,
buildStickySessionCookie,
buildStickySessionIp,
buildTcpUdpLoadBalancerServers
@@ -95,6 +96,7 @@ export async function getTraefikConfig(
domainId: resources.domainId,
enabled: resources.enabled,
stickySession: resources.stickySession,
loadBalancingStrategy: resources.loadBalancingStrategy,
tlsServerName: resources.tlsServerName,
setHostHeader: resources.setHostHeader,
enableProxy: resources.enableProxy,
@@ -225,6 +227,7 @@ export async function getTraefikConfig(
domainId: row.domainId,
enabled: row.enabled,
stickySession: row.stickySession,
loadBalancingStrategy: row.loadBalancingStrategy,
tlsServerName: row.tlsServerName,
setHostHeader: row.setHostHeader,
enableProxy: row.enableProxy,
@@ -542,6 +545,9 @@ export async function getTraefikConfig(
config_output.http.services![serviceName] = {
loadBalancer: {
servers: buildHttpLoadBalancerServers(targets),
...buildLoadBalancingStrategy(
resource.loadBalancingStrategy
),
...(resource.stickySession
? buildStickySessionCookie(resource.ssl)
: {})
+100
View File
@@ -0,0 +1,100 @@
import "@server/extendZod";
import { assertEqualsObj } from "@test/assert";
import {
buildHttpLoadBalancerServers,
buildLoadBalancingStrategy,
buildTcpUdpLoadBalancerServers
} from "./loadBalancer";
import type { TargetWithSite } from "./types";
function makeTarget(ip: string, siteType: string): TargetWithSite {
return {
targetId: 1,
resourceId: 1,
providerId: null,
siteId: 1,
ip,
port: 8080,
internalPort: 9000,
method: "http",
enabled: true,
health: "healthy",
path: null,
pathMatchType: null,
rewritePath: null,
rewritePathType: null,
priority: 100,
mode: "http",
authToken: null,
site: {
siteId: 1,
type: siteType,
subnet: null,
exitNodeId: null,
online: true
}
};
}
function runTests() {
const ipCases = [
{ ip: "2001:db8::1", host: "[2001:db8::1]" },
{ ip: "192.0.2.1", host: "192.0.2.1" }
];
const cases = [
...ipCases,
{ ip: "backend.example.com", host: "backend.example.com" }
];
for (const { ip, host } of cases) {
const target = makeTarget(ip, "local");
assertEqualsObj(
buildHttpLoadBalancerServers([target]),
[{ url: `http://${host}:8080` }],
`Local HTTP target ${ip} should format its endpoint`
);
assertEqualsObj(
buildTcpUdpLoadBalancerServers([target]),
[{ address: `${host}:8080` }],
`Local TCP/UDP target ${ip} should format its endpoint`
);
}
for (const { ip, host } of ipCases) {
const target = makeTarget("backend.example.com", "newt");
target.site.subnet = `${ip}/${ip.includes(":") ? 128 : 32}`;
assertEqualsObj(
buildHttpLoadBalancerServers([target]),
[{ url: `http://${host}:9000` }],
"Newt HTTP target should use the subnet endpoint and internal port"
);
assertEqualsObj(
buildTcpUdpLoadBalancerServers([target]),
[{ address: `${host}:9000` }],
"Newt TCP/UDP target should use the subnet endpoint and internal port"
);
}
assertEqualsObj(
buildLoadBalancingStrategy("wrr"),
{},
"Default wrr strategy should be omitted"
);
assertEqualsObj(
buildLoadBalancingStrategy(null),
{},
"Missing strategy should be omitted"
);
for (const strategy of ["p2c", "hrw", "leasttime"]) {
assertEqualsObj(
buildLoadBalancingStrategy(strategy),
{ strategy },
`${strategy} strategy should be emitted`
);
}
console.log("All load balancer endpoint tests passed!");
}
runTests();
+17 -4
View File
@@ -1,3 +1,4 @@
import { formatEndpoint } from "@server/lib/ip";
import { TargetWithSite } from "./types";
/**
@@ -48,12 +49,12 @@ export function buildHttpLoadBalancerServers(targets: TargetWithSite[]) {
target.site.type === "wireguard"
) {
return {
url: `${target.method}://${target.ip}:${target.port}`
url: `${target.method}://${formatEndpoint(target.ip, target.port)}`
};
} else if (target.site.type === "newt") {
const ip = target.site.subnet!.split("/")[0];
return {
url: `${target.method}://${ip}:${target.internalPort}`
url: `${target.method}://${formatEndpoint(ip, target.internalPort!)}`
};
}
})
@@ -62,6 +63,18 @@ export function buildHttpLoadBalancerServers(targets: TargetWithSite[]) {
);
}
/**
* Build the loadBalancer.strategy for an HTTP-mode resource. Traefik defaults
* to wrr when strategy is omitted, so only emit it for the non-default
* strategies to keep the generated config unchanged for existing resources.
*/
export function buildLoadBalancingStrategy(strategy: string | null) {
if (!strategy || strategy === "wrr") {
return {};
}
return { strategy };
}
export function buildStickySessionCookie(ssl: boolean | null) {
return {
sticky: {
@@ -111,12 +124,12 @@ export function buildTcpUdpLoadBalancerServers(targets: TargetWithSite[]) {
target.site.type === "wireguard"
) {
return {
address: `${target.ip}:${target.port}`
address: formatEndpoint(target.ip, target.port)
};
} else if (target.site.type === "newt") {
const ip = target.site.subnet!.split("/")[0];
return {
address: `${ip}:${target.internalPort}`
address: formatEndpoint(ip, target.internalPort!)
};
}
});