瀏覽代碼

add traefik settings to config and use fullDomain

Milo Schwartz 9 月之前
父節點
當前提交
6d9731f071
共有 5 個文件被更改,包括 45 次插入52 次删除
  1. 5 0
      config/config.example.yml
  2. 5 1
      server/config.ts
  3. 3 1
      server/db/schema.ts
  4. 4 4
      server/index.ts
  5. 28 46
      server/routers/traefik/getTraefikConfig.ts

+ 5 - 0
config/config.example.yml

@@ -10,6 +10,11 @@ server:
     internal_hostname: localhost
     secure_cookies: "false"
 
+traefik:
+    cert_resolver: "letsencrypt"
+    http_entrypoint: "http"
+    https_entrypoint: "https"
+
 rate_limit:
     window_minutes: "1"
     max_requests: "100"

+ 5 - 1
server/config.ts

@@ -10,7 +10,6 @@ const environmentSchema = z.object({
     app: z.object({
         name: z.string(),
         base_url: z.string().url(),
-        base_domain: z.string(),
         log_level: z.enum(["debug", "info", "warn", "error"]),
         save_logs: z.string().transform((val) => val === "true"),
     }),
@@ -26,6 +25,11 @@ const environmentSchema = z.object({
         internal_hostname: z.string(),
         secure_cookies: z.string().transform((val) => val === "true"),
     }),
+    traefik: z.object({
+        http_entrypoint: z.string(),
+        https_entrypoint: z.string().optional(),
+        cert_resolver: z.string().optional(),
+    }),
     rate_limit: z.object({
         window_minutes: z
             .string()

+ 3 - 1
server/db/schema.ts

@@ -24,7 +24,8 @@ export const sites = sqliteTable("sites", {
 });
 
 export const resources = sqliteTable("resources", {
-    resourceId: text("resourceId", { length: 2048 }).primaryKey(),
+    resourceId: integer("resourceId").primaryKey({ autoIncrement: true }),
+    fullDomain: text("fullDomain", { length: 2048 }),
     siteId: integer("siteId").references(() => sites.siteId, {
         onDelete: "cascade",
     }),
@@ -45,6 +46,7 @@ export const targets = sqliteTable("targets", {
     port: integer("port").notNull(),
     protocol: text("protocol"),
     enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
+    ssl: integer("ssl", { mode: "boolean" }).notNull().default(false),
 });
 
 export const exitNodes = sqliteTable("exitNodes", {

+ 4 - 4
server/index.ts

@@ -39,8 +39,8 @@ app.prepare().then(() => {
     if (!dev) {
         externalServer.use(
             rateLimitMiddleware({
-                windowMin: 1,
-                max: 100,
+                windowMin: config.rate_limit.window_minutes,
+                max: config.rate_limit.max_requests,
                 type: "IP_ONLY",
             }),
         );
@@ -88,7 +88,7 @@ app.prepare().then(() => {
     internalServer.use(errorHandlerMiddleware);
 });
 
-declare global {
+declare global { // TODO: eventually make seperate types that extend express.Request
     namespace Express {
         interface Request {
             user?: User;
@@ -97,4 +97,4 @@ declare global {
             userOrgIds?: string[];
         }
     }
-}
+}

+ 28 - 46
server/routers/traefik/getTraefikConfig.ts

@@ -2,10 +2,9 @@ import { Request, Response } from "express";
 import db from "@server/db";
 import * as schema from "@server/db/schema";
 import { DynamicTraefikConfig } from "./configSchema";
-import { and, like, eq } from "drizzle-orm";
+import { and, like, eq, isNotNull } from "drizzle-orm";
 import logger from "@server/logger";
 import HttpCode from "@server/types/HttpCode";
-import env from "@server/config";
 import config from "@server/config";
 
 export async function traefikConfigProvider(_: Request, res: Response) {
@@ -22,46 +21,36 @@ export async function traefikConfigProvider(_: Request, res: Response) {
 }
 
 export function buildTraefikConfig(
-    targets: schema.Target[],
+    targets: schema.Target[]
 ): DynamicTraefikConfig {
+    if (!targets.length) {
+        return { http: {} } as DynamicTraefikConfig;
+    }
+
     const middlewareName = "badger";
 
+    const baseDomain = new URL(config.app.base_url).hostname;
+
+    const tls = {
+        certResolver: config.traefik.cert_resolver,
+        // domains: [ // TODO: figure out if this is neccessary
+        //     {
+        //         main: baseDomain,
+        //         sans: ["*." + baseDomain], 
+        //     },
+        // ],
+    };
+
     const http: any = {
-        routers: {
-            main: {
-                entryPoints: ["https"],
-                middlewares: [],
-                service: "service-main",
-                rule: "Host(`fossorial.io`)",
-                tls: {
-                    certResolver: "letsencrypt",
-                    domains: [
-                        {
-                            main: "fossorial.io",
-                            sans: ["*.fossorial.io"],
-                        },
-                    ],
-                },
-            },
-        },
-        services: {
-            "service-main": {
-                loadBalancer: {
-                    servers: [
-                        {
-                            url: `http://${config.server.internal_hostname}:${config.server.external_port}`,
-                        },
-                    ],
-                },
-            },
-        },
+        routers: {},
+        services: {},
         middlewares: {
             [middlewareName]: {
                 plugin: {
                     [middlewareName]: {
                         apiBaseUrl: new URL(
                             "/api/v1",
-                            `http://${config.server.internal_hostname}:${config.server.internal_port}`,
+                            `http://${config.server.internal_hostname}:${config.server.internal_port}`
                         ).href,
                         appBaseUrl: config.app.base_url,
                     },
@@ -74,19 +63,11 @@ export function buildTraefikConfig(
         const serviceName = `service-${target.targetId}`;
 
         http.routers![routerName] = {
-            entryPoints: ["https"],
+            entryPoints: [target.ssl ? config.traefik.https_entrypoint : config.traefik.https_entrypoint],
             middlewares: [middlewareName],
             service: serviceName,
             rule: `Host(\`${target.resourceId}\`)`, // assuming resourceId is a valid full hostname
-            tls: {
-                certResolver: "letsencrypt",
-                domains: [
-                    {
-                        main: "fossorial.io",
-                        sans: ["*.fossorial.io"],
-                    },
-                ],
-            },
+            ...(target.ssl ? { tls } : {}),
         };
 
         http.services![serviceName] = {
@@ -105,11 +86,12 @@ export async function getAllTargets(): Promise<schema.Target[]> {
     const all = await db
         .select()
         .from(schema.targets)
+        .innerJoin(schema.resources, eq(schema.targets.resourceId, schema.resources.resourceId))
         .where(
             and(
                 eq(schema.targets.enabled, true),
-                like(schema.targets.resourceId, "%.%"),
-            ),
-        ); // any resourceId with a dot is a valid hostname; otherwise it's a UUID placeholder
-    return all;
+                isNotNull(schema.resources.fullDomain)
+            )
+        );
+    return all.map((row) => row.targets);
 }