@absolutejs/deploy 0.5.0 → 0.6.0

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/README.md CHANGED
@@ -287,6 +287,57 @@ limit). Persist the JSON; pass `account` back to subsequent
287
287
  mode `600`. Override `certPath` / `keyPath` / `mode` / `owner` /
288
288
  `reload` as needed.
289
289
 
290
+ ## Renewals — `renewCertificate` (0.6.0)
291
+
292
+ `issueCertificate` is one-shot; `renewCertificate` is the conditional
293
+ driver you wire to a cron / scheduled function. Reads the current
294
+ cert PEM, parses its `validTo`, and either returns `{ renewed: false
295
+ }` (cheap, no network IO) or runs the full issuance flow.
296
+
297
+ ```ts
298
+ import {
299
+ renewCertificate,
300
+ installCertificateOnTarget,
301
+ importAccount,
302
+ } from '@absolutejs/deploy/tls';
303
+ import { readFile, writeFile } from 'node:fs/promises';
304
+
305
+ const currentCertificatePem = await readFile('./cert.pem', 'utf8').catch(() => undefined);
306
+ const account = await importAccount(
307
+ JSON.parse(await readFile('./account.json', 'utf8'))
308
+ );
309
+
310
+ const result = await renewCertificate({
311
+ currentCertificatePem,
312
+ domains: ['api.example.com'],
313
+ dnsProvider: dns,
314
+ email: 'ops@example.com',
315
+ account,
316
+ renewWhenDaysRemaining: 30, // default
317
+ });
318
+
319
+ if (result.renewed) {
320
+ console.log(`renewed (${result.reason})`);
321
+ await installCertificateOnTarget(target, result.certificate, {
322
+ reload: 'systemctl reload nginx',
323
+ });
324
+ await writeFile('./cert.pem', result.certificate.certificatePem);
325
+ await writeFile('./key.pem', result.certificate.privateKeyPem);
326
+ } else {
327
+ console.log(
328
+ `still fresh — ${result.inspection.daysRemaining} days remaining`
329
+ );
330
+ }
331
+ ```
332
+
333
+ Pair with `inspectCertificate(pem)` for status pages, expiry
334
+ alerts, and observability:
335
+
336
+ ```ts
337
+ const info = inspectCertificate(certificatePem);
338
+ // { subjects, validFrom, validTo, daysRemaining, expired, issuer }
339
+ ```
340
+
290
341
  ## DigitalOcean Droplet — first deploy (manual)
291
342
 
292
343
  Assuming a fresh Ubuntu/Debian Droplet:
@@ -306,11 +357,10 @@ bun run my-deploy-script.ts
306
357
 
