Compare commits

..

18 commits

Author SHA1 Message Date
Stefano Del Prete
0b21dab8b2
Merge pull request #57 from Stef-00012/main
forgot to updste
2026-05-11 05:35:43 +02:00
Stefano Del Prete
d1c61e475c
forgot to update that too 2026-05-11 05:33:28 +02:00
Stefano Del Prete
c601b07fa4
forgot to update 2026-05-11 05:32:56 +02:00
github-actions[bot]
f0af7456cb [automated] update stats 2026-05-04 04:35:45 +00:00
7a835d5ed1
Merge pull request #55 from LunarisXOffical/main
Create reallunarisx.json
2026-05-04 04:35:31 +00:00
f1638606f6
Change TXT record format to array 2026-05-04 11:31:27 +07:00
011fd67186
Fix formatting in _vercel.reallunarisx.json 2026-05-04 11:25:41 +07:00
96f2285d48
Fix formatting in reallunarisx.json 2026-05-04 11:25:10 +07:00
47a0e18cdb
remove dot (added by ci) 2026-05-04 11:22:33 +07:00
LunarisX
db9a6e23c0
Update reallunarisx.json 2026-05-04 09:31:02 +07:00
LunarisX
3947beaf68
Add Vercel configuration for reallunarisx domain 2026-05-04 09:26:42 +07:00
LunarisX
ea1ffd63e5
Update TXT record in reallunarisx.json 2026-05-04 09:25:58 +07:00
LunarisX
82a1cdd892
Update DNS records in reallunarisx.json
Added TXT records for GitHub Pages challenge.
2026-05-04 09:23:25 +07:00
satr14
23d995e04c
link previews are now required 2026-05-03 16:15:12 +00:00
LunarisX
be2d6b450c
Create reallunarisx.json 2026-05-03 22:27:07 +07:00
satr14
fb98c8021a
time notice 2026-03-23 15:51:55 +07:00
satr14
21c96dcc55
rewrite is taking too long 2026-03-23 15:40:13 +07:00
github-actions[bot]
97dbf4b4fa [automated] update stats 2026-03-21 11:54:50 +00:00
45 changed files with 104 additions and 1037 deletions

View file

@ -8,7 +8,7 @@
- Github Pull Requests
- How DNS Works
4. When in doubt, read the docs before asking in PR
5. **PREVIEWS ARE REQUIRED FOR WEBSITES.** Can be a screenshot/link. If it's not a website then please state the use of the subdomain.
5. **PREVIEWS ARE REQUIRED FOR WEBSITES.** Must be a link. If it's not a website then please state the use of the subdomain.
-->
## Requirements
@ -38,5 +38,5 @@ _None provided..._
<!--
^^^^^^ Remove the line above to provide a link/screenshot
⚠️⚠️ ****REQUIRED IF ITS A WEBSITE**** ⚠️⚠️
Please provide a link/screenshot to your website below.
Please provide a link (required) and/or screenshot to your website below.
-->

5
.gitignore vendored
View file

@ -1,7 +1,2 @@
# DNSControl files
creds.json
types-dnscontrol.d.ts
# Zone files
result*
part-of.my.id.txt

View file

@ -1,5 +1,5 @@
> [!IMPORTANT]
> We are currently rewriting our registration process, CI/CD pipeline, documentation, and website. Pull requests are temporarily paused until the new system is ready. We will document the new registration process in this repository once it's ready. In the meantime, you can join our [discord server](https://discord.gg/rFyRF3MMhc) to get updates and support.
> We are currently rewriting our registration process, CI/CD pipeline, documentation, and website. Due to time constraints, **pull requests are still welcome** and will be migrated to the new syntax after the rewrite. We will document the new registration process in this repository once it's ready. In the meantime, you can join our [discord server](https://discord.gg/rFyRF3MMhc) to get updates and support.
> [!CAUTION]
> We currently **DO NOT** support Vercel, Netlify, and other services that requires us to be on the [PSL](https://github.com/publicsuffix/list). _We will apply to be on the list [only if theres high demand](https://publicsuffix.org/submit/#:~:text=We%20will%20generally%20decline%20small%20projects)_, so be patient and invite some of your friends!

View file

@ -1,76 +0,0 @@
{ 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";
};
};
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 = [
"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 = [ # 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"))
];
TXT = [
"v=spf1 include:mailgun.org ~all"
"dh=some-long-random-string"
];
MX = [
{
preference = 10;
exchange = "mail.protonmail.ch.";
}
{
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."
];
};
}

