支持IP可用检测, 优化配置方式, 检测时间改为可配置

This commit is contained in:
wood chen 2025-02-22 23:51:33 +08:00
parent e9fca3df94
commit 240f7eb3fa
9 changed files with 243 additions and 139 deletions

View File

@ -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
View File

@ -1,3 +1,4 @@
/__pycache__
.env
logs/
config.yaml

View File

@ -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
View 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

View File

@ -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", [])

View File

@ -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
View File

@ -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)

View File

@ -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...),可以配置任意数量的域名。
## 日志查看

View File

@ -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