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.
Files changed (72) hide show
  1. package/dist/bare-FA66N7GC.js +29 -0
  2. package/dist/{chunk-77QDF6UD.js → chunk-3MP6ACKM.js} +587 -156
  3. package/dist/{chunk-YIMOCZBZ.js → chunk-5DJOB7IX.js} +3 -3
  4. package/dist/{chunk-WVAML4H6.js → chunk-5I3COAFH.js} +274 -65
  5. package/dist/{chunk-5DMWSTOM.js → chunk-5UXHT6ED.js} +940 -224
  6. package/dist/{chunk-LUKYPVJ4.js → chunk-6GU6JSK6.js} +329 -466
  7. package/dist/{chunk-RSR7PFIQ.js → chunk-7PEXM2UI.js} +213 -45
  8. package/dist/{chunk-VTGPEC6B.js → chunk-7YBWXEM6.js} +6 -4
  9. package/dist/chunk-AHJYP7DG.js +938 -0
  10. package/dist/chunk-BO6CY4V2.js +265 -0
  11. package/dist/{chunk-FE7ZZQSE.js → chunk-D3OEMXYP.js} +3 -3
  12. package/dist/chunk-D6HU4VUZ.js +249 -0
  13. package/dist/{chunk-LFE5KQ7Y.js → chunk-ENKJ7XIF.js} +152 -57
  14. package/dist/{chunk-YMMQRE5J.js → chunk-GNOMEMNI.js} +1 -1
  15. package/dist/{chunk-QVW76WDL.js → chunk-IAMOUDHY.js} +190 -64
  16. package/dist/{chunk-OBPKYXQL.js → chunk-MUWVC5R4.js} +219 -58
  17. package/dist/chunk-N5YZMO33.js +111 -0
  18. package/dist/{chunk-A4GXYE4L.js → chunk-NIYD7MD2.js} +206 -52
  19. package/dist/{chunk-UVNMQM7W.js → chunk-NPSRGIHV.js} +8 -6
  20. package/dist/{chunk-2GWYYHOR.js → chunk-NRU2EBYZ.js} +2 -2
  21. package/dist/{chunk-CBLD2HS5.js → chunk-NUT6HKK6.js} +8 -5
  22. package/dist/{chunk-ZFJXPCQP.js → chunk-P7RCYELG.js} +4 -3
  23. package/dist/chunk-R4VDPFLS.js +106 -0
  24. package/dist/{chunk-HKRQXPTI.js → chunk-R5DC5PEF.js} +1 -1
  25. package/dist/{chunk-NK5N5LXN.js → chunk-RCMERVBL.js} +24 -9
  26. package/dist/{chunk-RXQBD4ZY.js → chunk-RHLNVZMS.js} +2 -2
  27. package/dist/chunk-SK4FFCFR.js +70 -0
  28. package/dist/{chunk-4I2BM2DT.js → chunk-T46BQOLY.js} +3 -3
  29. package/dist/{chunk-6FH4AXVY.js → chunk-T6DVUX4F.js} +1 -1
  30. package/dist/{chunk-PCPVTH6M.js → chunk-TAZZZKYO.js} +6 -4
  31. package/dist/{chunk-MLYSEZQB.js → chunk-UAWY6X7V.js} +43 -72
  32. package/dist/{chunk-Q7WGTQQ5.js → chunk-VAI5VOOI.js} +1 -1
  33. package/dist/{chunk-UJ6JZM65.js → chunk-WEZFTL4V.js} +1325 -513
  34. package/dist/{chunk-2YM3NI2E.js → chunk-X53JXRS4.js} +38 -44
  35. package/dist/{cloud-KRTZ2IL5.js → cloud-2CRDKOWQ.js} +1 -1
  36. package/dist/cloud-REV3DPJH.js +42 -0
  37. package/dist/{detect-OIE5V3VZ.js → detect-BBFT5GEU.js} +9 -6
  38. package/dist/{docker-VTR2GK5Y.js → docker-EG3QSE6I.js} +11 -8
  39. package/dist/{docker-edge-executor-22IBXNYJ.js → docker-edge-executor-E5TOTLMR.js} +8 -6
  40. package/dist/edge-import-YJVJUGYC.js +56 -0
  41. package/dist/{edge-preflight-GBNCPNVU.js → edge-preflight-QUW4BM7N.js} +15 -12
  42. package/dist/{ensure-container-edge-KPI2CVXJ.js → ensure-container-edge-FOEFQ3PI.js} +15 -10
  43. package/dist/{executor-7LXXZPBR.js → executor-NTWDJ3DB.js} +8 -6
  44. package/dist/host-channel-preflight-OMCZMMKR.js +54 -0
  45. package/dist/index.js +198 -76
  46. package/dist/{local-executor-WOFZD72X.js → local-executor-OQ2GRN7K.js} +3 -2
  47. package/dist/{nginx-KUFMLMAC.js → nginx-4U4C2XUS.js} +11 -8
  48. package/dist/{noop-NJ72F46T.js → noop-CLLJHCPL.js} +1 -1
  49. package/dist/{openresty-lua-ATTH35BE.js → openresty-lua-LLWT5XQW.js} +8 -5
  50. package/dist/{privilege-I4W3ZNZK.js → privilege-MNDVMDST.js} +6 -5
  51. package/dist/server/index.js +40049 -30378
  52. package/dist/server/migrations/0109_service_build_args.sql +9 -0
  53. package/dist/server/migrations/0110_service_deployment_host_ports.sql +18 -0
  54. package/dist/server/migrations/0111_host_port_claim.sql +381 -0
  55. package/dist/server/migrations/0112_backup_run_execution_lease.sql +12 -0
  56. package/dist/server/migrations/0113_docker_migration_execution_lease.sql +11 -0
  57. package/dist/server/migrations/0114_build_session_execution_lease_index.sql +6 -0
  58. package/dist/server/migrations/0115_orphan_target_key.sql +3 -0
  59. package/dist/server/migrations/0116_domain_ssl_challenge.sql +2 -0
  60. package/dist/server/migrations/meta/_journal.json +56 -0
  61. package/dist/setup-IFIMPQZJ.js +25 -0
  62. package/dist/{ssh-client-JBMA7SN4.js → ssh-client-2XWZCMGI.js} +3 -2
  63. package/dist/up-HQS7IR7H.js +61 -0
  64. package/package.json +1 -1
  65. package/dist/bare-OXJVW3AK.js +0 -25
  66. package/dist/chunk-LH2PLRQO.js +0 -434
  67. package/dist/chunk-SM26HDS2.js +0 -185
  68. package/dist/cloud-75VIT4KV.js +0 -37
  69. package/dist/edge-import-WJUB4M7F.js +0 -52
  70. package/dist/host-channel-preflight-KE6OONYK.js +0 -50
  71. package/dist/setup-XGLGUUFW.js +0 -22
  72. package/dist/up-K4BV7466.js +0 -57
