From da7166a7ea95f856b80edf9964e1b25f96c72d2d Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 17 Jul 2025 14:34:57 -0700 Subject: [PATCH 1/5] fix config --- install/config/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/config/config.yml b/install/config/config.yml index c4145fa2..5f81c141 100644 --- a/install/config/config.yml +++ b/install/config/config.yml @@ -30,7 +30,7 @@ orgs: email: smtp_host: "{{.EmailSMTPHost}}" smtp_port: {{.EmailSMTPPort}} - smtp_user: "{{.EmailSMTPUser}allow_base_domain_resources}" + smtp_user: "{{.EmailSMTPUser}}" smtp_pass: "{{.EmailSMTPPass}}" no_reply: "{{.EmailNoReply}}" {{end}} From 330b28ad9cd242e03d805d467bcebfd866847834 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 17 Jul 2025 14:35:09 -0700 Subject: [PATCH 2/5] Fix local sites --- server/routers/traefik/getTraefikConfig.ts | 93 ++++++++++++++-------- 1 file changed, 60 insertions(+), 33 deletions(-) diff --git a/server/routers/traefik/getTraefikConfig.ts b/server/routers/traefik/getTraefikConfig.ts index 79a56130..402498ab 100644 --- a/server/routers/traefik/getTraefikConfig.ts +++ b/server/routers/traefik/getTraefikConfig.ts @@ -42,42 +42,68 @@ export async function traefikConfigProvider( .from(exitNodes) .limit(1); - if (!exitNode) { - logger.error("No exit node found in the database"); - return []; + if (exitNode) { + currentExitNodeId = exitNode.exitNodeId; } - - currentExitNodeId = exitNode.exitNodeId; } } - // Get the site(s) on this exit node - const resourcesWithRelations = await tx - .select({ - // Resource fields - resourceId: resources.resourceId, - fullDomain: resources.fullDomain, - ssl: resources.ssl, - http: resources.http, - proxyPort: resources.proxyPort, - protocol: resources.protocol, - subdomain: resources.subdomain, - domainId: resources.domainId, - // Site fields - site: { - siteId: sites.siteId, - type: sites.type, - subnet: sites.subnet, - exitNodeId: sites.exitNodeId, - }, - enabled: resources.enabled, - stickySession: resources.stickySession, - tlsServerName: resources.tlsServerName, - setHostHeader: resources.setHostHeader - }) - .from(resources) - .innerJoin(sites, eq(sites.siteId, resources.siteId)) - .where(eq(sites.exitNodeId, currentExitNodeId)); + let resourcesWithRelations; + if (currentExitNodeId) { + // Get the site(s) on this exit node + resourcesWithRelations = await tx + .select({ + // Resource fields + resourceId: resources.resourceId, + fullDomain: resources.fullDomain, + ssl: resources.ssl, + http: resources.http, + proxyPort: resources.proxyPort, + protocol: resources.protocol, + subdomain: resources.subdomain, + domainId: resources.domainId, + // Site fields + site: { + siteId: sites.siteId, + type: sites.type, + subnet: sites.subnet, + exitNodeId: sites.exitNodeId + }, + enabled: resources.enabled, + stickySession: resources.stickySession, + tlsServerName: resources.tlsServerName, + setHostHeader: resources.setHostHeader + }) + .from(resources) + .innerJoin(sites, eq(sites.siteId, resources.siteId)) + .where(eq(sites.exitNodeId, currentExitNodeId)); + } else { + resourcesWithRelations = await tx + .select({ + // Resource fields + resourceId: resources.resourceId, + fullDomain: resources.fullDomain, + ssl: resources.ssl, + http: resources.http, + proxyPort: resources.proxyPort, + protocol: resources.protocol, + subdomain: resources.subdomain, + domainId: resources.domainId, + // Site fields + site: { + siteId: sites.siteId, + type: sites.type, + subnet: sites.subnet, + exitNodeId: sites.exitNodeId + }, + enabled: resources.enabled, + stickySession: resources.stickySession, + tlsServerName: resources.tlsServerName, + setHostHeader: resources.setHostHeader + }) + .from(resources) + .innerJoin(sites, eq(sites.siteId, resources.siteId)); + } // Get all resource IDs from the first query const resourceIds = resourcesWithRelations.map((r) => r.resourceId); @@ -284,7 +310,8 @@ export async function traefikConfigProvider( } else if (site.type === "newt") { if ( !target.internalPort || - !target.method || !site.subnet + !target.method || + !site.subnet ) { return false; } From 9c7e74ef37e5099f5e798a14b61d7b360ebd6d38 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 17 Jul 2025 14:43:47 -0700 Subject: [PATCH 3/5] Remove ping logic --- .../routers/newt/handleNewtRegisterMessage.ts | 94 +------------------ 1 file changed, 3 insertions(+), 91 deletions(-) diff --git a/server/routers/newt/handleNewtRegisterMessage.ts b/server/routers/newt/handleNewtRegisterMessage.ts index 5950aa93..71a6fd5c 100644 --- a/server/routers/newt/handleNewtRegisterMessage.ts +++ b/server/routers/newt/handleNewtRegisterMessage.ts @@ -258,101 +258,13 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { }; }; -/** - * Selects the most suitable exit node from a list of ping results. - * - * The selection algorithm follows these steps: - * - * 1. **Filter Invalid Nodes**: Excludes nodes with errors or zero weight. - * - * 2. **Sort by Latency**: Sorts valid nodes in ascending order of latency. - * - * 3. **Preferred Selection**: - * - If the lowest-latency node has sufficient capacity (≥10% weight), - * check if a previously connected node is also acceptable. - * - The previously connected node is preferred if its latency is within - * 30ms or 15% of the best node’s latency. - * - * 4. **Fallback to Next Best**: - * - If the lowest-latency node is under capacity, find the next node - * with acceptable capacity. - * - * 5. **Final Fallback**: - * - If no nodes meet the capacity threshold, fall back to the node - * with the highest weight (i.e., most available capacity). - * - */ function selectBestExitNode( pingResults: ExitNodePingResult[] ): ExitNodePingResult | null { - const MIN_CAPACITY_THRESHOLD = 0.1; - const LATENCY_TOLERANCE_MS = 30; - const LATENCY_TOLERANCE_PERCENT = 0.15; - - // Filter out invalid nodes - const validNodes = pingResults.filter((n) => !n.error && n.weight > 0); - - if (validNodes.length === 0) { - logger.error("No valid exit nodes available"); + if (!pingResults || pingResults.length === 0) { + logger.warn("No ping results provided"); return null; } - // Sort by latency (ascending) - const sortedNodes = validNodes - .slice() - .sort((a, b) => a.latencyMs - b.latencyMs); - const lowestLatencyNode = sortedNodes[0]; - - logger.info( - `Lowest latency node: ${lowestLatencyNode.exitNodeName} (${lowestLatencyNode.latencyMs} ms, weight=${lowestLatencyNode.weight.toFixed(2)})` - ); - - // If lowest latency node has enough capacity, check if previously connected node is acceptable - if (lowestLatencyNode.weight >= MIN_CAPACITY_THRESHOLD) { - const previouslyConnectedNode = sortedNodes.find( - (n) => - n.wasPreviouslyConnected && n.weight >= MIN_CAPACITY_THRESHOLD - ); - - if (previouslyConnectedNode) { - const latencyDiff = - previouslyConnectedNode.latencyMs - lowestLatencyNode.latencyMs; - const percentDiff = latencyDiff / lowestLatencyNode.latencyMs; - - if ( - latencyDiff <= LATENCY_TOLERANCE_MS || - percentDiff <= LATENCY_TOLERANCE_PERCENT - ) { - logger.info( - `Sticking with previously connected node: ${previouslyConnectedNode.exitNodeName} ` + - `(${previouslyConnectedNode.latencyMs} ms), latency diff = ${latencyDiff.toFixed(1)}ms ` + - `/ ${(percentDiff * 100).toFixed(1)}%.` - ); - return previouslyConnectedNode; - } - } - - return lowestLatencyNode; - } - - // Otherwise, find the next node (after the lowest) that has enough capacity - for (let i = 1; i < sortedNodes.length; i++) { - const node = sortedNodes[i]; - if (node.weight >= MIN_CAPACITY_THRESHOLD) { - logger.info( - `Lowest latency node under capacity. Using next best: ${node.exitNodeName} ` + - `(${node.latencyMs} ms, weight=${node.weight.toFixed(2)})` - ); - return node; - } - } - - // Fallback: pick the highest weight node - const fallbackNode = validNodes.reduce((a, b) => - a.weight > b.weight ? a : b - ); - logger.warn( - `No nodes with ≥10% weight. Falling back to highest capacity node: ${fallbackNode.exitNodeName}` - ); - return fallbackNode; + return pingResults[0]; } From 28b57ba6525655d1d1c55592edbd49d8d0141a8b Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 17 Jul 2025 14:57:09 -0700 Subject: [PATCH 4/5] Allow null exit node id as well --- server/routers/traefik/getTraefikConfig.ts | 90 ++++++++-------------- 1 file changed, 33 insertions(+), 57 deletions(-) diff --git a/server/routers/traefik/getTraefikConfig.ts b/server/routers/traefik/getTraefikConfig.ts index 402498ab..811f090f 100644 --- a/server/routers/traefik/getTraefikConfig.ts +++ b/server/routers/traefik/getTraefikConfig.ts @@ -1,6 +1,6 @@ import { Request, Response } from "express"; import { db, exitNodes } from "@server/db"; -import { and, eq, inArray } from "drizzle-orm"; +import { and, eq, inArray, or, isNull } from "drizzle-orm"; import logger from "@server/logger"; import HttpCode from "@server/types/HttpCode"; import config from "@server/lib/config"; @@ -48,62 +48,38 @@ export async function traefikConfigProvider( } } - let resourcesWithRelations; - if (currentExitNodeId) { - // Get the site(s) on this exit node - resourcesWithRelations = await tx - .select({ - // Resource fields - resourceId: resources.resourceId, - fullDomain: resources.fullDomain, - ssl: resources.ssl, - http: resources.http, - proxyPort: resources.proxyPort, - protocol: resources.protocol, - subdomain: resources.subdomain, - domainId: resources.domainId, - // Site fields - site: { - siteId: sites.siteId, - type: sites.type, - subnet: sites.subnet, - exitNodeId: sites.exitNodeId - }, - enabled: resources.enabled, - stickySession: resources.stickySession, - tlsServerName: resources.tlsServerName, - setHostHeader: resources.setHostHeader - }) - .from(resources) - .innerJoin(sites, eq(sites.siteId, resources.siteId)) - .where(eq(sites.exitNodeId, currentExitNodeId)); - } else { - resourcesWithRelations = await tx - .select({ - // Resource fields - resourceId: resources.resourceId, - fullDomain: resources.fullDomain, - ssl: resources.ssl, - http: resources.http, - proxyPort: resources.proxyPort, - protocol: resources.protocol, - subdomain: resources.subdomain, - domainId: resources.domainId, - // Site fields - site: { - siteId: sites.siteId, - type: sites.type, - subnet: sites.subnet, - exitNodeId: sites.exitNodeId - }, - enabled: resources.enabled, - stickySession: resources.stickySession, - tlsServerName: resources.tlsServerName, - setHostHeader: resources.setHostHeader - }) - .from(resources) - .innerJoin(sites, eq(sites.siteId, resources.siteId)); - } + // Get the site(s) on this exit node + const resourcesWithRelations = await tx + .select({ + // Resource fields + resourceId: resources.resourceId, + fullDomain: resources.fullDomain, + ssl: resources.ssl, + http: resources.http, + proxyPort: resources.proxyPort, + protocol: resources.protocol, + subdomain: resources.subdomain, + domainId: resources.domainId, + // Site fields + site: { + siteId: sites.siteId, + type: sites.type, + subnet: sites.subnet, + exitNodeId: sites.exitNodeId + }, + enabled: resources.enabled, + stickySession: resources.stickySession, + tlsServerName: resources.tlsServerName, + setHostHeader: resources.setHostHeader + }) + .from(resources) + .innerJoin(sites, eq(sites.siteId, resources.siteId)) + .where( + or( + eq(sites.exitNodeId, currentExitNodeId), + isNull(sites.exitNodeId) + ) + ); // Get all resource IDs from the first query const resourceIds = resourcesWithRelations.map((r) => r.resourceId); From 5148988dcc3b0d20970e7ddf3766753f3e60719b Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 17 Jul 2025 14:59:49 -0700 Subject: [PATCH 5/5] Also dont return if you are passing an exit node --- server/routers/traefik/getTraefikConfig.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/server/routers/traefik/getTraefikConfig.ts b/server/routers/traefik/getTraefikConfig.ts index 811f090f..654f0c4a 100644 --- a/server/routers/traefik/getTraefikConfig.ts +++ b/server/routers/traefik/getTraefikConfig.ts @@ -27,13 +27,9 @@ export async function traefikConfigProvider( }) .from(exitNodes) .where(eq(exitNodes.name, exitNodeName)); - if (!exitNode) { - logger.error( - `Exit node with name ${exitNodeName} not found in the database` - ); - return []; + if (exitNode) { + currentExitNodeId = exitNode.exitNodeId; } - currentExitNodeId = exitNode.exitNodeId; } else { const [exitNode] = await tx .select({