Created
March 12, 2022 10:55
-
-
Save KenBonny/38afab3460002dfd167f8ff4a062fd98 to your computer and use it in GitHub Desktop.
A script to create Fastmail DNS records in Cloudflare
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// resources used: | |
// https://api.cloudflare.com/#dns-records-for-a-zone-create-dns-record | |
// https://www.fastmail.help/hc/en-us/articles/360060591153-Domains-Advanced-configuration | |
#r "nuget: Flurl.Http, Version=3.2.2" | |
#r "nuget: Newtonsoft.Json, Version=13.0.1" | |
open Flurl | |
open Flurl.Http | |
open System | |
open Newtonsoft.Json | |
type Error = { | |
Code: int | |
Message: string | |
Error_Chain: Error list | |
} | |
type Response<'a> = { | |
Result: 'a | |
Success: bool | |
Errors: Error list | |
} | |
type Zone = { | |
Id: Guid | |
Name: string | |
} | |
type CreateDns = { | |
``Type``: string | |
Name: string | |
Content: string | |
Ttl: int | |
Priority: int Nullable | |
Proxied: bool | |
} | |
type SrvData = { | |
service: string | |
proto: string | |
name: string | |
priority: int | |
weight: int | |
port: int | |
target: string | |
} | |
type CreateSrv = { | |
Data: SrvData | |
} | |
with | |
member this.``type`` = "SRV" | |
type DnsCreated = { | |
Id: Guid | |
Locked: bool | |
} | |
let domain = fsi.CommandLineArgs[1] | |
let authToken = fsi.CommandLineArgs[2] | |
let cloudflare = "https://api.cloudflare.com/client/v4" | |
type Dns = | |
static member create(dns, content, ?subDomain, (?priority:int), ?proxied) = | |
let name = match subDomain with | |
| Some sub -> $"{sub}.{domain}" | |
| None -> domain | |
let prio = | |
match priority with | |
| Some x -> Nullable x | |
| None -> Nullable() | |
{ | |
``Type`` = dns | |
Name = name | |
Content = content | |
Ttl = 1 | |
Priority = prio | |
Proxied = defaultArg proxied false | |
} | |
static member createSrv(service, weight, port, target, ?priority) = | |
{ | |
Data = { | |
service = $"_{service}" | |
proto = "_tcp" | |
name = domain | |
priority = defaultArg priority 0 | |
weight = weight | |
port = port | |
target = target | |
} | |
} | |
let mx = "MX" | |
let smtp1 = Dns.create(mx, "in1-smtp.messagingengine.com", ?priority = Some 10) | |
let smtp2 = Dns.create(mx, "in2-smtp.messagingengine.com", ?priority = Some 20) | |
let smtpSubdomain1 = Dns.create(mx, "in1-smtp.messagingengine.com", "*", 10) | |
let smtpSubdomain2 = Dns.create(mx, "in2-smtp.messagingengine.com", "*", 20) | |
// this specific mail.domain.com allows receiving mail at user@mail.domain.com, otherwise the A record hides that | |
let smtpMailSubdomain1 = Dns.create(mx, "in1-smtp.messagingengine.com", "mail", 10) | |
let smtpMailSubdomain2 = Dns.create(mx, "in2-smtp.messagingengine.com", "mail", 20) | |
let a = "A" | |
let webmailPortal1 = Dns.create(a, "66.111.4.147", "mail", ?proxied = Some true) | |
let webmailPortal2 = Dns.create(a, "66.111.4.148", "mail", ?proxied = Some true) | |
let cname = "CNAME" | |
let dkim1 = Dns.create(cname, $"fm1.{domain}.dkim.fmhosted.com", "fm1._domainkey") | |
let dkim2 = Dns.create(cname, $"fm2.{domain}.dkim.fmhosted.com", "fm2._domainkey") | |
let dkim3 = Dns.create(cname, $"fm3.{domain}.dkim.fmhosted.com", "fm3._domainkey") | |
let txt = "TXT" | |
let spf = Dns.create(txt, "v=spf1 include:spf.messagingengine.com ?all") | |
let autoDiscoverSubmission = Dns.createSrv("submission", 1, 587, "smtp.fastmail.com") | |
let autoDiscoverImap = Dns.createSrv("imap", 0, 0, ".") | |
let autoDiscoverImapSecure = Dns.createSrv("imaps", 1, 993, "imap.fastmail.com") | |
let autoDiscoverPop3 = Dns.createSrv("pop3", 0, 0, ".") | |
let autoDiscoverPop3Secure = Dns.createSrv("pop3s", 1, 995, "pop.fastmail.com", 10) | |
let autoDiscoverJmap = Dns.createSrv("jmap", 1, 443, "jmap.fastmail.com") | |
let autoDiscoverCardDav = Dns.createSrv("carddav", 0, 0, ".") | |
let autoDiscoverCardDavSecure = Dns.createSrv("carddavs", 1, 443, "carddav.fastmail.com") | |
let autoDiscoverCalDav = Dns.createSrv("caldav", 0, 0, ".") | |
let autoDiscoverCalDavSecure = Dns.createSrv("caldavs", 1, 443, "caldav.fastmail.com") | |
let dnsRecords = [ | |
smtp1 | |
smtp2 | |
smtpSubdomain1 | |
smtpSubdomain2 | |
smtpMailSubdomain1 | |
smtpMailSubdomain2 | |
webmailPortal1 | |
webmailPortal2 | |
dkim1 | |
dkim2 | |
dkim3 | |
spf | |
] | |
let srvRecords = [ | |
autoDiscoverSubmission | |
autoDiscoverImap | |
autoDiscoverImapSecure | |
autoDiscoverPop3 | |
autoDiscoverPop3Secure | |
autoDiscoverJmap | |
autoDiscoverCardDav | |
autoDiscoverCardDavSecure | |
autoDiscoverCalDav | |
autoDiscoverCalDavSecure | |
] | |
let getZoneId = | |
task { | |
let! zones = cloudflare.AppendPathSegment("zones") | |
.WithOAuthBearerToken(authToken) | |
.SetQueryParam("name", domain) | |
.GetJsonAsync<Zone list Response>() | |
let zoneId = if zones.Success | |
then zones.Result[0].Id | |
else exit -1 | |
return zoneId.ToString("N") | |
} | |
let createDnsRecord record = | |
task { | |
let! zoneId = getZoneId | |
try | |
let! responseTask = cloudflare.AppendPathSegments("zones", zoneId, "dns_records") | |
.WithOAuthBearerToken(authToken) | |
.WithHeader("Content-Type", "application/json") | |
.PostStringAsync(record) | |
let! response = responseTask.GetJsonAsync<DnsCreated Response>() | |
return (record, response) | |
with | |
| :? FlurlHttpException as ex -> | |
let! response = ex.GetResponseJsonAsync<DnsCreated Response>() | |
return (record, response) | |
} | |
let print records = | |
records |> List.iter (fun record -> printfn $"{record}") | |
records | |
(dnsRecords |> List.map JsonConvert.SerializeObject) | |
@ (srvRecords |> List.map JsonConvert.SerializeObject) | |
|> List.map createDnsRecord | |
|> Threading.Tasks.Task.WhenAll | |
|> Async.AwaitTask | |
|> Async.RunSynchronously | |
//|> Array.filter (fun (_, response) -> not response.Success) | |
|> Array.iter (fun (record, response) -> printfn ""; printfn $"{record}";printfn $"{response}") | |
printfn $"created fastmail dns records for {domain}" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment