mirror of
https://github.com/woodchen-ink/dnspod-yxip.git
synced 2025-07-18 05:42:08 +08:00
支持IP可用检测, 优化配置方式, 检测时间改为可配置
This commit is contained in:
parent
e9fca3df94
commit
240f7eb3fa
41
.env.example
41
.env.example
@ -1,41 +0,0 @@
|
||||
# 腾讯云API配置
|
||||
TENCENT_SECRET_ID=your_secret_id_here
|
||||
TENCENT_SECRET_KEY=your_secret_key_here
|
||||
|
||||
# 日志级别
|
||||
LOG_LEVEL=INFO
|
||||
|
||||
# 域名配置 - 域名1
|
||||
DOMAIN_1=example1.com
|
||||
SUB_DOMAIN_1=@ # 子域名,@ 表示根域名
|
||||
REMARK_1=优选IP # 记录备注
|
||||
TTL_1=600 # TTL值(秒)
|
||||
IPV4_ENABLED_1=true # 是否启用IPv4记录
|
||||
IPV6_ENABLED_1=true # 是否启用IPv6记录
|
||||
ENABLED_1=true # 是否启用此域名配置
|
||||
|
||||
# 域名配置 - 域名2(可选)
|
||||
DOMAIN_2=example2.com
|
||||
SUB_DOMAIN_2=www
|
||||
REMARK_2=优选IP
|
||||
TTL_2=600
|
||||
IPV4_ENABLED_2=true
|
||||
IPV6_ENABLED_2=true
|
||||
ENABLED_2=true
|
||||
|
||||
# 域名配置 - 域名3(更长的缓存)
|
||||
DOMAIN_3=example3.com
|
||||
SUB_DOMAIN_3=*
|
||||
REMARK_3=CloudFlare优选 # 记录备注
|
||||
TTL_3=1800 # 更长的TTL
|
||||
IPV4_ENABLED_3=true # 启用IPv4
|
||||
IPV6_ENABLED_3=true # 启用IPv6
|
||||
ENABLED_3=true
|
||||
|
||||
# 可以继续添加更多域名配置...
|
||||
# 每个域名可以独立控制:
|
||||
# - 是否启用IPv4 (IPV4_ENABLED_n)
|
||||
# - 是否启用IPv6 (IPV6_ENABLED_n)
|
||||
# - TTL值 (TTL_n)
|
||||
# - 是否启用 (ENABLED_n)
|
||||
# - 记录备注 (REMARK_n)
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
/__pycache__
|
||||
.env
|
||||
logs/
|
||||
config.yaml
|
||||
|
@ -4,6 +4,9 @@ FROM python:3.11-slim
|
||||
ENV TZ=Asia/Shanghai
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
|
||||
# 安装ping工具
|
||||
RUN apt-get update && apt-get install -y iputils-ping && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 复制项目文件
|
||||
|
36
config.example.yaml
Normal file
36
config.example.yaml
Normal file
@ -0,0 +1,36 @@
|
||||
# 腾讯云API配置
|
||||
tencent:
|
||||
secret_id: your_secret_id_here
|
||||
secret_key: your_secret_key_here
|
||||
|
||||
# 日志级别
|
||||
log_level: INFO
|
||||
|
||||
# 更新检查间隔(分钟)
|
||||
check_interval: 15
|
||||
|
||||
# 域名配置列表
|
||||
domains:
|
||||
- domain: example1.com
|
||||
sub_domain: "@" # 子域名,@ 表示根域名
|
||||
remark: 优选IP # 记录备注
|
||||
ttl: 600 # TTL值(秒)
|
||||
ipv4_enabled: true # 是否启用IPv4记录
|
||||
ipv6_enabled: true # 是否启用IPv6记录
|
||||
enabled: true # 是否启用此域名配置
|
||||
|
||||
- domain: example2.com
|
||||
sub_domain: www
|
||||
remark: 优选IP
|
||||
ttl: 600
|
||||
ipv4_enabled: true
|
||||
ipv6_enabled: true
|
||||
enabled: true
|
||||
|
||||
- domain: example3.com
|
||||
sub_domain: "*" # 泛解析
|
||||
remark: CloudFlare优选
|
||||
ttl: 1800
|
||||
ipv4_enabled: true
|
||||
ipv6_enabled: true
|
||||
enabled: true
|
48
config.py
48
config.py
@ -1,38 +1,32 @@
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
from typing import Dict, List
|
||||
import yaml
|
||||
|
||||
# 加载环境变量
|
||||
load_dotenv()
|
||||
# 加载YAML配置
|
||||
def load_config() -> Dict:
|
||||
"""从YAML文件加载配置"""
|
||||
yaml_files = ["config.yaml", "config.example.yaml"]
|
||||
for yaml_file in yaml_files:
|
||||
if os.path.exists(yaml_file):
|
||||
with open(yaml_file, "r", encoding="utf-8") as f:
|
||||
return yaml.safe_load(f)
|
||||
return {}
|
||||
|
||||
# 加载配置
|
||||
config_data = load_config()
|
||||
|
||||
# 腾讯云API配置
|
||||
SECRET_ID = os.getenv("TENCENT_SECRET_ID")
|
||||
SECRET_KEY = os.getenv("TENCENT_SECRET_KEY")
|
||||
SECRET_ID = config_data.get("tencent", {}).get("secret_id")
|
||||
SECRET_KEY = config_data.get("tencent", {}).get("secret_key")
|
||||
|
||||
# API接口配置
|
||||
API_URL = "https://api.vvhan.com/tool/cf_ip"
|
||||
|
||||
# 日志级别
|
||||
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
|
||||
LOG_LEVEL = config_data.get("log_level", "INFO")
|
||||
|
||||
# 更新检查间隔(分钟)
|
||||
check_interval = config_data.get("check_interval", 15)
|
||||
|
||||
# 获取所有域名配置
|
||||
DOMAINS = []
|
||||
index = 1
|
||||
while True:
|
||||
domain = os.getenv(f"DOMAIN_{index}")
|
||||
if not domain:
|
||||
break
|
||||
|
||||
DOMAINS.append(
|
||||
{
|
||||
"domain": domain,
|
||||
"sub_domain": os.getenv(f"SUB_DOMAIN_{index}", "@"),
|
||||
"remark": os.getenv(f"REMARK_{index}", "优选IP"),
|
||||
"ttl": int(os.getenv(f"TTL_{index}", "600")),
|
||||
"ipv4_enabled": os.getenv(f"IPV4_ENABLED_{index}", "true").lower()
|
||||
== "true",
|
||||
"ipv6_enabled": os.getenv(f"IPV6_ENABLED_{index}", "true").lower()
|
||||
== "true",
|
||||
"enabled": os.getenv(f"ENABLED_{index}", "true").lower() == "true",
|
||||
}
|
||||
)
|
||||
index += 1
|
||||
DOMAINS = config_data.get("domains", [])
|
||||
|
@ -4,5 +4,5 @@ services:
|
||||
container_name: dnspod-yxip
|
||||
restart: always
|
||||
volumes:
|
||||
- ./.env:/app/.env:ro # 映射环境变量文件
|
||||
- ./config.yaml:/app/config.yaml:ro # 映射YAML配置文件
|
||||
- ./logs:/app/logs # 映射日志目录
|
160
main.py
160
main.py
@ -9,6 +9,9 @@ from tencentcloud.common import credential
|
||||
from tencentcloud.common.profile.client_profile import ClientProfile
|
||||
from tencentcloud.common.profile.http_profile import HttpProfile
|
||||
from tencentcloud.dnspod.v20210323 import dnspod_client, models
|
||||
import subprocess
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# 配置日志
|
||||
logger.add("logs/dnspod.log", rotation="10 MB", level=config.LOG_LEVEL)
|
||||
@ -30,6 +33,10 @@ class DNSPodManager:
|
||||
self.current_ips = (
|
||||
{}
|
||||
) # 格式: {domain: {'默认': {'A': ip, 'AAAA': ip}, '移动': {...}}}
|
||||
# IP可用性缓存,格式:{ip: {'available': bool, 'last_check': datetime}}
|
||||
self.ip_availability_cache = {}
|
||||
# IP可用性缓存时间设置为检查间隔的1/3
|
||||
self.cache_duration = max(1, config.check_interval // 3)
|
||||
# 初始化时获取所有域名当前的记录
|
||||
self.init_current_records()
|
||||
|
||||
@ -199,33 +206,122 @@ class DNSPodManager:
|
||||
logger.error(f"获取当前记录失败: {str(e)}")
|
||||
return {}
|
||||
|
||||
def update_domain_records(self, domain_config: Dict) -> None:
|
||||
"""更新单个域名的记录"""
|
||||
def check_ip_availability(self, ip: str) -> bool:
|
||||
"""检查IP是否可以ping通"""
|
||||
# 检查缓存
|
||||
now = datetime.now()
|
||||
if ip in self.ip_availability_cache:
|
||||
cache_info = self.ip_availability_cache[ip]
|
||||
if now - cache_info['last_check'] < timedelta(minutes=self.cache_duration):
|
||||
return cache_info['available']
|
||||
|
||||
try:
|
||||
# 根据操作系统选择ping命令参数
|
||||
if os.name == 'nt': # Windows系统
|
||||
ping_args = ['ping', '-n', '1', '-w', '1000', ip]
|
||||
else: # Linux/Unix系统
|
||||
ping_args = ['ping', '-c', '1', '-W', '1', ip]
|
||||
|
||||
result = subprocess.run(ping_args,
|
||||
capture_output=True,
|
||||
text=True)
|
||||
available = result.returncode == 0
|
||||
|
||||
# 更新缓存
|
||||
self.ip_availability_cache[ip] = {
|
||||
'available': available,
|
||||
'last_check': now
|
||||
}
|
||||
|
||||
if not available:
|
||||
logger.warning(f"IP {ip} ping测试失败,命令输出:{result.stdout if result.stdout else result.stderr}")
|
||||
return available
|
||||
except Exception as e:
|
||||
logger.error(f"Ping测试出错 - IP: {ip}, 错误信息: {str(e)}, 命令参数: {ping_args}")
|
||||
return False
|
||||
|
||||
def find_best_available_ip(self, ip_data: Dict, ip_version: str) -> Optional[Tuple[str, int]]:
|
||||
"""查找可用且延迟最低的IP,返回(IP, 延迟)"""
|
||||
if ip_version != "v4": # 只检查IPv4地址
|
||||
return self.find_best_ip(ip_data, ip_version)
|
||||
|
||||
# 按延迟排序所有IP地址,并获取优选IP,检查IP可用性,更新不同线路的记录等功能。
|
||||
all_ips = []
|
||||
for line_key in ["CM", "CU", "CT"]:
|
||||
if line_key in ip_data[ip_version]:
|
||||
all_ips.extend(ip_data[ip_version][line_key])
|
||||
|
||||
# 按延迟排序
|
||||
all_ips.sort(key=lambda x: x["latency"])
|
||||
|
||||
# 查找第一个可用的IP
|
||||
for ip_info in all_ips:
|
||||
if self.check_ip_availability(ip_info["ip"]):
|
||||
return (ip_info["ip"], ip_info["latency"])
|
||||
|
||||
return None
|
||||
|
||||
def find_line_best_available_ip(
|
||||
self, ip_data: Dict, ip_version: str, line_key: str
|
||||
) -> Optional[Tuple[str, int]]:
|
||||
"""查找指定线路可用且延迟最低的IP"""
|
||||
if ip_version != "v4": # 只检查IPv4地址
|
||||
return self.find_line_best_ip(ip_data, ip_version, line_key)
|
||||
|
||||
if line_key not in ip_data[ip_version]:
|
||||
return None
|
||||
|
||||
ips = ip_data[ip_version][line_key]
|
||||
if not ips:
|
||||
return None
|
||||
|
||||
# 按延迟排序
|
||||
ips.sort(key=lambda x: x["latency"])
|
||||
|
||||
# 查找第一个可用的IP
|
||||
for ip_info in ips:
|
||||
if self.check_ip_availability(ip_info["ip"]):
|
||||
return (ip_info["ip"], ip_info["latency"])
|
||||
|
||||
return None
|
||||
def update_domain_records(self, domain_config):
|
||||
"""更新指定域名的记录"""
|
||||
domain = domain_config["domain"]
|
||||
sub_domain = domain_config["sub_domain"]
|
||||
ttl = domain_config["ttl"]
|
||||
remark = domain_config["remark"]
|
||||
|
||||
# 获取优选IP数据
|
||||
ip_data = self.get_optimal_ips()
|
||||
if not ip_data:
|
||||
return
|
||||
ttl = domain_config.get("ttl", 600)
|
||||
remark = domain_config.get("remark")
|
||||
|
||||
# 获取当前记录
|
||||
if domain not in self.current_ips:
|
||||
self.current_ips[domain] = self.get_current_records(domain, sub_domain)
|
||||
current_records = self.current_ips[domain]
|
||||
current_records = self.get_current_records(domain, sub_domain)
|
||||
|
||||
# 获取优选IP
|
||||
ip_data = self.get_optimal_ips()
|
||||
if not ip_data:
|
||||
logger.error(f"无法获取优选IP,跳过更新 {domain}")
|
||||
return
|
||||
|
||||
# 处理IPv4记录
|
||||
if domain_config["ipv4_enabled"] and "v4" in ip_data:
|
||||
# 处理默认线路
|
||||
best_ip = self.find_best_ip(ip_data, "v4")
|
||||
# 获取所有线路的最佳IPv4地址
|
||||
line_mapping = {"移动": "CM", "联通": "CU", "电信": "CT"}
|
||||
best_ips = {}
|
||||
for line, line_key in line_mapping.items():
|
||||
best_ip = self.find_line_best_available_ip(ip_data, "v4", line_key)
|
||||
if best_ip:
|
||||
ip, latency = best_ip
|
||||
best_ips[line] = (ip, latency)
|
||||
|
||||
# 检查是否所有线路的IPv4地址都相同
|
||||
if best_ips:
|
||||
unique_ips = {ip for ip, _ in best_ips.values()}
|
||||
if len(unique_ips) == 1:
|
||||
# 所有线路的IPv4地址相同,只添加默认线路
|
||||
ip = list(unique_ips)[0]
|
||||
min_latency = min(latency for _, latency in best_ips.values())
|
||||
current_ip = current_records.get("默认", {}).get("A")
|
||||
if current_ip != ip:
|
||||
logger.info(
|
||||
f"更新A记录: {domain} - {sub_domain} - 默认 - {ip} (延迟: {latency}ms)"
|
||||
f"更新A记录: {domain} - {sub_domain} - 默认 - {ip} (延迟: {min_latency}ms) [所有线路IP相同]"
|
||||
)
|
||||
if self.update_record(
|
||||
domain, sub_domain, "A", "默认", ip, ttl, remark
|
||||
@ -235,14 +331,9 @@ class DNSPodManager:
|
||||
current_records["默认"] = {}
|
||||
current_records["默认"]["A"] = ip
|
||||
time.sleep(1)
|
||||
|
||||
# 更新其他线路的IPv4记录
|
||||
line_mapping = {"移动": "CM", "联通": "CU", "电信": "CT"}
|
||||
for line, line_key in line_mapping.items():
|
||||
if line_key in ip_data["v4"]:
|
||||
best_ip = self.find_line_best_ip(ip_data, "v4", line_key)
|
||||
if best_ip:
|
||||
ip, latency = best_ip
|
||||
else:
|
||||
# IPv4地址不同,需要为每个线路添加记录
|
||||
for line, (ip, latency) in best_ips.items():
|
||||
current_ip = current_records.get(line, {}).get("A")
|
||||
if current_ip != ip:
|
||||
logger.info(
|
||||
@ -257,13 +348,29 @@ class DNSPodManager:
|
||||
current_records[line]["A"] = ip
|
||||
time.sleep(1)
|
||||
|
||||
# 添加默认线路(使用延迟最低的IP)
|
||||
best_ip = min(best_ips.items(), key=lambda x: x[1][1])
|
||||
ip, latency = best_ip[1]
|
||||
current_ip = current_records.get("默认", {}).get("A")
|
||||
if current_ip != ip:
|
||||
logger.info(
|
||||
f"更新A记录: {domain} - {sub_domain} - 默认 - {ip} (延迟: {latency}ms)"
|
||||
)
|
||||
if self.update_record(
|
||||
domain, sub_domain, "A", "默认", ip, ttl, remark
|
||||
):
|
||||
# 更新成功后更新缓存
|
||||
if "默认" not in current_records:
|
||||
current_records["默认"] = {}
|
||||
current_records["默认"]["A"] = ip
|
||||
time.sleep(1)
|
||||
|
||||
# 处理IPv6记录
|
||||
if domain_config["ipv6_enabled"] and "v6" in ip_data:
|
||||
# 获取所有线路的最佳IPv6地址
|
||||
line_mapping = {"移动": "CM", "联通": "CU", "电信": "CT"}
|
||||
best_ips = {}
|
||||
for line, line_key in line_mapping.items():
|
||||
if line_key in ip_data["v6"]:
|
||||
best_ip = self.find_line_best_ip(ip_data, "v6", line_key)
|
||||
if best_ip:
|
||||
ip, latency = best_ip
|
||||
@ -339,9 +446,8 @@ def main():
|
||||
manager.check_and_update()
|
||||
|
||||
# 每5分钟检查一次是否需要更新
|
||||
schedule.every(5).minutes.do(manager.check_and_update)
|
||||
|
||||
logger.info("程序启动成功,开始监控更新(每5分钟检查一次)...")
|
||||
schedule.every(config.check_interval).minutes.do(manager.check_and_update)
|
||||
logger.info(f"程序启动成功,开始监控更新(每{config.check_interval}分钟检查一次)...")
|
||||
while True:
|
||||
schedule.run_pending()
|
||||
time.sleep(1)
|
||||
|
52
readme.md
52
readme.md
@ -29,23 +29,28 @@
|
||||
|
||||
1. 创建配置文件:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
cp config.example.yaml config.yaml
|
||||
```
|
||||
|
||||
2. 编辑 `.env` 文件,填写您的配置:
|
||||
```ini
|
||||
# DNSPOD API 配置
|
||||
TENCENT_SECRET_ID=your_secret_id_here
|
||||
TENCENT_SECRET_KEY=your_secret_key_here
|
||||
2. 编辑 `config.yaml` 文件,填写您的配置:
|
||||
```yaml
|
||||
# 腾讯云API配置
|
||||
tencent:
|
||||
secret_id: your_secret_id_here
|
||||
secret_key: your_secret_key_here
|
||||
|
||||
# 域名配置 - 域名1
|
||||
DOMAIN_1=example1.com
|
||||
SUB_DOMAIN_1=@ # 子域名,@ 表示根域名
|
||||
REMARK_1=优选IP # 记录备注
|
||||
TTL_1=600 # TTL值(秒)
|
||||
IPV4_ENABLED_1=true # 是否启用IPv4记录
|
||||
IPV6_ENABLED_1=true # 是否启用IPv6记录
|
||||
ENABLED_1=true # 是否启用此域名配置
|
||||
# 日志级别
|
||||
log_level: INFO
|
||||
|
||||
# 域名配置列表
|
||||
domains:
|
||||
- domain: example1.com
|
||||
sub_domain: "@" # 子域名,@ 表示根域名
|
||||
remark: 优选IP # 记录备注
|
||||
ttl: 600 # TTL值(秒)
|
||||
ipv4_enabled: true # 是否启用IPv4记录
|
||||
ipv6_enabled: true # 是否启用IPv6记录
|
||||
enabled: true # 是否启用此域名配置
|
||||
```
|
||||
|
||||
3. 拉取并运行容器:
|
||||
@ -64,8 +69,8 @@ cd dnspod-yxip
|
||||
|
||||
2. 创建并编辑配置文件:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# 编辑 .env 文件
|
||||
cp config.example.yaml config.yaml
|
||||
# 编辑 config 文件
|
||||
```
|
||||
|
||||
3. 构建镜像:
|
||||
@ -81,15 +86,14 @@ docker compose up -d
|
||||
## 配置说明
|
||||
|
||||
每个域名配置包含以下参数:
|
||||
- `DOMAIN_n`: 域名
|
||||
- `SUB_DOMAIN_n`: 子域名,@ 表示根域名,* 表示泛解析
|
||||
- `REMARK_n`: 记录备注
|
||||
- `TTL_n`: TTL值(秒)
|
||||
- `IPV4_ENABLED_n`: 是否启用IPv4记录
|
||||
- `IPV6_ENABLED_n`: 是否启用IPv6记录
|
||||
- `ENABLED_n`: 是否启用此域名配置
|
||||
- `DOMAIN`: 域名
|
||||
- `SUB_DOMAIN`: 子域名,@ 表示根域名,* 表示泛解析
|
||||
- `REMARK`: 记录备注
|
||||
- `TTL`: TTL值(秒)
|
||||
- `IPV4_ENABLED`: 是否启用IPv4记录
|
||||
- `IPV6_ENABLED`: 是否启用IPv6记录
|
||||
- `ENABLED`: 是否启用此域名配置
|
||||
|
||||
其中 n 是域名编号(1, 2, 3...),可以配置任意数量的域名。
|
||||
|
||||
## 日志查看
|
||||
|
||||
|
@ -3,3 +3,4 @@ python-dotenv>=1.0.0
|
||||
schedule>=1.2.1
|
||||
loguru>=0.7.2
|
||||
tencentcloud-sdk-python>=3.0.1000
|
||||
pyyaml>=6.0.2
|
Loading…
x
Reference in New Issue
Block a user