2023-02-10 05:59:20 -08:00
import { Command } from '@oclif/command' ;
2023-02-10 07:34:39 -08:00
import { ExitError } from '@oclif/errors' ;
2023-02-21 10:21:56 -08:00
import { Container } from 'typedi' ;
2023-02-10 05:59:20 -08:00
import { LoggerProxy , ErrorReporterProxy as ErrorReporter , sleep } from 'n8n-workflow' ;
2023-10-23 04:39:35 -07:00
import { BinaryDataService , InstanceSettings , ObjectStoreService } from 'n8n-core' ;
2023-05-10 01:27:04 -07:00
import type { AbstractServer } from '@/AbstractServer' ;
2023-01-27 05:56:56 -08:00
import { getLogger } from '@/Logger' ;
2023-02-10 05:59:20 -08:00
import config from '@/config' ;
2022-11-09 06:25:00 -08:00
import * as Db from '@/Db' ;
2023-02-10 05:59:20 -08:00
import * as CrashJournal from '@/CrashJournal' ;
2023-10-05 06:25:17 -07:00
import { LICENSE_FEATURES , inTest } from '@/constants' ;
2023-02-10 05:59:20 -08:00
import { initErrorHandling } from '@/ErrorReporting' ;
import { ExternalHooks } from '@/ExternalHooks' ;
import { NodeTypes } from '@/NodeTypes' ;
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials' ;
2023-09-17 02:05:54 -07:00
import type { IExternalHooksClass , N8nInstanceType } from '@/Interfaces' ;
2023-02-21 10:21:56 -08:00
import { InternalHooks } from '@/InternalHooks' ;
2023-02-21 00:35:35 -08:00
import { PostHogClient } from '@/posthog' ;
2023-05-23 04:01:50 -07:00
import { License } from '@/License' ;
2023-08-25 01:33:46 -07:00
import { ExternalSecretsManager } from '@/ExternalSecrets/ExternalSecretsManager.ee' ;
2023-09-21 05:57:45 -07:00
import { initExpressionEvaluator } from '@/ExpressionEvalator' ;
2023-09-26 04:58:06 -07:00
import { generateHostInstanceId } from '../databases/utils/generators' ;
2023-10-04 05:57:21 -07:00
import { WorkflowHistoryManager } from '@/workflows/workflowHistory/workflowHistoryManager.ee' ;
2023-02-10 05:59:20 -08:00
2022-06-25 21:03:46 -07:00
export abstract class BaseCommand extends Command {
2023-02-10 05:59:20 -08:00
protected logger = LoggerProxy . init ( getLogger ( ) ) ;
protected externalHooks : IExternalHooksClass ;
2023-05-11 07:19:55 -07:00
protected nodeTypes : NodeTypes ;
2022-06-25 21:03:46 -07:00
2023-10-23 04:39:35 -07:00
protected instanceSettings : InstanceSettings ;
2022-06-25 21:03:46 -07:00
2023-10-23 04:39:35 -07:00
private instanceType : N8nInstanceType = 'main' ;
2023-09-26 04:58:06 -07:00
queueModeId : string ;
2023-05-10 01:27:04 -07:00
protected server? : AbstractServer ;
2022-06-25 21:03:46 -07:00
async init ( ) : Promise < void > {
2023-02-10 05:59:20 -08:00
await initErrorHandling ( ) ;
2023-09-21 05:57:45 -07:00
initExpressionEvaluator ( ) ;
2022-06-25 21:03:46 -07:00
2023-02-10 07:34:39 -08:00
process . once ( 'SIGTERM' , async ( ) = > this . stopProcess ( ) ) ;
process . once ( 'SIGINT' , async ( ) = > this . stopProcess ( ) ) ;
2022-06-25 21:03:46 -07:00
2023-02-10 05:59:20 -08:00
// Make sure the settings exist
2023-10-23 04:39:35 -07:00
this . instanceSettings = Container . get ( InstanceSettings ) ;
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
2023-02-10 05:59:20 -08:00
await Db . init ( ) . catch ( async ( error : Error ) = >
this . exitWithCrash ( 'There was an error initializing DB' , error ) ,
) ;
2023-04-12 01:59:14 -07:00
2023-05-10 01:27:04 -07:00
await this . server ? . init ( ) ;
await Db . migrate ( ) . catch ( async ( error : Error ) = >
this . exitWithCrash ( 'There was an error running database migrations' , error ) ,
) ;
2023-05-08 03:16:20 -07:00
const dbType = config . getEnv ( 'database.type' ) ;
if ( [ 'mysqldb' , 'mariadb' ] . includes ( dbType ) ) {
LoggerProxy . warn (
'Support for MySQL/MariaDB has been deprecated and will be removed with an upcoming version of n8n. Please migrate to PostgreSQL.' ,
) ;
}
2023-05-08 06:03:44 -07:00
if ( process . env . EXECUTIONS_PROCESS === 'own' ) {
LoggerProxy . warn (
'Own mode has been deprecated and will be removed in a future version of n8n. If you need the isolation and performance gains, please consider using queue mode.' ,
) ;
}
2023-05-08 03:16:20 -07:00
2023-10-23 04:39:35 -07:00
await Container . get ( PostHogClient ) . init ( ) ;
await Container . get ( InternalHooks ) . init ( ) ;
2023-02-10 05:59:20 -08:00
}
2022-06-25 21:03:46 -07:00
2023-09-26 04:58:06 -07:00
protected setInstanceType ( instanceType : N8nInstanceType ) {
this . instanceType = instanceType ;
config . set ( 'generic.instanceType' , instanceType ) ;
}
protected setInstanceQueueModeId() {
2023-09-28 03:57:35 -07:00
if ( config . get ( 'redis.queueModeId' ) ) {
this . queueModeId = config . get ( 'redis.queueModeId' ) ;
return ;
2023-09-26 04:58:06 -07:00
}
2023-09-28 03:57:35 -07:00
this . queueModeId = generateHostInstanceId ( this . instanceType ) ;
config . set ( 'redis.queueModeId' , this . queueModeId ) ;
2023-09-26 04:58:06 -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 {
await CrashJournal . cleanup ( ) ;
} 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 ) {
ErrorReporter . error ( new Error ( message , { cause : error } ) , { level : 'fatal' } ) ;
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 ) {
throw new Error (
'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 ) {
LoggerProxy . debug (
'License found for external storage - object store to init in read-write mode' ,
) ;
await this . _initObjectStoreService ( ) ;
return ;
}
if ( isSelected && isAvailable && ! isLicensed ) {
LoggerProxy . debug (
'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 ) {
LoggerProxy . debug (
'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 ) ;
const host = config . getEnv ( 'externalStorage.s3.host' ) ;
if ( host === '' ) {
throw new Error (
'External storage host not configured. Please set `N8N_EXTERNAL_STORAGE_S3_HOST`.' ,
) ;
}
const bucket = {
name : config.getEnv ( 'externalStorage.s3.bucket.name' ) ,
region : config.getEnv ( 'externalStorage.s3.bucket.region' ) ,
} ;
if ( bucket . name === '' ) {
throw new Error (
'External storage bucket name not configured. Please set `N8N_EXTERNAL_STORAGE_S3_BUCKET_NAME`.' ,
) ;
}
if ( bucket . region === '' ) {
throw new Error (
'External storage bucket region not configured. Please set `N8N_EXTERNAL_STORAGE_S3_BUCKET_REGION`.' ,
) ;
}
const credentials = {
accessKey : config.getEnv ( 'externalStorage.s3.credentials.accessKey' ) ,
accessSecret : config.getEnv ( 'externalStorage.s3.credentials.accessSecret' ) ,
} ;
if ( credentials . accessKey === '' ) {
throw new Error (
'External storage access key not configured. Please set `N8N_EXTERNAL_STORAGE_S3_ACCESS_KEY`.' ,
) ;
}
if ( credentials . accessSecret === '' ) {
throw new Error (
'External storage access secret not configured. Please set `N8N_EXTERNAL_STORAGE_S3_ACCESS_SECRET`.' ,
) ;
}
LoggerProxy . debug ( 'Initializing object store service' ) ;
try {
await objectStoreService . init ( host , bucket , credentials ) ;
objectStoreService . setReadonly ( options . isReadOnly ) ;
LoggerProxy . debug ( 'Object store init completed' ) ;
} catch ( e ) {
const error = e instanceof Error ? e : new Error ( ` ${ e } ` ) ;
LoggerProxy . debug ( 'Object store init failed' , { error } ) ;
}
}
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 } ` ) ;
LoggerProxy . error ( ` Failed to init object store: ${ error . message } ` , { error } ) ;
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
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 > {
2023-05-23 04:01:50 -07:00
const license = Container . get ( License ) ;
2023-10-23 04:39:35 -07:00
await license . init ( this . instanceType ? ? 'main' ) ;
2023-05-23 04:01:50 -07:00
const activationKey = config . getEnv ( 'license.activationKey' ) ;
if ( activationKey ) {
const hasCert = ( await license . loadCertStr ( ) ) . length > 0 ;
if ( hasCert ) {
return LoggerProxy . debug ( 'Skipping license activation' ) ;
}
try {
LoggerProxy . debug ( 'Attempting license activation' ) ;
await license . activate ( activationKey ) ;
} catch ( e ) {
LoggerProxy . error ( 'Could not activate license' , e as Error ) ;
}
}
}
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
}
const exitCode = error instanceof ExitError ? error.oclif.exit : error ? 1 : 0 ;
this . exit ( exitCode ) ;
2022-06-25 21:03:46 -07:00
}
}