first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
import { Container } from '@n8n/di';
|
||||
|
||||
import { AiConfig } from '../ai.config';
|
||||
|
||||
describe('AiConfig', () => {
|
||||
beforeEach(() => {
|
||||
Container.reset();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should not poison openAiDefaultHeaders object globally when modified', () => {
|
||||
const { openAiDefaultHeaders } = Container.get(AiConfig);
|
||||
openAiDefaultHeaders.test = 'ok';
|
||||
expect(openAiDefaultHeaders.test).toBe('ok');
|
||||
expect(Container.get(AiConfig).openAiDefaultHeaders.test).toBeFalsy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Container } from '@n8n/di';
|
||||
|
||||
import { UserManagementConfig } from '../user-management.config';
|
||||
|
||||
describe('UserManagementConfig', () => {
|
||||
beforeEach(() => {
|
||||
Container.reset();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const originalEnv = process.env;
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
});
|
||||
|
||||
test('with refresh timout > session, sets refresh timout to `0`', () => {
|
||||
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
|
||||
|
||||
process.env = {
|
||||
N8N_USER_MANAGEMENT_JWT_DURATION_HOURS: '1',
|
||||
N8N_USER_MANAGEMENT_JWT_REFRESH_TIMEOUT_HOURS: '2',
|
||||
};
|
||||
|
||||
const config = Container.get(UserManagementConfig);
|
||||
|
||||
expect(config.jwtRefreshTimeoutHours).toBe(0);
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
||||
'N8N_USER_MANAGEMENT_JWT_REFRESH_TIMEOUT_HOURS needs to be smaller than N8N_USER_MANAGEMENT_JWT_DURATION_HOURS. Setting N8N_USER_MANAGEMENT_JWT_REFRESH_TIMEOUT_HOURS to 0.',
|
||||
);
|
||||
|
||||
consoleWarnSpy.mockRestore();
|
||||
});
|
||||
|
||||
test('with refresh timout == session, sets refresh timout to `0`', () => {
|
||||
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
|
||||
|
||||
process.env = {
|
||||
N8N_USER_MANAGEMENT_JWT_DURATION_HOURS: '1',
|
||||
N8N_USER_MANAGEMENT_JWT_REFRESH_TIMEOUT_HOURS: '1',
|
||||
};
|
||||
|
||||
const config = Container.get(UserManagementConfig);
|
||||
|
||||
expect(config.jwtRefreshTimeoutHours).toBe(0);
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
||||
'N8N_USER_MANAGEMENT_JWT_REFRESH_TIMEOUT_HOURS needs to be smaller than N8N_USER_MANAGEMENT_JWT_DURATION_HOURS. Setting N8N_USER_MANAGEMENT_JWT_REFRESH_TIMEOUT_HOURS to 0.',
|
||||
);
|
||||
|
||||
consoleWarnSpy.mockRestore();
|
||||
});
|
||||
|
||||
test('with refresh timout < session, keeps refresh timout intact', () => {
|
||||
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
|
||||
|
||||
process.env = {
|
||||
N8N_USER_MANAGEMENT_JWT_DURATION_HOURS: '10',
|
||||
N8N_USER_MANAGEMENT_JWT_REFRESH_TIMEOUT_HOURS: '5',
|
||||
};
|
||||
|
||||
const config = Container.get(UserManagementConfig);
|
||||
|
||||
expect(config.jwtRefreshTimeoutHours).toBe(5);
|
||||
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
||||
|
||||
consoleWarnSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
@Config
|
||||
export class AiAssistantConfig {
|
||||
/**
|
||||
* Base URL of the AI assistant service.
|
||||
* When set, requests are sent to this URL instead of the default provider endpoint.
|
||||
*/
|
||||
@Env('N8N_AI_ASSISTANT_BASE_URL')
|
||||
baseUrl: string = '';
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
@Config
|
||||
export class AiBuilderConfig {
|
||||
/**
|
||||
* API key for the Anthropic (Claude) provider used by the AI workflow builder.
|
||||
* When set, enables AI-powered workflow and node building.
|
||||
*/
|
||||
@Env('N8N_AI_ANTHROPIC_KEY')
|
||||
apiKey: string = '';
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
@Config
|
||||
export class AiConfig {
|
||||
/** Whether AI features (such as AI nodes and AI assistant) are enabled globally. */
|
||||
@Env('N8N_AI_ENABLED')
|
||||
enabled: boolean = false;
|
||||
|
||||
/**
|
||||
* Maximum time in milliseconds to wait for an HTTP response from an AI service.
|
||||
* Matches the maximum workflow execution timeout, EXECUTIONS_TIMEOUT_MAX (1 hour) so AI calls do not outlive executions.
|
||||
* Default: 3600000 (1 hour).
|
||||
*/
|
||||
@Env('N8N_AI_TIMEOUT_MAX')
|
||||
timeout: number = 3600000;
|
||||
|
||||
/**
|
||||
* Whether workflow and node parameter values may be sent to AI providers.
|
||||
* When false, only structure or placeholders are sent.
|
||||
*/
|
||||
@Env('N8N_AI_ALLOW_SENDING_PARAMETER_VALUES')
|
||||
allowSendingParameterValues: boolean = true;
|
||||
|
||||
/** Whether to persist AI workflow builder sessions to the database. */
|
||||
@Env('N8N_AI_PERSIST_BUILDER_SESSIONS')
|
||||
persistBuilderSessions: boolean = false;
|
||||
|
||||
get openAiDefaultHeaders(): Record<string, string> {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
return { 'openai-platform': 'org-qkmJQuJ2WnvoIKMr2UJwIJkZ' };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { Config, Env, Nested } from '../decorators';
|
||||
|
||||
const samesiteSchema = z.enum(['strict', 'lax', 'none']);
|
||||
|
||||
type Samesite = z.infer<typeof samesiteSchema>;
|
||||
|
||||
@Config
|
||||
class CookieConfig {
|
||||
/** Whether to set the `Secure` flag on the n8n authentication cookie (recommended for HTTPS). */
|
||||
@Env('N8N_SECURE_COOKIE')
|
||||
secure: boolean = true;
|
||||
|
||||
/** Value for the `SameSite` attribute on the n8n authentication cookie (`strict`, `lax`, or `none`). */
|
||||
@Env('N8N_SAMESITE_COOKIE', samesiteSchema)
|
||||
samesite: Samesite = 'lax';
|
||||
}
|
||||
|
||||
@Config
|
||||
export class AuthConfig {
|
||||
@Nested
|
||||
cookie: CookieConfig;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { Config, Env, Nested } from '../decorators';
|
||||
|
||||
const cacheBackendSchema = z.enum(['memory', 'redis', 'auto']);
|
||||
type CacheBackend = z.infer<typeof cacheBackendSchema>;
|
||||
|
||||
@Config
|
||||
class MemoryConfig {
|
||||
/** Maximum size of the in-memory cache in bytes. Default: 3 MiB. */
|
||||
@Env('N8N_CACHE_MEMORY_MAX_SIZE')
|
||||
maxSize: number = 3 * 1024 * 1024; // 3 MiB
|
||||
|
||||
/** Time to live in milliseconds for entries in the memory cache. Default: 1 hour. */
|
||||
@Env('N8N_CACHE_MEMORY_TTL')
|
||||
ttl: number = 3600 * 1000; // 1 hour
|
||||
}
|
||||
|
||||
@Config
|
||||
class RedisConfig {
|
||||
/** Key prefix for cache entries stored in Redis. */
|
||||
@Env('N8N_CACHE_REDIS_KEY_PREFIX')
|
||||
prefix: string = 'cache';
|
||||
|
||||
/** Time to live in milliseconds for Redis cache entries. Set to 0 to disable expiry. Default: 1 hour. */
|
||||
@Env('N8N_CACHE_REDIS_TTL')
|
||||
ttl: number = 3600 * 1000; // 1 hour
|
||||
}
|
||||
|
||||
@Config
|
||||
export class CacheConfig {
|
||||
/** Cache backend: `memory`, `redis`, or `auto` (choose based on deployment). */
|
||||
@Env('N8N_CACHE_BACKEND', cacheBackendSchema)
|
||||
backend: CacheBackend = 'auto';
|
||||
|
||||
@Nested
|
||||
memory: MemoryConfig;
|
||||
|
||||
@Nested
|
||||
redis: RedisConfig;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
@Config
|
||||
export class ChatHubConfig {
|
||||
/**
|
||||
* Time to live in seconds for execution context in Chat Hub.
|
||||
* Maximum duration for a single non-streaming Workflow Agent execution, including wait time.
|
||||
* After this TTL, responses from those executions are no longer captured or sent to the client.
|
||||
*/
|
||||
@Env('N8N_CHAT_HUB_EXECUTION_CONTEXT_TTL')
|
||||
executionContextTtl: number = 3600;
|
||||
|
||||
/**
|
||||
* Time to live in seconds for stream state in Chat Hub.
|
||||
* Inactive streams are cleaned up after this duration.
|
||||
*/
|
||||
@Env('N8N_CHAT_HUB_STREAM_STATE_TTL')
|
||||
streamStateTtl: number = 300;
|
||||
|
||||
/** Maximum number of response chunks to buffer per stream for reconnection in Chat Hub. */
|
||||
@Env('N8N_CHAT_HUB_MAX_BUFFERED_CHUNKS')
|
||||
maxBufferedChunks: number = 1000;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Config, Env, Nested } from '../decorators';
|
||||
|
||||
@Config
|
||||
class CredentialsOverwrite {
|
||||
/**
|
||||
* JSON object of prefilled credential data (overwrites). End users cannot view or edit these values.
|
||||
* Format: `{ "CREDENTIAL_NAME": { "PARAMETER": "VALUE" } }`.
|
||||
*/
|
||||
@Env('CREDENTIALS_OVERWRITE_DATA')
|
||||
data: string = '{}';
|
||||
|
||||
/** Endpoint of an internal API that returns overwritten credential definitions. When set, overwrites are loaded from this endpoint. */
|
||||
@Env('CREDENTIALS_OVERWRITE_ENDPOINT')
|
||||
endpoint: string = '';
|
||||
|
||||
/** Token used to authenticate requests to the credentials overwrite endpoint. */
|
||||
@Env('CREDENTIALS_OVERWRITE_ENDPOINT_AUTH_TOKEN')
|
||||
endpointAuthToken: string = '';
|
||||
|
||||
/** Whether to persist credential overwrites so they survive restarts. */
|
||||
@Env('CREDENTIALS_OVERWRITE_PERSISTENCE')
|
||||
persistence: boolean = false;
|
||||
}
|
||||
|
||||
@Config
|
||||
export class CredentialsConfig {
|
||||
/** Default name suggested when creating new credentials. */
|
||||
@Env('CREDENTIALS_DEFAULT_NAME')
|
||||
defaultName: string = 'My credentials';
|
||||
|
||||
@Nested
|
||||
overwrite: CredentialsOverwrite;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
@Config
|
||||
export class DataTableConfig {
|
||||
/** Maximum total size in bytes allowed for data tables. Default: 50 MiB. */
|
||||
@Env('N8N_DATA_TABLES_MAX_SIZE_BYTES')
|
||||
maxSize: number = 50 * 1024 * 1024;
|
||||
|
||||
/**
|
||||
* Size in bytes at which to warn that a data table is nearing capacity.
|
||||
* If unset, defaults to 80% of maxSize.
|
||||
*/
|
||||
@Env('N8N_DATA_TABLES_WARNING_THRESHOLD_BYTES')
|
||||
warningThreshold?: number;
|
||||
|
||||
/**
|
||||
* Duration in milliseconds to cache data table size checks.
|
||||
* Reduces database load when validating size repeatedly.
|
||||
*/
|
||||
@Env('N8N_DATA_TABLES_SIZE_CHECK_CACHE_DURATION_MS')
|
||||
sizeCheckCacheDuration: number = 5 * 1000;
|
||||
|
||||
/**
|
||||
* Maximum file size in bytes for CSV uploads to data tables.
|
||||
* If unset, the limit is the remaining available storage.
|
||||
*/
|
||||
@Env('N8N_DATA_TABLES_UPLOAD_MAX_FILE_SIZE_BYTES')
|
||||
uploadMaxFileSize?: number;
|
||||
|
||||
/** Interval in milliseconds between cleanup runs for orphaned upload files. Default: 60000 milliseconds. */
|
||||
@Env('N8N_DATA_TABLES_CLEANUP_INTERVAL_MS')
|
||||
cleanupIntervalMs: number = 60 * 1000;
|
||||
|
||||
/**
|
||||
* Age in milliseconds after which an uploaded file is treated as orphaned and deleted during cleanup.
|
||||
* Default: 2 minutes.
|
||||
*/
|
||||
@Env('N8N_DATA_TABLES_FILE_MAX_AGE_MS')
|
||||
fileMaxAgeMs: number = 2 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* Directory for temporary CSV uploads before import. Files in this directory are pruned by cleanup (see fileMaxAgeMs).
|
||||
* Resolved as `<system-tmp-dir>/n8nDataTableUploads` (for example, `/tmp/n8nDataTableUploads`).
|
||||
*/
|
||||
readonly uploadDir: string;
|
||||
|
||||
constructor() {
|
||||
this.uploadDir = path.join(tmpdir(), 'n8nDataTableUploads');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { Config, Env, Nested } from '../decorators';
|
||||
|
||||
const dbLoggingOptionsSchema = z.enum(['query', 'error', 'schema', 'warn', 'info', 'log', 'all']);
|
||||
type DbLoggingOptions = z.infer<typeof dbLoggingOptionsSchema>;
|
||||
|
||||
@Config
|
||||
class LoggingConfig {
|
||||
/** Whether database logging is enabled. */
|
||||
@Env('DB_LOGGING_ENABLED')
|
||||
enabled: boolean = false;
|
||||
|
||||
/**
|
||||
* Database logging verbosity. Only applies when `DB_LOGGING_MAX_EXECUTION_TIME` is greater than 0.
|
||||
*/
|
||||
@Env('DB_LOGGING_OPTIONS', dbLoggingOptionsSchema)
|
||||
options: DbLoggingOptions = 'error';
|
||||
|
||||
/** Only log queries that run longer than this many milliseconds. Set to 0 to disable slow-query logging. */
|
||||
@Env('DB_LOGGING_MAX_EXECUTION_TIME')
|
||||
maxQueryExecutionTime: number = 0;
|
||||
}
|
||||
|
||||
@Config
|
||||
class PostgresSSLConfig {
|
||||
/**
|
||||
* Whether to use SSL/TLS for the Postgres connection.
|
||||
* Defaults to true if any of the SSL cert/key/CA environment variables are set.
|
||||
*/
|
||||
@Env('DB_POSTGRESDB_SSL_ENABLED')
|
||||
enabled: boolean = false;
|
||||
|
||||
/** Path or contents of the CA certificate for Postgres SSL. */
|
||||
@Env('DB_POSTGRESDB_SSL_CA')
|
||||
ca: string = '';
|
||||
|
||||
/** Path or contents of the client certificate for Postgres SSL. */
|
||||
@Env('DB_POSTGRESDB_SSL_CERT')
|
||||
cert: string = '';
|
||||
|
||||
/** Path or contents of the client private key for Postgres SSL. */
|
||||
@Env('DB_POSTGRESDB_SSL_KEY')
|
||||
key: string = '';
|
||||
|
||||
/** Whether to reject Postgres connections when the server certificate cannot be verified. */
|
||||
@Env('DB_POSTGRESDB_SSL_REJECT_UNAUTHORIZED')
|
||||
rejectUnauthorized: boolean = true;
|
||||
}
|
||||
|
||||
@Config
|
||||
class PostgresConfig {
|
||||
/** Postgres database name. */
|
||||
@Env('DB_POSTGRESDB_DATABASE')
|
||||
database: string = 'n8n';
|
||||
|
||||
/** Postgres database host. */
|
||||
@Env('DB_POSTGRESDB_HOST')
|
||||
host: string = 'localhost';
|
||||
|
||||
/** Postgres database password. */
|
||||
@Env('DB_POSTGRESDB_PASSWORD')
|
||||
password: string = '';
|
||||
|
||||
/** Postgres database port. */
|
||||
@Env('DB_POSTGRESDB_PORT')
|
||||
port: number = 5432;
|
||||
|
||||
/** Postgres user name. */
|
||||
@Env('DB_POSTGRESDB_USER')
|
||||
user: string = 'postgres';
|
||||
|
||||
/** Postgres schema to use. */
|
||||
@Env('DB_POSTGRESDB_SCHEMA')
|
||||
schema: string = 'public';
|
||||
|
||||
/** Maximum number of connections in the Postgres connection pool. */
|
||||
@Env('DB_POSTGRESDB_POOL_SIZE')
|
||||
poolSize: number = 2;
|
||||
|
||||
/** Timeout in milliseconds when establishing a new Postgres connection. */
|
||||
@Env('DB_POSTGRESDB_CONNECTION_TIMEOUT')
|
||||
connectionTimeoutMs: number = 20_000;
|
||||
|
||||
/** Time in milliseconds after which an idle connection in the pool is closed. */
|
||||
@Env('DB_POSTGRESDB_IDLE_CONNECTION_TIMEOUT')
|
||||
idleTimeoutMs: number = 30_000;
|
||||
|
||||
/** Maximum time in milliseconds for a single query. Queries exceeding this are cancelled. Set to 0 to disable. */
|
||||
@Env('DB_POSTGRESDB_STATEMENT_TIMEOUT')
|
||||
statementTimeoutMs: number = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
@Nested
|
||||
ssl: PostgresSSLConfig;
|
||||
}
|
||||
|
||||
const sqlitePoolSizeSchema = z.coerce.number().int().gte(1);
|
||||
|
||||
@Config
|
||||
export class SqliteConfig {
|
||||
/** Path to the SQLite database file. */
|
||||
@Env('DB_SQLITE_DATABASE')
|
||||
database: string = 'database.sqlite';
|
||||
|
||||
/** Number of connections in the SQLite connection pool. Must be at least 1. */
|
||||
@Env('DB_SQLITE_POOL_SIZE', sqlitePoolSizeSchema)
|
||||
poolSize: number = 3;
|
||||
|
||||
/**
|
||||
* Whether to run SQLite VACUUM on startup to reclaim space and optimize the file.
|
||||
*
|
||||
* @warning Blocking operation; can significantly increase startup time.
|
||||
*/
|
||||
@Env('DB_SQLITE_VACUUM_ON_STARTUP')
|
||||
executeVacuumOnStartup: boolean = false;
|
||||
}
|
||||
|
||||
const dbTypeSchema = z.enum(['sqlite', 'postgresdb']);
|
||||
type DbType = z.infer<typeof dbTypeSchema>;
|
||||
|
||||
@Config
|
||||
export class DatabaseConfig {
|
||||
/** Database type: `sqlite` or `postgresdb`. */
|
||||
@Env('DB_TYPE', dbTypeSchema)
|
||||
type: DbType = 'sqlite';
|
||||
|
||||
/** Prefix prepended to all n8n table names (useful for shared databases). */
|
||||
@Env('DB_TABLE_PREFIX')
|
||||
tablePrefix: string = '';
|
||||
|
||||
/** Interval in seconds between health-check pings to the database. */
|
||||
@Env('DB_PING_INTERVAL_SECONDS')
|
||||
pingIntervalSeconds: number = 2;
|
||||
|
||||
@Nested
|
||||
logging: LoggingConfig;
|
||||
|
||||
@Nested
|
||||
postgresdb: PostgresConfig;
|
||||
|
||||
@Nested
|
||||
sqlite: SqliteConfig;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
@Config
|
||||
export class DeploymentConfig {
|
||||
/** Deployment type identifier (for example, `default`, `cloud`). Used for telemetry and feature behavior. */
|
||||
@Env('N8N_DEPLOYMENT_TYPE')
|
||||
type: string = 'default';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Config, Env, Nested } from '../decorators';
|
||||
|
||||
@Config
|
||||
class PostHogConfig {
|
||||
/** PostHog project API key for product analytics. */
|
||||
@Env('N8N_DIAGNOSTICS_POSTHOG_API_KEY')
|
||||
apiKey: string = 'phc_4URIAm1uYfJO7j8kWSe0J8lc8IqnstRLS7Jx8NcakHo';
|
||||
|
||||
/** PostHog API host URL. */
|
||||
@Env('N8N_DIAGNOSTICS_POSTHOG_API_HOST')
|
||||
apiHost: string = 'https://us.i.posthog.com';
|
||||
}
|
||||
|
||||
@Config
|
||||
export class DiagnosticsConfig {
|
||||
/** Whether anonymous diagnostics and telemetry are enabled for this instance. */
|
||||
@Env('N8N_DIAGNOSTICS_ENABLED')
|
||||
enabled: boolean = true;
|
||||
|
||||
/** Telemetry endpoint config for the frontend (format: key;baseUrl). */
|
||||
@Env('N8N_DIAGNOSTICS_CONFIG_FRONTEND')
|
||||
frontendConfig: string = '1zPn9bgWPzlQc0p8Gj1uiK6DOTn;https://telemetry.n8n.io';
|
||||
|
||||
/** Telemetry endpoint config for the backend (format: key;baseUrl). */
|
||||
@Env('N8N_DIAGNOSTICS_CONFIG_BACKEND')
|
||||
backendConfig: string = '1zPn7YoGC3ZXE9zLeTKLuQCB4F6;https://telemetry.n8n.io';
|
||||
|
||||
@Nested
|
||||
posthogConfig: PostHogConfig;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
@Config
|
||||
export class DynamicBannersConfig {
|
||||
/** URL to fetch dynamic banner content from (for example, in-app announcements). */
|
||||
@Env('N8N_DYNAMIC_BANNERS_ENDPOINT')
|
||||
endpoint: string = 'https://api.n8n.io/api/banners';
|
||||
|
||||
/** Whether to fetch and show dynamic banners (for example, announcements) from the endpoint. */
|
||||
@Env('N8N_DYNAMIC_BANNERS_ENABLED')
|
||||
enabled: boolean = true;
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { Config, Env, Nested } from '../decorators';
|
||||
|
||||
@Config
|
||||
class PrometheusMetricsConfig {
|
||||
/** Whether to enable the `/metrics` endpoint to expose Prometheus metrics. */
|
||||
@Env('N8N_METRICS')
|
||||
enable: boolean = false;
|
||||
|
||||
/** Prefix for Prometheus metric names. */
|
||||
@Env('N8N_METRICS_PREFIX')
|
||||
prefix: string = 'n8n_';
|
||||
|
||||
/** Whether to expose system and Node.js metrics. See: https://www.npmjs.com/package/prom-client */
|
||||
@Env('N8N_METRICS_INCLUDE_DEFAULT_METRICS')
|
||||
includeDefaultMetrics: boolean = true;
|
||||
|
||||
/** Whether to include a label for workflow ID on workflow metrics. */
|
||||
@Env('N8N_METRICS_INCLUDE_WORKFLOW_ID_LABEL')
|
||||
includeWorkflowIdLabel: boolean = false;
|
||||
|
||||
/** Whether to include a label for node type on node metrics. */
|
||||
@Env('N8N_METRICS_INCLUDE_NODE_TYPE_LABEL')
|
||||
includeNodeTypeLabel: boolean = false;
|
||||
|
||||
/** Whether to include a label for credential type on credential metrics. */
|
||||
@Env('N8N_METRICS_INCLUDE_CREDENTIAL_TYPE_LABEL')
|
||||
includeCredentialTypeLabel: boolean = false;
|
||||
|
||||
/** Whether to expose metrics for API endpoints. See: https://www.npmjs.com/package/express-prom-bundle */
|
||||
@Env('N8N_METRICS_INCLUDE_API_ENDPOINTS')
|
||||
includeApiEndpoints: boolean = false;
|
||||
|
||||
/** Whether to include a label for the path of API endpoint calls. */
|
||||
@Env('N8N_METRICS_INCLUDE_API_PATH_LABEL')
|
||||
includeApiPathLabel: boolean = false;
|
||||
|
||||
/** Whether to include a label for the HTTP method of API endpoint calls. */
|
||||
@Env('N8N_METRICS_INCLUDE_API_METHOD_LABEL')
|
||||
includeApiMethodLabel: boolean = false;
|
||||
|
||||
/** Whether to include a label for the status code of API endpoint calls. */
|
||||
@Env('N8N_METRICS_INCLUDE_API_STATUS_CODE_LABEL')
|
||||
includeApiStatusCodeLabel: boolean = false;
|
||||
|
||||
/** Whether to include metrics for cache hits and misses. */
|
||||
@Env('N8N_METRICS_INCLUDE_CACHE_METRICS')
|
||||
includeCacheMetrics: boolean = false;
|
||||
|
||||
/** Whether to include metrics derived from n8n's internal events */
|
||||
@Env('N8N_METRICS_INCLUDE_MESSAGE_EVENT_BUS_METRICS')
|
||||
includeMessageEventBusMetrics: boolean = false;
|
||||
|
||||
/** Whether to include metrics for jobs in scaling mode. Not supported in multi-main setup. */
|
||||
@Env('N8N_METRICS_INCLUDE_QUEUE_METRICS')
|
||||
includeQueueMetrics: boolean = false;
|
||||
|
||||
/** How often (in seconds) to update queue metrics. */
|
||||
@Env('N8N_METRICS_QUEUE_METRICS_INTERVAL')
|
||||
queueMetricsInterval: number = 20;
|
||||
|
||||
/** How often (in seconds) to update active workflow metric */
|
||||
@Env('N8N_METRICS_ACTIVE_WORKFLOW_METRIC_INTERVAL')
|
||||
activeWorkflowCountInterval: number = 60;
|
||||
|
||||
/** Whether to include a label for workflow name on workflow metrics. */
|
||||
@Env('N8N_METRICS_INCLUDE_WORKFLOW_NAME_LABEL')
|
||||
includeWorkflowNameLabel: boolean = false;
|
||||
|
||||
/** Whether to include workflow execution statistics as metrics. */
|
||||
@Env('N8N_METRICS_INCLUDE_WORKFLOW_STATISTICS')
|
||||
includeWorkflowStatistics: boolean = false;
|
||||
|
||||
/** How often (in seconds) to update workflow statistics metrics. */
|
||||
@Env('N8N_METRICS_WORKFLOW_STATISTICS_INTERVAL')
|
||||
workflowStatisticsInterval: number = 300;
|
||||
}
|
||||
|
||||
@Config
|
||||
export class EndpointsConfig {
|
||||
/** Maximum request payload size in MiB for the API. */
|
||||
@Env('N8N_PAYLOAD_SIZE_MAX')
|
||||
payloadSizeMax: number = 16;
|
||||
|
||||
/** Maximum size in MiB for a single file in multipart/form-data webhook payloads. */
|
||||
@Env('N8N_FORMDATA_FILE_SIZE_MAX')
|
||||
formDataFileSizeMax: number = 200;
|
||||
|
||||
@Nested
|
||||
metrics: PrometheusMetricsConfig;
|
||||
|
||||
/** Path segment for REST API endpoints. */
|
||||
@Env('N8N_ENDPOINT_REST')
|
||||
rest: string = 'rest';
|
||||
|
||||
/** Path segment for form endpoints. */
|
||||
@Env('N8N_ENDPOINT_FORM')
|
||||
form: string = 'form';
|
||||
|
||||
/** Path segment for test form endpoints. */
|
||||
@Env('N8N_ENDPOINT_FORM_TEST')
|
||||
formTest: string = 'form-test';
|
||||
|
||||
/** Path segment for waiting form endpoints. */
|
||||
@Env('N8N_ENDPOINT_FORM_WAIT')
|
||||
formWaiting: string = 'form-waiting';
|
||||
|
||||
/** Path segment for webhook endpoints. */
|
||||
@Env('N8N_ENDPOINT_WEBHOOK')
|
||||
webhook: string = 'webhook';
|
||||
|
||||
/** Path segment for test webhook endpoints. */
|
||||
@Env('N8N_ENDPOINT_WEBHOOK_TEST')
|
||||
webhookTest: string = 'webhook-test';
|
||||
|
||||
/** Path segment for waiting webhook endpoints. */
|
||||
@Env('N8N_ENDPOINT_WEBHOOK_WAIT')
|
||||
webhookWaiting: string = 'webhook-waiting';
|
||||
|
||||
/** Path segment for MCP endpoints. */
|
||||
@Env('N8N_ENDPOINT_MCP')
|
||||
mcp: string = 'mcp';
|
||||
|
||||
/** Path segment for test MCP endpoints. */
|
||||
@Env('N8N_ENDPOINT_MCP_TEST')
|
||||
mcpTest: string = 'mcp-test';
|
||||
|
||||
/** Whether to disable n8n's UI (frontend). */
|
||||
@Env('N8N_DISABLE_UI')
|
||||
disableUi: boolean = false;
|
||||
|
||||
/** Whether to disable production webhooks on the main process, when using webhook-specific processes. */
|
||||
@Env('N8N_DISABLE_PRODUCTION_MAIN_PROCESS')
|
||||
disableProductionWebhooksOnMainProcess: boolean = false;
|
||||
|
||||
/** Colon-separated list of path segments that should not serve the UI (for example, health or webhook-only routes). */
|
||||
@Env('N8N_ADDITIONAL_NON_UI_ROUTES')
|
||||
additionalNonUIRoutes: string = '';
|
||||
|
||||
/** Path for the health check endpoint. */
|
||||
@Env(
|
||||
'N8N_ENDPOINT_HEALTH',
|
||||
z.string().transform((val) => (val.startsWith('/') ? val : `/${val}`)),
|
||||
)
|
||||
health: string = '/healthz';
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { Config, Env, Nested } from '../decorators';
|
||||
|
||||
@Config
|
||||
class LogWriterConfig {
|
||||
/** Number of event log files to retain; older files are rotated out. */
|
||||
@Env('N8N_EVENTBUS_LOGWRITER_KEEPLOGCOUNT')
|
||||
keepLogCount: number = 3;
|
||||
|
||||
/** Maximum size in KB of a single event log file before rotation. Default: 10 MB. */
|
||||
@Env('N8N_EVENTBUS_LOGWRITER_MAXFILESIZEINKB')
|
||||
maxFileSizeInKB: number = 10240; // 10 MB
|
||||
|
||||
/** Base filename for event log files (extension and rotation suffix are added). */
|
||||
@Env('N8N_EVENTBUS_LOGWRITER_LOGBASENAME')
|
||||
logBaseName: string = 'n8nEventLog';
|
||||
}
|
||||
|
||||
const recoveryModeSchema = z.enum(['simple', 'extensive']);
|
||||
type RecoveryMode = z.infer<typeof recoveryModeSchema>;
|
||||
|
||||
@Config
|
||||
export class EventBusConfig {
|
||||
/** Interval in milliseconds to check for and resend unsent event-bus messages. Set to 0 to disable (may rarely allow duplicate sends when non-zero). */
|
||||
@Env('N8N_EVENTBUS_CHECKUNSENTINTERVAL')
|
||||
checkUnsentInterval: number = 0;
|
||||
|
||||
/** Endpoint to retrieve n8n version information from */
|
||||
@Nested
|
||||
logWriter: LogWriterConfig;
|
||||
|
||||
/** After a crash: `extensive` recovers full execution details; `simple` only marks executions as crashed. */
|
||||
@Env('N8N_EVENTBUS_RECOVERY_MODE', recoveryModeSchema)
|
||||
crashRecoveryMode: RecoveryMode = 'extensive';
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import z from 'zod';
|
||||
|
||||
import { Config, Env, Nested } from '../decorators';
|
||||
|
||||
@Config
|
||||
class PruningIntervalsConfig {
|
||||
/** How often (minutes) execution data should be hard-deleted. */
|
||||
@Env('EXECUTIONS_DATA_PRUNE_HARD_DELETE_INTERVAL')
|
||||
hardDelete: number = 15;
|
||||
|
||||
/** How often (minutes) execution data should be soft-deleted. */
|
||||
@Env('EXECUTIONS_DATA_PRUNE_SOFT_DELETE_INTERVAL')
|
||||
softDelete: number = 60;
|
||||
}
|
||||
|
||||
@Config
|
||||
class ConcurrencyConfig {
|
||||
/**
|
||||
* Max production executions allowed to run concurrently. `-1` means unlimited.
|
||||
*
|
||||
* Default for scaling mode is taken from the worker's `--concurrency` flag.
|
||||
*/
|
||||
@Env('N8N_CONCURRENCY_PRODUCTION_LIMIT')
|
||||
productionLimit: number = -1;
|
||||
|
||||
/** Max evaluation executions allowed to run concurrently. `-1` means unlimited. */
|
||||
@Env('N8N_CONCURRENCY_EVALUATION_LIMIT')
|
||||
evaluationLimit: number = -1;
|
||||
}
|
||||
|
||||
@Config
|
||||
class QueueRecoveryConfig {
|
||||
/** How often (minutes) to check for queue recovery. */
|
||||
@Env('N8N_EXECUTIONS_QUEUE_RECOVERY_INTERVAL')
|
||||
interval: number = 180;
|
||||
|
||||
/** Size of batch of executions to check for queue recovery. */
|
||||
@Env('N8N_EXECUTIONS_QUEUE_RECOVERY_BATCH')
|
||||
batchSize: number = 100;
|
||||
}
|
||||
|
||||
@Config
|
||||
class RecoveryConfig {
|
||||
/**
|
||||
* Number of last executions to check when determining if a workflow should be deactivated
|
||||
* when all of the last N executions have crashed.
|
||||
*/
|
||||
@Env('N8N_WORKFLOW_AUTODEACTIVATION_MAX_LAST_EXECUTIONS')
|
||||
maxLastExecutions: number = 3;
|
||||
|
||||
/**
|
||||
* Whether to automatically deactivate workflows that have all their last executions crashed.
|
||||
*/
|
||||
@Env('N8N_WORKFLOW_AUTODEACTIVATION_ENABLED')
|
||||
workflowDeactivationEnabled: boolean = false;
|
||||
}
|
||||
|
||||
const executionModeSchema = z.enum(['regular', 'queue']);
|
||||
|
||||
export type ExecutionMode = z.infer<typeof executionModeSchema>;
|
||||
|
||||
@Config
|
||||
export class ExecutionsConfig {
|
||||
/** Whether to run executions in regular mode (in-process) or scaling mode (in workers). */
|
||||
@Env('EXECUTIONS_MODE', executionModeSchema)
|
||||
mode: ExecutionMode = 'regular';
|
||||
|
||||
/**
|
||||
* How long (seconds) a workflow execution may run for before timeout.
|
||||
* On timeout, the execution will be forcefully stopped. `-1` for unlimited.
|
||||
* Currently unlimited by default - this default will change in a future version.
|
||||
*/
|
||||
@Env('EXECUTIONS_TIMEOUT')
|
||||
timeout: number = -1;
|
||||
|
||||
/** Upper bound in seconds for execution timeout. Default: 1 hour. */
|
||||
@Env('EXECUTIONS_TIMEOUT_MAX')
|
||||
maxTimeout: number = 3600; // 1h
|
||||
|
||||
/** Whether to delete past executions on a rolling basis. */
|
||||
@Env('EXECUTIONS_DATA_PRUNE')
|
||||
pruneData: boolean = true;
|
||||
|
||||
/** How old (hours) a finished execution must be to qualify for soft-deletion. */
|
||||
@Env('EXECUTIONS_DATA_MAX_AGE')
|
||||
pruneDataMaxAge: number = 336;
|
||||
|
||||
/**
|
||||
* Max number of finished executions to keep in database. Does not necessarily
|
||||
* prune to the exact max number. `0` for unlimited.
|
||||
*/
|
||||
@Env('EXECUTIONS_DATA_PRUNE_MAX_COUNT')
|
||||
pruneDataMaxCount: number = 10_000;
|
||||
|
||||
/**
|
||||
* How old (hours) a finished execution must be to qualify for hard-deletion.
|
||||
* This buffer by default excludes recent executions as the user may need
|
||||
* them while building a workflow.
|
||||
*/
|
||||
@Env('EXECUTIONS_DATA_HARD_DELETE_BUFFER')
|
||||
pruneDataHardDeleteBuffer: number = 1;
|
||||
|
||||
@Nested
|
||||
pruneDataIntervals: PruningIntervalsConfig;
|
||||
|
||||
@Nested
|
||||
concurrency: ConcurrencyConfig;
|
||||
|
||||
@Nested
|
||||
queueRecovery: QueueRecoveryConfig;
|
||||
|
||||
@Nested
|
||||
recovery: RecoveryConfig;
|
||||
|
||||
/** Whether to save execution data for failed production executions. This default can be overridden at a workflow level. */
|
||||
@Env('EXECUTIONS_DATA_SAVE_ON_ERROR')
|
||||
saveDataOnError: 'all' | 'none' = 'all';
|
||||
|
||||
/** Whether to save execution data for successful production executions. This default can be overridden at a workflow level. */
|
||||
@Env('EXECUTIONS_DATA_SAVE_ON_SUCCESS')
|
||||
saveDataOnSuccess: 'all' | 'none' = 'all';
|
||||
|
||||
/** Whether to save execution data as each node executes. This default can be overridden at a workflow level. */
|
||||
@Env('EXECUTIONS_DATA_SAVE_ON_PROGRESS')
|
||||
saveExecutionProgress: boolean = false;
|
||||
|
||||
/** Whether to save execution data for manual executions. This default can be overridden at a workflow level. */
|
||||
@Env('EXECUTIONS_DATA_SAVE_MANUAL_EXECUTIONS')
|
||||
saveDataManualExecutions: boolean = true;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { ColonSeparatedStringArray } from '../custom-types';
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
@Config
|
||||
export class ExternalHooksConfig {
|
||||
/** Paths to files that define external lifecycle hooks. Colon-separated for multiple files. */
|
||||
@Env('EXTERNAL_HOOK_FILES')
|
||||
files: ColonSeparatedStringArray = [];
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
const releaseChannelSchema = z.enum(['stable', 'beta', 'nightly', 'dev', 'rc']);
|
||||
type ReleaseChannel = z.infer<typeof releaseChannelSchema>;
|
||||
|
||||
@Config
|
||||
export class GenericConfig {
|
||||
/** Default timezone for the instance. Can be overridden per workflow. */
|
||||
@Env('GENERIC_TIMEZONE')
|
||||
timezone: string = 'America/New_York';
|
||||
|
||||
/** Release channel (for example, stable, beta, nightly). Affects update checks and some defaults. */
|
||||
@Env('N8N_RELEASE_TYPE', releaseChannelSchema)
|
||||
releaseChannel: ReleaseChannel = 'dev';
|
||||
|
||||
/** Seconds to wait for graceful shutdown (for example, finishing executions) before the process exits. */
|
||||
@Env('N8N_GRACEFUL_SHUTDOWN_TIMEOUT')
|
||||
gracefulShutdownTimeout: number = 30;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
@Config
|
||||
export class HiringBannerConfig {
|
||||
/** Whether to show the hiring/recruitment message in the browser devtools console. */
|
||||
@Env('N8N_HIRING_BANNER_ENABLED')
|
||||
enabled: boolean = true;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import path from 'node:path';
|
||||
|
||||
import { Config, Env } from '../decorators';
|
||||
import { getN8nFolder } from '../utils/utils';
|
||||
|
||||
@Config
|
||||
export class InstanceSettingsConfig {
|
||||
/**
|
||||
* Whether to enforce that n8n settings file doesn't have overly wide permissions.
|
||||
* If set to true, n8n will check the permissions of the settings file and
|
||||
* attempt change them to 0600 (only owner has rw access) if they are too wide.
|
||||
*/
|
||||
@Env('N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS')
|
||||
enforceSettingsFilePermissions: boolean = true;
|
||||
|
||||
/**
|
||||
* Encryption key to use for encrypting and decrypting credentials.
|
||||
* If none is provided, a random key will be generated and saved to the settings file on the first launch.
|
||||
* Can be provided directly via N8N_ENCRYPTION_KEY or via a file path using N8N_ENCRYPTION_KEY_FILE.
|
||||
*/
|
||||
@Env('N8N_ENCRYPTION_KEY')
|
||||
encryptionKey: string = '';
|
||||
|
||||
/** User home directory path; falls back to current working directory if not available. */
|
||||
readonly userHome: string;
|
||||
|
||||
/** n8n data directory (for example, ~/.n8n), used for settings, credentials, and local files. */
|
||||
readonly n8nFolder: string;
|
||||
|
||||
constructor() {
|
||||
this.n8nFolder = getN8nFolder();
|
||||
this.userHome = path.dirname(this.n8nFolder);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
@Config
|
||||
export class LicenseConfig {
|
||||
/** URL of the license server used to validate and refresh licenses. */
|
||||
@Env('N8N_LICENSE_SERVER_URL')
|
||||
serverUrl: string = 'https://license.n8n.io/v1';
|
||||
|
||||
/** Whether to automatically renew licenses before they expire. */
|
||||
@Env('N8N_LICENSE_AUTO_RENEW_ENABLED')
|
||||
autoRenewalEnabled: boolean = true;
|
||||
|
||||
/** Activation key used to activate or upgrade the instance license. */
|
||||
@Env('N8N_LICENSE_ACTIVATION_KEY')
|
||||
activationKey: string = '';
|
||||
|
||||
/** Whether to release floating entitlements back to the pool when the instance shuts down. */
|
||||
@Env('N8N_LICENSE_DETACH_FLOATING_ON_SHUTDOWN')
|
||||
detachFloatingOnShutdown: boolean = true;
|
||||
|
||||
/** Tenant identifier for the license SDK (for example, self-hosted, sandbox, embed, cloud). */
|
||||
@Env('N8N_LICENSE_TENANT_ID')
|
||||
tenantId: number = 1;
|
||||
|
||||
/** Ephemeral license certificate. See: https://github.com/n8n-io/license-management?tab=readme-ov-file#concept-ephemeral-entitlements */
|
||||
@Env('N8N_LICENSE_CERT')
|
||||
cert: string = '';
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { CommaSeparatedStringArray } from '../custom-types';
|
||||
import { Config, Env, Nested } from '../decorators';
|
||||
|
||||
/** Scopes (areas of functionality) to filter logs by. */
|
||||
export const LOG_SCOPES = [
|
||||
'concurrency',
|
||||
'external-secrets',
|
||||
'license',
|
||||
'mcp',
|
||||
'multi-main-setup',
|
||||
'pruning',
|
||||
'pubsub',
|
||||
'push',
|
||||
'quick-connect',
|
||||
'redis',
|
||||
'scaling',
|
||||
'waiting-executions',
|
||||
'task-runner',
|
||||
'task-runner-js',
|
||||
'task-runner-py',
|
||||
'insights',
|
||||
'workflow-activation',
|
||||
'ssh-client',
|
||||
'data-table',
|
||||
'cron',
|
||||
'community-nodes',
|
||||
'chat-hub',
|
||||
'breaking-changes',
|
||||
'circuit-breaker',
|
||||
'source-control',
|
||||
'dynamic-credentials',
|
||||
'workflow-history-compaction',
|
||||
] as const;
|
||||
|
||||
export type LogScope = (typeof LOG_SCOPES)[number];
|
||||
|
||||
@Config
|
||||
export class CronLoggingConfig {
|
||||
/**
|
||||
* Interval in minutes to log currently active cron jobs. Set to `0` to disable.
|
||||
*
|
||||
* @example `N8N_LOG_CRON_ACTIVE_INTERVAL=30` will log active crons every 30 minutes.
|
||||
*/
|
||||
@Env('N8N_LOG_CRON_ACTIVE_INTERVAL')
|
||||
activeInterval: number = 0;
|
||||
}
|
||||
|
||||
@Config
|
||||
class FileLoggingConfig {
|
||||
/**
|
||||
* Max number of log files to keep, or max number of days to keep logs for.
|
||||
* Once the limit is reached, the oldest log files will be rotated out.
|
||||
* If using days, append a `d` suffix. Only for `file` log output.
|
||||
*
|
||||
* @example `N8N_LOG_FILE_COUNT_MAX=7` will keep at most 7 files.
|
||||
* @example `N8N_LOG_FILE_COUNT_MAX=7d` will keep at most 7 days worth of files.
|
||||
*/
|
||||
@Env('N8N_LOG_FILE_COUNT_MAX')
|
||||
fileCountMax: number = 100;
|
||||
|
||||
/** Max size (in MiB) for each log file. Only for `file` log output. */
|
||||
@Env('N8N_LOG_FILE_SIZE_MAX')
|
||||
fileSizeMax: number = 16;
|
||||
|
||||
/** Location of the log files inside `~/.n8n`. Only for `file` log output. */
|
||||
@Env('N8N_LOG_FILE_LOCATION')
|
||||
location: string = 'logs/n8n.log';
|
||||
}
|
||||
|
||||
const logLevelSchema = z.enum(['error', 'warn', 'info', 'debug', 'silent']);
|
||||
type LogLevel = z.infer<typeof logLevelSchema>;
|
||||
|
||||
@Config
|
||||
export class LoggingConfig {
|
||||
/**
|
||||
* Minimum level of logs to output. Logs with this or higher level will be output;
|
||||
* logs with lower levels will not. Exception: `silent` disables all logging.
|
||||
*
|
||||
* @example `N8N_LOG_LEVEL=info` will output `error`, `warn` and `info` logs, but not `debug`.
|
||||
*/
|
||||
@Env('N8N_LOG_LEVEL', logLevelSchema)
|
||||
level: LogLevel = 'info';
|
||||
|
||||
/**
|
||||
* Where to output logs to. Options are: `console` or `file` or both in a comma separated list.
|
||||
*
|
||||
* @example `N8N_LOG_OUTPUT=console,file` will output to both console and file.
|
||||
*/
|
||||
@Env('N8N_LOG_OUTPUT')
|
||||
outputs: CommaSeparatedStringArray<'console' | 'file'> = ['console'];
|
||||
|
||||
/**
|
||||
* What format the logs should have.
|
||||
* `text` is only printing the human readable messages.
|
||||
* `json` is printing one JSON object per line containing the message, level,
|
||||
* timestamp and all the metadata.
|
||||
*/
|
||||
@Env('N8N_LOG_FORMAT')
|
||||
format: 'text' | 'json' = 'text';
|
||||
|
||||
@Nested
|
||||
file: FileLoggingConfig;
|
||||
|
||||
@Nested
|
||||
cron: CronLoggingConfig;
|
||||
|
||||
/**
|
||||
* Scopes to filter logs by. Nothing is filtered by default.
|
||||
*
|
||||
* Supported log scopes:
|
||||
*
|
||||
* - `concurrency`
|
||||
* - `external-secrets`
|
||||
* - `license`
|
||||
* - `multi-main-setup`
|
||||
* - `pruning`
|
||||
* - `pubsub`
|
||||
* - `push`
|
||||
* - `redis`
|
||||
* - `scaling`
|
||||
* - `waiting-executions`
|
||||
* - `task-runner-js`
|
||||
* - `task-runner-py`
|
||||
* - `workflow-activation`
|
||||
* - `insights`
|
||||
* - `chat-hub`
|
||||
*
|
||||
* @example
|
||||
* `N8N_LOG_SCOPES=license`
|
||||
* `N8N_LOG_SCOPES=license,waiting-executions`
|
||||
*/
|
||||
@Env('N8N_LOG_SCOPES')
|
||||
scopes: CommaSeparatedStringArray<LogScope> = [];
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
@Config
|
||||
export class MfaConfig {
|
||||
/** Whether multi-factor authentication (MFA) is enabled for the instance. */
|
||||
@Env('N8N_MFA_ENABLED')
|
||||
enabled: boolean = true;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
@Config
|
||||
export class MultiMainSetupConfig {
|
||||
/** Whether to enable multi-main setup when using scaling mode (requires license). */
|
||||
@Env('N8N_MULTI_MAIN_SETUP_ENABLED')
|
||||
enabled: boolean = false;
|
||||
|
||||
/** Time to live in seconds for the leader lock key; the current leader must renew before this expires. */
|
||||
@Env('N8N_MULTI_MAIN_SETUP_KEY_TTL')
|
||||
ttl: number = 10;
|
||||
|
||||
/** Interval in seconds between leader eligibility checks in multi-main setup. */
|
||||
@Env('N8N_MULTI_MAIN_SETUP_CHECK_INTERVAL')
|
||||
interval: number = 3;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
function isStringArray(input: unknown): input is string[] {
|
||||
return Array.isArray(input) && input.every((item) => typeof item === 'string');
|
||||
}
|
||||
|
||||
class JsonStringArray extends Array<string> {
|
||||
constructor(str: string) {
|
||||
super();
|
||||
|
||||
let parsed: unknown;
|
||||
|
||||
try {
|
||||
parsed = JSON.parse(str);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
|
||||
return isStringArray(parsed) ? parsed : [];
|
||||
}
|
||||
}
|
||||
|
||||
@Config
|
||||
export class NodesConfig {
|
||||
/** Node types to load. If empty, all available nodes are loaded. Example: `["n8n-nodes-base.hackerNews"]`. */
|
||||
@Env('NODES_INCLUDE')
|
||||
include: JsonStringArray = [];
|
||||
|
||||
/**
|
||||
* Node types to exclude from loading. Default excludes `ExecuteCommand` and `LocalFileTrigger` for security.
|
||||
* Set to an empty array to allow all node types.
|
||||
*
|
||||
* @example '["n8n-nodes-base.hackerNews"]'
|
||||
*/
|
||||
@Env('NODES_EXCLUDE')
|
||||
exclude: JsonStringArray = ['n8n-nodes-base.executeCommand', 'n8n-nodes-base.localFileTrigger'];
|
||||
|
||||
/** Node type name used as the default error trigger when workflow execution fails. */
|
||||
@Env('NODES_ERROR_TRIGGER_TYPE')
|
||||
errorTriggerType: string = 'n8n-nodes-base.errorTrigger';
|
||||
|
||||
/** Whether to enable Python execution on the Code node. */
|
||||
@Env('N8N_PYTHON_ENABLED')
|
||||
pythonEnabled: boolean = true;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
@Config
|
||||
export class PersonalizationConfig {
|
||||
/** Whether to enable personalization features. */
|
||||
@Env('N8N_PERSONALIZATION_ENABLED')
|
||||
enabled: boolean = true;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
@Config
|
||||
export class PublicApiConfig {
|
||||
/** When true, the public API is disabled and its routes are not registered. */
|
||||
@Env('N8N_PUBLIC_API_DISABLED')
|
||||
disabled: boolean = false;
|
||||
|
||||
/** URL path segment for the Public API (for example, /api/v1/...). */
|
||||
@Env('N8N_PUBLIC_API_ENDPOINT')
|
||||
path: string = 'api';
|
||||
|
||||
/** When true, the Swagger UI for the Public API is not served. */
|
||||
@Env('N8N_PUBLIC_API_SWAGGERUI_DISABLED')
|
||||
swaggerUiDisabled: boolean = false;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
@Config
|
||||
export class RedisConfig {
|
||||
/** Key prefix for all Redis keys used by n8n (avoids clashes when sharing a Redis instance). */
|
||||
@Env('N8N_REDIS_KEY_PREFIX')
|
||||
prefix: string = 'n8n';
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
const runnerModeSchema = z.enum(['internal', 'external']);
|
||||
|
||||
export type TaskRunnerMode = z.infer<typeof runnerModeSchema>;
|
||||
|
||||
@Config
|
||||
export class TaskRunnersConfig {
|
||||
/**
|
||||
* How the task runner runs: `internal` (child process of n8n) or `external` (separate process).
|
||||
*/
|
||||
@Env('N8N_RUNNERS_MODE', runnerModeSchema)
|
||||
mode: TaskRunnerMode = 'internal';
|
||||
|
||||
/** URL path segment where the task runner service is exposed (for example, `/runners`). */
|
||||
@Env('N8N_RUNNERS_PATH')
|
||||
path: string = '/runners';
|
||||
|
||||
/** Shared secret used to authenticate runner processes with the broker. */
|
||||
@Env('N8N_RUNNERS_AUTH_TOKEN')
|
||||
authToken: string = '';
|
||||
|
||||
/** Port the task runner broker listens on for runner connections. */
|
||||
@Env('N8N_RUNNERS_BROKER_PORT')
|
||||
port: number = 5679;
|
||||
|
||||
/** IP address the task runner broker binds to. */
|
||||
@Env('N8N_RUNNERS_BROKER_LISTEN_ADDRESS')
|
||||
listenAddress: string = '127.0.0.1';
|
||||
|
||||
/** Maximum size in bytes of a payload sent to a runner. Default: 1 GiB. */
|
||||
@Env('N8N_RUNNERS_MAX_PAYLOAD')
|
||||
maxPayload: number = 1024 * 1024 * 1024;
|
||||
|
||||
/** Node.js `--max-old-space-size` value in MB for the runner process. Empty lets Node choose based on memory. */
|
||||
@Env('N8N_RUNNERS_MAX_OLD_SPACE_SIZE')
|
||||
maxOldSpaceSize: string = '';
|
||||
|
||||
/** Maximum number of tasks a single runner can execute concurrently. */
|
||||
@Env('N8N_RUNNERS_MAX_CONCURRENCY')
|
||||
maxConcurrency: number = 10;
|
||||
|
||||
/**
|
||||
* How long (in seconds) a task is allowed to take for completion, else the
|
||||
* task will be aborted. (In internal mode, the runner will also be
|
||||
* restarted.) Must be greater than 0.
|
||||
*
|
||||
* Kept high for backwards compatibility - n8n v3 will reduce this to `60`
|
||||
*/
|
||||
@Env('N8N_RUNNERS_TASK_TIMEOUT')
|
||||
taskTimeout: number = 300; // 5 minutes
|
||||
|
||||
/**
|
||||
* How long (in seconds) a task request can wait for a runner to become
|
||||
* available before timing out. This prevents workflows from hanging
|
||||
* indefinitely when no runners are available. Must be greater than 0.
|
||||
*/
|
||||
@Env('N8N_RUNNERS_TASK_REQUEST_TIMEOUT')
|
||||
taskRequestTimeout: number = 60;
|
||||
|
||||
/** Interval in seconds between heartbeats from runner to broker; missing heartbeats abort the task (and restart the runner in internal mode). Must be > 0. */
|
||||
@Env('N8N_RUNNERS_HEARTBEAT_INTERVAL')
|
||||
heartbeatInterval: number = 30;
|
||||
|
||||
/**
|
||||
* Whether to disable all security measures in the task runner. **Discouraged for production use.**
|
||||
* Set to `true` for compatibility with modules that rely on insecure JS features.
|
||||
*/
|
||||
@Env('N8N_RUNNERS_INSECURE_MODE')
|
||||
insecureMode: boolean = false;
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import { Config, Env, Nested } from '../decorators';
|
||||
|
||||
@Config
|
||||
class HealthConfig {
|
||||
/**
|
||||
* Whether to enable worker health endpoints: `/healthz` (liveness) and `/healthz/readiness` (DB and Redis ready).
|
||||
*/
|
||||
@Env('QUEUE_HEALTH_CHECK_ACTIVE')
|
||||
active: boolean = false;
|
||||
|
||||
/** Port the worker HTTP server listens on for health checks. */
|
||||
@Env('QUEUE_HEALTH_CHECK_PORT')
|
||||
port: number = 5678;
|
||||
|
||||
/** IP address the worker server binds to. Use `::` for all interfaces. */
|
||||
@Env('N8N_WORKER_SERVER_ADDRESS')
|
||||
address: string = '::';
|
||||
}
|
||||
|
||||
@Config
|
||||
class RedisConfig {
|
||||
/** Redis database for Bull queue. */
|
||||
@Env('QUEUE_BULL_REDIS_DB')
|
||||
db: number = 0;
|
||||
|
||||
/** Redis host for Bull queue. */
|
||||
@Env('QUEUE_BULL_REDIS_HOST')
|
||||
host: string = 'localhost';
|
||||
|
||||
/** Password to authenticate with Redis. */
|
||||
@Env('QUEUE_BULL_REDIS_PASSWORD')
|
||||
password: string = '';
|
||||
|
||||
/** Port for Redis to listen on. */
|
||||
@Env('QUEUE_BULL_REDIS_PORT')
|
||||
port: number = 6379;
|
||||
|
||||
/** Max cumulative timeout (in milliseconds) of connection retries before process exit. */
|
||||
@Env('QUEUE_BULL_REDIS_TIMEOUT_THRESHOLD')
|
||||
timeoutThreshold: number = 10_000;
|
||||
|
||||
/** Slot refresh timeout (in milliseconds) before a timeout occurs while refreshing slots from the cluster. */
|
||||
@Env('QUEUE_BULL_REDIS_SLOT_REFRESH_TIMEOUT')
|
||||
slotsRefreshTimeout: number = 1_000;
|
||||
/** Slot refresh interval (in milliseconds) between every automatic slot refresh. */
|
||||
@Env('QUEUE_BULL_REDIS_SLOT_REFRESH_INTERVAL')
|
||||
slotsRefreshInterval: number = 5_000;
|
||||
|
||||
/** Redis username. Redis 6.0 or higher required. */
|
||||
@Env('QUEUE_BULL_REDIS_USERNAME')
|
||||
username: string = '';
|
||||
|
||||
/** Redis cluster startup nodes, as comma-separated list of `{host}:{port}` pairs. @example 'redis-1:6379,redis-2:6379' */
|
||||
@Env('QUEUE_BULL_REDIS_CLUSTER_NODES')
|
||||
clusterNodes: string = '';
|
||||
|
||||
/** Whether to enable TLS on Redis connections. */
|
||||
@Env('QUEUE_BULL_REDIS_TLS')
|
||||
tls: boolean = false;
|
||||
|
||||
/**
|
||||
* DNS resolution strategy for Redis hostnames on initial client connection.
|
||||
* - `LOOKUP` (default): Use system DNS resolver to resolve hostnames to IP addresses.
|
||||
* - `NONE`: Disable DNS resolution and pass hostnames directly to Redis client.
|
||||
*
|
||||
* DNS lookups can be error prone, especially in combination with TLS certificates.
|
||||
* Especially AWS Elasticache cluster connections often lead to invalid certificate errors due to hostname/ip mismatches.
|
||||
* For AWS ElastiCache clusters with TLS, it is recommended to set this option to `NONE`.
|
||||
* @see https://github.com/redis/ioredis?tab=readme-ov-file#special-note-aws-elasticache-clusters-with-tls
|
||||
*/
|
||||
@Env('QUEUE_BULL_REDIS_DNS_LOOKUP_STRATEGY')
|
||||
dnsResolveStrategy: 'LOOKUP' | 'NONE' = 'LOOKUP';
|
||||
|
||||
/** Whether to enable dual-stack hostname resolution for Redis connections. */
|
||||
@Env('QUEUE_BULL_REDIS_DUALSTACK')
|
||||
dualStack: boolean = false;
|
||||
|
||||
/** Whether to enable TCP keep-alive on Redis connections. */
|
||||
@Env('QUEUE_BULL_REDIS_KEEP_ALIVE')
|
||||
keepAlive: boolean = false;
|
||||
|
||||
/** TCP keep-alive initial delay in milliseconds. */
|
||||
@Env('QUEUE_BULL_REDIS_KEEP_ALIVE_DELAY')
|
||||
keepAliveDelay: number = 5000;
|
||||
|
||||
/** TCP keep-alive interval in milliseconds. */
|
||||
@Env('QUEUE_BULL_REDIS_KEEP_ALIVE_INTERVAL')
|
||||
keepAliveInterval: number = 5000;
|
||||
|
||||
/** Whether to reconnect to Redis on READONLY errors i.e., failover events. */
|
||||
@Env('QUEUE_BULL_REDIS_RECONNECT_ON_FAILOVER')
|
||||
reconnectOnFailover: boolean = true;
|
||||
}
|
||||
|
||||
@Config
|
||||
class SettingsConfig {
|
||||
/** How long (in milliseconds) is the lease period for a worker processing a job. */
|
||||
@Env('QUEUE_WORKER_LOCK_DURATION')
|
||||
lockDuration: number = 60_000;
|
||||
|
||||
/** How often (in milliseconds) a worker must renew the lease. */
|
||||
@Env('QUEUE_WORKER_LOCK_RENEW_TIME')
|
||||
lockRenewTime: number = 10_000;
|
||||
|
||||
/** How often (in milliseconds) Bull must check for stalled jobs. `0` to disable. */
|
||||
@Env('QUEUE_WORKER_STALLED_INTERVAL')
|
||||
stalledInterval: number = 30_000;
|
||||
}
|
||||
|
||||
@Config
|
||||
class BullConfig {
|
||||
/** Prefix for Bull keys on Redis. @example 'bull:jobs:23' */
|
||||
@Env('QUEUE_BULL_PREFIX')
|
||||
prefix: string = 'bull';
|
||||
|
||||
@Nested
|
||||
redis: RedisConfig;
|
||||
|
||||
/** @deprecated How long (in seconds) a worker must wait for active executions to finish before exiting. Use `N8N_GRACEFUL_SHUTDOWN_TIMEOUT` instead */
|
||||
@Env('QUEUE_WORKER_TIMEOUT')
|
||||
gracefulShutdownTimeout: number = 30;
|
||||
|
||||
@Nested
|
||||
settings: SettingsConfig;
|
||||
}
|
||||
|
||||
@Config
|
||||
export class ScalingModeConfig {
|
||||
@Nested
|
||||
health: HealthConfig;
|
||||
|
||||
@Nested
|
||||
bull: BullConfig;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import z from 'zod';
|
||||
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
const crossOriginOpenerPolicySchema = z.enum(['same-origin', 'same-origin-allow-popups']);
|
||||
|
||||
@Config
|
||||
export class SecurityConfig {
|
||||
/**
|
||||
* Dirs that the `ReadWriteFile` and `ReadBinaryFiles` nodes are allowed to access. Separate multiple dirs with semicolon `;`.
|
||||
* Set to an empty string to disable restrictions (insecure, not recommended for production).
|
||||
*
|
||||
* @example N8N_RESTRICT_FILE_ACCESS_TO=/home/john/my-n8n-files
|
||||
*/
|
||||
@Env('N8N_RESTRICT_FILE_ACCESS_TO')
|
||||
restrictFileAccessTo: string = '~/.n8n-files';
|
||||
|
||||
/**
|
||||
* Whether to block nodes from accessing files at dirs internally used by n8n:
|
||||
* - `~/.n8n`
|
||||
* - `~/.cache/n8n/public`
|
||||
* - any dirs specified by `N8N_CONFIG_FILES`, `N8N_CUSTOM_EXTENSIONS`, `N8N_BINARY_DATA_STORAGE_PATH`, `N8N_UM_EMAIL_TEMPLATES_INVITE`, and `UM_EMAIL_TEMPLATES_PWRESET`.
|
||||
*/
|
||||
@Env('N8N_BLOCK_FILE_ACCESS_TO_N8N_FILES')
|
||||
blockFileAccessToN8nFiles: boolean = true;
|
||||
|
||||
/**
|
||||
* Regex patterns for files and folders that `ReadWriteFile` and `ReadBinaryFiles` nodes cannot access.
|
||||
* Separate multiple patterns with semicolons. Default blocks `.git`. Set to empty to disable pattern-based blocking.
|
||||
*/
|
||||
@Env('N8N_BLOCK_FILE_PATTERNS')
|
||||
blockFilePatterns: string = '^(.*\\/)*\\.git(\\/.*)*$';
|
||||
|
||||
/**
|
||||
* In a [security audit](https://docs.n8n.io/hosting/securing/security-audit/), how many days for a workflow to be considered abandoned if not executed.
|
||||
*/
|
||||
@Env('N8N_SECURITY_AUDIT_DAYS_ABANDONED_WORKFLOW')
|
||||
daysAbandonedWorkflow: number = 90;
|
||||
|
||||
/**
|
||||
* Set [Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) headers as [helmet.js](https://helmetjs.github.io/#content-security-policy) nested directives object.
|
||||
* Example: { "frame-ancestors": ["http://localhost:3000"] }
|
||||
*/
|
||||
// TODO: create a new type that parses and validates this string into a strongly-typed object
|
||||
@Env('N8N_CONTENT_SECURITY_POLICY')
|
||||
contentSecurityPolicy: string = '{}';
|
||||
|
||||
/**
|
||||
* Whether to set the `Content-Security-Policy-Report-Only` header instead of `Content-Security-Policy`.
|
||||
*/
|
||||
@Env('N8N_CONTENT_SECURITY_POLICY_REPORT_ONLY')
|
||||
contentSecurityPolicyReportOnly: boolean = false;
|
||||
|
||||
/**
|
||||
* Configuration for the `Cross-Origin-Opener-Policy` header.
|
||||
*/
|
||||
@Env('N8N_CROSS_ORIGIN_OPENER_POLICY', crossOriginOpenerPolicySchema)
|
||||
crossOriginOpenerPolicy: z.infer<typeof crossOriginOpenerPolicySchema> = 'same-origin';
|
||||
|
||||
/**
|
||||
* Whether to disable HTML sandboxing for webhooks. The sandboxing mechanism uses CSP headers now,
|
||||
* but the name is kept for backwards compatibility.
|
||||
*/
|
||||
@Env('N8N_INSECURE_DISABLE_WEBHOOK_IFRAME_SANDBOX')
|
||||
disableWebhookHtmlSandboxing: boolean = false;
|
||||
|
||||
/**
|
||||
* Whether to disable bare repositories support in the Git node.
|
||||
*/
|
||||
@Env('N8N_GIT_NODE_DISABLE_BARE_REPOS')
|
||||
disableBareRepos: boolean = true;
|
||||
|
||||
/** Whether to allow access to AWS system credentials, e.g. in awsAssumeRole credentials */
|
||||
@Env('N8N_AWS_SYSTEM_CREDENTIALS_ACCESS_ENABLED')
|
||||
awsSystemCredentialsAccess: boolean = false;
|
||||
|
||||
/**
|
||||
* Whether to enable hooks (like pre-commit hooks) for the Git node.
|
||||
*/
|
||||
@Env('N8N_GIT_NODE_ENABLE_HOOKS')
|
||||
enableGitNodeHooks: boolean = false;
|
||||
|
||||
/**
|
||||
* Whether to enable arbitrary git config keys.
|
||||
*/
|
||||
@Env('N8N_GIT_NODE_ENABLE_ALL_CONFIG_KEYS')
|
||||
enableGitNodeAllConfigKeys: boolean = false;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
/** Schema for sample rates (0.0 to 1.0). */
|
||||
export const sampleRateSchema = z.number({ coerce: true }).min(0).max(1);
|
||||
|
||||
@Config
|
||||
export class SentryConfig {
|
||||
/** Sentry DSN (data source name) for the backend. */
|
||||
@Env('N8N_SENTRY_DSN')
|
||||
backendDsn: string = '';
|
||||
|
||||
/** Sentry DSN (data source name) for the frontend. */
|
||||
@Env('N8N_FRONTEND_SENTRY_DSN')
|
||||
frontendDsn: string = '';
|
||||
|
||||
/**
|
||||
* Sample rate for Sentry traces (0.0 to 1.0).
|
||||
* This determines whether tracing is enabled and what percentage of
|
||||
* transactions are traced.
|
||||
*
|
||||
* @default 0 (disabled)
|
||||
*/
|
||||
@Env('N8N_SENTRY_TRACES_SAMPLE_RATE', sampleRateSchema)
|
||||
tracesSampleRate: number = 0;
|
||||
|
||||
/**
|
||||
* Sample rate for Sentry profiling (0.0 to 1.0).
|
||||
* This determines whether profiling is enabled and what percentage of
|
||||
* transactions are profiled.
|
||||
*
|
||||
* @default 0 (disabled)
|
||||
*/
|
||||
@Env('N8N_SENTRY_PROFILES_SAMPLE_RATE', sampleRateSchema)
|
||||
profilesSampleRate: number = 0;
|
||||
|
||||
/**
|
||||
* Threshold in milliseconds for event loop block detection.
|
||||
* When the event loop is blocked for longer than this threshold,
|
||||
* Sentry will report it.
|
||||
*
|
||||
* @default 500
|
||||
*/
|
||||
@Env('N8N_SENTRY_EVENT_LOOP_BLOCK_THRESHOLD', z.number({ coerce: true }).int().positive())
|
||||
eventLoopBlockThreshold: number = 500;
|
||||
|
||||
/**
|
||||
* Environment of the n8n instance.
|
||||
*
|
||||
* @example 'production'
|
||||
*/
|
||||
@Env('ENVIRONMENT')
|
||||
environment: string = '';
|
||||
|
||||
/**
|
||||
* Name of the deployment, e.g. cloud account name.
|
||||
*
|
||||
* @example 'janober'
|
||||
*/
|
||||
@Env('DEPLOYMENT_NAME')
|
||||
deploymentName: string = '';
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { Config, Env, Nested } from '../decorators';
|
||||
|
||||
@Config
|
||||
class SamlConfig {
|
||||
/** Whether SAML-based single sign-on is enabled. */
|
||||
@Env('N8N_SSO_SAML_LOGIN_ENABLED')
|
||||
loginEnabled: boolean = false;
|
||||
|
||||
/** Label shown on the login button for SAML (for example, "Sign in with SAML"). */
|
||||
@Env('N8N_SSO_SAML_LOGIN_LABEL')
|
||||
loginLabel: string = '';
|
||||
}
|
||||
|
||||
@Config
|
||||
class OidcConfig {
|
||||
/** Whether OIDC-based single sign-on is enabled. */
|
||||
@Env('N8N_SSO_OIDC_LOGIN_ENABLED')
|
||||
loginEnabled: boolean = false;
|
||||
}
|
||||
|
||||
@Config
|
||||
class LdapConfig {
|
||||
/** Whether LDAP-based single sign-on is enabled. */
|
||||
@Env('N8N_SSO_LDAP_LOGIN_ENABLED')
|
||||
loginEnabled: boolean = false;
|
||||
|
||||
/** Label shown on the login button for LDAP (for example, "Sign in with LDAP"). */
|
||||
@Env('N8N_SSO_LDAP_LOGIN_LABEL')
|
||||
loginLabel: string = '';
|
||||
}
|
||||
|
||||
@Config
|
||||
class ProvisioningConfig {
|
||||
/** Whether to set the user's instance role from an SSO claim during login. */
|
||||
@Env('N8N_SSO_SCOPES_PROVISION_INSTANCE_ROLE')
|
||||
scopesProvisionInstanceRole: boolean = false;
|
||||
|
||||
/** Whether to set project–role mappings from an SSO claim during login. */
|
||||
@Env('N8N_SSO_SCOPES_PROVISION_PROJECT_ROLES')
|
||||
scopesProvisionProjectRoles: boolean = false;
|
||||
|
||||
/** Name of the OAuth scope to request for SSO provisioning. */
|
||||
@Env('N8N_SSO_SCOPES_NAME')
|
||||
scopesName: string = 'n8n';
|
||||
|
||||
/** Name of the SSO claim that contains the user's instance role (for provisioning). */
|
||||
@Env('N8N_SSO_SCOPES_INSTANCE_ROLE_CLAIM_NAME')
|
||||
scopesInstanceRoleClaimName: string = 'n8n_instance_role';
|
||||
|
||||
/** Name of the SSO claim that contains project–role mappings (for provisioning). */
|
||||
@Env('N8N_SSO_SCOPES_PROJECTS_ROLES_CLAIM_NAME')
|
||||
scopesProjectsRolesClaimName: string = 'n8n_projects';
|
||||
}
|
||||
|
||||
@Config
|
||||
export class SsoConfig {
|
||||
/** Whether to automatically create user accounts when someone signs in via SSO for the first time. */
|
||||
@Env('N8N_SSO_JUST_IN_TIME_PROVISIONING')
|
||||
justInTimeProvisioning: boolean = true;
|
||||
|
||||
/** Whether the login screen redirects directly to SSO instead of showing email/password. */
|
||||
@Env('N8N_SSO_REDIRECT_LOGIN_TO_SSO')
|
||||
redirectLoginToSso: boolean = true;
|
||||
|
||||
@Nested
|
||||
saml: SamlConfig;
|
||||
|
||||
@Nested
|
||||
oidc: OidcConfig;
|
||||
|
||||
@Nested
|
||||
ldap: LdapConfig;
|
||||
|
||||
@Nested
|
||||
provisioning: ProvisioningConfig;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
@Config
|
||||
export class TagsConfig {
|
||||
/** When true, workflow tags are disabled (no tagging UI or filtering by tag). */
|
||||
@Env('N8N_WORKFLOW_TAGS_DISABLED')
|
||||
disabled: boolean = false;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
@Config
|
||||
export class TemplatesConfig {
|
||||
/** Whether to enable loading and showing workflow templates. */
|
||||
@Env('N8N_TEMPLATES_ENABLED')
|
||||
enabled: boolean = true;
|
||||
|
||||
/** Base URL for the workflow templates API. */
|
||||
@Env('N8N_TEMPLATES_HOST')
|
||||
host: string = 'https://api.n8n.io/api/';
|
||||
|
||||
/** Base URL for fetching dynamic (contextual) templates. */
|
||||
@Env('N8N_DYNAMIC_TEMPLATES_HOST')
|
||||
dynamicTemplatesHost: string = 'https://dynamic-templates.n8n.io/templates';
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { Config, Env, Nested } from '../decorators';
|
||||
|
||||
@Config
|
||||
class SmtpAuth {
|
||||
/** SMTP login username */
|
||||
@Env('N8N_SMTP_USER')
|
||||
user: string = '';
|
||||
|
||||
/** SMTP login password */
|
||||
@Env('N8N_SMTP_PASS')
|
||||
pass: string = '';
|
||||
|
||||
/** SMTP OAuth Service Client */
|
||||
@Env('N8N_SMTP_OAUTH_SERVICE_CLIENT')
|
||||
serviceClient: string = '';
|
||||
|
||||
/** SMTP OAuth Private Key */
|
||||
@Env('N8N_SMTP_OAUTH_PRIVATE_KEY')
|
||||
privateKey: string = '';
|
||||
}
|
||||
|
||||
@Config
|
||||
class SmtpConfig {
|
||||
/** SMTP server host */
|
||||
@Env('N8N_SMTP_HOST')
|
||||
host: string = '';
|
||||
|
||||
/** SMTP server port */
|
||||
@Env('N8N_SMTP_PORT')
|
||||
port: number = 465;
|
||||
|
||||
/** Whether to use SSL for SMTP */
|
||||
@Env('N8N_SMTP_SSL')
|
||||
secure: boolean = true;
|
||||
|
||||
/** Whether to use STARTTLS for SMTP when SSL is disabled */
|
||||
@Env('N8N_SMTP_STARTTLS')
|
||||
startTLS: boolean = true;
|
||||
|
||||
/** How to display sender name */
|
||||
@Env('N8N_SMTP_SENDER')
|
||||
sender: string = '';
|
||||
|
||||
@Nested
|
||||
auth: SmtpAuth;
|
||||
}
|
||||
|
||||
@Config
|
||||
export class TemplateConfig {
|
||||
/** Overrides default HTML template for inviting new people (use full path) */
|
||||
@Env('N8N_UM_EMAIL_TEMPLATES_INVITE')
|
||||
'user-invited': string = '';
|
||||
|
||||
/** Overrides default HTML template for resetting password (use full path) */
|
||||
@Env('N8N_UM_EMAIL_TEMPLATES_PWRESET')
|
||||
'password-reset-requested': string = '';
|
||||
|
||||
/** Overrides default HTML template for notifying that a workflow was shared (use full path) */
|
||||
@Env('N8N_UM_EMAIL_TEMPLATES_WORKFLOW_SHARED')
|
||||
'workflow-shared': string = '';
|
||||
|
||||
/** Overrides default HTML template for notifying that a workflow was deactivated (use full path) */
|
||||
@Env('N8N_UM_EMAIL_TEMPLATES_WORKFLOW_AUTODEACTIVATED')
|
||||
'workflow-deactivated': string = '';
|
||||
|
||||
/** Overrides default HTML template for notifying that credentials were shared (use full path) */
|
||||
@Env('N8N_UM_EMAIL_TEMPLATES_CREDENTIALS_SHARED')
|
||||
'credentials-shared': string = '';
|
||||
|
||||
/** Overrides default HTML template for notifying that credentials were shared (use full path) */
|
||||
@Env('N8N_UM_EMAIL_TEMPLATES_PROJECT_SHARED')
|
||||
'project-shared': string = '';
|
||||
|
||||
/** Overrides default HTML template for notifying that a workflow failed in production (use full path) */
|
||||
@Env('N8N_UM_EMAIL_TEMPLATES_WORKFLOW_FAILURE')
|
||||
'workflow-failure': string = '';
|
||||
}
|
||||
|
||||
const emailModeSchema = z.enum(['', 'smtp']);
|
||||
type EmailMode = z.infer<typeof emailModeSchema>;
|
||||
|
||||
@Config
|
||||
class EmailConfig {
|
||||
/** Email delivery method: `smtp` or empty (disabled). */
|
||||
@Env('N8N_EMAIL_MODE', emailModeSchema)
|
||||
mode: EmailMode = 'smtp';
|
||||
|
||||
@Nested
|
||||
smtp: SmtpConfig;
|
||||
|
||||
@Nested
|
||||
template: TemplateConfig;
|
||||
}
|
||||
|
||||
const INVALID_JWT_REFRESH_TIMEOUT_WARNING =
|
||||
'N8N_USER_MANAGEMENT_JWT_REFRESH_TIMEOUT_HOURS needs to be smaller than N8N_USER_MANAGEMENT_JWT_DURATION_HOURS. Setting N8N_USER_MANAGEMENT_JWT_REFRESH_TIMEOUT_HOURS to 0.';
|
||||
|
||||
@Config
|
||||
export class UserManagementConfig {
|
||||
@Nested
|
||||
emails: EmailConfig;
|
||||
|
||||
/** JWT secret to use. If unset, n8n will generate its own. */
|
||||
@Env('N8N_USER_MANAGEMENT_JWT_SECRET')
|
||||
jwtSecret: string = '';
|
||||
|
||||
/** How long (in hours) before the JWT expires. */
|
||||
@Env('N8N_USER_MANAGEMENT_JWT_DURATION_HOURS')
|
||||
jwtSessionDurationHours: number = 168;
|
||||
|
||||
/**
|
||||
* Security Control: Invite Link Exposure Prevention
|
||||
*
|
||||
* When enabled, prevents exposure of invite URLs in API responses to users
|
||||
* with 'user:create' permission, mitigating account takeover risks via
|
||||
* invite link leakage (e.g., compromised admin accounts, network interception).
|
||||
*/
|
||||
@Env('N8N_INVITE_LINKS_EMAIL_ONLY')
|
||||
inviteLinksEmailOnly: boolean = false;
|
||||
|
||||
/**
|
||||
* How long (in hours) before expiration to automatically refresh it.
|
||||
* - `0` means 25% of `N8N_USER_MANAGEMENT_JWT_DURATION_HOURS`.
|
||||
* - `-1` means it will never refresh. This forces users to log back in after expiration.
|
||||
*/
|
||||
@Env('N8N_USER_MANAGEMENT_JWT_REFRESH_TIMEOUT_HOURS')
|
||||
jwtRefreshTimeoutHours: number = 0;
|
||||
|
||||
sanitize() {
|
||||
if (this.jwtRefreshTimeoutHours >= this.jwtSessionDurationHours) {
|
||||
console.warn(INVALID_JWT_REFRESH_TIMEOUT_WARNING);
|
||||
this.jwtRefreshTimeoutHours = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
@Config
|
||||
export class VersionNotificationsConfig {
|
||||
/** Whether to check for and show in-app notifications about new n8n versions. */
|
||||
@Env('N8N_VERSION_NOTIFICATIONS_ENABLED')
|
||||
enabled: boolean = true;
|
||||
|
||||
/** URL used to fetch current n8n version information. */
|
||||
@Env('N8N_VERSION_NOTIFICATIONS_ENDPOINT')
|
||||
endpoint: string = 'https://api.n8n.io/api/versions/';
|
||||
|
||||
/** Whether to fetch and show "What's New" content. Requires version notifications to be enabled. */
|
||||
@Env('N8N_VERSION_NOTIFICATIONS_WHATS_NEW_ENABLED')
|
||||
whatsNewEnabled: boolean = true;
|
||||
|
||||
/** URL used to fetch "What's New" articles. */
|
||||
@Env('N8N_VERSION_NOTIFICATIONS_WHATS_NEW_ENDPOINT')
|
||||
whatsNewEndpoint: string = 'https://api.n8n.io/api/whats-new';
|
||||
|
||||
/** URL linked from the versions panel (for example, upgrade instructions). */
|
||||
@Env('N8N_VERSION_NOTIFICATIONS_INFO_URL')
|
||||
infoUrl: string = 'https://docs.n8n.io/hosting/installation/updating/';
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
/**
|
||||
* Config for the workflow history compaction service, which compacts recent versions and trims old ones to manage storage.
|
||||
*/
|
||||
@Config
|
||||
export class WorkflowHistoryCompactionConfig {
|
||||
/**
|
||||
* Minimum age in hours before a workflow version is eligible for optimization.
|
||||
* Versions compacted are those with `createdAt` between `optimizingMinimumAgeHours - optimizingTimeWindowHours` and `optimizingMinimumAgeHours`.
|
||||
*/
|
||||
@Env('N8N_WORKFLOW_HISTORY_OPTIMIZING_MINIMUM_AGE_HOURS')
|
||||
optimizingMinimumAgeHours: number = 0.25;
|
||||
|
||||
/**
|
||||
* Time window in hours used when selecting versions to optimize.
|
||||
* Optimization runs roughly every `optimizingTimeWindowHours / 2` hours.
|
||||
*/
|
||||
@Env('N8N_WORKFLOW_HISTORY_OPTIMIZING_TIME_WINDOW_HOURS')
|
||||
optimizingTimeWindowHours: number = 2;
|
||||
|
||||
/**
|
||||
* Minimum age in days before a workflow version is eligible for trimming.
|
||||
* Versions trimmed are those with `createdAt` between `trimmingMinimumAgeDays - trimmingTimeWindowDays` and `trimmingMinimumAgeDays`.
|
||||
*/
|
||||
@Env('N8N_WORKFLOW_HISTORY_TRIMMING_MINIMUM_AGE_DAYS')
|
||||
trimmingMinimumAgeDays: number = 7;
|
||||
|
||||
/**
|
||||
* Time window in days used when selecting versions to trim. Trimming runs once per day.
|
||||
*/
|
||||
@Env('N8N_WORKFLOW_HISTORY_TRIMMING_TIME_WINDOW_DAYS')
|
||||
trimmingTimeWindowDays: number = 2;
|
||||
|
||||
/** Maximum number of workflow versions to process per workflow before waiting `batchDelayMs` before the next workflow. */
|
||||
@Env('N8N_WORKFLOW_HISTORY_COMPACTION_BATCH_SIZE')
|
||||
batchSize: number = 100;
|
||||
|
||||
/** Delay in milliseconds after processing `batchSize` versions before moving to the next workflow. */
|
||||
@Env('N8N_WORKFLOW_HISTORY_COMPACTION_BATCH_DELAY_MS')
|
||||
batchDelayMs: number = 1_000;
|
||||
|
||||
/**
|
||||
* Whether to run a trim pass on startup (for example, to fix existing history or for development).
|
||||
*
|
||||
* @warning Blocking; can significantly increase startup time.
|
||||
*/
|
||||
@Env('N8N_WORKFLOW_HISTORY_COMPACTION_TRIM_ON_START_UP')
|
||||
trimOnStartUp: boolean = false;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
@Config
|
||||
export class WorkflowHistoryConfig {
|
||||
/** How long in hours to keep workflow history versions before pruning. Use `-1` to keep forever. */
|
||||
@Env('N8N_WORKFLOW_HISTORY_PRUNE_TIME')
|
||||
pruneTime: number = -1;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
const callerPolicySchema = z.enum(['any', 'none', 'workflowsFromAList', 'workflowsFromSameOwner']);
|
||||
type CallerPolicy = z.infer<typeof callerPolicySchema>;
|
||||
|
||||
@Config
|
||||
export class WorkflowsConfig {
|
||||
/** Default name suggested when creating a new workflow. */
|
||||
@Env('WORKFLOWS_DEFAULT_NAME')
|
||||
defaultName: string = 'My workflow';
|
||||
|
||||
/** Default policy for which workflows are allowed to call this workflow (for example, same owner, any, none). */
|
||||
@Env('N8N_WORKFLOW_CALLER_POLICY_DEFAULT_OPTION', callerPolicySchema)
|
||||
callerPolicyDefaultOption: CallerPolicy = 'workflowsFromSameOwner';
|
||||
|
||||
/** Number of workflows to activate in parallel during startup. */
|
||||
@Env('N8N_WORKFLOW_ACTIVATION_BATCH_SIZE')
|
||||
activationBatchSize: number = 1;
|
||||
|
||||
/** Whether to build and maintain workflow dependency indexes (for example, for subworkflow callers). */
|
||||
@Env('N8N_WORKFLOWS_INDEXING_ENABLED')
|
||||
indexingEnabled: boolean = true;
|
||||
|
||||
/** Whether to use the workflow publication service. Still under development. */
|
||||
@Env('N8N_USE_WORKFLOW_PUBLICATION_SERVICE')
|
||||
useWorkflowPublicationService: boolean = false;
|
||||
}
|
||||
Reference in New Issue
Block a user