@@ -17,26 +17,30 @@ import {
17
17
  detectOpenRestyPaths,
18
18
  edgeChallengeVhostConf,
19
19
  isCloudFrontedHost,
20
- luaSourceAvailable
21
- } from "./chunk-RSR7PFIQ.js";
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-UVNMQM7W.js";
33
+ } from "./chunk-NPSRGIHV.js";
30
34
  import {
31
35
  logEntry,
32
36
  sq
33
- } from "./chunk-ZFJXPCQP.js";
37
+ } from "./chunk-P7RCYELG.js";
34
38
  import {
35
39
  edgeFailureReason
36
- } from "./chunk-2YM3NI2E.js";
40
+ } from "./chunk-X53JXRS4.js";
37
41
  import {
38
42
  probeListeningPort
39
- } from "./chunk-SM26HDS2.js";
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-UJ6JZM65.js";
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
- function buildCommand(servedPath) {
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
- return [
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
- "true"
303
- ].join("; ");
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
- async function probeStaticOutput(executor, servedPath) {
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: 5e3 });
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(`Invalid ${what} (contains characters that could inject nginx config): ${value}`);
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(text)) {
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, { containerEdge: this.containerEdge });
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(`ls -1 ${sq(this._commandSitesDir())} 2>/dev/null || true`);
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
- await this._restoreFile(configPath, confSnapshot);
1384
- await this._restoreFile(statePath, stateSnapshot);
1385
- await this.reload().catch(() => void 0);
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
- certonlyOut = await this._execCertbot([
1494
- ...eabConfig ? ["--config", eabConfig] : [],
1582
+ generatedDnsHooks = await this.createEphemeralDnsHooks(opts);
1583
+ const isDnsChallenge = opts?.challenge === "dns-01" || domain.startsWith("*.");
1584
+ const challengeArgs = isDnsChallenge ? [
1495
1585
  "certonly",
1496
- "--standalone",
1497
- "--http-01-port",
1498
- String(ACME_HTTP01_PORT),
1499
- "--cert-name",
1500
- domain,
1501
- "-d",
1502
- domain,
1503
- ...this.acmeDirectoryUrl ? ["--server", this.acmeDirectoryUrl] : [],
1504
- ...acmeKeyArgs(this.acmeKeyType),
1505
- ...emailArgs,
1506
- ...this.acmeTosAgreed ? ["--agree-tos"] : [],
1507
- "--non-interactive",
1508
- // Forced reissue: certbot would otherwise print "not due for renewal"
1509
- // (exit 0) when a lineage exists, leaving the stale cert in place.
1510
- ...opts?.force ? ["--force-renewal"] : []
1511
- ], opts?.onLog);
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(() => null);
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
- "--cert-name",
1605
- domain,
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
- await this.executor.exec(this.reloadCommand);
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
- await execFileAsync("sh", ["-lc", this.reloadCommand]);
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(`Failed to ensure rate-limit include: ${confPath} is missing an http block`);
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}`);