Added Stream forwarding support
This commit is contained in:
parent
d2130a24a1
commit
b57d1e5a66
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "nginx-proxy-manager",
|
||||
"version": "1.0.1",
|
||||
"version": "1.1.0",
|
||||
"description": "Nginx proxt with built in Web based management",
|
||||
"main": "src/backend/index.js",
|
||||
"dependencies": {
|
||||
|
@ -44,19 +44,31 @@ const internalHost = {
|
||||
*/
|
||||
create: payload => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Enforce lowercase hostnames
|
||||
payload.hostname = payload.hostname.toLowerCase();
|
||||
let existing_host = false;
|
||||
|
||||
// 1. Check that the hostname doesn't already exist
|
||||
let existing_host = db.hosts.findOne({hostname: payload.hostname});
|
||||
if (payload.type === 'stream') {
|
||||
// Check that the incoming port doesn't already exist
|
||||
existing_host = db.hosts.findOne({incoming_port: payload.incoming_port});
|
||||
|
||||
if (payload.incoming_port === 80 || payload.incoming_port === 81 || payload.incoming_port === 443) {
|
||||
reject(new error.ConfigurationError('Port ' + payload.incoming_port + ' is reserved'));
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
payload.hostname = payload.hostname.toLowerCase();
|
||||
|
||||
// Check that the hostname doesn't already exist
|
||||
existing_host = db.hosts.findOne({hostname: payload.hostname});
|
||||
}
|
||||
|
||||
if (existing_host) {
|
||||
reject(new error.ValidationError('Hostname already exists'));
|
||||
} else {
|
||||
// 2. Add host to db
|
||||
// Add host to db
|
||||
let host = db.hosts.save(payload);
|
||||
|
||||
// 3. Fire the config generation for this host
|
||||
// Fire the config generation for this host
|
||||
internalHost.configure(host, true)
|
||||
.then((/*result*/) => {
|
||||
resolve(host);
|
||||
@ -98,10 +110,16 @@ const internalHost = {
|
||||
}
|
||||
|
||||
// Check that the hostname doesn't already exist
|
||||
let other_host = db.hosts.findOne({hostname: payload.hostname});
|
||||
let other_host = false;
|
||||
|
||||
if (typeof payload.incoming_port !== 'undefined') {
|
||||
other_host = db.hosts.findOne({incoming_port: payload.incoming_port});
|
||||
} else {
|
||||
other_host = db.hosts.findOne({hostname: payload.hostname});
|
||||
}
|
||||
|
||||
if (other_host && other_host._id !== id) {
|
||||
reject(new error.ValidationError('Hostname already exists'));
|
||||
reject(new error.ValidationError((other_host.type === 'stream' ? 'Source Stream Port' : 'Hostname') + ' already exists'));
|
||||
} else {
|
||||
// 2. Update host
|
||||
db.hosts.update({_id: id}, payload, {multi: false, upsert: false});
|
||||
@ -126,17 +144,22 @@ const internalHost = {
|
||||
return data;
|
||||
})
|
||||
.then(data => {
|
||||
if (
|
||||
(data.original.ssl && !data.updated.ssl) || // ssl was enabled and is now disabled
|
||||
(data.original.ssl && data.original.hostname !== data.updated.hostname) // hostname was changed for a previously ssl-enabled host
|
||||
) {
|
||||
// SSL was turned off or hostname for ssl has changed so we should remove certs for the original
|
||||
return internalSsl.deleteCerts(data.original)
|
||||
.then(() => {
|
||||
db.hosts.update({_id: data.updated._id}, {ssl_expires: 0}, {multi: false, upsert: false});
|
||||
data.updated.ssl_expires = 0;
|
||||
return data;
|
||||
});
|
||||
if (data.updated.type !== 'stream') {
|
||||
if (
|
||||
(data.original.ssl && !data.updated.ssl) || // ssl was enabled and is now disabled
|
||||
(data.original.ssl && data.original.hostname !== data.updated.hostname) // hostname was changed for a previously ssl-enabled host
|
||||
) {
|
||||
// SSL was turned off or hostname for ssl has changed so we should remove certs for the original
|
||||
return internalSsl.deleteCerts(data.original)
|
||||
.then(() => {
|
||||
db.hosts.update({_id: data.updated._id}, {ssl_expires: 0}, {
|
||||
multi: false,
|
||||
upsert: false
|
||||
});
|
||||
data.updated.ssl_expires = 0;
|
||||
return data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
|
@ -32,6 +32,10 @@ const internalNginx = {
|
||||
* @returns {String}
|
||||
*/
|
||||
getConfigName: host => {
|
||||
if (host.type === 'stream') {
|
||||
return '/config/nginx/stream/' + host.incoming_port + '.conf';
|
||||
}
|
||||
|
||||
return '/config/nginx/' + host.hostname + '.conf';
|
||||
},
|
||||
|
||||
|
@ -12,7 +12,7 @@
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"pattern": "^(proxy|redirection|404)$"
|
||||
"pattern": "^(proxy|redirection|404|stream)$"
|
||||
},
|
||||
"hostname": {
|
||||
"$ref": "../definitions.json#/definitions/hostname"
|
||||
@ -59,6 +59,17 @@
|
||||
"access_list": {
|
||||
"type": "object",
|
||||
"readonly": true
|
||||
},
|
||||
"incoming_port": {
|
||||
"type": "integer",
|
||||
"minumum": 1,
|
||||
"maxiumum": 65535
|
||||
},
|
||||
"protocols": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"links": [
|
||||
@ -86,8 +97,7 @@
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"hostname"
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
@ -125,6 +135,12 @@
|
||||
},
|
||||
"access_list_id": {
|
||||
"$ref": "#/definitions/access_list_id"
|
||||
},
|
||||
"incoming_port": {
|
||||
"$ref": "#/definitions/incoming_port"
|
||||
},
|
||||
"protocols": {
|
||||
"$ref": "#/definitions/protocols"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -181,6 +197,12 @@
|
||||
},
|
||||
"access_list_id": {
|
||||
"$ref": "#/definitions/access_list_id"
|
||||
},
|
||||
"incoming_port": {
|
||||
"$ref": "#/definitions/incoming_port"
|
||||
},
|
||||
"protocols": {
|
||||
"$ref": "#/definitions/protocols"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -247,6 +269,12 @@
|
||||
},
|
||||
"advanced": {
|
||||
"$ref": "#/definitions/advanced"
|
||||
},
|
||||
"incoming_port": {
|
||||
"$ref": "#/definitions/incoming_port"
|
||||
},
|
||||
"protocols": {
|
||||
"$ref": "#/definitions/protocols"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
11
manager/src/backend/templates/stream.conf.ejs
Normal file
11
manager/src/backend/templates/stream.conf.ejs
Normal file
@ -0,0 +1,11 @@
|
||||
# <%- incoming_port %> - <%- protocols.join(',').toUpperCase() %>
|
||||
<%
|
||||
protocols.forEach(function (protocol) {
|
||||
%>
|
||||
server {
|
||||
listen <%- incoming_port %> <%- protocol === 'tcp' ? '' : protocol %>;
|
||||
proxy_pass <%- forward_server %>:<%- forward_port %>;
|
||||
}
|
||||
<%
|
||||
});
|
||||
%>
|
@ -86,6 +86,17 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Show Stream Host Form
|
||||
*
|
||||
* @param model
|
||||
*/
|
||||
showStreamHostForm: function (model) {
|
||||
require(['./main', './host/stream_form'], function (App, View) {
|
||||
App.UI.showModalDialog(new View({model: model}));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Show Delete Host Confirmation
|
||||
*
|
||||
|
@ -1,6 +1,6 @@
|
||||
<table class="table table-condensed table-striped">
|
||||
<thead>
|
||||
<th>Hostname</th>
|
||||
<th>Source</th>
|
||||
<th>Destination</th>
|
||||
<th>SSL</th>
|
||||
<th>Access List</th>
|
||||
@ -13,6 +13,7 @@
|
||||
<li><a href="#" class="new-proxy">Proxy Host</a></li>
|
||||
<li><a href="#" class="new-redirection">Redirection Host</a></li>
|
||||
<li><a href="#" class="new-404">404 Host</a></li>
|
||||
<li><a href="#" class="new-stream">Stream Host</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</th>
|
||||
|
@ -28,7 +28,8 @@ module.exports = Mn.View.extend({
|
||||
ui: {
|
||||
new_proxy: 'th .new-proxy',
|
||||
new_redirection: 'th .new-redirection',
|
||||
new_404: 'th .new-404'
|
||||
new_404: 'th .new-404',
|
||||
new_stream: 'th .new-stream'
|
||||
},
|
||||
|
||||
events: {
|
||||
@ -45,6 +46,11 @@ module.exports = Mn.View.extend({
|
||||
'click @ui.new_404': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.show404HostForm(new HostModel.Model);
|
||||
},
|
||||
|
||||
'click @ui.new_stream': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.showStreamHostForm(new HostModel.Model);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1,7 +1,14 @@
|
||||
<td><a href="<%- ssl ? 'https' : 'http' %>://<%- hostname %>" target="_blank"><%- hostname %></a></td>
|
||||
<td>
|
||||
<% if (type === 'stream') { %>
|
||||
<%- incoming_port %>
|
||||
<%- protocols.join(', ').toUpperCase() %>
|
||||
<% } else { %>
|
||||
<a href="<%- ssl ? 'https' : 'http' %>://<%- hostname %>" target="_blank"><%- hostname %></a>
|
||||
<% } %>
|
||||
</td>
|
||||
<td>
|
||||
<span class="monospace">
|
||||
<% if (type === 'proxy') { %>
|
||||
<% if (type === 'proxy' || type === 'stream') { %>
|
||||
<%- forward_server %>:<%- forward_port %>
|
||||
<% } else if (type === 'redirection') { %>
|
||||
<%- forward_host %>
|
||||
@ -11,19 +18,27 @@
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<% if (ssl && force_ssl) { %>
|
||||
Forced
|
||||
<% } else if (ssl) { %>
|
||||
Enabled
|
||||
<% if (type === 'stream') { %>
|
||||
-
|
||||
<% } else { %>
|
||||
No
|
||||
<% if (ssl && force_ssl) { %>
|
||||
Forced
|
||||
<% } else if (ssl) { %>
|
||||
Enabled
|
||||
<% } else { %>
|
||||
No
|
||||
<% } %>
|
||||
<% } %>
|
||||
</td>
|
||||
<td>
|
||||
<% if (access_list) { %>
|
||||
<a href="#" class="access_list"><%- access_list.name %></a>
|
||||
<% if (type === 'stream') { %>
|
||||
-
|
||||
<% } else { %>
|
||||
<em>None</em>
|
||||
<% if (access_list) { %>
|
||||
<a href="#" class="access_list"><%- access_list.name %></a>
|
||||
<% } else { %>
|
||||
<em>None</em>
|
||||
<% } %>
|
||||
<% } %>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
@ -31,7 +46,7 @@
|
||||
<button type="button" class="btn btn-default btn-xs renew" title="Renew SSL"><i class="fa fa-shield" aria-hidden="true"></i></button>
|
||||
<% } %>
|
||||
<button type="button" class="btn btn-default btn-xs reconfigure" title="Reconfigure Nginx"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||
<button type="button" class="btn btn-default btn-xs advanced" title="Advanced Configuration"><i class="fa fa-code" aria-hidden="true"></i></button>
|
||||
<button type="button" class="btn btn-default btn-xs advanced" title="Advanced Configuration"<%- type === 'stream' ? ' disabled' : '' %>><i class="fa fa-code" aria-hidden="true"></i></button>
|
||||
<button type="button" class="btn btn-warning btn-xs edit" title="Edit"><i class="fa fa-pencil" aria-hidden="true"></i></button>
|
||||
<button type="button" class="btn btn-danger btn-xs delete" title="Delete"><i class="fa fa-times" aria-hidden="true"></i></button>
|
||||
</td>
|
||||
|
@ -32,6 +32,9 @@ module.exports = Mn.View.extend({
|
||||
case '404':
|
||||
Controller.show404HostForm(this.model);
|
||||
break;
|
||||
case 'stream':
|
||||
Controller.showStreamHostForm(this.model);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
|
55
manager/src/frontend/js/app/host/stream_form.ejs
Normal file
55
manager/src/frontend/js/app/host/stream_form.ejs
Normal file
@ -0,0 +1,55 @@
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form class="form-horizontal">
|
||||
<div class="modal-header text-left">
|
||||
<h4 class="modal-title"><% if (typeof _id !== 'undefined') { %>Edit<% } else { %>Create<% } %> Stream Host</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-warning" role="alert">
|
||||
A Stream Host will forward a TCP/UDP connection directly to a another server on your network. <strong>There is no authentication.</strong>
|
||||
Note you will also have to open the incoming port in your docker configuration for this to work.
|
||||
<br>
|
||||
<br>
|
||||
You will not be able to use port <strong>80</strong>, <strong>81</strong> or <strong>443</strong> or any other previously configured Stream Host incoming port.
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">Incoming Port</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="number" minimum="1" maximum="65535" class="form-control" placeholder="" name="incoming_port" value="<%- incoming_port ? incoming_port : '' %>" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">Forwarding IP</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" class="form-control" placeholder="192.168.0.1" name="forward_server" value="<%- forward_server %>" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">Forwarding Port</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="number" minimum="1" maximum="65535" class="form-control" placeholder="" name="forward_port" value="<%- typeof _id === 'undefined' ? '' : forward_port %>" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-4 col-sm-8">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="protocols[]" value="tcp"<%- typeof _id === 'undefined' || hasStreamProtocol('tcp') ? ' checked' : '' %>> TCP Forwarding
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="protocols[]" value="udp"<%- hasStreamProtocol('udp') ? ' checked' : '' %>> UDP Forwarding
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-success save">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
63
manager/src/frontend/js/app/host/stream_form.js
Normal file
63
manager/src/frontend/js/app/host/stream_form.js
Normal file
@ -0,0 +1,63 @@
|
||||
'use strict';
|
||||
|
||||
import Mn from 'backbone.marionette';
|
||||
|
||||
const _ = require('lodash');
|
||||
const template = require('./stream_form.ejs');
|
||||
const Controller = require('../controller');
|
||||
const Api = require('../api');
|
||||
const App = require('../main');
|
||||
|
||||
require('jquery-serializejson');
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
template: template,
|
||||
|
||||
ui: {
|
||||
form: 'form',
|
||||
buttons: 'form button'
|
||||
},
|
||||
|
||||
events: {
|
||||
'submit @ui.form': function (e) {
|
||||
e.preventDefault();
|
||||
let data = _.extend({}, this.ui.form.serializeJSON());
|
||||
|
||||
data.type = 'stream';
|
||||
|
||||
// Ports are integers
|
||||
data.incoming_port = parseInt(data.incoming_port, 10);
|
||||
data.forward_port = parseInt(data.forward_port, 10);
|
||||
|
||||
if (typeof data.protocols === 'undefined' || !data.protocols.length) {
|
||||
alert('You must select one or more Protocols');
|
||||
return;
|
||||
}
|
||||
|
||||
this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
|
||||
let method = Api.Hosts.create;
|
||||
|
||||
if (this.model.get('_id')) {
|
||||
// edit
|
||||
method = Api.Hosts.update;
|
||||
data._id = this.model.get('_id');
|
||||
}
|
||||
|
||||
method(data)
|
||||
.then((/*result*/) => {
|
||||
App.UI.closeModal();
|
||||
Controller.showDashboard();
|
||||
})
|
||||
.catch((err) => {
|
||||
alert(err.message);
|
||||
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
templateContext: {
|
||||
hasStreamProtocol: function (protocol) {
|
||||
return this.protocols.indexOf(protocol) !== -1;
|
||||
}
|
||||
}
|
||||
});
|
@ -20,7 +20,9 @@ const model = Backbone.Model.extend({
|
||||
letsencrypt_email: '',
|
||||
accept_tos: false,
|
||||
access_list_id: '',
|
||||
advanced: ''
|
||||
advanced: '',
|
||||
incoming_port: 0,
|
||||
protocols: []
|
||||
};
|
||||
}
|
||||
});
|
||||
|
@ -53,3 +53,7 @@ http {
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
include /config/nginx/*.conf;
|
||||
}
|
||||
|
||||
stream {
|
||||
include /config/nginx/stream/*.conf;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
|
||||
mkdir -p /tmp/nginx /config/{nginx,logs,access} /var/lib/nginx/cache/{public,private}
|
||||
mkdir -p /tmp/nginx /config/{nginx,logs,access} /config/nginx/stream /var/lib/nginx/cache/{public,private}
|
||||
chown root /tmp/nginx
|
||||
exec nginx
|
||||
|
Loading…
Reference in New Issue
Block a user