From c0f20a4dce5cb7faed9c8c8ede107bbfca288688 Mon Sep 17 00:00:00 2001 From: ddgth <78608892@qq.com> Date: Tue, 4 Jun 2024 18:21:31 +0800 Subject: [PATCH 1/2] add dddb/cf2dns docker image --- README.md | 70 +++++++++----- docker/.dockerignore | 29 ++++++ docker/Dockerfile | 26 +++++ docker/README.md | 23 +++++ docker/requirements.txt | 6 ++ docker/src/config.ini | 35 +++++++ docker/src/dns/__init__.py | 0 docker/src/dns/aliyun.py | 89 +++++++++++++++++ docker/src/dns/huawei.py | 115 ++++++++++++++++++++++ docker/src/dns/qCloud.py | 129 +++++++++++++++++++++++++ docker/src/log.py | 38 ++++++++ docker/src/main.py | 190 +++++++++++++++++++++++++++++++++++++ 12 files changed, 726 insertions(+), 24 deletions(-) create mode 100644 docker/.dockerignore create mode 100644 docker/Dockerfile create mode 100644 docker/README.md create mode 100644 docker/requirements.txt create mode 100644 docker/src/config.ini create mode 100644 docker/src/dns/__init__.py create mode 100644 docker/src/dns/aliyun.py create mode 100644 docker/src/dns/huawei.py create mode 100644 docker/src/dns/qCloud.py create mode 100644 docker/src/log.py create mode 100644 docker/src/main.py diff --git a/README.md b/README.md index dd92633adc5..843743456c7 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,52 @@ +### 增加dddb/cf2dns docker镜像 --update 2024.6.4 + +> 使用方法 + +1. 拉取cf2dns docker镜像 `docker pull dddb/cf2dns` + +2. 新建cf2dns_docker工作路径 `cd ~ && mkdir -p cf2dns_docker/logs && cd cf2dns_docker` + +3. 下载所需配置文件 `wget --no-check-certificate -qO ./config.ini https://raw.githubusercontent.com/ddgth/cf2dns/master/docker/src/config.ini` + +4. 根据注释修改`config.ini`配置文件 + +5. 运行docker镜像 `docker run -d -v ~/cf2dns_docker/config.ini:/cf2dns/src/config.ini -v ~/cf2dns_docker/logs:/cf2dns/logs dddb/cf2dns` + +6. 查看运行日志 `tail -100f ~/cf2dns_docker/logs/cf2dns.log` + + + +wget --no-check-certificate -qO ./config.ini https://raw.githubusercontent.com/ddgth/cf2dns/master/docker/src/config.ini + +根据注释修改config.ini配置文件 + +docker run -d -v ./cf2dns_docker/src/config.ini:/cf2dns/src/config.ini -v ./cf2dns_docker/logs:/cf2dns/logs cf2dns + ### 修复腾讯云 DNS 无法调用 --update 2023.1.3 + [API 2.0下线通知](https://cloud.tencent.com/document/product/1278/82311) By github@z0z0r4 - + ### 新增支持Actions自选更新V4或V6 ——update 2022.12.19 + > 使用方法 - 1. 修改 **`.github/workflows/run.yml`** +1. 修改 **`.github/workflows/run.yml`** - 2. 新增secret **`DOMAINSV6`** -### 新增支持华为云DNS ——update 2022.10.25 -> 使用方法 +2. 新增secret **`DOMAINSV6`** + + ### 新增支持华为云DNS ——update 2022.10.25 + + > 使用方法 - 1. 安装依赖 **`pip install -r requirements.txt`** +3. 安装依赖 **`pip install -r requirements.txt`** - 2. 修改配置文件 **`DNS_SERVER`** **`SECRETID`** **`SECRETKEY`** **`REGION_HW`** +4. 修改配置文件 **`DNS_SERVER`** **`SECRETID`** **`SECRETKEY`** **`REGION_HW`** ### 新增优选IPv6功能 ——update 2022.07.06 + > 使用方法 -​ 更新代码,修改脚本中的 `TYPE` 参数即可 +​ 更新代码,修改脚本中的 `TYPE` 参数即可 ### 新增默认线路记录 ——update 2021.12.15 @@ -33,7 +62,7 @@ > 实现方式 -​ 之前不管您使用免费的key还是付费的key所筛选出来的Cloudflare IP都是多人共享的,如果其中有人的网站刚好被假墙,而您自选出来的IP刚好和他的相同,那么您的网站也有被假墙的风险,当然我也使用了各种手动去解决这个方法,比如增加接口返回IP数、随机获取优选IP等,但最总还是不能完全杜绝这情况的发现,所以现增加了一个优选IP池,只需在您的key后面加上 **`fgfw`** ,您就会每次执行脚本都能获取到**最新的独享优选IP**,由于这需要消耗更多的服务器硬件和带宽资源,那么每次调用获取最新的独享优选IP时,您只能获取到每个运营商的**2条**优选记录,并且每次调用您将消耗更多的key调用次数,执行频率建议与您DNS服务商的最小TTL保持一直(记得把脚本中的TTL参数也修改了)。 +​ 之前不管您使用免费的key还是付费的key所筛选出来的Cloudflare IP都是多人共享的,如果其中有人的网站刚好被假墙,而您自选出来的IP刚好和他的相同,那么您的网站也有被假墙的风险,当然我也使用了各种手动去解决这个方法,比如增加接口返回IP数、随机获取优选IP等,但最总还是不能完全杜绝这情况的发现,所以现增加了一个优选IP池,只需在您的key后面加上 **`fgfw`** ,您就会每次执行脚本都能获取到**最新的独享优选IP**,由于这需要消耗更多的服务器硬件和带宽资源,那么每次调用获取最新的独享优选IP时,您只能获取到每个运营商的**2条**优选记录,并且每次调用您将消耗更多的key调用次数,执行频率建议与您DNS服务商的最小TTL保持一直(记得把脚本中的TTL参数也修改了)。 > 使用方法: @@ -48,7 +77,6 @@ **详细的使用场景请移步我的[小站](https://blog.hostmonit.com/cloudflare-select-ip-plus/)** - ### 适用人群 1. 小站长,网站经常被打或网站放置在国外需要稳定且速度相对快的CDN @@ -58,9 +86,9 @@ ### 使用方法 > 必要条件: -> +> > ★ Cloudflare自选IP并已接入到DNSPod或阿里云DNS,不知道怎么自选IP可以查看这个[教程](https://blog.hostmonit.com/manually-select-ip/) -> +> > ★ Python3、pip环境 #### 方法一:在自己的VPS或电脑中运行(推荐) @@ -85,8 +113,6 @@ pip install -r requirements.txt python cf2dns.py ``` - - #### 方法二:GitHub Actions 运行 1. 登录[腾讯云后台](https://console.cloud.tencent.com/cam/capi)或者[阿里云后台](https://help.aliyun.com/document_detail/53045.html?spm=a2c4g.11186623.2.11.2c6a2fbdh13O53),获取 SecretId、SecretKey,如果使用阿里云DNS,注意需要添加DNS控制权限**AliyunDNSFullAccess** @@ -94,33 +120,29 @@ python cf2dns.py 2. Fork本项目到自己的仓库![fork.png](https://img.hostmonit.com/images/2020/11/05/fork.png) 3. 进入第二步中Fork的项目,点击Settings->Secrets and variables-> Actions -> New repository secret,分别是DOMAINS,KEY,SECRETID,SECRETKEY。 - + > - DOMAINS 需改域名信息,填写时注意不要有换行 例如:`{"hostmonit.com": {"@": ["CM","CU","CT"], "shop": ["CM", "CU", "CT"], "stock": ["CM","CU","CT"]},"4096.me": {"@": ["CM","CU","CT"], "vv":["CM","CU","CT"]}}` > - DOMAINSV6 如果需要更新AAA解析请增加此secrets,格式同DOMAINS。 > - KEY API密钥,从[商店](https://shop.hostmonit.com)购买KEY,也可以使用这个KEY `o1zrmHAF` ,区别是 `o1zrmHAF` 是历史优选的Cloudflare IP(也可以从这个[网站](https://stock.hostmonit.com/CloudFlareYes)查到IP的信息),而购买的KEY是15分钟内获取到的对各运营商速度最优的的Cloudflare IP > - SECRETID 第一部中从[腾讯云后台](https://console.cloud.tencent.com/cam/capi)或者[阿里云后台](https://help.aliyun.com/document_detail/53045.html?spm=a2c4g.11186623.2.11.2c6a2fbdh13O53),获取到的 `SECRETID ` > - SECRETKEY 第一部中从[腾讯云后台](https://console.cloud.tencent.com/cam/capi)或者[阿里云后台](https://help.aliyun.com/document_detail/53045.html?spm=a2c4g.11186623.2.11.2c6a2fbdh13O53),获取到的 `SECRETKEY` - + ![secret.png](https://img.hostmonit.com/images/2023/03/04/actions.png) 4. 修改您项目中的 `cf2dns_actions.py`文件中的`AFFECT_NUM`和`DNS_SERVER`参数,继续修改`.github/workflows/run.yml` 文件,定时执行的时长(建议15分钟执行一次),最后点击 `start commit` 提交即可在Actions中的build查看到执行情况,如果看到 `cf2dns` 执行日志中有 `CHANGE DNS SUCCESS` 详情输出,即表示运行成功。**需要注意观察下次定时是否能正确运行,有时候GitHub Actions 挺抽风的** - + ![modify.png](https://img.hostmonit.com/images/2020/11/05/modify.png) - ![commit.png](https://img.hostmonit.com/images/2020/11/05/commit.png) - - ![build.png](https://img.hostmonit.com/images/2020/11/05/build.png) ### 免责声明 > 1.网络环境错综复杂,适合我的不一定适合你,所以尽量先尝试免费的KEY或者购买试用版的KEY -> +> > 2.有什么问题和建议请提issue或者Email我,不接受谩骂、扯皮、吐槽 -> +> > 3.为什么收费? 这个标价我也根本不指望赚钱,甚至不够我国内一台VDS的钱。 -> +> > ★ 如果当前DNSPod有移动、联通、电信线路的解析将会覆盖掉 - diff --git a/docker/.dockerignore b/docker/.dockerignore new file mode 100644 index 00000000000..0224f2c0e43 --- /dev/null +++ b/docker/.dockerignore @@ -0,0 +1,29 @@ +**/__pycache__ +**/*venv +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/bin +**/charts +**/docker-compose* +**/compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +*.db +.python-version +LICENSE +README.md diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000000..12730a46033 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,26 @@ +# syntax=docker/dockerfile:1 + +FROM python:3.8-slim-buster + +LABEL maintainer="tongdongdong@outlook.com" + +# Keeps Python from generating .pyc files in the container +ENV PYTHONDONTWRITEBYTECODE=1 + +# Turns off buffering for easier container logging +ENV PYTHONUNBUFFERED=1 + +WORKDIR /cf2dns + +COPY . /cf2dns + +# Install pip requirements +RUN python -m pip install --no-cache-dir --upgrade -r requirements.txt + + + +# Creates a non-root user with an explicit UID and adds permission to access the /app folder +#RUN adduser -u 5678 --disabled-password --gecos "" appuser && chown -R appuser /app +#USER appuser + +CMD ["python3", "src/main.py"] diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 00000000000..d52f5fe01da --- /dev/null +++ b/docker/README.md @@ -0,0 +1,23 @@ +### 增加dddb/cf2dns docker镜像 --update 2024.6.4 + +> 使用方法 + +1. 拉取cf2dns docker镜像 `docker pull dddb/cf2dns` + +2. 新建cf2dns_docker工作路径 `cd ~ && mkdir -p cf2dns_docker/logs && cd cf2dns_docker` + +3. 下载所需配置文件 `wget --no-check-certificate -qO ./config.ini https://raw.githubusercontent.com/ddgth/cf2dns/master/docker/src/config.ini` + +4. 根据注释修改`config.ini`配置文件 + +5. 运行docker镜像 `docker run -d -v ~/cf2dns_docker/config.ini:/cf2dns/src/config.ini -v ~/cf2dns_docker/logs:/cf2dns/logs dddb/cf2dns` + +6. 查看运行日志 `tail -100f ~/cf2dns_docker/logs/cf2dns.log` + + + +wget --no-check-certificate -qO ./config.ini https://raw.githubusercontent.com/ddgth/cf2dns/master/docker/src/config.ini + +根据注释修改config.ini配置文件 + +docker run -d -v ./cf2dns_docker/src/config.ini:/cf2dns/src/config.ini -v ./cf2dns_docker/logs:/cf2dns/logs cf2dns \ No newline at end of file diff --git a/docker/requirements.txt b/docker/requirements.txt new file mode 100644 index 00000000000..a68328888f2 --- /dev/null +++ b/docker/requirements.txt @@ -0,0 +1,6 @@ +aliyun-python-sdk-alidns==2.6.19 +aliyun-python-sdk-core==2.13.29 +huaweicloudsdkcore==3.1.5 +huaweicloudsdkdns==3.1.5 +requests==2.28.1 +tencentcloud-sdk-python==3.0.806 \ No newline at end of file diff --git a/docker/src/config.ini b/docker/src/config.ini new file mode 100644 index 00000000000..3006c8c27d6 --- /dev/null +++ b/docker/src/config.ini @@ -0,0 +1,35 @@ +[DEFAULT] +;也可以从https://shop.hostmonit.com获取 +KEY = o1zrmHAF + +;修改需要优选的域名、子域名和线路信息 示例表示要优选的域名有 hostxxnit.com, shop.hostxxnit.com stock.hostxxnit.com, 484848.xyz, shop.484848.xyz +;优选线路字典对照表 CM:移动 CU:联通 CT:电信 AB:境外 DEF:默认 +DOMAINS = {"hostxxnit.com": {"@": ["CM","CU","CT"], "shop": ["CM", "CU", "CT"], "stock": ["CM","CU","CT"]},"484848.xyz": {"@": ["CM","CU","CT"], "shop": ["CM","CU","CT"]}} + +;解析生效条数 免费版DNSPod相同线路最多支持2条解析 +AFFECT_NUM = 2 + +;DNS服务商 DNSPod: 1, 阿里云: 2, 华为云: 3 +DNS_SERVER = 1 + +;如果使用华为云解析 需要从API凭证-项目列表中获取 REGION +REGION_HW = cn-east-3 + +;如果使用阿里云解析 REGION出现错误再修改 默认不需要修改 https://help.aliyun.com/document_detail/198326.html +REGION_ALI = cn-hongkong + +;解析生效时间,默认为600秒 如果不是DNS付费版用户 不要修改!!! +TTL = 600 + +;v4为筛选出IPv4的IP v6为筛选出IPv6的IP +TYPE = v4 + +;API 密钥 +;腾讯云后台获取 https://console.cloud.tencent.com/cam/capi +;阿里云后台获取 https://help.aliyun.com/document_detail/53045.html?spm=a2c4g.11186623.2.11.2c6a2fbdh13O53 注意需要添加DNS控制权限 AliyunDNSFullAccess +;华为云后台获取 https://support.huaweicloud.com/devg-apisign/api-sign-provide-aksk.html +SECRETID = WTTCWxxxxxxxxxxxxxxxxxxxxx84O0V +SECRETKEY = GXkG6D4X1Nxxxxxxxxxxxxxxxxxxxxx4lRg6lT + +;间隔多长时间执行一次 单位:秒 +TIMES = 300 \ No newline at end of file diff --git a/docker/src/dns/__init__.py b/docker/src/dns/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/docker/src/dns/aliyun.py b/docker/src/dns/aliyun.py new file mode 100644 index 00000000000..24e0a60424d --- /dev/null +++ b/docker/src/dns/aliyun.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Mail: tongdongdong@outlook.com +# Reference: https://help.aliyun.com/document_detail/29776.html?spm=a2c4g.11186623.2.38.3fc33efexrOFkT +# REGION: https://help.aliyun.com/document_detail/198326.html +import json +from aliyunsdkcore import client +from aliyunsdkalidns.request.v20150109 import DescribeDomainRecordsRequest +from aliyunsdkalidns.request.v20150109 import DeleteDomainRecordRequest +from aliyunsdkalidns.request.v20150109 import UpdateDomainRecordRequest +from aliyunsdkalidns.request.v20150109 import AddDomainRecordRequest + + +rc_format = 'json' +class AliApi(): + def __init__(self, ACCESSID, SECRETKEY, REGION='cn-hongkong'): + self.access_key_id = ACCESSID + self.access_key_secret = SECRETKEY + self.region = REGION + + def del_record(self, domain, record): + clt = client.AcsClient(self.access_key_id, self.access_key_secret, self.region) + request = DeleteDomainRecordRequest.DeleteDomainRecordRequest() + request.set_RecordId(record) + request.set_accept_format(rc_format) + result = clt.do_action(request).decode('utf-8') + result = json.JSONDecoder().decode(result) + return result + + def get_record(self, domain, length, sub_domain, record_type): + clt = client.AcsClient(self.access_key_id, self.access_key_secret, self.region) + request = DescribeDomainRecordsRequest.DescribeDomainRecordsRequest() + request.set_DomainName(domain) + request.set_PageSize(length) + request.set_RRKeyWord(sub_domain) + request.set_Type(record_type) + request.set_accept_format(rc_format) + result = clt.do_action(request).decode('utf-8').replace('DomainRecords', 'data', 1).replace('Record', 'records', 1).replace('RecordId', 'id').replace('Value', 'value').replace('Line', 'line').replace('telecom', '电信').replace('unicom', '联通').replace('mobile', '移动').replace('oversea', '境外').replace('default', '默认') + result = json.JSONDecoder().decode(result) + return result + + def create_record(self, domain, sub_domain, value, record_type, line, ttl): + clt = client.AcsClient(self.access_key_id, self.access_key_secret, self.region) + request = AddDomainRecordRequest.AddDomainRecordRequest() + request.set_DomainName(domain) + request.set_RR(sub_domain) + if line == "电信": + line = "telecom" + elif line == "联通": + line = "unicom" + elif line == "移动": + line = "mobile" + elif line == "境外": + line = "oversea" + elif line == "默认": + line = "default" + request.set_Line(line) + request.set_Type(record_type) + request.set_Value(value) + request.set_TTL(ttl) + request.set_accept_format(rc_format) + result = clt.do_action(request).decode('utf-8') + result = json.JSONDecoder().decode(result) + return result + + def change_record(self, domain, record_id, sub_domain, value, record_type, line, ttl): + clt = client.AcsClient(self.access_key_id, self.access_key_secret, self.region) + request = UpdateDomainRecordRequest.UpdateDomainRecordRequest() + request.set_RR(sub_domain) + request.set_RecordId(record_id) + if line == "电信": + line = "telecom" + elif line == "联通": + line = "unicom" + elif line == "移动": + line = "mobile" + elif line == "境外": + line = "oversea" + elif line == "默认": + line = "default" + request.set_Line(line) + request.set_Type(record_type) + request.set_Value(value) + request.set_TTL(ttl) + request.set_accept_format(rc_format) + result = clt.do_action(request).decode('utf-8') + result = json.JSONDecoder().decode(result) + return result + diff --git a/docker/src/dns/huawei.py b/docker/src/dns/huawei.py new file mode 100644 index 00000000000..b1150affc2d --- /dev/null +++ b/docker/src/dns/huawei.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Author/Mail: tongdongdong@outlook.com +# Reference1: https://github.com/huaweicloud/huaweicloud-sdk-python-v3/tree/ff7df92d2a496871c7c2d84dfd2a7f4e2467fff5/huaweicloud-sdk-dns/huaweicloudsdkdns/v2/model +# Reference2: https://support.huaweicloud.com/api-dns/dns_api_65003.html +# REGION: https://developer.huaweicloud.com/endpoint + +from re import sub +from huaweicloudsdkcore.auth.credentials import BasicCredentials +from huaweicloudsdkdns.v2 import * +from huaweicloudsdkdns.v2.region.dns_region import DnsRegion +import json + + +class HuaWeiApi(): + def __init__(self, ACCESSID, SECRETKEY, REGION = 'cn-east-3'): + self.AK = ACCESSID + self.SK = SECRETKEY + self.region = REGION + self.client = DnsClient.new_builder().with_credentials(BasicCredentials(self.AK, self.SK)).with_region(DnsRegion.value_of(self.region)).build() + self.zone_id = self.get_zones() + + def del_record(self, domain, record): + request = DeleteRecordSetsRequest() + request.zone_id = self.zone_id[domain + '.'] + request.recordset_id = record + response = self.client.delete_record_sets(request) + result = json.loads(str(response)) + print(result) + return result + + def get_record(self, domain, length, sub_domain, record_type): + request = ListRecordSetsWithLineRequest() + request.limit = length + request.type = record_type + if sub_domain == '@': + request.name = domain + "." + else: + request.name = sub_domain + '.' + domain + "." + response = self.client.list_record_sets_with_line(request) + data = json.loads(str(response)) + result = {} + records_temp = [] + for record in data['recordsets']: + if (sub_domain == '@' and domain + "." == record['name']) or (sub_domain + '.' + domain + "." == record['name']): + record['line'] = self.line_format(record['line']) + record['value'] = '1.1.1.1' + records_temp.append(record) + result['data'] = {'records': records_temp} + return result + + def create_record(self, domain, sub_domain, value, record_type, line, ttl): + request = CreateRecordSetWithLineRequest() + request.zone_id = self.zone_id[domain + '.'] + if sub_domain == '@': + name = domain + "." + else: + name = sub_domain + '.' + domain + "." + request.body = CreateRecordSetWithLineReq( + type = record_type, + name = name, + ttl = ttl, + weight = 1, + records = [value], + line = self.line_format(line) + ) + response = self.client.create_record_set_with_line(request) + result = json.loads(str(response)) + return result + + def change_record(self, domain, record_id, sub_domain, value, record_type, line, ttl): + request = UpdateRecordSetRequest() + request.zone_id = self.zone_id[domain + '.'] + request.recordset_id = record_id + if sub_domain == '@': + name = domain + "." + else: + name = sub_domain + '.' + domain + "." + request.body = UpdateRecordSetReq( + name = name, + type = record_type, + ttl = ttl, + records=[value] + ) + response = self.client.update_record_set(request) + result = json.loads(str(response)) + return result + + def get_zones(self): + request = ListPublicZonesRequest() + response = self.client.list_public_zones(request) + result = json.loads(str(response)) + zone_id = {} + for zone in result['zones']: + zone_id[zone['name']] = zone['id'] + return zone_id + + def line_format(self, line): + lines = { + '默认' : 'default_view', + '电信' : 'Dianxin', + '联通' : 'Liantong', + '移动' : 'Yidong', + '境外' : 'Abroad', + 'default_view' : '默认', + 'Dianxin' : '电信', + 'Liantong' : '联通', + 'Yidong' : '移动', + 'Abroad' : '境外', + } + return lines.get(line, None) + +if __name__ == '__main__': + hw_api = HuaWeiApi('WTTCWxxxxxxxxx84O0V', 'GXkG6D4X1Nxxxxxxxxxxxxxxxxxxxxx4lRg6lT') + print(hw_api.get_record('xxxx.com', 100, '@', 'A')) diff --git a/docker/src/dns/qCloud.py b/docker/src/dns/qCloud.py new file mode 100644 index 00000000000..d8720137fd5 --- /dev/null +++ b/docker/src/dns/qCloud.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Mail: tongdongdong@outlook.com +# Reference: https://cloud.tencent.com/document/product/302/8517 +# QcloudApiv3 DNSPod 的 API 更新了 By github@z0z0r4 + +import json +from tencentcloud.common import credential +from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException +from tencentcloud.dnspod.v20210323 import dnspod_client, models + +class QcloudApiv3(): + def __init__(self, SECRETID, SECRETKEY): + self.SecretId = SECRETID + self.secretKey = SECRETKEY + self.cred = credential.Credential(SECRETID, SECRETKEY) + + def del_record(self, domain: str, record_id: int): + client = dnspod_client.DnspodClient(self.cred, "") + req_model = models.DeleteRecordRequest() + params = { + "Domain": domain, + "RecordId": record_id + } + req_model.from_json_string(json.dumps(params)) + + + resp = client.DeleteRecord(req_model) + resp = json.loads(resp.to_json_string()) + resp["code"] = 0 + resp["message"] = "None" + return resp + + def get_record(self, domain: str, length: int, sub_domain: str, record_type: str): + def format_record(record: dict): + new_record = {} + record["id"] = record['RecordId'] + for key in record: + new_record[key.lower()] = record[key] + return new_record + try: + client = dnspod_client.DnspodClient(self.cred, "") + + req_model = models.DescribeRecordListRequest() + params = { + "Domain": domain, + "Subdomain": sub_domain, + "RecordType": record_type, + "Limit": length + } + req_model.from_json_string(json.dumps(params)) + + + resp = client.DescribeRecordList(req_model) + resp = json.loads(resp.to_json_string()) + temp_resp = {} + temp_resp["code"] = 0 + temp_resp["data"] = {} + temp_resp["data"]["records"] = [] + for record in resp['RecordList']: + temp_resp["data"]["records"].append(format_record(record)) + temp_resp["data"]["domain"] = {} + temp_resp["data"]["domain"]["grade"] = self.get_domain(domain)["DomainInfo"]["Grade"] # DP_Free + return temp_resp + except TencentCloudSDKException: + # 构造空响应... + temp_resp = {} + temp_resp["code"] = 0 + temp_resp["data"] = {} + temp_resp["data"]["records"] = [] + temp_resp["data"]["domain"] = {} + temp_resp["data"]["domain"]["grade"] = self.get_domain(domain)["DomainInfo"]["Grade"] # DP_Free + return temp_resp + + def create_record(self, domain: str, sub_domain: str, value: int, record_type: str = "A", line: str = "默认", ttl: int = 600): + client = dnspod_client.DnspodClient(self.cred, "") + req = models.CreateRecordRequest() + params = { + "Domain": domain, + "SubDomain": sub_domain, + "RecordType": record_type, + "RecordLine": line, + "Value": value, + "ttl": ttl + } + req.from_json_string(json.dumps(params)) + + # 返回的resp是一个CreateRecordResponse的实例,与请求对象对应 + resp = client.CreateRecord(req) + resp = json.loads(resp.to_json_string()) + resp["code"] = 0 + resp["message"] = "None" + return resp + + def change_record(self, domain: str, record_id: int, sub_domain: str, value: str, record_type: str = "A", line: str = "默认", ttl: int = 600): + client = dnspod_client.DnspodClient(self.cred, "") + req = models.ModifyRecordRequest() + params = { + "Domain": domain, + "SubDomain": sub_domain, + "RecordType": record_type, + "RecordLine": line, + "Value": value, + "TTL": ttl, + "RecordId": record_id + } + req.from_json_string(json.dumps(params)) + + # 返回的resp是一个ChangeRecordResponse的实例,与请求对象对应 + resp = client.ModifyRecord(req) + resp = json.loads(resp.to_json_string()) + resp["code"] = 0 + resp["message"] = "None" + return resp + + def get_domain(self, domain: str): + client = dnspod_client.DnspodClient(self.cred, "") + + # 实例化一个请求对象,每个接口都会对应一个request对象 + req = models.DescribeDomainRequest() + params = { + "Domain": domain + } + req.from_json_string(json.dumps(params)) + + # 返回的resp是一个DescribeDomainResponse的实例,与请求对象对应 + resp = client.DescribeDomain(req) + resp = json.loads(resp.to_json_string()) + return resp \ No newline at end of file diff --git a/docker/src/log.py b/docker/src/log.py new file mode 100644 index 00000000000..acb3fcd5ce7 --- /dev/null +++ b/docker/src/log.py @@ -0,0 +1,38 @@ +import logging +from logging import handlers + +class Logger(object): + level_relations = { + 'debug':logging.DEBUG, + 'info':logging.INFO, + 'warning':logging.WARNING, + 'error':logging.ERROR, + 'crit':logging.CRITICAL + }#日志级别关系映射 + + def __init__(self,filename,level='info',when='D',backCount=3,fmt='%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s'): + self.logger = logging.getLogger(filename) + format_str = logging.Formatter(fmt)#设置日志格式 + self.logger.setLevel(self.level_relations.get(level))#设置日志级别 + sh = logging.StreamHandler()#往屏幕上输出 + sh.setFormatter(format_str) #设置屏幕上显示的格式 + th = handlers.TimedRotatingFileHandler(filename=filename,when=when,backupCount=backCount,encoding='utf-8')#往文件里写入#指定间隔时间自动生成文件的处理器 + #实例化TimedRotatingFileHandler + #interval是时间间隔,backupCount是备份文件的个数,如果超过这个个数,就会自动删除,when是间隔的时间单位,单位有以下几种: + # S 秒 + # M 分 + # H 小时、 + # D 天、 + # W 每星期(interval==0时代表星期一) + # midnight 每天凌晨 + th.setFormatter(format_str)#设置文件里写入的格式 + self.logger.addHandler(sh) #把对象加到logger里 + self.logger.addHandler(th) +if __name__ == '__main__': + log = Logger('monitor.log',level='debug') + log.logger.debug('debug') + log.logger.info('info') + log.logger.warning('警告') + log.logger.error('报错') + log.logger.critical('严重') + Logger('error.log', level='error').logger.error('error') \ No newline at end of file diff --git a/docker/src/main.py b/docker/src/main.py new file mode 100644 index 00000000000..11a94d9e421 --- /dev/null +++ b/docker/src/main.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Mail: tongdongdong@outlook.com +import random +import time +import requests +from dns.qCloud import QcloudApiv3 # QcloudApiv3 DNSPod 的 API 更新了 By github@z0z0r4 +from dns.aliyun import AliApi +from dns.huawei import HuaWeiApi +from log import Logger +import traceback +import configparser +import json + +file = 'src/config.ini' +# 创建配置文件对象 +con = configparser.ConfigParser() + +# 读取文件 +con.read(file, encoding='utf-8') +# 获取特定section +items = con.items('DEFAULT') # 返回结果为元组 + +# 可以通过dict方法转换为字典 +items = dict(items) + +KEY = items['key'] +DOMAINS = json.loads(items['domains']) +AFFECT_NUM = int(items['affect_num']) +DNS_SERVER = int(items['dns_server']) +REGION_HW = items['region_hw'] +REGION_ALI = items['region_ali'] +TTL = int(items['ttl']) +TYPE = items['type'] +SECRETID = items['secretid'] +SECRETKEY = items['secretkey'] +TIMES = int(items['times']) + + +log_cf2dns = Logger('logs/cf2dns.log', level='debug') + +def get_optimization_ip(): + try: + headers = headers = {'Content-Type': 'application/json'} + data = {"key": KEY, "type": TYPE} + response = requests.post('https://api.hostmonit.com/get_optimization_ip', json=data, headers=headers) + if response.status_code == 200: + return response.json() + else: + log_cf2dns.logger.error("CHANGE OPTIMIZATION IP ERROR: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----MESSAGE: REQUEST STATUS CODE IS NOT 200") + return None + except Exception as e: + log_cf2dns.logger.error("CHANGE OPTIMIZATION IP ERROR: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----MESSAGE: " + str(e)) + return None + +def changeDNS(line, s_info, c_info, domain, sub_domain, cloud): + global AFFECT_NUM, TYPE + if TYPE == 'v6': + recordType = "AAAA" + else: + recordType = "A" + + lines = {"CM": "移动", "CU": "联通", "CT": "电信", "AB": "境外", "DEF": "默认"} + line = lines[line] + + try: + create_num = AFFECT_NUM - len(s_info) + if create_num == 0: + for info in s_info: + if len(c_info) == 0: + break + cf_ip = c_info.pop(random.randint(0,len(c_info)-1))["ip"] + if cf_ip in str(s_info): + continue + ret = cloud.change_record(domain, info["recordId"], sub_domain, cf_ip, recordType, line, TTL) + if(DNS_SERVER != 1 or ret["code"] == 0): + log_cf2dns.logger.info("CHANGE DNS SUCCESS: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----DOMAIN: " + domain + "----SUBDOMAIN: " + sub_domain + "----RECORDLINE: "+line+"----RECORDID: " + str(info["recordId"]) + "----VALUE: " + cf_ip ) + else: + log_cf2dns.logger.error("CHANGE DNS ERROR: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----DOMAIN: " + domain + "----SUBDOMAIN: " + sub_domain + "----RECORDLINE: "+line+"----RECORDID: " + str(info["recordId"]) + "----VALUE: " + cf_ip + "----MESSAGE: " + ret["message"] ) + elif create_num > 0: + for i in range(create_num): + if len(c_info) == 0: + break + cf_ip = c_info.pop(random.randint(0,len(c_info)-1))["ip"] + if cf_ip in str(s_info): + continue + ret = cloud.create_record(domain, sub_domain, cf_ip, recordType, line, TTL) + if(DNS_SERVER != 1 or ret["code"] == 0): + log_cf2dns.logger.info("CREATE DNS SUCCESS: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----DOMAIN: " + domain + "----SUBDOMAIN: " + sub_domain + "----RECORDLINE: "+line+"----VALUE: " + cf_ip ) + else: + log_cf2dns.logger.error("CREATE DNS ERROR: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----DOMAIN: " + domain + "----SUBDOMAIN: " + sub_domain + "----RECORDLINE: "+line+"----RECORDID: " + str(info["recordId"]) + "----VALUE: " + cf_ip + "----MESSAGE: " + ret["message"] ) + else: + for info in s_info: + if create_num == 0 or len(c_info) == 0: + break + cf_ip = c_info.pop(random.randint(0,len(c_info)-1))["ip"] + if cf_ip in str(s_info): + create_num += 1 + continue + ret = cloud.change_record(domain, info["recordId"], sub_domain, cf_ip, recordType, line, TTL) + if(DNS_SERVER != 1 or ret["code"] == 0): + log_cf2dns.logger.info("CHANGE DNS SUCCESS: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----DOMAIN: " + domain + "----SUBDOMAIN: " + sub_domain + "----RECORDLINE: "+line+"----RECORDID: " + str(info["recordId"]) + "----VALUE: " + cf_ip ) + else: + log_cf2dns.logger.error("CHANGE DNS ERROR: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----DOMAIN: " + domain + "----SUBDOMAIN: " + sub_domain + "----RECORDLINE: "+line+"----RECORDID: " + str(info["recordId"]) + "----VALUE: " + cf_ip + "----MESSAGE: " + ret["message"] ) + create_num += 1 + except Exception as e: + log_cf2dns.logger.error("CHANGE DNS ERROR: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----MESSAGE: " + str(e)) + +def main(cloud): + global AFFECT_NUM, TYPE, DOMAINS + if TYPE == 'v6': + recordType = "AAAA" + else: + recordType = "A" + if len(DOMAINS) > 0: + try: + cfips = get_optimization_ip() + if cfips == None or cfips["code"] != 200: + log_cf2dns.logger.error("GET CLOUDFLARE IP ERROR: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----MESSAGE: " + str(cfips["info"])) + return + cf_cmips = cfips["info"]["CM"] + cf_cuips = cfips["info"]["CU"] + cf_ctips = cfips["info"]["CT"] + for domain, sub_domains in DOMAINS.items(): + for sub_domain, lines in sub_domains.items(): + temp_cf_cmips = cf_cmips.copy() + temp_cf_cuips = cf_cuips.copy() + temp_cf_ctips = cf_ctips.copy() + temp_cf_abips = cf_ctips.copy() + temp_cf_defips = cf_ctips.copy() + if DNS_SERVER == 1: + ret = cloud.get_record(domain, 20, sub_domain, "CNAME") + if ret["code"] == 0: + for record in ret["data"]["records"]: + if record["line"] == "移动" or record["line"] == "联通" or record["line"] == "电信": + retMsg = cloud.del_record(domain, record["id"]) + if(retMsg["code"] == 0): + log_cf2dns.logger.info("DELETE DNS SUCCESS: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----DOMAIN: " + domain + "----SUBDOMAIN: " + sub_domain + "----RECORDLINE: "+record["line"] ) + else: + log_cf2dns.logger.error("DELETE DNS ERROR: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----DOMAIN: " + domain + "----SUBDOMAIN: " + sub_domain + "----RECORDLINE: "+record["line"] + "----MESSAGE: " + retMsg["message"] ) + ret = cloud.get_record(domain, 100, sub_domain, recordType) + if DNS_SERVER != 1 or ret["code"] == 0 : + if DNS_SERVER == 1 and "Free" in ret["data"]["domain"]["grade"] and AFFECT_NUM > 2: + AFFECT_NUM = 2 + cm_info = [] + cu_info = [] + ct_info = [] + ab_info = [] + def_info = [] + for record in ret["data"]["records"]: + info = {} + info["recordId"] = record["id"] + info["value"] = record["value"] + if record["line"] == "移动": + cm_info.append(info) + elif record["line"] == "联通": + cu_info.append(info) + elif record["line"] == "电信": + ct_info.append(info) + elif record["line"] == "境外": + ab_info.append(info) + elif record["line"] == "默认": + def_info.append(info) + for line in lines: + if line == "CM": + changeDNS("CM", cm_info, temp_cf_cmips, domain, sub_domain, cloud) + elif line == "CU": + changeDNS("CU", cu_info, temp_cf_cuips, domain, sub_domain, cloud) + elif line == "CT": + changeDNS("CT", ct_info, temp_cf_ctips, domain, sub_domain, cloud) + elif line == "AB": + changeDNS("AB", ab_info, temp_cf_abips, domain, sub_domain, cloud) + elif line == "DEF": + changeDNS("DEF", def_info, temp_cf_defips, domain, sub_domain, cloud) + except Exception as e: + traceback.print_exc() + log_cf2dns.logger.error("CHANGE DNS ERROR: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----MESSAGE: " + str(e)) + +if __name__ == '__main__': + cloud = None + if DNS_SERVER == 1: + cloud = QcloudApiv3(SECRETID, SECRETKEY) + elif DNS_SERVER == 2: + cloud = AliApi(SECRETID, SECRETKEY, REGION_ALI) + elif DNS_SERVER == 3: + cloud = HuaWeiApi(SECRETID, SECRETKEY, REGION_HW) + while(True): + main(cloud) + log_cf2dns.logger.error("CHANGE DNS SUCCESS: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----MESSAGE: ALL DONE" ) + time.sleep(TIMES) \ No newline at end of file From ed96f91e75bbde74f3a724913b9c7e39103909bf Mon Sep 17 00:00:00 2001 From: ddgth <78608892@qq.com> Date: Tue, 4 Jun 2024 18:25:32 +0800 Subject: [PATCH 2/2] update readme --- README.md | 10 ++-------- docker/README.md | 10 +--------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 843743456c7..22d8c6a9ede 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,6 @@ -wget --no-check-certificate -qO ./config.ini https://raw.githubusercontent.com/ddgth/cf2dns/master/docker/src/config.ini - -根据注释修改config.ini配置文件 - -docker run -d -v ./cf2dns_docker/src/config.ini:/cf2dns/src/config.ini -v ./cf2dns_docker/logs:/cf2dns/logs cf2dns - ### 修复腾讯云 DNS 无法调用 --update 2023.1.3 [API 2.0下线通知](https://cloud.tencent.com/document/product/1278/82311) By github@z0z0r4 @@ -132,9 +126,9 @@ python cf2dns.py 4. 修改您项目中的 `cf2dns_actions.py`文件中的`AFFECT_NUM`和`DNS_SERVER`参数,继续修改`.github/workflows/run.yml` 文件,定时执行的时长(建议15分钟执行一次),最后点击 `start commit` 提交即可在Actions中的build查看到执行情况,如果看到 `cf2dns` 执行日志中有 `CHANGE DNS SUCCESS` 详情输出,即表示运行成功。**需要注意观察下次定时是否能正确运行,有时候GitHub Actions 挺抽风的** ![modify.png](https://img.hostmonit.com/images/2020/11/05/modify.png) - + ![commit.png](https://img.hostmonit.com/images/2020/11/05/commit.png) - + ![build.png](https://img.hostmonit.com/images/2020/11/05/build.png) ### 免责声明 diff --git a/docker/README.md b/docker/README.md index d52f5fe01da..c4de84c3c03 100644 --- a/docker/README.md +++ b/docker/README.md @@ -12,12 +12,4 @@ 5. 运行docker镜像 `docker run -d -v ~/cf2dns_docker/config.ini:/cf2dns/src/config.ini -v ~/cf2dns_docker/logs:/cf2dns/logs dddb/cf2dns` -6. 查看运行日志 `tail -100f ~/cf2dns_docker/logs/cf2dns.log` - - - -wget --no-check-certificate -qO ./config.ini https://raw.githubusercontent.com/ddgth/cf2dns/master/docker/src/config.ini - -根据注释修改config.ini配置文件 - -docker run -d -v ./cf2dns_docker/src/config.ini:/cf2dns/src/config.ini -v ./cf2dns_docker/logs:/cf2dns/logs cf2dns \ No newline at end of file +6. 查看运行日志 `tail -100f ~/cf2dns_docker/logs/cf2dns.log` \ No newline at end of file