mirror of
https://github.com/fosrl/pangolin.git
synced 2025-05-13 13:50:40 +01:00
added traefik config provider endpoint
This commit is contained in:
parent
a67463a518
commit
07bf2059c6
10 changed files with 134 additions and 472 deletions
|
@ -4,8 +4,8 @@ import path from "path";
|
|||
|
||||
export default defineConfig({
|
||||
dialect: "sqlite",
|
||||
schema: "server/db/schema.ts",
|
||||
out: "server/migrations",
|
||||
schema: path.join(__dirname, "server", "db", "schema.ts"),
|
||||
out: path.join(__dirname, "server", "migrations"),
|
||||
verbose: true,
|
||||
dbCredentials: {
|
||||
url: path.join(environment.CONFIG_PATH, "db", "db.sqlite"),
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
|
||||
import db from "@server/db";
|
||||
import path from "path";
|
||||
|
||||
const runMigrations = async () => {
|
||||
console.log("Running migrations...");
|
||||
try {
|
||||
migrate(db, { migrationsFolder: "./server/migrations" });
|
||||
migrate(db, {
|
||||
migrationsFolder: path.join(__dirname, "server/migrations"),
|
||||
});
|
||||
console.log("Migrations completed successfully.");
|
||||
} catch (error) {
|
||||
console.error("Error running migrations:", error);
|
||||
|
|
|
@ -41,8 +41,8 @@ export const targets = sqliteTable("targets", {
|
|||
targetId: integer("targetId").primaryKey({ autoIncrement: true }),
|
||||
resourceId: text("resourceId").references(() => resources.resourceId, { onDelete: "cascade" }),
|
||||
ip: text("ip").notNull(),
|
||||
method: text("method"),
|
||||
port: integer("port"),
|
||||
method: text("method").notNull(),
|
||||
port: integer("port").notNull(),
|
||||
protocol: text("protocol"),
|
||||
});
|
||||
|
||||
|
|
0
server/migrations/.gitkeep
Normal file
0
server/migrations/.gitkeep
Normal file
|
@ -1,59 +0,0 @@
|
|||
CREATE TABLE `exitNodes` (
|
||||
`exitNodeId` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`name` text NOT NULL,
|
||||
`address` text NOT NULL,
|
||||
`privateKey` text,
|
||||
`listenPort` integer
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `orgs` (
|
||||
`orgId` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`name` text NOT NULL,
|
||||
`domain` text NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `resources` (
|
||||
`resourceId` text(2048) PRIMARY KEY NOT NULL,
|
||||
`siteId` integer,
|
||||
`name` text NOT NULL,
|
||||
`subdomain` text,
|
||||
FOREIGN KEY (`siteId`) REFERENCES `sites`(`siteId`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `routes` (
|
||||
`routeId` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`exitNodeId` integer,
|
||||
`subnet` text NOT NULL,
|
||||
FOREIGN KEY (`exitNodeId`) REFERENCES `exitNodes`(`exitNodeId`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `sites` (
|
||||
`siteId` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`orgId` integer,
|
||||
`exitNode` integer,
|
||||
`name` text NOT NULL,
|
||||
`subdomain` text,
|
||||
`pubKey` text,
|
||||
`subnet` text,
|
||||
FOREIGN KEY (`orgId`) REFERENCES `orgs`(`orgId`) ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY (`exitNode`) REFERENCES `exitNodes`(`exitNodeId`) ON UPDATE no action ON DELETE set null
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `targets` (
|
||||
`targetId` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`resourceId` text,
|
||||
`ip` text NOT NULL,
|
||||
`method` text,
|
||||
`port` integer,
|
||||
`protocol` text,
|
||||
FOREIGN KEY (`resourceId`) REFERENCES `resources`(`resourceId`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `users` (
|
||||
`userId` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`orgId` integer,
|
||||
`name` text NOT NULL,
|
||||
`email` text NOT NULL,
|
||||
`groups` text,
|
||||
FOREIGN KEY (`orgId`) REFERENCES `orgs`(`orgId`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
|
@ -1,394 +0,0 @@
|
|||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "379ca2f9-068a-4289-8a23-001e7dc269b1",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"tables": {
|
||||
"exitNodes": {
|
||||
"name": "exitNodes",
|
||||
"columns": {
|
||||
"exitNodeId": {
|
||||
"name": "exitNodeId",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"address": {
|
||||
"name": "address",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"privateKey": {
|
||||
"name": "privateKey",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"listenPort": {
|
||||
"name": "listenPort",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"orgs": {
|
||||
"name": "orgs",
|
||||
"columns": {
|
||||
"orgId": {
|
||||
"name": "orgId",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"domain": {
|
||||
"name": "domain",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"resources": {
|
||||
"name": "resources",
|
||||
"columns": {
|
||||
"resourceId": {
|
||||
"name": "resourceId",
|
||||
"type": "text(2048)",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"siteId": {
|
||||
"name": "siteId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"subdomain": {
|
||||
"name": "subdomain",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"resources_siteId_sites_siteId_fk": {
|
||||
"name": "resources_siteId_sites_siteId_fk",
|
||||
"tableFrom": "resources",
|
||||
"tableTo": "sites",
|
||||
"columnsFrom": [
|
||||
"siteId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"siteId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"routes": {
|
||||
"name": "routes",
|
||||
"columns": {
|
||||
"routeId": {
|
||||
"name": "routeId",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"exitNodeId": {
|
||||
"name": "exitNodeId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"subnet": {
|
||||
"name": "subnet",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"routes_exitNodeId_exitNodes_exitNodeId_fk": {
|
||||
"name": "routes_exitNodeId_exitNodes_exitNodeId_fk",
|
||||
"tableFrom": "routes",
|
||||
"tableTo": "exitNodes",
|
||||
"columnsFrom": [
|
||||
"exitNodeId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"exitNodeId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"sites": {
|
||||
"name": "sites",
|
||||
"columns": {
|
||||
"siteId": {
|
||||
"name": "siteId",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"orgId": {
|
||||
"name": "orgId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"exitNode": {
|
||||
"name": "exitNode",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"subdomain": {
|
||||
"name": "subdomain",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"pubKey": {
|
||||
"name": "pubKey",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"subnet": {
|
||||
"name": "subnet",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"sites_orgId_orgs_orgId_fk": {
|
||||
"name": "sites_orgId_orgs_orgId_fk",
|
||||
"tableFrom": "sites",
|
||||
"tableTo": "orgs",
|
||||
"columnsFrom": [
|
||||
"orgId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"orgId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"sites_exitNode_exitNodes_exitNodeId_fk": {
|
||||
"name": "sites_exitNode_exitNodes_exitNodeId_fk",
|
||||
"tableFrom": "sites",
|
||||
"tableTo": "exitNodes",
|
||||
"columnsFrom": [
|
||||
"exitNode"
|
||||
],
|
||||
"columnsTo": [
|
||||
"exitNodeId"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"targets": {
|
||||
"name": "targets",
|
||||
"columns": {
|
||||
"targetId": {
|
||||
"name": "targetId",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"resourceId": {
|
||||
"name": "resourceId",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"ip": {
|
||||
"name": "ip",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"method": {
|
||||
"name": "method",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"port": {
|
||||
"name": "port",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"protocol": {
|
||||
"name": "protocol",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"targets_resourceId_resources_resourceId_fk": {
|
||||
"name": "targets_resourceId_resources_resourceId_fk",
|
||||
"tableFrom": "targets",
|
||||
"tableTo": "resources",
|
||||
"columnsFrom": [
|
||||
"resourceId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"resourceId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"users": {
|
||||
"name": "users",
|
||||
"columns": {
|
||||
"userId": {
|
||||
"name": "userId",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"orgId": {
|
||||
"name": "orgId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"groups": {
|
||||
"name": "groups",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"users_orgId_orgs_orgId_fk": {
|
||||
"name": "users_orgId_orgs_orgId_fk",
|
||||
"tableFrom": "users",
|
||||
"tableTo": "orgs",
|
||||
"columnsFrom": [
|
||||
"orgId"
|
||||
],
|
||||
"columnsTo": [
|
||||
"orgId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"version": "7",
|
||||
"dialect": "sqlite",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "6",
|
||||
"when": 1727557783608,
|
||||
"tag": "0000_wealthy_captain_midlands",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import { Router } from "express";
|
||||
import badger from "./badger/badger";
|
||||
import gerbil from "./gerbil/gerbil";
|
||||
import { traefikConfigProvider } from "@server/traefik-config-provider";
|
||||
|
||||
const unauth = Router();
|
||||
|
||||
|
@ -11,4 +12,6 @@ unauth.get("/", (_, res) => {
|
|||
unauth.use("/badger", badger);
|
||||
unauth.use("/gerbil", gerbil);
|
||||
|
||||
unauth.get("/traefik-config-provider", traefikConfigProvider);
|
||||
|
||||
export default unauth;
|
||||
|
|
52
server/traefik-config-provider/configSchema.ts
Normal file
52
server/traefik-config-provider/configSchema.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
export type DynamicTraefikConfig = {
|
||||
http: Http;
|
||||
};
|
||||
|
||||
export type Http = {
|
||||
routers: Routers;
|
||||
services: Services;
|
||||
middlewares: Middlewares;
|
||||
};
|
||||
|
||||
export type Routers = {
|
||||
[key: string]: Router;
|
||||
};
|
||||
|
||||
export type Router = {
|
||||
entryPoints: string[];
|
||||
middlewares: string[];
|
||||
service: string;
|
||||
rule: string;
|
||||
};
|
||||
|
||||
export type Services = {
|
||||
[key: string]: Service;
|
||||
};
|
||||
|
||||
export type Service = {
|
||||
loadBalancer: LoadBalancer;
|
||||
};
|
||||
|
||||
export type LoadBalancer = {
|
||||
servers: Server[];
|
||||
};
|
||||
|
||||
export type Server = {
|
||||
url: string;
|
||||
};
|
||||
|
||||
export type Middlewares = {
|
||||
[key: string]: MiddlewarePlugin;
|
||||
};
|
||||
|
||||
export type MiddlewarePlugin = {
|
||||
plugin: Plugin;
|
||||
};
|
||||
|
||||
export type Plugin = {
|
||||
[key: string]: MiddlewarePluginConfig;
|
||||
};
|
||||
|
||||
export type MiddlewarePluginConfig = {
|
||||
[key: string]: any;
|
||||
};
|
70
server/traefik-config-provider/index.ts
Normal file
70
server/traefik-config-provider/index.ts
Normal file
|
@ -0,0 +1,70 @@
|
|||
import { Request, Response } from "express";
|
||||
import db from "@server/db";
|
||||
import * as schema from "@server/db/schema";
|
||||
import { DynamicTraefikConfig } from "./configSchema";
|
||||
import { like } from "drizzle-orm";
|
||||
import logger from "@server/logger";
|
||||
|
||||
export async function traefikConfigProvider(_: Request, res: Response) {
|
||||
try {
|
||||
const targets = await getAllTargets();
|
||||
const traefikConfig = buildTraefikConfig(targets);
|
||||
res.status(200).send(traefikConfig);
|
||||
} catch (e) {
|
||||
logger.error(`Failed to build traefik config: ${e}`);
|
||||
res.status(500).send({ message: "Failed to build traefik config" });
|
||||
}
|
||||
}
|
||||
|
||||
export function buildTraefikConfig(
|
||||
targets: schema.Target[],
|
||||
): DynamicTraefikConfig {
|
||||
const middlewareName = "gerbil";
|
||||
|
||||
const http: DynamicTraefikConfig["http"] = {
|
||||
routers: {},
|
||||
services: {},
|
||||
middlewares: {
|
||||
[middlewareName]: {
|
||||
plugin: {
|
||||
[middlewareName]: {
|
||||
// These are temporary values
|
||||
APIEndpoint:
|
||||
"http://host.docker.internal:3001/api/v1/gerbil",
|
||||
ValidToken: "abc123",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
for (const target of targets) {
|
||||
const routerName = `router-${target.targetId}`;
|
||||
const serviceName = `service-${target.targetId}`;
|
||||
|
||||
http.routers[routerName] = {
|
||||
entryPoints: [target.method],
|
||||
middlewares: [middlewareName],
|
||||
service: serviceName,
|
||||
rule: `Host(\`${target.resourceId}\`)`, // assuming resourceId is a valid full hostname
|
||||
};
|
||||
|
||||
http.services[serviceName] = {
|
||||
loadBalancer: {
|
||||
servers: [
|
||||
{ url: `${target.method}://${target.ip}:${target.port}` },
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return { http } as DynamicTraefikConfig;
|
||||
}
|
||||
|
||||
export async function getAllTargets(): Promise<schema.Target[]> {
|
||||
const all = await db
|
||||
.select()
|
||||
.from(schema.targets)
|
||||
.where(like(schema.targets.resourceId, "%.%")); // any resourceId with a dot is a valid hostname; otherwise it's a UUID placeholder
|
||||
return all;
|
||||
}
|
Loading…
Reference in a new issue