307
358
  The first run creates `/srv/<appName>/releases/<id>/`, drops a systemd unit at `/etc/systemd/system/<appName>.service` (if you're using `systemdManager`), starts the service, and probes. Subsequent runs just add a new release dir and swap the symlink.
308
359
 
309
- ## What v0.5.0 does NOT include
360
+ ## What v0.6.0 does NOT include
310
361
 
311
362
  - Cloud-provider compute targets beyond DigitalOcean + Hetzner. Linode / Vultr / Fly Machines follow the same shape and are next on the list.
312
363
  - DNS providers beyond Cloudflare. Route 53 / DigitalOcean DNS / Hetzner DNS slot into the same `DnsProvider` contract.
313
- - Cert renewal scheduling — `issueCertificate` is one-shot; wire it to a cron / scheduled-function and pass the persisted account JSON back in to renew. (Convention: renew at 30 days remaining.)
314
364
  - Bun installation on the remote — caller does it once, out of band.
315
365
  - Multi-target / fan-out deploys (caller iterates).
316
366
  - Zero-downtime port-swap (start new release on a fresh port, then nginx-reload). The default pipeline does stop-then-start; for true zero-downtime, replace the `restart` step.
package/dist/tls.d.ts CHANGED
@@ -24,6 +24,18 @@ import type { Target } from './targets';
24
24
  import type { DnsProvider } from './dns';
25
25
  export declare const LETSENCRYPT_PRODUCTION = "https://acme-v02.api.letsencrypt.org/directory";
26
26
  export declare const LETSENCRYPT_STAGING = "https://acme-staging-v02.api.letsencrypt.org/directory";
27
+ type JwsHeader = {
28
+ alg: 'ES256';
29
+ nonce: string;
30
+ url: string;
31
+ jwk?: JsonWebKey;
32
+ kid?: string;
33
+ };
34
+ type JwsBody = {
35
+ protected: string;
36
+ payload: string;
37
+ signature: string;
38
+ };
27
39
  export type AcmeAccount = {
28
40
  key: CryptoKeyPair;
29
41
  /** Account URL — set after first registration; persisted for renewals. */
@@ -97,6 +109,27 @@ export type InstallCertificateOptions = {
97
109
  /** Override the writeTo helper (tests). */
98
110
  writeFile?: (target: Target, path: string, contents: string) => Promise<void>;
99
111
  };
112
+ /**
113
+ * @internal — exposed for unit testing of cryptographic primitives.
114
+ * Not part of the public API; consumers should NOT depend on this.
115
+ */
116
+ export declare const __testing: {
117
+ base64UrlDecode: (value: string) => Uint8Array;
118
+ base64UrlEncode: (bytes: Uint8Array | ArrayBuffer) => string;
119
+ base64UrlEncodeString: (value: string) => string;
120
+ buildCsr: (domains: string[], keypair: CryptoKeyPair) => Promise<Uint8Array>;
121
+ derBitString: (payload: Uint8Array) => Uint8Array;
122
+ derInt: (value: number | Uint8Array) => Uint8Array;
123
+ derOid: (oid: string) => Uint8Array;
124
+ derSeq: (children: Uint8Array[]) => Uint8Array;
125
+ derSet: (children: Uint8Array[]) => Uint8Array;
126
+ ecdsaRawToDer: (rawSig: Uint8Array) => Uint8Array;
127
+ exportEcPrivateKeyPem: (keypair: CryptoKeyPair) => Promise<string>;
128
+ exportPublicJwk: (publicKey: CryptoKey) => Promise<JsonWebKey>;
129
+ generateCertKeyPair: () => Promise<CryptoKeyPair>;
130
+ jwkThumbprint: (publicJwk: JsonWebKey) => Promise<string>;
131
+ signJws: (privateKey: CryptoKey, header: JwsHeader, payload: object | string) => Promise<JwsBody>;
132
+ };
100
133
  /**
101
134
  * Upload cert + private key to the target. Composable with the deploy
102
135
  * pipeline as a verify-step or post-deploy hook.
@@ -105,3 +138,76 @@ export declare const installCertificateOnTarget: (target: Target, cert: IssuedCe
105
138
  certPath: string;
106
139
  keyPath: string;
107
140
  }>;
141
+ export type CertificateInspection = {
142
+ /** CN + every SAN, deduplicated. */
143
+ subjects: string[];
144
+ /** Issuance time, ms since epoch. */
145
+ validFrom: number;
146
+ /** Expiration time, ms since epoch. */
147
+ validTo: number;
148
+ /** Whole days remaining before `validTo`. Negative if expired. */
149
+ daysRemaining: number;
150
+ /** True when the cert is past `validTo`. */
151
+ expired: boolean;
152
+ /** Issuer DN string (for "Let's Encrypt vs staging vs other CA" checks). */
153
+ issuer: string;
154
+ };
155
+ /**
156
+ * Parse a PEM certificate into operator-shaped metadata. Used by
157
+ * {@link renewCertificate} to decide whether to re-issue; also fine
158
+ * for status pages and observability.
159
+ */
160
+ export declare const inspectCertificate: (pem: string, options?: {
161
+ now?: () => number;
162
+ }) => CertificateInspection;
163
+ export type RenewCertificateOptions = IssueCertificateOptions & {
164
+ /**
165
+ * Current cert PEM. When provided, the cert's `validTo` decides
166
+ * whether to re-issue; when absent, renewal always issues.
167
+ */
168
+ currentCertificatePem?: string;
169
+ /**
170
+ * Re-issue when fewer than this many days remain. Default 30,
171
+ * matching certbot's standard schedule (Let's Encrypt issues 90-day
172
+ * certs; renewing at 30 days leaves a 60-day error budget).
173
+ */
174
+ renewWhenDaysRemaining?: number;
175
+ /** Force re-issue regardless of remaining days. Default false. */
176
+ force?: boolean;
177
+ /** Override `Date.now()` for testing. */
178
+ now?: () => number;
179
+ };
180
+ export type RenewalResult = {
181
+ renewed: true;
182
+ certificate: IssuedCertificate;
183
+ reason: 'forced' | 'no-current-cert' | 'expiring-soon';
184
+ } | {
185
+ renewed: false;
186
+ reason: 'still-fresh';
187
+ inspection: CertificateInspection;
188
+ };
189
+ /**
190
+ * Conditional cert renewal. Parses the supplied cert (if any),
191
+ * decides whether to re-issue based on remaining days, and either
192
+ * returns the existing cert info ("still fresh") or issues a new
193
+ * one via {@link issueCertificate}.
194
+ *
195
+ * Wire to a scheduled function (cron / @absolutejs/sync schedule /
196
+ * GitHub Action) running at least once a week. Idempotent: a fresh
197
+ * cert returns `renewed: false` cheaply (one PEM parse, zero
198
+ * network IO).
199
+ *
200
+ * @example
201
+ * const result = await renewCertificate({
202
+ * currentCertificatePem: existingPem, // omit to force first-issue
203
+ * domains: ['api.example.com'],
204
+ * dnsProvider: dns,
205
+ * email: 'ops@example.com',
206
+ * account: persistedAccount, // reuse to avoid CA rate limit
207
+ * });
208
+ * if (result.renewed) {
209
+ * await installCertificateOnTarget(target, result.certificate, { reload });
210
+ * }
211
+ */
212
+ export declare const renewCertificate: (options: RenewCertificateOptions) => Promise<RenewalResult>;
213
+ export {};
package/dist/tls.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // @bun
2
2
  // src/tls.ts
3
+ import { X509Certificate } from "crypto";
3
4
  var LETSENCRYPT_PRODUCTION = "https://acme-v02.api.letsencrypt.org/directory";
4
5
  var LETSENCRYPT_STAGING = "https://acme-staging-v02.api.letsencrypt.org/directory";
5
6
  var base64UrlEncode = (bytes) => {
@@ -10,6 +11,15 @@ var base64UrlEncode = (bytes) => {
10
11
  return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
11
12
  };
12
13
  var base64UrlEncodeString = (value) => base64UrlEncode(new TextEncoder().encode(value));
14
+ var base64UrlDecode = (value) => {
15
+ const padded = value.replaceAll("-", "+").replaceAll("_", "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
16
+ const binary = atob(padded);
17
+ const bytes = new Uint8Array(binary.length);
18
+ for (let index = 0;index < binary.length; index += 1) {
19
+ bytes[index] = binary.charCodeAt(index);
20
+ }
21
+ return bytes;
22
+ };
13
23
  var derLength = (length) => {
14
24
  if (length < 128)
15
25
  return Uint8Array.from([length]);
@@ -409,6 +419,23 @@ var defaultWriteFile = async (target, remotePath, contents) => {
409
419
  ${contents}__ABS_TLS_EOF__
410
420
  `);
411
421
  };
422
+ var __testing = {
423
+ base64UrlDecode,
424
+ base64UrlEncode,
425
+ base64UrlEncodeString,
426
+ buildCsr,
427
+ derBitString,
428
+ derInt,
429
+ derOid,
430
+ derSeq,
431
+ derSet,
432
+ ecdsaRawToDer,
433
+ exportEcPrivateKeyPem,
434
+ exportPublicJwk,
435
+ generateCertKeyPair,
436
+ jwkThumbprint,
437
+ signJws
438
+ };
412
439
  var installCertificateOnTarget = async (target, cert, options = {}) => {
413
440
  const domain = cert.domains[0];
414
441
  if (domain === undefined) {
@@ -429,16 +456,74 @@ var installCertificateOnTarget = async (target, cert, options = {}) => {
429
456
  }
430
457
  return { certPath, keyPath };
431
458
  };
459
+ var parseSubjects = (x509) => {
460
+ const subjects = new Set;
461
+ const cnMatch = x509.subject.match(/CN=([^,\n]+)/);
462
+ if (cnMatch !== null && cnMatch[1] !== undefined) {
463
+ subjects.add(cnMatch[1].trim());
464
+ }
465
+ const san = x509.subjectAltName;
466
+ if (san !== undefined && san !== null) {
467
+ for (const part of san.split(",")) {
468
+ const dnsMatch = part.trim().match(/^DNS:(.+)$/);
469
+ if (dnsMatch !== null && dnsMatch[1] !== undefined) {
470
+ subjects.add(dnsMatch[1].trim());
471
+ }
472
+ }
473
+ }
474
+ return [...subjects];
475
+ };
476
+ var inspectCertificate = (pem, options = {}) => {
477
+ const x509 = new X509Certificate(pem);
478
+ const validFrom = new Date(x509.validFrom).getTime();
479
+ const validTo = new Date(x509.validTo).getTime();
480
+ const now = (options.now ?? Date.now)();
481
+ const daysRemaining = Math.floor((validTo - now) / (24 * 60 * 60 * 1000));
482
+ return {
483
+ daysRemaining,
484
+ expired: validTo < now,
485
+ issuer: x509.issuer,
486
+ subjects: parseSubjects(x509),
487
+ validFrom,
488
+ validTo
489
+ };
490
+ };
491
+ var renewCertificate = async (options) => {
492
+ const threshold = options.renewWhenDaysRemaining ?? 30;
493
+ if (threshold < 0) {
494
+ throw new Error("[deploy/tls] renewWhenDaysRemaining must be non-negative");
495
+ }
496
+ if (options.force === true) {
497
+ const certificate2 = await issueCertificate(options);
498
+ return { certificate: certificate2, reason: "forced", renewed: true };
499
+ }
500
+ if (options.currentCertificatePem === undefined) {
501
+ const certificate2 = await issueCertificate(options);
502
+ return { certificate: certificate2, reason: "no-current-cert", renewed: true };
503
+ }
504
+ const nowFn = options.now ?? Date.now;
505
+ const inspection = inspectCertificate(options.currentCertificatePem, {
506
+ now: nowFn
507
+ });
508
+ if (!inspection.expired && inspection.daysRemaining >= threshold) {
509
+ return { inspection, reason: "still-fresh", renewed: false };
510
+ }
511
+ const certificate = await issueCertificate(options);
512
+ return { certificate, reason: "expiring-soon", renewed: true };
513
+ };
432
514
  export {
515
+ renewCertificate,
433
516
  issueCertificate,
434
517
  installCertificateOnTarget,
518
+ inspectCertificate,
435
519
  importAccount,
436
520
  generateAccountKey,
437
521
  exportAccount,
522
+ __testing,
438
523
  LETSENCRYPT_STAGING,
439
524
  LETSENCRYPT_PRODUCTION,
440
525
  AcmeError
441
526
  };
442
527
 
443
- //# debugId=54D17E1F39C4D88964756E2164756E21
528
+ //# debugId=F710513ADD2FEFC064756E2164756E21
444
529
  //# sourceMappingURL=tls.js.map
package/dist/tls.js.map CHANGED
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/tls.ts"],
4
4
  "sourcesContent": [
5
- "/**\n * @absolutejs/deploy/tls — ACME (RFC 8555) client for Let's Encrypt and\n * compatible CAs. DNS-01 challenges only — uses the {@link DnsProvider}\n * abstraction from `./dns`, so the same Cloudflare / Route 53 /\n * Hetzner-DNS providers that point hostnames at boxes also satisfy\n * ACME's challenge requirement.\n *\n * Zero peer deps. Bun's `crypto.subtle` covers JWS signing and ECDSA\n * key generation; a small DER encoder builds the CSR.\n *\n * Public API:\n *\n * issueCertificate({ domains, dnsProvider, email })\n * → { certificatePem, privateKeyPem, account, domains }\n *\n * installCertificateOnTarget(target, cert, { certPath, keyPath, reload? })\n * → uploads PEM files via Target.upload + optional reload exec\n *\n * exportAccountKey / importAccountKey for persistence between runs\n * (reuse the same account across cert renewals — cheaper, doesn't\n * hit Let's Encrypt's account-creation rate limit)\n */\n\nimport type { Target } from './targets';\nimport type { DnsProvider } from './dns';\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nexport const LETSENCRYPT_PRODUCTION =\n\t'https://acme-v02.api.letsencrypt.org/directory';\nexport const LETSENCRYPT_STAGING =\n\t'https://acme-staging-v02.api.letsencrypt.org/directory';\n\n// =============================================================================\n// base64url\n// =============================================================================\n\nconst base64UrlEncode = (bytes: Uint8Array | ArrayBuffer): string => {\n\tconst buf = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);\n\tlet binary = '';\n\tfor (const byte of buf) binary += String.fromCharCode(byte);\n\treturn btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '');\n};\n\nconst base64UrlEncodeString = (value: string): string =>\n\tbase64UrlEncode(new TextEncoder().encode(value));\n\nconst base64UrlDecode = (value: string): Uint8Array => {\n\tconst padded = value.replaceAll('-', '+').replaceAll('_', '/').padEnd(\n\t\tMath.ceil(value.length / 4) * 4,\n\t\t'='\n\t);\n\tconst binary = atob(padded);\n\tconst bytes = new Uint8Array(binary.length);\n\tfor (let index = 0; index < binary.length; index += 1) {\n\t\tbytes[index] = binary.charCodeAt(index);\n\t}\n\treturn bytes;\n};\n\n// =============================================================================\n// DER encoding (minimal subset for CSR)\n// =============================================================================\n\nconst derLength = (length: number): Uint8Array => {\n\tif (length < 0x80) return Uint8Array.from([length]);\n\tconst bytes: number[] = [];\n\tlet remaining = length;\n\twhile (remaining > 0) {\n\t\tbytes.unshift(remaining & 0xff);\n\t\tremaining >>= 8;\n\t}\n\treturn Uint8Array.from([0x80 | bytes.length, ...bytes]);\n};\n\nconst derTag = (tag: number, payload: Uint8Array): Uint8Array => {\n\tconst length = derLength(payload.length);\n\tconst out = new Uint8Array(1 + length.length + payload.length);\n\tout[0] = tag;\n\tout.set(length, 1);\n\tout.set(payload, 1 + length.length);\n\treturn out;\n};\n\nconst derSeq = (children: Uint8Array[]): Uint8Array => {\n\tconst total = children.reduce((sum, child) => sum + child.length, 0);\n\tconst payload = new Uint8Array(total);\n\tlet offset = 0;\n\tfor (const child of children) {\n\t\tpayload.set(child, offset);\n\t\toffset += child.length;\n\t}\n\treturn derTag(0x30, payload);\n};\n\nconst derSet = (children: Uint8Array[]): Uint8Array => {\n\tconst total = children.reduce((sum, child) => sum + child.length, 0);\n\tconst payload = new Uint8Array(total);\n\tlet offset = 0;\n\tfor (const child of children) {\n\t\tpayload.set(child, offset);\n\t\toffset += child.length;\n\t}\n\treturn derTag(0x31, payload);\n};\n\nconst derInt = (value: number | Uint8Array): Uint8Array => {\n\tif (typeof value === 'number') {\n\t\t// Small unsigned int — used for the CSR version field (0).\n\t\tif (value === 0) return derTag(0x02, Uint8Array.from([0]));\n\t\tconst bytes: number[] = [];\n\t\tlet remaining = value;\n\t\twhile (remaining > 0) {\n\t\t\tbytes.unshift(remaining & 0xff);\n\t\t\tremaining >>= 8;\n\t\t}\n\t\tif ((bytes[0] as number) & 0x80) bytes.unshift(0); // ensure positive\n\t\treturn derTag(0x02, Uint8Array.from(bytes));\n\t}\n\t// Big-endian unsigned bytes — strip leading zeros, then prepend 0x00 if\n\t// the high bit is set (DER INTEGER is signed two's complement).\n\tlet start = 0;\n\twhile (start < value.length - 1 && value[start] === 0) start += 1;\n\tlet payload = value.subarray(start);\n\tif ((payload[0] as number) & 0x80) {\n\t\tconst padded = new Uint8Array(payload.length + 1);\n\t\tpadded.set(payload, 1);\n\t\tpayload = padded;\n\t}\n\treturn derTag(0x02, payload);\n};\n\nconst derOid = (oid: string): Uint8Array => {\n\tconst parts = oid.split('.').map((part) => Number.parseInt(part, 10));\n\tconst first = parts[0];\n\tconst second = parts[1];\n\tif (first === undefined || second === undefined) {\n\t\tthrow new Error(`[deploy/tls] invalid OID: ${oid}`);\n\t}\n\tconst bytes: number[] = [first * 40 + second];\n\tfor (let index = 2; index < parts.length; index += 1) {\n\t\tconst value = parts[index] as number;\n\t\tconst chunks: number[] = [];\n\t\tlet remaining = value;\n\t\tdo {\n\t\t\tchunks.unshift(remaining & 0x7f);\n\t\t\tremaining >>= 7;\n\t\t} while (remaining > 0);\n\t\tfor (let chunkIndex = 0; chunkIndex < chunks.length - 1; chunkIndex += 1) {\n\t\t\tchunks[chunkIndex] = (chunks[chunkIndex] as number) | 0x80;\n\t\t}\n\t\tbytes.push(...chunks);\n\t}\n\treturn derTag(0x06, Uint8Array.from(bytes));\n};\n\nconst derPrintableString = (value: string): Uint8Array =>\n\tderTag(0x13, new TextEncoder().encode(value));\n\nconst derUtf8String = (value: string): Uint8Array =>\n\tderTag(0x0c, new TextEncoder().encode(value));\n\nconst derIa5String = (value: string): Uint8Array =>\n\tderTag(0x16, new TextEncoder().encode(value));\n\nconst derOctetString = (payload: Uint8Array): Uint8Array =>\n\tderTag(0x04, payload);\n\nconst derBitString = (payload: Uint8Array): Uint8Array => {\n\t// 0x00 prefix = number of unused bits in the final byte; for byte-aligned\n\t// signatures + keys this is always zero.\n\tconst bits = new Uint8Array(payload.length + 1);\n\tbits[0] = 0;\n\tbits.set(payload, 1);\n\treturn derTag(0x03, bits);\n};\n\nconst derContextTag = (\n\ttag: number,\n\tpayload: Uint8Array,\n\tconstructed = true\n): Uint8Array => derTag(0xa0 + tag + (constructed ? 0 : -0x20), payload);\n\n// =============================================================================\n// ECDSA signature: raw r||s ↔ DER SEQUENCE\n// =============================================================================\n\nconst ecdsaRawToDer = (rawSig: Uint8Array): Uint8Array => {\n\tconst half = rawSig.length / 2;\n\tconst r = rawSig.subarray(0, half);\n\tconst s = rawSig.subarray(half);\n\treturn derSeq([derInt(r), derInt(s)]);\n};\n\n// =============================================================================\n// JWS (RFC 7515) Flattened JSON Serialization for ACME\n// =============================================================================\n\ntype JwsHeader = {\n\talg: 'ES256';\n\tnonce: string;\n\turl: string;\n\tjwk?: JsonWebKey;\n\tkid?: string;\n};\n\ntype JwsBody = {\n\tprotected: string;\n\tpayload: string;\n\tsignature: string;\n};\n\nconst signJws = async (\n\tprivateKey: CryptoKey,\n\theader: JwsHeader,\n\tpayload: object | string\n): Promise<JwsBody> => {\n\tconst protectedHeader = base64UrlEncodeString(JSON.stringify(header));\n\tconst payloadEncoded =\n\t\tpayload === ''\n\t\t\t? '' // POST-as-GET: empty string, NOT empty object\n\t\t\t: base64UrlEncodeString(JSON.stringify(payload));\n\tconst signingInput = new TextEncoder().encode(\n\t\t`${protectedHeader}.${payloadEncoded}`\n\t);\n\tconst rawSig = new Uint8Array(\n\t\tawait crypto.subtle.sign(\n\t\t\t{ hash: 'SHA-256', name: 'ECDSA' },\n\t\t\tprivateKey,\n\t\t\tsigningInput\n\t\t)\n\t);\n\treturn {\n\t\tpayload: payloadEncoded,\n\t\tprotected: protectedHeader,\n\t\tsignature: base64UrlEncode(rawSig)\n\t};\n};\n\n// =============================================================================\n// JWK helpers — canonical thumbprint, public-key extraction\n// =============================================================================\n\nconst exportPublicJwk = async (publicKey: CryptoKey): Promise<JsonWebKey> => {\n\tconst jwk = await crypto.subtle.exportKey('jwk', publicKey);\n\t// Strip private fields if the export included them (shouldn't, on a\n\t// public key, but be defensive).\n\treturn {\n\t\tcrv: jwk.crv,\n\t\tkty: jwk.kty,\n\t\tx: jwk.x,\n\t\ty: jwk.y\n\t};\n};\n\nconst jwkThumbprint = async (publicJwk: JsonWebKey): Promise<string> => {\n\t// RFC 7638 — canonical JSON: required fields only, lex order.\n\tconst canonical = JSON.stringify({\n\t\tcrv: publicJwk.crv,\n\t\tkty: publicJwk.kty,\n\t\tx: publicJwk.x,\n\t\ty: publicJwk.y\n\t});\n\tconst hash = await crypto.subtle.digest(\n\t\t'SHA-256',\n\t\tnew TextEncoder().encode(canonical)\n\t);\n\treturn base64UrlEncode(hash);\n};\n\n// =============================================================================\n// Account key — generation + JSON export/import for persistence\n// =============================================================================\n\nexport type AcmeAccount = {\n\tkey: CryptoKeyPair;\n\t/** Account URL — set after first registration; persisted for renewals. */\n\tkid?: string;\n};\n\nexport type AcmeAccountJson = {\n\tpublicJwk: JsonWebKey;\n\tprivateJwk: JsonWebKey;\n\tkid?: string;\n};\n\nexport const generateAccountKey = async (): Promise<AcmeAccount> => {\n\tconst key = await crypto.subtle.generateKey(\n\t\t{ name: 'ECDSA', namedCurve: 'P-256' },\n\t\ttrue,\n\t\t['sign', 'verify']\n\t);\n\treturn { key };\n};\n\nexport const exportAccount = async (\n\taccount: AcmeAccount\n): Promise<AcmeAccountJson> => {\n\tconst publicJwk = await crypto.subtle.exportKey('jwk', account.key.publicKey);\n\tconst privateJwk = await crypto.subtle.exportKey(\n\t\t'jwk',\n\t\taccount.key.privateKey\n\t);\n\treturn {\n\t\tprivateJwk,\n\t\tpublicJwk,\n\t\t...(account.kid !== undefined ? { kid: account.kid } : {})\n\t};\n};\n\nexport const importAccount = async (\n\tjson: AcmeAccountJson\n): Promise<AcmeAccount> => {\n\tconst publicKey = await crypto.subtle.importKey(\n\t\t'jwk',\n\t\tjson.publicJwk,\n\t\t{ name: 'ECDSA', namedCurve: 'P-256' },\n\t\ttrue,\n\t\t['verify']\n\t);\n\tconst privateKey = await crypto.subtle.importKey(\n\t\t'jwk',\n\t\tjson.privateJwk,\n\t\t{ name: 'ECDSA', namedCurve: 'P-256' },\n\t\ttrue,\n\t\t['sign']\n\t);\n\treturn {\n\t\tkey: { privateKey, publicKey },\n\t\t...(json.kid !== undefined ? { kid: json.kid } : {})\n\t};\n};\n\n// =============================================================================\n// CSR — Certificate Signing Request\n// =============================================================================\n\nconst buildSanExtension = (domains: string[]): Uint8Array => {\n\t// SubjectAltName extension value: SEQUENCE OF GeneralName\n\tconst generalNames = domains.map((domain) =>\n\t\tderTag(0x82, new TextEncoder().encode(domain))\n\t);\n\tconst sanSequence = derSeq(generalNames);\n\tconst extensionValue = derOctetString(sanSequence);\n\treturn derSeq([derOid('2.5.29.17'), extensionValue]);\n};\n\nconst buildExtensionRequestAttribute = (domains: string[]): Uint8Array => {\n\tconst extensions = derSeq([buildSanExtension(domains)]);\n\treturn derSeq([\n\t\tderOid('1.2.840.113549.1.9.14'), // PKCS#9 extensionRequest\n\t\tderSet([extensions])\n\t]);\n};\n\nconst generateCertKeyPair = async (): Promise<CryptoKeyPair> =>\n\tcrypto.subtle.generateKey(\n\t\t{ name: 'ECDSA', namedCurve: 'P-256' },\n\t\ttrue,\n\t\t['sign', 'verify']\n\t);\n\nconst buildCsr = async (\n\tdomains: string[],\n\tkeypair: CryptoKeyPair\n): Promise<Uint8Array> => {\n\tif (domains.length === 0) {\n\t\tthrow new Error('[deploy/tls] CSR requires at least one domain');\n\t}\n\tconst commonName = domains[0] as string;\n\n\tconst spkiDer = new Uint8Array(\n\t\tawait crypto.subtle.exportKey('spki', keypair.publicKey)\n\t);\n\n\tconst version = derInt(0);\n\tconst subject = derSeq([\n\t\tderSet([\n\t\t\tderSeq([\n\t\t\t\tderOid('2.5.4.3'), // commonName\n\t\t\t\tderUtf8String(commonName)\n\t\t\t])\n\t\t])\n\t]);\n\n\tconst attributes = derContextTag(0, buildExtensionRequestAttribute(domains));\n\n\tconst certificationRequestInfo = derSeq([\n\t\tversion,\n\t\tsubject,\n\t\tspkiDer,\n\t\tattributes\n\t]);\n\n\tconst rawSig = new Uint8Array(\n\t\tawait crypto.subtle.sign(\n\t\t\t{ hash: 'SHA-256', name: 'ECDSA' },\n\t\t\tkeypair.privateKey,\n\t\t\tcertificationRequestInfo as BufferSource\n\t\t)\n\t);\n\tconst sigDer = ecdsaRawToDer(rawSig);\n\n\tconst sigAlgorithm = derSeq([derOid('1.2.840.10045.4.3.2')]);\n\t// ecdsa-with-SHA256\n\tconst signatureBits = derBitString(sigDer);\n\n\treturn derSeq([certificationRequestInfo, sigAlgorithm, signatureBits]);\n};\n\n// =============================================================================\n// PEM\n// =============================================================================\n\nconst derToPem = (label: string, der: Uint8Array): string => {\n\tconst b64 = btoa(String.fromCharCode(...der));\n\tconst lines = b64.match(/.{1,64}/g) ?? [];\n\treturn `-----BEGIN ${label}-----\\n${lines.join('\\n')}\\n-----END ${label}-----\\n`;\n};\n\nconst exportEcPrivateKeyPem = async (\n\tkeypair: CryptoKeyPair\n): Promise<string> => {\n\tconst pkcs8 = new Uint8Array(\n\t\tawait crypto.subtle.exportKey('pkcs8', keypair.privateKey)\n\t);\n\treturn derToPem('PRIVATE KEY', pkcs8);\n};\n\n// =============================================================================\n// ACME client\n// =============================================================================\n\ntype AcmeDirectory = {\n\tnewNonce: string;\n\tnewAccount: string;\n\tnewOrder: string;\n};\n\ntype AcmeOrder = {\n\tstatus: string;\n\tidentifiers: Array<{ type: string; value: string }>;\n\tauthorizations: string[];\n\tfinalize: string;\n\tcertificate?: string;\n};\n\ntype AcmeAuthorization = {\n\tstatus: string;\n\tidentifier: { type: string; value: string };\n\tchallenges: Array<{\n\t\ttype: string;\n\t\tstatus: string;\n\t\turl: string;\n\t\ttoken: string;\n\t}>;\n};\n\nexport type IssueCertificateOptions = {\n\t/** Domain(s) to include. First is the CN; all are SANs. */\n\tdomains: string[];\n\t/** DNS provider used for DNS-01 challenges (must own the zone). */\n\tdnsProvider: DnsProvider;\n\t/** Contact email for the ACME account. */\n\temail: string;\n\t/** ACME directory URL. Default Let's Encrypt production. */\n\tdirectoryUrl?: string;\n\t/** Reuse an existing account. Pass `exportAccount`'s output via `importAccount`. */\n\taccount?: AcmeAccount;\n\t/** Override fetch (tests). Default global fetch. */\n\tfetch?: typeof fetch;\n\t/** Poll interval. Default 3 s. */\n\tpollIntervalMs?: number;\n\t/**\n\t * Max wait before notifying ACME that the DNS challenge is ready.\n\t * Set ~30-60s for Cloudflare; longer for slower providers. Default 30 s.\n\t */\n\tdnsPropagationDelayMs?: number;\n\t/** Max wait for the order to become valid. Default 5 min. */\n\torderTimeoutMs?: number;\n\t/** Status log lines. */\n\tonLog?: (line: string) => void;\n\t/** Override sleep (tests). */\n\tsleep?: (ms: number) => Promise<void>;\n\t/**\n\t * Optional pre-check: returns true when DNS has propagated globally.\n\t * Default: just wait `dnsPropagationDelayMs` then proceed. Override\n\t * for production to actually verify (e.g. resolve from multiple\n\t * public resolvers).\n\t */\n\tcheckDnsPropagated?: (\n\t\trecordName: string,\n\t\texpectedValue: string\n\t) => Promise<boolean>;\n};\n\nexport type IssuedCertificate = {\n\tcertificatePem: string;\n\tprivateKeyPem: string;\n\taccount: AcmeAccount;\n\tdomains: string[];\n};\n\nexport class AcmeError extends Error {\n\treadonly status: number;\n\treadonly body: unknown;\n\tconstructor(message: string, status: number, body: unknown) {\n\t\tsuper(message);\n\t\tthis.name = 'AcmeError';\n\t\tthis.status = status;\n\t\tthis.body = body;\n\t}\n}\n\nconst defaultSleep = (ms: number): Promise<void> =>\n\tnew Promise((resolve) => setTimeout(resolve, ms));\n\nexport const issueCertificate = async (\n\toptions: IssueCertificateOptions\n): Promise<IssuedCertificate> => {\n\tconst log = options.onLog ?? (() => {});\n\tconst sleep = options.sleep ?? defaultSleep;\n\tconst fetcher = options.fetch ?? fetch;\n\tconst directoryUrl = options.directoryUrl ?? LETSENCRYPT_PRODUCTION;\n\tconst pollMs = options.pollIntervalMs ?? 3_000;\n\tconst dnsDelay = options.dnsPropagationDelayMs ?? 30_000;\n\tconst orderTimeout = options.orderTimeoutMs ?? 5 * 60_000;\n\tconst account = options.account ?? (await generateAccountKey());\n\n\tif (options.domains.length === 0) {\n\t\tthrow new Error('[deploy/tls] at least one domain is required');\n\t}\n\n\t// 1. Fetch directory.\n\tlog(`[tls] fetching ACME directory ${directoryUrl}`);\n\tconst directoryResponse = await fetcher(directoryUrl);\n\tif (!directoryResponse.ok) {\n\t\tthrow new AcmeError(\n\t\t\t`failed to fetch ACME directory: ${directoryResponse.status}`,\n\t\t\tdirectoryResponse.status,\n\t\t\tawait directoryResponse.text()\n\t\t);\n\t}\n\tconst directory = (await directoryResponse.json()) as AcmeDirectory;\n\n\t// 2. Get initial nonce.\n\tlet nonce = await fetchNonce(fetcher, directory.newNonce);\n\n\t// Helper: signed POST. Tracks nonce; returns parsed JSON body + headers.\n\tconst post = async (\n\t\turl: string,\n\t\tpayload: object | string,\n\t\tidentification: { kid?: string; jwk?: JsonWebKey }\n\t): Promise<{ status: number; body: unknown; headers: Headers }> => {\n\t\tconst header: JwsHeader = {\n\t\t\talg: 'ES256',\n\t\t\tnonce,\n\t\t\turl,\n\t\t\t...(identification.kid !== undefined ? { kid: identification.kid } : {}),\n\t\t\t...(identification.jwk !== undefined ? { jwk: identification.jwk } : {})\n\t\t};\n\t\tconst jws = await signJws(account.key.privateKey, header, payload);\n\t\tconst response = await fetcher(url, {\n\t\t\tbody: JSON.stringify(jws),\n\t\t\theaders: { 'content-type': 'application/jose+json' },\n\t\t\tmethod: 'POST'\n\t\t});\n\t\tconst newNonce = response.headers.get('replay-nonce');\n\t\tif (newNonce !== null) nonce = newNonce;\n\t\tconst text = await response.text();\n\t\tconst body =\n\t\t\ttext.length > 0 && response.headers.get('content-type')?.includes('json')\n\t\t\t\t? JSON.parse(text)\n\t\t\t\t: text;\n\t\tif (!response.ok) {\n\t\t\tthrow new AcmeError(\n\t\t\t\t`ACME ${url} → ${response.status}`,\n\t\t\t\tresponse.status,\n\t\t\t\tbody\n\t\t\t);\n\t\t}\n\t\treturn { body, headers: response.headers, status: response.status };\n\t};\n\n\t// 3. Register the account (or reuse if kid is set).\n\tconst publicJwk = await exportPublicJwk(account.key.publicKey);\n\tif (account.kid === undefined) {\n\t\tlog(`[tls] registering ACME account for ${options.email}`);\n\t\tconst result = await post(\n\t\t\tdirectory.newAccount,\n\t\t\t{\n\t\t\t\tcontact: [`mailto:${options.email}`],\n\t\t\t\ttermsOfServiceAgreed: true\n\t\t\t},\n\t\t\t{ jwk: publicJwk }\n\t\t);\n\t\taccount.kid = result.headers.get('location') ?? undefined;\n\t\tif (account.kid === undefined) {\n\t\t\tthrow new Error(\n\t\t\t\t'[deploy/tls] ACME newAccount response missing Location header'\n\t\t\t);\n\t\t}\n\t\tlog(`[tls] account registered: ${account.kid}`);\n\t} else {\n\t\tlog(`[tls] reusing account ${account.kid}`);\n\t}\n\n\tconst accountKid = account.kid;\n\n\t// 4. Submit the order.\n\tlog(`[tls] submitting order for ${options.domains.join(', ')}`);\n\tconst orderResult = await post(\n\t\tdirectory.newOrder,\n\t\t{\n\t\t\tidentifiers: options.domains.map((domain) => ({\n\t\t\t\ttype: 'dns',\n\t\t\t\tvalue: domain\n\t\t\t}))\n\t\t},\n\t\t{ kid: accountKid }\n\t);\n\tlet order = orderResult.body as AcmeOrder;\n\tconst orderUrl = orderResult.headers.get('location');\n\tif (orderUrl === null) {\n\t\tthrow new Error(\n\t\t\t'[deploy/tls] ACME newOrder response missing Location header'\n\t\t);\n\t}\n\n\t// 5. For each authorization, complete the DNS-01 challenge.\n\tconst cleanups: Array<() => Promise<void>> = [];\n\tconst dnsCreated: Array<{\n\t\trecordId: string;\n\t\trecordName: string;\n\t\trecordValue: string;\n\t}> = [];\n\n\ttry {\n\t\tconst thumbprint = await jwkThumbprint(publicJwk);\n\n\t\tfor (const authUrl of order.authorizations) {\n\t\t\tconst authResult = await post(authUrl, '', { kid: accountKid });\n\t\t\tconst auth = authResult.body as AcmeAuthorization;\n\t\t\tconst challenge = auth.challenges.find((c) => c.type === 'dns-01');\n\t\t\tif (challenge === undefined) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`[deploy/tls] no dns-01 challenge for ${auth.identifier.value}`\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst keyAuthorization = `${challenge.token}.${thumbprint}`;\n\t\t\tconst txtBytes = new Uint8Array(\n\t\t\t\tawait crypto.subtle.digest(\n\t\t\t\t\t'SHA-256',\n\t\t\t\t\tnew TextEncoder().encode(keyAuthorization)\n\t\t\t\t)\n\t\t\t);\n\t\t\tconst txtValue = base64UrlEncode(txtBytes);\n\t\t\tconst recordName = `_acme-challenge.${auth.identifier.value}`;\n\t\t\tlog(`[tls] DNS-01: setting ${recordName} = \"${txtValue}\"`);\n\t\t\tconst record = await options.dnsProvider.upsert({\n\t\t\t\tcontent: txtValue,\n\t\t\t\tname: recordName,\n\t\t\t\tttl: 60,\n\t\t\t\ttype: 'TXT'\n\t\t\t});\n\t\t\tdnsCreated.push({\n\t\t\t\trecordId: record.id,\n\t\t\t\trecordName,\n\t\t\t\trecordValue: txtValue\n\t\t\t});\n\t\t\tcleanups.push(() => options.dnsProvider.delete(record.id));\n\n\t\t\tif (options.checkDnsPropagated !== undefined) {\n\t\t\t\tlog('[tls] waiting for DNS propagation (custom checker)…');\n\t\t\t\tconst deadline = Date.now() + dnsDelay;\n\t\t\t\twhile (Date.now() < deadline) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tawait options.checkDnsPropagated(recordName, txtValue)\n\t\t\t\t\t) break;\n\t\t\t\t\tawait sleep(pollMs);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlog(`[tls] sleeping ${dnsDelay}ms for DNS propagation`);\n\t\t\t\tawait sleep(dnsDelay);\n\t\t\t}\n\n\t\t\tlog(`[tls] notifying ACME of dns-01 readiness for ${auth.identifier.value}`);\n\t\t\tawait post(challenge.url, {}, { kid: accountKid });\n\n\t\t\t// Poll the authorization until valid/invalid.\n\t\t\tconst authDeadline = Date.now() + orderTimeout;\n\t\t\twhile (Date.now() < authDeadline) {\n\t\t\t\tconst polledResult = await post(authUrl, '', { kid: accountKid });\n\t\t\t\tconst polled = polledResult.body as AcmeAuthorization;\n\t\t\t\tif (polled.status === 'valid') break;\n\t\t\t\tif (polled.status === 'invalid') {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`[deploy/tls] authorization failed for ${auth.identifier.value}: ${JSON.stringify(polled.challenges)}`\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tawait sleep(pollMs);\n\t\t\t}\n\t\t}\n\n\t\t// 6. Finalize — generate cert keypair + CSR.\n\t\tlog('[tls] generating cert keypair + CSR');\n\t\tconst certKey = await generateCertKeyPair();\n\t\tconst csrDer = await buildCsr(options.domains, certKey);\n\t\tconst csr = base64UrlEncode(csrDer);\n\n\t\tawait post(order.finalize, { csr }, { kid: accountKid });\n\n\t\t// 7. Poll the order until valid + certificate URL appears.\n\t\tconst orderDeadline = Date.now() + orderTimeout;\n\t\twhile (Date.now() < orderDeadline) {\n\t\t\tconst refreshed = await post(orderUrl, '', { kid: accountKid });\n\t\t\torder = refreshed.body as AcmeOrder;\n\t\t\tif (order.status === 'valid' && order.certificate !== undefined) break;\n\t\t\tif (order.status === 'invalid') {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`[deploy/tls] order failed: ${JSON.stringify(order)}`\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait sleep(pollMs);\n\t\t}\n\n\t\tif (order.certificate === undefined) {\n\t\t\tthrow new Error('[deploy/tls] order timed out before issuing certificate');\n\t\t}\n\n\t\t// 8. Download the cert (PEM bundle).\n\t\tlog(`[tls] downloading certificate from ${order.certificate}`);\n\t\tconst certResult = await post(order.certificate, '', { kid: accountKid });\n\t\tconst certificatePem =\n\t\t\ttypeof certResult.body === 'string'\n\t\t\t\t? certResult.body\n\t\t\t\t: String(certResult.body);\n\n\t\tconst privateKeyPem = await exportEcPrivateKeyPem(certKey);\n\n\t\treturn {\n\t\t\taccount,\n\t\t\tcertificatePem,\n\t\t\tdomains: options.domains,\n\t\t\tprivateKeyPem\n\t\t};\n\t} finally {\n\t\tfor (const cleanup of cleanups) {\n\t\t\ttry {\n\t\t\t\tawait cleanup();\n\t\t\t} catch (error) {\n\t\t\t\tlog(`[tls] cleanup failed (continuing): ${String(error)}`);\n\t\t\t}\n\t\t}\n\t\t// Suppress unused-variable lint without changing behavior.\n\t\tvoid dnsCreated;\n\t}\n};\n\nconst fetchNonce = async (\n\tfetcher: typeof fetch,\n\turl: string\n): Promise<string> => {\n\tconst response = await fetcher(url, { method: 'HEAD' });\n\tconst nonce = response.headers.get('replay-nonce');\n\tif (nonce === null) {\n\t\tthrow new Error('[deploy/tls] newNonce response missing Replay-Nonce header');\n\t}\n\treturn nonce;\n};\n\n// =============================================================================\n// installCertificateOnTarget\n// =============================================================================\n\nexport type InstallCertificateOptions = {\n\t/** Remote path for the cert chain. Default `/etc/ssl/<firstDomain>/fullchain.pem`. */\n\tcertPath?: string;\n\t/** Remote path for the private key. Default `/etc/ssl/<firstDomain>/privkey.pem`. */\n\tkeyPath?: string;\n\t/** Mode (chmod) for the cert + key. Default `600`. */\n\tmode?: string;\n\t/** Owner (chown) for the cert + key. Default unchanged. */\n\towner?: string;\n\t/** Optional reload command run after install (e.g. `'systemctl reload nginx'`). */\n\treload?: string;\n\t/** Override the writeTo helper (tests). */\n\twriteFile?: (target: Target, path: string, contents: string) => Promise<void>;\n};\n\nconst defaultWriteFile = async (\n\ttarget: Target,\n\tremotePath: string,\n\tcontents: string\n): Promise<void> => {\n\tconst escaped = contents.replaceAll(\"'\", \"'\\\\''\");\n\tawait target.exec(`mkdir -p \"$(dirname '${remotePath}')\"`);\n\tawait target.exec(`cat > '${remotePath}' <<'__ABS_TLS_EOF__'\\n${contents}__ABS_TLS_EOF__\\n`);\n\tvoid escaped;\n};\n\n/**\n * Upload cert + private key to the target. Composable with the deploy\n * pipeline as a verify-step or post-deploy hook.\n */\nexport const installCertificateOnTarget = async (\n\ttarget: Target,\n\tcert: IssuedCertificate,\n\toptions: InstallCertificateOptions = {}\n): Promise<{ certPath: string; keyPath: string }> => {\n\tconst domain = cert.domains[0];\n\tif (domain === undefined) {\n\t\tthrow new Error('[deploy/tls] certificate has no domains');\n\t}\n\tconst certPath = options.certPath ?? `/etc/ssl/${domain}/fullchain.pem`;\n\tconst keyPath = options.keyPath ?? `/etc/ssl/${domain}/privkey.pem`;\n\tconst mode = options.mode ?? '600';\n\tconst writer = options.writeFile ?? defaultWriteFile;\n\n\tawait writer(target, certPath, cert.certificatePem);\n\tawait writer(target, keyPath, cert.privateKeyPem);\n\tawait target.exec(`chmod ${mode} '${certPath}' '${keyPath}'`);\n\tif (options.owner !== undefined) {\n\t\tawait target.exec(`chown ${options.owner} '${certPath}' '${keyPath}'`);\n\t}\n\tif (options.reload !== undefined) {\n\t\tawait target.exec(options.reload);\n\t}\n\n\treturn { certPath, keyPath };\n};\n"
5
+ "/**\n * @absolutejs/deploy/tls — ACME (RFC 8555) client for Let's Encrypt and\n * compatible CAs. DNS-01 challenges only — uses the {@link DnsProvider}\n * abstraction from `./dns`, so the same Cloudflare / Route 53 /\n * Hetzner-DNS providers that point hostnames at boxes also satisfy\n * ACME's challenge requirement.\n *\n * Zero peer deps. Bun's `crypto.subtle` covers JWS signing and ECDSA\n * key generation; a small DER encoder builds the CSR.\n *\n * Public API:\n *\n * issueCertificate({ domains, dnsProvider, email })\n * → { certificatePem, privateKeyPem, account, domains }\n *\n * installCertificateOnTarget(target, cert, { certPath, keyPath, reload? })\n * → uploads PEM files via Target.upload + optional reload exec\n *\n * exportAccountKey / importAccountKey for persistence between runs\n * (reuse the same account across cert renewals — cheaper, doesn't\n * hit Let's Encrypt's account-creation rate limit)\n */\n\nimport { X509Certificate } from 'node:crypto';\nimport type { Target } from './targets';\nimport type { DnsProvider } from './dns';\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nexport const LETSENCRYPT_PRODUCTION =\n\t'https://acme-v02.api.letsencrypt.org/directory';\nexport const LETSENCRYPT_STAGING =\n\t'https://acme-staging-v02.api.letsencrypt.org/directory';\n\n// =============================================================================\n// base64url\n// =============================================================================\n\nconst base64UrlEncode = (bytes: Uint8Array | ArrayBuffer): string => {\n\tconst buf = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);\n\tlet binary = '';\n\tfor (const byte of buf) binary += String.fromCharCode(byte);\n\treturn btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '');\n};\n\nconst base64UrlEncodeString = (value: string): string =>\n\tbase64UrlEncode(new TextEncoder().encode(value));\n\nconst base64UrlDecode = (value: string): Uint8Array => {\n\tconst padded = value.replaceAll('-', '+').replaceAll('_', '/').padEnd(\n\t\tMath.ceil(value.length / 4) * 4,\n\t\t'='\n\t);\n\tconst binary = atob(padded);\n\tconst bytes = new Uint8Array(binary.length);\n\tfor (let index = 0; index < binary.length; index += 1) {\n\t\tbytes[index] = binary.charCodeAt(index);\n\t}\n\treturn bytes;\n};\n\n// =============================================================================\n// DER encoding (minimal subset for CSR)\n// =============================================================================\n\nconst derLength = (length: number): Uint8Array => {\n\tif (length < 0x80) return Uint8Array.from([length]);\n\tconst bytes: number[] = [];\n\tlet remaining = length;\n\twhile (remaining > 0) {\n\t\tbytes.unshift(remaining & 0xff);\n\t\tremaining >>= 8;\n\t}\n\treturn Uint8Array.from([0x80 | bytes.length, ...bytes]);\n};\n\nconst derTag = (tag: number, payload: Uint8Array): Uint8Array => {\n\tconst length = derLength(payload.length);\n\tconst out = new Uint8Array(1 + length.length + payload.length);\n\tout[0] = tag;\n\tout.set(length, 1);\n\tout.set(payload, 1 + length.length);\n\treturn out;\n};\n\nconst derSeq = (children: Uint8Array[]): Uint8Array => {\n\tconst total = children.reduce((sum, child) => sum + child.length, 0);\n\tconst payload = new Uint8Array(total);\n\tlet offset = 0;\n\tfor (const child of children) {\n\t\tpayload.set(child, offset);\n\t\toffset += child.length;\n\t}\n\treturn derTag(0x30, payload);\n};\n\nconst derSet = (children: Uint8Array[]): Uint8Array => {\n\tconst total = children.reduce((sum, child) => sum + child.length, 0);\n\tconst payload = new Uint8Array(total);\n\tlet offset = 0;\n\tfor (const child of children) {\n\t\tpayload.set(child, offset);\n\t\toffset += child.length;\n\t}\n\treturn derTag(0x31, payload);\n};\n\nconst derInt = (value: number | Uint8Array): Uint8Array => {\n\tif (typeof value === 'number') {\n\t\t// Small unsigned int — used for the CSR version field (0).\n\t\tif (value === 0) return derTag(0x02, Uint8Array.from([0]));\n\t\tconst bytes: number[] = [];\n\t\tlet remaining = value;\n\t\twhile (remaining > 0) {\n\t\t\tbytes.unshift(remaining & 0xff);\n\t\t\tremaining >>= 8;\n\t\t}\n\t\tif ((bytes[0] as number) & 0x80) bytes.unshift(0); // ensure positive\n\t\treturn derTag(0x02, Uint8Array.from(bytes));\n\t}\n\t// Big-endian unsigned bytes — strip leading zeros, then prepend 0x00 if\n\t// the high bit is set (DER INTEGER is signed two's complement).\n\tlet start = 0;\n\twhile (start < value.length - 1 && value[start] === 0) start += 1;\n\tlet payload = value.subarray(start);\n\tif ((payload[0] as number) & 0x80) {\n\t\tconst padded = new Uint8Array(payload.length + 1);\n\t\tpadded.set(payload, 1);\n\t\tpayload = padded;\n\t}\n\treturn derTag(0x02, payload);\n};\n\nconst derOid = (oid: string): Uint8Array => {\n\tconst parts = oid.split('.').map((part) => Number.parseInt(part, 10));\n\tconst first = parts[0];\n\tconst second = parts[1];\n\tif (first === undefined || second === undefined) {\n\t\tthrow new Error(`[deploy/tls] invalid OID: ${oid}`);\n\t}\n\tconst bytes: number[] = [first * 40 + second];\n\tfor (let index = 2; index < parts.length; index += 1) {\n\t\tconst value = parts[index] as number;\n\t\tconst chunks: number[] = [];\n\t\tlet remaining = value;\n\t\tdo {\n\t\t\tchunks.unshift(remaining & 0x7f);\n\t\t\tremaining >>= 7;\n\t\t} while (remaining > 0);\n\t\tfor (let chunkIndex = 0; chunkIndex < chunks.length - 1; chunkIndex += 1) {\n\t\t\tchunks[chunkIndex] = (chunks[chunkIndex] as number) | 0x80;\n\t\t}\n\t\tbytes.push(...chunks);\n\t}\n\treturn derTag(0x06, Uint8Array.from(bytes));\n};\n\nconst derPrintableString = (value: string): Uint8Array =>\n\tderTag(0x13, new TextEncoder().encode(value));\n\nconst derUtf8String = (value: string): Uint8Array =>\n\tderTag(0x0c, new TextEncoder().encode(value));\n\nconst derIa5String = (value: string): Uint8Array =>\n\tderTag(0x16, new TextEncoder().encode(value));\n\nconst derOctetString = (payload: Uint8Array): Uint8Array =>\n\tderTag(0x04, payload);\n\nconst derBitString = (payload: Uint8Array): Uint8Array => {\n\t// 0x00 prefix = number of unused bits in the final byte; for byte-aligned\n\t// signatures + keys this is always zero.\n\tconst bits = new Uint8Array(payload.length + 1);\n\tbits[0] = 0;\n\tbits.set(payload, 1);\n\treturn derTag(0x03, bits);\n};\n\nconst derContextTag = (\n\ttag: number,\n\tpayload: Uint8Array,\n\tconstructed = true\n): Uint8Array => derTag(0xa0 + tag + (constructed ? 0 : -0x20), payload);\n\n// =============================================================================\n// ECDSA signature: raw r||s ↔ DER SEQUENCE\n// =============================================================================\n\nconst ecdsaRawToDer = (rawSig: Uint8Array): Uint8Array => {\n\tconst half = rawSig.length / 2;\n\tconst r = rawSig.subarray(0, half);\n\tconst s = rawSig.subarray(half);\n\treturn derSeq([derInt(r), derInt(s)]);\n};\n\n// =============================================================================\n// JWS (RFC 7515) Flattened JSON Serialization for ACME\n// =============================================================================\n\ntype JwsHeader = {\n\talg: 'ES256';\n\tnonce: string;\n\turl: string;\n\tjwk?: JsonWebKey;\n\tkid?: string;\n};\n\ntype JwsBody = {\n\tprotected: string;\n\tpayload: string;\n\tsignature: string;\n};\n\nconst signJws = async (\n\tprivateKey: CryptoKey,\n\theader: JwsHeader,\n\tpayload: object | string\n): Promise<JwsBody> => {\n\tconst protectedHeader = base64UrlEncodeString(JSON.stringify(header));\n\tconst payloadEncoded =\n\t\tpayload === ''\n\t\t\t? '' // POST-as-GET: empty string, NOT empty object\n\t\t\t: base64UrlEncodeString(JSON.stringify(payload));\n\tconst signingInput = new TextEncoder().encode(\n\t\t`${protectedHeader}.${payloadEncoded}`\n\t);\n\tconst rawSig = new Uint8Array(\n\t\tawait crypto.subtle.sign(\n\t\t\t{ hash: 'SHA-256', name: 'ECDSA' },\n\t\t\tprivateKey,\n\t\t\tsigningInput\n\t\t)\n\t);\n\treturn {\n\t\tpayload: payloadEncoded,\n\t\tprotected: protectedHeader,\n\t\tsignature: base64UrlEncode(rawSig)\n\t};\n};\n\n// =============================================================================\n// JWK helpers — canonical thumbprint, public-key extraction\n// =============================================================================\n\nconst exportPublicJwk = async (publicKey: CryptoKey): Promise<JsonWebKey> => {\n\tconst jwk = await crypto.subtle.exportKey('jwk', publicKey);\n\t// Strip private fields if the export included them (shouldn't, on a\n\t// public key, but be defensive).\n\treturn {\n\t\tcrv: jwk.crv,\n\t\tkty: jwk.kty,\n\t\tx: jwk.x,\n\t\ty: jwk.y\n\t};\n};\n\nconst jwkThumbprint = async (publicJwk: JsonWebKey): Promise<string> => {\n\t// RFC 7638 — canonical JSON: required fields only, lex order.\n\tconst canonical = JSON.stringify({\n\t\tcrv: publicJwk.crv,\n\t\tkty: publicJwk.kty,\n\t\tx: publicJwk.x,\n\t\ty: publicJwk.y\n\t});\n\tconst hash = await crypto.subtle.digest(\n\t\t'SHA-256',\n\t\tnew TextEncoder().encode(canonical)\n\t);\n\treturn base64UrlEncode(hash);\n};\n\n// =============================================================================\n// Account key — generation + JSON export/import for persistence\n// =============================================================================\n\nexport type AcmeAccount = {\n\tkey: CryptoKeyPair;\n\t/** Account URL — set after first registration; persisted for renewals. */\n\tkid?: string;\n};\n\nexport type AcmeAccountJson = {\n\tpublicJwk: JsonWebKey;\n\tprivateJwk: JsonWebKey;\n\tkid?: string;\n};\n\nexport const generateAccountKey = async (): Promise<AcmeAccount> => {\n\tconst key = await crypto.subtle.generateKey(\n\t\t{ name: 'ECDSA', namedCurve: 'P-256' },\n\t\ttrue,\n\t\t['sign', 'verify']\n\t);\n\treturn { key };\n};\n\nexport const exportAccount = async (\n\taccount: AcmeAccount\n): Promise<AcmeAccountJson> => {\n\tconst publicJwk = await crypto.subtle.exportKey('jwk', account.key.publicKey);\n\tconst privateJwk = await crypto.subtle.exportKey(\n\t\t'jwk',\n\t\taccount.key.privateKey\n\t);\n\treturn {\n\t\tprivateJwk,\n\t\tpublicJwk,\n\t\t...(account.kid !== undefined ? { kid: account.kid } : {})\n\t};\n};\n\nexport const importAccount = async (\n\tjson: AcmeAccountJson\n): Promise<AcmeAccount> => {\n\tconst publicKey = await crypto.subtle.importKey(\n\t\t'jwk',\n\t\tjson.publicJwk,\n\t\t{ name: 'ECDSA', namedCurve: 'P-256' },\n\t\ttrue,\n\t\t['verify']\n\t);\n\tconst privateKey = await crypto.subtle.importKey(\n\t\t'jwk',\n\t\tjson.privateJwk,\n\t\t{ name: 'ECDSA', namedCurve: 'P-256' },\n\t\ttrue,\n\t\t['sign']\n\t);\n\treturn {\n\t\tkey: { privateKey, publicKey },\n\t\t...(json.kid !== undefined ? { kid: json.kid } : {})\n\t};\n};\n\n// =============================================================================\n// CSR — Certificate Signing Request\n// =============================================================================\n\nconst buildSanExtension = (domains: string[]): Uint8Array => {\n\t// SubjectAltName extension value: SEQUENCE OF GeneralName\n\tconst generalNames = domains.map((domain) =>\n\t\tderTag(0x82, new TextEncoder().encode(domain))\n\t);\n\tconst sanSequence = derSeq(generalNames);\n\tconst extensionValue = derOctetString(sanSequence);\n\treturn derSeq([derOid('2.5.29.17'), extensionValue]);\n};\n\nconst buildExtensionRequestAttribute = (domains: string[]): Uint8Array => {\n\tconst extensions = derSeq([buildSanExtension(domains)]);\n\treturn derSeq([\n\t\tderOid('1.2.840.113549.1.9.14'), // PKCS#9 extensionRequest\n\t\tderSet([extensions])\n\t]);\n};\n\nconst generateCertKeyPair = async (): Promise<CryptoKeyPair> =>\n\tcrypto.subtle.generateKey(\n\t\t{ name: 'ECDSA', namedCurve: 'P-256' },\n\t\ttrue,\n\t\t['sign', 'verify']\n\t);\n\nconst buildCsr = async (\n\tdomains: string[],\n\tkeypair: CryptoKeyPair\n): Promise<Uint8Array> => {\n\tif (domains.length === 0) {\n\t\tthrow new Error('[deploy/tls] CSR requires at least one domain');\n\t}\n\tconst commonName = domains[0] as string;\n\n\tconst spkiDer = new Uint8Array(\n\t\tawait crypto.subtle.exportKey('spki', keypair.publicKey)\n\t);\n\n\tconst version = derInt(0);\n\tconst subject = derSeq([\n\t\tderSet([\n\t\t\tderSeq([\n\t\t\t\tderOid('2.5.4.3'), // commonName\n\t\t\t\tderUtf8String(commonName)\n\t\t\t])\n\t\t])\n\t]);\n\n\tconst attributes = derContextTag(0, buildExtensionRequestAttribute(domains));\n\n\tconst certificationRequestInfo = derSeq([\n\t\tversion,\n\t\tsubject,\n\t\tspkiDer,\n\t\tattributes\n\t]);\n\n\tconst rawSig = new Uint8Array(\n\t\tawait crypto.subtle.sign(\n\t\t\t{ hash: 'SHA-256', name: 'ECDSA' },\n\t\t\tkeypair.privateKey,\n\t\t\tcertificationRequestInfo as BufferSource\n\t\t)\n\t);\n\tconst sigDer = ecdsaRawToDer(rawSig);\n\n\tconst sigAlgorithm = derSeq([derOid('1.2.840.10045.4.3.2')]);\n\t// ecdsa-with-SHA256\n\tconst signatureBits = derBitString(sigDer);\n\n\treturn derSeq([certificationRequestInfo, sigAlgorithm, signatureBits]);\n};\n\n// =============================================================================\n// PEM\n// =============================================================================\n\nconst derToPem = (label: string, der: Uint8Array): string => {\n\tconst b64 = btoa(String.fromCharCode(...der));\n\tconst lines = b64.match(/.{1,64}/g) ?? [];\n\treturn `-----BEGIN ${label}-----\\n${lines.join('\\n')}\\n-----END ${label}-----\\n`;\n};\n\nconst exportEcPrivateKeyPem = async (\n\tkeypair: CryptoKeyPair\n): Promise<string> => {\n\tconst pkcs8 = new Uint8Array(\n\t\tawait crypto.subtle.exportKey('pkcs8', keypair.privateKey)\n\t);\n\treturn derToPem('PRIVATE KEY', pkcs8);\n};\n\n// =============================================================================\n// ACME client\n// =============================================================================\n\ntype AcmeDirectory = {\n\tnewNonce: string;\n\tnewAccount: string;\n\tnewOrder: string;\n};\n\ntype AcmeOrder = {\n\tstatus: string;\n\tidentifiers: Array<{ type: string; value: string }>;\n\tauthorizations: string[];\n\tfinalize: string;\n\tcertificate?: string;\n};\n\ntype AcmeAuthorization = {\n\tstatus: string;\n\tidentifier: { type: string; value: string };\n\tchallenges: Array<{\n\t\ttype: string;\n\t\tstatus: string;\n\t\turl: string;\n\t\ttoken: string;\n\t}>;\n};\n\nexport type IssueCertificateOptions = {\n\t/** Domain(s) to include. First is the CN; all are SANs. */\n\tdomains: string[];\n\t/** DNS provider used for DNS-01 challenges (must own the zone). */\n\tdnsProvider: DnsProvider;\n\t/** Contact email for the ACME account. */\n\temail: string;\n\t/** ACME directory URL. Default Let's Encrypt production. */\n\tdirectoryUrl?: string;\n\t/** Reuse an existing account. Pass `exportAccount`'s output via `importAccount`. */\n\taccount?: AcmeAccount;\n\t/** Override fetch (tests). Default global fetch. */\n\tfetch?: typeof fetch;\n\t/** Poll interval. Default 3 s. */\n\tpollIntervalMs?: number;\n\t/**\n\t * Max wait before notifying ACME that the DNS challenge is ready.\n\t * Set ~30-60s for Cloudflare; longer for slower providers. Default 30 s.\n\t */\n\tdnsPropagationDelayMs?: number;\n\t/** Max wait for the order to become valid. Default 5 min. */\n\torderTimeoutMs?: number;\n\t/** Status log lines. */\n\tonLog?: (line: string) => void;\n\t/** Override sleep (tests). */\n\tsleep?: (ms: number) => Promise<void>;\n\t/**\n\t * Optional pre-check: returns true when DNS has propagated globally.\n\t * Default: just wait `dnsPropagationDelayMs` then proceed. Override\n\t * for production to actually verify (e.g. resolve from multiple\n\t * public resolvers).\n\t */\n\tcheckDnsPropagated?: (\n\t\trecordName: string,\n\t\texpectedValue: string\n\t) => Promise<boolean>;\n};\n\nexport type IssuedCertificate = {\n\tcertificatePem: string;\n\tprivateKeyPem: string;\n\taccount: AcmeAccount;\n\tdomains: string[];\n};\n\nexport class AcmeError extends Error {\n\treadonly status: number;\n\treadonly body: unknown;\n\tconstructor(message: string, status: number, body: unknown) {\n\t\tsuper(message);\n\t\tthis.name = 'AcmeError';\n\t\tthis.status = status;\n\t\tthis.body = body;\n\t}\n}\n\nconst defaultSleep = (ms: number): Promise<void> =>\n\tnew Promise((resolve) => setTimeout(resolve, ms));\n\nexport const issueCertificate = async (\n\toptions: IssueCertificateOptions\n): Promise<IssuedCertificate> => {\n\tconst log = options.onLog ?? (() => {});\n\tconst sleep = options.sleep ?? defaultSleep;\n\tconst fetcher = options.fetch ?? fetch;\n\tconst directoryUrl = options.directoryUrl ?? LETSENCRYPT_PRODUCTION;\n\tconst pollMs = options.pollIntervalMs ?? 3_000;\n\tconst dnsDelay = options.dnsPropagationDelayMs ?? 30_000;\n\tconst orderTimeout = options.orderTimeoutMs ?? 5 * 60_000;\n\tconst account = options.account ?? (await generateAccountKey());\n\n\tif (options.domains.length === 0) {\n\t\tthrow new Error('[deploy/tls] at least one domain is required');\n\t}\n\n\t// 1. Fetch directory.\n\tlog(`[tls] fetching ACME directory ${directoryUrl}`);\n\tconst directoryResponse = await fetcher(directoryUrl);\n\tif (!directoryResponse.ok) {\n\t\tthrow new AcmeError(\n\t\t\t`failed to fetch ACME directory: ${directoryResponse.status}`,\n\t\t\tdirectoryResponse.status,\n\t\t\tawait directoryResponse.text()\n\t\t);\n\t}\n\tconst directory = (await directoryResponse.json()) as AcmeDirectory;\n\n\t// 2. Get initial nonce.\n\tlet nonce = await fetchNonce(fetcher, directory.newNonce);\n\n\t// Helper: signed POST. Tracks nonce; returns parsed JSON body + headers.\n\tconst post = async (\n\t\turl: string,\n\t\tpayload: object | string,\n\t\tidentification: { kid?: string; jwk?: JsonWebKey }\n\t): Promise<{ status: number; body: unknown; headers: Headers }> => {\n\t\tconst header: JwsHeader = {\n\t\t\talg: 'ES256',\n\t\t\tnonce,\n\t\t\turl,\n\t\t\t...(identification.kid !== undefined ? { kid: identification.kid } : {}),\n\t\t\t...(identification.jwk !== undefined ? { jwk: identification.jwk } : {})\n\t\t};\n\t\tconst jws = await signJws(account.key.privateKey, header, payload);\n\t\tconst response = await fetcher(url, {\n\t\t\tbody: JSON.stringify(jws),\n\t\t\theaders: { 'content-type': 'application/jose+json' },\n\t\t\tmethod: 'POST'\n\t\t});\n\t\tconst newNonce = response.headers.get('replay-nonce');\n\t\tif (newNonce !== null) nonce = newNonce;\n\t\tconst text = await response.text();\n\t\tconst body =\n\t\t\ttext.length > 0 && response.headers.get('content-type')?.includes('json')\n\t\t\t\t? JSON.parse(text)\n\t\t\t\t: text;\n\t\tif (!response.ok) {\n\t\t\tthrow new AcmeError(\n\t\t\t\t`ACME ${url} → ${response.status}`,\n\t\t\t\tresponse.status,\n\t\t\t\tbody\n\t\t\t);\n\t\t}\n\t\treturn { body, headers: response.headers, status: response.status };\n\t};\n\n\t// 3. Register the account (or reuse if kid is set).\n\tconst publicJwk = await exportPublicJwk(account.key.publicKey);\n\tif (account.kid === undefined) {\n\t\tlog(`[tls] registering ACME account for ${options.email}`);\n\t\tconst result = await post(\n\t\t\tdirectory.newAccount,\n\t\t\t{\n\t\t\t\tcontact: [`mailto:${options.email}`],\n\t\t\t\ttermsOfServiceAgreed: true\n\t\t\t},\n\t\t\t{ jwk: publicJwk }\n\t\t);\n\t\taccount.kid = result.headers.get('location') ?? undefined;\n\t\tif (account.kid === undefined) {\n\t\t\tthrow new Error(\n\t\t\t\t'[deploy/tls] ACME newAccount response missing Location header'\n\t\t\t);\n\t\t}\n\t\tlog(`[tls] account registered: ${account.kid}`);\n\t} else {\n\t\tlog(`[tls] reusing account ${account.kid}`);\n\t}\n\n\tconst accountKid = account.kid;\n\n\t// 4. Submit the order.\n\tlog(`[tls] submitting order for ${options.domains.join(', ')}`);\n\tconst orderResult = await post(\n\t\tdirectory.newOrder,\n\t\t{\n\t\t\tidentifiers: options.domains.map((domain) => ({\n\t\t\t\ttype: 'dns',\n\t\t\t\tvalue: domain\n\t\t\t}))\n\t\t},\n\t\t{ kid: accountKid }\n\t);\n\tlet order = orderResult.body as AcmeOrder;\n\tconst orderUrl = orderResult.headers.get('location');\n\tif (orderUrl === null) {\n\t\tthrow new Error(\n\t\t\t'[deploy/tls] ACME newOrder response missing Location header'\n\t\t);\n\t}\n\n\t// 5. For each authorization, complete the DNS-01 challenge.\n\tconst cleanups: Array<() => Promise<void>> = [];\n\tconst dnsCreated: Array<{\n\t\trecordId: string;\n\t\trecordName: string;\n\t\trecordValue: string;\n\t}> = [];\n\n\ttry {\n\t\tconst thumbprint = await jwkThumbprint(publicJwk);\n\n\t\tfor (const authUrl of order.authorizations) {\n\t\t\tconst authResult = await post(authUrl, '', { kid: accountKid });\n\t\t\tconst auth = authResult.body as AcmeAuthorization;\n\t\t\tconst challenge = auth.challenges.find((c) => c.type === 'dns-01');\n\t\t\tif (challenge === undefined) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`[deploy/tls] no dns-01 challenge for ${auth.identifier.value}`\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst keyAuthorization = `${challenge.token}.${thumbprint}`;\n\t\t\tconst txtBytes = new Uint8Array(\n\t\t\t\tawait crypto.subtle.digest(\n\t\t\t\t\t'SHA-256',\n\t\t\t\t\tnew TextEncoder().encode(keyAuthorization)\n\t\t\t\t)\n\t\t\t);\n\t\t\tconst txtValue = base64UrlEncode(txtBytes);\n\t\t\tconst recordName = `_acme-challenge.${auth.identifier.value}`;\n\t\t\tlog(`[tls] DNS-01: setting ${recordName} = \"${txtValue}\"`);\n\t\t\tconst record = await options.dnsProvider.upsert({\n\t\t\t\tcontent: txtValue,\n\t\t\t\tname: recordName,\n\t\t\t\tttl: 60,\n\t\t\t\ttype: 'TXT'\n\t\t\t});\n\t\t\tdnsCreated.push({\n\t\t\t\trecordId: record.id,\n\t\t\t\trecordName,\n\t\t\t\trecordValue: txtValue\n\t\t\t});\n\t\t\tcleanups.push(() => options.dnsProvider.delete(record.id));\n\n\t\t\tif (options.checkDnsPropagated !== undefined) {\n\t\t\t\tlog('[tls] waiting for DNS propagation (custom checker)…');\n\t\t\t\tconst deadline = Date.now() + dnsDelay;\n\t\t\t\twhile (Date.now() < deadline) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tawait options.checkDnsPropagated(recordName, txtValue)\n\t\t\t\t\t) break;\n\t\t\t\t\tawait sleep(pollMs);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlog(`[tls] sleeping ${dnsDelay}ms for DNS propagation`);\n\t\t\t\tawait sleep(dnsDelay);\n\t\t\t}\n\n\t\t\tlog(`[tls] notifying ACME of dns-01 readiness for ${auth.identifier.value}`);\n\t\t\tawait post(challenge.url, {}, { kid: accountKid });\n\n\t\t\t// Poll the authorization until valid/invalid.\n\t\t\tconst authDeadline = Date.now() + orderTimeout;\n\t\t\twhile (Date.now() < authDeadline) {\n\t\t\t\tconst polledResult = await post(authUrl, '', { kid: accountKid });\n\t\t\t\tconst polled = polledResult.body as AcmeAuthorization;\n\t\t\t\tif (polled.status === 'valid') break;\n\t\t\t\tif (polled.status === 'invalid') {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`[deploy/tls] authorization failed for ${auth.identifier.value}: ${JSON.stringify(polled.challenges)}`\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tawait sleep(pollMs);\n\t\t\t}\n\t\t}\n\n\t\t// 6. Finalize — generate cert keypair + CSR.\n\t\tlog('[tls] generating cert keypair + CSR');\n\t\tconst certKey = await generateCertKeyPair();\n\t\tconst csrDer = await buildCsr(options.domains, certKey);\n\t\tconst csr = base64UrlEncode(csrDer);\n\n\t\tawait post(order.finalize, { csr }, { kid: accountKid });\n\n\t\t// 7. Poll the order until valid + certificate URL appears.\n\t\tconst orderDeadline = Date.now() + orderTimeout;\n\t\twhile (Date.now() < orderDeadline) {\n\t\t\tconst refreshed = await post(orderUrl, '', { kid: accountKid });\n\t\t\torder = refreshed.body as AcmeOrder;\n\t\t\tif (order.status === 'valid' && order.certificate !== undefined) break;\n\t\t\tif (order.status === 'invalid') {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`[deploy/tls] order failed: ${JSON.stringify(order)}`\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait sleep(pollMs);\n\t\t}\n\n\t\tif (order.certificate === undefined) {\n\t\t\tthrow new Error('[deploy/tls] order timed out before issuing certificate');\n\t\t}\n\n\t\t// 8. Download the cert (PEM bundle).\n\t\tlog(`[tls] downloading certificate from ${order.certificate}`);\n\t\tconst certResult = await post(order.certificate, '', { kid: accountKid });\n\t\tconst certificatePem =\n\t\t\ttypeof certResult.body === 'string'\n\t\t\t\t? certResult.body\n\t\t\t\t: String(certResult.body);\n\n\t\tconst privateKeyPem = await exportEcPrivateKeyPem(certKey);\n\n\t\treturn {\n\t\t\taccount,\n\t\t\tcertificatePem,\n\t\t\tdomains: options.domains,\n\t\t\tprivateKeyPem\n\t\t};\n\t} finally {\n\t\tfor (const cleanup of cleanups) {\n\t\t\ttry {\n\t\t\t\tawait cleanup();\n\t\t\t} catch (error) {\n\t\t\t\tlog(`[tls] cleanup failed (continuing): ${String(error)}`);\n\t\t\t}\n\t\t}\n\t\t// Suppress unused-variable lint without changing behavior.\n\t\tvoid dnsCreated;\n\t}\n};\n\nconst fetchNonce = async (\n\tfetcher: typeof fetch,\n\turl: string\n): Promise<string> => {\n\tconst response = await fetcher(url, { method: 'HEAD' });\n\tconst nonce = response.headers.get('replay-nonce');\n\tif (nonce === null) {\n\t\tthrow new Error('[deploy/tls] newNonce response missing Replay-Nonce header');\n\t}\n\treturn nonce;\n};\n\n// =============================================================================\n// installCertificateOnTarget\n// =============================================================================\n\nexport type InstallCertificateOptions = {\n\t/** Remote path for the cert chain. Default `/etc/ssl/<firstDomain>/fullchain.pem`. */\n\tcertPath?: string;\n\t/** Remote path for the private key. Default `/etc/ssl/<firstDomain>/privkey.pem`. */\n\tkeyPath?: string;\n\t/** Mode (chmod) for the cert + key. Default `600`. */\n\tmode?: string;\n\t/** Owner (chown) for the cert + key. Default unchanged. */\n\towner?: string;\n\t/** Optional reload command run after install (e.g. `'systemctl reload nginx'`). */\n\treload?: string;\n\t/** Override the writeTo helper (tests). */\n\twriteFile?: (target: Target, path: string, contents: string) => Promise<void>;\n};\n\nconst defaultWriteFile = async (\n\ttarget: Target,\n\tremotePath: string,\n\tcontents: string\n): Promise<void> => {\n\tconst escaped = contents.replaceAll(\"'\", \"'\\\\''\");\n\tawait target.exec(`mkdir -p \"$(dirname '${remotePath}')\"`);\n\tawait target.exec(`cat > '${remotePath}' <<'__ABS_TLS_EOF__'\\n${contents}__ABS_TLS_EOF__\\n`);\n\tvoid escaped;\n};\n\n/**\n * @internal — exposed for unit testing of cryptographic primitives.\n * Not part of the public API; consumers should NOT depend on this.\n */\nexport const __testing = {\n\tbase64UrlDecode,\n\tbase64UrlEncode,\n\tbase64UrlEncodeString,\n\tbuildCsr,\n\tderBitString,\n\tderInt,\n\tderOid,\n\tderSeq,\n\tderSet,\n\tecdsaRawToDer,\n\texportEcPrivateKeyPem,\n\texportPublicJwk,\n\tgenerateCertKeyPair,\n\tjwkThumbprint,\n\tsignJws\n};\n\n/**\n * Upload cert + private key to the target. Composable with the deploy\n * pipeline as a verify-step or post-deploy hook.\n */\nexport const installCertificateOnTarget = async (\n\ttarget: Target,\n\tcert: IssuedCertificate,\n\toptions: InstallCertificateOptions = {}\n): Promise<{ certPath: string; keyPath: string }> => {\n\tconst domain = cert.domains[0];\n\tif (domain === undefined) {\n\t\tthrow new Error('[deploy/tls] certificate has no domains');\n\t}\n\tconst certPath = options.certPath ?? `/etc/ssl/${domain}/fullchain.pem`;\n\tconst keyPath = options.keyPath ?? `/etc/ssl/${domain}/privkey.pem`;\n\tconst mode = options.mode ?? '600';\n\tconst writer = options.writeFile ?? defaultWriteFile;\n\n\tawait writer(target, certPath, cert.certificatePem);\n\tawait writer(target, keyPath, cert.privateKeyPem);\n\tawait target.exec(`chmod ${mode} '${certPath}' '${keyPath}'`);\n\tif (options.owner !== undefined) {\n\t\tawait target.exec(`chown ${options.owner} '${certPath}' '${keyPath}'`);\n\t}\n\tif (options.reload !== undefined) {\n\t\tawait target.exec(options.reload);\n\t}\n\n\treturn { certPath, keyPath };\n};\n\n// =============================================================================\n// Certificate inspection + renewal scheduling\n// =============================================================================\n\nexport type CertificateInspection = {\n\t/** CN + every SAN, deduplicated. */\n\tsubjects: string[];\n\t/** Issuance time, ms since epoch. */\n\tvalidFrom: number;\n\t/** Expiration time, ms since epoch. */\n\tvalidTo: number;\n\t/** Whole days remaining before `validTo`. Negative if expired. */\n\tdaysRemaining: number;\n\t/** True when the cert is past `validTo`. */\n\texpired: boolean;\n\t/** Issuer DN string (for \"Let's Encrypt vs staging vs other CA\" checks). */\n\tissuer: string;\n};\n\nconst parseSubjects = (x509: X509Certificate): string[] => {\n\tconst subjects = new Set<string>();\n\tconst cnMatch = x509.subject.match(/CN=([^,\\n]+)/);\n\tif (cnMatch !== null && cnMatch[1] !== undefined) {\n\t\tsubjects.add(cnMatch[1].trim());\n\t}\n\tconst san = x509.subjectAltName;\n\tif (san !== undefined && san !== null) {\n\t\tfor (const part of san.split(',')) {\n\t\t\tconst dnsMatch = part.trim().match(/^DNS:(.+)$/);\n\t\t\tif (dnsMatch !== null && dnsMatch[1] !== undefined) {\n\t\t\t\tsubjects.add(dnsMatch[1].trim());\n\t\t\t}\n\t\t}\n\t}\n\treturn [...subjects];\n};\n\n/**\n * Parse a PEM certificate into operator-shaped metadata. Used by\n * {@link renewCertificate} to decide whether to re-issue; also fine\n * for status pages and observability.\n */\nexport const inspectCertificate = (\n\tpem: string,\n\toptions: { now?: () => number } = {}\n): CertificateInspection => {\n\tconst x509 = new X509Certificate(pem);\n\tconst validFrom = new Date(x509.validFrom).getTime();\n\tconst validTo = new Date(x509.validTo).getTime();\n\tconst now = (options.now ?? Date.now)();\n\tconst daysRemaining = Math.floor(\n\t\t(validTo - now) / (24 * 60 * 60 * 1000)\n\t);\n\treturn {\n\t\tdaysRemaining,\n\t\texpired: validTo < now,\n\t\tissuer: x509.issuer,\n\t\tsubjects: parseSubjects(x509),\n\t\tvalidFrom,\n\t\tvalidTo\n\t};\n};\n\nexport type RenewCertificateOptions = IssueCertificateOptions & {\n\t/**\n\t * Current cert PEM. When provided, the cert's `validTo` decides\n\t * whether to re-issue; when absent, renewal always issues.\n\t */\n\tcurrentCertificatePem?: string;\n\t/**\n\t * Re-issue when fewer than this many days remain. Default 30,\n\t * matching certbot's standard schedule (Let's Encrypt issues 90-day\n\t * certs; renewing at 30 days leaves a 60-day error budget).\n\t */\n\trenewWhenDaysRemaining?: number;\n\t/** Force re-issue regardless of remaining days. Default false. */\n\tforce?: boolean;\n\t/** Override `Date.now()` for testing. */\n\tnow?: () => number;\n};\n\nexport type RenewalResult =\n\t| {\n\t\t\trenewed: true;\n\t\t\tcertificate: IssuedCertificate;\n\t\t\treason: 'forced' | 'no-current-cert' | 'expiring-soon';\n\t }\n\t| {\n\t\t\trenewed: false;\n\t\t\treason: 'still-fresh';\n\t\t\tinspection: CertificateInspection;\n\t };\n\n/**\n * Conditional cert renewal. Parses the supplied cert (if any),\n * decides whether to re-issue based on remaining days, and either\n * returns the existing cert info (\"still fresh\") or issues a new\n * one via {@link issueCertificate}.\n *\n * Wire to a scheduled function (cron / @absolutejs/sync schedule /\n * GitHub Action) running at least once a week. Idempotent: a fresh\n * cert returns `renewed: false` cheaply (one PEM parse, zero\n * network IO).\n *\n * @example\n * const result = await renewCertificate({\n * currentCertificatePem: existingPem, // omit to force first-issue\n * domains: ['api.example.com'],\n * dnsProvider: dns,\n * email: 'ops@example.com',\n * account: persistedAccount, // reuse to avoid CA rate limit\n * });\n * if (result.renewed) {\n * await installCertificateOnTarget(target, result.certificate, { reload });\n * }\n */\nexport const renewCertificate = async (\n\toptions: RenewCertificateOptions\n): Promise<RenewalResult> => {\n\tconst threshold = options.renewWhenDaysRemaining ?? 30;\n\tif (threshold < 0) {\n\t\tthrow new Error(\n\t\t\t'[deploy/tls] renewWhenDaysRemaining must be non-negative'\n\t\t);\n\t}\n\n\tif (options.force === true) {\n\t\tconst certificate = await issueCertificate(options);\n\t\treturn { certificate, reason: 'forced', renewed: true };\n\t}\n\n\tif (options.currentCertificatePem === undefined) {\n\t\tconst certificate = await issueCertificate(options);\n\t\treturn { certificate, reason: 'no-current-cert', renewed: true };\n\t}\n\n\tconst nowFn = options.now ?? Date.now;\n\tconst inspection = inspectCertificate(options.currentCertificatePem, {\n\t\tnow: nowFn\n\t});\n\n\tif (!inspection.expired && inspection.daysRemaining >= threshold) {\n\t\treturn { inspection, reason: 'still-fresh', renewed: false };\n\t}\n\n\tconst certificate = await issueCertificate(options);\n\treturn { certificate, reason: 'expiring-soon', renewed: true };\n};\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "54D17E1F39C4D88964756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "F710513ADD2FEFC064756E2164756E21",
9
9
  "names": []
10
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/deploy",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Generic Bun-project deploy pipeline. A Target (localTarget / sshTarget) is anywhere you can exec + upload — DigitalOcean droplets, Linode, Hetzner, Vultr, your own boxes. Bundled pipeline: prepare → upload → install → build → link → restart → verify. Atomic symlink swap, release history, prune, hooks. SSH shells out to system ssh/rsync — zero ssh2 deps.",
5
5
  "repository": {
6
6
  "type": "git",