├── .gitignore ├── LICENSE ├── README.md └── gandi-ddns.py /.gitignore: -------------------------------------------------------------------------------- 1 | config.txt 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Matt D 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | gandi-ddns 2 | ========== 3 | 4 | Simple quick & dirty script to update DNS A record of your domain dynamically using gandi.net's API. It is very similar to no-ip and dyndns et al where you can have a domain on the internet which points at your computer's IP address, except it is free (once you have registered the domain) and does not suffer from any forced refreshing etc. 5 | 6 | This was designed specifically with Raspberry Pi servers in mind, but could be used anywhere. 7 | 8 | Every time the script runs it will get the current domain config from gandi.net's API and look for the IP in the A record for the domain (default name for the record is '@' but you can change that if you want to). It will then get your current external IP from a public "what is my ip" site. Once it has both IPs it will compare what is in the DNS config vs what your IP is, and update the DNS config for the domain as appropriate so that it resolves to your current IP address. 9 | 10 | The configuration is stored in the script directory under the file `config.txt`. The syntax is the standard Python configuration file, and the format is the following: 11 | ``` 12 | [local] 13 | # gandi.net API (Production) key 14 | apikey = 15 | # Domain 16 | domain = 17 | # A-record name 18 | a_name = @ 19 | # TTL (seconds = 5 mintes to 30 days) 20 | ttl = 900 21 | # Production API 22 | api = https://rpc.gandi.net/xmlrpc/ 23 | # Host which IP should be changed 24 | host = localhost 25 | ``` 26 | 27 | - Do not forget to replace the values marked with `` with your 24-character API key and domain. 28 | - You can have more than one config section (`[local]` above) if you need to update more than one domain/a_name. 29 | - `host` is either `localhost` in which case the script will fetch the current external address, or any other public name. Using an other ddns account will allow you to sync it with Gandi (useful when you are not running on the same IP than the one you want to update). 30 | 31 | Usage 32 | ----- 33 | You will need to make sure that your domain is registered on gandi.net, and that you are using the gandi.net DNS servers (if you are using the default gandi.net zone for other domains you have on gandi.net, you might want to create a dedicated zone for the domain you will be using). You'll also need to register for the API to get a key. 34 | 35 | Once you have the production key (not the test environment key) and your domain on gandi.net, edit the 'apikey' and 'domain' variables in the script appropriately. 36 | 37 | Once you have done this you can then set up the script to run via crontab: 38 | 39 | ``` 40 | sudo crontab -e 41 | ``` 42 | 43 | Then add the following line so that the script is run after a reboot: 44 | 45 | ``` 46 | @reboot python /home/pi/gandi-ddns.py & 47 | ``` 48 | 49 | And then to make it check for a new IP every 15 mintes you can add: 50 | 51 | ``` 52 | */15 * * * * python /home/pi/gandi-ddns.py 53 | ``` 54 | You can then start and/or reload the cron config: 55 | 56 | ``` 57 | sudo /etc/init.d/cron start 58 | sudo /etc/init.d/cron reload 59 | 60 | ``` 61 | 62 | FAQ 63 | --- 64 | **I am getting a python trace about ```Error on object : OBJECT_FQDN (CAUSE_BADPARAMETER) [string '' does not match '^(?:(?!-)[-a-zA-Z0-9]{1,63}(? 103 | # Domain 104 | domain = 105 | # A-record name 106 | a_name = @ 107 | # TTL (seconds = 5 mintes to 30 days) 108 | ttl = 900 109 | # Production API 110 | api = https://rpc.gandi.net/xmlrpc/ 111 | # Host which IP should be changed 112 | host = localhost 113 | """ 114 | 115 | 116 | def read_config(config_path): 117 | """ Open the configuration file or create it if it doesn't exists """ 118 | if not os.path.exists(config_path): 119 | with open(config_path, "w") as f: 120 | f.write(default_config) 121 | return None 122 | cfg = configparser.ConfigParser() 123 | cfg.read(config_path) 124 | return cfg 125 | 126 | 127 | def main(): 128 | global api, zone_id 129 | path = config_file 130 | if not path.startswith('/'): 131 | path = os.path.join(SCRIPT_DIR, path) 132 | config = read_config(path) 133 | if not config: 134 | sys.exit("please fill in the 'config.txt' file") 135 | 136 | for section in config.sections(): 137 | api = xmlrpclient.ServerProxy(config.get(section, "api"), verbose=False) 138 | 139 | zone_ip = get_zone_ip(config, section).strip() 140 | current_ip = socket.gethostbyname(config.get(section, "host")) 141 | if current_ip == '127.0.0.1': 142 | current_ip = get_ip() 143 | 144 | if (zone_ip.strip() == current_ip.strip()): 145 | sys.exit() 146 | else: 147 | print('DNS Mistmatch detected: A-record: ', 148 | zone_ip, ' WAN IP: ', current_ip) 149 | change_zone_ip(config, section, current_ip) 150 | zone_id = None 151 | zone_ip = get_zone_ip(config, section) 152 | print('DNS A record update complete - set to ', zone_ip) 153 | 154 | 155 | if __name__ == "__main__": 156 | main() 157 | --------------------------------------------------------------------------------