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:
+12
-8
@@ -1,5 +1,6 @@
|
|||||||
import { drizzle as DrizzlePostgres } from "drizzle-orm/node-postgres";
|
import { drizzle as DrizzlePostgres } from "drizzle-orm/node-postgres";
|
||||||
import { readConfigFile } from "@server/lib/readConfigFile";
|
import { readConfigFile } from "@server/lib/readConfigFile";
|
||||||
|
import { readEnvOrFile } from "@server/lib/getEnvOrYaml";
|
||||||
import { withReplicas } from "drizzle-orm/pg-core";
|
import { withReplicas } from "drizzle-orm/pg-core";
|
||||||
import { createPool } from "./poolConfig";
|
import { createPool } from "./poolConfig";
|
||||||
|
|
||||||
@@ -7,17 +8,20 @@ function createDb() {
|
|||||||
const config = readConfigFile();
|
const config = readConfigFile();
|
||||||
|
|
||||||
// check the environment variables for postgres config first before the config file
|
// 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 = {
|
config.postgres = {
|
||||||
connection_string: process.env.POSTGRES_CONNECTION_STRING
|
connection_string: envConnectionString
|
||||||
};
|
};
|
||||||
if (process.env.POSTGRES_REPLICA_CONNECTION_STRINGS) {
|
const replicaConnectionStrings = readEnvOrFile(
|
||||||
const replicas =
|
"POSTGRES_REPLICA_CONNECTION_STRINGS"
|
||||||
process.env.POSTGRES_REPLICA_CONNECTION_STRINGS.split(",").map(
|
|
||||||
(conn) => ({
|
|
||||||
connection_string: conn.trim()
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
if (replicaConnectionStrings) {
|
||||||
|
const replicas = replicaConnectionStrings
|
||||||
|
.split(",")
|
||||||
|
.map((conn) => ({
|
||||||
|
connection_string: conn.trim()
|
||||||
|
}));
|
||||||
config.postgres.replicas = replicas;
|
config.postgres.replicas = replicas;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { drizzle as DrizzlePostgres } from "drizzle-orm/node-postgres";
|
import { drizzle as DrizzlePostgres } from "drizzle-orm/node-postgres";
|
||||||
import { readConfigFile } from "@server/lib/readConfigFile";
|
import { readConfigFile } from "@server/lib/readConfigFile";
|
||||||
|
import { readEnvOrFile } from "@server/lib/getEnvOrYaml";
|
||||||
import { withReplicas } from "drizzle-orm/pg-core";
|
import { withReplicas } from "drizzle-orm/pg-core";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import { db as mainDb } from "./driver";
|
import { db as mainDb } from "./driver";
|
||||||
@@ -17,7 +18,7 @@ function createLogsDb() {
|
|||||||
const logsConfig = config.postgres_logs;
|
const logsConfig = config.postgres_logs;
|
||||||
|
|
||||||
// Check environment variable first
|
// 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 }> = [];
|
let replicaConnections: Array<{ connection_string: string }> = [];
|
||||||
|
|
||||||
if (!connectionString && logsConfig) {
|
if (!connectionString && logsConfig) {
|
||||||
@@ -26,13 +27,15 @@ function createLogsDb() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS is set, use it
|
// If POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS is set, use it
|
||||||
if (process.env.POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS) {
|
const replicaConnectionStrings = readEnvOrFile(
|
||||||
replicaConnections =
|
"POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS"
|
||||||
process.env.POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS.split(",").map(
|
|
||||||
(conn) => ({
|
|
||||||
connection_string: conn.trim()
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
if (replicaConnectionStrings) {
|
||||||
|
replicaConnections = replicaConnectionStrings
|
||||||
|
.split(",")
|
||||||
|
.map((conn) => ({
|
||||||
|
connection_string: conn.trim()
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no logs database is configured, fall back to main database
|
// 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 { APP_PATH } from "@server/lib/consts";
|
||||||
import { existsSync, mkdirSync } from "fs";
|
import { existsSync, mkdirSync } from "fs";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
|
import { readEnvOrFile } from "@server/lib/getEnvOrYaml";
|
||||||
|
|
||||||
export const location = path.join(APP_PATH, "db", "db.sqlite");
|
export const location = path.join(APP_PATH, "db", "db.sqlite");
|
||||||
export const exists = checkFileExists(location);
|
export const exists = checkFileExists(location);
|
||||||
@@ -19,7 +20,7 @@ function createDb() {
|
|||||||
: undefined;
|
: undefined;
|
||||||
const sqlite = new Database(location, { verbose });
|
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
|
// Enable WAL mode — allows concurrent readers + single writer, preventing
|
||||||
// contention across subsystems (verifySession, Traefik, audit, ping).
|
// contention across subsystems (verifySession, Traefik, audit, ping).
|
||||||
// NOTE: journal_mode persists in the DB file once set; unsetting this
|
// NOTE: journal_mode persists in the DB file once set; unsetting this
|
||||||
|
|||||||
@@ -1,3 +1,37 @@
|
|||||||
export const getEnvOrYaml = (envVar: string) => (valFromYaml: any) => {
|
import fs from "fs";
|
||||||
return process.env[envVar] ?? valFromYaml;
|
|
||||||
|
// 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 { configFilePath1, configFilePath2 } from "./consts";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import stoi from "./stoi";
|
import stoi from "./stoi";
|
||||||
import { getEnvOrYaml } from "./getEnvOrYaml";
|
import { getEnvOrYaml, readEnvOrFile } from "./getEnvOrYaml";
|
||||||
|
|
||||||
const portSchema = z.number().positive().gt(0).lte(65535);
|
const portSchema = z.number().positive().gt(0).lte(65535);
|
||||||
|
|
||||||
@@ -160,14 +160,17 @@ export const configSchema = z
|
|||||||
.boolean()
|
.boolean()
|
||||||
.optional()
|
.optional()
|
||||||
.default(false)
|
.default(false)
|
||||||
.transform((val) =>
|
.transform((val) => {
|
||||||
process.env.ENABLE_AI_GATEWAY_CLIENT_IP_HEADER !==
|
const envVal = readEnvOrFile(
|
||||||
undefined
|
"ENABLE_AI_GATEWAY_CLIENT_IP_HEADER"
|
||||||
? process.env.ENABLE_AI_GATEWAY_CLIENT_IP_HEADER ===
|
);
|
||||||
"true"
|
return envVal !== undefined ? envVal === "true" : val;
|
||||||
: val
|
}),
|
||||||
),
|
secret: z
|
||||||
secret: z.string().pipe(z.string().min(8)).optional(),
|
.string()
|
||||||
|
.pipe(z.string().min(8))
|
||||||
|
.optional()
|
||||||
|
.transform(getEnvOrYaml("SERVER_SECRET")),
|
||||||
maxmind_db_path: z.string().optional(),
|
maxmind_db_path: z.string().optional(),
|
||||||
maxmind_asn_path: z.string().optional()
|
maxmind_asn_path: z.string().optional()
|
||||||
})
|
})
|
||||||
@@ -198,7 +201,8 @@ export const configSchema = z
|
|||||||
dashboard_session_length_hours: 720,
|
dashboard_session_length_hours: 720,
|
||||||
resource_session_length_hours: 720,
|
resource_session_length_hours: 720,
|
||||||
trust_proxy: 1,
|
trust_proxy: 1,
|
||||||
enable_ai_gateway_client_ip_header: false
|
enable_ai_gateway_client_ip_header: false,
|
||||||
|
secret: undefined
|
||||||
}),
|
}),
|
||||||
postgres: z
|
postgres: z
|
||||||
.object({
|
.object({
|
||||||
@@ -499,10 +503,7 @@ export const configSchema = z
|
|||||||
)
|
)
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
// If hybrid is not defined, server secret must be defined. If its not defined already then pull it from env
|
// If hybrid is not defined, server secret must be defined
|
||||||
if (data.server?.secret === undefined) {
|
|
||||||
data.server.secret = process.env.SERVER_SECRET;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
data.server?.secret !== undefined &&
|
data.server?.secret !== undefined &&
|
||||||
data.server.secret.length > 0
|
data.server.secret.length > 0
|
||||||
|
|||||||
Reference in New Issue
Block a user