mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-16 07:40:04 +02:00
Support _FILE env vars
Closes https://github.com/fosrl/docs-v2/issues/141
This commit is contained in:
+13
-9
@@ -1,5 +1,6 @@
|
||||
import { drizzle as DrizzlePostgres } from "drizzle-orm/node-postgres";
|
||||
import { readConfigFile } from "@server/lib/readConfigFile";
|
||||
import { readEnvOrFile } from "@server/lib/getEnvOrYaml";
|
||||
import { withReplicas } from "drizzle-orm/pg-core";
|
||||
import { createPool } from "./poolConfig";
|
||||
|
||||
@@ -7,17 +8,20 @@ function createDb() {
|
||||
const config = readConfigFile();
|
||||
|
||||
// check the environment variables for postgres config first before the config file
|
||||
if (process.env.POSTGRES_CONNECTION_STRING) {
|
||||
const envConnectionString = readEnvOrFile("POSTGRES_CONNECTION_STRING");
|
||||
if (envConnectionString) {
|
||||
config.postgres = {
|
||||
connection_string: process.env.POSTGRES_CONNECTION_STRING
|
||||
connection_string: envConnectionString
|
||||
};
|
||||
if (process.env.POSTGRES_REPLICA_CONNECTION_STRINGS) {
|
||||
const replicas =
|
||||
process.env.POSTGRES_REPLICA_CONNECTION_STRINGS.split(",").map(
|
||||
(conn) => ({
|
||||
connection_string: conn.trim()
|
||||
})
|
||||
);
|
||||
const replicaConnectionStrings = readEnvOrFile(
|
||||
"POSTGRES_REPLICA_CONNECTION_STRINGS"
|
||||
);
|
||||
if (replicaConnectionStrings) {
|
||||
const replicas = replicaConnectionStrings
|
||||
.split(",")
|
||||
.map((conn) => ({
|
||||
connection_string: conn.trim()
|
||||
}));
|
||||
config.postgres.replicas = replicas;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { drizzle as DrizzlePostgres } from "drizzle-orm/node-postgres";
|
||||
import { readConfigFile } from "@server/lib/readConfigFile";
|
||||
import { readEnvOrFile } from "@server/lib/getEnvOrYaml";
|
||||
import { withReplicas } from "drizzle-orm/pg-core";
|
||||
import { build } from "@server/build";
|
||||
import { db as mainDb } from "./driver";
|
||||
@@ -17,7 +18,7 @@ function createLogsDb() {
|
||||
const logsConfig = config.postgres_logs;
|
||||
|
||||
// Check environment variable first
|
||||
let connectionString = process.env.POSTGRES_LOGS_CONNECTION_STRING;
|
||||
let connectionString = readEnvOrFile("POSTGRES_LOGS_CONNECTION_STRING");
|
||||
let replicaConnections: Array<{ connection_string: string }> = [];
|
||||
|
||||
if (!connectionString && logsConfig) {
|
||||
@@ -26,13 +27,15 @@ function createLogsDb() {
|
||||
}
|
||||
|
||||
// If POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS is set, use it
|
||||
if (process.env.POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS) {
|
||||
replicaConnections =
|
||||
process.env.POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS.split(",").map(
|
||||
(conn) => ({
|
||||
connection_string: conn.trim()
|
||||
})
|
||||
);
|
||||
const replicaConnectionStrings = readEnvOrFile(
|
||||
"POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS"
|
||||
);
|
||||
if (replicaConnectionStrings) {
|
||||
replicaConnections = replicaConnectionStrings
|
||||
.split(",")
|
||||
.map((conn) => ({
|
||||
connection_string: conn.trim()
|
||||
}));
|
||||
}
|
||||
|
||||
// If no logs database is configured, fall back to main database
|
||||
|
||||
@@ -6,6 +6,7 @@ import fs from "fs";
|
||||
import { APP_PATH } from "@server/lib/consts";
|
||||
import { existsSync, mkdirSync } from "fs";
|
||||
import logger from "@server/logger";
|
||||
import { readEnvOrFile } from "@server/lib/getEnvOrYaml";
|
||||
|
||||
export const location = path.join(APP_PATH, "db", "db.sqlite");
|
||||
export const exists = checkFileExists(location);
|
||||
@@ -19,7 +20,7 @@ function createDb() {
|
||||
: undefined;
|
||||
const sqlite = new Database(location, { verbose });
|
||||
|
||||
if (process.env.ENABLE_SQLITE_WAL_MODE == "true") {
|
||||
if (readEnvOrFile("ENABLE_SQLITE_WAL_MODE") == "true") {
|
||||
// Enable WAL mode — allows concurrent readers + single writer, preventing
|
||||
// contention across subsystems (verifySession, Traefik, audit, ping).
|
||||
// NOTE: journal_mode persists in the DB file once set; unsetting this
|
||||
|
||||
@@ -1,3 +1,37 @@
|
||||
export const getEnvOrYaml = (envVar: string) => (valFromYaml: any) => {
|
||||
return process.env[envVar] ?? valFromYaml;
|
||||
import fs from "fs";
|
||||
|
||||
// Resolves an environment variable, also honoring a `<envVar>_FILE` variant
|
||||
// that points to a file whose (trimmed) contents should be used as the
|
||||
// value. This is the common convention for consuming Docker/Swarm secrets
|
||||
// (e.g. mounted at /run/secrets/...) without putting the raw value in the
|
||||
// container's environment.
|
||||
export const readEnvOrFile = (envVar: string): string | undefined => {
|
||||
const fileEnvVar = `${envVar}_FILE`;
|
||||
const filePath = process.env[fileEnvVar];
|
||||
|
||||
if (filePath) {
|
||||
if (process.env[envVar]) {
|
||||
throw new Error(
|
||||
`Both ${envVar} and ${fileEnvVar} are set. Please set only one.`
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
return fs.readFileSync(filePath, "utf8").trim();
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to read ${fileEnvVar} (${filePath}): ${
|
||||
error instanceof Error ? error.message : error
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return process.env[envVar];
|
||||
};
|
||||
|
||||
export const getEnvOrYaml =
|
||||
(envVar: string) =>
|
||||
(valFromYaml: string | undefined): string | undefined => {
|
||||
return readEnvOrFile(envVar) ?? valFromYaml;
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as yaml from "js-yaml";
|
||||
import { configFilePath1, configFilePath2 } from "./consts";
|
||||
import { z } from "zod";
|
||||
import stoi from "./stoi";
|
||||
import { getEnvOrYaml } from "./getEnvOrYaml";
|
||||
import { getEnvOrYaml, readEnvOrFile } from "./getEnvOrYaml";
|
||||
|
||||
const portSchema = z.number().positive().gt(0).lte(65535);
|
||||
|
||||
@@ -160,14 +160,17 @@ export const configSchema = z
|
||||
.boolean()
|
||||
.optional()
|
||||
.default(false)
|
||||
.transform((val) =>
|
||||
process.env.ENABLE_AI_GATEWAY_CLIENT_IP_HEADER !==
|
||||
undefined
|
||||
? process.env.ENABLE_AI_GATEWAY_CLIENT_IP_HEADER ===
|
||||
"true"
|
||||
: val
|
||||
),
|
||||
secret: z.string().pipe(z.string().min(8)).optional(),
|
||||
.transform((val) => {
|
||||
const envVal = readEnvOrFile(
|
||||
"ENABLE_AI_GATEWAY_CLIENT_IP_HEADER"
|
||||
);
|
||||
return envVal !== undefined ? envVal === "true" : val;
|
||||
}),
|
||||
secret: z
|
||||
.string()
|
||||
.pipe(z.string().min(8))
|
||||
.optional()
|
||||
.transform(getEnvOrYaml("SERVER_SECRET")),
|
||||
maxmind_db_path: z.string().optional(),
|
||||
maxmind_asn_path: z.string().optional()
|
||||
})
|
||||
@@ -198,7 +201,8 @@ export const configSchema = z
|
||||
dashboard_session_length_hours: 720,
|
||||
resource_session_length_hours: 720,
|
||||
trust_proxy: 1,
|
||||
enable_ai_gateway_client_ip_header: false
|
||||
enable_ai_gateway_client_ip_header: false,
|
||||
secret: undefined
|
||||
}),
|
||||
postgres: z
|
||||
.object({
|
||||
@@ -499,10 +503,7 @@ export const configSchema = z
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
// If hybrid is not defined, server secret must be defined. If its not defined already then pull it from env
|
||||
if (data.server?.secret === undefined) {
|
||||
data.server.secret = process.env.SERVER_SECRET;
|
||||
}
|
||||
// If hybrid is not defined, server secret must be defined
|
||||
return (
|
||||
data.server?.secret !== undefined &&
|
||||
data.server.secret.length > 0
|
||||
|
||||
Reference in New Issue
Block a user