diff --git a/README.md b/README.md index 114ae2b..2465153 100644 --- a/README.md +++ b/README.md @@ -5,29 +5,33 @@ and replacing the IP with the public IP of the machine on which the code is exec This script was inspired by matt1's gandi-ddns script : [matt1/gandi-ddns](https://github.com/matt1/gandi-ddns) ## Usage -1. **Define an object of class GanDynDns at the end of the file** : -``` -main = GanDynDns("example.org", "@", "A", "your-api-key") -``` -Or if you want to define multiple domains to update : ``` -domain = "example.org" -apikey = "your-api-key" +usage: gandyndns.py [-h] [-v] [--type TYPE] DOMAIN SUBDOMAIN APIKEY -main = GanDynDns(domain, "@", "A", apikey) -mail = GanDynDns(domain, "mail", "A" apikey) +A script which connect to Gandi.net API to change IP associated to a DNS record + +positional arguments: + DOMAIN The domain for which you want to write a DNS record. Example: example.com + SUBDOMAIN The subdomain to point to. Examples: 'sub' or '@' + APIKEY Your Gandi.net API key + +options: + -h, --help show this help message and exit + -v, --verbose Enable verbose mode + --type TYPE The type of DNS record to create. Default: A ``` -Each time the script runs and the objects are created they check if *the IPs in the DNS record* match the *current public IP*. -2. **Run the script using a cron task** : +Each time the script runs it check if *the IPs in the DNS record* match the *current public IP* and if they dont use a PUT request to apply the new IP to the DNS. -``` +## Automation : **Run the script using a cron task** + +```bash sudo crontab -e ``` -And then to execute *every 15 minutes* write in the crontab : +And then to execute *every X minutes* (where X should be an integer between 1 and 59) write in the crontab : ``` - */15 * * * * python /opt/services/scripts/gandyndns.py + */X * * * * python /path/to/script/gandyndns.py example.com @ my-api-key ``` diff --git a/gandyndns.py b/gandyndns.py index 90c4b48..dc565ca 100644 --- a/gandyndns.py +++ b/gandyndns.py @@ -3,65 +3,96 @@ # @author: lordof20th import requests +import argparse -class GanDynDns: - def __init__(self, fqdn, rrset_name, rrset_type, apikey) -> None: - self.fqdn = fqdn - self.rrset_name = rrset_name - self.rrset_type = rrset_type - self.domain_record_string = f"DNS {self.rrset_type} record for {self.rrset_name}.{self.fqdn}" - self.apiUrl = f"https://api.gandi.net/v5/livedns/domains/{fqdn}/records/{rrset_name}/{rrset_type}" - self.headers = {"Authorization": f"Apikey {apikey}", 'User-Agent': 'Mozilla/5.0', "Content-Type": "application/json"} - self.update_dns_IP() +parser = argparse.ArgumentParser( + description="A script which connect to Gandi.net API to change IP associated to a DNS record") +parser.add_argument("-v", "--verbose", + help="Enable verbose mode", action="store_true") +parser.add_argument("domain", metavar="DOMAIN", + help="The domain for which you want to write a DNS record. Example: example.com") +parser.add_argument("subdomain", metavar="SUBDOMAIN", + help="The subdomain to point to. Examples: 'sub' or '@'") +parser.add_argument("apikey", metavar="APIKEY", help="Your Gandi.net API key") +parser.add_argument("--type", metavar="TYPE", default='A', + help="The type of DNS record to create. Default: A") - def retrieve_dns_IP(self): - """Retrieves the IP in the DNS record using the fqdn, rrset_name (for instance subdomain like www etc) and rrset_type (the DNS record type A, CNAME ...). - - The function uses requests library and connect to Gandi.net API - - Returns: - retrievedDnsIp (str): string of the IP address in the DNS record""" - - response = requests.get(self.apiUrl, headers=self.headers) +args = parser.parse_args() +print(args) - responseJson = response.json() # IPs are stored in a list as string - try: - retrievedDnsIp = responseJson['rrset_values'][0] - except KeyError as key_error: - print(f"{key_error} means the record doesn't exist, we'll return an empty string instead") - retrievedDnsIp ="" - return retrievedDnsIp +verbose = args.verbose +domain = args.domain +apikey = args.apikey +subdomain = args.subdomain +type = args.type - def retrieve_public_IP(self): - """Retrieves the public IP by connecting to ipinfo.io API - - Returns: - data['ip'] (str) : string of the public IP address""" - endpoint = "https://ipinfo.io/json" - response = requests.get(endpoint, verify = True) - - data = response.json() - return data['ip'] +domain_record_string = f"DNS {type} record for {subdomain}.{domain}" - def ips_are_equals(self): - """The method compares both IPs and returns a boolean for the equality test - - Returns: - (bool) : result of the IP equality test""" - self.currentPublicIP = self.retrieve_public_IP() - self.dnsIP = self.retrieve_dns_IP() - return self.currentPublicIP == self.dnsIP +domain_record_string_lenght = len(domain_record_string) - def update_dns_IP(self): - """Updates the IP in the DNS record using the IP provided (acquired by retrieve_public_IP) if it is different from the DNS IP""" - - if not self.ips_are_equals(): - data = { - "rrset_values": [self.currentPublicIP] - } - print(f"{self.domain_record_string : <40} | Old IP : {self.dnsIP} replaced -> by New IP : {self.currentPublicIP}") - requests.put(url=self.apiUrl, headers=self.headers, json=data) +apiUrl = f"https://api.gandi.net/v5/livedns/domains/{domain}/records/{subdomain}/{type}" + +headers = {"Authorization": f"Apikey {apikey}", + 'User-Agent': 'Mozilla/5.0', "Content-Type": "application/json"} + + +def retrieve_public_IP(): + """Retrieves the public IP by connecting to ipinfo.io API + + Returns: + data['ip'] (str) : string of the public IP address""" + endpoint = "https://ipinfo.io/json" + response = requests.get(endpoint, verify=True) + + data = response.json() + return data['ip'] + + +def retrieve_dns_IP(apiUrl, headers): + """Retrieves the IP in the DNS record using the domain, rrset_name (for instance subdomain like www etc) and rrset_type (the DNS record type A, CNAME ...). + + The function uses requests library and connect to Gandi.net API + + Returns: + retrievedDnsIp (str): string of the IP address in the DNS record""" + if verbose: + print("Retrieving the DNS IP") + response = requests.get(apiUrl, headers=headers) + + responseJson = response.json() # IPs are stored in a list as string + try: + retrievedDnsIp = responseJson['rrset_values'][0] + except KeyError as key_error: + print( + f"{key_error} means the record doesn't exist, we'll return an empty string instead") + retrievedDnsIp = "" + return retrievedDnsIp + + +def update_dns_IP(apiUrl, headers): + """Updates the IP in the DNS record using the IP provided (acquired by retrieve_public_IP) if it is different from the DNS IP""" + publicIP = retrieve_public_IP() + dnsIP = retrieve_dns_IP(apiUrl, headers) + if verbose: + print(f"Public IP is : {publicIP}") + if dnsIP: + print(f"DNS IP is : {dnsIP}") else: - print(f"{self.domain_record_string : <40} | {'IPs are the same.':<17}") + print("The subdomain doesn't exist, no DNS IP associated") + if not publicIP == dnsIP: + if verbose: + print("IPs do not match, setting DNS IP") + data = { + "rrset_values": [publicIP] + } + print(f"{domain_record_string : <{domain_record_string_lenght}} | Old IP : {dnsIP} replaced -> by New IP : {publicIP}") + response = requests.put(url=apiUrl, headers=headers, json=data) + if verbose: + print(f"Request exited with status code : {response.status_code}") + else: + print( + f"{domain_record_string : <{domain_record_string_lenght}} | {'IPs are the same.':<17}") -main = GanDynDns("example.org", "@", "A", "your-api-key") \ No newline at end of file + +if __name__ == '__main__': + update_dns_IP(apiUrl, headers)