dkimkit 0.0.0-stage → 0.7.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.
Files changed (132) hide show
  1. package/README.md +161 -2
  2. package/dist/src/cli.d.ts +2 -0
  3. package/dist/src/cli.js +398 -0
  4. package/dist/src/cli.js.map +1 -0
  5. package/dist/src/dns-host.d.ts +10 -0
  6. package/dist/src/dns-host.js +31 -0
  7. package/dist/src/dns-host.js.map +1 -0
  8. package/dist/src/errors.d.ts +17 -0
  9. package/dist/src/errors.js +51 -0
  10. package/dist/src/errors.js.map +1 -0
  11. package/dist/src/hosted/api.d.ts +107 -0
  12. package/dist/src/hosted/api.js +919 -0
  13. package/dist/src/hosted/api.js.map +1 -0
  14. package/dist/src/hosted/auth.d.ts +80 -0
  15. package/dist/src/hosted/auth.js +147 -0
  16. package/dist/src/hosted/auth.js.map +1 -0
  17. package/dist/src/hosted/billing-dodo.d.ts +58 -0
  18. package/dist/src/hosted/billing-dodo.js +139 -0
  19. package/dist/src/hosted/billing-dodo.js.map +1 -0
  20. package/dist/src/hosted/billing-stripe.d.ts +82 -0
  21. package/dist/src/hosted/billing-stripe.js +205 -0
  22. package/dist/src/hosted/billing-stripe.js.map +1 -0
  23. package/dist/src/hosted/billing.d.ts +114 -0
  24. package/dist/src/hosted/billing.js +173 -0
  25. package/dist/src/hosted/billing.js.map +1 -0
  26. package/dist/src/hosted/bulk.d.ts +47 -0
  27. package/dist/src/hosted/bulk.js +66 -0
  28. package/dist/src/hosted/bulk.js.map +1 -0
  29. package/dist/src/hosted/cli.d.ts +2 -0
  30. package/dist/src/hosted/cli.js +318 -0
  31. package/dist/src/hosted/cli.js.map +1 -0
  32. package/dist/src/hosted/crypto.d.ts +22 -0
  33. package/dist/src/hosted/crypto.js +55 -0
  34. package/dist/src/hosted/crypto.js.map +1 -0
  35. package/dist/src/hosted/index.d.ts +7 -0
  36. package/dist/src/hosted/index.js +11 -0
  37. package/dist/src/hosted/index.js.map +1 -0
  38. package/dist/src/hosted/markdown.d.ts +16 -0
  39. package/dist/src/hosted/markdown.js +168 -0
  40. package/dist/src/hosted/markdown.js.map +1 -0
  41. package/dist/src/hosted/net.d.ts +9 -0
  42. package/dist/src/hosted/net.js +60 -0
  43. package/dist/src/hosted/net.js.map +1 -0
  44. package/dist/src/hosted/postgres-store.d.ts +62 -0
  45. package/dist/src/hosted/postgres-store.js +387 -0
  46. package/dist/src/hosted/postgres-store.js.map +1 -0
  47. package/dist/src/hosted/serialize.d.ts +144 -0
  48. package/dist/src/hosted/serialize.js +126 -0
  49. package/dist/src/hosted/serialize.js.map +1 -0
  50. package/dist/src/hosted/service.d.ts +170 -0
  51. package/dist/src/hosted/service.js +603 -0
  52. package/dist/src/hosted/service.js.map +1 -0
  53. package/dist/src/hosted/site.d.ts +84 -0
  54. package/dist/src/hosted/site.js +653 -0
  55. package/dist/src/hosted/site.js.map +1 -0
  56. package/dist/src/hosted/sqlite-store.d.ts +45 -0
  57. package/dist/src/hosted/sqlite-store.js +351 -0
  58. package/dist/src/hosted/sqlite-store.js.map +1 -0
  59. package/dist/src/hosted/store.d.ts +257 -0
  60. package/dist/src/hosted/store.js +181 -0
  61. package/dist/src/hosted/store.js.map +1 -0
  62. package/dist/src/hosted/widget.d.ts +46 -0
  63. package/dist/src/hosted/widget.js +130 -0
  64. package/dist/src/hosted/widget.js.map +1 -0
  65. package/dist/src/hosted/worker.d.ts +45 -0
  66. package/dist/src/hosted/worker.js +142 -0
  67. package/dist/src/hosted/worker.js.map +1 -0
  68. package/dist/src/http.d.ts +14 -0
  69. package/dist/src/http.js +54 -0
  70. package/dist/src/http.js.map +1 -0
  71. package/dist/src/index.d.ts +19 -0
  72. package/dist/src/index.js +19 -0
  73. package/dist/src/index.js.map +1 -0
  74. package/dist/src/orchestrator.d.ts +70 -0
  75. package/dist/src/orchestrator.js +69 -0
  76. package/dist/src/orchestrator.js.map +1 -0
  77. package/dist/src/propagation.d.ts +30 -0
  78. package/dist/src/propagation.js +76 -0
  79. package/dist/src/propagation.js.map +1 -0
  80. package/dist/src/providers/dns/aws.d.ts +28 -0
  81. package/dist/src/providers/dns/aws.js +103 -0
  82. package/dist/src/providers/dns/aws.js.map +1 -0
  83. package/dist/src/providers/dns/cloudflare.d.ts +26 -0
  84. package/dist/src/providers/dns/cloudflare.js +97 -0
  85. package/dist/src/providers/dns/cloudflare.js.map +1 -0
  86. package/dist/src/providers/dns/manual.d.ts +12 -0
  87. package/dist/src/providers/dns/manual.js +18 -0
  88. package/dist/src/providers/dns/manual.js.map +1 -0
  89. package/dist/src/providers/dns/route53.d.ts +34 -0
  90. package/dist/src/providers/dns/route53.js +176 -0
  91. package/dist/src/providers/dns/route53.js.map +1 -0
  92. package/dist/src/providers/email/factory.d.ts +29 -0
  93. package/dist/src/providers/email/factory.js +67 -0
  94. package/dist/src/providers/email/factory.js.map +1 -0
  95. package/dist/src/providers/email/mailgun.d.ts +36 -0
  96. package/dist/src/providers/email/mailgun.js +141 -0
  97. package/dist/src/providers/email/mailgun.js.map +1 -0
  98. package/dist/src/providers/email/postmark.d.ts +37 -0
  99. package/dist/src/providers/email/postmark.js +121 -0
  100. package/dist/src/providers/email/postmark.js.map +1 -0
  101. package/dist/src/providers/email/sendgrid.d.ts +31 -0
  102. package/dist/src/providers/email/sendgrid.js +109 -0
  103. package/dist/src/providers/email/sendgrid.js.map +1 -0
  104. package/dist/src/providers/email/ses.d.ts +39 -0
  105. package/dist/src/providers/email/ses.js +123 -0
  106. package/dist/src/providers/email/ses.js.map +1 -0
  107. package/dist/src/providers/factory.d.ts +14 -0
  108. package/dist/src/providers/factory.js +32 -0
  109. package/dist/src/providers/factory.js.map +1 -0
  110. package/dist/src/records.d.ts +35 -0
  111. package/dist/src/records.js +109 -0
  112. package/dist/src/records.js.map +1 -0
  113. package/dist/src/server.d.ts +67 -0
  114. package/dist/src/server.js +301 -0
  115. package/dist/src/server.js.map +1 -0
  116. package/dist/src/types.d.ts +75 -0
  117. package/dist/src/types.js +2 -0
  118. package/dist/src/types.js.map +1 -0
  119. package/dist/src/xml.d.ts +6 -0
  120. package/dist/src/xml.js +23 -0
  121. package/dist/src/xml.js.map +1 -0
  122. package/docs/billing.md +43 -0
  123. package/docs/getting-started.md +66 -0
  124. package/docs/hosted-api.md +192 -0
  125. package/docs/library.md +253 -0
  126. package/docs/self-hosting.md +153 -0
  127. package/docs/widget.md +121 -0
  128. package/package.json +63 -4
  129. package/web/dashboard.html +669 -0
  130. package/web/index.html +697 -0
  131. package/web/widget-frame.html +480 -0
  132. package/web/widget.js +91 -0
package/README.md CHANGED
@@ -1,3 +1,162 @@
1
- # Temporary Holding Version
1
+ # DKIMkit
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Authenticate a sending domain with **SendGrid, Postmark, Mailgun or Amazon SES**, and optionally set up inbound email, with one command or from a local web interface. The tool gets the records from your email provider, writes them to your DNS host, waits until they're live, has the provider verify them, and registers the inbound webhook.
4
+
5
+ | Provider | Records | Inbound email |
6
+ |---|---|---|
7
+ | SendGrid | 3 CNAMEs | Inbound Parse, at `parse.<domain>` |
8
+ | Postmark | DKIM TXT + Return-Path CNAME | One inbound domain per Postmark server |
9
+ | Mailgun | SPF + DKIM TXT, tracking CNAME | A route, on the Mailgun domain itself (send from a subdomain like `mg.example.com`) |
10
+ | Amazon SES | 3 DKIM CNAMEs + MAIL FROM MX and SPF | Not supported yet |
11
+
12
+ When a provider needs an SPF record and the name already has one, the provider is added to the existing record. Two SPF records on one name would make SPF fail.
13
+
14
+ ```bash
15
+ export SENDGRID_API_KEY=SG.xxxx
16
+ export CLOUDFLARE_API_TOKEN=xxxx # or use --dns route53, or --dns manual for copy-paste records
17
+
18
+ npx dkimkit --domain example.com \
19
+ --inbound-url https://api.example.com/webhooks/inbound-email
20
+ ```
21
+
22
+ ```
23
+ ✔ SendGrid domain created: example.com (id 12345678)
24
+
25
+ DNS plan (cloudflare):
26
+ + create CNAME em1234.example.com → u1234.wl.sendgrid.net
27
+ + create CNAME s1._domainkey.example.com → s1.domainkey.u1234.wl.sendgrid.net
28
+ + create CNAME s2._domainkey.example.com → s2.domainkey.u1234.wl.sendgrid.net
29
+ + create MX parse.example.com → 10 mx.sendgrid.net
30
+
31
+ Apply these changes to cloudflare? [y/N] y
32
+ ✔ Wrote 4 DNS record(s)
33
+ ✔ All 4 DNS records are live
34
+ ✔ SendGrid verified the domain
35
+ ✔ Inbound parse created: parse.example.com → https://api.example.com/webhooks/inbound-email
36
+
37
+ example.com is authenticated with SendGrid.
38
+ ```
39
+
40
+ ## Web interface
41
+
42
+ ```bash
43
+ npx dkimkit ui # opens http://127.0.0.1:4545/?token=…
44
+ ```
45
+
46
+ The web interface walks through the same flow: enter the domain and credentials, preview the exact DNS changes (with conflicts highlighted), then apply them while each step updates live. Credentials typed into the page are used for that request only and never stored. Leave a field blank to use the environment variable.
47
+
48
+ The server listens on 127.0.0.1 only. The one-time link swaps its token for an `HttpOnly; SameSite=Strict` cookie. Requests whose `Host` header isn't localhost are refused, which blocks DNS rebinding. API calls must be JSON POSTs, which other sites can't send without a CORS preflight that this server never approves.
49
+
50
+ ## Hosted multi-tenant API
51
+
52
+ For SaaS products whose customers send from their own domains. Register a customer's domain with one call and get the records back to show them, or have the API write them through the customer's Cloudflare or Route 53 account. A background worker drives each domain to verified and calls your signed webhook along the way.
53
+
54
+ ```bash
55
+ export ENCRYPTION_KEY=$(npx dkimkit-api generate-encryption-key)
56
+ SENDGRID_API_KEY=SG.xxx npx dkimkit-api create-account --name "Acme" --with-sendgrid-key
57
+ npx dkimkit-api serve
58
+
59
+ curl -X POST localhost:8080/v1/domains -H "Authorization: Bearer dk_live_…" -H "Content-Type: application/json" \
60
+ -d '{"domain": "customer.com", "tenant_id": "cus_42", "dns": {"provider": "manual"}}'
61
+ ```
62
+
63
+ See **[docs/hosted-api.md](docs/hosted-api.md)** for endpoints, lifecycle, webhooks and deployment (Dockerfile included).
64
+
65
+ ### Bulk setup in the dashboard
66
+
67
+ Signed-in users can paste up to 100 domains from any registrar into **Domains** at `/dashboard`. Each gets sender authentication at the account's email provider and, optionally, inbound email to a shared webhook URL. The DNS host of each domain is detected from its nameservers: domains on Cloudflare or Route 53 have their records written with a token or keys entered in the form, and the rest get their records grouped by DNS host, with CSV and zone file downloads. Statuses update live until each domain is verified. See **[docs/getting-started.md](docs/getting-started.md)**.
68
+
69
+ ### Plans and billing
70
+
71
+ Self-serve and pay-as-you-go: anyone signs up free at `/dashboard` with just an email and gets 3 domains. Adding a payment method unlocks unlimited domains at $2 per active domain per month after the first 3. Payments run through [Dodo Payments](https://dodopayments.com), a merchant of record that handles global tax and works for individual developers (Stripe is supported too). Usage is reported to the provider's meter automatically, and customers manage payment and cancellation in its hosted portal. See **[docs/billing.md](docs/billing.md)**.
72
+
73
+ ## What it does
74
+
75
+ Steps below use SendGrid; the other providers follow the same flow through their own APIs.
76
+
77
+ 1. **Domain authentication.** Reuses an existing SendGrid authenticated domain, or creates one with `POST /v3/whitelabel/domains` (`automatic_security: true`, which gives 3 CNAMEs).
78
+ 2. **DNS plan.** Diffs the required records against what is already in the zone. A record that already matches is left alone. A proxied Cloudflare CNAME has proxying turned off. A record pointing somewhere else is reported as a **conflict** and nothing is written, unless you pass `--overwrite`. Records of other types are never deleted.
79
+ 3. **Apply.** Writes the records after you confirm, or straight away with `--yes`.
80
+ 4. **Propagation.** Polls the zone's **authoritative nameservers** directly, not a cached resolver, until every record is visible (`--wait`, default 300s).
81
+ 5. **Verify.** Calls `POST /v3/whitelabel/domains/{id}/validate`. It retries, because SendGrid's resolvers can lag behind the authoritative servers.
82
+ 6. **Inbound parse.** Creates or updates `/v3/user/parse/settings` and adds the `MX → mx.sendgrid.net` record. This step runs only after the domain is verified, because SendGrid requires that.
83
+
84
+ Each step is idempotent. If a run ends with "DNS not visible yet" or "not validated yet", run the same command again and it picks up where it stopped.
85
+
86
+ ## CLI
87
+
88
+ | Flag | |
89
+ |---|---|
90
+ | `-d, --domain` | Domain to authenticate |
91
+ | `--provider sendgrid\|postmark\|mailgun\|ses` | Email provider (default `sendgrid`) |
92
+ | `--region` | Mailgun `us`/`eu`, or the Amazon SES region (or `AWS_REGION`) |
93
+ | `--subdomain` | Return-path / MAIL FROM label (SendGrid picks one by default; Postmark `pm-bounces`; SES `mail`) |
94
+ | `--dns cloudflare\|route53\|manual` | DNS host. `manual` prints records for any registrar and then polls DNS until they appear |
95
+ | `--aws-profile`, `--zone-id` | Route53: credentials profile, and a hosted zone id if you don't want it looked up by name |
96
+ | `--inbound-url`, `--inbound-host` | Receive email (host defaults to `parse.<domain>`; Mailgun: the domain itself) |
97
+ | `--spam-check`, `--send-raw` | Inbound parse options |
98
+ | `--overwrite` | Replace CNAME/MX records that point elsewhere |
99
+ | `--dry-run` | Show the plan only. It may still create the SendGrid domain entry, because that is the only way to get the records |
100
+ | `--on-behalf-of` | Run as a SendGrid subuser |
101
+ | `-y, --yes`, `--json` | For CI and scripts |
102
+
103
+ `dkimkit status -d example.com` shows SendGrid's per-record validation without changing anything.
104
+
105
+ Exit codes: `0` done, `1` error, `2` incomplete (DNS pending, validation failed, or aborted).
106
+
107
+ **Credentials.**
108
+ - SendGrid (`SENDGRID_API_KEY`): needs *Sender Authentication* and *Inbound Parse* access.
109
+ - Postmark (`POSTMARK_ACCOUNT_TOKEN`, plus `POSTMARK_INBOUND_SERVER_ID` to receive): an account API token.
110
+ - Mailgun (`MAILGUN_API_KEY`, `MAILGUN_REGION`): a private API key.
111
+ - Amazon SES: the usual AWS credentials plus `AWS_REGION`; needs `ses:CreateEmailIdentity`, `GetEmailIdentity`, `PutEmailIdentityMailFromAttributes` and `DeleteEmailIdentity`.
112
+ - Cloudflare token: needs *Zone:Read* and *DNS:Edit* on the zone.
113
+ - Route53: `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY`/`AWS_SESSION_TOKEN`, or a profile in `~/.aws/credentials` (`AWS_PROFILE` or `--aws-profile`). The credentials need `route53:ListHostedZonesByName`, `route53:ListResourceRecordSets` and `route53:ChangeResourceRecordSets`. SSO and instance roles aren't read directly; export them with `aws configure export-credentials --format env`.
114
+
115
+ Route53 keeps one record set per name and type, so the tool merges into existing sets. For example, the SPF TXT is added next to your existing TXT values rather than replacing them. All changes for a zone go in one atomic `UPSERT` batch. Requests are signed with a built-in SigV4 implementation, whose test fixtures were generated with botocore.
116
+
117
+ ## Library
118
+
119
+ Full reference: **[docs/library.md](docs/library.md)** (also at [dkimkit.com/docs/library](https://dkimkit.com/docs/library)).
120
+
121
+ The CLI is a thin layer over the library. Use the library to embed the same flow in your app's onboarding, for example per tenant with subusers:
122
+
123
+ ```ts
124
+ import { setupDomain, SendGridProvider, CloudflareDns } from 'dkimkit';
125
+
126
+ const result = await setupDomain(
127
+ new SendGridProvider({ apiKey, onBehalfOf: tenant.sendgridSubuser }),
128
+ new CloudflareDns({ apiToken: tenant.cloudflareToken }),
129
+ {
130
+ domain: tenant.domain,
131
+ inbound: { hostname: `parse.${tenant.domain}`, url: `https://app.example.com/inbound/${tenant.id}` },
132
+ onEvent: (e) => progress.push(tenant.id, e), // stream steps to your UI
133
+ },
134
+ );
135
+ // result.status: 'complete' | 'dns-pending' | 'validation-failed' | 'aborted' | 'dry-run'
136
+ ```
137
+
138
+ The flow depends on two interfaces in `src/types.ts`:
139
+
140
+ - **`EmailProvider`** (`ensureDomain`, `validateDomain`, `inboundRecords`, `ensureInboundParse`). SendGrid, Postmark, Mailgun and Amazon SES (`createEmailProvider(name, credentials)`).
141
+ - **`DnsProvider`** (`plan`, `apply`). Cloudflare, Route53 and manual today. Route53, DigitalOcean, GoDaddy and others fit this interface. `decideChange()` in `src/records.ts` handles the generic diff and conflict rules, so a new DNS provider only maps its API to `ExistingRecord`.
142
+
143
+ ## Development
144
+
145
+ ```bash
146
+ npm install
147
+ npm test # builds, then runs node:test suites with mocked APIs and DNS
148
+
149
+ # Also run the hosted API, widget and store suites against Postgres
150
+ # (each test uses its own throwaway schema):
151
+ TEST_DATABASE_URL=postgres://user@localhost/dkimkit_test npm test
152
+ ```
153
+
154
+ No runtime dependencies. Requires Node 20+.
155
+
156
+ ## Roadmap
157
+
158
+ - Managed DNS: point a domain's nameservers to the service once, and every record is handled from then on
159
+ - Bulk domain buying: register many sending domains at once from the dashboard, authenticated automatically
160
+ - DNS providers: DigitalOcean, GoDaddy, Namecheap, plus Domain Connect where the registrar supports it
161
+ - Inbound email for Amazon SES (receipt rules → SNS → webhook)
162
+ - OAuth connect for DNS hosts that support it, instead of pasted API tokens
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,398 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { createInterface } from 'node:readline/promises';
4
+ import { parseArgs } from 'node:util';
5
+ import { spawn } from 'node:child_process';
6
+ import { ApiError, apiErrorHint, ConfigError, DnsConflictError } from './errors.js';
7
+ import { checkStatus, setupDomain } from './orchestrator.js';
8
+ import { loadAwsCredentials } from './providers/dns/aws.js';
9
+ import { createEmailProvider, EMAIL_PROVIDERS, isEmailProvider } from './providers/email/factory.js';
10
+ import { createDnsProvider } from './providers/factory.js';
11
+ import { describeRecord, normalizeName } from './records.js';
12
+ import { startUiServer } from './server.js';
13
+ const { version } = createRequire(import.meta.url)('../../package.json');
14
+ const HELP = `dkimkit ${version}
15
+
16
+ Authenticate a sending domain with SendGrid, Postmark, Mailgun or Amazon SES
17
+ and (optionally) set up inbound email, writing the DNS records for you.
18
+
19
+ Usage
20
+ dkimkit [setup] --domain example.com [options]
21
+ dkimkit status --domain example.com
22
+ dkimkit ui [--port 4545] [--no-open] Open the web interface
23
+
24
+ Options
25
+ -d, --domain <domain> Domain to authenticate
26
+ --provider <name> sendgrid | postmark | mailgun | ses (default: sendgrid)
27
+ --subdomain <name> Return-path / MAIL FROM label (SendGrid picks one by default,
28
+ Postmark pm-bounces, SES mail; not used by Mailgun)
29
+ --region <region> Mailgun: us | eu. SES: AWS region (or AWS_REGION)
30
+ --dns <provider> cloudflare | route53 | manual (default: cloudflare if CLOUDFLARE_API_TOKEN is set)
31
+ --inbound-url <url> Receive email, POSTing it to this webhook (not with SES)
32
+ --inbound-host <host> Hostname receiving mail (default: parse.<domain>;
33
+ Mailgun: the domain itself)
34
+ --spam-check Inbound: run spam check
35
+ --send-raw Inbound: post the raw MIME message
36
+ --overwrite Replace existing CNAME/MX records that point elsewhere
37
+ --dry-run Show the DNS plan without writing anything
38
+ --wait <seconds> Max time to wait for DNS propagation (default: 300)
39
+ --on-behalf-of <user> Act as a SendGrid subuser
40
+ --aws-profile <name> Route53: profile from ~/.aws/credentials
41
+ --zone-id <id> Route53: hosted zone id (default: look up by domain)
42
+ -y, --yes Apply DNS changes without asking
43
+ --json Print the result as JSON
44
+ -h, --help Show this help
45
+ -v, --version Show the version
46
+
47
+ Environment
48
+ SENDGRID_API_KEY SendGrid: Sender Authentication and Inbound Parse access
49
+ POSTMARK_ACCOUNT_TOKEN Postmark account API token
50
+ POSTMARK_INBOUND_SERVER_ID Postmark server that receives inbound mail
51
+ MAILGUN_API_KEY, MAILGUN_REGION
52
+ AWS_REGION Amazon SES region (SES uses the AWS credentials below)
53
+ CLOUDFLARE_API_TOKEN Needs Zone:Read and DNS:Edit for the domain's zone
54
+ AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, AWS_PROFILE
55
+ Route53 credentials (or ~/.aws/credentials)
56
+
57
+ Re-running is safe: existing domains and records are reused.`;
58
+ const useColor = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
59
+ const paint = (code) => (s) => (useColor ? `\x1b[${code}m${s}\x1b[0m` : s);
60
+ const green = paint(32);
61
+ const red = paint(31);
62
+ const yellow = paint(33);
63
+ const dim = paint(2);
64
+ const bold = paint(1);
65
+ class UsageError extends Error {
66
+ }
67
+ async function main() {
68
+ const { values, positionals } = parseArgs({
69
+ allowPositionals: true,
70
+ options: {
71
+ domain: { type: 'string', short: 'd' },
72
+ subdomain: { type: 'string' },
73
+ dns: { type: 'string' },
74
+ 'inbound-url': { type: 'string' },
75
+ 'inbound-host': { type: 'string' },
76
+ 'spam-check': { type: 'boolean', default: false },
77
+ 'send-raw': { type: 'boolean', default: false },
78
+ overwrite: { type: 'boolean', default: false },
79
+ 'dry-run': { type: 'boolean', default: false },
80
+ wait: { type: 'string', default: '300' },
81
+ 'on-behalf-of': { type: 'string' },
82
+ provider: { type: 'string', default: 'sendgrid' },
83
+ region: { type: 'string' },
84
+ 'aws-profile': { type: 'string' },
85
+ 'zone-id': { type: 'string' },
86
+ port: { type: 'string', default: '4545' },
87
+ 'no-open': { type: 'boolean', default: false },
88
+ yes: { type: 'boolean', short: 'y', default: false },
89
+ json: { type: 'boolean', default: false },
90
+ help: { type: 'boolean', short: 'h', default: false },
91
+ version: { type: 'boolean', short: 'v', default: false },
92
+ },
93
+ });
94
+ if (values.help)
95
+ return print(HELP), 0;
96
+ if (values.version)
97
+ return print(version), 0;
98
+ const command = positionals[0] ?? 'setup';
99
+ if (!['setup', 'status', 'ui'].includes(command))
100
+ throw new UsageError(`Unknown command "${command}". See --help.`);
101
+ if (command === 'ui') {
102
+ const port = Number(values.port);
103
+ if (!Number.isInteger(port) || port < 0 || port > 65535)
104
+ throw new UsageError('--port must be a port number.');
105
+ const ui = await startUiServer({ port, version }).catch((err) => {
106
+ if (err.code === 'EADDRINUSE')
107
+ throw new UsageError(`Port ${port} is in use. Try --port ${port + 1}.`);
108
+ throw err;
109
+ });
110
+ print(`${green('✔')} Web interface running. Open this link (it only works on this machine):\n\n ${bold(ui.url)}\n`);
111
+ print(dim('Press Ctrl+C to stop.'));
112
+ if (!values['no-open'])
113
+ openBrowser(ui.url);
114
+ await new Promise((resolve) => process.once('SIGINT', resolve));
115
+ await ui.close();
116
+ return 0;
117
+ }
118
+ const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY) && !values.json;
119
+ const providerName = values.provider;
120
+ if (!isEmailProvider(providerName))
121
+ throw new UsageError(`--provider must be one of: ${EMAIL_PROVIDERS.join(', ')}.`);
122
+ if (values['on-behalf-of'] && providerName !== 'sendgrid')
123
+ throw new UsageError('--on-behalf-of only applies to SendGrid.');
124
+ const credentials = await emailCredentialsFromEnv(providerName, values.region, values['aws-profile'], interactive);
125
+ const domainInput = values.domain || (interactive ? await ask('Domain to authenticate: ') : '');
126
+ if (!domainInput)
127
+ throw new UsageError('Pass --domain.');
128
+ const domain = normalizeName(domainInput);
129
+ const email = createEmailProvider(providerName, credentials, { subuser: values['on-behalf-of'] });
130
+ if (command === 'status') {
131
+ const status = await checkStatus(email, domain, values.subdomain);
132
+ if (values.json)
133
+ return print(JSON.stringify(status, null, 2)), status.validation?.valid ? 0 : 2;
134
+ if (!status.domain)
135
+ return print(`${red('✘')} No ${email.label} domain found for ${domain}.`), 2;
136
+ print(`${bold(domain)} ${dim(`(${email.label} id ${status.domain.id})`)}`);
137
+ for (const r of status.validation?.results ?? []) {
138
+ const record = status.domain.records.find((x) => x.purpose === r.record);
139
+ print(` ${r.valid ? green('✔') : red('✘')} ${r.record.padEnd(12)} ${record ? describeRecord(record) : ''}`);
140
+ if (r.reason)
141
+ print(` ${dim(r.reason)}`);
142
+ }
143
+ print(status.validation?.valid ? green('Authenticated.') : yellow('Not yet authenticated.'));
144
+ return status.validation?.valid ? 0 : 2;
145
+ }
146
+ const waitSeconds = Number(values.wait);
147
+ if (!Number.isFinite(waitSeconds) || waitSeconds < 0)
148
+ throw new UsageError('--wait must be a number of seconds.');
149
+ const dnsName = values.dns ?? (process.env.CLOUDFLARE_API_TOKEN ? 'cloudflare' : 'manual');
150
+ const cloudflareApiToken = dnsName === 'cloudflare' && !process.env.CLOUDFLARE_API_TOKEN && interactive
151
+ ? await askSecret('Cloudflare API token: ')
152
+ : undefined;
153
+ const dns = createDnsProvider(dnsName, {
154
+ cloudflareApiToken,
155
+ awsProfile: values['aws-profile'],
156
+ route53HostedZoneId: values['zone-id'],
157
+ onManualRecords: async (records) => {
158
+ if (values.json)
159
+ return;
160
+ print(`\nAdd these records at your DNS host ${dim('(disable any proxy/CDN on the CNAMEs)')}:\n`);
161
+ printRecordTable(records);
162
+ if (interactive)
163
+ await ask(`\nPress Enter once they're saved… `);
164
+ },
165
+ });
166
+ const inboundUrl = values['inbound-url'];
167
+ if (inboundUrl && !/^https:\/\//.test(inboundUrl))
168
+ throw new UsageError('--inbound-url must be an https:// URL.');
169
+ if (inboundUrl && !email.supportsInbound)
170
+ throw new UsageError(`Receiving email isn't supported with ${email.label} yet.`);
171
+ const inbound = inboundUrl
172
+ ? {
173
+ hostname: normalizeName(values['inbound-host'] ?? email.defaultInboundHostname(domain)),
174
+ url: inboundUrl,
175
+ spamCheck: values['spam-check'],
176
+ sendRaw: values['send-raw'],
177
+ }
178
+ : undefined;
179
+ const confirm = async () => {
180
+ if (values.yes || dns.name === 'manual')
181
+ return true;
182
+ if (!interactive) {
183
+ if (!values.json)
184
+ print(yellow('\nNot a terminal: pass --yes to apply these changes.'));
185
+ return false;
186
+ }
187
+ const answer = await ask(`\nApply these changes to ${dns.name}? [y/N] `);
188
+ return /^y(es)?$/i.test(answer);
189
+ };
190
+ const result = await setupDomain(email, dns, {
191
+ domain,
192
+ subdomain: values.subdomain,
193
+ inbound,
194
+ overwrite: values.overwrite,
195
+ dryRun: values['dry-run'],
196
+ confirm,
197
+ propagation: { timeoutMs: waitSeconds * 1000 },
198
+ onEvent: values.json ? undefined : createEventPrinter(dns.name, values['dry-run'], email.label),
199
+ });
200
+ if (values.json)
201
+ print(JSON.stringify(result, null, 2));
202
+ else
203
+ printSummary(result, email.label);
204
+ return result.status === 'complete' || result.status === 'dry-run' ? 0 : 2;
205
+ }
206
+ /** Credentials for the chosen provider from its environment variables (or a prompt, for single keys). */
207
+ async function emailCredentialsFromEnv(provider, region, awsProfile, interactive) {
208
+ const env = process.env;
209
+ const secret = async (value, prompt, variable) => {
210
+ const v = value || (interactive ? await askSecret(prompt) : '');
211
+ if (!v)
212
+ throw new UsageError(`Set ${variable}.`);
213
+ return v;
214
+ };
215
+ switch (provider) {
216
+ case 'postmark':
217
+ return {
218
+ accountToken: await secret(env.POSTMARK_ACCOUNT_TOKEN, 'Postmark account API token: ', 'POSTMARK_ACCOUNT_TOKEN'),
219
+ ...(env.POSTMARK_INBOUND_SERVER_ID ? { inboundServerId: env.POSTMARK_INBOUND_SERVER_ID } : {}),
220
+ };
221
+ case 'mailgun':
222
+ return { apiKey: await secret(env.MAILGUN_API_KEY, 'Mailgun API key: ', 'MAILGUN_API_KEY'), region: region ?? env.MAILGUN_REGION ?? 'us' };
223
+ case 'ses': {
224
+ const aws = await loadAwsCredentials({ profile: awsProfile });
225
+ const sesRegion = region ?? env.AWS_REGION ?? env.AWS_DEFAULT_REGION;
226
+ if (!sesRegion)
227
+ throw new UsageError('Set AWS_REGION or pass --region for Amazon SES.');
228
+ return { ...aws, region: sesRegion };
229
+ }
230
+ default:
231
+ return { apiKey: await secret(env.SENDGRID_API_KEY, 'SendGrid API key: ', 'SENDGRID_API_KEY') };
232
+ }
233
+ }
234
+ function createEventPrinter(dnsName, dryRun, providerLabel) {
235
+ let progressShown = false;
236
+ const endProgress = () => {
237
+ if (progressShown && process.stdout.isTTY)
238
+ process.stdout.write('\n');
239
+ progressShown = false;
240
+ };
241
+ return (event) => {
242
+ switch (event.type) {
243
+ case 'domain':
244
+ print(`${green('✔')} ${providerLabel} domain ${event.auth.created ? 'created' : 'found'}: ${bold(event.auth.domain)} ${dim(`(id ${event.auth.id})`)}`);
245
+ break;
246
+ case 'plan':
247
+ if (dnsName !== 'manual') {
248
+ print(`\nDNS plan (${dnsName}):`);
249
+ printPlan(event.changes);
250
+ }
251
+ else if (dryRun) {
252
+ print('\nRecords to add at your DNS host:\n');
253
+ printRecordTable(event.changes.map((c) => c.record));
254
+ }
255
+ break;
256
+ case 'applied':
257
+ if (dnsName !== 'manual')
258
+ print(`${green('✔')} Wrote ${event.changes.length} DNS record(s)`);
259
+ break;
260
+ case 'propagation': {
261
+ const seconds = Math.round(event.elapsedMs / 1000);
262
+ const done = event.total - event.pending.length;
263
+ const line = `… DNS visible on authoritative servers: ${done}/${event.total} ${dim(`(${seconds}s)`)}`;
264
+ if (event.pending.length === 0) {
265
+ endProgress();
266
+ print(`${green('✔')} All ${event.total} DNS records are live`);
267
+ }
268
+ else if (process.stdout.isTTY) {
269
+ process.stdout.write(`\r\x1b[K${line}`);
270
+ progressShown = true;
271
+ }
272
+ else {
273
+ print(line);
274
+ }
275
+ break;
276
+ }
277
+ case 'validation':
278
+ endProgress();
279
+ if (event.result.valid)
280
+ print(`${green('✔')} ${providerLabel} verified the domain`);
281
+ else
282
+ print(dim(`… ${providerLabel} validation attempt ${event.attempt}: not valid yet`));
283
+ break;
284
+ case 'inbound':
285
+ print(`${green('✔')} Inbound parse ${event.action}: ${event.hostname} → ${event.url}`);
286
+ break;
287
+ }
288
+ };
289
+ }
290
+ function printPlan(changes) {
291
+ const label = {
292
+ create: ['+ create', green],
293
+ update: ['~ update', yellow],
294
+ noop: ['= exists', dim],
295
+ conflict: ['! conflict', red],
296
+ };
297
+ for (const c of changes) {
298
+ const [text, color] = label[c.action];
299
+ const reason = c.reason ? dim(` (${c.reason})`) : '';
300
+ print(` ${color(text.padEnd(10))} ${describeRecord(c.record)}${reason}`);
301
+ }
302
+ }
303
+ function printRecordTable(records) {
304
+ const rows = records.map((r) => [r.type, r.name, r.priority !== undefined ? `${r.priority} ${r.value}` : r.value]);
305
+ const widths = [0, 1].map((i) => Math.max(...rows.map((row) => row[i].length), i === 0 ? 4 : 4));
306
+ print(` ${bold('Type'.padEnd(widths[0]))} ${bold('Name'.padEnd(widths[1]))} ${bold('Value')}`);
307
+ rows.forEach(([type, name, value], i) => {
308
+ print(` ${type.padEnd(widths[0])} ${name.padEnd(widths[1])} ${value}`);
309
+ if (records[i].note)
310
+ print(` ${' '.repeat(widths[0])} ${dim(records[i].note)}`);
311
+ });
312
+ }
313
+ function printSummary(result, providerLabel) {
314
+ print('');
315
+ switch (result.status) {
316
+ case 'complete':
317
+ print(green(bold(`${result.domain.domain} is authenticated with ${providerLabel}.`)));
318
+ break;
319
+ case 'dry-run':
320
+ print(dim('Dry run: no DNS changes made.'));
321
+ if (result.domain.created)
322
+ print(dim(`The ${providerLabel} domain entry was created so its records could be shown.`));
323
+ break;
324
+ case 'aborted':
325
+ print(yellow('Aborted: no DNS changes made.'));
326
+ break;
327
+ case 'dns-pending':
328
+ print(yellow('These records are not visible in DNS yet:'));
329
+ for (const r of result.pending ?? [])
330
+ print(` ${describeRecord(r)}`);
331
+ print('DNS can take a while to propagate. Re-run the same command later; it picks up where it left off.');
332
+ break;
333
+ case 'validation-failed':
334
+ print(yellow(`DNS is live, but ${providerLabel} has not validated the domain yet:`));
335
+ for (const r of result.validation?.results ?? []) {
336
+ if (!r.valid)
337
+ print(` ${red('✘')} ${r.record}${r.reason ? `: ${r.reason}` : ''}`);
338
+ }
339
+ print(`${providerLabel} can lag a few minutes behind DNS. Re-run the same command, or check with \`status\`.`);
340
+ break;
341
+ }
342
+ }
343
+ function openBrowser(url) {
344
+ const [cmd, args] = process.platform === 'darwin' ? ['open', [url]] : process.platform === 'win32' ? ['cmd', ['/c', 'start', '', url]] : ['xdg-open', [url]];
345
+ try {
346
+ spawn(cmd, args, { stdio: 'ignore', detached: true }).on('error', () => { }).unref();
347
+ }
348
+ catch {
349
+ // The link is printed; opening it is a convenience.
350
+ }
351
+ }
352
+ function print(line) {
353
+ process.stdout.write(line + '\n');
354
+ }
355
+ async function ask(question) {
356
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
357
+ try {
358
+ return (await rl.question(question)).trim();
359
+ }
360
+ finally {
361
+ rl.close();
362
+ }
363
+ }
364
+ async function askSecret(question) {
365
+ const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true });
366
+ process.stdout.write(question);
367
+ // Suppress echo of typed characters.
368
+ rl._writeToOutput = () => { };
369
+ try {
370
+ return (await rl.question('')).trim();
371
+ }
372
+ finally {
373
+ rl.close();
374
+ process.stdout.write('\n');
375
+ }
376
+ }
377
+ main().then((code) => process.exit(code), (err) => {
378
+ if (err instanceof UsageError || err instanceof ConfigError) {
379
+ process.stderr.write(`${red('✘')} ${err.message}\n`);
380
+ }
381
+ else if (err instanceof DnsConflictError) {
382
+ process.stderr.write(`${red('✘')} ${err.message}\nNothing was written. Fix those records, or pass --overwrite to replace CNAME/MX values.\n`);
383
+ }
384
+ else if (err instanceof ApiError) {
385
+ process.stderr.write(`${red('✘')} ${err.message}\n`);
386
+ const hint = apiErrorHint(err);
387
+ if (hint)
388
+ process.stderr.write(`${hint}\n`);
389
+ }
390
+ else if (err instanceof Error && 'code' in err && String(err.code).startsWith('ERR_PARSE_ARGS')) {
391
+ process.stderr.write(`${red('✘')} ${err.message}. See --help.\n`);
392
+ }
393
+ else {
394
+ process.stderr.write(`${red('✘')} ${err instanceof Error ? err.message : String(err)}\n`);
395
+ }
396
+ process.exit(1);
397
+ });
398
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { DnsProviderName } from './providers/factory.js';
2
+ export interface DnsHost {
3
+ /** Display name, e.g. "GoDaddy"; null when unrecognized. */
4
+ name: string | null;
5
+ /** Set when we can write records there automatically. */
6
+ provider: Exclude<DnsProviderName, 'manual'> | null;
7
+ nameservers: string[];
8
+ }
9
+ /** Identifies where a domain's DNS is hosted from its NS records. Never throws. */
10
+ export declare function detectDnsHost(domain: string, resolveNs?: (name: string) => Promise<string[]>): Promise<DnsHost>;
@@ -0,0 +1,31 @@
1
+ import { findZoneNameservers } from './propagation.js';
2
+ // Nameserver hostname patterns of common DNS hosts.
3
+ const HOSTS = [
4
+ [/\.ns\.cloudflare\.com$/, 'Cloudflare', 'cloudflare'],
5
+ [/\.awsdns-\d+\.(com|net|org|co\.uk)$/, 'Amazon Route 53', 'route53'],
6
+ [/\.domaincontrol\.com$/, 'GoDaddy', null],
7
+ [/\.registrar-servers\.com$/, 'Namecheap', null],
8
+ [/^ns-cloud-[a-e]\d\.googledomains\.com$/, 'Google Cloud DNS', null],
9
+ [/\.digitalocean\.com$/, 'DigitalOcean', null],
10
+ [/\.vercel-dns\.com$/, 'Vercel', null],
11
+ [/\.azure-dns\.(com|net|org|info)$/, 'Azure DNS', null],
12
+ [/\.ui-dns\.(com|de|org|biz)$/, 'IONOS', null],
13
+ [/\.wixdns\.net$/, 'Wix', null],
14
+ [/\.nsone\.net$/, 'NS1', null],
15
+ [/\.gandi\.net$/, 'Gandi', null],
16
+ [/\.ovh\.net$/, 'OVHcloud', null],
17
+ [/\.porkbun\.com$/, 'Porkbun', null],
18
+ [/\.dnsimple(-edge)?\.(com|net|org)$/, 'DNSimple', null],
19
+ [/\.hover\.com$/, 'Hover', null],
20
+ ];
21
+ /** Identifies where a domain's DNS is hosted from its NS records. Never throws. */
22
+ export async function detectDnsHost(domain, resolveNs) {
23
+ const found = await findZoneNameservers(domain, resolveNs).catch(() => null);
24
+ const nameservers = found?.nameservers.map((n) => n.replace(/\.$/, '')) ?? [];
25
+ for (const [pattern, name, provider] of HOSTS) {
26
+ if (nameservers.length > 0 && nameservers.every((ns) => pattern.test(ns)))
27
+ return { name, provider, nameservers };
28
+ }
29
+ return { name: null, provider: null, nameservers };
30
+ }
31
+ //# sourceMappingURL=dns-host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dns-host.js","sourceRoot":"","sources":["../../src/dns-host.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAWvD,oDAAoD;AACpD,MAAM,KAAK,GAA4C;IACrD,CAAC,wBAAwB,EAAE,YAAY,EAAE,YAAY,CAAC;IACtD,CAAC,qCAAqC,EAAE,iBAAiB,EAAE,SAAS,CAAC;IACrE,CAAC,uBAAuB,EAAE,SAAS,EAAE,IAAI,CAAC;IAC1C,CAAC,2BAA2B,EAAE,WAAW,EAAE,IAAI,CAAC;IAChD,CAAC,wCAAwC,EAAE,kBAAkB,EAAE,IAAI,CAAC;IACpE,CAAC,sBAAsB,EAAE,cAAc,EAAE,IAAI,CAAC;IAC9C,CAAC,oBAAoB,EAAE,QAAQ,EAAE,IAAI,CAAC;IACtC,CAAC,kCAAkC,EAAE,WAAW,EAAE,IAAI,CAAC;IACvD,CAAC,6BAA6B,EAAE,OAAO,EAAE,IAAI,CAAC;IAC9C,CAAC,gBAAgB,EAAE,KAAK,EAAE,IAAI,CAAC;IAC/B,CAAC,eAAe,EAAE,KAAK,EAAE,IAAI,CAAC;IAC9B,CAAC,eAAe,EAAE,OAAO,EAAE,IAAI,CAAC;IAChC,CAAC,aAAa,EAAE,UAAU,EAAE,IAAI,CAAC;IACjC,CAAC,iBAAiB,EAAE,SAAS,EAAE,IAAI,CAAC;IACpC,CAAC,oCAAoC,EAAE,UAAU,EAAE,IAAI,CAAC;IACxD,CAAC,eAAe,EAAE,OAAO,EAAE,IAAI,CAAC;CACjC,CAAC;AAEF,mFAAmF;AACnF,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,MAAc,EACd,SAA+C;IAE/C,MAAM,KAAK,GAAG,MAAM,mBAAmB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAC7E,MAAM,WAAW,GAAG,KAAK,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9E,KAAK,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,KAAK,EAAE,CAAC;QAC9C,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,IAAI,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;IACpH,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;AACrD,CAAC"}