77from ipaddress import IPv4Address , IPv6Address , ip_address
88from typing import Union , NewType , Dict
99from urllib .parse import urlencode
10+ from pathlib import Path
1011
1112import requests
1213from dotenv import load_dotenv
@@ -146,6 +147,46 @@ def createNFSNAuthHeader(nfsn_username, nfsn_apikey, url_path, body) -> Dict[str
146147 return {"X-NFSN-Authentication" : f"{ uts } ;{ full_hash } " }
147148
148149
150+ def getAllDNSRecords (domain , nfsn_username , nfsn_apikey ):
151+ action = "listRRs"
152+
153+ path = f"/dns/{ domain } /{ action } "
154+
155+ response_data = makeNFSNHTTPRequest (path , None , nfsn_username , nfsn_apikey )
156+
157+ return response_data
158+
159+ def NFSNDnsToZoneFile (dnsRecords ):
160+
161+ def sortKey (record ):
162+ host = record .get ("name" )
163+ return ("@" if host == "" else host ) \
164+ + record .get ("data" )
165+
166+
167+ dnsRecords = sorted (dnsRecords , key = sortKey )
168+
169+ outputList = []
170+ for record in dnsRecords :
171+ host = record .get ("name" )
172+ host = "@" if host == "" else host
173+ record_type = record .get ("type" )
174+ data = record .get ("data" )
175+ if record_type == "TXT" :
176+ data = f'"{ data } "'
177+ elif record_type == "MX" :
178+ data = f'{ record .get ("aux" )} { data } '
179+ outputList .append (
180+ '\t ' .join ([
181+ host ,
182+ str (record .get ("ttl" )),
183+ "IN" ,
184+ record_type ,
185+ data
186+ ])
187+ )
188+ outputList .append ("" )
189+ return outputList
149190
150191def updateIPs (domain , subdomain , domain_ip , current_ip , nfsn_username , nfsn_apikey , v6 = False , create_if_not_exists = False ):
151192 # When there's no existing record for a domain name, the
@@ -190,6 +231,7 @@ def check_ips(nfsn_domain, nfsn_subdomain, nfsn_username, nfsn_apikey, v6=False,
190231 parser = argparse .ArgumentParser (description = 'automate the updating of domain records to create Dynamic DNS for domains registered with NearlyFreeSpeech.net' )
191232 parser .add_argument ('--ipv6' , '-6' , action = 'store_true' , help = 'also check and update the AAAA (IPv6) records' )
192233 parser .add_argument ('--useDig' , '-d' , action = 'store_true' , help = 'use the dig command to query dns' )
234+ parser .add_argument ('--export-to' , help = 'the filename to export the zone file to' )
193235 args = parser .parse_args ()
194236
195237 nfsn_username = os .getenv ('USERNAME' )
@@ -201,7 +243,17 @@ def check_ips(nfsn_domain, nfsn_subdomain, nfsn_username, nfsn_apikey, v6=False,
201243 ensure_present (nfsn_apikey , "API_KEY" )
202244 ensure_present (nfsn_domain , "DOMAIN" )
203245
204- use_dig_command = os .getenv ('IP_USE_DIG' , args .useDig )
205- v6_enabled = os .getenv ('ENABLE_IPV6' , args .ipv6 )
246+ if args .export_to :
247+
248+ dns = getAllDNSRecords (nfsn_domain , nfsn_username , nfsn_apikey )
249+
250+ zonedata = NFSNDnsToZoneFile (dns )
206251
207- check_ips (nfsn_domain , nfsn_subdomain , nfsn_username , nfsn_apikey , v6 = v6_enabled , dig = use_dig_command , create_if_not_exists = False )
252+ Path (args .export_to ).write_text ('\n ' .join (zonedata ), encoding = 'utf-8' )
253+ else :
254+ use_dig_command = os .getenv ('IP_USE_DIG' , args .useDig )
255+ v6_enabled = os .getenv ('ENABLE_IPV6' , args .ipv6 )
256+
257+ check_ips (nfsn_domain , nfsn_subdomain , nfsn_username , nfsn_apikey , v6 = v6_enabled , dig = use_dig_command , create_if_not_exists = False )
258+
259+
0 commit comments