From 0b30cfc341984b8de49012b7ec743dff3d5070e2 Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 4 Aug 2026 11:48:36 -0400 Subject: [PATCH] Working on inference resource management --- server/lib/rebuildClientAssociations.ts | 19 +++++--- server/routers/olm/sync.ts | 43 +++++++++++++++---- .../siteResource/createSiteResource.ts | 8 ++-- .../siteResource/updateSiteResource.ts | 24 ++++++----- .../private/[niceId]/inference/page.tsx | 2 - src/lib/privateResourceForm.ts | 20 ++++++++- 6 files changed, 87 insertions(+), 29 deletions(-) diff --git a/server/lib/rebuildClientAssociations.ts b/server/lib/rebuildClientAssociations.ts index 194bfe446..63a71382d 100644 --- a/server/lib/rebuildClientAssociations.ts +++ b/server/lib/rebuildClientAssociations.ts @@ -1177,12 +1177,12 @@ async function syncClientExitNodeConnections( const connectPayloads: { clientId: string; message: { type: string; data: any }; - options: { compress: boolean }; + options: { compress: boolean; incrementConfigVersion: boolean }; }[] = []; const disconnectPayloads: { clientId: string; message: { type: string; data: any }; - options: { compress: boolean }; + options: { compress: boolean; incrementConfigVersion: boolean }; }[] = []; for (const client of clientsData) { @@ -1218,7 +1218,10 @@ async function syncClientExitNodeConnections( tunnelIP: client.exitNodeSubnet.split("/")[0] } }, - options: { compress: canCompress(olm.version, "olm") } + options: { + compress: canCompress(olm.version, "olm"), + incrementConfigVersion: true + } }); } else { disconnectPayloads.push({ @@ -1227,7 +1230,10 @@ async function syncClientExitNodeConnections( type: "olm/wg/exitnode/disconnect", data: {} }, - options: { compress: canCompress(olm.version, "olm") } + options: { + compress: canCompress(olm.version, "olm"), + incrementConfigVersion: true + } }); } } @@ -1293,7 +1299,10 @@ async function syncClientExitNodeAliasUpdate( newAliases } }, - options: { compress: canCompress(olm.version, "olm") } + options: { + compress: canCompress(olm.version, "olm"), + incrementConfigVersion: true // this is important information we would need to sync + } })); if (updatePayloads.length > 0) { diff --git a/server/routers/olm/sync.ts b/server/routers/olm/sync.ts index 46e1fbd88..19d759769 100644 --- a/server/routers/olm/sync.ts +++ b/server/routers/olm/sync.ts @@ -1,6 +1,7 @@ import { Client, db, + ExitNode, exitNodes, Olm, sites, @@ -48,12 +49,26 @@ export async function sendOlmSyncMessage(olm: Olm, client: Client) { } // NOTE: WE ARE HARDCODING THE RELAY PARAMETER TO FALSE HERE BUT IN THE REGISTER MESSAGE ITS DEFINED BY THE CLIENT - const siteConfigurations = await buildSiteConfigurationForOlmClient( - client, - client.pubKey, - false, - jitMode - ); + const { siteConfigurations, exitNodeAliases } = + await buildSiteConfigurationForOlmClient( + client, + client.pubKey, + false, + jitMode + ); + + // The exit node the client itself is assigned to (for site resources hosted + // on it, e.g. inference), same as what's sent in the initial olm/wg/connect + // message. This is separate from exitNodesData below, which is only the set + // of exit nodes used for hole punching to reach site peers. + let clientExitNode: ExitNode | null = null; + if (client.exitNodeId) { + [clientExitNode] = await db + .select() + .from(exitNodes) + .where(eq(exitNodes.exitNodeId, client.exitNodeId)) + .limit(1); + } // Get all exit nodes from sites where the client has peers const clientSites = await db @@ -113,11 +128,23 @@ export async function sendOlmSyncMessage(olm: Olm, client: Client) { type: "olm/sync", data: { sites: siteConfigurations, - exitNodes: exitNodesData + exitNodes: exitNodesData, // this is for the holepunch information + // this is for the backhaul connection to the exit node + exitNode: + clientExitNode && client.exitNodeSubnet + ? { + aliases: exitNodeAliases, + connect: exitNodeAliases.length > 0, // we do not need to connect to the exit node if we do not have inference resources and right now all site resources on the exit node have an alias + endpoint: `${clientExitNode.endpoint}:${clientExitNode.listenPort}`, + publicKey: clientExitNode.publicKey, + serverIP: clientExitNode.address.split("/")[0], + tunnelIP: client.exitNodeSubnet.split("/")[0] + } + : undefined } }, { - compress: canCompress(olm.version, "olm") + compress: canCompress(olm.version, "olm") // we dont increment the version here or we could get into a loop! } ).catch((error) => { logger.warn(`Error sending olm sync message:`, error); diff --git a/server/routers/siteResource/createSiteResource.ts b/server/routers/siteResource/createSiteResource.ts index 9a6f91ad7..b80775b49 100644 --- a/server/routers/siteResource/createSiteResource.ts +++ b/server/routers/siteResource/createSiteResource.ts @@ -571,7 +571,7 @@ export async function createSiteResource( } let tcpPortRangeStringAdjusted = tcpPortRangeString; - if (mode === "http") { + if (mode === "http" || mode === "inference") { tcpPortRangeStringAdjusted = "443,80"; } else if (mode === "ssh") { tcpPortRangeStringAdjusted = destinationPort @@ -594,12 +594,14 @@ export async function createSiteResource( aliasAddress, tcpPortRangeString: tcpPortRangeStringAdjusted, udpPortRangeString: - mode == "http" || mode == "ssh" + mode == "http" || mode == "ssh" || mode == "inference" ? "" : udpPortRangeString, disableIcmp: disableIcmp || - (mode == "http" || mode == "ssh" ? true : false), // default to true for http resources, otherwise false + (mode == "http" || mode == "ssh" || mode == "inference" + ? true + : false), // default to true for http resources, otherwise false domainId, subdomain: finalSubdomain, fullDomain, diff --git a/server/routers/siteResource/updateSiteResource.ts b/server/routers/siteResource/updateSiteResource.ts index 0ae57c087..caf524804 100644 --- a/server/routers/siteResource/updateSiteResource.ts +++ b/server/routers/siteResource/updateSiteResource.ts @@ -166,8 +166,11 @@ const updateSiteResourceSchema = z if (data.mode === undefined && data.destination === undefined) { return true; } - // destination is only optional for ssh mode with native authDaemonMode - if (data.mode === "ssh" && data.authDaemonMode === "native") { + // destination is only optional for ssh mode with native authDaemonMode or inference + if ( + (data.mode === "ssh" && data.authDaemonMode === "native") || + data.mode == "inference" + ) { return true; } return ( @@ -558,8 +561,9 @@ export async function updateSiteResource( }) } : {}; + let tcpPortRangeStringAdjusted = tcpPortRangeString; - if (mode === "http") { + if (mode === "http" || mode == "inference") { tcpPortRangeStringAdjusted = "443,80"; } else if (mode === "ssh") { tcpPortRangeStringAdjusted = destinationPort @@ -583,20 +587,20 @@ export async function updateSiteResource( ? alias ? alias.trim() : null - : mode !== undefined && - mode !== "host" && - mode !== "ssh" - ? null - : undefined, + : undefined, tcpPortRangeString: tcpPortRangeStringAdjusted, udpPortRangeString: - mode == "http" || mode == "ssh" + mode == "http" || mode == "ssh" || mode == "inference" ? "" : udpPortRangeString, disableIcmp: mode !== undefined ? disableIcmp || - (mode == "http" || mode == "ssh" ? true : false) + (mode == "http" || + mode == "ssh" || + mode == "inference" + ? true + : false) : disableIcmp, domainId, subdomain: finalSubdomain, diff --git a/src/app/[orgId]/settings/resources/private/[niceId]/inference/page.tsx b/src/app/[orgId]/settings/resources/private/[niceId]/inference/page.tsx index 3f27f1172..24aab5067 100644 --- a/src/app/[orgId]/settings/resources/private/[niceId]/inference/page.tsx +++ b/src/app/[orgId]/settings/resources/private/[niceId]/inference/page.tsx @@ -20,9 +20,7 @@ import { useTranslations } from "next-intl"; import { useActionState, useMemo, useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; -import { PrivateResourceSitesField } from "@app/components/PrivateResourceSitesField"; import { PrivateResourceInferenceDestinationFields } from "@app/components/PrivateResourceDestinationFields"; -import { PrivateResourcePortRanges } from "@app/components/PrivateResourcePortRanges"; import { useSaveSiteResource } from "@app/hooks/useSaveSiteResource"; import { asAnyControl, diff --git a/src/lib/privateResourceForm.ts b/src/lib/privateResourceForm.ts index f2ff103f6..2f11b6e7b 100644 --- a/src/lib/privateResourceForm.ts +++ b/src/lib/privateResourceForm.ts @@ -211,6 +211,14 @@ export function buildCreateSiteResourcePayload( authDaemonPort: data.authDaemonPort }) }), + ...(data.mode === "inference" && { + alias: + data.alias && + typeof data.alias === "string" && + data.alias.trim() + ? data.alias + : undefined + }), ...((data.mode === "host" || data.mode === "cidr") && { tcpPortRangeString: data.tcpPortRangeString, udpPortRangeString: data.udpPortRangeString, @@ -237,7 +245,9 @@ export function buildUpdateSiteResourcePayload( enabled: data.enabled, ...(isNativeSsh ? { destination: null, destinationPort: null } - : { destination: data.destination ?? undefined }), + : data.mode !== "inference" + ? { destination: data.destination ?? undefined } + : {}), ...(data.mode === "http" && { scheme: data.scheme, ssl: data.ssl ?? false, @@ -281,6 +291,14 @@ export function buildUpdateSiteResourcePayload( authDaemonPort: data.authDaemonPort || null }) }), + ...(data.mode === "inference" && { + alias: + data.alias && + typeof data.alias === "string" && + data.alias.trim() + ? data.alias + : null + }), ...((data.mode === "host" || data.mode === "cidr") && { tcpPortRangeString: data.tcpPortRangeString, udpPortRangeString: data.udpPortRangeString,