diff --git a/.jenkins/config-mysql.json b/.jenkins/config-mysql.json deleted file mode 100644 index 19ad2237f..000000000 --- a/.jenkins/config-mysql.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "database": { - "engine": "mysql", - "host": "db", - "name": "npm", - "user": "npm", - "password": "npm", - "port": 3306 - } -} \ No newline at end of file diff --git a/.jenkins/config-sqlite.json b/.jenkins/config-sqlite.json deleted file mode 100644 index 97d688d21..000000000 --- a/.jenkins/config-sqlite.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "database": { - "engine": "knex-native", - "knex": { - "client": "sqlite3", - "connection": { - "filename": "/data/database.sqlite" - } - } - } -} diff --git a/.version b/.version index 097a15a2a..24ba9a38d 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -2.6.2 +2.7.0 diff --git a/README.md b/README.md index 9fb93d717..a80d05243 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@



- + diff --git a/backend/index.js b/backend/index.js index 0c08af328..50a263bac 100644 --- a/backend/index.js +++ b/backend/index.js @@ -2,7 +2,10 @@ const logger = require('./logger').global; -function appStart () { +async function appStart () { + // Create config file db settings if environment variables have been set + await createDbConfigFromEnvironment(); + const migrate = require('./migrate'); const setup = require('./setup'); const app = require('./app'); @@ -10,6 +13,7 @@ function appStart () { const internalCertificate = require('./internal/certificate'); const internalIpRanges = require('./internal/ip_ranges'); + return migrate.latest() .then(setup) .then(() => { @@ -39,6 +43,87 @@ function appStart () { }); } +async function createDbConfigFromEnvironment(){ + return new Promise((resolve, reject) => { + const envMysqlHost = process.env.DB_MYSQL_HOST; + const envMysqlPort = process.env.DB_MYSQL_PORT; + const envMysqlUser = process.env.DB_MYSQL_USER; + const envMysqlName = process.env.DB_MYSQL_NAME; + const envSqliteFile = process.env.DB_SQLITE_FILE; + if ((envMysqlHost && envMysqlPort && envMysqlUser && envMysqlName) || envSqliteFile) { + const fs = require('fs'); + const filename = (process.env.NODE_CONFIG_DIR || './config') + '/' + (process.env.NODE_ENV || 'default') + '.json'; + let configData = {}; + + try { + configData = require(filename); + } catch (err) { + // do nothing + } + + if (configData.database && configData.database.engine && !configData.database.fromEnv) { + logger.info('Manual db configuration already exists, skipping config creation from environment variables'); + resolve(); + return; + } + + if (envMysqlHost && envMysqlPort && envMysqlUser && envMysqlName) { + const newConfig = { + fromEnv: true, + engine: 'mysql', + host: envMysqlHost, + port: envMysqlPort, + user: envMysqlUser, + password: process.env.DB_MYSQL_PASSWORD, + name: envMysqlName, + }; + + if (JSON.stringify(configData.database) === JSON.stringify(newConfig)) { + // Config is unchanged, skip overwrite + resolve(); + return; + } + + logger.info('Generating MySQL db configuration from environment variables'); + configData.database = newConfig; + + } else { + const newConfig = { + fromEnv: true, + engine: 'knex-native', + knex: { + client: 'sqlite3', + connection: { + filename: envSqliteFile + } + } + }; + if (JSON.stringify(configData.database) === JSON.stringify(newConfig)) { + // Config is unchanged, skip overwrite + resolve(); + return; + } + + logger.info('Generating Sqlite db configuration from environment variables'); + configData.database = newConfig; + } + + // Write config + fs.writeFile(filename, JSON.stringify(configData, null, 2), (err) => { + if (err) { + logger.error('Could not write db config to config file: ' + filename); + reject(err); + } else { + logger.info('Wrote db configuration to config file: ' + filename); + resolve(); + } + }); + } else { + // resolve(); + } + }); +} + try { appStart(); } catch (err) { diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js index 19e0592e9..3725c1c8f 100644 --- a/backend/internal/certificate.js +++ b/backend/internal/certificate.js @@ -216,6 +216,13 @@ const internalCertificate = { return saved_row; }); }); + }).catch(async (error) => { + // Delete the certificate from the database if it was not created successfully + await certificateModel + .query() + .deleteById(certificate.id); + + throw error; }); } else { return certificate; diff --git a/docker/docker-compose.ci.yml b/docker/docker-compose.ci.yml index 2228623f5..89c38572d 100644 --- a/docker/docker-compose.ci.yml +++ b/docker/docker-compose.ci.yml @@ -5,11 +5,15 @@ services: fullstack-mysql: image: ${IMAGE}:ci-${BUILD_NUMBER} environment: - - NODE_ENV=development - - FORCE_COLOR=1 + NODE_ENV: "development" + FORCE_COLOR: 1 + DB_MYSQL_HOST: "db" + DB_MYSQL_PORT: 3306 + DB_MYSQL_USER: "npm" + DB_MYSQL_PASSWORD: "npm" + DB_MYSQL_NAME: "npm" volumes: - npm_data:/data - - ../.jenkins/config-mysql.json:/app/config/development.json expose: - 81 - 80 @@ -20,11 +24,11 @@ services: fullstack-sqlite: image: ${IMAGE}:ci-${BUILD_NUMBER} environment: - - NODE_ENV=development - - FORCE_COLOR=1 + NODE_ENV: "development" + FORCE_COLOR: 1 + DB_SQLITE_FILE: "/data/database.sqlite" volumes: - npm_data:/data - - ../.jenkins/config-sqlite.json:/app/config/development.json expose: - 81 - 80 diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 5668dbd27..a0b4547ba 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -14,10 +14,16 @@ services: networks: - nginx_proxy_manager environment: - - NODE_ENV=development - - FORCE_COLOR=1 - - DEVELOPMENT=true - #- DISABLE_IPV6=true + NODE_ENV: "development" + FORCE_COLOR: 1 + DEVELOPMENT: "true" + DB_MYSQL_HOST: "db" + DB_MYSQL_PORT: 3306 + DB_MYSQL_USER: "npm" + DB_MYSQL_PASSWORD: "npm" + DB_MYSQL_NAME: "npm" + # DB_SQLITE_FILE: "/data/database.sqlite" + # DISABLE_IPV6: "true" volumes: - npm_data:/data - le_data:/etc/letsencrypt diff --git a/docs/README.md b/docs/README.md index 0ee8154ec..3f2f62822 100644 --- a/docs/README.md +++ b/docs/README.md @@ -45,21 +45,7 @@ footer: MIT Licensed | Copyright © 2016-present jc21.com - [Docker Install documentation](https://docs.docker.com/install/) - [Docker-Compose Install documentation](https://docs.docker.com/compose/install/) -2. Create a config file for example -```json -{ - "database": { - "engine": "mysql", - "host": "db", - "name": "npm", - "user": "npm", - "password": "npm", - "port": 3306 - } -} -``` - -3. Create a docker-compose.yml file similar to this: +2. Create a docker-compose.yml file similar to this: ```yml version: '3' @@ -70,8 +56,13 @@ services: - '80:80' - '81:81' - '443:443' + environment: + DB_MYSQL_HOST: "db" + DB_MYSQL_PORT: 3306 + DB_MYSQL_USER: "npm" + DB_MYSQL_PASSWORD: "npm" + DB_MYSQL_NAME: "npm" volumes: - - ./config.json:/app/config/production.json - ./data:/data - ./letsencrypt:/etc/letsencrypt db: @@ -85,13 +76,13 @@ services: - ./data/mysql:/var/lib/mysql ``` -4. Bring up your stack +3. Bring up your stack ```bash docker-compose up -d ``` -5. Log in to the Admin UI +4. Log in to the Admin UI When your docker container is running, connect to it on port `81` for the admin interface. Sometimes this can take a little bit because of the entropy of keys. diff --git a/docs/setup/README.md b/docs/setup/README.md index 52160e177..8af7eee23 100644 --- a/docs/setup/README.md +++ b/docs/setup/README.md @@ -1,50 +1,5 @@ # Full Setup Instructions -### Configuration File - -**The configuration file needs to be provided by you!** - -Don't worry, this is easy to do. - -The app requires a configuration file to let it know what database you're using. By default, this file is called `config.json` - -Here's an example configuration for `mysql` (or mariadb) that is compatible with the docker-compose example below: - -```json -{ - "database": { - "engine": "mysql", - "host": "db", - "name": "npm", - "user": "npm", - "password": "npm", - "port": 3306 - } -} -``` - -Alternatively if you would like to use a Sqlite database file: - -```json -{ - "database": { - "engine": "knex-native", - "knex": { - "client": "sqlite3", - "connection": { - "filename": "/data/database.sqlite" - } - } - } -} -``` - -Once you've created your configuration file it's easy to mount it in the docker container. - -**Note:** After the first run of the application, the config file will be altered to include generated encryption keys unique to your installation. These keys -affect the login and session management of the application. If these keys change for any reason, all users will be logged out. - - ### MySQL Database If you opt for the MySQL configuration you will have to provide the database server yourself. You can also use MariaDB. Here are the minimum supported versions: @@ -61,7 +16,6 @@ When using a `mariadb` database, the NPM configuration file should still use the ::: - ### Running the App Via `docker-compose`: @@ -70,7 +24,7 @@ Via `docker-compose`: version: "3" services: app: - image: jc21/nginx-proxy-manager:2 + image: 'jc21/nginx-proxy-manager:latest' restart: always ports: # Public HTTP Port: @@ -80,11 +34,18 @@ services: # Admin Web Port: - '81:81' environment: + # These are the settings to access your db + DB_MYSQL_HOST: "db" + DB_MYSQL_PORT: 3306 + DB_MYSQL_USER: "npm" + DB_MYSQL_PASSWORD: "npm" + DB_MYSQL_NAME: "npm" + # If you would rather use Sqlite uncomment this + # and remove all DB_MYSQL_* lines above + # DB_SQLITE_FILE: "/data/database.sqlite" # Uncomment this if IPv6 is not enabled on your host # DISABLE_IPV6: 'true' volumes: - # Make sure this config.json file exists as per instructions above: - - ./config.json:/app/config/production.json - ./data:/data - ./letsencrypt:/etc/letsencrypt depends_on: @@ -101,14 +62,14 @@ services: - ./data/mysql:/var/lib/mysql ``` +_Please note, that `DB_MYSQL_*` environment variables will take precedent over `DB_SQLITE_*` variables. So if you keep the MySQL variables, you will not be able to use Sqlite._ + Then: ```bash docker-compose up -d ``` -The config file (config.json) must be present in this directory. - ### Running on Raspberry PI / ARM devices The docker images support the following architectures: @@ -146,3 +107,49 @@ Password: changeme ``` Immediately after logging in with this default user you will be asked to modify your details and change your password. + +### Configuration File + +::: warning + +This section is meant for advanced users + +::: + +If you would like more control over the database settings you can define a custom config JSON file. + + +Here's an example for `sqlite` configuration as it is generated from the environment variables: + +```json +{ + "database": { + "engine": "knex-native", + "knex": { + "client": "sqlite3", + "connection": { + "filename": "/data/database.sqlite" + } + } + } +} +``` + +You can modify the `knex` object with your custom configuration, but note that not all knex clients might be installed in the image. + +Once you've created your configuration file you can mount it to `/app/config/production.json` inside you container using: + +``` +[...] +services: + app: + image: 'jc21/nginx-proxy-manager:latest' + [...] + volumes: + - ./config.json:/app/config/production.json + [...] +[...] +``` + +**Note:** After the first run of the application, the config file will be altered to include generated encryption keys unique to your installation. +These keys affect the login and session management of the application. If these keys change for any reason, all users will be logged out. diff --git a/frontend/js/app/nginx/certificates/delete.js b/frontend/js/app/nginx/certificates/delete.js index 426e25064..89a2e5e83 100644 --- a/frontend/js/app/nginx/certificates/delete.js +++ b/frontend/js/app/nginx/certificates/delete.js @@ -16,6 +16,8 @@ module.exports = Mn.View.extend({ events: { 'click @ui.save': function (e) { e.preventDefault(); + this.ui.save.addClass('btn-loading'); + this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); App.Api.Nginx.Certificates.delete(this.model.get('id')) .then(() => { @@ -25,6 +27,7 @@ module.exports = Mn.View.extend({ .catch(err => { alert(err.message); this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); + this.ui.save.removeClass('btn-loading'); }); } }