1import * as ACME from "@/ACME";
2import * as AdoptPolicy from "@/AdoptPolicy";
3import { retain } from "@/RemovalPolicy";
4import * as Cloudflare from "@/Cloudflare";
5import * as Output from "@/Output";
6import * as ZeroSsl from "@distilled.cloud/zerossl";
7import * as Effect from "effect/Effect";
8import * as Layer from "effect/Layer";
9import * as FetchHttpClient from "effect/http/FetchHttpClient";
10
11export const ZONE_NAME = "alchemy-test-2.us";
12
13/** The standing test zone, adopted (never deleted on destroy). */
14export const Zone = Cloudflare.Zone.Zone("AcmeZone", {
15 name: ZONE_NAME,
16}).pipe(AdoptPolicy.adopt(), retain());
17
18export const Staging = ACME.Account("Staging", {
19 ca: ACME.LetsEncryptStaging,
20 contact: ["mailto:ops@alchemy.run"],
21 termsOfServiceAgreed: true,
22});
23
24/**
25 * External Account Binding minted from `ZERO_SSL_KEY` when the stack is
26 * evaluated. Only `newAccount` consumes it; later deploys reuse the account.
27 *
28 * Minted once and cached: each prop below evaluates the effect, and the
29 * pair must match. Props resolve `Output`s and `Config`s, not bare Effects,
30 * so each half is lifted with `Output.fromEffect`.
31 */
32const zeroSslEab = Effect.runSync(
33 Effect.cached(
34 ZeroSsl.zerossl
35 .generateEabCredentials({})
36 .pipe(
37 Effect.provide(
38 Layer.mergeAll(ZeroSsl.CredentialsFromEnv, FetchHttpClient.layer),
39 ),
40 Effect.orDie,
41 ),
42 ),
43);
44
45/**
46 * Let's Encrypt staging returned TLS errors from Workers during bring-up;
47 * the deployed-Worker test uses ZeroSSL.
48 */
49export const ZeroSSLAccount = ACME.Account("ZeroSSL", {
50 ca: ACME.ZeroSSL,
51 eab: {
52 keyId: Output.fromEffect(
53 zeroSslEab.pipe(Effect.map((eab) => eab.eab_kid!)),
54 ),
55 hmacKey: Output.fromEffect(
56 zeroSslEab.pipe(Effect.map((eab) => eab.eab_hmac_key!)),
57 ),
58 },
59 termsOfServiceAgreed: true,
60});