Эх сурвалжийг харах

Merge pull request #2703 from NginxProxyManager/develop

v2.9.21
jc21 2 жил өмнө
parent
commit
095bc8f676
56 өөрчлөгдсөн 941 нэмэгдсэн , 444 устгасан
  1. 1 1
      .version
  2. 18 23
      backend/internal/access-list.js
  3. 2 2
      backend/internal/audit-log.js
  4. 16 20
      backend/internal/certificate.js
  5. 15 18
      backend/internal/dead-host.js
  6. 2 5
      backend/internal/ip_ranges.js
  7. 77 76
      backend/internal/nginx.js
  8. 18 19
      backend/internal/proxy-host.js
  9. 17 20
      backend/internal/redirection-host.js
  10. 17 19
      backend/internal/stream.js
  11. 16 21
      backend/internal/user.js
  12. 2 2
      backend/lib/access.js
  13. 69 5
      backend/lib/utils.js
  14. 0 16
      backend/models/access_list.js
  15. 0 1
      backend/models/access_list_auth.js
  16. 0 5
      backend/models/access_list_client.js
  17. 0 3
      backend/models/audit-log.js
  18. 0 3
      backend/models/auth.js
  19. 0 1
      backend/models/certificate.js
  20. 0 2
      backend/models/dead_host.js
  21. 3 3
      backend/models/now_helper.js
  22. 0 3
      backend/models/proxy_host.js
  23. 1 2
      backend/models/redirection_host.js
  24. 0 1
      backend/models/stream.js
  25. 0 2
      backend/models/token.js
  26. 0 3
      backend/models/user.js
  27. 5 5
      backend/package.json
  28. 25 0
      backend/templates/_access.conf
  29. 1 22
      backend/templates/_location.conf
  30. 1 21
      backend/templates/proxy_host.conf
  31. 535 54
      backend/yarn.lock
  32. 1 1
      docker/Dockerfile
  33. 3 4
      docker/dev/Dockerfile
  34. 0 0
      docker/rootfs/etc/s6-overlay/s6-rc.d/backend/dependencies.d/prepare
  35. 4 4
      docker/rootfs/etc/s6-overlay/s6-rc.d/backend/run
  36. 1 0
      docker/rootfs/etc/s6-overlay/s6-rc.d/backend/type
  37. 0 0
      docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/dependencies.d/prepare
  38. 4 1
      docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/run
  39. 1 0
      docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/type
  40. 0 0
      docker/rootfs/etc/s6-overlay/s6-rc.d/nginx/dependencies.d/prepare
  41. 7 0
      docker/rootfs/etc/s6-overlay/s6-rc.d/nginx/run
  42. 1 0
      docker/rootfs/etc/s6-overlay/s6-rc.d/nginx/type
  43. 0 0
      docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/dependencies.d/base
  44. 63 0
      docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/script.sh
  45. 1 0
      docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/type
  46. 2 0
      docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/up
  47. 0 0
      docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/backend
  48. 0 0
      docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/frontend
  49. 0 0
      docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/nginx
  50. 0 0
      docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/prepare
  51. 0 6
      docker/rootfs/etc/services.d/frontend/finish
  52. 0 3
      docker/rootfs/etc/services.d/manager/finish
  53. 0 1
      docker/rootfs/etc/services.d/nginx/finish
  54. 0 38
      docker/rootfs/etc/services.d/nginx/run
  55. 9 5
      docker/scripts/install-s6
  56. 3 3
      global/certbot-dns-plugins.js

+ 1 - 1
.version

@@ -1 +1 @@
-2.9.20
+2.9.21

+ 18 - 23
backend/internal/access-list.js

@@ -3,13 +3,13 @@ const fs                    = require('fs');
 const batchflow             = require('batchflow');
 const logger                = require('../logger').access;
 const error                 = require('../lib/error');
+const utils                 = require('../lib/utils');
 const accessListModel       = require('../models/access_list');
 const accessListAuthModel   = require('../models/access_list_auth');
 const accessListClientModel = require('../models/access_list_client');
 const proxyHostModel        = require('../models/proxy_host');
 const internalAuditLog      = require('./audit-log');
 const internalNginx         = require('./nginx');