9
domains/@.json Normal file
View file

@ -0,0 +1,9 @@
{
"description": "dashboard and main website",
"owner": {
"username": "partofmyid"
},
"record": {
"ALIAS": "website-e7n.pages.dev"
}
}

View file

@ -0,0 +1,9 @@
{
"owner": {
"username": "LunarisXOffical"
},
"record": {
"TXT": [ "vc-domain-verify=reallunarisx.part-of.my.id,eb89acab3adcd3ee3acd" ]
},
"proxy": false
}

View file

@ -1,12 +0,0 @@
{ dns, ... }: {
metadata = {
description = "Discord verification";
proxy = false;
owner = {
username = "ColinLeDev";
};
};
records = with dns.lib.combinators; {
TXT = [ "dh=279643a6f8677dedb1c5c63d007fc4516149679c" ];
};
}

View file

@ -1,13 +0,0 @@
{ dns, ... }: {
metadata = {
proxy = false;
owner = {
username = "CuteDog5695";
email = "cutedog5695@gmail.com";
repo = "https://github.com/CuteDog5695/cutedog5695.github.io";
};
};
records = with dns.lib.combinators; {
TXT = [ "dh=a7c19efb0f6bc38b97a33760f6c1ee84df4151b1" ];
};
}

View file

@ -1,13 +0,0 @@
{ dns, ... }: {
metadata = {
proxy = false;
owner = {
username = "JustDeveloper1";
email = "justdeveloper@juststudio.is-a.dev";
repo = "https://github.com/JustDeveloper1/Website";
};
};
records = with dns.lib.combinators; {
TXT = [ "dh=6024027bc233825451e290ac37a4b4a1f838ee70" ];
};
}

View file

@ -1,11 +0,0 @@
{ dns, ... }: {
metadata = {
proxy = false;
owner = {
username = "satr14washere";
};
};
records = with dns.lib.combinators; {
TXT = [ "dh=d509fc9014e196311ed887c2e410cdefa833436e" ];
};
}

View file

@ -1,11 +0,0 @@
{ dns, ... }: {
metadata = {
owner = {
username = "Roki100";
discord = "289479495444987904";
};
};
records = with dns.lib.combinators; {
TXT = [ "dh=5633078cd5bfd347a896ddb0f0de017c5423aa06" ];
};
}

View file

@ -1,11 +0,0 @@
{ dns, ... }: {
metadata = {
proxy = true;
owner = {
username = "shadowe1ite";
};
};
records = with dns.lib.combinators; {
CNAME = [ "shadowe1ite.github.io." ];
};
}

View file

@ -1,12 +0,0 @@
{ dns, ... }: {
metadata = {
proxy = false;
owner = {
username = "orangci";
email = "c@orangc.xyz";
};
};
records = with dns.lib.combinators; {
CNAME = [ "edge.redirect.pizza." ];
};
}

View file

@ -1,12 +0,0 @@
{ dns, ... }: {
metadata = {
description = "My personal portfolio hosted on my server";
proxy = false;
owner = {
username = "ColinLeDev";
};
};
records = with dns.lib.combinators; {
CNAME = [ "proxy.col1n.fr." ];
};
}

View file

@ -1,13 +0,0 @@
{ dns, ... }: {
metadata = {
proxy = false;
owner = {
username = "CuteDog5695";
email = "cutedog5695@gmail.com";
repo = "https://github.com/CuteDog5695/cutedog5695.github.io";
};
};
records = with dns.lib.combinators; {
CNAME = [ "edge.redirect.pizza." ];
};
}

View file

