diff --git a/docker/compose/withMongo/docker-compose.yml b/docker/compose/withMongo/docker-compose.yml index 0533efd210..d9eb12d74d 100644 --- a/docker/compose/withMongo/docker-compose.yml +++ b/docker/compose/withMongo/docker-compose.yml @@ -17,6 +17,9 @@ services: n8n: image: n8n restart: always + environment: + - DB_TYPE=mongodb + - DB_MONGODB_CONNECTION_URL=mongodb://n8nuser:${MONGO_NON_ROOT_PASSWORD}@mongo:27017/${MONGO_INITDB_DATABASE} ports: - 5678:5678 links: @@ -25,4 +28,4 @@ services: - ~/.n8n:/root/.n8n # Wait 5 seconds to start n8n to make sure that MongoDB is ready # when n8n tries to connect to it - command: /bin/sh -c "sleep 5; n8n start --NODE_CONFIG='{\"database\":{\"type\":\"mongodb\", \"mongodbConfig\":{\"url\":\"mongodb://n8nuser:${MONGO_NON_ROOT_PASSWORD}@mongo:27017/${MONGO_INITDB_DATABASE}\"}}}'" + command: /bin/sh -c "sleep 5; n8n start" diff --git a/docker/images/n8n/README.md b/docker/images/n8n/README.md index 500a485ef6..775d709db2 100644 --- a/docker/images/n8n/README.md +++ b/docker/images/n8n/README.md @@ -89,10 +89,11 @@ Replace the following placeholders with the actual data: docker run -it --rm \ --name n8n \ -p 5678:5678 \ + -e DB_TYPE=mongodb \ + -e DB_MONGODB_CONNECTION_URL="mongodb://MONGO_USER:MONGO_PASSWORD@MONGO_SERVER:MONGO_PORT/MONGO_DATABASE" \ -v ~/.n8n:/root/.n8n \ n8nio/n8n \ - n8n start \ - --NODE_CONFIG='{\"database\":{\"type\":\"mongodb\", \"mongodbConfig\":{\"url\":\"mongodb://MONGO_USER:MONGO_PASSWORD@MONGO_SERVER:MONGO_PORT/MONGO_DATABASE\"}}}'" + n8n start ``` A full working setup with docker-compose can be found [here](../../compose/withMongo/README.md) diff --git a/packages/cli/README.md b/packages/cli/README.md index 2f0c69cb01..a9851e0963 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -71,30 +71,22 @@ n8n start --tunnel ### Start with MongoDB as Database By default n8n uses SQLite to save credentials, past executions and workflows. -To use MongoDB instead you can either overwrite the default configuration on -startup like this: +To use MongoDB instead you can provide the environment varialbles `DB_TYPE` and +`DB_MONGODB_CONNECTION_URL` like in the example bellow. + +Replace the following placeholders with the actual data: + - MONGO_DATABASE + - MONGO_HOST + - MONGO_PORT + - MONGO_USER + - MONGO_PASSWORD ```bash -n8n start \ - --NODE_CONFIG='{\"database\":{\"type\":\"mongodb\", \"mongodbConfig\":{\"url\":\"mongodb://MONGO_USER:MONGO_PASSWORD@MONGO_SERVER:MONGO_PORT/MONGO_DATABASE\"}}}'" +export DB_TYPE=mongodb +export DB_MONGODB_CONNECTION_URL=mongodb://MONGO_USER:MONGO_PASSWORD@MONGO_HOST:MONGO_PORT/MONGO_DATABASE +n8n start ``` -Or you can provide a custom configuration file by copying the default -configuration file [(config/defaults.ts)](https://github.com/n8n-io/n8n/blob/master/packages/cli/config/default.ts). -Make sure the file is also called `default.ts` and then set the path of the -parent directory as environment variable `NODE_CONFIG_DIR`. - -For example like this: -```bash -export NODE_CONFIG_DIR=/directory-containing-config-file -``` - -Change in the config file the value under `database.type` from `sqlite` -to `mongodb` and adjust the Mongo connection URL -`database.mongodbConfig` accordingly. - -n8n will then read your custom configuration and use MongoDB instead. - ## Execute Workflow from CLI diff --git a/packages/cli/commands/start.ts b/packages/cli/commands/start.ts index 3e8f965382..6c67c4c0f6 100644 --- a/packages/cli/commands/start.ts +++ b/packages/cli/commands/start.ts @@ -1,6 +1,6 @@ import Vorpal = require('vorpal'); import { Args } from 'vorpal'; -import * as config from 'config'; +import * as config from '../config'; const open = require('open'); @@ -105,7 +105,7 @@ flag "--init" to fix this problem!`); subdomain: userSettings.tunnelSubdomain, }; - const port = config.get('urls.port') as number; + const port = config.get('port') as number; // @ts-ignore const webhookTunnel = await tunnel(port, tunnelSettings); diff --git a/packages/cli/config/default.ts b/packages/cli/config/default.ts deleted file mode 100644 index fadf756a08..0000000000 --- a/packages/cli/config/default.ts +++ /dev/null @@ -1,53 +0,0 @@ -module.exports = { - urls: { - // Default path of the rest-endpoint - endpointRest: 'rest', - // Default path of the webhook-endpoint - endpointWebhook: 'webhook', - // Default path of the webhook-endpoint for testing - endpointWebhookTest: 'webhook-test', - - // How n8n can be reached (Editor & REST-API) - host: 'localhost', - port: 5678, - protocol: 'http', - }, - database: { - type: 'sqlite', // Available types: sqlite, mongodb - - // MongoDB specific settings - mongodbConfig: { - url: 'mongodb://user:password@localhost:27017/database', - }, - }, - - executions: { - // If a workflow executes all the data gets saved by default. This - // could be a problem when a workflow gets executed a lot and processes - // a lot of data. To not write the database full it is possible to - // not save the execution at all. - // Depending on if the execution did succeed or error a different - // save behaviour can be set. - saveDataErrorExecution: 'all', // Available options: all, none - saveDataSuccessExecution: 'all', // Available options: all, none - - // If the executions of workflows which got started via the editor - // should be saved. By default they will not be saved as this runs - // are normally only for testing and debugging. This setting can - // also be overwritten on a per workflow basis in the workflow settings - // in the editor. - saveManualExecutions: false, - }, - - nodes: { - // Nodes not to load even if found - // exclude: ['n8n-nodes-base.executeCommand'], - errorTriggerType: 'n8n-nodes-base.errorTrigger', - }, - - // The timezone to use. Is important for nodes like "Cron" which start the - // workflow automatically at a specified time. This setting can also be - // overwritten on a per worfklow basis in the workflow settings in the - // editor. - timezone: 'America/New_York', -}; diff --git a/packages/cli/config/index.ts b/packages/cli/config/index.ts new file mode 100644 index 0000000000..7f01b1ef36 --- /dev/null +++ b/packages/cli/config/index.ts @@ -0,0 +1,150 @@ +import * as convict from 'convict'; +import * as dotenv from 'dotenv'; + +dotenv.config(); + +const config = convict({ + + database: { + type: { + doc: 'Type of database to use', + format: ['sqlite', 'mongodb'], + default: 'sqlite', + env: 'DB_TYPE' + }, + mongodb: { + connectionUrl: { + doc: 'MongoDB Connection URL', + format: '*', + default: 'mongodb://user:password@localhost:27017/database', + env: 'DB_MONGODB_CONNECTION_URL' + } + }, + }, + + executions: { + // If a workflow executes all the data gets saved by default. This + // could be a problem when a workflow gets executed a lot and processes + // a lot of data. To not write the database full it is possible to + // not save the execution at all. + // Depending on if the execution did succeed or error a different + // save behaviour can be set. + saveDataOnError: { + doc: 'What workflow execution data to save on error', + format: ['all', 'none'], + default: 'all', + env: 'EXECUTIONS_DATA_SAVE_ON_ERROR' + }, + saveDataOnSuccess: { + doc: 'What workflow execution data to save on success', + format: ['all', 'none'], + default: 'all', + env: 'EXECUTIONS_DATA_SAVE_ON_SUCCESS' + }, + + // If the executions of workflows which got started via the editor + // should be saved. By default they will not be saved as this runs + // are normally only for testing and debugging. This setting can + // also be overwritten on a per workflow basis in the workflow settings + // in the editor. + saveDataManualExecutions: { + doc: 'Save data of executions when started manually via editor', + default: false, + env: 'EXECUTIONS_DATA_SAVE_MANUAL_EXECUTIONS' + }, + }, + + generic: { + // The timezone to use. Is important for nodes like "Cron" which start the + // workflow automatically at a specified time. This setting can also be + // overwritten on a per worfklow basis in the workflow settings in the + // editor. + timezone: { + doc: 'The timezone to use', + format: '*', + default: 'America/New_York', + env: 'GENERIC_TIMEZONE' + }, + }, + + // How n8n can be reached (Editor & REST-API) + host: { + format: String, + default: 'localhost', + arg: 'host', + env: 'N8N_HOST', + doc: 'Host name n8n can be reached' + }, + port: { + format: Number, + default: 5678, + arg: 'port', + env: 'N8N_PORT', + doc: 'HTTP port n8n can be reached' + }, + protocol: { + format: ['http', 'https'], + default: 'http', + env: 'N8N_PROTOCOL', + doc: 'HTTP Protocol via which n8n can be reached' + }, + + endpoints: { + rest: { + format: String, + default: 'rest', + env: 'N8N_ENDPOINT_REST', + doc: 'Path for rest endpoint' + }, + webhook: { + format: String, + default: 'webhook', + env: 'N8N_ENDPOINT_WEBHOOK', + doc: 'Path for webhook endpoint' + }, + webhookTest: { + format: String, + default: 'webhook-test', + env: 'N8N_ENDPOINT_WEBHOOK_TEST', + doc: 'Path for test-webhook endpoint' + }, + }, + + nodes: { + exclude: { + doc: 'Nodes not to load', + format: function check(rawValue) { + try { + const values = JSON.parse(rawValue); + if (!Array.isArray(values)) { + throw new Error(); + } + + for (const value of values) { + if (typeof value !== 'string') { + throw new Error(); + } + } + + } catch (error) { + throw new TypeError(`The Nodes to exclude is not a valid Array of strings.`); + } + }, + default: '[]', + env: 'NODES_EXCLUDE' + }, + errorTriggerType: { + doc: 'Node Type to use as Error Trigger', + format: String, + default: 'n8n-nodes-base.errorTrigger', + env: 'NODES_ERROR_TRIGGER_TYPE' + }, + }, + +}); + +config.validate({ + allowed: 'strict', +}); + +export = config; diff --git a/packages/cli/package.json b/packages/cli/package.json index 43e045b78b..695fbe323b 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -39,8 +39,9 @@ "dist" ], "devDependencies": { - "@types/config": "0.0.34", "@types/connect-history-api-fallback": "^1.3.1", + "@types/convict": "^4.2.1", + "@types/dotenv": "^6.1.1", "@types/express": "^4.16.1", "@types/jest": "^23.3.2", "@types/localtunnel": "^1.9.0", @@ -58,8 +59,9 @@ }, "dependencies": { "body-parser": "^1.18.3", - "config": "^3.0.1", "connect-history-api-fallback": "^1.6.0", + "convict": "^5.0.0", + "dotenv": "^8.0.0", "express": "^4.16.4", "flatted": "^2.0.0", "glob-promise": "^3.4.0", diff --git a/packages/cli/src/Db.ts b/packages/cli/src/Db.ts index 42fc64b8dd..29f99a7f53 100644 --- a/packages/cli/src/Db.ts +++ b/packages/cli/src/Db.ts @@ -13,7 +13,7 @@ import { getRepository, } from "typeorm"; -import * as config from 'config'; +import * as config from './../config'; import { @@ -40,7 +40,7 @@ export async function init(): Promise { entities = MongoDb; connectionOptions = { type: 'mongodb', - url: config.get('database.mongodbConfig.url') as string, + url: config.get('database.mongodb.connectionUrl') as string, useNewUrlParser: true, }; } else if (dbType === 'sqlite') { diff --git a/packages/cli/src/GenericHelpers.ts b/packages/cli/src/GenericHelpers.ts index 30cc4dcba0..ff32510a1e 100644 --- a/packages/cli/src/GenericHelpers.ts +++ b/packages/cli/src/GenericHelpers.ts @@ -1,4 +1,4 @@ -import * as config from 'config'; +import * as config from '../config'; import * as express from 'express'; @@ -25,9 +25,9 @@ export function logOutput(message: string, level = 'log'): void { * @returns {string} */ export function getBaseUrl(): string { - const protocol = config.get('urls.protocol') as string; - const host = config.get('urls.host') as string; - const port = config.get('urls.port') as number; + const protocol = config.get('protocol') as string; + const host = config.get('host') as string; + const port = config.get('port') as number; if (protocol === 'http' && port === 80 || protocol === 'https' && port === 443) { return `${protocol}://${host}/`; diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index a64f8b8160..239c684df7 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -89,6 +89,7 @@ export interface ICredentialsDecryptedResponse extends ICredentialsDecryptedDb { } export type DatabaseType = 'mongodb' | 'sqlite'; +export type SaveExecutionDataType = 'all' | 'none'; export interface IExecutionBase { id?: number | string | ObjectID; @@ -171,18 +172,47 @@ export interface IExecutionDeleteFilter { export interface IN8nConfig { database: IN8nConfigDatabase; - nodes?: IN8nConfigNodes; + endpoints: IN8nConfigEndpoints; + executions: IN8nConfigExecutions; + generic: IN8nConfigGeneric; + host: string; + nodes: IN8nConfigNodes; + port: number; + protocol: 'http' | 'https'; } export interface IN8nConfigDatabase { type: DatabaseType; - mongodbConfig?: { - url: string; + mongodb: { + connectionUrl: string; }; } +export interface IN8nConfigEndpoints { + rest: string; + webhook: string; + webhookTest: string; +} + +export interface IN8nConfigExecutions { + saveDataOnError: SaveExecutionDataType; + saveDataOnSuccess: SaveExecutionDataType; + saveDataManualExecutions: boolean; +} + +export interface IN8nConfigExecutions { + saveDataOnError: SaveExecutionDataType; + saveDataOnSuccess: SaveExecutionDataType; + saveDataManualExecutions: boolean; +} + +export interface IN8nConfigGeneric { + timezone: string; +} + export interface IN8nConfigNodes { - exclude?: string[]; + errorTriggerType: string; + exclude: string[]; } diff --git a/packages/cli/src/LoadNodesAndCredentials.ts b/packages/cli/src/LoadNodesAndCredentials.ts index a7ea301253..d3534786c2 100644 --- a/packages/cli/src/LoadNodesAndCredentials.ts +++ b/packages/cli/src/LoadNodesAndCredentials.ts @@ -6,11 +6,8 @@ import { ICredentialType, INodeType, } from 'n8n-workflow'; -import { - IN8nConfigNodes, -} from './'; -import * as config from 'config'; +import * as config from '../config'; import { access as fsAccess, readdir as fsReaddir, @@ -66,10 +63,7 @@ class LoadNodesAndCredentialsClass { throw new Error('Could not find "node_modules" folder!'); } - const nodeSettings = config.get('nodes') as IN8nConfigNodes | undefined; - if (nodeSettings !== undefined && nodeSettings.exclude !== undefined) { - this.excludeNodes = nodeSettings.exclude; - } + this.excludeNodes = config.get('nodes.exclude'); // Get all the installed packages which contain n8n nodes const packages = await this.getN8nNodePackages(); diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index a09ebe85d4..40c7b0df90 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -62,7 +62,7 @@ import { } from 'typeorm'; import * as parseUrl from 'parseurl'; -import * as config from 'config'; +import * as config from '../config'; // @ts-ignore import * as timezones from 'google-timezones-json'; @@ -83,12 +83,12 @@ class App { constructor() { this.app = express(); - this.endpointWebhook = config.get('urls.endpointWebhook') as string; - this.endpointWebhookTest = config.get('urls.endpointWebhookTest') as string; - this.saveDataErrorExecution = config.get('executions.saveDataErrorExecution') as string; - this.saveDataSuccessExecution = config.get('executions.saveDataSuccessExecution') as string; - this.saveManualExecutions = config.get('executions.saveManualExecutions') as boolean; - this.timezone = config.get('timezone') as string; + this.endpointWebhook = config.get('endpoints.webhook') as string; + this.endpointWebhookTest = config.get('endpoints.webhookTest') as string; + this.saveDataErrorExecution = config.get('executions.saveDataOnError') as string; + this.saveDataSuccessExecution = config.get('executions.saveDataOnSuccess') as string; + this.saveManualExecutions = config.get('executions.saveDataManualExecutions') as boolean; + this.timezone = config.get('generic.timezone') as string; this.config(); this.activeWorkflowRunner = ActiveWorkflowRunner.getInstance(); @@ -1030,7 +1030,7 @@ class App { } export function start() { - const PORT = config.get('urls.port'); + const PORT = config.get('port'); const app = new App().app; diff --git a/packages/cli/src/WorkflowExecuteAdditionalData.ts b/packages/cli/src/WorkflowExecuteAdditionalData.ts index 53a1b6762d..78b348a566 100644 --- a/packages/cli/src/WorkflowExecuteAdditionalData.ts +++ b/packages/cli/src/WorkflowExecuteAdditionalData.ts @@ -25,7 +25,7 @@ import { Workflow, } from 'n8n-workflow'; -import * as config from 'config'; +import * as config from '../config'; const pushInstance = Push.getInstance(); @@ -120,7 +120,7 @@ const hooks = (mode: WorkflowExecuteMode, workflowData: IWorkflowBase, workflowI const workflowSavePromise = WorkflowHelpers.saveStaticData(workflowInstance); - let saveManualExecutions = config.get('executions.saveManualExecutions') as boolean; + let saveManualExecutions = config.get('executions.saveDataManualExecutions') as boolean; if (workflowInstance.settings !== undefined && workflowInstance.settings.saveManualExecutions !== undefined) { // Apply to workflow override saveManualExecutions = workflowInstance.settings.saveManualExecutions as boolean; @@ -137,8 +137,8 @@ const hooks = (mode: WorkflowExecuteMode, workflowData: IWorkflowBase, workflowI } // Check config to know if execution should be saved or not - let saveDataErrorExecution = config.get('executions.saveDataErrorExecution') as string; - let saveDataSuccessExecution = config.get('executions.saveDataSuccessExecution') as string; + let saveDataErrorExecution = config.get('executions.saveDataOnError') as string; + let saveDataSuccessExecution = config.get('executions.saveDataOnSuccess') as string; if (workflowInstance.settings !== undefined) { saveDataErrorExecution = (workflowInstance.settings.saveDataErrorExecution as string) || saveDataErrorExecution; saveDataSuccessExecution = (workflowInstance.settings.saveDataSuccessExecution as string) || saveDataSuccessExecution; @@ -198,9 +198,9 @@ const hooks = (mode: WorkflowExecuteMode, workflowData: IWorkflowBase, workflowI export async function get(mode: WorkflowExecuteMode, workflowData: IWorkflowBase, workflowInstance: Workflow, sessionId?: string, retryOf?: string): Promise { const urlBaseWebhook = WebhookHelpers.getWebhookBaseUrl(); - const timezone = config.get('timezone') as string; - const webhookBaseUrl = urlBaseWebhook + config.get('urls.endpointWebhook') as string; - const webhookTestBaseUrl = urlBaseWebhook + config.get('urls.endpointWebhookTest') as string; + const timezone = config.get('generic.timezone') as string; + const webhookBaseUrl = urlBaseWebhook + config.get('endpoints.webhook') as string; + const webhookTestBaseUrl = urlBaseWebhook + config.get('endpoints.webhookTest') as string; const encryptionKey = await UserSettings.getEncryptionKey(); if (encryptionKey === undefined) { diff --git a/packages/cli/src/WorkflowHelpers.ts b/packages/cli/src/WorkflowHelpers.ts index fdf7c7f79e..f4d8925a89 100644 --- a/packages/cli/src/WorkflowHelpers.ts +++ b/packages/cli/src/WorkflowHelpers.ts @@ -16,7 +16,7 @@ import { Workflow, } from 'n8n-workflow'; -import * as config from 'config'; +import * as config from '../config'; const ERROR_TRIGGER_TYPE = config.get('nodes.errorTriggerType') as string; diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index 07a1bac94d..f765a076da 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -16,6 +16,7 @@ // Have to deactivate for TypeORM // "strict": true, "preserveConstEnums": true, + "resolveJsonModule": true, "declaration": true, "outDir": "./dist/", "target": "es2017",