mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-08 04:02:54 +02:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b43fc0c6ea | |||
| 17375348b0 | |||
| e7fdbf9e85 | |||
| cddb5ecc3d |
Generated
+893
-415
File diff suppressed because it is too large
Load Diff
+20
-20
@@ -33,7 +33,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@asteasolutions/zod-to-openapi": "9.1.0",
|
"@asteasolutions/zod-to-openapi": "9.1.0",
|
||||||
"@aws-sdk/client-s3": "3.1121.0",
|
"@aws-sdk/client-s3": "3.1127.0",
|
||||||
"@devolutions/iron-remote-desktop": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-0.0.0.tgz",
|
"@devolutions/iron-remote-desktop": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-0.0.0.tgz",
|
||||||
"@devolutions/iron-remote-desktop-rdp": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-rdp-0.0.1.tgz",
|
"@devolutions/iron-remote-desktop-rdp": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-rdp-0.0.1.tgz",
|
||||||
"@headlessui/react": "2.2.10",
|
"@headlessui/react": "2.2.10",
|
||||||
@@ -65,11 +65,11 @@
|
|||||||
"@react-email/components": "1.0.12",
|
"@react-email/components": "1.0.12",
|
||||||
"@react-email/render": "2.1.0",
|
"@react-email/render": "2.1.0",
|
||||||
"@react-email/tailwind": "2.0.7",
|
"@react-email/tailwind": "2.0.7",
|
||||||
"@simplewebauthn/browser": "13.3.0",
|
"@simplewebauthn/browser": "14.0.0",
|
||||||
"@simplewebauthn/server": "13.3.3",
|
"@simplewebauthn/server": "14.0.0",
|
||||||
"@tailwindcss/forms": "0.5.11",
|
"@tailwindcss/forms": "0.5.11",
|
||||||
"@tanstack/react-query": "5.102.8",
|
"@tanstack/react-query": "5.102.8",
|
||||||
"@tanstack/react-table": "8.21.3",
|
"@tanstack/react-table": "9.2.4",
|
||||||
"@xterm/addon-fit": "^0.11.0",
|
"@xterm/addon-fit": "^0.11.0",
|
||||||
"@xterm/addon-web-links": "^0.12.0",
|
"@xterm/addon-web-links": "^0.12.0",
|
||||||
"@xterm/xterm": "^6.0.0",
|
"@xterm/xterm": "^6.0.0",
|
||||||
@@ -96,21 +96,21 @@
|
|||||||
"jmespath": "0.16.0",
|
"jmespath": "0.16.0",
|
||||||
"js-yaml": "5.4.1",
|
"js-yaml": "5.4.1",
|
||||||
"jsonwebtoken": "9.0.3",
|
"jsonwebtoken": "9.0.3",
|
||||||
"lucide-react": "1.38.0",
|
"lucide-react": "1.41.0",
|
||||||
"maxmind": "5.0.7",
|
"maxmind": "5.0.7",
|
||||||
"moment": "2.30.1",
|
"moment": "2.30.1",
|
||||||
"next": "16.3.3",
|
"next": "16.3.4",
|
||||||
"next-intl": "4.14.1",
|
"next-intl": "4.14.2",
|
||||||
"next-themes": "0.4.6",
|
"next-themes": "0.4.6",
|
||||||
"nextjs-toploader": "3.9.17",
|
"nextjs-toploader": "3.9.17",
|
||||||
"node-cache": "5.1.2",
|
"node-cache": "5.1.2",
|
||||||
"nodemailer": "9.1.0",
|
"nodemailer": "10.0.0",
|
||||||
"oslo": "1.2.1",
|
"oslo": "1.2.1",
|
||||||
"pg": "8.23.0",
|
"pg": "8.23.0",
|
||||||
"posthog-node": "5.51.4",
|
"posthog-node": "5.51.6",
|
||||||
"qrcode.react": "4.2.0",
|
"qrcode.react": "4.2.0",
|
||||||
"react": "19.2.8",
|
"react": "19.2.8",
|
||||||
"react-day-picker": "9.14.0",
|
"react-day-picker": "10.0.1",
|
||||||
"react-dom": "19.2.8",
|
"react-dom": "19.2.8",
|
||||||
"react-easy-sort": "1.8.0",
|
"react-easy-sort": "1.8.0",
|
||||||
"react-hook-form": "7.87.0",
|
"react-hook-form": "7.87.0",
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
"reodotdev": "1.1.0",
|
"reodotdev": "1.1.0",
|
||||||
"semver": "7.8.5",
|
"semver": "7.8.5",
|
||||||
"sshpk": "1.18.0",
|
"sshpk": "1.18.0",
|
||||||
"stripe": "22.6.0",
|
"stripe": "22.6.1",
|
||||||
"swagger-ui-express": "5.0.1",
|
"swagger-ui-express": "5.0.1",
|
||||||
"tailwind-merge": "3.6.0",
|
"tailwind-merge": "3.6.0",
|
||||||
"topojson-client": "3.1.0",
|
"topojson-client": "3.1.0",
|
||||||
@@ -152,12 +152,12 @@
|
|||||||
"@types/jmespath": "0.15.2",
|
"@types/jmespath": "0.15.2",
|
||||||
"@types/js-yaml": "4.0.9",
|
"@types/js-yaml": "4.0.9",
|
||||||
"@types/jsonwebtoken": "9.0.10",
|
"@types/jsonwebtoken": "9.0.10",
|
||||||
"@types/node": "26.4.0",
|
"@types/node": "26.4.1",
|
||||||
"@types/nodemailer": "8.0.1",
|
"@types/nodemailer": "8.0.1",
|
||||||
"@types/nprogress": "0.2.3",
|
"@types/nprogress": "0.2.3",
|
||||||
"@types/pg": "8.23.1",
|
"@types/pg": "8.23.1",
|
||||||
"@types/react": "19.2.18",
|
"@types/react": "19.2.18",
|
||||||
"@types/react-dom": "19.2.5",
|
"@types/react-dom": "19.2.7",
|
||||||
"@types/semver": "7.8.0",
|
"@types/semver": "7.8.0",
|
||||||
"@types/sshpk": "1.17.5",
|
"@types/sshpk": "1.17.5",
|
||||||
"@types/swagger-ui-express": "4.1.8",
|
"@types/swagger-ui-express": "4.1.8",
|
||||||
@@ -168,20 +168,20 @@
|
|||||||
"drizzle-kit": "0.31.10",
|
"drizzle-kit": "0.31.10",
|
||||||
"esbuild": "0.28.2",
|
"esbuild": "0.28.2",
|
||||||
"esbuild-node-externals": "2.0.0",
|
"esbuild-node-externals": "2.0.0",
|
||||||
"eslint": "10.9.1",
|
"eslint": "10.10.0",
|
||||||
"eslint-config-next": "16.3.3",
|
"eslint-config-next": "16.3.4",
|
||||||
"postcss": "8.5.26",
|
"postcss": "8.5.28",
|
||||||
"prettier": "3.9.6",
|
"prettier": "3.9.6",
|
||||||
"react-email": "6.9.3",
|
"react-email": "6.9.3",
|
||||||
"tailwindcss": "4.3.3",
|
"tailwindcss": "4.3.3",
|
||||||
"tsc-alias": "1.9.2",
|
"tsc-alias": "1.9.4",
|
||||||
"tsx": "4.23.13",
|
"tsx": "4.23.13",
|
||||||
"typescript": "6.0.3",
|
"typescript": "7.0.2",
|
||||||
"typescript-eslint": "8.68.0"
|
"typescript-eslint": "8.69.0"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"esbuild": "0.28.2",
|
"esbuild": "0.28.2",
|
||||||
"dompurify": "3.4.0",
|
"dompurify": "3.4.0",
|
||||||
"postcss": "8.5.26"
|
"postcss": "8.5.28"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { db, exitNodes, newts, sites } from "@server/db";
|
|||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import redisManager from "#private/lib/redis";
|
import redisManager from "#private/lib/redis";
|
||||||
import { sendToClient } from "#private/routers/ws";
|
// import { sendToClient } from "#private/routers/ws";
|
||||||
|
|
||||||
const INITIAL_DELAY_MS = 15 * 1000; // 15 seconds before first check
|
const INITIAL_DELAY_MS = 15 * 1000; // 15 seconds before first check
|
||||||
const CHECK_INTERVAL_MS = 10 * 1000; // Check every 10 seconds
|
const CHECK_INTERVAL_MS = 10 * 1000; // Check every 10 seconds
|
||||||
@@ -26,7 +26,7 @@ const REDIS_HASH_PREFIX = "exit-node-reconnect:";
|
|||||||
|
|
||||||
interface PendingReconnect {
|
interface PendingReconnect {
|
||||||
startTime: number;
|
startTime: number;
|
||||||
endpoint: string;
|
reachableAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// In-memory tracking for this node
|
// In-memory tracking for this node
|
||||||
@@ -40,15 +40,15 @@ let schedulerInterval: NodeJS.Timeout | null = null;
|
|||||||
*/
|
*/
|
||||||
export async function scheduleExitNodeReconnect(
|
export async function scheduleExitNodeReconnect(
|
||||||
exitNodeId: number,
|
exitNodeId: number,
|
||||||
endpoint: string
|
reachableAt: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
logger.info(
|
logger.info(
|
||||||
`Scheduling newt reconnect for exit node ${exitNodeId} (endpoint: ${endpoint})`
|
`Scheduling newt reconnect for exit node ${exitNodeId} (reachableAt: ${reachableAt})`
|
||||||
);
|
);
|
||||||
|
|
||||||
const entry: PendingReconnect = {
|
const entry: PendingReconnect = {
|
||||||
startTime: Date.now(),
|
startTime: Date.now(),
|
||||||
endpoint
|
reachableAt
|
||||||
};
|
};
|
||||||
|
|
||||||
pendingReconnects.set(exitNodeId, entry);
|
pendingReconnects.set(exitNodeId, entry);
|
||||||
@@ -63,8 +63,8 @@ export async function scheduleExitNodeReconnect(
|
|||||||
);
|
);
|
||||||
await redisManager.hset(
|
await redisManager.hset(
|
||||||
`${REDIS_HASH_PREFIX}${exitNodeId}`,
|
`${REDIS_HASH_PREFIX}${exitNodeId}`,
|
||||||
"endpoint",
|
"reachableAt",
|
||||||
endpoint
|
reachableAt
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,14 +101,14 @@ async function processPendingReconnects(): Promise<void> {
|
|||||||
`${REDIS_HASH_PREFIX}${id}`,
|
`${REDIS_HASH_PREFIX}${id}`,
|
||||||
"startTime"
|
"startTime"
|
||||||
);
|
);
|
||||||
const endpoint = await redisManager.hget(
|
const reachableAt = await redisManager.hget(
|
||||||
`${REDIS_HASH_PREFIX}${id}`,
|
`${REDIS_HASH_PREFIX}${id}`,
|
||||||
"endpoint"
|
"reachableAt"
|
||||||
);
|
);
|
||||||
if (startTimeStr && endpoint) {
|
if (startTimeStr && reachableAt) {
|
||||||
toProcess.set(id, {
|
toProcess.set(id, {
|
||||||
startTime: parseInt(startTimeStr, 10),
|
startTime: parseInt(startTimeStr, 10),
|
||||||
endpoint
|
reachableAt
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,7 +135,7 @@ async function processPendingReconnects(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if the exit node HTTP endpoint is reachable
|
// Check if the exit node HTTP endpoint is reachable
|
||||||
const pingUrl = `http://${entry.endpoint}/ping`;
|
const pingUrl = `${entry.reachableAt}/ping`;
|
||||||
try {
|
try {
|
||||||
await axios.get(pingUrl, { timeout: 5000 });
|
await axios.get(pingUrl, { timeout: 5000 });
|
||||||
} catch {
|
} catch {
|
||||||
@@ -150,47 +150,47 @@ async function processPendingReconnects(): Promise<void> {
|
|||||||
`Exit node ${exitNodeId} is reachable. Sending newt/wg/reconnect to connected newts.`
|
`Exit node ${exitNodeId} is reachable. Sending newt/wg/reconnect to connected newts.`
|
||||||
);
|
);
|
||||||
|
|
||||||
await sendReconnectToNewts(exitNodeId);
|
// await sendReconnectToNewts(exitNodeId);
|
||||||
await removePending(exitNodeId);
|
await removePending(exitNodeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendReconnectToNewts(exitNodeId: number): Promise<void> {
|
// async function sendReconnectToNewts(exitNodeId: number): Promise<void> {
|
||||||
try {
|
// try {
|
||||||
const connectedNewts = await db
|
// const connectedNewts = await db
|
||||||
.select({ newtId: newts.newtId })
|
// .select({ newtId: newts.newtId })
|
||||||
.from(newts)
|
// .from(newts)
|
||||||
.innerJoin(sites, eq(newts.siteId, sites.siteId))
|
// .innerJoin(sites, eq(newts.siteId, sites.siteId))
|
||||||
.where(eq(sites.exitNodeId, exitNodeId));
|
// .where(eq(sites.exitNodeId, exitNodeId));
|
||||||
|
|
||||||
if (connectedNewts.length === 0) {
|
// if (connectedNewts.length === 0) {
|
||||||
logger.debug(
|
// logger.debug(
|
||||||
`No newts found for exit node ${exitNodeId}, nothing to reconnect`
|
// `No newts found for exit node ${exitNodeId}, nothing to reconnect`
|
||||||
);
|
// );
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
logger.info(
|
// logger.info(
|
||||||
`Sending newt/wg/reconnect to ${connectedNewts.length} newt(s) for exit node ${exitNodeId}`
|
// `Sending newt/wg/reconnect to ${connectedNewts.length} newt(s) for exit node ${exitNodeId}`
|
||||||
);
|
// );
|
||||||
|
|
||||||
const reconnectMessage = {
|
// const reconnectMessage = {
|
||||||
type: "newt/wg/reconnect",
|
// type: "newt/wg/reconnect",
|
||||||
data: {}
|
// data: {}
|
||||||
};
|
// };
|
||||||
|
|
||||||
await Promise.allSettled(
|
// await Promise.allSettled(
|
||||||
connectedNewts.map(({ newtId }) =>
|
// connectedNewts.map(({ newtId }) =>
|
||||||
sendToClient(newtId, reconnectMessage)
|
// sendToClient(newtId, reconnectMessage)
|
||||||
)
|
// )
|
||||||
);
|
// );
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
logger.error(
|
// logger.error(
|
||||||
`Failed to send reconnect messages for exit node ${exitNodeId}`,
|
// `Failed to send reconnect messages for exit node ${exitNodeId}`,
|
||||||
{ error }
|
// { error }
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
async function removePending(exitNodeId: number): Promise<void> {
|
async function removePending(exitNodeId: number): Promise<void> {
|
||||||
pendingReconnects.delete(exitNodeId);
|
pendingReconnects.delete(exitNodeId);
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export const handleRemoteExitNodePingMessage: MessageHandler = async (
|
|||||||
try {
|
try {
|
||||||
// Fetch the current state before updating so we can detect the offline→online transition
|
// Fetch the current state before updating so we can detect the offline→online transition
|
||||||
const [currentExitNode] = await db
|
const [currentExitNode] = await db
|
||||||
.select({ online: exitNodes.online, endpoint: exitNodes.endpoint })
|
.select({ online: exitNodes.online, reachableAt: exitNodes.reachableAt })
|
||||||
.from(exitNodes)
|
.from(exitNodes)
|
||||||
.where(eq(exitNodes.exitNodeId, remoteExitNode.exitNodeId))
|
.where(eq(exitNodes.exitNodeId, remoteExitNode.exitNodeId))
|
||||||
.limit(1);
|
.limit(1);
|
||||||
@@ -55,18 +55,12 @@ export const handleRemoteExitNodePingMessage: MessageHandler = async (
|
|||||||
.where(eq(exitNodes.exitNodeId, remoteExitNode.exitNodeId));
|
.where(eq(exitNodes.exitNodeId, remoteExitNode.exitNodeId));
|
||||||
|
|
||||||
// If the exit node was offline and is now coming online, schedule newt reconnects
|
// If the exit node was offline and is now coming online, schedule newt reconnects
|
||||||
if (
|
if (currentExitNode && !currentExitNode.online && currentExitNode.reachableAt) {
|
||||||
currentExitNode &&
|
|
||||||
!currentExitNode.online &&
|
|
||||||
currentExitNode.endpoint
|
|
||||||
) {
|
|
||||||
scheduleExitNodeReconnect(
|
scheduleExitNodeReconnect(
|
||||||
remoteExitNode.exitNodeId,
|
remoteExitNode.exitNodeId,
|
||||||
currentExitNode.endpoint
|
currentExitNode.reachableAt
|
||||||
).catch((error) => {
|
).catch((error) => {
|
||||||
logger.error("Failed to schedule exit node reconnect", {
|
logger.error("Failed to schedule exit node reconnect", { error });
|
||||||
error
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user