@ -1,10 +0,0 @@
{ dns, ... }: {
metadata = {
owner = {
username = "elkhaff";
};
};
records = with dns.lib.combinators; {
CNAME = [ "portofolio-pixel.pages.dev." ];
};
}

View file

@ -1,13 +0,0 @@
{ dns, ... }: {
metadata = {
proxy = false;
owner = {
username = "JustDeveloper1";
email = "support@juststudio.is-a.dev";
repo = "https://github.com/JustStudio7/Website";
};
};
records = with dns.lib.combinators; {
CNAME = [ "edge.redirect.pizza." ];
};
}

View file

@ -1,11 +0,0 @@
{ dns, ... }: {
metadata = {
proxy = false;
owner = {
username = "jacobrdale";
};
};
records = with dns.lib.combinators; {
CNAME = [ "hexon404.onrender.com." ];
};
}

View file

@ -1,13 +0,0 @@
{ dns, ... }: {
metadata = {
proxy = false;
owner = {
username = "JustDeveloper1";
email = "justdeveloper@juststudio.is-a.dev";
repo = "https://github.com/JustDeveloper1/Website";
};
};
records = with dns.lib.combinators; {
CNAME = [ "edge.redirect.pizza." ];
};
}

View file

@ -1,11 +0,0 @@
{ dns, ... }: {
metadata = {
proxy = false;
owner = {
username = "FWEEaaaa1";
};
};
records = with dns.lib.combinators; {
A = [ "128.204.223.115" ];
};
}

View file

@ -1,19 +0,0 @@
{ dns, ... }: {
metadata = {
proxy = false;
owner = {
username = "joestr";
email = "strasser999@gmail.com";
};
};
records = with dns.lib.combinators; {
A = [ "142.132.173.34" ];
AAAA = [ "2a01:4f8:1c0c:6cc0::1" ];
MX = [
{
preference = 10;
exchange = "achlys.infra.joestr.at.";
}
];
};
}

View file

@ -1,13 +0,0 @@
{ dns, ... }: {
metadata = {
proxy = false;
owner = {
username = "JustDeveloper1";
email = "support@juststudio.is-a.dev";
repo = "https://github.com/JustStudio7/Website";
};
};
records = with dns.lib.combinators; {
CNAME = [ "edge.redirect.pizza." ];
};
}

View file

@ -1,13 +0,0 @@
{ dns, ... }: {
metadata = {
proxy = false;
owner = {
username = "JustDeveloper1";
email = "justdeveloper@juststudio.is-a.dev";
repo = "https://github.com/JustDeveloper1/Website";
};
};
records = with dns.lib.combinators; {
CNAME = [ "edge.redirect.pizza." ];
};
}

View file

@ -1,13 +0,0 @@
{ dns, ... }: {
metadata = {
proxy = false;
owner = {
username = "JustDeveloper1";
email = "justdeveloper@juststudio.is-a.dev";
repo = "https://github.com/JustDeveloper1/Website";
};
};
records = with dns.lib.combinators; {
CNAME = [ "edge.redirect.pizza." ];
};
}

View file

@ -1,13 +0,0 @@
{ dns, ... }: {
metadata = {
proxy = false;
owner = {
username = "JustDeveloper1";
email = "justdeveloper@juststudio.is-a.dev";
repo = "https://github.com/JustDeveloper1/Website";
};
};
records = with dns.lib.combinators; {
CNAME = [ "edge.redirect.pizza." ];
};
}

View file

@ -1,13 +0,0 @@
{ dns, ... }: {
metadata = {
proxy = false;
owner = {
username = "JustDeveloper1";
email = "support@juststudio.is-a.dev";
repo = "https://github.com/JustStudio7/Website";
};
};
records = with dns.lib.combinators; {
CNAME = [ "edge.redirect.pizza." ];
};
}

View file

@ -1,11 +0,0 @@
{ dns, ... }: {
metadata = {
proxy = false;
owner = {
username = "Bananalolok";
};
};
records = with dns.lib.combinators; {
A = [ "69.197.135.205" ];
};
}

View file

