2023-10-25 05:51:10 -07:00
import 'reflect-metadata' ;
2024-07-05 02:43:27 -07:00
import { GlobalConfig } from '@n8n/config' ;
2025-01-06 01:21:24 -08:00
import { Container } from '@n8n/di' ;
2024-09-12 09:07:18 -07:00
import { Command , Errors } from '@oclif/core' ;
2024-10-10 08:12:05 -07:00
import {
BinaryDataService ,
InstanceSettings ,
2024-12-23 04:46:13 -08:00
Logger ,
2024-10-10 08:12:05 -07:00
ObjectStoreService ,
DataDeduplicationService ,
2024-12-11 06:36:17 -08:00
ErrorReporter ,
2024-10-10 08:12:05 -07:00
} from 'n8n-core' ;
2024-12-11 06:36:17 -08:00
import { ApplicationError , ensureError , sleep } from 'n8n-workflow' ;
2024-09-12 09:07:18 -07:00
2024-08-22 02:10:37 -07:00
import type { AbstractServer } from '@/abstract-server' ;
2023-02-10 05:59:20 -08:00
import config from '@/config' ;
2024-01-08 07:46:45 -08:00
import { LICENSE_FEATURES , inDevelopment , inTest } from '@/constants' ;
2024-09-12 09:07:18 -07:00
import * as CrashJournal from '@/crash-journal' ;
import * as Db from '@/db' ;
2024-10-10 08:12:05 -07:00
import { getDataDeduplicationService } from '@/deduplication' ;
2024-11-22 04:49:00 -08:00
import { DeprecationService } from '@/deprecation/deprecation.service' ;
2024-09-12 09:07:18 -07:00
import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus' ;
2024-09-30 01:44:03 -07:00
import { TelemetryEventRelay } from '@/events/relays/telemetry.event-relay' ;
2024-09-12 09:07:18 -07:00
import { initExpressionEvaluator } from '@/expression-evaluator' ;
2024-08-22 02:10:37 -07:00
import { ExternalHooks } from '@/external-hooks' ;
2024-12-24 04:02:05 -08:00
import { ExternalSecretsManager } from '@/external-secrets.ee/external-secrets-manager.ee' ;
2024-08-22 02:10:37 -07:00
import { License } from '@/license' ;
2024-09-12 09:07:18 -07:00
import { LoadNodesAndCredentials } from '@/load-nodes-and-credentials' ;
import { NodeTypes } from '@/node-types' ;
import { PostHogClient } from '@/posthog' ;
2024-08-22 02:10:37 -07:00
import { ShutdownService } from '@/shutdown/shutdown.service' ;
2024-12-24 04:02:05 -08:00
import { WorkflowHistoryManager } from '@/workflows/workflow-history.ee/workflow-history-manager.ee' ;
2023-02-10 05:59:20 -08:00
2022-06-25 21:03:46 -07:00
export abstract class BaseCommand extends Command {
2023-10-25 07:35:22 -07:00
protected logger = Container . get ( Logger ) ;
2023-02-10 05:59:20 -08:00
2024-12-13 08:34:37 -08:00
protected errorReporter : ErrorReporter ;
2024-12-11 06:36:17 -08:00
2023-12-27 02:50:43 -08:00
protected externalHooks? : ExternalHooks ;
2023-02-10 05:59:20 -08:00
2023-05-11 07:19:55 -07:00
protected nodeTypes : NodeTypes ;
2022-06-25 21:03:46 -07:00
2024-09-16 04:37:14 -07:00
protected instanceSettings : InstanceSettings = Container . get ( InstanceSettings ) ;
2023-09-26 04:58:06 -07:00
2023-05-10 01:27:04 -07:00
protected server? : AbstractServer ;
2023-12-22 02:39:58 -08:00
protected shutdownService : ShutdownService = Container . get ( ShutdownService ) ;
2023-12-15 07:35:22 -08:00
2024-05-06 00:04:16 -07:00
protected license : License ;
2024-08-05 02:52:06 -07:00
protected readonly globalConfig = Container . get ( GlobalConfig ) ;
2024-07-24 04:08:20 -07:00
2023-12-18 00:53:34 -08:00
/ * *
* How long to wait for graceful shutdown before force killing the process .
* /
2024-10-21 03:57:37 -07:00
protected gracefulShutdownTimeoutInS =
Container . get ( GlobalConfig ) . generic . gracefulShutdownTimeout ;
2023-12-18 00:53:34 -08:00
2024-08-05 02:52:06 -07:00
/** Whether to init community packages (if enabled) */
protected needsCommunityPackages = false ;
2022-06-25 21:03:46 -07:00
async init ( ) : Promise < void > {
2024-12-13 08:34:37 -08:00
this . errorReporter = Container . get ( ErrorReporter ) ;
2025-01-08 08:47:40 -08:00
const { backendDsn , n8nVersion , environment , deploymentName } = this . globalConfig . sentry ;
await this . errorReporter . init ( {
serverType : this.instanceSettings.instanceType ,
dsn : backendDsn ,
environment ,
release : n8nVersion ,
serverName : deploymentName ,
} ) ;
2023-09-21 05:57:45 -07:00
initExpressionEvaluator ( ) ;
2022-06-25 21:03:46 -07:00
2023-12-15 07:35:22 -08:00
process . once ( 'SIGTERM' , this . onTerminationSignal ( 'SIGTERM' ) ) ;
process . once ( 'SIGINT' , this . onTerminationSignal ( 'SIGINT' ) ) ;
2022-06-25 21:03:46 -07:00
2023-02-21 10:21:56 -08:00
this . nodeTypes = Container . get ( NodeTypes ) ;
2023-10-25 04:59:38 -07:00
await Container . get ( LoadNodesAndCredentials ) . init ( ) ;
2022-06-25 21:03:46 -07:00
2024-01-17 07:08:50 -08:00
await Db . init ( ) . catch (
async ( error : Error ) = > await this . exitWithCrash ( 'There was an error initializing DB' , error ) ,
2023-02-10 05:59:20 -08:00
) ;
2023-04-12 01:59:14 -07:00
2024-01-08 07:46:45 -08:00
// This needs to happen after DB.init() or otherwise DB Connection is not
// available via the dependency Container that services depend on.
if ( inDevelopment || inTest ) {
this . shutdownService . validate ( ) ;
}
2023-05-10 01:27:04 -07:00
await this . server ? . init ( ) ;
2024-01-17 07:08:50 -08:00
await Db . migrate ( ) . catch (
async ( error : Error ) = >
await this . exitWithCrash ( 'There was an error running database migrations' , error ) ,
2023-05-10 01:27:04 -07:00
) ;
2024-11-22 04:49:00 -08:00
Container . get ( DeprecationService ) . warn ( ) ;
2023-12-15 01:56:35 -08:00
2023-12-01 03:51:45 -08:00
if (
2024-11-22 04:49:00 -08:00
config . getEnv ( 'executions.mode' ) === 'queue' &&
this . globalConfig . database . type === 'sqlite'
2023-12-01 03:51:45 -08:00
) {
this . logger . warn (
2024-11-22 04:49:00 -08:00
'Scaling mode is not officially supported with sqlite. Please use PostgreSQL instead.' ,
2023-12-01 03:51:45 -08:00
) ;
}
2024-08-05 02:52:06 -07:00
const { communityPackages } = this . globalConfig . nodes ;
if ( communityPackages . enabled && this . needsCommunityPackages ) {
2024-08-28 08:57:46 -07:00
const { CommunityPackagesService } = await import ( '@/services/community-packages.service' ) ;
2024-08-05 02:52:06 -07:00
await Container . get ( CommunityPackagesService ) . checkForMissingPackages ( ) ;
}
2024-08-27 05:33:25 -07:00
// TODO: remove this after the cyclic dependencies around the event-bus are resolved
Container . get ( MessageEventBus ) ;
2023-10-23 04:39:35 -07:00
await Container . get ( PostHogClient ) . init ( ) ;
2024-07-17 02:56:27 -07:00
await Container . get ( TelemetryEventRelay ) . init ( ) ;
2023-02-10 05:59:20 -08:00
}
2022-06-25 21:03:46 -07:00
2023-02-10 05:59:20 -08:00
protected async stopProcess() {
// This needs to be overridden
}
2022-06-25 21:03:46 -07:00
2023-02-10 05:59:20 -08:00
protected async initCrashJournal() {
await CrashJournal . init ( ) ;
}
2022-06-25 21:03:46 -07:00
2023-02-10 05:59:20 -08:00
protected async exitSuccessFully() {
try {
2023-12-15 07:16:35 -08:00
await Promise . all ( [ CrashJournal . cleanup ( ) , Db . close ( ) ] ) ;
2023-02-10 05:59:20 -08:00
} finally {
process . exit ( ) ;
}
}
2022-06-25 21:03:46 -07:00
2023-02-10 05:59:20 -08:00
protected async exitWithCrash ( message : string , error : unknown ) {
2024-12-11 06:36:17 -08:00
this . errorReporter . error ( new Error ( message , { cause : error } ) , { level : 'fatal' } ) ;
2023-02-10 05:59:20 -08:00
await sleep ( 2000 ) ;
process . exit ( 1 ) ;
}
2022-06-25 21:03:46 -07:00
2023-10-05 06:25:17 -07:00
async initObjectStoreService() {
const isSelected = config . getEnv ( 'binaryDataManager.mode' ) === 's3' ;
const isAvailable = config . getEnv ( 'binaryDataManager.availableModes' ) . includes ( 's3' ) ;
if ( ! isSelected && ! isAvailable ) return ;
if ( isSelected && ! isAvailable ) {
2023-11-29 03:25:10 -08:00
throw new ApplicationError (
2023-10-05 06:25:17 -07:00
'External storage selected but unavailable. Please make external storage available by adding "s3" to `N8N_AVAILABLE_BINARY_DATA_MODES`.' ,
) ;
}
const isLicensed = Container . get ( License ) . isFeatureEnabled ( LICENSE_FEATURES . BINARY_DATA_S3 ) ;
if ( isSelected && isAvailable && isLicensed ) {
2023-10-25 07:35:22 -07:00
this . logger . debug (
2023-10-05 06:25:17 -07:00
'License found for external storage - object store to init in read-write mode' ,
) ;
await this . _initObjectStoreService ( ) ;
return ;
}
if ( isSelected && isAvailable && ! isLicensed ) {
2023-10-25 07:35:22 -07:00
this . logger . debug (
2023-10-05 06:25:17 -07:00
'No license found for external storage - object store to init with writes blocked. To enable writes, please upgrade to a license that supports this feature.' ,
) ;
await this . _initObjectStoreService ( { isReadOnly : true } ) ;
return ;
}
if ( ! isSelected && isAvailable ) {
2023-10-25 07:35:22 -07:00
this . logger . debug (
2023-10-05 06:25:17 -07:00
'External storage unselected but available - object store to init with writes unused' ,
) ;
await this . _initObjectStoreService ( ) ;
return ;
}
}
private async _initObjectStoreService ( options = { isReadOnly : false } ) {
const objectStoreService = Container . get ( ObjectStoreService ) ;
2024-07-24 04:08:20 -07:00
const { host , bucket , credentials } = this . globalConfig . externalStorage . s3 ;
2023-10-05 06:25:17 -07:00
if ( host === '' ) {
2023-11-29 03:25:10 -08:00
throw new ApplicationError (
2023-10-05 06:25:17 -07:00
'External storage host not configured. Please set `N8N_EXTERNAL_STORAGE_S3_HOST`.' ,
) ;
}
if ( bucket . name === '' ) {
2023-11-29 03:25:10 -08:00
throw new ApplicationError (
2023-10-05 06:25:17 -07:00
'External storage bucket name not configured. Please set `N8N_EXTERNAL_STORAGE_S3_BUCKET_NAME`.' ,
) ;
}
if ( bucket . region === '' ) {
2023-11-29 03:25:10 -08:00
throw new ApplicationError (
2023-10-05 06:25:17 -07:00
'External storage bucket region not configured. Please set `N8N_EXTERNAL_STORAGE_S3_BUCKET_REGION`.' ,
) ;
}
if ( credentials . accessKey === '' ) {
2023-11-29 03:25:10 -08:00
throw new ApplicationError (
2023-10-05 06:25:17 -07:00
'External storage access key not configured. Please set `N8N_EXTERNAL_STORAGE_S3_ACCESS_KEY`.' ,
) ;
}
if ( credentials . accessSecret === '' ) {
2023-11-29 03:25:10 -08:00
throw new ApplicationError (
2023-10-05 06:25:17 -07:00
'External storage access secret not configured. Please set `N8N_EXTERNAL_STORAGE_S3_ACCESS_SECRET`.' ,
) ;
}
2023-10-25 07:35:22 -07:00
this . logger . debug ( 'Initializing object store service' ) ;
2023-10-05 06:25:17 -07:00
try {
await objectStoreService . init ( host , bucket , credentials ) ;
objectStoreService . setReadonly ( options . isReadOnly ) ;
2023-10-25 07:35:22 -07:00
this . logger . debug ( 'Object store init completed' ) ;
2023-10-05 06:25:17 -07:00
} catch ( e ) {
const error = e instanceof Error ? e : new Error ( ` ${ e } ` ) ;
2023-10-25 07:35:22 -07:00
this . logger . debug ( 'Object store init failed' , { error } ) ;
2023-10-05 06:25:17 -07:00
}
}
2023-09-22 08:22:12 -07:00
async initBinaryDataService() {
2023-10-05 06:25:17 -07:00
try {
await this . initObjectStoreService ( ) ;
} catch ( e ) {
const error = e instanceof Error ? e : new Error ( ` ${ e } ` ) ;
2023-10-25 07:35:22 -07:00
this . logger . error ( ` Failed to init object store: ${ error . message } ` , { error } ) ;
2023-10-05 06:25:17 -07:00
process . exit ( 1 ) ;
}
2023-02-10 05:59:20 -08:00
const binaryDataConfig = config . getEnv ( 'binaryDataManager' ) ;
2023-09-22 08:22:12 -07:00
await Container . get ( BinaryDataService ) . init ( binaryDataConfig ) ;
2023-02-10 05:59:20 -08:00
}
2022-06-25 21:03:46 -07:00
2024-10-10 08:12:05 -07:00
protected async initDataDeduplicationService() {
const dataDeduplicationService = getDataDeduplicationService ( ) ;
await DataDeduplicationService . init ( dataDeduplicationService ) ;
}
2023-09-07 05:44:19 -07:00
async initExternalHooks() {
2023-02-21 10:21:56 -08:00
this . externalHooks = Container . get ( ExternalHooks ) ;
2023-02-10 05:59:20 -08:00
await this . externalHooks . init ( ) ;
}
2022-06-25 21:03:46 -07:00
2023-09-26 04:58:06 -07:00
async initLicense ( ) : Promise < void > {
2024-05-06 00:04:16 -07:00
this . license = Container . get ( License ) ;
2024-09-16 04:37:14 -07:00
await this . license . init ( ) ;
2023-05-23 04:01:50 -07:00
2024-10-28 02:52:31 -07:00
const { activationKey } = this . globalConfig . license ;
2023-05-23 04:01:50 -07:00
if ( activationKey ) {
2024-05-06 00:04:16 -07:00
const hasCert = ( await this . license . loadCertStr ( ) ) . length > 0 ;
2023-05-23 04:01:50 -07:00
if ( hasCert ) {
2023-10-25 07:35:22 -07:00
return this . logger . debug ( 'Skipping license activation' ) ;
2023-05-23 04:01:50 -07:00
}
try {
2023-10-25 07:35:22 -07:00
this . logger . debug ( 'Attempting license activation' ) ;
2024-05-06 00:04:16 -07:00
await this . license . activate ( activationKey ) ;
2023-11-06 03:03:35 -08:00
this . logger . debug ( 'License init complete' ) ;
2024-10-09 03:56:31 -07:00
} catch ( e : unknown ) {
const error = ensureError ( e ) ;
this . logger . error ( 'Could not activate license' , { error } ) ;
2023-05-23 04:01:50 -07:00
}
}
}
2023-08-25 01:33:46 -07:00
async initExternalSecrets() {
const secretsManager = Container . get ( ExternalSecretsManager ) ;
await secretsManager . init ( ) ;
}
2023-10-04 05:57:21 -07:00
initWorkflowHistory() {
Container . get ( WorkflowHistoryManager ) . init ( ) ;
}
2023-02-10 05:59:20 -08:00
async finally ( error : Error | undefined ) {
if ( inTest || this . id === 'start' ) return ;
2023-05-10 01:27:04 -07:00
if ( Db . connectionState . connected ) {
2023-02-10 07:34:39 -08:00
await sleep ( 100 ) ; // give any in-flight query some time to finish
2023-05-10 01:27:04 -07:00
await Db . close ( ) ;
2023-02-10 07:34:39 -08:00
}
2024-06-27 08:26:19 -07:00
const exitCode = error instanceof Errors . ExitError ? error.oclif.exit : error ? 1 : 0 ;
2023-02-10 07:34:39 -08:00
this . exit ( exitCode ) ;
2022-06-25 21:03:46 -07:00
}
2023-12-15 07:35:22 -08:00
2024-08-12 02:41:21 -07:00
protected onTerminationSignal ( signal : string ) {
2023-12-15 07:35:22 -08:00
return async ( ) = > {
2023-12-22 02:39:58 -08:00
if ( this . shutdownService . isShuttingDown ( ) ) {
2023-12-15 07:35:22 -08:00
this . logger . info ( ` Received ${ signal } . Already shutting down... ` ) ;
return ;
}
2023-12-18 00:53:34 -08:00
const forceShutdownTimer = setTimeout ( async ( ) = > {
// In case that something goes wrong with shutdown we
// kill after timeout no matter what
2024-05-03 06:24:27 -07:00
this . logger . info ( ` process exited after ${ this . gracefulShutdownTimeoutInS } s ` ) ;
2023-12-18 00:53:34 -08:00
const errorMsg = ` Shutdown timed out after ${ this . gracefulShutdownTimeoutInS } seconds ` ;
await this . exitWithCrash ( errorMsg , new Error ( errorMsg ) ) ;
} , this . gracefulShutdownTimeoutInS * 1000 ) ;
2023-12-15 07:35:22 -08:00
this . logger . info ( ` Received ${ signal } . Shutting down... ` ) ;
2023-12-22 02:39:58 -08:00
this . shutdownService . shutdown ( ) ;
2023-12-18 00:53:34 -08:00
2024-08-13 00:14:52 -07:00
await this . shutdownService . waitForShutdown ( ) ;
await this . stopProcess ( ) ;
2023-12-18 00:53:34 -08:00
clearTimeout ( forceShutdownTimer ) ;
2023-12-15 07:35:22 -08:00
} ;
}
2022-06-25 21:03:46 -07:00
}