openship 0.6.6 → 0.6.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bare-FA66N7GC.js +29 -0
- package/dist/{chunk-77QDF6UD.js → chunk-3MP6ACKM.js} +587 -156
- package/dist/{chunk-YIMOCZBZ.js → chunk-5DJOB7IX.js} +3 -3
- package/dist/{chunk-WVAML4H6.js → chunk-5I3COAFH.js} +274 -65
- package/dist/{chunk-5DMWSTOM.js → chunk-5UXHT6ED.js} +940 -224
- package/dist/{chunk-LUKYPVJ4.js → chunk-6GU6JSK6.js} +329 -466
- package/dist/{chunk-RSR7PFIQ.js → chunk-7PEXM2UI.js} +213 -45
- package/dist/{chunk-VTGPEC6B.js → chunk-7YBWXEM6.js} +6 -4
- package/dist/chunk-AHJYP7DG.js +938 -0
- package/dist/chunk-BO6CY4V2.js +265 -0
- package/dist/{chunk-FE7ZZQSE.js → chunk-D3OEMXYP.js} +3 -3
- package/dist/chunk-D6HU4VUZ.js +249 -0
- package/dist/{chunk-LFE5KQ7Y.js → chunk-ENKJ7XIF.js} +152 -57
- package/dist/{chunk-YMMQRE5J.js → chunk-GNOMEMNI.js} +1 -1
- package/dist/{chunk-QVW76WDL.js → chunk-IAMOUDHY.js} +190 -64
- package/dist/{chunk-OBPKYXQL.js → chunk-MUWVC5R4.js} +219 -58
- package/dist/chunk-N5YZMO33.js +111 -0
- package/dist/{chunk-A4GXYE4L.js → chunk-NIYD7MD2.js} +206 -52
- package/dist/{chunk-UVNMQM7W.js → chunk-NPSRGIHV.js} +8 -6
- package/dist/{chunk-2GWYYHOR.js → chunk-NRU2EBYZ.js} +2 -2
- package/dist/{chunk-CBLD2HS5.js → chunk-NUT6HKK6.js} +8 -5
- package/dist/{chunk-ZFJXPCQP.js → chunk-P7RCYELG.js} +4 -3
- package/dist/chunk-R4VDPFLS.js +106 -0
- package/dist/{chunk-HKRQXPTI.js → chunk-R5DC5PEF.js} +1 -1
- package/dist/{chunk-NK5N5LXN.js → chunk-RCMERVBL.js} +24 -9
- package/dist/{chunk-RXQBD4ZY.js → chunk-RHLNVZMS.js} +2 -2
- package/dist/chunk-SK4FFCFR.js +70 -0
- package/dist/{chunk-4I2BM2DT.js → chunk-T46BQOLY.js} +3 -3
- package/dist/{chunk-6FH4AXVY.js → chunk-T6DVUX4F.js} +1 -1
- package/dist/{chunk-PCPVTH6M.js → chunk-TAZZZKYO.js} +6 -4
- package/dist/{chunk-MLYSEZQB.js → chunk-UAWY6X7V.js} +43 -72
- package/dist/{chunk-Q7WGTQQ5.js → chunk-VAI5VOOI.js} +1 -1
- package/dist/{chunk-UJ6JZM65.js → chunk-WEZFTL4V.js} +1325 -513
- package/dist/{chunk-2YM3NI2E.js → chunk-X53JXRS4.js} +38 -44
- package/dist/{cloud-KRTZ2IL5.js → cloud-2CRDKOWQ.js} +1 -1
- package/dist/cloud-REV3DPJH.js +42 -0
- package/dist/{detect-OIE5V3VZ.js → detect-BBFT5GEU.js} +9 -6
- package/dist/{docker-VTR2GK5Y.js → docker-EG3QSE6I.js} +11 -8
- package/dist/{docker-edge-executor-22IBXNYJ.js → docker-edge-executor-E5TOTLMR.js} +8 -6
- package/dist/edge-import-YJVJUGYC.js +56 -0
- package/dist/{edge-preflight-GBNCPNVU.js → edge-preflight-QUW4BM7N.js} +15 -12
- package/dist/{ensure-container-edge-KPI2CVXJ.js → ensure-container-edge-FOEFQ3PI.js} +15 -10
- package/dist/{executor-7LXXZPBR.js → executor-NTWDJ3DB.js} +8 -6
- package/dist/host-channel-preflight-OMCZMMKR.js +54 -0
- package/dist/index.js +198 -76
- package/dist/{local-executor-WOFZD72X.js → local-executor-OQ2GRN7K.js} +3 -2
- package/dist/{nginx-KUFMLMAC.js → nginx-4U4C2XUS.js} +11 -8
- package/dist/{noop-NJ72F46T.js → noop-CLLJHCPL.js} +1 -1
- package/dist/{openresty-lua-ATTH35BE.js → openresty-lua-LLWT5XQW.js} +8 -5
- package/dist/{privilege-I4W3ZNZK.js → privilege-MNDVMDST.js} +6 -5
- package/dist/server/index.js +40049 -30378
- package/dist/server/migrations/0109_service_build_args.sql +9 -0
- package/dist/server/migrations/0110_service_deployment_host_ports.sql +18 -0
- package/dist/server/migrations/0111_host_port_claim.sql +381 -0
- package/dist/server/migrations/0112_backup_run_execution_lease.sql +12 -0
- package/dist/server/migrations/0113_docker_migration_execution_lease.sql +11 -0
- package/dist/server/migrations/0114_build_session_execution_lease_index.sql +6 -0
- package/dist/server/migrations/0115_orphan_target_key.sql +3 -0
- package/dist/server/migrations/0116_domain_ssl_challenge.sql +2 -0
- package/dist/server/migrations/meta/_journal.json +56 -0
- package/dist/setup-IFIMPQZJ.js +25 -0
- package/dist/{ssh-client-JBMA7SN4.js → ssh-client-2XWZCMGI.js} +3 -2
- package/dist/up-HQS7IR7H.js +61 -0
- package/package.json +1 -1
- package/dist/bare-OXJVW3AK.js +0 -25
- package/dist/chunk-LH2PLRQO.js +0 -434
- package/dist/chunk-SM26HDS2.js +0 -185
- package/dist/cloud-75VIT4KV.js +0 -37
- package/dist/edge-import-WJUB4M7F.js +0 -52
- package/dist/host-channel-preflight-KE6OONYK.js +0 -50
- package/dist/setup-XGLGUUFW.js +0 -22
- package/dist/up-K4BV7466.js +0 -57
|
@@ -17,26 +17,30 @@ import {
|
|
|
17
17
|
detectOpenRestyPaths,
|
|
18
18
|
edgeChallengeVhostConf,
|
|
19
19
|
isCloudFrontedHost,
|
|
20
|
-
luaSourceAvailable
|
|
21
|
-
|
|
20
|
+
luaSourceAvailable,
|
|
21
|
+
reloadBareOpenResty
|
|
22
|
+
} from "./chunk-7PEXM2UI.js";
|
|
22
23
|
import {
|
|
23
24
|
edgeDownExplanation,
|
|
24
25
|
explainEdgeDown,
|
|
25
26
|
isEdgeDownFailure
|
|
26
27
|
} from "./chunk-I3DPMTSF.js";
|
|
28
|
+
import {
|
|
29
|
+
probeHostedHttp
|
|
30
|
+
} from "./chunk-R4VDPFLS.js";
|
|
27
31
|
import {
|
|
28
32
|
EXECUTOR_DELEGATE
|
|
29
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-NPSRGIHV.js";
|
|
30
34
|
import {
|
|
31
35
|
logEntry,
|
|
32
36
|
sq
|
|
33
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-P7RCYELG.js";
|
|
34
38
|
import {
|
|
35
39
|
edgeFailureReason
|
|
36
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-X53JXRS4.js";
|
|
37
41
|
import {
|
|
38
42
|
probeListeningPort
|
|
39
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-BO6CY4V2.js";
|
|
40
44
|
import {
|
|
41
45
|
PROXY_DIRECTIVES,
|
|
42
46
|
parseProxyValue,
|
|
@@ -44,7 +48,7 @@ import {
|
|
|
44
48
|
resolveRedirectStatus,
|
|
45
49
|
safeErrorMessage,
|
|
46
50
|
sanitizeProxySettings
|
|
47
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-WEZFTL4V.js";
|
|
48
52
|
|
|
49
53
|
// ../../packages/adapters/src/infra/nginx.ts
|
|
50
54
|
import {
|
|
@@ -294,21 +298,49 @@ async function readDeclaredPair(exec, certPath, keyPath, container) {
|
|
|
294
298
|
}
|
|
295
299
|
|
|
296
300
|
// ../../packages/adapters/src/system/output-exists.ts
|
|
297
|
-
|
|
301
|
+
var SAFE_HOST = /^[A-Za-z0-9][A-Za-z0-9.-]{0,252}$/;
|
|
302
|
+
var SAFE_PATH = /^\/[A-Za-z0-9._~/-]*$/;
|
|
303
|
+
function safeRequestPath(path) {
|
|
304
|
+
return SAFE_PATH.test(path) && !path.split("/").includes("..");
|
|
305
|
+
}
|
|
306
|
+
function buildCommand(servedPath, opts) {
|
|
298
307
|
const p = servedPath.replace(/'/g, "'\\''");
|
|
299
|
-
|
|
308
|
+
const parts = [
|
|
300
309
|
`if [ -e '${p}' ]; then echo FOUND; fi`,
|
|
301
|
-
`if [ -f '${p}' ] || [ -f '${p}/index.html' ]; then echo INDEX; fi
|
|
302
|
-
|
|
303
|
-
|
|
310
|
+
`if [ -f '${p}' ] || [ -f '${p}/index.html' ]; then echo INDEX; fi`
|
|
311
|
+
];
|
|
312
|
+
const host = opts?.hostname;
|
|
313
|
+
const path = opts?.requestPath ?? "/";
|
|
314
|
+
if (host && SAFE_HOST.test(host) && safeRequestPath(path)) {
|
|
315
|
+
const port = opts?.edgePort ?? 80;
|
|
316
|
+
const maxTime = opts?.timeoutSeconds ?? 3;
|
|
317
|
+
parts.push(
|
|
318
|
+
`if command -v curl >/dev/null 2>&1; then echo "HTTP $(curl -sS -o /dev/null -w '%{http_code} %{num_connects}' --max-time ${maxTime} -H 'Host: ${host}' -H 'X-Forwarded-Proto: https' 'http://127.0.0.1:${port}${path}' 2>/dev/null)"; fi`
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
parts.push("true");
|
|
322
|
+
return parts.join("; ");
|
|
323
|
+
}
|
|
324
|
+
function statusIsServed(status) {
|
|
325
|
+
if (status >= 200 && status < 400) return true;
|
|
326
|
+
return status === 401 || status === 403;
|
|
304
327
|
}
|
|
305
|
-
|
|
328
|
+
function parseHttp(out) {
|
|
329
|
+
const match = /(^|\n)HTTP (\d+) (\d+)(\r?\n|$)/.exec(out);
|
|
330
|
+
if (!match) return {};
|
|
331
|
+
const status = Number(match[2]);
|
|
332
|
+
const connects = Number(match[3]);
|
|
333
|
+
if (!Number.isFinite(status) || status === 0 || connects === 0) return {};
|
|
334
|
+
return { status, served: statusIsServed(status) };
|
|
335
|
+
}
|
|
336
|
+
async function probeStaticOutput(executor, servedPath, opts) {
|
|
306
337
|
try {
|
|
307
|
-
const out = await executor.exec(buildCommand(servedPath), { timeout:
|
|
338
|
+
const out = await executor.exec(buildCommand(servedPath, opts), { timeout: 8e3 });
|
|
308
339
|
return {
|
|
309
340
|
found: /(^|\n)FOUND(\r?\n|$)/.test(out),
|
|
310
341
|
hasIndex: /(^|\n)INDEX(\r?\n|$)/.test(out),
|
|
311
|
-
checked: true
|
|
342
|
+
checked: true,
|
|
343
|
+
...parseHttp(out)
|
|
312
344
|
};
|
|
313
345
|
} catch {
|
|
314
346
|
return { found: false, hasIndex: false, checked: false };
|
|
@@ -562,7 +594,7 @@ function acmeKeyArgs(keyType) {
|
|
|
562
594
|
return [];
|
|
563
595
|
}
|
|
564
596
|
}
|
|
565
|
-
var DOMAIN_RE = /^[a-zA-Z0-9]([a-zA-Z0-9.-]*[a-zA-Z0-9])?$/;
|
|
597
|
+
var DOMAIN_RE = /^(\*\.)?[a-zA-Z0-9]([a-zA-Z0-9.-]*[a-zA-Z0-9])?$/;
|
|
566
598
|
function assertValidDomain(domain) {
|
|
567
599
|
if (!DOMAIN_RE.test(domain) || domain.length > 253) {
|
|
568
600
|
throw new Error(`Invalid domain: ${domain}`);
|
|
@@ -570,7 +602,9 @@ function assertValidDomain(domain) {
|
|
|
570
602
|
}
|
|
571
603
|
function assertNoNginxInjection(value, what) {
|
|
572
604
|
if (/[\s;{}#\\'"]/.test(value)) {
|
|
573
|
-
throw new Error(
|
|
605
|
+
throw new Error(
|
|
606
|
+
`Invalid ${what} (contains characters that could inject nginx config): ${value}`
|
|
607
|
+
);
|
|
574
608
|
}
|
|
575
609
|
}
|
|
576
610
|
function assertNoRuntimeVariable(value, what, allowCaptures = false) {
|
|
@@ -667,7 +701,9 @@ function certbotDiagnosis(output, domain) {
|
|
|
667
701
|
diagnosis = `Couldn't start the certificate challenge listener for ${domain} on port ${ACME_HTTP01_PORT}: something on this server is already holding it. That port is used only while a certificate is being issued, so this is almost always a certificate run from an earlier attempt that hasn't exited \u2014 retry, and if it persists find the holder with \`ss -ltnp | grep ${ACME_HTTP01_PORT}\` and stop it. Nothing was requested from Let's Encrypt, so this is not a DNS or routing problem.`;
|
|
668
702
|
} else if (/timeout during connect|connection refused|failed to connect|Timeout/i.test(text)) {
|
|
669
703
|
diagnosis = `Port 80 for ${domain} isn't reachable from the internet \u2014 a firewall / cloud security group is blocking it, the domain doesn't point at this server, or another proxy is still bound to :80.`;
|
|
670
|
-
} else if (/NXDOMAIN|no\s+(A|AAAA)\s+record|DNS problem|could not be resolved|no records? found/i.test(
|
|
704
|
+
} else if (/NXDOMAIN|no\s+(A|AAAA)\s+record|DNS problem|could not be resolved|no records? found/i.test(
|
|
705
|
+
text
|
|
706
|
+
)) {
|
|
671
707
|
diagnosis = `${domain} doesn't resolve to this server yet \u2014 the DNS A record is missing or hasn't propagated. Wait for propagation, then retry from the Domains tab.`;
|
|
672
708
|
} else if (/\b52[56]\b|ssl handshake failed/i.test(text)) {
|
|
673
709
|
diagnosis = `Cloudflare's proxy is answering the certificate challenge for ${domain} instead of forwarding it to this server: it redirects the request to HTTPS, then rejects the origin's TLS. Fix it one of three ways \u2014 set SSL/TLS \u2192 Overview to "Full" (not "Full (strict)") until the first certificate is issued, or turn the orange cloud off for this record until it is, or upload a Cloudflare Origin CA certificate from the Domains tab and skip Let's Encrypt entirely.`;
|
|
@@ -690,6 +726,7 @@ var NginxProvider = class {
|
|
|
690
726
|
acmeTosAgreed;
|
|
691
727
|
certDir;
|
|
692
728
|
executor;
|
|
729
|
+
paths;
|
|
693
730
|
reloadCommand;
|
|
694
731
|
pinPaths;
|
|
695
732
|
containerEdge;
|
|
@@ -713,8 +750,9 @@ var NginxProvider = class {
|
|
|
713
750
|
this.acmeTosAgreed = opts.acmeTosAgreed ?? true;
|
|
714
751
|
this.certDir = opts.certDir ?? DEFAULT_CERT_DIR;
|
|
715
752
|
this.executor = opts.executor ?? null;
|
|
753
|
+
this.paths = opts.paths;
|
|
716
754
|
this.containerEdge = opts.containerEdge ?? false;
|
|
717
|
-
this.reloadCommand = buildReloadCommand(opts.paths
|
|
755
|
+
this.reloadCommand = buildReloadCommand(opts.paths);
|
|
718
756
|
this.pinPaths = opts.pinPaths ?? false;
|
|
719
757
|
this.challengeDir = opts.challengeDir ?? EDGE_CHALLENGE_DIR;
|
|
720
758
|
}
|
|
@@ -979,8 +1017,19 @@ var NginxProvider = class {
|
|
|
979
1017
|
*
|
|
980
1018
|
* Advisory: never throws.
|
|
981
1019
|
*/
|
|
982
|
-
async probeStaticRoot(servedPath) {
|
|
983
|
-
if (this.executor) return probeStaticOutput(this.executor, servedPath);
|
|
1020
|
+
async probeStaticRoot(servedPath, opts) {
|
|
1021
|
+
if (this.executor) return probeStaticOutput(this.executor, servedPath, opts);
|
|
1022
|
+
const fs = await this.probeStaticRootViaFs(servedPath);
|
|
1023
|
+
if (!opts?.hostname) return fs;
|
|
1024
|
+
const http = await probeHostedHttp({
|
|
1025
|
+
hostname: opts.hostname,
|
|
1026
|
+
path: opts.requestPath ?? "/",
|
|
1027
|
+
port: opts.edgePort ?? 80,
|
|
1028
|
+
timeoutMs: (opts.timeoutSeconds ?? 3) * 1e3
|
|
1029
|
+
});
|
|
1030
|
+
return http ? { ...fs, status: http.status, served: http.served } : fs;
|
|
1031
|
+
}
|
|
1032
|
+
async probeStaticRootViaFs(servedPath) {
|
|
984
1033
|
const found = await this._exists(servedPath);
|
|
985
1034
|
if (!found) return { found: false, hasIndex: false, checked: true };
|
|
986
1035
|
const isFile = await fsStat(servedPath).then((s) => s.isFile()).catch(() => false);
|
|
@@ -1052,6 +1101,31 @@ eab-hmac-key = ${this.acmeEabHmacKey}
|
|
|
1052
1101
|
}
|
|
1053
1102
|
return path;
|
|
1054
1103
|
}
|
|
1104
|
+
/** Materialize generated DNS hooks on the same target where Certbot runs. */
|
|
1105
|
+
async createEphemeralDnsHooks(opts) {
|
|
1106
|
+
if (!opts?.dnsAuthHookScript) return null;
|
|
1107
|
+
const dir = join(dirname(this.certDir), `.openship-dns-${randomBytes(12).toString("hex")}`);
|
|
1108
|
+
const authPath = join(dir, "auth.sh");
|
|
1109
|
+
const cleanupPath = join(dir, "cleanup.sh");
|
|
1110
|
+
const recordPath = join(dir, "record-id.txt");
|
|
1111
|
+
await this._mkdir(dir);
|
|
1112
|
+
try {
|
|
1113
|
+
await this._chmod(dir, 448);
|
|
1114
|
+
await this._writeFile(authPath, opts.dnsAuthHookScript, 448);
|
|
1115
|
+
if (opts.dnsCleanupHookScript) {
|
|
1116
|
+
await this._writeFile(cleanupPath, opts.dnsCleanupHookScript, 448);
|
|
1117
|
+
}
|
|
1118
|
+
} catch (err) {
|
|
1119
|
+
await this._rm(dir).catch(() => void 0);
|
|
1120
|
+
throw err;
|
|
1121
|
+
}
|
|
1122
|
+
const recordEnv = `OPENSHIP_DNS_RECORD_FILE=${sq(recordPath)}`;
|
|
1123
|
+
return {
|
|
1124
|
+
dir,
|
|
1125
|
+
authCommand: `${recordEnv} ${sq(authPath)}`,
|
|
1126
|
+
cleanupCommand: opts.dnsCleanupHookScript ? `${recordEnv} ${sq(cleanupPath)}` : void 0
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1055
1129
|
async _captureFile(path) {
|
|
1056
1130
|
if (!await this._exists(path)) {
|
|
1057
1131
|
return { exists: false };
|
|
@@ -1305,7 +1379,9 @@ ${serveLocation}
|
|
|
1305
1379
|
*/
|
|
1306
1380
|
async _lsSites() {
|
|
1307
1381
|
if (!this.executor) return fsReaddir(this.sitesDir);
|
|
1308
|
-
const out = await this.executor.exec(
|
|
1382
|
+
const out = await this.executor.exec(
|
|
1383
|
+
`ls -1 ${sq(this._commandSitesDir())} 2>/dev/null || true`
|
|
1384
|
+
);
|
|
1309
1385
|
return out.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
1310
1386
|
}
|
|
1311
1387
|
/**
|
|
@@ -1368,8 +1444,11 @@ ${serveLocation}
|
|
|
1368
1444
|
* from the old in-memory config (an inconsistent half-state that would silently
|
|
1369
1445
|
* vanish on the next unrelated reload). The error is re-thrown for retry.
|
|
1370
1446
|
*/
|
|
1371
|
-
async removeRoute(domain) {
|
|
1447
|
+
async removeRoute(domain, opts) {
|
|
1372
1448
|
assertValidDomain(domain);
|
|
1449
|
+
if (opts?.signal?.aborted) {
|
|
1450
|
+
throw new Error(`Route removal aborted before it started: ${domain}`);
|
|
1451
|
+
}
|
|
1373
1452
|
const slug = await this.resolveSlug(domain);
|
|
1374
1453
|
const configPath = join(this.sitesDir, `${slug}.conf`);
|
|
1375
1454
|
const statePath = this.routeStatePath(slug);
|
|
@@ -1378,11 +1457,20 @@ ${serveLocation}
|
|
|
1378
1457
|
await this._rm(configPath);
|
|
1379
1458
|
await this._rm(statePath).catch(() => void 0);
|
|
1380
1459
|
try {
|
|
1381
|
-
await this.reload();
|
|
1460
|
+
await this.reload({ allowStopped: true });
|
|
1382
1461
|
} catch (err) {
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1462
|
+
try {
|
|
1463
|
+
if (!opts?.signal?.aborted) await this._restoreFile(configPath, confSnapshot);
|
|
1464
|
+
if (!opts?.signal?.aborted) await this._restoreFile(statePath, stateSnapshot);
|
|
1465
|
+
if (!opts?.signal?.aborted) {
|
|
1466
|
+
await this.reload({ allowStopped: true }).catch(() => void 0);
|
|
1467
|
+
}
|
|
1468
|
+
} finally {
|
|
1469
|
+
if (opts?.signal?.aborted) {
|
|
1470
|
+
await this._rm(configPath).catch(() => void 0);
|
|
1471
|
+
await this._rm(statePath).catch(() => void 0);
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1386
1474
|
throw err;
|
|
1387
1475
|
}
|
|
1388
1476
|
await this._rm(this.bootstrapCertDir(domain)).catch(() => void 0);
|
|
@@ -1489,31 +1577,47 @@ ${serveLocation}
|
|
|
1489
1577
|
const emailArgs = validEmail ? ["--email", this.acmeEmail] : ["--register-unsafely-without-email"];
|
|
1490
1578
|
let certonlyOut = "";
|
|
1491
1579
|
const eabConfig = await this.createEphemeralEabConfig();
|
|
1580
|
+
let generatedDnsHooks = null;
|
|
1492
1581
|
try {
|
|
1493
|
-
|
|
1494
|
-
|
|
1582
|
+
generatedDnsHooks = await this.createEphemeralDnsHooks(opts);
|
|
1583
|
+
const isDnsChallenge = opts?.challenge === "dns-01" || domain.startsWith("*.");
|
|
1584
|
+
const challengeArgs = isDnsChallenge ? [
|
|
1495
1585
|
"certonly",
|
|
1496
|
-
"--
|
|
1497
|
-
"--
|
|
1498
|
-
|
|
1499
|
-
"--
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1586
|
+
"--manual",
|
|
1587
|
+
"--preferred-challenges",
|
|
1588
|
+
"dns",
|
|
1589
|
+
...generatedDnsHooks?.authCommand || opts?.dnsAuthHook ? ["--manual-auth-hook", generatedDnsHooks?.authCommand ?? opts.dnsAuthHook] : [],
|
|
1590
|
+
...generatedDnsHooks?.cleanupCommand || opts?.dnsCleanupHook ? [
|
|
1591
|
+
"--manual-cleanup-hook",
|
|
1592
|
+
generatedDnsHooks?.cleanupCommand ?? opts.dnsCleanupHook
|
|
1593
|
+
] : []
|
|
1594
|
+
] : ["certonly", "--standalone", "--http-01-port", String(ACME_HTTP01_PORT)];
|
|
1595
|
+
certonlyOut = await this._execCertbot(
|
|
1596
|
+
[
|
|
1597
|
+
...eabConfig ? ["--config", eabConfig] : [],
|
|
1598
|
+
...challengeArgs,
|
|
1599
|
+
"--cert-name",
|
|
1600
|
+
domain,
|
|
1601
|
+
"-d",
|
|
1602
|
+
domain,
|
|
1603
|
+
...this.acmeDirectoryUrl ? ["--server", this.acmeDirectoryUrl] : [],
|
|
1604
|
+
...acmeKeyArgs(this.acmeKeyType),
|
|
1605
|
+
...emailArgs,
|
|
1606
|
+
...this.acmeTosAgreed ? ["--agree-tos"] : [],
|
|
1607
|
+
"--non-interactive",
|
|
1608
|
+
// Forced reissue: certbot would otherwise print "not due for renewal"
|
|
1609
|
+
// (exit 0) when a lineage exists, leaving the stale cert in place.
|
|
1610
|
+
...opts?.force ? ["--force-renewal"] : []
|
|
1611
|
+
],
|
|
1612
|
+
opts?.onLog
|
|
1613
|
+
);
|
|
1512
1614
|
} catch (err) {
|
|
1513
1615
|
const raw = this.redactAcmeSecrets(safeErrorMessage(err));
|
|
1514
1616
|
let summary = summarizeCertbotFailure(raw, domain);
|
|
1515
1617
|
if (isAcmePortBindFailure(raw) && this.executor) {
|
|
1516
|
-
const occupant = await probeListeningPort(this.executor, ACME_HTTP01_PORT).catch(
|
|
1618
|
+
const occupant = await probeListeningPort(this.executor, ACME_HTTP01_PORT).catch(
|
|
1619
|
+
() => null
|
|
1620
|
+
);
|
|
1517
1621
|
if (occupant) {
|
|
1518
1622
|
summary += ` Currently held by ${occupant.command}` + (occupant.pid !== null ? ` (pid ${occupant.pid})` : "") + (occupant.systemdUnit ? ` via ${occupant.systemdUnit}` : "") + `.`;
|
|
1519
1623
|
}
|
|
@@ -1521,6 +1625,7 @@ ${serveLocation}
|
|
|
1521
1625
|
throw new Error(summary);
|
|
1522
1626
|
} finally {
|
|
1523
1627
|
if (eabConfig) await this._rm(dirname(eabConfig)).catch(() => void 0);
|
|
1628
|
+
if (generatedDnsHooks) await this._rm(generatedDnsHooks.dir).catch(() => void 0);
|
|
1524
1629
|
}
|
|
1525
1630
|
const slug = await this.resolveSlug(domain);
|
|
1526
1631
|
const configPath = join(this.sitesDir, `${slug}.conf`);
|
|
@@ -1588,24 +1693,24 @@ ${tail}` : "")
|
|
|
1588
1693
|
/**
|
|
1589
1694
|
* Renew a TLS certificate using certbot.
|
|
1590
1695
|
*/
|
|
1591
|
-
async renewCert(domain) {
|
|
1696
|
+
async renewCert(domain, opts) {
|
|
1592
1697
|
assertValidDomain(domain);
|
|
1698
|
+
if (opts?.challenge === "dns-01" || domain.startsWith("*.")) {
|
|
1699
|
+
return this.provisionCert(domain, { ...opts, challenge: "dns-01", force: true });
|
|
1700
|
+
}
|
|
1593
1701
|
if (!await this.certsExist(domain)) {
|
|
1594
|
-
return this.provisionCert(domain);
|
|
1702
|
+
return this.provisionCert(domain, opts);
|
|
1595
1703
|
}
|
|
1596
1704
|
if (!await this.hasCertbotLineage(domain)) {
|
|
1597
|
-
return this.provisionCert(domain, { force: true });
|
|
1705
|
+
return this.provisionCert(domain, { ...opts, force: true });
|
|
1598
1706
|
}
|
|
1599
1707
|
if (!await this.lineageServerMatches(domain)) {
|
|
1600
|
-
return this.provisionCert(domain, { force: true });
|
|
1708
|
+
return this.provisionCert(domain, { ...opts, force: true });
|
|
1601
1709
|
}
|
|
1602
|
-
await this._execCertbot(
|
|
1603
|
-
"renew",
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
...acmeKeyArgs(this.acmeKeyType),
|
|
1607
|
-
"--non-interactive"
|
|
1608
|
-
]);
|
|
1710
|
+
await this._execCertbot(
|
|
1711
|
+
["renew", "--cert-name", domain, ...acmeKeyArgs(this.acmeKeyType), "--non-interactive"],
|
|
1712
|
+
opts?.onLog
|
|
1713
|
+
);
|
|
1609
1714
|
await this.reload();
|
|
1610
1715
|
return this.readCertInfo(domain);
|
|
1611
1716
|
}
|
|
@@ -1763,23 +1868,42 @@ ${tail}` : "")
|
|
|
1763
1868
|
* config may move). Re-detecting on every reload keeps the provider
|
|
1764
1869
|
* in sync without requiring an API restart.
|
|
1765
1870
|
*/
|
|
1766
|
-
async reload() {
|
|
1871
|
+
async reload(opts = {}) {
|
|
1767
1872
|
if (this.executor) {
|
|
1768
1873
|
if (!this.pinPaths) {
|
|
1769
1874
|
try {
|
|
1770
1875
|
const freshPaths = await detectOpenRestyPaths(this.executor);
|
|
1876
|
+
this.paths = freshPaths;
|
|
1771
1877
|
this.sitesDir = freshPaths.sitesDir;
|
|
1772
1878
|
this.nginxVersion = freshPaths.nginxVersion ?? this.nginxVersion;
|
|
1773
|
-
this.reloadCommand = buildReloadCommand(freshPaths
|
|
1774
|
-
containerEdge: this.containerEdge
|
|
1775
|
-
});
|
|
1879
|
+
this.reloadCommand = buildReloadCommand(freshPaths);
|
|
1776
1880
|
} catch {
|
|
1777
1881
|
}
|
|
1778
1882
|
}
|
|
1779
|
-
|
|
1883
|
+
if (this.containerEdge) {
|
|
1884
|
+
await this.executor.exec(this.reloadCommand, { timeout: 15e3 });
|
|
1885
|
+
} else {
|
|
1886
|
+
await reloadBareOpenResty(this.executor, this.paths, {
|
|
1887
|
+
allowStopped: opts.allowStopped ?? false
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1890
|
+
return;
|
|
1891
|
+
}
|
|
1892
|
+
if (this.containerEdge) {
|
|
1893
|
+
await execFileAsync("sh", ["-lc", this.reloadCommand], { timeout: 15e3 });
|
|
1780
1894
|
return;
|
|
1781
1895
|
}
|
|
1782
|
-
|
|
1896
|
+
const localExecutor = {
|
|
1897
|
+
exec: async (command, execOpts) => {
|
|
1898
|
+
const { stdout } = await execFileAsync("sh", ["-lc", command], {
|
|
1899
|
+
timeout: execOpts?.timeout
|
|
1900
|
+
});
|
|
1901
|
+
return String(stdout);
|
|
1902
|
+
}
|
|
1903
|
+
};
|
|
1904
|
+
await reloadBareOpenResty(localExecutor, this.paths, {
|
|
1905
|
+
allowStopped: opts.allowStopped ?? false
|
|
1906
|
+
});
|
|
1783
1907
|
}
|
|
1784
1908
|
async certsExist(domain) {
|
|
1785
1909
|
const certPath = join(this.certDir, domain, "fullchain.pem");
|
|
@@ -1961,7 +2085,9 @@ ${geoEntries.join("\n")}
|
|
|
1961
2085
|
if (!foundDesiredInclude) {
|
|
1962
2086
|
const httpIndex = nextLines.findIndex((line) => /^\s*http\s*\{\s*$/.test(line));
|
|
1963
2087
|
if (httpIndex === -1) {
|
|
1964
|
-
throw new Error(
|
|
2088
|
+
throw new Error(
|
|
2089
|
+
`Failed to ensure rate-limit include: ${confPath} is missing an http block`
|
|
2090
|
+
);
|
|
1965
2091
|
}
|
|
1966
2092
|
const indent = nextLines[httpIndex].match(/^\s*/)?.[0] ?? "";
|
|
1967
2093
|
nextLines.splice(httpIndex + 1, 0, `${indent} ${desiredIncludeLine}`);
|