iac/terraform/dns.tf
Sergei Poljanski 94bc2d7ee9
dns: add admin.asxp.io
Superuser login for the asxp.io admin moves to admin.asxp.io, which
Traefik only serves to WireGuard and the VPC. The public A record is
there for the Let's Encrypt HTTP challenge, like gf; clients reach it
through split-horizon hosts entries.

Assisted-by: Claude:opus-5-5
2026-11-13 06:54:58 +04:00

150 lines
11 KiB
HCL

# DNS for asxp.io and czsk.it (Hetzner DNS, project asxpio). Every record
# set of both zones is here except SOA and NS, which Hetzner manages.
#
# Keys are "<name>/<type>"; "@" is the apex. Records of our own servers
# point at the server resources, so moving a service is one edit here.
# Every record set has Hetzner's change protection except those of the
# test servers in local.unprotected: a protected set cannot be changed or
# deleted until protection is lifted, so a change to one is two applies
# (set protection off for it, then edit). Both zones are delete-protected.
locals {
ip = {
ops_v4 = hcloud_server.ops.ipv4_address
ops_v6 = hcloud_server.ops.ipv6_address
services_v4 = hcloud_server.services.ipv4_address
services_v6 = hcloud_server.services.ipv6_address
}
zones = {
"asxp.io" = { ttl = 3600, delete_protection = true }
"czsk.it" = { ttl = 86400, delete_protection = true }
}
# Test XMPP servers (prosody, ejabberd, anotherim) and their subdomains
unprotected = "(^|\\.)(prosody|ejabberd|anotherim)$"
dns = {
"asxp.io" = {
"@/A" = { records = [local.ip.ops_v4] }
"anotherim/A" = { records = ["178.105.241.125"] }
"blog/A" = { records = [local.ip.ops_v4], ttl = 86400 }
"conference/A" = { records = [local.ip.services_v4], ttl = 300 }
"conference.anotherim/A" = { records = ["178.105.241.125"] }
"conference.ejabberd/A" = { records = ["178.105.240.144"] }
"conference.prosody/A" = { records = ["178.105.250.162"] }
"conference.xmpp/A" = { records = [local.ip.services_v4], ttl = 300 }
"ejabberd/A" = { records = ["178.105.240.144"] }
"mc/A" = { records = [local.ip.services_v4] }
"prosody/A" = { records = ["178.105.250.162"] }
"proxy.anotherim/A" = { records = ["178.105.241.125"] }
"proxy.ejabberd/A" = { records = ["178.105.240.144"] }
"proxy.prosody/A" = { records = ["178.105.250.162"] }
"pubsub.anotherim/A" = { records = ["178.105.241.125"] }
"pubsub.ejabberd/A" = { records = ["178.105.240.144"] }
"pubsub.prosody/A" = { records = ["178.105.250.162"] }
"s3/A" = { records = [local.ip.ops_v4] }
"traefik/A" = { records = [local.ip.ops_v4] }
"upload.ejabberd/A" = { records = ["178.105.240.144"] }
"upload.prosody/A" = { records = ["178.105.250.162"] }
"www/A" = { records = [local.ip.ops_v4] }
"xmpp/A" = { records = [local.ip.services_v4], ttl = 300 }
"@/AAAA" = { records = [local.ip.ops_v6] }
"anotherim/AAAA" = { records = ["2a01:4f8:c01e:27d9::1"] }
"blog/AAAA" = { records = [local.ip.ops_v6], ttl = 86400 }
"conference/AAAA" = { records = [local.ip.services_v6], ttl = 300 }
"conference.anotherim/AAAA" = { records = ["2a01:4f8:c01e:27d9::1"] }
"conference.ejabberd/AAAA" = { records = ["2a01:4f8:c01e:11b6::1"] }
"conference.prosody/AAAA" = { records = ["2a01:4f8:c01e:2891::1"] }
"conference.xmpp/AAAA" = { records = [local.ip.services_v6], ttl = 300 }
"ejabberd/AAAA" = { records = ["2a01:4f8:c01e:11b6::1"] }
"prosody/AAAA" = { records = ["2a01:4f8:c01e:2891::1"] }
"proxy.anotherim/AAAA" = { records = ["2a01:4f8:c01e:27d9::1"] }
"proxy.ejabberd/AAAA" = { records = ["2a01:4f8:c01e:11b6::1"] }
"proxy.prosody/AAAA" = { records = ["2a01:4f8:c01e:2891::1"] }
"pubsub.anotherim/AAAA" = { records = ["2a01:4f8:c01e:27d9::1"] }
"pubsub.ejabberd/AAAA" = { records = ["2a01:4f8:c01e:11b6::1"] }
"pubsub.prosody/AAAA" = { records = ["2a01:4f8:c01e:2891::1"] }
"s3/AAAA" = { records = [local.ip.ops_v6] }
"upload.ejabberd/AAAA" = { records = ["2a01:4f8:c01e:11b6::1"] }
"upload.prosody/AAAA" = { records = ["2a01:4f8:c01e:2891::1"] }
"www/AAAA" = { records = [local.ip.ops_v6] }
"xmpp/AAAA" = { records = [local.ip.services_v6], ttl = 300 }
"fm1._domainkey/CNAME" = { records = ["fm1.asxp.io.dkim.fmhosted.com."] }
"fm2._domainkey/CNAME" = { records = ["fm2.asxp.io.dkim.fmhosted.com."] }
"fm3._domainkey/CNAME" = { records = ["fm3.asxp.io.dkim.fmhosted.com."] }
"gf/A" = { records = [local.ip.ops_v4] }
"admin/A" = { records = [local.ip.ops_v4] }
"git/CNAME" = { records = ["git.czsk.it."], ttl = 86400 }
"mail/CNAME" = { records = ["mail.fastmail.com."] }
"@/MX" = { records = ["10 in1-smtp.messagingengine.com.", "20 in2-smtp.messagingengine.com."] }
"@/TXT" = { records = ["\"google-site-verification=H1sj22-DZ-4uW_Ri7z18RDKXo50bPwJXqIT1SyjthZg\"", "\"nfd=5777245\"", "\"v=spf1 include:spf.messagingengine.com ?all\""] }
"_discord/TXT" = { records = ["\"dh=8d39ced1e23afda4492219ee2456a81d209fecaa\""] }
"_dmarc/TXT" = { records = ["\"v=DMARC1;p=reject;rua=mailto:tr@asxp.io;ruf=mailto:tr@asxp.io;rf=afrf;pct=100\""] }
"mail._domainkey/TXT" = { records = ["\"k=rsa; t=s; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0X0brpvueSc7IiGZA2Qx zu12CWjeSN7/SquxrZM+afjU2If9BL5MHiE9iza/k6HgVnvc7exfMUdCHeLPUJJr WUIgCpYUR6ogJF0pe402zltYBExxjJFIMASAXuhTi0Gs4nc7fcfM56zSLckmJpO9 6jMrZPM7x3BxCeJt79j+4NLegClP8d5eRk1aByz9rujRYE\" \" rSJzaUcHuR47uEE5wz bSbY64hUadaSduBuD/OnAy0F3iqMSzVV5KNzDnAyWD4V7QcEWK7GAF9nuj3KhhS9 QLSHydPw/CCrhrXVjcprDDpfDG8dQw1JPfYPKs8h+Z44SGFw1ZxvqYIUmQ6mYrxv uwIDAQAB\""] }
"_stun._tcp/SRV" = { records = ["0 5 3478 xmpp.asxp.io."] }
"_stun._tcp.anotherim/SRV" = { records = ["0 5 3478 anotherim.asxp.io."] }
"_stun._tcp.ejabberd/SRV" = { records = ["0 5 3478 ejabberd.asxp.io."] }
"_stun._udp/SRV" = { records = ["0 5 3478 xmpp.asxp.io."] }
"_stun._udp.anotherim/SRV" = { records = ["0 5 3478 anotherim.asxp.io."] }
"_stun._udp.ejabberd/SRV" = { records = ["0 5 3478 ejabberd.asxp.io."] }
"_turn._tcp/SRV" = { records = ["0 5 3478 xmpp.asxp.io."] }
"_turn._tcp.anotherim/SRV" = { records = ["0 5 3478 anotherim.asxp.io."] }
"_turn._tcp.ejabberd/SRV" = { records = ["0 5 3478 ejabberd.asxp.io."] }
"_turn._udp/SRV" = { records = ["0 5 3478 xmpp.asxp.io."] }
"_turn._udp.anotherim/SRV" = { records = ["0 5 3478 anotherim.asxp.io."] }
"_turn._udp.ejabberd/SRV" = { records = ["0 5 3478 ejabberd.asxp.io."] }
"_xmpp-client._tcp/SRV" = { records = ["0 5 5222 xmpp.asxp.io."], ttl = 3600 }
"_xmpp-client._tcp.anotherim/SRV" = { records = ["0 5 5222 anotherim.asxp.io."] }
"_xmpp-client._tcp.ejabberd/SRV" = { records = ["0 5 5222 ejabberd.asxp.io."] }
"_xmpp-client._tcp.prosody/SRV" = { records = ["0 5 5222 prosody.asxp.io."] }
"_xmpp-server._tcp/SRV" = { records = ["0 5 5269 xmpp.asxp.io."], ttl = 3600 }
"_xmpp-server._tcp.anotherim/SRV" = { records = ["0 5 5269 anotherim.asxp.io."] }
"_xmpp-server._tcp.conference/SRV" = { records = ["0 5 5269 xmpp.asxp.io."], ttl = 3600 }
"_xmpp-server._tcp.conference.anotherim/SRV" = { records = ["0 5 5269 anotherim.asxp.io."] }
"_xmpp-server._tcp.conference.ejabberd/SRV" = { records = ["0 5 5269 ejabberd.asxp.io."] }
"_xmpp-server._tcp.conference.prosody/SRV" = { records = ["0 5 5269 prosody.asxp.io."] }
"_xmpp-server._tcp.ejabberd/SRV" = { records = ["0 5 5269 ejabberd.asxp.io."] }
"_xmpp-server._tcp.prosody/SRV" = { records = ["0 5 5269 prosody.asxp.io."] }
"_xmpps-client._tcp/SRV" = { records = ["0 5 5223 xmpp.asxp.io."], ttl = 3600 }
"_xmpps-client._tcp.anotherim/SRV" = { records = ["0 5 5223 anotherim.asxp.io."] }
"_xmpps-client._tcp.ejabberd/SRV" = { records = ["0 5 5223 ejabberd.asxp.io."] }
"_xmpps-client._tcp.prosody/SRV" = { records = ["0 5 5223 prosody.asxp.io."] }
},
"czsk.it" = {
"@/A" = { records = [local.ip.ops_v4] }
"www/A" = { records = [local.ip.ops_v4] }
"@/AAAA" = { records = [local.ip.ops_v6] }
}
}
# "<zone>/<name>/<type>" => record set; the key is also the import ID
rrsets = merge([
for zone, sets in local.dns : {
for key, set in sets : "${zone}/${key}" => merge(set, {
zone = zone
name = split("/", key)[0]
type = split("/", key)[1]
})
}
]...)
}
resource "hcloud_zone" "zone" {
for_each = local.zones
name = each.key
mode = "primary"
ttl = each.value.ttl
delete_protection = each.value.delete_protection
}
resource "hcloud_zone_rrset" "rr" {
for_each = local.rrsets
zone = hcloud_zone.zone[each.value.zone].name
name = each.value.name
type = each.value.type
ttl = try(each.value.ttl, null)
# A set can opt out with protected = false (e.g. to delete it next apply)
change_protection = try(each.value.protected, !can(regex(local.unprotected, each.value.name)))
records = [for value in each.value.records : { value = value }]
}