From bac0de73006878d4505e2265aa07a30aefa9ccf0 Mon Sep 17 00:00:00 2001 From: Gergo Torcsvari Date: Sun, 15 Dec 2019 15:48:13 +0100 Subject: [PATCH] added experimental multidb support --- .dockerignore | 7 ++ Dockerfile | 20 ++-- doc/INSTALL.md | 35 ++++++ package.json | 6 +- src/backend/db.js | 34 ++++-- .../migrations/20180929054513_websockets.js | 2 +- .../migrations/20190227065017_settings.js | 16 --- src/backend/models/access_list.js | 7 +- src/backend/models/access_list_auth.js | 7 +- src/backend/models/audit-log.js | 7 +- src/backend/models/auth.js | 7 +- src/backend/models/certificate.js | 9 +- src/backend/models/dead_host.js | 7 +- src/backend/models/now_helper.js | 15 +++ src/backend/models/proxy_host.js | 7 +- src/backend/models/redirection_host.js | 7 +- src/backend/models/stream.js | 7 +- src/backend/models/user.js | 7 +- src/backend/models/user_permission.js | 7 +- src/backend/setup.js | 113 ++++++++++++------ 20 files changed, 213 insertions(+), 114 deletions(-) create mode 100644 .dockerignore create mode 100644 src/backend/models/now_helper.js diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..f4f4f265 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +.github/* +.idea/* +config/* +data/* +doc/* +dist/* +node_modules/* diff --git a/Dockerfile b/Dockerfile index a3f45064..6936e6f3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,15 +16,6 @@ COPY rootfs / RUN curl -L -o /tmp/s6-overlay-amd64.tar.gz "https://github.com/just-containers/s6-overlay/releases/download/v1.21.4.0/s6-overlay-amd64.tar.gz" \ && tar xzf /tmp/s6-overlay-amd64.tar.gz -C / -# App -ENV NODE_ENV=production - -ADD dist /app/dist -ADD node_modules /app/node_modules -ADD src/backend /app/src/backend -ADD package.json /app/package.json -ADD knexfile.js /app/knexfile.js - # Volumes VOLUME [ "/data", "/etc/letsencrypt" ] CMD [ "/init" ] @@ -37,3 +28,14 @@ EXPOSE 9876 HEALTHCHECK --interval=15s --timeout=3s CMD curl -f http://localhost:9876/health || exit 1 +# App +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm set progress=false && npm config set depth 0 && npm cache clean --force +RUN npm install +ENV NODE_ENV=production + +COPY . . + +RUN npm run-script build + diff --git a/doc/INSTALL.md b/doc/INSTALL.md index ad74c08d..24489ba3 100644 --- a/doc/INSTALL.md +++ b/doc/INSTALL.md @@ -40,6 +40,7 @@ affect the login and session management of the application. If these keys change ### Database +#### Tested: This app doesn't come with a database, you have to provide one yourself. Currently only `mysql/mariadb` is supported for the minimum versions: - MySQL v5.7.8+ @@ -48,6 +49,40 @@ This app doesn't come with a database, you have to provide one yourself. Current It's easy to use another docker container for your database also and link it as part of the docker stack, so that's what the following examples are going to use. +#### Experimental: +The app uses `knex` as its database connector library. +In theory it should work with postgres and sqlite too. +These are not battle-tested, but if you are feel yoursef confident enough you can try to config them! + +Example sqlite3 config: +```json +{ + "database": { + "engine": "knex-native", + "knex": { + "client": "sqlite3", + "connection": { + "filename": "/app/config/mydb.sqlite" + }, + "pool": { + "min": 0, + "max": 1, + "createTimeoutMillis": 3000, + "acquireTimeoutMillis": 31000, + "idleTimeoutMillis": 30000, + "reapIntervalMillis": 1000, + "createRetryIntervalMillis": 100, + "propagateCreateError": false + }, + "migrations": { + "tableName": "migrations" + }, + "useNullAsDefault": true + } + } +} +``` +With using the `knex-native` engine, you can add a [knex config](http://knexjs.org/#Installation-client) under the `knex` key. ### Running the App diff --git a/package.json b/package.json index e35e6b6d..24be4107 100644 --- a/package.json +++ b/package.json @@ -49,16 +49,18 @@ "html-entities": "^1.2.1", "json-schema-ref-parser": "^5.0.3", "jsonwebtoken": "^8.3.0", - "knex": "^0.15.2", + "knex": "^0.20.0", "liquidjs": "^5.1.1", "lodash": "^4.17.10", "moment": "^2.22.2", - "mysql": "^2.15.0", + "mysql2": "^1.7.0", "node-rsa": "^1.0.0", "objection": "^1.1.10", "path": "^0.12.7", + "pg": "^7.12.1", "restler": "^3.4.0", "signale": "^1.2.1", + "sqlite3": "^4.1.1", "temp-write": "^3.4.0", "unix-timestamp": "^0.2.0" }, diff --git a/src/backend/db.js b/src/backend/db.js index 6ad3f34c..c4506908 100644 --- a/src/backend/db.js +++ b/src/backend/db.js @@ -4,19 +4,27 @@ if (!config.has('database')) { throw new Error('Database config does not exist! Please read the instructions: https://github.com/jc21/nginx-proxy-manager/blob/master/doc/INSTALL.md'); } -let data = { - client: config.database.engine, - connection: { - host: config.database.host, - user: config.database.user, - password: config.database.password, - database: config.database.name, - port: config.database.port - }, - migrations: { - tableName: 'migrations' - } -}; +function generateDbConfig() { + if (config.database.engine === 'knex-native') { + return config.database.knex; + } else + return { + client: config.database.engine, + connection: { + host: config.database.host, + user: config.database.user, + password: config.database.password, + database: config.database.name, + port: config.database.port + }, + migrations: { + tableName: 'migrations' + } + }; +} + + +let data = generateDbConfig(); if (typeof config.database.version !== 'undefined') { data.version = config.database.version; diff --git a/src/backend/migrations/20180929054513_websockets.js b/src/backend/migrations/20180929054513_websockets.js index 22bafce1..27fba236 100644 --- a/src/backend/migrations/20180929054513_websockets.js +++ b/src/backend/migrations/20180929054513_websockets.js @@ -32,4 +32,4 @@ exports.up = function (knex/*, Promise*/) { exports.down = function (knex, Promise) { logger.warn('[' + migrate_name + '] You can\'t migrate down this one.'); return Promise.resolve(true); -}; \ No newline at end of file +}; diff --git a/src/backend/migrations/20190227065017_settings.js b/src/backend/migrations/20190227065017_settings.js index 6ba3653f..802d2e8d 100644 --- a/src/backend/migrations/20190227065017_settings.js +++ b/src/backend/migrations/20190227065017_settings.js @@ -22,22 +22,6 @@ exports.up = function (knex/*, Promise*/) { }) .then(() => { logger.info('[' + migrate_name + '] setting Table created'); - - // TODO: add settings - let settingModel = require('../models/setting'); - - return settingModel - .query() - .insert({ - id: 'default-site', - name: 'Default Site', - description: 'What to show when Nginx is hit with an unknown Host', - value: 'congratulations', - meta: {} - }); - }) - .then(() => { - logger.info('[' + migrate_name + '] Default settings added'); }); }; diff --git a/src/backend/models/access_list.js b/src/backend/models/access_list.js index 1c7cb51e..062ffd1b 100644 --- a/src/backend/models/access_list.js +++ b/src/backend/models/access_list.js @@ -5,13 +5,14 @@ const db = require('../db'); const Model = require('objection').Model; const User = require('./user'); const AccessListAuth = require('./access_list_auth'); +const now = require('./now_helper'); Model.knex(db); class AccessList extends Model { $beforeInsert () { - this.created_on = Model.raw('NOW()'); - this.modified_on = Model.raw('NOW()'); + this.created_on = now(); + this.modified_on = now(); // Default for meta if (typeof this.meta === 'undefined') { @@ -20,7 +21,7 @@ class AccessList extends Model { } $beforeUpdate () { - this.modified_on = Model.raw('NOW()'); + this.modified_on = now(); } static get name () { diff --git a/src/backend/models/access_list_auth.js b/src/backend/models/access_list_auth.js index e4ebd204..5882846b 100644 --- a/src/backend/models/access_list_auth.js +++ b/src/backend/models/access_list_auth.js @@ -3,13 +3,14 @@ const db = require('../db'); const Model = require('objection').Model; +const now = require('./now_helper'); Model.knex(db); class AccessListAuth extends Model { $beforeInsert () { - this.created_on = Model.raw('NOW()'); - this.modified_on = Model.raw('NOW()'); + this.created_on = now(); + this.modified_on = now(); // Default for meta if (typeof this.meta === 'undefined') { @@ -18,7 +19,7 @@ class AccessListAuth extends Model { } $beforeUpdate () { - this.modified_on = Model.raw('NOW()'); + this.modified_on = now(); } static get name () { diff --git a/src/backend/models/audit-log.js b/src/backend/models/audit-log.js index 31da1748..38c87426 100644 --- a/src/backend/models/audit-log.js +++ b/src/backend/models/audit-log.js @@ -4,13 +4,14 @@ const db = require('../db'); const Model = require('objection').Model; const User = require('./user'); +const now = require('./now_helper'); Model.knex(db); class AuditLog extends Model { $beforeInsert () { - this.created_on = Model.raw('NOW()'); - this.modified_on = Model.raw('NOW()'); + this.created_on = now(); + this.modified_on = now(); // Default for meta if (typeof this.meta === 'undefined') { @@ -19,7 +20,7 @@ class AuditLog extends Model { } $beforeUpdate () { - this.modified_on = Model.raw('NOW()'); + this.modified_on = now(); } static get name () { diff --git a/src/backend/models/auth.js b/src/backend/models/auth.js index b793a6fd..52469bd0 100644 --- a/src/backend/models/auth.js +++ b/src/backend/models/auth.js @@ -5,6 +5,7 @@ const bcrypt = require('bcrypt'); const db = require('../db'); const Model = require('objection').Model; const User = require('./user'); +const now = require('./now_helper'); Model.knex(db); @@ -24,8 +25,8 @@ function encryptPassword () { class Auth extends Model { $beforeInsert (queryContext) { - this.created_on = Model.raw('NOW()'); - this.modified_on = Model.raw('NOW()'); + this.created_on = now(); + this.modified_on = now(); // Default for meta if (typeof this.meta === 'undefined') { @@ -36,7 +37,7 @@ class Auth extends Model { } $beforeUpdate (queryContext) { - this.modified_on = Model.raw('NOW()'); + this.modified_on = now(); return encryptPassword.apply(this, queryContext); } diff --git a/src/backend/models/certificate.js b/src/backend/models/certificate.js index 8d016755..86c08939 100644 --- a/src/backend/models/certificate.js +++ b/src/backend/models/certificate.js @@ -4,17 +4,18 @@ const db = require('../db'); const Model = require('objection').Model; const User = require('./user'); +const now = require('./now_helper'); Model.knex(db); class Certificate extends Model { $beforeInsert () { - this.created_on = Model.raw('NOW()'); - this.modified_on = Model.raw('NOW()'); + this.created_on = now(); + this.modified_on = now(); // Default for expires_on if (typeof this.expires_on === 'undefined') { - this.expires_on = Model.raw('NOW()'); + this.expires_on = now(); } // Default for domain_names @@ -31,7 +32,7 @@ class Certificate extends Model { } $beforeUpdate () { - this.modified_on = Model.raw('NOW()'); + this.modified_on = now(); // Sort domain_names if (typeof this.domain_names !== 'undefined') { diff --git a/src/backend/models/dead_host.js b/src/backend/models/dead_host.js index d8357157..89115dbc 100644 --- a/src/backend/models/dead_host.js +++ b/src/backend/models/dead_host.js @@ -5,13 +5,14 @@ const db = require('../db'); const Model = require('objection').Model; const User = require('./user'); const Certificate = require('./certificate'); +const now = require('./now_helper'); Model.knex(db); class DeadHost extends Model { $beforeInsert () { - this.created_on = Model.raw('NOW()'); - this.modified_on = Model.raw('NOW()'); + this.created_on = now(); + this.modified_on = now(); // Default for domain_names if (typeof this.domain_names === 'undefined') { @@ -27,7 +28,7 @@ class DeadHost extends Model { } $beforeUpdate () { - this.modified_on = Model.raw('NOW()'); + this.modified_on = now(); // Sort domain_names if (typeof this.domain_names !== 'undefined') { diff --git a/src/backend/models/now_helper.js b/src/backend/models/now_helper.js new file mode 100644 index 00000000..3d29e1b0 --- /dev/null +++ b/src/backend/models/now_helper.js @@ -0,0 +1,15 @@ + +const db = require('../db'); +const config = require('config'); +const Model = require('objection').Model; + +Model.knex(db); + +module.exports = function () { + if(config.database.knex && config.database.knex.client === 'sqlite3') { + return Model.raw("date('now')"); + } else { + return Model.raw('NOW()'); + } + }; + diff --git a/src/backend/models/proxy_host.js b/src/backend/models/proxy_host.js index 801796a1..e96c2191 100644 --- a/src/backend/models/proxy_host.js +++ b/src/backend/models/proxy_host.js @@ -6,13 +6,14 @@ const Model = require('objection').Model; const User = require('./user'); const AccessList = require('./access_list'); const Certificate = require('./certificate'); +const now = require('./now_helper'); Model.knex(db); class ProxyHost extends Model { $beforeInsert () { - this.created_on = Model.raw('NOW()'); - this.modified_on = Model.raw('NOW()'); + this.created_on = now(); + this.modified_on = now(); // Default for domain_names if (typeof this.domain_names === 'undefined') { @@ -28,7 +29,7 @@ class ProxyHost extends Model { } $beforeUpdate () { - this.modified_on = Model.raw('NOW()'); + this.modified_on = now(); // Sort domain_names if (typeof this.domain_names !== 'undefined') { diff --git a/src/backend/models/redirection_host.js b/src/backend/models/redirection_host.js index c7157f65..d9f10418 100644 --- a/src/backend/models/redirection_host.js +++ b/src/backend/models/redirection_host.js @@ -5,13 +5,14 @@ const db = require('../db'); const Model = require('objection').Model; const User = require('./user'); const Certificate = require('./certificate'); +const now = require('./now_helper'); Model.knex(db); class RedirectionHost extends Model { $beforeInsert () { - this.created_on = Model.raw('NOW()'); - this.modified_on = Model.raw('NOW()'); + this.created_on = now(); + this.modified_on = now(); // Default for domain_names if (typeof this.domain_names === 'undefined') { @@ -27,7 +28,7 @@ class RedirectionHost extends Model { } $beforeUpdate () { - this.modified_on = Model.raw('NOW()'); + this.modified_on = now(); // Sort domain_names if (typeof this.domain_names !== 'undefined') { diff --git a/src/backend/models/stream.js b/src/backend/models/stream.js index 5394f725..0cebed65 100644 --- a/src/backend/models/stream.js +++ b/src/backend/models/stream.js @@ -4,13 +4,14 @@ const db = require('../db'); const Model = require('objection').Model; const User = require('./user'); +const now = require('./now_helper'); Model.knex(db); class Stream extends Model { $beforeInsert () { - this.created_on = Model.raw('NOW()'); - this.modified_on = Model.raw('NOW()'); + this.created_on = now(); + this.modified_on = now(); // Default for meta if (typeof this.meta === 'undefined') { @@ -19,7 +20,7 @@ class Stream extends Model { } $beforeUpdate () { - this.modified_on = Model.raw('NOW()'); + this.modified_on = now(); } static get name () { diff --git a/src/backend/models/user.js b/src/backend/models/user.js index 09df952b..11763fbb 100644 --- a/src/backend/models/user.js +++ b/src/backend/models/user.js @@ -4,13 +4,14 @@ const db = require('../db'); const Model = require('objection').Model; const UserPermission = require('./user_permission'); +const now = require('./now_helper'); Model.knex(db); class User extends Model { $beforeInsert () { - this.created_on = Model.raw('NOW()'); - this.modified_on = Model.raw('NOW()'); + this.created_on = now(); + this.modified_on = now(); // Default for roles if (typeof this.roles === 'undefined') { @@ -19,7 +20,7 @@ class User extends Model { } $beforeUpdate () { - this.modified_on = Model.raw('NOW()'); + this.modified_on = now(); } static get name () { diff --git a/src/backend/models/user_permission.js b/src/backend/models/user_permission.js index 5ffcfa04..0eb57669 100644 --- a/src/backend/models/user_permission.js +++ b/src/backend/models/user_permission.js @@ -3,17 +3,18 @@ const db = require('../db'); const Model = require('objection').Model; +const now = require('./now_helper'); Model.knex(db); class UserPermission extends Model { $beforeInsert () { - this.created_on = Model.raw('NOW()'); - this.modified_on = Model.raw('NOW()'); + this.created_on = now(); + this.modified_on = now(); } $beforeUpdate () { - this.modified_on = Model.raw('NOW()'); + this.modified_on = now(); } static get name () { diff --git a/src/backend/setup.js b/src/backend/setup.js index ef870cbe..1eccf569 100644 --- a/src/backend/setup.js +++ b/src/backend/setup.js @@ -5,10 +5,11 @@ const logger = require('./logger').setup; const userModel = require('./models/user'); const userPermissionModel = require('./models/user_permission'); const authModel = require('./models/auth'); +const settingModel = require('./models/setting'); const debug_mode = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG; module.exports = function () { - return new Promise((resolve, reject) => { + function setupJwt(resolve, reject) { // Now go and check if the jwt gpg keys have been created and if not, create them if (!config.has('jwt') || !config.has('jwt.key') || !config.has('jwt.pub')) { logger.info('Creating a new JWT key pair...'); @@ -56,15 +57,15 @@ module.exports = function () { resolve(); } - }) - .then(() => { - return userModel - .query() - .select(userModel.raw('COUNT(`id`) as `count`')) - .where('is_deleted', 0) - .first(); - }) - .then(row => { + } + + function setupDefaultUser() { + (userModel + .query() + .select(userModel.raw('COUNT(`id`) as `count`')) + .where('is_deleted', 0) + .first() + ).then(row => { if (!row.count) { // Create a new user and set password logger.info('Creating a new user: admin@example.com with password: changeme'); @@ -79,37 +80,71 @@ module.exports = function () { }; return userModel - .query() - .insertAndFetch(data) - .then(user => { - return authModel - .query() - .insert({ - user_id: user.id, - type: 'password', - secret: 'changeme', - meta: {} - }) - .then(() => { - return userPermissionModel - .query() - .insert({ - user_id: user.id, - visibility: 'all', - proxy_hosts: 'manage', - redirection_hosts: 'manage', - dead_hosts: 'manage', - streams: 'manage', - access_lists: 'manage', - certificates: 'manage' - }); - }); - }) - .then(() => { - logger.info('Initial setup completed'); - }); + .query() + .insertAndFetch(data) + .then(user => { + return authModel + .query() + .insert({ + user_id: user.id, + type: 'password', + secret: 'changeme', + meta: {} + }) + .then(() => { + return userPermissionModel + .query() + .insert({ + user_id: user.id, + visibility: 'all', + proxy_hosts: 'manage', + redirection_hosts: 'manage', + dead_hosts: 'manage', + streams: 'manage', + access_lists: 'manage', + certificates: 'manage' + }); + }); + }) + .then(() => { + logger.info('Initial admin setup completed'); + }); } else if (debug_mode) { logger.debug('Admin user setup not required'); } }); + } + + function setupDefaultSettings() { + return settingModel + .query() + .select(userModel.raw('COUNT(`id`) as `count`')) + .first() + .then(row => { + if (!row.count) { + settingModel + .query() + .insert({ + id: 'default-site', + name: 'Default Site', + description: 'What to show when Nginx is hit with an unknown Host', + value: 'congratulations', + meta: {} + }).then(() => { + logger.info('Default settings added'); + }) + } if (debug_mode) { + logger.debug('Default setting setup not required'); + } + }); + } + + return new Promise((resolve, reject) => { + return setupJwt(resolve, reject); + }).then(() => { + return setupDefaultUser(); + }).then(() => { + return setupDefaultSettings(); + }); + };