@ -1,12 +0,0 @@
{ dns, ... }: {
metadata = {
proxy = false;
owner = {
username = "EducatedSuddenBucket";
email = "me@esb.is-a.dev";
};
};
records = with dns.lib.combinators; {
CNAME = [ "educatedsuddenbucket-github-io.onrender.com." ];
};
}

View file

@ -1,11 +0,0 @@
{ dns, ... }: {
metadata = {
proxy = false;
owner = {
username = "heypxl";
};
};
records = with dns.lib.combinators; {
CNAME = [ "heypxl.github.io." ];
};
}

View file

@ -1,11 +0,0 @@
{ dns, ... }: {
metadata = {
proxy = false;
owner = {
username = "vortexprime24";
};
};
records = with dns.lib.combinators; {
CNAME = [ "fire.hackclub.app." ];
};
}

View file

@ -1,12 +0,0 @@
{ dns, ... }: {
metadata = {
proxy = false;
owner = {
username = "Roki100";
discord = "289479495444987904";
};
};
records = with dns.lib.combinators; {
CNAME = [ "edge.redirect.pizza." ];
};
}

View file

@ -1,10 +0,0 @@
{ dns, ... }: {
metadata = {
owner = {
username = "satr14washere";
};
};
records = with dns.lib.combinators; {
CNAME = [ "5th-site.pages.dev." ];
};
}

View file

@ -1,12 +0,0 @@
{ dns, ... }: {
metadata = {
proxy = false;
owner = {
username = "Stef-00012";
email = "admin@stefdp.lol";
};
};
records = with dns.lib.combinators; {
CNAME = [ "proxy.stefdp.lol." ];
};
}

View file

@ -1,13 +0,0 @@
{ dns, ... }: {
metadata = {
description = "my website";
proxy = false;
owner = {
username = "ukriu";
email = "partofmyid@ukriu.com";
};
};
records = with dns.lib.combinators; {
CNAME = [ "ukriu.pages.dev." ];
};
}

View file

@ -1,12 +0,0 @@
{ dns, ... }: {
metadata = {
proxy = false;
owner = {
username = "Stef-00012";
email = "admin@stefdp.com";
};
};
records = with dns.lib.combinators; {
CNAME = [ "proxy.stefdp.com." ];
};
}

View file

@ -0,0 +1,9 @@
{
"owner": {
"username": "LunarisXOffical"
},
"record": {
"CNAME": "3bdbf404a94a1470.vercel-dns-017.com"
},
"proxy": false
}

View file

@ -1,10 +1,10 @@
{
"owner": {
"email": "admin@stefdp.lol",
"email": "me@stefdp.com",
"username": "Stef-00012"
},
"record": {
"CNAME": "proxy.stefdp.lol"
"CNAME": "proxy.stefdp.com"
},
"proxied": false
}

View file

