Skip to content

Instantly share code, notes, and snippets.

@KenBonny
Created March 12, 2022 10:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save KenBonny/38afab3460002dfd167f8ff4a062fd98 to your computer and use it in GitHub Desktop.
Save KenBonny/38afab3460002dfd167f8ff4a062fd98 to your computer and use it in GitHub Desktop.
A script to create Fastmail DNS records in Cloudflare
// 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)
//|> print
|> 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