diff --git a/docs/example.nix b/docs/example.nix index 20cb392..31378e9 100644 --- a/docs/example.nix +++ b/docs/example.nix @@ -1,49 +1,76 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "satr14washere"; - email = "admin@satr14.my.id"; +{ dns, ... }: { + metadata = { + description = "Example domain configuration for dns.nix"; # optional, description of use + proxy = false; # optional, defaults to false. proxy through Cloudflare + owner = { # add extra contacts if needed + username = "satr14washere"; # required, github username + email = "admin@satr14.my.id"; + }; }; - proxy = false; - records = { + records = with dns.lib.combinators; { # full list of records supported: https://github.com/nix-community/dns.nix/tree/master/dns/types/records + # dns.lib.combinators is optional but provides a lot of useful shortcuts: + # https://github.com/nix-community/dns.nix/blob/master/dns/combinators.nix + A = [ - { - address = "203.0.113.1"; - ttl = 60 * 60; - } - "203.0.113.2" - (ttl (60 * 60) (a "203.0.113.3")) + "203.0.113.50" + "198.51.100.50" + + # or: + + { address = "203.0.113.50"; ttl = 60 * 60; } # TTL is optional + { address = "198.51.100.50"; ttl = 60 * 60; } + + # using dns.lib.combinators: + + (ttl (60 * 60) (a "203.0.113.50")) # standalone A record + (ttl (60 * 60) (a "2198.51.100.50")) # record with TTL ]; - AAAA = [ - "4321:0:1:2:3:4:567:89ab" + + AAAA = [ # mostly same as above + "2001:db8::1" + "2001:db8::2" + + # or: + + { address = "2001:db8::1"; ttl = 60 * 60; } + { address = "2001:db8::2"; ttl = 60 * 60; } + + # using dns.lib.combinators: + + (ttl (60 * 60) (aaaa "2001:db8::1")) + (ttl (60 * 60) (aaaa "2001:db8::2")) ]; - MX = mx.google; + TXT = [ - ( - with spf; - strict [ - "a:mail.example.com" - google - ] - ) + "v=spf1 include:mailgun.org ~all" + "dh=some-long-random-string" ]; - CNAME = [ "example.com." ]; - DMARC = [ (dmarc.postmarkapp "mailto:re+abcdefghijk@dmarc.postmarkapp.com") ]; - CAA = letsEncrypt "admin@example.com"; - SRV = [ + + MX = [ { - service = "sip"; - proto = "tcp"; - port = 5060; - target = "sip.example.com"; + preference = 10; + exchange = "mail.protonmail.ch."; } - ]; - TLSA = [ { - certUsage = "dane-ee"; - selector = "spki"; - matchingType = "sha256"; - certificate = "899EB4AC9285578AFDA3CCBE152EE78D8618B8F3862FEF2703E1FC7011E9B8AA"; + preference = 20; + exchange = "mailsec.protonmail.ch."; } + + # using dns.lib.combinators: + + (mx.mx 10 "mail.protonmail.ch.") + (mx.mx 20 "mailsec.protonmail.ch.") ]; + + # a few notes about CNAME records: + # - value must end with a dot (.) + # - cannot coexist with other record types (e.g. A, AAAA, MX) for the same subdomain + # - can only be one despite being a list (this example defined multiple only for demonstrating valid values) + CNAME = [ + "edge.redirect.pizza." + "username.github.io." + "site.pages.dev." + ]; + }; } diff --git a/domains/_discord.colin.nix b/domains/_discord.colin.nix index 3ef69b9..f6f4a78 100644 --- a/domains/_discord.colin.nix +++ b/domains/_discord.colin.nix @@ -1,9 +1,12 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "ColinLeDev"; +{ dns, ... }: { + metadata = { + description = "Discord verification"; + proxy = false; + owner = { + username = "ColinLeDev"; + }; }; - proxy = false; - records = { + records = with dns.lib.combinators; { TXT = [ "dh=279643a6f8677dedb1c5c63d007fc4516149679c" ]; }; } diff --git a/domains/_discord.cutedog5695.nix b/domains/_discord.cutedog5695.nix index 053e506..6b41ac4 100644 --- a/domains/_discord.cutedog5695.nix +++ b/domains/_discord.cutedog5695.nix @@ -1,11 +1,13 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "CuteDog5695"; - email = "cutedog5695@gmail.com"; - repo = "https://github.com/CuteDog5695/cutedog5695.github.io"; +{ dns, ... }: { + metadata = { + proxy = false; + owner = { + username = "CuteDog5695"; + email = "cutedog5695@gmail.com"; + repo = "https://github.com/CuteDog5695/cutedog5695.github.io"; + }; }; - proxy = false; - records = { + records = with dns.lib.combinators; { TXT = [ "dh=a7c19efb0f6bc38b97a33760f6c1ee84df4151b1" ]; }; } diff --git a/domains/_discord.justdeveloper.nix b/domains/_discord.justdeveloper.nix index 03c4efc..ceae91a 100644 --- a/domains/_discord.justdeveloper.nix +++ b/domains/_discord.justdeveloper.nix @@ -1,11 +1,13 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "JustDeveloper1"; - email = "justdeveloper@juststudio.is-a.dev"; - repo = "https://github.com/JustDeveloper1/Website"; +{ dns, ... }: { + metadata = { + proxy = false; + owner = { + username = "JustDeveloper1"; + email = "justdeveloper@juststudio.is-a.dev"; + repo = "https://github.com/JustDeveloper1/Website"; + }; }; - proxy = false; - records = { + records = with dns.lib.combinators; { TXT = [ "dh=6024027bc233825451e290ac37a4b4a1f838ee70" ]; }; } diff --git a/domains/_discord.nix b/domains/_discord.nix index 82fea5e..efcc1e7 100644 --- a/domains/_discord.nix +++ b/domains/_discord.nix @@ -1,9 +1,11 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "satr14washere"; +{ dns, ... }: { + metadata = { + proxy = false; + owner = { + username = "satr14washere"; + }; }; - proxy = false; - records = { + records = with dns.lib.combinators; { TXT = [ "dh=d509fc9014e196311ed887c2e410cdefa833436e" ]; }; } diff --git a/domains/_discord.roki.nix b/domains/_discord.roki.nix index d87f060..bb55110 100644 --- a/domains/_discord.roki.nix +++ b/domains/_discord.roki.nix @@ -1,9 +1,11 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "Roki100"; - discord = "289479495444987904"; +{ dns, ... }: { + metadata = { + owner = { + username = "Roki100"; + discord = "289479495444987904"; + }; }; - records = { + records = with dns.lib.combinators; { TXT = [ "dh=5633078cd5bfd347a896ddb0f0de017c5423aa06" ]; }; } diff --git a/domains/batman.nix b/domains/batman.nix index 3f3c50e..b3683bb 100644 --- a/domains/batman.nix +++ b/domains/batman.nix @@ -1,9 +1,11 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "shadowe1ite"; +{ dns, ... }: { + metadata = { + proxy = true; + owner = { + username = "shadowe1ite"; + }; }; - proxy = true; - records = { + records = with dns.lib.combinators; { CNAME = [ "shadowe1ite.github.io." ]; }; } diff --git a/domains/c.nix b/domains/c.nix index e6dd392..c56ef7a 100644 --- a/domains/c.nix +++ b/domains/c.nix @@ -1,10 +1,12 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "orangci"; - email = "c@orangc.xyz"; +{ dns, ... }: { + metadata = { + proxy = false; + owner = { + username = "orangci"; + email = "c@orangc.xyz"; + }; }; - proxy = false; - records = { + records = with dns.lib.combinators; { CNAME = [ "edge.redirect.pizza." ]; }; } diff --git a/domains/colin.nix b/domains/colin.nix index e04a8a7..9bcb1c3 100644 --- a/domains/colin.nix +++ b/domains/colin.nix @@ -1,9 +1,12 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "ColinLeDev"; +{ dns, ... }: { + metadata = { + description = "My personal portfolio hosted on my server"; + proxy = false; + owner = { + username = "ColinLeDev"; + }; }; - proxy = false; - records = { + records = with dns.lib.combinators; { CNAME = [ "proxy.col1n.fr." ]; }; } diff --git a/domains/cutedog5695.nix b/domains/cutedog5695.nix index 3691ced..be93952 100644 --- a/domains/cutedog5695.nix +++ b/domains/cutedog5695.nix @@ -1,11 +1,13 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "CuteDog5695"; - email = "cutedog5695@gmail.com"; - repo = "https://github.com/CuteDog5695/cutedog5695.github.io"; +{ dns, ... }: { + metadata = { + proxy = false; + owner = { + username = "CuteDog5695"; + email = "cutedog5695@gmail.com"; + repo = "https://github.com/CuteDog5695/cutedog5695.github.io"; + }; }; - proxy = false; - records = { + records = with dns.lib.combinators; { CNAME = [ "edge.redirect.pizza." ]; }; } diff --git a/domains/elkaff.nix b/domains/elkaff.nix index 34db15e..682bf6e 100644 --- a/domains/elkaff.nix +++ b/domains/elkaff.nix @@ -1,8 +1,10 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "elkhaff"; +{ dns, ... }: { + metadata = { + owner = { + username = "elkhaff"; + }; }; - records = { + records = with dns.lib.combinators; { CNAME = [ "portofolio-pixel.pages.dev." ]; }; } diff --git a/domains/j.nix b/domains/j.nix index db44276..75598f9 100644 --- a/domains/j.nix +++ b/domains/j.nix @@ -1,11 +1,13 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "JustDeveloper1"; - email = "support@juststudio.is-a.dev"; - repo = "https://github.com/JustStudio7/Website"; +{ dns, ... }: { + metadata = { + proxy = false; + owner = { + username = "JustDeveloper1"; + email = "support@juststudio.is-a.dev"; + repo = "https://github.com/JustStudio7/Website"; + }; }; - proxy = false; - records = { + records = with dns.lib.combinators; { CNAME = [ "edge.redirect.pizza." ]; }; } diff --git a/domains/jacob.nix b/domains/jacob.nix index fdba947..cc7f30d 100644 --- a/domains/jacob.nix +++ b/domains/jacob.nix @@ -1,9 +1,11 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "jacobrdale"; +{ dns, ... }: { + metadata = { + proxy = false; + owner = { + username = "jacobrdale"; + }; }; - proxy = false; - records = { + records = with dns.lib.combinators; { CNAME = [ "hexon404.onrender.com." ]; }; } diff --git a/domains/jd.nix b/domains/jd.nix index 6fbcf5c..ebfc556 100644 --- a/domains/jd.nix +++ b/domains/jd.nix @@ -1,11 +1,13 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "JustDeveloper1"; - email = "justdeveloper@juststudio.is-a.dev"; - repo = "https://github.com/JustDeveloper1/Website"; +{ dns, ... }: { + metadata = { + proxy = false; + owner = { + username = "JustDeveloper1"; + email = "justdeveloper@juststudio.is-a.dev"; + repo = "https://github.com/JustDeveloper1/Website"; + }; }; - proxy = false; - records = { + records = with dns.lib.combinators; { CNAME = [ "edge.redirect.pizza." ]; }; } diff --git a/domains/job.nix b/domains/job.nix index 8051716..4e380cf 100644 --- a/domains/job.nix +++ b/domains/job.nix @@ -1,9 +1,11 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "FWEEaaaa1"; +{ dns, ... }: { + metadata = { + proxy = false; + owner = { + username = "FWEEaaaa1"; + }; }; - proxy = false; - records = { + records = with dns.lib.combinators; { A = [ "128.204.223.115" ]; }; } diff --git a/domains/joel.nix b/domains/joel.nix index 22314e4..0513cd2 100644 --- a/domains/joel.nix +++ b/domains/joel.nix @@ -1,16 +1,18 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "joestr"; - email = "strasser999@gmail.com"; +{ dns, ... }: { + metadata = { + proxy = false; + owner = { + username = "joestr"; + email = "strasser999@gmail.com"; + }; }; - proxy = false; - records = { + records = with dns.lib.combinators; { A = [ "142.132.173.34" ]; AAAA = [ "2a01:4f8:1c0c:6cc0::1" ]; MX = [ { - exchange = "achlys.infra.joestr.at."; preference = 10; + exchange = "achlys.infra.joestr.at."; } ]; }; diff --git a/domains/js.nix b/domains/js.nix index db44276..75598f9 100644 --- a/domains/js.nix +++ b/domains/js.nix @@ -1,11 +1,13 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "JustDeveloper1"; - email = "support@juststudio.is-a.dev"; - repo = "https://github.com/JustStudio7/Website"; +{ dns, ... }: { + metadata = { + proxy = false; + owner = { + username = "JustDeveloper1"; + email = "support@juststudio.is-a.dev"; + repo = "https://github.com/JustStudio7/Website"; + }; }; - proxy = false; - records = { + records = with dns.lib.combinators; { CNAME = [ "edge.redirect.pizza." ]; }; } diff --git a/domains/just.nix b/domains/just.nix index 6fbcf5c..ebfc556 100644 --- a/domains/just.nix +++ b/domains/just.nix @@ -1,11 +1,13 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "JustDeveloper1"; - email = "justdeveloper@juststudio.is-a.dev"; - repo = "https://github.com/JustDeveloper1/Website"; +{ dns, ... }: { + metadata = { + proxy = false; + owner = { + username = "JustDeveloper1"; + email = "justdeveloper@juststudio.is-a.dev"; + repo = "https://github.com/JustDeveloper1/Website"; + }; }; - proxy = false; - records = { + records = with dns.lib.combinators; { CNAME = [ "edge.redirect.pizza." ]; }; } diff --git a/domains/justdev.nix b/domains/justdev.nix index 6fbcf5c..ebfc556 100644 --- a/domains/justdev.nix +++ b/domains/justdev.nix @@ -1,11 +1,13 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "JustDeveloper1"; - email = "justdeveloper@juststudio.is-a.dev"; - repo = "https://github.com/JustDeveloper1/Website"; +{ dns, ... }: { + metadata = { + proxy = false; + owner = { + username = "JustDeveloper1"; + email = "justdeveloper@juststudio.is-a.dev"; + repo = "https://github.com/JustDeveloper1/Website"; + }; }; - proxy = false; - records = { + records = with dns.lib.combinators; { CNAME = [ "edge.redirect.pizza." ]; }; } diff --git a/domains/justdeveloper.nix b/domains/justdeveloper.nix index 6fbcf5c..ebfc556 100644 --- a/domains/justdeveloper.nix +++ b/domains/justdeveloper.nix @@ -1,11 +1,13 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "JustDeveloper1"; - email = "justdeveloper@juststudio.is-a.dev"; - repo = "https://github.com/JustDeveloper1/Website"; +{ dns, ... }: { + metadata = { + proxy = false; + owner = { + username = "JustDeveloper1"; + email = "justdeveloper@juststudio.is-a.dev"; + repo = "https://github.com/JustDeveloper1/Website"; + }; }; - proxy = false; - records = { + records = with dns.lib.combinators; { CNAME = [ "edge.redirect.pizza." ]; }; } diff --git a/domains/juststudio.nix b/domains/juststudio.nix index db44276..75598f9 100644 --- a/domains/juststudio.nix +++ b/domains/juststudio.nix @@ -1,11 +1,13 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "JustDeveloper1"; - email = "support@juststudio.is-a.dev"; - repo = "https://github.com/JustStudio7/Website"; +{ dns, ... }: { + metadata = { + proxy = false; + owner = { + username = "JustDeveloper1"; + email = "support@juststudio.is-a.dev"; + repo = "https://github.com/JustStudio7/Website"; + }; }; - proxy = false; - records = { + records = with dns.lib.combinators; { CNAME = [ "edge.redirect.pizza." ]; }; } diff --git a/domains/katz.nix b/domains/katz.nix index cf78f0d..09f3469 100644 --- a/domains/katz.nix +++ b/domains/katz.nix @@ -1,9 +1,11 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "Bananalolok"; +{ dns, ... }: { + metadata = { + proxy = false; + owner = { + username = "Bananalolok"; + }; }; - proxy = false; - records = { + records = with dns.lib.combinators; { A = [ "69.197.135.205" ]; }; } diff --git a/domains/no-one-is.nix b/domains/no-one-is.nix index 4d86e78..ac78aba 100644 --- a/domains/no-one-is.nix +++ b/domains/no-one-is.nix @@ -1,10 +1,12 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "EducatedSuddenBucket"; - email = "me@esb.is-a.dev"; +{ dns, ... }: { + metadata = { + proxy = false; + owner = { + username = "EducatedSuddenBucket"; + email = "me@esb.is-a.dev"; + }; }; - proxy = false; - records = { + records = with dns.lib.combinators; { CNAME = [ "educatedsuddenbucket-github-io.onrender.com." ]; }; } diff --git a/domains/pxl.nix b/domains/pxl.nix index ce8eff6..57446eb 100644 --- a/domains/pxl.nix +++ b/domains/pxl.nix @@ -1,9 +1,11 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "heypxl"; +{ dns, ... }: { + metadata = { + proxy = false; + owner = { + username = "heypxl"; + }; }; - proxy = false; - records = { + records = with dns.lib.combinators; { CNAME = [ "heypxl.github.io." ]; }; } diff --git a/domains/rchessauth.nix b/domains/rchessauth.nix index 9ce886d..20ca546 100644 --- a/domains/rchessauth.nix +++ b/domains/rchessauth.nix @@ -1,9 +1,11 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "vortexprime24"; +{ dns, ... }: { + metadata = { + proxy = false; + owner = { + username = "vortexprime24"; + }; }; - proxy = false; - records = { + records = with dns.lib.combinators; { CNAME = [ "fire.hackclub.app." ]; }; } diff --git a/domains/roki.nix b/domains/roki.nix index cae347c..0cbf5b4 100644 --- a/domains/roki.nix +++ b/domains/roki.nix @@ -1,10 +1,12 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "Roki100"; - discord = "289479495444987904"; +{ dns, ... }: { + metadata = { + proxy = false; + owner = { + username = "Roki100"; + discord = "289479495444987904"; + }; }; - proxy = false; - records = { + records = with dns.lib.combinators; { CNAME = [ "edge.redirect.pizza." ]; }; } diff --git a/domains/satr14.nix b/domains/satr14.nix index f9d4cc7..17817fc 100644 --- a/domains/satr14.nix +++ b/domains/satr14.nix @@ -1,8 +1,10 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "satr14washere"; +{ dns, ... }: { + metadata = { + owner = { + username = "satr14washere"; + }; }; - records = { + records = with dns.lib.combinators; { CNAME = [ "5th-site.pages.dev." ]; }; } diff --git a/domains/stef.nix b/domains/stef.nix index 054b4c1..dfe1827 100644 --- a/domains/stef.nix +++ b/domains/stef.nix @@ -1,10 +1,12 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "Stef-00012"; - email = "admin@stefdp.lol"; +{ dns, ... }: { + metadata = { + proxy = false; + owner = { + username = "Stef-00012"; + email = "admin@stefdp.lol"; + }; }; - proxy = false; - records = { + records = with dns.lib.combinators; { CNAME = [ "proxy.stefdp.lol." ]; }; } diff --git a/domains/ukriu.nix b/domains/ukriu.nix index edea7cd..138199b 100644 --- a/domains/ukriu.nix +++ b/domains/ukriu.nix @@ -1,10 +1,13 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "ukriu"; - email = "partofmyid@ukriu.com"; +{ dns, ... }: { + metadata = { + description = "my website"; + proxy = false; + owner = { + username = "ukriu"; + email = "partofmyid@ukriu.com"; + }; }; - proxy = false; - records = { + records = with dns.lib.combinators; { CNAME = [ "ukriu.pages.dev." ]; }; } diff --git a/domains/you-are.nix b/domains/you-are.nix index 196d500..94278bb 100644 --- a/domains/you-are.nix +++ b/domains/you-are.nix @@ -1,10 +1,12 @@ -{ dns, ... }: with dns.lib.combinators; { - owner = { - username = "Stef-00012"; - email = "admin@stefdp.com"; +{ dns, ... }: { + metadata = { + proxy = false; + owner = { + username = "Stef-00012"; + email = "admin@stefdp.com"; + }; }; - proxy = false; - records = { + records = with dns.lib.combinators; { CNAME = [ "proxy.stefdp.com." ]; }; } diff --git a/scripts/migrate-nix.py b/scripts/migrate-nix.py index 683b968..986595a 100755 --- a/scripts/migrate-nix.py +++ b/scripts/migrate-nix.py @@ -1,16 +1,29 @@ #!/usr/bin/env python3 """ -Migration script to convert domains/*.json to domains/*.nix +Migrate domains/*.json to domains/*.nix -Reads each JSON domain config and generates a corresponding .nix file -following the format from docs/example.nix. +Converts each JSON domain config into a .nix file matching the format +from docs/example.nix: + + { dns, ... }: { + metadata = { + description = "..."; + proxy = false; + owner = { + username = "..."; + }; + }; + records = with dns.lib.combinators; { + CNAME = [ "example.com." ]; + }; + } Usage: python3 scripts/migrate-nix.py [--dry-run] [--delete-json] Options: - --dry-run Print generated .nix content to stdout without writing files - --delete-json Delete the original .json files after successful conversion + --dry-run Print generated .nix to stdout without writing files + --delete-json Delete the .json files after successful conversion """ import json @@ -20,224 +33,198 @@ from pathlib import Path DOMAINS_DIR = Path(__file__).resolve().parent.parent / "domains" -def json_to_nix(data: dict) -> str: - """Convert a single domain JSON config to a .nix file string.""" +# --- Nix string helpers --- + +def escape(s: str) -> str: + """Escape a string for use inside Nix double quotes.""" + return s.replace("\\", "\\\\").replace('"', '\\"').replace("${", "\\${") + + +def fqdn(s: str) -> str: + """Ensure a domain string ends with a trailing dot.""" + return s if s.endswith(".") else s + "." + + +# --- Block builders --- + +def build_metadata(data: dict) -> list[str]: + """Build the metadata = { ... }; block.""" owner = data.get("owner", {}) description = data.get("description") - record = data.get("record", {}) - # Some files use "proxy", others use "proxied" proxy = data.get("proxied", data.get("proxy")) - lines = [] + lines = [" metadata = {"] - # Header — no let block, just the function head with `with` - lines.append("{ dns, ... }: with dns.lib.combinators; {") - - # Owner block as a top-level attribute - lines.append(" owner = {") - if owner.get("username"): - lines.append(f' username = "{escape_nix_string(owner["username"])}";') - if owner.get("email"): - lines.append(f' email = "{escape_nix_string(owner["email"])}";') - if owner.get("discord"): - lines.append(f' discord = "{escape_nix_string(owner["discord"])}";') - if owner.get("repo"): - lines.append(f' repo = "{escape_nix_string(owner["repo"])}";') - lines.append(" };") - - # Description as a top-level attribute if description is not None: - lines.append(f' description = "{escape_nix_string(description)}";') + lines.append(f' description = "{escape(description)}";') - # Proxy as a top-level attribute if proxy is not None: - lines.append(f" proxy = {'true' if proxy else 'false'};") + lines.append(f" proxy = {'true' if proxy else 'false'};") - # Records nested under `records` - record_lines = build_record_lines(record) - if record_lines: - lines.append(" records = {") - for rl in record_lines: - lines.append(rl) - lines.append(" };") + owner_keys = ["username", "email", "discord", "repo"] + owner_fields = [(k, owner[k]) for k in owner_keys if owner.get(k)] - lines.append("}") - lines.append("") - - return "\n".join(lines) - - -def escape_nix_string(s: str) -> str: - """Escape special characters for a Nix double-quoted string.""" - s = s.replace("\\", "\\\\") - s = s.replace('"', '\\"') - s = s.replace("${", "\\${") - return s - - -def build_record_lines(record: dict) -> list[str]: - """Build the Nix record lines from the JSON record dict. - - These are indented with 4 spaces since they sit inside `records = { ... };`. - """ - lines = [] - - if "A" in record: - values = record["A"] - if isinstance(values, list): - if len(values) == 1: - lines.append(f' A = [ "{values[0]}" ];') - else: - lines.append(" A = [") - for v in values: - lines.append(f' "{v}"') - lines.append(" ];") - else: - lines.append(f' A = [ "{values}" ];') - - if "AAAA" in record: - values = record["AAAA"] - if isinstance(values, list): - if len(values) == 1: - lines.append(f' AAAA = [ "{values[0]}" ];') - else: - lines.append(" AAAA = [") - for v in values: - lines.append(f' "{v}"') - lines.append(" ];") - else: - lines.append(f' AAAA = [ "{values}" ];') - - if "CNAME" in record: - value = record["CNAME"] - if isinstance(value, list): - value = value[0] - lines.append(f' CNAME = [ "{ensure_fqdn(value)}" ];') - - if "ALIAS" in record: - value = record["ALIAS"] - if isinstance(value, list): - value = value[0] - # ALIAS is typically handled as CNAME in dns.nix - lines.append(f' CNAME = [ "{ensure_fqdn(value)}" ];') - - if "MX" in record: - values = record["MX"] - if isinstance(values, list): - lines.append(" MX = [") - for i, v in enumerate(values): - priority = (i + 1) * 10 - lines.append(" {") - lines.append(f' exchange = "{ensure_fqdn(v)}";') - lines.append(f" preference = {priority};") - lines.append(" }") - lines.append(" ];") - else: - lines.append(" MX = [") - lines.append(" {") - lines.append(f' exchange = "{ensure_fqdn(values)}";') - lines.append(" preference = 10;") - lines.append(" }") - lines.append(" ];") - - if "TXT" in record: - values = record["TXT"] - if isinstance(values, list): - if len(values) == 1: - lines.append(f' TXT = [ "{escape_nix_string(values[0])}" ];') - else: - lines.append(" TXT = [") - for v in values: - lines.append(f' "{escape_nix_string(v)}"') - lines.append(" ];") - else: - lines.append(f' TXT = [ "{escape_nix_string(values)}" ];') - - if "NS" in record: - values = record["NS"] - if isinstance(values, list): - if len(values) == 1: - lines.append(f' NS = [ "{ensure_fqdn(values[0])}" ];') - else: - lines.append(" NS = [") - for v in values: - lines.append(f' "{ensure_fqdn(v)}"') - lines.append(" ];") - else: - lines.append(f' NS = [ "{ensure_fqdn(values)}" ];') - - if "SRV" in record: - values = record["SRV"] - if isinstance(values, list): - lines.append(" SRV = [") - for srv in values: - lines.append(" {") - if "service" in srv: - lines.append(f' service = "{srv["service"]}";') - if "proto" in srv: - lines.append(f' proto = "{srv["proto"]}";') - if "port" in srv: - lines.append(f" port = {srv['port']};") - if "priority" in srv: - lines.append(f" priority = {srv['priority']};") - if "weight" in srv: - lines.append(f" weight = {srv['weight']};") - if "target" in srv: - lines.append(f' target = "{ensure_fqdn(srv["target"])}";') - lines.append(" }") - lines.append(" ];") - - if "CAA" in record: - values = record["CAA"] - if isinstance(values, list): - lines.append(" CAA = [") - for caa in values: - lines.append(" {") - if "flags" in caa: - lines.append(f" flags = {caa['flags']};") - if "tag" in caa: - lines.append(f' tag = "{caa["tag"]}";') - if "value" in caa: - lines.append(f' value = "{escape_nix_string(caa["value"])}";') - lines.append(" }") - lines.append(" ];") + if owner_fields: + lines.append(" owner = {") + for key, val in owner_fields: + lines.append(f' {key} = "{escape(val)}";') + lines.append(" };") + lines.append(" };") return lines -def ensure_fqdn(domain: str) -> str: - """Ensure a domain name ends with a dot (FQDN).""" - if not domain.endswith("."): - return domain + "." - return domain +def build_records(record: dict) -> list[str]: + """Build the records = with dns.lib.combinators; { ... }; block.""" + entries = [] + + # A records + if "A" in record: + entries.extend(string_list_record("A", as_list(record["A"]))) + + # AAAA records + if "AAAA" in record: + entries.extend(string_list_record("AAAA", as_list(record["AAAA"]))) + + # CNAME (also handles ALIAS → CNAME) + cname = record.get("CNAME") or record.get("ALIAS") + if cname is not None: + val = cname[0] if isinstance(cname, list) else cname + entries.append(f' CNAME = [ "{fqdn(val)}" ];') + + # MX records + if "MX" in record: + entries.extend(build_mx(as_list(record["MX"]))) + + # TXT records + if "TXT" in record: + escaped = [escape(v) for v in as_list(record["TXT"])] + entries.extend(string_list_record("TXT", escaped)) + + # NS records + if "NS" in record: + fqdns = [fqdn(v) for v in as_list(record["NS"])] + entries.extend(string_list_record("NS", fqdns)) + + # SRV records + if "SRV" in record: + entries.extend(build_srv(as_list(record["SRV"]))) + + # CAA records + if "CAA" in record: + entries.extend(build_caa(as_list(record["CAA"]))) + + if not entries: + return [" records = with dns.lib.combinators; {};"] + + lines = [" records = with dns.lib.combinators; {"] + lines.extend(entries) + lines.append(" };") + return lines -def migrate_file(json_path: Path, dry_run: bool = False, delete_json: bool = False) -> bool: - """Migrate a single JSON file to .nix. Returns True on success.""" +# --- Record type formatters --- + +def as_list(value) -> list: + """Wrap a scalar in a list if it isn't one already.""" + return value if isinstance(value, list) else [value] + + +def string_list_record(rtype: str, values: list[str]) -> list[str]: + """Format a record type whose values are plain strings.""" + if len(values) == 1: + return [f' {rtype} = [ "{values[0]}" ];'] + + lines = [f" {rtype} = ["] + for v in values: + lines.append(f' "{v}"') + lines.append(" ];") + return lines + + +def build_mx(values: list) -> list[str]: + """Format MX records as attrsets with preference + exchange.""" + lines = [" MX = ["] + for i, v in enumerate(values): + pref = (i + 1) * 10 + lines.append(" {") + lines.append(f" preference = {pref};") + lines.append(f' exchange = "{fqdn(v)}";') + lines.append(" }") + lines.append(" ];") + return lines + + +def build_srv(values: list[dict]) -> list[str]: + """Format SRV records.""" + lines = [" SRV = ["] + for srv in values: + lines.append(" {") + for key in ("service", "proto"): + if key in srv: + lines.append(f' {key} = "{srv[key]}";') + for key in ("priority", "weight", "port"): + if key in srv: + lines.append(f" {key} = {srv[key]};") + if "target" in srv: + lines.append(f' target = "{fqdn(srv["target"])}";') + lines.append(" }") + lines.append(" ];") + return lines + + +def build_caa(values: list[dict]) -> list[str]: + """Format CAA records.""" + lines = [" CAA = ["] + for caa in values: + lines.append(" {") + if "flags" in caa: + lines.append(f" flags = {caa['flags']};") + if "tag" in caa: + lines.append(f' tag = "{caa["tag"]}";') + if "value" in caa: + lines.append(f' value = "{escape(caa["value"])}";') + lines.append(" }") + lines.append(" ];") + return lines + + +# --- Top-level conversion --- + +def json_to_nix(data: dict) -> str: + """Convert a parsed JSON domain config to a complete .nix file string.""" + lines = ["{ dns, ... }: {"] + lines.extend(build_metadata(data)) + lines.extend(build_records(data.get("record", {}))) + lines.append("}") + lines.append("") + return "\n".join(lines) + + +# --- File operations --- + +def migrate_file(path: Path, *, dry_run: bool, delete_json: bool) -> bool: + """Migrate a single .json file. Returns True on success.""" try: - with open(json_path, "r") as f: - data = json.load(f) + data = json.loads(path.read_text()) except json.JSONDecodeError as e: - print(f" ERROR: Failed to parse {json_path.name}: {e}", file=sys.stderr) + print(f" ERROR: {path.name}: {e}", file=sys.stderr) return False - nix_content = json_to_nix(data) - nix_filename = json_path.stem + ".nix" - nix_path = json_path.parent / nix_filename + nix = json_to_nix(data) + nix_path = path.with_suffix(".nix") if dry_run: - print(f"--- {nix_filename} ---") - print(nix_content) + print(f"--- {nix_path.name} ---") + print(nix) return True - with open(nix_path, "w") as f: - f.write(nix_content) - + nix_path.write_text(nix) print(f" Created {nix_path.name}") if delete_json: - json_path.unlink() - print(f" Deleted {json_path.name}") + path.unlink() + print(f" Deleted {path.name}") return True @@ -247,31 +234,30 @@ def main(): delete_json = "--delete-json" in sys.argv if not DOMAINS_DIR.exists(): - print(f"Error: domains directory not found at {DOMAINS_DIR}", file=sys.stderr) + print(f"Error: {DOMAINS_DIR} not found", file=sys.stderr) sys.exit(1) - json_files = sorted(DOMAINS_DIR.glob("*.json")) - - if not json_files: - print("No JSON files found in domains/") + files = sorted(DOMAINS_DIR.glob("*.json")) + if not files: + print("No .json files found in domains/") sys.exit(0) - print(f"Found {len(json_files)} JSON file(s) to migrate") + print(f"Found {len(files)} JSON file(s) to migrate") if dry_run: print("(dry run — no files will be written)\n") success = 0 failed = 0 - for json_path in json_files: - print(f"Migrating {json_path.name}...") - if migrate_file(json_path, dry_run=dry_run, delete_json=delete_json): + for f in files: + print(f"Migrating {f.name}...") + if migrate_file(f, dry_run=dry_run, delete_json=delete_json): success += 1 else: failed += 1 print(f"\nDone: {success} succeeded, {failed} failed") - if failed > 0: + if failed: sys.exit(1)