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:
Jamie Curnow
2025-10-26 00:28:03 +10:00
parent f2b5b19a83
commit 5b7013b8d5
18 changed files with 553 additions and 306 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
.DS_Store .DS_Store
.idea .idea
.qodo
._* ._*
.vscode .vscode
certbot-help.txt certbot-help.txt

View File

@@ -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:

View File

@@ -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) => {

View File

@@ -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}

View File

@@ -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;
}; };

View File

@@ -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, {

View File

@@ -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);
} }
}); });

View 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"
}
}
}
}

View File

@@ -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"
}
}
}
}
}
}

View File

@@ -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",

View File

@@ -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}": {

View File

@@ -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);
} }
}); });

View File

@@ -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 \

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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'