From 595720a1290197ec9375685107d519240c59473d Mon Sep 17 00:00:00 2001 From: lolo859 Date: Sat, 21 Mar 2026 14:00:33 +0100 Subject: [PATCH] first commit --- example_config.json | 10 +++++ readme.md | 45 +++++++++++++++++++++++ vydns.py | 90 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+) create mode 100644 example_config.json create mode 100644 readme.md create mode 100644 vydns.py diff --git a/example_config.json b/example_config.json new file mode 100644 index 0000000..7fb3af4 --- /dev/null +++ b/example_config.json @@ -0,0 +1,10 @@ +{ + "ip_server":"https://api.ipify.org", + "dns_endpoint":"https://dns.eu.ovhapis.com/nic/update", + "username":"username", + "password":"password", + "domains":[ + "example.com" + ], + "delay":3600 +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..0e0f427 --- /dev/null +++ b/readme.md @@ -0,0 +1,45 @@ +# VYDNS + +A simple DynDNS v2 client written in Python, compatible with providers such as OVH DynHost. + +## Features + +- Periodically checks the public IP address +- Updates DNS records only when the IP changes +- Uses a local cache to avoid unnecessary updates +- No external dependencies except `requests` module + +## Usage + +Run the script with Python. It will periodically: + +- Query a web endpoint that returns the public IP in plain text +- Compare it with the cached IP +- If the IP has not changed, sleep for the configured delay +- If the IP has changed, update the DNS records and refresh the cache +- If no cache exists, create it and perform an initial update + +## Configuration + +The script relies on a JSON configuration file named `config.json` located in the working directory. + +It must contain the following keys: + +- `ip_server`: URL of the endpoint used to retrieve the public IP (must return plain text) +- `dns_endpoint`: DynDNS v2 API endpoint of the provider +- `username`: DynDNS account username +- `password`: DynDNS account password +- `domains`: list of domain names to update +- `delay`: interval in seconds between checks + +You can find an example configuration file in this repo. +**Warning:** the password is stored in plain text. Changing config file permissions is recommended. + +## Supported DynDNS providers + +To this day, these DynDNS providers have been found to work well with the script: +- OVH + +## License + +This script and the provided examples are under the MIT license. diff --git a/vydns.py b/vydns.py new file mode 100644 index 0000000..2ac9b25 --- /dev/null +++ b/vydns.py @@ -0,0 +1,90 @@ +import requests +import os +import json +import ipaddress +import time +def recreate_cache(ip_server:str): + print("[VYDNS] Obtaining public IP using server '"+ip_server+"'") + ip="" + try: + ip=requests.get(ip_server,timeout=10).text.strip() + ipaddress.ip_address(ip) + except Exception as e: + print("[VYDNS] Error: counldn't obtain IP or IP isn't valid. Exception: "+str(e)) + exit() + print("[VYDNS] "+ip) + cache={"ip":ip} + with open("cache.json","w") as f: + json.dump(cache,f) + print("[VYDNS] Succesfully obtained IP.") + return ip +def obtain_ip(ip_server:str): + print("[VYDNS] Obtaining public IP using server '"+ip_server+"'") + ip="" + try: + ip=requests.get(ip_server,timeout=10).text.strip() + ipaddress.ip_address(ip) + except Exception as e: + print("[VYDNS] Error: counldn't obtain IP or IP isn't valid. Exception: "+str(e)) + exit() + print("[VYDNS] "+ip) + print("[VYDNS] Succesfully obtained IP.") + return ip +def update_ip(ip:str,dns_endpoint:str,domains:str,username:str,password:str): + print("[VYDNS] Updating "+str(len(domains))+" domains:") + for d in domains: + print("[VYDNS] Updating '"+d+"' :") + params={ + "system":"dyndns", + "hostname":d, + "myip":ip + } + r=requests.get(dns_endpoint,params=params,auth=(username,password),timeout=10) + print(r.text) + if "good" in r.text or "nochg" in r.text: + print("[VYDNS] Update OK.") + else: + print("[VYDNS] Update failed.") +conffile={} +if os.path.exists("config.json"): + conffile=json.load(open("config.json","r")) +else: + print("[VYDNS] Error: config file doesn't exist.") + exit() +if ("ip_server" not in conffile) or ("dns_endpoint" not in conffile) or ("username" not in conffile) or ("password" not in conffile) or ("domains" not in conffile) or ("delay" not in conffile): + print("[VYDNS] Error: config file isn't formatted as expected.") + exit() +if (conffile["ip_server"]=="") or (conffile["dns_endpoint"]=="") or (conffile["username"]=="") or (conffile["password"]=="") or (conffile["delay"]<=0): + print("[VYDNS] Error: one of the key is empty or invalid.") + exit() +if (conffile["domains"]==[]): + print("[VYDNS] Error: no domains provided.") + exit() +while True: + obtained_ip="" + if not os.path.exists("cache.json"): + print("[VYDNS] Cache file doesn't exist, recreating it.") + obtained_ip=recreate_cache(conffile["ip_server"]) + update_ip(obtained_ip,conffile["dns_endpoint"],conffile["domains"],conffile["username"],conffile["password"]) + print("[VYDNS] Sleeping for "+str(conffile["delay"])+" seconds.") + time.sleep(conffile["delay"]) + continue + else: + print("[VYDNS] Cache file exist, obtaining current IP for comparaison.") + obtained_ip=obtain_ip(conffile["ip_server"]) + with open("cache.json","r") as f: + cachefile=json.load(f) + if cachefile["ip"]==obtained_ip: + print("[VYDNS] According to cache file, IP didn't change.") + print("[VYDNS] Sleeping for "+str(conffile["delay"])+" seconds.") + time.sleep(conffile["delay"]) + continue + else: + print("[VYDNS] According to cache file, IP did changed.") + update_ip(obtained_ip,conffile["dns_endpoint"],conffile["domains"],conffile["username"],conffile["password"]) + cache={"ip":obtained_ip} + with open("cache.json","w") as f: + json.dump(cache,f) + print("[VYDNS] Sleeping for "+str(conffile["delay"])+" seconds.") + time.sleep(conffile["delay"]) + continue