mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-06 18:56:39 +00:00
support postgresql as database option
This commit is contained in:
@@ -1,21 +1,21 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { z } from 'zod';
|
||||
import { sites, resources, targets, exitNodes } from '@server/db/schemas';
|
||||
import { db } from '@server/db';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { sites, resources, targets, exitNodes } from "@server/db";
|
||||
import { db } from "@server/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from '@server/types/HttpCode';
|
||||
import createHttpError from 'http-errors';
|
||||
import logger from '@server/logger';
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import createHttpError from "http-errors";
|
||||
import logger from "@server/logger";
|
||||
import config from "@server/lib/config";
|
||||
import { getUniqueExitNodeEndpointName } from '@server/db/names';
|
||||
import { getUniqueExitNodeEndpointName } from "../../db/names";
|
||||
import { findNextAvailableCidr } from "@server/lib/ip";
|
||||
import { fromError } from 'zod-validation-error';
|
||||
import { getAllowedIps } from '../target/helpers';
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { getAllowedIps } from "../target/helpers";
|
||||
// Define Zod schema for request validation
|
||||
const getConfigSchema = z.object({
|
||||
publicKey: z.string(),
|
||||
reachableAt: z.string().optional(),
|
||||
reachableAt: z.string().optional()
|
||||
});
|
||||
|
||||
export type GetConfigResponse = {
|
||||
@@ -25,9 +25,13 @@ export type GetConfigResponse = {
|
||||
publicKey: string | null;
|
||||
allowedIps: string[];
|
||||
}[];
|
||||
}
|
||||
};
|
||||
|
||||
export async function getConfig(req: Request, res: Response, next: NextFunction): Promise<any> {
|
||||
export async function getConfig(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<any> {
|
||||
try {
|
||||
// Validate request parameters
|
||||
const parsedParams = getConfigSchema.safeParse(req.body);
|
||||
@@ -43,11 +47,16 @@ export async function getConfig(req: Request, res: Response, next: NextFunction)
|
||||
const { publicKey, reachableAt } = parsedParams.data;
|
||||
|
||||
if (!publicKey) {
|
||||
return next(createHttpError(HttpCode.BAD_REQUEST, 'publicKey is required'));
|
||||
return next(
|
||||
createHttpError(HttpCode.BAD_REQUEST, "publicKey is required")
|
||||
);
|
||||
}
|
||||
|
||||
// Fetch exit node
|
||||
let exitNodeQuery = await db.select().from(exitNodes).where(eq(exitNodes.publicKey, publicKey));
|
||||
let exitNodeQuery = await db
|
||||
.select()
|
||||
.from(exitNodes)
|
||||
.where(eq(exitNodes.publicKey, publicKey));
|
||||
let exitNode;
|
||||
if (exitNodeQuery.length === 0) {
|
||||
const address = await getNextAvailableSubnet();
|
||||
@@ -60,40 +69,53 @@ export async function getConfig(req: Request, res: Response, next: NextFunction)
|
||||
}
|
||||
|
||||
// create a new exit node
|
||||
exitNode = await db.insert(exitNodes).values({
|
||||
publicKey,
|
||||
endpoint: `${subEndpoint}${subEndpoint != "" ? "." : ""}${config.getRawConfig().gerbil.base_endpoint}`,
|
||||
address,
|
||||
listenPort,
|
||||
reachableAt,
|
||||
name: `Exit Node ${publicKey.slice(0, 8)}`,
|
||||
}).returning().execute();
|
||||
exitNode = await db
|
||||
.insert(exitNodes)
|
||||
.values({
|
||||
publicKey,
|
||||
endpoint: `${subEndpoint}${subEndpoint != "" ? "." : ""}${config.getRawConfig().gerbil.base_endpoint}`,
|
||||
address,
|
||||
listenPort,
|
||||
reachableAt,
|
||||
name: `Exit Node ${publicKey.slice(0, 8)}`
|
||||
})
|
||||
.returning()
|
||||
.execute();
|
||||
|
||||
logger.info(`Created new exit node ${exitNode[0].name} with address ${exitNode[0].address} and port ${exitNode[0].listenPort}`);
|
||||
logger.info(
|
||||
`Created new exit node ${exitNode[0].name} with address ${exitNode[0].address} and port ${exitNode[0].listenPort}`
|
||||
);
|
||||
} else {
|
||||
exitNode = exitNodeQuery;
|
||||
}
|
||||
|
||||
if (!exitNode) {
|
||||
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "Failed to create exit node"));
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.INTERNAL_SERVER_ERROR,
|
||||
"Failed to create exit node"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Fetch sites for this exit node
|
||||
const sitesRes = await db.query.sites.findMany({
|
||||
where: eq(sites.exitNodeId, exitNode[0].exitNodeId),
|
||||
});
|
||||
const sitesRes = await db
|
||||
.select()
|
||||
.from(sites)
|
||||
.where(eq(sites.exitNodeId, exitNode[0].exitNodeId));
|
||||
|
||||
const peers = await Promise.all(sitesRes.map(async (site) => {
|
||||
return {
|
||||
publicKey: site.pubKey,
|
||||
allowedIps: await getAllowedIps(site.siteId)
|
||||
};
|
||||
}));
|
||||
const peers = await Promise.all(
|
||||
sitesRes.map(async (site) => {
|
||||
return {
|
||||
publicKey: site.pubKey,
|
||||
allowedIps: await getAllowedIps(site.siteId)
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const configResponse: GetConfigResponse = {
|
||||
listenPort: exitNode[0].listenPort || 51820,
|
||||
ipAddress: exitNode[0].address,
|
||||
peers,
|
||||
peers
|
||||
};
|
||||
|
||||
logger.debug("Sending config: ", configResponse);
|
||||
@@ -101,32 +123,49 @@ export async function getConfig(req: Request, res: Response, next: NextFunction)
|
||||
return res.status(HttpCode.OK).send(configResponse);
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.INTERNAL_SERVER_ERROR,
|
||||
"An error occurred..."
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function getNextAvailableSubnet(): Promise<string> {
|
||||
// Get all existing subnets from routes table
|
||||
const existingAddresses = await db.select({
|
||||
address: exitNodes.address,
|
||||
}).from(exitNodes);
|
||||
const existingAddresses = await db
|
||||
.select({
|
||||
address: exitNodes.address
|
||||
})
|
||||
.from(exitNodes);
|
||||
|
||||
const addresses = existingAddresses.map(a => a.address);
|
||||
let subnet = findNextAvailableCidr(addresses, config.getRawConfig().gerbil.block_size, config.getRawConfig().gerbil.subnet_group);
|
||||
const addresses = existingAddresses.map((a) => a.address);
|
||||
let subnet = findNextAvailableCidr(
|
||||
addresses,
|
||||
config.getRawConfig().gerbil.block_size,
|
||||
config.getRawConfig().gerbil.subnet_group
|
||||
);
|
||||
if (!subnet) {
|
||||
throw new Error('No available subnets remaining in space');
|
||||
throw new Error("No available subnets remaining in space");
|
||||
}
|
||||
|
||||
// replace the last octet with 1
|
||||
subnet = subnet.split('.').slice(0, 3).join('.') + '.1' + '/' + subnet.split('/')[1];
|
||||
subnet =
|
||||
subnet.split(".").slice(0, 3).join(".") +
|
||||
".1" +
|
||||
"/" +
|
||||
subnet.split("/")[1];
|
||||
return subnet;
|
||||
}
|
||||
|
||||
async function getNextAvailablePort(): Promise<number> {
|
||||
// Get all existing ports from exitNodes table
|
||||
const existingPorts = await db.select({
|
||||
listenPort: exitNodes.listenPort,
|
||||
}).from(exitNodes);
|
||||
const existingPorts = await db
|
||||
.select({
|
||||
listenPort: exitNodes.listenPort
|
||||
})
|
||||
.from(exitNodes);
|
||||
|
||||
// Find the first available port between 1024 and 65535
|
||||
let nextPort = config.getRawConfig().gerbil.start_port;
|
||||
@@ -136,7 +175,7 @@ async function getNextAvailablePort(): Promise<number> {
|
||||
}
|
||||
nextPort++;
|
||||
if (nextPort > 65535) {
|
||||
throw new Error('No available ports remaining in space');
|
||||
throw new Error("No available ports remaining in space");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import axios from 'axios';
|
||||
import logger from '@server/logger';
|
||||
import db from '@server/db';
|
||||
import { exitNodes } from '@server/db/schemas';
|
||||
import { db } from "@server/db";
|
||||
import { exitNodes } from '@server/db';
|
||||
import { eq } from 'drizzle-orm';
|
||||
|
||||
export async function addPeer(exitNodeId: number, peer: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { DrizzleError, eq } from "drizzle-orm";
|
||||
import { sites, resources, targets, exitNodes } from "@server/db/schemas";
|
||||
import db from "@server/db";
|
||||
import { sites, resources, targets, exitNodes } from "@server/db";
|
||||
import { db } from "@server/db";
|
||||
import logger from "@server/logger";
|
||||
import createHttpError from "http-errors";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
@@ -29,10 +29,11 @@ export const receiveBandwidth = async (
|
||||
for (const peer of bandwidthData) {
|
||||
const { publicKey, bytesIn, bytesOut } = peer;
|
||||
|
||||
// Find the site by public key
|
||||
const site = await trx.query.sites.findFirst({
|
||||
where: eq(sites.pubKey, publicKey)
|
||||
});
|
||||
const [site] = await trx
|
||||
.select()
|
||||
.from(sites)
|
||||
.where(eq(sites.pubKey, publicKey))
|
||||
.limit(1);
|
||||
|
||||
if (!site) {
|
||||
logger.warn(`Site not found for public key: ${publicKey}`);
|
||||
|
||||
Reference in New Issue
Block a user