Compare commits

..

51 Commits

Author SHA1 Message Date
e923db7e94 Merge pull request #29 from jc21/develop
Http2 support
2018-11-26 16:48:30 +10:00
e53d9fa3eb Fix forms for http2_support 2018-11-21 08:04:31 +10:00
411734f392 Fix unsetting http2_support 2018-11-13 20:51:55 +10:00
a457a40359 Fix incorrect template var 2018-11-13 20:42:10 +10:00
caa183c8de Fix bad schema link 2018-11-12 21:48:12 +10:00
0ea5014edb Added develop build to CI 2018-11-12 21:48:12 +10:00
046cb0b76e Added HTTP/2 Support for SSL enabled hosts 2018-11-12 21:45:23 +10:00
9fd480cf77 Updated readme 2018-11-08 13:02:00 +10:00
0f94e68dca Fix redirect loop introduced in 2.0.4 2018-11-08 12:50:10 +10:00
c15edf318d Better webpack chunking 2018-11-07 09:31:42 +10:00
a73cbc7116 Whoops, stupid missing semicolon 2018-11-05 13:10:55 +10:00
f9876326c9 Use default ciphers for default ssl host to prevent confusing browser errors 2018-11-05 11:53:46 +10:00
7d5ca84501 Remove cruft from webpack 2018-11-05 10:58:57 +11:00
0335370cfb Fixes #19 and also added debug output for dummy ssl generation 2018-11-05 10:58:22 +11:00
9b852f01e3 Merge pull request #23 from jlesage/default-https-site
Added default HTTPs site.
2018-11-05 09:36:29 +10:00
20fd185652 Added default HTTPs site. 2018-11-02 21:12:40 -04:00
ad41986bd5 Updated CI 2018-10-25 10:53:45 +10:00
c826ed8c1f Fix proto forwarded header to industry standard 2018-10-25 08:51:43 +10:00
eaebc48f66 Made the hostnames clickable and open new tabs (yay) 2018-10-19 16:51:31 +10:00
eb391959aa Added resolvers auto generation in order for hostnames to work 2018-10-19 16:24:44 +10:00
dba4340548 Host or IP specified for Proxy Host Forward 2018-10-19 15:38:14 +10:00
0b8a49469f Bumped version 2018-09-29 16:06:36 +10:00
b16a68052f Bumped version 2018-09-29 16:02:28 +10:00
83686c4535 Added websock upgrade option for base proxy host locations 2018-09-29 16:02:05 +10:00
efa1424cad Fixes #11 - After creating keys the app needs to completely restart due to aggressive module caching 2018-09-12 11:14:06 +10:00
4fe26ec4c0 Expose ports for host network mode support 2018-09-05 10:03:43 +10:00
a72811ee25 Updated version in readme 2018-09-04 10:33:35 +10:00
ac9d9cdddd Fix SSL custom certificates being saved, nginx errors being reported and leaking custom certs in api calls 2018-09-04 10:33:09 +10:00
5ac0e3dc95 Bump version 2018-09-03 09:51:03 +10:00
bbe02bc70a Fixes #10 - Don't use defaults for json fields in migration 2018-09-03 09:50:18 +10:00
02b4def04a Fix #9 - Variable definition was incorrectly scoped 2018-09-03 09:21:12 +10:00
5dd723a864 Removed v1 cruft 2018-09-03 09:13:17 +10:00
d04f5a619a Updated dockerhub readme 2018-08-30 14:44:49 +10:00
6542f4c2fe Updated readme 2018-08-30 14:42:16 +10:00
32d6bf83c8 Merge branch 'v2' of github.com:jc21/nginx-proxy-manager 2018-08-30 14:32:46 +10:00
a4b918e648 Merge branch 'v2' of github.com:jc21/nginx-proxy-manager into v2 2018-08-27 17:00:06 +10:00
de367ae194 Fix redirection config bug 2018-08-27 16:59:05 +10:00
2226359cfe Added major docker image to CI 2018-08-24 21:29:31 +10:00
10e87d2e6f Updated README with armhf 2018-07-31 10:28:53 +10:00
5b58fb3bd2 Fix missing dir for armhf nginx build 2018-07-31 09:51:55 +10:00
968750a856 Merge branch 'master' of github.com:jc21/nginx-proxy-manager 2018-07-31 09:14:07 +10:00
673cfb0027 Updated CI 2018-07-31 09:09:20 +10:00
5b45312f17 Whoops, fix CI again 2018-07-30 13:52:41 +10:00
95d9e87f51 Update CI notifications 2018-07-30 12:47:52 +10:00
1382350d3a Fix CI 2018-07-30 12:33:26 +10:00
353eeaa0ae Fix CI 2018-07-30 12:06:23 +10:00
3dee0eba4e Upgraded to webpack4 2018-07-30 11:59:28 +10:00
2111d2913b Use commonjs requires instead of mixing imports/exports/requires 2018-07-30 11:59:05 +10:00
36b014aa47 Updated CI for armhf 2018-07-30 10:10:42 +10:00
e2c6aedbfa Updated CI for armhf 2018-07-30 10:06:08 +10:00
cb449a8f5d Armhf build attempt 2018-07-29 19:09:40 +10:00
65 changed files with 836 additions and 229 deletions

View File

@ -29,4 +29,11 @@ ADD knexfile.js /app/knexfile.js
VOLUME [ "/data", "/etc/letsencrypt" ]
CMD [ "/init" ]
# Ports
EXPOSE 80
EXPOSE 81
EXPOSE 443
EXPOSE 9876
HEALTHCHECK --interval=15s --timeout=3s CMD curl -f http://localhost:9876/health || exit 1

View File

@ -29,4 +29,10 @@ ADD knexfile.js /app/knexfile.js
VOLUME [ "/data", "/etc/letsencrypt" ]
CMD [ "/init" ]
# Ports
EXPOSE 80
EXPOSE 81
EXPOSE 443
EXPOSE 9876
HEALTHCHECK --interval=15s --timeout=3s CMD curl -f http://localhost:9876/health || exit 1

64
Jenkinsfile vendored
View File

