mirror of
				https://github.com/NginxProxyManager/nginx-proxy-manager.git
				synced 2025-10-30 23:33:34 +00:00 
			
		
		
		
	Compare commits
	
		
			1 Commits
		
	
	
		
			dependabot
			...
			bump-freed
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | c2965789a0 | 
							
								
								
									
										57
									
								
								Jenkinsfile
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										57
									
								
								Jenkinsfile
									
									
									
									
										vendored
									
									
								
							| @@ -128,7 +128,7 @@ pipeline { | |||||||
| 					sh 'docker-compose down --remove-orphans --volumes -t 30 || true' | 					sh 'docker-compose down --remove-orphans --volumes -t 30 || true' | ||||||
| 				} | 				} | ||||||
| 				unstable { | 				unstable { | ||||||
| 					dir(path: 'test/results') { | 					dir(path: 'testing/results') { | ||||||
| 						archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml') | 						archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml') | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| @@ -161,45 +161,7 @@ pipeline { | |||||||
| 					sh 'docker-compose down --remove-orphans --volumes -t 30 || true' | 					sh 'docker-compose down --remove-orphans --volumes -t 30 || true' | ||||||
| 				} | 				} | ||||||
| 				unstable { | 				unstable { | ||||||
| 					dir(path: 'test/results') { | 					dir(path: 'testing/results') { | ||||||
| 						archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml') |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		stage('Test Postgres') { |  | ||||||
| 			environment { |  | ||||||
| 				COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}_postgres" |  | ||||||
| 				COMPOSE_FILE         = 'docker/docker-compose.ci.yml:docker/docker-compose.ci.postgres.yml' |  | ||||||
| 			} |  | ||||||
| 			when { |  | ||||||
| 				not { |  | ||||||
| 					equals expected: 'UNSTABLE', actual: currentBuild.result |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			steps { |  | ||||||
| 				sh 'rm -rf ./test/results/junit/*' |  | ||||||
| 				sh './scripts/ci/fulltest-cypress' |  | ||||||
| 			} |  | ||||||
| 			post { |  | ||||||
| 				always { |  | ||||||
| 					// Dumps to analyze later |  | ||||||
| 					sh 'mkdir -p debug/postgres' |  | ||||||
| 					sh 'docker logs $(docker-compose ps --all -q fullstack) > debug/postgres/docker_fullstack.log 2>&1' |  | ||||||
| 					sh 'docker logs $(docker-compose ps --all -q stepca) > debug/postgres/docker_stepca.log 2>&1' |  | ||||||
| 					sh 'docker logs $(docker-compose ps --all -q pdns) > debug/postgres/docker_pdns.log 2>&1' |  | ||||||
| 					sh 'docker logs $(docker-compose ps --all -q pdns-db) > debug/postgres/docker_pdns-db.log 2>&1' |  | ||||||
| 					sh 'docker logs $(docker-compose ps --all -q dnsrouter) > debug/postgres/docker_dnsrouter.log 2>&1' |  | ||||||
| 					sh 'docker logs $(docker-compose ps --all -q db-postgres) > debug/postgres/docker_db-postgres.log 2>&1' |  | ||||||
| 					sh 'docker logs $(docker-compose ps --all -q authentik) > debug/postgres/docker_authentik.log 2>&1' |  | ||||||
| 					sh 'docker logs $(docker-compose ps --all -q authentik-redis) > debug/postgres/docker_authentik-redis.log 2>&1' |  | ||||||
| 					sh 'docker logs $(docker-compose ps --all -q authentik-ldap) > debug/postgres/docker_authentik-ldap.log 2>&1' |  | ||||||
|  |  | ||||||
| 					junit 'test/results/junit/*' |  | ||||||
| 					sh 'docker-compose down --remove-orphans --volumes -t 30 || true' |  | ||||||
| 				} |  | ||||||
| 				unstable { |  | ||||||
| 					dir(path: 'test/results') { |  | ||||||
| 						archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml') | 						archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml') | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| @@ -241,17 +203,12 @@ pipeline { | |||||||
| 					} | 					} | ||||||
| 					steps { | 					steps { | ||||||
| 						script { | 						script { | ||||||
| 							npmGithubPrComment("""Docker Image for build ${BUILD_NUMBER} is available on [DockerHub](https://cloud.docker.com/repository/docker/nginxproxymanager/${IMAGE}-dev): | 							npmGithubPrComment("""Docker Image for build ${BUILD_NUMBER} is available on | ||||||
| ``` | [DockerHub](https://cloud.docker.com/repository/docker/nginxproxymanager/${IMAGE}-dev) | ||||||
| nginxproxymanager/${IMAGE}-dev:${BRANCH_LOWER} | as `nginxproxymanager/${IMAGE}-dev:${BRANCH_LOWER}` | ||||||
| ``` |  | ||||||
|  |  | ||||||
| > [!NOTE] | **Note:** ensure you backup your NPM instance before testing this image! Especially if there are database changes | ||||||
| > Ensure you backup your NPM instance before testing this image! Especially if there are database changes. | **Note:** this is a different docker image namespace than the official image | ||||||
| > This is a different docker image namespace than the official image. |  | ||||||
|  |  | ||||||
| > [!WARNING] |  | ||||||
| > Changes and additions to DNS Providers require verification by at least 2 members of the community! |  | ||||||
| """, true) | """, true) | ||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| <p align="center"> | <p align="center"> | ||||||
| 	<img src="https://nginxproxymanager.com/github.png"> | 	<img src="https://nginxproxymanager.com/github.png"> | ||||||
| 	<br><br> | 	<br><br> | ||||||
| 	<img src="https://img.shields.io/badge/version-2.12.6-green.svg?style=for-the-badge"> | 	<img src="https://img.shields.io/badge/version-2.12.1-green.svg?style=for-the-badge"> | ||||||
| 	<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager"> | 	<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager"> | ||||||
| 		<img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge"> | 		<img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge"> | ||||||
| 	</a> | 	</a> | ||||||
|   | |||||||
| @@ -3,8 +3,6 @@ | |||||||
| const schema = require('./schema'); | const schema = require('./schema'); | ||||||
| const logger = require('./logger').global; | const logger = require('./logger').global; | ||||||
|  |  | ||||||
| const IP_RANGES_FETCH_ENABLED = process.env.IP_RANGES_FETCH_ENABLED !== 'false'; |  | ||||||
|  |  | ||||||
| async function appStart () { | async function appStart () { | ||||||
| 	const migrate             = require('./migrate'); | 	const migrate             = require('./migrate'); | ||||||
| 	const setup               = require('./setup'); | 	const setup               = require('./setup'); | ||||||
| @@ -15,16 +13,7 @@ async function appStart () { | |||||||
| 	return migrate.latest() | 	return migrate.latest() | ||||||
| 		.then(setup) | 		.then(setup) | ||||||
| 		.then(schema.getCompiledSchema) | 		.then(schema.getCompiledSchema) | ||||||
| 		.then(() => { | 		.then(internalIpRanges.fetch) | ||||||
| 			if (IP_RANGES_FETCH_ENABLED) { |  | ||||||
| 				logger.info('IP Ranges fetch is enabled'); |  | ||||||
| 				return internalIpRanges.fetch().catch((err) => { |  | ||||||
| 					logger.error('IP Ranges fetch failed, continuing anyway:', err.message); |  | ||||||
| 				}); |  | ||||||
| 			} else { |  | ||||||
| 				logger.info('IP Ranges fetch is disabled by environment variable'); |  | ||||||
| 			} |  | ||||||
| 		}) |  | ||||||
| 		.then(() => { | 		.then(() => { | ||||||
| 			internalCertificate.initTimer(); | 			internalCertificate.initTimer(); | ||||||
| 			internalIpRanges.initTimer(); | 			internalIpRanges.initTimer(); | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| const _                     = require('lodash'); | const _                     = require('lodash'); | ||||||
| const fs                    = require('node:fs'); | const fs                    = require('fs'); | ||||||
| const batchflow             = require('batchflow'); | const batchflow             = require('batchflow'); | ||||||
| const logger                = require('../logger').access; | const logger                = require('../logger').access; | ||||||
| const error                 = require('../lib/error'); | const error                 = require('../lib/error'); | ||||||
| @@ -38,7 +38,7 @@ const internalAccessList = { | |||||||
| 			.then((row) => { | 			.then((row) => { | ||||||
| 				data.id = row.id; | 				data.id = row.id; | ||||||
|  |  | ||||||
| 				const promises = []; | 				let promises = []; | ||||||
|  |  | ||||||
| 				// Now add the items | 				// Now add the items | ||||||
| 				data.items.map((item) => { | 				data.items.map((item) => { | ||||||
| @@ -81,7 +81,7 @@ const internalAccessList = { | |||||||
|  |  | ||||||
| 				return internalAccessList.build(row) | 				return internalAccessList.build(row) | ||||||
| 					.then(() => { | 					.then(() => { | ||||||
| 						if (parseInt(row.proxy_host_count, 10)) { | 						if (row.proxy_host_count) { | ||||||
| 							return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts); | 							return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts); | ||||||
| 						} | 						} | ||||||
| 					}) | 					}) | ||||||
| @@ -116,7 +116,7 @@ const internalAccessList = { | |||||||
| 			.then((row) => { | 			.then((row) => { | ||||||
| 				if (row.id !== data.id) { | 				if (row.id !== data.id) { | ||||||
| 					// Sanity check that something crazy hasn't happened | 					// Sanity check that something crazy hasn't happened | ||||||
| 					throw new error.InternalValidationError(`Access List could not be updated, IDs do not match: ${row.id} !== ${data.id}`); | 					throw new error.InternalValidationError('Access List could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); | ||||||
| 				} | 				} | ||||||
| 			}) | 			}) | ||||||
| 			.then(() => { | 			.then(() => { | ||||||
| @@ -135,10 +135,10 @@ const internalAccessList = { | |||||||
| 			.then(() => { | 			.then(() => { | ||||||
| 				// Check for items and add/update/remove them | 				// Check for items and add/update/remove them | ||||||
| 				if (typeof data.items !== 'undefined' && data.items) { | 				if (typeof data.items !== 'undefined' && data.items) { | ||||||
| 					const promises      = []; | 					let promises      = []; | ||||||
| 					const items_to_keep = []; | 					let items_to_keep = []; | ||||||
|  |  | ||||||
| 					data.items.map((item) => { | 					data.items.map(function (item) { | ||||||
| 						if (item.password) { | 						if (item.password) { | ||||||
| 							promises.push(accessListAuthModel | 							promises.push(accessListAuthModel | ||||||
| 								.query() | 								.query() | ||||||
| @@ -154,7 +154,7 @@ const internalAccessList = { | |||||||
| 						} | 						} | ||||||
| 					}); | 					}); | ||||||
|  |  | ||||||
| 					const query = accessListAuthModel | 					let query = accessListAuthModel | ||||||
| 						.query() | 						.query() | ||||||
| 						.delete() | 						.delete() | ||||||
| 						.where('access_list_id', data.id); | 						.where('access_list_id', data.id); | ||||||
| @@ -175,9 +175,9 @@ const internalAccessList = { | |||||||
| 			.then(() => { | 			.then(() => { | ||||||
| 				// Check for clients and add/update/remove them | 				// Check for clients and add/update/remove them | ||||||
| 				if (typeof data.clients !== 'undefined' && data.clients) { | 				if (typeof data.clients !== 'undefined' && data.clients) { | ||||||
| 					const promises = []; | 					let promises = []; | ||||||
|  |  | ||||||
| 					data.clients.map((client) => { | 					data.clients.map(function (client) { | ||||||
| 						if (client.address) { | 						if (client.address) { | ||||||
| 							promises.push(accessListClientModel | 							promises.push(accessListClientModel | ||||||
| 								.query() | 								.query() | ||||||
| @@ -190,7 +190,7 @@ const internalAccessList = { | |||||||
| 						} | 						} | ||||||
| 					}); | 					}); | ||||||
|  |  | ||||||
| 					const query = accessListClientModel | 					let query = accessListClientModel | ||||||
| 						.query() | 						.query() | ||||||
| 						.delete() | 						.delete() | ||||||
| 						.where('access_list_id', data.id); | 						.where('access_list_id', data.id); | ||||||
| @@ -223,7 +223,7 @@ const internalAccessList = { | |||||||
| 			.then((row) => { | 			.then((row) => { | ||||||
| 				return internalAccessList.build(row) | 				return internalAccessList.build(row) | ||||||
| 					.then(() => { | 					.then(() => { | ||||||
| 						if (parseInt(row.proxy_host_count, 10)) { | 						if (row.proxy_host_count) { | ||||||
| 							return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts); | 							return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts); | ||||||
| 						} | 						} | ||||||
| 					}).then(internalNginx.reload) | 					}).then(internalNginx.reload) | ||||||
| @@ -249,16 +249,12 @@ const internalAccessList = { | |||||||
|  |  | ||||||
| 		return access.can('access_lists:get', data.id) | 		return access.can('access_lists:get', data.id) | ||||||
| 			.then((access_data) => { | 			.then((access_data) => { | ||||||
| 				const query = accessListModel | 				let query = accessListModel | ||||||
| 					.query() | 					.query() | ||||||
| 					.select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count')) | 					.select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count')) | ||||||
| 					.leftJoin('proxy_host', function() { | 					.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0') | ||||||
| 						this.on('proxy_host.access_list_id', '=', 'access_list.id') |  | ||||||
| 							.andOn('proxy_host.is_deleted', '=', 0); |  | ||||||
| 					}) |  | ||||||
| 					.where('access_list.is_deleted', 0) | 					.where('access_list.is_deleted', 0) | ||||||
| 					.andWhere('access_list.id', data.id) | 					.andWhere('access_list.id', data.id) | ||||||
| 					.groupBy('access_list.id') |  | ||||||
| 					.allowGraph('[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]') | 					.allowGraph('[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]') | ||||||
| 					.first(); | 					.first(); | ||||||
|  |  | ||||||
| @@ -267,7 +263,7 @@ const internalAccessList = { | |||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				if (typeof data.expand !== 'undefined' && data.expand !== null) { | 				if (typeof data.expand !== 'undefined' && data.expand !== null) { | ||||||
| 					query.withGraphFetched(`[${data.expand.join(', ')}]`); | 					query.withGraphFetched('[' + data.expand.join(', ') + ']'); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				return query.then(utils.omitRow(omissions())); | 				return query.then(utils.omitRow(omissions())); | ||||||
| @@ -327,7 +323,7 @@ const internalAccessList = { | |||||||
| 									// 3. reconfigure those hosts, then reload nginx | 									// 3. reconfigure those hosts, then reload nginx | ||||||
|  |  | ||||||
| 									// set the access_list_id to zero for these items | 									// set the access_list_id to zero for these items | ||||||
| 									row.proxy_hosts.map((_val, idx) => { | 									row.proxy_hosts.map(function (val, idx) { | ||||||
| 										row.proxy_hosts[idx].access_list_id = 0; | 										row.proxy_hosts[idx].access_list_id = 0; | ||||||
| 									}); | 									}); | ||||||
|  |  | ||||||
| @@ -340,11 +336,11 @@ const internalAccessList = { | |||||||
| 					}) | 					}) | ||||||
| 					.then(() => { | 					.then(() => { | ||||||
| 						// delete the htpasswd file | 						// delete the htpasswd file | ||||||
| 						const htpasswd_file = internalAccessList.getFilename(row); | 						let htpasswd_file = internalAccessList.getFilename(row); | ||||||
|  |  | ||||||
| 						try { | 						try { | ||||||
| 							fs.unlinkSync(htpasswd_file); | 							fs.unlinkSync(htpasswd_file); | ||||||
| 						} catch (_err) { | 						} catch (err) { | ||||||
| 							// do nothing | 							// do nothing | ||||||
| 						} | 						} | ||||||
| 					}) | 					}) | ||||||
| @@ -374,13 +370,10 @@ const internalAccessList = { | |||||||
| 	getAll: (access, expand, search_query) => { | 	getAll: (access, expand, search_query) => { | ||||||
| 		return access.can('access_lists:list') | 		return access.can('access_lists:list') | ||||||
| 			.then((access_data) => { | 			.then((access_data) => { | ||||||
| 				const query = accessListModel | 				let query = accessListModel | ||||||
| 					.query() | 					.query() | ||||||
| 					.select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count')) | 					.select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count')) | ||||||
| 					.leftJoin('proxy_host', function() { | 					.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0') | ||||||
| 						this.on('proxy_host.access_list_id', '=', 'access_list.id') |  | ||||||
| 							.andOn('proxy_host.is_deleted', '=', 0); |  | ||||||
| 					}) |  | ||||||
| 					.where('access_list.is_deleted', 0) | 					.where('access_list.is_deleted', 0) | ||||||
| 					.groupBy('access_list.id') | 					.groupBy('access_list.id') | ||||||
| 					.allowGraph('[owner,items,clients]') | 					.allowGraph('[owner,items,clients]') | ||||||
| @@ -393,19 +386,19 @@ const internalAccessList = { | |||||||
| 				// Query is used for searching | 				// Query is used for searching | ||||||
| 				if (typeof search_query === 'string') { | 				if (typeof search_query === 'string') { | ||||||
| 					query.where(function () { | 					query.where(function () { | ||||||
| 						this.where('name', 'like', `%${search_query}%`); | 						this.where('name', 'like', '%' + search_query + '%'); | ||||||
| 					}); | 					}); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				if (typeof expand !== 'undefined' && expand !== null) { | 				if (typeof expand !== 'undefined' && expand !== null) { | ||||||
| 					query.withGraphFetched(`[${expand.join(', ')}]`); | 					query.withGraphFetched('[' + expand.join(', ') + ']'); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				return query.then(utils.omitRows(omissions())); | 				return query.then(utils.omitRows(omissions())); | ||||||
| 			}) | 			}) | ||||||
| 			.then((rows) => { | 			.then((rows) => { | ||||||
| 				if (rows) { | 				if (rows) { | ||||||
| 					rows.map((row, idx) => { | 					rows.map(function (row, idx) { | ||||||
| 						if (typeof row.items !== 'undefined' && row.items) { | 						if (typeof row.items !== 'undefined' && row.items) { | ||||||
| 							rows[idx] = internalAccessList.maskItems(row); | 							rows[idx] = internalAccessList.maskItems(row); | ||||||
| 						} | 						} | ||||||
| @@ -424,7 +417,7 @@ const internalAccessList = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	getCount: (user_id, visibility) => { | 	getCount: (user_id, visibility) => { | ||||||
| 		const query = accessListModel | 		let query = accessListModel | ||||||
| 			.query() | 			.query() | ||||||
| 			.count('id as count') | 			.count('id as count') | ||||||
| 			.where('is_deleted', 0); | 			.where('is_deleted', 0); | ||||||
| @@ -445,7 +438,7 @@ const internalAccessList = { | |||||||
| 	 */ | 	 */ | ||||||
| 	maskItems: (list) => { | 	maskItems: (list) => { | ||||||
| 		if (list && typeof list.items !== 'undefined') { | 		if (list && typeof list.items !== 'undefined') { | ||||||
| 			list.items.map((val, idx) => { | 			list.items.map(function (val, idx) { | ||||||
| 				let repeat_for = 8; | 				let repeat_for = 8; | ||||||
| 				let first_char = '*'; | 				let first_char = '*'; | ||||||
|  |  | ||||||
| @@ -468,7 +461,7 @@ const internalAccessList = { | |||||||
| 	 * @returns {String} | 	 * @returns {String} | ||||||
| 	 */ | 	 */ | ||||||
| 	getFilename: (list) => { | 	getFilename: (list) => { | ||||||
| 		return `/data/access/${list.id}`; | 		return '/data/access/' + list.id; | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| @@ -479,15 +472,15 @@ const internalAccessList = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	build: (list) => { | 	build: (list) => { | ||||||
| 		logger.info(`Building Access file #${list.id} for: ${list.name}`); | 		logger.info('Building Access file #' + list.id + ' for: ' + list.name); | ||||||
|  |  | ||||||
| 		return new Promise((resolve, reject) => { | 		return new Promise((resolve, reject) => { | ||||||
| 			const htpasswd_file = internalAccessList.getFilename(list); | 			let htpasswd_file = internalAccessList.getFilename(list); | ||||||
|  |  | ||||||
| 			// 1. remove any existing access file | 			// 1. remove any existing access file | ||||||
| 			try { | 			try { | ||||||
| 				fs.unlinkSync(htpasswd_file); | 				fs.unlinkSync(htpasswd_file); | ||||||
| 			} catch (_err) { | 			} catch (err) { | ||||||
| 				// do nothing | 				// do nothing | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| @@ -504,17 +497,12 @@ const internalAccessList = { | |||||||
| 				if (list.items.length) { | 				if (list.items.length) { | ||||||
| 					return new Promise((resolve, reject) => { | 					return new Promise((resolve, reject) => { | ||||||
| 						batchflow(list.items).sequential() | 						batchflow(list.items).sequential() | ||||||
| 							.each((_i, item, next) => { | 							.each((i, item, next) => { | ||||||
| 								if (typeof item.password !== 'undefined' && item.password.length) { | 								if (typeof item.password !== 'undefined' && item.password.length) { | ||||||
| 									logger.info(`Adding: ${item.username}`); | 									logger.info('Adding: ' + item.username); | ||||||
|  |  | ||||||
| 									utils.execFile('openssl', ['passwd', '-apr1', item.password]) | 									utils.execFile('/usr/bin/htpasswd', ['-b', htpasswd_file, item.username, item.password]) | ||||||
| 										.then((res) => { | 										.then((/*result*/) => { | ||||||
| 											try { |  | ||||||
| 												fs.appendFileSync(htpasswd_file, `${item.username}:${res}\n`, {encoding: 'utf8'}); |  | ||||||
| 											} catch (err) { |  | ||||||
| 												reject(err); |  | ||||||
| 											} |  | ||||||
| 											next(); | 											next(); | ||||||
| 										}) | 										}) | ||||||
| 										.catch((err) => { | 										.catch((err) => { | ||||||
| @@ -528,7 +516,7 @@ const internalAccessList = { | |||||||
| 								reject(err); | 								reject(err); | ||||||
| 							}) | 							}) | ||||||
| 							.end((results) => { | 							.end((results) => { | ||||||
| 								logger.success(`Built Access file #${list.id} for: ${list.name}`); | 								logger.success('Built Access file #' + list.id + ' for: ' + list.name); | ||||||
| 								resolve(results); | 								resolve(results); | ||||||
| 							}); | 							}); | ||||||
| 					}); | 					}); | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| const error            = require('../lib/error'); | const error         = require('../lib/error'); | ||||||
| const auditLogModel    = require('../models/audit-log'); | const auditLogModel = require('../models/audit-log'); | ||||||
| const {castJsonIfNeed} = require('../lib/helpers'); |  | ||||||
|  |  | ||||||
| const internalAuditLog = { | const internalAuditLog = { | ||||||
|  |  | ||||||
| @@ -23,9 +22,9 @@ const internalAuditLog = { | |||||||
| 					.allowGraph('[user]'); | 					.allowGraph('[user]'); | ||||||
|  |  | ||||||
| 				// Query is used for searching | 				// Query is used for searching | ||||||
| 				if (typeof search_query === 'string' && search_query.length > 0) { | 				if (typeof search_query === 'string') { | ||||||
| 					query.where(function () { | 					query.where(function () { | ||||||
| 						this.where(castJsonIfNeed('meta'), 'like', '%' + search_query + '%'); | 						this.where('meta', 'like', '%' + search_query + '%'); | ||||||
| 					}); | 					}); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| const _                = require('lodash'); | const _                = require('lodash'); | ||||||
| const fs               = require('node:fs'); | const fs               = require('fs'); | ||||||
| const https            = require('node:https'); | const https            = require('https'); | ||||||
| const tempWrite        = require('temp-write'); | const tempWrite        = require('temp-write'); | ||||||
| const moment           = require('moment'); | const moment           = require('moment'); | ||||||
| const archiver         = require('archiver'); | const archiver         = require('archiver'); | ||||||
| @@ -49,7 +49,7 @@ const internalCertificate = { | |||||||
| 	processExpiringHosts: () => { | 	processExpiringHosts: () => { | ||||||
| 		if (!internalCertificate.intervalProcessing) { | 		if (!internalCertificate.intervalProcessing) { | ||||||
| 			internalCertificate.intervalProcessing = true; | 			internalCertificate.intervalProcessing = true; | ||||||
| 			logger.info(`Renewing SSL certs expiring within ${internalCertificate.renewBeforeExpirationBy[0]} ${internalCertificate.renewBeforeExpirationBy[1]} ...`); | 			logger.info('Renewing SSL certs expiring within ' + internalCertificate.renewBeforeExpirationBy[0] + ' ' + internalCertificate.renewBeforeExpirationBy[1] + ' ...'); | ||||||
|  |  | ||||||
| 			const expirationThreshold = moment().add(internalCertificate.renewBeforeExpirationBy[0], internalCertificate.renewBeforeExpirationBy[1]).format('YYYY-MM-DD HH:mm:ss'); | 			const expirationThreshold = moment().add(internalCertificate.renewBeforeExpirationBy[0], internalCertificate.renewBeforeExpirationBy[1]).format('YYYY-MM-DD HH:mm:ss'); | ||||||
|  |  | ||||||
| @@ -70,7 +70,7 @@ const internalCertificate = { | |||||||
| 					 */ | 					 */ | ||||||
| 					let sequence = Promise.resolve(); | 					let sequence = Promise.resolve(); | ||||||
|  |  | ||||||
| 					certificates.forEach((certificate) => { | 					certificates.forEach(function (certificate) { | ||||||
| 						sequence = sequence.then(() => | 						sequence = sequence.then(() => | ||||||
| 							internalCertificate | 							internalCertificate | ||||||
| 								.renew( | 								.renew( | ||||||
| @@ -202,7 +202,7 @@ const internalCertificate = { | |||||||
| 						.then(() => { | 						.then(() => { | ||||||
| 							// At this point, the letsencrypt cert should exist on disk. | 							// At this point, the letsencrypt cert should exist on disk. | ||||||
| 							// Lets get the expiry date from the file and update the row silently | 							// Lets get the expiry date from the file and update the row silently | ||||||
| 							return internalCertificate.getCertificateInfoFromFile(`${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`) | 							return internalCertificate.getCertificateInfoFromFile('/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem') | ||||||
| 								.then((cert_info) => { | 								.then((cert_info) => { | ||||||
| 									return certificateModel | 									return certificateModel | ||||||
| 										.query() | 										.query() | ||||||
| @@ -263,7 +263,7 @@ const internalCertificate = { | |||||||
| 			.then((row) => { | 			.then((row) => { | ||||||
| 				if (row.id !== data.id) { | 				if (row.id !== data.id) { | ||||||
| 					// Sanity check that something crazy hasn't happened | 					// Sanity check that something crazy hasn't happened | ||||||
| 					throw new error.InternalValidationError(`Certificate could not be updated, IDs do not match: ${row.id} !== ${data.id}`); | 					throw new error.InternalValidationError('Certificate could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				return certificateModel | 				return certificateModel | ||||||
| @@ -308,14 +308,11 @@ const internalCertificate = { | |||||||
|  |  | ||||||
| 		return access.can('certificates:get', data.id) | 		return access.can('certificates:get', data.id) | ||||||
| 			.then((access_data) => { | 			.then((access_data) => { | ||||||
| 				const query = certificateModel | 				let query = certificateModel | ||||||
| 					.query() | 					.query() | ||||||
| 					.where('is_deleted', 0) | 					.where('is_deleted', 0) | ||||||
| 					.andWhere('id', data.id) | 					.andWhere('id', data.id) | ||||||
| 					.allowGraph('[owner]') | 					.allowGraph('[owner]') | ||||||
| 					.allowGraph('[proxy_hosts]') |  | ||||||
| 					.allowGraph('[redirection_hosts]') |  | ||||||
| 					.allowGraph('[dead_hosts]') |  | ||||||
| 					.first(); | 					.first(); | ||||||
|  |  | ||||||
| 				if (access_data.permission_visibility !== 'all') { | 				if (access_data.permission_visibility !== 'all') { | ||||||
| @@ -323,7 +320,7 @@ const internalCertificate = { | |||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				if (typeof data.expand !== 'undefined' && data.expand !== null) { | 				if (typeof data.expand !== 'undefined' && data.expand !== null) { | ||||||
| 					query.withGraphFetched(`[${data.expand.join(', ')}]`); | 					query.withGraphFetched('[' + data.expand.join(', ') + ']'); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				return query.then(utils.omitRow(omissions())); | 				return query.then(utils.omitRow(omissions())); | ||||||
| @@ -354,17 +351,17 @@ const internalCertificate = { | |||||||
| 				}) | 				}) | ||||||
| 				.then((certificate) => { | 				.then((certificate) => { | ||||||
| 					if (certificate.provider === 'letsencrypt') { | 					if (certificate.provider === 'letsencrypt') { | ||||||
| 						const zipDirectory = internalCertificate.getLiveCertPath(data.id); | 						const zipDirectory = '/etc/letsencrypt/live/npm-' + data.id; | ||||||
|  |  | ||||||
| 						if (!fs.existsSync(zipDirectory)) { | 						if (!fs.existsSync(zipDirectory)) { | ||||||
| 							throw new error.ItemNotFoundError(`Certificate ${certificate.nice_name} does not exists`); | 							throw new error.ItemNotFoundError('Certificate ' + certificate.nice_name + ' does not exists'); | ||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						const certFiles    = fs.readdirSync(zipDirectory) | 						let certFiles      = fs.readdirSync(zipDirectory) | ||||||
| 							.filter((fn) => fn.endsWith('.pem')) | 							.filter((fn) => fn.endsWith('.pem')) | ||||||
| 							.map((fn) => fs.realpathSync(path.join(zipDirectory, fn))); | 							.map((fn) => fs.realpathSync(path.join(zipDirectory, fn))); | ||||||
| 						const downloadName = `npm-${data.id}-${Date.now()}.zip`; | 						const downloadName = 'npm-' + data.id + '-' + `${Date.now()}.zip`; | ||||||
| 						const opName       = `/tmp/${downloadName}`; | 						const opName       = '/tmp/' + downloadName; | ||||||
| 						internalCertificate.zipFiles(certFiles, opName) | 						internalCertificate.zipFiles(certFiles, opName) | ||||||
| 							.then(() => { | 							.then(() => { | ||||||
| 								logger.debug('zip completed : ', opName); | 								logger.debug('zip completed : ', opName); | ||||||
| @@ -392,7 +389,7 @@ const internalCertificate = { | |||||||
| 		return new Promise((resolve, reject) => { | 		return new Promise((resolve, reject) => { | ||||||
| 			source | 			source | ||||||
| 				.map((fl) => { | 				.map((fl) => { | ||||||
| 					const fileName = path.basename(fl); | 					let fileName = path.basename(fl); | ||||||
| 					logger.debug(fl, 'added to certificate zip'); | 					logger.debug(fl, 'added to certificate zip'); | ||||||
| 					archive.file(fl, { name: fileName }); | 					archive.file(fl, { name: fileName }); | ||||||
| 				}); | 				}); | ||||||
| @@ -462,14 +459,11 @@ const internalCertificate = { | |||||||
| 	getAll: (access, expand, search_query) => { | 	getAll: (access, expand, search_query) => { | ||||||
| 		return access.can('certificates:list') | 		return access.can('certificates:list') | ||||||
| 			.then((access_data) => { | 			.then((access_data) => { | ||||||
| 				const query = certificateModel | 				let query = certificateModel | ||||||
| 					.query() | 					.query() | ||||||
| 					.where('is_deleted', 0) | 					.where('is_deleted', 0) | ||||||
| 					.groupBy('id') | 					.groupBy('id') | ||||||
| 					.allowGraph('[owner]') | 					.allowGraph('[owner]') | ||||||
| 					.allowGraph('[proxy_hosts]') |  | ||||||
| 					.allowGraph('[redirection_hosts]') |  | ||||||
| 					.allowGraph('[dead_hosts]') |  | ||||||
| 					.orderBy('nice_name', 'ASC'); | 					.orderBy('nice_name', 'ASC'); | ||||||
|  |  | ||||||
| 				if (access_data.permission_visibility !== 'all') { | 				if (access_data.permission_visibility !== 'all') { | ||||||
| @@ -479,12 +473,12 @@ const internalCertificate = { | |||||||
| 				// Query is used for searching | 				// Query is used for searching | ||||||
| 				if (typeof search_query === 'string') { | 				if (typeof search_query === 'string') { | ||||||
| 					query.where(function () { | 					query.where(function () { | ||||||
| 						this.where('nice_name', 'like', `%${search_query}%`); | 						this.where('nice_name', 'like', '%' + search_query + '%'); | ||||||
| 					}); | 					}); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				if (typeof expand !== 'undefined' && expand !== null) { | 				if (typeof expand !== 'undefined' && expand !== null) { | ||||||
| 					query.withGraphFetched(`[${expand.join(', ')}]`); | 					query.withGraphFetched('[' + expand.join(', ') + ']'); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				return query.then(utils.omitRows(omissions())); | 				return query.then(utils.omitRows(omissions())); | ||||||
| @@ -499,7 +493,7 @@ const internalCertificate = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	getCount: (user_id, visibility) => { | 	getCount: (user_id, visibility) => { | ||||||
| 		const query = certificateModel | 		let query = certificateModel | ||||||
| 			.query() | 			.query() | ||||||
| 			.count('id as count') | 			.count('id as count') | ||||||
| 			.where('is_deleted', 0); | 			.where('is_deleted', 0); | ||||||
| @@ -521,7 +515,7 @@ const internalCertificate = { | |||||||
| 	writeCustomCert: (certificate) => { | 	writeCustomCert: (certificate) => { | ||||||
| 		logger.info('Writing Custom Certificate:', certificate); | 		logger.info('Writing Custom Certificate:', certificate); | ||||||
|  |  | ||||||
| 		const dir = `/data/custom_ssl/npm-${certificate.id}`; | 		const dir = '/data/custom_ssl/npm-' + certificate.id; | ||||||
|  |  | ||||||
| 		return new Promise((resolve, reject) => { | 		return new Promise((resolve, reject) => { | ||||||
| 			if (certificate.provider === 'letsencrypt') { | 			if (certificate.provider === 'letsencrypt') { | ||||||
| @@ -531,7 +525,7 @@ const internalCertificate = { | |||||||
|  |  | ||||||
| 			let certData = certificate.meta.certificate; | 			let certData = certificate.meta.certificate; | ||||||
| 			if (typeof certificate.meta.intermediate_certificate !== 'undefined') { | 			if (typeof certificate.meta.intermediate_certificate !== 'undefined') { | ||||||
| 				certData = `${certData}\n${certificate.meta.intermediate_certificate}`; | 				certData = certData + '\n' + certificate.meta.intermediate_certificate; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			try { | 			try { | ||||||
| @@ -543,7 +537,7 @@ const internalCertificate = { | |||||||
| 				return; | 				return; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			fs.writeFile(`${dir}/fullchain.pem`, certData, (err) => { | 			fs.writeFile(dir + '/fullchain.pem', certData, function (err) { | ||||||
| 				if (err) { | 				if (err) { | ||||||
| 					reject(err); | 					reject(err); | ||||||
| 				} else { | 				} else { | ||||||
| @@ -553,7 +547,7 @@ const internalCertificate = { | |||||||
| 		}) | 		}) | ||||||
| 			.then(() => { | 			.then(() => { | ||||||
| 				return new Promise((resolve, reject) => { | 				return new Promise((resolve, reject) => { | ||||||
| 					fs.writeFile(`${dir}/privkey.pem`, certificate.meta.certificate_key, (err) => { | 					fs.writeFile(dir + '/privkey.pem', certificate.meta.certificate_key, function (err) { | ||||||
| 						if (err) { | 						if (err) { | ||||||
| 							reject(err); | 							reject(err); | ||||||
| 						} else { | 						} else { | ||||||
| @@ -591,7 +585,7 @@ const internalCertificate = { | |||||||
| 	validate: (data) => { | 	validate: (data) => { | ||||||
| 		return new Promise((resolve) => { | 		return new Promise((resolve) => { | ||||||
| 			// Put file contents into an object | 			// Put file contents into an object | ||||||
| 			const files = {}; | 			let files = {}; | ||||||
| 			_.map(data.files, (file, name) => { | 			_.map(data.files, (file, name) => { | ||||||
| 				if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) { | 				if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) { | ||||||
| 					files[name] = file.data.toString(); | 					files[name] = file.data.toString(); | ||||||
| @@ -603,7 +597,7 @@ const internalCertificate = { | |||||||
| 			.then((files) => { | 			.then((files) => { | ||||||
| 				// For each file, create a temp file and write the contents to it | 				// For each file, create a temp file and write the contents to it | ||||||
| 				// Then test it depending on the file type | 				// Then test it depending on the file type | ||||||
| 				const promises = []; | 				let promises = []; | ||||||
| 				_.map(files, (content, type) => { | 				_.map(files, (content, type) => { | ||||||
| 					promises.push(new Promise((resolve) => { | 					promises.push(new Promise((resolve) => { | ||||||
| 						if (type === 'certificate_key') { | 						if (type === 'certificate_key') { | ||||||
| @@ -688,11 +682,11 @@ const internalCertificate = { | |||||||
| 						reject(new error.ValidationError('Result Validation Error: Validation timed out. This could be due to the key being passphrase-protected.')); | 						reject(new error.ValidationError('Result Validation Error: Validation timed out. This could be due to the key being passphrase-protected.')); | ||||||
| 					}, 10000); | 					}, 10000); | ||||||
| 					utils | 					utils | ||||||
| 						.exec(`openssl pkey -in ${filepath} -check -noout 2>&1 `) | 						.exec('openssl pkey -in ' + filepath + ' -check -noout 2>&1 ') | ||||||
| 						.then((result) => { | 						.then((result) => { | ||||||
| 							clearTimeout(failTimeout); | 							clearTimeout(failTimeout); | ||||||
| 							if (!result.toLowerCase().includes('key is valid')) { | 							if (!result.toLowerCase().includes('key is valid')) { | ||||||
| 								reject(new error.ValidationError(`Result Validation Error: ${result}`)); | 								reject(new error.ValidationError('Result Validation Error: ' + result)); | ||||||
| 							} | 							} | ||||||
| 							fs.unlinkSync(filepath); | 							fs.unlinkSync(filepath); | ||||||
| 							resolve(true); | 							resolve(true); | ||||||
| @@ -700,7 +694,7 @@ const internalCertificate = { | |||||||
| 						.catch((err) => { | 						.catch((err) => { | ||||||
| 							clearTimeout(failTimeout); | 							clearTimeout(failTimeout); | ||||||
| 							fs.unlinkSync(filepath); | 							fs.unlinkSync(filepath); | ||||||
| 							reject(new error.ValidationError(`Certificate Key is not valid (${err.message})`, err)); | 							reject(new error.ValidationError('Certificate Key is not valid (' + err.message + ')', err)); | ||||||
| 						}); | 						}); | ||||||
| 				}); | 				}); | ||||||
| 			}); | 			}); | ||||||
| @@ -735,9 +729,9 @@ const internalCertificate = { | |||||||
| 	 * @param {Boolean} [throw_expired]  Throw when the certificate is out of date | 	 * @param {Boolean} [throw_expired]  Throw when the certificate is out of date | ||||||
| 	 */ | 	 */ | ||||||
| 	getCertificateInfoFromFile: (certificate_file, throw_expired) => { | 	getCertificateInfoFromFile: (certificate_file, throw_expired) => { | ||||||
| 		const certData = {}; | 		let certData = {}; | ||||||
|  |  | ||||||
| 		return utils.execFile('openssl', ['x509', '-in', certificate_file, '-subject', '-noout']) | 		return utils.exec('openssl x509 -in ' + certificate_file + ' -subject -noout') | ||||||
| 			.then((result) => { | 			.then((result) => { | ||||||
| 				// Examples: | 				// Examples: | ||||||
| 				// subject=CN = *.jc21.com | 				// subject=CN = *.jc21.com | ||||||
| @@ -745,11 +739,11 @@ const internalCertificate = { | |||||||
| 				const regex = /(?:subject=)?[^=]+=\s+(\S+)/gim; | 				const regex = /(?:subject=)?[^=]+=\s+(\S+)/gim; | ||||||
| 				const match = regex.exec(result); | 				const match = regex.exec(result); | ||||||
| 				if (match && typeof match[1] !== 'undefined') { | 				if (match && typeof match[1] !== 'undefined') { | ||||||
| 					certData.cn = match[1]; | 					certData['cn'] = match[1]; | ||||||
| 				} | 				} | ||||||
| 			}) | 			}) | ||||||
| 			.then(() => { | 			.then(() => { | ||||||
| 				return utils.execFile('openssl', ['x509', '-in', certificate_file, '-issuer', '-noout']); | 				return utils.exec('openssl x509 -in ' + certificate_file + ' -issuer -noout'); | ||||||
| 			}) | 			}) | ||||||
|  |  | ||||||
| 			.then((result) => { | 			.then((result) => { | ||||||
| @@ -760,11 +754,11 @@ const internalCertificate = { | |||||||
| 				const regex = /^(?:issuer=)?(.*)$/gim; | 				const regex = /^(?:issuer=)?(.*)$/gim; | ||||||
| 				const match = regex.exec(result); | 				const match = regex.exec(result); | ||||||
| 				if (match && typeof match[1] !== 'undefined') { | 				if (match && typeof match[1] !== 'undefined') { | ||||||
| 					certData.issuer = match[1]; | 					certData['issuer'] = match[1]; | ||||||
| 				} | 				} | ||||||
| 			}) | 			}) | ||||||
| 			.then(() => { | 			.then(() => { | ||||||
| 				return utils.execFile('openssl', ['x509', '-in', certificate_file, '-dates', '-noout']); | 				return utils.exec('openssl x509 -in ' + certificate_file + ' -dates -noout'); | ||||||
| 			}) | 			}) | ||||||
| 			.then((result) => { | 			.then((result) => { | ||||||
| 				// notBefore=Jul 14 04:04:29 2018 GMT | 				// notBefore=Jul 14 04:04:29 2018 GMT | ||||||
| @@ -773,7 +767,7 @@ const internalCertificate = { | |||||||
| 				let validTo   = null; | 				let validTo   = null; | ||||||
|  |  | ||||||
| 				const lines = result.split('\n'); | 				const lines = result.split('\n'); | ||||||
| 				lines.map((str) => { | 				lines.map(function (str) { | ||||||
| 					const regex = /^(\S+)=(.*)$/gim; | 					const regex = /^(\S+)=(.*)$/gim; | ||||||
| 					const match = regex.exec(str.trim()); | 					const match = regex.exec(str.trim()); | ||||||
|  |  | ||||||
| @@ -789,21 +783,21 @@ const internalCertificate = { | |||||||
| 				}); | 				}); | ||||||
|  |  | ||||||
| 				if (!validFrom || !validTo) { | 				if (!validFrom || !validTo) { | ||||||
| 					throw new error.ValidationError(`Could not determine dates from certificate: ${result}`); | 					throw new error.ValidationError('Could not determine dates from certificate: ' + result); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				if (throw_expired && validTo < parseInt(moment().format('X'), 10)) { | 				if (throw_expired && validTo < parseInt(moment().format('X'), 10)) { | ||||||
| 					throw new error.ValidationError('Certificate has expired'); | 					throw new error.ValidationError('Certificate has expired'); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				certData.dates = { | 				certData['dates'] = { | ||||||
| 					from: validFrom, | 					from: validFrom, | ||||||
| 					to:   validTo | 					to:   validTo | ||||||
| 				}; | 				}; | ||||||
|  |  | ||||||
| 				return certData; | 				return certData; | ||||||
| 			}).catch((err) => { | 			}).catch((err) => { | ||||||
| 				throw new error.ValidationError(`Certificate is not valid (${err.message})`, err); | 				throw new error.ValidationError('Certificate is not valid (' + err.message + ')', err); | ||||||
| 			}); | 			}); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| @@ -814,7 +808,7 @@ const internalCertificate = { | |||||||
| 	 * @param   {Boolean} [remove] | 	 * @param   {Boolean} [remove] | ||||||
| 	 * @returns {Object} | 	 * @returns {Object} | ||||||
| 	 */ | 	 */ | ||||||
| 	cleanMeta: (meta, remove) => { | 	cleanMeta: function (meta, remove) { | ||||||
| 		internalCertificate.allowedSslFiles.map((key) => { | 		internalCertificate.allowedSslFiles.map((key) => { | ||||||
| 			if (typeof meta[key] !== 'undefined' && meta[key]) { | 			if (typeof meta[key] !== 'undefined' && meta[key]) { | ||||||
| 				if (remove) { | 				if (remove) { | ||||||
| @@ -834,35 +828,24 @@ const internalCertificate = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	requestLetsEncryptSsl: (certificate) => { | 	requestLetsEncryptSsl: (certificate) => { | ||||||
| 		logger.info(`Requesting LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); | 		logger.info('Requesting Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); | ||||||
|  |  | ||||||
| 		const args = [ | 		const cmd = `${certbotCommand} certonly ` + | ||||||
| 			'certonly', | 			`--config '${letsencryptConfig}' ` + | ||||||
| 			'--config', | 			'--work-dir "/tmp/letsencrypt-lib" ' + | ||||||
| 			letsencryptConfig, | 			'--logs-dir "/tmp/letsencrypt-log" ' + | ||||||
| 			'--work-dir', | 			`--cert-name "npm-${certificate.id}" ` + | ||||||
| 			'/tmp/letsencrypt-lib', | 			'--agree-tos ' + | ||||||
| 			'--logs-dir', | 			'--authenticator webroot ' + | ||||||
| 			'/tmp/letsencrypt-log', | 			`--email '${certificate.meta.letsencrypt_email}' ` + | ||||||
| 			'--cert-name', | 			'--preferred-challenges "dns,http" ' + | ||||||
| 			`npm-${certificate.id}`, | 			`--domains "${certificate.domain_names.join(',')}" ` + | ||||||
| 			'--agree-tos', | 			(letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') + | ||||||
| 			'--authenticator', | 			(letsencryptStaging && letsencryptServer === null ? '--staging ' : ''); | ||||||
| 			'webroot', |  | ||||||
| 			'--email', |  | ||||||
| 			certificate.meta.letsencrypt_email, |  | ||||||
| 			'--preferred-challenges', |  | ||||||
| 			'dns,http', |  | ||||||
| 			'--domains', |  | ||||||
| 			certificate.domain_names.join(','), |  | ||||||
| 		]; |  | ||||||
|  |  | ||||||
| 		const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id); | 		logger.info('Command:', cmd); | ||||||
| 		args.push(...adds.args); |  | ||||||
|  |  | ||||||
| 		logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`); | 		return utils.exec(cmd) | ||||||
|  |  | ||||||
| 		return utils.execFile(certbotCommand, args, adds.opts) |  | ||||||
| 			.then((result) => { | 			.then((result) => { | ||||||
| 				logger.success(result); | 				logger.success(result); | ||||||
| 				return result; | 				return result; | ||||||
| @@ -879,48 +862,50 @@ const internalCertificate = { | |||||||
| 	requestLetsEncryptSslWithDnsChallenge: async (certificate) => { | 	requestLetsEncryptSslWithDnsChallenge: async (certificate) => { | ||||||
| 		await certbot.installPlugin(certificate.meta.dns_provider); | 		await certbot.installPlugin(certificate.meta.dns_provider); | ||||||
| 		const dnsPlugin = dnsPlugins[certificate.meta.dns_provider]; | 		const dnsPlugin = dnsPlugins[certificate.meta.dns_provider]; | ||||||
| 		logger.info(`Requesting LetsEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); | 		logger.info(`Requesting Let'sEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); | ||||||
|  |  | ||||||
| 		const credentialsLocation = `/etc/letsencrypt/credentials/credentials-${certificate.id}`; | 		const credentialsLocation = '/etc/letsencrypt/credentials/credentials-' + certificate.id; | ||||||
| 		fs.mkdirSync('/etc/letsencrypt/credentials', { recursive: true }); | 		fs.mkdirSync('/etc/letsencrypt/credentials', { recursive: true }); | ||||||
| 		fs.writeFileSync(credentialsLocation, certificate.meta.dns_provider_credentials, {mode: 0o600}); | 		fs.writeFileSync(credentialsLocation, certificate.meta.dns_provider_credentials, {mode: 0o600}); | ||||||
|  |  | ||||||
| 		// Whether the plugin has a --<name>-credentials argument | 		// Whether the plugin has a --<name>-credentials argument | ||||||
| 		const hasConfigArg = certificate.meta.dns_provider !== 'route53'; | 		const hasConfigArg = certificate.meta.dns_provider !== 'route53'; | ||||||
|  |  | ||||||
| 		const args = [ | 		let mainCmd = certbotCommand + ' certonly ' + | ||||||
| 			'certonly', | 			`--config '${letsencryptConfig}' ` + | ||||||
| 			'--config', | 			'--work-dir "/tmp/letsencrypt-lib" ' + | ||||||
| 			letsencryptConfig, | 			'--logs-dir "/tmp/letsencrypt-log" ' + | ||||||
| 			'--work-dir', | 			`--cert-name 'npm-${certificate.id}' ` + | ||||||
| 			'/tmp/letsencrypt-lib', | 			'--agree-tos ' + | ||||||
| 			'--logs-dir', | 			`--email '${certificate.meta.letsencrypt_email}' ` + | ||||||
| 			'/tmp/letsencrypt-log', | 			`--domains '${certificate.domain_names.join(',')}' ` + | ||||||
| 			'--cert-name', | 			`--authenticator '${dnsPlugin.full_plugin_name}' ` + | ||||||
| 			`npm-${certificate.id}`, | 			( | ||||||
| 			'--agree-tos', | 				hasConfigArg | ||||||
| 			'--email', | 					? `--${dnsPlugin.full_plugin_name}-credentials '${credentialsLocation}' ` | ||||||
| 			certificate.meta.letsencrypt_email, | 					: '' | ||||||
| 			'--domains', | 			) + | ||||||
| 			certificate.domain_names.join(','), | 			( | ||||||
| 			'--authenticator', | 				certificate.meta.propagation_seconds !== undefined | ||||||
| 			dnsPlugin.full_plugin_name, | 					? `--${dnsPlugin.full_plugin_name}-propagation-seconds '${certificate.meta.propagation_seconds}' ` | ||||||
| 		]; | 					: '' | ||||||
|  | 			) + | ||||||
|  | 			(letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') + | ||||||
|  | 			(letsencryptStaging && letsencryptServer === null ? '--staging ' : ''); | ||||||
|  |  | ||||||
| 		if (hasConfigArg) { | 		// Prepend the path to the credentials file as an environment variable | ||||||
| 			args.push(`--${dnsPlugin.full_plugin_name}-credentials`, credentialsLocation); | 		if (certificate.meta.dns_provider === 'route53') { | ||||||
| 		} | 			mainCmd = 'AWS_CONFIG_FILE=\'' + credentialsLocation + '\' ' + mainCmd; | ||||||
| 		if (certificate.meta.propagation_seconds !== undefined) { |  | ||||||
| 			args.push(`--${dnsPlugin.full_plugin_name}-propagation-seconds`, certificate.meta.propagation_seconds.toString()); |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); | 		if (certificate.meta.dns_provider === 'duckdns') { | ||||||
| 		args.push(...adds.args); | 			mainCmd = mainCmd + ' --dns-duckdns-no-txt-restore'; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`); | 		logger.info('Command:', mainCmd); | ||||||
|  |  | ||||||
| 		try { | 		try { | ||||||
| 			const result = await utils.execFile(certbotCommand, args, adds.opts); | 			const result = await utils.exec(mainCmd); | ||||||
| 			logger.info(result); | 			logger.info(result); | ||||||
| 			return result; | 			return result; | ||||||
| 		} catch (err) { | 		} catch (err) { | ||||||
| @@ -948,7 +933,7 @@ const internalCertificate = { | |||||||
|  |  | ||||||
| 					return renewMethod(certificate) | 					return renewMethod(certificate) | ||||||
| 						.then(() => { | 						.then(() => { | ||||||
| 							return internalCertificate.getCertificateInfoFromFile(`${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`); | 							return internalCertificate.getCertificateInfoFromFile('/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem'); | ||||||
| 						}) | 						}) | ||||||
| 						.then((cert_info) => { | 						.then((cert_info) => { | ||||||
| 							return certificateModel | 							return certificateModel | ||||||
| @@ -980,31 +965,22 @@ const internalCertificate = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	renewLetsEncryptSsl: (certificate) => { | 	renewLetsEncryptSsl: (certificate) => { | ||||||
| 		logger.info(`Renewing LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); | 		logger.info('Renewing Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); | ||||||
|  |  | ||||||
| 		const args = [ | 		const cmd = certbotCommand + ' renew --force-renewal ' + | ||||||
| 			'renew', | 			`--config '${letsencryptConfig}' ` + | ||||||
| 			'--force-renewal', | 			'--work-dir "/tmp/letsencrypt-lib" ' + | ||||||
| 			'--config', | 			'--logs-dir "/tmp/letsencrypt-log" ' + | ||||||
| 			letsencryptConfig, | 			`--cert-name 'npm-${certificate.id}' ` + | ||||||
| 			'--work-dir', | 			'--preferred-challenges "dns,http" ' + | ||||||
| 			'/tmp/letsencrypt-lib', | 			'--no-random-sleep-on-renew ' + | ||||||
| 			'--logs-dir', | 			'--disable-hook-validation ' + | ||||||
| 			'/tmp/letsencrypt-log', | 			(letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') + | ||||||
| 			'--cert-name', | 			(letsencryptStaging && letsencryptServer === null ? '--staging ' : ''); | ||||||
| 			`npm-${certificate.id}`, |  | ||||||
| 			'--preferred-challenges', |  | ||||||
| 			'dns,http', |  | ||||||
| 			'--no-random-sleep-on-renew', |  | ||||||
| 			'--disable-hook-validation', |  | ||||||
| 		]; |  | ||||||
|  |  | ||||||
| 		const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); | 		logger.info('Command:', cmd); | ||||||
| 		args.push(...adds.args); |  | ||||||
|  |  | ||||||
| 		logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`); | 		return utils.exec(cmd) | ||||||
|  |  | ||||||
| 		return utils.execFile(certbotCommand, args, adds.opts) |  | ||||||
| 			.then((result) => { | 			.then((result) => { | ||||||
| 				logger.info(result); | 				logger.info(result); | ||||||
| 				return result; | 				return result; | ||||||
| @@ -1022,29 +998,27 @@ const internalCertificate = { | |||||||
| 			throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`); | 			throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		logger.info(`Renewing LetsEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); | 		logger.info(`Renewing Let'sEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); | ||||||
|  |  | ||||||
| 		const args = [ | 		let mainCmd = certbotCommand + ' renew --force-renewal ' + | ||||||
| 			'renew', | 			`--config "${letsencryptConfig}" ` + | ||||||
| 			'--force-renewal', | 			'--work-dir "/tmp/letsencrypt-lib" ' + | ||||||
| 			'--config', | 			'--logs-dir "/tmp/letsencrypt-log" ' + | ||||||
| 			letsencryptConfig, | 			`--cert-name 'npm-${certificate.id}' ` + | ||||||
| 			'--work-dir', | 			'--disable-hook-validation ' + | ||||||
| 			'/tmp/letsencrypt-lib', | 			'--no-random-sleep-on-renew ' + | ||||||
| 			'--logs-dir', | 			(letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') + | ||||||
| 			'/tmp/letsencrypt-log', | 			(letsencryptStaging && letsencryptServer === null ? '--staging ' : ''); | ||||||
| 			'--cert-name', |  | ||||||
| 			`npm-${certificate.id}`, |  | ||||||
| 			'--disable-hook-validation', |  | ||||||
| 			'--no-random-sleep-on-renew', |  | ||||||
| 		]; |  | ||||||
|  |  | ||||||
| 		const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); | 		// Prepend the path to the credentials file as an environment variable | ||||||
| 		args.push(...adds.args); | 		if (certificate.meta.dns_provider === 'route53') { | ||||||
|  | 			const credentialsLocation = '/etc/letsencrypt/credentials/credentials-' + certificate.id; | ||||||
|  | 			mainCmd                   = 'AWS_CONFIG_FILE=\'' + credentialsLocation + '\' ' + mainCmd; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`); | 		logger.info('Command:', mainCmd); | ||||||
|  |  | ||||||
| 		return utils.execFile(certbotCommand, args, adds.opts) | 		return utils.exec(mainCmd) | ||||||
| 			.then(async (result) => { | 			.then(async (result) => { | ||||||
| 				logger.info(result); | 				logger.info(result); | ||||||
| 				return result; | 				return result; | ||||||
| @@ -1057,29 +1031,25 @@ const internalCertificate = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	revokeLetsEncryptSsl: (certificate, throw_errors) => { | 	revokeLetsEncryptSsl: (certificate, throw_errors) => { | ||||||
| 		logger.info(`Revoking LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); | 		logger.info('Revoking Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); | ||||||
|  |  | ||||||
| 		const args = [ | 		const mainCmd = certbotCommand + ' revoke ' + | ||||||
| 			'revoke', | 			`--config '${letsencryptConfig}' ` + | ||||||
| 			'--config', | 			'--work-dir "/tmp/letsencrypt-lib" ' + | ||||||
| 			letsencryptConfig, | 			'--logs-dir "/tmp/letsencrypt-log" ' + | ||||||
| 			'--work-dir', | 			`--cert-path '/etc/letsencrypt/live/npm-${certificate.id}/fullchain.pem' ` + | ||||||
| 			'/tmp/letsencrypt-lib', | 			'--delete-after-revoke ' + | ||||||
| 			'--logs-dir', | 			(letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') + | ||||||
| 			'/tmp/letsencrypt-log', | 			(letsencryptStaging && letsencryptServer === null ? '--staging ' : ''); | ||||||
| 			'--cert-path', |  | ||||||
| 			`${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`, |  | ||||||
| 			'--delete-after-revoke', |  | ||||||
| 		]; |  | ||||||
|  |  | ||||||
| 		const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id); | 		// Don't fail command if file does not exist | ||||||
| 		args.push(...adds.args); | 		const delete_credentialsCmd = `rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`; | ||||||
|  |  | ||||||
| 		logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`); | 		logger.info('Command:', mainCmd + '; ' + delete_credentialsCmd); | ||||||
|  |  | ||||||
| 		return utils.execFile(certbotCommand, args, adds.opts) | 		return utils.exec(mainCmd) | ||||||
| 			.then(async (result) => { | 			.then(async (result) => { | ||||||
| 				await utils.exec(`rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`); | 				await utils.exec(delete_credentialsCmd); | ||||||
| 				logger.info(result); | 				logger.info(result); | ||||||
| 				return result; | 				return result; | ||||||
| 			}) | 			}) | ||||||
| @@ -1097,8 +1067,9 @@ const internalCertificate = { | |||||||
| 	 * @returns {Boolean} | 	 * @returns {Boolean} | ||||||
| 	 */ | 	 */ | ||||||
| 	hasLetsEncryptSslCerts: (certificate) => { | 	hasLetsEncryptSslCerts: (certificate) => { | ||||||
| 		const letsencryptPath = internalCertificate.getLiveCertPath(certificate.id); | 		const letsencryptPath = '/etc/letsencrypt/live/npm-' + certificate.id; | ||||||
| 		return fs.existsSync(`${letsencryptPath}/fullchain.pem`) && fs.existsSync(`${letsencryptPath}/privkey.pem`); |  | ||||||
|  | 		return fs.existsSync(letsencryptPath + '/fullchain.pem') && fs.existsSync(letsencryptPath + '/privkey.pem'); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| @@ -1110,7 +1081,7 @@ const internalCertificate = { | |||||||
| 	 */ | 	 */ | ||||||
| 	disableInUseHosts: (in_use_result) => { | 	disableInUseHosts: (in_use_result) => { | ||||||
| 		if (in_use_result.total_count) { | 		if (in_use_result.total_count) { | ||||||
| 			const promises = []; | 			let promises = []; | ||||||
|  |  | ||||||
| 			if (in_use_result.proxy_hosts.length) { | 			if (in_use_result.proxy_hosts.length) { | ||||||
| 				promises.push(internalNginx.bulkDeleteConfigs('proxy_host', in_use_result.proxy_hosts)); | 				promises.push(internalNginx.bulkDeleteConfigs('proxy_host', in_use_result.proxy_hosts)); | ||||||
| @@ -1140,7 +1111,7 @@ const internalCertificate = { | |||||||
| 	 */ | 	 */ | ||||||
| 	enableInUseHosts: (in_use_result) => { | 	enableInUseHosts: (in_use_result) => { | ||||||
| 		if (in_use_result.total_count) { | 		if (in_use_result.total_count) { | ||||||
| 			const promises = []; | 			let promises = []; | ||||||
|  |  | ||||||
| 			if (in_use_result.proxy_hosts.length) { | 			if (in_use_result.proxy_hosts.length) { | ||||||
| 				promises.push(internalNginx.bulkGenerateConfigs('proxy_host', in_use_result.proxy_hosts)); | 				promises.push(internalNginx.bulkGenerateConfigs('proxy_host', in_use_result.proxy_hosts)); | ||||||
| @@ -1173,12 +1144,12 @@ const internalCertificate = { | |||||||
|  |  | ||||||
| 		// Create a test challenge file | 		// Create a test challenge file | ||||||
| 		const testChallengeDir  = '/data/letsencrypt-acme-challenge/.well-known/acme-challenge'; | 		const testChallengeDir  = '/data/letsencrypt-acme-challenge/.well-known/acme-challenge'; | ||||||
| 		const testChallengeFile = `${testChallengeDir}/test-challenge`; | 		const testChallengeFile = testChallengeDir + '/test-challenge'; | ||||||
| 		fs.mkdirSync(testChallengeDir, {recursive: true}); | 		fs.mkdirSync(testChallengeDir, {recursive: true}); | ||||||
| 		fs.writeFileSync(testChallengeFile, 'Success', {encoding: 'utf8'}); | 		fs.writeFileSync(testChallengeFile, 'Success', {encoding: 'utf8'}); | ||||||
|  |  | ||||||
| 		async function performTestForDomain (domain) { | 		async function performTestForDomain (domain) { | ||||||
| 			logger.info(`Testing http challenge for ${domain}`); | 			logger.info('Testing http challenge for ' + domain); | ||||||
| 			const url      = `http://${domain}/.well-known/acme-challenge/test-challenge`; | 			const url      = `http://${domain}/.well-known/acme-challenge/test-challenge`; | ||||||
| 			const formBody = `method=G&url=${encodeURI(url)}&bodytype=T&requestbody=&headername=User-Agent&headervalue=None&locationid=1&ch=false&cc=false`; | 			const formBody = `method=G&url=${encodeURI(url)}&bodytype=T&requestbody=&headername=User-Agent&headervalue=None&locationid=1&ch=false&cc=false`; | ||||||
| 			const options  = { | 			const options  = { | ||||||
| @@ -1192,16 +1163,13 @@ const internalCertificate = { | |||||||
|  |  | ||||||
| 			const result = await new Promise((resolve) => { | 			const result = await new Promise((resolve) => { | ||||||
|  |  | ||||||
| 				const req = https.request('https://www.site24x7.com/tools/restapi-tester', options, (res) => { | 				const req = https.request('https://www.site24x7.com/tools/restapi-tester', options, function (res) { | ||||||
| 					let responseBody = ''; | 					let responseBody = ''; | ||||||
|  |  | ||||||
| 					res.on('data', (chunk) => { | 					res.on('data', (chunk) => responseBody = responseBody + chunk); | ||||||
| 						responseBody = responseBody + chunk; | 					res.on('end', function () { | ||||||
| 					}); |  | ||||||
|  |  | ||||||
| 					res.on('end', () => { |  | ||||||
| 						try { | 						try { | ||||||
| 							const parsedBody = JSON.parse(`${responseBody}`); | 							const parsedBody = JSON.parse(responseBody + ''); | ||||||
| 							if (res.statusCode !== 200) { | 							if (res.statusCode !== 200) { | ||||||
| 								logger.warn(`Failed to test HTTP challenge for domain ${domain} because HTTP status code ${res.statusCode} was returned: ${parsedBody.message}`); | 								logger.warn(`Failed to test HTTP challenge for domain ${domain} because HTTP status code ${res.statusCode} was returned: ${parsedBody.message}`); | ||||||
| 								resolve(undefined); | 								resolve(undefined); | ||||||
| @@ -1222,7 +1190,7 @@ const internalCertificate = { | |||||||
| 				// Make sure to write the request body. | 				// Make sure to write the request body. | ||||||
| 				req.write(formBody); | 				req.write(formBody); | ||||||
| 				req.end(); | 				req.end(); | ||||||
| 				req.on('error', (e) => { logger.warn(`Failed to test HTTP challenge for domain ${domain}`, e); | 				req.on('error', function (e) { logger.warn(`Failed to test HTTP challenge for domain ${domain}`, e); | ||||||
| 					resolve(undefined); }); | 					resolve(undefined); }); | ||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
| @@ -1264,34 +1232,6 @@ const internalCertificate = { | |||||||
| 		fs.unlinkSync(testChallengeFile); | 		fs.unlinkSync(testChallengeFile); | ||||||
|  |  | ||||||
| 		return results; | 		return results; | ||||||
| 	}, |  | ||||||
|  |  | ||||||
| 	getAdditionalCertbotArgs: (certificate_id, dns_provider) => { |  | ||||||
| 		const args = []; |  | ||||||
| 		if (letsencryptServer !== null) { |  | ||||||
| 			args.push('--server', letsencryptServer); |  | ||||||
| 		} |  | ||||||
| 		if (letsencryptStaging && letsencryptServer === null) { |  | ||||||
| 			args.push('--staging'); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// For route53, add the credentials file as an environment variable, |  | ||||||
| 		// inheriting the process env |  | ||||||
| 		const opts = {}; |  | ||||||
| 		if (certificate_id && dns_provider === 'route53') { |  | ||||||
| 			opts.env                 = process.env; |  | ||||||
| 			opts.env.AWS_CONFIG_FILE = `/etc/letsencrypt/credentials/credentials-${certificate_id}`; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if (dns_provider === 'duckdns') { |  | ||||||
| 			args.push('--dns-duckdns-no-txt-restore'); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		return {args: args, opts: opts}; |  | ||||||
| 	}, |  | ||||||
|  |  | ||||||
| 	getLiveCertPath: (certificate_id) => { |  | ||||||
| 		return `/etc/letsencrypt/live/npm-${certificate_id}`; |  | ||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,7 +6,6 @@ const internalHost        = require('./host'); | |||||||
| const internalNginx       = require('./nginx'); | const internalNginx       = require('./nginx'); | ||||||
| const internalAuditLog    = require('./audit-log'); | const internalAuditLog    = require('./audit-log'); | ||||||
| const internalCertificate = require('./certificate'); | const internalCertificate = require('./certificate'); | ||||||
| const {castJsonIfNeed}    = require('../lib/helpers'); |  | ||||||
|  |  | ||||||
| function omissions () { | function omissions () { | ||||||
| 	return ['is_deleted']; | 	return ['is_deleted']; | ||||||
| @@ -410,16 +409,16 @@ const internalDeadHost = { | |||||||
| 					.where('is_deleted', 0) | 					.where('is_deleted', 0) | ||||||
| 					.groupBy('id') | 					.groupBy('id') | ||||||
| 					.allowGraph('[owner,certificate]') | 					.allowGraph('[owner,certificate]') | ||||||
| 					.orderBy(castJsonIfNeed('domain_names'), 'ASC'); | 					.orderBy('domain_names', 'ASC'); | ||||||
|  |  | ||||||
| 				if (access_data.permission_visibility !== 'all') { | 				if (access_data.permission_visibility !== 'all') { | ||||||
| 					query.andWhere('owner_user_id', access.token.getUserId(1)); | 					query.andWhere('owner_user_id', access.token.getUserId(1)); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				// Query is used for searching | 				// Query is used for searching | ||||||
| 				if (typeof search_query === 'string' && search_query.length > 0) { | 				if (typeof search_query === 'string') { | ||||||
| 					query.where(function () { | 					query.where(function () { | ||||||
| 						this.where(castJsonIfNeed('domain_names'), 'like', '%' + search_query + '%'); | 						this.where('domain_names', 'like', '%' + search_query + '%'); | ||||||
| 					}); | 					}); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,7 +2,6 @@ const _                    = require('lodash'); | |||||||
| const proxyHostModel       = require('../models/proxy_host'); | const proxyHostModel       = require('../models/proxy_host'); | ||||||
| const redirectionHostModel = require('../models/redirection_host'); | const redirectionHostModel = require('../models/redirection_host'); | ||||||
| const deadHostModel        = require('../models/dead_host'); | const deadHostModel        = require('../models/dead_host'); | ||||||
| const {castJsonIfNeed}     = require('../lib/helpers'); |  | ||||||
|  |  | ||||||
| const internalHost = { | const internalHost = { | ||||||
|  |  | ||||||
| @@ -18,7 +17,7 @@ const internalHost = { | |||||||
| 	cleanSslHstsData: function (data, existing_data) { | 	cleanSslHstsData: function (data, existing_data) { | ||||||
| 		existing_data = existing_data === undefined ? {} : existing_data; | 		existing_data = existing_data === undefined ? {} : existing_data; | ||||||
|  |  | ||||||
| 		const combined_data = _.assign({}, existing_data, data); | 		let combined_data = _.assign({}, existing_data, data); | ||||||
|  |  | ||||||
| 		if (!combined_data.certificate_id) { | 		if (!combined_data.certificate_id) { | ||||||
| 			combined_data.ssl_forced    = false; | 			combined_data.ssl_forced    = false; | ||||||
| @@ -74,7 +73,7 @@ const internalHost = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	getHostsWithDomains: function (domain_names) { | 	getHostsWithDomains: function (domain_names) { | ||||||
| 		const promises = [ | 		let promises = [ | ||||||
| 			proxyHostModel | 			proxyHostModel | ||||||
| 				.query() | 				.query() | ||||||
| 				.where('is_deleted', 0), | 				.where('is_deleted', 0), | ||||||
| @@ -126,19 +125,19 @@ const internalHost = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	isHostnameTaken: function (hostname, ignore_type, ignore_id) { | 	isHostnameTaken: function (hostname, ignore_type, ignore_id) { | ||||||
| 		const promises = [ | 		let promises = [ | ||||||
| 			proxyHostModel | 			proxyHostModel | ||||||
| 				.query() | 				.query() | ||||||
| 				.where('is_deleted', 0) | 				.where('is_deleted', 0) | ||||||
| 				.andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%'), | 				.andWhere('domain_names', 'like', '%' + hostname + '%'), | ||||||
| 			redirectionHostModel | 			redirectionHostModel | ||||||
| 				.query() | 				.query() | ||||||
| 				.where('is_deleted', 0) | 				.where('is_deleted', 0) | ||||||
| 				.andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%'), | 				.andWhere('domain_names', 'like', '%' + hostname + '%'), | ||||||
| 			deadHostModel | 			deadHostModel | ||||||
| 				.query() | 				.query() | ||||||
| 				.where('is_deleted', 0) | 				.where('is_deleted', 0) | ||||||
| 				.andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%') | 				.andWhere('domain_names', 'like', '%' + hostname + '%') | ||||||
| 		]; | 		]; | ||||||
|  |  | ||||||
| 		return Promise.all(promises) | 		return Promise.all(promises) | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| const _      = require('lodash'); | const _      = require('lodash'); | ||||||
| const fs     = require('node:fs'); | const fs     = require('fs'); | ||||||
| const logger = require('../logger').nginx; | const logger = require('../logger').nginx; | ||||||
| const config = require('../lib/config'); | const config = require('../lib/config'); | ||||||
| const utils  = require('../lib/utils'); | const utils  = require('../lib/utils'); | ||||||
| @@ -57,9 +57,9 @@ const internalNginx = { | |||||||
| 						// It will always look like this: | 						// 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) | 						//   nginx: [alert] could not open error log file: open() "/var/log/nginx/error.log" failed (6: No such device or address) | ||||||
|  |  | ||||||
| 						const valid_lines = []; | 						let valid_lines = []; | ||||||
| 						const err_lines   = err.message.split('\n'); | 						let err_lines   = err.message.split('\n'); | ||||||
| 						err_lines.map((line) => { | 						err_lines.map(function (line) { | ||||||
| 							if (line.indexOf('/var/log/nginx/error.log') === -1) { | 							if (line.indexOf('/var/log/nginx/error.log') === -1) { | ||||||
| 								valid_lines.push(line); | 								valid_lines.push(line); | ||||||
| 							} | 							} | ||||||
| @@ -105,7 +105,7 @@ const internalNginx = { | |||||||
| 			logger.info('Testing Nginx configuration'); | 			logger.info('Testing Nginx configuration'); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		return utils.execFile('/usr/sbin/nginx', ['-t', '-g', 'error_log off;']); | 		return utils.exec('/usr/sbin/nginx -t -g "error_log off;"'); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| @@ -115,7 +115,7 @@ const internalNginx = { | |||||||
| 		return internalNginx.test() | 		return internalNginx.test() | ||||||
| 			.then(() => { | 			.then(() => { | ||||||
| 				logger.info('Reloading Nginx'); | 				logger.info('Reloading Nginx'); | ||||||
| 				return utils.execFile('/usr/sbin/nginx', ['-s', 'reload']); | 				return utils.exec('/usr/sbin/nginx -s reload'); | ||||||
| 			}); | 			}); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| @@ -128,7 +128,7 @@ const internalNginx = { | |||||||
| 		if (host_type === 'default') { | 		if (host_type === 'default') { | ||||||
| 			return '/data/nginx/default_host/site.conf'; | 			return '/data/nginx/default_host/site.conf'; | ||||||
| 		} | 		} | ||||||
| 		return `/data/nginx/${internalNginx.getFileFriendlyHostType(host_type)}/${host_id}.conf`; | 		return '/data/nginx/' + internalNginx.getFileFriendlyHostType(host_type) + '/' + host_id + '.conf'; | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| @@ -141,7 +141,7 @@ const internalNginx = { | |||||||
| 			let template; | 			let template; | ||||||
|  |  | ||||||
| 			try { | 			try { | ||||||
| 				template = fs.readFileSync(`${__dirname}/../templates/_location.conf`, {encoding: 'utf8'}); | 				template = fs.readFileSync(__dirname + '/../templates/_location.conf', {encoding: 'utf8'}); | ||||||
| 			} catch (err) { | 			} catch (err) { | ||||||
| 				reject(new error.ConfigurationError(err.message)); | 				reject(new error.ConfigurationError(err.message)); | ||||||
| 				return; | 				return; | ||||||
| @@ -152,7 +152,7 @@ const internalNginx = { | |||||||
|  |  | ||||||
| 			const locationRendering = async () => { | 			const locationRendering = async () => { | ||||||
| 				for (let i = 0; i < host.locations.length; i++) { | 				for (let i = 0; i < host.locations.length; i++) { | ||||||
| 					const 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}, | 						{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}, | 						{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}, | 						{hsts_enabled: host.hsts_enabled}, {hsts_subdomains: host.hsts_subdomains}, {access_list: host.access_list}, | ||||||
| @@ -183,21 +183,21 @@ const internalNginx = { | |||||||
| 	 */ | 	 */ | ||||||
| 	generateConfig: (host_type, host_row) => { | 	generateConfig: (host_type, host_row) => { | ||||||
| 		// Prevent modifying the original object: | 		// Prevent modifying the original object: | ||||||
| 		const host           = JSON.parse(JSON.stringify(host_row)); | 		let host             = JSON.parse(JSON.stringify(host_row)); | ||||||
| 		const nice_host_type = internalNginx.getFileFriendlyHostType(host_type); | 		const nice_host_type = internalNginx.getFileFriendlyHostType(host_type); | ||||||
|  |  | ||||||
| 		if (config.debug()) { | 		if (config.debug()) { | ||||||
| 			logger.info(`Generating ${nice_host_type} Config:`, JSON.stringify(host, null, 2)); | 			logger.info('Generating ' + nice_host_type + ' Config:', JSON.stringify(host, null, 2)); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		const renderEngine = utils.getRenderEngine(); | 		const renderEngine = utils.getRenderEngine(); | ||||||
|  |  | ||||||
| 		return new Promise((resolve, reject) => { | 		return new Promise((resolve, reject) => { | ||||||
| 			let template   = null; | 			let template = null; | ||||||
| 			const filename = internalNginx.getConfigName(nice_host_type, host.id); | 			let filename = internalNginx.getConfigName(nice_host_type, host.id); | ||||||
|  |  | ||||||
| 			try { | 			try { | ||||||
| 				template = fs.readFileSync(`${__dirname}/../templates/${nice_host_type}.conf`, {encoding: 'utf8'}); | 				template = fs.readFileSync(__dirname + '/../templates/' + nice_host_type + '.conf', {encoding: 'utf8'}); | ||||||
| 			} catch (err) { | 			} catch (err) { | ||||||
| 				reject(new error.ConfigurationError(err.message)); | 				reject(new error.ConfigurationError(err.message)); | ||||||
| 				return; | 				return; | ||||||
| @@ -252,7 +252,7 @@ const internalNginx = { | |||||||
| 					}) | 					}) | ||||||
| 					.catch((err) => { | 					.catch((err) => { | ||||||
| 						if (config.debug()) { | 						if (config.debug()) { | ||||||
| 							logger.warn(`Could not write ${filename}:`, err.message); | 							logger.warn('Could not write ' + filename + ':', err.message); | ||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						reject(new error.ConfigurationError(err.message)); | 						reject(new error.ConfigurationError(err.message)); | ||||||
| @@ -277,11 +277,11 @@ const internalNginx = { | |||||||
| 		const renderEngine = utils.getRenderEngine(); | 		const renderEngine = utils.getRenderEngine(); | ||||||
|  |  | ||||||
| 		return new Promise((resolve, reject) => { | 		return new Promise((resolve, reject) => { | ||||||
| 			let template   = null; | 			let template = null; | ||||||
| 			const filename = `/data/nginx/temp/letsencrypt_${certificate.id}.conf`; | 			let filename = '/data/nginx/temp/letsencrypt_' + certificate.id + '.conf'; | ||||||
|  |  | ||||||
| 			try { | 			try { | ||||||
| 				template = fs.readFileSync(`${__dirname}/../templates/letsencrypt-request.conf`, {encoding: 'utf8'}); | 				template = fs.readFileSync(__dirname + '/../templates/letsencrypt-request.conf', {encoding: 'utf8'}); | ||||||
| 			} catch (err) { | 			} catch (err) { | ||||||
| 				reject(new error.ConfigurationError(err.message)); | 				reject(new error.ConfigurationError(err.message)); | ||||||
| 				return; | 				return; | ||||||
| @@ -302,7 +302,7 @@ const internalNginx = { | |||||||
| 				}) | 				}) | ||||||
| 				.catch((err) => { | 				.catch((err) => { | ||||||
| 					if (config.debug()) { | 					if (config.debug()) { | ||||||
| 						logger.warn(`Could not write ${filename}:`, err.message); | 						logger.warn('Could not write ' + filename + ':', err.message); | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 					reject(new error.ConfigurationError(err.message)); | 					reject(new error.ConfigurationError(err.message)); | ||||||
| @@ -316,7 +316,7 @@ const internalNginx = { | |||||||
| 	 * @param   {String}  filename | 	 * @param   {String}  filename | ||||||
| 	 */ | 	 */ | ||||||
| 	deleteFile: (filename) => { | 	deleteFile: (filename) => { | ||||||
| 		logger.debug(`Deleting file: ${filename}`); | 		logger.debug('Deleting file: ' + filename); | ||||||
| 		try { | 		try { | ||||||
| 			fs.unlinkSync(filename); | 			fs.unlinkSync(filename); | ||||||
| 		} catch (err) { | 		} catch (err) { | ||||||
| @@ -330,7 +330,7 @@ const internalNginx = { | |||||||
| 	 * @returns String | 	 * @returns String | ||||||
| 	 */ | 	 */ | ||||||
| 	getFileFriendlyHostType: (host_type) => { | 	getFileFriendlyHostType: (host_type) => { | ||||||
| 		return host_type.replace(/-/g, '_'); | 		return host_type.replace(new RegExp('-', 'g'), '_'); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| @@ -340,7 +340,7 @@ const internalNginx = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	deleteLetsEncryptRequestConfig: (certificate) => { | 	deleteLetsEncryptRequestConfig: (certificate) => { | ||||||
| 		const config_file = `/data/nginx/temp/letsencrypt_${certificate.id}.conf`; | 		const config_file = '/data/nginx/temp/letsencrypt_' + certificate.id + '.conf'; | ||||||
| 		return new Promise((resolve/*, reject*/) => { | 		return new Promise((resolve/*, reject*/) => { | ||||||
| 			internalNginx.deleteFile(config_file); | 			internalNginx.deleteFile(config_file); | ||||||
| 			resolve(); | 			resolve(); | ||||||
| @@ -355,7 +355,7 @@ const internalNginx = { | |||||||
| 	 */ | 	 */ | ||||||
| 	deleteConfig: (host_type, host, delete_err_file) => { | 	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     = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id); | ||||||
| 		const config_file_err = `${config_file}.err`; | 		const config_file_err = config_file + '.err'; | ||||||
|  |  | ||||||
| 		return new Promise((resolve/*, reject*/) => { | 		return new Promise((resolve/*, reject*/) => { | ||||||
| 			internalNginx.deleteFile(config_file); | 			internalNginx.deleteFile(config_file); | ||||||
| @@ -373,7 +373,7 @@ const internalNginx = { | |||||||
| 	 */ | 	 */ | ||||||
| 	renameConfigAsError: (host_type, host) => { | 	renameConfigAsError: (host_type, host) => { | ||||||
| 		const config_file     = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id); | 		const config_file     = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id); | ||||||
| 		const config_file_err = `${config_file}.err`; | 		const config_file_err = config_file + '.err'; | ||||||
|  |  | ||||||
| 		return new Promise((resolve/*, reject*/) => { | 		return new Promise((resolve/*, reject*/) => { | ||||||
| 			fs.unlink(config_file, () => { | 			fs.unlink(config_file, () => { | ||||||
| @@ -392,8 +392,8 @@ const internalNginx = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	bulkGenerateConfigs: (host_type, hosts) => { | 	bulkGenerateConfigs: (host_type, hosts) => { | ||||||
| 		const promises = []; | 		let promises = []; | ||||||
| 		hosts.map((host) => { | 		hosts.map(function (host) { | ||||||
| 			promises.push(internalNginx.generateConfig(host_type, host)); | 			promises.push(internalNginx.generateConfig(host_type, host)); | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| @@ -406,8 +406,8 @@ const internalNginx = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	bulkDeleteConfigs: (host_type, hosts) => { | 	bulkDeleteConfigs: (host_type, hosts) => { | ||||||
| 		const promises = []; | 		let promises = []; | ||||||
| 		hosts.map((host) => { | 		hosts.map(function (host) { | ||||||
| 			promises.push(internalNginx.deleteConfig(host_type, host, true)); | 			promises.push(internalNginx.deleteConfig(host_type, host, true)); | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| @@ -418,12 +418,14 @@ const internalNginx = { | |||||||
| 	 * @param   {string}  config | 	 * @param   {string}  config | ||||||
| 	 * @returns {boolean} | 	 * @returns {boolean} | ||||||
| 	 */ | 	 */ | ||||||
| 	advancedConfigHasDefaultLocation: (cfg) => !!cfg.match(/^(?:.*;)?\s*?location\s*?\/\s*?{/im), | 	advancedConfigHasDefaultLocation: function (cfg) { | ||||||
|  | 		return !!cfg.match(/^(?:.*;)?\s*?location\s*?\/\s*?{/im); | ||||||
|  | 	}, | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| 	 * @returns {boolean} | 	 * @returns {boolean} | ||||||
| 	 */ | 	 */ | ||||||
| 	ipv6Enabled: () => { | 	ipv6Enabled: function () { | ||||||
| 		if (typeof process.env.DISABLE_IPV6 !== 'undefined') { | 		if (typeof process.env.DISABLE_IPV6 !== 'undefined') { | ||||||
| 			const disabled = process.env.DISABLE_IPV6.toLowerCase(); | 			const disabled = process.env.DISABLE_IPV6.toLowerCase(); | ||||||
| 			return !(disabled === 'on' || disabled === 'true' || disabled === '1' || disabled === 'yes'); | 			return !(disabled === 'on' || disabled === 'true' || disabled === '1' || disabled === 'yes'); | ||||||
|   | |||||||
| @@ -6,7 +6,6 @@ const internalHost        = require('./host'); | |||||||
| const internalNginx       = require('./nginx'); | const internalNginx       = require('./nginx'); | ||||||
| const internalAuditLog    = require('./audit-log'); | const internalAuditLog    = require('./audit-log'); | ||||||
| const internalCertificate = require('./certificate'); | const internalCertificate = require('./certificate'); | ||||||
| const {castJsonIfNeed}    = require('../lib/helpers'); |  | ||||||
|  |  | ||||||
| function omissions () { | function omissions () { | ||||||
| 	return ['is_deleted', 'owner.is_deleted']; | 	return ['is_deleted', 'owner.is_deleted']; | ||||||
| @@ -417,16 +416,16 @@ const internalProxyHost = { | |||||||
| 					.where('is_deleted', 0) | 					.where('is_deleted', 0) | ||||||
| 					.groupBy('id') | 					.groupBy('id') | ||||||
| 					.allowGraph('[owner,access_list,certificate]') | 					.allowGraph('[owner,access_list,certificate]') | ||||||
| 					.orderBy(castJsonIfNeed('domain_names'), 'ASC'); | 					.orderBy('domain_names', 'ASC'); | ||||||
|  |  | ||||||
| 				if (access_data.permission_visibility !== 'all') { | 				if (access_data.permission_visibility !== 'all') { | ||||||
| 					query.andWhere('owner_user_id', access.token.getUserId(1)); | 					query.andWhere('owner_user_id', access.token.getUserId(1)); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				// Query is used for searching | 				// Query is used for searching | ||||||
| 				if (typeof search_query === 'string' && search_query.length > 0) { | 				if (typeof search_query === 'string') { | ||||||
| 					query.where(function () { | 					query.where(function () { | ||||||
| 						this.where(castJsonIfNeed('domain_names'), 'like', `%${search_query}%`); | 						this.where('domain_names', 'like', '%' + search_query + '%'); | ||||||
| 					}); | 					}); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,7 +6,6 @@ const internalHost         = require('./host'); | |||||||
| const internalNginx        = require('./nginx'); | const internalNginx        = require('./nginx'); | ||||||
| const internalAuditLog     = require('./audit-log'); | const internalAuditLog     = require('./audit-log'); | ||||||
| const internalCertificate  = require('./certificate'); | const internalCertificate  = require('./certificate'); | ||||||
| const {castJsonIfNeed}     = require('../lib/helpers'); |  | ||||||
|  |  | ||||||
| function omissions () { | function omissions () { | ||||||
| 	return ['is_deleted']; | 	return ['is_deleted']; | ||||||
| @@ -410,16 +409,16 @@ const internalRedirectionHost = { | |||||||
| 					.where('is_deleted', 0) | 					.where('is_deleted', 0) | ||||||
| 					.groupBy('id') | 					.groupBy('id') | ||||||
| 					.allowGraph('[owner,certificate]') | 					.allowGraph('[owner,certificate]') | ||||||
| 					.orderBy(castJsonIfNeed('domain_names'), 'ASC'); | 					.orderBy('domain_names', 'ASC'); | ||||||
|  |  | ||||||
| 				if (access_data.permission_visibility !== 'all') { | 				if (access_data.permission_visibility !== 'all') { | ||||||
| 					query.andWhere('owner_user_id', access.token.getUserId(1)); | 					query.andWhere('owner_user_id', access.token.getUserId(1)); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				// Query is used for searching | 				// Query is used for searching | ||||||
| 				if (typeof search_query === 'string' && search_query.length > 0) { | 				if (typeof search_query === 'string') { | ||||||
| 					query.where(function () { | 					query.where(function () { | ||||||
| 						this.where(castJsonIfNeed('domain_names'), 'like', `%${search_query}%`); | 						this.where('domain_names', 'like', '%' + search_query + '%'); | ||||||
| 					}); | 					}); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,15 +1,12 @@ | |||||||
| const _                   = require('lodash'); | const _                = require('lodash'); | ||||||
| const error               = require('../lib/error'); | const error            = require('../lib/error'); | ||||||
| const utils               = require('../lib/utils'); | const utils            = require('../lib/utils'); | ||||||
| const streamModel         = require('../models/stream'); | const streamModel      = require('../models/stream'); | ||||||
| const internalNginx       = require('./nginx'); | const internalNginx    = require('./nginx'); | ||||||
| const internalAuditLog    = require('./audit-log'); | const internalAuditLog = require('./audit-log'); | ||||||
| const internalCertificate = require('./certificate'); |  | ||||||
| const internalHost        = require('./host'); |  | ||||||
| const {castJsonIfNeed}    = require('../lib/helpers'); |  | ||||||
|  |  | ||||||
| function omissions () { | function omissions () { | ||||||
| 	return ['is_deleted', 'owner.is_deleted', 'certificate.is_deleted']; | 	return ['is_deleted']; | ||||||
| } | } | ||||||
|  |  | ||||||
| const internalStream = { | const internalStream = { | ||||||
| @@ -20,12 +17,6 @@ const internalStream = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	create: (access, data) => { | 	create: (access, data) => { | ||||||
| 		const create_certificate = data.certificate_id === 'new'; |  | ||||||
|  |  | ||||||
| 		if (create_certificate) { |  | ||||||
| 			delete data.certificate_id; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		return access.can('streams:create', data) | 		return access.can('streams:create', data) | ||||||
| 			.then((/*access_data*/) => { | 			.then((/*access_data*/) => { | ||||||
| 				// TODO: At this point the existing ports should have been checked | 				// TODO: At this point the existing ports should have been checked | ||||||
| @@ -35,44 +26,16 @@ const internalStream = { | |||||||
| 					data.meta = {}; | 					data.meta = {}; | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				// streams aren't routed by domain name so don't store domain names in the DB |  | ||||||
| 				let data_no_domains = structuredClone(data); |  | ||||||
| 				delete data_no_domains.domain_names; |  | ||||||
|  |  | ||||||
| 				return streamModel | 				return streamModel | ||||||
| 					.query() | 					.query() | ||||||
| 					.insertAndFetch(data_no_domains) | 					.insertAndFetch(data) | ||||||
| 					.then(utils.omitRow(omissions())); | 					.then(utils.omitRow(omissions())); | ||||||
| 			}) | 			}) | ||||||
| 			.then((row) => { |  | ||||||
| 				if (create_certificate) { |  | ||||||
| 					return internalCertificate.createQuickCertificate(access, data) |  | ||||||
| 						.then((cert) => { |  | ||||||
| 							// update host with cert id |  | ||||||
| 							return internalStream.update(access, { |  | ||||||
| 								id:             row.id, |  | ||||||
| 								certificate_id: cert.id |  | ||||||
| 							}); |  | ||||||
| 						}) |  | ||||||
| 						.then(() => { |  | ||||||
| 							return row; |  | ||||||
| 						}); |  | ||||||
| 				} else { |  | ||||||
| 					return row; |  | ||||||
| 				} |  | ||||||
| 			}) |  | ||||||
| 			.then((row) => { |  | ||||||
| 				// re-fetch with cert |  | ||||||
| 				return internalStream.get(access, { |  | ||||||
| 					id:     row.id, |  | ||||||
| 					expand: ['certificate', 'owner'] |  | ||||||
| 				}); |  | ||||||
| 			}) |  | ||||||
| 			.then((row) => { | 			.then((row) => { | ||||||
| 				// Configure nginx | 				// Configure nginx | ||||||
| 				return internalNginx.configure(streamModel, 'stream', row) | 				return internalNginx.configure(streamModel, 'stream', row) | ||||||
| 					.then(() => { | 					.then(() => { | ||||||
| 						return row; | 						return internalStream.get(access, {id: row.id, expand: ['owner']}); | ||||||
| 					}); | 					}); | ||||||
| 			}) | 			}) | ||||||
| 			.then((row) => { | 			.then((row) => { | ||||||
| @@ -96,12 +59,6 @@ const internalStream = { | |||||||
| 	 * @return {Promise} | 	 * @return {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	update: (access, data) => { | 	update: (access, data) => { | ||||||
| 		const create_certificate = data.certificate_id === 'new'; |  | ||||||
|  |  | ||||||
| 		if (create_certificate) { |  | ||||||
| 			delete data.certificate_id; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		return access.can('streams:update', data.id) | 		return access.can('streams:update', data.id) | ||||||
| 			.then((/*access_data*/) => { | 			.then((/*access_data*/) => { | ||||||
| 				// TODO: at this point the existing streams should have been checked | 				// TODO: at this point the existing streams should have been checked | ||||||
| @@ -113,32 +70,16 @@ const internalStream = { | |||||||
| 					throw new error.InternalValidationError('Stream could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); | 					throw new error.InternalValidationError('Stream could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				if (create_certificate) { |  | ||||||
| 					return internalCertificate.createQuickCertificate(access, { |  | ||||||
| 						domain_names: data.domain_names || row.domain_names, |  | ||||||
| 						meta:         _.assign({}, row.meta, data.meta) |  | ||||||
| 					}) |  | ||||||
| 						.then((cert) => { |  | ||||||
| 							// update host with cert id |  | ||||||
| 							data.certificate_id = cert.id; |  | ||||||
| 						}) |  | ||||||
| 						.then(() => { |  | ||||||
| 							return row; |  | ||||||
| 						}); |  | ||||||
| 				} else { |  | ||||||
| 					return row; |  | ||||||
| 				} |  | ||||||
| 			}) |  | ||||||
| 			.then((row) => { |  | ||||||
| 				// Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here. |  | ||||||
| 				data = _.assign({}, { |  | ||||||
| 					domain_names: row.domain_names |  | ||||||
| 				}, data); |  | ||||||
|  |  | ||||||
| 				return streamModel | 				return streamModel | ||||||
| 					.query() | 					.query() | ||||||
| 					.patchAndFetchById(row.id, data) | 					.patchAndFetchById(row.id, data) | ||||||
| 					.then(utils.omitRow(omissions())) | 					.then(utils.omitRow(omissions())) | ||||||
|  | 					.then((saved_row) => { | ||||||
|  | 						return internalNginx.configure(streamModel, 'stream', saved_row) | ||||||
|  | 							.then(() => { | ||||||
|  | 								return internalStream.get(access, {id: row.id, expand: ['owner']}); | ||||||
|  | 							}); | ||||||
|  | 					}) | ||||||
| 					.then((saved_row) => { | 					.then((saved_row) => { | ||||||
| 						// Add to audit log | 						// Add to audit log | ||||||
| 						return internalAuditLog.add(access, { | 						return internalAuditLog.add(access, { | ||||||
| @@ -151,17 +92,6 @@ const internalStream = { | |||||||
| 								return saved_row; | 								return saved_row; | ||||||
| 							}); | 							}); | ||||||
| 					}); | 					}); | ||||||
| 			}) |  | ||||||
| 			.then(() => { |  | ||||||
| 				return internalStream.get(access, {id: data.id, expand: ['owner', 'certificate']}) |  | ||||||
| 					.then((row) => { |  | ||||||
| 						return internalNginx.configure(streamModel, 'stream', row) |  | ||||||
| 							.then((new_meta) => { |  | ||||||
| 								row.meta = new_meta; |  | ||||||
| 								row      = internalHost.cleanRowCertificateMeta(row); |  | ||||||
| 								return _.omit(row, omissions()); |  | ||||||
| 							}); |  | ||||||
| 					}); |  | ||||||
| 			}); | 			}); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| @@ -184,7 +114,7 @@ const internalStream = { | |||||||
| 					.query() | 					.query() | ||||||
| 					.where('is_deleted', 0) | 					.where('is_deleted', 0) | ||||||
| 					.andWhere('id', data.id) | 					.andWhere('id', data.id) | ||||||
| 					.allowGraph('[owner,certificate]') | 					.allowGraph('[owner]') | ||||||
| 					.first(); | 					.first(); | ||||||
|  |  | ||||||
| 				if (access_data.permission_visibility !== 'all') { | 				if (access_data.permission_visibility !== 'all') { | ||||||
| @@ -201,7 +131,6 @@ const internalStream = { | |||||||
| 				if (!row || !row.id) { | 				if (!row || !row.id) { | ||||||
| 					throw new error.ItemNotFoundError(data.id); | 					throw new error.ItemNotFoundError(data.id); | ||||||
| 				} | 				} | ||||||
| 				row = internalHost.cleanRowCertificateMeta(row); |  | ||||||
| 				// Custom omissions | 				// Custom omissions | ||||||
| 				if (typeof data.omit !== 'undefined' && data.omit !== null) { | 				if (typeof data.omit !== 'undefined' && data.omit !== null) { | ||||||
| 					row = _.omit(row, data.omit); | 					row = _.omit(row, data.omit); | ||||||
| @@ -267,14 +196,14 @@ const internalStream = { | |||||||
| 			.then(() => { | 			.then(() => { | ||||||
| 				return internalStream.get(access, { | 				return internalStream.get(access, { | ||||||
| 					id:     data.id, | 					id:     data.id, | ||||||
| 					expand: ['certificate', 'owner'] | 					expand: ['owner'] | ||||||
| 				}); | 				}); | ||||||
| 			}) | 			}) | ||||||
| 			.then((row) => { | 			.then((row) => { | ||||||
| 				if (!row || !row.id) { | 				if (!row || !row.id) { | ||||||
| 					throw new error.ItemNotFoundError(data.id); | 					throw new error.ItemNotFoundError(data.id); | ||||||
| 				} else if (row.enabled) { | 				} else if (row.enabled) { | ||||||
| 					throw new error.ValidationError('Stream is already enabled'); | 					throw new error.ValidationError('Host is already enabled'); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				row.enabled = 1; | 				row.enabled = 1; | ||||||
| @@ -320,7 +249,7 @@ const internalStream = { | |||||||
| 				if (!row || !row.id) { | 				if (!row || !row.id) { | ||||||
| 					throw new error.ItemNotFoundError(data.id); | 					throw new error.ItemNotFoundError(data.id); | ||||||
| 				} else if (!row.enabled) { | 				} else if (!row.enabled) { | ||||||
| 					throw new error.ValidationError('Stream is already disabled'); | 					throw new error.ValidationError('Host is already disabled'); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				row.enabled = 0; | 				row.enabled = 0; | ||||||
| @@ -364,11 +293,11 @@ const internalStream = { | |||||||
| 	getAll: (access, expand, search_query) => { | 	getAll: (access, expand, search_query) => { | ||||||
| 		return access.can('streams:list') | 		return access.can('streams:list') | ||||||
| 			.then((access_data) => { | 			.then((access_data) => { | ||||||
| 				const query = streamModel | 				let query = streamModel | ||||||
| 					.query() | 					.query() | ||||||
| 					.where('is_deleted', 0) | 					.where('is_deleted', 0) | ||||||
| 					.groupBy('id') | 					.groupBy('id') | ||||||
| 					.allowGraph('[owner,certificate]') | 					.allowGraph('[owner]') | ||||||
| 					.orderBy('incoming_port', 'ASC'); | 					.orderBy('incoming_port', 'ASC'); | ||||||
|  |  | ||||||
| 				if (access_data.permission_visibility !== 'all') { | 				if (access_data.permission_visibility !== 'all') { | ||||||
| @@ -376,9 +305,9 @@ const internalStream = { | |||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				// Query is used for searching | 				// Query is used for searching | ||||||
| 				if (typeof search_query === 'string' && search_query.length > 0) { | 				if (typeof search_query === 'string') { | ||||||
| 					query.where(function () { | 					query.where(function () { | ||||||
| 						this.where(castJsonIfNeed('incoming_port'), 'like', `%${search_query}%`); | 						this.where('incoming_port', 'like', '%' + search_query + '%'); | ||||||
| 					}); | 					}); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| @@ -387,13 +316,6 @@ const internalStream = { | |||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				return query.then(utils.omitRows(omissions())); | 				return query.then(utils.omitRows(omissions())); | ||||||
| 			}) |  | ||||||
| 			.then((rows) => { |  | ||||||
| 				if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) { |  | ||||||
| 					return internalHost.cleanAllRowsCertificateMeta(rows); |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				return rows; |  | ||||||
| 			}); | 			}); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| @@ -405,9 +327,9 @@ const internalStream = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	getCount: (user_id, visibility) => { | 	getCount: (user_id, visibility) => { | ||||||
| 		const query = streamModel | 		let query = streamModel | ||||||
| 			.query() | 			.query() | ||||||
| 			.count('id AS count') | 			.count('id as count') | ||||||
| 			.where('is_deleted', 0); | 			.where('is_deleted', 0); | ||||||
|  |  | ||||||
| 		if (visibility !== 'all') { | 		if (visibility !== 'all') { | ||||||
|   | |||||||
| @@ -5,8 +5,6 @@ const authModel  = require('../models/auth'); | |||||||
| const helpers    = require('../lib/helpers'); | const helpers    = require('../lib/helpers'); | ||||||
| const TokenModel = require('../models/token'); | const TokenModel = require('../models/token'); | ||||||
|  |  | ||||||
| const ERROR_MESSAGE_INVALID_AUTH = 'Invalid email or password'; |  | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| @@ -71,15 +69,15 @@ module.exports = { | |||||||
| 													}; | 													}; | ||||||
| 												}); | 												}); | ||||||
| 										} else { | 										} else { | ||||||
| 											throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH); | 											throw new error.AuthError('Invalid password'); | ||||||
| 										} | 										} | ||||||
| 									}); | 									}); | ||||||
| 							} else { | 							} else { | ||||||
| 								throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH); | 								throw new error.AuthError('No password auth for user'); | ||||||
| 							} | 							} | ||||||
| 						}); | 						}); | ||||||
| 				} else { | 				} else { | ||||||
| 					throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH); | 					throw new error.AuthError('No relevant user found'); | ||||||
| 				} | 				} | ||||||
| 			}); | 			}); | ||||||
| 	}, | 	}, | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ const certbot = { | |||||||
| 	/** | 	/** | ||||||
| 	 * @param {array} pluginKeys | 	 * @param {array} pluginKeys | ||||||
| 	 */ | 	 */ | ||||||
| 	installPlugins: async (pluginKeys) => { | 	installPlugins: async function (pluginKeys) { | ||||||
| 		let hasErrors = false; | 		let hasErrors = false; | ||||||
|  |  | ||||||
| 		return new Promise((resolve, reject) => { | 		return new Promise((resolve, reject) => { | ||||||
| @@ -21,7 +21,7 @@ const certbot = { | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			batchflow(pluginKeys).sequential() | 			batchflow(pluginKeys).sequential() | ||||||
| 				.each((_i, pluginKey, next) => { | 				.each((i, pluginKey, next) => { | ||||||
| 					certbot.installPlugin(pluginKey) | 					certbot.installPlugin(pluginKey) | ||||||
| 						.then(() => { | 						.then(() => { | ||||||
| 							next(); | 							next(); | ||||||
| @@ -51,7 +51,7 @@ const certbot = { | |||||||
| 	 * @param   {string}  pluginKey | 	 * @param   {string}  pluginKey | ||||||
| 	 * @returns {Object} | 	 * @returns {Object} | ||||||
| 	 */ | 	 */ | ||||||
| 	installPlugin: async (pluginKey) => { | 	installPlugin: async function (pluginKey) { | ||||||
| 		if (typeof dnsPlugins[pluginKey] === 'undefined') { | 		if (typeof dnsPlugins[pluginKey] === 'undefined') { | ||||||
| 			// throw Error(`Certbot plugin ${pluginKey} not found`); | 			// throw Error(`Certbot plugin ${pluginKey} not found`); | ||||||
| 			throw new error.ItemNotFoundError(pluginKey); | 			throw new error.ItemNotFoundError(pluginKey); | ||||||
| @@ -63,15 +63,8 @@ const certbot = { | |||||||
| 		plugin.version      = plugin.version.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT); | 		plugin.version      = plugin.version.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT); | ||||||
| 		plugin.dependencies = plugin.dependencies.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT); | 		plugin.dependencies = plugin.dependencies.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT); | ||||||
|  |  | ||||||
| 		// SETUPTOOLS_USE_DISTUTILS is required for certbot plugins to install correctly | 		const cmd = '. /opt/certbot/bin/activate && pip install --no-cache-dir ' + plugin.dependencies + ' ' + plugin.package_name + plugin.version + ' ' + ' && deactivate'; | ||||||
| 		// in new versions of Python | 		return utils.exec(cmd) | ||||||
| 		let env = Object.assign({}, process.env, {SETUPTOOLS_USE_DISTUTILS: 'stdlib'}); |  | ||||||
| 		if (typeof plugin.env === 'object') { |  | ||||||
| 			env = Object.assign(env, plugin.env); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		const cmd = `. /opt/certbot/bin/activate && pip install --no-cache-dir ${plugin.dependencies} ${plugin.package_name}${plugin.version}  && deactivate`; |  | ||||||
| 		return utils.exec(cmd, {env}) |  | ||||||
| 			.then((result) => { | 			.then((result) => { | ||||||
| 				logger.complete(`Installed ${pluginKey}`); | 				logger.complete(`Installed ${pluginKey}`); | ||||||
| 				return result; | 				return result; | ||||||
|   | |||||||
| @@ -2,10 +2,7 @@ const fs      = require('fs'); | |||||||
| const NodeRSA = require('node-rsa'); | const NodeRSA = require('node-rsa'); | ||||||
| const logger  = require('../logger').global; | const logger  = require('../logger').global; | ||||||
|  |  | ||||||
| const keysFile         = '/data/keys.json'; | const keysFile = '/data/keys.json'; | ||||||
| const mysqlEngine      = 'mysql2'; |  | ||||||
| const postgresEngine   = 'pg'; |  | ||||||
| const sqliteClientName = 'sqlite3'; |  | ||||||
|  |  | ||||||
| let instance = null; | let instance = null; | ||||||
|  |  | ||||||
| @@ -17,7 +14,7 @@ const configure = () => { | |||||||
| 		let configData; | 		let configData; | ||||||
| 		try { | 		try { | ||||||
| 			configData = require(filename); | 			configData = require(filename); | ||||||
| 		} catch (_) { | 		} catch (err) { | ||||||
| 			// do nothing | 			// do nothing | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -37,7 +34,7 @@ const configure = () => { | |||||||
| 		logger.info('Using MySQL configuration'); | 		logger.info('Using MySQL configuration'); | ||||||
| 		instance = { | 		instance = { | ||||||
| 			database: { | 			database: { | ||||||
| 				engine:   mysqlEngine, | 				engine:   'mysql2', | ||||||
| 				host:     envMysqlHost, | 				host:     envMysqlHost, | ||||||
| 				port:     process.env.DB_MYSQL_PORT || 3306, | 				port:     process.env.DB_MYSQL_PORT || 3306, | ||||||
| 				user:     envMysqlUser, | 				user:     envMysqlUser, | ||||||
| @@ -49,33 +46,13 @@ const configure = () => { | |||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	const envPostgresHost = process.env.DB_POSTGRES_HOST || null; |  | ||||||
| 	const envPostgresUser = process.env.DB_POSTGRES_USER || null; |  | ||||||
| 	const envPostgresName = process.env.DB_POSTGRES_NAME || null; |  | ||||||
| 	if (envPostgresHost && envPostgresUser && envPostgresName) { |  | ||||||
| 		// we have enough postgres creds to go with postgres |  | ||||||
| 		logger.info('Using Postgres configuration'); |  | ||||||
| 		instance = { |  | ||||||
| 			database: { |  | ||||||
| 				engine:   postgresEngine, |  | ||||||
| 				host:     envPostgresHost, |  | ||||||
| 				port:     process.env.DB_POSTGRES_PORT || 5432, |  | ||||||
| 				user:     envPostgresUser, |  | ||||||
| 				password: process.env.DB_POSTGRES_PASSWORD, |  | ||||||
| 				name:     envPostgresName, |  | ||||||
| 			}, |  | ||||||
| 			keys: getKeys(), |  | ||||||
| 		}; |  | ||||||
| 		return; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	const envSqliteFile = process.env.DB_SQLITE_FILE || '/data/database.sqlite'; | 	const envSqliteFile = process.env.DB_SQLITE_FILE || '/data/database.sqlite'; | ||||||
| 	logger.info(`Using Sqlite: ${envSqliteFile}`); | 	logger.info(`Using Sqlite: ${envSqliteFile}`); | ||||||
| 	instance = { | 	instance = { | ||||||
| 		database: { | 		database: { | ||||||
| 			engine: 'knex-native', | 			engine: 'knex-native', | ||||||
| 			knex:   { | 			knex:   { | ||||||
| 				client:     sqliteClientName, | 				client:     'sqlite3', | ||||||
| 				connection: { | 				connection: { | ||||||
| 					filename: envSqliteFile | 					filename: envSqliteFile | ||||||
| 				}, | 				}, | ||||||
| @@ -166,27 +143,7 @@ module.exports = { | |||||||
| 	 */ | 	 */ | ||||||
| 	isSqlite: function () { | 	isSqlite: function () { | ||||||
| 		instance === null && configure(); | 		instance === null && configure(); | ||||||
| 		return instance.database.knex && instance.database.knex.client === sqliteClientName; | 		return instance.database.knex && instance.database.knex.client === 'sqlite3'; | ||||||
| 	}, |  | ||||||
|  |  | ||||||
| 	/** |  | ||||||
| 	 * Is this a mysql configuration? |  | ||||||
| 	 * |  | ||||||
| 	 * @returns {boolean} |  | ||||||
| 	 */ |  | ||||||
| 	isMysql: function () { |  | ||||||
| 		instance === null && configure(); |  | ||||||
| 		return instance.database.engine === mysqlEngine; |  | ||||||
| 	}, |  | ||||||
| 	 |  | ||||||
| 	/** |  | ||||||
| 		 * Is this a postgres configuration? |  | ||||||
| 		 * |  | ||||||
| 		 * @returns {boolean} |  | ||||||
| 		 */ |  | ||||||
| 	isPostgres: function () { |  | ||||||
| 		instance === null && configure(); |  | ||||||
| 		return instance.database.engine === postgresEngine; |  | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
|   | |||||||
| @@ -1,6 +1,4 @@ | |||||||
| const moment       = require('moment'); | const moment = require('moment'); | ||||||
| const {isPostgres} = require('./config'); |  | ||||||
| const {ref}        = require('objection'); |  | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|  |  | ||||||
| @@ -47,16 +45,6 @@ module.exports = { | |||||||
| 			} | 			} | ||||||
| 		}); | 		}); | ||||||
| 		return obj; | 		return obj; | ||||||
| 	}, |  | ||||||
|  |  | ||||||
| 	/** |  | ||||||
| 	 * Casts a column to json if using postgres |  | ||||||
| 	 * |  | ||||||
| 	 * @param {string} colName |  | ||||||
| 	 * @returns {string|Objection.ReferenceBuilder} |  | ||||||
| 	 */ |  | ||||||
| 	castJsonIfNeed: function (colName) { |  | ||||||
| 		return isPostgres() ? ref(colName).castText() : colName; |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,13 +1,13 @@ | |||||||
| const _          = require('lodash'); | const _          = require('lodash'); | ||||||
| const exec       = require('node:child_process').exec; | const exec       = require('child_process').exec; | ||||||
| const execFile   = require('node:child_process').execFile; | const execFile   = require('child_process').execFile; | ||||||
| const { Liquid } = require('liquidjs'); | const { Liquid } = require('liquidjs'); | ||||||
| const logger     = require('../logger').global; | const logger     = require('../logger').global; | ||||||
| const error      = require('./error'); | const error      = require('./error'); | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|  |  | ||||||
| 	exec: async (cmd, options = {}) => { | 	exec: async function(cmd, options = {}) { | ||||||
| 		logger.debug('CMD:', cmd); | 		logger.debug('CMD:', cmd); | ||||||
|  |  | ||||||
| 		const { stdout, stderr } = await new Promise((resolve, reject) => { | 		const { stdout, stderr } = await new Promise((resolve, reject) => { | ||||||
| @@ -29,19 +29,15 @@ module.exports = { | |||||||
| 	/** | 	/** | ||||||
| 	 * @param   {String} cmd | 	 * @param   {String} cmd | ||||||
| 	 * @param   {Array}  args | 	 * @param   {Array}  args | ||||||
| 	 * @param   {Object|undefined}  options |  | ||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	execFile: (cmd, args, options) => { | 	execFile: function (cmd, args) { | ||||||
| 		logger.debug(`CMD: ${cmd} ${args ? args.join(' ') : ''}`); | 		// logger.debug('CMD: ' + cmd + ' ' + (args ? args.join(' ') : '')); | ||||||
| 		if (typeof options === 'undefined') { |  | ||||||
| 			options = {}; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		return new Promise((resolve, reject) => { | 		return new Promise((resolve, reject) => { | ||||||
| 			execFile(cmd, args, options, (err, stdout, stderr) => { | 			execFile(cmd, args, function (err, stdout, /*stderr*/) { | ||||||
| 				if (err && typeof err === 'object') { | 				if (err && typeof err === 'object') { | ||||||
| 					reject(new error.CommandError(stderr, 1, err)); | 					reject(err); | ||||||
| 				} else { | 				} else { | ||||||
| 					resolve(stdout.trim()); | 					resolve(stdout.trim()); | ||||||
| 				} | 				} | ||||||
| @@ -55,7 +51,7 @@ module.exports = { | |||||||
| 	 * @param   {Array}  omissions | 	 * @param   {Array}  omissions | ||||||
| 	 * @returns {Function} | 	 * @returns {Function} | ||||||
| 	 */ | 	 */ | ||||||
| 	omitRow: (omissions) => { | 	omitRow: function (omissions) { | ||||||
| 		/** | 		/** | ||||||
| 		 * @param   {Object} row | 		 * @param   {Object} row | ||||||
| 		 * @returns {Object} | 		 * @returns {Object} | ||||||
| @@ -71,7 +67,7 @@ module.exports = { | |||||||
| 	 * @param   {Array}  omissions | 	 * @param   {Array}  omissions | ||||||
| 	 * @returns {Function} | 	 * @returns {Function} | ||||||
| 	 */ | 	 */ | ||||||
| 	omitRows: (omissions) => { | 	omitRows: function (omissions) { | ||||||
| 		/** | 		/** | ||||||
| 		 * @param   {Array} rows | 		 * @param   {Array} rows | ||||||
| 		 * @returns {Object} | 		 * @returns {Object} | ||||||
| @@ -87,9 +83,9 @@ module.exports = { | |||||||
| 	/** | 	/** | ||||||
| 	 * @returns {Object} Liquid render engine | 	 * @returns {Object} Liquid render engine | ||||||
| 	 */ | 	 */ | ||||||
| 	getRenderEngine: () => { | 	getRenderEngine: function () { | ||||||
| 		const renderEngine = new Liquid({ | 		const renderEngine = new Liquid({ | ||||||
| 			root: `${__dirname}/../templates/` | 			root: __dirname + '/../templates/' | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| 		/** | 		/** | ||||||
|   | |||||||
| @@ -1,38 +0,0 @@ | |||||||
| const migrate_name = 'stream_ssl'; |  | ||||||
| const logger       = require('../logger').migrate; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Migrate |  | ||||||
|  * |  | ||||||
|  * @see http://knexjs.org/#Schema |  | ||||||
|  * |  | ||||||
|  * @param   {Object} knex |  | ||||||
|  * @returns {Promise} |  | ||||||
|  */ |  | ||||||
| exports.up = function (knex) { |  | ||||||
| 	logger.info('[' + migrate_name + '] Migrating Up...'); |  | ||||||
|  |  | ||||||
| 	return knex.schema.table('stream', (table) => { |  | ||||||
| 		table.integer('certificate_id').notNull().unsigned().defaultTo(0); |  | ||||||
| 	}) |  | ||||||
| 		.then(function () { |  | ||||||
| 			logger.info('[' + migrate_name + '] stream Table altered'); |  | ||||||
| 		}); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Undo Migrate |  | ||||||
|  * |  | ||||||
|  * @param   {Object} knex |  | ||||||
|  * @returns {Promise} |  | ||||||
|  */ |  | ||||||
| exports.down = function (knex) { |  | ||||||
| 	logger.info('[' + migrate_name + '] Migrating Down...'); |  | ||||||
|  |  | ||||||
| 	return knex.schema.table('stream', (table) => { |  | ||||||
| 		table.dropColumn('certificate_id'); |  | ||||||
| 	}) |  | ||||||
| 		.then(function () { |  | ||||||
| 			logger.info('[' + migrate_name + '] stream Table altered'); |  | ||||||
| 		}); |  | ||||||
| }; |  | ||||||
| @@ -4,6 +4,7 @@ | |||||||
| const db      = require('../db'); | const db      = require('../db'); | ||||||
| const helpers = require('../lib/helpers'); | const helpers = require('../lib/helpers'); | ||||||
| const Model   = require('objection').Model; | const Model   = require('objection').Model; | ||||||
|  | const User    = require('./user'); | ||||||
| const now     = require('./now_helper'); | const now     = require('./now_helper'); | ||||||
|  |  | ||||||
| Model.knex(db); | Model.knex(db); | ||||||
| @@ -67,11 +68,6 @@ class Certificate extends Model { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	static get relationMappings () { | 	static get relationMappings () { | ||||||
| 		const ProxyHost       = require('./proxy_host'); |  | ||||||
| 		const DeadHost        = require('./dead_host'); |  | ||||||
| 		const User            = require('./user'); |  | ||||||
| 		const RedirectionHost = require('./redirection_host'); |  | ||||||
|  |  | ||||||
| 		return { | 		return { | ||||||
| 			owner: { | 			owner: { | ||||||
| 				relation:   Model.HasOneRelation, | 				relation:   Model.HasOneRelation, | ||||||
| @@ -83,39 +79,6 @@ class Certificate extends Model { | |||||||
| 				modify: function (qb) { | 				modify: function (qb) { | ||||||
| 					qb.where('user.is_deleted', 0); | 					qb.where('user.is_deleted', 0); | ||||||
| 				} | 				} | ||||||
| 			}, |  | ||||||
| 			proxy_hosts: { |  | ||||||
| 				relation:   Model.HasManyRelation, |  | ||||||
| 				modelClass: ProxyHost, |  | ||||||
| 				join:       { |  | ||||||
| 					from: 'certificate.id', |  | ||||||
| 					to:   'proxy_host.certificate_id' |  | ||||||
| 				}, |  | ||||||
| 				modify: function (qb) { |  | ||||||
| 					qb.where('proxy_host.is_deleted', 0); |  | ||||||
| 				} |  | ||||||
| 			}, |  | ||||||
| 			dead_hosts: { |  | ||||||
| 				relation:   Model.HasManyRelation, |  | ||||||
| 				modelClass: DeadHost, |  | ||||||
| 				join:       { |  | ||||||
| 					from: 'certificate.id', |  | ||||||
| 					to:   'dead_host.certificate_id' |  | ||||||
| 				}, |  | ||||||
| 				modify: function (qb) { |  | ||||||
| 					qb.where('dead_host.is_deleted', 0); |  | ||||||
| 				} |  | ||||||
| 			}, |  | ||||||
| 			redirection_hosts: { |  | ||||||
| 				relation:   Model.HasManyRelation, |  | ||||||
| 				modelClass: RedirectionHost, |  | ||||||
| 				join:       { |  | ||||||
| 					from: 'certificate.id', |  | ||||||
| 					to:   'redirection_host.certificate_id' |  | ||||||
| 				}, |  | ||||||
| 				modify: function (qb) { |  | ||||||
| 					qb.where('redirection_host.is_deleted', 0); |  | ||||||
| 				} |  | ||||||
| 			} | 			} | ||||||
| 		}; | 		}; | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -12,11 +12,7 @@ Model.knex(db); | |||||||
|  |  | ||||||
| const boolFields = [ | const boolFields = [ | ||||||
| 	'is_deleted', | 	'is_deleted', | ||||||
| 	'ssl_forced', |  | ||||||
| 	'http2_support', |  | ||||||
| 	'enabled', | 	'enabled', | ||||||
| 	'hsts_enabled', |  | ||||||
| 	'hsts_subdomains', |  | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| class DeadHost extends Model { | class DeadHost extends Model { | ||||||
|   | |||||||
| @@ -17,9 +17,6 @@ const boolFields = [ | |||||||
| 	'preserve_path', | 	'preserve_path', | ||||||
| 	'ssl_forced', | 	'ssl_forced', | ||||||
| 	'block_exploits', | 	'block_exploits', | ||||||
| 	'hsts_enabled', |  | ||||||
| 	'hsts_subdomains', |  | ||||||
| 	'http2_support', |  | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| class RedirectionHost extends Model { | class RedirectionHost extends Model { | ||||||
|   | |||||||
| @@ -1,15 +1,16 @@ | |||||||
| const Model       = require('objection').Model; | // Objection Docs: | ||||||
| const db          = require('../db'); | // http://vincit.github.io/objection.js/ | ||||||
| const helpers     = require('../lib/helpers'); |  | ||||||
| const User        = require('./user'); | const db      = require('../db'); | ||||||
| const Certificate = require('./certificate'); | const helpers = require('../lib/helpers'); | ||||||
| const now         = require('./now_helper'); | const Model   = require('objection').Model; | ||||||
|  | const User    = require('./user'); | ||||||
|  | const now     = require('./now_helper'); | ||||||
|  |  | ||||||
| Model.knex(db); | Model.knex(db); | ||||||
|  |  | ||||||
| const boolFields = [ | const boolFields = [ | ||||||
| 	'is_deleted', | 	'is_deleted', | ||||||
| 	'enabled', |  | ||||||
| 	'tcp_forwarding', | 	'tcp_forwarding', | ||||||
| 	'udp_forwarding', | 	'udp_forwarding', | ||||||
| ]; | ]; | ||||||
| @@ -63,17 +64,6 @@ class Stream extends Model { | |||||||
| 				modify: function (qb) { | 				modify: function (qb) { | ||||||
| 					qb.where('user.is_deleted', 0); | 					qb.where('user.is_deleted', 0); | ||||||
| 				} | 				} | ||||||
| 			}, |  | ||||||
| 			certificate: { |  | ||||||
| 				relation:   Model.HasOneRelation, |  | ||||||
| 				modelClass: Certificate, |  | ||||||
| 				join:       { |  | ||||||
| 					from: 'stream.certificate_id', |  | ||||||
| 					to:   'certificate.id' |  | ||||||
| 				}, |  | ||||||
| 				modify: function (qb) { |  | ||||||
| 					qb.where('certificate.is_deleted', 0); |  | ||||||
| 				} |  | ||||||
| 			} | 			} | ||||||
| 		}; | 		}; | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -23,7 +23,6 @@ | |||||||
| 		"node-rsa": "^1.0.8", | 		"node-rsa": "^1.0.8", | ||||||
| 		"objection": "3.0.1", | 		"objection": "3.0.1", | ||||||
| 		"path": "^0.12.7", | 		"path": "^0.12.7", | ||||||
| 		"pg": "^8.13.1", |  | ||||||
| 		"signale": "1.4.0", | 		"signale": "1.4.0", | ||||||
| 		"sqlite3": "5.1.6", | 		"sqlite3": "5.1.6", | ||||||
| 		"temp-write": "^4.0.0" | 		"temp-write": "^4.0.0" | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ const apiValidator        = require('../../lib/validator/api'); | |||||||
| const internalCertificate = require('../../internal/certificate'); | const internalCertificate = require('../../internal/certificate'); | ||||||
| const schema              = require('../../schema'); | const schema              = require('../../schema'); | ||||||
|  |  | ||||||
| const router = express.Router({ | let router = express.Router({ | ||||||
| 	caseSensitive: true, | 	caseSensitive: true, | ||||||
| 	strict:        true, | 	strict:        true, | ||||||
| 	mergeParams:   true | 	mergeParams:   true | ||||||
| @@ -231,7 +231,7 @@ router | |||||||
|  */ |  */ | ||||||
| router | router | ||||||
| 	.route('/:certificate_id/download') | 	.route('/:certificate_id/download') | ||||||
| 	.options((_req, res) => { | 	.options((req, res) => { | ||||||
| 		res.sendStatus(204); | 		res.sendStatus(204); | ||||||
| 	}) | 	}) | ||||||
| 	.all(jwtdecode()) | 	.all(jwtdecode()) | ||||||
|   | |||||||
| @@ -181,7 +181,7 @@ router | |||||||
| 				return internalUser.setPassword(res.locals.access, payload); | 				return internalUser.setPassword(res.locals.access, payload); | ||||||
| 			}) | 			}) | ||||||
| 			.then((result) => { | 			.then((result) => { | ||||||
| 				res.status(200) | 				res.status(201) | ||||||
| 					.send(result); | 					.send(result); | ||||||
| 			}) | 			}) | ||||||
| 			.catch(next); | 			.catch(next); | ||||||
| @@ -212,7 +212,7 @@ router | |||||||
| 				return internalUser.setPermissions(res.locals.access, payload); | 				return internalUser.setPermissions(res.locals.access, payload); | ||||||
| 			}) | 			}) | ||||||
| 			.then((result) => { | 			.then((result) => { | ||||||
| 				res.status(200) | 				res.status(201) | ||||||
| 					.send(result); | 					.send(result); | ||||||
| 			}) | 			}) | ||||||
| 			.catch(next); | 			.catch(next); | ||||||
| @@ -238,7 +238,7 @@ router | |||||||
| 	.post((req, res, next) => { | 	.post((req, res, next) => { | ||||||
| 		internalUser.loginAs(res.locals.access, {id: parseInt(req.params.user_id, 10)}) | 		internalUser.loginAs(res.locals.access, {id: parseInt(req.params.user_id, 10)}) | ||||||
| 			.then((result) => { | 			.then((result) => { | ||||||
| 				res.status(200) | 				res.status(201) | ||||||
| 					.send(result); | 					.send(result); | ||||||
| 			}) | 			}) | ||||||
| 			.catch(next); | 			.catch(next); | ||||||
|   | |||||||
| @@ -110,11 +110,6 @@ | |||||||
| 		"caching_enabled": { | 		"caching_enabled": { | ||||||
| 			"description": "Should we cache assets", | 			"description": "Should we cache assets", | ||||||
| 			"type": "boolean" | 			"type": "boolean" | ||||||
| 		}, |  | ||||||
| 		"email": { |  | ||||||
| 			"description": "Email address", |  | ||||||
| 			"type": "string", |  | ||||||
| 			"pattern": "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$" |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -69,7 +69,7 @@ | |||||||
| 					"type": "object" | 					"type": "object" | ||||||
| 				}, | 				}, | ||||||
| 				"letsencrypt_email": { | 				"letsencrypt_email": { | ||||||
| 					"$ref": "../common.json#/properties/email" | 					"type": "string" | ||||||
| 				}, | 				}, | ||||||
| 				"propagation_seconds": { | 				"propagation_seconds": { | ||||||
| 					"type": "integer", | 					"type": "integer", | ||||||
|   | |||||||
| @@ -22,7 +22,8 @@ | |||||||
| 		"enabled", | 		"enabled", | ||||||
| 		"locations", | 		"locations", | ||||||
| 		"hsts_enabled", | 		"hsts_enabled", | ||||||
| 		"hsts_subdomains" | 		"hsts_subdomains", | ||||||
|  | 		"certificate" | ||||||
| 	], | 	], | ||||||
| 	"additionalProperties": false, | 	"additionalProperties": false, | ||||||
| 	"properties": { | 	"properties": { | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| { | { | ||||||
| 	"type": "array", | 	"type": "array", | ||||||
| 	"description": "Streams list", | 	"description": "Proxy Hosts list", | ||||||
| 	"items": { | 	"items": { | ||||||
| 		"$ref": "./stream-object.json" | 		"$ref": "./proxy-host-object.json" | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -53,24 +53,8 @@ | |||||||
| 		"enabled": { | 		"enabled": { | ||||||
| 			"$ref": "../common.json#/properties/enabled" | 			"$ref": "../common.json#/properties/enabled" | ||||||
| 		}, | 		}, | ||||||
| 		"certificate_id": { |  | ||||||
| 			"$ref": "../common.json#/properties/certificate_id" |  | ||||||
| 		}, |  | ||||||
| 		"meta": { | 		"meta": { | ||||||
| 			"type": "object" | 			"type": "object" | ||||||
| 		}, |  | ||||||
| 		"owner": { |  | ||||||
| 			"$ref": "./user-object.json" |  | ||||||
| 		}, |  | ||||||
| 		"certificate": { |  | ||||||
| 			"oneOf": [ |  | ||||||
| 				{ |  | ||||||
| 					"type": "null" |  | ||||||
| 				}, |  | ||||||
| 				{ |  | ||||||
| 					"$ref": "./certificate-object.json" |  | ||||||
| 				} |  | ||||||
| 			] |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,9 +5,10 @@ | |||||||
| 	"additionalProperties": false, | 	"additionalProperties": false, | ||||||
| 	"properties": { | 	"properties": { | ||||||
| 		"expires": { | 		"expires": { | ||||||
| 			"description": "Token Expiry ISO Time String", | 			"description": "Token Expiry Unix Time", | ||||||
| 			"example": "2025-02-04T20:40:46.340Z", | 			"example": 1566540249, | ||||||
| 			"type": "string" | 			"minimum": 1, | ||||||
|  | 			"type": "number" | ||||||
| 		}, | 		}, | ||||||
| 		"token": { | 		"token": { | ||||||
| 			"description": "JWT Token", | 			"description": "JWT Token", | ||||||
|   | |||||||
| @@ -49,7 +49,8 @@ | |||||||
| 										"minLength": 1 | 										"minLength": 1 | ||||||
| 									}, | 									}, | ||||||
| 									"password": { | 									"password": { | ||||||
| 										"type": "string" | 										"type": "string", | ||||||
|  | 										"minLength": 1 | ||||||
| 									} | 									} | ||||||
| 								} | 								} | ||||||
| 							} | 							} | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ | |||||||
| 			"description": "Expansions", | 			"description": "Expansions", | ||||||
| 			"schema": { | 			"schema": { | ||||||
| 				"type": "string", | 				"type": "string", | ||||||
| 				"enum": ["owner", "certificate"] | 				"enum": ["access_list", "owner", "certificate"] | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	], | 	], | ||||||
| @@ -40,8 +40,7 @@ | |||||||
| 										"nginx_online": true, | 										"nginx_online": true, | ||||||
| 										"nginx_err": null | 										"nginx_err": null | ||||||
| 									}, | 									}, | ||||||
| 									"enabled": true, | 									"enabled": true | ||||||
| 									"certificate_id": 0 |  | ||||||
| 								} | 								} | ||||||
| 							] | 							] | ||||||
| 						} | 						} | ||||||
|   | |||||||
| @@ -32,9 +32,6 @@ | |||||||
| 						"udp_forwarding": { | 						"udp_forwarding": { | ||||||
| 							"$ref": "../../../components/stream-object.json#/properties/udp_forwarding" | 							"$ref": "../../../components/stream-object.json#/properties/udp_forwarding" | ||||||
| 						}, | 						}, | ||||||
| 						"certificate_id": { |  | ||||||
| 							"$ref": "../../../components/stream-object.json#/properties/certificate_id" |  | ||||||
| 						}, |  | ||||||
| 						"meta": { | 						"meta": { | ||||||
| 							"$ref": "../../../components/stream-object.json#/properties/meta" | 							"$ref": "../../../components/stream-object.json#/properties/meta" | ||||||
| 						} | 						} | ||||||
| @@ -76,8 +73,7 @@ | |||||||
| 									"nickname": "Admin", | 									"nickname": "Admin", | ||||||
| 									"avatar": "", | 									"avatar": "", | ||||||
| 									"roles": ["admin"] | 									"roles": ["admin"] | ||||||
| 								}, | 								} | ||||||
| 								"certificate_id": 0 |  | ||||||
| 							} | 							} | ||||||
| 						} | 						} | ||||||
| 					}, | 					}, | ||||||
|   | |||||||
| @@ -40,8 +40,7 @@ | |||||||
| 									"nginx_online": true, | 									"nginx_online": true, | ||||||
| 									"nginx_err": null | 									"nginx_err": null | ||||||
| 								}, | 								}, | ||||||
| 								"enabled": true, | 								"enabled": true | ||||||
| 								"certificate_id": 0 |  | ||||||
| 							} | 							} | ||||||
| 						} | 						} | ||||||
| 					}, | 					}, | ||||||
|   | |||||||
| @@ -29,26 +29,56 @@ | |||||||
| 					"additionalProperties": false, | 					"additionalProperties": false, | ||||||
| 					"minProperties": 1, | 					"minProperties": 1, | ||||||
| 					"properties": { | 					"properties": { | ||||||
| 						"incoming_port": { | 						"domain_names": { | ||||||
| 							"$ref": "../../../../components/stream-object.json#/properties/incoming_port" | 							"$ref": "../../../../components/proxy-host-object.json#/properties/domain_names" | ||||||
| 						}, | 						}, | ||||||
| 						"forwarding_host": { | 						"forward_scheme": { | ||||||
| 							"$ref": "../../../../components/stream-object.json#/properties/forwarding_host" | 							"$ref": "../../../../components/proxy-host-object.json#/properties/forward_scheme" | ||||||
| 						}, | 						}, | ||||||
| 						"forwarding_port": { | 						"forward_host": { | ||||||
| 							"$ref": "../../../../components/stream-object.json#/properties/forwarding_port" | 							"$ref": "../../../../components/proxy-host-object.json#/properties/forward_host" | ||||||
| 						}, | 						}, | ||||||
| 						"tcp_forwarding": { | 						"forward_port": { | ||||||
| 							"$ref": "../../../../components/stream-object.json#/properties/tcp_forwarding" | 							"$ref": "../../../../components/proxy-host-object.json#/properties/forward_port" | ||||||
| 						}, |  | ||||||
| 						"udp_forwarding": { |  | ||||||
| 							"$ref": "../../../../components/stream-object.json#/properties/udp_forwarding" |  | ||||||
| 						}, | 						}, | ||||||
| 						"certificate_id": { | 						"certificate_id": { | ||||||
| 							"$ref": "../../../../components/stream-object.json#/properties/certificate_id" | 							"$ref": "../../../../components/proxy-host-object.json#/properties/certificate_id" | ||||||
|  | 						}, | ||||||
|  | 						"ssl_forced": { | ||||||
|  | 							"$ref": "../../../../components/proxy-host-object.json#/properties/ssl_forced" | ||||||
|  | 						}, | ||||||
|  | 						"hsts_enabled": { | ||||||
|  | 							"$ref": "../../../../components/proxy-host-object.json#/properties/hsts_enabled" | ||||||
|  | 						}, | ||||||
|  | 						"hsts_subdomains": { | ||||||
|  | 							"$ref": "../../../../components/proxy-host-object.json#/properties/hsts_subdomains" | ||||||
|  | 						}, | ||||||
|  | 						"http2_support": { | ||||||
|  | 							"$ref": "../../../../components/proxy-host-object.json#/properties/http2_support" | ||||||
|  | 						}, | ||||||
|  | 						"block_exploits": { | ||||||
|  | 							"$ref": "../../../../components/proxy-host-object.json#/properties/block_exploits" | ||||||
|  | 						}, | ||||||
|  | 						"caching_enabled": { | ||||||
|  | 							"$ref": "../../../../components/proxy-host-object.json#/properties/caching_enabled" | ||||||
|  | 						}, | ||||||
|  | 						"allow_websocket_upgrade": { | ||||||
|  | 							"$ref": "../../../../components/proxy-host-object.json#/properties/allow_websocket_upgrade" | ||||||
|  | 						}, | ||||||
|  | 						"access_list_id": { | ||||||
|  | 							"$ref": "../../../../components/proxy-host-object.json#/properties/access_list_id" | ||||||
|  | 						}, | ||||||
|  | 						"advanced_config": { | ||||||
|  | 							"$ref": "../../../../components/proxy-host-object.json#/properties/advanced_config" | ||||||
|  | 						}, | ||||||
|  | 						"enabled": { | ||||||
|  | 							"$ref": "../../../../components/proxy-host-object.json#/properties/enabled" | ||||||
| 						}, | 						}, | ||||||
| 						"meta": { | 						"meta": { | ||||||
| 							"$ref": "../../../../components/stream-object.json#/properties/meta" | 							"$ref": "../../../../components/proxy-host-object.json#/properties/meta" | ||||||
|  | 						}, | ||||||
|  | 						"locations": { | ||||||
|  | 							"$ref": "../../../../components/proxy-host-object.json#/properties/locations" | ||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| @@ -64,32 +94,42 @@ | |||||||
| 						"default": { | 						"default": { | ||||||
| 							"value": { | 							"value": { | ||||||
| 								"id": 1, | 								"id": 1, | ||||||
| 								"created_on": "2024-10-09T02:33:45.000Z", | 								"created_on": "2024-10-08T23:23:03.000Z", | ||||||
| 								"modified_on": "2024-10-09T02:33:45.000Z", | 								"modified_on": "2024-10-08T23:26:37.000Z", | ||||||
| 								"owner_user_id": 1, | 								"owner_user_id": 1, | ||||||
| 								"incoming_port": 9090, | 								"domain_names": ["test.example.com"], | ||||||
| 								"forwarding_host": "router.internal", | 								"forward_host": "192.168.0.10", | ||||||
| 								"forwarding_port": 80, | 								"forward_port": 8989, | ||||||
| 								"tcp_forwarding": true, | 								"access_list_id": 0, | ||||||
| 								"udp_forwarding": false, | 								"certificate_id": 0, | ||||||
|  | 								"ssl_forced": false, | ||||||
|  | 								"caching_enabled": false, | ||||||
|  | 								"block_exploits": false, | ||||||
|  | 								"advanced_config": "", | ||||||
| 								"meta": { | 								"meta": { | ||||||
| 									"nginx_online": true, | 									"nginx_online": true, | ||||||
| 									"nginx_err": null | 									"nginx_err": null | ||||||
| 								}, | 								}, | ||||||
|  | 								"allow_websocket_upgrade": false, | ||||||
|  | 								"http2_support": false, | ||||||
|  | 								"forward_scheme": "http", | ||||||
| 								"enabled": true, | 								"enabled": true, | ||||||
|  | 								"hsts_enabled": false, | ||||||
|  | 								"hsts_subdomains": false, | ||||||
| 								"owner": { | 								"owner": { | ||||||
| 									"id": 1, | 									"id": 1, | ||||||
| 									"created_on": "2024-10-09T02:33:16.000Z", | 									"created_on": "2024-10-07T22:43:55.000Z", | ||||||
| 									"modified_on": "2024-10-09T02:33:16.000Z", | 									"modified_on": "2024-10-08T12:52:54.000Z", | ||||||
| 									"is_deleted": false, | 									"is_deleted": false, | ||||||
| 									"is_disabled": false, | 									"is_disabled": false, | ||||||
| 									"email": "admin@example.com", | 									"email": "admin@example.com", | ||||||
| 									"name": "Administrator", | 									"name": "Administrator", | ||||||
| 									"nickname": "Admin", | 									"nickname": "some guy", | ||||||
| 									"avatar": "", | 									"avatar": "//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?default=mm", | ||||||
| 									"roles": ["admin"] | 									"roles": ["admin"] | ||||||
| 								}, | 								}, | ||||||
| 								"certificate_id": 0 | 								"certificate": null, | ||||||
|  | 								"access_list": null | ||||||
| 							} | 							} | ||||||
| 						} | 						} | ||||||
| 					}, | 					}, | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ | |||||||
| 					"examples": { | 					"examples": { | ||||||
| 						"default": { | 						"default": { | ||||||
| 							"value": { | 							"value": { | ||||||
| 								"expires": "2025-02-04T20:40:46.340Z", | 								"expires": 1566540510, | ||||||
| 								"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4" | 								"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4" | ||||||
| 							} | 							} | ||||||
| 						} | 						} | ||||||
|   | |||||||
| @@ -38,7 +38,7 @@ | |||||||
| 						"default": { | 						"default": { | ||||||
| 							"value": { | 							"value": { | ||||||
| 								"result": { | 								"result": { | ||||||
| 									"expires": "2025-02-04T20:40:46.340Z", | 									"expires": 1566540510, | ||||||
| 									"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4" | 									"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4" | ||||||
| 								} | 								} | ||||||
| 							} | 							} | ||||||
|   | |||||||
| @@ -9,15 +9,6 @@ | |||||||
| 			"url": "http://127.0.0.1:81/api" | 			"url": "http://127.0.0.1:81/api" | ||||||
| 		} | 		} | ||||||
| 	], | 	], | ||||||
| 	"components": { |  | ||||||
| 		"securitySchemes": { |  | ||||||
| 			"bearerAuth": { |  | ||||||
| 				"type": "http", |  | ||||||
| 				"scheme": "bearer", |  | ||||||
| 				"bearerFormat": "JWT" |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
| 	"paths": { | 	"paths": { | ||||||
| 		"/": { | 		"/": { | ||||||
| 			"get": { | 			"get": { | ||||||
|   | |||||||
| @@ -15,18 +15,18 @@ const certbot             = require('./lib/certbot'); | |||||||
| const setupDefaultUser = () => { | const setupDefaultUser = () => { | ||||||
| 	return userModel | 	return userModel | ||||||
| 		.query() | 		.query() | ||||||
| 		.select('id', ) | 		.select(userModel.raw('COUNT(`id`) as `count`')) | ||||||
| 		.where('is_deleted', 0) | 		.where('is_deleted', 0) | ||||||
| 		.first() | 		.first() | ||||||
| 		.then((row) => { | 		.then((row) => { | ||||||
| 			if (!row || !row.id) { | 			if (!row.count) { | ||||||
| 				// Create a new user and set password | 				// Create a new user and set password | ||||||
| 				const email    = (process.env.INITIAL_ADMIN_EMAIL || 'admin@example.com').toLowerCase(); | 				let email    = process.env.INITIAL_ADMIN_EMAIL || 'admin@example.com'; | ||||||
| 				const password = process.env.INITIAL_ADMIN_PASSWORD || 'changeme'; | 				let password = process.env.INITIAL_ADMIN_PASSWORD || 'changeme'; | ||||||
| 				 | 				 | ||||||
| 				logger.info(`Creating a new user: ${email} with password: ${password}`); | 				logger.info('Creating a new user: ' + email + ' with password: ' + password); | ||||||
|  |  | ||||||
| 				const data = { | 				let data = { | ||||||
| 					is_deleted: 0, | 					is_deleted: 0, | ||||||
| 					email:      email, | 					email:      email, | ||||||
| 					name:       'Administrator', | 					name:       'Administrator', | ||||||
| @@ -77,11 +77,11 @@ const setupDefaultUser = () => { | |||||||
| const setupDefaultSettings = () => { | const setupDefaultSettings = () => { | ||||||
| 	return settingModel | 	return settingModel | ||||||
| 		.query() | 		.query() | ||||||
| 		.select('id') | 		.select(settingModel.raw('COUNT(`id`) as `count`')) | ||||||
| 		.where({id: 'default-site'}) | 		.where({id: 'default-site'}) | ||||||
| 		.first() | 		.first() | ||||||
| 		.then((row) => { | 		.then((row) => { | ||||||
| 			if (!row || !row.id) { | 			if (!row.count) { | ||||||
| 				settingModel | 				settingModel | ||||||
| 					.query() | 					.query() | ||||||
| 					.insert({ | 					.insert({ | ||||||
| @@ -113,20 +113,20 @@ const setupCertbotPlugins = () => { | |||||||
| 		.andWhere('provider', 'letsencrypt') | 		.andWhere('provider', 'letsencrypt') | ||||||
| 		.then((certificates) => { | 		.then((certificates) => { | ||||||
| 			if (certificates && certificates.length) { | 			if (certificates && certificates.length) { | ||||||
| 				const plugins  = []; | 				let plugins  = []; | ||||||
| 				const promises = []; | 				let promises = []; | ||||||
|  |  | ||||||
| 				certificates.map((certificate) => { | 				certificates.map(function (certificate) { | ||||||
| 					if (certificate.meta && certificate.meta.dns_challenge === true) { | 					if (certificate.meta && certificate.meta.dns_challenge === true) { | ||||||
| 						if (plugins.indexOf(certificate.meta.dns_provider) === -1) { | 						if (plugins.indexOf(certificate.meta.dns_provider) === -1) { | ||||||
| 							plugins.push(certificate.meta.dns_provider); | 							plugins.push(certificate.meta.dns_provider); | ||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						// Make sure credentials file exists | 						// Make sure credentials file exists | ||||||
| 						const credentials_loc = `/etc/letsencrypt/credentials/credentials-${certificate.id}`; | 						const credentials_loc = '/etc/letsencrypt/credentials/credentials-' + certificate.id; | ||||||
| 						// Escape single quotes and backslashes | 						// Escape single quotes and backslashes | ||||||
| 						const escapedCredentials = certificate.meta.dns_provider_credentials.replaceAll('\'', '\\\'').replaceAll('\\', '\\\\'); | 						const escapedCredentials = certificate.meta.dns_provider_credentials.replaceAll('\'', '\\\'').replaceAll('\\', '\\\\'); | ||||||
| 						const credentials_cmd    = `[ -f '${credentials_loc}' ] || { mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo '${escapedCredentials}' > '${credentials_loc}' && chmod 600 '${credentials_loc}'; }`; | 						const credentials_cmd    = '[ -f \'' + credentials_loc + '\' ] || { mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo \'' + escapedCredentials + '\' > \'' + credentials_loc + '\' && chmod 600 \'' + credentials_loc + '\'; }'; | ||||||
| 						promises.push(utils.exec(credentials_cmd)); | 						promises.push(utils.exec(credentials_cmd)); | ||||||
| 					} | 					} | ||||||
| 				}); | 				}); | ||||||
| @@ -136,7 +136,7 @@ const setupCertbotPlugins = () => { | |||||||
| 						if (promises.length) { | 						if (promises.length) { | ||||||
| 							return Promise.all(promises) | 							return Promise.all(promises) | ||||||
| 								.then(() => { | 								.then(() => { | ||||||
| 									logger.info(`Added Certbot plugins ${plugins.join(', ')}`); | 									logger.info('Added Certbot plugins ' + plugins.join(', ')); | ||||||
| 								}); | 								}); | ||||||
| 						} | 						} | ||||||
| 					}); | 					}); | ||||||
| @@ -165,7 +165,9 @@ const setupLogrotation = () => { | |||||||
| 	return runLogrotate(); | 	return runLogrotate(); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| module.exports = () => setupDefaultUser() | module.exports = function () { | ||||||
| 	.then(setupDefaultSettings) | 	return setupDefaultUser() | ||||||
| 	.then(setupCertbotPlugins) | 		.then(setupDefaultSettings) | ||||||
| 	.then(setupLogrotation); | 		.then(setupCertbotPlugins) | ||||||
|  | 		.then(setupLogrotation); | ||||||
|  | }; | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ | |||||||
|     auth_basic            "Authorization required"; |     auth_basic            "Authorization required"; | ||||||
|     auth_basic_user_file  /data/access/{{ access_list_id }}; |     auth_basic_user_file  /data/access/{{ access_list_id }}; | ||||||
|  |  | ||||||
|     {% if access_list.pass_auth == 0 or access_list.pass_auth == true %} |     {% if access_list.pass_auth == 0 %} | ||||||
|     proxy_set_header Authorization ""; |     proxy_set_header Authorization ""; | ||||||
|     {% endif %} |     {% endif %} | ||||||
|  |  | ||||||
| @@ -17,7 +17,7 @@ | |||||||
|     deny all; |     deny all; | ||||||
|  |  | ||||||
|     # Access checks must... |     # Access checks must... | ||||||
|     {% if access_list.satisfy_any == 1 or access_list.satisfy_any == true %} |     {% if access_list.satisfy_any == 1 %} | ||||||
|     satisfy any; |     satisfy any; | ||||||
|     {% else %} |     {% else %} | ||||||
|     satisfy all; |     satisfy all; | ||||||
|   | |||||||
| @@ -2,7 +2,6 @@ | |||||||
| {% if certificate.provider == "letsencrypt" %} | {% if certificate.provider == "letsencrypt" %} | ||||||
|   # Let's Encrypt SSL |   # Let's Encrypt SSL | ||||||
|   include conf.d/include/letsencrypt-acme-challenge.conf; |   include conf.d/include/letsencrypt-acme-challenge.conf; | ||||||
|   include conf.d/include/ssl-cache.conf; |  | ||||||
|   include conf.d/include/ssl-ciphers.conf; |   include conf.d/include/ssl-ciphers.conf; | ||||||
|   ssl_certificate /etc/letsencrypt/live/npm-{{ certificate_id }}/fullchain.pem; |   ssl_certificate /etc/letsencrypt/live/npm-{{ certificate_id }}/fullchain.pem; | ||||||
|   ssl_certificate_key /etc/letsencrypt/live/npm-{{ certificate_id }}/privkey.pem; |   ssl_certificate_key /etc/letsencrypt/live/npm-{{ certificate_id }}/privkey.pem; | ||||||
|   | |||||||
| @@ -1,13 +0,0 @@ | |||||||
| {% if certificate and certificate_id > 0 %} |  | ||||||
| {% if certificate.provider == "letsencrypt" %} |  | ||||||
|   # Let's Encrypt SSL |  | ||||||
|   include conf.d/include/ssl-cache-stream.conf; |  | ||||||
|   include conf.d/include/ssl-ciphers.conf; |  | ||||||
|   ssl_certificate /etc/letsencrypt/live/npm-{{ certificate_id }}/fullchain.pem; |  | ||||||
|   ssl_certificate_key /etc/letsencrypt/live/npm-{{ certificate_id }}/privkey.pem; |  | ||||||
| {%- else %} |  | ||||||
|   # Custom SSL |  | ||||||
|   ssl_certificate /data/custom_ssl/npm-{{ certificate_id }}/fullchain.pem; |  | ||||||
|   ssl_certificate_key /data/custom_ssl/npm-{{ certificate_id }}/privkey.pem; |  | ||||||
| {%- endif -%} |  | ||||||
| {%- endif -%} |  | ||||||
| @@ -5,16 +5,11 @@ | |||||||
|   #listen [::]:80; |   #listen [::]:80; | ||||||
| {% endif %} | {% endif %} | ||||||
| {% if certificate -%} | {% if certificate -%} | ||||||
|   listen 443 ssl; |   listen 443 ssl{% if http2_support == 1 or http2_support == true %} http2{% endif %}; | ||||||
| {% if ipv6 -%} | {% if ipv6 -%} | ||||||
|   listen [::]:443 ssl; |   listen [::]:443 ssl{% if http2_support == 1 or http2_support == true %} http2{% endif %}; | ||||||
| {% else -%} | {% else -%} | ||||||
|   #listen [::]:443; |   #listen [::]:443; | ||||||
| {% endif %} | {% endif %} | ||||||
| {% endif %} | {% endif %} | ||||||
|   server_name {{ domain_names | join: " " }}; |   server_name {{ domain_names | join: " " }}; | ||||||
| {% if http2_support == 1 or http2_support == true %} |  | ||||||
|   http2 on; |  | ||||||
| {% else -%} |  | ||||||
|   http2 off; |  | ||||||
| {% endif %} |  | ||||||
| @@ -7,7 +7,11 @@ | |||||||
|     proxy_set_header X-Forwarded-For    $remote_addr; |     proxy_set_header X-Forwarded-For    $remote_addr; | ||||||
|     proxy_set_header X-Real-IP		$remote_addr; |     proxy_set_header X-Real-IP		$remote_addr; | ||||||
|  |  | ||||||
|     proxy_pass       {{ forward_scheme }}://{{ forward_host }}:{{ forward_port }}{{ forward_path }}; |     set $proxy_forward_scheme {{ forward_scheme }}; | ||||||
|  |     set $proxy_server         "{{ forward_host }}"; | ||||||
|  |     set $proxy_port           {{ forward_port }}; | ||||||
|  |  | ||||||
|  |     proxy_pass       $proxy_forward_scheme://$proxy_server:$proxy_port{{ forward_path }}; | ||||||
|  |  | ||||||
|     {% include "_access.conf" %} |     {% include "_access.conf" %} | ||||||
|     {% include "_assets.conf" %} |     {% include "_assets.conf" %} | ||||||
|   | |||||||
| @@ -22,7 +22,5 @@ server { | |||||||
|   } |   } | ||||||
| {% endif %} | {% endif %} | ||||||
|  |  | ||||||
|   # Custom |  | ||||||
|   include /data/nginx/custom/server_dead[.]conf; |  | ||||||
| } | } | ||||||
| {% endif %} | {% endif %} | ||||||
|   | |||||||
| @@ -5,10 +5,12 @@ | |||||||
| {% if enabled %} | {% if enabled %} | ||||||
| {% if tcp_forwarding == 1 or tcp_forwarding == true -%} | {% if tcp_forwarding == 1 or tcp_forwarding == true -%} | ||||||
| server { | server { | ||||||
|   listen {{ incoming_port }} {%- if certificate %} ssl {%- endif %}; |   listen {{ incoming_port }}; | ||||||
|   {% unless ipv6 -%} # {%- endunless -%} listen [::]:{{ incoming_port }} {%- if certificate %} ssl {%- endif %}; | {% if ipv6 -%} | ||||||
|  |   listen [::]:{{ incoming_port }}; | ||||||
|   {%- include "_certificates_stream.conf" %} | {% else -%} | ||||||
|  |   #listen [::]:{{ incoming_port }}; | ||||||
|  | {% endif %} | ||||||
|  |  | ||||||
|   proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; |   proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; | ||||||
|  |  | ||||||
| @@ -17,12 +19,14 @@ server { | |||||||
|   include /data/nginx/custom/server_stream_tcp[.]conf; |   include /data/nginx/custom/server_stream_tcp[.]conf; | ||||||
| } | } | ||||||
| {% endif %} | {% endif %} | ||||||
|  | {% if udp_forwarding == 1 or udp_forwarding == true %} | ||||||
| {% if udp_forwarding == 1 or udp_forwarding == true -%} |  | ||||||
| server { | server { | ||||||
|   listen {{ incoming_port }} udp; |   listen {{ incoming_port }} udp; | ||||||
|   {% unless ipv6 -%} # {%- endunless -%} listen [::]:{{ incoming_port }} udp; | {% if ipv6 -%} | ||||||
|  |   listen [::]:{{ incoming_port }} udp; | ||||||
|  | {% else -%} | ||||||
|  |   #listen [::]:{{ incoming_port }} udp; | ||||||
|  | {% endif %} | ||||||
|   proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; |   proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; | ||||||
|  |  | ||||||
|   # Custom |   # Custom | ||||||
|   | |||||||
| @@ -492,9 +492,9 @@ boxen@^4.2.0: | |||||||
|     widest-line "^3.1.0" |     widest-line "^3.1.0" | ||||||
|  |  | ||||||
| brace-expansion@^1.1.7: | brace-expansion@^1.1.7: | ||||||
|   version "1.1.12" |   version "1.1.11" | ||||||
|   resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" |   resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" | ||||||
|   integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== |   integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== | ||||||
|   dependencies: |   dependencies: | ||||||
|     balanced-match "^1.0.0" |     balanced-match "^1.0.0" | ||||||
|     concat-map "0.0.1" |     concat-map "0.0.1" | ||||||
| @@ -830,9 +830,9 @@ crc32-stream@^4.0.2: | |||||||
|     readable-stream "^3.4.0" |     readable-stream "^3.4.0" | ||||||
|  |  | ||||||
| cross-spawn@^7.0.2: | cross-spawn@^7.0.2: | ||||||
|   version "7.0.6" |   version "7.0.3" | ||||||
|   resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" |   resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" | ||||||
|   integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== |   integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== | ||||||
|   dependencies: |   dependencies: | ||||||
|     path-key "^3.1.0" |     path-key "^3.1.0" | ||||||
|     shebang-command "^2.0.0" |     shebang-command "^2.0.0" | ||||||
| @@ -2735,67 +2735,11 @@ path@^0.12.7: | |||||||
|     process "^0.11.1" |     process "^0.11.1" | ||||||
|     util "^0.10.3" |     util "^0.10.3" | ||||||
|  |  | ||||||
| pg-cloudflare@^1.1.1: |  | ||||||
|   version "1.1.1" |  | ||||||
|   resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98" |  | ||||||
|   integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q== |  | ||||||
|  |  | ||||||
| pg-connection-string@2.5.0: | pg-connection-string@2.5.0: | ||||||
|   version "2.5.0" |   version "2.5.0" | ||||||
|   resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34" |   resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34" | ||||||
|   integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ== |   integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ== | ||||||
|  |  | ||||||
| pg-connection-string@^2.7.0: |  | ||||||
|   version "2.7.0" |  | ||||||
|   resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.7.0.tgz#f1d3489e427c62ece022dba98d5262efcb168b37" |  | ||||||
|   integrity sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA== |  | ||||||
|  |  | ||||||
| pg-int8@1.0.1: |  | ||||||
|   version "1.0.1" |  | ||||||
|   resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" |  | ||||||
|   integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== |  | ||||||
|  |  | ||||||
| pg-pool@^3.7.0: |  | ||||||
|   version "3.7.0" |  | ||||||
|   resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.7.0.tgz#d4d3c7ad640f8c6a2245adc369bafde4ebb8cbec" |  | ||||||
|   integrity sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g== |  | ||||||
|  |  | ||||||
| pg-protocol@^1.7.0: |  | ||||||
|   version "1.7.0" |  | ||||||
|   resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.7.0.tgz#ec037c87c20515372692edac8b63cf4405448a93" |  | ||||||
|   integrity sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ== |  | ||||||
|  |  | ||||||
| pg-types@^2.1.0: |  | ||||||
|   version "2.2.0" |  | ||||||
|   resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" |  | ||||||
|   integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== |  | ||||||
|   dependencies: |  | ||||||
|     pg-int8 "1.0.1" |  | ||||||
|     postgres-array "~2.0.0" |  | ||||||
|     postgres-bytea "~1.0.0" |  | ||||||
|     postgres-date "~1.0.4" |  | ||||||
|     postgres-interval "^1.1.0" |  | ||||||
|  |  | ||||||
| pg@^8.13.1: |  | ||||||
|   version "8.13.1" |  | ||||||
|   resolved "https://registry.yarnpkg.com/pg/-/pg-8.13.1.tgz#6498d8b0a87ff76c2df7a32160309d3168c0c080" |  | ||||||
|   integrity sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ== |  | ||||||
|   dependencies: |  | ||||||
|     pg-connection-string "^2.7.0" |  | ||||||
|     pg-pool "^3.7.0" |  | ||||||
|     pg-protocol "^1.7.0" |  | ||||||
|     pg-types "^2.1.0" |  | ||||||
|     pgpass "1.x" |  | ||||||
|   optionalDependencies: |  | ||||||
|     pg-cloudflare "^1.1.1" |  | ||||||
|  |  | ||||||
| pgpass@1.x: |  | ||||||
|   version "1.0.5" |  | ||||||
|   resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" |  | ||||||
|   integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== |  | ||||||
|   dependencies: |  | ||||||
|     split2 "^4.1.0" |  | ||||||
|  |  | ||||||
| picomatch@^2.0.4, picomatch@^2.2.1: | picomatch@^2.0.4, picomatch@^2.2.1: | ||||||
|   version "2.2.2" |   version "2.2.2" | ||||||
|   resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" |   resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" | ||||||
| @@ -2814,28 +2758,6 @@ pkg-conf@^2.1.0: | |||||||
|     find-up "^2.0.0" |     find-up "^2.0.0" | ||||||
|     load-json-file "^4.0.0" |     load-json-file "^4.0.0" | ||||||
|  |  | ||||||
| postgres-array@~2.0.0: |  | ||||||
|   version "2.0.0" |  | ||||||
|   resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" |  | ||||||
|   integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== |  | ||||||
|  |  | ||||||
| postgres-bytea@~1.0.0: |  | ||||||
|   version "1.0.0" |  | ||||||
|   resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" |  | ||||||
|   integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== |  | ||||||
|  |  | ||||||
| postgres-date@~1.0.4: |  | ||||||
|   version "1.0.7" |  | ||||||
|   resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" |  | ||||||
|   integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== |  | ||||||
|  |  | ||||||
| postgres-interval@^1.1.0: |  | ||||||
|   version "1.2.0" |  | ||||||
|   resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" |  | ||||||
|   integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== |  | ||||||
|   dependencies: |  | ||||||
|     xtend "^4.0.0" |  | ||||||
|  |  | ||||||
| prelude-ls@^1.2.1: | prelude-ls@^1.2.1: | ||||||
|   version "1.2.1" |   version "1.2.1" | ||||||
|   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" |   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" | ||||||
| @@ -3272,11 +3194,6 @@ socks@^2.6.2: | |||||||
|     ip "^2.0.0" |     ip "^2.0.0" | ||||||
|     smart-buffer "^4.2.0" |     smart-buffer "^4.2.0" | ||||||
|  |  | ||||||
| split2@^4.1.0: |  | ||||||
|   version "4.2.0" |  | ||||||
|   resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" |  | ||||||
|   integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== |  | ||||||
|  |  | ||||||
| sprintf-js@~1.0.2: | sprintf-js@~1.0.2: | ||||||
|   version "1.0.3" |   version "1.0.3" | ||||||
|   resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" |   resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" | ||||||
| @@ -3748,11 +3665,6 @@ xdg-basedir@^4.0.0: | |||||||
|   resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" |   resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" | ||||||
|   integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== |   integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== | ||||||
|  |  | ||||||
| xtend@^4.0.0: |  | ||||||
|   version "4.0.2" |  | ||||||
|   resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" |  | ||||||
|   integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== |  | ||||||
|  |  | ||||||
| y18n@^4.0.0: | y18n@^4.0.0: | ||||||
|   version "4.0.1" |   version "4.0.1" | ||||||
|   resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" |   resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" | ||||||
|   | |||||||
| @@ -1,8 +0,0 @@ | |||||||
| AUTHENTIK_SECRET_KEY=gl8woZe8L6IIX8SC0c5Ocsj0xPkX5uJo5DVZCFl+L/QGbzuplfutYuua2ODNLEiDD3aFd9H2ylJmrke0 |  | ||||||
| AUTHENTIK_REDIS__HOST=authentik-redis |  | ||||||
| AUTHENTIK_POSTGRESQL__HOST=db-postgres |  | ||||||
| AUTHENTIK_POSTGRESQL__USER=authentik |  | ||||||
| AUTHENTIK_POSTGRESQL__NAME=authentik |  | ||||||
| AUTHENTIK_POSTGRESQL__PASSWORD=07EKS5NLI6Tpv68tbdvrxfvj |  | ||||||
| AUTHENTIK_BOOTSTRAP_PASSWORD=admin |  | ||||||
| AUTHENTIK_BOOTSTRAP_EMAIL=admin@example.com |  | ||||||
										
											Binary file not shown.
										
									
								
							| @@ -29,8 +29,7 @@ COPY scripts/install-s6 /tmp/install-s6 | |||||||
| RUN rm -f /etc/nginx/conf.d/production.conf \ | RUN rm -f /etc/nginx/conf.d/production.conf \ | ||||||
| 	&& chmod 644 /etc/logrotate.d/nginx-proxy-manager \ | 	&& chmod 644 /etc/logrotate.d/nginx-proxy-manager \ | ||||||
| 	&& /tmp/install-s6 "${TARGETPLATFORM}" \ | 	&& /tmp/install-s6 "${TARGETPLATFORM}" \ | ||||||
| 	&& rm -f /tmp/install-s6 \ | 	&& rm -f /tmp/install-s6 | ||||||
| 	&& chmod 644 -R /root/.cache |  | ||||||
|  |  | ||||||
| # Certs for testing purposes | # Certs for testing purposes | ||||||
| COPY --from=pebbleca /test/certs/pebble.minica.pem /etc/ssl/certs/pebble.minica.pem | COPY --from=pebbleca /test/certs/pebble.minica.pem /etc/ssl/certs/pebble.minica.pem | ||||||
|   | |||||||
| @@ -18,7 +18,6 @@ services: | |||||||
|       MYSQL_DATABASE: 'npm' |       MYSQL_DATABASE: 'npm' | ||||||
|       MYSQL_USER: 'npm' |       MYSQL_USER: 'npm' | ||||||
|       MYSQL_PASSWORD: 'npmpass' |       MYSQL_PASSWORD: 'npmpass' | ||||||
|       MARIADB_AUTO_UPGRADE: '1' |  | ||||||
|     volumes: |     volumes: | ||||||
|       - mysql_vol:/var/lib/mysql |       - mysql_vol:/var/lib/mysql | ||||||
|     networks: |     networks: | ||||||
|   | |||||||
| @@ -1,78 +0,0 @@ | |||||||
| # WARNING: This is a CI docker-compose file used for building and testing of the entire app, it should not be used for production. |  | ||||||
| services: |  | ||||||
|  |  | ||||||
|   cypress: |  | ||||||
|     environment: |  | ||||||
|       CYPRESS_stack: 'postgres' |  | ||||||
|  |  | ||||||
|   fullstack: |  | ||||||
|     environment: |  | ||||||
|       DB_POSTGRES_HOST: 'db-postgres' |  | ||||||
|       DB_POSTGRES_PORT: '5432' |  | ||||||
|       DB_POSTGRES_USER: 'npm' |  | ||||||
|       DB_POSTGRES_PASSWORD: 'npmpass' |  | ||||||
|       DB_POSTGRES_NAME: 'npm' |  | ||||||
|     depends_on: |  | ||||||
|       - db-postgres |  | ||||||
|       - authentik |  | ||||||
|       - authentik-worker |  | ||||||
|       - authentik-ldap |  | ||||||
|  |  | ||||||
|   db-postgres: |  | ||||||
|     image: postgres:latest |  | ||||||
|     environment: |  | ||||||
|       POSTGRES_USER: 'npm' |  | ||||||
|       POSTGRES_PASSWORD: 'npmpass' |  | ||||||
|       POSTGRES_DB: 'npm' |  | ||||||
|     volumes: |  | ||||||
|       - psql_vol:/var/lib/postgresql/data |  | ||||||
|       - ./ci/postgres:/docker-entrypoint-initdb.d |  | ||||||
|     networks: |  | ||||||
|       - fulltest |  | ||||||
|  |  | ||||||
|   authentik-redis: |  | ||||||
|     image: 'redis:alpine' |  | ||||||
|     command: --save 60 1 --loglevel warning |  | ||||||
|     restart: unless-stopped |  | ||||||
|     healthcheck: |  | ||||||
|       test: ['CMD-SHELL', 'redis-cli ping | grep PONG'] |  | ||||||
|       start_period: 20s |  | ||||||
|       interval: 30s |  | ||||||
|       retries: 5 |  | ||||||
|       timeout: 3s |  | ||||||
|     volumes: |  | ||||||
|       - redis_vol:/data |  | ||||||
|  |  | ||||||
|   authentik: |  | ||||||
|     image: ghcr.io/goauthentik/server:2024.10.1 |  | ||||||
|     restart: unless-stopped |  | ||||||
|     command: server |  | ||||||
|     env_file: |  | ||||||
|       - ci.env |  | ||||||
|     depends_on: |  | ||||||
|       - authentik-redis |  | ||||||
|       - db-postgres |  | ||||||
|  |  | ||||||
|   authentik-worker: |  | ||||||
|     image: ghcr.io/goauthentik/server:2024.10.1 |  | ||||||
|     restart: unless-stopped |  | ||||||
|     command: worker |  | ||||||
|     env_file: |  | ||||||
|       - ci.env |  | ||||||
|     depends_on: |  | ||||||
|       - authentik-redis |  | ||||||
|       - db-postgres |  | ||||||
|  |  | ||||||
|   authentik-ldap: |  | ||||||
|     image: ghcr.io/goauthentik/ldap:2024.10.1 |  | ||||||
|     environment: |  | ||||||
|       AUTHENTIK_HOST: 'http://authentik:9000' |  | ||||||
|       AUTHENTIK_INSECURE: 'true' |  | ||||||
|       AUTHENTIK_TOKEN: 'wKYZuRcI0ETtb8vWzMCr04oNbhrQUUICy89hSpDln1OEKLjiNEuQ51044Vkp' |  | ||||||
|     restart: unless-stopped |  | ||||||
|     depends_on: |  | ||||||
|       - authentik |  | ||||||
|  |  | ||||||
| volumes: |  | ||||||
|   psql_vol: |  | ||||||
|   redis_vol: |  | ||||||
| @@ -22,10 +22,6 @@ services: | |||||||
|       test: ["CMD", "/usr/bin/check-health"] |       test: ["CMD", "/usr/bin/check-health"] | ||||||
|       interval: 10s |       interval: 10s | ||||||
|       timeout: 3s |       timeout: 3s | ||||||
|     expose: |  | ||||||
|       - '80-81/tcp' |  | ||||||
|       - '443/tcp' |  | ||||||
|       - '1500-1503/tcp' |  | ||||||
|     networks: |     networks: | ||||||
|       fulltest: |       fulltest: | ||||||
|         aliases: |         aliases: | ||||||
| @@ -44,7 +40,7 @@ services: | |||||||
|           - ca.internal |           - ca.internal | ||||||
|  |  | ||||||
|   pdns: |   pdns: | ||||||
|     image: pschiffe/pdns-mysql:4.8 |     image: pschiffe/pdns-mysql | ||||||
|     volumes: |     volumes: | ||||||
|       - '/etc/localtime:/etc/localtime:ro' |       - '/etc/localtime:/etc/localtime:ro' | ||||||
|     environment: |     environment: | ||||||
| @@ -101,7 +97,7 @@ services: | |||||||
|       HTTP_PROXY: 'squid:3128' |       HTTP_PROXY: 'squid:3128' | ||||||
|       HTTPS_PROXY: 'squid:3128' |       HTTPS_PROXY: 'squid:3128' | ||||||
|     volumes: |     volumes: | ||||||
|       - 'cypress_logs:/test/results' |       - 'cypress_logs:/results' | ||||||
|       - './dev/resolv.conf:/etc/resolv.conf:ro' |       - './dev/resolv.conf:/etc/resolv.conf:ro' | ||||||
|       - '/etc/localtime:/etc/localtime:ro' |       - '/etc/localtime:/etc/localtime:ro' | ||||||
|     command: cypress run --browser chrome --config-file=cypress/config/ci.js |     command: cypress run --browser chrome --config-file=cypress/config/ci.js | ||||||
|   | |||||||
| @@ -2,8 +2,8 @@ | |||||||
| services: | services: | ||||||
|  |  | ||||||
|   fullstack: |   fullstack: | ||||||
|     image: npm2dev:core |     image: nginxproxymanager:dev | ||||||
|     container_name: npm2dev.core |     container_name: npm_core | ||||||
|     build: |     build: | ||||||
|       context: ./ |       context: ./ | ||||||
|       dockerfile: ./dev/Dockerfile |       dockerfile: ./dev/Dockerfile | ||||||
| @@ -26,17 +26,11 @@ services: | |||||||
|       DEVELOPMENT: 'true' |       DEVELOPMENT: 'true' | ||||||
|       LE_STAGING: 'true' |       LE_STAGING: 'true' | ||||||
|       # db: |       # db: | ||||||
|       # DB_MYSQL_HOST: 'db' |       DB_MYSQL_HOST: 'db' | ||||||
|       # DB_MYSQL_PORT: '3306' |       DB_MYSQL_PORT: '3306' | ||||||
|       # DB_MYSQL_USER: 'npm' |       DB_MYSQL_USER: 'npm' | ||||||
|       # DB_MYSQL_PASSWORD: 'npm' |       DB_MYSQL_PASSWORD: 'npm' | ||||||
|       # DB_MYSQL_NAME: 'npm' |       DB_MYSQL_NAME: 'npm' | ||||||
|       # db-postgres: |  | ||||||
|       DB_POSTGRES_HOST: 'db-postgres' |  | ||||||
|       DB_POSTGRES_PORT: '5432' |  | ||||||
|       DB_POSTGRES_USER: 'npm' |  | ||||||
|       DB_POSTGRES_PASSWORD: 'npmpass' |  | ||||||
|       DB_POSTGRES_NAME: 'npm' |  | ||||||
|       # DB_SQLITE_FILE: "/data/database.sqlite" |       # DB_SQLITE_FILE: "/data/database.sqlite" | ||||||
|       # DISABLE_IPV6: "true" |       # DISABLE_IPV6: "true" | ||||||
|       # Required for DNS Certificate provisioning testing: |       # Required for DNS Certificate provisioning testing: | ||||||
| @@ -55,15 +49,11 @@ services: | |||||||
|       timeout: 3s |       timeout: 3s | ||||||
|     depends_on: |     depends_on: | ||||||
|       - db |       - db | ||||||
|       - db-postgres |  | ||||||
|       - authentik |  | ||||||
|       - authentik-worker |  | ||||||
|       - authentik-ldap |  | ||||||
|     working_dir: /app |     working_dir: /app | ||||||
|  |  | ||||||
|   db: |   db: | ||||||
|     image: jc21/mariadb-aria |     image: jc21/mariadb-aria | ||||||
|     container_name: npm2dev.db |     container_name: npm_db | ||||||
|     ports: |     ports: | ||||||
|       - 33306:3306 |       - 33306:3306 | ||||||
|     networks: |     networks: | ||||||
| @@ -76,22 +66,8 @@ services: | |||||||
|     volumes: |     volumes: | ||||||
|       - db_data:/var/lib/mysql |       - db_data:/var/lib/mysql | ||||||
|  |  | ||||||
|   db-postgres: |  | ||||||
|     image: postgres:latest |  | ||||||
|     container_name: npm2dev.db-postgres |  | ||||||
|     networks: |  | ||||||
|       - nginx_proxy_manager |  | ||||||
|     environment: |  | ||||||
|       POSTGRES_USER: 'npm' |  | ||||||
|       POSTGRES_PASSWORD: 'npmpass' |  | ||||||
|       POSTGRES_DB: 'npm' |  | ||||||
|     volumes: |  | ||||||
|       - psql_data:/var/lib/postgresql/data |  | ||||||
|       - ./ci/postgres:/docker-entrypoint-initdb.d |  | ||||||
|  |  | ||||||
|   stepca: |   stepca: | ||||||
|     image: jc21/testca |     image: jc21/testca | ||||||
|     container_name: npm2dev.stepca |  | ||||||
|     volumes: |     volumes: | ||||||
|       - './dev/resolv.conf:/etc/resolv.conf:ro' |       - './dev/resolv.conf:/etc/resolv.conf:ro' | ||||||
|       - '/etc/localtime:/etc/localtime:ro' |       - '/etc/localtime:/etc/localtime:ro' | ||||||
| @@ -102,7 +78,6 @@ services: | |||||||
|  |  | ||||||
|   dnsrouter: |   dnsrouter: | ||||||
|     image: jc21/dnsrouter |     image: jc21/dnsrouter | ||||||
|     container_name: npm2dev.dnsrouter |  | ||||||
|     volumes: |     volumes: | ||||||
|       - ./dev/dnsrouter-config.json.tmp:/dnsrouter-config.json:ro |       - ./dev/dnsrouter-config.json.tmp:/dnsrouter-config.json:ro | ||||||
|     networks: |     networks: | ||||||
| @@ -110,7 +85,7 @@ services: | |||||||
|  |  | ||||||
|   swagger: |   swagger: | ||||||
|     image: swaggerapi/swagger-ui:latest |     image: swaggerapi/swagger-ui:latest | ||||||
|     container_name: npm2dev.swagger |     container_name: npm_swagger | ||||||
|     ports: |     ports: | ||||||
|       - 3082:80 |       - 3082:80 | ||||||
|     environment: |     environment: | ||||||
| @@ -121,7 +96,7 @@ services: | |||||||
|  |  | ||||||
|   squid: |   squid: | ||||||
|     image: ubuntu/squid |     image: ubuntu/squid | ||||||
|     container_name: npm2dev.squid |     container_name: npm_squid | ||||||
|     volumes: |     volumes: | ||||||
|       - './dev/squid.conf:/etc/squid/squid.conf:ro' |       - './dev/squid.conf:/etc/squid/squid.conf:ro' | ||||||
|       - './dev/resolv.conf:/etc/resolv.conf:ro' |       - './dev/resolv.conf:/etc/resolv.conf:ro' | ||||||
| @@ -132,8 +107,7 @@ services: | |||||||
|       - 8128:3128 |       - 8128:3128 | ||||||
|  |  | ||||||
|   pdns: |   pdns: | ||||||
|     image: pschiffe/pdns-mysql:4.8 |     image: pschiffe/pdns-mysql | ||||||
|     container_name: npm2dev.pdns |  | ||||||
|     volumes: |     volumes: | ||||||
|       - '/etc/localtime:/etc/localtime:ro' |       - '/etc/localtime:/etc/localtime:ro' | ||||||
|     environment: |     environment: | ||||||
| @@ -162,7 +136,6 @@ services: | |||||||
|  |  | ||||||
|   pdns-db: |   pdns-db: | ||||||
|     image: mariadb |     image: mariadb | ||||||
|     container_name: npm2dev.pdns-db |  | ||||||
|     environment: |     environment: | ||||||
|       MYSQL_ROOT_PASSWORD: 'pdns' |       MYSQL_ROOT_PASSWORD: 'pdns' | ||||||
|       MYSQL_DATABASE: 'pdns' |       MYSQL_DATABASE: 'pdns' | ||||||
| @@ -176,8 +149,7 @@ services: | |||||||
|       - nginx_proxy_manager |       - nginx_proxy_manager | ||||||
|  |  | ||||||
|   cypress: |   cypress: | ||||||
|     image: npm2dev:cypress |     image: "npm_dev_cypress" | ||||||
|     container_name: npm2dev.cypress |  | ||||||
|     build: |     build: | ||||||
|       context: ../ |       context: ../ | ||||||
|       dockerfile: test/cypress/Dockerfile |       dockerfile: test/cypress/Dockerfile | ||||||
| @@ -192,77 +164,16 @@ services: | |||||||
|     networks: |     networks: | ||||||
|       - nginx_proxy_manager |       - nginx_proxy_manager | ||||||
|  |  | ||||||
|   authentik-redis: |  | ||||||
|     image: 'redis:alpine' |  | ||||||
|     container_name: npm2dev.authentik-redis |  | ||||||
|     command: --save 60 1 --loglevel warning |  | ||||||
|     networks: |  | ||||||
|       - nginx_proxy_manager |  | ||||||
|     restart: unless-stopped |  | ||||||
|     healthcheck: |  | ||||||
|       test: ['CMD-SHELL', 'redis-cli ping | grep PONG'] |  | ||||||
|       start_period: 20s |  | ||||||
|       interval: 30s |  | ||||||
|       retries: 5 |  | ||||||
|       timeout: 3s |  | ||||||
|     volumes: |  | ||||||
|       - redis_data:/data |  | ||||||
|  |  | ||||||
|   authentik: |  | ||||||
|     image: ghcr.io/goauthentik/server:2024.10.1 |  | ||||||
|     container_name: npm2dev.authentik |  | ||||||
|     restart: unless-stopped |  | ||||||
|     command: server |  | ||||||
|     networks: |  | ||||||
|       - nginx_proxy_manager |  | ||||||
|     env_file: |  | ||||||
|       - ci.env |  | ||||||
|     ports: |  | ||||||
|       - 9000:9000 |  | ||||||
|     depends_on: |  | ||||||
|       - authentik-redis |  | ||||||
|       - db-postgres |  | ||||||
|  |  | ||||||
|   authentik-worker: |  | ||||||
|     image: ghcr.io/goauthentik/server:2024.10.1 |  | ||||||
|     container_name: npm2dev.authentik-worker |  | ||||||
|     restart: unless-stopped |  | ||||||
|     command: worker |  | ||||||
|     networks: |  | ||||||
|       - nginx_proxy_manager |  | ||||||
|     env_file: |  | ||||||
|       - ci.env |  | ||||||
|     depends_on: |  | ||||||
|       - authentik-redis |  | ||||||
|       - db-postgres |  | ||||||
|  |  | ||||||
|   authentik-ldap: |  | ||||||
|     image: ghcr.io/goauthentik/ldap:2024.10.1 |  | ||||||
|     container_name: npm2dev.authentik-ldap |  | ||||||
|     networks: |  | ||||||
|       - nginx_proxy_manager |  | ||||||
|     environment: |  | ||||||
|       AUTHENTIK_HOST: 'http://authentik:9000' |  | ||||||
|       AUTHENTIK_INSECURE: 'true' |  | ||||||
|       AUTHENTIK_TOKEN: 'wKYZuRcI0ETtb8vWzMCr04oNbhrQUUICy89hSpDln1OEKLjiNEuQ51044Vkp' |  | ||||||
|     restart: unless-stopped |  | ||||||
|     depends_on: |  | ||||||
|       - authentik |  | ||||||
|  |  | ||||||
| volumes: | volumes: | ||||||
|   npm_data: |   npm_data: | ||||||
|     name: npm2dev_core_data |     name: npm_core_data | ||||||
|   le_data: |   le_data: | ||||||
|     name: npm2dev_le_data |     name: npm_le_data | ||||||
|   db_data: |   db_data: | ||||||
|     name: npm2dev_db_data |     name: npm_db_data | ||||||
|   pdns_mysql: |   pdns_mysql: | ||||||
|     name: npnpm2dev_pdns_mysql |     name: npm_pdns_mysql | ||||||
|   psql_data: |  | ||||||
|     name: npm2dev_psql_data |  | ||||||
|   redis_data: |  | ||||||
|     name: npm2dev_redis_data |  | ||||||
|  |  | ||||||
| networks: | networks: | ||||||
|   nginx_proxy_manager: |   nginx_proxy_manager: | ||||||
|     name: npm2dev_network |     name: npm_network | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| location ~* ^.*\.(css|js|jpe?g|gif|png|webp|woff|woff2|eot|ttf|svg|ico|css\.map|js\.map)$ { | location ~* ^.*\.(css|js|jpe?g|gif|png|webp|woff|eot|ttf|svg|ico|css\.map|js\.map)$ { | ||||||
| 	if_modified_since off; | 	if_modified_since off; | ||||||
|  |  | ||||||
| 	# use the public cache | 	# use the public cache | ||||||
|   | |||||||
| @@ -1,2 +0,0 @@ | |||||||
| ssl_session_timeout 5m; |  | ||||||
| ssl_session_cache shared:SSL_stream:50m; |  | ||||||
| @@ -1,2 +0,0 @@ | |||||||
| ssl_session_timeout 5m; |  | ||||||
| ssl_session_cache shared:SSL:50m; |  | ||||||
| @@ -1,3 +1,6 @@ | |||||||
|  | ssl_session_timeout 5m; | ||||||
|  | ssl_session_cache shared:SSL:50m; | ||||||
|  |  | ||||||
| # intermediate configuration. tweak to your needs. | # intermediate configuration. tweak to your needs. | ||||||
| ssl_protocols TLSv1.2 TLSv1.3; | ssl_protocols TLSv1.2 TLSv1.3; | ||||||
| ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384'; | ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384'; | ||||||
|   | |||||||
| @@ -8,53 +8,21 @@ log_info 'Setting ownership ...' | |||||||
| # root | # root | ||||||
| chown root /tmp/nginx | chown root /tmp/nginx | ||||||
|  |  | ||||||
| locations=( | # npm user and group | ||||||
| 	"/data" | chown -R "$PUID:$PGID" /data | ||||||
| 	"/etc/letsencrypt" | chown -R "$PUID:$PGID" /etc/letsencrypt | ||||||
| 	"/run/nginx" | chown -R "$PUID:$PGID" /run/nginx | ||||||
| 	"/tmp/nginx" | chown -R "$PUID:$PGID" /tmp/nginx | ||||||
| 	"/var/cache/nginx" | chown -R "$PUID:$PGID" /var/cache/nginx | ||||||
| 	"/var/lib/logrotate" | chown -R "$PUID:$PGID" /var/lib/logrotate | ||||||
| 	"/var/lib/nginx" | chown -R "$PUID:$PGID" /var/lib/nginx | ||||||
| 	"/var/log/nginx" | chown -R "$PUID:$PGID" /var/log/nginx | ||||||
| 	"/etc/nginx/nginx" |  | ||||||
| 	"/etc/nginx/nginx.conf" |  | ||||||
| 	"/etc/nginx/conf.d" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| chownit() { | # Don't chown entire /etc/nginx folder as this causes crashes on some systems | ||||||
| 	local dir="$1" | chown -R "$PUID:$PGID" /etc/nginx/nginx | ||||||
| 	local recursive="${2:-true}" | chown -R "$PUID:$PGID" /etc/nginx/nginx.conf | ||||||
|  | chown -R "$PUID:$PGID" /etc/nginx/conf.d | ||||||
|  |  | ||||||
| 	local have | # Prevents errors when installing python certbot plugins when non-root | ||||||
| 	have="$(stat -c '%u:%g' "$dir")" | chown "$PUID:$PGID" /opt/certbot /opt/certbot/bin | ||||||
| 	echo "- $dir ... " | find /opt/certbot/lib/python*/site-packages -not -user "$PUID" -execdir chown "$PUID:$PGID" {} \+ | ||||||
|  |  | ||||||
| 	if [ "$have" != "$PUID:$PGID" ]; then |  | ||||||
| 		if [ "$recursive" = 'true' ] && [ -d "$dir" ]; then |  | ||||||
| 			chown -R "$PUID:$PGID" "$dir" |  | ||||||
| 		else |  | ||||||
| 			chown "$PUID:$PGID" "$dir" |  | ||||||
| 		fi |  | ||||||
| 		echo "    DONE" |  | ||||||
| 	else |  | ||||||
| 		echo "    SKIPPED" |  | ||||||
| 	fi |  | ||||||
| } |  | ||||||
|  |  | ||||||
| for loc in "${locations[@]}"; do |  | ||||||
| 	chownit "$loc" |  | ||||||
| done |  | ||||||
|  |  | ||||||
| if [ "$(is_true "${SKIP_CERTBOT_OWNERSHIP:-}")" = '1' ]; then |  | ||||||
| 	log_info 'Skipping ownership change of certbot directories' |  | ||||||
| else |  | ||||||
| 	log_info 'Changing ownership of certbot directories, this may take some time ...' |  | ||||||
| 	chownit "/opt/certbot" false |  | ||||||
| 	chownit "/opt/certbot/bin" false |  | ||||||
|  |  | ||||||
| 	# Handle all site-packages directories efficiently |  | ||||||
| 	find /opt/certbot/lib -type d -name "site-packages" | while read -r SITE_PACKAGES_DIR; do |  | ||||||
| 		chownit "$SITE_PACKAGES_DIR" |  | ||||||
| 	done |  | ||||||
| fi |  | ||||||
|   | |||||||
| @@ -5,9 +5,12 @@ set -e | |||||||
|  |  | ||||||
| log_info 'Dynamic resolvers ...' | log_info 'Dynamic resolvers ...' | ||||||
|  |  | ||||||
|  | DISABLE_IPV6=$(echo "${DISABLE_IPV6:-}" | tr '[:upper:]' '[:lower:]') | ||||||
|  |  | ||||||
| # Dynamically generate resolvers file, if resolver is IPv6, enclose in `[]` | # Dynamically generate resolvers file, if resolver is IPv6, enclose in `[]` | ||||||
| # thanks @tfmm | # thanks @tfmm | ||||||
| if [ "$(is_true "$DISABLE_IPV6")" = '1' ]; then | 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 | 	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 | 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 | 	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 | ||||||
|   | |||||||
| @@ -8,11 +8,14 @@ set -e | |||||||
|  |  | ||||||
| log_info 'IPv6 ...' | log_info 'IPv6 ...' | ||||||
|  |  | ||||||
|  | # Lowercase | ||||||
|  | DISABLE_IPV6=$(echo "${DISABLE_IPV6:-}" | tr '[:upper:]' '[:lower:]') | ||||||
|  |  | ||||||
| process_folder () { | process_folder () { | ||||||
| 	FILES=$(find "$1" -type f -name "*.conf") | 	FILES=$(find "$1" -type f -name "*.conf") | ||||||
| 	SED_REGEX= | 	SED_REGEX= | ||||||
|  |  | ||||||
| 	if [ "$(is_true "$DISABLE_IPV6")" = '1' ]; then | 	if [ "$DISABLE_IPV6" == "true" ] || [ "$DISABLE_IPV6" == "on" ] || [ "$DISABLE_IPV6" == "1" ] || [ "$DISABLE_IPV6" == "yes" ]; then | ||||||
| 		# IPV6 is disabled | 		# IPV6 is disabled | ||||||
| 		echo "Disabling IPV6 in hosts in: $1" | 		echo "Disabling IPV6 in hosts in: $1" | ||||||
| 		SED_REGEX='s/^([^#]*)listen \[::\]/\1#listen [::]/g' | 		SED_REGEX='s/^([^#]*)listen \[::\]/\1#listen [::]/g' | ||||||
|   | |||||||
| @@ -56,13 +56,3 @@ get_group_id () { | |||||||
| 		getent group "$1" | cut -d: -f3 | 		getent group "$1" | cut -d: -f3 | ||||||
| 	fi | 	fi | ||||||
| } | } | ||||||
|  |  | ||||||
| # param $1: value |  | ||||||
| is_true () { |  | ||||||
| 	VAL=$(echo "${1:-}" | tr '[:upper:]' '[:lower:]') |  | ||||||
| 	if [ "$VAL" == 'true' ] || [ "$VAL" == 'on' ] || [ "$VAL" == '1' ] || [ "$VAL" == 'yes' ]; then |  | ||||||
| 		echo '1' |  | ||||||
| 	else |  | ||||||
| 		echo '0' |  | ||||||
| 	fi |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ BLUE='\E[1;34m' | |||||||
| GREEN='\E[1;32m' | GREEN='\E[1;32m' | ||||||
| RESET='\E[0m' | RESET='\E[0m' | ||||||
|  |  | ||||||
| S6_OVERLAY_VERSION=3.2.1.0 | S6_OVERLAY_VERSION=3.1.5.0 | ||||||
| TARGETPLATFORM=${1:-linux/amd64} | TARGETPLATFORM=${1:-linux/amd64} | ||||||
|  |  | ||||||
| # Determine the correct binary file for the architecture given | # Determine the correct binary file for the architecture given | ||||||
|   | |||||||
| @@ -50,6 +50,7 @@ networks: | |||||||
| Let's look at a Portainer example: | Let's look at a Portainer example: | ||||||
|  |  | ||||||
| ```yml | ```yml | ||||||
|  | version: '3.8' | ||||||
| services: | services: | ||||||
|  |  | ||||||
|   portainer: |   portainer: | ||||||
| @@ -91,6 +92,8 @@ This image supports the use of Docker secrets to import from files and keep sens | |||||||
| You can set any environment variable from a file by appending `__FILE` (double-underscore FILE) to the environmental variable name. | You can set any environment variable from a file by appending `__FILE` (double-underscore FILE) to the environmental variable name. | ||||||
|  |  | ||||||
| ```yml | ```yml | ||||||
|  | version: '3.8' | ||||||
|  |  | ||||||
| secrets: | secrets: | ||||||
|   # Secrets are single-line text files where the sole content is the secret |   # Secrets are single-line text files where the sole content is the secret | ||||||
|   # Paths in this example assume that secrets are kept in local folder called ".secrets" |   # Paths in this example assume that secrets are kept in local folder called ".secrets" | ||||||
| @@ -161,14 +164,6 @@ The easy fix is to add a Docker environment variable to the Nginx Proxy Manager | |||||||
|       DISABLE_IPV6: 'true' |       DISABLE_IPV6: 'true' | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ## Disabling IP Ranges Fetch |  | ||||||
|  |  | ||||||
| By default, NPM fetches IP ranges from CloudFront and Cloudflare during application startup. In environments with limited internet access or to speed up container startup, this fetch can be disabled: |  | ||||||
|  |  | ||||||
| ```yml |  | ||||||
|     environment: |  | ||||||
|       IP_RANGES_FETCH_ENABLED: 'false' |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ## Custom Nginx Configurations | ## Custom Nginx Configurations | ||||||
|  |  | ||||||
| @@ -189,7 +184,6 @@ You can add your custom configuration snippet files at `/data/nginx/custom` as f | |||||||
|  - `/data/nginx/custom/server_stream.conf`: Included at the end of every stream server block |  - `/data/nginx/custom/server_stream.conf`: Included at the end of every stream server block | ||||||
|  - `/data/nginx/custom/server_stream_tcp.conf`: Included at the end of every TCP stream server block |  - `/data/nginx/custom/server_stream_tcp.conf`: Included at the end of every TCP stream server block | ||||||
|  - `/data/nginx/custom/server_stream_udp.conf`: Included at the end of every UDP stream server block |  - `/data/nginx/custom/server_stream_udp.conf`: Included at the end of every UDP stream server block | ||||||
|  - `/data/nginx/custom/server_dead.conf`: Included at the end of every 404 server block |  | ||||||
|  |  | ||||||
| Every file is optional. | Every file is optional. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ outline: deep | |||||||
| Create a `docker-compose.yml` file: | Create a `docker-compose.yml` file: | ||||||
|  |  | ||||||
| ```yml | ```yml | ||||||
|  | version: '3.8' | ||||||
| services: | services: | ||||||
|   app: |   app: | ||||||
|     image: 'jc21/nginx-proxy-manager:latest' |     image: 'jc21/nginx-proxy-manager:latest' | ||||||
| @@ -21,7 +22,8 @@ services: | |||||||
|       # Add any other Stream port you want to expose |       # Add any other Stream port you want to expose | ||||||
|       # - '21:21' # FTP |       # - '21:21' # FTP | ||||||
|  |  | ||||||
|     #environment: |     # Uncomment the next line if you uncomment anything in the section | ||||||
|  |     # environment: | ||||||
|       # Uncomment this if you want to change the location of |       # Uncomment this if you want to change the location of | ||||||
|       # the SQLite DB file within the container |       # the SQLite DB file within the container | ||||||
|       # DB_SQLITE_FILE: "/data/database.sqlite" |       # DB_SQLITE_FILE: "/data/database.sqlite" | ||||||
| @@ -53,6 +55,7 @@ are going to use. | |||||||
| Here is an example of what your `docker-compose.yml` will look like when using a MariaDB container: | Here is an example of what your `docker-compose.yml` will look like when using a MariaDB container: | ||||||
|  |  | ||||||
| ```yml | ```yml | ||||||
|  | version: '3.8' | ||||||
| services: | services: | ||||||
|   app: |   app: | ||||||
|     image: 'jc21/nginx-proxy-manager:latest' |     image: 'jc21/nginx-proxy-manager:latest' | ||||||
| @@ -98,53 +101,6 @@ Please note, that `DB_MYSQL_*` environment variables will take precedent over `D | |||||||
|  |  | ||||||
| ::: | ::: | ||||||
|  |  | ||||||
| ## Using Postgres database |  | ||||||
|  |  | ||||||
| Similar to the MySQL server setup: |  | ||||||
|  |  | ||||||
| ```yml |  | ||||||
| services: |  | ||||||
|   app: |  | ||||||
|     image: 'jc21/nginx-proxy-manager:latest' |  | ||||||
|     restart: unless-stopped |  | ||||||
|     ports: |  | ||||||
|       # These ports are in format <host-port>:<container-port> |  | ||||||
|       - '80:80' # Public HTTP Port |  | ||||||
|       - '443:443' # Public HTTPS Port |  | ||||||
|       - '81:81' # Admin Web Port |  | ||||||
|       # Add any other Stream port you want to expose |  | ||||||
|       # - '21:21' # FTP |  | ||||||
|     environment: |  | ||||||
|       # Postgres parameters: |  | ||||||
|       DB_POSTGRES_HOST: 'db' |  | ||||||
|       DB_POSTGRES_PORT: '5432' |  | ||||||
|       DB_POSTGRES_USER: 'npm' |  | ||||||
|       DB_POSTGRES_PASSWORD: 'npmpass' |  | ||||||
|       DB_POSTGRES_NAME: 'npm' |  | ||||||
|       # Uncomment this if IPv6 is not enabled on your host |  | ||||||
|       # DISABLE_IPV6: 'true' |  | ||||||
|     volumes: |  | ||||||
|       - ./data:/data |  | ||||||
|       - ./letsencrypt:/etc/letsencrypt |  | ||||||
|     depends_on: |  | ||||||
|       - db |  | ||||||
|  |  | ||||||
|   db: |  | ||||||
|     image: postgres:latest |  | ||||||
|     environment: |  | ||||||
|       POSTGRES_USER: 'npm' |  | ||||||
|       POSTGRES_PASSWORD: 'npmpass' |  | ||||||
|       POSTGRES_DB: 'npm' |  | ||||||
|     volumes: |  | ||||||
|       - ./postgres:/var/lib/postgresql/data |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ::: warning |  | ||||||
|  |  | ||||||
| Custom Postgres schema is not supported, as such `public` will be used. |  | ||||||
|  |  | ||||||
| ::: |  | ||||||
|  |  | ||||||
| ## Running on Raspberry PI / ARM devices | ## Running on Raspberry PI / ARM devices | ||||||
|  |  | ||||||
| The docker images support the following architectures: | The docker images support the following architectures: | ||||||
| @@ -181,13 +137,5 @@ Email:    admin@example.com | |||||||
| Password: changeme | Password: changeme | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| Immediately after logging in with this default user you will be asked to modify your details and change your password. You can change defaults with: | Immediately after logging in with this default user you will be asked to modify your details and change your password. | ||||||
|  |  | ||||||
|  |  | ||||||
| ``` |  | ||||||
|     environment: |  | ||||||
|       INITIAL_ADMIN_EMAIL: my@example.com |  | ||||||
|       INITIAL_ADMIN_PASSWORD: mypassword1 |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								docs/src/third-party/index.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								docs/src/third-party/index.md
									
									
									
									
										vendored
									
									
								
							| @@ -12,7 +12,6 @@ Known integrations: | |||||||
| - [HomeAssistant Hass.io plugin](https://github.com/hassio-addons/addon-nginx-proxy-manager) | - [HomeAssistant Hass.io plugin](https://github.com/hassio-addons/addon-nginx-proxy-manager) | ||||||
| - [UnRaid / Synology](https://github.com/jlesage/docker-nginx-proxy-manager) | - [UnRaid / Synology](https://github.com/jlesage/docker-nginx-proxy-manager) | ||||||
| - [Proxmox Scripts](https://github.com/ej52/proxmox-scripts/tree/main/apps/nginx-proxy-manager) | - [Proxmox Scripts](https://github.com/ej52/proxmox-scripts/tree/main/apps/nginx-proxy-manager) | ||||||
| - [Proxmox VE Helper-Scripts](https://community-scripts.github.io/ProxmoxVE/scripts?id=nginxproxymanager) |  | ||||||
| - [nginxproxymanagerGraf](https://github.com/ma-karai/nginxproxymanagerGraf) | - [nginxproxymanagerGraf](https://github.com/ma-karai/nginxproxymanagerGraf) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -873,9 +873,9 @@ mitt@^3.0.1: | |||||||
|   integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw== |   integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw== | ||||||
|  |  | ||||||
| nanoid@^3.3.7: | nanoid@^3.3.7: | ||||||
|   version "3.3.8" |   version "3.3.7" | ||||||
|   resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" |   resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" | ||||||
|   integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== |   integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== | ||||||
|  |  | ||||||
| oniguruma-to-js@0.4.3: | oniguruma-to-js@0.4.3: | ||||||
|   version "0.4.3" |   version "0.4.3" | ||||||
| @@ -1065,9 +1065,9 @@ vfile@^6.0.0: | |||||||
|     vfile-message "^4.0.0" |     vfile-message "^4.0.0" | ||||||
|  |  | ||||||
| vite@^5.4.8: | vite@^5.4.8: | ||||||
|   version "5.4.19" |   version "5.4.8" | ||||||
|   resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.19.tgz#20efd060410044b3ed555049418a5e7d1998f959" |   resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.8.tgz#af548ce1c211b2785478d3ba3e8da51e39a287e8" | ||||||
|   integrity sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA== |   integrity sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ== | ||||||
|   dependencies: |   dependencies: | ||||||
|     esbuild "^0.21.3" |     esbuild "^0.21.3" | ||||||
|     postcss "^8.4.43" |     postcss "^8.4.43" | ||||||
|   | |||||||
| @@ -4,438 +4,444 @@ const Tokens   = require('./tokens'); | |||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * @param {String} route |      * @param {String} route | ||||||
| 	 * @param {Object} [options] |      * @param {Object} [options] | ||||||
| 	 * @returns {Boolean} |      * @returns {Boolean} | ||||||
| 	 */ |      */ | ||||||
| 	navigate: function (route, options) { |     navigate: function (route, options) { | ||||||
| 		options = options || {}; |         options = options || {}; | ||||||
| 		Backbone.history.navigate(route.toString(), options); |         Backbone.history.navigate(route.toString(), options); | ||||||
| 		return true; |         return true; | ||||||
| 	}, |     }, | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * Login |      * Login | ||||||
| 	 */ |      */ | ||||||
| 	showLogin: function () { |     showLogin: function () { | ||||||
| 		window.location = '/login'; |         window.location = '/login'; | ||||||
| 	}, |     }, | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * Users |      * Users | ||||||
| 	 */ |      */ | ||||||
| 	showUsers: function () { |     showUsers: function () { | ||||||
| 		const controller = this; |         let controller = this; | ||||||
| 		if (Cache.User.isAdmin()) { |         if (Cache.User.isAdmin()) { | ||||||
| 			require(['./main', './users/main'], (App, View) => { |             require(['./main', './users/main'], (App, View) => { | ||||||
| 				controller.navigate('/users'); |                 controller.navigate('/users'); | ||||||
| 				App.UI.showAppContent(new View()); |                 App.UI.showAppContent(new View()); | ||||||
| 			}); |             }); | ||||||
| 		} else { |         } else { | ||||||
| 			this.showDashboard(); |             this.showDashboard(); | ||||||
| 		} |         } | ||||||
| 	}, |     }, | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * User Form |      * User Form | ||||||
| 	 * |      * | ||||||
| 	 * @param [model] |      * @param [model] | ||||||
| 	 */ |      */ | ||||||
| 	showUserForm: function (model) { |     showUserForm: function (model) { | ||||||
| 		if (Cache.User.isAdmin()) { |         if (Cache.User.isAdmin()) { | ||||||
| 			require(['./main', './user/form'], function (App, View) { |             require(['./main', './user/form'], function (App, View) { | ||||||
| 				App.UI.showModalDialog(new View({model: model})); |                 App.UI.showModalDialog(new View({model: model})); | ||||||
| 			}); |             }); | ||||||
| 		} |         } | ||||||
| 	}, |     }, | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * User Permissions Form |      * User Permissions Form | ||||||
| 	 * |      * | ||||||
| 	 * @param model |      * @param model | ||||||
| 	 */ |      */ | ||||||
| 	showUserPermissions: function (model) { |     showUserPermissions: function (model) { | ||||||
| 		if (Cache.User.isAdmin()) { |         if (Cache.User.isAdmin()) { | ||||||
| 			require(['./main', './user/permissions'], function (App, View) { |             require(['./main', './user/permissions'], function (App, View) { | ||||||
| 				App.UI.showModalDialog(new View({model: model})); |                 App.UI.showModalDialog(new View({model: model})); | ||||||
| 			}); |             }); | ||||||
| 		} |         } | ||||||
| 	}, |     }, | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * User Password Form |      * User Password Form | ||||||
| 	 * |      * | ||||||
| 	 * @param model |      * @param model | ||||||
| 	 */ |      */ | ||||||
| 	showUserPasswordForm: function (model) { |     showUserPasswordForm: function (model) { | ||||||
| 		if (Cache.User.isAdmin() || model.get('id') === Cache.User.get('id')) { |         if (Cache.User.isAdmin() || model.get('id') === Cache.User.get('id')) { | ||||||
| 			require(['./main', './user/password'], function (App, View) { |             require(['./main', './user/password'], function (App, View) { | ||||||
| 				App.UI.showModalDialog(new View({model: model})); |                 App.UI.showModalDialog(new View({model: model})); | ||||||
| 			}); |             }); | ||||||
| 		} |         } | ||||||
| 	}, |     }, | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * User Delete Confirm |      * User Delete Confirm | ||||||
| 	 * |      * | ||||||
| 	 * @param model |      * @param model | ||||||
| 	 */ |      */ | ||||||
| 	showUserDeleteConfirm: function (model) { |     showUserDeleteConfirm: function (model) { | ||||||
| 		if (Cache.User.isAdmin() && model.get('id') !== Cache.User.get('id')) { |         if (Cache.User.isAdmin() && model.get('id') !== Cache.User.get('id')) { | ||||||
| 			require(['./main', './user/delete'], function (App, View) { |             require(['./main', './user/delete'], function (App, View) { | ||||||
| 				App.UI.showModalDialog(new View({model: model})); |                 App.UI.showModalDialog(new View({model: model})); | ||||||
| 			}); |             }); | ||||||
| 		} |         } | ||||||
| 	}, |     }, | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * Dashboard |      * Dashboard | ||||||
| 	 */ |      */ | ||||||
| 	showDashboard: function () { |     showDashboard: function () { | ||||||
| 		const controller = this; |         let controller = this; | ||||||
| 		require(['./main', './dashboard/main'], (App, View) => { |  | ||||||
| 			controller.navigate('/'); |  | ||||||
| 			App.UI.showAppContent(new View()); |  | ||||||
| 		}); |  | ||||||
| 	}, |  | ||||||
|  |  | ||||||
| 	/** |         require(['./main', './dashboard/main'], (App, View) => { | ||||||
| 	 * Nginx Proxy Hosts |             controller.navigate('/'); | ||||||
| 	 */ |             App.UI.showAppContent(new View()); | ||||||
| 	showNginxProxy: function () { |         }); | ||||||
| 		if (Cache.User.isAdmin() || Cache.User.canView('proxy_hosts')) { |     }, | ||||||
| 			const controller = this; |  | ||||||
|  |  | ||||||
| 			require(['./main', './nginx/proxy/main'], (App, View) => { |     /** | ||||||
| 				controller.navigate('/nginx/proxy'); |      * Nginx Proxy Hosts | ||||||
| 				App.UI.showAppContent(new View()); |      */ | ||||||
| 			}); |     showNginxProxy: function () { | ||||||
| 		} |         if (Cache.User.isAdmin() || Cache.User.canView('proxy_hosts')) { | ||||||
| 	}, |             let controller = this; | ||||||
|  |  | ||||||
| 	/** |             require(['./main', './nginx/proxy/main'], (App, View) => { | ||||||
| 	 * Nginx Proxy Host Form |                 controller.navigate('/nginx/proxy'); | ||||||
| 	 * |                 App.UI.showAppContent(new View()); | ||||||
| 	 * @param [model] |             }); | ||||||
| 	 */ |         } | ||||||
| 	showNginxProxyForm: function (model) { |     }, | ||||||
| 		if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) { |  | ||||||
| 			require(['./main', './nginx/proxy/form'], function (App, View) { |  | ||||||
| 				App.UI.showModalDialog(new View({model: model})); |  | ||||||
| 			}); |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * Proxy Host Delete Confirm |      * Nginx Proxy Host Form | ||||||
| 	 * |      * | ||||||
| 	 * @param model |      * @param [model] | ||||||
| 	 */ |      */ | ||||||
| 	showNginxProxyDeleteConfirm: function (model) { |     showNginxProxyForm: function (model) { | ||||||
| 		if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) { |         if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) { | ||||||
| 			require(['./main', './nginx/proxy/delete'], function (App, View) { |             require(['./main', './nginx/proxy/form'], function (App, View) { | ||||||
| 				App.UI.showModalDialog(new View({model: model})); |                 App.UI.showModalDialog(new View({model: model})); | ||||||
| 			}); |             }); | ||||||
| 		} |         } | ||||||
| 	}, |     }, | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * Nginx Redirection Hosts |      * Proxy Host Delete Confirm | ||||||
| 	 */ |      * | ||||||
| 	showNginxRedirection: function () { |      * @param model | ||||||
| 		if (Cache.User.isAdmin() || Cache.User.canView('redirection_hosts')) { |      */ | ||||||
| 			const controller = this; |     showNginxProxyDeleteConfirm: function (model) { | ||||||
| 			require(['./main', './nginx/redirection/main'], (App, View) => { |         if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) { | ||||||
| 				controller.navigate('/nginx/redirection'); |             require(['./main', './nginx/proxy/delete'], function (App, View) { | ||||||
| 				App.UI.showAppContent(new View()); |                 App.UI.showModalDialog(new View({model: model})); | ||||||
| 			}); |             }); | ||||||
| 		} |         } | ||||||
| 	}, |     }, | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * Nginx Redirection Host Form |      * Nginx Redirection Hosts | ||||||
| 	 * |      */ | ||||||
| 	 * @param [model] |     showNginxRedirection: function () { | ||||||
| 	 */ |         if (Cache.User.isAdmin() || Cache.User.canView('redirection_hosts')) { | ||||||
| 	showNginxRedirectionForm: function (model) { |             let controller = this; | ||||||
| 		if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) { |  | ||||||
| 			require(['./main', './nginx/redirection/form'], function (App, View) { |  | ||||||
| 				App.UI.showModalDialog(new View({model: model})); |  | ||||||
| 			}); |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
|  |  | ||||||
| 	/** |             require(['./main', './nginx/redirection/main'], (App, View) => { | ||||||
| 	 * Proxy Redirection Delete Confirm |                 controller.navigate('/nginx/redirection'); | ||||||
| 	 * |                 App.UI.showAppContent(new View()); | ||||||
| 	 * @param model |             }); | ||||||
| 	 */ |         } | ||||||
| 	showNginxRedirectionDeleteConfirm: function (model) { |     }, | ||||||
| 		if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) { |  | ||||||
| 			require(['./main', './nginx/redirection/delete'], function (App, View) { |  | ||||||
| 				App.UI.showModalDialog(new View({model: model})); |  | ||||||
| 			}); |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * Nginx Stream Hosts |      * Nginx Redirection Host Form | ||||||
| 	 */ |      * | ||||||
| 	showNginxStream: function () { |      * @param [model] | ||||||
| 		if (Cache.User.isAdmin() || Cache.User.canView('streams')) { |      */ | ||||||
| 			const controller = this; |     showNginxRedirectionForm: function (model) { | ||||||
| 			require(['./main', './nginx/stream/main'], (App, View) => { |         if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) { | ||||||
| 				controller.navigate('/nginx/stream'); |             require(['./main', './nginx/redirection/form'], function (App, View) { | ||||||
| 				App.UI.showAppContent(new View()); |                 App.UI.showModalDialog(new View({model: model})); | ||||||
| 			}); |             }); | ||||||
| 		} |         } | ||||||
| 	}, |     }, | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * Stream Form |      * Proxy Redirection Delete Confirm | ||||||
| 	 * |      * | ||||||
| 	 * @param [model] |      * @param model | ||||||
| 	 */ |      */ | ||||||
| 	showNginxStreamForm: function (model) { |     showNginxRedirectionDeleteConfirm: function (model) { | ||||||
| 		if (Cache.User.isAdmin() || Cache.User.canManage('streams')) { |         if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) { | ||||||
| 			require(['./main', './nginx/stream/form'], function (App, View) { |             require(['./main', './nginx/redirection/delete'], function (App, View) { | ||||||
| 				App.UI.showModalDialog(new View({model: model})); |                 App.UI.showModalDialog(new View({model: model})); | ||||||
| 			}); |             }); | ||||||
| 		} |         } | ||||||
| 	}, |     }, | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * Stream Delete Confirm |      * Nginx Stream Hosts | ||||||
| 	 * |      */ | ||||||
| 	 * @param model |     showNginxStream: function () { | ||||||
| 	 */ |         if (Cache.User.isAdmin() || Cache.User.canView('streams')) { | ||||||
| 	showNginxStreamDeleteConfirm: function (model) { |             let controller = this; | ||||||
| 		if (Cache.User.isAdmin() || Cache.User.canManage('streams')) { |  | ||||||
| 			require(['./main', './nginx/stream/delete'], function (App, View) { |  | ||||||
| 				App.UI.showModalDialog(new View({model: model})); |  | ||||||
| 			}); |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
|  |  | ||||||
| 	/** |             require(['./main', './nginx/stream/main'], (App, View) => { | ||||||
| 	 * Nginx Dead Hosts |                 controller.navigate('/nginx/stream'); | ||||||
| 	 */ |                 App.UI.showAppContent(new View()); | ||||||
| 	showNginxDead: function () { |             }); | ||||||
| 		if (Cache.User.isAdmin() || Cache.User.canView('dead_hosts')) { |         } | ||||||
| 			const controller = this; |     }, | ||||||
| 			require(['./main', './nginx/dead/main'], (App, View) => { |  | ||||||
| 				controller.navigate('/nginx/404'); |  | ||||||
| 				App.UI.showAppContent(new View()); |  | ||||||
| 			}); |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * Dead Host Form |      * Stream Form | ||||||
| 	 * |      * | ||||||
| 	 * @param [model] |      * @param [model] | ||||||
| 	 */ |      */ | ||||||
| 	showNginxDeadForm: function (model) { |     showNginxStreamForm: function (model) { | ||||||
| 		if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) { |         if (Cache.User.isAdmin() || Cache.User.canManage('streams')) { | ||||||
| 			require(['./main', './nginx/dead/form'], function (App, View) { |             require(['./main', './nginx/stream/form'], function (App, View) { | ||||||
| 				App.UI.showModalDialog(new View({model: model})); |                 App.UI.showModalDialog(new View({model: model})); | ||||||
| 			}); |             }); | ||||||
| 		} |         } | ||||||
| 	}, |     }, | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * Dead Host Delete Confirm |      * Stream Delete Confirm | ||||||
| 	 * |      * | ||||||
| 	 * @param model |      * @param model | ||||||
| 	 */ |      */ | ||||||
| 	showNginxDeadDeleteConfirm: function (model) { |     showNginxStreamDeleteConfirm: function (model) { | ||||||
| 		if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) { |         if (Cache.User.isAdmin() || Cache.User.canManage('streams')) { | ||||||
| 			require(['./main', './nginx/dead/delete'], function (App, View) { |             require(['./main', './nginx/stream/delete'], function (App, View) { | ||||||
| 				App.UI.showModalDialog(new View({model: model})); |                 App.UI.showModalDialog(new View({model: model})); | ||||||
| 			}); |             }); | ||||||
| 		} |         } | ||||||
| 	}, |     }, | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * Help Dialog |      * Nginx Dead Hosts | ||||||
| 	 * |      */ | ||||||
| 	 * @param {String}  title |     showNginxDead: function () { | ||||||
| 	 * @param {String}  content |         if (Cache.User.isAdmin() || Cache.User.canView('dead_hosts')) { | ||||||
| 	 */ |             let controller = this; | ||||||
| 	showHelp: function (title, content) { |  | ||||||
| 		require(['./main', './help/main'], function (App, View) { |  | ||||||
| 			App.UI.showModalDialog(new View({title: title, content: content})); |  | ||||||
| 		}); |  | ||||||
| 	}, |  | ||||||
|  |  | ||||||
| 	/** |             require(['./main', './nginx/dead/main'], (App, View) => { | ||||||
| 	 * Nginx Access |                 controller.navigate('/nginx/404'); | ||||||
| 	 */ |                 App.UI.showAppContent(new View()); | ||||||
| 	showNginxAccess: function () { |             }); | ||||||
| 		if (Cache.User.isAdmin() || Cache.User.canView('access_lists')) { |         } | ||||||
| 			const controller = this; |     }, | ||||||
| 			require(['./main', './nginx/access/main'], (App, View) => { |  | ||||||
| 				controller.navigate('/nginx/access'); |  | ||||||
| 				App.UI.showAppContent(new View()); |  | ||||||
| 			}); |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * Nginx Access List Form |      * Dead Host Form | ||||||
| 	 * |      * | ||||||
| 	 * @param [model] |      * @param [model] | ||||||
| 	 */ |      */ | ||||||
| 	showNginxAccessListForm: function (model) { |     showNginxDeadForm: function (model) { | ||||||
| 		if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) { |         if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) { | ||||||
| 			require(['./main', './nginx/access/form'], function (App, View) { |             require(['./main', './nginx/dead/form'], function (App, View) { | ||||||
| 				App.UI.showModalDialog(new View({model: model})); |                 App.UI.showModalDialog(new View({model: model})); | ||||||
| 			}); |             }); | ||||||
| 		} |         } | ||||||
| 	}, |     }, | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * Access List Delete Confirm |      * Dead Host Delete Confirm | ||||||
| 	 * |      * | ||||||
| 	 * @param model |      * @param model | ||||||
| 	 */ |      */ | ||||||
| 	showNginxAccessListDeleteConfirm: function (model) { |     showNginxDeadDeleteConfirm: function (model) { | ||||||
| 		if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) { |         if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) { | ||||||
| 			require(['./main', './nginx/access/delete'], function (App, View) { |             require(['./main', './nginx/dead/delete'], function (App, View) { | ||||||
| 				App.UI.showModalDialog(new View({model: model})); |                 App.UI.showModalDialog(new View({model: model})); | ||||||
| 			}); |             }); | ||||||
| 		} |         } | ||||||
| 	}, |     }, | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * Nginx Certificates |      * Help Dialog | ||||||
| 	 */ |      * | ||||||
| 	showNginxCertificates: function () { |      * @param {String}  title | ||||||
| 		if (Cache.User.isAdmin() || Cache.User.canView('certificates')) { |      * @param {String}  content | ||||||
| 			const controller = this; |      */ | ||||||
| 			require(['./main', './nginx/certificates/main'], (App, View) => { |     showHelp: function (title, content) { | ||||||
| 				controller.navigate('/nginx/certificates'); |         require(['./main', './help/main'], function (App, View) { | ||||||
| 				App.UI.showAppContent(new View()); |             App.UI.showModalDialog(new View({title: title, content: content})); | ||||||
| 			}); |         }); | ||||||
| 		} |     }, | ||||||
| 	}, |  | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * Nginx Certificate Form |      * Nginx Access | ||||||
| 	 * |      */ | ||||||
| 	 * @param [model] |     showNginxAccess: function () { | ||||||
| 	 */ |         if (Cache.User.isAdmin() || Cache.User.canView('access_lists')) { | ||||||
| 	showNginxCertificateForm: function (model) { |             let controller = this; | ||||||
| 		if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { |  | ||||||
| 			require(['./main', './nginx/certificates/form'], function (App, View) { |  | ||||||
| 				App.UI.showModalDialog(new View({model: model})); |  | ||||||
| 			}); |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
|  |  | ||||||
| 	/** |             require(['./main', './nginx/access/main'], (App, View) => { | ||||||
| 	 * Certificate Renew |                 controller.navigate('/nginx/access'); | ||||||
| 	 * |                 App.UI.showAppContent(new View()); | ||||||
| 	 * @param model |             }); | ||||||
| 	 */ |         } | ||||||
| 	showNginxCertificateRenew: function (model) { |     }, | ||||||
| 		if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { |  | ||||||
| 			require(['./main', './nginx/certificates/renew'], function (App, View) { |  | ||||||
| 				App.UI.showModalDialog(new View({model: model})); |  | ||||||
| 			}); |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * Certificate Delete Confirm |      * Nginx Access List Form | ||||||
| 	 * |      * | ||||||
| 	 * @param model |      * @param [model] | ||||||
| 	 */ |      */ | ||||||
| 	showNginxCertificateDeleteConfirm: function (model) { |     showNginxAccessListForm: function (model) { | ||||||
| 		if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { |         if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) { | ||||||
| 			require(['./main', './nginx/certificates/delete'], function (App, View) { |             require(['./main', './nginx/access/form'], function (App, View) { | ||||||
| 				App.UI.showModalDialog(new View({model: model})); |                 App.UI.showModalDialog(new View({model: model})); | ||||||
| 			}); |             }); | ||||||
| 		} |         } | ||||||
| 	}, |     }, | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * Certificate Test Reachability |      * Access List Delete Confirm | ||||||
| 	 * |      * | ||||||
| 	 * @param model |      * @param model | ||||||
| 	 */ |      */ | ||||||
| 	showNginxCertificateTestReachability: function (model) { |     showNginxAccessListDeleteConfirm: function (model) { | ||||||
| 	  if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { |         if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) { | ||||||
| 		require(['./main', './nginx/certificates/test'], function (App, View) { |             require(['./main', './nginx/access/delete'], function (App, View) { | ||||||
| 		  App.UI.showModalDialog(new View({model: model})); |                 App.UI.showModalDialog(new View({model: model})); | ||||||
| 		}); |             }); | ||||||
| 	  } |         } | ||||||
| 	}, |     }, | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * Audit Log |      * Nginx Certificates | ||||||
| 	 */ |      */ | ||||||
| 	showAuditLog: function () { |     showNginxCertificates: function () { | ||||||
| 		const controller = this; |         if (Cache.User.isAdmin() || Cache.User.canView('certificates')) { | ||||||
| 		if (Cache.User.isAdmin()) { |             let controller = this; | ||||||
| 			require(['./main', './audit-log/main'], (App, View) => { |  | ||||||
| 				controller.navigate('/audit-log'); |  | ||||||
| 				App.UI.showAppContent(new View()); |  | ||||||
| 			}); |  | ||||||
| 		} else { |  | ||||||
| 			this.showDashboard(); |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
|  |  | ||||||
| 	/** |             require(['./main', './nginx/certificates/main'], (App, View) => { | ||||||
| 	 * Audit Log Metadata |                 controller.navigate('/nginx/certificates'); | ||||||
| 	 * |                 App.UI.showAppContent(new View()); | ||||||
| 	 * @param model |             }); | ||||||
| 	 */ |         } | ||||||
| 	showAuditMeta: function (model) { |     }, | ||||||
| 		if (Cache.User.isAdmin()) { |  | ||||||
| 			require(['./main', './audit-log/meta'], function (App, View) { |  | ||||||
| 				App.UI.showModalDialog(new View({model: model})); |  | ||||||
| 			}); |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * Settings |      * Nginx Certificate Form | ||||||
| 	 */ |      * | ||||||
| 	showSettings: function () { |      * @param [model] | ||||||
| 		const controller = this; |      */ | ||||||
| 		if (Cache.User.isAdmin()) { |     showNginxCertificateForm: function (model) { | ||||||
| 			require(['./main', './settings/main'], (App, View) => { |         if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { | ||||||
| 				controller.navigate('/settings'); |             require(['./main', './nginx/certificates/form'], function (App, View) { | ||||||
| 				App.UI.showAppContent(new View()); |                 App.UI.showModalDialog(new View({model: model})); | ||||||
| 			}); |             }); | ||||||
| 		} else { |         } | ||||||
| 			this.showDashboard(); |     }, | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * Settings Item Form |      * Certificate Renew | ||||||
| 	 * |      * | ||||||
| 	 * @param model |      * @param model | ||||||
| 	 */ |      */ | ||||||
| 	showSettingForm: function (model) { |     showNginxCertificateRenew: function (model) { | ||||||
| 		if (Cache.User.isAdmin()) { |         if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { | ||||||
| 			if (model.get('id') === 'default-site') { |             require(['./main', './nginx/certificates/renew'], function (App, View) { | ||||||
| 				require(['./main', './settings/default-site/main'], function (App, View) { |                 App.UI.showModalDialog(new View({model: model})); | ||||||
| 					App.UI.showModalDialog(new View({model: model})); |             }); | ||||||
| 				}); |         } | ||||||
| 			} |     }, | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
|  |  | ||||||
| 	/** |     /** | ||||||
| 	 * Logout |      * Certificate Delete Confirm | ||||||
| 	 */ |      * | ||||||
| 	logout: function () { |      * @param model | ||||||
| 		Tokens.dropTopToken(); |      */ | ||||||
| 		this.showLogin(); |     showNginxCertificateDeleteConfirm: function (model) { | ||||||
| 	} |         if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { | ||||||
|  |             require(['./main', './nginx/certificates/delete'], function (App, View) { | ||||||
|  |                 App.UI.showModalDialog(new View({model: model})); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Certificate Test Reachability | ||||||
|  |      * | ||||||
|  |      * @param model | ||||||
|  |      */ | ||||||
|  |     showNginxCertificateTestReachability: function (model) { | ||||||
|  |       if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { | ||||||
|  |         require(['./main', './nginx/certificates/test'], function (App, View) { | ||||||
|  |           App.UI.showModalDialog(new View({model: model})); | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Audit Log | ||||||
|  |      */ | ||||||
|  |     showAuditLog: function () { | ||||||
|  |         let controller = this; | ||||||
|  |         if (Cache.User.isAdmin()) { | ||||||
|  |             require(['./main', './audit-log/main'], (App, View) => { | ||||||
|  |                 controller.navigate('/audit-log'); | ||||||
|  |                 App.UI.showAppContent(new View()); | ||||||
|  |             }); | ||||||
|  |         } else { | ||||||
|  |             this.showDashboard(); | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Audit Log Metadata | ||||||
|  |      * | ||||||
|  |      * @param model | ||||||
|  |      */ | ||||||
|  |     showAuditMeta: function (model) { | ||||||
|  |         if (Cache.User.isAdmin()) { | ||||||
|  |             require(['./main', './audit-log/meta'], function (App, View) { | ||||||
|  |                 App.UI.showModalDialog(new View({model: model})); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Settings | ||||||
|  |      */ | ||||||
|  |     showSettings: function () { | ||||||
|  |         let controller = this; | ||||||
|  |         if (Cache.User.isAdmin()) { | ||||||
|  |             require(['./main', './settings/main'], (App, View) => { | ||||||
|  |                 controller.navigate('/settings'); | ||||||
|  |                 App.UI.showAppContent(new View()); | ||||||
|  |             }); | ||||||
|  |         } else { | ||||||
|  |             this.showDashboard(); | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Settings Item Form | ||||||
|  |      * | ||||||
|  |      * @param model | ||||||
|  |      */ | ||||||
|  |     showSettingForm: function (model) { | ||||||
|  |         if (Cache.User.isAdmin()) { | ||||||
|  |             if (model.get('id') === 'default-site') { | ||||||
|  |                 require(['./main', './settings/default-site/main'], function (App, View) { | ||||||
|  |                     App.UI.showModalDialog(new View({model: model})); | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Logout | ||||||
|  |      */ | ||||||
|  |     logout: function () { | ||||||
|  |         Tokens.dropTopToken(); | ||||||
|  |         this.showLogin(); | ||||||
|  |     } | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -6,85 +6,87 @@ const Helpers    = require('../../lib/helpers'); | |||||||
| const template   = require('./main.ejs'); | const template   = require('./main.ejs'); | ||||||
|  |  | ||||||
| module.exports = Mn.View.extend({ | module.exports = Mn.View.extend({ | ||||||
| 	template: template, |     template: template, | ||||||
| 	id:       'dashboard', |     id:       'dashboard', | ||||||
| 	columns:  0, |     columns:  0, | ||||||
|  |  | ||||||
| 	stats: {}, |     stats: {}, | ||||||
|  |  | ||||||
| 	ui: { |     ui: { | ||||||
| 		links: 'a' |         links: 'a' | ||||||
| 	}, |     }, | ||||||
|  |  | ||||||
| 	events: { |     events: { | ||||||
| 		'click @ui.links': function (e) { |         'click @ui.links': function (e) { | ||||||
| 			e.preventDefault(); |             e.preventDefault(); | ||||||
| 			Controller.navigate($(e.currentTarget).attr('href'), true); |             Controller.navigate($(e.currentTarget).attr('href'), true); | ||||||
| 		} |         } | ||||||
| 	}, |     }, | ||||||
|  |  | ||||||
| 	templateContext: function () { |     templateContext: function () { | ||||||
| 		const view = this; |         let view = this; | ||||||
|  |  | ||||||
| 		return { |         return { | ||||||
| 			getUserName: function () { |             getUserName: function () { | ||||||
| 				return Cache.User.get('nickname') || Cache.User.get('name'); |                 return Cache.User.get('nickname') || Cache.User.get('name'); | ||||||
| 			}, |             }, | ||||||
|  |  | ||||||
| 			getHostStat: function (type) { |             getHostStat: function (type) { | ||||||
| 				if (view.stats && typeof view.stats.hosts !== 'undefined' && typeof view.stats.hosts[type] !== 'undefined') { |                 if (view.stats && typeof view.stats.hosts !== 'undefined' && typeof view.stats.hosts[type] !== 'undefined') { | ||||||
| 					return Helpers.niceNumber(view.stats.hosts[type]); |                     return Helpers.niceNumber(view.stats.hosts[type]); | ||||||
| 				} |                 } | ||||||
|  |  | ||||||
| 				return '-'; |                 return '-'; | ||||||
| 			}, |             }, | ||||||
|  |  | ||||||
| 			canShow: function (perm) { |             canShow: function (perm) { | ||||||
| 				return Cache.User.isAdmin() || Cache.User.canView(perm); |                 return Cache.User.isAdmin() || Cache.User.canView(perm); | ||||||
| 			}, |             }, | ||||||
|  |  | ||||||
| 			columns: view.columns |             columns: view.columns | ||||||
| 		}; |         }; | ||||||
| 	}, |     }, | ||||||
|  |  | ||||||
| 	onRender: function () { |     onRender: function () { | ||||||
| 		const view = this; |         let view = this; | ||||||
| 		if (typeof view.stats.hosts === 'undefined') { |  | ||||||
| 			Api.Reports.getHostStats() |  | ||||||
| 				.then(response => { |  | ||||||
| 					if (!view.isDestroyed()) { |  | ||||||
| 						view.stats.hosts = response; |  | ||||||
| 						view.render(); |  | ||||||
| 					} |  | ||||||
| 				}) |  | ||||||
| 				.catch(err => { |  | ||||||
| 					console.log(err); |  | ||||||
| 				}); |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
|  |  | ||||||
| 	/** |         if (typeof view.stats.hosts === 'undefined') { | ||||||
| 	 * @param {Object}  [model] |             Api.Reports.getHostStats() | ||||||
| 	 */ |                 .then(response => { | ||||||
| 	preRender: function (model) { |                     if (!view.isDestroyed()) { | ||||||
| 		this.columns = 0; |                         view.stats.hosts = response; | ||||||
|  |                         view.render(); | ||||||
|  |                     } | ||||||
|  |                 }) | ||||||
|  |                 .catch(err => { | ||||||
|  |                     console.log(err); | ||||||
|  |                 }); | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |  | ||||||
| 		// calculate the available columns based on permissions for the objects |     /** | ||||||
| 		// and store as a variable |      * @param {Object}  [model] | ||||||
| 		const perms = ['proxy_hosts', 'redirection_hosts', 'streams', 'dead_hosts']; |      */ | ||||||
|  |     preRender: function (model) { | ||||||
|  |         this.columns = 0; | ||||||
|  |  | ||||||
| 		perms.map(perm => { |         // calculate the available columns based on permissions for the objects | ||||||
| 			this.columns += Cache.User.isAdmin() || Cache.User.canView(perm) ? 1 : 0; |         // and store as a variable | ||||||
| 		}); |         //let view = this; | ||||||
|  |         let perms = ['proxy_hosts', 'redirection_hosts', 'streams', 'dead_hosts']; | ||||||
|  |  | ||||||
| 		// Prevent double rendering on initial calls |         perms.map(perm => { | ||||||
| 		if (typeof model !== 'undefined') { |             this.columns += Cache.User.isAdmin() || Cache.User.canView(perm) ? 1 : 0; | ||||||
| 			this.render(); |         }); | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
|  |  | ||||||
| 	initialize: function () { |         // Prevent double rendering on initial calls | ||||||
| 		this.preRender(); |         if (typeof model !== 'undefined') { | ||||||
| 		this.listenTo(Cache.User, 'change', this.preRender); |             this.render(); | ||||||
| 	} |         } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     initialize: function () { | ||||||
|  |         this.preRender(); | ||||||
|  |         this.listenTo(Cache.User, 'change', this.preRender); | ||||||
|  |     } | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <td class="text-center"> | <td class="text-center"> | ||||||
|     <div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>"> |     <div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>"> | ||||||
|         <span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span> |         <span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span> | ||||||
|     </div> |     </div> | ||||||
| </td> | </td> | ||||||
| <td> | <td> | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <td class="text-center"> | <td class="text-center"> | ||||||
|     <div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>"> |     <div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>"> | ||||||
|         <span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span> |         <span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span> | ||||||
|     </div> |     </div> | ||||||
| </td> | </td> | ||||||
| <td> | <td> | ||||||
| @@ -33,13 +33,6 @@ | |||||||
| <td class="<%- isExpired() ? 'text-danger' : '' %>"> | <td class="<%- isExpired() ? 'text-danger' : '' %>"> | ||||||
|     <%- formatDbDate(expires_on, 'Do MMMM YYYY, h:mm a') %> |     <%- formatDbDate(expires_on, 'Do MMMM YYYY, h:mm a') %> | ||||||
| </td> | </td> | ||||||
| <td> |  | ||||||
|     <% if (active_domain_names().length > 0) { %> |  | ||||||
|         <span class="status-icon bg-success"></span> <%- i18n('certificates', 'in-use') %> |  | ||||||
|     <% } else { %> |  | ||||||
|         <span class="status-icon bg-danger"></span> <%- i18n('certificates', 'inactive') %> |  | ||||||
|     <% } %> |  | ||||||
| </td> |  | ||||||
| <% if (canManage) { %> | <% if (canManage) { %> | ||||||
| <td class="text-right"> | <td class="text-right"> | ||||||
|     <div class="item-action dropdown"> |     <div class="item-action dropdown"> | ||||||
| @@ -55,13 +48,6 @@ | |||||||
|                 <div class="dropdown-divider"></div> |                 <div class="dropdown-divider"></div> | ||||||
|             <% } %> |             <% } %> | ||||||
|             <a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a> |             <a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a> | ||||||
|             <% if (active_domain_names().length > 0) { %> |  | ||||||
|                 <div class="dropdown-divider"></div> |  | ||||||
|                 <span class="dropdown-header"><%- i18n('certificates', 'active-domain_names') %></span> |  | ||||||
|                 <% active_domain_names().forEach(function(host) { %> |  | ||||||
|                     <a href="https://<%- host %>" class="dropdown-item" target="_blank"><%- host %></a> |  | ||||||
|                 <% }); %> |  | ||||||
|             <% } %> |  | ||||||
|         </div> |         </div> | ||||||
|     </div> |     </div> | ||||||
| </td> | </td> | ||||||
|   | |||||||
| @@ -44,24 +44,14 @@ module.exports = Mn.View.extend({ | |||||||
|         }, |         }, | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     templateContext: function () { |     templateContext: { | ||||||
|         return { |         canManage: App.Cache.User.canManage('certificates'), | ||||||
|             canManage: App.Cache.User.canManage('certificates'), |         isExpired: function () { | ||||||
|             isExpired: function () { |             return moment(this.expires_on).isBefore(moment()); | ||||||
|                 return moment(this.expires_on).isBefore(moment()); |         }, | ||||||
|             }, |         dns_providers: dns_providers | ||||||
|             dns_providers: dns_providers, |  | ||||||
|             active_domain_names: function () { |  | ||||||
|                 const { proxy_hosts = [], redirect_hosts = [], dead_hosts = [] } = this; |  | ||||||
|                 return [...proxy_hosts, ...redirect_hosts, ...dead_hosts].reduce((acc, host) => { |  | ||||||
|                     acc.push(...(host.domain_names || [])); |  | ||||||
|                     return acc; |  | ||||||
|                 }, []); |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|  |  | ||||||
|     initialize: function () { |     initialize: function () { | ||||||
|         this.listenTo(this.model, 'change', this.render); |         this.listenTo(this.model, 'change', this.render); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -3,7 +3,6 @@ | |||||||
|     <th><%- i18n('str', 'name') %></th> |     <th><%- i18n('str', 'name') %></th> | ||||||
|     <th><%- i18n('all-hosts', 'cert-provider') %></th> |     <th><%- i18n('all-hosts', 'cert-provider') %></th> | ||||||
|     <th><%- i18n('str', 'expires') %></th> |     <th><%- i18n('str', 'expires') %></th> | ||||||
|     <th><%- i18n('str', 'status') %></th> |  | ||||||
|     <% if (canManage) { %> |     <% if (canManage) { %> | ||||||
|     <th> </th> |     <th> </th> | ||||||
|     <% } %> |     <% } %> | ||||||
|   | |||||||
| @@ -74,7 +74,7 @@ module.exports = Mn.View.extend({ | |||||||
|             e.preventDefault(); |             e.preventDefault(); | ||||||
|             let query = this.ui.query.val(); |             let query = this.ui.query.val(); | ||||||
|  |  | ||||||
|             this.fetch(['owner','proxy_hosts', 'dead_hosts', 'redirection_hosts'], query) |             this.fetch(['owner'], query) | ||||||
|                 .then(response => this.showData(response)) |                 .then(response => this.showData(response)) | ||||||
|                 .catch(err => { |                 .catch(err => { | ||||||
|                     this.showError(err); |                     this.showError(err); | ||||||
| @@ -89,7 +89,7 @@ module.exports = Mn.View.extend({ | |||||||
|     onRender: function () { |     onRender: function () { | ||||||
|         let view = this; |         let view = this; | ||||||
|  |  | ||||||
|         view.fetch(['owner','proxy_hosts', 'dead_hosts', 'redirection_hosts']) |         view.fetch(['owner']) | ||||||
|             .then(response => { |             .then(response => { | ||||||
|                 if (!view.isDestroyed()) { |                 if (!view.isDestroyed()) { | ||||||
|                     if (response && response.length) { |                     if (response && response.length) { | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <td class="text-center"> | <td class="text-center"> | ||||||
|     <div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>"> |     <div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>"> | ||||||
|         <span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span> |         <span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span> | ||||||
|     </div> |     </div> | ||||||
| </td> | </td> | ||||||
| <td> | <td> | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <td class="text-center"> | <td class="text-center"> | ||||||
|     <div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>"> |     <div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>"> | ||||||
|         <span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span> |         <span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span> | ||||||
|     </div> |     </div> | ||||||
| </td> | </td> | ||||||
| <td> | <td> | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <td class="text-center"> | <td class="text-center"> | ||||||
|     <div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>"> |     <div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>"> | ||||||
|         <span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span> |         <span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span> | ||||||
|     </div> |     </div> | ||||||
| </td> | </td> | ||||||
| <td> | <td> | ||||||
|   | |||||||
| @@ -3,187 +3,48 @@ | |||||||
|         <h5 class="modal-title"><%- i18n('streams', 'form-title', {id: id}) %></h5> |         <h5 class="modal-title"><%- i18n('streams', 'form-title', {id: id}) %></h5> | ||||||
|         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> |         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> | ||||||
|     </div> |     </div> | ||||||
|     <div class="modal-body has-tabs"> |     <div class="modal-body"> | ||||||
|         <div class="alert alert-danger mb-0 rounded-0" id="le-error-info" role="alert"></div> |  | ||||||
|         <form> |         <form> | ||||||
|             <ul class="nav nav-tabs" role="tablist"> |             <div class="row"> | ||||||
|                 <li role="presentation" class="nav-item"><a href="#details" aria-controls="tab1" role="tab" data-toggle="tab" class="nav-link active"><i class="fe fe-zap"></i> <%- i18n('all-hosts', 'details') %></a></li> |                 <div class="col-sm-12 col-md-12"> | ||||||
|                 <li role="presentation" class="nav-item"><a href="#ssl-options" aria-controls="tab2" role="tab" data-toggle="tab" class="nav-link"><i class="fe fe-shield"></i> <%- i18n('str', 'ssl') %></a></li> |                     <div class="form-group"> | ||||||
|             </ul> |                         <label class="form-label"><%- i18n('streams', 'incoming-port') %> <span class="form-required">*</span></label> | ||||||
|             <div class="tab-content"> |                         <input name="incoming_port" type="number" class="form-control text-monospace" placeholder="eg: 8080" min="1" max="65535" value="<%- incoming_port %>" required> | ||||||
|                 <!-- Details --> |  | ||||||
|                 <div role="tabpanel" class="tab-pane active" id="details"> |  | ||||||
|                     <div class="row"> |  | ||||||
|                         <div class="col-sm-12 col-md-12"> |  | ||||||
|                             <div class="form-group"> |  | ||||||
|                                 <label class="form-label"><%- i18n('streams', 'incoming-port') %> <span class="form-required">*</span></label> |  | ||||||
|                                 <input name="incoming_port" type="number" class="form-control text-monospace" placeholder="eg: 8080" min="1" max="65535" value="<%- incoming_port %>" required> |  | ||||||
|                             </div> |  | ||||||
|                         </div> |  | ||||||
|                         <div class="col-sm-8 col-md-8"> |  | ||||||
|                             <div class="form-group"> |  | ||||||
|                                 <label class="form-label"><%- i18n('streams', 'forwarding-host') %><span class="form-required">*</span></label> |  | ||||||
|                                 <input type="text" name="forwarding_host" class="form-control text-monospace" placeholder="example.com or 10.0.0.1 or 2001:db8:3333:4444:5555:6666:7777:8888" value="<%- forwarding_host %>" autocomplete="off" maxlength="255" required> |  | ||||||
|                             </div> |  | ||||||
|                         </div> |  | ||||||
|                         <div class="col-sm-4 col-md-4"> |  | ||||||
|                             <div class="form-group"> |  | ||||||
|                                 <label class="form-label"><%- i18n('streams', 'forwarding-port') %> <span class="form-required">*</span></label> |  | ||||||
|                                 <input name="forwarding_port" type="number" class="form-control text-monospace" placeholder="eg: 80" min="1" max="65535" value="<%- forwarding_port %>" required> |  | ||||||
|                             </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="tcp_forwarding" value="1"<%- tcp_forwarding ? ' checked' : '' %>> |  | ||||||
|                                     <span class="custom-switch-indicator"></span> |  | ||||||
|                                     <span class="custom-switch-description"><%- i18n('streams', 'tcp-forwarding') %></span> |  | ||||||
|                                 </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="udp_forwarding" value="1"<%- udp_forwarding ? ' checked' : '' %>> |  | ||||||
|                                     <span class="custom-switch-indicator"></span> |  | ||||||
|                                     <span class="custom-switch-description"><%- i18n('streams', 'udp-forwarding') %></span> |  | ||||||
|                                 </label> |  | ||||||
|                             </div> |  | ||||||
|                         </div> |  | ||||||
|                         <div class="col-sm-12 col-md-12"> |  | ||||||
|                             <div class="forward-type-error invalid-feedback"><%- i18n('streams', 'forward-type-error') %></div> |  | ||||||
|                         </div> |  | ||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|  |                 <div class="col-sm-8 col-md-8"> | ||||||
|                 <!-- SSL --> |                     <div class="form-group"> | ||||||
|                 <div role="tabpanel" class="tab-pane" id="ssl-options"> |                         <label class="form-label"><%- i18n('streams', 'forwarding-host') %><span class="form-required">*</span></label> | ||||||
|                     <div class="row"> |                         <input type="text" name="forwarding_host" class="form-control text-monospace" placeholder="example.com or 10.0.0.1 or 2001:db8:3333:4444:5555:6666:7777:8888" value="<%- forwarding_host %>" autocomplete="off" maxlength="255" required> | ||||||
|                         <div class="col-sm-12 col-md-12"> |  | ||||||
|                             <div class="form-group"> |  | ||||||
|                                 <label class="form-label"><%- i18n('streams', 'ssl-certificate') %></label> |  | ||||||
|                                 <select name="certificate_id" class="form-control custom-select" placeholder="<%- i18n('all-hosts', 'none') %>"> |  | ||||||
|                                     <option selected value="0" data-data="{"id":0}" <%- certificate_id ? '' : 'selected' %>><%- i18n('all-hosts', 'none') %></option> |  | ||||||
|                                     <option selected value="new" data-data="{"id":"new"}"><%- i18n('all-hosts', 'new-cert') %></option> |  | ||||||
|                                 </select> |  | ||||||
|                             </div> |  | ||||||
|                         </div> |  | ||||||
|  |  | ||||||
|                         <!-- DNS challenge --> |  | ||||||
|                         <div class="col-sm-12 col-md-12 letsencrypt"> |  | ||||||
|                             <div class="form-group"> |  | ||||||
|                                 <label class="form-label"><%- i18n('all-hosts', 'domain-names') %> <span class="form-required">*</span></label> |  | ||||||
|                                 <input type="text" name="domain_names" class="form-control" id="input-domains" value="<%- domain_names.join(',') %>"> |  | ||||||
|                             </div> |  | ||||||
|                             <div class="form-group"> |  | ||||||
|                                 <label class="custom-switch"> |  | ||||||
|                                     <input |  | ||||||
|                                             type="checkbox" |  | ||||||
|                                             class="custom-switch-input" |  | ||||||
|                                             name="meta[dns_challenge]" |  | ||||||
|                                             value="1" |  | ||||||
|                                             checked |  | ||||||
|                                             disabled |  | ||||||
|                                     > |  | ||||||
|                                     <span class="custom-switch-indicator"></span> |  | ||||||
|                                     <span class="custom-switch-description"><%= i18n('ssl', 'dns-challenge') %></span> |  | ||||||
|                                 </label> |  | ||||||
|                             </div> |  | ||||||
|                         </div> |  | ||||||
|                         <div class="col-sm-12 col-md-12 letsencrypt"> |  | ||||||
|                             <fieldset class="form-fieldset dns-challenge"> |  | ||||||
|                                 <div class="text-red mb-4"><i class="fe fe-alert-triangle"></i> <%= i18n('ssl', 'certbot-warning') %></div> |  | ||||||
|  |  | ||||||
|                                 <!-- Certbot DNS plugin selection --> |  | ||||||
|                                 <div class="row"> |  | ||||||
|                                     <div class="col-sm-12 col-md-12"> |  | ||||||
|                                         <div class="form-group"> |  | ||||||
|                                             <label class="form-label"><%- i18n('ssl', 'dns-provider') %> <span class="form-required">*</span></label> |  | ||||||
|                                             <select |  | ||||||
|                                                     name="meta[dns_provider]" |  | ||||||
|                                                     id="dns_provider" |  | ||||||
|                                                     class="form-control custom-select" |  | ||||||
|                                             > |  | ||||||
|                                                 <option |  | ||||||
|                                                         value="" |  | ||||||
|                                                         disabled |  | ||||||
|                                                         hidden |  | ||||||
|                                                         <%- getDnsProvider() === null ? 'selected' : '' %> |  | ||||||
|                                                 >Please Choose...</option> |  | ||||||
|                                                 <% _.each(dns_plugins, function(plugin_info, plugin_name){ %> |  | ||||||
|                                                     <option |  | ||||||
|                                                             value="<%- plugin_name %>" |  | ||||||
|                                                             <%- getDnsProvider() === plugin_name ? 'selected' : '' %> |  | ||||||
|                                                     ><%- plugin_info.name %></option> |  | ||||||
|                                                 <% }); %> |  | ||||||
|                                             </select> |  | ||||||
|                                         </div> |  | ||||||
|                                     </div> |  | ||||||
|                                 </div> |  | ||||||
|  |  | ||||||
|                                 <!-- Certbot credentials file content --> |  | ||||||
|                                 <div class="row credentials-file-content"> |  | ||||||
|                                     <div class="col-sm-12 col-md-12"> |  | ||||||
|                                         <div class="form-group"> |  | ||||||
|                                             <label class="form-label"><%- i18n('ssl', 'credentials-file-content') %> <span class="form-required">*</span></label> |  | ||||||
|                                             <textarea |  | ||||||
|                                                     name="meta[dns_provider_credentials]" |  | ||||||
|                                                     class="form-control text-monospace" |  | ||||||
|                                                     id="dns_provider_credentials" |  | ||||||
|                                             ><%- getDnsProviderCredentials() %></textarea> |  | ||||||
|                                             <div class="text-secondary small"> |  | ||||||
|                                                 <i class="fe fe-info"></i> |  | ||||||
|                                                 <%= i18n('ssl', 'credentials-file-content-info') %> |  | ||||||
|                                             </div> |  | ||||||
|                                             <div class="text-red small"> |  | ||||||
|                                                 <i class="fe fe-alert-triangle"></i> |  | ||||||
|                                                 <%= i18n('ssl', 'stored-as-plaintext-info') %> |  | ||||||
|                                             </div> |  | ||||||
|                                         </div> |  | ||||||
|                                     </div> |  | ||||||
|                                 </div> |  | ||||||
|  |  | ||||||
|                                 <!-- DNS propagation delay --> |  | ||||||
|                                 <div class="row"> |  | ||||||
|                                     <div class="col-sm-12 col-md-12"> |  | ||||||
|                                         <div class="form-group mb-0"> |  | ||||||
|                                             <label class="form-label"><%- i18n('ssl', 'propagation-seconds') %></label> |  | ||||||
|                                             <input |  | ||||||
|                                                     type="number" |  | ||||||
|                                                     min="0" |  | ||||||
|                                                     name="meta[propagation_seconds]" |  | ||||||
|                                                     class="form-control" |  | ||||||
|                                                     id="propagation_seconds" |  | ||||||
|                                                     value="<%- getPropagationSeconds() %>" |  | ||||||
|                                             > |  | ||||||
|                                             <div class="text-secondary small"> |  | ||||||
|                                                 <i class="fe fe-info"></i> |  | ||||||
|                                                 <%= i18n('ssl', 'propagation-seconds-info') %> |  | ||||||
|                                             </div> |  | ||||||
|                                         </div> |  | ||||||
|                                     </div> |  | ||||||
|                                 </div> |  | ||||||
|                             </fieldset> |  | ||||||
|                         </div> |  | ||||||
|  |  | ||||||
|                         <!-- Lets encrypt --> |  | ||||||
|                         <div class="col-sm-12 col-md-12 letsencrypt"> |  | ||||||
|                             <div class="form-group"> |  | ||||||
|                                 <label class="form-label"><%- i18n('ssl', 'letsencrypt-email') %> <span class="form-required">*</span></label> |  | ||||||
|                                 <input name="meta[letsencrypt_email]" type="email" class="form-control" placeholder="" value="<%- getLetsencryptEmail() %>" required disabled> |  | ||||||
|                             </div> |  | ||||||
|                         </div> |  | ||||||
|                         <div class="col-sm-12 col-md-12 letsencrypt"> |  | ||||||
|                             <div class="form-group"> |  | ||||||
|                                 <label class="custom-switch"> |  | ||||||
|                                     <input type="checkbox" class="custom-switch-input" name="meta[letsencrypt_agree]" value="1" required disabled> |  | ||||||
|                                     <span class="custom-switch-indicator"></span> |  | ||||||
|                                     <span class="custom-switch-description"><%= i18n('ssl', 'letsencrypt-agree', {url: 'https://letsencrypt.org/repository/'}) %> <span class="form-required">*</span></span> |  | ||||||
|                                 </label> |  | ||||||
|                             </div> |  | ||||||
|                         </div> |  | ||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|  |                 <div class="col-sm-4 col-md-4"> | ||||||
|  |                     <div class="form-group"> | ||||||
|  |                         <label class="form-label"><%- i18n('streams', 'forwarding-port') %> <span class="form-required">*</span></label> | ||||||
|  |                         <input name="forwarding_port" type="number" class="form-control text-monospace" placeholder="eg: 80" min="1" max="65535" value="<%- forwarding_port %>" required> | ||||||
|  |                     </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="tcp_forwarding" value="1"<%- tcp_forwarding ? ' checked' : '' %>> | ||||||
|  |                             <span class="custom-switch-indicator"></span> | ||||||
|  |                             <span class="custom-switch-description"><%- i18n('streams', 'tcp-forwarding') %></span> | ||||||
|  |                         </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="udp_forwarding" value="1"<%- udp_forwarding ? ' checked' : '' %>> | ||||||
|  |                             <span class="custom-switch-indicator"></span> | ||||||
|  |                             <span class="custom-switch-description"><%- i18n('streams', 'udp-forwarding') %></span> | ||||||
|  |                         </label> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="col-sm-12 col-md-12"> | ||||||
|  |                     <div class="forward-type-error invalid-feedback"><%- i18n('streams', 'forward-type-error') %></div> | ||||||
|  |                 </div> | ||||||
|             </div> |             </div> | ||||||
|         </form> |         </form> | ||||||
|     </div> |     </div> | ||||||
|   | |||||||
| @@ -1,38 +1,24 @@ | |||||||
| const Mn            = require('backbone.marionette'); | const Mn          = require('backbone.marionette'); | ||||||
| const App           = require('../../main'); | const App         = require('../../main'); | ||||||
| const StreamModel   = require('../../../models/stream'); | const StreamModel = require('../../../models/stream'); | ||||||
| const template      = require('./form.ejs'); | const template    = require('./form.ejs'); | ||||||
| const dns_providers = require('../../../../../global/certbot-dns-plugins'); |  | ||||||
|  |  | ||||||
| require('jquery-serializejson'); | require('jquery-serializejson'); | ||||||
| require('jquery-mask-plugin'); | require('jquery-mask-plugin'); | ||||||
| require('selectize'); | require('selectize'); | ||||||
| const Helpers = require("../../../lib/helpers"); |  | ||||||
| const certListItemTemplate = require("../certificates-list-item.ejs"); |  | ||||||
| const i18n = require("../../i18n"); |  | ||||||
|  |  | ||||||
| module.exports = Mn.View.extend({ | module.exports = Mn.View.extend({ | ||||||
|     template:  template, |     template:  template, | ||||||
|     className: 'modal-dialog', |     className: 'modal-dialog', | ||||||
|  |  | ||||||
|     ui: { |     ui: { | ||||||
|         form:                     'form', |         form:       'form', | ||||||
|         forwarding_host:          'input[name="forwarding_host"]', |         forwarding_host: 'input[name="forwarding_host"]', | ||||||
|         type_error:               '.forward-type-error', |         type_error: '.forward-type-error', | ||||||
|         buttons:                  '.modal-footer button', |         buttons:    '.modal-footer button', | ||||||
|         switches:                 '.custom-switch-input', |         switches:   '.custom-switch-input', | ||||||
|         cancel:                   'button.cancel', |         cancel:     'button.cancel', | ||||||
|         save:                     'button.save', |         save:       'button.save' | ||||||
|         le_error_info:            '#le-error-info', |  | ||||||
|         certificate_select:       'select[name="certificate_id"]', |  | ||||||
|         domain_names:             'input[name="domain_names"]', |  | ||||||
|         dns_challenge_switch:     'input[name="meta[dns_challenge]"]', |  | ||||||
|         dns_challenge_content:    '.dns-challenge', |  | ||||||
|         dns_provider:             'select[name="meta[dns_provider]"]', |  | ||||||
|         credentials_file_content: '.credentials-file-content', |  | ||||||
|         dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]', |  | ||||||
|         propagation_seconds:      'input[name="meta[propagation_seconds]"]', |  | ||||||
|         letsencrypt:              '.letsencrypt' |  | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     events: { |     events: { | ||||||
| @@ -62,35 +48,6 @@ module.exports = Mn.View.extend({ | |||||||
|             data.tcp_forwarding  = !!data.tcp_forwarding; |             data.tcp_forwarding  = !!data.tcp_forwarding; | ||||||
|             data.udp_forwarding  = !!data.udp_forwarding; |             data.udp_forwarding  = !!data.udp_forwarding; | ||||||
|  |  | ||||||
|             if (typeof data.meta === 'undefined') data.meta = {}; |  | ||||||
|             data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1; |  | ||||||
|             data.meta.dns_challenge = true; |  | ||||||
|  |  | ||||||
|             if (data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined; |  | ||||||
|  |  | ||||||
|             if (typeof data.domain_names === 'string' && data.domain_names) { |  | ||||||
|                 data.domain_names = data.domain_names.split(','); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // Check for any domain names containing wildcards, which are not allowed with letsencrypt |  | ||||||
|             if (data.certificate_id === 'new') { |  | ||||||
|                 let domain_err = false; |  | ||||||
|                 if (!data.meta.dns_challenge) { |  | ||||||
|                     data.domain_names.map(function (name) { |  | ||||||
|                         if (name.match(/\*/im)) { |  | ||||||
|                             domain_err = true; |  | ||||||
|                         } |  | ||||||
|                     }); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 if (domain_err) { |  | ||||||
|                     alert(i18n('ssl', 'no-wildcard-without-dns')); |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|             } else { |  | ||||||
|                 data.certificate_id = parseInt(data.certificate_id, 10); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             let method = App.Api.Nginx.Streams.create; |             let method = App.Api.Nginx.Streams.create; | ||||||
|             let is_new = true; |             let is_new = true; | ||||||
|  |  | ||||||
| @@ -113,108 +70,10 @@ module.exports = Mn.View.extend({ | |||||||
|                     }); |                     }); | ||||||
|                 }) |                 }) | ||||||
|                 .catch(err => { |                 .catch(err => { | ||||||
|                     let more_info = ''; |                     alert(err.message); | ||||||
|                     if (err.code === 500 && err.debug) { |  | ||||||
|                         try { |  | ||||||
|                             more_info = JSON.parse(err.debug).debug.stack.join("\n"); |  | ||||||
|                         } catch (e) { |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                     this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `<pre class="mt-3">${more_info}</pre>` : ''}`; |  | ||||||
|                     this.ui.le_error_info.show(); |  | ||||||
|                     this.ui.le_error_info[0].scrollIntoView(); |  | ||||||
|                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); |                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||||
|                     this.ui.save.removeClass('btn-loading'); |  | ||||||
|                 }); |                 }); | ||||||
|         }, |         } | ||||||
|  |  | ||||||
|         'change @ui.certificate_select': function () { |  | ||||||
|             let id = this.ui.certificate_select.val(); |  | ||||||
|             if (id === 'new') { |  | ||||||
|                 this.ui.letsencrypt.show().find('input').prop('disabled', false); |  | ||||||
|                 this.ui.domain_names.prop('required', 'required'); |  | ||||||
|  |  | ||||||
|                 this.ui.dns_challenge_switch |  | ||||||
|                     .prop('disabled', true) |  | ||||||
|                     .parents('.form-group') |  | ||||||
|                     .css('opacity', 0.5); |  | ||||||
|  |  | ||||||
|                 this.ui.dns_provider.prop('required', 'required'); |  | ||||||
|                 const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; |  | ||||||
|                 if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { |  | ||||||
|                     this.ui.dns_provider_credentials.prop('required', 'required'); |  | ||||||
|                 } |  | ||||||
|                 this.ui.dns_challenge_content.show(); |  | ||||||
|             } else { |  | ||||||
|                 this.ui.letsencrypt.hide().find('input').prop('disabled', true); |  | ||||||
|             } |  | ||||||
|         }, |  | ||||||
|  |  | ||||||
|         'change @ui.dns_provider': function () { |  | ||||||
|             const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; |  | ||||||
|             if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { |  | ||||||
|                 this.ui.dns_provider_credentials.prop('required', 'required'); |  | ||||||
|                 this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials; |  | ||||||
|                 this.ui.credentials_file_content.show(); |  | ||||||
|             } else { |  | ||||||
|                 this.ui.dns_provider_credentials.prop('required', false); |  | ||||||
|                 this.ui.credentials_file_content.hide(); |  | ||||||
|             } |  | ||||||
|         }, |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     templateContext: { |  | ||||||
|         getLetsencryptEmail: function () { |  | ||||||
|             return App.Cache.User.get('email'); |  | ||||||
|         }, |  | ||||||
|         getDnsProvider: function () { |  | ||||||
|             return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null; |  | ||||||
|         }, |  | ||||||
|         getDnsProviderCredentials: function () { |  | ||||||
|             return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : ''; |  | ||||||
|         }, |  | ||||||
|         getPropagationSeconds: function () { |  | ||||||
|             return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : ''; |  | ||||||
|         }, |  | ||||||
|         dns_plugins: dns_providers, |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     onRender: function () { |  | ||||||
|         let view = this; |  | ||||||
|  |  | ||||||
|         // Certificates |  | ||||||
|         this.ui.le_error_info.hide(); |  | ||||||
|         this.ui.dns_challenge_content.hide(); |  | ||||||
|         this.ui.credentials_file_content.hide(); |  | ||||||
|         this.ui.letsencrypt.hide(); |  | ||||||
|         this.ui.certificate_select.selectize({ |  | ||||||
|             valueField:       'id', |  | ||||||
|             labelField:       'nice_name', |  | ||||||
|             searchField:      ['nice_name', 'domain_names'], |  | ||||||
|             create:           false, |  | ||||||
|             preload:          true, |  | ||||||
|             allowEmptyOption: true, |  | ||||||
|             render:           { |  | ||||||
|                 option: function (item) { |  | ||||||
|                     item.i18n         = App.i18n; |  | ||||||
|                     item.formatDbDate = Helpers.formatDbDate; |  | ||||||
|                     return certListItemTemplate(item); |  | ||||||
|                 } |  | ||||||
|             }, |  | ||||||
|             load:             function (query, callback) { |  | ||||||
|                 App.Api.Nginx.Certificates.getAll() |  | ||||||
|                     .then(rows => { |  | ||||||
|                         callback(rows); |  | ||||||
|                     }) |  | ||||||
|                     .catch(err => { |  | ||||||
|                         console.error(err); |  | ||||||
|                         callback(); |  | ||||||
|                     }); |  | ||||||
|             }, |  | ||||||
|             onLoad:           function () { |  | ||||||
|                 view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id')); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     initialize: function (options) { |     initialize: function (options) { | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <td class="text-center"> | <td class="text-center"> | ||||||
|     <div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>"> |     <div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>"> | ||||||
|         <span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span> |         <span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span> | ||||||
|     </div> |     </div> | ||||||
| </td> | </td> | ||||||
| <td> | <td> | ||||||
| @@ -16,10 +16,7 @@ | |||||||
| </td> | </td> | ||||||
| <td> | <td> | ||||||
|     <div> |     <div> | ||||||
|         <% if (certificate) { %> |         <% if (tcp_forwarding) { %> | ||||||
|             <span class="tag"><%- i18n('streams', 'tcp+ssl') %></span> |  | ||||||
|         <% } |  | ||||||
|         else if (tcp_forwarding) { %> |  | ||||||
|             <span class="tag"><%- i18n('streams', 'tcp') %></span> |             <span class="tag"><%- i18n('streams', 'tcp') %></span> | ||||||
|         <% } |         <% } | ||||||
|         if (udp_forwarding) { %> |         if (udp_forwarding) { %> | ||||||
| @@ -27,9 +24,6 @@ | |||||||
|         <% } %> |         <% } %> | ||||||
|     </div> |     </div> | ||||||
| </td> | </td> | ||||||
| <td> |  | ||||||
|     <div><%- certificate && certificate_id ? i18n('ssl', certificate.provider) : i18n('all-hosts', 'none') %></div> |  | ||||||
| </td> |  | ||||||
| <td> | <td> | ||||||
|     <% |     <% | ||||||
|     var o = isOnline(); |     var o = isOnline(); | ||||||
|   | |||||||
| @@ -3,7 +3,6 @@ | |||||||
|     <th><%- i18n('streams', 'incoming-port') %></th> |     <th><%- i18n('streams', 'incoming-port') %></th> | ||||||
|     <th><%- i18n('str', 'destination') %></th> |     <th><%- i18n('str', 'destination') %></th> | ||||||
|     <th><%- i18n('streams', 'protocol') %></th> |     <th><%- i18n('streams', 'protocol') %></th> | ||||||
|     <th><%- i18n('str', 'ssl') %></th> |  | ||||||
|     <th><%- i18n('str', 'status') %></th> |     <th><%- i18n('str', 'status') %></th> | ||||||
|     <% if (canManage) { %> |     <% if (canManage) { %> | ||||||
|     <th> </th> |     <th> </th> | ||||||
|   | |||||||
| @@ -88,7 +88,7 @@ module.exports = Mn.View.extend({ | |||||||
|     onRender: function () { |     onRender: function () { | ||||||
|         let view = this; |         let view = this; | ||||||
|  |  | ||||||
|         view.fetch(['owner', 'certificate']) |         view.fetch(['owner']) | ||||||
|             .then(response => { |             .then(response => { | ||||||
|                 if (!view.isDestroyed()) { |                 if (!view.isDestroyed()) { | ||||||
|                     if (response && response.length) { |                     if (response && response.length) { | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| <div class="modal-content"> | <div class="modal-content"> | ||||||
|     <form> |     <div class="modal-header"> | ||||||
|         <div class="modal-header"> |         <h5 class="modal-title"><%- i18n('users', 'form-title', {id: id}) %></h5> | ||||||
|             <h5 class="modal-title"><%- i18n('users', 'form-title', {id: id}) %></h5> |         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> | ||||||
|             <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> |     </div> | ||||||
|         </div> |     <div class="modal-body"> | ||||||
|         <div class="modal-body"> |         <form> | ||||||
|             <div class="row"> |             <div class="row"> | ||||||
|                 <div class="col-sm-6 col-md-6"> |                 <div class="col-sm-6 col-md-6"> | ||||||
|                     <div class="form-group"> |                     <div class="form-group"> | ||||||
| @@ -49,10 +49,10 @@ | |||||||
|                 </div> |                 </div> | ||||||
|                 <% } %> |                 <% } %> | ||||||
|             </div> |             </div> | ||||||
|         </div> |         </form> | ||||||
|         <div class="modal-footer"> |     </div> | ||||||
|             <button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button> |     <div class="modal-footer"> | ||||||
|             <button type="submit" class="btn btn-teal save"><%- i18n('str', 'save') %></button> |         <button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button> | ||||||
|         </div> |         <button type="button" class="btn btn-teal save"><%- i18n('str', 'save') %></button> | ||||||
|     </form> |     </div> | ||||||
| </div> | </div> | ||||||
|   | |||||||
| @@ -19,7 +19,7 @@ module.exports = Mn.View.extend({ | |||||||
|  |  | ||||||
|     events: { |     events: { | ||||||
|  |  | ||||||
|         'submit @ui.form': function (e) { |         'click @ui.save': function (e) { | ||||||
|             e.preventDefault(); |             e.preventDefault(); | ||||||
|             this.ui.error.hide(); |             this.ui.error.hide(); | ||||||
|             let view = this; |             let view = this; | ||||||
|   | |||||||
| @@ -60,7 +60,7 @@ | |||||||
|     }, |     }, | ||||||
|     "footer": { |     "footer": { | ||||||
|       "fork-me": "Fork me on Github", |       "fork-me": "Fork me on Github", | ||||||
|       "copy": "© 2025 <a href=\"{url}\" target=\"_blank\">jc21.com</a>.", |       "copy": "© 2024 <a href=\"{url}\" target=\"_blank\">jc21.com</a>.", | ||||||
|       "theme": "Theme by <a href=\"{url}\" target=\"_blank\">Tabler</a>" |       "theme": "Theme by <a href=\"{url}\" target=\"_blank\">Tabler</a>" | ||||||
|     }, |     }, | ||||||
|     "dashboard": { |     "dashboard": { | ||||||
| @@ -179,9 +179,7 @@ | |||||||
|       "delete-confirm": "Are you sure you want to delete this Stream?", |       "delete-confirm": "Are you sure you want to delete this Stream?", | ||||||
|       "help-title": "What is a Stream?", |       "help-title": "What is a Stream?", | ||||||
|       "help-content": "A relatively new feature for Nginx, a Stream will serve to forward TCP/UDP traffic directly to another computer on the network.\nIf you're running game servers, FTP or SSH servers this can come in handy.", |       "help-content": "A relatively new feature for Nginx, a Stream will serve to forward TCP/UDP traffic directly to another computer on the network.\nIf you're running game servers, FTP or SSH servers this can come in handy.", | ||||||
|       "search": "Search Incoming Port…", |       "search": "Search Incoming Port…" | ||||||
|       "ssl-certificate": "SSL Certificate for TCP Forwarding", |  | ||||||
|       "tcp+ssl": "TCP+SSL" |  | ||||||
|     }, |     }, | ||||||
|     "certificates": { |     "certificates": { | ||||||
|       "title": "SSL Certificates", |       "title": "SSL Certificates", | ||||||
| @@ -208,10 +206,7 @@ | |||||||
|       "reachability-other": "There is a server found at this domain but it returned an unexpected status code {code}. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running.", |       "reachability-other": "There is a server found at this domain but it returned an unexpected status code {code}. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running.", | ||||||
|       "download": "Download", |       "download": "Download", | ||||||
|       "renew-title": "Renew Let's Encrypt Certificate", |       "renew-title": "Renew Let's Encrypt Certificate", | ||||||
|       "search": "Search Certificate…", |       "search": "Search Certificate…" | ||||||
|       "in-use"  : "In use", |  | ||||||
|       "inactive": "Inactive", |  | ||||||
|       "active-domain_names": "Active domain names" |  | ||||||
|     }, |     }, | ||||||
|     "access-lists": { |     "access-lists": { | ||||||
|       "title": "Access Lists", |       "title": "Access Lists", | ||||||
|   | |||||||
| @@ -15,11 +15,8 @@ const model = Backbone.Model.extend({ | |||||||
|             udp_forwarding:  false, |             udp_forwarding:  false, | ||||||
|             enabled:         true, |             enabled:         true, | ||||||
|             meta:            {}, |             meta:            {}, | ||||||
|             certificate_id:  0, |  | ||||||
|             domain_names:    [], |  | ||||||
|             // The following are expansions: |             // The following are expansions: | ||||||
|             owner:           null, |             owner:           null | ||||||
|             certificate:     null |  | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -26,8 +26,8 @@ | |||||||
|     "messageformat": "^2.3.0", |     "messageformat": "^2.3.0", | ||||||
|     "messageformat-loader": "^0.8.1", |     "messageformat-loader": "^0.8.1", | ||||||
|     "mini-css-extract-plugin": "^0.9.0", |     "mini-css-extract-plugin": "^0.9.0", | ||||||
|     "moment": "^2.30.1", |     "moment": "^2.29.4", | ||||||
|     "sass": "^1.92.1", |     "node-sass": "^9.0.0", | ||||||
|     "nodemon": "^2.0.2", |     "nodemon": "^2.0.2", | ||||||
|     "numeral": "^2.0.6", |     "numeral": "^2.0.6", | ||||||
|     "sass-loader": "^10.0.0", |     "sass-loader": "^10.0.0", | ||||||
|   | |||||||
| @@ -167,5 +167,4 @@ $pink: #f66d9b; | |||||||
|  |  | ||||||
| textarea.form-control.text-monospace { | textarea.form-control.text-monospace { | ||||||
|     font-size: 12px; |     font-size: 12px; | ||||||
|     font-family: monospace; |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										1497
									
								
								frontend/yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										1497
									
								
								frontend/yarn.lock
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -7,18 +7,18 @@ | |||||||
| 		"credentials": "dns_acmedns_api_url = http://acmedns-server/\ndns_acmedns_registration_file = /data/acme-registration.json", | 		"credentials": "dns_acmedns_api_url = http://acmedns-server/\ndns_acmedns_registration_file = /data/acme-registration.json", | ||||||
| 		"full_plugin_name": "dns-acmedns" | 		"full_plugin_name": "dns-acmedns" | ||||||
| 	}, | 	}, | ||||||
| 	"active24": { |     "active24":{ | ||||||
| 		"name": "Active24", | 		"name": "Active24", | ||||||
| 		"package_name": "certbot-dns-active24", | 		"package_name": "certbot-dns-active24", | ||||||
| 		"version": "~=2.0.0", | 		"version": "~=1.5.1", | ||||||
| 		"dependencies": "", | 		"dependencies": "", | ||||||
| 		"credentials": "dns_active24_api_key = <identifier>\ndns_active24_secret = <secret>", | 		"credentials": "dns_active24_token=\"TOKEN\"", | ||||||
| 		"full_plugin_name": "dns-active24" | 		"full_plugin_name": "dns-active24" | ||||||
| 	}, | 	}, | ||||||
| 	"aliyun": { | 	"aliyun": { | ||||||
| 		"name": "Aliyun", | 		"name": "Aliyun", | ||||||
| 		"package_name": "certbot-dns-aliyun", | 		"package_name": "certbot-dns-aliyun", | ||||||
| 		"version": "~=2.0.0", | 		"version": "~=0.38.1", | ||||||
| 		"dependencies": "", | 		"dependencies": "", | ||||||
| 		"credentials": "dns_aliyun_access_key = 12345678\ndns_aliyun_access_key_secret = 1234567890abcdef1234567890abcdef", | 		"credentials": "dns_aliyun_access_key = 12345678\ndns_aliyun_access_key_secret = 1234567890abcdef1234567890abcdef", | ||||||
| 		"full_plugin_name": "dns-aliyun" | 		"full_plugin_name": "dns-aliyun" | ||||||
| @@ -31,22 +31,6 @@ | |||||||
| 		"credentials": "# This plugin supported API authentication using either Service Principals or utilizing a Managed Identity assigned to the virtual machine.\n# Regardless which authentication method used, the identity will need the “DNS Zone Contributor” role assigned to it.\n# As multiple Azure DNS Zones in multiple resource groups can exist, the config file needs a mapping of zone to resource group ID. Multiple zones -> ID mappings can be listed by using the key dns_azure_zoneX where X is a unique number. At least 1 zone mapping is required.\n\n# Using a service principal (option 1)\ndns_azure_sp_client_id = 912ce44a-0156-4669-ae22-c16a17d34ca5\ndns_azure_sp_client_secret = E-xqXU83Y-jzTI6xe9fs2YC~mck3ZzUih9\ndns_azure_tenant_id = ed1090f3-ab18-4b12-816c-599af8a88cf7\n\n# Using used assigned MSI (option 2)\n# dns_azure_msi_client_id = 912ce44a-0156-4669-ae22-c16a17d34ca5\n\n# Using system assigned MSI (option 3)\n# dns_azure_msi_system_assigned = true\n\n# Zones (at least one always required)\ndns_azure_zone1 = example.com:/subscriptions/c135abce-d87d-48df-936c-15596c6968a5/resourceGroups/dns1\ndns_azure_zone2 = example.org:/subscriptions/99800903-fb14-4992-9aff-12eaf2744622/resourceGroups/dns2", | 		"credentials": "# This plugin supported API authentication using either Service Principals or utilizing a Managed Identity assigned to the virtual machine.\n# Regardless which authentication method used, the identity will need the “DNS Zone Contributor” role assigned to it.\n# As multiple Azure DNS Zones in multiple resource groups can exist, the config file needs a mapping of zone to resource group ID. Multiple zones -> ID mappings can be listed by using the key dns_azure_zoneX where X is a unique number. At least 1 zone mapping is required.\n\n# Using a service principal (option 1)\ndns_azure_sp_client_id = 912ce44a-0156-4669-ae22-c16a17d34ca5\ndns_azure_sp_client_secret = E-xqXU83Y-jzTI6xe9fs2YC~mck3ZzUih9\ndns_azure_tenant_id = ed1090f3-ab18-4b12-816c-599af8a88cf7\n\n# Using used assigned MSI (option 2)\n# dns_azure_msi_client_id = 912ce44a-0156-4669-ae22-c16a17d34ca5\n\n# Using system assigned MSI (option 3)\n# dns_azure_msi_system_assigned = true\n\n# Zones (at least one always required)\ndns_azure_zone1 = example.com:/subscriptions/c135abce-d87d-48df-936c-15596c6968a5/resourceGroups/dns1\ndns_azure_zone2 = example.org:/subscriptions/99800903-fb14-4992-9aff-12eaf2744622/resourceGroups/dns2", | ||||||
| 		"full_plugin_name": "dns-azure" | 		"full_plugin_name": "dns-azure" | ||||||
| 	}, | 	}, | ||||||
| 	"baidu": { |  | ||||||
| 		"name": "baidu", |  | ||||||
| 		"package_name": "certbot-dns-baidu", |  | ||||||
| 		"version": "~=0.1.1", |  | ||||||
| 		"dependencies": "", |  | ||||||
| 		"credentials": "dns_baidu_access_key = 12345678\ndns_baidu_secret_key = 1234567890abcdef1234567890abcdef", |  | ||||||
| 		"full_plugin_name": "dns-baidu" |  | ||||||
| 	}, |  | ||||||
| 	"beget": { |  | ||||||
| 		"name":"Beget", |  | ||||||
| 		"package_name": "certbot-beget-plugin", |  | ||||||
| 		"version": "~=1.0.0.dev9", |  | ||||||
| 		"dependencies": "", |  | ||||||
| 		"credentials": "# Beget API credentials used by Certbot\nbeget_plugin_username = username\nbeget_plugin_password = password", |  | ||||||
| 		"full_plugin_name": "beget-plugin" |  | ||||||
| 	}, |  | ||||||
| 	"bunny": { | 	"bunny": { | ||||||
| 		"name": "bunny.net", | 		"name": "bunny.net", | ||||||
| 		"package_name": "certbot-dns-bunny", | 		"package_name": "certbot-dns-bunny", | ||||||
| @@ -55,19 +39,11 @@ | |||||||
| 		"credentials": "# Bunny API token used by Certbot (see https://dash.bunny.net/account/settings)\ndns_bunny_api_key = xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx", | 		"credentials": "# Bunny API token used by Certbot (see https://dash.bunny.net/account/settings)\ndns_bunny_api_key = xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx", | ||||||
| 		"full_plugin_name": "dns-bunny" | 		"full_plugin_name": "dns-bunny" | ||||||
| 	}, | 	}, | ||||||
| 	"cdmon": { |  | ||||||
| 		"name": "cdmon", |  | ||||||
| 		"package_name": "certbot-dns-cdmon", |  | ||||||
| 		"version": "~=0.4.1", |  | ||||||
| 		"dependencies": "", |  | ||||||
| 		"credentials": "dns_cdmon_api_key=your-cdmon-api-token\ndns_cdmon_domain=your_domain_is_optional", |  | ||||||
| 		"full_plugin_name": "dns-cdmon" |  | ||||||
| 	}, |  | ||||||
| 	"cloudflare": { | 	"cloudflare": { | ||||||
| 		"name": "Cloudflare", | 		"name": "Cloudflare", | ||||||
| 		"package_name": "certbot-dns-cloudflare", | 		"package_name": "certbot-dns-cloudflare", | ||||||
| 		"version": "=={{certbot-version}}", | 		"version": "=={{certbot-version}}", | ||||||
| 		"dependencies": "acme=={{certbot-version}}", | 		"dependencies": "cloudflare==2.19.* acme=={{certbot-version}}", | ||||||
| 		"credentials": "# Cloudflare API token\ndns_cloudflare_api_token=0123456789abcdef0123456789abcdef01234567", | 		"credentials": "# Cloudflare API token\ndns_cloudflare_api_token=0123456789abcdef0123456789abcdef01234567", | ||||||
| 		"full_plugin_name": "dns-cloudflare" | 		"full_plugin_name": "dns-cloudflare" | ||||||
| 	}, | 	}, | ||||||
| @@ -106,19 +82,11 @@ | |||||||
| 	"cpanel": { | 	"cpanel": { | ||||||
| 		"name": "cPanel", | 		"name": "cPanel", | ||||||
| 		"package_name": "certbot-dns-cpanel", | 		"package_name": "certbot-dns-cpanel", | ||||||
| 		"version": "~=0.4.0", | 		"version": "~=0.2.2", | ||||||
| 		"dependencies": "", | 		"dependencies": "", | ||||||
| 		"credentials": "cpanel_url = https://cpanel.example.com:2083\ncpanel_username = your_username\ncpanel_password = your_password\ncpanel_token = your_api_token", | 		"credentials": "cpanel_url = https://cpanel.example.com:2083\ncpanel_username = user\ncpanel_password = hunter2", | ||||||
| 		"full_plugin_name": "cpanel" | 		"full_plugin_name": "cpanel" | ||||||
| 	}, | 	}, | ||||||
| 	"ddnss": { |  | ||||||
| 		"name": "DDNSS", |  | ||||||
| 		"package_name": "certbot-dns-ddnss", |  | ||||||
| 		"version": "~=1.1.0", |  | ||||||
| 		"dependencies": "", |  | ||||||
| 		"credentials": "dns_ddnss_token = YOUR_DDNSS_API_TOKEN", |  | ||||||
| 		"full_plugin_name": "dns-ddnss" |  | ||||||
| 	}, |  | ||||||
| 	"desec": { | 	"desec": { | ||||||
| 		"name": "deSEC", | 		"name": "deSEC", | ||||||
| 		"package_name": "certbot-dns-desec", | 		"package_name": "certbot-dns-desec", | ||||||
| @@ -185,11 +153,11 @@ | |||||||
| 	}, | 	}, | ||||||
| 	"domainoffensive": { | 	"domainoffensive": { | ||||||
| 		"name": "DomainOffensive (do.de)", | 		"name": "DomainOffensive (do.de)", | ||||||
| 		"package_name": "certbot-dns-domainoffensive", | 		"package_name": "certbot-dns-do", | ||||||
| 		"version": "~=2.0.0", | 		"version": "~=0.31.0", | ||||||
| 		"dependencies": "", | 		"dependencies": "", | ||||||
| 		"credentials": "dns_domainoffensive_api_token = YOUR_DO_DE_AUTH_TOKEN", | 		"credentials": "dns_do_api_token = YOUR_DO_DE_AUTH_TOKEN", | ||||||
| 		"full_plugin_name": "dns-domainoffensive" | 		"full_plugin_name": "dns-do" | ||||||
| 	}, | 	}, | ||||||
| 	"domeneshop": { | 	"domeneshop": { | ||||||
| 		"name": "Domeneshop", | 		"name": "Domeneshop", | ||||||
| @@ -223,38 +191,22 @@ | |||||||
| 		"credentials": "dns_eurodns_applicationId = myuser\ndns_eurodns_apiKey = mysecretpassword\ndns_eurodns_endpoint = https://rest-api.eurodns.com/user-api-gateway/proxy", | 		"credentials": "dns_eurodns_applicationId = myuser\ndns_eurodns_apiKey = mysecretpassword\ndns_eurodns_endpoint = https://rest-api.eurodns.com/user-api-gateway/proxy", | ||||||
| 		"full_plugin_name": "dns-eurodns" | 		"full_plugin_name": "dns-eurodns" | ||||||
| 	}, | 	}, | ||||||
| 	"firstdomains": { |  | ||||||
|                 "name": "First Domains", |  | ||||||
|                 "package_name": "certbot-dns-firstdomains", |  | ||||||
|                 "version": ">=1.0", |  | ||||||
|                 "dependencies": "", |  | ||||||
|                 "credentials": "dns_firstdomains_username = myremoteuser\ndns_firstdomains_password = verysecureremoteuserpassword", |  | ||||||
|                 "full_plugin_name": "dns-firstdomains" |  | ||||||
|         }, |  | ||||||
| 	"freedns": { | 	"freedns": { | ||||||
| 		"name": "FreeDNS", | 		"name": "FreeDNS", | ||||||
| 		"package_name": "certbot-dns-freedns", | 		"package_name": "certbot-dns-freedns", | ||||||
| 		"version": "~=0.1.0", | 		"version": "~=0.2.0", | ||||||
| 		"dependencies": "", | 		"dependencies": "", | ||||||
| 		"credentials": "dns_freedns_username = myremoteuser\ndns_freedns_password = verysecureremoteuserpassword", | 		"credentials": "dns_freedns_username = myremoteuser\ndns_freedns_password = verysecureremoteuserpassword", | ||||||
| 		"full_plugin_name": "dns-freedns" | 		"full_plugin_name": "dns-freedns" | ||||||
| 	}, | 	}, | ||||||
| 	"gandi": { | 	"gandi": { | ||||||
| 		"name": "Gandi Live DNS", | 		"name": "Gandi Live DNS", | ||||||
| 		"package_name": "certbot-dns-gandi", | 		"package_name": "certbot_plugin_gandi", | ||||||
| 		"version": "~=1.6.1", | 		"version": "~=1.5.0", | ||||||
| 		"dependencies": "", | 		"dependencies": "", | ||||||
| 		"credentials": "# Gandi personal access token\ndns_gandi_token=PERSONAL_ACCESS_TOKEN", | 		"credentials": "# Gandi personal access token\ndns_gandi_token=PERSONAL_ACCESS_TOKEN", | ||||||
| 		"full_plugin_name": "dns-gandi" | 		"full_plugin_name": "dns-gandi" | ||||||
| 	}, | 	}, | ||||||
| 	"gcore": { |  | ||||||
| 		"name": "Gcore DNS", |  | ||||||
| 		"package_name": "certbot-dns-gcore", |  | ||||||
| 		"version": "~=0.1.8", |  | ||||||
| 		"dependencies": "", |  | ||||||
| 		"credentials": "dns_gcore_apitoken = 0123456789abcdef0123456789abcdef01234567", |  | ||||||
| 		"full_plugin_name": "dns-gcore" |  | ||||||
| 	}, |  | ||||||
| 	"godaddy": { | 	"godaddy": { | ||||||
| 		"name": "GoDaddy", | 		"name": "GoDaddy", | ||||||
| 		"package_name": "certbot-dns-godaddy", | 		"package_name": "certbot-dns-godaddy", | ||||||
| @@ -295,14 +247,6 @@ | |||||||
| 		"credentials": "dns_hetzner_api_token = 0123456789abcdef0123456789abcdef", | 		"credentials": "dns_hetzner_api_token = 0123456789abcdef0123456789abcdef", | ||||||
| 		"full_plugin_name": "dns-hetzner" | 		"full_plugin_name": "dns-hetzner" | ||||||
| 	}, | 	}, | ||||||
| 	"hostingnl": { |  | ||||||
| 		"name": "Hosting.nl", |  | ||||||
| 		"package_name": "certbot-dns-hostingnl", |  | ||||||
| 		"version": "~=0.1.5", |  | ||||||
| 		"dependencies": "", |  | ||||||
| 		"credentials": "dns_hostingnl_api_key = 0123456789abcdef0123456789abcdef", |  | ||||||
| 		"full_plugin_name": "dns-hostingnl" |  | ||||||
| 	}, |  | ||||||
| 	"hover": { | 	"hover": { | ||||||
| 		"name": "Hover", | 		"name": "Hover", | ||||||
| 		"package_name": "certbot-dns-hover", | 		"package_name": "certbot-dns-hover", | ||||||
| @@ -359,14 +303,6 @@ | |||||||
| 		"credentials": "dns_joker_username = <Dynamic DNS Authentication Username>\ndns_joker_password = <Dynamic DNS Authentication Password>\ndns_joker_domain = <Dynamic DNS Domain>", | 		"credentials": "dns_joker_username = <Dynamic DNS Authentication Username>\ndns_joker_password = <Dynamic DNS Authentication Password>\ndns_joker_domain = <Dynamic DNS Domain>", | ||||||
| 		"full_plugin_name": "dns-joker" | 		"full_plugin_name": "dns-joker" | ||||||
| 	}, | 	}, | ||||||
| 	"leaseweb": { |  | ||||||
| 		"name": "LeaseWeb", |  | ||||||
| 		"package_name": "certbot-dns-leaseweb", |  | ||||||
| 		"version": "~=1.0.1", |  | ||||||
| 		"dependencies": "", |  | ||||||
| 		"credentials": "dns_leaseweb_api_token = 01234556789", |  | ||||||
| 		"full_plugin_name": "dns-leaseweb" |  | ||||||
| 	}, |  | ||||||
| 	"linode": { | 	"linode": { | ||||||
| 		"name": "Linode", | 		"name": "Linode", | ||||||
| 		"package_name": "certbot-dns-linode", | 		"package_name": "certbot-dns-linode", | ||||||
| @@ -396,7 +332,7 @@ | |||||||
| 		"package_name": "certbot-dns-mijn-host", | 		"package_name": "certbot-dns-mijn-host", | ||||||
| 		"version": "~=0.0.4", | 		"version": "~=0.0.4", | ||||||
| 		"dependencies": "", | 		"dependencies": "", | ||||||
| 		"credentials": "dns_mijn_host_api_key=0123456789abcdef0123456789abcdef", | 		"credentials": "dns-mijn-host-credentials = /etc/letsencrypt/mijnhost-credentials.ini", | ||||||
| 		"full_plugin_name": "dns-mijn-host" | 		"full_plugin_name": "dns-mijn-host" | ||||||
| 	}, | 	}, | ||||||
| 	"namecheap": { | 	"namecheap": { | ||||||
| @@ -415,14 +351,6 @@ | |||||||
| 		"credentials": "dns_netcup_customer_id  = 123456\ndns_netcup_api_key      = 0123456789abcdef0123456789abcdef01234567\ndns_netcup_api_password = abcdef0123456789abcdef01234567abcdef0123", | 		"credentials": "dns_netcup_customer_id  = 123456\ndns_netcup_api_key      = 0123456789abcdef0123456789abcdef01234567\ndns_netcup_api_password = abcdef0123456789abcdef01234567abcdef0123", | ||||||
| 		"full_plugin_name": "dns-netcup" | 		"full_plugin_name": "dns-netcup" | ||||||
| 	}, | 	}, | ||||||
| 	"nicru": { |  | ||||||
| 		"name": "nic.ru", |  | ||||||
| 		"package_name": "certbot-dns-nicru", |  | ||||||
| 		"version": "~=1.0.3", |  | ||||||
| 		"dependencies": "", |  | ||||||
| 		"credentials": "dns_nicru_client_id = application-id\ndns_nicru_client_secret = application-token\ndns_nicru_username = 0001110/NIC-D\ndns_nicru_password = password\ndns_nicru_scope = .+:.+/zones/example.com(/.+)?\ndns_nicru_service = DNS_SERVICE_NAME\ndns_nicru_zone = example.com", |  | ||||||
| 		"full_plugin_name": "dns-nicru" |  | ||||||
| 	}, |  | ||||||
| 	"njalla": { | 	"njalla": { | ||||||
| 		"name": "Njalla", | 		"name": "Njalla", | ||||||
| 		"package_name": "certbot-dns-njalla", | 		"package_name": "certbot-dns-njalla", | ||||||
| @@ -466,7 +394,7 @@ | |||||||
| 	"porkbun": { | 	"porkbun": { | ||||||
| 		"name": "Porkbun", | 		"name": "Porkbun", | ||||||
| 		"package_name": "certbot-dns-porkbun", | 		"package_name": "certbot-dns-porkbun", | ||||||
| 		"version": "~=0.9", | 		"version": "~=0.2", | ||||||
| 		"dependencies": "", | 		"dependencies": "", | ||||||
| 		"credentials": "dns_porkbun_key=your-porkbun-api-key\ndns_porkbun_secret=your-porkbun-api-secret", | 		"credentials": "dns_porkbun_key=your-porkbun-api-key\ndns_porkbun_secret=your-porkbun-api-secret", | ||||||
| 		"full_plugin_name": "dns-porkbun" | 		"full_plugin_name": "dns-porkbun" | ||||||
| @@ -496,13 +424,13 @@ | |||||||
| 		"full_plugin_name": "dns-rfc2136" | 		"full_plugin_name": "dns-rfc2136" | ||||||
| 	}, | 	}, | ||||||
| 	"rockenstein": { | 	"rockenstein": { | ||||||
| 		"name": "rockenstein AG", |                 "name": "rockenstein AG", | ||||||
| 		"package_name": "certbot-dns-rockenstein", |                 "package_name": "certbot-dns-rockenstein", | ||||||
| 		"version": "~=1.0.0", |                 "version": "~=1.0.0", | ||||||
| 		"dependencies": "", |                 "dependencies": "", | ||||||
| 		"credentials": "dns_rockenstein_token=<token>", |                 "credentials": "dns_rockenstein_token=<token>", | ||||||
| 		"full_plugin_name": "dns-rockenstein" |                 "full_plugin_name": "dns-rockenstein" | ||||||
| 	}, |         }, | ||||||
| 	"route53": { | 	"route53": { | ||||||
| 		"name": "Route 53 (Amazon)", | 		"name": "Route 53 (Amazon)", | ||||||
| 		"package_name": "certbot-dns-route53", | 		"package_name": "certbot-dns-route53", | ||||||
| @@ -511,30 +439,14 @@ | |||||||
| 		"credentials": "[default]\naws_access_key_id=AKIAIOSFODNN7EXAMPLE\naws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", | 		"credentials": "[default]\naws_access_key_id=AKIAIOSFODNN7EXAMPLE\naws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", | ||||||
| 		"full_plugin_name": "dns-route53" | 		"full_plugin_name": "dns-route53" | ||||||
| 	}, | 	}, | ||||||
| 	"spaceship": { |  | ||||||
| 		"name": "Spaceship", |  | ||||||
| 		"package_name": "certbot-dns-spaceship", |  | ||||||
| 		"version": "~=1.0.4", |  | ||||||
| 		"dependencies": "", |  | ||||||
| 		"credentials": "[spaceship]\napi_key=your_api_key\napi_secret=your_api_secret", |  | ||||||
| 		"full_plugin_name": "dns-spaceship" |  | ||||||
| 	}, |  | ||||||
| 	"strato": { | 	"strato": { | ||||||
| 		"name": "Strato", | 		"name": "Strato", | ||||||
| 		"package_name": "certbot-dns-strato", | 		"package_name": "certbot-dns-strato", | ||||||
| 		"version": "~=0.2.2", | 		"version": "~=0.2.1", | ||||||
| 		"dependencies": "", | 		"dependencies": "", | ||||||
| 		"credentials": "dns_strato_username = user\ndns_strato_password = pass\n# uncomment if youre using two factor authentication:\n# dns_strato_totp_devicename = 2fa_device\n# dns_strato_totp_secret = 2fa_secret\n#\n# uncomment if domain name contains special characters\n# insert domain display name as seen on your account page here\n# dns_strato_domain_display_name = my-punicode-url.de\n#\n# if youre not using strato.de or another special endpoint you can customise it below\n# you will probably only need to adjust the host, but you can also change the complete endpoint url\n# dns_strato_custom_api_scheme = https\n# dns_strato_custom_api_host = www.strato.de\n# dns_strato_custom_api_port = 443\n# dns_strato_custom_api_path = \"/apps/CustomerService\"", | 		"credentials": "dns_strato_username = user\ndns_strato_password = pass\n# uncomment if youre using two factor authentication:\n# dns_strato_totp_devicename = 2fa_device\n# dns_strato_totp_secret = 2fa_secret\n#\n# uncomment if domain name contains special characters\n# insert domain display name as seen on your account page here\n# dns_strato_domain_display_name = my-punicode-url.de\n#\n# if youre not using strato.de or another special endpoint you can customise it below\n# you will probably only need to adjust the host, but you can also change the complete endpoint url\n# dns_strato_custom_api_scheme = https\n# dns_strato_custom_api_host = www.strato.de\n# dns_strato_custom_api_port = 443\n# dns_strato_custom_api_path = \"/apps/CustomerService\"", | ||||||
| 		"full_plugin_name": "dns-strato" | 		"full_plugin_name": "dns-strato" | ||||||
| 	}, | 	}, | ||||||
| 	        "selectelv2": { |  | ||||||
|                 "name": "Selectel api v2", |  | ||||||
|                 "package_name": "certbot-dns-selectel-api-v2", |  | ||||||
|                 "version": "~=0.3.0", |  | ||||||
|                 "dependencies": "", |  | ||||||
|                 "credentials": "dns_selectel_api_v2_account_id = your_account_id\ndns_selectel_api_v2_project_name = your_project\ndns_selectel_api_v2_username = your_username\ndns_selectel_api_v2_password = your_password", |  | ||||||
|                 "full_plugin_name": "dns-selectel-api-v2" |  | ||||||
|         }, |  | ||||||
| 	"timeweb": { | 	"timeweb": { | ||||||
| 		"name": "Timeweb Cloud", | 		"name": "Timeweb Cloud", | ||||||
| 		"package_name": "certbot-dns-timeweb", | 		"package_name": "certbot-dns-timeweb", | ||||||
| @@ -575,7 +487,7 @@ | |||||||
| 		"credentials": "dns_websupport_identifier = <api_key>\ndns_websupport_secret_key = <secret>", | 		"credentials": "dns_websupport_identifier = <api_key>\ndns_websupport_secret_key = <secret>", | ||||||
| 		"full_plugin_name": "dns-websupport" | 		"full_plugin_name": "dns-websupport" | ||||||
| 	}, | 	}, | ||||||
| 	"wedos": { | 	"wedos":{ | ||||||
| 		"name": "Wedos", | 		"name": "Wedos", | ||||||
| 		"package_name": "certbot-dns-wedos", | 		"package_name": "certbot-dns-wedos", | ||||||
| 		"version": "~=2.2", | 		"version": "~=2.2", | ||||||
| @@ -590,13 +502,5 @@ | |||||||
| 		"dependencies": "", | 		"dependencies": "", | ||||||
| 		"credentials": "edgedns_client_secret = as3d1asd5d1a32sdfsdfs2d1asd5=\nedgedns_host = sdflskjdf-dfsdfsdf-sdfsdfsdf.luna.akamaiapis.net\nedgedns_access_token = kjdsi3-34rfsdfsdf-234234fsdfsdf\nedgedns_client_token = dkfjdf-342fsdfsd-23fsdfsdfsdf", | 		"credentials": "edgedns_client_secret = as3d1asd5d1a32sdfsdfs2d1asd5=\nedgedns_host = sdflskjdf-dfsdfsdf-sdfsdfsdf.luna.akamaiapis.net\nedgedns_access_token = kjdsi3-34rfsdfsdf-234234fsdfsdf\nedgedns_client_token = dkfjdf-342fsdfsd-23fsdfsdfsdf", | ||||||
| 		"full_plugin_name": "edgedns" | 		"full_plugin_name": "edgedns" | ||||||
| 	}, | 	} | ||||||
| 	"zoneedit": { |  | ||||||
| 		"name": "ZoneEdit", |  | ||||||
| 		"package_name": "certbot-dns-zoneedit", |  | ||||||
| 		"version": "~=0.3.2", |  | ||||||
| 		"dependencies": "--no-deps dnspython", |  | ||||||
| 		"credentials": "dns_zoneedit_user = <login-user-id>\ndns_zoneedit_token = <dyn-authentication-token>", |  | ||||||
| 		"full_plugin_name": "dns-zoneedit" |  | ||||||
|  	} |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ YELLOW='\E[1;33m' | |||||||
| export BLUE CYAN GREEN RED RESET YELLOW | export BLUE CYAN GREEN RED RESET YELLOW | ||||||
|  |  | ||||||
| # Docker Compose | # Docker Compose | ||||||
| COMPOSE_PROJECT_NAME="npm2dev" | COMPOSE_PROJECT_NAME="npmdev" | ||||||
| COMPOSE_FILE="docker/docker-compose.dev.yml" | COMPOSE_FILE="docker/docker-compose.dev.yml" | ||||||
|  |  | ||||||
| export COMPOSE_FILE COMPOSE_PROJECT_NAME | export COMPOSE_FILE COMPOSE_PROJECT_NAME | ||||||
|   | |||||||
| @@ -67,8 +67,6 @@ printf "nameserver %s\noptions ndots:0" "${DNSROUTER_IP}" > "${LOCAL_RESOLVE}" | |||||||
| # bring up all remaining containers, except cypress! | # bring up all remaining containers, except cypress! | ||||||
| docker-compose up -d --remove-orphans stepca squid | docker-compose up -d --remove-orphans stepca squid | ||||||
| docker-compose pull db-mysql || true # ok to fail | docker-compose pull db-mysql || true # ok to fail | ||||||
| docker-compose pull db-postgres || true # ok to fail |  | ||||||
| docker-compose pull authentik authentik-redis authentik-ldap || true # ok to fail |  | ||||||
| docker-compose up -d --remove-orphans --pull=never fullstack | docker-compose up -d --remove-orphans --pull=never fullstack | ||||||
|  |  | ||||||
| # wait for main container to be healthy | # wait for main container to be healthy | ||||||
|   | |||||||
| @@ -36,11 +36,12 @@ if hash docker-compose 2>/dev/null; then | |||||||
|  |  | ||||||
| 	# bring up all remaining containers, except cypress! | 	# bring up all remaining containers, except cypress! | ||||||
| 	docker-compose up -d --remove-orphans stepca squid | 	docker-compose up -d --remove-orphans stepca squid | ||||||
| 	docker-compose pull db db-postgres authentik-redis authentik authentik-worker authentik-ldap | 	docker-compose pull db | ||||||
| 	docker-compose build --pull --parallel fullstack | 	docker-compose up -d --remove-orphans --pull=never fullstack | ||||||
| 	docker-compose up -d --remove-orphans fullstack |  | ||||||
| 	docker-compose up -d --remove-orphans swagger | 	docker-compose up -d --remove-orphans swagger | ||||||
|  |  | ||||||
|  | 	# docker-compose up -d --remove-orphans --force-recreate --build | ||||||
|  |  | ||||||
| 	# wait for main container to be healthy | 	# wait for main container to be healthy | ||||||
| 	bash "$DIR/wait-healthy" "$(docker-compose ps --all -q fullstack)" 120 | 	bash "$DIR/wait-healthy" "$(docker-compose ps --all -q fullstack)" 120 | ||||||
|  |  | ||||||
| @@ -52,10 +53,10 @@ if hash docker-compose 2>/dev/null; then | |||||||
|  |  | ||||||
| 	if [ "$1" == "-f" ]; then | 	if [ "$1" == "-f" ]; then | ||||||
| 		echo -e "${BLUE}❯ ${YELLOW}Following Backend Container:${RESET}" | 		echo -e "${BLUE}❯ ${YELLOW}Following Backend Container:${RESET}" | ||||||
| 		docker logs -f npm2dev.core | 		docker logs -f npm_core | ||||||
| 	else | 	else | ||||||
| 		echo -e "${YELLOW}Hint:${RESET} You can follow the output of some of the containers with:" | 		echo -e "${YELLOW}Hint:${RESET} You can follow the output of some of the containers with:" | ||||||
| 		echo "  docker logs -f npm2dev.core" | 		echo "  docker logs -f npm_core" | ||||||
| 	fi | 	fi | ||||||
| else | else | ||||||
| 	echo -e "${RED}❯ docker-compose command is not available${RESET}" | 	echo -e "${RED}❯ docker-compose command is not available${RESET}" | ||||||
|   | |||||||
| @@ -1,22 +1,11 @@ | |||||||
| FROM cypress/included:14.0.1 | FROM cypress/included:13.9.0 | ||||||
|  |  | ||||||
|  | COPY --chown=1000 ./test /test | ||||||
|  |  | ||||||
| # Disable Cypress CLI colors | # Disable Cypress CLI colors | ||||||
| ENV FORCE_COLOR=0 | ENV FORCE_COLOR=0 | ||||||
| ENV NO_COLOR=1 | ENV NO_COLOR=1 | ||||||
|  |  | ||||||
| # testssl.sh and mkcert |  | ||||||
| RUN wget "https://github.com/testssl/testssl.sh/archive/refs/tags/v3.2rc4.tar.gz" -O /tmp/testssl.tgz -q \ |  | ||||||
| 	&& tar -xzf /tmp/testssl.tgz -C /tmp \ |  | ||||||
| 	&& mv /tmp/testssl.sh-3.2rc4 /testssl \ |  | ||||||
| 	&& rm /tmp/testssl.tgz \ |  | ||||||
| 	&& apt-get update \ |  | ||||||
| 	&& apt-get install -y bsdmainutils curl dnsutils \ |  | ||||||
| 	&& apt-get clean \ |  | ||||||
| 	&& rm -rf /var/lib/apt/lists/* \ |  | ||||||
| 	&& wget "https://github.com/FiloSottile/mkcert/releases/download/v1.4.4/mkcert-v1.4.4-linux-amd64" -O /bin/mkcert \ |  | ||||||
| 	&& chmod +x /bin/mkcert |  | ||||||
|  |  | ||||||
| COPY --chown=1000 ./test /test |  | ||||||
| WORKDIR /test | WORKDIR /test | ||||||
| RUN yarn install && yarn cache clean | RUN yarn install && yarn cache clean | ||||||
| ENTRYPOINT [] | ENTRYPOINT [] | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ describe('Certificates endpoints', () => { | |||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	it('Validate custom certificate', () => { | 	it('Validate custom certificate', function() { | ||||||
| 		cy.task('backendApiPostFiles', { | 		cy.task('backendApiPostFiles', { | ||||||
| 			token: token, | 			token: token, | ||||||
| 			path:  '/api/nginx/certificates/validate', | 			path:  '/api/nginx/certificates/validate', | ||||||
| @@ -25,7 +25,7 @@ describe('Certificates endpoints', () => { | |||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	it('Custom certificate lifecycle', () => { | 	it('Custom certificate lifecycle', function() { | ||||||
| 		// Create custom cert | 		// Create custom cert | ||||||
| 		cy.task('backendApiPost', { | 		cy.task('backendApiPost', { | ||||||
| 			token: token, | 			token: token, | ||||||
| @@ -73,7 +73,7 @@ describe('Certificates endpoints', () => { | |||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	it('Request Certificate - CVE-2024-46256/CVE-2024-46257', () => { | 	it('Request Certificate - CVE-2024-46256/CVE-2024-46257', function() { | ||||||
| 		cy.task('backendApiPost', { | 		cy.task('backendApiPost', { | ||||||
| 			token: token, | 			token: token, | ||||||
| 			path:  '/api/nginx/certificates', | 			path:  '/api/nginx/certificates', | ||||||
| @@ -96,28 +96,4 @@ describe('Certificates endpoints', () => { | |||||||
| 			expect(data.error.message).to.contain('data/domain_names/0 must match pattern'); | 			expect(data.error.message).to.contain('data/domain_names/0 must match pattern'); | ||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	it('Request Certificate - LE Email Escaped', () => { |  | ||||||
| 		cy.task('backendApiPost', { |  | ||||||
| 			token: token, |  | ||||||
| 			path:  '/api/nginx/certificates', |  | ||||||
| 			data:  { |  | ||||||
| 				domain_names: ['test.com"||echo hello-world||\\\\n test.com"'], |  | ||||||
| 				meta:         { |  | ||||||
| 					dns_challenge:     false, |  | ||||||
| 					letsencrypt_agree: true, |  | ||||||
| 					letsencrypt_email: "admin@example.com' --version;echo hello-world", |  | ||||||
| 				}, |  | ||||||
| 				provider: 'letsencrypt', |  | ||||||
| 			}, |  | ||||||
| 			returnOnError: true, |  | ||||||
| 		}).then((data) => { |  | ||||||
| 			cy.validateSwaggerSchema('post', 400, '/nginx/certificates', data); |  | ||||||
| 			expect(data).to.have.property('error'); |  | ||||||
| 			expect(data.error).to.have.property('message'); |  | ||||||
| 			expect(data.error).to.have.property('code'); |  | ||||||
| 			expect(data.error.code).to.equal(400); |  | ||||||
| 			expect(data.error.message).to.contain('data/meta/letsencrypt_email must match pattern'); |  | ||||||
| 		}); |  | ||||||
| 	}); |  | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -1,25 +0,0 @@ | |||||||
| /// <reference types="cypress" /> |  | ||||||
|  |  | ||||||
| describe('Dashboard endpoints', () => { |  | ||||||
| 	let token; |  | ||||||
|  |  | ||||||
| 	before(() => { |  | ||||||
| 		cy.getToken().then((tok) => { |  | ||||||
| 			token = tok; |  | ||||||
| 		}); |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	it('Should be able to get host counts', () => { |  | ||||||
| 		cy.task('backendApiGet', { |  | ||||||
| 			token: token, |  | ||||||
| 			path:  '/api/reports/hosts' |  | ||||||
| 		}).then((data) => { |  | ||||||
| 			cy.validateSwaggerSchema('get', 200, '/reports/hosts', data); |  | ||||||
| 			expect(data).to.have.property('dead'); |  | ||||||
| 			expect(data).to.have.property('proxy'); |  | ||||||
| 			expect(data).to.have.property('redirection'); |  | ||||||
| 			expect(data).to.have.property('stream'); |  | ||||||
| 		}); |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| }); |  | ||||||
| @@ -9,7 +9,7 @@ describe('Full Certificate Provisions', () => { | |||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	it('Should be able to create new http certificate', () => { | 	it('Should be able to create new http certificate', function() { | ||||||
| 		cy.task('backendApiPost', { | 		cy.task('backendApiPost', { | ||||||
| 			token: token, | 			token: token, | ||||||
| 			path:  '/api/nginx/certificates', | 			path:  '/api/nginx/certificates', | ||||||
| @@ -32,7 +32,7 @@ describe('Full Certificate Provisions', () => { | |||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	it('Should be able to create new DNS certificate with Powerdns', () => { | 	it('Should be able to create new DNS certificate with Powerdns', function() { | ||||||
| 		cy.task('backendApiPost', { | 		cy.task('backendApiPost', { | ||||||
| 			token: token, | 			token: token, | ||||||
| 			path:  '/api/nginx/certificates', | 			path:  '/api/nginx/certificates', | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| /// <reference types="cypress" /> | /// <reference types="cypress" /> | ||||||
|  |  | ||||||
| describe('Basic API checks', () => { | describe('Basic API checks', () => { | ||||||
| 	it('Should return a valid health payload', () => { | 	it('Should return a valid health payload', function () { | ||||||
| 		cy.task('backendApiGet', { | 		cy.task('backendApiGet', { | ||||||
| 			path: '/api/', | 			path: '/api/', | ||||||
| 		}).then((data) => { | 		}).then((data) => { | ||||||
| @@ -10,9 +10,9 @@ describe('Basic API checks', () => { | |||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	it('Should return a valid schema payload', () => { | 	it('Should return a valid schema payload', function () { | ||||||
| 		cy.task('backendApiGet', { | 		cy.task('backendApiGet', { | ||||||
| 			path: `/api/schema?ts=${Date.now()}`, | 			path: '/api/schema?ts=' + Date.now(), | ||||||
| 		}).then((data) => { | 		}).then((data) => { | ||||||
| 			expect(data.openapi).to.be.equal('3.1.0'); | 			expect(data.openapi).to.be.equal('3.1.0'); | ||||||
| 		}); | 		}); | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user