mirror of
https://github.com/zadam/trilium.git
synced 2025-10-21 15:49:00 +02:00
feat(config): fix previously documented env var formula not working (#6726)
This commit is contained in:
commit
e793b2f661
348
apps/server/src/services/config.spec.ts
Normal file
348
apps/server/src/services/config.spec.ts
Normal file
@ -0,0 +1,348 @@
|
|||||||
|
import { vi, describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||||
|
import fs from "fs";
|
||||||
|
import ini from "ini";
|
||||||
|
|
||||||
|
// Mock dependencies
|
||||||
|
vi.mock("fs");
|
||||||
|
vi.mock("./data_dir.js", () => ({
|
||||||
|
default: {
|
||||||
|
CONFIG_INI_PATH: "/test/config.ini"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
vi.mock("./resource_dir.js", () => ({
|
||||||
|
default: {
|
||||||
|
RESOURCE_DIR: "/test/resources"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("Config Service", () => {
|
||||||
|
let originalEnv: NodeJS.ProcessEnv;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Save original environment
|
||||||
|
originalEnv = { ...process.env };
|
||||||
|
|
||||||
|
// Clear all TRILIUM env vars
|
||||||
|
Object.keys(process.env).forEach(key => {
|
||||||
|
if (key.startsWith("TRILIUM_")) {
|
||||||
|
delete process.env[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock fs to return empty config
|
||||||
|
vi.mocked(fs.existsSync).mockReturnValue(true);
|
||||||
|
vi.mocked(fs.readFileSync).mockImplementation((path) => {
|
||||||
|
if (String(path).includes("config-sample.ini")) {
|
||||||
|
return "" as any; // Return string for INI parsing
|
||||||
|
}
|
||||||
|
// Return empty INI config as string
|
||||||
|
return `
|
||||||
|
[General]
|
||||||
|
[Network]
|
||||||
|
[Session]
|
||||||
|
[Sync]
|
||||||
|
[MultiFactorAuthentication]
|
||||||
|
[Logging]
|
||||||
|
` as any;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear module cache to reload config with new env vars
|
||||||
|
vi.resetModules();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// Restore original environment
|
||||||
|
process.env = originalEnv;
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Environment Variable Naming", () => {
|
||||||
|
it("should use standard environment variables following TRILIUM_[SECTION]_[KEY] pattern", async () => {
|
||||||
|
// Set standard env vars
|
||||||
|
process.env.TRILIUM_GENERAL_INSTANCENAME = "test-instance";
|
||||||
|
process.env.TRILIUM_NETWORK_CORSALLOWORIGIN = "https://example.com";
|
||||||
|
process.env.TRILIUM_SYNC_SYNCSERVERHOST = "sync.example.com";
|
||||||
|
process.env.TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHBASEURL = "https://auth.example.com";
|
||||||
|
process.env.TRILIUM_LOGGING_RETENTIONDAYS = "30";
|
||||||
|
|
||||||
|
const { default: config } = await import("./config.js");
|
||||||
|
|
||||||
|
expect(config.General.instanceName).toBe("test-instance");
|
||||||
|
expect(config.Network.corsAllowOrigin).toBe("https://example.com");
|
||||||
|
expect(config.Sync.syncServerHost).toBe("sync.example.com");
|
||||||
|
expect(config.MultiFactorAuthentication.oauthBaseUrl).toBe("https://auth.example.com");
|
||||||
|
expect(config.Logging.retentionDays).toBe(30);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should maintain backward compatibility with alias environment variables", async () => {
|
||||||
|
// Set alias/legacy env vars
|
||||||
|
process.env.TRILIUM_NETWORK_CORS_ALLOW_ORIGIN = "https://legacy.com";
|
||||||
|
process.env.TRILIUM_SYNC_SERVER_HOST = "legacy-sync.com";
|
||||||
|
process.env.TRILIUM_OAUTH_BASE_URL = "https://legacy-auth.com";
|
||||||
|
process.env.TRILIUM_LOGGING_RETENTION_DAYS = "60";
|
||||||
|
|
||||||
|
const { default: config } = await import("./config.js");
|
||||||
|
|
||||||
|
expect(config.Network.corsAllowOrigin).toBe("https://legacy.com");
|
||||||
|
expect(config.Sync.syncServerHost).toBe("legacy-sync.com");
|
||||||
|
expect(config.MultiFactorAuthentication.oauthBaseUrl).toBe("https://legacy-auth.com");
|
||||||
|
expect(config.Logging.retentionDays).toBe(60);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should prioritize standard env vars over aliases when both are set", async () => {
|
||||||
|
// Set both standard and alias env vars - standard should win
|
||||||
|
process.env.TRILIUM_NETWORK_CORSALLOWORIGIN = "standard-cors.com";
|
||||||
|
process.env.TRILIUM_NETWORK_CORS_ALLOW_ORIGIN = "alias-cors.com";
|
||||||
|
|
||||||
|
process.env.TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHBASEURL = "standard-auth.com";
|
||||||
|
process.env.TRILIUM_OAUTH_BASE_URL = "alias-auth.com";
|
||||||
|
|
||||||
|
const { default: config } = await import("./config.js");
|
||||||
|
|
||||||
|
expect(config.Network.corsAllowOrigin).toBe("standard-cors.com");
|
||||||
|
expect(config.MultiFactorAuthentication.oauthBaseUrl).toBe("standard-auth.com");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle all CORS environment variables correctly", async () => {
|
||||||
|
// Test with standard naming
|
||||||
|
process.env.TRILIUM_NETWORK_CORSALLOWORIGIN = "*";
|
||||||
|
process.env.TRILIUM_NETWORK_CORSALLOWMETHODS = "GET,POST,PUT";
|
||||||
|
process.env.TRILIUM_NETWORK_CORSALLOWHEADERS = "Content-Type,Authorization";
|
||||||
|
|
||||||
|
let { default: config } = await import("./config.js");
|
||||||
|
|
||||||
|
expect(config.Network.corsAllowOrigin).toBe("*");
|
||||||
|
expect(config.Network.corsAllowMethods).toBe("GET,POST,PUT");
|
||||||
|
expect(config.Network.corsAllowHeaders).toBe("Content-Type,Authorization");
|
||||||
|
|
||||||
|
// Clear and test with alias naming
|
||||||
|
delete process.env.TRILIUM_NETWORK_CORSALLOWORIGIN;
|
||||||
|
delete process.env.TRILIUM_NETWORK_CORSALLOWMETHODS;
|
||||||
|
delete process.env.TRILIUM_NETWORK_CORSALLOWHEADERS;
|
||||||
|
|
||||||
|
process.env.TRILIUM_NETWORK_CORS_ALLOW_ORIGIN = "https://app.com";
|
||||||
|
process.env.TRILIUM_NETWORK_CORS_ALLOW_METHODS = "GET,POST";
|
||||||
|
process.env.TRILIUM_NETWORK_CORS_ALLOW_HEADERS = "X-Custom-Header";
|
||||||
|
|
||||||
|
vi.resetModules();
|
||||||
|
config = (await import("./config.js")).default;
|
||||||
|
|
||||||
|
expect(config.Network.corsAllowOrigin).toBe("https://app.com");
|
||||||
|
expect(config.Network.corsAllowMethods).toBe("GET,POST");
|
||||||
|
expect(config.Network.corsAllowHeaders).toBe("X-Custom-Header");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle all OAuth/MFA environment variables correctly", async () => {
|
||||||
|
// Test with standard naming
|
||||||
|
process.env.TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHBASEURL = "https://oauth.standard.com";
|
||||||
|
process.env.TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHCLIENTID = "standard-client-id";
|
||||||
|
process.env.TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHCLIENTSECRET = "standard-secret";
|
||||||
|
process.env.TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERBASEURL = "https://issuer.standard.com";
|
||||||
|
process.env.TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERNAME = "Standard Auth";
|
||||||
|
process.env.TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERICON = "standard-icon.png";
|
||||||
|
|
||||||
|
let { default: config } = await import("./config.js");
|
||||||
|
|
||||||
|
expect(config.MultiFactorAuthentication.oauthBaseUrl).toBe("https://oauth.standard.com");
|
||||||
|
expect(config.MultiFactorAuthentication.oauthClientId).toBe("standard-client-id");
|
||||||
|
expect(config.MultiFactorAuthentication.oauthClientSecret).toBe("standard-secret");
|
||||||
|
expect(config.MultiFactorAuthentication.oauthIssuerBaseUrl).toBe("https://issuer.standard.com");
|
||||||
|
expect(config.MultiFactorAuthentication.oauthIssuerName).toBe("Standard Auth");
|
||||||
|
expect(config.MultiFactorAuthentication.oauthIssuerIcon).toBe("standard-icon.png");
|
||||||
|
|
||||||
|
// Clear and test with alias naming
|
||||||
|
Object.keys(process.env).forEach(key => {
|
||||||
|
if (key.startsWith("TRILIUM_MULTIFACTORAUTHENTICATION_")) {
|
||||||
|
delete process.env[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
process.env.TRILIUM_OAUTH_BASE_URL = "https://oauth.alias.com";
|
||||||
|
process.env.TRILIUM_OAUTH_CLIENT_ID = "alias-client-id";
|
||||||
|
process.env.TRILIUM_OAUTH_CLIENT_SECRET = "alias-secret";
|
||||||
|
process.env.TRILIUM_OAUTH_ISSUER_BASE_URL = "https://issuer.alias.com";
|
||||||
|
process.env.TRILIUM_OAUTH_ISSUER_NAME = "Alias Auth";
|
||||||
|
process.env.TRILIUM_OAUTH_ISSUER_ICON = "alias-icon.png";
|
||||||
|
|
||||||
|
vi.resetModules();
|
||||||
|
config = (await import("./config.js")).default;
|
||||||
|
|
||||||
|
expect(config.MultiFactorAuthentication.oauthBaseUrl).toBe("https://oauth.alias.com");
|
||||||
|
expect(config.MultiFactorAuthentication.oauthClientId).toBe("alias-client-id");
|
||||||
|
expect(config.MultiFactorAuthentication.oauthClientSecret).toBe("alias-secret");
|
||||||
|
expect(config.MultiFactorAuthentication.oauthIssuerBaseUrl).toBe("https://issuer.alias.com");
|
||||||
|
expect(config.MultiFactorAuthentication.oauthIssuerName).toBe("Alias Auth");
|
||||||
|
expect(config.MultiFactorAuthentication.oauthIssuerIcon).toBe("alias-icon.png");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle all Sync environment variables correctly", async () => {
|
||||||
|
// Test with standard naming
|
||||||
|
process.env.TRILIUM_SYNC_SYNCSERVERHOST = "sync-standard.com";
|
||||||
|
process.env.TRILIUM_SYNC_SYNCSERVERTIMEOUT = "60000";
|
||||||
|
process.env.TRILIUM_SYNC_SYNCPROXY = "proxy-standard.com";
|
||||||
|
|
||||||
|
let { default: config } = await import("./config.js");
|
||||||
|
|
||||||
|
expect(config.Sync.syncServerHost).toBe("sync-standard.com");
|
||||||
|
expect(config.Sync.syncServerTimeout).toBe("60000");
|
||||||
|
expect(config.Sync.syncProxy).toBe("proxy-standard.com");
|
||||||
|
|
||||||
|
// Clear and test with alias naming
|
||||||
|
delete process.env.TRILIUM_SYNC_SYNCSERVERHOST;
|
||||||
|
delete process.env.TRILIUM_SYNC_SYNCSERVERTIMEOUT;
|
||||||
|
delete process.env.TRILIUM_SYNC_SYNCPROXY;
|
||||||
|
|
||||||
|
process.env.TRILIUM_SYNC_SERVER_HOST = "sync-alias.com";
|
||||||
|
process.env.TRILIUM_SYNC_SERVER_TIMEOUT = "30000";
|
||||||
|
process.env.TRILIUM_SYNC_SERVER_PROXY = "proxy-alias.com";
|
||||||
|
|
||||||
|
vi.resetModules();
|
||||||
|
config = (await import("./config.js")).default;
|
||||||
|
|
||||||
|
expect(config.Sync.syncServerHost).toBe("sync-alias.com");
|
||||||
|
expect(config.Sync.syncServerTimeout).toBe("30000");
|
||||||
|
expect(config.Sync.syncProxy).toBe("proxy-alias.com");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("INI Config Integration", () => {
|
||||||
|
it("should fall back to INI config when no env vars are set", async () => {
|
||||||
|
// Mock INI config with values
|
||||||
|
vi.mocked(fs.readFileSync).mockImplementation((path) => {
|
||||||
|
if (String(path).includes("config-sample.ini")) {
|
||||||
|
return "" as any;
|
||||||
|
}
|
||||||
|
return `
|
||||||
|
[General]
|
||||||
|
instanceName=ini-instance
|
||||||
|
|
||||||
|
[Network]
|
||||||
|
corsAllowOrigin=https://ini-cors.com
|
||||||
|
port=9000
|
||||||
|
|
||||||
|
[Sync]
|
||||||
|
syncServerHost=ini-sync.com
|
||||||
|
|
||||||
|
[MultiFactorAuthentication]
|
||||||
|
oauthBaseUrl=https://ini-oauth.com
|
||||||
|
|
||||||
|
[Logging]
|
||||||
|
retentionDays=45
|
||||||
|
` as any;
|
||||||
|
});
|
||||||
|
|
||||||
|
const { default: config } = await import("./config.js");
|
||||||
|
|
||||||
|
expect(config.General.instanceName).toBe("ini-instance");
|
||||||
|
expect(config.Network.corsAllowOrigin).toBe("https://ini-cors.com");
|
||||||
|
expect(config.Network.port).toBe("9000");
|
||||||
|
expect(config.Sync.syncServerHost).toBe("ini-sync.com");
|
||||||
|
expect(config.MultiFactorAuthentication.oauthBaseUrl).toBe("https://ini-oauth.com");
|
||||||
|
expect(config.Logging.retentionDays).toBe(45);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should prioritize env vars over INI config", async () => {
|
||||||
|
// Mock INI config with values
|
||||||
|
vi.mocked(fs.readFileSync).mockImplementation((path) => {
|
||||||
|
if (String(path).includes("config-sample.ini")) {
|
||||||
|
return "" as any;
|
||||||
|
}
|
||||||
|
return `
|
||||||
|
[General]
|
||||||
|
instanceName=ini-instance
|
||||||
|
|
||||||
|
[Network]
|
||||||
|
corsAllowOrigin=https://ini-cors.com
|
||||||
|
` as any;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set env vars that should override INI
|
||||||
|
process.env.TRILIUM_GENERAL_INSTANCENAME = "env-instance";
|
||||||
|
process.env.TRILIUM_NETWORK_CORS_ALLOW_ORIGIN = "https://env-cors.com"; // Using alias
|
||||||
|
|
||||||
|
const { default: config } = await import("./config.js");
|
||||||
|
|
||||||
|
expect(config.General.instanceName).toBe("env-instance");
|
||||||
|
expect(config.Network.corsAllowOrigin).toBe("https://env-cors.com");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Type Transformations", () => {
|
||||||
|
it("should correctly transform boolean values", async () => {
|
||||||
|
process.env.TRILIUM_GENERAL_NOAUTHENTICATION = "true";
|
||||||
|
process.env.TRILIUM_GENERAL_NOBACKUP = "1";
|
||||||
|
process.env.TRILIUM_GENERAL_READONLY = "false";
|
||||||
|
process.env.TRILIUM_NETWORK_HTTPS = "0";
|
||||||
|
|
||||||
|
const { default: config } = await import("./config.js");
|
||||||
|
|
||||||
|
expect(config.General.noAuthentication).toBe(true);
|
||||||
|
expect(config.General.noBackup).toBe(true);
|
||||||
|
expect(config.General.readOnly).toBe(false);
|
||||||
|
expect(config.Network.https).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should correctly transform integer values", async () => {
|
||||||
|
process.env.TRILIUM_SESSION_COOKIEMAXAGE = "3600";
|
||||||
|
process.env.TRILIUM_LOGGING_RETENTIONDAYS = "7";
|
||||||
|
|
||||||
|
const { default: config } = await import("./config.js");
|
||||||
|
|
||||||
|
expect(config.Session.cookieMaxAge).toBe(3600);
|
||||||
|
expect(config.Logging.retentionDays).toBe(7);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use default values for invalid integers", async () => {
|
||||||
|
process.env.TRILIUM_SESSION_COOKIEMAXAGE = "invalid";
|
||||||
|
process.env.TRILIUM_LOGGING_RETENTION_DAYS = "not-a-number"; // Using alias
|
||||||
|
|
||||||
|
const { default: config } = await import("./config.js");
|
||||||
|
|
||||||
|
expect(config.Session.cookieMaxAge).toBe(21 * 24 * 60 * 60); // Default
|
||||||
|
expect(config.Logging.retentionDays).toBe(90); // Default
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Default Values", () => {
|
||||||
|
it("should use correct default values when no config is provided", async () => {
|
||||||
|
const { default: config } = await import("./config.js");
|
||||||
|
|
||||||
|
// General defaults
|
||||||
|
expect(config.General.instanceName).toBe("");
|
||||||
|
expect(config.General.noAuthentication).toBe(false);
|
||||||
|
expect(config.General.noBackup).toBe(false);
|
||||||
|
expect(config.General.noDesktopIcon).toBe(false);
|
||||||
|
expect(config.General.readOnly).toBe(false);
|
||||||
|
|
||||||
|
// Network defaults
|
||||||
|
expect(config.Network.host).toBe("0.0.0.0");
|
||||||
|
expect(config.Network.port).toBe("3000");
|
||||||
|
expect(config.Network.https).toBe(false);
|
||||||
|
expect(config.Network.certPath).toBe("");
|
||||||
|
expect(config.Network.keyPath).toBe("");
|
||||||
|
expect(config.Network.trustedReverseProxy).toBe(false);
|
||||||
|
expect(config.Network.corsAllowOrigin).toBe("");
|
||||||
|
expect(config.Network.corsAllowMethods).toBe("");
|
||||||
|
expect(config.Network.corsAllowHeaders).toBe("");
|
||||||
|
|
||||||
|
// Session defaults
|
||||||
|
expect(config.Session.cookieMaxAge).toBe(21 * 24 * 60 * 60);
|
||||||
|
|
||||||
|
// Sync defaults
|
||||||
|
expect(config.Sync.syncServerHost).toBe("");
|
||||||
|
expect(config.Sync.syncServerTimeout).toBe("120000");
|
||||||
|
expect(config.Sync.syncProxy).toBe("");
|
||||||
|
|
||||||
|
// OAuth defaults
|
||||||
|
expect(config.MultiFactorAuthentication.oauthBaseUrl).toBe("");
|
||||||
|
expect(config.MultiFactorAuthentication.oauthClientId).toBe("");
|
||||||
|
expect(config.MultiFactorAuthentication.oauthClientSecret).toBe("");
|
||||||
|
expect(config.MultiFactorAuthentication.oauthIssuerBaseUrl).toBe("https://accounts.google.com");
|
||||||
|
expect(config.MultiFactorAuthentication.oauthIssuerName).toBe("Google");
|
||||||
|
expect(config.MultiFactorAuthentication.oauthIssuerIcon).toBe("");
|
||||||
|
|
||||||
|
// Logging defaults
|
||||||
|
expect(config.Logging.retentionDays).toBe(90);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,3 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* ╔════════════════════════════════════════════════════════════════════════════╗
|
||||||
|
* ║ TRILIUM CONFIGURATION RESOLUTION ORDER ║
|
||||||
|
* ╠════════════════════════════════════════════════════════════════════════════╣
|
||||||
|
* ║ ║
|
||||||
|
* ║ Priority │ Source │ Example ║
|
||||||
|
* ║ ─────────┼─────────────────────────────────┼─────────────────────────────║
|
||||||
|
* ║ 1 │ Environment Variables │ TRILIUM_NETWORK_PORT=8080 ║
|
||||||
|
* ║ ↓ │ (Highest Priority - Overrides all) ║
|
||||||
|
* ║ │ ║
|
||||||
|
* ║ 2 │ config.ini File │ [Network] ║
|
||||||
|
* ║ ↓ │ (User Configuration) │ port=8080 ║
|
||||||
|
* ║ │ ║
|
||||||
|
* ║ 3 │ Default Values │ port='3000' ║
|
||||||
|
* ║ │ (Lowest Priority - Fallback) │ (hardcoded defaults) ║
|
||||||
|
* ║ ║
|
||||||
|
* ╠════════════════════════════════════════════════════════════════════════════╣
|
||||||
|
* ║ IMPORTANT: Environment variables ALWAYS override config.ini values! ║
|
||||||
|
* ╚════════════════════════════════════════════════════════════════════════════╝
|
||||||
|
*/
|
||||||
|
|
||||||
import ini from "ini";
|
import ini from "ini";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import dataDir from "./data_dir.js";
|
import dataDir from "./data_dir.js";
|
||||||
@ -5,153 +26,594 @@ import path from "path";
|
|||||||
import resourceDir from "./resource_dir.js";
|
import resourceDir from "./resource_dir.js";
|
||||||
import { envToBoolean, stringToInt } from "./utils.js";
|
import { envToBoolean, stringToInt } from "./utils.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Path to the sample configuration file that serves as a template for new installations.
|
||||||
|
* This file contains all available configuration options with documentation.
|
||||||
|
*/
|
||||||
const configSampleFilePath = path.resolve(resourceDir.RESOURCE_DIR, "config-sample.ini");
|
const configSampleFilePath = path.resolve(resourceDir.RESOURCE_DIR, "config-sample.ini");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize config.ini file if it doesn't exist.
|
||||||
|
* On first run, copies the sample configuration to the data directory,
|
||||||
|
* allowing users to customize their settings.
|
||||||
|
*/
|
||||||
if (!fs.existsSync(dataDir.CONFIG_INI_PATH)) {
|
if (!fs.existsSync(dataDir.CONFIG_INI_PATH)) {
|
||||||
const configSample = fs.readFileSync(configSampleFilePath).toString("utf8");
|
const configSample = fs.readFileSync(configSampleFilePath).toString("utf8");
|
||||||
|
|
||||||
fs.writeFileSync(dataDir.CONFIG_INI_PATH, configSample);
|
fs.writeFileSync(dataDir.CONFIG_INI_PATH, configSample);
|
||||||
}
|
}
|
||||||
|
|
||||||
const iniConfig = ini.parse(fs.readFileSync(dataDir.CONFIG_INI_PATH, "utf-8"));
|
/**
|
||||||
|
* Type definition for the parsed INI configuration structure.
|
||||||
|
* The ini parser returns an object with string keys and values that can be
|
||||||
|
* strings, booleans, numbers, or nested objects.
|
||||||
|
*/
|
||||||
|
type IniConfigValue = string | number | boolean | null | undefined;
|
||||||
|
type IniConfigSection = Record<string, IniConfigValue>;
|
||||||
|
type IniConfig = Record<string, IniConfigSection | IniConfigValue>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the config.ini file into a JavaScript object.
|
||||||
|
* This object contains all user-defined configuration from the INI file,
|
||||||
|
* which will be merged with environment variables and defaults.
|
||||||
|
*/
|
||||||
|
const iniConfig = ini.parse(fs.readFileSync(dataDir.CONFIG_INI_PATH, "utf-8")) as IniConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete type-safe configuration interface for Trilium.
|
||||||
|
* This interface defines all configuration options available through
|
||||||
|
* environment variables, config.ini, or defaults.
|
||||||
|
*/
|
||||||
export interface TriliumConfig {
|
export interface TriliumConfig {
|
||||||
|
/** General application settings */
|
||||||
General: {
|
General: {
|
||||||
|
/** Custom instance name for identifying this Trilium instance */
|
||||||
instanceName: string;
|
instanceName: string;
|
||||||
|
/** Whether to disable authentication (useful for local-only instances) */
|
||||||
noAuthentication: boolean;
|
noAuthentication: boolean;
|
||||||
|
/** Whether to disable automatic backups */
|
||||||
noBackup: boolean;
|
noBackup: boolean;
|
||||||
|
/** Whether to prevent desktop icon creation (desktop app only) */
|
||||||
noDesktopIcon: boolean;
|
noDesktopIcon: boolean;
|
||||||
|
/** Whether to run in read-only mode (prevents all data modifications) */
|
||||||
readOnly: boolean;
|
readOnly: boolean;
|
||||||
};
|
};
|
||||||
|
/** Network and server configuration */
|
||||||
Network: {
|
Network: {
|
||||||
|
/** Host/IP address to bind the server to (e.g., '0.0.0.0' for all interfaces) */
|
||||||
host: string;
|
host: string;
|
||||||
|
/** Port number for the HTTP/HTTPS server */
|
||||||
port: string;
|
port: string;
|
||||||
|
/** Whether to enable HTTPS (requires certPath and keyPath) */
|
||||||
https: boolean;
|
https: boolean;
|
||||||
|
/** Path to SSL certificate file (required when https=true) */
|
||||||
certPath: string;
|
certPath: string;
|
||||||
|
/** Path to SSL private key file (required when https=true) */
|
||||||
keyPath: string;
|
keyPath: string;
|
||||||
|
/** Trust reverse proxy headers (boolean or specific IP/subnet string) */
|
||||||
trustedReverseProxy: boolean | string;
|
trustedReverseProxy: boolean | string;
|
||||||
|
/** CORS allowed origins (comma-separated list or '*' for all) */
|
||||||
corsAllowOrigin: string;
|
corsAllowOrigin: string;
|
||||||
|
/** CORS allowed methods (comma-separated HTTP methods) */
|
||||||
corsAllowMethods: string;
|
corsAllowMethods: string;
|
||||||
|
/** CORS allowed headers (comma-separated header names) */
|
||||||
corsAllowHeaders: string;
|
corsAllowHeaders: string;
|
||||||
};
|
};
|
||||||
|
/** Session management configuration */
|
||||||
Session: {
|
Session: {
|
||||||
|
/** Maximum age of session cookies in seconds (default: 21 days) */
|
||||||
cookieMaxAge: number;
|
cookieMaxAge: number;
|
||||||
};
|
};
|
||||||
|
/** Synchronization settings for multi-instance setups */
|
||||||
Sync: {
|
Sync: {
|
||||||
|
/** URL of the sync server to connect to */
|
||||||
syncServerHost: string;
|
syncServerHost: string;
|
||||||
|
/** Timeout for sync operations in milliseconds */
|
||||||
syncServerTimeout: string;
|
syncServerTimeout: string;
|
||||||
|
/** Proxy URL for sync connections (if behind corporate proxy) */
|
||||||
syncProxy: string;
|
syncProxy: string;
|
||||||
};
|
};
|
||||||
|
/** Multi-factor authentication and OAuth/OpenID configuration */
|
||||||
MultiFactorAuthentication: {
|
MultiFactorAuthentication: {
|
||||||
|
/** Base URL for OAuth authentication endpoint */
|
||||||
oauthBaseUrl: string;
|
oauthBaseUrl: string;
|
||||||
|
/** OAuth client ID from your identity provider */
|
||||||
oauthClientId: string;
|
oauthClientId: string;
|
||||||
|
/** OAuth client secret from your identity provider */
|
||||||
oauthClientSecret: string;
|
oauthClientSecret: string;
|
||||||
|
/** Base URL of the OAuth issuer (e.g., 'https://accounts.google.com') */
|
||||||
oauthIssuerBaseUrl: string;
|
oauthIssuerBaseUrl: string;
|
||||||
|
/** Display name of the OAuth provider (shown in UI) */
|
||||||
oauthIssuerName: string;
|
oauthIssuerName: string;
|
||||||
|
/** URL to the OAuth provider's icon/logo */
|
||||||
oauthIssuerIcon: string;
|
oauthIssuerIcon: string;
|
||||||
};
|
};
|
||||||
|
/** Logging configuration */
|
||||||
Logging: {
|
Logging: {
|
||||||
/**
|
/**
|
||||||
* The number of days to keep the log files around. When rotating the logs, log files created by Trilium older than the specified amount of time will be deleted.
|
* The number of days to keep the log files around. When rotating the logs,
|
||||||
|
* log files created by Trilium older than the specified amount of time will be deleted.
|
||||||
*/
|
*/
|
||||||
retentionDays: number;
|
retentionDays: number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default retention period for log files in days.
|
||||||
|
* After this period, old log files are automatically deleted during rotation.
|
||||||
|
*/
|
||||||
export const LOGGING_DEFAULT_RETENTION_DAYS = 90;
|
export const LOGGING_DEFAULT_RETENTION_DAYS = 90;
|
||||||
|
|
||||||
//prettier-ignore
|
/**
|
||||||
const config: TriliumConfig = {
|
* Configuration value source with precedence handling.
|
||||||
|
* This interface defines how each configuration value is resolved from multiple sources.
|
||||||
|
*/
|
||||||
|
interface ConfigValue<T> {
|
||||||
|
/**
|
||||||
|
* Standard environment variable name following TRILIUM_[SECTION]_[KEY] pattern.
|
||||||
|
* This is the primary way to override configuration via environment.
|
||||||
|
*/
|
||||||
|
standardEnvVar?: string;
|
||||||
|
/**
|
||||||
|
* Alternative environment variable names for additional flexibility.
|
||||||
|
* These provide shorter or more intuitive names for common settings.
|
||||||
|
*/
|
||||||
|
aliasEnvVars?: string[];
|
||||||
|
/**
|
||||||
|
* Function to retrieve the value from the parsed INI configuration.
|
||||||
|
* Returns undefined if the value is not set in config.ini.
|
||||||
|
*/
|
||||||
|
iniGetter: () => IniConfigValue | IniConfigSection;
|
||||||
|
/**
|
||||||
|
* Default value used when no environment variable or INI value is found.
|
||||||
|
* This ensures every configuration has a sensible default.
|
||||||
|
*/
|
||||||
|
defaultValue: T;
|
||||||
|
/**
|
||||||
|
* Optional transformer function to convert string values to the correct type.
|
||||||
|
* Common transformers handle boolean and integer conversions.
|
||||||
|
*/
|
||||||
|
transformer?: (value: unknown) => T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Core configuration resolution function.
|
||||||
|
*
|
||||||
|
* Resolves configuration values using a clear precedence order:
|
||||||
|
* 1. Standard environment variable (highest priority) - Follows TRILIUM_[SECTION]_[KEY] pattern
|
||||||
|
* 2. Alias environment variables - Alternative names for convenience and compatibility
|
||||||
|
* 3. INI config file value - User-defined settings in config.ini
|
||||||
|
* 4. Default value (lowest priority) - Fallback to ensure valid configuration
|
||||||
|
*
|
||||||
|
* This precedence allows for flexible configuration management:
|
||||||
|
* - Environment variables for container/cloud deployments
|
||||||
|
* - config.ini for traditional installations
|
||||||
|
* - Defaults ensure the application always has valid settings
|
||||||
|
*
|
||||||
|
* @param config - Configuration value definition with sources and transformers
|
||||||
|
* @returns The resolved configuration value with appropriate type
|
||||||
|
*/
|
||||||
|
function getConfigValue<T>(config: ConfigValue<T>): T {
|
||||||
|
// Check standard env var first
|
||||||
|
if (config.standardEnvVar && process.env[config.standardEnvVar] !== undefined) {
|
||||||
|
const value = process.env[config.standardEnvVar];
|
||||||
|
return config.transformer ? config.transformer(value) : value as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check alternative env vars for additional flexibility
|
||||||
|
if (config.aliasEnvVars) {
|
||||||
|
for (const aliasVar of config.aliasEnvVars) {
|
||||||
|
if (process.env[aliasVar] !== undefined) {
|
||||||
|
const value = process.env[aliasVar];
|
||||||
|
return config.transformer ? config.transformer(value) : value as T;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check INI config
|
||||||
|
const iniValue = config.iniGetter();
|
||||||
|
if (iniValue !== undefined && iniValue !== null && iniValue !== '') {
|
||||||
|
return config.transformer ? config.transformer(iniValue) : iniValue as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return default
|
||||||
|
return config.defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to safely access INI config sections.
|
||||||
|
* The ini parser can return either a section object or a primitive value,
|
||||||
|
* so we need to check the type before accessing nested properties.
|
||||||
|
*
|
||||||
|
* @param sectionName - The name of the INI section to access
|
||||||
|
* @returns The section object or undefined if not found or not an object
|
||||||
|
*/
|
||||||
|
function getIniSection(sectionName: string): IniConfigSection | undefined {
|
||||||
|
const section = iniConfig[sectionName];
|
||||||
|
if (section && typeof section === 'object' && !Array.isArray(section)) {
|
||||||
|
return section as IniConfigSection;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform a value to boolean, handling various input formats.
|
||||||
|
*
|
||||||
|
* This function provides flexible boolean parsing to handle different
|
||||||
|
* configuration sources (environment variables, INI files, etc.):
|
||||||
|
* - String "true"/"false" (case-insensitive)
|
||||||
|
* - String "1"/"0"
|
||||||
|
* - Numeric 1/0
|
||||||
|
* - Actual boolean values
|
||||||
|
* - Any other value defaults to false
|
||||||
|
*
|
||||||
|
* @param value - The value to transform (string, number, boolean, etc.)
|
||||||
|
* @returns The boolean value or false as default
|
||||||
|
*/
|
||||||
|
function transformBoolean(value: unknown): boolean {
|
||||||
|
// First try the standard envToBoolean function which handles "true"/"false" strings
|
||||||
|
const result = envToBoolean(String(value));
|
||||||
|
if (result !== undefined) return result;
|
||||||
|
|
||||||
|
// Handle numeric boolean values (both string and number types)
|
||||||
|
if (value === "1" || value === 1) return true;
|
||||||
|
if (value === "0" || value === 0) return false;
|
||||||
|
|
||||||
|
// Default to false for any other value
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete configuration mapping that defines how each setting is resolved.
|
||||||
|
*
|
||||||
|
* This mapping structure:
|
||||||
|
* 1. Mirrors the INI file sections for consistency
|
||||||
|
* 2. Defines multiple sources for each configuration value
|
||||||
|
* 3. Provides type transformers where needed
|
||||||
|
* 4. Maintains compatibility with various environment variable formats
|
||||||
|
*
|
||||||
|
* Environment Variable Patterns:
|
||||||
|
* - Standard: TRILIUM_[SECTION]_[KEY] (e.g., TRILIUM_GENERAL_INSTANCENAME)
|
||||||
|
* - Aliases: Shorter alternatives (e.g., TRILIUM_OAUTH_BASE_URL)
|
||||||
|
*
|
||||||
|
* Both patterns are equally valid and can be used based on preference.
|
||||||
|
* The standard pattern provides consistency, while aliases offer convenience.
|
||||||
|
*/
|
||||||
|
const configMapping = {
|
||||||
General: {
|
General: {
|
||||||
instanceName:
|
instanceName: {
|
||||||
process.env.TRILIUM_GENERAL_INSTANCENAME || iniConfig.General.instanceName || "",
|
standardEnvVar: 'TRILIUM_GENERAL_INSTANCENAME',
|
||||||
|
iniGetter: () => getIniSection("General")?.instanceName,
|
||||||
noAuthentication:
|
defaultValue: ''
|
||||||
envToBoolean(process.env.TRILIUM_GENERAL_NOAUTHENTICATION) || iniConfig.General.noAuthentication || false,
|
},
|
||||||
|
noAuthentication: {
|
||||||
noBackup:
|
standardEnvVar: 'TRILIUM_GENERAL_NOAUTHENTICATION',
|
||||||
envToBoolean(process.env.TRILIUM_GENERAL_NOBACKUP) || iniConfig.General.noBackup || false,
|
iniGetter: () => getIniSection("General")?.noAuthentication,
|
||||||
|
defaultValue: false,
|
||||||
noDesktopIcon:
|
transformer: transformBoolean
|
||||||
envToBoolean(process.env.TRILIUM_GENERAL_NODESKTOPICON) || iniConfig.General.noDesktopIcon || false,
|
},
|
||||||
|
noBackup: {
|
||||||
readOnly:
|
standardEnvVar: 'TRILIUM_GENERAL_NOBACKUP',
|
||||||
envToBoolean(process.env.TRILIUM_GENERAL_READONLY) || iniConfig.General.readOnly || false
|
iniGetter: () => getIniSection("General")?.noBackup,
|
||||||
|
defaultValue: false,
|
||||||
|
transformer: transformBoolean
|
||||||
|
},
|
||||||
|
noDesktopIcon: {
|
||||||
|
standardEnvVar: 'TRILIUM_GENERAL_NODESKTOPICON',
|
||||||
|
iniGetter: () => getIniSection("General")?.noDesktopIcon,
|
||||||
|
defaultValue: false,
|
||||||
|
transformer: transformBoolean
|
||||||
|
},
|
||||||
|
readOnly: {
|
||||||
|
standardEnvVar: 'TRILIUM_GENERAL_READONLY',
|
||||||
|
iniGetter: () => getIniSection("General")?.readOnly,
|
||||||
|
defaultValue: false,
|
||||||
|
transformer: transformBoolean
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
Network: {
|
Network: {
|
||||||
host:
|
host: {
|
||||||
process.env.TRILIUM_NETWORK_HOST || iniConfig.Network.host || "0.0.0.0",
|
standardEnvVar: 'TRILIUM_NETWORK_HOST',
|
||||||
|
iniGetter: () => getIniSection("Network")?.host,
|
||||||
port:
|
defaultValue: '0.0.0.0'
|
||||||
process.env.TRILIUM_NETWORK_PORT || iniConfig.Network.port || "3000",
|
},
|
||||||
|
port: {
|
||||||
https:
|
standardEnvVar: 'TRILIUM_NETWORK_PORT',
|
||||||
envToBoolean(process.env.TRILIUM_NETWORK_HTTPS) || iniConfig.Network.https || false,
|
iniGetter: () => getIniSection("Network")?.port,
|
||||||
|
defaultValue: '3000'
|
||||||
certPath:
|
},
|
||||||
process.env.TRILIUM_NETWORK_CERTPATH || iniConfig.Network.certPath || "",
|
https: {
|
||||||
|
standardEnvVar: 'TRILIUM_NETWORK_HTTPS',
|
||||||
keyPath:
|
iniGetter: () => getIniSection("Network")?.https,
|
||||||
process.env.TRILIUM_NETWORK_KEYPATH || iniConfig.Network.keyPath || "",
|
defaultValue: false,
|
||||||
|
transformer: transformBoolean
|
||||||
trustedReverseProxy:
|
},
|
||||||
process.env.TRILIUM_NETWORK_TRUSTEDREVERSEPROXY || iniConfig.Network.trustedReverseProxy || false,
|
certPath: {
|
||||||
|
standardEnvVar: 'TRILIUM_NETWORK_CERTPATH',
|
||||||
corsAllowOrigin:
|
iniGetter: () => getIniSection("Network")?.certPath,
|
||||||
process.env.TRILIUM_NETWORK_CORS_ALLOW_ORIGIN || iniConfig.Network.corsAllowOrigin || "",
|
defaultValue: ''
|
||||||
|
},
|
||||||
corsAllowMethods:
|
keyPath: {
|
||||||
process.env.TRILIUM_NETWORK_CORS_ALLOW_METHODS || iniConfig.Network.corsAllowMethods || "",
|
standardEnvVar: 'TRILIUM_NETWORK_KEYPATH',
|
||||||
|
iniGetter: () => getIniSection("Network")?.keyPath,
|
||||||
corsAllowHeaders:
|
defaultValue: ''
|
||||||
process.env.TRILIUM_NETWORK_CORS_ALLOW_HEADERS || iniConfig.Network.corsAllowHeaders || ""
|
},
|
||||||
|
trustedReverseProxy: {
|
||||||
|
standardEnvVar: 'TRILIUM_NETWORK_TRUSTEDREVERSEPROXY',
|
||||||
|
iniGetter: () => getIniSection("Network")?.trustedReverseProxy,
|
||||||
|
defaultValue: false as boolean | string
|
||||||
|
},
|
||||||
|
corsAllowOrigin: {
|
||||||
|
standardEnvVar: 'TRILIUM_NETWORK_CORSALLOWORIGIN',
|
||||||
|
// alternative with underscore format
|
||||||
|
aliasEnvVars: ['TRILIUM_NETWORK_CORS_ALLOW_ORIGIN'],
|
||||||
|
iniGetter: () => getIniSection("Network")?.corsAllowOrigin,
|
||||||
|
defaultValue: ''
|
||||||
|
},
|
||||||
|
corsAllowMethods: {
|
||||||
|
standardEnvVar: 'TRILIUM_NETWORK_CORSALLOWMETHODS',
|
||||||
|
// alternative with underscore format
|
||||||
|
aliasEnvVars: ['TRILIUM_NETWORK_CORS_ALLOW_METHODS'],
|
||||||
|
iniGetter: () => getIniSection("Network")?.corsAllowMethods,
|
||||||
|
defaultValue: ''
|
||||||
|
},
|
||||||
|
corsAllowHeaders: {
|
||||||
|
standardEnvVar: 'TRILIUM_NETWORK_CORSALLOWHEADERS',
|
||||||
|
// alternative with underscore format
|
||||||
|
aliasEnvVars: ['TRILIUM_NETWORK_CORS_ALLOW_HEADERS'],
|
||||||
|
iniGetter: () => getIniSection("Network")?.corsAllowHeaders,
|
||||||
|
defaultValue: ''
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
Session: {
|
Session: {
|
||||||
cookieMaxAge:
|
cookieMaxAge: {
|
||||||
parseInt(String(process.env.TRILIUM_SESSION_COOKIEMAXAGE)) || parseInt(iniConfig?.Session?.cookieMaxAge) || 21 * 24 * 60 * 60 // 21 Days in Seconds
|
standardEnvVar: 'TRILIUM_SESSION_COOKIEMAXAGE',
|
||||||
|
iniGetter: () => getIniSection("Session")?.cookieMaxAge,
|
||||||
|
defaultValue: 21 * 24 * 60 * 60, // 21 Days in Seconds
|
||||||
|
transformer: (value: unknown) => parseInt(String(value)) || 21 * 24 * 60 * 60
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
Sync: {
|
Sync: {
|
||||||
syncServerHost:
|
syncServerHost: {
|
||||||
process.env.TRILIUM_SYNC_SERVER_HOST || iniConfig?.Sync?.syncServerHost || "",
|
standardEnvVar: 'TRILIUM_SYNC_SYNCSERVERHOST',
|
||||||
|
// alternative format
|
||||||
syncServerTimeout:
|
aliasEnvVars: ['TRILIUM_SYNC_SERVER_HOST'],
|
||||||
process.env.TRILIUM_SYNC_SERVER_TIMEOUT || iniConfig?.Sync?.syncServerTimeout || "120000",
|
iniGetter: () => getIniSection("Sync")?.syncServerHost,
|
||||||
|
defaultValue: ''
|
||||||
syncProxy:
|
},
|
||||||
// additionally checking in iniConfig for inconsistently named syncProxy for backwards compatibility
|
syncServerTimeout: {
|
||||||
process.env.TRILIUM_SYNC_SERVER_PROXY || iniConfig?.Sync?.syncProxy || iniConfig?.Sync?.syncServerProxy || ""
|
standardEnvVar: 'TRILIUM_SYNC_SYNCSERVERTIMEOUT',
|
||||||
|
// alternative format
|
||||||
|
aliasEnvVars: ['TRILIUM_SYNC_SERVER_TIMEOUT'],
|
||||||
|
iniGetter: () => getIniSection("Sync")?.syncServerTimeout,
|
||||||
|
defaultValue: '120000'
|
||||||
|
},
|
||||||
|
syncProxy: {
|
||||||
|
standardEnvVar: 'TRILIUM_SYNC_SYNCPROXY',
|
||||||
|
// alternative shorter formats
|
||||||
|
aliasEnvVars: ['TRILIUM_SYNC_SERVER_PROXY'],
|
||||||
|
// The INI config uses 'syncServerProxy' key for historical reasons (see config-sample.ini)
|
||||||
|
// We check both 'syncProxy' and 'syncServerProxy' for backward compatibility with old configs
|
||||||
|
iniGetter: () => getIniSection("Sync")?.syncProxy || getIniSection("Sync")?.syncServerProxy,
|
||||||
|
defaultValue: ''
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
MultiFactorAuthentication: {
|
MultiFactorAuthentication: {
|
||||||
oauthBaseUrl:
|
oauthBaseUrl: {
|
||||||
process.env.TRILIUM_OAUTH_BASE_URL || iniConfig?.MultiFactorAuthentication?.oauthBaseUrl || "",
|
standardEnvVar: 'TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHBASEURL',
|
||||||
|
// alternative shorter format (commonly used)
|
||||||
oauthClientId:
|
aliasEnvVars: ['TRILIUM_OAUTH_BASE_URL'],
|
||||||
process.env.TRILIUM_OAUTH_CLIENT_ID || iniConfig?.MultiFactorAuthentication?.oauthClientId || "",
|
iniGetter: () => getIniSection("MultiFactorAuthentication")?.oauthBaseUrl,
|
||||||
|
defaultValue: ''
|
||||||
oauthClientSecret:
|
},
|
||||||
process.env.TRILIUM_OAUTH_CLIENT_SECRET || iniConfig?.MultiFactorAuthentication?.oauthClientSecret || "",
|
oauthClientId: {
|
||||||
|
standardEnvVar: 'TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHCLIENTID',
|
||||||
oauthIssuerBaseUrl:
|
// alternative format
|
||||||
process.env.TRILIUM_OAUTH_ISSUER_BASE_URL || iniConfig?.MultiFactorAuthentication?.oauthIssuerBaseUrl || "https://accounts.google.com",
|
aliasEnvVars: ['TRILIUM_OAUTH_CLIENT_ID'],
|
||||||
|
iniGetter: () => getIniSection("MultiFactorAuthentication")?.oauthClientId,
|
||||||
oauthIssuerName:
|
defaultValue: ''
|
||||||
process.env.TRILIUM_OAUTH_ISSUER_NAME || iniConfig?.MultiFactorAuthentication?.oauthIssuerName || "Google",
|
},
|
||||||
|
oauthClientSecret: {
|
||||||
oauthIssuerIcon:
|
standardEnvVar: 'TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHCLIENTSECRET',
|
||||||
process.env.TRILIUM_OAUTH_ISSUER_ICON || iniConfig?.MultiFactorAuthentication?.oauthIssuerIcon || ""
|
// alternative format
|
||||||
|
aliasEnvVars: ['TRILIUM_OAUTH_CLIENT_SECRET'],
|
||||||
|
iniGetter: () => getIniSection("MultiFactorAuthentication")?.oauthClientSecret,
|
||||||
|
defaultValue: ''
|
||||||
|
},
|
||||||
|
oauthIssuerBaseUrl: {
|
||||||
|
standardEnvVar: 'TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERBASEURL',
|
||||||
|
// alternative format
|
||||||
|
aliasEnvVars: ['TRILIUM_OAUTH_ISSUER_BASE_URL'],
|
||||||
|
iniGetter: () => getIniSection("MultiFactorAuthentication")?.oauthIssuerBaseUrl,
|
||||||
|
defaultValue: 'https://accounts.google.com'
|
||||||
|
},
|
||||||
|
oauthIssuerName: {
|
||||||
|
standardEnvVar: 'TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERNAME',
|
||||||
|
// alternative format
|
||||||
|
aliasEnvVars: ['TRILIUM_OAUTH_ISSUER_NAME'],
|
||||||
|
iniGetter: () => getIniSection("MultiFactorAuthentication")?.oauthIssuerName,
|
||||||
|
defaultValue: 'Google'
|
||||||
|
},
|
||||||
|
oauthIssuerIcon: {
|
||||||
|
standardEnvVar: 'TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERICON',
|
||||||
|
// alternative format
|
||||||
|
aliasEnvVars: ['TRILIUM_OAUTH_ISSUER_ICON'],
|
||||||
|
iniGetter: () => getIniSection("MultiFactorAuthentication")?.oauthIssuerIcon,
|
||||||
|
defaultValue: ''
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
Logging: {
|
Logging: {
|
||||||
retentionDays:
|
retentionDays: {
|
||||||
stringToInt(process.env.TRILIUM_LOGGING_RETENTION_DAYS) ??
|
standardEnvVar: 'TRILIUM_LOGGING_RETENTIONDAYS',
|
||||||
stringToInt(iniConfig?.Logging?.retentionDays) ??
|
// alternative with underscore format
|
||||||
LOGGING_DEFAULT_RETENTION_DAYS
|
aliasEnvVars: ['TRILIUM_LOGGING_RETENTION_DAYS'],
|
||||||
|
iniGetter: () => getIniSection("Logging")?.retentionDays,
|
||||||
|
defaultValue: LOGGING_DEFAULT_RETENTION_DAYS,
|
||||||
|
transformer: (value: unknown) => stringToInt(String(value)) ?? LOGGING_DEFAULT_RETENTION_DAYS
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the final configuration object by resolving all values through the mapping.
|
||||||
|
*
|
||||||
|
* This creates the runtime configuration used throughout the application by:
|
||||||
|
* 1. Iterating through each section and key in the mapping
|
||||||
|
* 2. Calling getConfigValue() to resolve each setting with proper precedence
|
||||||
|
* 3. Applying type transformers where needed (booleans, integers)
|
||||||
|
* 4. Returning a fully typed TriliumConfig object
|
||||||
|
*
|
||||||
|
* The resulting config object is immutable at runtime and represents
|
||||||
|
* the complete application configuration.
|
||||||
|
*/
|
||||||
|
const config: TriliumConfig = {
|
||||||
|
General: {
|
||||||
|
instanceName: getConfigValue(configMapping.General.instanceName),
|
||||||
|
noAuthentication: getConfigValue(configMapping.General.noAuthentication),
|
||||||
|
noBackup: getConfigValue(configMapping.General.noBackup),
|
||||||
|
noDesktopIcon: getConfigValue(configMapping.General.noDesktopIcon),
|
||||||
|
readOnly: getConfigValue(configMapping.General.readOnly)
|
||||||
|
},
|
||||||
|
Network: {
|
||||||
|
host: getConfigValue(configMapping.Network.host),
|
||||||
|
port: getConfigValue(configMapping.Network.port),
|
||||||
|
https: getConfigValue(configMapping.Network.https),
|
||||||
|
certPath: getConfigValue(configMapping.Network.certPath),
|
||||||
|
keyPath: getConfigValue(configMapping.Network.keyPath),
|
||||||
|
trustedReverseProxy: getConfigValue(configMapping.Network.trustedReverseProxy),
|
||||||
|
corsAllowOrigin: getConfigValue(configMapping.Network.corsAllowOrigin),
|
||||||
|
corsAllowMethods: getConfigValue(configMapping.Network.corsAllowMethods),
|
||||||
|
corsAllowHeaders: getConfigValue(configMapping.Network.corsAllowHeaders)
|
||||||
|
},
|
||||||
|
Session: {
|
||||||
|
cookieMaxAge: getConfigValue(configMapping.Session.cookieMaxAge)
|
||||||
|
},
|
||||||
|
Sync: {
|
||||||
|
syncServerHost: getConfigValue(configMapping.Sync.syncServerHost),
|
||||||
|
syncServerTimeout: getConfigValue(configMapping.Sync.syncServerTimeout),
|
||||||
|
syncProxy: getConfigValue(configMapping.Sync.syncProxy)
|
||||||
|
},
|
||||||
|
MultiFactorAuthentication: {
|
||||||
|
oauthBaseUrl: getConfigValue(configMapping.MultiFactorAuthentication.oauthBaseUrl),
|
||||||
|
oauthClientId: getConfigValue(configMapping.MultiFactorAuthentication.oauthClientId),
|
||||||
|
oauthClientSecret: getConfigValue(configMapping.MultiFactorAuthentication.oauthClientSecret),
|
||||||
|
oauthIssuerBaseUrl: getConfigValue(configMapping.MultiFactorAuthentication.oauthIssuerBaseUrl),
|
||||||
|
oauthIssuerName: getConfigValue(configMapping.MultiFactorAuthentication.oauthIssuerName),
|
||||||
|
oauthIssuerIcon: getConfigValue(configMapping.MultiFactorAuthentication.oauthIssuerIcon)
|
||||||
|
},
|
||||||
|
Logging: {
|
||||||
|
retentionDays: getConfigValue(configMapping.Logging.retentionDays)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* =====================================================================
|
||||||
|
* ENVIRONMENT VARIABLE REFERENCE
|
||||||
|
* =====================================================================
|
||||||
|
*
|
||||||
|
* Trilium supports flexible environment variable configuration with multiple
|
||||||
|
* naming patterns. Both formats below are equally valid and can be used
|
||||||
|
* based on your preference.
|
||||||
|
*
|
||||||
|
* CONFIGURATION PRECEDENCE:
|
||||||
|
* 1. Environment variables (highest priority)
|
||||||
|
* 2. config.ini file values
|
||||||
|
* 3. Default values (lowest priority)
|
||||||
|
*
|
||||||
|
* FULL FORMAT VARIABLES (following TRILIUM_[SECTION]_[KEY] pattern):
|
||||||
|
* ====================================================================
|
||||||
|
*
|
||||||
|
* General Section:
|
||||||
|
* - TRILIUM_GENERAL_INSTANCENAME : Custom instance identifier
|
||||||
|
* - TRILIUM_GENERAL_NOAUTHENTICATION : Disable auth (true/false)
|
||||||
|
* - TRILIUM_GENERAL_NOBACKUP : Disable backups (true/false)
|
||||||
|
* - TRILIUM_GENERAL_NODESKTOPICON : No desktop icon (true/false)
|
||||||
|
* - TRILIUM_GENERAL_READONLY : Read-only mode (true/false)
|
||||||
|
*
|
||||||
|
* Network Section:
|
||||||
|
* - TRILIUM_NETWORK_HOST : Bind address (e.g., "0.0.0.0")
|
||||||
|
* - TRILIUM_NETWORK_PORT : Server port (e.g., "8080")
|
||||||
|
* - TRILIUM_NETWORK_HTTPS : Enable HTTPS (true/false)
|
||||||
|
* - TRILIUM_NETWORK_CERTPATH : SSL certificate file path
|
||||||
|
* - TRILIUM_NETWORK_KEYPATH : SSL private key file path
|
||||||
|
* - TRILIUM_NETWORK_TRUSTEDREVERSEPROXY : Trust proxy headers (true/false/IP)
|
||||||
|
* - TRILIUM_NETWORK_CORSALLOWORIGIN : CORS allowed origins
|
||||||
|
* - TRILIUM_NETWORK_CORSALLOWMETHODS : CORS allowed HTTP methods
|
||||||
|
* - TRILIUM_NETWORK_CORSALLOWHEADERS : CORS allowed headers
|
||||||
|
*
|
||||||
|
* Session Section:
|
||||||
|
* - TRILIUM_SESSION_COOKIEMAXAGE : Cookie lifetime in seconds
|
||||||
|
*
|
||||||
|
* Sync Section:
|
||||||
|
* - TRILIUM_SYNC_SYNCSERVERHOST : Sync server URL
|
||||||
|
* - TRILIUM_SYNC_SYNCSERVERTIMEOUT : Sync timeout in milliseconds
|
||||||
|
* - TRILIUM_SYNC_SYNCPROXY : Proxy URL for sync
|
||||||
|
*
|
||||||
|
* Multi-Factor Authentication Section:
|
||||||
|
* - TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHBASEURL : OAuth base URL
|
||||||
|
* - TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHCLIENTID : OAuth client ID
|
||||||
|
* - TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHCLIENTSECRET : OAuth client secret
|
||||||
|
* - TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERBASEURL : OAuth issuer URL
|
||||||
|
* - TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERNAME : OAuth provider name
|
||||||
|
* - TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERICON : OAuth provider icon
|
||||||
|
*
|
||||||
|
* Logging Section:
|
||||||
|
* - TRILIUM_LOGGING_RETENTIONDAYS : Log retention period in days
|
||||||
|
*
|
||||||
|
* SHORTER ALTERNATIVE VARIABLES (equally valid, for convenience):
|
||||||
|
* ================================================================
|
||||||
|
*
|
||||||
|
* Network CORS (with underscores):
|
||||||
|
* - TRILIUM_NETWORK_CORS_ALLOW_ORIGIN : Same as TRILIUM_NETWORK_CORSALLOWORIGIN
|
||||||
|
* - TRILIUM_NETWORK_CORS_ALLOW_METHODS : Same as TRILIUM_NETWORK_CORSALLOWMETHODS
|
||||||
|
* - TRILIUM_NETWORK_CORS_ALLOW_HEADERS : Same as TRILIUM_NETWORK_CORSALLOWHEADERS
|
||||||
|
*
|
||||||
|
* Sync (with SERVER prefix):
|
||||||
|
* - TRILIUM_SYNC_SERVER_HOST : Same as TRILIUM_SYNC_SYNCSERVERHOST
|
||||||
|
* - TRILIUM_SYNC_SERVER_TIMEOUT : Same as TRILIUM_SYNC_SYNCSERVERTIMEOUT
|
||||||
|
* - TRILIUM_SYNC_SERVER_PROXY : Same as TRILIUM_SYNC_SYNCPROXY
|
||||||
|
*
|
||||||
|
* OAuth (simplified without section name):
|
||||||
|
* - TRILIUM_OAUTH_BASE_URL : Same as TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHBASEURL
|
||||||
|
* - TRILIUM_OAUTH_CLIENT_ID : Same as TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHCLIENTID
|
||||||
|
* - TRILIUM_OAUTH_CLIENT_SECRET : Same as TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHCLIENTSECRET
|
||||||
|
* - TRILIUM_OAUTH_ISSUER_BASE_URL : Same as TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERBASEURL
|
||||||
|
* - TRILIUM_OAUTH_ISSUER_NAME : Same as TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERNAME
|
||||||
|
* - TRILIUM_OAUTH_ISSUER_ICON : Same as TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERICON
|
||||||
|
*
|
||||||
|
* Logging (with underscore):
|
||||||
|
* - TRILIUM_LOGGING_RETENTION_DAYS : Same as TRILIUM_LOGGING_RETENTIONDAYS
|
||||||
|
*
|
||||||
|
* BOOLEAN VALUES:
|
||||||
|
* - Accept: "true", "false", "1", "0", 1, 0
|
||||||
|
* - Default to false for invalid values
|
||||||
|
*
|
||||||
|
* EXAMPLES:
|
||||||
|
* export TRILIUM_NETWORK_PORT="8080" # Using full format
|
||||||
|
* export TRILIUM_OAUTH_CLIENT_ID="my-client-id" # Using shorter alternative
|
||||||
|
* export TRILIUM_GENERAL_NOAUTHENTICATION="true" # Boolean value
|
||||||
|
* export TRILIUM_SYNC_SERVER_HOST="https://sync.example.com" # Using alternative with SERVER
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The exported configuration object used throughout the Trilium application.
|
||||||
|
* This object is resolved once at startup and remains immutable during runtime.
|
||||||
|
*
|
||||||
|
* To override any setting:
|
||||||
|
* 1. Set an environment variable (see documentation above)
|
||||||
|
* 2. Edit config.ini in your data directory
|
||||||
|
* 3. Defaults will be used if neither is provided
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Accessing configuration in other modules:
|
||||||
|
* import config from './services/config.js';
|
||||||
|
*
|
||||||
|
* if (config.General.noAuthentication) {
|
||||||
|
* // Skip authentication checks
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* const server = https.createServer({
|
||||||
|
* cert: fs.readFileSync(config.Network.certPath),
|
||||||
|
* key: fs.readFileSync(config.Network.keyPath)
|
||||||
|
* });
|
||||||
|
*/
|
||||||
export default config;
|
export default config;
|
Loading…
x
Reference in New Issue
Block a user