2023-10-23 04:39:35 -07:00
import { createHash , randomBytes } from 'crypto' ;
2024-09-30 06:38:56 -07:00
import { existsSync , mkdirSync , readFileSync , writeFileSync } from 'fs' ;
2024-01-16 09:25:53 -08:00
import { ApplicationError , jsonParse } from 'n8n-workflow' ;
2024-09-30 06:38:56 -07:00
import path from 'path' ;
import { Service } from 'typedi' ;
2023-10-23 04:39:35 -07:00
interface ReadOnlySettings {
encryptionKey : string ;
}
interface WritableSettings {
tunnelSubdomain? : string ;
}
type Settings = ReadOnlySettings & WritableSettings ;
2024-08-02 06:18:33 -07:00
type InstanceRole = 'unset' | 'leader' | 'follower' ;
2024-09-16 04:37:14 -07:00
export type InstanceType = 'main' | 'webhook' | 'worker' ;
2024-01-16 09:25:53 -08:00
const inTest = process . env . NODE_ENV === 'test' ;
2023-10-23 04:39:35 -07:00
@Service ( )
export class InstanceSettings {
2023-11-07 06:58:28 -08:00
private readonly userHome = this . getUserHome ( ) ;
2023-10-23 04:39:35 -07:00
/** The path to the n8n folder in which all n8n related data gets saved */
readonly n8nFolder = path . join ( this . userHome , '.n8n' ) ;
2023-11-07 06:58:28 -08:00
/** The path to the folder where all generated static assets are copied to */
readonly staticCacheDir = path . join ( this . userHome , '.cache/n8n/public' ) ;
2023-10-23 04:39:35 -07:00
/** The path to the folder containing custom nodes and credentials */
readonly customExtensionDir = path . join ( this . n8nFolder , 'custom' ) ;
/** The path to the folder containing installed nodes (like community nodes) */
readonly nodesDownloadDir = path . join ( this . n8nFolder , 'nodes' ) ;
private readonly settingsFile = path . join ( this . n8nFolder , 'config' ) ;
private settings = this . loadOrCreate ( ) ;
2023-10-24 00:55:57 -07:00
readonly instanceId = this . generateInstanceId ( ) ;
2024-09-16 04:37:14 -07:00
readonly instanceType : InstanceType ;
constructor ( ) {
const command = process . argv [ 2 ] ;
this . instanceType = [ 'webhook' , 'worker' ] . includes ( command )
? ( command as InstanceType )
: 'main' ;
}
2024-08-26 03:35:39 -07:00
/ * *
* A main is :
* - ` unset ` during bootup ,
* - ` leader ` after bootup in single - main setup ,
* - ` leader ` or ` follower ` after bootup in multi - main setup .
*
* A non - main instance type ( e . g . ` worker ` ) is always ` unset ` .
* /
instanceRole : InstanceRole = 'unset' ;
2024-08-02 06:18:33 -07:00
get isLeader() {
return this . instanceRole === 'leader' ;
}
markAsLeader() {
this . instanceRole = 'leader' ;
}
get isFollower() {
return this . instanceRole === 'follower' ;
}
markAsFollower() {
this . instanceRole = 'follower' ;
}
2023-10-23 04:39:35 -07:00
get encryptionKey() {
return this . settings . encryptionKey ;
}
get tunnelSubdomain() {
return this . settings . tunnelSubdomain ;
}
update ( newSettings : WritableSettings ) {
this . save ( { . . . this . settings , . . . newSettings } ) ;
}
/ * *
* The home folder path of the user .
* If none can be found it falls back to the current working directory
* /
private getUserHome() {
const homeVarName = process . platform === 'win32' ? 'USERPROFILE' : 'HOME' ;
return process . env . N8N_USER_FOLDER ? ? process . env [ homeVarName ] ? ? process . cwd ( ) ;
}
2024-01-16 09:25:53 -08:00
/ * *
* Load instance settings from the settings file . If missing , create a new
* settings file with an auto - generated encryption key .
* /
2023-10-23 04:39:35 -07:00
private loadOrCreate ( ) : Settings {
2024-01-16 09:25:53 -08:00
if ( existsSync ( this . settingsFile ) ) {
const content = readFileSync ( this . settingsFile , 'utf8' ) ;
const settings = jsonParse < Settings > ( content , {
errorMessage : ` Error parsing n8n-config file " ${ this . settingsFile } ". It does not seem to be valid JSON. ` ,
2023-10-23 04:39:35 -07:00
} ) ;
2024-01-16 09:25:53 -08:00
if ( ! inTest ) console . info ( ` User settings loaded from: ${ this . settingsFile } ` ) ;
const { encryptionKey , tunnelSubdomain } = settings ;
if ( process . env . N8N_ENCRYPTION_KEY && encryptionKey !== process . env . N8N_ENCRYPTION_KEY ) {
throw new ApplicationError (
` Mismatching encryption keys. The encryption key in the settings file ${ this . settingsFile } does not match the N8N_ENCRYPTION_KEY env var. Please make sure both keys match. More information: https://docs.n8n.io/hosting/environment-variables/configuration-methods/#encryption-key ` ,
) ;
}
return { encryptionKey , tunnelSubdomain } ;
}
mkdirSync ( this . n8nFolder , { recursive : true } ) ;
const encryptionKey = process . env . N8N_ENCRYPTION_KEY ? ? randomBytes ( 24 ) . toString ( 'base64' ) ;
const settings : Settings = { encryptionKey } ;
this . save ( settings ) ;
if ( ! inTest && ! process . env . N8N_ENCRYPTION_KEY ) {
console . info ( ` No encryption key found - Auto-generated and saved to: ${ this . settingsFile } ` ) ;
2023-10-23 04:39:35 -07:00
}
2024-01-16 09:25:53 -08:00
return settings ;
2023-10-24 00:55:57 -07:00
}
private generateInstanceId() {
const { encryptionKey } = this ;
return createHash ( 'sha256' )
2023-10-23 04:39:35 -07:00
. update ( encryptionKey . slice ( Math . round ( encryptionKey . length / 2 ) ) )
. digest ( 'hex' ) ;
}
private save ( settings : Settings ) {
this . settings = settings ;
writeFileSync ( this . settingsFile , JSON . stringify ( settings , null , '\t' ) , 'utf-8' ) ;
}
}