Merge branch 'aig' of https://github.com/fosrl/pangolin into aig

This commit is contained in:
miloschwartz
2026-08-05 16:55:58 -04:00
3 changed files with 101 additions and 32 deletions
+36 -1
View File
@@ -6,13 +6,48 @@ import fs from "fs";
import { APP_PATH } from "@server/lib/consts";
import { existsSync, mkdirSync } from "fs";
// Temporary diagnostic trace for the random better-sqlite3 native aborts
// (Statement::~Statement -> RemoveEnvironmentCleanupHook assertion). That
// abort is a hard SIGABRT from native code, so it bypasses uncaughtException/
// unhandledRejection and can outrun winston's async file transport. This
// writes every statement text synchronously (fsync'd via appendFileSync) so
// the statements immediately preceding a crash survive it. better-sqlite3's
// `verbose` hook fires for BEGIN/SAVEPOINT/RELEASE/COMMIT/ROLLBACK too, since
// those are just prepared statements under the hood - so this also lets us
// see if two "transactions" ever overlap on the shared connection.
// Enable with SQL_TRACE=true; remove once the crash is root-caused.
function sqlTraceVerbose():
| ((message: unknown, ...args: unknown[]) => void)
| undefined {
if (process.env.SQL_TRACE !== "true") {
return undefined;
}
const traceLogDir = path.join(APP_PATH, "logs");
if (!existsSync(traceLogDir)) {
mkdirSync(traceLogDir, { recursive: true });
}
const traceLogPath = path.join(traceLogDir, "sql-trace.log");
let seq = 0;
return (message: unknown) => {
seq += 1;
const line = `${new Date().toISOString()} pid=${process.pid} #${seq} ${String(message).replace(/\s+/g, " ").trim()}\n`;
try {
fs.appendFileSync(traceLogPath, line);
} catch {
// best-effort diagnostic logging only
}
};
}
export const location = path.join(APP_PATH, "db", "db.sqlite");
export const exists = checkFileExists(location);
bootstrapVolume();
function createDb() {
const sqlite = new Database(location);
const verbose =
process.env.QUERY_LOGGING == "true" ? sqlTraceVerbose() : undefined;
const sqlite = new Database(location, { verbose });
if (process.env.ENABLE_SQLITE_WAL_MODE == "true") {
// Enable WAL mode — allows concurrent readers + single writer, preventing
+38 -29
View File
@@ -1542,6 +1542,12 @@ export async function getTraefikConfig(
aiGatewayHost = undefined;
}
// The p-host smuggling above is only necessary when the AI gateway
// is overridden to a different host than the resource's own. In the
// default case, leave the Host header untouched so it's visible on
// the other end.
const aiGatewayOverride = config.getRawConfig().server.ai_gateway_override;
// Public inference resources: same TLS/cert-resolver handling as
// plain http-mode resources, but the service points at the AI
// gateway instead of any real backend targets.
@@ -1607,23 +1613,24 @@ export async function getTraefikConfig(
}
}
const irHeadersMiddlewareName = `${irKey}-headers-middleware`;
config_output.http.middlewares[irHeadersMiddlewareName] = {
headers: {
customRequestHeaders: {
...(aiGatewayHost ? { Host: aiGatewayHost } : {}),
"p-host": fullDomain
}
}
};
const additionalMiddlewares =
config.getRawConfig().traefik.additional_middlewares || [];
const routerMiddlewares = [
badgerMiddlewareName,
irHeadersMiddlewareName,
...additionalMiddlewares
];
const routerMiddlewares = [badgerMiddlewareName];
if (aiGatewayOverride) {
const irHeadersMiddlewareName = `${irKey}-headers-middleware`;
config_output.http.middlewares[irHeadersMiddlewareName] = {
headers: {
customRequestHeaders: {
...(aiGatewayHost ? { Host: aiGatewayHost } : {}),
"p-host": fullDomain
}
}
};
routerMiddlewares.push(irHeadersMiddlewareName);
}
routerMiddlewares.push(...additionalMiddlewares);
if (ir.ssl) {
config_output.http.routers[routerName + "-redirect"] = {
@@ -1705,22 +1712,24 @@ export async function getTraefikConfig(
}
}
const srHeadersMiddlewareName = `${srKey}-headers-middleware`;
config_output.http.middlewares[srHeadersMiddlewareName] = {
headers: {
customRequestHeaders: {
...(aiGatewayHost ? { Host: aiGatewayHost } : {}),
"p-host": alias
}
}
};
const additionalMiddlewares =
config.getRawConfig().traefik.additional_middlewares || [];
const routerMiddlewares = [
srHeadersMiddlewareName,
...additionalMiddlewares
];
const routerMiddlewares: string[] = [];
if (aiGatewayOverride) {
const srHeadersMiddlewareName = `${srKey}-headers-middleware`;
config_output.http.middlewares[srHeadersMiddlewareName] = {
headers: {
customRequestHeaders: {
...(aiGatewayHost ? { Host: aiGatewayHost } : {}),
"p-host": alias
}
}
};
routerMiddlewares.push(srHeadersMiddlewareName);
}
routerMiddlewares.push(...additionalMiddlewares);
if (sr.ssl) {
config_output.http.routers[routerName + "-redirect"] = {
+27 -2
View File
@@ -1,6 +1,6 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { sites, exitNodes, ExitNode } from "@server/db";
import { sites, exitNodes, ExitNode, clients } from "@server/db";
import { db } from "@server/db";
import { eq, isNotNull, and } from "drizzle-orm";
import HttpCode from "@server/types/HttpCode";
@@ -93,7 +93,23 @@ export async function generateGerbilConfig(exitNode: ExitNode) {
)
);
const peers = await Promise.all(
const clientsRes = await db
.select()
.from(clients)
.where(
and(
eq(clients.exitNodeId, exitNode.exitNodeId),
isNotNull(clients.pubKey),
isNotNull(clients.exitNodeSubnet)
)
);
let peers: {
publicKey: string | null;
allowedIps: string[];
}[] = [];
const sitePeers = await Promise.all(
sitesRes.map(async (site) => {
if (site.type === "wireguard") {
return {
@@ -113,6 +129,15 @@ export async function generateGerbilConfig(exitNode: ExitNode) {
})
);
const clientPeers = clientsRes.map((client) => {
return {
publicKey: client.pubKey,
allowedIps: [client.exitNodeSubnet!]
};
});
peers = [...sitePeers, ...clientPeers];
const configResponse: GetConfigResponse = {
listenPort: exitNode.listenPort || 51820,
ipAddress: exitNode.address,