From 0fb5ace9c7f31da1af615c2faa6a4b5f6f07cd6a Mon Sep 17 00:00:00 2001 From: Owen Date: Sat, 13 Jun 2026 14:08:03 -0700 Subject: [PATCH 1/5] Support the browser gateways on the remote nodes --- server/lib/traefik/TraefikConfigManager.ts | 9 ++++++++- server/lib/traefik/getTraefikConfig.ts | 2 +- server/private/lib/traefik/getTraefikConfig.ts | 12 ++++-------- server/private/routers/hybrid.ts | 4 +++- server/routers/traefik/traefikConfigProvider.ts | 7 ++++++- 5 files changed, 22 insertions(+), 12 deletions(-) diff --git a/server/lib/traefik/TraefikConfigManager.ts b/server/lib/traefik/TraefikConfigManager.ts index 42baf41b5..5f5a539ca 100644 --- a/server/lib/traefik/TraefikConfigManager.ts +++ b/server/lib/traefik/TraefikConfigManager.ts @@ -511,6 +511,12 @@ export class TraefikConfigManager { let traefikConfig; try { const currentExitNode = await getCurrentExitNodeId(); + + const maintenancePort = config.getRawConfig().server.next_port; + const maintenanceHost = + config.getRawConfig().server.internal_hostname; + const browserGatewayUiUrl = `http://${maintenanceHost}:${maintenancePort}`; + // logger.debug(`Fetching traefik config for exit node: ${currentExitNode}`); traefikConfig = await getTraefikConfig( // this is called by the local exit node to get its own config @@ -521,7 +527,8 @@ export class TraefikConfigManager { build == "saas" ? false : config.getRawConfig().traefik.allow_raw_resources, // dont allow raw resources on saas otherwise use config - build != "oss" // generate browser gateway targets on cloud and enterprise + build != "oss", // generate maintenance pages on cloud and hybrid + browserGatewayUiUrl // generate browser gateway targets on cloud and hybrid ); const domains = new Set(); diff --git a/server/lib/traefik/getTraefikConfig.ts b/server/lib/traefik/getTraefikConfig.ts index 48eb03638..c11a0c1e0 100644 --- a/server/lib/traefik/getTraefikConfig.ts +++ b/server/lib/traefik/getTraefikConfig.ts @@ -45,7 +45,7 @@ export async function getTraefikConfig( generateLoginPageRouters = false, // UNUSED BUT USED IN PRIVATE allowRawResources = true, allowMaintenancePage = true, // UNUSED BUT USED IN PRIVATE - allowBrowserGatewayResources = true + browserGatewayUiUrl: string | null = null // UNUSED BUT USED IN PRIVATE ): Promise { // 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 diff --git a/server/private/lib/traefik/getTraefikConfig.ts b/server/private/lib/traefik/getTraefikConfig.ts index e81715d3b..4395bc259 100644 --- a/server/private/lib/traefik/getTraefikConfig.ts +++ b/server/private/lib/traefik/getTraefikConfig.ts @@ -85,7 +85,7 @@ export async function getTraefikConfig( generateLoginPageRouters = false, allowRawResources = true, allowMaintenancePage = true, - allowBrowserGatewayResources = true + browserGatewayUiUrl: string | null = null ): Promise { // 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 @@ -317,7 +317,7 @@ export async function getTraefikConfig( BrowserGatewayResourceEntry >(); - if (allowBrowserGatewayResources) { + if (browserGatewayUiUrl) { for (const row of resourcesWithTargetsAndSites) { if (!["ssh", "vnc", "rdp"].includes(row.mode)) { continue; @@ -1027,7 +1027,7 @@ export async function getTraefikConfig( } } - if (allowBrowserGatewayResources) { + if (browserGatewayUiUrl) { // Generate Traefik config for browser gateway resources const browserGatewayPort = 39999; for (const [, bgResource] of browserGatewayResourcesMap.entries()) { @@ -1129,10 +1129,6 @@ export async function getTraefikConfig( const entrypointHttps = config.getRawConfig().traefik.https_entrypoint; - const maintenancePort = config.getRawConfig().server.next_port; - const maintenanceHost = - config.getRawConfig().server.internal_hostname; - if (!config_output.http.services) config_output.http.services = {}; if (!config_output.http.middlewares) @@ -1144,7 +1140,7 @@ export async function getTraefikConfig( loadBalancer: { servers: [ { - url: `http://${maintenanceHost}:${maintenancePort}` + url: browserGatewayUiUrl } ], passHostHeader: true diff --git a/server/private/routers/hybrid.ts b/server/private/routers/hybrid.ts index c6be3e7d1..c6245fc08 100644 --- a/server/private/routers/hybrid.ts +++ b/server/private/routers/hybrid.ts @@ -277,6 +277,8 @@ hybridRouter.get( ); } + const browserGatewayUiUrl = config.getRawConfig().app.dashboard_url; // points to the dashboard to serve from there + try { const traefikConfig = await getTraefikConfig( remoteExitNode.exitNodeId, @@ -285,7 +287,7 @@ hybridRouter.get( false, // Dont include login pages, true, // allow raw resources false, // dont generate maintenance page - false // dont generate browser gateway targets + browserGatewayUiUrl // generate browser gateway targets ); return response(res, { diff --git a/server/routers/traefik/traefikConfigProvider.ts b/server/routers/traefik/traefikConfigProvider.ts index 5da8eba4b..adc60ff12 100644 --- a/server/routers/traefik/traefikConfigProvider.ts +++ b/server/routers/traefik/traefikConfigProvider.ts @@ -17,13 +17,18 @@ export async function traefikConfigProvider( // Get the current exit node name from config const currentExitNodeId = await getCurrentExitNodeId(); + const maintenancePort = config.getRawConfig().server.next_port; + const maintenanceHost = config.getRawConfig().server.internal_hostname; + const browserGatewayUiUrl = `http://${maintenanceHost}:${maintenancePort}`; + const traefikConfig = await getTraefikConfig( currentExitNodeId, config.getRawConfig().traefik.site_types, build == "oss", // filter out the namespace domains in open source build != "oss", // generate the login pages on the cloud and and enterprise, config.getRawConfig().traefik.allow_raw_resources, - build != "oss" // generate browser gateway resources on cloud and enterprise + build != "oss", // generate maintenance page on cloud and enterprise + browserGatewayUiUrl ); if (traefikConfig?.http?.middlewares) { From c6ddd5c402ebb7cafeaa81aeba96f435edf02988 Mon Sep 17 00:00:00 2001 From: Owen Date: Sat, 13 Jun 2026 14:14:34 -0700 Subject: [PATCH 2/5] Open up holepunch requirements --- server/routers/newt/handleNewtGetConfigMessage.ts | 2 +- server/routers/olm/handleOlmRegisterMessage.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/routers/newt/handleNewtGetConfigMessage.ts b/server/routers/newt/handleNewtGetConfigMessage.ts index d78fa6f71..ff5d83799 100644 --- a/server/routers/newt/handleNewtGetConfigMessage.ts +++ b/server/routers/newt/handleNewtGetConfigMessage.ts @@ -54,7 +54,7 @@ export const handleNewtGetConfigMessage: MessageHandler = async (context) => { // TODO: somehow we should make sure a recent hole punch has happened if this occurs (hole punch could be from the last restart if done quickly) } - if (existingSite.lastHolePunch && now - existingSite.lastHolePunch > 5) { + if (existingSite.lastHolePunch && now - existingSite.lastHolePunch > 12) { logger.warn( `Site last hole punch is too old; skipping this register. The site is failing to hole punch and identify its network address with the server. Can the site reach the server on UDP port ${config.getRawConfig().gerbil.clients_start_port}?` ); diff --git a/server/routers/olm/handleOlmRegisterMessage.ts b/server/routers/olm/handleOlmRegisterMessage.ts index 3b0e1637a..9fe09736f 100644 --- a/server/routers/olm/handleOlmRegisterMessage.ts +++ b/server/routers/olm/handleOlmRegisterMessage.ts @@ -348,7 +348,7 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => { // this prevents us from accepting a register from an olm that has not hole punched yet. // the olm will pump the register so we can keep checking // TODO: I still think there is a better way to do this rather than locking it out here but ??? - if (now - (client.lastHolePunch || 0) > 5 && sitesCount > 0) { + if (now - (client.lastHolePunch || 0) > 12 && sitesCount > 0) { logger.warn( `[handleOlmRegisterMessage] Client last hole punch is too old and we have sites to send; skipping this register. The client is failing to hole punch and identify its network address with the server. Can the client reach the server on UDP port ${config.getRawConfig().gerbil.clients_start_port}?`, { orgId: client.orgId, clientId: client.clientId } From 50da863bb78094f05f42e1841bbd751e844c54d1 Mon Sep 17 00:00:00 2001 From: Owen Date: Sat, 13 Jun 2026 21:45:52 -0700 Subject: [PATCH 3/5] Add maintence page support for remote nodes --- server/lib/traefik/TraefikConfigManager.ts | 2 +- server/private/lib/traefik/getTraefikConfig.ts | 14 +++++--------- server/private/routers/hybrid.ts | 6 +++--- server/routers/traefik/traefikConfigProvider.ts | 6 +++--- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/server/lib/traefik/TraefikConfigManager.ts b/server/lib/traefik/TraefikConfigManager.ts index 5f5a539ca..ea9d4907a 100644 --- a/server/lib/traefik/TraefikConfigManager.ts +++ b/server/lib/traefik/TraefikConfigManager.ts @@ -527,7 +527,7 @@ export class TraefikConfigManager { build == "saas" ? false : config.getRawConfig().traefik.allow_raw_resources, // dont allow raw resources on saas otherwise use config - build != "oss", // generate maintenance pages on cloud and hybrid + build != "oss" ? browserGatewayUiUrl : null, // generate maintenance pages on cloud and hybrid browserGatewayUiUrl // generate browser gateway targets on cloud and hybrid ); diff --git a/server/private/lib/traefik/getTraefikConfig.ts b/server/private/lib/traefik/getTraefikConfig.ts index 4395bc259..c188178a3 100644 --- a/server/private/lib/traefik/getTraefikConfig.ts +++ b/server/private/lib/traefik/getTraefikConfig.ts @@ -84,7 +84,7 @@ export async function getTraefikConfig( filterOutNamespaceDomains = false, generateLoginPageRouters = false, allowRawResources = true, - allowMaintenancePage = true, + maintenancePageUiUrl: string | null = null, browserGatewayUiUrl: string | null = null ): Promise { // Get resources with their targets and sites in a single optimized query @@ -630,7 +630,7 @@ export async function getTraefikConfig( } } - if (showMaintenancePage && allowMaintenancePage) { + if (showMaintenancePage && maintenancePageUiUrl) { const maintenanceServiceName = `${key}-maintenance-service`; const maintenanceRouterName = `${key}-maintenance-router`; const rewriteMiddlewareName = `${key}-maintenance-rewrite`; @@ -646,15 +646,11 @@ export async function getTraefikConfig( ? `*.${domainParts.slice(1).join(".")}` : fullDomain; - const maintenancePort = config.getRawConfig().server.next_port; - const maintenanceHost = - config.getRawConfig().server.internal_hostname; - config_output.http.services[maintenanceServiceName] = { loadBalancer: { servers: [ { - url: `http://${maintenanceHost}:${maintenancePort}` + url: maintenancePageUiUrl } ], passHostHeader: true @@ -1119,7 +1115,7 @@ export async function getTraefikConfig( } } - if (showBgMaintenancePage && allowMaintenancePage) { + if (showBgMaintenancePage && maintenancePageUiUrl) { const bgMaintenanceServiceName = `bg-r${bgResource.resourceId}-maintenance-service`; const bgMaintenanceRouterName = `bg-r${bgResource.resourceId}-maintenance-router`; const bgRewriteMiddlewareName = `bg-r${bgResource.resourceId}-maintenance-rewrite`; @@ -1140,7 +1136,7 @@ export async function getTraefikConfig( loadBalancer: { servers: [ { - url: browserGatewayUiUrl + url: maintenancePageUiUrl } ], passHostHeader: true diff --git a/server/private/routers/hybrid.ts b/server/private/routers/hybrid.ts index c6245fc08..8beea35f0 100644 --- a/server/private/routers/hybrid.ts +++ b/server/private/routers/hybrid.ts @@ -277,7 +277,7 @@ hybridRouter.get( ); } - const browserGatewayUiUrl = config.getRawConfig().app.dashboard_url; // points to the dashboard to serve from there + const pangolinUIUrl = config.getRawConfig().app.dashboard_url; // points to the dashboard to serve from there try { const traefikConfig = await getTraefikConfig( @@ -286,8 +286,8 @@ hybridRouter.get( true, // But don't allow domain namespace resources false, // Dont include login pages, true, // allow raw resources - false, // dont generate maintenance page - browserGatewayUiUrl // generate browser gateway targets + pangolinUIUrl, // dont generate maintenance page + pangolinUIUrl // generate browser gateway targets ); return response(res, { diff --git a/server/routers/traefik/traefikConfigProvider.ts b/server/routers/traefik/traefikConfigProvider.ts index adc60ff12..04cb30530 100644 --- a/server/routers/traefik/traefikConfigProvider.ts +++ b/server/routers/traefik/traefikConfigProvider.ts @@ -19,7 +19,7 @@ export async function traefikConfigProvider( const maintenancePort = config.getRawConfig().server.next_port; const maintenanceHost = config.getRawConfig().server.internal_hostname; - const browserGatewayUiUrl = `http://${maintenanceHost}:${maintenancePort}`; + const pangolinUIUrl = `http://${maintenanceHost}:${maintenancePort}`; const traefikConfig = await getTraefikConfig( currentExitNodeId, @@ -27,8 +27,8 @@ export async function traefikConfigProvider( build == "oss", // filter out the namespace domains in open source build != "oss", // generate the login pages on the cloud and and enterprise, config.getRawConfig().traefik.allow_raw_resources, - build != "oss", // generate maintenance page on cloud and enterprise - browserGatewayUiUrl + pangolinUIUrl, + pangolinUIUrl ); if (traefikConfig?.http?.middlewares) { From f39cbc9bf4976b66c567e750b4176da484f79749 Mon Sep 17 00:00:00 2001 From: Owen Date: Sun, 14 Jun 2026 11:03:14 -0700 Subject: [PATCH 4/5] Add same signature to oss --- server/lib/traefik/getTraefikConfig.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/lib/traefik/getTraefikConfig.ts b/server/lib/traefik/getTraefikConfig.ts index c11a0c1e0..c63b5b718 100644 --- a/server/lib/traefik/getTraefikConfig.ts +++ b/server/lib/traefik/getTraefikConfig.ts @@ -44,7 +44,7 @@ export async function getTraefikConfig( filterOutNamespaceDomains = false, // UNUSED BUT USED IN PRIVATE generateLoginPageRouters = false, // UNUSED BUT USED IN PRIVATE allowRawResources = true, - allowMaintenancePage = true, // UNUSED BUT USED IN PRIVATE + maintenancePageUiUrl: string | null = null, // UNUSED BUT USED IN PRIVATE browserGatewayUiUrl: string | null = null // UNUSED BUT USED IN PRIVATE ): Promise { // Get resources with their targets and sites in a single optimized query From 90eceb457a648ff889c28ac901f6165d848b57bf Mon Sep 17 00:00:00 2001 From: Owen Date: Sun, 14 Jun 2026 11:10:05 -0700 Subject: [PATCH 5/5] Clean up url passing --- server/lib/traefik/TraefikConfigManager.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/lib/traefik/TraefikConfigManager.ts b/server/lib/traefik/TraefikConfigManager.ts index ea9d4907a..cc7299ff7 100644 --- a/server/lib/traefik/TraefikConfigManager.ts +++ b/server/lib/traefik/TraefikConfigManager.ts @@ -515,7 +515,7 @@ export class TraefikConfigManager { const maintenancePort = config.getRawConfig().server.next_port; const maintenanceHost = config.getRawConfig().server.internal_hostname; - const browserGatewayUiUrl = `http://${maintenanceHost}:${maintenancePort}`; + const pangolinUIUrl = `http://${maintenanceHost}:${maintenancePort}`; // logger.debug(`Fetching traefik config for exit node: ${currentExitNode}`); traefikConfig = await getTraefikConfig( @@ -527,8 +527,8 @@ export class TraefikConfigManager { build == "saas" ? false : config.getRawConfig().traefik.allow_raw_resources, // dont allow raw resources on saas otherwise use config - build != "oss" ? browserGatewayUiUrl : null, // generate maintenance pages on cloud and hybrid - browserGatewayUiUrl // generate browser gateway targets on cloud and hybrid + pangolinUIUrl, // generate maintenance pages on cloud and hybrid + pangolinUIUrl // generate browser gateway targets on cloud and hybrid ); const domains = new Set();