Adjust algo for showing maintence page

This commit is contained in:
Owen
2025-12-21 16:31:01 -05:00
parent 90d07f9794
commit 8ea6b0cd9e
2 changed files with 129 additions and 103 deletions

View File

@@ -19,6 +19,25 @@ import { sanitize, validatePathRewriteConfig } from "./utils";
const redirectHttpsMiddlewareName = "redirect-to-https"; const redirectHttpsMiddlewareName = "redirect-to-https";
const badgerMiddlewareName = "badger"; const badgerMiddlewareName = "badger";
// Define extended target type with site information
type TargetWithSite = Target & {
resourceId: number;
targetId: number;
ip: string | null;
method: string | null;
port: number | null;
internalPort: number | null;
enabled: boolean;
health: string | null;
site: {
siteId: number;
type: string;
subnet: string | null;
exitNodeId: number | null;
online: boolean;
};
};
export async function getTraefikConfig( export async function getTraefikConfig(
exitNodeId: number, exitNodeId: number,
siteTypes: string[], siteTypes: string[],
@@ -26,17 +45,6 @@ export async function getTraefikConfig(
generateLoginPageRouters = false, generateLoginPageRouters = false,
allowRawResources = true allowRawResources = true
): Promise<any> { ): Promise<any> {
// Define extended target type with site information
type TargetWithSite = Target & {
site: {
siteId: number;
type: string;
subnet: string | null;
exitNodeId: number | null;
online: boolean;
};
};
// Get resources with their targets and sites in a single optimized query // Get resources with their targets and sites in a single optimized query
// Start from sites on this exit node, then join to targets and resources // Start from sites on this exit node, then join to targets and resources
const resourcesWithTargetsAndSites = await db const resourcesWithTargetsAndSites = await db
@@ -104,10 +112,6 @@ export async function getTraefikConfig(
eq(sites.type, "local") eq(sites.type, "local")
) )
), ),
or(
ne(targetHealthCheck.hcHealth, "unhealthy"), // Exclude unhealthy targets
isNull(targetHealthCheck.hcHealth) // Include targets with no health check record
),
inArray(sites.type, siteTypes), inArray(sites.type, siteTypes),
allowRawResources allowRawResources
? isNotNull(resources.http) // ignore the http check if allow_raw_resources is true ? isNotNull(resources.http) // ignore the http check if allow_raw_resources is true
@@ -193,6 +197,7 @@ export async function getTraefikConfig(
port: row.port, port: row.port,
internalPort: row.internalPort, internalPort: row.internalPort,
enabled: row.targetEnabled, enabled: row.targetEnabled,
health: row.hcHealth,
site: { site: {
siteId: row.siteId, siteId: row.siteId,
type: row.siteType, type: row.siteType,
@@ -222,7 +227,7 @@ export async function getTraefikConfig(
// get the key and the resource // get the key and the resource
for (const [key, resource] of resourcesMap.entries()) { for (const [key, resource] of resourcesMap.entries()) {
const targets = resource.targets; const targets = resource.targets as TargetWithSite[];
const routerName = `${key}-${resource.name}-router`; const routerName = `${key}-${resource.name}-router`;
const serviceName = `${key}-${resource.name}-service`; const serviceName = `${key}-${resource.name}-service`;
@@ -471,16 +476,20 @@ export async function getTraefikConfig(
// TODO: HOW TO HANDLE ^^^^^^ BETTER // TODO: HOW TO HANDLE ^^^^^^ BETTER
const anySitesOnline = ( const anySitesOnline = (
targets as TargetWithSite[] targets
).some((target: TargetWithSite) => target.site.online); ).some((target) => target.site.online);
return ( return (
(targets as TargetWithSite[]) targets
.filter((target: TargetWithSite) => { .filter((target) => {
if (!target.enabled) { if (!target.enabled) {
return false; return false;
} }
if (target.health == "unhealthy") {
return false;
}
// If any sites are online, exclude offline sites // If any sites are online, exclude offline sites
if (anySitesOnline && !target.site.online) { if (anySitesOnline && !target.site.online) {
return false; return false;
@@ -508,7 +517,7 @@ export async function getTraefikConfig(
} }
return true; return true;
}) })
.map((target: TargetWithSite) => { .map((target) => {
if ( if (
target.site.type === "local" || target.site.type === "local" ||
target.site.type === "wireguard" target.site.type === "wireguard"
@@ -595,11 +604,11 @@ export async function getTraefikConfig(
servers: (() => { servers: (() => {
// Check if any sites are online // Check if any sites are online
const anySitesOnline = ( const anySitesOnline = (
targets as TargetWithSite[] targets
).some((target: TargetWithSite) => target.site.online); ).some((target) => target.site.online);
return (targets as TargetWithSite[]) return targets
.filter((target: TargetWithSite) => { .filter((target) => {
if (!target.enabled) { if (!target.enabled) {
return false; return false;
} }
@@ -626,7 +635,7 @@ export async function getTraefikConfig(
} }
return true; return true;
}) })
.map((target: TargetWithSite) => { .map((target) => {
if ( if (
target.site.type === "local" || target.site.type === "local" ||
target.site.type === "wireguard" target.site.type === "wireguard"

View File

@@ -47,6 +47,25 @@ const redirectHttpsMiddlewareName = "redirect-to-https";
const redirectToRootMiddlewareName = "redirect-to-root"; const redirectToRootMiddlewareName = "redirect-to-root";
const badgerMiddlewareName = "badger"; const badgerMiddlewareName = "badger";
// Define extended target type with site information
type TargetWithSite = Target & {
resourceId: number;
targetId: number;
ip: string | null;
method: string | null;
port: number | null;
internalPort: number | null;
enabled: boolean;
health: string | null;
site: {
siteId: number;
type: string;
subnet: string | null;
exitNodeId: number | null;
online: boolean;
};
};
export async function getTraefikConfig( export async function getTraefikConfig(
exitNodeId: number, exitNodeId: number,
siteTypes: string[], siteTypes: string[],
@@ -54,16 +73,6 @@ export async function getTraefikConfig(
generateLoginPageRouters = false, generateLoginPageRouters = false,
allowRawResources = true allowRawResources = true
): Promise<any> { ): Promise<any> {
// Define extended target type with site information
type TargetWithSite = Target & {
site: {
siteId: number;
type: string;
subnet: string | null;
exitNodeId: number | null;
online: boolean;
};
};
// Get resources with their targets and sites in a single optimized query // Get resources with their targets and sites in a single optimized query
// Start from sites on this exit node, then join to targets and resources // Start from sites on this exit node, then join to targets and resources
@@ -147,10 +156,6 @@ export async function getTraefikConfig(
sql`(${build != "saas" ? 1 : 0} = 1)` // Dont allow undefined local sites in cloud sql`(${build != "saas" ? 1 : 0} = 1)` // Dont allow undefined local sites in cloud
) )
), ),
or(
ne(targetHealthCheck.hcHealth, "unhealthy"), // Exclude unhealthy targets
isNull(targetHealthCheck.hcHealth) // Include targets with no health check record
),
inArray(sites.type, siteTypes), inArray(sites.type, siteTypes),
allowRawResources allowRawResources
? isNotNull(resources.http) // ignore the http check if allow_raw_resources is true ? isNotNull(resources.http) // ignore the http check if allow_raw_resources is true
@@ -233,7 +238,7 @@ export async function getTraefikConfig(
maintenanceModeType: row.maintenanceModeType, maintenanceModeType: row.maintenanceModeType,
maintenanceTitle: row.maintenanceTitle, maintenanceTitle: row.maintenanceTitle,
maintenanceMessage: row.maintenanceMessage, maintenanceMessage: row.maintenanceMessage,
maintenanceEstimatedTime: row.maintenanceEstimatedTime, maintenanceEstimatedTime: row.maintenanceEstimatedTime
}); });
} }
@@ -246,6 +251,7 @@ export async function getTraefikConfig(
port: row.port, port: row.port,
internalPort: row.internalPort, internalPort: row.internalPort,
enabled: row.targetEnabled, enabled: row.targetEnabled,
health: row.hcHealth,
site: { site: {
siteId: row.siteId, siteId: row.siteId,
type: row.siteType, type: row.siteType,
@@ -291,7 +297,7 @@ export async function getTraefikConfig(
// get the key and the resource // get the key and the resource
for (const [key, resource] of resourcesMap.entries()) { for (const [key, resource] of resourcesMap.entries()) {
const targets = resource.targets; const targets = resource.targets as TargetWithSite[];
const routerName = `${key}-${resource.name}-router`; const routerName = `${key}-${resource.name}-router`;
const serviceName = `${key}-${resource.name}-service`; const serviceName = `${key}-${resource.name}-service`;
@@ -408,12 +414,12 @@ export async function getTraefikConfig(
certResolver: resolverName, certResolver: resolverName,
...(preferWildcard ...(preferWildcard
? { ? {
domains: [ domains: [
{ {
main: wildCard main: wildCard
} }
] ]
} }
: {}) : {})
}; };
} else { } else {
@@ -429,21 +435,15 @@ export async function getTraefikConfig(
} }
} }
const availableServers = (targets as TargetWithSite[]).filter( const availableServers = targets.filter(
(target: TargetWithSite) => { (target) => {
if (!target.enabled) return false; if (!target.enabled) return false;
const anySitesOnline = (targets as TargetWithSite[]).some( if (!target.site.online) return false;
(t: TargetWithSite) => t.site.online
);
if (anySitesOnline && !target.site.online) return false;
if (target.site.type === "local" || target.site.type === "wireguard") { if (target.health == "unhealthy") return false;
return target.ip && target.port && target.method;
} else if (target.site.type === "newt") { return true;
return target.internalPort && target.method && target.site.subnet;
}
return false;
} }
); );
@@ -471,8 +471,10 @@ export async function getTraefikConfig(
const maintenanceRouterName = `${key}-maintenance-router`; const maintenanceRouterName = `${key}-maintenance-router`;
const rewriteMiddlewareName = `${key}-maintenance-rewrite`; const rewriteMiddlewareName = `${key}-maintenance-rewrite`;
const entrypointHttp = config.getRawConfig().traefik.http_entrypoint; const entrypointHttp =
const entrypointHttps = config.getRawConfig().traefik.https_entrypoint; config.getRawConfig().traefik.http_entrypoint;
const entrypointHttps =
config.getRawConfig().traefik.https_entrypoint;
const fullDomain = resource.fullDomain; const fullDomain = resource.fullDomain;
const domainParts = fullDomain.split("."); const domainParts = fullDomain.split(".");
@@ -481,11 +483,16 @@ export async function getTraefikConfig(
: fullDomain; : fullDomain;
const maintenancePort = config.getRawConfig().server.next_port; const maintenancePort = config.getRawConfig().server.next_port;
const maintenanceHost = config.getRawConfig().server.internal_hostname; const maintenanceHost =
config.getRawConfig().server.internal_hostname;
config_output.http.services[maintenanceServiceName] = { config_output.http.services[maintenanceServiceName] = {
loadBalancer: { loadBalancer: {
servers: [{ url: `http://${maintenanceHost}:${maintenancePort}` }], servers: [
{
url: `http://${maintenanceHost}:${maintenancePort}`
}
],
passHostHeader: true passHostHeader: true
} }
}; };
@@ -503,7 +510,9 @@ export async function getTraefikConfig(
}; };
config_output.http.routers[maintenanceRouterName] = { config_output.http.routers[maintenanceRouterName] = {
entryPoints: [resource.ssl ? entrypointHttps : entrypointHttp], entryPoints: [
resource.ssl ? entrypointHttps : entrypointHttp
],
service: maintenanceServiceName, service: maintenanceServiceName,
middlewares: [rewriteMiddlewareName], middlewares: [rewriteMiddlewareName],
rule: rule, rule: rule,
@@ -512,13 +521,16 @@ export async function getTraefikConfig(
}; };
// Router to allow Next.js assets to load without rewrite // Router to allow Next.js assets to load without rewrite
config_output.http.routers[`${maintenanceRouterName}-assets`] = { config_output.http.routers[`${maintenanceRouterName}-assets`] =
entryPoints: [resource.ssl ? entrypointHttps : entrypointHttp], {
service: maintenanceServiceName, entryPoints: [
rule: `Host(\`${fullDomain}\`) && (PathPrefix(\`/_next\`) || PathRegexp(\`^/__nextjs*\`))`, resource.ssl ? entrypointHttps : entrypointHttp
priority: 2001, ],
...(resource.ssl ? { tls } : {}) service: maintenanceServiceName,
}; rule: `Host(\`${fullDomain}\`) && (PathPrefix(\`/_next\`) || PathRegexp(\`^/__nextjs*\`))`,
priority: 2001,
...(resource.ssl ? { tls } : {})
};
// logger.info(`Maintenance mode active for ${fullDomain}`); // logger.info(`Maintenance mode active for ${fullDomain}`);
@@ -654,17 +666,21 @@ export async function getTraefikConfig(
// RECEIVE BANDWIDTH ENDPOINT. // RECEIVE BANDWIDTH ENDPOINT.
// TODO: HOW TO HANDLE ^^^^^^ BETTER // TODO: HOW TO HANDLE ^^^^^^ BETTER
const anySitesOnline = ( const anySitesOnline = targets.some(
targets as TargetWithSite[] (target) => target.site.online
).some((target: TargetWithSite) => target.site.online); );
return ( return (
(targets as TargetWithSite[]) targets
.filter((target: TargetWithSite) => { .filter((target) => {
if (!target.enabled) { if (!target.enabled) {
return false; return false;
} }
if (target.health == "unhealthy") {
return false;
}
// If any sites are online, exclude offline sites // If any sites are online, exclude offline sites
if (anySitesOnline && !target.site.online) { if (anySitesOnline && !target.site.online) {
return false; return false;
@@ -692,7 +708,7 @@ export async function getTraefikConfig(
} }
return true; return true;
}) })
.map((target: TargetWithSite) => { .map((target) => {
if ( if (
target.site.type === "local" || target.site.type === "local" ||
target.site.type === "wireguard" target.site.type === "wireguard"
@@ -719,14 +735,14 @@ export async function getTraefikConfig(
})(), })(),
...(resource.stickySession ...(resource.stickySession
? { ? {
sticky: { sticky: {
cookie: { cookie: {
name: "p_sticky", // TODO: make this configurable via config.yml like other cookies name: "p_sticky", // TODO: make this configurable via config.yml like other cookies
secure: resource.ssl, secure: resource.ssl,
httpOnly: true httpOnly: true
} }
} }
} }
: {}) : {})
} }
}; };
@@ -779,11 +795,11 @@ export async function getTraefikConfig(
servers: (() => { servers: (() => {
// Check if any sites are online // Check if any sites are online
const anySitesOnline = ( const anySitesOnline = (
targets as TargetWithSite[] targets
).some((target: TargetWithSite) => target.site.online); ).some((target) => target.site.online);
return (targets as TargetWithSite[]) return targets
.filter((target: TargetWithSite) => { .filter((target) => {
if (!target.enabled) { if (!target.enabled) {
return false; return false;
} }
@@ -810,7 +826,7 @@ export async function getTraefikConfig(
} }
return true; return true;
}) })
.map((target: TargetWithSite) => { .map((target) => {
if ( if (
target.site.type === "local" || target.site.type === "local" ||
target.site.type === "wireguard" target.site.type === "wireguard"
@@ -829,18 +845,18 @@ export async function getTraefikConfig(
})(), })(),
...(resource.proxyProtocol && protocol == "tcp" // proxy protocol only works for tcp ...(resource.proxyProtocol && protocol == "tcp" // proxy protocol only works for tcp
? { ? {
serversTransport: `${ppPrefix}${resource.proxyProtocolVersion || 1}@file` // TODO: does @file here cause issues? serversTransport: `${ppPrefix}${resource.proxyProtocolVersion || 1}@file` // TODO: does @file here cause issues?
} }
: {}), : {}),
...(resource.stickySession ...(resource.stickySession
? { ? {
sticky: { sticky: {
ipStrategy: { ipStrategy: {
depth: 0, depth: 0,
sourcePort: true sourcePort: true
} }
} }
} }
: {}) : {})
} }
}; };
@@ -888,9 +904,10 @@ export async function getTraefikConfig(
loadBalancer: { loadBalancer: {
servers: [ servers: [
{ {
url: `http://${config.getRawConfig().server url: `http://${
.internal_hostname config.getRawConfig().server
}:${config.getRawConfig().server.next_port}` .internal_hostname
}:${config.getRawConfig().server.next_port}`
} }
] ]
} }