From c93b36c757b318395e2f14f50c2fdffc51048fb4 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Sat, 8 Mar 2025 18:05:53 -0500 Subject: [PATCH] remove environment variable support and config file autogeneration --- Dockerfile | 1 - config/traefik/dynamic_config.example.yml | 53 ------- config/traefik/traefik_config.example.yml | 44 ------ server/lib/config.ts | 168 +--------------------- server/setup/clearStaleData.ts | 16 +-- server/setup/migrations.ts | 6 +- 6 files changed, 17 insertions(+), 271 deletions(-) delete mode 100644 config/traefik/dynamic_config.example.yml delete mode 100644 config/traefik/traefik_config.example.yml diff --git a/Dockerfile b/Dockerfile index 4a54d92..389e1e1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,6 @@ COPY --from=builder /app/.next ./.next COPY --from=builder /app/dist ./dist COPY --from=builder /app/init ./dist/init -COPY config/config.example.yml ./dist/config.example.yml COPY config/traefik/traefik_config.example.yml ./dist/traefik_config.example.yml COPY config/traefik/dynamic_config.example.yml ./dist/dynamic_config.example.yml COPY server/db/names.json ./dist/names.json diff --git a/config/traefik/dynamic_config.example.yml b/config/traefik/dynamic_config.example.yml deleted file mode 100644 index cca0ea1..0000000 --- a/config/traefik/dynamic_config.example.yml +++ /dev/null @@ -1,53 +0,0 @@ -http: - middlewares: - redirect-to-https: - redirectScheme: - scheme: https - - routers: - # HTTP to HTTPS redirect router - main-app-router-redirect: - rule: "Host(`{{.DashboardDomain}}`)" - service: next-service - entryPoints: - - web - middlewares: - - redirect-to-https - - # Next.js router (handles everything except API and WebSocket paths) - next-router: - rule: "Host(`{{.DashboardDomain}}`) && !PathPrefix(`/api/v1`)" - service: next-service - entryPoints: - - websecure - tls: - certResolver: letsencrypt - - # API router (handles /api/v1 paths) - api-router: - rule: "Host(`{{.DashboardDomain}}`) && PathPrefix(`/api/v1`)" - service: api-service - entryPoints: - - websecure - tls: - certResolver: letsencrypt - - # WebSocket router - ws-router: - rule: "Host(`{{.DashboardDomain}}`)" - service: api-service - entryPoints: - - websecure - tls: - certResolver: letsencrypt - - services: - next-service: - loadBalancer: - servers: - - url: "http://pangolin:{{.NEXT_PORT}}" # Next.js server - - api-service: - loadBalancer: - servers: - - url: "http://pangolin:{{.EXTERNAL_PORT}}" # API/WebSocket server diff --git a/config/traefik/traefik_config.example.yml b/config/traefik/traefik_config.example.yml deleted file mode 100644 index 01d0590..0000000 --- a/config/traefik/traefik_config.example.yml +++ /dev/null @@ -1,44 +0,0 @@ -api: - insecure: true - dashboard: true - -providers: - http: - endpoint: "http://pangolin:{{.INTERNAL_PORT}}/api/v1/traefik-config" - pollInterval: "5s" - file: - filename: "/etc/traefik/dynamic_config.yml" - -experimental: - plugins: - badger: - moduleName: "github.com/fosrl/badger" - version: "v1.0.0-beta.3" - -log: - level: "INFO" - format: "common" - -certificatesResolvers: - letsencrypt: - acme: - httpChallenge: - entryPoint: web - email: "{{.LetsEncryptEmail}}" - storage: "/letsencrypt/acme.json" - caServer: "https://acme-v02.api.letsencrypt.org/directory" - -entryPoints: - web: - address: ":80" - websecure: - address: ":443" - transport: - respondingTimeouts: - readTimeout: "30m" - http: - tls: - certResolver: "letsencrypt" - -serversTransport: - insecureSkipVerify: true diff --git a/server/lib/config.ts b/server/lib/config.ts index ee6d6c5..e799dea 100644 --- a/server/lib/config.ts +++ b/server/lib/config.ts @@ -1,11 +1,9 @@ import fs from "fs"; import yaml from "js-yaml"; -import path from "path"; import { z } from "zod"; import { fromError } from "zod-validation-error"; import { __DIRNAME, - APP_PATH, APP_VERSION, configFilePath1, configFilePath2 @@ -14,12 +12,6 @@ import { passwordSchema } from "@server/auth/passwordSchema"; import stoi from "./stoi"; const portSchema = z.number().positive().gt(0).lte(65535); -// const hostnameSchema = z -// .string() -// .regex( -// /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)+([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$/ -// ) -// .or(z.literal("localhost")); const getEnvOrYaml = (envVar: string) => (valFromYaml: any) => { return process.env[envVar] ?? valFromYaml; @@ -31,7 +23,6 @@ const configSchema = z.object({ .string() .url() .optional() - .transform(getEnvOrYaml("APP_DASHBOARDURL")) .pipe(z.string().url()) .transform((url) => url.toLowerCase()), log_level: z.enum(["debug", "info", "warn", "error"]), @@ -63,37 +54,11 @@ const configSchema = z.object({ { message: "At least one domain must be defined" } - ) - .refine( - (domains) => { - const envBaseDomain = process.env.APP_BASE_DOMAIN; - - if (envBaseDomain) { - return z.string().nonempty().safeParse(envBaseDomain).success; - } - - return true; - }, - { - message: "APP_BASE_DOMAIN must be a valid hostname" - } ), server: z.object({ - external_port: portSchema - .optional() - .transform(getEnvOrYaml("SERVER_EXTERNALPORT")) - .transform(stoi) - .pipe(portSchema), - internal_port: portSchema - .optional() - .transform(getEnvOrYaml("SERVER_INTERNALPORT")) - .transform(stoi) - .pipe(portSchema), - next_port: portSchema - .optional() - .transform(getEnvOrYaml("SERVER_NEXTPORT")) - .transform(stoi) - .pipe(portSchema), + external_port: portSchema.optional().transform(stoi).pipe(portSchema), + internal_port: portSchema.optional().transform(stoi).pipe(portSchema), + next_port: portSchema.optional().transform(stoi).pipe(portSchema), internal_hostname: z.string().transform((url) => url.toLowerCase()), session_cookie_name: z.string(), resource_access_token_param: z.string(), @@ -126,15 +91,10 @@ const configSchema = z.object({ additional_middlewares: z.array(z.string()).optional() }), gerbil: z.object({ - start_port: portSchema - .optional() - .transform(getEnvOrYaml("GERBIL_STARTPORT")) - .transform(stoi) - .pipe(portSchema), + start_port: portSchema.optional().transform(stoi).pipe(portSchema), base_endpoint: z .string() .optional() - .transform(getEnvOrYaml("GERBIL_BASEENDPOINT")) .pipe(z.string()) .transform((url) => url.toLowerCase()), use_subdomain: z.boolean(), @@ -197,10 +157,6 @@ export class Config { constructor() { this.loadConfig(); - - if (process.env.GENERATE_TRAEFIK_CONFIG === "true") { - this.createTraefikConfig(); - } } public loadConfig() { @@ -226,44 +182,9 @@ export class Config { environment = loadConfig(configFilePath2); } if (!environment) { - const exampleConfigPath = path.join( - __DIRNAME, - "config.example.yml" + throw new Error( + "No configuration file found. Please create one. https://docs.fossorial.io/" ); - if (fs.existsSync(exampleConfigPath)) { - try { - const exampleConfigContent = fs.readFileSync( - exampleConfigPath, - "utf8" - ); - fs.writeFileSync( - configFilePath1, - exampleConfigContent, - "utf8" - ); - environment = loadConfig(configFilePath1); - } catch (error) { - console.log( - "See the docs for information about what to include in the configuration file: https://docs.fossorial.io/Pangolin/Configuration/config" - ); - if (error instanceof Error) { - throw new Error( - `Error creating configuration file from example: ${ - error.message - }` - ); - } - throw error; - } - } else { - throw new Error( - "No configuration file found and no example configuration available" - ); - } - } - - if (!environment) { - throw new Error("No configuration file found"); } const parsedConfig = configSchema.safeParse(environment); @@ -309,17 +230,6 @@ export class Config { : "false"; process.env.DASHBOARD_URL = parsedConfig.data.app.dashboard_url; - if (process.env.APP_BASE_DOMAIN) { - console.log( - `DEPRECATED! APP_BASE_DOMAIN is deprecated and will be removed in a future release. Use the domains section in the configuration file instead. See https://docs.fossorial.io/Pangolin/Configuration/config for more information.` - ); - - parsedConfig.data.domains.domain1 = { - base_domain: process.env.APP_BASE_DOMAIN, - cert_resolver: "letsencrypt" - }; - } - this.rawConfig = parsedConfig.data; } @@ -336,72 +246,6 @@ export class Config { public getDomain(domainId: string) { return this.rawConfig.domains[domainId]; } - - private createTraefikConfig() { - try { - // check if traefik_config.yml and dynamic_config.yml exists in APP_PATH/traefik - const defaultTraefikConfigPath = path.join( - __DIRNAME, - "traefik_config.example.yml" - ); - const defaultDynamicConfigPath = path.join( - __DIRNAME, - "dynamic_config.example.yml" - ); - - const traefikPath = path.join(APP_PATH, "traefik"); - if (!fs.existsSync(traefikPath)) { - return; - } - - // load default configs - let traefikConfig = fs.readFileSync( - defaultTraefikConfigPath, - "utf8" - ); - let dynamicConfig = fs.readFileSync( - defaultDynamicConfigPath, - "utf8" - ); - - traefikConfig = traefikConfig - .split("{{.LetsEncryptEmail}}") - .join(this.rawConfig.users.server_admin.email); - traefikConfig = traefikConfig - .split("{{.INTERNAL_PORT}}") - .join(this.rawConfig.server.internal_port.toString()); - - dynamicConfig = dynamicConfig - .split("{{.DashboardDomain}}") - .join(new URL(this.rawConfig.app.dashboard_url).hostname); - dynamicConfig = dynamicConfig - .split("{{.NEXT_PORT}}") - .join(this.rawConfig.server.next_port.toString()); - dynamicConfig = dynamicConfig - .split("{{.EXTERNAL_PORT}}") - .join(this.rawConfig.server.external_port.toString()); - - // write thiese to the traefik directory - const traefikConfigPath = path.join( - traefikPath, - "traefik_config.yml" - ); - const dynamicConfigPath = path.join( - traefikPath, - "dynamic_config.yml" - ); - - fs.writeFileSync(traefikConfigPath, traefikConfig, "utf8"); - fs.writeFileSync(dynamicConfigPath, dynamicConfig, "utf8"); - - console.log("Traefik configuration files created"); - } catch (e) { - console.log( - "Failed to generate the Traefik configuration files. Please create them manually." - ); - console.error(e); - } - } } export const config = new Config(); diff --git a/server/setup/clearStaleData.ts b/server/setup/clearStaleData.ts index 1378919..ccadfc3 100644 --- a/server/setup/clearStaleData.ts +++ b/server/setup/clearStaleData.ts @@ -18,7 +18,7 @@ export async function clearStaleData() { .delete(sessions) .where(lt(sessions.expiresAt, new Date().getTime())); } catch (e) { - logger.error("Error clearing expired sessions:", e); + logger.warn("Error clearing expired sessions:", e); } try { @@ -26,7 +26,7 @@ export async function clearStaleData() { .delete(newtSessions) .where(lt(newtSessions.expiresAt, new Date().getTime())); } catch (e) { - logger.error("Error clearing expired newtSessions:", e); + logger.warn("Error clearing expired newtSessions:", e); } try { @@ -34,7 +34,7 @@ export async function clearStaleData() { .delete(emailVerificationCodes) .where(lt(emailVerificationCodes.expiresAt, new Date().getTime())); } catch (e) { - logger.error("Error clearing expired emailVerificationCodes:", e); + logger.warn("Error clearing expired emailVerificationCodes:", e); } try { @@ -42,7 +42,7 @@ export async function clearStaleData() { .delete(passwordResetTokens) .where(lt(passwordResetTokens.expiresAt, new Date().getTime())); } catch (e) { - logger.error("Error clearing expired passwordResetTokens:", e); + logger.warn("Error clearing expired passwordResetTokens:", e); } try { @@ -50,7 +50,7 @@ export async function clearStaleData() { .delete(userInvites) .where(lt(userInvites.expiresAt, new Date().getTime())); } catch (e) { - logger.error("Error clearing expired userInvites:", e); + logger.warn("Error clearing expired userInvites:", e); } try { @@ -58,7 +58,7 @@ export async function clearStaleData() { .delete(resourceAccessToken) .where(lt(resourceAccessToken.expiresAt, new Date().getTime())); } catch (e) { - logger.error("Error clearing expired resourceAccessToken:", e); + logger.warn("Error clearing expired resourceAccessToken:", e); } try { @@ -66,7 +66,7 @@ export async function clearStaleData() { .delete(resourceSessions) .where(lt(resourceSessions.expiresAt, new Date().getTime())); } catch (e) { - logger.error("Error clearing expired resourceSessions:", e); + logger.warn("Error clearing expired resourceSessions:", e); } try { @@ -74,6 +74,6 @@ export async function clearStaleData() { .delete(resourceOtp) .where(lt(resourceOtp.expiresAt, new Date().getTime())); } catch (e) { - logger.error("Error clearing expired resourceOtp:", e); + logger.warn("Error clearing expired resourceOtp:", e); } } diff --git a/server/setup/migrations.ts b/server/setup/migrations.ts index 5ea065d..c3af101 100644 --- a/server/setup/migrations.ts +++ b/server/setup/migrations.ts @@ -40,9 +40,6 @@ const migrations = [ await run(); async function run() { - // backup the database - backupDb(); - // run the migrations await runMigrations(); } @@ -127,6 +124,9 @@ async function executeScripts() { console.log(`Running migration ${migration.version}`); try { + // Backup the database before running the migration + backupDb(); + await migration.run(); // Update version in database