Skip to content

Instantly share code, notes, and snippets.

@padeoe
Last active April 11, 2022 07:06
Show Gist options
  • Save padeoe/2a5f831cb90aeeaa0b1e83e186c4c8a7 to your computer and use it in GitHub Desktop.
Save padeoe/2a5f831cb90aeeaa0b1e83e186c4c8a7 to your computer and use it in GitHub Desktop.
unofficial ddns for domain on name.com

DDNS for Domain on Name.com

NAME.COM provide the API to create/update DNS record, we can implement DDNS without depending on any third party DDNS service.

  • Python 3.8+ (because I used the latest := operator in 3.8, if you use 3.6 or others, you can modify the codes, very easy)
  • v4 REST API of name.com
  • Use ipinfo.io to get public IP

Step 1: Prepare a Proxy (Static IP)

Since we would like to setup DDNS, which means our server doesn't have a fixed IP, but the API of name.com requires the caller's IP whitelisted in advance, which means the caller's IP need to be fixed or limited in the number. However, the dynamic from ISP is not stable. So, a proxy server is required to solve the problem.

HTTP Proxy or Socks5 Proxy are required before the following steps. e.g. http://localhost:8080, socks5://localhost:1080

Step 2: Get a API token

Get your own api token provided by name.com: https://www.name.com/account/settings/api.

ATTENTION

  • get username and token from PRODUCTION, not DEVELOPMENT/TEST ENVIRONMENT
  • WHITELIST the IP of your proxy server.

Step 3: Save the script to your server

Download ddns.py save it to your server.

Step 4: Make a Test

For example, I want to use home.padeoe.com for DDNS.

python ddns.py padeoe.com home.padeoe.com \
  --api_username padeoe \
  --api_token 5c06xxxxxxxxxxxxxxxxxxxxxxxxx \
  --proxy socks5://localhost:1086

Step 5: Make a crontab job every 10 minutes

crontab -e
*/10 * * * * /opt/conda/bin/python ~/project/misc/ddns.py padeoe.com home.padeoe.com --api_username padeoe --api_token 5c06xxxxxxxxxxxxxxxxxxxxxxxxx --proxy socks5://localhost:1086
import argparse
import json
import os
import base64
import logging
from logging.handlers import RotatingFileHandler
import requests
parser = argparse.ArgumentParser(
description="DDNS service for domain on name.com. Periodically check the current host IP, and update record by API of name.com"
)
parser.add_argument("domain", help="top domain, for example: example.com")
parser.add_argument("fqdn", help="domain for DDNS, for example: xxx.example.com")
parser.add_argument("--api_username", help="api username", required=True)
parser.add_argument("--api_token", help="api token", required=True)
parser.add_argument(
"--proxy", help="proxy url,for example socks5://localhost:1086", required=False
)
parser.add_argument(
"--log",
default=os.path.join(
os.path.dirname(os.path.realpath(__file__)), "ddns_name_com.log"
),
help="path for log file",
required=False,
)
logger = logging.getLogger("DDNS")
logger.setLevel(logging.INFO)
formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
)
ch = logging.StreamHandler()
logger.addHandler(ch)
ch.setFormatter(formatter)
def get_host_id(domain, fqdn: str, api_username, api_password, proxies=None):
cache_config = os.path.join(
os.path.dirname(os.path.realpath(__file__)), ".ddns_config"
)
if os.path.exists(cache_config):
with open(cache_config, encoding="utf-8") as f:
items = json.loads(f.read())
host_id = items.get("host_id")
if host_id:
return host_id
url = f"https://api.name.com/v4/domains/{domain}/records"
headers = {
"Authorization": "Basic %s"
% base64.b64encode(str.encode(f"{api_username}:{api_password}")).decode()
}
if not fqdn.endswith("."):
fqdn = fqdn + "."
r = requests.get(url, headers=headers, proxies=proxies)
if r.status_code == 200 and (records := r.json().get("records", None)) is not None:
for record in records:
if record["fqdn"] == fqdn:
with open(cache_config, encoding="utf-8", mode="w+") as f:
if (host_id := str(record["id"])) is not None:
json.dump({"host_id": host_id}, f)
return record["id"]
def update_ip(fqdn, host_id, ip, api_username, api_password, proxies=None):
url = f"https://api.name.com/v4/domains/{fqdn}/records/{host_id}"
payload = json.dumps({"type": "A", "answer": ip, "ttl": 300})
headers = {
"Authorization": "Basic %s"
% base64.b64encode(str.encode(f"{api_username}:{api_password}")).decode()
}
r = requests.put(url, headers=headers, data=payload, proxies=proxies)
result = r.text
logger.info("Update Result:" + result.strip())
return result
def ddns(domain, fqdn, api_username, api_password, proxy_url):
proxies = None
if proxy_url:
proxies = {
"http": proxy_url,
"https": proxy_url,
}
host_id = get_host_id(domain, fqdn, api_username, api_password, proxies)
last_ip = None
file_cache_ip = os.path.join(
os.path.dirname(os.path.realpath(__file__)), ".ddns_ip.txt"
)
if os.path.exists(file_cache_ip):
with open(file_cache_ip, encoding="utf-8") as f:
last_ip = f.read()
current_ip = requests.get(
"https://ipinfo.io/ip", headers={"User-Agent": "curl/7.71.1"}
).text
logger.info(f"Current IP: {current_ip}")
if current_ip != last_ip:
logger.warning(
f"IP changed,current IP:{current_ip}, last IP:{last_ip},now update DNS record"
)
with open(file_cache_ip, encoding="utf-8", mode="w+") as f:
f.write(current_ip)
update_ip(fqdn, host_id, current_ip, api_username, api_password, proxies)
if __name__ == "__main__":
args = parser.parse_args()
if not os.path.exists(args.log):
mode = "w+"
else:
mode = "a"
fh = RotatingFileHandler(
args.log,
mode=mode,
maxBytes=5 * 1024 * 1024,
backupCount=2,
encoding="utf-8",
delay=0,
)
logger.addHandler(fh)
fh.setFormatter(formatter)
ddns(args.domain, args.fqdn, args.api_username, args.api_token, args.proxy)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment