Merge branch 'develop' into develop

This commit is contained in:
jc21
2026-05-31 22:04:14 +10:00
committed by GitHub
49 changed files with 1110 additions and 749 deletions
+1 -1
View File
@@ -62,7 +62,7 @@ app.use("/", mainRoutes);
app.use((err, req, res, _) => {
const payload = {
error: {
code: err.status,
code: err.status || 500,
message: err.public ? err.message : "Internal Error",
},
};
+9 -1
View File
@@ -335,6 +335,14 @@
"package_name": "certbot-dns-hetzner-cloud",
"version": "~=1.0.4"
},
"hostinger": {
"credentials": "dns_hostinger_api_token = 0123456789abcdef0123456789abcdef",
"dependencies": "",
"full_plugin_name": "dns-hostinger",
"name": "Hostinger.com",
"package_name": "certbot-dns-hostinger",
"version": "~=0.1.3"
},
"hostingnl": {
"credentials": "dns_hostingnl_api_key = 0123456789abcdef0123456789abcdef",
"dependencies": "",
@@ -545,7 +553,7 @@
},
"powerdns": {
"credentials": "dns_powerdns_api_url = https://api.mypowerdns.example.org\ndns_powerdns_api_key = AbCbASsd!@34",
"dependencies": "PyYAML==5.3.1",
"dependencies": "acme=={{certbot-version}}",
"full_plugin_name": "dns-powerdns",
"name": "PowerDNS",
"package_name": "certbot-dns-powerdns",
+7 -3
View File
@@ -614,7 +614,7 @@ const internalCertificate = {
const certificate = await internalCertificate.update(access, {
id: data.id,
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 ? [validations.certificate.cn] : [],
meta: _.clone(row.meta), // Prevent the update method from changing this value that we'll use later
});
@@ -683,13 +683,15 @@ const internalCertificate = {
try {
const result = await utils.execFile("openssl", ["x509", "-in", certificateFile, "-subject", "-noout"]);
// Examples:
// subject=CN = *.jc21.com
// subject=CN = something.example.com
const regex = /(?:subject=)?[^=]+=\s+(\S+)/gim;
// subject=CN=*.jc21.com
const regex = /(?:subject=)?[^=]+=\s*(\S+)/gim;
const match = regex.exec(result);
if (match && typeof match[1] !== "undefined") {
certData.cn = match[1];
certData.cn = match[1].trim();
}
const result2 = await utils.execFile("openssl", ["x509", "-in", certificateFile, "-issuer", "-noout"]);
@@ -779,6 +781,7 @@ const internalCertificate = {
const args = [
"certonly",
"-n", // non-interactive
"--config",
letsencryptConfig,
"--work-dir",
@@ -834,6 +837,7 @@ const internalCertificate = {
const args = [
"certonly",
"-n", // non-interactive
"--config",
letsencryptConfig,
"--work-dir",
+17 -13
View File
@@ -4,8 +4,6 @@ import { certbot as logger } from "../logger.js";
import errs from "./error.js";
import utils from "./utils.js";
const CERTBOT_VERSION_REPLACEMENT = "$(certbot --version | grep -Eo '[0-9](\\.[0-9]+)+')";
/**
* Installs a cerbot plugin given the key for the object from
* ../certbot/dns-plugins.json
@@ -15,24 +13,32 @@ const CERTBOT_VERSION_REPLACEMENT = "$(certbot --version | grep -Eo '[0-9](\\.[0
*/
const installPlugin = async (pluginKey) => {
if (typeof dnsPlugins[pluginKey] === "undefined") {
// throw Error(`Certbot plugin ${pluginKey} not found`);
throw new errs.ItemNotFoundError(pluginKey);
}
const plugin = dnsPlugins[pluginKey];
logger.start(`Installing ${pluginKey}...`);
plugin.version = plugin.version.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT);
plugin.dependencies = plugin.dependencies.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT);
plugin.version = plugin.version.replace(/{{certbot-version}}/g, process.env.CERTBOT_VERSION);
plugin.dependencies = plugin.dependencies.replace(/{{certbot-version}}/g, process.env.CERTBOT_VERSION);
// SETUPTOOLS_USE_DISTUTILS is required for certbot plugins to install correctly
// in new versions of Python
let env = Object.assign({}, process.env, { SETUPTOOLS_USE_DISTUTILS: "stdlib" });
// SETUPTOOLS_USE_DISTUTILS=local uses setuptools' own bundled distutils.
// "stdlib" breaks Python 3.13+ where distutils was removed from the standard library.
let env = Object.assign({}, process.env, { SETUPTOOLS_USE_DISTUTILS: "local" });
if (typeof plugin.env === "object") {
env = Object.assign(env, plugin.env);
}
const cmd = `. /opt/certbot/bin/activate && pip install --no-cache-dir ${plugin.dependencies} ${plugin.package_name}${plugin.version} && deactivate`;
const quotedDeps = plugin.dependencies.trim()
? plugin.dependencies
.trim()
.split(/\s+/)
.filter(Boolean)
.map((d) => `'${d}'`)
.join(" ")
: "";
const cmd = `. /opt/certbot/bin/activate && pip install --no-cache-dir ${quotedDeps} '${plugin.package_name}${plugin.version}' && deactivate`;
return utils
.exec(cmd, { env })
.then((result) => {
@@ -73,9 +79,7 @@ const installPlugins = async (pluginKeys) => {
})
.end(() => {
if (hasErrors) {
reject(
new errs.CommandError("Some plugins failed to install. Please check the logs above", 1),
);
reject(new errs.CommandError("Some plugins failed to install. Please check the logs above", 1));
} else {
resolve();
}
@@ -83,4 +87,4 @@ const installPlugins = async (pluginKeys) => {
});
};
export { installPlugins, installPlugin };
export { installPlugin, installPlugins };
+7
View File
@@ -0,0 +1,7 @@
import chalk from "chalk";
import { debug, express as logger } from "../../logger.js";
export default (req, _res, next) => {
debug(logger, `[${chalk.yellow(req.method.toUpperCase())}] ${chalk.green(req.path)}`);
next();
};
-3
View File
@@ -3,14 +3,12 @@ import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { Liquid } from "liquidjs";
import _ from "lodash";
import { debug, global as logger } from "../logger.js";
import errs from "./error.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const exec = async (cmd, options = {}) => {
debug(logger, "CMD:", cmd);
const { stdout, stderr } = await new Promise((resolve, reject) => {
const child = nodeExec(cmd, options, (isError, stdout, stderr) => {
if (isError) {
@@ -34,7 +32,6 @@ const exec = async (cmd, options = {}) => {
* @returns {Promise}
*/
const execFile = (cmd, args, options) => {
debug(logger, `CMD: ${cmd} ${args ? args.join(" ") : ""}`);
const opts = options || {};
return new Promise((resolve, reject) => {
+14 -6
View File
@@ -15,6 +15,16 @@ Model.knex(db());
const boolFields = ["is_deleted"];
const cleanDomainNames = (domainNames) => {
// Sort domain_names
if (typeof domainNames !== "undefined") {
const newDomainNames = domainNames.filter((name) => name != null);
newDomainNames.sort();
return newDomainNames;
}
return [];
};
class Certificate extends Model {
$beforeInsert() {
this.created_on = now();
@@ -26,7 +36,9 @@ class Certificate extends Model {
}
// Default for domain_names
if (typeof this.domain_names === "undefined") {
if (typeof this.domain_names !== "undefined") {
this.domain_names = cleanDomainNames(this.domain_names);
} else {
this.domain_names = [];
}
@@ -34,16 +46,12 @@ class Certificate extends Model {
if (typeof this.meta === "undefined") {
this.meta = {};
}
this.domain_names.sort();
}
$beforeUpdate() {
this.modified_on = now();
// Sort domain_names
if (typeof this.domain_names !== "undefined") {
this.domain_names.sort();
this.domain_names = cleanDomainNames(this.domain_names);
}
}
+3 -3
View File
@@ -20,13 +20,14 @@
"bcrypt": "^6.0.0",
"better-sqlite3": "^12.10.0",
"body-parser": "^2.2.2",
"chalk": "5.6.2",
"compression": "^1.8.1",
"express": "^5.2.1",
"express-fileupload": "^1.5.2",
"gravatar": "^1.8.2",
"jsonwebtoken": "^9.0.3",
"knex": "3.2.10",
"liquidjs": "10.25.7",
"liquidjs": "10.27.0",
"lodash": "^4.18.1",
"moment": "^2.30.1",
"mysql2": "^3.22.3",
@@ -34,7 +35,7 @@
"objection": "3.1.5",
"otplib": "^13.4.0",
"path": "^0.12.7",
"pg": "^8.20.0",
"pg": "^8.21.0",
"proxy-agent": "^8.0.1",
"signale": "1.4.0",
"sqlite3": "^6.0.1",
@@ -43,7 +44,6 @@
"devDependencies": {
"@apidevtools/swagger-parser": "^12.1.0",
"@biomejs/biome": "^2.4.15",
"chalk": "5.6.2",
"nodemon": "^3.1.14"
},
"signale": {
+59
View File
@@ -0,0 +1,59 @@
import express from "express";
import dnsPlugins from "../certbot/dns-plugins.json" with { type: "json" };
import { installPlugin } from "../lib/certbot.js";
import { debug, express as logger } from "../logger.js";
const router = express.Router({
caseSensitive: true,
strict: true,
mergeParams: true,
});
/**
* ONLY AVAILABLE IN CI ENVIRONMENT!
*/
/**
* /api/ci/certbot-plugins
*/
router
.route("/certbot-plugins")
.options((_, res) => {
res.sendStatus(204);
})
// Return all certbot plugins
.get(async (_req, res, _next) => {
res.status(200).send(dnsPlugins);
});
/**
* /api/ci/certbot-plugins/{plugin}
*/
router
.route("/certbot-plugins/:plugin")
.options((_, res) => {
res.sendStatus(204);
})
// Install a certbot plugin
.post(async (req, res, next) => {
try {
const pluginName = req.params.plugin;
// check if plugin exists
if (!dnsPlugins[pluginName]) {
return res.status(404).send({
error: "Plugin not found",
});
}
await installPlugin(pluginName);
res.status(200).send(true);
} catch (err) {
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
return;
});
export default router;
+10
View File
@@ -1,8 +1,11 @@
import express from "express";
import { isCI } from "../lib/config.js";
import errs from "../lib/error.js";
import logRequest from "../lib/express/log-request.js";
import pjson from "../package.json" with { type: "json" };
import { isSetup } from "../setup.js";
import auditLogRoutes from "./audit-log.js";
import ciRoutes from "./ci.js";
import accessListsRoutes from "./nginx/access_lists.js";
import certificatesHostsRoutes from "./nginx/certificates.js";
import deadHostsRoutes from "./nginx/dead_hosts.js";
@@ -22,6 +25,8 @@ const router = express.Router({
mergeParams: true,
});
router.use(logRequest);
/**
* Health Check
* GET /api
@@ -55,6 +60,11 @@ router.use("/nginx/streams", streamsRoutes);
router.use("/nginx/access-lists", accessListsRoutes);
router.use("/nginx/certificates", certificatesHostsRoutes);
// Only include CI routes if we're in a CI environment
if (isCI()) {
router.use("/ci", ciRoutes);
}
/**
* API 404 for all other routes
*
+2
View File
@@ -10,11 +10,13 @@
{% endif %}
{% if access_list.clients.length > 0 %}
# Access Rules: {{ access_list.clients | size }} total
{% for client in access_list.clients %}
{{client | nginxAccessRule}}
{% endfor %}
deny all;
{% endif %}
# Access checks must...
{% if access_list.satisfy_any == 1 or access_list.satisfy_any == true %}
+31 -31
View File
@@ -1279,10 +1279,10 @@ lazystream@^1.0.0:
dependencies:
readable-stream "^2.0.5"
liquidjs@10.25.7:
version "10.25.7"
resolved "https://registry.yarnpkg.com/liquidjs/-/liquidjs-10.25.7.tgz#75c5765ae42e04da30fba15ae20daab57c4a7a8c"
integrity sha512-rPCjJLiD4eDhQjvv964AeXFC+HbeYBbZrd7Z82Q6hqv1lX7G+5w4SJcKLn9CAAAwHI4aS3dTdo083UB79K3pDA==
liquidjs@10.27.0:
version "10.27.0"
resolved "https://registry.yarnpkg.com/liquidjs/-/liquidjs-10.27.0.tgz#e31dc4c539e1a26aee46c847b4e60a6ede32564a"
integrity sha512-tw/OA59K7aIBlMKIrKlumr37fiZUheShVHXY8cVctWisgY1p9mc5hreOvlreoS0wTiwlWk14Ya7305c2a/Cg5w==
dependencies:
commander "^10.0.0"
@@ -1707,35 +1707,35 @@ path@^0.12.7:
process "^0.11.1"
util "^0.10.3"
pg-cloudflare@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz#386035d4bfcf1a7045b026f8b21acf5353f14d65"
integrity sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==
pg-cloudflare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz#4b4c20e6d8ae531d400730f4804571a8d62f1497"
integrity sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==
pg-connection-string@2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.2.tgz#713d82053de4e2bd166fab70cd4f26ad36aab475"
integrity sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==
pg-connection-string@^2.12.0:
version "2.12.0"
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.12.0.tgz#4084f917902bb2daae3dc1376fe24ac7b4eaccf2"
integrity sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==
pg-connection-string@^2.13.0:
version "2.13.0"
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.13.0.tgz#8678113465a5af3cc977dcb51eadc847b27aa2de"
integrity sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig==
pg-int8@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c"
integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==
pg-pool@^3.13.0:
version "3.13.0"
resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.13.0.tgz#416482e9700e8f80c685a6ae5681697a413c13a3"
integrity sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==
pg-pool@^3.14.0:
version "3.14.0"
resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.14.0.tgz#f35ae4eb846780cad71af24099b3edfa9781ad90"
integrity sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==
pg-protocol@^1.13.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.13.0.tgz#fdaf6d020bca590d58bb991b4b16fc448efe0511"
integrity sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==
pg-protocol@^1.14.0:
version "1.14.0"
resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.14.0.tgz#c1f045b74274b007078c687147141f785f59b8de"
integrity sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA==
pg-types@2.2.0:
version "2.2.0"
@@ -1748,18 +1748,18 @@ pg-types@2.2.0:
postgres-date "~1.0.4"
postgres-interval "^1.1.0"
pg@^8.20.0:
version "8.20.0"
resolved "https://registry.yarnpkg.com/pg/-/pg-8.20.0.tgz#1a274de944cb329fd6dd77a6d371a005ba6b136d"
integrity sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==
pg@^8.21.0:
version "8.21.0"
resolved "https://registry.yarnpkg.com/pg/-/pg-8.21.0.tgz#d7fa2118d960cec5cc7d2b24525f9850dd5932b0"
integrity sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==
dependencies:
pg-connection-string "^2.12.0"
pg-pool "^3.13.0"
pg-protocol "^1.13.0"
pg-connection-string "^2.13.0"
pg-pool "^3.14.0"
pg-protocol "^1.14.0"
pg-types "2.2.0"
pgpass "1.0.5"
optionalDependencies:
pg-cloudflare "^1.3.0"
pg-cloudflare "^1.4.0"
pgpass@1.0.5:
version "1.0.5"
@@ -1887,9 +1887,9 @@ pump@^3.0.0:
once "^1.3.1"
qs@^6.14.0, qs@^6.14.1:
version "6.15.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.15.0.tgz#db8fd5d1b1d2d6b5b33adaf87429805f1909e7b3"
integrity sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==
version "6.15.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.15.2.tgz#fd55426d710403ddccc45e0f9eab16db7727ece9"
integrity sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==
dependencies:
side-channel "^1.1.0"