mirror of
				https://github.com/NginxProxyManager/nginx-proxy-manager.git
				synced 2025-10-31 07:43:33 +00:00 
			
		
		
		
	Moved certrbot plugin list to backend
frontend doesn't include when building in react version adds swagger for existing dns-providers endpoint
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,6 @@ | |||||||
| .DS_Store | .DS_Store | ||||||
| .idea | .idea | ||||||
|  | .qodo | ||||||
| ._* | ._* | ||||||
| .vscode | .vscode | ||||||
| certbot-help.txt | certbot-help.txt | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| # certbot-dns-plugins | # Certbot dns-plugins | ||||||
| 
 | 
 | ||||||
| This file contains info about available Certbot DNS plugins. | This file contains info about available Certbot DNS plugins. | ||||||
| This only works for plugins which use the standard argument structure, so: | This only works for plugins which use the standard argument structure, so: | ||||||
| @@ -1,11 +1,11 @@ | |||||||
| import fs from "node:fs"; | import fs from "node:fs"; | ||||||
| import https from "node:https"; | import https from "node:https"; | ||||||
| import path from "path"; |  | ||||||
| import archiver from "archiver"; | import archiver from "archiver"; | ||||||
| import _ from "lodash"; | import _ from "lodash"; | ||||||
| import moment from "moment"; | import moment from "moment"; | ||||||
|  | import path from "path"; | ||||||
| import tempWrite from "temp-write"; | import tempWrite from "temp-write"; | ||||||
| import dnsPlugins from "../global/certbot-dns-plugins.json" with { type: "json" }; | import dnsPlugins from "../certbot/dns-plugins.json" with { type: "json" }; | ||||||
| import { installPlugin } from "../lib/certbot.js"; | import { installPlugin } from "../lib/certbot.js"; | ||||||
| import { useLetsencryptServer, useLetsencryptStaging } from "../lib/config.js"; | import { useLetsencryptServer, useLetsencryptStaging } from "../lib/config.js"; | ||||||
| import error from "../lib/error.js"; | import error from "../lib/error.js"; | ||||||
| @@ -26,7 +26,11 @@ const omissions = () => { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| const internalCertificate = { | const internalCertificate = { | ||||||
| 	allowedSslFiles: ["certificate", "certificate_key", "intermediate_certificate"], | 	allowedSslFiles: [ | ||||||
|  | 		"certificate", | ||||||
|  | 		"certificate_key", | ||||||
|  | 		"intermediate_certificate", | ||||||
|  | 	], | ||||||
| 	intervalTimeout: 1000 * 60 * 60, // 1 hour | 	intervalTimeout: 1000 * 60 * 60, // 1 hour | ||||||
| 	interval: null, | 	interval: null, | ||||||
| 	intervalProcessing: false, | 	intervalProcessing: false, | ||||||
| @@ -53,7 +57,10 @@ const internalCertificate = { | |||||||
| 			); | 			); | ||||||
|  |  | ||||||
| 			const expirationThreshold = moment() | 			const expirationThreshold = moment() | ||||||
| 				.add(internalCertificate.renewBeforeExpirationBy[0], internalCertificate.renewBeforeExpirationBy[1]) | 				.add( | ||||||
|  | 					internalCertificate.renewBeforeExpirationBy[0], | ||||||
|  | 					internalCertificate.renewBeforeExpirationBy[1], | ||||||
|  | 				) | ||||||
| 				.format("YYYY-MM-DD HH:mm:ss"); | 				.format("YYYY-MM-DD HH:mm:ss"); | ||||||
|  |  | ||||||
| 			// Fetch all the letsencrypt certs from the db that will expire within the configured threshold | 			// Fetch all the letsencrypt certs from the db that will expire within the configured threshold | ||||||
| @@ -119,8 +126,13 @@ const internalCertificate = { | |||||||
| 			data.nice_name = data.domain_names.join(", "); | 			data.nice_name = data.domain_names.join(", "); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		const certificate = await certificateModel.query().insertAndFetch(data).then(utils.omitRow(omissions())); | 		// this command really should clean up and delete the cert if it can't fully succeed | ||||||
|  | 		const certificate = await certificateModel | ||||||
|  | 			.query() | ||||||
|  | 			.insertAndFetch(data) | ||||||
|  | 			.then(utils.omitRow(omissions())); | ||||||
|  |  | ||||||
|  | 		try { | ||||||
| 			if (certificate.provider === "letsencrypt") { | 			if (certificate.provider === "letsencrypt") { | ||||||
| 				// Request a new Cert from LE. Let the fun begin. | 				// Request a new Cert from LE. Let the fun begin. | ||||||
|  |  | ||||||
| @@ -132,12 +144,18 @@ const internalCertificate = { | |||||||
| 				// 6. Re-instate previously disabled hosts | 				// 6. Re-instate previously disabled hosts | ||||||
|  |  | ||||||
| 				// 1. Find out any hosts that are using any of the hostnames in this cert | 				// 1. Find out any hosts that are using any of the hostnames in this cert | ||||||
| 			const inUseResult = await internalHost.getHostsWithDomains(certificate.domain_names); | 				const inUseResult = await internalHost.getHostsWithDomains( | ||||||
|  | 					certificate.domain_names, | ||||||
|  | 				); | ||||||
|  |  | ||||||
| 				// 2. Disable them in nginx temporarily | 				// 2. Disable them in nginx temporarily | ||||||
| 				await internalCertificate.disableInUseHosts(inUseResult); | 				await internalCertificate.disableInUseHosts(inUseResult); | ||||||
|  |  | ||||||
| 			const user = await userModel.query().where("is_deleted", 0).andWhere("id", data.owner_user_id).first(); | 				const user = await userModel | ||||||
|  | 					.query() | ||||||
|  | 					.where("is_deleted", 0) | ||||||
|  | 					.andWhere("id", data.owner_user_id) | ||||||
|  | 					.first(); | ||||||
| 				if (!user || !user.email) { | 				if (!user || !user.email) { | ||||||
| 					throw new error.ValidationError( | 					throw new error.ValidationError( | ||||||
| 						"A valid email address must be set on your user account to use Let's Encrypt", | 						"A valid email address must be set on your user account to use Let's Encrypt", | ||||||
| @@ -149,7 +167,10 @@ const internalCertificate = { | |||||||
| 					try { | 					try { | ||||||
| 						await internalNginx.reload(); | 						await internalNginx.reload(); | ||||||
| 						// 4. Request cert | 						// 4. Request cert | ||||||
| 					await internalCertificate.requestLetsEncryptSslWithDnsChallenge(certificate, user.email); | 						await internalCertificate.requestLetsEncryptSslWithDnsChallenge( | ||||||
|  | 							certificate, | ||||||
|  | 							user.email, | ||||||
|  | 						); | ||||||
| 						await internalNginx.reload(); | 						await internalNginx.reload(); | ||||||
| 						// 6. Re-instate previously disabled hosts | 						// 6. Re-instate previously disabled hosts | ||||||
| 						await internalCertificate.enableInUseHosts(inUseResult); | 						await internalCertificate.enableInUseHosts(inUseResult); | ||||||
| @@ -166,7 +187,10 @@ const internalCertificate = { | |||||||
| 						await internalNginx.reload(); | 						await internalNginx.reload(); | ||||||
| 						setTimeout(() => {}, 5000); | 						setTimeout(() => {}, 5000); | ||||||
| 						// 4. Request cert | 						// 4. Request cert | ||||||
| 					await internalCertificate.requestLetsEncryptSsl(certificate, user.email); | 						await internalCertificate.requestLetsEncryptSsl( | ||||||
|  | 							certificate, | ||||||
|  | 							user.email, | ||||||
|  | 						); | ||||||
| 						// 5. Remove LE config | 						// 5. Remove LE config | ||||||
| 						await internalNginx.deleteLetsEncryptRequestConfig(certificate); | 						await internalNginx.deleteLetsEncryptRequestConfig(certificate); | ||||||
| 						await internalNginx.reload(); | 						await internalNginx.reload(); | ||||||
| @@ -190,7 +214,9 @@ const internalCertificate = { | |||||||
| 					const savedRow = await certificateModel | 					const savedRow = await certificateModel | ||||||
| 						.query() | 						.query() | ||||||
| 						.patchAndFetchById(certificate.id, { | 						.patchAndFetchById(certificate.id, { | ||||||
| 						expires_on: moment(certInfo.dates.to, "X").format("YYYY-MM-DD HH:mm:ss"), | 							expires_on: moment(certInfo.dates.to, "X").format( | ||||||
|  | 								"YYYY-MM-DD HH:mm:ss", | ||||||
|  | 							), | ||||||
| 						}) | 						}) | ||||||
| 						.then(utils.omitRow(omissions())); | 						.then(utils.omitRow(omissions())); | ||||||
|  |  | ||||||
| @@ -205,6 +231,11 @@ const internalCertificate = { | |||||||
| 					throw err; | 					throw err; | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | 		} catch (err) { | ||||||
|  | 			// Delete the certificate here. This is a hard delete, since it never existed properly | ||||||
|  | 			await certificateModel.query().deleteById(certificate.id); | ||||||
|  | 			throw err; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		data.meta = _.assign({}, data.meta || {}, certificate.meta); | 		data.meta = _.assign({}, data.meta || {}, certificate.meta); | ||||||
|  |  | ||||||
| @@ -313,7 +344,9 @@ const internalCertificate = { | |||||||
| 		if (certificate.provider === "letsencrypt") { | 		if (certificate.provider === "letsencrypt") { | ||||||
| 			const zipDirectory = internalCertificate.getLiveCertPath(data.id); | 			const zipDirectory = internalCertificate.getLiveCertPath(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 | 			const certFiles = fs | ||||||
| @@ -330,7 +363,9 @@ const internalCertificate = { | |||||||
| 				fileName: opName, | 				fileName: opName, | ||||||
| 			}; | 			}; | ||||||
| 		} | 		} | ||||||
| 		throw new error.ValidationError("Only Let'sEncrypt certificates can be downloaded"); | 		throw new error.ValidationError( | ||||||
|  | 			"Only Let'sEncrypt certificates can be downloaded", | ||||||
|  | 		); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| @@ -435,7 +470,10 @@ const internalCertificate = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	getCount: async (userId, visibility) => { | 	getCount: async (userId, visibility) => { | ||||||
| 		const query = certificateModel.query().count("id as count").where("is_deleted", 0); | 		const query = certificateModel | ||||||
|  | 			.query() | ||||||
|  | 			.count("id as count") | ||||||
|  | 			.where("is_deleted", 0); | ||||||
|  |  | ||||||
| 		if (visibility !== "all") { | 		if (visibility !== "all") { | ||||||
| 			query.andWhere("owner_user_id", userId); | 			query.andWhere("owner_user_id", userId); | ||||||
| @@ -483,13 +521,17 @@ 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, | ||||||
|  | 					(err) => { | ||||||
| 						if (err) { | 						if (err) { | ||||||
| 							reject(err); | 							reject(err); | ||||||
| 						} else { | 						} else { | ||||||
| 							resolve(); | 							resolve(); | ||||||
| 						} | 						} | ||||||
| 				}); | 					}, | ||||||
|  | 				); | ||||||
| 			}); | 			}); | ||||||
| 		}); | 		}); | ||||||
| 	}, | 	}, | ||||||
| @@ -562,7 +604,9 @@ const internalCertificate = { | |||||||
| 	upload: async (access, data) => { | 	upload: async (access, data) => { | ||||||
| 		const row = await internalCertificate.get(access, { id: data.id }); | 		const row = await internalCertificate.get(access, { id: data.id }); | ||||||
| 		if (row.provider !== "other") { | 		if (row.provider !== "other") { | ||||||
| 			throw new error.ValidationError("Cannot upload certificates for this type of provider"); | 			throw new error.ValidationError( | ||||||
|  | 				"Cannot upload certificates for this type of provider", | ||||||
|  | 			); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		const validations = await internalCertificate.validate(data); | 		const validations = await internalCertificate.validate(data); | ||||||
| @@ -578,7 +622,9 @@ const internalCertificate = { | |||||||
|  |  | ||||||
| 		const certificate = await internalCertificate.update(access, { | 		const certificate = await internalCertificate.update(access, { | ||||||
| 			id: data.id, | 			id: data.id, | ||||||
| 			expires_on: moment(validations.certificate.dates.to, "X").format("YYYY-MM-DD HH:mm:ss"), | 			expires_on: moment(validations.certificate.dates.to, "X").format( | ||||||
|  | 				"YYYY-MM-DD HH:mm:ss", | ||||||
|  | 			), | ||||||
| 			domain_names: [validations.certificate.cn], | 			domain_names: [validations.certificate.cn], | ||||||
| 			meta: _.clone(row.meta), // Prevent the update method from changing this value that we'll use later | 			meta: _.clone(row.meta), // Prevent the update method from changing this value that we'll use later | ||||||
| 		}); | 		}); | ||||||
| @@ -603,7 +649,9 @@ const internalCertificate = { | |||||||
| 		}, 10000); | 		}, 10000); | ||||||
|  |  | ||||||
| 		try { | 		try { | ||||||
| 			const result = await utils.exec(`openssl pkey -in ${filepath} -check -noout 2>&1 `); | 			const result = await utils.exec( | ||||||
|  | 				`openssl pkey -in ${filepath} -check -noout 2>&1 `, | ||||||
|  | 			); | ||||||
| 			clearTimeout(failTimeout); | 			clearTimeout(failTimeout); | ||||||
| 			if (!result.toLowerCase().includes("key is valid")) { | 			if (!result.toLowerCase().includes("key is valid")) { | ||||||
| 				throw new error.ValidationError(`Result Validation Error: ${result}`); | 				throw new error.ValidationError(`Result Validation Error: ${result}`); | ||||||
| @@ -613,7 +661,10 @@ const internalCertificate = { | |||||||
| 		} catch (err) { | 		} catch (err) { | ||||||
| 			clearTimeout(failTimeout); | 			clearTimeout(failTimeout); | ||||||
| 			fs.unlinkSync(filepath); | 			fs.unlinkSync(filepath); | ||||||
| 			throw new error.ValidationError(`Certificate Key is not valid (${err.message})`, err); | 			throw new error.ValidationError( | ||||||
|  | 				`Certificate Key is not valid (${err.message})`, | ||||||
|  | 				err, | ||||||
|  | 			); | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| @@ -627,7 +678,10 @@ const internalCertificate = { | |||||||
| 	getCertificateInfo: async (certificate, throwExpired) => { | 	getCertificateInfo: async (certificate, throwExpired) => { | ||||||
| 		try { | 		try { | ||||||
| 			const filepath = await tempWrite(certificate, "/tmp"); | 			const filepath = await tempWrite(certificate, "/tmp"); | ||||||
| 			const certData = await internalCertificate.getCertificateInfoFromFile(filepath, throwExpired); | 			const certData = await internalCertificate.getCertificateInfoFromFile( | ||||||
|  | 				filepath, | ||||||
|  | 				throwExpired, | ||||||
|  | 			); | ||||||
| 			fs.unlinkSync(filepath); | 			fs.unlinkSync(filepath); | ||||||
| 			return certData; | 			return certData; | ||||||
| 		} catch (err) { | 		} catch (err) { | ||||||
| @@ -647,7 +701,13 @@ const internalCertificate = { | |||||||
| 		const certData = {}; | 		const certData = {}; | ||||||
|  |  | ||||||
| 		try { | 		try { | ||||||
| 			const result = await utils.execFile("openssl", ["x509", "-in", certificateFile, "-subject", "-noout"]); | 			const result = await utils.execFile("openssl", [ | ||||||
|  | 				"x509", | ||||||
|  | 				"-in", | ||||||
|  | 				certificateFile, | ||||||
|  | 				"-subject", | ||||||
|  | 				"-noout", | ||||||
|  | 			]); | ||||||
| 			// Examples: | 			// Examples: | ||||||
| 			// subject=CN = *.jc21.com | 			// subject=CN = *.jc21.com | ||||||
| 			// subject=CN = something.example.com | 			// subject=CN = something.example.com | ||||||
| @@ -657,7 +717,13 @@ const internalCertificate = { | |||||||
| 				certData.cn = match[1]; | 				certData.cn = match[1]; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			const result2 = await utils.execFile("openssl", ["x509", "-in", certificateFile, "-issuer", "-noout"]); | 			const result2 = await utils.execFile("openssl", [ | ||||||
|  | 				"x509", | ||||||
|  | 				"-in", | ||||||
|  | 				certificateFile, | ||||||
|  | 				"-issuer", | ||||||
|  | 				"-noout", | ||||||
|  | 			]); | ||||||
| 			// Examples: | 			// Examples: | ||||||
| 			// issuer=C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3 | 			// issuer=C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3 | ||||||
| 			// issuer=C = US, O = Let's Encrypt, CN = E5 | 			// issuer=C = US, O = Let's Encrypt, CN = E5 | ||||||
| @@ -668,7 +734,13 @@ const internalCertificate = { | |||||||
| 				certData.issuer = match2[1]; | 				certData.issuer = match2[1]; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			const result3 = await utils.execFile("openssl", ["x509", "-in", certificateFile, "-dates", "-noout"]); | 			const result3 = await utils.execFile("openssl", [ | ||||||
|  | 				"x509", | ||||||
|  | 				"-in", | ||||||
|  | 				certificateFile, | ||||||
|  | 				"-dates", | ||||||
|  | 				"-noout", | ||||||
|  | 			]); | ||||||
| 			// notBefore=Jul 14 04:04:29 2018 GMT | 			// notBefore=Jul 14 04:04:29 2018 GMT | ||||||
| 			// notAfter=Oct 12 04:04:29 2018 GMT | 			// notAfter=Oct 12 04:04:29 2018 GMT | ||||||
| 			let validFrom = null; | 			let validFrom = null; | ||||||
| @@ -680,7 +752,10 @@ const internalCertificate = { | |||||||
| 				const match = regex.exec(str.trim()); | 				const match = regex.exec(str.trim()); | ||||||
|  |  | ||||||
| 				if (match && typeof match[2] !== "undefined") { | 				if (match && typeof match[2] !== "undefined") { | ||||||
| 					const date = Number.parseInt(moment(match[2], "MMM DD HH:mm:ss YYYY z").format("X"), 10); | 					const date = Number.parseInt( | ||||||
|  | 						moment(match[2], "MMM DD HH:mm:ss YYYY z").format("X"), | ||||||
|  | 						10, | ||||||
|  | 					); | ||||||
|  |  | ||||||
| 					if (match[1].toLowerCase() === "notbefore") { | 					if (match[1].toLowerCase() === "notbefore") { | ||||||
| 						validFrom = date; | 						validFrom = date; | ||||||
| @@ -692,10 +767,15 @@ 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 < Number.parseInt(moment().format("X"), 10)) { | 			if ( | ||||||
|  | 				throw_expired && | ||||||
|  | 				validTo < Number.parseInt(moment().format("X"), 10) | ||||||
|  | 			) { | ||||||
| 				throw new error.ValidationError("Certificate has expired"); | 				throw new error.ValidationError("Certificate has expired"); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| @@ -706,7 +786,10 @@ const internalCertificate = { | |||||||
|  |  | ||||||
| 			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, | ||||||
|  | 			); | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| @@ -787,7 +870,11 @@ const internalCertificate = { | |||||||
|  |  | ||||||
| 		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"; | ||||||
| @@ -812,7 +899,10 @@ const internalCertificate = { | |||||||
| 		]; | 		]; | ||||||
|  |  | ||||||
| 		if (hasConfigArg) { | 		if (hasConfigArg) { | ||||||
| 			args.push(`--${dnsPlugin.full_plugin_name}-credentials`, credentialsLocation); | 			args.push( | ||||||
|  | 				`--${dnsPlugin.full_plugin_name}-credentials`, | ||||||
|  | 				credentialsLocation, | ||||||
|  | 			); | ||||||
| 		} | 		} | ||||||
| 		if (certificate.meta.propagation_seconds !== undefined) { | 		if (certificate.meta.propagation_seconds !== undefined) { | ||||||
| 			args.push( | 			args.push( | ||||||
| @@ -821,7 +911,10 @@ const internalCertificate = { | |||||||
| 			); | 			); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); | 		const adds = internalCertificate.getAdditionalCertbotArgs( | ||||||
|  | 			certificate.id, | ||||||
|  | 			certificate.meta.dns_provider, | ||||||
|  | 		); | ||||||
| 		args.push(...adds.args); | 		args.push(...adds.args); | ||||||
|  |  | ||||||
| 		logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); | 		logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); | ||||||
| @@ -857,8 +950,12 @@ const internalCertificate = { | |||||||
| 				`${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`, | 				`${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`, | ||||||
| 			); | 			); | ||||||
|  |  | ||||||
| 			const updatedCertificate = await certificateModel.query().patchAndFetchById(certificate.id, { | 			const updatedCertificate = await certificateModel | ||||||
| 				expires_on: moment(certInfo.dates.to, "X").format("YYYY-MM-DD HH:mm:ss"), | 				.query() | ||||||
|  | 				.patchAndFetchById(certificate.id, { | ||||||
|  | 					expires_on: moment(certInfo.dates.to, "X").format( | ||||||
|  | 						"YYYY-MM-DD HH:mm:ss", | ||||||
|  | 					), | ||||||
| 				}); | 				}); | ||||||
|  |  | ||||||
| 			// Add to audit log | 			// Add to audit log | ||||||
| @@ -869,7 +966,9 @@ const internalCertificate = { | |||||||
| 				meta: updatedCertificate, | 				meta: updatedCertificate, | ||||||
| 			}); | 			}); | ||||||
| 		} else { | 		} else { | ||||||
| 			throw new error.ValidationError("Only Let'sEncrypt certificates can be renewed"); | 			throw new error.ValidationError( | ||||||
|  | 				"Only Let'sEncrypt certificates can be renewed", | ||||||
|  | 			); | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| @@ -899,7 +998,10 @@ const internalCertificate = { | |||||||
| 			"--disable-hook-validation", | 			"--disable-hook-validation", | ||||||
| 		]; | 		]; | ||||||
|  |  | ||||||
| 		const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); | 		const adds = internalCertificate.getAdditionalCertbotArgs( | ||||||
|  | 			certificate.id, | ||||||
|  | 			certificate.meta.dns_provider, | ||||||
|  | 		); | ||||||
| 		args.push(...adds.args); | 		args.push(...adds.args); | ||||||
|  |  | ||||||
| 		logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); | 		logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); | ||||||
| @@ -938,7 +1040,10 @@ const internalCertificate = { | |||||||
| 			"--no-random-sleep-on-renew", | 			"--no-random-sleep-on-renew", | ||||||
| 		]; | 		]; | ||||||
|  |  | ||||||
| 		const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); | 		const adds = internalCertificate.getAdditionalCertbotArgs( | ||||||
|  | 			certificate.id, | ||||||
|  | 			certificate.meta.dns_provider, | ||||||
|  | 		); | ||||||
| 		args.push(...adds.args); | 		args.push(...adds.args); | ||||||
|  |  | ||||||
| 		logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); | 		logger.info(`Command: ${certbotCommand} ${args ? args.join(" ") : ""}`); | ||||||
| @@ -978,7 +1083,9 @@ const internalCertificate = { | |||||||
|  |  | ||||||
| 		try { | 		try { | ||||||
| 			const result = await utils.execFile(certbotCommand, args, adds.opts); | 			const result = await utils.execFile(certbotCommand, args, adds.opts); | ||||||
| 			await utils.exec(`rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`); | 			await utils.exec( | ||||||
|  | 				`rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`, | ||||||
|  | 			); | ||||||
| 			logger.info(result); | 			logger.info(result); | ||||||
| 			return result; | 			return result; | ||||||
| 		} catch (err) { | 		} catch (err) { | ||||||
| @@ -995,7 +1102,10 @@ const internalCertificate = { | |||||||
| 	 */ | 	 */ | ||||||
| 	hasLetsEncryptSslCerts: (certificate) => { | 	hasLetsEncryptSslCerts: (certificate) => { | ||||||
| 		const letsencryptPath = internalCertificate.getLiveCertPath(certificate.id); | 		const letsencryptPath = internalCertificate.getLiveCertPath(certificate.id); | ||||||
| 		return fs.existsSync(`${letsencryptPath}/fullchain.pem`) && fs.existsSync(`${letsencryptPath}/privkey.pem`); | 		return ( | ||||||
|  | 			fs.existsSync(`${letsencryptPath}/fullchain.pem`) && | ||||||
|  | 			fs.existsSync(`${letsencryptPath}/privkey.pem`) | ||||||
|  | 		); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| @@ -1009,15 +1119,24 @@ const internalCertificate = { | |||||||
| 	disableInUseHosts: async (inUseResult) => { | 	disableInUseHosts: async (inUseResult) => { | ||||||
| 		if (inUseResult?.total_count) { | 		if (inUseResult?.total_count) { | ||||||
| 			if (inUseResult?.proxy_hosts.length) { | 			if (inUseResult?.proxy_hosts.length) { | ||||||
| 				await internalNginx.bulkDeleteConfigs("proxy_host", inUseResult.proxy_hosts); | 				await internalNginx.bulkDeleteConfigs( | ||||||
|  | 					"proxy_host", | ||||||
|  | 					inUseResult.proxy_hosts, | ||||||
|  | 				); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if (inUseResult?.redirection_hosts.length) { | 			if (inUseResult?.redirection_hosts.length) { | ||||||
| 				await internalNginx.bulkDeleteConfigs("redirection_host", inUseResult.redirection_hosts); | 				await internalNginx.bulkDeleteConfigs( | ||||||
|  | 					"redirection_host", | ||||||
|  | 					inUseResult.redirection_hosts, | ||||||
|  | 				); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if (inUseResult?.dead_hosts.length) { | 			if (inUseResult?.dead_hosts.length) { | ||||||
| 				await internalNginx.bulkDeleteConfigs("dead_host", inUseResult.dead_hosts); | 				await internalNginx.bulkDeleteConfigs( | ||||||
|  | 					"dead_host", | ||||||
|  | 					inUseResult.dead_hosts, | ||||||
|  | 				); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
| @@ -1033,36 +1152,56 @@ const internalCertificate = { | |||||||
| 	enableInUseHosts: async (inUseResult) => { | 	enableInUseHosts: async (inUseResult) => { | ||||||
| 		if (inUseResult.total_count) { | 		if (inUseResult.total_count) { | ||||||
| 			if (inUseResult.proxy_hosts.length) { | 			if (inUseResult.proxy_hosts.length) { | ||||||
| 				await internalNginx.bulkGenerateConfigs("proxy_host", inUseResult.proxy_hosts); | 				await internalNginx.bulkGenerateConfigs( | ||||||
|  | 					"proxy_host", | ||||||
|  | 					inUseResult.proxy_hosts, | ||||||
|  | 				); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if (inUseResult.redirection_hosts.length) { | 			if (inUseResult.redirection_hosts.length) { | ||||||
| 				await internalNginx.bulkGenerateConfigs("redirection_host", inUseResult.redirection_hosts); | 				await internalNginx.bulkGenerateConfigs( | ||||||
|  | 					"redirection_host", | ||||||
|  | 					inUseResult.redirection_hosts, | ||||||
|  | 				); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if (inUseResult.dead_hosts.length) { | 			if (inUseResult.dead_hosts.length) { | ||||||
| 				await internalNginx.bulkGenerateConfigs("dead_host", inUseResult.dead_hosts); | 				await internalNginx.bulkGenerateConfigs( | ||||||
|  | 					"dead_host", | ||||||
|  | 					inUseResult.dead_hosts, | ||||||
|  | 				); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	testHttpsChallenge: async (access, domains) => { | 	/** | ||||||
|  | 	 * | ||||||
|  | 	 * @param   {Object}    payload | ||||||
|  | 	 * @param   {string[]}  payload.domains | ||||||
|  | 	 * @returns | ||||||
|  | 	 */ | ||||||
|  | 	testHttpsChallenge: async (access, payload) => { | ||||||
| 		await access.can("certificates:list"); | 		await access.can("certificates:list"); | ||||||
|  |  | ||||||
| 		if (!isArray(domains)) { |  | ||||||
| 			throw new error.InternalValidationError("Domains must be an array of strings"); |  | ||||||
| 		} |  | ||||||
| 		if (domains.length === 0) { |  | ||||||
| 			throw new error.InternalValidationError("No domains provided"); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// 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) { | 		const results = {}; | ||||||
|  | 		for (const domain of payload.domains) { | ||||||
|  | 			results[domain] = await internalCertificate.performTestForDomain(domain); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Remove the test challenge file | ||||||
|  | 		fs.unlinkSync(testChallengeFile); | ||||||
|  |  | ||||||
|  | 		return results; | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	performTestForDomain: async (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`; | ||||||
| @@ -1076,7 +1215,10 @@ 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, | ||||||
|  | 				(res) => { | ||||||
| 					let responseBody = ""; | 					let responseBody = ""; | ||||||
|  |  | ||||||
| 					res.on("data", (chunk) => { | 					res.on("data", (chunk) => { | ||||||
| @@ -1107,7 +1249,8 @@ const internalCertificate = { | |||||||
| 							resolve(undefined); | 							resolve(undefined); | ||||||
| 						} | 						} | ||||||
| 					}); | 					}); | ||||||
| 				}); | 				}, | ||||||
|  | 			); | ||||||
|  |  | ||||||
| 			// Make sure to write the request body. | 			// Make sure to write the request body. | ||||||
| 			req.write(formBody); | 			req.write(formBody); | ||||||
| @@ -1128,7 +1271,10 @@ const internalCertificate = { | |||||||
| 			); | 			); | ||||||
| 			return `other:${result.error.msg}`; | 			return `other:${result.error.msg}`; | ||||||
| 		} | 		} | ||||||
| 			if (`${result.responsecode}` === "200" && result.htmlresponse === "Success") { | 		if ( | ||||||
|  | 			`${result.responsecode}` === "200" && | ||||||
|  | 			result.htmlresponse === "Success" | ||||||
|  | 		) { | ||||||
| 			// Server exists and has responded with the correct data | 			// Server exists and has responded with the correct data | ||||||
| 			return "ok"; | 			return "ok"; | ||||||
| 		} | 		} | ||||||
| @@ -1142,15 +1288,20 @@ const internalCertificate = { | |||||||
| 		} | 		} | ||||||
| 		if (`${result.responsecode}` === "404") { | 		if (`${result.responsecode}` === "404") { | ||||||
| 			// Server exists but responded with a 404 | 			// Server exists but responded with a 404 | ||||||
| 				logger.info(`HTTP challenge test failed for domain ${domain} because code 404 was returned`); | 			logger.info( | ||||||
|  | 				`HTTP challenge test failed for domain ${domain} because code 404 was returned`, | ||||||
|  | 			); | ||||||
| 			return "404"; | 			return "404"; | ||||||
| 		} | 		} | ||||||
| 		if ( | 		if ( | ||||||
| 			`${result.responsecode}` === "0" || | 			`${result.responsecode}` === "0" || | ||||||
| 				(typeof result.reason === "string" && result.reason.toLowerCase() === "host unavailable") | 			(typeof result.reason === "string" && | ||||||
|  | 				result.reason.toLowerCase() === "host unavailable") | ||||||
| 		) { | 		) { | ||||||
| 			// Server does not exist at domain | 			// Server does not exist at domain | ||||||
| 				logger.info(`HTTP challenge test failed for domain ${domain} the host was not found`); | 			logger.info( | ||||||
|  | 				`HTTP challenge test failed for domain ${domain} the host was not found`, | ||||||
|  | 			); | ||||||
| 			return "no-host"; | 			return "no-host"; | ||||||
| 		} | 		} | ||||||
| 		// Other errors | 		// Other errors | ||||||
| @@ -1158,18 +1309,6 @@ const internalCertificate = { | |||||||
| 			`HTTP challenge test failed for domain ${domain} because code ${result.responsecode} was returned`, | 			`HTTP challenge test failed for domain ${domain} because code ${result.responsecode} was returned`, | ||||||
| 		); | 		); | ||||||
| 		return `other:${result.responsecode}`; | 		return `other:${result.responsecode}`; | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		const results = {}; |  | ||||||
|  |  | ||||||
| 		for (const domain of domains) { |  | ||||||
| 			results[domain] = await performTestForDomain(domain); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Remove the test challenge file |  | ||||||
| 		fs.unlinkSync(testChallengeFile); |  | ||||||
|  |  | ||||||
| 		return results; |  | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	getAdditionalCertbotArgs: (certificate_id, dns_provider) => { | 	getAdditionalCertbotArgs: (certificate_id, dns_provider) => { | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import batchflow from "batchflow"; | import batchflow from "batchflow"; | ||||||
| import dnsPlugins from "../global/certbot-dns-plugins.json" with { type: "json" }; | import dnsPlugins from "../certbot/dns-plugins.json" with { type: "json" }; | ||||||
| import { certbot as logger } from "../logger.js"; | import { certbot as logger } from "../logger.js"; | ||||||
| import errs from "./error.js"; | import errs from "./error.js"; | ||||||
| import utils from "./utils.js"; | import utils from "./utils.js"; | ||||||
| @@ -8,7 +8,7 @@ const CERTBOT_VERSION_REPLACEMENT = "$(certbot --version | grep -Eo '[0-9](\\.[0 | |||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Installs a cerbot plugin given the key for the object from |  * Installs a cerbot plugin given the key for the object from | ||||||
|  * ../global/certbot-dns-plugins.json |  * ../certbot/dns-plugins.json | ||||||
|  * |  * | ||||||
|  * @param   {string}  pluginKey |  * @param   {string}  pluginKey | ||||||
|  * @returns {Object} |  * @returns {Object} | ||||||
|   | |||||||
| @@ -24,16 +24,21 @@ const apiValidator = async (schema, payload /*, description*/) => { | |||||||
| 		throw new errs.ValidationError("Payload is undefined"); | 		throw new errs.ValidationError("Payload is undefined"); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  |  | ||||||
| 	const validate = ajv.compile(schema); | 	const validate = ajv.compile(schema); | ||||||
|  |  | ||||||
| 	const valid = validate(payload); | 	const valid = validate(payload); | ||||||
|  |  | ||||||
|  |  | ||||||
| 	if (valid && !validate.errors) { | 	if (valid && !validate.errors) { | ||||||
| 		return payload; | 		return payload; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| 	const message = ajv.errorsText(validate.errors); | 	const message = ajv.errorsText(validate.errors); | ||||||
| 	const err = new errs.ValidationError(message); | 	const err = new errs.ValidationError(message); | ||||||
| 	err.debug = [validate.errors, payload]; | 	err.debug = {validationErrors: validate.errors, payload}; | ||||||
| 	throw err; | 	throw err; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import express from "express"; | import express from "express"; | ||||||
| import dnsPlugins from "../../global/certbot-dns-plugins.json" with { type: "json" }; | import dnsPlugins from "../../certbot/dns-plugins.json" with { type: "json" }; | ||||||
| import internalCertificate from "../../internal/certificate.js"; | import internalCertificate from "../../internal/certificate.js"; | ||||||
| import errs from "../../lib/error.js"; | import errs from "../../lib/error.js"; | ||||||
| import jwtdecode from "../../lib/express/jwt-decode.js"; | import jwtdecode from "../../lib/express/jwt-decode.js"; | ||||||
| @@ -44,11 +44,18 @@ router | |||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
| 				{ | 				{ | ||||||
| 					expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, | 					expand: | ||||||
|  | 						typeof req.query.expand === "string" | ||||||
|  | 							? req.query.expand.split(",") | ||||||
|  | 							: null, | ||||||
| 					query: typeof req.query.query === "string" ? req.query.query : null, | 					query: typeof req.query.query === "string" ? req.query.query : null, | ||||||
| 				}, | 				}, | ||||||
| 			); | 			); | ||||||
| 			const rows = await internalCertificate.getAll(res.locals.access, data.expand, data.query); | 			const rows = await internalCertificate.getAll( | ||||||
|  | 				res.locals.access, | ||||||
|  | 				data.expand, | ||||||
|  | 				data.query, | ||||||
|  | 			); | ||||||
| 			res.status(200).send(rows); | 			res.status(200).send(rows); | ||||||
| 		} catch (err) { | 		} catch (err) { | ||||||
| 			logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); | 			logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); | ||||||
| @@ -63,9 +70,15 @@ router | |||||||
| 	 */ | 	 */ | ||||||
| 	.post(async (req, res, next) => { | 	.post(async (req, res, next) => { | ||||||
| 		try { | 		try { | ||||||
| 			const payload = await apiValidator(getValidationSchema("/nginx/certificates", "post"), req.body); | 			const payload = await apiValidator( | ||||||
|  | 				getValidationSchema("/nginx/certificates", "post"), | ||||||
|  | 				req.body, | ||||||
|  | 			); | ||||||
| 			req.setTimeout(900000); // 15 minutes timeout | 			req.setTimeout(900000); // 15 minutes timeout | ||||||
| 			const result = await internalCertificate.create(res.locals.access, payload); | 			const result = await internalCertificate.create( | ||||||
|  | 				res.locals.access, | ||||||
|  | 				payload, | ||||||
|  | 			); | ||||||
| 			res.status(201).send(result); | 			res.status(201).send(result); | ||||||
| 		} catch (err) { | 		} catch (err) { | ||||||
| 			logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); | 			logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); | ||||||
| @@ -120,20 +133,21 @@ router | |||||||
| 	.all(jwtdecode()) | 	.all(jwtdecode()) | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| 	 * GET /api/nginx/certificates/test-http | 	 * POST /api/nginx/certificates/test-http | ||||||
| 	 * | 	 * | ||||||
| 	 * Test HTTP challenge for domains | 	 * Test HTTP challenge for domains | ||||||
| 	 */ | 	 */ | ||||||
| 	.get(async (req, res, next) => { | 	.post(async (req, res, next) => { | ||||||
| 		if (req.query.domains === undefined) { |  | ||||||
| 			next(new errs.ValidationError("Domains are required as query parameters")); |  | ||||||
| 			return; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		try { | 		try { | ||||||
|  | 			const payload = await apiValidator( | ||||||
|  | 				getValidationSchema("/nginx/certificates/test-http", "post"), | ||||||
|  | 				req.body, | ||||||
|  | 			); | ||||||
|  | 			req.setTimeout(60000); // 1 minute timeout | ||||||
|  |  | ||||||
| 			const result = await internalCertificate.testHttpsChallenge( | 			const result = await internalCertificate.testHttpsChallenge( | ||||||
| 				res.locals.access, | 				res.locals.access, | ||||||
| 				JSON.parse(req.query.domains), | 				payload, | ||||||
| 			); | 			); | ||||||
| 			res.status(200).send(result); | 			res.status(200).send(result); | ||||||
| 		} catch (err) { | 		} catch (err) { | ||||||
| @@ -142,7 +156,6 @@ router | |||||||
| 		} | 		} | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Validate Certs before saving |  * Validate Certs before saving | ||||||
|  * |  * | ||||||
| @@ -211,7 +224,10 @@ router | |||||||
| 				}, | 				}, | ||||||
| 				{ | 				{ | ||||||
| 					certificate_id: req.params.certificate_id, | 					certificate_id: req.params.certificate_id, | ||||||
| 					expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null, | 					expand: | ||||||
|  | 						typeof req.query.expand === "string" | ||||||
|  | 							? req.query.expand.split(",") | ||||||
|  | 							: null, | ||||||
| 				}, | 				}, | ||||||
| 			); | 			); | ||||||
| 			const row = await internalCertificate.get(res.locals.access, { | 			const row = await internalCertificate.get(res.locals.access, { | ||||||
|   | |||||||
| @@ -65,7 +65,7 @@ router | |||||||
| 			const result = await internalProxyHost.create(res.locals.access, payload); | 			const result = await internalProxyHost.create(res.locals.access, payload); | ||||||
| 			res.status(201).send(result); | 			res.status(201).send(result); | ||||||
| 		} catch (err) { | 		} catch (err) { | ||||||
| 			logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); | 			logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err} ${JSON.stringify(err.debug, null, 2)}`); | ||||||
| 			next(err); | 			next(err); | ||||||
| 		} | 		} | ||||||
| 	}); | 	}); | ||||||
|   | |||||||
							
								
								
									
										23
									
								
								backend/schema/components/dns-providers-list.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								backend/schema/components/dns-providers-list.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | { | ||||||
|  | 	"type": "array", | ||||||
|  | 	"description": "DNS Providers list", | ||||||
|  | 	"items": { | ||||||
|  | 		"type": "object", | ||||||
|  | 		"required": ["id", "name", "credentials"], | ||||||
|  | 		"additionalProperties": false, | ||||||
|  | 		"properties": { | ||||||
|  | 			"id": { | ||||||
|  | 				"type": "string", | ||||||
|  | 				"description": "Unique identifier for the DNS provider, matching the python package" | ||||||
|  | 			}, | ||||||
|  | 			"name": { | ||||||
|  | 				"type": "string", | ||||||
|  | 				"description": "Human-readable name of the DNS provider" | ||||||
|  | 			}, | ||||||
|  | 			"credentials": { | ||||||
|  | 				"type": "string", | ||||||
|  | 				"description": "Instructions on how to format the credentials for this DNS provider" | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,52 @@ | |||||||
|  | { | ||||||
|  | 	"operationId": "getDNSProviders", | ||||||
|  | 	"summary": "Get DNS Providers for Certificates", | ||||||
|  | 	"tags": [ | ||||||
|  | 		"Certificates" | ||||||
|  | 	], | ||||||
|  | 	"security": [ | ||||||
|  | 		{ | ||||||
|  | 			"BearerAuth": [ | ||||||
|  | 				"certificates" | ||||||
|  | 			] | ||||||
|  | 		} | ||||||
|  | 	], | ||||||
|  | 	"responses": { | ||||||
|  | 		"200": { | ||||||
|  | 			"description": "200 response", | ||||||
|  | 			"content": { | ||||||
|  | 				"application/json": { | ||||||
|  | 					"examples": { | ||||||
|  | 						"default": { | ||||||
|  | 							"value": [ | ||||||
|  | 								{ | ||||||
|  | 									"id": "vultr", | ||||||
|  | 									"name": "Vultr", | ||||||
|  | 									"credentials": "dns_vultr_key = YOUR_VULTR_API_KEY" | ||||||
|  | 								}, | ||||||
|  | 								{ | ||||||
|  | 									"id": "websupport", | ||||||
|  | 									"name": "Websupport.sk", | ||||||
|  | 									"credentials": "dns_websupport_identifier = <api_key>\ndns_websupport_secret_key = <secret>" | ||||||
|  | 								}, | ||||||
|  | 								{ | ||||||
|  | 									"id": "wedos", | ||||||
|  | 									"name": "Wedos", | ||||||
|  | 									"credentials": "dns_wedos_user = <wedos_registration>\ndns_wedos_auth = <wapi_password>" | ||||||
|  | 								}, | ||||||
|  | 								{ | ||||||
|  | 									"id": "zoneedit", | ||||||
|  | 									"name": "ZoneEdit", | ||||||
|  | 									"credentials": "dns_zoneedit_user = <login-user-id>\ndns_zoneedit_token = <dyn-authentication-token>" | ||||||
|  | 								} | ||||||
|  | 							] | ||||||
|  | 						} | ||||||
|  | 					}, | ||||||
|  | 					"schema": { | ||||||
|  | 						"$ref": "../../../../components/dns-providers-list.json" | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -7,18 +7,24 @@ | |||||||
| 			"BearerAuth": ["certificates"] | 			"BearerAuth": ["certificates"] | ||||||
| 		} | 		} | ||||||
| 	], | 	], | ||||||
| 	"parameters": [ | 	"requestBody": { | ||||||
| 		{ | 		"description": "Test Payload", | ||||||
| 			"in": "query", |  | ||||||
| 			"name": "domains", |  | ||||||
| 			"description": "Expansions", |  | ||||||
| 		"required": true, | 		"required": true, | ||||||
|  | 		"content": { | ||||||
|  | 			"application/json": { | ||||||
| 				"schema": { | 				"schema": { | ||||||
| 				"type": "string", | 					"type": "object", | ||||||
| 				"example": "[\"test.example.ord\",\"test.example.com\",\"nonexistent.example.com\"]" | 					"additionalProperties": false, | ||||||
|  | 					"required": ["domains"], | ||||||
|  | 					"properties": { | ||||||
|  | 						"domains": { | ||||||
|  | 							"$ref": "../../../../common.json#/properties/domain_names" | ||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| 	], | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
| 	"responses": { | 	"responses": { | ||||||
| 		"200": { | 		"200": { | ||||||
| 			"description": "200 response", | 			"description": "200 response", | ||||||
| @@ -61,14 +61,19 @@ | |||||||
| 				"$ref": "./paths/nginx/certificates/post.json" | 				"$ref": "./paths/nginx/certificates/post.json" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  | 		"/nginx/certificates/dns-providers": { | ||||||
|  | 			"get": { | ||||||
|  | 				"$ref": "./paths/nginx/certificates/dns-providers/get.json" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
| 		"/nginx/certificates/validate": { | 		"/nginx/certificates/validate": { | ||||||
| 			"post": { | 			"post": { | ||||||
| 				"$ref": "./paths/nginx/certificates/validate/post.json" | 				"$ref": "./paths/nginx/certificates/validate/post.json" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 		"/nginx/certificates/test-http": { | 		"/nginx/certificates/test-http": { | ||||||
| 			"get": { | 			"post": { | ||||||
| 				"$ref": "./paths/nginx/certificates/test-http/get.json" | 				"$ref": "./paths/nginx/certificates/test-http/post.json" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 		"/nginx/certificates/{certID}": { | 		"/nginx/certificates/{certID}": { | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| #!/usr/bin/node | #!/usr/bin/node | ||||||
|  |  | ||||||
| // Usage: | // Usage: | ||||||
| //   Install all plugins defined in `certbot-dns-plugins.json`: | //   Install all plugins defined in `../certbot/dns-plugins.json`: | ||||||
| //    ./install-certbot-plugins | //    ./install-certbot-plugins | ||||||
| //   Install one or more specific plugins: | //   Install one or more specific plugins: | ||||||
| //    ./install-certbot-plugins route53 cloudflare | //    ./install-certbot-plugins route53 cloudflare | ||||||
| @@ -10,20 +10,21 @@ | |||||||
| //    docker exec npm_core /command/s6-setuidgid 1000:1000 bash -c "/app/scripts/install-certbot-plugins" | //    docker exec npm_core /command/s6-setuidgid 1000:1000 bash -c "/app/scripts/install-certbot-plugins" | ||||||
| // | // | ||||||
|  |  | ||||||
| import dnsPlugins from "../global/certbot-dns-plugins.json" with { type: "json" }; | import batchflow from "batchflow"; | ||||||
|  | import dnsPlugins from "../certbot/dns-plugins.json" with { type: "json" }; | ||||||
| import { installPlugin } from "../lib/certbot.js"; | import { installPlugin } from "../lib/certbot.js"; | ||||||
| import { certbot as logger } from "../logger.js"; | import { certbot as logger } from "../logger.js"; | ||||||
| import batchflow from "batchflow"; |  | ||||||
|  |  | ||||||
| let hasErrors = false; | let hasErrors = false; | ||||||
| let failingPlugins = []; | const failingPlugins = []; | ||||||
|  |  | ||||||
| let pluginKeys = Object.keys(dnsPlugins); | let pluginKeys = Object.keys(dnsPlugins); | ||||||
| if (process.argv.length > 2) { | if (process.argv.length > 2) { | ||||||
| 	pluginKeys = process.argv.slice(2); | 	pluginKeys = process.argv.slice(2); | ||||||
| } | } | ||||||
|  |  | ||||||
| batchflow(pluginKeys).sequential() | batchflow(pluginKeys) | ||||||
|  | 	.sequential() | ||||||
| 	.each((i, pluginKey, next) => { | 	.each((i, pluginKey, next) => { | ||||||
| 		installPlugin(pluginKey) | 		installPlugin(pluginKey) | ||||||
| 			.then(() => { | 			.then(() => { | ||||||
| @@ -40,10 +41,14 @@ batchflow(pluginKeys).sequential() | |||||||
| 	}) | 	}) | ||||||
| 	.end(() => { | 	.end(() => { | ||||||
| 		if (hasErrors) { | 		if (hasErrors) { | ||||||
| 			logger.error('Some plugins failed to install. Please check the logs above. Failing plugins: ' + '\n - ' + failingPlugins.join('\n - ')); | 			logger.error( | ||||||
|  | 				"Some plugins failed to install. Please check the logs above. Failing plugins: " + | ||||||
|  | 					"\n - " + | ||||||
|  | 					failingPlugins.join("\n - "), | ||||||
|  | 			); | ||||||
| 			process.exit(1); | 			process.exit(1); | ||||||
| 		} else { | 		} else { | ||||||
| 			logger.complete('Plugins installed successfully'); | 			logger.complete("Plugins installed successfully"); | ||||||
| 			process.exit(0); | 			process.exit(0); | ||||||
| 		} | 		} | ||||||
| 	}); | 	}); | ||||||
|   | |||||||
| @@ -39,7 +39,6 @@ EXPOSE 80 81 443 | |||||||
|  |  | ||||||
| COPY backend       /app | COPY backend       /app | ||||||
| COPY frontend/dist /app/frontend | COPY frontend/dist /app/frontend | ||||||
| COPY global        /app/global |  | ||||||
|  |  | ||||||
| WORKDIR /app | WORKDIR /app | ||||||
| RUN yarn install \ | RUN yarn install \ | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| # WARNING: This is a DEVELOPMENT docker-compose file, it should not be used for production. | # WARNING: This is a DEVELOPMENT docker-compose file, it should not be used for production. | ||||||
| services: | services: | ||||||
|  |  | ||||||
|   fullstack: |   fullstack: | ||||||
|     image: npm2dev:core |     image: npm2dev:core | ||||||
|     container_name: npm2dev.core |     container_name: npm2dev.core | ||||||
| @@ -23,9 +22,9 @@ services: | |||||||
|       PGID: 1000 |       PGID: 1000 | ||||||
|       FORCE_COLOR: 1 |       FORCE_COLOR: 1 | ||||||
|       # specifically for dev: |       # specifically for dev: | ||||||
|       DEBUG: 'true' |       DEBUG: "true" | ||||||
|       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' | ||||||
| @@ -33,26 +32,25 @@ services: | |||||||
|       # DB_MYSQL_PASSWORD: 'npm' |       # DB_MYSQL_PASSWORD: 'npm' | ||||||
|       # DB_MYSQL_NAME: 'npm' |       # DB_MYSQL_NAME: 'npm' | ||||||
|       # db-postgres: |       # db-postgres: | ||||||
|       DB_POSTGRES_HOST: 'db-postgres' |       DB_POSTGRES_HOST: "db-postgres" | ||||||
|       DB_POSTGRES_PORT: '5432' |       DB_POSTGRES_PORT: "5432" | ||||||
|       DB_POSTGRES_USER: 'npm' |       DB_POSTGRES_USER: "npm" | ||||||
|       DB_POSTGRES_PASSWORD: 'npmpass' |       DB_POSTGRES_PASSWORD: "npmpass" | ||||||
|       DB_POSTGRES_NAME: 'npm' |       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: | ||||||
|       LE_SERVER: 'https://ca.internal/acme/acme/directory' |       LE_SERVER: "https://ca.internal/acme/acme/directory" | ||||||
|       REQUESTS_CA_BUNDLE: '/etc/ssl/certs/NginxProxyManager.crt' |       REQUESTS_CA_BUNDLE: "/etc/ssl/certs/NginxProxyManager.crt" | ||||||
|     volumes: |     volumes: | ||||||
|       - npm_data:/data |       - npm_data:/data | ||||||
|       - le_data:/etc/letsencrypt |       - le_data:/etc/letsencrypt | ||||||
|       - './dev/resolv.conf:/etc/resolv.conf:ro' |       - "./dev/resolv.conf:/etc/resolv.conf:ro" | ||||||
|       - ../backend:/app |       - ../backend:/app | ||||||
|       - ../frontend:/app/frontend |       - ../frontend:/frontend | ||||||
|       - ../global:/app/global |       - "/etc/localtime:/etc/localtime:ro" | ||||||
|       - '/etc/localtime:/etc/localtime:ro' |  | ||||||
|     healthcheck: |     healthcheck: | ||||||
|       test: [ "CMD", "/usr/bin/check-health" ] |       test: ["CMD", "/usr/bin/check-health"] | ||||||
|       interval: 10s |       interval: 10s | ||||||
|       timeout: 3s |       timeout: 3s | ||||||
|     depends_on: |     depends_on: | ||||||
| @@ -72,13 +70,13 @@ services: | |||||||
|       - nginx_proxy_manager |       - nginx_proxy_manager | ||||||
|     environment: |     environment: | ||||||
|       TZ: "${TZ:-Australia/Brisbane}" |       TZ: "${TZ:-Australia/Brisbane}" | ||||||
|       MYSQL_ROOT_PASSWORD: 'npm' |       MYSQL_ROOT_PASSWORD: "npm" | ||||||
|       MYSQL_DATABASE: 'npm' |       MYSQL_DATABASE: "npm" | ||||||
|       MYSQL_USER: 'npm' |       MYSQL_USER: "npm" | ||||||
|       MYSQL_PASSWORD: 'npm' |       MYSQL_PASSWORD: "npm" | ||||||
|     volumes: |     volumes: | ||||||
|       - db_data:/var/lib/mysql |       - db_data:/var/lib/mysql | ||||||
|       - '/etc/localtime:/etc/localtime:ro' |       - "/etc/localtime:/etc/localtime:ro" | ||||||
|  |  | ||||||
|   db-postgres: |   db-postgres: | ||||||
|     image: postgres:latest |     image: postgres:latest | ||||||
| @@ -86,9 +84,9 @@ services: | |||||||
|     networks: |     networks: | ||||||
|       - nginx_proxy_manager |       - nginx_proxy_manager | ||||||
|     environment: |     environment: | ||||||
|       POSTGRES_USER: 'npm' |       POSTGRES_USER: "npm" | ||||||
|       POSTGRES_PASSWORD: 'npmpass' |       POSTGRES_PASSWORD: "npmpass" | ||||||
|       POSTGRES_DB: 'npm' |       POSTGRES_DB: "npm" | ||||||
|     volumes: |     volumes: | ||||||
|       - psql_data:/var/lib/postgresql/data |       - psql_data:/var/lib/postgresql/data | ||||||
|       - ./ci/postgres:/docker-entrypoint-initdb.d |       - ./ci/postgres:/docker-entrypoint-initdb.d | ||||||
| @@ -97,8 +95,8 @@ services: | |||||||
|     image: jc21/testca |     image: jc21/testca | ||||||
|     container_name: npm2dev.stepca |     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" | ||||||
|     networks: |     networks: | ||||||
|       nginx_proxy_manager: |       nginx_proxy_manager: | ||||||
|         aliases: |         aliases: | ||||||
| @@ -119,7 +117,7 @@ services: | |||||||
|       - 3082:80 |       - 3082:80 | ||||||
|     environment: |     environment: | ||||||
|       URL: "http://npm:81/api/schema" |       URL: "http://npm:81/api/schema" | ||||||
|       PORT: '80' |       PORT: "80" | ||||||
|     depends_on: |     depends_on: | ||||||
|       - fullstack |       - fullstack | ||||||
|  |  | ||||||
| @@ -127,9 +125,9 @@ services: | |||||||
|     image: ubuntu/squid |     image: ubuntu/squid | ||||||
|     container_name: npm2dev.squid |     container_name: npm2dev.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" | ||||||
|       - '/etc/localtime:/etc/localtime:ro' |       - "/etc/localtime:/etc/localtime:ro" | ||||||
|     networks: |     networks: | ||||||
|       - nginx_proxy_manager |       - nginx_proxy_manager | ||||||
|     ports: |     ports: | ||||||
| @@ -139,18 +137,18 @@ services: | |||||||
|     image: pschiffe/pdns-mysql:4.8 |     image: pschiffe/pdns-mysql:4.8 | ||||||
|     container_name: npm2dev.pdns |     container_name: npm2dev.pdns | ||||||
|     volumes: |     volumes: | ||||||
|       - '/etc/localtime:/etc/localtime:ro' |       - "/etc/localtime:/etc/localtime:ro" | ||||||
|     environment: |     environment: | ||||||
|       PDNS_master: 'yes' |       PDNS_master: "yes" | ||||||
|       PDNS_api: 'yes' |       PDNS_api: "yes" | ||||||
|       PDNS_api_key: 'npm' |       PDNS_api_key: "npm" | ||||||
|       PDNS_webserver: 'yes' |       PDNS_webserver: "yes" | ||||||
|       PDNS_webserver_address: '0.0.0.0' |       PDNS_webserver_address: "0.0.0.0" | ||||||
|       PDNS_webserver_password: 'npm' |       PDNS_webserver_password: "npm" | ||||||
|       PDNS_webserver-allow-from: '127.0.0.0/8,192.0.0.0/8,10.0.0.0/8,172.0.0.0/8' |       PDNS_webserver-allow-from: "127.0.0.0/8,192.0.0.0/8,10.0.0.0/8,172.0.0.0/8" | ||||||
|       PDNS_version_string: 'anonymous' |       PDNS_version_string: "anonymous" | ||||||
|       PDNS_default_ttl: 1500 |       PDNS_default_ttl: 1500 | ||||||
|       PDNS_allow_axfr_ips: '127.0.0.0/8,192.0.0.0/8,10.0.0.0/8,172.0.0.0/8' |       PDNS_allow_axfr_ips: "127.0.0.0/8,192.0.0.0/8,10.0.0.0/8,172.0.0.0/8" | ||||||
|       PDNS_gmysql_host: pdns-db |       PDNS_gmysql_host: pdns-db | ||||||
|       PDNS_gmysql_port: 3306 |       PDNS_gmysql_port: 3306 | ||||||
|       PDNS_gmysql_user: pdns |       PDNS_gmysql_user: pdns | ||||||
| @@ -168,14 +166,14 @@ services: | |||||||
|     image: mariadb |     image: mariadb | ||||||
|     container_name: npm2dev.pdns-db |     container_name: npm2dev.pdns-db | ||||||
|     environment: |     environment: | ||||||
|       MYSQL_ROOT_PASSWORD: 'pdns' |       MYSQL_ROOT_PASSWORD: "pdns" | ||||||
|       MYSQL_DATABASE: 'pdns' |       MYSQL_DATABASE: "pdns" | ||||||
|       MYSQL_USER: 'pdns' |       MYSQL_USER: "pdns" | ||||||
|       MYSQL_PASSWORD: 'pdns' |       MYSQL_PASSWORD: "pdns" | ||||||
|     volumes: |     volumes: | ||||||
|       - 'pdns_mysql:/var/lib/mysql' |       - "pdns_mysql:/var/lib/mysql" | ||||||
|       - '/etc/localtime:/etc/localtime:ro' |       - "/etc/localtime:/etc/localtime:ro" | ||||||
|       - './dev/pdns-db.sql:/docker-entrypoint-initdb.d/01_init.sql:ro' |       - "./dev/pdns-db.sql:/docker-entrypoint-initdb.d/01_init.sql:ro" | ||||||
|     networks: |     networks: | ||||||
|       - nginx_proxy_manager |       - nginx_proxy_manager | ||||||
|  |  | ||||||
| @@ -186,25 +184,25 @@ services: | |||||||
|       context: ../ |       context: ../ | ||||||
|       dockerfile: test/cypress/Dockerfile |       dockerfile: test/cypress/Dockerfile | ||||||
|     environment: |     environment: | ||||||
|       HTTP_PROXY: 'squid:3128' |       HTTP_PROXY: "squid:3128" | ||||||
|       HTTPS_PROXY: 'squid:3128' |       HTTPS_PROXY: "squid:3128" | ||||||
|     volumes: |     volumes: | ||||||
|       - '../test/results:/results' |       - "../test/results:/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 | ||||||
|     networks: |     networks: | ||||||
|       - nginx_proxy_manager |       - nginx_proxy_manager | ||||||
|  |  | ||||||
|   authentik-redis: |   authentik-redis: | ||||||
|     image: 'redis:alpine' |     image: "redis:alpine" | ||||||
|     container_name: npm2dev.authentik-redis |     container_name: npm2dev.authentik-redis | ||||||
|     command: --save 60 1 --loglevel warning |     command: --save 60 1 --loglevel warning | ||||||
|     networks: |     networks: | ||||||
|       - nginx_proxy_manager |       - nginx_proxy_manager | ||||||
|     restart: unless-stopped |     restart: unless-stopped | ||||||
|     healthcheck: |     healthcheck: | ||||||
|       test: [ 'CMD-SHELL', 'redis-cli ping | grep PONG' ] |       test: ["CMD-SHELL", "redis-cli ping | grep PONG"] | ||||||
|       start_period: 20s |       start_period: 20s | ||||||
|       interval: 30s |       interval: 30s | ||||||
|       retries: 5 |       retries: 5 | ||||||
| @@ -246,9 +244,9 @@ services: | |||||||
|     networks: |     networks: | ||||||
|       - nginx_proxy_manager |       - nginx_proxy_manager | ||||||
|     environment: |     environment: | ||||||
|       AUTHENTIK_HOST: 'http://authentik:9000' |       AUTHENTIK_HOST: "http://authentik:9000" | ||||||
|       AUTHENTIK_INSECURE: 'true' |       AUTHENTIK_INSECURE: "true" | ||||||
|       AUTHENTIK_TOKEN: 'wKYZuRcI0ETtb8vWzMCr04oNbhrQUUICy89hSpDln1OEKLjiNEuQ51044Vkp' |       AUTHENTIK_TOKEN: "wKYZuRcI0ETtb8vWzMCr04oNbhrQUUICy89hSpDln1OEKLjiNEuQ51044Vkp" | ||||||
|     restart: unless-stopped |     restart: unless-stopped | ||||||
|     depends_on: |     depends_on: | ||||||
|       - authentik |       - authentik | ||||||
|   | |||||||
| @@ -7,11 +7,11 @@ set -e | |||||||
|  |  | ||||||
| if [ "$DEVELOPMENT" = 'true' ]; then | if [ "$DEVELOPMENT" = 'true' ]; then | ||||||
| 	. /usr/bin/common.sh | 	. /usr/bin/common.sh | ||||||
| 	cd /app/frontend || exit 1 | 	cd /frontend || exit 1 | ||||||
| 	HOME=$NPMHOME | 	HOME=$NPMHOME | ||||||
| 	export HOME | 	export HOME | ||||||
| 	mkdir -p /app/frontend/dist | 	mkdir -p /frontend/dist | ||||||
| 	chown -R "$PUID:$PGID" /app/frontend/dist | 	chown -R "$PUID:$PGID" /frontend/dist | ||||||
|  |  | ||||||
| 	log_info 'Starting frontend ...' | 	log_info 'Starting frontend ...' | ||||||
| 	s6-setuidgid "$PUID:$PGID" yarn install | 	s6-setuidgid "$PUID:$PGID" yarn install | ||||||
|   | |||||||
| @@ -15,7 +15,6 @@ if hash docker 2>/dev/null; then | |||||||
| 		-e CI=true \ | 		-e CI=true \ | ||||||
| 		-e NODE_OPTIONS=--openssl-legacy-provider \ | 		-e NODE_OPTIONS=--openssl-legacy-provider \ | ||||||
| 		-v "$(pwd)/frontend:/app/frontend" \ | 		-v "$(pwd)/frontend:/app/frontend" \ | ||||||
| 		-v "$(pwd)/global:/app/global" \ |  | ||||||
| 		-w /app/frontend "${DOCKER_IMAGE}" \ | 		-w /app/frontend "${DOCKER_IMAGE}" \ | ||||||
| 		sh -c "yarn install && yarn lint && yarn build && chown -R $(id -u):$(id -g) /app/frontend" | 		sh -c "yarn install && yarn lint && yarn build && chown -R $(id -u):$(id -g) /app/frontend" | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,7 +10,6 @@ docker pull "${TESTING_IMAGE}" | |||||||
| echo -e "${BLUE}❯ ${CYAN}Testing backend ...${RESET}" | echo -e "${BLUE}❯ ${CYAN}Testing backend ...${RESET}" | ||||||
| docker run --rm \ | docker run --rm \ | ||||||
| 	-v "$(pwd)/backend:/app" \ | 	-v "$(pwd)/backend:/app" \ | ||||||
| 	-v "$(pwd)/global:/app/global" \ |  | ||||||
| 	-w /app \ | 	-w /app \ | ||||||
| 	"${TESTING_IMAGE}" \ | 	"${TESTING_IMAGE}" \ | ||||||
| 	sh -c 'yarn install && yarn lint . && rm -rf node_modules' | 	sh -c 'yarn install && yarn lint . && rm -rf node_modules' | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user