Use convict for configuration to make n8n easier to configure

This commit is contained in:
Jan Oberhauser 2019-07-21 19:47:41 +02:00
parent fbaf445bf8
commit d027545986
15 changed files with 234 additions and 114 deletions

View file

@ -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"

View file

@ -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)

View file

@ -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

View file

@ -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);

View file

@ -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',
};

View file

@ -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;

View file

@ -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",

View file

@ -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<IDatabaseCollections> {
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') {

View file

@ -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}/`;

View file

@ -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[];
}

View file

@ -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();

View file

@ -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;

View file

@ -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<IWorkflowExecuteAdditionalData> {
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) {

View file

@ -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;

View file

@ -16,6 +16,7 @@
// Have to deactivate for TypeORM
// "strict": true,
"preserveConstEnums": true,
"resolveJsonModule": true,
"declaration": true,
"outDir": "./dist/",
"target": "es2017",