@ -1,6 +1,6 @@
{
"owner": {
"email": "admin@stefdp.com",
"email": "me@stefdp.com",
"username": "Stef-00012"
},
"record": {

View file

@ -1,57 +1,30 @@
{
description = "Zone File Generator";
description = "Zone File Generator For part-of.my.id";
inputs.dns.url = "github:nix-community/dns.nix";
outputs =
{ dns, ... }:
let
outputs = { dns, ... }: let
email = "admin@satr14.my.id";
domains = [
"0" = {
domains."0" = {
domain = "part-of.my.id";
nameservers = [
"adele.ns.cloudflare.com"
"fattouche.ns.cloudflare.com"
];
};
"1" = {
domain = "is-my.id";
nameservers = [
"adele.ns.cloudflare.com"
"fattouche.ns.cloudflare.com"
];
};
];
inherit (import <nixpkgs> { }) lib;
domainsFolder = builtins.readDir ./domains;
domainFiles = lib.filterAttrs (
name: type: type == "regular" && builtins.match ".*\\.nix" name != null
) domainsFolder;
subdomains = lib.mapAttrs' (
name: _:
let
key = builtins.replaceStrings [ ".nix" ] [ "" ] name;
in
{
name = key;
value = (import (./domains + "/${name}") { inherit dns; }).records;
}
) domainFiles;
in
{
packages.x86_64-linux = builtins.mapAttrs (
_: domain:
in {
packages.x86_64-linux = builtins.mapAttrs (_: domain:
dns.util.x86_64-linux.writeZone domain.domain (
with dns.lib.combinators;
{
with dns.lib.combinators; {
SOA = {
adminEmail = email;
nameServer = builtins.head domain.nameservers;
serial = builtins.currentTime;
};
NS = domain.nameservers;
# note: Cloudflare ignores SOA and NS records uploaded via Zone File, they are included just so that dns.nix builds a valid zone file.
CNAME = [ "website-e7n.pages.dev." ];
inherit subdomains;
# note: Cloudflare ignores SOA and NS records uploaded via Zone File, they are just so that dns.nix builds a valid zone file.
A = [ "1.1.1.1" ];
}
)
) domains;

View file

@ -1,72 +0,0 @@
;;
;; Domain: is-my.id.
;; Exported: 2026-03-21 23:44:57
;;
;; This file is intended for use for informational and archival
;; purposes ONLY and MUST be edited before use on a production
;; DNS server. In particular, you must:
;; -- update the SOA record with the correct authoritative name server
;; -- update the SOA record with the contact e-mail address information
;; -- update the NS record(s) with the authoritative name servers for this domain.
;;
;; For further information, please consult the BIND documentation
;; located on the following website:
;;
;; http://www.isc.org/
;;
;; And RFC 1035:
;;
;; http://www.ietf.org/rfc/rfc1035.txt
;;
;; Please note that we do NOT offer technical support for any use
;; of this zone data, the BIND name server, or any other third-party
;; DNS software.
;;
;; Use at your own risk.
;; SOA Record
is-my.id 3600 IN SOA adele.ns.cloudflare.com. dns.cloudflare.com. 2052580329 10000 2400 604800 3600
;; NS Records
is-my.id. 86400 IN NS adele.ns.cloudflare.com.
is-my.id. 86400 IN NS fattouche.ns.cloudflare.com.
;; A Records
job.is-my.id. 1 IN A 128.204.223.115 ; cf_tags=cf-proxied:false
joel.is-my.id. 1 IN A 142.132.173.34 ; cf_tags=cf-proxied:false
katz.is-my.id. 1 IN A 69.197.135.205 ; cf_tags=cf-proxied:false
;; AAAA Records
joel.is-my.id. 1 IN AAAA 2a01:4f8:1c0c:6cc0::1 ; cf_tags=cf-proxied:false
;; CNAME Records
batman.is-my.id. 1 IN CNAME shadowe1ite.github.io. ; cf_tags=cf-proxied:true
colin.is-my.id. 1 IN CNAME proxy.col1n.fr. ; cf_tags=cf-proxied:false
c.is-my.id. 1 IN CNAME edge.redirect.pizza. ; cf_tags=cf-proxied:false
cutedog5695.is-my.id. 1 IN CNAME edge.redirect.pizza. ; cf_tags=cf-proxied:false
elkaff.is-my.id. 1 IN CNAME portofolio-pixel.pages.dev. ; cf_tags=cf-proxied:false
jacob.is-my.id. 1 IN CNAME hexon404.onrender.com. ; cf_tags=cf-proxied:false
jd.is-my.id. 1 IN CNAME edge.redirect.pizza. ; cf_tags=cf-proxied:false
j.is-my.id. 1 IN CNAME edge.redirect.pizza. ; cf_tags=cf-proxied:false
js.is-my.id. 1 IN CNAME edge.redirect.pizza. ; cf_tags=cf-proxied:false
justdeveloper.is-my.id. 1 IN CNAME edge.redirect.pizza. ; cf_tags=cf-proxied:false
justdev.is-my.id. 1 IN CNAME edge.redirect.pizza. ; cf_tags=cf-proxied:false
just.is-my.id. 1 IN CNAME edge.redirect.pizza. ; cf_tags=cf-proxied:false
juststudio.is-my.id. 1 IN CNAME edge.redirect.pizza. ; cf_tags=cf-proxied:false
no-one-is.is-my.id. 1 IN CNAME educatedsuddenbucket-github-io.onrender.com. ; cf_tags=cf-proxied:false
is-my.id. 1 IN CNAME website-e7n.pages.dev. ; cf_tags=cf-proxied:false
pxl.is-my.id. 1 IN CNAME heypxl.github.io. ; cf_tags=cf-proxied:false
rchessauth.is-my.id. 1 IN CNAME fire.hackclub.app. ; cf_tags=cf-proxied:false
roki.is-my.id. 1 IN CNAME edge.redirect.pizza. ; cf_tags=cf-proxied:false
stef.is-my.id. 1 IN CNAME proxy.stefdp.lol. ; cf_tags=cf-proxied:false
ukriu.is-my.id. 1 IN CNAME ukriu.pages.dev. ; cf_tags=cf-proxied:false
you-are.is-my.id. 1 IN CNAME proxy.stefdp.com. ; cf_tags=cf-proxied:false
;; MX Records
joel.is-my.id. 1 IN MX 10 achlys.infra.joestr.at.
;; TXT Records
_discord.colin.is-my.id. 1 IN TXT "dh=279643a6f8677dedb1c5c63d007fc4516149679c"
_discord.cutedog5695.is-my.id. 1 IN TXT "dh=a7c19efb0f6bc38b97a33760f6c1ee84df4151b1"
_discord.justdeveloper.is-my.id. 1 IN TXT "dh=6024027bc233825451e290ac37a4b4a1f838ee70"
_discord.is-my.id. 1 IN TXT "dh=d509fc9014e196311ed887c2e410cdefa833436e"
_discord.roki.is-my.id. 1 IN TXT "dh=5633078cd5bfd347a896ddb0f0de017c5423aa06"

View file

@ -1,200 +0,0 @@
#!/usr/bin/env bash
#
# compare-zones.sh — Compare two BIND-format zone files
#
# Normalizes both files (strips comments, TTLs, SOA records, and whitespace
# differences) then performs a record-by-record comparison.
#
# Usage:
# ./scripts/compare-zones.sh <zone-file-a> <zone-file-b>
#
# Examples:
# ./scripts/compare-zones.sh expected.zone generated.zone
# ./scripts/compare-zones.sh part-of.my.id.txt result
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
RESET='\033[0m'
usage() {
echo "Usage: $0 <zone-file-a> <zone-file-b>"
echo ""
echo "Compare two BIND-format zone files."
echo ""
echo "Arguments:"
echo " zone-file-a Path to the first zone file"
echo " zone-file-b Path to the second zone file"
exit 1
}
if [[ $# -lt 2 ]]; then
usage
fi
FILE_A="$1"
FILE_B="$2"
for f in "$FILE_A" "$FILE_B"; do
resolved="$f"
# Resolve symlinks (e.g. nix store results)
if [[ -L "$resolved" ]]; then
resolved="$(readlink -f "$resolved")"
fi
if [[ ! -f "$resolved" ]]; then
echo -e "${RED}Error:${RESET} File not found: $f"
exit 1
fi
done
# Resolve symlinks for display
RESOLVED_A="$FILE_A"
RESOLVED_B="$FILE_B"
[[ -L "$FILE_A" ]] && RESOLVED_A="$(readlink -f "$FILE_A")"
[[ -L "$FILE_B" ]] && RESOLVED_B="$(readlink -f "$FILE_B")"
TMPDIR="$(mktemp -d)"
trap 'rm -rf "$TMPDIR"' EXIT
# normalize_zone <input-file> <output-file>
#
# Extracts resource records, strips comments, normalizes whitespace and TTLs,
# ensures FQDNs have trailing dots, skips SOA (which always differs), and sorts.
normalize_zone() {
local input="$1"
local output="$2"
# Resolve symlinks
if [[ -L "$input" ]]; then
input="$(readlink -f "$input")"
fi
grep -E '^\S+' "$input" \
| grep -v '^\s*;' \
| grep -v '^\s*$' \
| grep -v '^\$' \
| sed 's/\s*;.*$//' \
| sed 's/\t\+/ /g; s/ \+/ /g' \
| awk '
{
# Expected formats after cleanup:
# name TTL IN TYPE rdata...
# name IN TYPE rdata...
#
# We output: name TYPE rdata...
name = $1
idx = 2
# Skip TTL if present (a number)
if ($idx ~ /^[0-9]+$/) idx++
# Skip class (IN, CS, CH, HS)
if (toupper($idx) == "IN" || toupper($idx) == "CS" || toupper($idx) == "CH" || toupper($idx) == "HS") idx++
rtype = toupper($idx)
idx++
# Skip SOA — serial and timers will always differ
if (rtype == "SOA") next
rdata = ""
for (i = idx; i <= NF; i++) {
val = $i
# Ensure trailing dot on targets for NS, CNAME, MX
if ((rtype == "NS" || rtype == "CNAME") && i == idx) {
if (val !~ /\.$/) val = val "."
}
if (rtype == "MX" && i == NF) {
if (val !~ /\.$/) val = val "."
}
if (rdata != "") rdata = rdata " "
rdata = rdata val
}
print name " " rtype " " rdata
}
' \
| sort > "$output"
}
LABEL_A="$(basename "$FILE_A")"
LABEL_B="$(basename "$FILE_B")"
echo -e "${BOLD}Comparing zones${RESET}"
echo -e " A: ${CYAN}${RESOLVED_A}${RESET}"
echo -e " B: ${CYAN}${RESOLVED_B}${RESET}"
echo ""
normalize_zone "$FILE_A" "$TMPDIR/a.norm"
normalize_zone "$FILE_B" "$TMPDIR/b.norm"
COUNT_A=$(wc -l < "$TMPDIR/a.norm")
COUNT_B=$(wc -l < "$TMPDIR/b.norm")
echo -e " A records: ${BOLD}$COUNT_A${RESET} (excluding SOA)"
echo -e " B records: ${BOLD}$COUNT_B${RESET} (excluding SOA)"
echo ""
# Compute differences
comm -23 "$TMPDIR/a.norm" "$TMPDIR/b.norm" > "$TMPDIR/only-a.txt"
comm -13 "$TMPDIR/a.norm" "$TMPDIR/b.norm" > "$TMPDIR/only-b.txt"
comm -12 "$TMPDIR/a.norm" "$TMPDIR/b.norm" > "$TMPDIR/matching.txt"
MATCH_COUNT=$(wc -l < "$TMPDIR/matching.txt")
ONLY_A_COUNT=$(wc -l < "$TMPDIR/only-a.txt")
ONLY_B_COUNT=$(wc -l < "$TMPDIR/only-b.txt")
echo -e "${BOLD}Results${RESET}"
echo -e " ${GREEN}✓ Matching:${RESET} $MATCH_COUNT"
echo -e " ${RED}✗ Only in A:${RESET} $ONLY_A_COUNT"
echo -e " ${YELLOW}+ Only in B:${RESET} $ONLY_B_COUNT"
echo ""
if [[ "$ONLY_A_COUNT" -gt 0 ]]; then
echo -e "${RED}${BOLD}Records only in A (${LABEL_A}):${RESET}"
while IFS= read -r line; do
echo -e " ${RED}-${RESET} $line"
done < "$TMPDIR/only-a.txt"
echo ""
fi
if [[ "$ONLY_B_COUNT" -gt 0 ]]; then
echo -e "${YELLOW}${BOLD}Records only in B (${LABEL_B}):${RESET}"
while IFS= read -r line; do
echo -e " ${YELLOW}+${RESET} $line"
done < "$TMPDIR/only-b.txt"
echo ""
fi
if [[ "$ONLY_A_COUNT" -eq 0 && "$ONLY_B_COUNT" -eq 0 ]]; then
echo -e "${GREEN}${BOLD}✓ Zones are identical!${RESET}"
exit 0
else
# Unified diff
echo -e "${BOLD}Diff (unified):${RESET}"
diff -u \
--label "$LABEL_A" "$TMPDIR/a.norm" \
--label "$LABEL_B" "$TMPDIR/b.norm" \
| head -80 || true
echo ""
# Summary by record type
echo -e "${BOLD}Summary by record type:${RESET}"
echo -e " ${BOLD}Type A-only B-only Matching${RESET}"
{
cat "$TMPDIR/only-a.txt" "$TMPDIR/only-b.txt" "$TMPDIR/matching.txt"
} | awk '{print $2}' | sort -u | while read -r rtype; do
a_only=$(grep -c "^[^ ]* ${rtype} " "$TMPDIR/only-a.txt" || true)
b_only=$(grep -c "^[^ ]* ${rtype} " "$TMPDIR/only-b.txt" || true)
matching=$(grep -c "^[^ ]* ${rtype} " "$TMPDIR/matching.txt" || true)
printf " %-6s %6d %6d %8d\n" "$rtype" "$a_only" "$b_only" "$matching"
done
echo ""
exit 1
fi

42
scripts/deploy-apex.sh Normal file
View file

@ -0,0 +1,42 @@
#!/usr/bin/env bash
# script to deploy the APEX domain to Cloudflare with CNAME flattening
set -euo pipefail
ZONE_ID="${CF_ZONE_ID:?}"
TOKEN="${CF_API_TOKEN:?}"
TARGET="website-e7n.pages.dev"
EXISTING=$(curl -s \
"https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records?type=CNAME&name=@" \
-H "Authorization: Bearer ${TOKEN}" \
| jq -r '.result[0] // empty')
EXISTING_CONTENT=$(echo "$EXISTING" | jq -r '.content // empty')
EXISTING_ID=$(echo "$EXISTING" | jq -r '.id // empty')
if [[ "$EXISTING_CONTENT" == "$TARGET" ]]; then
echo "Apex CNAME unchanged, skipping."
exit 0
fi
if [[ -z "$EXISTING_ID" ]]; then
echo "No apex CNAME found, creating..."
METHOD="POST"
URL="https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records"
else
echo "Apex CNAME changed ($EXISTING_CONTENT$TARGET), updating..."
METHOD="PUT"
URL="https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records/${EXISTING_ID}"
fi
curl -s -X "$METHOD" "$URL" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
--data "{
\"type\": \"CNAME\",
\"name\": \"@\",
\"content\": \"${TARGET}\",
\"proxied\": true
}" | jq -e '.success'

View file

@ -1,265 +0,0 @@
#!/usr/bin/env python3
"""
Migrate domains/*.json to domains/*.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 to stdout without writing files
--delete-json Delete the .json files after successful conversion
"""
import json
import sys
from pathlib import Path
DOMAINS_DIR = Path(__file__).resolve().parent.parent / "domains"
# --- 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")
proxy = data.get("proxied", data.get("proxy"))
lines = [" metadata = {"]
if description is not None:
lines.append(f' description = "{escape(description)}";')
if proxy is not None:
lines.append(f" proxy = {'true' if proxy else 'false'};")
owner_keys = ["username", "email", "discord", "repo"]
owner_fields = [(k, owner[k]) for k in owner_keys if owner.get(k)]
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 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
# --- 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:
data = json.loads(path.read_text())
except json.JSONDecodeError as e:
print(f" ERROR: {path.name}: {e}", file=sys.stderr)
return False
nix = json_to_nix(data)
nix_path = path.with_suffix(".nix")
if dry_run:
print(f"--- {nix_path.name} ---")
print(nix)
return True
nix_path.write_text(nix)
print(f" Created {nix_path.name}")
if delete_json:
path.unlink()
print(f" Deleted {path.name}")
return True
def main():
dry_run = "--dry-run" in sys.argv
delete_json = "--delete-json" in sys.argv
if not DOMAINS_DIR.exists():
print(f"Error: {DOMAINS_DIR} not found", file=sys.stderr)
sys.exit(1)
files = sorted(DOMAINS_DIR.glob("*.json"))
if not files:
print("No .json files found in domains/")
sys.exit(0)
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 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:
sys.exit(1)
if __name__ == "__main__":
main()

View file

@ -1,3 +0,0 @@
#!/usr/bin/env bash
curl

View file

@ -1 +1 @@
31
32