mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-08-03 16:03:38 +00:00
Merge branch 'developo' into develop
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
<p align="center" class="items-center">
|
<p align="center" class="items-center">
|
||||||
<img src="https://nginxproxymanager.com/github.png">
|
<img src="https://nginxproxymanager.com/github.png">
|
||||||
<br><br>
|
<br><br>
|
||||||
<img src="https://img.shields.io/badge/version-2.9.22+-green.svg?style=for-the-badge">
|
<img src="https://img.shields.io/badge/version-2.10.2+-green.svg?style=for-the-badge">
|
||||||
<a href="https://hub.docker.com/r/zoeyvid/nginx-proxy-manager">
|
<a href="https://hub.docker.com/r/zoeyvid/nginx-proxy-manager">
|
||||||
<img src="https://img.shields.io/docker/stars/zoeyvid/nginx-proxy-manager.svg?style=for-the-badge">
|
<img src="https://img.shields.io/docker/stars/zoeyvid/nginx-proxy-manager.svg?style=for-the-badge">
|
||||||
</a>
|
</a>
|
||||||
@@ -66,12 +66,17 @@ so that the barrier for entry here is low.
|
|||||||
- Passwort reset (only sqlite) (`docker exec -it nginx-proxy-manager password-reset.js USER_EMAIL PASSWORD`)
|
- Passwort reset (only sqlite) (`docker exec -it nginx-proxy-manager password-reset.js USER_EMAIL PASSWORD`)
|
||||||
|
|
||||||
## Soon
|
## Soon
|
||||||
|
- disabling IPv4/IPv6
|
||||||
|
- MariaDB/MySQL TLS support (if requested)
|
||||||
|
- support changing the PUID/PGID (maybe)
|
||||||
- more
|
- more
|
||||||
|
|
||||||
## migration
|
## migration
|
||||||
- **NOTE: migrating back to the original is not possible**, so make first a **backup** before migration, so you can use the backup to switch back
|
- **NOTE: migrating back to the original is not possible**, so make first a **backup** before migration, so you can use the backup to switch back
|
||||||
- if you use custom certificates, you need to upload the CA/Intermediate Certificate (file name: `chain.pem`) in the `/opt/npm/tls/custom/npm-[certificate-id]` folder
|
- if you use custom certificates, you need to upload the CA/Intermediate Certificate (file name: `chain.pem`) in the `/opt/npm/tls/custom/npm-[certificate-id]` folder
|
||||||
- some buttons have changed, check if they are still correct
|
- some buttons have changed, check if they are still correct
|
||||||
|
- please delete all dnspod certs and recreate them
|
||||||
|
- changing the PUID/PGID is not supported (since it would break running in network_mode host)
|
||||||
|
|
||||||
# Use as webserver
|
# Use as webserver
|
||||||
|
|
||||||
|
@@ -2,6 +2,7 @@ const express = require('express');
|
|||||||
const bodyParser = require('body-parser');
|
const bodyParser = require('body-parser');
|
||||||
const fileUpload = require('express-fileupload');
|
const fileUpload = require('express-fileupload');
|
||||||
const compression = require('compression');
|
const compression = require('compression');
|
||||||
|
const config = require('./lib/config');
|
||||||
const log = require('./logger').express;
|
const log = require('./logger').express;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,7 +25,7 @@ app.enable('trust proxy', ['loopback', 'linklocal', 'uniquelocal']);
|
|||||||
app.enable('strict routing');
|
app.enable('strict routing');
|
||||||
|
|
||||||
// pretty print JSON when not live
|
// pretty print JSON when not live
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (config.debug()) {
|
||||||
app.set('json spaces', 2);
|
app.set('json spaces', 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +66,7 @@ app.use(function (err, req, res, next) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development' || (req.baseUrl + req.path).includes('nginx/certificates')) {
|
if (config.debug() || (req.baseUrl + req.path).includes('nginx/certificates')) {
|
||||||
payload.debug = {
|
payload.debug = {
|
||||||
stack: typeof err.stack !== 'undefined' && err.stack ? err.stack.split('\n') : null,
|
stack: typeof err.stack !== 'undefined' && err.stack ? err.stack.split('\n') : null,
|
||||||
previous: err.previous
|
previous: err.previous
|
||||||
@@ -74,7 +75,7 @@ app.use(function (err, req, res, next) {
|
|||||||
|
|
||||||
// Not every error is worth logging - but this is good for now until it gets annoying.
|
// Not every error is worth logging - but this is good for now until it gets annoying.
|
||||||
if (typeof err.stack !== 'undefined' && err.stack) {
|
if (typeof err.stack !== 'undefined' && err.stack) {
|
||||||
if (process.env.NODE_ENV === 'development' || process.env.DEBUG) {
|
if (config.debug()) {
|
||||||
log.debug(err.stack);
|
log.debug(err.stack);
|
||||||
} else if (typeof err.public == 'undefined' || !err.public) {
|
} else if (typeof err.public == 'undefined' || !err.public) {
|
||||||
log.warn(err.message);
|
log.warn(err.message);
|
||||||
|
@@ -1,44 +1,27 @@
|
|||||||
const config = require('config');
|
const config = require('./lib/config');
|
||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
if (!config.has('database')) {
|
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');
|
throw new Error('Database config does not exist! Please read the instructions: https://nginxproxymanager.com/setup/');
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateDbConfig() {
|
function generateDbConfig() {
|
||||||
if (config.database.engine === 'knex-native') {
|
const cfg = config.get('database');
|
||||||
return config.database.knex;
|
if (cfg.engine === 'knex-native') {
|
||||||
} else {
|
return cfg.knex;
|
||||||
let newConfig = {
|
}
|
||||||
client: config.database.engine,
|
return {
|
||||||
|
client: cfg.engine,
|
||||||
connection: {
|
connection: {
|
||||||
host: config.database.host,
|
host: cfg.host,
|
||||||
user: config.database.user,
|
user: cfg.user,
|
||||||
password: config.database.password,
|
password: cfg.password,
|
||||||
database: config.database.name,
|
database: cfg.name,
|
||||||
port: config.database.port
|
port: cfg.port
|
||||||
},
|
},
|
||||||
migrations: {
|
migrations: {
|
||||||
tableName: 'migrations'
|
tableName: 'migrations'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (process.env.DB_MYSQL_CA) {
|
|
||||||
newConfig.connection.ssl = {
|
|
||||||
ca: fs.readFileSync(process.env.DB_MYSQL_CA),
|
|
||||||
rejectUnauthorized: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return newConfig;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports = require('knex')(generateDbConfig());
|
||||||
let data = generateDbConfig();
|
|
||||||
|
|
||||||
if (typeof config.database.version !== 'undefined') {
|
|
||||||
data.version = config.database.version;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = require('knex')(data);
|
|
||||||
|
@@ -40,6 +40,210 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/nginx/proxy-hosts": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "getProxyHosts",
|
||||||
|
"summary": "Get all proxy hosts",
|
||||||
|
"tags": ["Proxy Hosts"],
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": ["users"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "query",
|
||||||
|
"name": "expand",
|
||||||
|
"description": "Expansions",
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["access_list", "owner", "certificate"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "200 response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"examples": {
|
||||||
|
"default": {
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"created_on": "2023-03-30T01:12:23.000Z",
|
||||||
|
"modified_on": "2023-03-30T02:15:40.000Z",
|
||||||
|
"owner_user_id": 1,
|
||||||
|
"domain_names": ["aasdasdad"],
|
||||||
|
"forward_host": "asdasd",
|
||||||
|
"forward_port": 80,
|
||||||
|
"access_list_id": 0,
|
||||||
|
"certificate_id": 0,
|
||||||
|
"ssl_forced": 0,
|
||||||
|
"caching_enabled": 0,
|
||||||
|
"block_exploits": 0,
|
||||||
|
"advanced_config": "sdfsdfsdf",
|
||||||
|
"meta": {
|
||||||
|
"letsencrypt_agree": false,
|
||||||
|
"dns_challenge": false,
|
||||||
|
"nginx_online": false,
|
||||||
|
"nginx_err": "Command failed: /usr/sbin/nginx -t -g \"error_log off;\"\nnginx: [emerg] unknown directive \"sdfsdfsdf\" in /data/nginx/proxy_host/1.conf:37\nnginx: configuration file /etc/nginx/nginx.conf test failed\n"
|
||||||
|
},
|
||||||
|
"allow_websocket_upgrade": 0,
|
||||||
|
"http2_support": 0,
|
||||||
|
"forward_scheme": "http",
|
||||||
|
"enabled": 1,
|
||||||
|
"locations": [],
|
||||||
|
"hsts_enabled": 0,
|
||||||
|
"hsts_subdomains": 0,
|
||||||
|
"owner": {
|
||||||
|
"id": 1,
|
||||||
|
"created_on": "2023-03-30T01:11:50.000Z",
|
||||||
|
"modified_on": "2023-03-30T01:11:50.000Z",
|
||||||
|
"is_deleted": 0,
|
||||||
|
"is_disabled": 0,
|
||||||
|
"email": "admin@example.com",
|
||||||
|
"name": "Administrator",
|
||||||
|
"nickname": "Admin",
|
||||||
|
"avatar": "",
|
||||||
|
"roles": ["admin"]
|
||||||
|
},
|
||||||
|
"access_list": null,
|
||||||
|
"certificate": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"created_on": "2023-03-30T02:11:49.000Z",
|
||||||
|
"modified_on": "2023-03-30T02:11:49.000Z",
|
||||||
|
"owner_user_id": 1,
|
||||||
|
"domain_names": ["test.example.com"],
|
||||||
|
"forward_host": "1.1.1.1",
|
||||||
|
"forward_port": 80,
|
||||||
|
"access_list_id": 0,
|
||||||
|
"certificate_id": 0,
|
||||||
|
"ssl_forced": 0,
|
||||||
|
"caching_enabled": 0,
|
||||||
|
"block_exploits": 0,
|
||||||
|
"advanced_config": "",
|
||||||
|
"meta": {
|
||||||
|
"letsencrypt_agree": false,
|
||||||
|
"dns_challenge": false,
|
||||||
|
"nginx_online": true,
|
||||||
|
"nginx_err": null
|
||||||
|
},
|
||||||
|
"allow_websocket_upgrade": 0,
|
||||||
|
"http2_support": 0,
|
||||||
|
"forward_scheme": "http",
|
||||||
|
"enabled": 1,
|
||||||
|
"locations": [],
|
||||||
|
"hsts_enabled": 0,
|
||||||
|
"hsts_subdomains": 0,
|
||||||
|
"owner": {
|
||||||
|
"id": 1,
|
||||||
|
"created_on": "2023-03-30T01:11:50.000Z",
|
||||||
|
"modified_on": "2023-03-30T01:11:50.000Z",
|
||||||
|
"is_deleted": 0,
|
||||||
|
"is_disabled": 0,
|
||||||
|
"email": "admin@example.com",
|
||||||
|
"name": "Administrator",
|
||||||
|
"nickname": "Admin",
|
||||||
|
"avatar": "",
|
||||||
|
"roles": ["admin"]
|
||||||
|
},
|
||||||
|
"access_list": null,
|
||||||
|
"certificate": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProxyHostsList"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"operationId": "createProxyHost",
|
||||||
|
"summary": "Create a Proxy Host",
|
||||||
|
"tags": ["Proxy Hosts"],
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": ["users"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "body",
|
||||||
|
"name": "proxyhost",
|
||||||
|
"description": "Proxy Host Payload",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProxyHostObject"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "201 response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"examples": {
|
||||||
|
"default": {
|
||||||
|
"value": {
|
||||||
|
"id": 3,
|
||||||
|
"created_on": "2023-03-30T02:31:27.000Z",
|
||||||
|
"modified_on": "2023-03-30T02:31:27.000Z",
|
||||||
|
"owner_user_id": 1,
|
||||||
|
"domain_names": ["test2.example.com"],
|
||||||
|
"forward_host": "1.1.1.1",
|
||||||
|
"forward_port": 80,
|
||||||
|
"access_list_id": 0,
|
||||||
|
"certificate_id": 0,
|
||||||
|
"ssl_forced": 0,
|
||||||
|
"caching_enabled": 0,
|
||||||
|
"block_exploits": 0,
|
||||||
|
"advanced_config": "",
|
||||||
|
"meta": {
|
||||||
|
"letsencrypt_agree": false,
|
||||||
|
"dns_challenge": false
|
||||||
|
},
|
||||||
|
"allow_websocket_upgrade": 0,
|
||||||
|
"http2_support": 0,
|
||||||
|
"forward_scheme": "http",
|
||||||
|
"enabled": 1,
|
||||||
|
"locations": [],
|
||||||
|
"hsts_enabled": 0,
|
||||||
|
"hsts_subdomains": 0,
|
||||||
|
"certificate": null,
|
||||||
|
"owner": {
|
||||||
|
"id": 1,
|
||||||
|
"created_on": "2023-03-30T01:11:50.000Z",
|
||||||
|
"modified_on": "2023-03-30T01:11:50.000Z",
|
||||||
|
"is_deleted": 0,
|
||||||
|
"is_disabled": 0,
|
||||||
|
"email": "admin@example.com",
|
||||||
|
"name": "Administrator",
|
||||||
|
"nickname": "Admin",
|
||||||
|
"avatar": "",
|
||||||
|
"roles": ["admin"]
|
||||||
|
},
|
||||||
|
"access_list": null,
|
||||||
|
"use_default_location": true,
|
||||||
|
"ipv6": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProxyHostObject"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/schema": {
|
"/schema": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "schema",
|
"operationId": "schema",
|
||||||
@@ -55,14 +259,10 @@
|
|||||||
"get": {
|
"get": {
|
||||||
"operationId": "refreshToken",
|
"operationId": "refreshToken",
|
||||||
"summary": "Refresh your access token",
|
"summary": "Refresh your access token",
|
||||||
"tags": [
|
"tags": ["Tokens"],
|
||||||
"Tokens"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["tokens"]
|
||||||
"tokens"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@@ -104,19 +304,14 @@
|
|||||||
"scope": {
|
"scope": {
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": ["user"]
|
||||||
"user"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"secret": {
|
"secret": {
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["identity", "secret"],
|
||||||
"identity",
|
|
||||||
"secret"
|
|
||||||
],
|
|
||||||
"type": "object"
|
"type": "object"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,23 +339,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"summary": "Request a new access token from credentials",
|
"summary": "Request a new access token from credentials",
|
||||||
"tags": [
|
"tags": ["Tokens"]
|
||||||
"Tokens"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/settings": {
|
"/settings": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "getSettings",
|
"operationId": "getSettings",
|
||||||
"summary": "Get all settings",
|
"summary": "Get all settings",
|
||||||
"tags": [
|
"tags": ["Settings"],
|
||||||
"Settings"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["settings"]
|
||||||
"settings"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@@ -194,14 +383,10 @@
|
|||||||
"get": {
|
"get": {
|
||||||
"operationId": "getSetting",
|
"operationId": "getSetting",
|
||||||
"summary": "Get a setting",
|
"summary": "Get a setting",
|
||||||
"tags": [
|
"tags": ["Settings"],
|
||||||
"Settings"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["settings"]
|
||||||
"settings"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -244,14 +429,10 @@
|
|||||||
"put": {
|
"put": {
|
||||||
"operationId": "updateSetting",
|
"operationId": "updateSetting",
|
||||||
"summary": "Update a setting",
|
"summary": "Update a setting",
|
||||||
"tags": [
|
"tags": ["Settings"],
|
||||||
"Settings"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["settings"]
|
||||||
"settings"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -305,14 +486,10 @@
|
|||||||
"get": {
|
"get": {
|
||||||
"operationId": "getUsers",
|
"operationId": "getUsers",
|
||||||
"summary": "Get all users",
|
"summary": "Get all users",
|
||||||
"tags": [
|
"tags": ["Users"],
|
||||||
"Users"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["users"]
|
||||||
"users"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -322,9 +499,7 @@
|
|||||||
"description": "Expansions",
|
"description": "Expansions",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": ["permissions"]
|
||||||
"permissions"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -345,9 +520,7 @@
|
|||||||
"name": "Jamie Curnow",
|
"name": "Jamie Curnow",
|
||||||
"nickname": "James",
|
"nickname": "James",
|
||||||
"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
|
"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
|
||||||
"roles": [
|
"roles": ["admin"]
|
||||||
"admin"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -362,9 +535,7 @@
|
|||||||
"name": "Jamie Curnow",
|
"name": "Jamie Curnow",
|
||||||
"nickname": "James",
|
"nickname": "James",
|
||||||
"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
|
"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
|
||||||
"roles": [
|
"roles": ["admin"],
|
||||||
"admin"
|
|
||||||
],
|
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"visibility": "all",
|
"visibility": "all",
|
||||||
"proxy_hosts": "manage",
|
"proxy_hosts": "manage",
|
||||||
@@ -389,14 +560,10 @@
|
|||||||
"post": {
|
"post": {
|
||||||
"operationId": "createUser",
|
"operationId": "createUser",
|
||||||
"summary": "Create a User",
|
"summary": "Create a User",
|
||||||
"tags": [
|
"tags": ["Users"],
|
||||||
"Users"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["users"]
|
||||||
"users"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -426,9 +593,7 @@
|
|||||||
"name": "Jamie Curnow",
|
"name": "Jamie Curnow",
|
||||||
"nickname": "James",
|
"nickname": "James",
|
||||||
"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
|
"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
|
||||||
"roles": [
|
"roles": ["admin"],
|
||||||
"admin"
|
|
||||||
],
|
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"visibility": "all",
|
"visibility": "all",
|
||||||
"proxy_hosts": "manage",
|
"proxy_hosts": "manage",
|
||||||
@@ -454,14 +619,10 @@
|
|||||||
"get": {
|
"get": {
|
||||||
"operationId": "getUser",
|
"operationId": "getUser",
|
||||||
"summary": "Get a user",
|
"summary": "Get a user",
|
||||||
"tags": [
|
"tags": ["Users"],
|
||||||
"Users"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["users"]
|
||||||
"users"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -501,9 +662,7 @@
|
|||||||
"name": "Jamie Curnow",
|
"name": "Jamie Curnow",
|
||||||
"nickname": "James",
|
"nickname": "James",
|
||||||
"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
|
"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
|
||||||
"roles": [
|
"roles": ["admin"]
|
||||||
"admin"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -518,14 +677,10 @@
|
|||||||
"put": {
|
"put": {
|
||||||
"operationId": "updateUser",
|
"operationId": "updateUser",
|
||||||
"summary": "Update a User",
|
"summary": "Update a User",
|
||||||
"tags": [
|
"tags": ["Users"],
|
||||||
"Users"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["users"]
|
||||||
"users"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -574,9 +729,7 @@
|
|||||||
"name": "Jamie Curnow",
|
"name": "Jamie Curnow",
|
||||||
"nickname": "James",
|
"nickname": "James",
|
||||||
"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
|
"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
|
||||||
"roles": [
|
"roles": ["admin"]
|
||||||
"admin"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -591,14 +744,10 @@
|
|||||||
"delete": {
|
"delete": {
|
||||||
"operationId": "deleteUser",
|
"operationId": "deleteUser",
|
||||||
"summary": "Delete a User",
|
"summary": "Delete a User",
|
||||||
"tags": [
|
"tags": ["Users"],
|
||||||
"Users"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["users"]
|
||||||
"users"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -637,14 +786,10 @@
|
|||||||
"put": {
|
"put": {
|
||||||
"operationId": "updateUserAuth",
|
"operationId": "updateUserAuth",
|
||||||
"summary": "Update a User's Authentication",
|
"summary": "Update a User's Authentication",
|
||||||
"tags": [
|
"tags": ["Users"],
|
||||||
"Users"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["users"]
|
||||||
"users"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -700,14 +845,10 @@
|
|||||||
"put": {
|
"put": {
|
||||||
"operationId": "updateUserPermissions",
|
"operationId": "updateUserPermissions",
|
||||||
"summary": "Update a User's Permissions",
|
"summary": "Update a User's Permissions",
|
||||||
"tags": [
|
"tags": ["Users"],
|
||||||
"Users"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["users"]
|
||||||
"users"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -755,14 +896,10 @@
|
|||||||
"put": {
|
"put": {
|
||||||
"operationId": "loginAsUser",
|
"operationId": "loginAsUser",
|
||||||
"summary": "Login as this user",
|
"summary": "Login as this user",
|
||||||
"tags": [
|
"tags": ["Users"],
|
||||||
"Users"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["users"]
|
||||||
"users"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -797,9 +934,7 @@
|
|||||||
"name": "Jamie Curnow",
|
"name": "Jamie Curnow",
|
||||||
"nickname": "James",
|
"nickname": "James",
|
||||||
"avatar": "//www.gravatar.com/avatar/3c8d73f45fd8763f827b964c76e6032a?default=mm",
|
"avatar": "//www.gravatar.com/avatar/3c8d73f45fd8763f827b964c76e6032a?default=mm",
|
||||||
"roles": [
|
"roles": ["admin"]
|
||||||
"admin"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -807,11 +942,7 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Login object",
|
"description": "Login object",
|
||||||
"required": [
|
"required": ["expires", "token", "user"],
|
||||||
"expires",
|
|
||||||
"token",
|
|
||||||
"user"
|
|
||||||
],
|
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"expires": {
|
"expires": {
|
||||||
@@ -840,14 +971,10 @@
|
|||||||
"get": {
|
"get": {
|
||||||
"operationId": "reportsHosts",
|
"operationId": "reportsHosts",
|
||||||
"summary": "Report on Host Statistics",
|
"summary": "Report on Host Statistics",
|
||||||
"tags": [
|
"tags": ["Reports"],
|
||||||
"Reports"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["reports"]
|
||||||
"reports"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@@ -878,14 +1005,10 @@
|
|||||||
"get": {
|
"get": {
|
||||||
"operationId": "getAuditLog",
|
"operationId": "getAuditLog",
|
||||||
"summary": "Get Audit Log",
|
"summary": "Get Audit Log",
|
||||||
"tags": [
|
"tags": ["Audit Log"],
|
||||||
"Audit Log"
|
|
||||||
],
|
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": [
|
"BearerAuth": ["audit-log"]
|
||||||
"audit-log"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@@ -925,10 +1048,7 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Health object",
|
"description": "Health object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": [
|
"required": ["status", "version"],
|
||||||
"status",
|
|
||||||
"version"
|
|
||||||
],
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"status": {
|
"status": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -944,11 +1064,7 @@
|
|||||||
"revision": 0
|
"revision": 0
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": [
|
"required": ["major", "minor", "revision"],
|
||||||
"major",
|
|
||||||
"minor",
|
|
||||||
"revision"
|
|
||||||
],
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"major": {
|
"major": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
@@ -969,10 +1085,7 @@
|
|||||||
"TokenObject": {
|
"TokenObject": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Token object",
|
"description": "Token object",
|
||||||
"required": [
|
"required": ["expires", "token"],
|
||||||
"expires",
|
|
||||||
"token"
|
|
||||||
],
|
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"expires": {
|
"expires": {
|
||||||
@@ -988,16 +1101,147 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ProxyHostObject": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Proxy Host object",
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"created_on",
|
||||||
|
"modified_on",
|
||||||
|
"owner_user_id",
|
||||||
|
"domain_names",
|
||||||
|
"forward_host",
|
||||||
|
"forward_port",
|
||||||
|
"access_list_id",
|
||||||
|
"certificate_id",
|
||||||
|
"ssl_forced",
|
||||||
|
"caching_enabled",
|
||||||
|
"block_exploits",
|
||||||
|
"advanced_config",
|
||||||
|
"meta",
|
||||||
|
"allow_websocket_upgrade",
|
||||||
|
"http2_support",
|
||||||
|
"forward_scheme",
|
||||||
|
"enabled",
|
||||||
|
"locations",
|
||||||
|
"hsts_enabled",
|
||||||
|
"hsts_subdomains",
|
||||||
|
"certificate",
|
||||||
|
"use_default_location",
|
||||||
|
"ipv6"
|
||||||
|
],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Proxy Host ID",
|
||||||
|
"minimum": 1,
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"created_on": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Created Date",
|
||||||
|
"example": "2020-01-30T09:36:08.000Z"
|
||||||
|
},
|
||||||
|
"modified_on": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Modified Date",
|
||||||
|
"example": "2020-01-30T09:41:04.000Z"
|
||||||
|
},
|
||||||
|
"owner_user_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"domain_names": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"forward_host": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"forward_port": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
|
"access_list_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"certificate_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"ssl_forced": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"caching_enabled": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"block_exploits": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"advanced_config": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"allow_websocket_upgrade": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"http2_support": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"forward_scheme": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"locations": {
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"hsts_enabled": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"hsts_subdomains": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"certificate": {
|
||||||
|
"type": "object",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"type": "object",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"access_list": {
|
||||||
|
"type": "object",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"use_default_location": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"ipv6": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ProxyHostsList": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Proxyn Hosts list",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/ProxyHostObject"
|
||||||
|
}
|
||||||
|
},
|
||||||
"SettingObject": {
|
"SettingObject": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Setting object",
|
"description": "Setting object",
|
||||||
"required": [
|
"required": ["id", "name", "description", "value", "meta"],
|
||||||
"id",
|
|
||||||
"name",
|
|
||||||
"description",
|
|
||||||
"value",
|
|
||||||
"meta"
|
|
||||||
],
|
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
@@ -1057,17 +1301,7 @@
|
|||||||
"UserObject": {
|
"UserObject": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "User object",
|
"description": "User object",
|
||||||
"required": [
|
"required": ["id", "created_on", "modified_on", "is_disabled", "email", "name", "nickname", "avatar", "roles"],
|
||||||
"id",
|
|
||||||
"created_on",
|
|
||||||
"modified_on",
|
|
||||||
"is_disabled",
|
|
||||||
"email",
|
|
||||||
"name",
|
|
||||||
"nickname",
|
|
||||||
"avatar",
|
|
||||||
"roles"
|
|
||||||
],
|
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
@@ -1117,9 +1351,7 @@
|
|||||||
},
|
},
|
||||||
"roles": {
|
"roles": {
|
||||||
"description": "Roles applied",
|
"description": "Roles applied",
|
||||||
"example": [
|
"example": ["admin"],
|
||||||
"admin"
|
|
||||||
],
|
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -1137,10 +1369,7 @@
|
|||||||
"AuthObject": {
|
"AuthObject": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Authentication Object",
|
"description": "Authentication Object",
|
||||||
"required": [
|
"required": ["type", "secret"],
|
||||||
"type",
|
|
||||||
"secret"
|
|
||||||
],
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -1167,64 +1396,37 @@
|
|||||||
"visibility": {
|
"visibility": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Visibility Type",
|
"description": "Visibility Type",
|
||||||
"enum": [
|
"enum": ["all", "user"]
|
||||||
"all",
|
|
||||||
"user"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"access_lists": {
|
"access_lists": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Access Lists Permissions",
|
"description": "Access Lists Permissions",
|
||||||
"enum": [
|
"enum": ["hidden", "view", "manage"]
|
||||||
"hidden",
|
|
||||||
"view",
|
|
||||||
"manage"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"dead_hosts": {
|
"dead_hosts": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "404 Hosts Permissions",
|
"description": "404 Hosts Permissions",
|
||||||
"enum": [
|
"enum": ["hidden", "view", "manage"]
|
||||||
"hidden",
|
|
||||||
"view",
|
|
||||||
"manage"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"proxy_hosts": {
|
"proxy_hosts": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Proxy Hosts Permissions",
|
"description": "Proxy Hosts Permissions",
|
||||||
"enum": [
|
"enum": ["hidden", "view", "manage"]
|
||||||
"hidden",
|
|
||||||
"view",
|
|
||||||
"manage"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"redirection_hosts": {
|
"redirection_hosts": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Redirection Permissions",
|
"description": "Redirection Permissions",
|
||||||
"enum": [
|
"enum": ["hidden", "view", "manage"]
|
||||||
"hidden",
|
|
||||||
"view",
|
|
||||||
"manage"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"streams": {
|
"streams": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Streams Permissions",
|
"description": "Streams Permissions",
|
||||||
"enum": [
|
"enum": ["hidden", "view", "manage"]
|
||||||
"hidden",
|
|
||||||
"view",
|
|
||||||
"manage"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"certificates": {
|
"certificates": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Certificates Permissions",
|
"description": "Certificates Permissions",
|
||||||
"enum": [
|
"enum": ["hidden", "view", "manage"]
|
||||||
"hidden",
|
|
||||||
"view",
|
|
||||||
"manage"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -3,9 +3,6 @@
|
|||||||
const logger = require('./logger').global;
|
const logger = require('./logger').global;
|
||||||
|
|
||||||
async function appStart () {
|
async function appStart () {
|
||||||
// Create config file db settings if environment variables have been set
|
|
||||||
await createDbConfigFromEnvironment();
|
|
||||||
|
|
||||||
const migrate = require('./migrate');
|
const migrate = require('./migrate');
|
||||||
const setup = require('./setup');
|
const setup = require('./setup');
|
||||||
const app = require('./app');
|
const app = require('./app');
|
||||||
@@ -42,90 +39,6 @@ async function appStart () {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createDbConfigFromEnvironment() {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const envMysqlHost = process.env.DB_MYSQL_HOST || null;
|
|
||||||
const envMysqlPort = process.env.DB_MYSQL_PORT || null;
|
|
||||||
const envMysqlUser = process.env.DB_MYSQL_USER || null;
|
|
||||||
const envMysqlName = process.env.DB_MYSQL_NAME || null;
|
|
||||||
let envSqliteFile = process.env.DB_SQLITE_FILE || null;
|
|
||||||
|
|
||||||
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) && !envSqliteFile){
|
|
||||||
envSqliteFile = '/data/database.sqlite';
|
|
||||||
logger.info(`No valid environment variables for database provided, using default SQLite file '${envSqliteFile}'`);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 knex configuration from environment variables');
|
|
||||||
configData.database = newConfig;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
const newConfig = {
|
|
||||||
fromEnv: true,
|
|
||||||
engine: 'knex-native',
|
|
||||||
knex: {
|
|
||||||
client: 'sqlite3',
|
|
||||||
connection: {
|
|
||||||
filename: envSqliteFile
|
|
||||||
},
|
|
||||||
useNullAsDefault: true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (JSON.stringify(configData.database) === JSON.stringify(newConfig)) {
|
|
||||||
// Config is unchanged, skip overwrite
|
|
||||||
resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info('Generating SQLite knex configuration');
|
|
||||||
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.debug('Wrote db configuration to config file: ' + filename);
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
appStart();
|
appStart();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@@ -4,6 +4,7 @@ const https = require('https');
|
|||||||
const tempWrite = require('temp-write');
|
const tempWrite = require('temp-write');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
const logger = require('../logger').ssl;
|
const logger = require('../logger').ssl;
|
||||||
|
const config = require('../lib/config');
|
||||||
const error = require('../lib/error');
|
const error = require('../lib/error');
|
||||||
const utils = require('../lib/utils');
|
const utils = require('../lib/utils');
|
||||||
const certificateModel = require('../models/certificate');
|
const certificateModel = require('../models/certificate');
|
||||||
@@ -14,7 +15,8 @@ const internalHost = require('./host');
|
|||||||
const archiver = require('archiver');
|
const archiver = require('archiver');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { isArray } = require('lodash');
|
const { isArray } = require('lodash');
|
||||||
const certbotConfig = '/data/tls/certbot/config.ini';
|
|
||||||
|
const letsencryptConfig = '/data/tls/certbot/config.ini';
|
||||||
const certbotCommand = 'certbot --config-dir /data/tls/certbot';
|
const certbotCommand = 'certbot --config-dir /data/tls/certbot';
|
||||||
|
|
||||||
function omissions() {
|
function omissions() {
|
||||||
@@ -874,7 +876,7 @@ const internalCertificate = {
|
|||||||
// Escape single quotes and backslashes
|
// Escape single quotes and backslashes
|
||||||
const escapedCredentials = certificate.meta.dns_provider_credentials.replaceAll('\'', '\\\'').replaceAll('\\', '\\\\');
|
const escapedCredentials = certificate.meta.dns_provider_credentials.replaceAll('\'', '\\\'').replaceAll('\\', '\\\\');
|
||||||
const credentialsCmd = 'mkdir -p /data/tls/certbot/credentials 2> /dev/null; echo \'' + escapedCredentials + '\' > \'' + credentialsLocation + '\' && chmod 600 \'' + credentialsLocation + '\'';
|
const credentialsCmd = 'mkdir -p /data/tls/certbot/credentials 2> /dev/null; echo \'' + escapedCredentials + '\' > \'' + credentialsLocation + '\' && chmod 600 \'' + credentialsLocation + '\'';
|
||||||
let prepareCmd = 'pip install ' + dns_plugin.package_name + (dns_plugin.version_requirement || '') + ' ' + dns_plugin.dependencies;
|
const prepareCmd = 'pip install --no-cache-dir ' + dns_plugin.package_name + (dns_plugin.version_requirement || '') + ' ' + dns_plugin.dependencies;
|
||||||
|
|
||||||
// Whether the plugin has a --<name>-credentials argument
|
// Whether the plugin has a --<name>-credentials argument
|
||||||
const hasConfigArg = certificate.meta.dns_provider !== 'route53';
|
const hasConfigArg = certificate.meta.dns_provider !== 'route53';
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const logger = require('../logger').nginx;
|
const logger = require('../logger').nginx;
|
||||||
|
const config = require('../lib/config');
|
||||||
const utils = require('../lib/utils');
|
const utils = require('../lib/utils');
|
||||||
const error = require('../lib/error');
|
const error = require('../lib/error');
|
||||||
const debug_mode = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG;
|
|
||||||
|
|
||||||
const internalNginx = {
|
const internalNginx = {
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ const internalNginx = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (debug_mode) {
|
if (config.debug()) {
|
||||||
logger.error('Nginx test failed:', valid_lines.join('\n'));
|
logger.error('Nginx test failed:', valid_lines.join('\n'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ const internalNginx = {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
test: () => {
|
test: () => {
|
||||||
if (debug_mode) {
|
if (config.debug()) {
|
||||||
logger.info('Testing Nginx configuration');
|
logger.info('Testing Nginx configuration');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +184,7 @@ const internalNginx = {
|
|||||||
generateConfig: (host_type, host) => {
|
generateConfig: (host_type, host) => {
|
||||||
const nice_host_type = internalNginx.getFileFriendlyHostType(host_type);
|
const nice_host_type = internalNginx.getFileFriendlyHostType(host_type);
|
||||||
|
|
||||||
if (debug_mode) {
|
if (config.debug()) {
|
||||||
logger.info('Generating ' + nice_host_type + ' Config:', JSON.stringify(host, null, 2));
|
logger.info('Generating ' + nice_host_type + ' Config:', JSON.stringify(host, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,7 +239,7 @@ const internalNginx = {
|
|||||||
.then((config_text) => {
|
.then((config_text) => {
|
||||||
fs.writeFileSync(filename, config_text, {encoding: 'utf8'});
|
fs.writeFileSync(filename, config_text, {encoding: 'utf8'});
|
||||||
|
|
||||||
if (debug_mode) {
|
if (config.debug()) {
|
||||||
logger.success('Wrote config:', filename, config_text);
|
logger.success('Wrote config:', filename, config_text);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,7 +249,7 @@ const internalNginx = {
|
|||||||
resolve(true);
|
resolve(true);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (debug_mode) {
|
if (config.debug()) {
|
||||||
logger.warn('Could not write ' + filename + ':', err.message);
|
logger.warn('Could not write ' + filename + ':', err.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,7 +268,7 @@ const internalNginx = {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
generateLetsEncryptRequestConfig: (certificate) => {
|
generateLetsEncryptRequestConfig: (certificate) => {
|
||||||
if (debug_mode) {
|
if (config.debug()) {
|
||||||
logger.info('Generating certbot Request Config:', certificate);
|
logger.info('Generating certbot Request Config:', certificate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,14 +292,14 @@ const internalNginx = {
|
|||||||
.then((config_text) => {
|
.then((config_text) => {
|
||||||
fs.writeFileSync(filename, config_text, {encoding: 'utf8'});
|
fs.writeFileSync(filename, config_text, {encoding: 'utf8'});
|
||||||
|
|
||||||
if (debug_mode) {
|
if (config.debug()) {
|
||||||
logger.success('Wrote config:', filename, config_text);
|
logger.success('Wrote config:', filename, config_text);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(true);
|
resolve(true);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (debug_mode) {
|
if (config.debug()) {
|
||||||
logger.warn('Could not write ' + filename + ':', err.message);
|
logger.warn('Could not write ' + filename + ':', err.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,8 +416,8 @@ const internalNginx = {
|
|||||||
* @param {string} config
|
* @param {string} config
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
advancedConfigHasDefaultLocation: function (config) {
|
advancedConfigHasDefaultLocation: function (cfg) {
|
||||||
return !!config.match(/^(?:.*;)?\s*?location\s*?\/\s*?{/im);
|
return !!cfg.match(/^(?:.*;)?\s*?location\s*?\/\s*?{/im);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
184
backend/lib/config.js
Normal file
184
backend/lib/config.js
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const NodeRSA = require('node-rsa');
|
||||||
|
const logger = require('../logger').global;
|
||||||
|
|
||||||
|
const keysFile = '/data/keys.json';
|
||||||
|
|
||||||
|
let instance = null;
|
||||||
|
|
||||||
|
// 1. Load from config file first (not recommended anymore)
|
||||||
|
// 2. Use config env variables next
|
||||||
|
const configure = () => {
|
||||||
|
const filename = (process.env.NODE_CONFIG_DIR || './config') + '/' + (process.env.NODE_ENV || 'default') + '.json';
|
||||||
|
if (fs.existsSync(filename)) {
|
||||||
|
let configData;
|
||||||
|
try {
|
||||||
|
configData = require(filename);
|
||||||
|
} catch (err) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configData && configData.database) {
|
||||||
|
logger.info(`Using configuration from file: ${filename}`);
|
||||||
|
instance = configData;
|
||||||
|
instance.keys = getKeys();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const envMysqlHost = process.env.DB_MYSQL_HOST || null;
|
||||||
|
const envMysqlUser = process.env.DB_MYSQL_USER || null;
|
||||||
|
const envMysqlName = process.env.DB_MYSQL_NAME || null;
|
||||||
|
if (envMysqlHost && envMysqlUser && envMysqlName) {
|
||||||
|
// we have enough mysql creds to go with mysql
|
||||||
|
logger.info('Using MySQL configuration');
|
||||||
|
instance = {
|
||||||
|
database: {
|
||||||
|
engine: 'mysql',
|
||||||
|
host: envMysqlHost,
|
||||||
|
port: process.env.DB_MYSQL_PORT || 3306,
|
||||||
|
user: envMysqlUser,
|
||||||
|
password: process.env.DB_MYSQL_PASSWORD,
|
||||||
|
name: envMysqlName,
|
||||||
|
},
|
||||||
|
keys: getKeys(),
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const envSqliteFile = process.env.DB_SQLITE_FILE || '/data/database.sqlite';
|
||||||
|
logger.info(`Using Sqlite: ${envSqliteFile}`);
|
||||||
|
instance = {
|
||||||
|
database: {
|
||||||
|
engine: 'knex-native',
|
||||||
|
knex: {
|
||||||
|
client: 'sqlite3',
|
||||||
|
connection: {
|
||||||
|
filename: envSqliteFile
|
||||||
|
},
|
||||||
|
useNullAsDefault: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
keys: getKeys(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getKeys = () => {
|
||||||
|
// Get keys from file
|
||||||
|
if (!fs.existsSync(keysFile)) {
|
||||||
|
generateKeys();
|
||||||
|
} else if (process.env.DEBUG) {
|
||||||
|
logger.info('Keys file exists OK');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return require(keysFile);
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('Could not read JWT key pair from config file: ' + keysFile, err);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateKeys = () => {
|
||||||
|
logger.info('Creating a new JWT key pair...');
|
||||||
|
// Now create the keys and save them in the config.
|
||||||
|
const key = new NodeRSA({ b: 2048 });
|
||||||
|
key.generateKeyPair();
|
||||||
|
|
||||||
|
const keys = {
|
||||||
|
key: key.exportKey('private').toString(),
|
||||||
|
pub: key.exportKey('public').toString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Write keys config
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(keysFile, JSON.stringify(keys, null, 2));
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('Could not write JWT key pair to config file: ' + keysFile + ': ' . err.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
logger.info('Wrote JWT key pair to config file: ' + keysFile);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} key ie: 'database' or 'database.engine'
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
has: function(key) {
|
||||||
|
instance === null && configure();
|
||||||
|
const keys = key.split('.');
|
||||||
|
let level = instance;
|
||||||
|
let has = true;
|
||||||
|
keys.forEach((keyItem) =>{
|
||||||
|
if (typeof level[keyItem] === 'undefined') {
|
||||||
|
has = false;
|
||||||
|
} else {
|
||||||
|
level = level[keyItem];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return has;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a specific key from the top level
|
||||||
|
*
|
||||||
|
* @param {string} key
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
get: function (key) {
|
||||||
|
instance === null && configure();
|
||||||
|
if (key && typeof instance[key] !== 'undefined') {
|
||||||
|
return instance[key];
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this a sqlite configuration?
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
isSqlite: function () {
|
||||||
|
instance === null && configure();
|
||||||
|
return instance.database.knex && instance.database.knex.client === 'sqlite3';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Are we running in debug mdoe?
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
debug: function () {
|
||||||
|
return !!process.env.DEBUG;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a public key
|
||||||
|
*
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
getPublicKey: function () {
|
||||||
|
instance === null && configure();
|
||||||
|
return instance.keys.pub;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a private key
|
||||||
|
*
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
getPrivateKey: function () {
|
||||||
|
instance === null && configure();
|
||||||
|
return instance.keys.key;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
useLetsencryptStaging: function () {
|
||||||
|
return !!process.env.LE_STAGING;
|
||||||
|
}
|
||||||
|
};
|
@@ -5,7 +5,7 @@ const definitions = require('../../schema/definitions.json');
|
|||||||
RegExp.prototype.toJSON = RegExp.prototype.toString;
|
RegExp.prototype.toJSON = RegExp.prototype.toString;
|
||||||
|
|
||||||
const ajv = require('ajv')({
|
const ajv = require('ajv')({
|
||||||
verbose: true, //process.env.NODE_ENV === 'development',
|
verbose: true,
|
||||||
allErrors: true,
|
allErrors: true,
|
||||||
format: 'full', // strict regexes for format checks
|
format: 'full', // strict regexes for format checks
|
||||||
coerceTypes: true,
|
coerceTypes: true,
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
const db = require('../db');
|
const db = require('../db');
|
||||||
const config = require('config');
|
const config = require('../lib/config');
|
||||||
const Model = require('objection').Model;
|
const Model = require('objection').Model;
|
||||||
|
|
||||||
Model.knex(db);
|
Model.knex(db);
|
||||||
|
|
||||||
module.exports = function () {
|
module.exports = function () {
|
||||||
if (config.database.knex && config.database.knex.client === 'sqlite3') {
|
if (config.isSqlite()) {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
return Model.raw("datetime('now','localtime')");
|
return Model.raw("datetime('now','localtime')");
|
||||||
}
|
}
|
||||||
|
@@ -6,44 +6,36 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const jwt = require('jsonwebtoken');
|
const jwt = require('jsonwebtoken');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
|
const config = require('../lib/config');
|
||||||
const error = require('../lib/error');
|
const error = require('../lib/error');
|
||||||
|
const logger = require('../logger').global;
|
||||||
const ALGO = 'RS256';
|
const ALGO = 'RS256';
|
||||||
|
|
||||||
let public_key = null;
|
|
||||||
let private_key = null;
|
|
||||||
|
|
||||||
function checkJWTKeyPair() {
|
|
||||||
if (!public_key || !private_key) {
|
|
||||||
let config = require('config');
|
|
||||||
public_key = config.get('jwt.pub');
|
|
||||||
private_key = config.get('jwt.key');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = function () {
|
module.exports = function () {
|
||||||
|
|
||||||
let token_data = {};
|
let token_data = {};
|
||||||
|
|
||||||
let self = {
|
const self = {
|
||||||
/**
|
/**
|
||||||
* @param {Object} payload
|
* @param {Object} payload
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
create: (payload) => {
|
create: (payload) => {
|
||||||
|
if (!config.getPrivateKey()) {
|
||||||
|
logger.error('Private key is empty!');
|
||||||
|
}
|
||||||
// sign with RSA SHA256
|
// sign with RSA SHA256
|
||||||
let options = {
|
const options = {
|
||||||
algorithm: ALGO,
|
algorithm: ALGO,
|
||||||
expiresIn: payload.expiresIn || '1d'
|
expiresIn: payload.expiresIn || '1d'
|
||||||
};
|
};
|
||||||
|
|
||||||
payload.jti = crypto.randomBytes(12)
|
payload.jti = crypto.randomBytes(12)
|
||||||
.toString('base64')
|
.toString('base64')
|
||||||
.substr(-8);
|
.substring(-8);
|
||||||
|
|
||||||
checkJWTKeyPair();
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
jwt.sign(payload, private_key, options, (err, token) => {
|
jwt.sign(payload, config.getPrivateKey(), options, (err, token) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
} else {
|
} else {
|
||||||
@@ -62,13 +54,15 @@ module.exports = function () {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
load: function (token) {
|
load: function (token) {
|
||||||
|
if (!config.getPublicKey()) {
|
||||||
|
logger.error('Public key is empty!');
|
||||||
|
}
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
checkJWTKeyPair();
|
|
||||||
try {
|
try {
|
||||||
if (!token || token === null || token === 'null') {
|
if (!token || token === null || token === 'null') {
|
||||||
reject(new error.AuthError('Empty token'));
|
reject(new error.AuthError('Empty token'));
|
||||||
} else {
|
} else {
|
||||||
jwt.verify(token, public_key, {ignoreExpiration: false, algorithms: [ALGO]}, (err, result) => {
|
jwt.verify(token, config.getPublicKey(), {ignoreExpiration: false, algorithms: [ALGO]}, (err, result) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|
||||||
if (err.name === 'TokenExpiredError') {
|
if (err.name === 'TokenExpiredError') {
|
||||||
@@ -132,7 +126,7 @@ module.exports = function () {
|
|||||||
* @returns {Integer}
|
* @returns {Integer}
|
||||||
*/
|
*/
|
||||||
getUserId: (default_value) => {
|
getUserId: (default_value) => {
|
||||||
let attrs = self.get('attrs');
|
const attrs = self.get('attrs');
|
||||||
if (attrs && typeof attrs.id !== 'undefined' && attrs.id) {
|
if (attrs && typeof attrs.id !== 'undefined' && attrs.id) {
|
||||||
return attrs.id;
|
return attrs.id;
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,6 @@
|
|||||||
"bcrypt": "5.1.0",
|
"bcrypt": "5.1.0",
|
||||||
"body-parser": "1.20.2",
|
"body-parser": "1.20.2",
|
||||||
"compression": "1.7.4",
|
"compression": "1.7.4",
|
||||||
"config": "3.3.9",
|
|
||||||
"express": "4.18.2",
|
"express": "4.18.2",
|
||||||
"express-fileupload": "1.4.0",
|
"express-fileupload": "1.4.0",
|
||||||
"gravatar": "1.8.2",
|
"gravatar": "1.8.2",
|
||||||
@@ -22,7 +21,6 @@
|
|||||||
"moment": "2.29.4",
|
"moment": "2.29.4",
|
||||||
"mysql": "2.18.1",
|
"mysql": "2.18.1",
|
||||||
"node-rsa": "1.1.1",
|
"node-rsa": "1.1.1",
|
||||||
"nodemon": "2.0.22",
|
|
||||||
"objection": "3.0.1",
|
"objection": "3.0.1",
|
||||||
"path": "0.12.7",
|
"path": "0.12.7",
|
||||||
"signale": "1.4.0",
|
"signale": "1.4.0",
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
const fs = require('fs');
|
const config = require('./lib/config');
|
||||||
const NodeRSA = require('node-rsa');
|
|
||||||
const config = require('config');
|
|
||||||
const logger = require('./logger').setup;
|
const logger = require('./logger').setup;
|
||||||
const certificateModel = require('./models/certificate');
|
const certificateModel = require('./models/certificate');
|
||||||
const userModel = require('./models/user');
|
const userModel = require('./models/user');
|
||||||
@@ -9,62 +7,6 @@ const utils = require('./lib/utils');
|
|||||||
const authModel = require('./models/auth');
|
const authModel = require('./models/auth');
|
||||||
const settingModel = require('./models/setting');
|
const settingModel = require('./models/setting');
|
||||||
const dns_plugins = require('./global/certbot-dns-plugins');
|
const dns_plugins = require('./global/certbot-dns-plugins');
|
||||||
const debug_mode = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new JWT RSA Keypair if not alread set on the config
|
|
||||||
*
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
const setupJwt = () => {
|
|
||||||
return new Promise((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...');
|
|
||||||
|
|
||||||
// jwt keys are not configured properly
|
|
||||||
const filename = config.util.getEnv('NODE_CONFIG_DIR') + '/' + (config.util.getEnv('NODE_ENV') || 'default') + '.json';
|
|
||||||
let config_data = {};
|
|
||||||
|
|
||||||
try {
|
|
||||||
config_data = require(filename);
|
|
||||||
} catch (err) {
|
|
||||||
// do nothing
|
|
||||||
if (debug_mode) {
|
|
||||||
logger.debug(filename + ' config file could not be required');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now create the keys and save them in the config.
|
|
||||||
let key = new NodeRSA({ b: 2048 });
|
|
||||||
key.generateKeyPair();
|
|
||||||
|
|
||||||
config_data.jwt = {
|
|
||||||
key: key.exportKey('private').toString(),
|
|
||||||
pub: key.exportKey('public').toString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Write config
|
|
||||||
fs.writeFile(filename, JSON.stringify(config_data, null, 2), (err) => {
|
|
||||||
if (err) {
|
|
||||||
logger.error('Could not write JWT key pair to config file: ' + filename);
|
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
logger.info('Wrote JWT key pair to config file: ' + filename);
|
|
||||||
delete require.cache[require.resolve('config')];
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// JWT key pair exists
|
|
||||||
if (debug_mode) {
|
|
||||||
logger.debug('JWT Keypair already exists');
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a default admin users if one doesn't already exist in the database
|
* Creates a default admin users if one doesn't already exist in the database
|
||||||
@@ -119,8 +61,8 @@ const setupDefaultUser = () => {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
logger.info('Initial admin setup completed');
|
logger.info('Initial admin setup completed');
|
||||||
});
|
});
|
||||||
} else if (debug_mode) {
|
} else if (config.debug()) {
|
||||||
logger.debug('Admin user setup not required');
|
logger.info('Admin user setup not required');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -151,8 +93,8 @@ const setupDefaultSettings = () => {
|
|||||||
logger.info('Default settings added');
|
logger.info('Default settings added');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (debug_mode) {
|
if (config.debug()) {
|
||||||
logger.debug('Default setting setup not required');
|
logger.info('Default setting setup not required');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -189,7 +131,7 @@ const setupCertbotPlugins = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (plugins.length) {
|
if (plugins.length) {
|
||||||
const install_cmd = 'pip install ' + plugins.join(' ');
|
const install_cmd = 'pip install --no-cache-dir ' + plugins.join(' ');
|
||||||
promises.push(utils.exec(install_cmd));
|
promises.push(utils.exec(install_cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,8 +146,7 @@ const setupCertbotPlugins = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
module.exports = function () {
|
module.exports = function () {
|
||||||
return setupJwt()
|
return setupDefaultUser()
|
||||||
.then(setupDefaultUser)
|
|
||||||
.then(setupDefaultSettings)
|
.then(setupDefaultSettings)
|
||||||
.then(setupCertbotPlugins);
|
.then(setupCertbotPlugins);
|
||||||
};
|
};
|
||||||
|
42
docker/rootfs/bin/common.sh
Normal file
42
docker/rootfs/bin/common.sh
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
CYAN='\E[1;36m'
|
||||||
|
BLUE='\E[1;34m'
|
||||||
|
YELLOW='\E[1;33m'
|
||||||
|
RED='\E[1;31m'
|
||||||
|
RESET='\E[0m'
|
||||||
|
export CYAN BLUE YELLOW RED RESET
|
||||||
|
|
||||||
|
PUID=${PUID:-0}
|
||||||
|
PGID=${PGID:-0}
|
||||||
|
|
||||||
|
if [[ "$PUID" -ne '0' ]] && [ "$PGID" = '0' ]; then
|
||||||
|
# set group id to same as user id,
|
||||||
|
# the user probably forgot to specify the group id and
|
||||||
|
# it would be rediculous to intentionally use the root group
|
||||||
|
# for a non-root user
|
||||||
|
PGID=$PUID
|
||||||
|
fi
|
||||||
|
|
||||||
|
export PUID PGID
|
||||||
|
|
||||||
|
log_info () {
|
||||||
|
echo -e "${BLUE}❯ ${CYAN}$1${RESET}"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error () {
|
||||||
|
echo -e "${RED}❯ $1${RESET}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# The `run` file will only execute 1 line so this helps keep things
|
||||||
|
# logically separated
|
||||||
|
|
||||||
|
log_fatal () {
|
||||||
|
echo -e "${RED}--------------------------------------${RESET}"
|
||||||
|
echo -e "${RED}ERROR: $1${RESET}"
|
||||||
|
echo -e "${RED}--------------------------------------${RESET}"
|
||||||
|
/run/s6/basedir/bin/halt
|
||||||
|
exit 1
|
||||||
|
}
|
18
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/00-all.sh
Executable file
18
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/00-all.sh
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/command/with-contenv bash
|
||||||
|
# shellcheck shell=bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
. /bin/common.sh
|
||||||
|
|
||||||
|
if [ "$(id -u)" != "0" ]; then
|
||||||
|
log_fatal "This docker container must be run as root, do not specify a user.\nYou can specify PUID and PGID env vars to run processes as that user and group after initialization."
|
||||||
|
fi
|
||||||
|
|
||||||
|
. /etc/s6-overlay/s6-rc.d/prepare/10-npmuser.sh
|
||||||
|
. /etc/s6-overlay/s6-rc.d/prepare/20-paths.sh
|
||||||
|
. /etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh
|
||||||
|
. /etc/s6-overlay/s6-rc.d/prepare/40-dynamic.sh
|
||||||
|
. /etc/s6-overlay/s6-rc.d/prepare/50-ipv6.sh
|
||||||
|
. /etc/s6-overlay/s6-rc.d/prepare/60-secrets.sh
|
||||||
|
. /etc/s6-overlay/s6-rc.d/prepare/90-banner.sh
|
20
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/10-npmuser.sh
Executable file
20
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/10-npmuser.sh
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/command/with-contenv bash
|
||||||
|
# shellcheck shell=bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
log_info 'Configuring npmuser ...'
|
||||||
|
|
||||||
|
if id -u npmuser; then
|
||||||
|
# user already exists
|
||||||
|
usermod -u "$PUID" npmuser || exit 1
|
||||||
|
else
|
||||||
|
# Add npmuser user
|
||||||
|
useradd -o -u "$PUID" -U -d /tmp/npmuserhome -s /bin/false npmuser || exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
usermod -G "$PGID" npmuser || exit 1
|
||||||
|
groupmod -o -g "$PGID" npmuser || exit 1
|
||||||
|
# Home for npmuser
|
||||||
|
mkdir -p /tmp/npmuserhome
|
||||||
|
chown -R "$PUID:$PGID" /tmp/npmuserhome
|
41
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/20-paths.sh
Executable file
41
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/20-paths.sh
Executable file
@@ -0,0 +1,41 @@
|
|||||||
|
#!/command/with-contenv bash
|
||||||
|
# shellcheck shell=bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
log_info 'Checking paths ...'
|
||||||
|
|
||||||
|
# Ensure /data is mounted
|
||||||
|
if [ ! -d '/data' ]; then
|
||||||
|
log_fatal '/data is not mounted! Check your docker configuration.'
|
||||||
|
fi
|
||||||
|
# Ensure /etc/letsencrypt is mounted
|
||||||
|
if [ ! -d '/etc/letsencrypt' ]; then
|
||||||
|
log_fatal '/etc/letsencrypt is not mounted! Check your docker configuration.'
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create required folders
|
||||||
|
mkdir -p \
|
||||||
|
/data/nginx \
|
||||||
|
/data/custom_ssl \
|
||||||
|
/data/logs \
|
||||||
|
/data/access \
|
||||||
|
/data/nginx/default_host \
|
||||||
|
/data/nginx/default_www \
|
||||||
|
/data/nginx/proxy_host \
|
||||||
|
/data/nginx/redirection_host \
|
||||||
|
/data/nginx/stream \
|
||||||
|
/data/nginx/dead_host \
|
||||||
|
/data/nginx/temp \
|
||||||
|
/data/letsencrypt-acme-challenge \
|
||||||
|
/run/nginx \
|
||||||
|
/tmp/nginx/body \
|
||||||
|
/var/log/nginx \
|
||||||
|
/var/lib/nginx/cache/public \
|
||||||
|
/var/lib/nginx/cache/private \
|
||||||
|
/var/cache/nginx/proxy_temp
|
||||||
|
|
||||||
|
touch /var/log/nginx/error.log || true
|
||||||
|
chmod 777 /var/log/nginx/error.log || true
|
||||||
|
chmod -R 777 /var/cache/nginx || true
|
||||||
|
chmod 644 /etc/logrotate.d/nginx-proxy-manager
|
24
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh
Executable file
24
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh
Executable file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/command/with-contenv bash
|
||||||
|
# shellcheck shell=bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
log_info 'Setting ownership ...'
|
||||||
|
|
||||||
|
# root
|
||||||
|
chown root /tmp/nginx
|
||||||
|
|
||||||
|
# npmuser
|
||||||
|
chown -R "$PUID:$PGID" /data \
|
||||||
|
/etc/letsencrypt \
|
||||||
|
/run/nginx \
|
||||||
|
/tmp/nginx \
|
||||||
|
/var/cache/nginx \
|
||||||
|
/var/lib/logrotate \
|
||||||
|
/var/lib/nginx \
|
||||||
|
/var/log/nginx
|
||||||
|
|
||||||
|
# Don't chown entire /etc/nginx folder as this causes crashes on some systems
|
||||||
|
chown -R "$PUID:$PGID" /etc/nginx/nginx \
|
||||||
|
/etc/nginx/nginx.conf \
|
||||||
|
/etc/nginx/conf.d
|
17
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/40-dynamic.sh
Executable file
17
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/40-dynamic.sh
Executable file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/command/with-contenv bash
|
||||||
|
# shellcheck shell=bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
log_info 'Dynamic resolvers ...'
|
||||||
|
|
||||||
|
DISABLE_IPV6=$(echo "${DISABLE_IPV6:-}" | tr '[:upper:]' '[:lower:]')
|
||||||
|
|
||||||
|
# Dynamically generate resolvers file, if resolver is IPv6, enclose in `[]`
|
||||||
|
# thanks @tfmm
|
||||||
|
if [ "$DISABLE_IPV6" == "true" ] || [ "$DISABLE_IPV6" == "on" ] || [ "$DISABLE_IPV6" == "1" ] || [ "$DISABLE_IPV6" == "yes" ];
|
||||||
|
then
|
||||||
|
echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf) ipv6=off valid=10s;" > /etc/nginx/conf.d/include/resolvers.conf
|
||||||
|
else
|
||||||
|
echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf) valid=10s;" > /etc/nginx/conf.d/include/resolvers.conf
|
||||||
|
fi
|
36
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/50-ipv6.sh
Executable file
36
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/50-ipv6.sh
Executable file
@@ -0,0 +1,36 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# This command reads the `DISABLE_IPV6` env var and will either enable
|
||||||
|
# or disable ipv6 in all nginx configs based on this setting.
|
||||||
|
|
||||||
|
log_info 'IPv6 ...'
|
||||||
|
|
||||||
|
# Lowercase
|
||||||
|
DISABLE_IPV6=$(echo "${DISABLE_IPV6:-}" | tr '[:upper:]' '[:lower:]')
|
||||||
|
|
||||||
|
process_folder () {
|
||||||
|
FILES=$(find "$1" -type f -name "*.conf")
|
||||||
|
SED_REGEX=
|
||||||
|
|
||||||
|
if [ "$DISABLE_IPV6" == "true" ] || [ "$DISABLE_IPV6" == "on" ] || [ "$DISABLE_IPV6" == "1" ] || [ "$DISABLE_IPV6" == "yes" ]; then
|
||||||
|
# IPV6 is disabled
|
||||||
|
echo "Disabling IPV6 in hosts in: $1"
|
||||||
|
SED_REGEX='s/^([^#]*)listen \[::\]/\1#listen [::]/g'
|
||||||
|
else
|
||||||
|
# IPV6 is enabled
|
||||||
|
echo "Enabling IPV6 in hosts in: $1"
|
||||||
|
SED_REGEX='s/^(\s*)#listen \[::\]/\1listen [::]/g'
|
||||||
|
fi
|
||||||
|
|
||||||
|
for FILE in $FILES
|
||||||
|
do
|
||||||
|
echo "- ${FILE}"
|
||||||
|
sed -E -i "$SED_REGEX" "$FILE"
|
||||||
|
done
|
||||||
|
|
||||||
|
# ensure the files are still owned by the npmuser
|
||||||
|
chown -R "$PUID:$PGID" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
process_folder /etc/nginx/conf.d
|
||||||
|
process_folder /data/nginx
|
30
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/60-secrets.sh
Executable file
30
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/60-secrets.sh
Executable file
@@ -0,0 +1,30 @@
|
|||||||
|
#!/command/with-contenv bash
|
||||||
|
# shellcheck shell=bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# in s6, environmental variables are written as text files for s6 to monitor
|
||||||
|
# search through full-path filenames for files ending in "__FILE"
|
||||||
|
log_info 'Docker secrets ...'
|
||||||
|
|
||||||
|
for FILENAME in $(find /var/run/s6/container_environment/ | grep "__FILE$"); do
|
||||||
|
echo "[secret-init] Evaluating ${FILENAME##*/} ..."
|
||||||
|
|
||||||
|
# set SECRETFILE to the contents of the full-path textfile
|
||||||
|
SECRETFILE=$(cat "${FILENAME}")
|
||||||
|
# if SECRETFILE exists / is not null
|
||||||
|
if [[ -f "${SECRETFILE}" ]]; then
|
||||||
|
# strip the appended "__FILE" from environmental variable name ...
|
||||||
|
STRIPFILE=$(echo "${FILENAME}" | sed "s/__FILE//g")
|
||||||
|
# echo "[secret-init] Set STRIPFILE to ${STRIPFILE}" # DEBUG - rm for prod!
|
||||||
|
|
||||||
|
# ... and set value to contents of secretfile
|
||||||
|
# since s6 uses text files, this is effectively "export ..."
|
||||||
|
printf $(cat "${SECRETFILE}") > "${STRIPFILE}"
|
||||||
|
# echo "[secret-init] Set ${STRIPFILE##*/} to $(cat ${STRIPFILE})" # DEBUG - rm for prod!"
|
||||||
|
echo "Success: ${STRIPFILE##*/} set from ${FILENAME##*/}"
|
||||||
|
|
||||||
|
else
|
||||||
|
echo "Cannot find secret in ${FILENAME}"
|
||||||
|
fi
|
||||||
|
done
|
17
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/90-banner.sh
Executable file
17
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/90-banner.sh
Executable file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/command/with-contenv bash
|
||||||
|
# shellcheck shell=bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "
|
||||||
|
-------------------------------------
|
||||||
|
_ _ ____ __ __
|
||||||
|
| \ | | _ \| \/ |
|
||||||
|
| \| | |_) | |\/| |
|
||||||
|
| |\ | __/| | | |
|
||||||
|
|_| \_|_| |_| |_|
|
||||||
|
-------------------------------------
|
||||||
|
User ID: $PUID
|
||||||
|
Group ID: $PGID
|
||||||
|
-------------------------------------
|
||||||
|
"
|
@@ -1 +1 @@
|
|||||||
2.9.22
|
2.10.2
|
||||||
|
48
test/cypress/integration/api/Hosts.spec.js
Normal file
48
test/cypress/integration/api/Hosts.spec.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
/// <reference types="Cypress" />
|
||||||
|
|
||||||
|
describe('Hosts endpoints', () => {
|
||||||
|
let token;
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
cy.getToken().then((tok) => {
|
||||||
|
token = tok;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should be able to create a http host', function() {
|
||||||
|
cy.task('backendApiPost', {
|
||||||
|
token: token,
|
||||||
|
path: '/api/nginx/proxy-hosts',
|
||||||
|
data: {
|
||||||
|
domain_names: ['test.example.com'],
|
||||||
|
forward_scheme: 'http',
|
||||||
|
forward_host: '1.1.1.1',
|
||||||
|
forward_port: 80,
|
||||||
|
access_list_id: '0',
|
||||||
|
certificate_id: 0,
|
||||||
|
meta: {
|
||||||
|
letsencrypt_agree: false,
|
||||||
|
dns_challenge: false
|
||||||
|
},
|
||||||
|
advanced_config: '',
|
||||||
|
locations: [],
|
||||||
|
block_exploits: false,
|
||||||
|
caching_enabled: false,
|
||||||
|
allow_websocket_upgrade: false,
|
||||||
|
http2_support: false,
|
||||||
|
hsts_enabled: false,
|
||||||
|
hsts_subdomains: false,
|
||||||
|
ssl_forced: false
|
||||||
|
}
|
||||||
|
}).then((data) => {
|
||||||
|
cy.validateSwaggerSchema('post', 201, '/nginx/proxy-hosts', data);
|
||||||
|
expect(data).to.have.property('id');
|
||||||
|
expect(data.id).to.be.greaterThan(0);
|
||||||
|
expect(data).to.have.property('enabled');
|
||||||
|
expect(data.enabled).to.be.greaterThan(0);
|
||||||
|
expect(data).to.have.property('meta');
|
||||||
|
expect(typeof data.meta.nginx_online).to.be.equal('undefined');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
Reference in New Issue
Block a user