From 795302a351049603936b4730ccf1997371d8309e Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Sun, 2 Nov 2025 21:16:50 +0530 Subject: [PATCH] refactor files and add func to private traefik config generator file --- server/index.ts | 2 +- server/lib/traefik/getTraefikConfig.ts | 103 ------------- .../maintenance-server.ts | 2 +- server/maintenance/maintenanceUI.ts | 102 +++++++++++++ .../private/lib/traefik/getTraefikConfig.ts | 142 ++++++++++++++---- 5 files changed, 220 insertions(+), 131 deletions(-) rename server/{lib/traefik => maintenance}/maintenance-server.ts (98%) create mode 100644 server/maintenance/maintenanceUI.ts diff --git a/server/index.ts b/server/index.ts index 0e6e9dae..7389242a 100644 --- a/server/index.ts +++ b/server/index.ts @@ -23,7 +23,7 @@ import { initCleanup } from "#dynamic/cleanup"; import license from "#dynamic/license/license"; import { initLogCleanupInterval } from "@server/lib/cleanupLogs"; import { fetchServerIp } from "@server/lib/serverIpService"; -import { startMaintenanceServer } from "./lib/traefik/maintenance-server.js"; +import { startMaintenanceServer } from "./maintenance/maintenance-server.js"; async function startServers() { await setHostMeta(); diff --git a/server/lib/traefik/getTraefikConfig.ts b/server/lib/traefik/getTraefikConfig.ts index 0137f66a..3169158f 100644 --- a/server/lib/traefik/getTraefikConfig.ts +++ b/server/lib/traefik/getTraefikConfig.ts @@ -19,109 +19,6 @@ import { sanitize, validatePathRewriteConfig } from "./utils"; const redirectHttpsMiddlewareName = "redirect-to-https"; const badgerMiddlewareName = "badger"; - -function escapeHtml(text: string): string { - const map: Record = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - }; - return text.replace(/[&<>"']/g, (char) => map[char]); -} - - -export function generateMaintenanceHTML( - title: string | null, - message: string | null, - estimatedTime: string | null -): string { - const safeTitle = escapeHtml(title || 'Service Temporarily Unavailable'); - const safeMessage = escapeHtml(message || 'We are currently experiencing technical difficulties. Please check back soon.'); - const safeEstimatedTime = estimatedTime ? escapeHtml(estimatedTime) : null; - - return ` - - - - - - ${safeTitle} - - - -
-
🔧
-

${safeTitle}

-

${safeMessage}

- ${safeEstimatedTime ? - `
- Estimated completion:
- ${safeEstimatedTime} -
` - : ''} -
- -`; -} - export async function getTraefikConfig( exitNodeId: number, siteTypes: string[], diff --git a/server/lib/traefik/maintenance-server.ts b/server/maintenance/maintenance-server.ts similarity index 98% rename from server/lib/traefik/maintenance-server.ts rename to server/maintenance/maintenance-server.ts index 7c40c0a9..efa5a874 100644 --- a/server/lib/traefik/maintenance-server.ts +++ b/server/maintenance/maintenance-server.ts @@ -1,7 +1,7 @@ import express from 'express'; import { db, resources } from '@server/db'; import { eq } from 'drizzle-orm'; -import { generateMaintenanceHTML } from './getTraefikConfig'; +import { generateMaintenanceHTML } from './maintenanceUI'; import config from '@server/lib/config'; import logger from '@server/logger'; import path from 'path'; diff --git a/server/maintenance/maintenanceUI.ts b/server/maintenance/maintenanceUI.ts new file mode 100644 index 00000000..9096e6e3 --- /dev/null +++ b/server/maintenance/maintenanceUI.ts @@ -0,0 +1,102 @@ + +function escapeHtml(text: string): string { + const map: Record = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; + return text.replace(/[&<>"']/g, (char) => map[char]); +} + + +export function generateMaintenanceHTML( + title: string | null, + message: string | null, + estimatedTime: string | null +): string { + const safeTitle = escapeHtml(title || 'Service Temporarily Unavailable'); + const safeMessage = escapeHtml(message || 'We are currently experiencing technical difficulties. Please check back soon.'); + const safeEstimatedTime = estimatedTime ? escapeHtml(estimatedTime) : null; + + return ` + + + + + + ${safeTitle} + + + +
+
🔧
+

${safeTitle}

+

${safeMessage}

+ ${safeEstimatedTime ? + `
+ Estimated completion:
+ ${safeEstimatedTime} +
` + : ''} +
+ +`; +} \ No newline at end of file diff --git a/server/private/lib/traefik/getTraefikConfig.ts b/server/private/lib/traefik/getTraefikConfig.ts index 82568216..cba61d11 100644 --- a/server/private/lib/traefik/getTraefikConfig.ts +++ b/server/private/lib/traefik/getTraefikConfig.ts @@ -308,6 +308,97 @@ export async function getTraefikConfig( config_output.http.services = {}; } + const availableServers = (targets as TargetWithSite[]).filter( + (target: TargetWithSite) => { + if (!target.enabled) return false; + + const anySitesOnline = (targets as TargetWithSite[]).some( + (t: TargetWithSite) => t.site.online + ); + if (anySitesOnline && !target.site.online) return false; + + if (target.site.type === "local" || target.site.type === "wireguard") { + return target.ip && target.port && target.method; + } else if (target.site.type === "newt") { + return target.internalPort && target.method && target.site.subnet; + } + return false; + } + ); + + const hasHealthyServers = availableServers.length > 0; + + let showMaintenancePage = false; + if (resource.maintenanceModeEnabled) { + if (resource.maintenanceModeType === "forced") { + showMaintenancePage = true; + logger.debug( + `Resource ${resource.name} (${fullDomain}) is in FORCED maintenance mode` + ); + } else if (resource.maintenanceModeType === "automatic") { + showMaintenancePage = !hasHealthyServers; + if (showMaintenancePage) { + logger.warn( + `Resource ${resource.name} (${fullDomain}) has no healthy servers - showing maintenance page (AUTOMATIC mode)` + ); + } + } + } + + if (showMaintenancePage) { + const maintenanceServiceName = `${key}-maintenance-service`; + const maintenanceRouterName = `${key}-maintenance-router`; + + const maintenancePort = config.getRawConfig().traefik.maintenance_port || 8888; + const entrypointHttp = config.getRawConfig().traefik.http_entrypoint; + const entrypointHttps = config.getRawConfig().traefik.https_entrypoint; + + const fullDomain = resource.fullDomain; + const domainParts = fullDomain.split("."); + const wildCard = resource.subdomain + ? `*.${domainParts.slice(1).join(".")}` + : fullDomain; + + const tls = { + certResolver: resource.domainCertResolver?.trim() || + config.getRawConfig().traefik.cert_resolver, + ...(config.getRawConfig().traefik.prefer_wildcard_cert + ? { domains: [{ main: wildCard }] } + : {}) + }; + + const maintenanceHost = config.getRawConfig().traefik?.maintenance_host || 'pangolin'; + + config_output.http.services[maintenanceServiceName] = { + loadBalancer: { + servers: [{ url: `http://${maintenanceHost}:${maintenancePort}` }], + passHostHeader: true + } + }; + + const rule = `Host(\`${fullDomain}\`)`; + + config_output.http.routers[maintenanceRouterName] = { + entryPoints: [resource.ssl ? entrypointHttps : entrypointHttp], + service: maintenanceServiceName, + rule, + priority: 2000, + ...(resource.ssl ? { tls } : {}) + }; + + if (resource.ssl) { + config_output.http.routers[`${maintenanceRouterName}-redirect`] = { + entryPoints: [entrypointHttp], + middlewares: [redirectHttpsMiddlewareName], + service: maintenanceServiceName, + rule, + priority: 2000 + }; + } + + continue; + } + const domainParts = fullDomain.split("."); let wildCard; if (domainParts.length <= 2) { @@ -366,12 +457,12 @@ export async function getTraefikConfig( certResolver: resolverName, ...(preferWildcard ? { - domains: [ - { - main: wildCard - } - ] - } + domains: [ + { + main: wildCard + } + ] + } : {}) }; } else { @@ -624,14 +715,14 @@ export async function getTraefikConfig( })(), ...(resource.stickySession ? { - sticky: { - cookie: { - name: "p_sticky", // TODO: make this configurable via config.yml like other cookies - secure: resource.ssl, - httpOnly: true - } - } - } + sticky: { + cookie: { + name: "p_sticky", // TODO: make this configurable via config.yml like other cookies + secure: resource.ssl, + httpOnly: true + } + } + } : {}) } }; @@ -734,18 +825,18 @@ export async function getTraefikConfig( })(), ...(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 ? { - sticky: { - ipStrategy: { - depth: 0, - sourcePort: true - } - } - } + sticky: { + ipStrategy: { + depth: 0, + sourcePort: true + } + } + } : {}) } }; @@ -793,10 +884,9 @@ export async function getTraefikConfig( loadBalancer: { servers: [ { - url: `http://${ - config.getRawConfig().server + url: `http://${config.getRawConfig().server .internal_hostname - }:${config.getRawConfig().server.next_port}` + }:${config.getRawConfig().server.next_port}` } ] }