mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-04 13:39:01 +01:00 
			
		
		
		
	feat(config): fix previously documented env var formula not working
asdf
This commit is contained in:
		
							parent
							
								
									8abd3ed3f1
								
							
						
					
					
						commit
						eb27ec2234
					
				
							
								
								
									
										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
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default config;
 | 
					/**
 | 
				
			||||||
 | 
					 * 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;
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user