-const utils                 = require('../lib/utils');
 
 function omissions () {
 	return ['is_deleted'];
@@ -27,13 +27,13 @@ const internalAccessList = {
 			.then((/*access_data*/) => {
 				return accessListModel
 					.query()
-					.omit(omissions())
 					.insertAndFetch({
 						name:          data.name,
 						satisfy_any:   data.satisfy_any,
 						pass_auth:     data.pass_auth,
 						owner_user_id: access.token.getUserId(1)
-					});
+					})
+					.then(utils.omitRow(omissions()));
 			})
 			.then((row) => {
 				data.id = row.id;
@@ -256,35 +256,31 @@ const internalAccessList = {
 					.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0')
 					.where('access_list.is_deleted', 0)
 					.andWhere('access_list.id', data.id)
-					.allowEager('[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]')
-					.omit(['access_list.is_deleted'])
+					.allowGraph('[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]')
 					.first();
 
 				if (access_data.permission_visibility !== 'all') {
 					query.andWhere('access_list.owner_user_id', access.token.getUserId(1));
 				}
 
-				// Custom omissions
-				if (typeof data.omit !== 'undefined' && data.omit !== null) {
-					query.omit(data.omit);
-				}
-
 				if (typeof data.expand !== 'undefined' && data.expand !== null) {
-					query.eager('[' + data.expand.join(', ') + ']');
+					query.withGraphFetched('[' + data.expand.join(', ') + ']');
 				}
 
-				return query;
+				return query.then(utils.omitRow(omissions()));
 			})
 			.then((row) => {
-				if (row) {
-					if (!skip_masking && typeof row.items !== 'undefined' && row.items) {
-						row = internalAccessList.maskItems(row);
-					}
-
-					return _.omit(row, omissions());
-				} else {
+				if (!row) {
 					throw new error.ItemNotFoundError(data.id);
 				}
+				if (!skip_masking && typeof row.items !== 'undefined' && row.items) {
+					row = internalAccessList.maskItems(row);
+				}
+				// Custom omissions
+				if (typeof data.omit !== 'undefined' && data.omit !== null) {
+					row = _.omit(row, data.omit);
+				}
+				return row;
 			});
 	},
 
@@ -381,8 +377,7 @@ const internalAccessList = {
 					.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0')
 					.where('access_list.is_deleted', 0)
 					.groupBy('access_list.id')
-					.omit(['access_list.is_deleted'])
-					.allowEager('[owner,items,clients]')
+					.allowGraph('[owner,items,clients]')
 					.orderBy('access_list.name', 'ASC');
 
 				if (access_data.permission_visibility !== 'all') {
@@ -397,10 +392,10 @@ const internalAccessList = {
 				}
 
 				if (typeof expand !== 'undefined' && expand !== null) {
-					query.eager('[' + expand.join(', ') + ']');
+					query.withGraphFetched('[' + expand.join(', ') + ']');
 				}
 
-				return query;
+				return query.then(utils.omitRows(omissions()));
 			})
 			.then((rows) => {
 				if (rows) {

+ 2 - 2
backend/internal/audit-log.js

@@ -19,7 +19,7 @@ const internalAuditLog = {
 					.orderBy('created_on', 'DESC')
 					.orderBy('id', 'DESC')
 					.limit(100)
-					.allowEager('[user]');
+					.allowGraph('[user]');
 
 				// Query is used for searching
 				if (typeof search_query === 'string') {
@@ -29,7 +29,7 @@ const internalAuditLog = {
 				}
 
 				if (typeof expand !== 'undefined' && expand !== null) {
-					query.eager('[' + expand.join(', ') + ']');
+					query.withGraphFetched('[' + expand.join(', ') + ']');
 				}
 
 				return query;

+ 16 - 20
backend/internal/certificate.js

@@ -121,8 +121,8 @@ const internalCertificate = {
 
 				return certificateModel
 					.query()
-					.omit(omissions())
-					.insertAndFetch(data);
+					.insertAndFetch(data)
+					.then(utils.omitRow(omissions()));
 			})
 			.then((certificate) => {
 				if (certificate.provider === 'letsencrypt') {
@@ -269,8 +269,8 @@ const internalCertificate = {
 
 				return certificateModel
 					.query()
-					.omit(omissions())
 					.patchAndFetchById(row.id, data)
+					.then(utils.omitRow(omissions()))
 					.then((saved_row) => {
 						saved_row.meta = internalCertificate.cleanMeta(saved_row.meta);
 						data.meta      = internalCertificate.cleanMeta(data.meta);
@@ -288,7 +288,7 @@ const internalCertificate = {
 							meta:        _.omit(data, ['expires_on']) // this prevents json circular reference because expires_on might be raw
 						})
 							.then(() => {
-								return _.omit(saved_row, omissions());
+								return saved_row;
 							});
 					});
 			});
@@ -313,30 +313,28 @@ const internalCertificate = {
 					.query()
 					.where('is_deleted', 0)
 					.andWhere('id', data.id)
-					.allowEager('[owner]')
+					.allowGraph('[owner]')
 					.first();
 
 				if (access_data.permission_visibility !== 'all') {
 					query.andWhere('owner_user_id', access.token.getUserId(1));
 				}
 
-				// Custom omissions
-				if (typeof data.omit !== 'undefined' && data.omit !== null) {
-					query.omit(data.omit);
-				}
-
 				if (typeof data.expand !== 'undefined' && data.expand !== null) {
-					query.eager('[' + data.expand.join(', ') + ']');
+					query.withGraphFetched('[' + data.expand.join(', ') + ']');
 				}
 
-				return query;
+				return query.then(utils.omitRow(omissions()));
 			})
 			.then((row) => {
-				if (row) {
-					return _.omit(row, omissions());
-				} else {
+				if (!row) {
 					throw new error.ItemNotFoundError(data.id);
 				}
+				// Custom omissions
+				if (typeof data.omit !== 'undefined' && data.omit !== null) {
+					row = _.omit(row, data.omit);
+				}
+				return row;
 			});
 	},
 
@@ -466,8 +464,7 @@ const internalCertificate = {
 					.query()
 					.where('is_deleted', 0)
 					.groupBy('id')
-					.omit(['is_deleted'])
-					.allowEager('[owner]')
+					.allowGraph('[owner]')
 					.orderBy('nice_name', 'ASC');
 
 				if (access_data.permission_visibility !== 'all') {
@@ -482,10 +479,10 @@ const internalCertificate = {
 				}
 
 				if (typeof expand !== 'undefined' && expand !== null) {
-					query.eager('[' + expand.join(', ') + ']');
+					query.withGraphFetched('[' + expand.join(', ') + ']');
 				}
 
-				return query;
+				return query.then(utils.omitRows(omissions()));
 			});
 	},
 
@@ -662,7 +659,6 @@ const internalCertificate = {
 							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);
 							});

+ 15 - 18
backend/internal/dead-host.js

@@ -1,5 +1,6 @@
 const _                   = require('lodash');
 const error               = require('../lib/error');
+const utils               = require('../lib/utils');
 const deadHostModel       = require('../models/dead_host');
 const internalHost        = require('./host');
 const internalNginx       = require('./nginx');
@@ -49,8 +50,8 @@ const internalDeadHost = {
 
 				return deadHostModel
 					.query()
-					.omit(omissions())
-					.insertAndFetch(data);
+					.insertAndFetch(data)
+					.then(utils.omitRow(omissions()));
 			})
 			.then((row) => {
 				if (create_certificate) {
@@ -218,31 +219,28 @@ const internalDeadHost = {
 					.query()
 					.where('is_deleted', 0)
 					.andWhere('id', data.id)
-					.allowEager('[owner,certificate]')
+					.allowGraph('[owner,certificate]')
 					.first();
 
 				if (access_data.permission_visibility !== 'all') {
 					query.andWhere('owner_user_id', access.token.getUserId(1));
 				}
 
-				// Custom omissions
-				if (typeof data.omit !== 'undefined' && data.omit !== null) {
-					query.omit(data.omit);
-				}
-
 				if (typeof data.expand !== 'undefined' && data.expand !== null) {
-					query.eager('[' + data.expand.join(', ') + ']');
+					query.withGraphFetched('[' + data.expand.join(', ') + ']');
 				}
 
-				return query;
+				return query.then(utils.omitRow(omissions()));
 			})
 			.then((row) => {
-				if (row) {
-					row = internalHost.cleanRowCertificateMeta(row);
-					return _.omit(row, omissions());
-				} else {
+				if (!row) {
 					throw new error.ItemNotFoundError(data.id);
 				}
+				// Custom omissions
+				if (typeof data.omit !== 'undefined' && data.omit !== null) {
+					row = _.omit(row, data.omit);
+				}
+				return row;
 			});
 	},
 
@@ -404,8 +402,7 @@ const internalDeadHost = {
 					.query()
 					.where('is_deleted', 0)
 					.groupBy('id')
-					.omit(['is_deleted'])
-					.allowEager('[owner,certificate]')
+					.allowGraph('[owner,certificate]')
 					.orderBy('domain_names', 'ASC');
 
 				if (access_data.permission_visibility !== 'all') {
@@ -420,10 +417,10 @@ const internalDeadHost = {
 				}
 
 				if (typeof expand !== 'undefined' && expand !== null) {
-					query.eager('[' + expand.join(', ') + ']');
+					query.withGraphFetched('[' + expand.join(', ') + ']');
 				}
 
-				return query;
+				return query.then(utils.omitRows(omissions()));
 			})
 			.then((rows) => {
 				if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {

+ 2 - 5
backend/internal/ip_ranges.js

@@ -2,8 +2,8 @@ const https         = require('https');
 const fs            = require('fs');
 const logger        = require('../logger').ip_ranges;
 const error         = require('../lib/error');
+const utils         = require('../lib/utils');
 const internalNginx = require('./nginx');
-const { Liquid }    = require('liquidjs');
 
 const CLOUDFRONT_URL   = 'https://ip-ranges.amazonaws.com/ip-ranges.json';
 const CLOUDFARE_V4_URL = 'https://www.cloudflare.com/ips-v4';
@@ -119,10 +119,7 @@ const internalIpRanges = {
 	 * @returns {Promise}
 	 */
 	generateConfig: (ip_ranges) => {
-		let renderEngine = new Liquid({
-			root: __dirname + '/../templates/'
-		});
-
+		const renderEngine = utils.getRenderEngine();
 		return new Promise((resolve, reject) => {
 			let template = null;
 			let filename = '/etc/nginx/conf.d/include/ip_ranges.conf';

+ 77 - 76
backend/internal/nginx.js

@@ -3,7 +3,6 @@ const fs         = require('fs');
 const logger     = require('../logger').nginx;
 const utils      = require('../lib/utils');
 const error      = require('../lib/error');
-const { Liquid } = require('liquidjs');
 const debug_mode = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG;
 
 const internalNginx = {
@@ -29,7 +28,9 @@ const internalNginx = {
 			.then(() => {
 				// Nginx is OK
 				// We're deleting this config regardless.
-				return internalNginx.deleteConfig(host_type, host); // Don't throw errors, as the file may not exist at all
+				// Don't throw errors, as the file may not exist at all
+				// Delete the .err file too
+				return internalNginx.deleteConfig(host_type, host, false, true);
 			})
 			.then(() => {
 				return internalNginx.generateConfig(host_type, host);
@@ -80,6 +81,9 @@ const internalNginx = {
 							.patch({
 								meta: combined_meta
 							})
+							.then(() => {
+								internalNginx.renameConfigAsError(host_type, host);
+							})
 							.then(() => {
 								return internalNginx.deleteConfig(host_type, host, true);
 							});
@@ -121,13 +125,10 @@ const internalNginx = {
 	 * @returns {String}
 	 */
 	getConfigName: (host_type, host_id) => {
-		host_type = host_type.replace(new RegExp('-', 'g'), '_');
-
 		if (host_type === 'default') {
 			return '/data/nginx/default_host/site.conf';
 		}
-
-		return '/data/nginx/' + host_type + '/' + host_id + '.conf';
+		return '/data/nginx/' + internalNginx.getFileFriendlyHostType(host_type) + '/' + host_id + '.conf';
 	},
 
 	/**
@@ -136,8 +137,6 @@ const internalNginx = {
 	 * @returns {Promise}
 	 */
 	renderLocations: (host) => {
-
-		//logger.info('host = ' + JSON.stringify(host, null, 2));
 		return new Promise((resolve, reject) => {
 			let template;
 
@@ -148,19 +147,17 @@ const internalNginx = {
 				return;
 			}
 
-			let renderer          = new Liquid({
-				root: __dirname + '/../templates/'
-			});
+			const renderEngine    = utils.getRenderEngine();
 			let renderedLocations = '';
 
 			const locationRendering = async () => {
 				for (let i = 0; i < host.locations.length; i++) {
-					let locationCopy = Object.assign({}, {access_list_id: host.access_list_id}, {certificate_id: host.certificate_id}, 
+					let locationCopy = Object.assign({}, {access_list_id: host.access_list_id}, {certificate_id: host.certificate_id},
 						{ssl_forced: host.ssl_forced}, {caching_enabled: host.caching_enabled}, {block_exploits: host.block_exploits},
 						{allow_websocket_upgrade: host.allow_websocket_upgrade}, {http2_support: host.http2_support},
 						{hsts_enabled: host.hsts_enabled}, {hsts_subdomains: host.hsts_subdomains}, {access_list: host.access_list},
 						{certificate: host.certificate}, host.locations[i]);
-			
+
 					if (locationCopy.forward_host.indexOf('/') > -1) {
 						const splitted = locationCopy.forward_host.split('/');
 
@@ -168,16 +165,14 @@ const internalNginx = {
 						locationCopy.forward_path = `/${splitted.join('/')}`;
 					}
 
-					//logger.info('locationCopy = ' + JSON.stringify(locationCopy, null, 2));
-
 					// eslint-disable-next-line
-					renderedLocations += await renderer.parseAndRender(template, locationCopy);
+					renderedLocations += await renderEngine.parseAndRender(template, locationCopy);
 				}
 
 			};
 
 			locationRendering().then(() => resolve(renderedLocations));
-			
+
 		});
 	},
 
@@ -187,24 +182,20 @@ const internalNginx = {
 	 * @returns {Promise}
 	 */
 	generateConfig: (host_type, host) => {
-		host_type = host_type.replace(new RegExp('-', 'g'), '_');
+		const nice_host_type = internalNginx.getFileFriendlyHostType(host_type);
 
 		if (debug_mode) {
-			logger.info('Generating ' + host_type + ' Config:', host);
+			logger.info('Generating ' + nice_host_type + ' Config:', JSON.stringify(host, null, 2));
 		}
 
-		// logger.info('host = ' + JSON.stringify(host, null, 2));
-
-		let renderEngine = new Liquid({
-			root: __dirname + '/../templates/'
-		});
+		const renderEngine = utils.getRenderEngine();
 
 		return new Promise((resolve, reject) => {
 			let template = null;
-			let filename = internalNginx.getConfigName(host_type, host.id);
+			let filename = internalNginx.getConfigName(nice_host_type, host.id);
 
 			try {
-				template = fs.readFileSync(__dirname + '/../templates/' + host_type + '.conf', {encoding: 'utf8'});
+				template = fs.readFileSync(__dirname + '/../templates/' + nice_host_type + '.conf', {encoding: 'utf8'});
 			} catch (err) {
 				reject(new error.ConfigurationError(err.message));
 				return;
@@ -214,7 +205,7 @@ const internalNginx = {
 			let origLocations;
 
 			// Manipulate the data a bit before sending it to the template
-			if (host_type !== 'default') {
+			if (nice_host_type !== 'default') {
 				host.use_default_location = true;
 				if (typeof host.advanced_config !== 'undefined' && host.advanced_config) {
 					host.use_default_location = !internalNginx.advancedConfigHasDefaultLocation(host.advanced_config);
@@ -281,9 +272,7 @@ const internalNginx = {
 			logger.info('Generating LetsEncrypt Request Config:', certificate);
 		}
 
-		let renderEngine = new Liquid({
-			root: __dirname + '/../templates/'
-		});
+		const renderEngine = utils.getRenderEngine();
 
 		return new Promise((resolve, reject) => {
 			let template = null;
@@ -319,33 +308,39 @@ const internalNginx = {
 		});
 	},
 
+	/**
+	 * A simple wrapper around unlinkSync that writes to the logger
+	 *
+	 * @param   {String}  filename
+	 */
+	deleteFile: (filename) => {
+		logger.debug('Deleting file: ' + filename);
+		try {
+			fs.unlinkSync(filename);
+		} catch (err) {
+			logger.debug('Could not delete file:', JSON.stringify(err, null, 2));
+		}
+	},
+
+	/**
+	 *
+	 * @param   {String} host_type
+	 * @returns String
+	 */
+	getFileFriendlyHostType: (host_type) => {
+		return host_type.replace(new RegExp('-', 'g'), '_');
+	},
+
 	/**
 	 * This removes the temporary nginx config file generated by `generateLetsEncryptRequestConfig`
 	 *
 	 * @param   {Object}  certificate
-	 * @param   {Boolean} [throw_errors]
 	 * @returns {Promise}
 	 */
-	deleteLetsEncryptRequestConfig: (certificate, throw_errors) => {
-		return new Promise((resolve, reject) => {
-			try {
-				let config_file = '/data/nginx/temp/letsencrypt_' + certificate.id + '.conf';
-
-				if (debug_mode) {
-					logger.warn('Deleting nginx config: ' + config_file);
-				}
-
-				fs.unlinkSync(config_file);
-			} catch (err) {
-				if (debug_mode) {
-					logger.warn('Could not delete config:', err.message);
-				}
-
-				if (throw_errors) {
-					reject(err);
-				}
-			}
-
+	deleteLetsEncryptRequestConfig: (certificate) => {
+		const config_file = '/data/nginx/temp/letsencrypt_' + certificate.id + '.conf';
+		return new Promise((resolve/*, reject*/) => {
+			internalNginx.deleteFile(config_file);
 			resolve();
 		});
 	},
@@ -353,35 +348,42 @@ const internalNginx = {
 	/**
 	 * @param   {String}  host_type
 	 * @param   {Object}  [host]
-	 * @param   {Boolean} [throw_errors]
+	 * @param   {Boolean} [delete_err_file]
 	 * @returns {Promise}
 	 */
-	deleteConfig: (host_type, host, throw_errors) => {
-		host_type = host_type.replace(new RegExp('-', 'g'), '_');
-
-		return new Promise((resolve, reject) => {
-			try {
-				let config_file = internalNginx.getConfigName(host_type, typeof host === 'undefined' ? 0 : host.id);
-
-				if (debug_mode) {
-					logger.warn('Deleting nginx config: ' + config_file);
-				}
-
-				fs.unlinkSync(config_file);
-			} catch (err) {
-				if (debug_mode) {
-					logger.warn('Could not delete config:', err.message);
-				}
-
-				if (throw_errors) {
-					reject(err);
-				}
+	deleteConfig: (host_type, host, delete_err_file) => {
+		const config_file     = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id);
+		const config_file_err = config_file + '.err';
+
+		return new Promise((resolve/*, reject*/) => {
+			internalNginx.deleteFile(config_file);
+			if (delete_err_file) {
+				internalNginx.deleteFile(config_file_err);
 			}
-
 			resolve();
 		});
 	},
 
+	/**
+	 * @param   {String}  host_type
+	 * @param   {Object}  [host]
+	 * @returns {Promise}
+	 */
+	renameConfigAsError: (host_type, host) => {
+		const config_file     = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id);
+		const config_file_err = config_file + '.err';
+
+		return new Promise((resolve/*, reject*/) => {
+			fs.unlink(config_file, () => {
+				// ignore result, continue
+				fs.rename(config_file, config_file_err, () => {
+					// also ignore result, as this is a debugging informative file anyway
+					resolve();
+				});
+			});
+		});
+	},
+
 	/**
 	 * @param   {String}  host_type
 	 * @param   {Array}   hosts
@@ -399,13 +401,12 @@ const internalNginx = {
 	/**
 	 * @param   {String}  host_type
 	 * @param   {Array}   hosts
-	 * @param   {Boolean} [throw_errors]
 	 * @returns {Promise}
 	 */
-	bulkDeleteConfigs: (host_type, hosts, throw_errors) => {
+	bulkDeleteConfigs: (host_type, hosts) => {
 		let promises = [];
 		hosts.map(function (host) {
-			promises.push(internalNginx.deleteConfig(host_type, host, throw_errors));
+			promises.push(internalNginx.deleteConfig(host_type, host, true));
 		});
 
 		return Promise.all(promises);

+ 18 - 19
backend/internal/proxy-host.js

@@ -1,5 +1,6 @@
 const _                   = require('lodash');
 const error               = require('../lib/error');
+const utils               = require('../lib/utils');
 const proxyHostModel      = require('../models/proxy_host');
 const internalHost        = require('./host');
 const internalNginx       = require('./nginx');
@@ -49,8 +50,8 @@ const internalProxyHost = {
 
 				return proxyHostModel
 					.query()
-					.omit(omissions())
-					.insertAndFetch(data);
+					.insertAndFetch(data)
+					.then(utils.omitRow(omissions()));
 			})
 			.then((row) => {
 				if (create_certificate) {
@@ -170,6 +171,7 @@ const internalProxyHost = {
 					.query()
 					.where({id: data.id})
 					.patch(data)
+					.then(utils.omitRow(omissions()))
 					.then((saved_row) => {
 						// Add to audit log
 						return internalAuditLog.add(access, {
@@ -179,7 +181,7 @@ const internalProxyHost = {
 							meta:        data
 						})
 							.then(() => {
-								return _.omit(saved_row, omissions());
+								return saved_row;
 							});
 					});
 			})
@@ -223,31 +225,29 @@ const internalProxyHost = {
 					.query()
 					.where('is_deleted', 0)
 					.andWhere('id', data.id)
-					.allowEager('[owner,access_list,access_list.[clients,items],certificate]')
+					.allowGraph('[owner,access_list,access_list.[clients,items],certificate]')
 					.first();
 
 				if (access_data.permission_visibility !== 'all') {
 					query.andWhere('owner_user_id', access.token.getUserId(1));
 				}
 
-				// Custom omissions
-				if (typeof data.omit !== 'undefined' && data.omit !== null) {
-					query.omit(data.omit);
-				}
-
 				if (typeof data.expand !== 'undefined' && data.expand !== null) {
-					query.eager('[' + data.expand.join(', ') + ']');
+					query.withGraphFetched('[' + data.expand.join(', ') + ']');
 				}
 
-				return query;
+				return query.then(utils.omitRow(omissions()));
 			})
 			.then((row) => {
-				if (row) {
-					row = internalHost.cleanRowCertificateMeta(row);
-					return _.omit(row, omissions());
-				} else {
+				if (!row) {
 					throw new error.ItemNotFoundError(data.id);
 				}
+				row = internalHost.cleanRowCertificateMeta(row);
+				// Custom omissions
+				if (typeof data.omit !== 'undefined' && data.omit !== null) {
+					row = _.omit(row, data.omit);
+				}
+				return row;
 			});
 	},
 
@@ -409,8 +409,7 @@ const internalProxyHost = {
 					.query()
 					.where('is_deleted', 0)
 					.groupBy('id')
-					.omit(['is_deleted'])
-					.allowEager('[owner,access_list,certificate]')
+					.allowGraph('[owner,access_list,certificate]')
 					.orderBy('domain_names', 'ASC');
 
 				if (access_data.permission_visibility !== 'all') {
@@ -425,10 +424,10 @@ const internalProxyHost = {
 				}
 
 				if (typeof expand !== 'undefined' && expand !== null) {
-					query.eager('[' + expand.join(', ') + ']');
+					query.withGraphFetched('[' + expand.join(', ') + ']');
 				}
 
-				return query;
+				return query.then(utils.omitRows(omissions()));
 			})
 			.then((rows) => {
 				if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {

+ 17 - 20
backend/internal/redirection-host.js

@@ -1,5 +1,6 @@
 const _                    = require('lodash');
 const error                = require('../lib/error');
+const utils                = require('../lib/utils');
 const redirectionHostModel = require('../models/redirection_host');
 const internalHost         = require('./host');
 const internalNginx        = require('./nginx');
@@ -49,8 +50,8 @@ const internalRedirectionHost = {
 
 				return redirectionHostModel
 					.query()
-					.omit(omissions())
-					.insertAndFetch(data);
+					.insertAndFetch(data)
+					.then(utils.omitRow(omissions()));
 			})
 			.then((row) => {
 				if (create_certificate) {
@@ -65,9 +66,8 @@ const internalRedirectionHost = {
 						.then(() => {
 							return row;
 						});
-				} else {
-					return row;
 				}
+				return row;
 			})
 			.then((row) => {
 				// re-fetch with cert
@@ -218,31 +218,29 @@ const internalRedirectionHost = {
 					.query()
 					.where('is_deleted', 0)
 					.andWhere('id', data.id)
-					.allowEager('[owner,certificate]')
+					.allowGraph('[owner,certificate]')
 					.first();
 
 				if (access_data.permission_visibility !== 'all') {
 					query.andWhere('owner_user_id', access.token.getUserId(1));
 				}
 
-				// Custom omissions
-				if (typeof data.omit !== 'undefined' && data.omit !== null) {
-					query.omit(data.omit);
-				}
-
 				if (typeof data.expand !== 'undefined' && data.expand !== null) {
-					query.eager('[' + data.expand.join(', ') + ']');
+					query.withGraphFetched('[' + data.expand.join(', ') + ']');
 				}
 
-				return query;
+				return query.then(utils.omitRow(omissions()));
 			})
 			.then((row) => {
-				if (row) {
-					row = internalHost.cleanRowCertificateMeta(row);
-					return _.omit(row, omissions());
-				} else {
+				if (!row) {
 					throw new error.ItemNotFoundError(data.id);
 				}
+				row = internalHost.cleanRowCertificateMeta(row);
+				// Custom omissions
+				if (typeof data.omit !== 'undefined' && data.omit !== null) {
+					row = _.omit(row, data.omit);
+				}
+				return row;
 			});
 	},
 
@@ -404,8 +402,7 @@ const internalRedirectionHost = {
 					.query()
 					.where('is_deleted', 0)
 					.groupBy('id')
-					.omit(['is_deleted'])
-					.allowEager('[owner,certificate]')
+					.allowGraph('[owner,certificate]')
 					.orderBy('domain_names', 'ASC');
 
 				if (access_data.permission_visibility !== 'all') {
@@ -420,10 +417,10 @@ const internalRedirectionHost = {
 				}
 
 				if (typeof expand !== 'undefined' && expand !== null) {
-					query.eager('[' + expand.join(', ') + ']');
+					query.withGraphFetched('[' + expand.join(', ') + ']');
 				}
 
-				return query;
+				return query.then(utils.omitRows(omissions()));
 			})
 			.then((rows) => {
 				if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {

+ 17 - 19
backend/internal/stream.js

@@ -1,5 +1,6 @@
 const _                = require('lodash');
 const error            = require('../lib/error');
+const utils            = require('../lib/utils');
 const streamModel      = require('../models/stream');
 const internalNginx    = require('./nginx');
 const internalAuditLog = require('./audit-log');
@@ -27,8 +28,8 @@ const internalStream = {
 
 				return streamModel
 					.query()
-					.omit(omissions())
-					.insertAndFetch(data);
+					.insertAndFetch(data)
+					.then(utils.omitRow(omissions()));
 			})
 			.then((row) => {
 				// Configure nginx
@@ -71,8 +72,8 @@ const internalStream = {
 
 				return streamModel
 					.query()
-					.omit(omissions())
 					.patchAndFetchById(row.id, data)
+					.then(utils.omitRow(omissions()))
 					.then((saved_row) => {
 						return internalNginx.configure(streamModel, 'stream', saved_row)
 							.then(() => {
@@ -88,7 +89,7 @@ const internalStream = {
 							meta:        data
 						})
 							.then(() => {
-								return _.omit(saved_row, omissions());
+								return saved_row;
 							});
 					});
 			});
@@ -113,30 +114,28 @@ const internalStream = {
 					.query()
 					.where('is_deleted', 0)
 					.andWhere('id', data.id)
-					.allowEager('[owner]')
+					.allowGraph('[owner]')
 					.first();
 
 				if (access_data.permission_visibility !== 'all') {
 					query.andWhere('owner_user_id', access.token.getUserId(1));
 				}
 
-				// Custom omissions
-				if (typeof data.omit !== 'undefined' && data.omit !== null) {
-					query.omit(data.omit);
-				}
-
 				if (typeof data.expand !== 'undefined' && data.expand !== null) {
-					query.eager('[' + data.expand.join(', ') + ']');
+					query.withGraphFetched('[' + data.expand.join(', ') + ']');
 				}
 
-				return query;
+				return query.then(utils.omitRow(omissions()));
 			})
 			.then((row) => {
-				if (row) {
-					return _.omit(row, omissions());
-				} else {
+				if (!row) {
 					throw new error.ItemNotFoundError(data.id);
 				}
+				// Custom omissions
+				if (typeof data.omit !== 'undefined' && data.omit !== null) {
+					row = _.omit(row, data.omit);
+				}
+				return row;
 			});
 	},
 
@@ -298,8 +297,7 @@ const internalStream = {
 					.query()
 					.where('is_deleted', 0)
 					.groupBy('id')
-					.omit(['is_deleted'])
-					.allowEager('[owner]')
+					.allowGraph('[owner]')
 					.orderBy('incoming_port', 'ASC');
 
 				if (access_data.permission_visibility !== 'all') {
@@ -314,10 +312,10 @@ const internalStream = {
 				}
 
 				if (typeof expand !== 'undefined' && expand !== null) {
-					query.eager('[' + expand.join(', ') + ']');
+					query.withGraphFetched('[' + expand.join(', ') + ']');
 				}
 
-				return query;
+				return query.then(utils.omitRows(omissions()));
 			});
 	},
 

+ 16 - 21
backend/internal/user.js

@@ -1,5 +1,6 @@
 const _                   = require('lodash');
 const error               = require('../lib/error');
+const utils               = require('../lib/utils');
 const userModel           = require('../models/user');
 const userPermissionModel = require('../models/user_permission');
 const authModel           = require('../models/auth');
@@ -35,8 +36,8 @@ const internalUser = {
 
 				return userModel
 					.query()
-					.omit(omissions())
-					.insertAndFetch(data);
+					.insertAndFetch(data)
+					.then(utils.omitRow(omissions()));
 			})
 			.then((user) => {
 				if (auth) {
@@ -140,11 +141,8 @@ const internalUser = {
 
 				return userModel
 					.query()
-					.omit(omissions())
 					.patchAndFetchById(user.id, data)
-					.then((saved_user) => {
-						return _.omit(saved_user, omissions());
-					});
+					.then(utils.omitRow(omissions()));
 			})
 			.then(() => {
 				return internalUser.get(access, {id: data.id});
@@ -186,26 +184,24 @@ const internalUser = {
 					.query()
 					.where('is_deleted', 0)
 					.andWhere('id', data.id)
-					.allowEager('[permissions]')
+					.allowGraph('[permissions]')
 					.first();
 
-				// Custom omissions
-				if (typeof data.omit !== 'undefined' && data.omit !== null) {
-					query.omit(data.omit);
-				}
-
 				if (typeof data.expand !== 'undefined' && data.expand !== null) {
-					query.eager('[' + data.expand.join(', ') + ']');
+					query.withGraphFetched('[' + data.expand.join(', ') + ']');
 				}
 
-				return query;
+				return query.then(utils.omitRow(omissions()));
 			})
 			.then((row) => {
-				if (row) {
-					return _.omit(row, omissions());
-				} else {
+				if (!row) {
 					throw new error.ItemNotFoundError(data.id);
 				}
+				// Custom omissions
+				if (typeof data.omit !== 'undefined' && data.omit !== null) {
+					row = _.omit(row, data.omit);
+				}
+				return row;
 			});
 	},
 
@@ -322,8 +318,7 @@ const internalUser = {
 					.query()
 					.where('is_deleted', 0)
 					.groupBy('id')
-					.omit(['is_deleted'])
-					.allowEager('[permissions]')
+					.allowGraph('[permissions]')
 					.orderBy('name', 'ASC');
 
 				// Query is used for searching
@@ -335,10 +330,10 @@ const internalUser = {
 				}
 
 				if (typeof expand !== 'undefined' && expand !== null) {
-					query.eager('[' + expand.join(', ') + ']');
+					query.withGraphFetched('[' + expand.join(', ') + ']');
 				}
 
-				return query;
+				return query.then(utils.omitRows(omissions()));
 			});
 	},
 

+ 2 - 2
backend/lib/access.js

@@ -55,8 +55,8 @@ module.exports = function (token_string) {
 								.where('id', token_data.attrs.id)
 								.andWhere('is_deleted', 0)
 								.andWhere('is_disabled', 0)
-								.allowEager('[permissions]')
-								.eager('[permissions]')
+								.allowGraph('[permissions]')
+								.withGraphFetched('[permissions]')
 								.first()
 								.then((user) => {
 									if (user) {

+ 69 - 5
backend/lib/utils.js

@@ -1,5 +1,8 @@
-const exec     = require('child_process').exec;
-const execFile = require('child_process').execFile;
+const _          = require('lodash');
+const exec       = require('child_process').exec;
+const execFile   = require('child_process').execFile;
+const { Liquid } = require('liquidjs');
+const logger     = require('../logger').global;
 
 module.exports = {
 
@@ -20,12 +23,14 @@ module.exports = {
 	},
 
 	/**
-	 * @param   {Array} cmd
+	 * @param   {String} cmd
+	 * @param   {Array}  args
 	 * @returns {Promise}
 	 */
-	execFile: function (cmd) {
+	execFile: function (cmd, args) {
+		logger.debug('CMD: ' + cmd + ' ' + (args ? args.join(' ') : ''));
 		return new Promise((resolve, reject) => {
-			execFile(cmd, function (err, stdout, /*stderr*/) {
+			execFile(cmd, args, function (err, stdout, /*stderr*/) {
 				if (err && typeof err === 'object') {
 					reject(err);
 				} else {
@@ -33,5 +38,64 @@ module.exports = {
 				}
 			});
 		});
+	},
+
+	/**
+	 * Used in objection query builder
+	 *
+	 * @param   {Array}  omissions
+	 * @returns {Function}
+	 */
+	omitRow: function (omissions) {
+		/**
+		 * @param   {Object} row
+		 * @returns {Object}
+		 */
+		return (row) => {
+			return _.omit(row, omissions);
+		};
+	},
+
+	/**
+	 * Used in objection query builder
+	 *
+	 * @param   {Array}  omissions
+	 * @returns {Function}
+	 */
+	omitRows: function (omissions) {
+		/**
+		 * @param   {Array} rows
+		 * @returns {Object}
+		 */
+		return (rows) => {
+			rows.forEach((row, idx) => {
+				rows[idx] = _.omit(row, omissions);
+			});
+			return rows;
+		};
+	},
+
+	/**
+	 * @returns {Object} Liquid render engine
+	 */
+	getRenderEngine: function () {
+		const renderEngine = new Liquid({
+			root: __dirname + '/../templates/'
+		});
+
+		/**
+		 * nginxAccessRule expects the object given to have 2 properties:
+		 *
+		 * directive  string
+		 * address    string
+		 */
+		renderEngine.registerFilter('nginxAccessRule', (v) => {
+			if (typeof v.directive !== 'undefined' && typeof v.address !== 'undefined' && v.directive && v.address) {
+				return `${v.directive} ${v.address};`;
+			}
+			return '';
+		});
+
+		return renderEngine;
 	}
 };

+ 0 - 16
backend/models/access_list.js

@@ -50,7 +50,6 @@ class AccessList extends Model {
 				},
 				modify: function (qb) {
 					qb.where('user.is_deleted', 0);
-					qb.omit(['id', 'created_on', 'modified_on', 'is_deleted', 'email', 'roles']);
 				}
 			},
 			items: {
@@ -59,9 +58,6 @@ class AccessList extends Model {
 				join:       {
 					from: 'access_list.id',
 					to:   'access_list_auth.access_list_id'
-				},
-				modify: function (qb) {
-					qb.omit(['id', 'created_on', 'modified_on', 'access_list_id', 'meta']);
 				}
 			},
 			clients: {
@@ -70,9 +66,6 @@ class AccessList extends Model {
 				join:       {
 					from: 'access_list.id',
 					to:   'access_list_client.access_list_id'
-				},
-				modify: function (qb) {
-					qb.omit(['id', 'created_on', 'modified_on', 'access_list_id', 'meta']);
 				}
 			},
 			proxy_hosts: {
@@ -84,19 +77,10 @@ class AccessList extends Model {
 				},
 				modify: function (qb) {
 					qb.where('proxy_host.is_deleted', 0);
-					qb.omit(['is_deleted', 'meta']);
 				}
 			}
 		};
 	}
-
-	get satisfy() {
-		return this.satisfy_any ? 'satisfy any' : 'satisfy all';
-	}
-
-	get passauth() {
-		return this.pass_auth ? '' : 'proxy_set_header Authorization "";';
-	}
 }
 
 module.exports = AccessList;

+ 0 - 1
backend/models/access_list_auth.js

@@ -45,7 +45,6 @@ class AccessListAuth extends Model {
 				},
 				modify: function (qb) {
 					qb.where('access_list.is_deleted', 0);
-					qb.omit(['created_on', 'modified_on', 'is_deleted', 'access_list_id']);
 				}
 			}
 		};

+ 0 - 5
backend/models/access_list_client.js

@@ -45,15 +45,10 @@ class AccessListClient extends Model {
 				},
 				modify: function (qb) {
 					qb.where('access_list.is_deleted', 0);
-					qb.omit(['created_on', 'modified_on', 'is_deleted', 'access_list_id']);
 				}
 			}
 		};
 	}
-
-	get rule() {
-		return `${this.directive} ${this.address}`;
-	}
 }
 
 module.exports = AccessListClient;

+ 0 - 3
backend/models/audit-log.js

@@ -43,9 +43,6 @@ class AuditLog extends Model {
 				join:       {
 					from: 'audit_log.user_id',
 					to:   'user.id'
-				},
-				modify: function (qb) {
-					qb.omit(['id', 'created_on', 'modified_on', 'roles']);
 				}
 			}
 		};

+ 0 - 3
backend/models/auth.js

@@ -74,9 +74,6 @@ class Auth extends Model {
 				},
 				filter: {
 					is_deleted: 0
-				},
-				modify: function (qb) {
-					qb.omit(['is_deleted']);
 				}
 			}
 		};

+ 0 - 1
backend/models/certificate.js

@@ -63,7 +63,6 @@ class Certificate extends Model {
 				},
 				modify: function (qb) {
 					qb.where('user.is_deleted', 0);
-					qb.omit(['id', 'created_on', 'modified_on', 'is_deleted', 'email', 'roles']);
 				}
 			}
 		};

+ 0 - 2
backend/models/dead_host.js

@@ -59,7 +59,6 @@ class DeadHost extends Model {
 				},
 				modify: function (qb) {
 					qb.where('user.is_deleted', 0);
-					qb.omit(['id', 'created_on', 'modified_on', 'is_deleted', 'email', 'roles']);
 				}
 			},
 			certificate: {
@@ -71,7 +70,6 @@ class DeadHost extends Model {
 				},
 				modify: function (qb) {
 					qb.where('certificate.is_deleted', 0);
-					qb.omit(['id', 'created_on', 'modified_on', 'is_deleted']);
 				}
 			}
 		};

+ 3 - 3
backend/models/now_helper.js

@@ -6,8 +6,8 @@ Model.knex(db);
 
 module.exports = function () {
 	if (config.database.knex && config.database.knex.client === 'sqlite3') {
-		return Model.raw('datetime(\'now\',\'localtime\')');
-	} else {
-		return Model.raw('NOW()');
+		// eslint-disable-next-line
+		return Model.raw("datetime('now','localtime')");
 	}
+	return Model.raw('NOW()');
 };

+ 0 - 3
backend/models/proxy_host.js

@@ -60,7 +60,6 @@ class ProxyHost extends Model {
 				},
 				modify: function (qb) {
 					qb.where('user.is_deleted', 0);
-					qb.omit(['id', 'created_on', 'modified_on', 'is_deleted', 'email', 'roles']);
 				}
 			},
 			access_list: {
@@ -72,7 +71,6 @@ class ProxyHost extends Model {
 				},
 				modify: function (qb) {
 					qb.where('access_list.is_deleted', 0);
-					qb.omit(['id', 'created_on', 'modified_on', 'is_deleted']);
 				}
 			},
 			certificate: {
@@ -84,7 +82,6 @@ class ProxyHost extends Model {
 				},
 				modify: function (qb) {
 					qb.where('certificate.is_deleted', 0);
-					qb.omit(['id', 'created_on', 'modified_on', 'is_deleted']);
 				}
 			}
 		};

+ 1 - 2
backend/models/redirection_host.js

@@ -1,3 +1,4 @@
+
 // Objection Docs:
 // http://vincit.github.io/objection.js/
 
@@ -59,7 +60,6 @@ class RedirectionHost extends Model {
 				},
 				modify: function (qb) {
 					qb.where('user.is_deleted', 0);
-					qb.omit(['id', 'created_on', 'modified_on', 'is_deleted', 'email', 'roles']);
 				}
 			},
 			certificate: {
@@ -71,7 +71,6 @@ class RedirectionHost extends Model {
 				},
 				modify: function (qb) {
 					qb.where('certificate.is_deleted', 0);
-					qb.omit(['id', 'created_on', 'modified_on', 'is_deleted']);
 				}
 			}
 		};

+ 0 - 1
backend/models/stream.js

@@ -46,7 +46,6 @@ class Stream extends Model {
 				},
 				modify: function (qb) {
 					qb.where('user.is_deleted', 0);
-					qb.omit(['id', 'created_on', 'modified_on', 'is_deleted', 'email', 'roles']);
 				}
 			}
 		};

+ 0 - 2
backend/models/token.js

@@ -83,8 +83,6 @@ module.exports = function () {
 								// Hack: some tokens out in the wild have a scope of 'all' instead of 'user'.
 								// For 30 days at least, we need to replace 'all' with user.
 								if ((typeof token_data.scope !== 'undefined' && _.indexOf(token_data.scope, 'all') !== -1)) {
-									//console.log('Warning! Replacing "all" scope with "user"');
-
 									token_data.scope = ['user'];
 								}
 

+ 0 - 3
backend/models/user.js

@@ -43,9 +43,6 @@ class User extends Model {
 				join:       {
 					from: 'user.id',
 					to:   'user_permission.user_id'
-				},
-				modify: function (qb) {
-					qb.omit(['id', 'created_on', 'modified_on', 'user_id']);
 				}
 			}
 		};

+ 5 - 5
backend/package.json

@@ -16,17 +16,17 @@
 		"gravatar": "^1.8.0",
 		"json-schema-ref-parser": "^8.0.0",
 		"jsonwebtoken": "^9.0.0",
-		"knex": "^2.4.0",
-		"liquidjs": "^10.0.0",
+		"knex": "2.4.2",
+		"liquidjs": "10.6.1",
 		"lodash": "^4.17.21",
 		"moment": "^2.29.4",
 		"mysql": "^2.18.1",
 		"node-rsa": "^1.0.8",
 		"nodemon": "^2.0.2",
-		"objection": "^2.2.16",
+		"objection": "3.0.1",
 		"path": "^0.12.7",
-		"signale": "^1.4.0",
-		"sqlite3": "^4.1.1",
+		"signale": "1.4.0",
+		"sqlite3": "5.1.6",
 		"temp-write": "^4.0.0"
 	},
 	"signale": {

+ 25 - 0
backend/templates/_access.conf

@@ -0,0 +1,25 @@
+{% if access_list_id > 0 %}
+    {% if access_list.items.length > 0 %}
+    # Authorization
+    auth_basic            "Authorization required";
+    auth_basic_user_file  /data/access/{{ access_list_id }};
+
+    {% if access_list.pass_auth == 0 %}
+    proxy_set_header Authorization "";
+    {% endif %}
+
+    {% endif %}
+
+    # Access Rules: {{ access_list.clients | size }} total
+    {% for client in access_list.clients %}
+    {{client | nginxAccessRule}}
+    {% endfor %}
+    deny all;
+
+    # Access checks must...
+    {% if access_list.satisfy_any == 1 %}
+    satisfy any;
+    {% else %}
+    satisfy all;
+    {% endif %}
+{% endif %}

+ 1 - 22
backend/templates/_location.conf

@@ -6,30 +6,9 @@
     proxy_set_header X-Real-IP		$remote_addr;
     proxy_pass       {{ forward_scheme }}://{{ forward_host }}:{{ forward_port }}{{ forward_path }};
 
-    {% if access_list_id > 0 %}
-    {% if access_list.items.length > 0 %}
-    # Authorization
-    auth_basic            "Authorization required";
-    auth_basic_user_file  /data/access/{{ access_list_id }};
- 
-    {{ access_list.passauth }}
-    {% endif %}
- 
-    # Access Rules
-    {% for client in access_list.clients %}
-    {{- client.rule -}};
-    {% endfor %}deny all;
- 
-    # Access checks must...
-    {% if access_list.satisfy %}
-    {{ access_list.satisfy }};
-    {% endif %}
- 
-    {% endif %}
-
+    {% include "_access.conf" %}
     {% include "_assets.conf" %}
     {% include "_exploits.conf" %}
-
     {% include "_forced_ssl.conf" %}
     {% include "_hsts.conf" %}
 

+ 1 - 21
backend/templates/proxy_host.conf

@@ -30,27 +30,7 @@ proxy_http_version 1.1;
 
   location / {
 
-    {% if access_list_id > 0 %}
-    {% if access_list.items.length > 0 %}
-    # Authorization
-    auth_basic            "Authorization required";
-    auth_basic_user_file  /data/access/{{ access_list_id }};
-
-    {{ access_list.passauth }}
-    {% endif %}
-
-    # Access Rules
-    {% for client in access_list.clients %}
-    {{- client.rule -}};
-    {% endfor %}deny all;
-
-    # Access checks must...
-    {% if access_list.satisfy %}
-    {{ access_list.satisfy }};
-    {% endif %}
-
-    {% endif %}
-
+{% include "_access.conf" %}
 {% include "_hsts.conf" %}
 
     {% if allow_websocket_upgrade == 1 or allow_websocket_upgrade == true %}

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 535 - 54
backend/yarn.lock


+ 1 - 1
docker/Dockerfile

@@ -25,7 +25,7 @@ RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \
 	&& rm -rf /var/lib/apt/lists/*
 
 # s6 overlay
-COPY scripts/install-s6 /tmp/install-s6
+COPY docker/scripts/install-s6 /tmp/install-s6
 RUN /tmp/install-s6 "${TARGETPLATFORM}" && rm -f /tmp/install-s6
 
 EXPOSE 80 81 443

+ 3 - 4
docker/dev/Dockerfile

@@ -7,7 +7,7 @@ ENV S6_LOGGING=0 \
 
 RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \
 	&& apt-get update \
-	&& apt-get install -y certbot jq python3-pip logrotate \
+	&& apt-get install -y jq python3-pip logrotate \
 	&& apt-get clean \
 	&& rm -rf /var/lib/apt/lists/*
 
@@ -21,9 +21,8 @@ RUN rm -f /etc/nginx/conf.d/production.conf
 RUN chmod 644 /etc/logrotate.d/nginx-proxy-manager
 
 # s6 overlay
-RUN curl -L -o /tmp/s6-overlay-amd64.tar.gz "https://github.com/just-containers/s6-overlay/releases/download/v1.22.1.0/s6-overlay-amd64.tar.gz" \
-	&& tar -xzf /tmp/s6-overlay-amd64.tar.gz -C /
+COPY scripts/install-s6 /tmp/install-s6
+RUN /tmp/install-s6 "${TARGETPLATFORM}" && rm -f /tmp/install-s6
 
 EXPOSE 80 81 443
 ENTRYPOINT [ "/init" ]
-

+ 0 - 0
docker/rootfs/etc/s6-overlay/s6-rc.d/backend/dependencies.d/prepare


+ 4 - 4
docker/rootfs/etc/services.d/manager/run → docker/rootfs/etc/s6-overlay/s6-rc.d/backend/run

@@ -1,9 +1,9 @@
-#!/usr/bin/with-contenv bash
+#!/command/with-contenv bash
+# shellcheck shell=bash
 
-mkdir -p /data/letsencrypt-acme-challenge
-
-cd /app || echo
+set -e
 
+echo "❯ Starting backend ..."
 if [ "$DEVELOPMENT" == "true" ]; then
 	cd /app || exit 1
 	# If yarn install fails: add --verbose --network-concurrency 1

+ 1 - 0
docker/rootfs/etc/s6-overlay/s6-rc.d/backend/type

@@ -0,0 +1 @@
+longrun

+ 0 - 0
docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/dependencies.d/prepare


+ 4 - 1
docker/rootfs/etc/services.d/frontend/run → docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/run

@@ -1,4 +1,7 @@
-#!/usr/bin/with-contenv bash
+#!/command/with-contenv bash
+# shellcheck shell=bash
+
+set -e
 
 # This service is DEVELOPMENT only.
 

+ 1 - 0
docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/type

@@ -0,0 +1 @@
+longrun

+ 0 - 0
docker/rootfs/etc/s6-overlay/s6-rc.d/nginx/dependencies.d/prepare


+ 7 - 0
docker/rootfs/etc/s6-overlay/s6-rc.d/nginx/run

@@ -0,0 +1,7 @@
+#!/command/with-contenv bash
+# shellcheck shell=bash
+
+set -e
+
+echo "❯ Starting nginx ..."
+exec nginx

+ 1 - 0
docker/rootfs/etc/s6-overlay/s6-rc.d/nginx/type

@@ -0,0 +1 @@
+longrun

+ 0 - 0
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/dependencies.d/base


+ 63 - 0
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/script.sh

@@ -0,0 +1,63 @@
+#!/command/with-contenv bash
+# shellcheck shell=bash
+
+set -e
+
+DATA_PATH=/data
+
+# Ensure /data is mounted
+if [ ! -d "$DATA_PATH" ]; then
+	echo '--------------------------------------'
+	echo "ERROR: $DATA_PATH is not mounted! Check your docker configuration."
+	echo '--------------------------------------'
+	/run/s6/basedir/bin/halt
+	exit 1
+fi
+
+echo "❯ Checking folder structure ..."
+
+# Create required folders
+mkdir -p /tmp/nginx/body \
+	/run/nginx \
+	/var/log/nginx \
+	/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 \
+	/var/lib/nginx/cache/public \
+	/var/lib/nginx/cache/private \
+	/var/cache/nginx/proxy_temp \
+	/data/letsencrypt-acme-challenge
+
+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, 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
+
+# Handle IPV6 settings
+/bin/handle-ipv6-setting /etc/nginx/conf.d
+/bin/handle-ipv6-setting /data/nginx
+
+echo
+echo "-------------------------------------
+ _   _ ____  __  __
+| \ | |  _ \|  \/  |
+|  \| | |_) | |\/| |
+| |\  |  __/| |  | |
+|_| \_|_|   |_|  |_|
+-------------------------------------
+"

+ 1 - 0
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/type

@@ -0,0 +1 @@
+oneshot

+ 2 - 0
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/up

@@ -0,0 +1,2 @@
+# shellcheck shell=bash
+/etc/s6-overlay/s6-rc.d/prepare/script.sh

+ 0 - 0
docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/backend


+ 0 - 0
docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/frontend


+ 0 - 0
docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/nginx


+ 0 - 0
docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/prepare


+ 0 - 6
docker/rootfs/etc/services.d/frontend/finish

@@ -1,6 +0,0 @@
-#!/usr/bin/execlineb -S1
-if { s6-test ${1} -ne 0 }
-if { s6-test ${1} -ne 256 }
-
-s6-svscanctl -t /var/run/s6/services
-

+ 0 - 3
docker/rootfs/etc/services.d/manager/finish

@@ -1,3 +0,0 @@
-#!/usr/bin/with-contenv bash
-
-s6-svscanctl -t /var/run/s6/services

+ 0 - 1
docker/rootfs/etc/services.d/nginx/finish

@@ -1 +0,0 @@
-/bin/true

+ 0 - 38
docker/rootfs/etc/services.d/nginx/run

@@ -1,38 +0,0 @@
-#!/usr/bin/with-contenv bash
-
-# Create required folders
-mkdir -p /tmp/nginx/body \
-	/run/nginx \
-	/var/log/nginx \
-	/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 \
-	/var/lib/nginx/cache/public \
-	/var/lib/nginx/cache/private \
-	/var/cache/nginx/proxy_temp
-
-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, 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
-
-# Handle IPV6 settings
-/bin/handle-ipv6-setting /etc/nginx/conf.d
-/bin/handle-ipv6-setting /data/nginx
-
-exec nginx

+ 9 - 5
scripts/install-s6 → docker/scripts/install-s6

@@ -8,8 +8,8 @@ BLUE='\E[1;34m'
 GREEN='\E[1;32m'
 RESET='\E[0m'
 
-S6_OVERLAY_VERSION=1.22.1.0
-TARGETPLATFORM=$1
+S6_OVERLAY_VERSION=3.1.4.1
+TARGETPLATFORM=${1:unspecified}
 
 # Determine the correct binary file for the architecture given
 case $TARGETPLATFORM in
@@ -22,13 +22,17 @@ case $TARGETPLATFORM in
 		;;
 
 	*)
-		S6_ARCH=amd64
+		S6_ARCH=x86_64
 		;;
 esac
 
 echo -e "${BLUE}❯ ${CYAN}Installing S6-overlay v${S6_OVERLAY_VERSION} for ${YELLOW}${TARGETPLATFORM} (${S6_ARCH})${RESET}"
 
-curl -L -o "/tmp/s6-overlay-${S6_ARCH}.tar.gz" "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-${S6_ARCH}.tar.gz" \
-	&& tar -xzf "/tmp/s6-overlay-${S6_ARCH}.tar.gz" -C /
+curl -L -o '/tmp/s6-overlay-noarch.tar.xz' "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz"
+curl -L -o "/tmp/s6-overlay-${S6_ARCH}.tar.xz" "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-${S6_ARCH}.tar.xz"
+tar -C / -Jxpf '/tmp/s6-overlay-noarch.tar.xz'
+tar -C / -Jxpf "/tmp/s6-overlay-${S6_ARCH}.tar.xz"
+
+rm -rf "/tmp/s6-overlay-${S6_ARCH}.tar.xz"
 
 echo -e "${BLUE}❯ ${GREEN}S6-overlay install Complete${RESET}"

+ 3 - 3
global/certbot-dns-plugins.js

@@ -487,9 +487,9 @@ dns_powerdns_api_key = AbCbASsd!@34`,
 		package_name:        'certbot-regru',
 		version_requirement: '~=1.0.2',
 		dependencies:        '',
-		credentials:         `certbot_regru:dns_username=username
-certbot_regru:dns_password=password`,
-		full_plugin_name: 'certbot-regru:dns',
+		credentials:         `dns_username=username
+dns_password=password`,
+		full_plugin_name: 'dns',
 	},
 	//####################################################//
 	rfc2136: {

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно