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.
- package/README.md +161 -2
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +398 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/dns-host.d.ts +10 -0
- package/dist/src/dns-host.js +31 -0
- package/dist/src/dns-host.js.map +1 -0
- package/dist/src/errors.d.ts +17 -0
- package/dist/src/errors.js +51 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/hosted/api.d.ts +107 -0
- package/dist/src/hosted/api.js +919 -0
- package/dist/src/hosted/api.js.map +1 -0
- package/dist/src/hosted/auth.d.ts +80 -0
- package/dist/src/hosted/auth.js +147 -0
- package/dist/src/hosted/auth.js.map +1 -0
- package/dist/src/hosted/billing-dodo.d.ts +58 -0
- package/dist/src/hosted/billing-dodo.js +139 -0
- package/dist/src/hosted/billing-dodo.js.map +1 -0
- package/dist/src/hosted/billing-stripe.d.ts +82 -0
- package/dist/src/hosted/billing-stripe.js +205 -0
- package/dist/src/hosted/billing-stripe.js.map +1 -0
- package/dist/src/hosted/billing.d.ts +114 -0
- package/dist/src/hosted/billing.js +173 -0
- package/dist/src/hosted/billing.js.map +1 -0
- package/dist/src/hosted/bulk.d.ts +47 -0
- package/dist/src/hosted/bulk.js +66 -0
- package/dist/src/hosted/bulk.js.map +1 -0
- package/dist/src/hosted/cli.d.ts +2 -0
- package/dist/src/hosted/cli.js +318 -0
- package/dist/src/hosted/cli.js.map +1 -0
- package/dist/src/hosted/crypto.d.ts +22 -0
- package/dist/src/hosted/crypto.js +55 -0
- package/dist/src/hosted/crypto.js.map +1 -0
- package/dist/src/hosted/index.d.ts +7 -0
- package/dist/src/hosted/index.js +11 -0
- package/dist/src/hosted/index.js.map +1 -0
- package/dist/src/hosted/markdown.d.ts +16 -0
- package/dist/src/hosted/markdown.js +168 -0
- package/dist/src/hosted/markdown.js.map +1 -0
- package/dist/src/hosted/net.d.ts +9 -0
- package/dist/src/hosted/net.js +60 -0
- package/dist/src/hosted/net.js.map +1 -0
- package/dist/src/hosted/postgres-store.d.ts +62 -0
- package/dist/src/hosted/postgres-store.js +387 -0
- package/dist/src/hosted/postgres-store.js.map +1 -0
- package/dist/src/hosted/serialize.d.ts +144 -0
- package/dist/src/hosted/serialize.js +126 -0
- package/dist/src/hosted/serialize.js.map +1 -0
- package/dist/src/hosted/service.d.ts +170 -0
- package/dist/src/hosted/service.js +603 -0
- package/dist/src/hosted/service.js.map +1 -0
- package/dist/src/hosted/site.d.ts +84 -0
- package/dist/src/hosted/site.js +653 -0
- package/dist/src/hosted/site.js.map +1 -0
- package/dist/src/hosted/sqlite-store.d.ts +45 -0
- package/dist/src/hosted/sqlite-store.js +351 -0
- package/dist/src/hosted/sqlite-store.js.map +1 -0
- package/dist/src/hosted/store.d.ts +257 -0
- package/dist/src/hosted/store.js +181 -0
- package/dist/src/hosted/store.js.map +1 -0
- package/dist/src/hosted/widget.d.ts +46 -0
- package/dist/src/hosted/widget.js +130 -0
- package/dist/src/hosted/widget.js.map +1 -0
- package/dist/src/hosted/worker.d.ts +45 -0
- package/dist/src/hosted/worker.js +142 -0
- package/dist/src/hosted/worker.js.map +1 -0
- package/dist/src/http.d.ts +14 -0
- package/dist/src/http.js +54 -0
- package/dist/src/http.js.map +1 -0
- package/dist/src/index.d.ts +19 -0
- package/dist/src/index.js +19 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/orchestrator.d.ts +70 -0
- package/dist/src/orchestrator.js +69 -0
- package/dist/src/orchestrator.js.map +1 -0
- package/dist/src/propagation.d.ts +30 -0
- package/dist/src/propagation.js +76 -0
- package/dist/src/propagation.js.map +1 -0
- package/dist/src/providers/dns/aws.d.ts +28 -0
- package/dist/src/providers/dns/aws.js +103 -0
- package/dist/src/providers/dns/aws.js.map +1 -0
- package/dist/src/providers/dns/cloudflare.d.ts +26 -0
- package/dist/src/providers/dns/cloudflare.js +97 -0
- package/dist/src/providers/dns/cloudflare.js.map +1 -0
- package/dist/src/providers/dns/manual.d.ts +12 -0
- package/dist/src/providers/dns/manual.js +18 -0
- package/dist/src/providers/dns/manual.js.map +1 -0
- package/dist/src/providers/dns/route53.d.ts +34 -0
- package/dist/src/providers/dns/route53.js +176 -0
- package/dist/src/providers/dns/route53.js.map +1 -0
- package/dist/src/providers/email/factory.d.ts +29 -0
- package/dist/src/providers/email/factory.js +67 -0
- package/dist/src/providers/email/factory.js.map +1 -0
- package/dist/src/providers/email/mailgun.d.ts +36 -0
- package/dist/src/providers/email/mailgun.js +141 -0
- package/dist/src/providers/email/mailgun.js.map +1 -0
- package/dist/src/providers/email/postmark.d.ts +37 -0
- package/dist/src/providers/email/postmark.js +121 -0
- package/dist/src/providers/email/postmark.js.map +1 -0
- package/dist/src/providers/email/sendgrid.d.ts +31 -0
- package/dist/src/providers/email/sendgrid.js +109 -0
- package/dist/src/providers/email/sendgrid.js.map +1 -0
- package/dist/src/providers/email/ses.d.ts +39 -0
- package/dist/src/providers/email/ses.js +123 -0
- package/dist/src/providers/email/ses.js.map +1 -0
- package/dist/src/providers/factory.d.ts +14 -0
- package/dist/src/providers/factory.js +32 -0
- package/dist/src/providers/factory.js.map +1 -0
- package/dist/src/records.d.ts +35 -0
- package/dist/src/records.js +109 -0
- package/dist/src/records.js.map +1 -0
- package/dist/src/server.d.ts +67 -0
- package/dist/src/server.js +301 -0
- package/dist/src/server.js.map +1 -0
- package/dist/src/types.d.ts +75 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/xml.d.ts +6 -0
- package/dist/src/xml.js +23 -0
- package/dist/src/xml.js.map +1 -0
- package/docs/billing.md +43 -0
- package/docs/getting-started.md +66 -0
- package/docs/hosted-api.md +192 -0
- package/docs/library.md +253 -0
- package/docs/self-hosting.md +153 -0
- package/docs/widget.md +121 -0
- package/package.json +63 -4
- package/web/dashboard.html +669 -0
- package/web/index.html +697 -0
- package/web/widget-frame.html +480 -0
- package/web/widget.js +91 -0
package/README.md
CHANGED
|
@@ -1,3 +1,162 @@
|
|
|
1
|
-
#
|
|
1
|
+
# DKIMkit
|
|
2
2
|
|
|
3
|
-
|
|
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
|
package/dist/src/cli.js
ADDED
|
@@ -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"}
|