@ -6,7 +6,7 @@ pipeline {
agent any
environment {
IMAGE_NAME = "nginx-proxy-manager"
BASE_IMAGE_NAME = "jc21/nginx-proxy-manager-base"
BASE_IMAGE_NAME = "jc21/nginx-proxy-manager-base:v2"
TEMP_IMAGE_NAME = "nginx-proxy-manager-build_${BUILD_NUMBER}"
TEMP_IMAGE_NAME_ARM = "nginx-proxy-manager-arm-build_${BUILD_NUMBER}"
TAG_VERSION = getPackageVersion()
@ -18,16 +18,50 @@ pipeline {
sh 'docker pull $DOCKER_CI_TOOLS'
}
}
stage('Build') {
stage('Build Develop') {
when {
branch 'develop'
}
steps {
ansiColor('xterm') {
// Codebase
sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME yarn install'
sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME npm run-script build'
sh 'rm -rf node_modules'
sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME yarn install --prod'
sh 'docker run --rm -v $(pwd):/data $DOCKER_CI_TOOLS node-prune'
// Docker Build
sh 'docker build --pull --no-cache --squash --compress -t $TEMP_IMAGE_NAME .'
// Private Registry
sh 'docker tag $TEMP_IMAGE_NAME $DOCKER_PRIVATE_REGISTRY/$IMAGE_NAME:develop'
sh 'docker push $DOCKER_PRIVATE_REGISTRY/$IMAGE_NAME:develop'
// Dockerhub
sh 'docker tag $TEMP_IMAGE_NAME docker.io/jc21/$IMAGE_NAME:develop'
withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
sh "docker login -u '${duser}' -p '$dpass'"
sh 'docker push docker.io/jc21/$IMAGE_NAME:develop'
}
sh 'docker rmi $TEMP_IMAGE_NAME'
}
}
}
stage('Build Master') {
parallel {
stage('x86_64') {
when {
branch 'master'
}
steps {
ansiColor('xterm') {
// Codebase
sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME:latest yarn --registry=$NPM_REGISTRY install'
sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME:latest npm run-script build'
sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME yarn install'
sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME npm run-script build'
sh 'rm -rf node_modules'
sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME:latest yarn --registry=$NPM_REGISTRY install --prod'
sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME yarn install --prod'
sh 'docker run --rm -v $(pwd):/data $DOCKER_CI_TOOLS node-prune'
// Docker Build
@ -38,15 +72,19 @@ pipeline {
sh 'docker push $DOCKER_PRIVATE_REGISTRY/$IMAGE_NAME:$TAG_VERSION'
sh 'docker tag $TEMP_IMAGE_NAME $DOCKER_PRIVATE_REGISTRY/$IMAGE_NAME:$MAJOR_VERSION'
sh 'docker push $DOCKER_PRIVATE_REGISTRY/$IMAGE_NAME:$MAJOR_VERSION'
sh 'docker tag $TEMP_IMAGE_NAME $DOCKER_PRIVATE_REGISTRY/$IMAGE_NAME:latest'
sh 'docker push $DOCKER_PRIVATE_REGISTRY/$IMAGE_NAME:latest'
// Dockerhub
sh 'docker tag $TEMP_IMAGE_NAME docker.io/jc21/$IMAGE_NAME:$TAG_VERSION'
sh 'docker tag $TEMP_IMAGE_NAME docker.io/jc21/$IMAGE_NAME:$MAJOR_VERSION'
sh 'docker tag $TEMP_IMAGE_NAME docker.io/jc21/$IMAGE_NAME:latest'
withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
sh "docker login -u '${duser}' -p '$dpass'"
sh 'docker push docker.io/jc21/$IMAGE_NAME:$TAG_VERSION'
sh 'docker push docker.io/jc21/$IMAGE_NAME:$MAJOR_VERSION'
sh 'docker push docker.io/jc21/$IMAGE_NAME:latest'
}
sh 'docker rmi $TEMP_IMAGE_NAME'
@ -54,34 +92,41 @@ pipeline {
}
}
stage('armhf') {
when {
branch 'master'
}
agent {
label 'armhf'
}
steps {
ansiColor('xterm') {
// Codebase
sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME:armhf yarn --registry=$NPM_REGISTRY install'
sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME:armhf npm run-script build'
sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME-armhf yarn install'
sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME-armhf npm run-script build'
sh 'rm -rf node_modules'
sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME:armhf yarn --registry=$NPM_REGISTRY install --prod'
sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME-armhf yarn install --prod'
// Docker Build
sh 'docker build --pull --no-cache --squash --compress -t $TEMP_IMAGE_NAME_ARM -f Dockerfile.armhf .'
sh 'docker build --pull --no-cache --squash --compress -t $TEMP_IMAGE_NAME_ARM -f Dockerfile.armhf .'
// Private Registry
sh 'docker tag $TEMP_IMAGE_NAME_ARM $DOCKER_PRIVATE_REGISTRY/$IMAGE_NAME:$TAG_VERSION-armhf'
sh 'docker push $DOCKER_PRIVATE_REGISTRY/$IMAGE_NAME:$TAG_VERSION-armhf'
sh 'docker tag $TEMP_IMAGE_NAME_ARM $DOCKER_PRIVATE_REGISTRY/$IMAGE_NAME:$MAJOR_VERSION-armhf'
sh 'docker push $DOCKER_PRIVATE_REGISTRY/$IMAGE_NAME:$MAJOR_VERSION-armhf'
sh 'docker tag $TEMP_IMAGE_NAME_ARM $DOCKER_PRIVATE_REGISTRY/$IMAGE_NAME:latest-armhf'
sh 'docker push $DOCKER_PRIVATE_REGISTRY/$IMAGE_NAME:latest-armhf'
// Dockerhub
sh 'docker tag $TEMP_IMAGE_NAME_ARM docker.io/jc21/$IMAGE_NAME:$TAG_VERSION-armhf'
sh 'docker tag $TEMP_IMAGE_NAME_ARM docker.io/jc21/$IMAGE_NAME:$MAJOR_VERSION-armhf'
sh 'docker tag $TEMP_IMAGE_NAME_ARM docker.io/jc21/$IMAGE_NAME:latest-armhf'
withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
sh "docker login -u '${duser}' -p '$dpass'"
sh 'docker push docker.io/jc21/$IMAGE_NAME:$TAG_VERSION-armhf'
sh 'docker push docker.io/jc21/$IMAGE_NAME:$MAJOR_VERSION-armhf'
sh 'docker push docker.io/jc21/$IMAGE_NAME:latest-armhf'
}
sh 'docker rmi $TEMP_IMAGE_NAME_ARM'
@ -107,3 +152,4 @@ def getPackageVersion() {
ver = sh(script: 'docker run --rm -v $(pwd):/data $DOCKER_CI_TOOLS bash -c "cat /data/package.json|jq -r \'.version\'"', returnStdout: true)
return ver.trim()
}

View File

@ -2,7 +2,7 @@
# Nginx Proxy Manager
![Version](https://img.shields.io/badge/version-2.0.0-green.svg?style=for-the-badge)
![Version](https://img.shields.io/badge/version-2.0.6-green.svg?style=for-the-badge)
![Stars](https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge)
![Pulls](https://img.shields.io/docker/pulls/jc21/nginx-proxy-manager.svg?style=for-the-badge)

17
TODO.md
View File

@ -1,17 +0,0 @@
# TODO
- Dashboard stats are caching instead of querying
Next version:
- UI Log tail
- Enable/Disable a config
Testing:
- Access Levels
- Adding a proxy host without access to read certs or access lists
- Visibility
- Forwarding
- Cert renewals
- Custom certs

26
bin/migrate_create Executable file
View File

@ -0,0 +1,26 @@
#!/bin/bash
if [ "$1" == "" ]; then
echo "Error: migrate name must be specified as first arg"
exit 1
else
# Code path
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
if hash realpath 2>/dev/null; then
export CODEBASE=$(realpath $SCRIPT_DIR/..)
elif hash grealpath 2>/dev/null; then
export CODEBASE=$(grealpath $SCRIPT_DIR/..)
else
export CODEBASE=$(readlink -e $SCRIPT_DIR/..)
fi
if [ -z "$CODEBASE" ]; then
echo "Unable to determine absolute codebase directory"
exit 1
fi
cd "$CODEBASE"
sudo /usr/local/bin/docker-compose run --rm --no-deps app node node_modules/knex/bin/cli.js migrate:make "$1"
exit $?
fi

View File

@ -2,7 +2,7 @@
# Nginx Proxy Manager
![Version](https://img.shields.io/badge/version-2.0.0-green.svg?style=for-the-badge)
![Version](https://img.shields.io/badge/version-2.0.6-green.svg?style=for-the-badge)
![Stars](https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge)
![Pulls](https://img.shields.io/docker/pulls/jc21/nginx-proxy-manager.svg?style=for-the-badge)
@ -14,16 +14,16 @@ running at home or otherwise, including free SSL, without having to know too muc
## Tags
* latest 1, 1.x.x ([Dockerfile](https://github.com/jc21/nginx-proxy-manager/blob/master/Dockerfile))
* latest-armhf, 1-armhf, 1.x.x-armhf ([Dockerfile](https://github.com/jc21/nginx-proxy-manager/blob/master/Dockerfile.armhf))
* 2, 2.x.x ([Dockerfile](https://github.com/jc21/nginx-proxy-manager/blob/v2/Dockerfile))
* 2-armhf, 2.x.x-armhf ([Dockerfile](https://github.com/jc21/nginx-proxy-manager/blob/v2/Dockerfile.armhf))
* latest 2, 2.x.x ([Dockerfile](https://github.com/jc21/nginx-proxy-manager/blob/master/Dockerfile))
* latest-armhf, 2-armhf, 2.x.x-armhf ([Dockerfile](https://github.com/jc21/nginx-proxy-manager/blob/master/Dockerfile.armhf))
* 1, 1.x.x ([Dockerfile](https://github.com/jc21/nginx-proxy-manager/blob/1.1.2/Dockerfile))
* 1-armhf, 1.x.x-armhf ([Dockerfile](https://github.com/jc21/nginx-proxy-manager/blob/1.1.2/Dockerfile.armhf))
## Getting started
Please consult the [installation instructions](https://github.com/jc21/nginx-proxy-manager/blob/v2/doc/INSTALL.md) for a complete guide or
if you just want to get up and running in the quickest time possible, grab all the files in the [doc/example/](https://github.com/jc21/nginx-proxy-manager/tree/v2/doc/example) folder and run `docker-compose up -d`
Please consult the [installation instructions](https://github.com/jc21/nginx-proxy-manager/blob/master/doc/INSTALL.md) for a complete guide or
if you just want to get up and running in the quickest time possible, grab all the files in the [doc/example/](https://github.com/jc21/nginx-proxy-manager/tree/master/doc/example) folder and run `docker-compose up -d`
## Screenshots

View File

@ -1,6 +1,6 @@
{
"name": "nginx-proxy-manager",
"version": "2.0.0",
"version": "2.0.6",
"description": "A beautiful interface for creating Nginx endpoints",
"main": "src/backend/index.js",
"devDependencies": {
@ -30,8 +30,8 @@
"style-loader": "^0.22.1",
"tabler-ui": "git+https://github.com/tabler/tabler.git",
"underscore": "^1.8.3",
"webpack": "^4.12.0",
"webpack-cli": "^3.0.8",
"webpack": "^4.25.1",
"webpack-cli": "^3.1.2",
"webpack-visualizer-plugin": "^0.1.11"
},
"dependencies": {

View File

@ -36,3 +36,17 @@ server {
root /var/www/html;
}
}
# Default 443 Host
server {
listen 443 ssl default;
server_name localhost;
access_log /data/logs/default.log proxy;
ssl_certificate /data/nginx/dummycert.pem;
ssl_certificate_key /data/nginx/dummykey.pem;
include conf.d/include/ssl-ciphers.conf;
return 444;
}

View File

@ -1,6 +1,6 @@
add_header X-Served-By $host;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-Protocol $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass http://$server:$port;

View File

@ -0,0 +1 @@
# Intentionally blank

View File

@ -51,6 +51,10 @@ http {
access_log /data/logs/default.log proxy;
# Dynamically generated resolvers file
include /etc/nginx/conf.d/include/resolvers.conf;
# Files generated by NPM
include /etc/nginx/conf.d/*.conf;
include /data/nginx/proxy_host/*.conf;
include /data/nginx/redirection_host/*.conf;
@ -59,6 +63,7 @@ http {
}
stream {
# Files generated by NPM
include /data/nginx/stream/*.conf;
}

View File

@ -3,4 +3,9 @@
mkdir -p /data/letsencrypt-acme-challenge
cd /app
node --abort_on_uncaught_exception --max_old_space_size=250 /app/src/backend/index.js
while :
do
node --abort_on_uncaught_exception --max_old_space_size=250 /app/src/backend/index.js
sleep 1
done

View File

@ -1,5 +1,6 @@
#!/usr/bin/with-contenv bash
# Create required folders
mkdir -p /tmp/nginx/body \
/var/log/nginx \
/data/nginx \
@ -12,9 +13,30 @@ mkdir -p /tmp/nginx/body \
/data/nginx/dead_host \
/data/nginx/temp \
/var/lib/nginx/cache/public \
/var/lib/nginx/cache/private
/var/lib/nginx/cache/private \
/var/cache/nginx/proxy_temp
touch /var/log/nginx/error.log && chmod 777 /var/log/nginx/error.log
touch /var/log/nginx/error.log && chmod 777 /var/log/nginx/error.log && chmod -R 777 /var/cache/nginx
chown root /tmp/nginx
# Dynamically generate resolvers file
echo resolver $(awk 'BEGIN{ORS=" "} $1=="nameserver" {print $2}' /etc/resolv.conf) ";" > /etc/nginx/conf.d/include/resolvers.conf
# Generate dummy self-signed certificate.
if [ ! -f /data/nginx/dummycert.pem ] || [ ! -f /data/nginx/dummykey.pem ]
then
echo "Generating dummy SSL certificate..."
openssl req \
-new \
-newkey rsa:2048 \
-days 3650 \
-nodes \
-x509 \
-subj '/O=Nginx Proxy Manager/OU=Dummy Certificate/CN=localhost' \
-keyout /data/nginx/dummykey.pem \
-out /data/nginx/dummycert.pem
echo "Complete"
fi
# Run
exec nginx

View File

@ -1,9 +1,10 @@
'use strict';
const fs = require('fs');
const logger = require('./logger').import;
const utils = require('./lib/utils');
const batchflow = require('batchflow');
const fs = require('fs');
const logger = require('./logger').import;
const utils = require('./lib/utils');
const batchflow = require('batchflow');
const debug_mode = process.env.NODE_ENV !== 'production';
const internalProxyHost = require('./internal/proxy-host');
const internalRedirectionHost = require('./internal/redirection-host');
@ -353,7 +354,7 @@ module.exports = function () {
.insertAndFetch({
owner_user_id: 1,
domain_names: [host.hostname],
forward_ip: host.forward_server,
forward_host: host.forward_server,
forward_port: host.forward_port,
access_list_id: access_list_id,
certificate_id: certificate_id,
@ -534,6 +535,10 @@ module.exports = function () {
);
} else {
if (debug_mode) {
logger.debug('Importer skipped');
}
resolve();
}
});

View File

@ -183,10 +183,7 @@ const internalCertificate = {
});
});
} else {
return internalCertificate.writeCustomCert(certificate)
.then(() => {
return certificate;
});
return certificate;
}
}).then(certificate => {
@ -409,9 +406,13 @@ const internalCertificate = {
* @returns {Promise}
*/
writeCustomCert: certificate => {
return new Promise((resolve, reject) => {
let dir = '/data/custom_ssl/npm-' + certificate.id;
if (debug_mode) {
logger.info('Writing Custom Certificate:', certificate);
}
let dir = '/data/custom_ssl/npm-' + certificate.id;
return new Promise((resolve, reject) => {
if (certificate.provider === 'letsencrypt') {
reject(new Error('Refusing to write letsencrypt certs here'));
return;
@ -549,8 +550,13 @@ const internalCertificate = {
id: data.id,
expires_on: certificateModel.raw('FROM_UNIXTIME(' + validations.certificate.dates.to + ')'),
domain_names: [validations.certificate.cn],
meta: row.meta
});
meta: _.clone(row.meta) // Prevent the update method from changing this value that we'll use later
})
.then(certificate => {
console.log('ROWMETA:', row.meta);
certificate.meta = row.meta;
return internalCertificate.writeCustomCert(certificate);
})
})
.then(() => {
return _.pick(row.meta, internalCertificate.allowed_ssl_files);

View File

@ -189,7 +189,9 @@ const internalDeadHost = {
.then(row => {
// Configure nginx
return internalNginx.configure(deadHostModel, 'dead_host', row)
.then(() => {
.then(new_meta => {
row.meta = new_meta;
row = internalHost.cleanRowCertificateMeta(row);
return _.omit(row, omissions());
});
});
@ -235,6 +237,7 @@ const internalDeadHost = {
})
.then(row => {
if (row) {
row = internalHost.cleanRowCertificateMeta(row);
return _.omit(row, omissions());
} else {
throw new error.ItemNotFoundError(data.id);
@ -322,6 +325,13 @@ const internalDeadHost = {
}
return query;
})
.then(rows => {
if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {
return internalHost.cleanAllRowsCertificateMeta(rows);
}
return rows;
});
},

View File

@ -6,6 +6,36 @@ const deadHostModel = require('../models/dead_host');
const internalHost = {
/**
* used by the getAll functions of hosts, this removes the certificate meta if present
*
* @param {Array} rows
* @returns {Array}
*/
cleanAllRowsCertificateMeta: function (rows) {
rows.map(function (row, idx) {
if (typeof rows[idx].certificate !== 'undefined' && rows[idx].certificate) {
rows[idx].certificate.meta = {};
}
});
return rows;
},
/**
* used by the get/update functions of hosts, this removes the certificate meta if present
*
* @param {Object} row
* @returns {Object}
*/
cleanRowCertificateMeta: function (row) {
if (typeof row.certificate !== 'undefined' && row.certificate) {
row.certificate.meta = {};
}
return row;
},
/**
* This returns all the host types with any domain listed in the provided domain_names array.
* This is used by the certificates to temporarily disable any host that is using the domain

View File

@ -25,6 +25,8 @@ const internalNginx = {
* @returns {Promise}
*/
configure: (model, host_type, host) => {
let combined_meta = {};
return internalNginx.test()
.then(() => {
// Nginx is OK
@ -39,30 +41,46 @@ const internalNginx = {
return internalNginx.test()
.then(() => {
// nginx is ok
combined_meta = _.assign({}, host.meta, {
nginx_online: true,
nginx_err: null
});
return model
.query()
.where('id', host.id)
.patch({
meta: _.assign({}, host.meta, {
nginx_online: true,
nginx_err: null
})
meta: combined_meta
});
})
.catch(err => {
// Remove the error_log line because it's a docker-ism false positive that doesn't need to be reported.
// It will always look like this:
// nginx: [alert] could not open error log file: open() "/var/log/nginx/error.log" failed (6: No such device or address)
let valid_lines = [];
let err_lines = err.message.split("\n");
err_lines.map(function (line) {
if (line.indexOf('/var/log/nginx/error.log') === -1) {
valid_lines.push(line);
}
});
if (debug_mode) {
logger.error('Nginx test failed:', err.message);
logger.error('Nginx test failed:', valid_lines.join("\n"));
}
// config is bad, update meta and delete config
combined_meta = _.assign({}, host.meta, {
nginx_online: false,
nginx_err: valid_lines.join("\n")
});
return model
.query()
.where('id', host.id)
.patch({
meta: _.assign({}, host.meta, {
nginx_online: false,
nginx_err: err.message
})
meta: combined_meta
})
.then(() => {
return internalNginx.deleteConfig(host_type, host, true);
@ -71,7 +89,10 @@ const internalNginx = {
})
.then(() => {
return internalNginx.reload();
});
})
.then(() => {
return combined_meta;
})
},
/**
@ -82,7 +103,7 @@ const internalNginx = {
logger.info('Testing Nginx configuration');
}
return utils.exec('/usr/sbin/nginx -t');
return utils.exec('/usr/sbin/nginx -t -g "error_log off;"');
},
/**

View File

@ -190,7 +190,9 @@ const internalProxyHost = {
.then(row => {
// Configure nginx
return internalNginx.configure(proxyHostModel, 'proxy_host', row)
.then(() => {
.then(new_meta => {
row.meta = new_meta;
row = internalHost.cleanRowCertificateMeta(row);
return _.omit(row, omissions());
});
});
@ -236,6 +238,7 @@ const internalProxyHost = {
})
.then(row => {
if (row) {
row = internalHost.cleanRowCertificateMeta(row);
return _.omit(row, omissions());
} else {
throw new error.ItemNotFoundError(data.id);
@ -323,6 +326,13 @@ const internalProxyHost = {
}
return query;
})
.then(rows => {
if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {
return internalHost.cleanAllRowsCertificateMeta(rows);
}
return rows;
});
},

View File

@ -189,7 +189,9 @@ const internalRedirectionHost = {
.then(row => {
// Configure nginx
return internalNginx.configure(redirectionHostModel, 'redirection_host', row)
.then(() => {
.then(new_meta => {
row.meta = new_meta;
row = internalHost.cleanRowCertificateMeta(row);
return _.omit(row, omissions());
});
});
@ -235,6 +237,7 @@ const internalRedirectionHost = {
})
.then(row => {
if (row) {
row = internalHost.cleanRowCertificateMeta(row);
return _.omit(row, omissions());
} else {
throw new error.ItemNotFoundError(data.id);
@ -322,6 +325,13 @@ const internalRedirectionHost = {
}
return query;
})
.then(rows => {
if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {
return internalHost.cleanAllRowsCertificateMeta(rows);
}
return rows;
});
},

View File

@ -8,4 +8,5 @@ module.exports = {
nginx: new Signale({scope: 'Nginx '}),
ssl: new Signale({scope: 'SSL '}),
import: new Signale({scope: 'Importer'}),
setup: new Signale({scope: 'Setup '})
};

View File

@ -22,7 +22,7 @@ exports.up = function (knex/*, Promise*/) {
table.integer('user_id').notNull().unsigned();
table.string('type', 30).notNull();
table.string('secret').notNull();
table.json('meta').notNull().defaultTo('{}');
table.json('meta').notNull();
table.integer('is_deleted').notNull().unsigned().defaultTo(0);
})
.then(() => {
@ -77,7 +77,7 @@ exports.up = function (knex/*, Promise*/) {
table.integer('caching_enabled').notNull().unsigned().defaultTo(0);
table.integer('block_exploits').notNull().unsigned().defaultTo(0);
table.text('advanced_config').notNull().defaultTo('');
table.json('meta').notNull().defaultTo('{}');
table.json('meta').notNull();
});
})
.then(() => {
@ -96,7 +96,7 @@ exports.up = function (knex/*, Promise*/) {
table.integer('ssl_forced').notNull().unsigned().defaultTo(0);
table.integer('block_exploits').notNull().unsigned().defaultTo(0);
table.text('advanced_config').notNull().defaultTo('');
table.json('meta').notNull().defaultTo('{}');
table.json('meta').notNull();
});
})
.then(() => {
@ -112,7 +112,7 @@ exports.up = function (knex/*, Promise*/) {
table.integer('certificate_id').notNull().unsigned().defaultTo(0);
table.integer('ssl_forced').notNull().unsigned().defaultTo(0);
table.text('advanced_config').notNull().defaultTo('');
table.json('meta').notNull().defaultTo('{}');
table.json('meta').notNull();
});
})
.then(() => {
@ -129,7 +129,7 @@ exports.up = function (knex/*, Promise*/) {
table.integer('forwarding_port').notNull().unsigned();
table.integer('tcp_forwarding').notNull().unsigned().defaultTo(0);
table.integer('udp_forwarding').notNull().unsigned().defaultTo(0);
table.json('meta').notNull().defaultTo('{}');
table.json('meta').notNull();
});
})
.then(() => {
@ -142,7 +142,7 @@ exports.up = function (knex/*, Promise*/) {
table.integer('owner_user_id').notNull().unsigned();
table.integer('is_deleted').notNull().unsigned().defaultTo(0);
table.string('name').notNull();
table.json('meta').notNull().defaultTo('{}');
table.json('meta').notNull();
});
})
.then(() => {
@ -156,9 +156,9 @@ exports.up = function (knex/*, Promise*/) {
table.integer('is_deleted').notNull().unsigned().defaultTo(0);
table.string('provider').notNull();
table.string('nice_name').notNull().defaultTo('');
table.json('domain_names').notNull().defaultTo('[]');
table.json('domain_names').notNull();
table.dateTime('expires_on').notNull();
table.json('meta').notNull().defaultTo('{}');
table.json('meta').notNull();
});
})
.then(() => {
@ -171,7 +171,7 @@ exports.up = function (knex/*, Promise*/) {
table.integer('access_list_id').notNull().unsigned();
table.string('username').notNull();
table.string('password').notNull();
table.json('meta').notNull().defaultTo('{}');
table.json('meta').notNull();
});
})
.then(() => {
@ -185,7 +185,7 @@ exports.up = function (knex/*, Promise*/) {
table.string('object_type').notNull().defaultTo('');
table.integer('object_id').notNull().unsigned().defaultTo(0);
table.string('action').notNull();
table.json('meta').notNull().defaultTo('{}');
table.json('meta').notNull();
});
})
.then(() => {

View File

@ -0,0 +1,37 @@
'use strict';
const migrate_name = 'websockets';
const logger = require('../logger').migrate;
/**
* Migrate
*
* @see http://knexjs.org/#Schema
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.up = function (knex/*, Promise*/) {
logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.table('proxy_host', function (proxy_host) {
proxy_host.integer('allow_websocket_upgrade').notNull().unsigned().defaultTo(0);
})
.then(() => {
logger.info('[' + migrate_name + '] proxy_host Table altered');
});
};
/**
* Undo Migrate
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.down = function (knex, Promise) {
logger.warn('[' + migrate_name + '] You can\'t migrate down this one.');
return Promise.resolve(true);
};

View File

@ -0,0 +1,36 @@
'use strict';
const migrate_name = 'forward_host';
const logger = require('../logger').migrate;
/**
* Migrate
*
* @see http://knexjs.org/#Schema
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.up = function (knex/*, Promise*/) {
logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.table('proxy_host', function (proxy_host) {
proxy_host.renameColumn('forward_ip', 'forward_host');
})
.then(() => {
logger.info('[' + migrate_name + '] proxy_host Table altered');
});
};
/**
* Undo Migrate
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.down = function (knex, Promise) {
logger.warn('[' + migrate_name + '] You can\'t migrate down this one.');
return Promise.resolve(true);
};

View File

@ -0,0 +1,51 @@
'use strict';
const migrate_name = 'http2_support';
const logger = require('../logger').migrate;
/**
* Migrate
*
* @see http://knexjs.org/#Schema
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.up = function (knex/*, Promise*/) {
logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.table('proxy_host', function (proxy_host) {
proxy_host.integer('http2_support').notNull().unsigned().defaultTo(0);
})
.then(() => {
logger.info('[' + migrate_name + '] proxy_host Table altered');
return knex.schema.table('redirection_host', function (redirection_host) {
redirection_host.integer('http2_support').notNull().unsigned().defaultTo(0);
});
})
.then(() => {
logger.info('[' + migrate_name + '] redirection_host Table altered');
return knex.schema.table('dead_host', function (dead_host) {
dead_host.integer('http2_support').notNull().unsigned().defaultTo(0);
});
})
.then(() => {
logger.info('[' + migrate_name + '] dead_host Table altered');
});
};
/**
* Undo Migrate
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.down = function (knex, Promise) {
logger.warn('[' + migrate_name + '] You can\'t migrate down this one.');
return Promise.resolve(true);
};

View File

@ -14,6 +14,11 @@ class AccessList extends Model {
$beforeInsert () {
this.created_on = Model.raw('NOW()');
this.modified_on = Model.raw('NOW()');
// Default for meta
if (typeof this.meta === 'undefined') {
this.meta = {};
}
}
$beforeUpdate () {

View File

@ -12,6 +12,11 @@ class AccessListAuth extends Model {
$beforeInsert () {
this.created_on = Model.raw('NOW()');
this.modified_on = Model.raw('NOW()');
// Default for meta
if (typeof this.meta === 'undefined') {
this.meta = {};
}
}
$beforeUpdate () {

View File

@ -13,6 +13,11 @@ class AuditLog extends Model {
$beforeInsert () {
this.created_on = Model.raw('NOW()');
this.modified_on = Model.raw('NOW()');
// Default for meta
if (typeof this.meta === 'undefined') {
this.meta = {};
}
}
$beforeUpdate () {

View File

@ -29,6 +29,11 @@ class Auth extends Model {
this.created_on = Model.raw('NOW()');
this.modified_on = Model.raw('NOW()');
// Default for meta
if (typeof this.meta === 'undefined') {
this.meta = {};
}
return encryptPassword.apply(this, queryContext);
}

View File

@ -14,13 +14,31 @@ class Certificate extends Model {
this.created_on = Model.raw('NOW()');
this.modified_on = Model.raw('NOW()');
// Default for expires_on
if (typeof this.expires_on === 'undefined') {
this.expires_on = Model.raw('NOW()');
}
// Default for domain_names
if (typeof this.domain_names === 'undefined') {
this.domain_names = [];
}
// Default for meta
if (typeof this.meta === 'undefined') {
this.meta = {};
}
this.domain_names.sort();
}
$beforeUpdate () {
this.modified_on = Model.raw('NOW()');
// Sort domain_names
if (typeof this.domain_names !== 'undefined') {
this.domain_names.sort();
}
}
static get name () {

View File

@ -14,10 +14,27 @@ class DeadHost extends Model {
$beforeInsert () {
this.created_on = Model.raw('NOW()');
this.modified_on = Model.raw('NOW()');
// Default for domain_names
if (typeof this.domain_names === 'undefined') {
this.domain_names = [];
}
// Default for meta
if (typeof this.meta === 'undefined') {
this.meta = {};
}
this.domain_names.sort();
}
$beforeUpdate () {
this.modified_on = Model.raw('NOW()');
// Sort domain_names
if (typeof this.domain_names !== 'undefined') {
this.domain_names.sort();
}
}
static get name () {

View File

@ -15,11 +15,24 @@ class ProxyHost extends Model {
$beforeInsert () {
this.created_on = Model.raw('NOW()');
this.modified_on = Model.raw('NOW()');
// Default for domain_names
if (typeof this.domain_names === 'undefined') {
this.domain_names = [];
}
// Default for meta
if (typeof this.meta === 'undefined') {
this.meta = {};
}
this.domain_names.sort();
}
$beforeUpdate () {
this.modified_on = Model.raw('NOW()');
// Sort domain_names
if (typeof this.domain_names !== 'undefined') {
this.domain_names.sort();
}

View File

@ -14,10 +14,27 @@ class RedirectionHost extends Model {
$beforeInsert () {
this.created_on = Model.raw('NOW()');
this.modified_on = Model.raw('NOW()');
// Default for domain_names
if (typeof this.domain_names === 'undefined') {
this.domain_names = [];
}
// Default for meta
if (typeof this.meta === 'undefined') {
this.meta = {};
}
this.domain_names.sort();
}
$beforeUpdate () {
this.modified_on = Model.raw('NOW()');
// Sort domain_names
if (typeof this.domain_names !== 'undefined') {
this.domain_names.sort();
}
}
static get name () {

View File

@ -13,6 +13,11 @@ class Stream extends Model {
$beforeInsert () {
this.created_on = Model.raw('NOW()');
this.modified_on = Model.raw('NOW()');
// Default for meta
if (typeof this.meta === 'undefined') {
this.meta = {};
}
}
$beforeUpdate () {

View File

@ -19,7 +19,6 @@ module.exports = function () {
let token_data = {};
let self = {
//return {
/**
* @param {Object} payload
* @param {Object} [user_options]

View File

@ -13,6 +13,11 @@ class User extends Model {
$beforeInsert () {
this.created_on = Model.raw('NOW()');
this.modified_on = Model.raw('NOW()');
// Default for roles
if (typeof this.roles === 'undefined') {
this.roles = [];
}
}
$beforeUpdate () {

View File

@ -186,6 +186,11 @@
"type": "string",
"pattern": "^(letsencrypt|other)$"
},
"http2_support": {
"description": "HTTP2 Protocol Support",
"example": false,
"type": "boolean"
},
"block_exploits": {
"description": "Should we block common exploits",
"example": true,

View File

@ -24,6 +24,9 @@
"ssl_forced": {
"$ref": "../definitions.json#/definitions/ssl_forced"
},
"http2_support": {
"$ref": "../definitions.json#/definitions/http2_support"
},
"advanced_config": {
"type": "string"
},
@ -50,6 +53,9 @@
"ssl_forced": {
"$ref": "#/definitions/ssl_forced"
},
"http2_support": {
"$ref": "#/definitions/http2_support"
},
"advanced_config": {
"$ref": "#/definitions/advanced_config"
},
@ -101,6 +107,9 @@
"ssl_forced": {
"$ref": "#/definitions/ssl_forced"
},
"http2_support": {
"$ref": "#/definitions/http2_support"
},
"advanced_config": {
"$ref": "#/definitions/advanced_config"
},
@ -138,6 +147,9 @@
"ssl_forced": {
"$ref": "#/definitions/ssl_forced"
},
"http2_support": {
"$ref": "#/definitions/http2_support"
},
"advanced_config": {
"$ref": "#/definitions/advanced_config"
},

View File

@ -18,9 +18,10 @@
"domain_names": {
"$ref": "../definitions.json#/definitions/domain_names"
},
"forward_ip": {
"forward_host": {
"type": "string",
"format": "ipv4"
"minLength": 1,
"maxLength": 50
},
"forward_port": {
"type": "integer",
@ -33,12 +34,20 @@
"ssl_forced": {
"$ref": "../definitions.json#/definitions/ssl_forced"
},
"http2_support": {
"$ref": "../definitions.json#/definitions/http2_support"
},
"block_exploits": {
"$ref": "../definitions.json#/definitions/block_exploits"
},
"caching_enabled": {
"$ref": "../definitions.json#/definitions/caching_enabled"
},
"allow_websocket_upgrade": {
"description": "Allow Websocket Upgrade for all paths",
"example": true,
"type": "boolean"
},
"access_list_id": {
"$ref": "../definitions.json#/definitions/access_list_id"
},
@ -62,8 +71,8 @@
"domain_names": {
"$ref": "#/definitions/domain_names"
},
"forward_ip": {
"$ref": "#/definitions/forward_ip"
"forward_host": {
"$ref": "#/definitions/forward_host"
},
"forward_port": {
"$ref": "#/definitions/forward_port"
@ -74,12 +83,18 @@
"ssl_forced": {
"$ref": "#/definitions/ssl_forced"
},
"http2_support": {
"$ref": "#/definitions/http2_support"
},
"block_exploits": {
"$ref": "#/definitions/block_exploits"
},
"caching_enabled": {
"$ref": "#/definitions/caching_enabled"
},
"allow_websocket_upgrade": {
"$ref": "#/definitions/allow_websocket_upgrade"
},
"access_list_id": {
"$ref": "#/definitions/access_list_id"
},
@ -123,15 +138,15 @@
"additionalProperties": false,
"required": [
"domain_names",
"forward_ip",
"forward_host",
"forward_port"
],
"properties": {
"domain_names": {
"$ref": "#/definitions/domain_names"
},
"forward_ip": {
"$ref": "#/definitions/forward_ip"
"forward_host": {
"$ref": "#/definitions/forward_host"
},
"forward_port": {
"$ref": "#/definitions/forward_port"
@ -142,12 +157,18 @@
"ssl_forced": {
"$ref": "#/definitions/ssl_forced"
},
"http2_support": {
"$ref": "#/definitions/http2_support"
},
"block_exploits": {
"$ref": "#/definitions/block_exploits"
},
"caching_enabled": {
"$ref": "#/definitions/caching_enabled"
},
"allow_websocket_upgrade": {
"$ref": "#/definitions/allow_websocket_upgrade"
},
"access_list_id": {
"$ref": "#/definitions/access_list_id"
},
@ -182,8 +203,8 @@
"domain_names": {
"$ref": "#/definitions/domain_names"
},
"forward_ip": {
"$ref": "#/definitions/forward_ip"
"forward_host": {
"$ref": "#/definitions/forward_host"
},
"forward_port": {
"$ref": "#/definitions/forward_port"
@ -194,12 +215,18 @@
"ssl_forced": {
"$ref": "#/definitions/ssl_forced"
},
"http2_support": {
"$ref": "#/definitions/http2_support"
},
"block_exploits": {
"$ref": "#/definitions/block_exploits"
},
"caching_enabled": {
"$ref": "#/definitions/caching_enabled"
},
"allow_websocket_upgrade": {
"$ref": "#/definitions/allow_websocket_upgrade"
},
"access_list_id": {
"$ref": "#/definitions/access_list_id"
},

View File

@ -32,6 +32,9 @@
"ssl_forced": {
"$ref": "../definitions.json#/definitions/ssl_forced"
},
"http2_support": {
"$ref": "../definitions.json#/definitions/http2_support"
},
"block_exploits": {
"$ref": "../definitions.json#/definitions/block_exploits"
},
@ -67,6 +70,9 @@
"ssl_forced": {
"$ref": "#/definitions/ssl_forced"
},
"http2_support": {
"$ref": "#/definitions/http2_support"
},
"block_exploits": {
"$ref": "#/definitions/block_exploits"
},
@ -128,6 +134,9 @@
"ssl_forced": {
"$ref": "#/definitions/ssl_forced"
},
"http2_support": {
"$ref": "#/definitions/http2_support"
},
"block_exploits": {
"$ref": "#/definitions/block_exploits"
},
@ -174,6 +183,9 @@
"ssl_forced": {
"$ref": "#/definitions/ssl_forced"
},
"http2_support": {
"$ref": "#/definitions/http2_support"
},
"block_exploits": {
"$ref": "#/definitions/block_exploits"
},

View File

@ -3,10 +3,11 @@
const fs = require('fs');
const NodeRSA = require('node-rsa');
const config = require('config');
const logger = require('./logger').global;
const logger = require('./logger').setup;
const userModel = require('./models/user');
const userPermissionModel = require('./models/user_permission');
const authModel = require('./models/auth');
const debug_mode = process.env.NODE_ENV !== 'production';
module.exports = function () {
return new Promise((resolve, reject) => {
@ -22,6 +23,9 @@ module.exports = function () {
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.
@ -40,12 +44,18 @@ module.exports = function () {
reject(err);
} else {
logger.info('Wrote JWT key pair to config file: ' + filename);
config.util.loadFileConfigs();
resolve();
logger.warn('Restarting interface to apply new configuration');
process.exit(0);
}
});
} else {
// JWT key pair exists
if (debug_mode) {
logger.debug('JWT Keypair already exists');
}
resolve();
}
})
@ -54,49 +64,54 @@ module.exports = function () {
.query()
.select(userModel.raw('COUNT(`id`) as `count`'))
.where('is_deleted', 0)
.first('count')
.then(row => {
if (!row.count) {
// Create a new user and set password
logger.info('Creating a new user: admin@example.com with password: changeme');
.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');
let data = {
is_deleted: 0,
email: 'admin@example.com',
name: 'Administrator',
nickname: 'Admin',
avatar: '',
roles: ['admin']
};
let data = {
is_deleted: 0,
email: 'admin@example.com',
name: 'Administrator',
nickname: 'Admin',
avatar: '',
roles: ['admin']
};
return userModel
return userModel
.query()
.insertAndFetch(data)
.then(user => {
return authModel
.query()
.insertAndFetch(data)
.then(user => {
return authModel
.insert({
user_id: user.id,
type: 'password',
secret: 'changeme',
meta: {}
})
.then(() => {
return userPermissionModel
.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'
});
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');
});
} else if (debug_mode) {
logger.debug('Admin user setup not required');
}
});
};

View File

@ -1,5 +1,5 @@
listen 80;
{% if certificate -%}
listen 443 ssl;
listen 443 ssl{% if http2_support %} http2{% endif %};
{% endif %}
server_name {{ domain_names | join: " " }};
server_name {{ domain_names | join: " " }};

View File

@ -1,7 +1,7 @@
{% include "_header_comment.conf" %}
server {
set $server {{ forward_ip }};
set $server "{{ forward_host }}";
set $port {{ forward_port }};
{% include "_listen.conf" %}
@ -22,6 +22,12 @@ server {
{% include "_forced_ssl.conf" %}
{% if allow_websocket_upgrade == 1 or allow_websocket_upgrade == true %}
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
{% endif %}
# Proxy!
include conf.d/include/proxy.conf;
}

View File

@ -16,9 +16,9 @@ server {
{% include "_forced_ssl.conf" %}
{% if preserve_path == 1 or preserve_path == true %}
return 301 $scheme://{{ forward_domain_name }}$request_uri$request_uri;
{% else %}
return 301 $scheme://{{ forward_domain_name }}$request_uri;
{% else %}
return 301 $scheme://{{ forward_domain_name }};
{% endif %}
}
}

View File

@ -5,5 +5,5 @@
<span class="loader"></span>
</div>
<script type="text/javascript" src="/js/main.js?v=<%= version %>"></script>
<script type="text/javascript" src="/js/main.bundle.js?v=<%= version %>"></script>
<%- include partials/footer.ejs %>

View File

@ -5,5 +5,5 @@
<span class="loader"></span>
</div>
<script type="text/javascript" src="/js/login.js?v=<%= version %>"></script>
<script type="text/javascript" src="/js/login.bundle.js?v=<%= version %>"></script>
<%- include partials/footer.ejs %>

View File

@ -36,7 +36,7 @@
</select>
</div>
</div>
<div class="col-sm-12 col-md-12">
<div class="col-sm-6 col-md-6">
<div class="form-group">
<label class="custom-switch">
<input type="checkbox" class="custom-switch-input" name="ssl_forced" value="1"<%- ssl_forced ? ' checked' : '' %><%- certificate_id ? '' : ' disabled' %>>
@ -45,6 +45,15 @@
</label>
</div>
</div>
<div class="col-sm-6 col-md-6">
<div class="form-group">
<label class="custom-switch">
<input type="checkbox" class="custom-switch-input" name="http2_support" value="1"<%- http2_support ? ' checked' : '' %><%- certificate_id ? '' : ' disabled' %>>
<span class="custom-switch-indicator"></span>
<span class="custom-switch-description"><%- i18n('all-hosts', 'http2-support') %></span>
</label>
</div>
</div>
<!-- Lets encrypt -->
<div class="col-sm-12 col-md-12 letsencrypt">

View File

@ -22,6 +22,7 @@ module.exports = Mn.View.extend({
save: 'button.save',
certificate_select: 'select[name="certificate_id"]',
ssl_forced: 'input[name="ssl_forced"]',
http2_support: 'input[name="http2_support"]',
letsencrypt: '.letsencrypt'
},
@ -35,7 +36,11 @@ module.exports = Mn.View.extend({
}
let enabled = id === 'new' || parseInt(id, 10) > 0;
this.ui.ssl_forced.prop('disabled', !enabled).parents('.form-group').css('opacity', enabled ? 1 : 0.5);
this.ui.ssl_forced.add(this.ui.http2_support)
.prop('disabled', !enabled)
.parents('.form-group')
.css('opacity', enabled ? 1 : 0.5);
this.ui.http2_support.prop('disabled', !enabled);
},
'click @ui.save': function (e) {
@ -54,6 +59,10 @@ module.exports = Mn.View.extend({
data.ssl_forced = true;
}
if (typeof data.http2_support !== 'undefined') {
data.http2_support = !!data.http2_support;
}
if (typeof data.domain_names === 'string' && data.domain_names) {
data.domain_names = data.domain_names.split(',');
}

View File

@ -6,9 +6,15 @@
<td>
<div>
<% domain_names.map(function(host) {
%>
<span class="tag"><%- host %></span>
<%
if (host.indexOf('*') === -1) {
%>
<span class="tag host-link hover-red" rel="http<%- certificate_id ? 's' : '' %>://<%- host %>"><%- host %></span>
<%
} else {
%>
<span class="tag"><%- host %></span>
<%
}
});
%>
</div>

View File

@ -9,8 +9,9 @@ module.exports = Mn.View.extend({
tagName: 'tr',
ui: {
edit: 'a.edit',
delete: 'a.delete'
edit: 'a.edit',
delete: 'a.delete',
host_link: '.host-link'
},
events: {
@ -22,6 +23,12 @@ module.exports = Mn.View.extend({
'click @ui.delete': function (e) {
e.preventDefault();
App.Controller.showNginxDeadDeleteConfirm(this.model);
},
'click @ui.host_link': function (e) {
e.preventDefault();
let win = window.open($(e.currentTarget).attr('rel'), '_blank');
win.focus();
}
},

View File

@ -22,8 +22,8 @@
</div>
<div class="col-sm-8 col-md-8">
<div class="form-group">
<label class="form-label"><%- i18n('proxy-hosts', 'forward-ip') %><span class="form-required">*</span></label>
<input type="text" name="forward_ip" class="form-control text-monospace" placeholder="000.000.000.000" value="<%- forward_ip %>" autocomplete="off" maxlength="15" required>
<label class="form-label"><%- i18n('proxy-hosts', 'forward-host') %><span class="form-required">*</span></label>
<input type="text" name="forward_host" class="form-control text-monospace" placeholder="" value="<%- forward_host %>" autocomplete="off" maxlength="50" required>
</div>
</div>
<div class="col-sm-4 col-md-4">
@ -50,6 +50,15 @@
</label>
</div>
</div>
<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="custom-switch">
<input type="checkbox" class="custom-switch-input" name="allow_websocket_upgrade" value="1"<%- allow_websocket_upgrade ? ' checked' : '' %>>
<span class="custom-switch-indicator"></span>
<span class="custom-switch-description"><%- i18n('proxy-hosts', 'allow-websocket-upgrade') %></span>
</label>
</div>
</div>
<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="form-label"><%- i18n('proxy-hosts', 'access-list') %></label>
@ -73,7 +82,7 @@
</select>
</div>
</div>
<div class="col-sm-12 col-md-12">
<div class="col-sm-6 col-md-6">
<div class="form-group">
<label class="custom-switch">
<input type="checkbox" class="custom-switch-input" name="ssl_forced" value="1"<%- ssl_forced ? ' checked' : '' %><%- certificate_id ? '' : ' disabled' %>>
@ -82,6 +91,15 @@
</label>
</div>
</div>
<div class="col-sm-6 col-md-6">
<div class="form-group">
<label class="custom-switch">
<input type="checkbox" class="custom-switch-input" name="http2_support" value="1"<%- http2_support ? ' checked' : '' %><%- certificate_id ? '' : ' disabled' %>>
<span class="custom-switch-indicator"></span>
<span class="custom-switch-description"><%- i18n('all-hosts', 'http2-support') %></span>
</label>
</div>
</div>
<!-- Lets encrypt -->
<div class="col-sm-12 col-md-12 letsencrypt">

View File

@ -9,7 +9,6 @@ const accessListItemTemplate = require('./access-list-item.ejs');
const Helpers = require('../../../lib/helpers');
require('jquery-serializejson');
require('jquery-mask-plugin');
require('selectize');
module.exports = Mn.View.extend({
@ -19,13 +18,14 @@ module.exports = Mn.View.extend({
ui: {
form: 'form',
domain_names: 'input[name="domain_names"]',
forward_ip: 'input[name="forward_ip"]',
forward_host: 'input[name="forward_host"]',
buttons: '.modal-footer button',
cancel: 'button.cancel',
save: 'button.save',
certificate_select: 'select[name="certificate_id"]',
access_list_select: 'select[name="access_list_id"]',
ssl_forced: 'input[name="ssl_forced"]',
http2_support: 'input[name="http2_support"]',
letsencrypt: '.letsencrypt'
},
@ -39,7 +39,12 @@ module.exports = Mn.View.extend({
}
let enabled = id === 'new' || parseInt(id, 10) > 0;
this.ui.ssl_forced.prop('disabled', !enabled).parents('.form-group').css('opacity', enabled ? 1 : 0.5);
this.ui.ssl_forced.add(this.ui.http2_support)
.prop('disabled', !enabled)
.parents('.form-group')
.css('opacity', enabled ? 1 : 0.5);
this.ui.http2_support.prop('disabled', !enabled);
},
'click @ui.save': function (e) {
@ -54,14 +59,19 @@ module.exports = Mn.View.extend({
let data = this.ui.form.serializeJSON();
// Manipulate
data.forward_port = parseInt(data.forward_port, 10);
data.block_exploits = !!data.block_exploits;
data.caching_enabled = !!data.caching_enabled;
data.forward_port = parseInt(data.forward_port, 10);
data.block_exploits = !!data.block_exploits;
data.caching_enabled = !!data.caching_enabled;
data.allow_websocket_upgrade = !!data.allow_websocket_upgrade;
if (typeof data.ssl_forced !== 'undefined' && data.ssl_forced === '1') {
data.ssl_forced = true;
}
if (typeof data.http2_support !== 'undefined') {
data.http2_support = !!data.http2_support;
}
if (typeof data.domain_names === 'string' && data.domain_names) {
data.domain_names = data.domain_names.split(',');
}
@ -122,12 +132,6 @@ module.exports = Mn.View.extend({
onRender: function () {
let view = this;
// IP Address
this.ui.forward_ip.mask('099.099.099.099', {
clearIfNotMatch: true,
placeholder: '000.000.000.000'
});
// Domain names
this.ui.domain_names.selectize({
delimiter: ',',

View File

@ -6,9 +6,15 @@
<td>
<div>
<% domain_names.map(function(host) {
%>
<span class="tag"><%- host %></span>
<%
if (host.indexOf('*') === -1) {
%>
<span class="tag host-link hover-green" rel="http<%- certificate_id ? 's' : '' %>://<%- host %>"><%- host %></span>
<%
} else {
%>
<span class="tag"><%- host %></span>
<%
}
});
%>
</div>
@ -17,7 +23,7 @@
</div>
</td>
<td>
<div class="text-monospace"><%- forward_ip %>:<%- forward_port %></div>
<div class="text-monospace"><%- forward_host %>:<%- forward_port %></div>
</td>
<td>
<div><%- certificate && certificate_id ? i18n('ssl', certificate.provider) : i18n('ssl', 'none') %></div>

View File

@ -9,8 +9,9 @@ module.exports = Mn.View.extend({
tagName: 'tr',
ui: {
edit: 'a.edit',
delete: 'a.delete'
edit: 'a.edit',
delete: 'a.delete',
host_link: '.host-link'
},
events: {
@ -22,6 +23,12 @@ module.exports = Mn.View.extend({
'click @ui.delete': function (e) {
e.preventDefault();
App.Controller.showNginxProxyDeleteConfirm(this.model);
},
'click @ui.host_link': function (e) {
e.preventDefault();
let win = window.open($(e.currentTarget).attr('rel'), '_blank');
win.focus();
}
},

View File

@ -60,7 +60,7 @@
</select>
</div>
</div>
<div class="col-sm-12 col-md-12">
<div class="col-sm-6 col-md-6">
<div class="form-group">
<label class="custom-switch">
<input type="checkbox" class="custom-switch-input" name="ssl_forced" value="1"<%- ssl_forced ? ' checked' : '' %><%- certificate_id ? '' : ' disabled' %>>
@ -69,6 +69,15 @@
</label>
</div>
</div>
<div class="col-sm-6 col-md-6">
<div class="form-group">
<label class="custom-switch">
<input type="checkbox" class="custom-switch-input" name="http2_support" value="1"<%- http2_support ? ' checked' : '' %><%- certificate_id ? '' : ' disabled' %>>
<span class="custom-switch-indicator"></span>
<span class="custom-switch-description"><%- i18n('all-hosts', 'http2-support') %></span>
</label>
</div>
</div>
<!-- Lets encrypt -->
<div class="col-sm-12 col-md-12 letsencrypt">

View File

@ -22,6 +22,7 @@ module.exports = Mn.View.extend({
save: 'button.save',
certificate_select: 'select[name="certificate_id"]',
ssl_forced: 'input[name="ssl_forced"]',
http2_support: 'input[name="http2_support"]',
letsencrypt: '.letsencrypt'
},
@ -35,7 +36,11 @@ module.exports = Mn.View.extend({
}
let enabled = id === 'new' || parseInt(id, 10) > 0;
this.ui.ssl_forced.prop('disabled', !enabled).parents('.form-group').css('opacity', enabled ? 1 : 0.5);
this.ui.ssl_forced.add(this.ui.http2_support)
.prop('disabled', !enabled)
.parents('.form-group')
.css('opacity', enabled ? 1 : 0.5);
this.ui.http2_support.prop('disabled', !enabled);
},
'click @ui.save': function (e) {
@ -57,6 +62,10 @@ module.exports = Mn.View.extend({
data.ssl_forced = true;
}
if (typeof data.http2_support !== 'undefined') {
data.http2_support = !!data.http2_support;
}
if (typeof data.domain_names === 'string' && data.domain_names) {
data.domain_names = data.domain_names.split(',');
}

View File

@ -6,9 +6,15 @@
<td>
<div>
<% domain_names.map(function(host) {
%>
<span class="tag"><%- host %></span>
<%
if (host.indexOf('*') === -1) {
%>
<span class="tag host-link hover-yellow" rel="http<%- certificate_id ? 's' : '' %>://<%- host %>"><%- host %></span>
<%
} else {
%>
<span class="tag"><%- host %></span>
<%
}
});
%>
</div>

View File

@ -9,8 +9,9 @@ module.exports = Mn.View.extend({
tagName: 'tr',
ui: {
edit: 'a.edit',
delete: 'a.delete'
edit: 'a.edit',
delete: 'a.delete',
host_link: '.host-link'
},
events: {
@ -22,6 +23,12 @@ module.exports = Mn.View.extend({
'click @ui.delete': function (e) {
e.preventDefault();
App.Controller.showNginxRedirectionDeleteConfirm(this.model);
},
'click @ui.host_link': function (e) {
e.preventDefault();
let win = window.open($(e.currentTarget).attr('rel'), '_blank');
win.focus();
}
},

View File

@ -65,6 +65,7 @@
"details": "Details",
"enable-ssl": "Enable SSL",
"force-ssl": "Force SSL",
"http2-support": "HTTP/2 Support",
"domain-names": "Domain Names",
"cert-provider": "Certificate Provider",
"block-exploits": "Block Common Exploits",
@ -92,13 +93,14 @@
"empty": "There are no Proxy Hosts",
"add": "Add Proxy Host",
"form-title": "{id, select, undefined{New} other{Edit}} Proxy Host",
"forward-ip": "Forward IP",
"forward-host": "Forward Hostname / IP",
"forward-port": "Forward Port",
"delete": "Delete Proxy Host",
"delete-confirm": "Are you sure you want to delete the Proxy host for: <strong>{domains}</strong>?",
"help-title": "What is a Proxy Host?",
"help-content": "A Proxy Host is the incoming endpoint for a web service that you want to forward.\nIt provides optional SSL termination for your service that might not have SSL support built in.\nProxy Hosts are the most common use for the Nginx Proxy Manager.",
"access-list": "Access List"
"access-list": "Access List",
"allow-websocket-upgrade": "Allow Websocket HTTP Upgrades"
},
"redirection-hosts": {
"title": "Redirection Hosts",

View File

@ -13,6 +13,7 @@ const model = Backbone.Model.extend({
domain_names: [],
certificate_id: 0,
ssl_forced: false,
http2_support: false,
meta: {},
advanced_config: '',
// The following are expansions:

View File

@ -7,23 +7,25 @@ const model = Backbone.Model.extend({
defaults: function () {
return {
id: undefined,
created_on: null,
modified_on: null,
domain_names: [],
forward_ip: '',
forward_port: null,
access_list_id: 0,
certificate_id: 0,
ssl_forced: false,
caching_enabled: false,
block_exploits: false,
advanced_config: '',
meta: {},
id: undefined,
created_on: null,
modified_on: null,
domain_names: [],
forward_host: '',
forward_port: null,
access_list_id: 0,
certificate_id: 0,
ssl_forced: false,
caching_enabled: false,
allow_websocket_upgrade: false,
block_exploits: false,
http2_support: false,
advanced_config: '',
meta: {},
// The following are expansions:
owner: null,
access_list: null,
certificate: null
owner: null,
access_list: null,
certificate: null
};
}
});

View File

@ -16,6 +16,7 @@ const model = Backbone.Model.extend({
certificate_id: 0,
ssl_forced: false,
block_exploits: false,
http2_support: false,
advanced_config: '',
meta: {},
// The following are expansions:

View File

@ -3,6 +3,18 @@ $yellow: #f1c40f;
$blue: #467fcf;
$pink: #f66d9b;
.tag.hover-green:hover, .tag.hover-green:active, .tag.hover-green:focus {
background-color: #5eba00;
cursor: pointer;
color: #fff;
}
.tag.hover-red:hover, .tag.hover-red:active, .tag.hover-red:focus {
background-color: #cd201f;
cursor: pointer;
color: #fff;
}
/* For Card bodies where I don't want padding */
.card-body.no-padding {
padding: 0;
@ -28,6 +40,12 @@ $pink: #f66d9b;
border-color: $teal;
}
.tag.hover-teal:hover, .tag.hover-teal:active, .tag.hover-teal:focus {
background-color: $teal;
color: #fff;
cursor: pointer;
}
/* Yellow Outline Buttons */
.btn-outline-yellow {
color: $yellow;
@ -48,6 +66,12 @@ $pink: #f66d9b;
border-color: $yellow;
}
.tag.hover-yellow:hover, .tag.hover-yellow:active, .tag.hover-yellow:focus {
background-color: $yellow;
cursor: pointer;
color: #fff;
}
/* Blue Outline Buttons */
.btn-outline-blue {
color: $blue;
@ -68,6 +92,12 @@ $pink: #f66d9b;
border-color: $blue;
}
.tag.hover-blue:hover, .tag.hover-blue:active, .tag.hover-blue:focus {
background-color: $blue;
cursor: pointer;
color: #fff;
}
/* Pink Outline Buttons */
.btn-outline-pink {
color: $pink;
@ -88,6 +118,11 @@ $pink: #f66d9b;
border-color: $pink;
}
.tag.hover-pink:hover, .tag.hover-pink:active, .tag.hover-pink:focus {
background-color: $pink;
cursor: pointer;
}
/* dimmer */
.dimmer .loader {

View File

@ -10,9 +10,10 @@ module.exports = {
login: './src/frontend/js/login.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].js',
publicPath: '/'
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].bundle.js',
chunkFilename: 'js/[name].bundle.[id].js',
publicPath: '/'
},
resolve: {
alias: {
@ -108,41 +109,6 @@ module.exports = {
to: 'images',
toType: 'dir',
context: '/app'
}]),
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1, // Must be greater than or equal to one
minChunkSize: 999999999
})
],
/*
optimization: {
splitChunks: {
chunks (chunk) {
// exclude `my-excluded-chunk`
return false;
},
minSize: 999999999,
minChunks: 1,
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
},
*/
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 8080,
disableHostCheck: true,
host: '0.0.0.0'
}
}])
]
};