├── .gitignore ├── README.md └── dnspod.py /.gitignore: -------------------------------------------------------------------------------- 1 | conf.yaml 2 | conf.md5 3 | log.txt 4 | last.ip 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dnspod.py 2 | 3 | ``` 4 | @author migege 5 | @version 0.0.2 6 | ``` 7 | 8 | dnspod.py 是基于 [DNSPod](http://www.dnspod.cn/docs/records.html#dns) 服务的动态 DNS 脚本,用于检测 IP 变化并更新至 DNSPod,支持多域名解析。支持 Linux 设备,包括树莓派([Raspberry Pi](https://www.raspberrypi.org/))。 9 | 10 | # Prerequisites 11 | 12 | 1. python 13 | 1. pyyaml 14 | 1. requests 15 | 16 | python 的模块可通过 ```pip install``` 命令安装。如果未安装 [pip](https://pip.pypa.io/),请先安装 pip。 17 | 18 | # Installation 19 | 20 | 安装 [git](https://git-scm.com/) 客户端,通过本命令获取 dnspod.py 21 | 22 |
23 | git clone https://github.com/migege/dnspod.git dnspod
24 | 
25 | 26 | 然后到 dnspod 目录下新建 ```conf.yaml``` 文件,根据您的 DNSPod 设置,填入以下内容: 27 | 28 |
29 | token: <your_api_token>
30 | sub_domains:
31 |   <your_first_sub_domain_name>:
32 |     domain_id: <your_domain_id>
33 |     record_id: <your_record_id>
34 |   <your_second_sub_domain_name>:
35 |     domain_id: <your_domain_id>
36 |     record_id: <your_record_id>
37 | 
38 | 39 | 最后设置 crontab 定时任务 40 | 41 |
42 | */10 * * * * cd <path_to_dnspod>; /usr/bin/python dnspod.py conf.yaml > /dev/null 2>&1 &
43 | 
44 | 45 | # Tips 46 | 47 | 1. */10 表示每 10 分钟执行一次 dnspod.py 48 | 1. 如果 python 可执行路径不是 /usr/bin/python,请自行替换 49 | -------------------------------------------------------------------------------- /dnspod.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf8 -*- 3 | import os, sys 4 | import requests 5 | import socket 6 | import yaml 7 | 8 | reload(sys) 9 | sys.setdefaultencoding("utf8") 10 | 11 | 12 | def logger(): 13 | import logging 14 | LOG_FORMAT = "[%(asctime)s]\t[%(levelname)s]\t[%(message)s]" 15 | LOG_FILE = os.path.join(os.path.dirname(os.path.realpath(__file__)), "log.txt") 16 | logging.basicConfig(format=LOG_FORMAT, level=logging.DEBUG, filename=LOG_FILE) 17 | return logging.getLogger(__name__) 18 | 19 | 20 | def getopts(): 21 | import argparse 22 | parser = argparse.ArgumentParser(description='github.com/migege/dnspod') 23 | parser.add_argument('config', help='config file in yaml') 24 | opts = parser.parse_args() 25 | return opts 26 | 27 | 28 | class Last(object): 29 | 30 | def __init__(self, tag): 31 | self.fn = os.path.join(os.path.dirname(os.path.realpath(__file__)), tag) 32 | 33 | def Read(self): 34 | try: 35 | with open(self.fn, "r") as fp: 36 | return fp.read() 37 | except: 38 | return None 39 | 40 | def Write(self, value): 41 | with open(self.fn, "w") as fp: 42 | fp.write(value) 43 | 44 | 45 | class DNSPod(object): 46 | 47 | def __init__(self, conf): 48 | self.ip = Last('last.ip').Read() 49 | self.conf_md5 = Last('conf.md5').Read() 50 | self.conf = conf 51 | 52 | def run(self): 53 | ip = self.GetIP() 54 | conf_md5 = self.GetConfMD5() 55 | if ip and ip != self.ip: 56 | logger().info("IP changed from '%s' to '%s'", self.ip, ip) 57 | if self.DDns(ip): 58 | self.ip = ip 59 | Last('last.ip').Write(self.ip) 60 | return 61 | if conf_md5 and conf_md5 != self.conf_md5: 62 | logger().info("MD5 of conf changed") 63 | if self.DDns(ip): 64 | self.ip = ip 65 | self.conf_md5 = conf_md5 66 | Last('last.ip').Write(self.ip) 67 | Last('conf.md5').Write(self.conf_md5) 68 | return 69 | 70 | def GetIP(self): 71 | try: 72 | sock = socket.create_connection(address=('ns1.dnspod.net', 6666), timeout=10) 73 | ip = sock.recv(32) 74 | sock.close() 75 | return ip 76 | except Exception, e: 77 | logger().error("GetIP Error: %s", e) 78 | return None 79 | 80 | def GetConfMD5(self): 81 | try: 82 | import hashlib 83 | import json 84 | md5 = hashlib.md5(json.dumps(self.conf)).hexdigest() 85 | return md5 86 | except Exception, e: 87 | logger().error('GetConfMD5 Error: %s', e) 88 | return None 89 | 90 | def __DDnsImpl(self, ip, todo_list): 91 | url = "https://dnsapi.cn/Record.Ddns" 92 | headers = { 93 | "User-Agent": "github.com#migege#dnspod/0.0.2 (lzw.whu@gmail.com)", 94 | } 95 | 96 | retry_list = [] 97 | for sub_domain, v in todo_list: 98 | try: 99 | valid = v["valid"] 100 | if not valid: 101 | continue 102 | except: 103 | pass 104 | 105 | try: 106 | domain_id = v["domain_id"] 107 | record_id = v["record_id"] 108 | data = { 109 | "login_token": self.conf["token"], 110 | "format": "json", 111 | "domain_id": domain_id, 112 | "record_id": record_id, 113 | "sub_domain": sub_domain, 114 | "record_line": "默认", 115 | "value": ip, 116 | } 117 | 118 | r = requests.post(url, data=data, headers=headers) 119 | if int(r.json()["status"]["code"]) == 1: 120 | logger().info("DDns OK for subdomain [%s]", sub_domain) 121 | else: 122 | logger().error("DDns response for subdomain [%s]: %s", sub_domain, r.text) 123 | retry_list.append((sub_domain, v)) 124 | except Exception, e: 125 | logger().error("DDns Error for subdomain [%s]: %s", sub_domain, e) 126 | retry_list.append((sub_domain, v)) 127 | return retry_list 128 | 129 | def DDns(self, ip): 130 | RETRY_LIMIT = 2 131 | retry_list = self.__DDnsImpl(ip, self.conf["sub_domains"].items()) 132 | retry = 0 133 | while retry_list and retry < RETRY_LIMIT: 134 | retry_list = self.__DDnsImpl(ip, retry_list) 135 | retry += 1 136 | 137 | if not retry_list: 138 | return True 139 | else: 140 | return False 141 | 142 | 143 | if __name__ == '__main__': 144 | opts = getopts() 145 | conf = yaml.load(open(os.path.join(os.path.dirname(os.path.realpath(__file__)), opts.config), "r")) 146 | dnspod = DNSPod(conf) 147 | dnspod.run() 148 | --------------------------------------------------------------------------------