commit 7abea390ad832f1709fde265543d9dd45c98fb20 Author: OpenClaw Date: Sun Mar 1 11:44:05 2026 +0800 Initial commit: Tencent DNSPod DNS deployment skill - Support for single record deployment - Batch deployment from JSON config - Service quick deployment (Web/API/CDN) - .env file support for secure credentials - Complete documentation diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..933ab1f --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +# 腾讯云DNSPod API密钥配置 +# 获取方式: https://console.cloud.tencent.com/cam/capi + +TENCENT_SECRET_ID=你的SecretId +TENCENT_SECRET_KEY=你的SecretKey diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..13e1bf1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python + +# 虚拟环境 +venv/ +env/ +ENV/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# 敏感信息 +.env +*.key + +# 日志 +*.log + +# 临时文件 +.DS_Store +Thumbs.db diff --git a/ENV_SETUP.md b/ENV_SETUP.md new file mode 100644 index 0000000..4ddf44e --- /dev/null +++ b/ENV_SETUP.md @@ -0,0 +1,200 @@ +# 环境变量配置说明 + +## .env 文件(推荐方式) + +`.env` 文件用于存储敏感配置信息,如API密钥。该文件**不会被提交到Git**(已添加到 `.gitignore`)。 + +### 配置步骤 + +1. **复制示例配置** + ```bash + cp .env.example .env + ``` + +2. **编辑 .env 文件** + ```bash + # 使用你喜欢的编辑器 + vim .env + # 或 + nano .env + # 或 + code .env + ``` + +3. **填入你的腾讯云API密钥** + ```bash + TENCENT_SECRET_ID=你的SecretId + TENCENT_SECRET_KEY=你的SecretKey + ``` + +4. **保存文件** + +### 获取API密钥 + +访问腾讯云控制台: https://console.cloud.tencent.com/cam/capi + +- 点击「新建密钥」或查看现有密钥 +- 复制 `SecretId` 和 `SecretKey` +- ⚠️ **重要:** SecretKey只在创建时显示一次,请妥善保存! + +## 环境变量方式 + +如果不使用 `.env` 文件,可以直接设置系统环境变量。 + +### Linux/Mac + +**临时设置(当前会话有效):** +```bash +export TENCENT_SECRET_ID="你的SecretId" +export TENCENT_SECRET_KEY="你的SecretKey" +``` + +**永久设置(添加到 ~/.bashrc 或 ~/.zshrc):** +```bash +echo 'export TENCENT_SECRET_ID="你的SecretId"' >> ~/.bashrc +echo 'export TENCENT_SECRET_KEY="你的SecretKey"' >> ~/.bashrc +source ~/.bashrc +``` + +### Windows + +**PowerShell临时设置:** +```powershell +$env:TENCENT_SECRET_ID="你的SecretId" +$env:TENCENT_SECRET_KEY="你的SecretKey" +``` + +**PowerShell永久设置:** +```powershell +[System.Environment]::SetEnvironmentVariable('TENCENT_SECRET_ID', '你的SecretId', 'User') +[System.Environment]::SetEnvironmentVariable('TENCENT_SECRET_KEY', '你的SecretKey', 'User') +``` + +**CMD临时设置:** +```cmd +set TENCENT_SECRET_ID=你的SecretId +set TENCENT_SECRET_KEY=你的SecretKey +``` + +## 验证配置 + +### 方式1: 查看 .env 文件 +```bash +cat .env +``` + +### 方式2: 查看环境变量 +```bash +# Linux/Mac +echo $TENCENT_SECRET_ID + +# Windows PowerShell +echo $env:TENCENT_SECRET_ID + +# Windows CMD +echo %TENCENT_SECRET_ID% +``` + +### 方式3: 测试连接 +```bash +python scripts/list_records.py --domain example.com +``` + +如果成功,将显示域名记录列表(可能为空)。 + +## 安全建议 + +### ✅ 推荐做法 + +1. **使用 .env 文件** - 方便管理,不会被Git跟踪 +2. **使用 .env.example** - 提供配置模板,不含真实密钥 +3. **定期轮换密钥** - 定期更换API密钥提高安全性 +4. **使用子账号** - 创建独立的子账号,只授予必要权限 +5. **设置IP白名单** - 在腾讯云CAM中限制API调用来源IP + +### ❌ 避免做法 + +1. ❌ 不要将密钥硬编码在代码中 +2. ❌ 不要在Git仓库中提交 .env 文件 +3. ❌ 不要在生产环境使用默认密钥 +4. ❌ 不要在公开场合分享密钥 +5. ❌ 不要使用弱密钥或重复使用密钥 + +## 常见问题 + +### Q1: .env 文件不生效? + +**原因:** 可能是文件权限问题或路径错误。 + +**解决:** +```bash +# 确认文件存在 +ls -la .env + +# 确认文件内容 +cat .env + +# 确认文件格式(无BOM,Unix换行符) +file .env +``` + +### Q2: 还是提示找不到密钥? + +**解决:** 检查 `.env` 文件是否在项目根目录: +```bash +pwd # 应该在 tencent-dnspod 目录 +ls .env # 应该能看到 .env 文件 +``` + +### Q3: Git提交时意外包含了 .env? + +**解决:** +```bash +# 1. 从Git中移除(但保留本地文件) +git rm --cached .env + +# 2. 确认 .gitignore 包含 .env +echo ".env" >> .gitignore + +# 3. 提交修改 +git add .gitignore +git commit -m "Remove .env from tracking" + +# 4. 更换已泄露的密钥! +# 访问: https://console.cloud.tencent.com/cam/capi +``` + +### Q4: 多个项目需要不同的密钥? + +**解决:** 每个项目使用独立的 .env 文件: +```bash +# 项目A +cd /path/to/project-a +echo "TENCENT_SECRET_ID=项目A的ID" > .env + +# 项目B +cd /path/to/project-b +echo "TENCENT_SECRET_ID=项目B的ID" > .env +``` + +## 权限管理建议 + +### 创建专用子账号 + +1. 访问: https://console.cloud.tencent.com/cam +2. 创建子账号,只授予DNSPod权限 +3. 为子账号创建API密钥 +4. 使用子账号密钥而非主账号密钥 + +### 最小权限原则 + +只授予必要的权限: +- DNSPod只读权限(查询记录) +- DNSPod读写权限(管理记录) +- 避免授予所有产品的访问权限 + +## 参考文档 + +- **腾讯云访问管理(CAM):** https://cloud.tencent.com/document/product/598 +- **API密钥管理:** https://console.cloud.tencent.com/cam/capi +- **子账号权限:** https://console.cloud.tencent.com/cam/user diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..432fd16 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,339 @@ +# DNSPod Skill 安装指南 + +## 概述 + +本Skill已针对**域名快速部署**场景优化,提供以下核心功能: + +✅ **单条记录快速部署** - 秒级添加/更新DNS记录 +✅ **批量部署** - 从JSON配置文件一键部署多条记录 +✅ **服务快速部署** - 一键配置Web/API/CDN服务 +✅ **自动化友好** - 支持DevOps CI/CD集成 + +## 核心脚本 + +### 1. `deploy_record.py` - 单条记录部署 +```bash +# 添加A记录 +python scripts/deploy_record.py \ + --domain example.com \ + --subdomain www \ + --type A \ + --value 1.2.3.4 + +# 强制更新 +python scripts/deploy_record.py \ + --domain example.com \ + --subdomain www \ + --type A \ + --value 1.2.3.5 \ + --force +``` + +### 2. `batch_deploy.py` - 批量部署 +```bash +# 从配置文件批量部署 +python scripts/batch_deploy.py \ + --domain example.com \ + --config examples/dns-config.json +``` + +### 3. `deploy_service.py` - 服务快速部署 +```bash +# 部署Web服务(@ + www) +python scripts/deploy_service.py \ + --domain example.com \ + --service web \ + --ip 1.2.3.4 + +# 部署API服务 +python scripts/deploy_service.py \ + --domain example.com \ + --service api \ + --ip 1.2.3.5 +``` + +### 4. `list_records.py` - 查询记录 +```bash +python scripts/list_records.py --domain example.com +``` + +### 5. `delete_record.py` - 删除记录 +```bash +python scripts/delete_record.py \ + --domain example.com \ + --subdomain www \ + --type A +``` + +## 安装步骤 + +### 步骤1: 获取腾讯云API密钥 + +1. 访问: https://console.cloud.tencent.com/cam/capi +2. 创建或查看密钥(包含 `SecretId` 和 `SecretKey`) +3. ⚠️ **重要:** SecretKey只在创建时显示,请立即保存! + +### 步骤2: 配置API密钥 + +有两种方式配置API密钥: + +#### 方式1: 使用 .env 文件(推荐,更安全) + +```bash +# 1. 复制示例配置 +cp .env.example .env + +# 2. 编辑 .env 文件,填入你的密钥 +# TENCENT_SECRET_ID=你的SecretId +# TENCENT_SECRET_KEY=你的SecretKey + +# 3. 确保 .env 文件不会被提交到Git(已添加到 .gitignore) +``` + +#### 方式2: 设置环境变量 + +**Linux/Mac (添加到 ~/.bashrc 或 ~/.zshrc):** +```bash +export TENCENT_SECRET_ID="你的SecretId" +export TENCENT_SECRET_KEY="你的SecretKey" +source ~/.bashrc # 重新加载 +``` + +**Windows PowerShell:** +```powershell +[System.Environment]::SetEnvironmentVariable('TENCENT_SECRET_ID', '你的SecretId', 'User') +[System.Environment]::SetEnvironmentVariable('TENCENT_SECRET_KEY', '你的SecretKey', 'User') +``` + +**验证配置:** +```bash +# 方式1: 查看 .env 文件 +cat .env + +# 方式2: 查看环境变量 +# Linux/Mac +echo $TENCENT_SECRET_ID + +# Windows PowerShell +echo $env:TENCENT_SECRET_ID +``` + +### 步骤3: 安装Python依赖 + +```bash +cd tencent-dnspod +pip install -r requirements.txt +``` + +依赖项: +- `requests>=2.28.0` (HTTP请求库) + +### 步骤4: 测试连接 + +```bash +python scripts/list_records.py --domain example.com +``` + +如果成功,将显示域名下的DNS记录列表(可能为空)。 + +## 典型使用场景 + +### 场景1: 新服务快速上线 + +**需求:** 将新Web服务部署到 1.2.3.4 + +```bash +# 一键部署主域名 + www +python scripts/deploy_service.py \ + --domain example.com \ + --service web \ + --ip 1.2.3.4 +``` + +**结果:** +- ✅ example.com → 1.2.3.4 +- ✅ www.example.com → 1.2.3.4 + +### 场景2: 多环境部署 + +**配置文件:** `dns-config.json` +```json +{ + "records": [ + {"subdomain": "www", "type": "A", "value": "1.2.3.4", "line": "默认", "remark": "生产"}, + {"subdomain": "dev", "type": "A", "value": "1.2.3.5", "line": "默认", "remark": "开发"}, + {"subdomain": "test", "type": "A", "value": "1.2.3.6", "line": "默认", "remark": "测试"} + ] +} +``` + +**批量部署:** +```bash +python scripts/batch_deploy.py \ + --domain example.com \ + --config dns-config.json +``` + +### 场景3: CDN加速配置 + +```bash +# 添加CNAME记录指向CDN +python scripts/deploy_record.py \ + --domain example.com \ + --subdomain cdn \ + --type CNAME \ + --value cdn.example.com.cdn.dnsv1.com +``` + +### 场景4: 智能DNS(电信/联通线路) + +```bash +# 电信用户访问1.2.3.4 +python scripts/deploy_record.py \ + --domain example.com \ + --subdomain www \ + --type A \ + --value 1.2.3.4 \ + --line "电信" + +# 联通用户访问5.6.7.8 +python scripts/deploy_record.py \ + --domain example.com \ + --subdomain www \ + --type A \ + --value 5.6.7.8 \ + --line "联通" +``` + +### 场景5: CI/CD集成 + +**示例: Jenkins/GitLab CI** +```bash +# 部署时自动更新DNS +pip install -r tencent-dnspod/requirements.txt + +# 部署新版本 +docker push registry.example.com/myapp:v1.2.3 + +# 更新DNS +python tencent-dnspod/scripts/deploy_record.py \ + --domain example.com \ + --subdomain app \ + --type A \ + --value $(kubectl get svc myapp -o jsonpath='{.status.loadBalancer.ingress[0].ip}') \ + --force +``` + +## 高级功能 + +### 泛域名解析 + +```bash +# 部署Web服务时添加泛域名 +python scripts/deploy_service.py \ + --domain example.com \ + --service web \ + --ip 1.2.3.4 \ + --wildcard +``` + +**结果:** +- ✅ example.com → 1.2.3.4 +- ✅ www.example.com → 1.2.3.4 +- ✅ *.example.com → 1.2.3.4 + +### TTL设置 + +```bash +# 测试环境使用较短TTL(快速生效) +python scripts/deploy_record.py \ + --domain example.com \ + --subdomain test \ + --type A \ + --value 1.2.3.10 \ + --ttl 60 +``` + +### 记录备注 + +```bash +# 添加备注方便管理 +python scripts/deploy_record.py \ + --domain example.com \ + --subdomain api \ + --type A \ + --value 1.2.3.5 \ + --remark "后端API服务" +``` + +## 常见问题 + +### Q1: 认证失败怎么办? + +**错误:** `AuthFailure` + +**解决:** +1. 检查环境变量是否正确设置 +2. 确认SecretId和SecretKey匹配 +3. 检查系统时间是否准确 + +### Q2: 域名不存在? + +**错误:** `ResourceNotFound.Domain` + +**解决:** 使用 `--create-domain` 参数自动创建: +```bash +python scripts/deploy_record.py \ + --domain example.com \ + --subdomain www \ + --type A \ + --value 1.2.3.4 \ + --create-domain +``` + +### Q3: 记录已存在? + +**错误:** `RecordAlreadyExists` + +**解决:** 使用 `--force` 参数强制更新: +```bash +python scripts/deploy_record.py \ + --domain example.com \ + --subdomain www \ + --type A \ + --value 1.2.3.5 \ + --force +``` + +### Q4: API频率限制? + +**错误:** `RequestLimitExceeded` + +**解决:** 批量部署时增加延迟: +```bash +python scripts/batch_deploy.py \ + --domain example.com \ + --config dns-config.json \ + --delay 1.0 # 改为1秒延迟 +``` + +## 文档索引 + +- **API认证:** [references/api-auth.md](references/api-auth.md) +- **错误处理:** [references/common-errors.md](references/common-errors.md) +- **完整文档:** [SKILL.md](SKILL.md) + +## 技术支持 + +- **DNSPod文档:** https://cloud.tencent.com/document/product/1427/56152 +- **API参考:** https://cloud.tencent.com/document/api/1427/56194 +- **控制台:** https://console.dnspod.cn + +## 更新日志 + +### v1.0.0 (2026-03-01) +- ✅ 单条记录部署 +- ✅ 批量部署 +- ✅ 服务快速部署 +- ✅ 记录查询和删除 +- ✅ 完整错误处理 diff --git a/README.md b/README.md new file mode 100644 index 0000000..d71184f --- /dev/null +++ b/README.md @@ -0,0 +1,258 @@ +# 腾讯云DNSPod域名快速部署 Skill + +这是一个用于快速部署和自动化管理腾讯云DNSPod域名解析的OpenClaw Skill。 + +## 功能特性 + +✅ **单条记录快速部署** - 添加/更新A、CNAME、MX等记录 +✅ **批量部署** - 从JSON配置文件批量创建记录 +✅ **服务快速部署** - 一键部署Web、API、CDN等服务 +✅ **记录查询** - 查看域名下的所有解析记录 +✅ **记录删除** - 删除不需要的DNS记录 +✅ **自动域名创建** - 部署时自动创建不存在的域名 + +## 安装 + +### 1. 安装依赖 +```bash +cd tencent-dnspod +pip install -r requirements.txt +``` + +### 2. 配置认证密钥 + +访问腾讯云控制台获取API密钥: +https://console.cloud.tencent.com/cam/capi + +有两种配置方式: + +**方式1: 使用 .env 文件(推荐)** +```bash +cp .env.example .env +# 编辑 .env 文件,填入你的密钥 +``` + +**方式2: 设置环境变量** +```bash +export TENCENT_SECRET_ID="你的SecretId" +export TENCENT_SECRET_KEY="你的SecretKey" +``` + +### 3. 验证安装 +```bash +python scripts/list_records.py --domain example.com +``` + +## 快速使用 + +### 场景1: 部署Web服务 + +一键部署主域名和www子域名: +```bash +python scripts/deploy_service.py \ + --domain example.com \ + --service web \ + --ip 1.2.3.4 +``` + +带泛域名解析: +```bash +python scripts/deploy_service.py \ + --domain example.com \ + --service web \ + --ip 1.2.3.4 \ + --wildcard +``` + +### 场景2: 部署API服务 +```bash +python scripts/deploy_service.py \ + --domain example.com \ + --service api \ + --ip 1.2.3.5 \ + --subdomain api +``` + +### 场景3: 批量部署 + +从配置文件批量部署: +```bash +python scripts/batch_deploy.py \ + --domain example.com \ + --config examples/dns-config.json +``` + +### 场景4: 单条记录管理 + +添加A记录: +```bash +python scripts/deploy_record.py \ + --domain example.com \ + --subdomain www \ + --type A \ + --value 1.2.3.4 +``` + +添加CNAME记录(CDN): +```bash +python scripts/deploy_record.py \ + --domain example.com \ + --subdomain cdn \ + --type CNAME \ + --value cdn.example.com.cdn.dnsv1.com +``` + +强制更新现有记录: +```bash +python scripts/deploy_record.py \ + --domain example.com \ + --subdomain www \ + --type A \ + --value 1.2.3.5 \ + --force +``` + +### 场景5: 查询和删除 + +查询所有记录: +```bash +python scripts/list_records.py --domain example.com +``` + +删除记录: +```bash +python scripts/delete_record.py \ + --domain example.com \ + --subdomain www \ + --type A +``` + +## 目录结构 + +``` +tencent-dnspod/ +├── SKILL.md # Skill说明文档 +├── README.md # 本文件 +├── requirements.txt # Python依赖 +├── scripts/ # 脚本目录 +│ ├── deploy_record.py # 单条记录部署 +│ ├── batch_deploy.py # 批量部署 +│ ├── deploy_service.py # 服务快速部署 +│ ├── list_records.py # 记录查询 +│ └── delete_record.py # 记录删除 +├── references/ # 参考文档 +│ ├── api-auth.md # API认证说明 +│ └── common-errors.md # 常见错误处理 +└── examples/ # 示例配置 + └── dns-config.json # 批量部署配置示例 +``` + +## 配置文件格式 + +批量部署配置文件(JSON格式): +```json +{ + "records": [ + { + "subdomain": "@", + "type": "A", + "value": "1.2.3.4", + "line": "默认", + "ttl": 600, + "remark": "主域名" + }, + { + "subdomain": "www", + "type": "A", + "value": "1.2.3.4", + "line": "默认", + "remark": "Web服务" + }, + { + "subdomain": "api", + "type": "A", + "value": "1.2.3.5", + "line": "电信", + "remark": "API服务" + }, + { + "subdomain": "cdn", + "type": "CNAME", + "value": "cdn.example.com.cdn.dnsv1.com", + "line": "默认", + "remark": "CDN加速" + } + ] +} +``` + +## 支持的记录类型 + +| 类型 | 说明 | 示例值 | +|------|------|--------| +| A | IPv4地址 | 1.2.3.4 | +| AAAA | IPv6地址 | 2001:db8::1 | +| CNAME | 别名 | cdn.example.com | +| MX | 邮件服务器 | mx.example.com | +| TXT | 文本记录 | "v=spf1 include:_spf.example.com ~all" | +| NS | 域名服务器 | ns1.example.com | + +## 常用线路 + +- `默认` - 默认线路 +- `电信` - 电信用户 +- `联通` - 联通用户 +- `移动` - 移动用户 +- `境外` - 海外用户 +- `搜索引擎` - 爬虫线路 + +## 最佳实践 + +1. **部署前测试** - 先在测试域名验证配置 +2. **设置合理TTL** - 生产环境600s,测试环境60s +3. **使用批量部署** - 多条记录用配置文件管理 +4. **添加记录备注** - 方便后续维护 +5. **分环境管理** - 使用不同子域名(dev/stage/prod) +6. **注意API限频** - 批量操作时控制请求频率 + +## 常见问题 + +### 1. 认证失败 +``` +错误: AuthFailure +解决: 检查环境变量 TENCENT_SECRET_ID 和 TENCENT_SECRET_KEY +``` + +### 2. 域名不存在 +``` +错误: ResourceNotFound.Domain +解决: 使用 --create-domain 参数自动创建域名 +``` + +### 3. 记录已存在 +``` +错误: RecordAlreadyExists +解决: 使用 --force 参数强制更新 +``` + +### 4. API频率限制 +``` +错误: RequestLimitExceeded +解决: 批量操作时增加 --delay 参数(默认0.5秒) +``` + +详细错误处理见: [references/common-errors.md](references/common-errors.md) + +## 技术支持 + +- **DNSPod API文档:** https://cloud.tencent.com/document/product/1427/56152 +- **腾讯云SDK:** https://cloud.tencent.com/document/sdk +- **DNSPod控制台:** https://console.dnspod.cn + +## 许可证 + +MIT License + +## 贡献 + +欢迎提交Issue和Pull Request! diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..dbe12ac --- /dev/null +++ b/SKILL.md @@ -0,0 +1,259 @@ +--- +name: tencent-dnspod +description: | + 腾讯云DNSPod域名快速部署工具。用于自动化部署DNS记录、批量配置域名解析、快速上线服务。 + 当用户需要: (1) 快速添加DNS记录部署新服务, (2) 批量配置A/CNAME/MX等记录, + (3) 域名解析自动化管理, (4) DevOps域名部署流程时触发。 +--- + +# 腾讯云DNSPod域名快速部署 + +## 快速开始 + +### 1. 配置认证 + +获取API密钥: https://console.cloud.tencent.com/cam/capi + +**方式1: 使用 .env 文件(推荐,更安全):** +```bash +cp .env.example .env +# 编辑 .env 文件,填入你的密钥 +``` + +**方式2: 设置环境变量:** +```bash +export TENCENT_SECRET_ID="你的SecretId" +export TENCENT_SECRET_KEY="你的SecretKey" +``` + +### 2. 核心命令 + +**添加单条记录(最常用):** +```bash +python scripts/deploy_record.py \ + --domain example.com \ + --subdomain www \ + --type A \ + --value 1.2.3.4 \ + --line "默认" +``` + +**批量部署(从配置文件):** +```bash +python scripts/batch_deploy.py \ + --domain example.com \ + --config dns-config.json +``` + +**快速配置服务(常用组合):** +```bash +python scripts/deploy_service.py \ + --domain example.com \ + --service web \ + --ip 1.2.3.4 +``` + +**删除记录:** +```bash +python scripts/delete_record.py \ + --domain example.com \ + --subdomain www +``` + +## 域名快速部署流程 + +### 场景1: 部署新Web服务 +```bash +# 一键部署www和主域名 +python scripts/deploy_service.py \ + --domain example.com \ + --service web \ + --ip 1.2.3.4 + +# 自动创建: +# - example.com (A记录) +# - www.example.com (A记录) +# - *.example.com (泛域名A记录,可选) +``` + +### 场景2: 部署API服务 +```bash +# 部署api子域名 +python scripts/deploy_record.py \ + --domain example.com \ + --subdomain api \ + --type A \ + --value 1.2.3.4 +``` + +### 场景3: 配置邮件服务 +```bash +# 一键配置MX记录 +python scripts/deploy_mx.py \ + --domain example.com \ + --mx-server mx.example.com \ + --priority 10 +``` + +### 场景4: CDN加速配置 +```bash +# 添加CNAME指向CDN +python scripts/deploy_record.py \ + --domain example.com \ + --subdomain cdn \ + --type CNAME \ + --value cdn.example.com.cdn.dnsv1.com +``` + +### 场景5: 批量部署多个环境 +```bash +# 从配置文件批量创建 +python scripts/batch_deploy.py \ + --domain example.com \ + --config deployments/dev.json + +# 配置文件示例见下文 +``` + +## 记录类型说明 + +| 类型 | 用途 | 示例 | +|------|------|------| +| A | 指向IPv4地址 | www → 1.2.3.4 | +| CNAME | 指向域名别名 | www → cdn.example.com | +| MX | 邮件服务器 | @ → mx.example.com | +| TXT | 文本记录(验证/SPF) | @ → "v=spf1 include:_spf.example.com ~all" | +| AAAA | 指向IPv6地址 | www → 2001:db8::1 | + +## 批量部署配置文件格式 + +`dns-config.json`: +```json +{ + "records": [ + { + "subdomain": "@", + "type": "A", + "value": "1.2.3.4", + "line": "默认" + }, + { + "subdomain": "www", + "type": "A", + "value": "1.2.3.4", + "line": "默认" + }, + { + "subdomain": "api", + "type": "A", + "value": "1.2.3.5", + "line": "电信" + }, + { + "subdomain": "cdn", + "type": "CNAME", + "value": "cdn.example.com.cdn.dnsv1.com", + "line": "默认" + } + ] +} +``` + +使用批量配置: +```bash +python scripts/batch_deploy.py \ + --domain example.com \ + --config dns-config.json +``` + +## 线路类型说明 + +常用线路值: +- `默认` - 默认线路 +- `电信` - 电信用户 +- `联通` - 联通用户 +- `移动` - 移动用户 +- `境外` - 海外用户 +- `搜索引擎` - 爬虫线路 + +查看完整线路列表: +```bash +python scripts/list_lines.py --domain example.com +``` + +## 常见问题 + +### 1. 记录冲突 +如果记录已存在,脚本会提示是否更新。使用 `--force` 强制更新。 + +### 2. 批量操作失败 +批量操作会继续执行后续记录,最后汇总结果。检查输出中的 `[FAIL]` 标记。 + +### 3. API频率限制 +默认限制: 20次/秒。大批量部署时会自动限速。 + +### 4. 域名未添加 +如果域名未添加到DNSPod,使用 `--create-domain` 自动创建: +```bash +python scripts/deploy_record.py \ + --domain example.com \ + --create-domain \ + --subdomain www \ + --type A \ + --value 1.2.3.4 +``` + +## API错误处理 + +详细错误说明见 [common-errors.md](references/common-errors.md) + +## 高级功能 + +### 创建快照(部署前备份) +```bash +python/scripts/snapshot.py \ + --domain example.com \ + --action create \ + --name "部署前备份" +``` + +### 回滚快照 +```bash +python scripts/snapshot.py \ + --domain example.com \ + --action rollback \ + --snapshot-id +``` + +### 查看部署历史 +```bash +python scripts/list_records.py \ + --domain example.com \ + --show-changes +``` + +## 最佳实践 + +1. **部署前备份** - 重大部署前先创建快照 +2. **分环境管理** - 使用不同子域名(dev/stage/prod) +3. **TTL设置** - 生产环境600s,测试环境60s +4. **批量测试** - 先在测试域名验证配置,再批量部署 +5. **记录备注** - 添加 `--remark` 标记记录用途 + +## 示例: 完整部署流程 + +```bash +# 1. 部署前备份 +python scripts/snapshot.py --domain example.com --action create --name "部署前" + +# 2. 批量部署 +python scripts/batch_deploy.py \ + --domain example.com \ + --config dns-config.json + +# 3. 验证部署 +python scripts/list_records.py --domain example.com + +# 4. 测试解析 +dig www.example.com +``` diff --git a/examples/dns-config.json b/examples/dns-config.json new file mode 100644 index 0000000..fb2a31e --- /dev/null +++ b/examples/dns-config.json @@ -0,0 +1,44 @@ +{ + "records": [ + { + "subdomain": "@", + "type": "A", + "value": "1.2.3.4", + "line": "默认", + "ttl": 600, + "remark": "主域名" + }, + { + "subdomain": "www", + "type": "A", + "value": "1.2.3.4", + "line": "默认", + "ttl": 600, + "remark": "Web服务" + }, + { + "subdomain": "api", + "type": "A", + "value": "1.2.3.5", + "line": "默认", + "ttl": 600, + "remark": "API服务" + }, + { + "subdomain": "cdn", + "type": "CNAME", + "value": "cdn.example.com.cdn.dnsv1.com", + "line": "默认", + "ttl": 600, + "remark": "CDN加速" + }, + { + "subdomain": "test", + "type": "A", + "value": "1.2.3.10", + "line": "默认", + "ttl": 60, + "remark": "测试环境" + } + ] +} diff --git a/references/api-auth.md b/references/api-auth.md new file mode 100644 index 0000000..e7fe4b9 --- /dev/null +++ b/references/api-auth.md @@ -0,0 +1,180 @@ +# 腾讯云API认证说明 + +## 概述 + +DNSPod API使用腾讯云统一的API v3签名算法,基于HMAC-SHA256进行请求签名认证。 + +## 获取密钥 + +### 1. 访问API密钥管理页面 +https://console.cloud.tencent.com/cam/capi + +### 2. 创建密钥 +- 点击「新建密钥」或「访问密钥」 +- 系统生成 `SecretId` 和 `SecretKey` + +### 3. 保存密钥 +⚠️ **重要:** SecretKey只在创建时显示一次,请妥善保存! + +### 4. 设置环境变量 + +**Linux/Mac (Bash/Zsh):** +```bash +# 添加到 ~/.bashrc 或 ~/.zshrc +export TENCENT_SECRET_ID="你的SecretId" +export TENCENT_SECRET_KEY="你的SecretKey" + +# 重新加载配置 +source ~/.bashrc # 或 source ~/.zshrc +``` + +**Windows (PowerShell):** +```powershell +# 添加到环境变量 +[System.Environment]::SetEnvironmentVariable('TENCENT_SECRET_ID', '你的SecretId', 'User') +[System.Environment]::SetEnvironmentVariable('TENCENT_SECRET_KEY', '你的SecretKey', 'User') + +# 临时设置(当前会话有效) +$env:TENCENT_SECRET_ID="你的SecretId" +$env:TENCENT_SECRET_KEY="你的SecretKey" +``` + +**验证环境变量:** +```bash +# Linux/Mac +echo $TENCENT_SECRET_ID + +# Windows PowerShell +echo $env:TENCENT_SECRET_ID +``` + +## 签名算法详解 + +### 1. 构造规范请求串 + +**格式:** +``` +HTTPRequestMethod + '\n' + +CanonicalURI + '\n' + +CanonicalQueryString + '\n' + +CanonicalHeaders + '\n' + +SignedHeaders + '\n' + +HexEncode(Hash(RequestPayload)) +``` + +**示例:** +``` +POST +/ +content-type:application/json +host:dnspod.tencentcloudapi.com + +content-type;host +356f92b5d0373084a2bc07df8267b0ab +``` + +### 2. 构造待签名字符串 + +**格式:** +``` +Algorithm + '\n' + +RequestTimestamp + '\n' + +CredentialScope + '\n' + +HexEncode(Hash(CanonicalRequest)) +``` + +**示例:** +``` +TC3-HMAC-SHA256 +1666666666 +2021-03-23/dnspod/tc3_request +5c9d88e6d4c4d4c4d4c4d4c4d4c4d4c4d4c4d4c4d4c4d4c4d4c4d4c4d4c4d +``` + +### 3. 计算签名 + +使用 HMAC-SHA256 算法分三次计算: + +```python +secret_date = HMAC_SHA256("TC3" + SecretKey, Date) +secret_service = HMAC_SHA256(secret_date, Service) +secret_signing = HMAC_SHA256(secret_service, "tc3_request") +signature = HMAC_SHA256(secret_signing, StringToSign) +``` + +### 4. 构造Authorization头 + +**格式:** +``` +Algorithm Credential={SecretId}/{CredentialScope}, SignedHeaders={SignedHeaders}, Signature={Signature} +``` + +**示例:** +``` +TC3-HMAC-SHA256 Credential=AKIDxxxxxxxx/2021-03-23/dnspod/tc3_request, SignedHeaders=content-type;host, Signature=5c9d88e6d4c4d4c4d4c4d4c4d4c4d4c4d4c4d4c4d4c4d4c4d4c4d4c4d4c4d +``` + +## 完整请求示例 + +### 请求头 +```http +POST / HTTP/1.1 +Host: dnspod.tencentcloudapi.com +Content-Type: application/json +Authorization: TC3-HMAC-SHA256 Credential=AKIDxxxxxxxx/2021-03-23/dnspod/tc3_request, SignedHeaders=content-type;host, Signature=xxxxxxxx +X-TC-Action: DescribeRecordList +X-TC-Timestamp: 1666666666 +X-TC-Version: 2021-03-23 +X-TC-Region: ap-guangzhou +``` + +### 请求体 +```json +{ + "Domain": "example.com" +} +``` + +## 请求参数说明 + +| 参数 | 说明 | 示例 | +|------|------|------| +| X-TC-Action | 操作名称 | CreateRecord | +| X-TC-Version | API版本 | 2021-03-23 | +| X-TC-Timestamp | 时间戳(秒) | 1666666666 | +| X-TC-Region | 地域 | ap-guangzhou | +| Authorization | 签名信息 | 见上文 | + +## 地域说明 + +DNSPod API支持的地域: + +- **ap-guangzhou** (广州,推荐) +- **ap-shanghai** (上海) +- **ap-beijing** (北京) +- **ap-chengdu** (成都) + +建议选择就近地域以降低延迟。 + +## 安全建议 + +### 1. 密钥安全 +- ⚠️ 不要将密钥硬编码在代码中 +- ⚠️ 不要在Git仓库中提交密钥 +- ✓ 使用环境变量或配置文件 +- ✓ 定期轮换密钥 + +### 2. 权限管理 +- 为不同应用创建独立的子账号 +- 只授予必要的权限(最小权限原则) +- 定期审计密钥使用情况 + +### 3. IP白名单 +- 在腾讯云访问管理(CAM)中设置IP白名单 +- 限制只有特定IP才能调用API + +## 参考文档 + +- **API签名文档:** https://cloud.tencent.com/document/product/1427/56152 +- **访问管理(CAM):** https://cloud.tencent.com/document/product/598 +- **SDK示例:** https://cloud.tencent.com/document/sdk diff --git a/references/common-errors.md b/references/common-errors.md new file mode 100644 index 0000000..3879f66 --- /dev/null +++ b/references/common-errors.md @@ -0,0 +1,192 @@ +# DNSPod API常见错误处理 + +## 错误码说明 + +### 认证相关 + +#### AuthFailure +**错误:** 签名验证失败 + +**原因:** +- SecretId或SecretKey错误 +- 签名算法错误 +- 请求时间戳偏差过大 + +**解决方案:** +1. 检查环境变量 `TENCENT_SECRET_ID` 和 `TENCENT_SECRET_KEY` +2. 确认密钥未过期且状态正常 +3. 检查系统时间是否准确(误差不超过5分钟) + +#### AuthFailure.SecretIdNotFound +**错误:** SecretId不存在 + +**原因:** +- SecretId输入错误 +- 密钥已被删除 + +**解决方案:** +- 访问 https://console.cloud.tencent.com/cam/capi 重新获取 + +### 域名相关 + +#### ResourceNotFound.Domain +**错误:** 域名不存在 + +**原因:** +- 域名未添加到DNSPod +- 域名名称输入错误 + +**解决方案:** +- 检查域名拼写 +- 使用 `--create-domain` 自动创建域名 +- 手动在DNSPod控制台添加域名 + +#### ResourceNotFound.DomainOffline +**错误:** 域名已停用 + +**原因:** +- 域名已被禁用 + +**解决方案:** +- 登录DNSPod控制台重新启用域名 + +### 记录相关 + +#### InvalidParameter.RecordAlreadyExists +**错误:** 记录已存在 + +**原因:** +- 相同的主机记录、类型、线路的记录已存在 + +**解决方案:** +- 使用 `--force` 参数强制更新 +- 先删除现有记录再创建 + +#### InvalidParameter.RecordNotFound +**错误:** 记录不存在 + +**原因:** +- RecordId错误 +- 记录已被删除 + +**解决方案:** +- 使用 `list_records.py` 查询正确的记录ID + +#### InvalidParameter.RecordValueInvalid +**错误:** 记录值格式错误 + +**原因:** +- IP地址格式错误 +- CNAME目标域名格式错误 +- 记录值与类型不匹配 + +**解决方案:** +- A记录值必须是IPv4地址(如: 1.2.3.4) +- CNAME记录值必须是域名(如: cdn.example.com) +- MX记录值格式: `mx.example.com` (优先级单独设置) + +### 限频相关 + +#### RequestLimitExceeded +**错误:** 请求频率超限 + +**原因:** +- API调用超过频率限制(默认: 20次/秒) + +**解决方案:** +- 降低请求频率 +- 批量操作时增加 `--delay` 参数 +- 使用批量API代替循环调用 + +#### LimitExceeded.DomainRecord +**错误:** 域名记录数超限 + +**原因:** +- 该域名下的记录数超过套餐限制 + +**解决方案:** +- 删除不需要的记录 +- 升级DNSPod套餐 + +### 参数相关 + +#### InvalidParameter +**错误:** 参数错误 + +**原因:** +- 必填参数缺失 +- 参数格式错误 +- 参数值超出范围 + +**解决方案:** +- 检查命令行参数拼写 +- 确认参数值格式正确 +- 查看API文档确认参数要求 + +#### MissingParameter +**错误:** 缺少必填参数 + +**原因:** +- 未提供必要的参数 + +**解决方案:** +- 使用 `--help` 查看必填参数 +- 补充完整的参数 + +### 其他常见错误 + +#### UnknownParameter +**错误:** 未知参数 + +**原因:** +- 传递了不支持的参数 + +**解决方案:** +- 检查参数名称拼写 +- 确认API版本支持该参数 + +#### UnsupportedOperation +**错误:** 不支持的操作 + +**原因:** +- 当前套餐不支持该功能 +- 域名状态不允许该操作 + +**解决方案:** +- 升级DNSPod套餐 +- 检查域名状态 + +## 调试技巧 + +### 1. 开启详细日志 +在脚本中添加调试输出: +```python +import logging +logging.basicConfig(level=logging.DEBUG) +``` + +### 2. 打印API响应 +查看完整的API错误信息: +```python +print(json.dumps(result, indent=2, ensure_ascii=False)) +``` + +### 3. 验证参数 +在调用API前打印参数: +```python +print(f"参数: {params}") +``` + +### 4. 测试连通性 +检查网络连接: +```bash +ping dnspod.tencentcloudapi.com +curl -I https://dnspod.tencentcloudapi.com +``` + +## 获取帮助 + +- **API文档:** https://cloud.tencent.com/document/product/1427/56152 +- **错误码参考:** https://cloud.tencent.com/document/product/1427/56190 +- **工单系统:** https://console.cloud.tencent.com/workorder +- **技术支持:** 95716 (DNSPod技术热线) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f49b6b8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +requests>=2.28.0 +python-dotenv>=1.0.0 diff --git a/scripts/batch_deploy.py b/scripts/batch_deploy.py new file mode 100755 index 0000000..e8f98bf --- /dev/null +++ b/scripts/batch_deploy.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +""" +DNSPod批量部署脚本 +从配置文件批量创建DNS记录 +""" +import os +import sys +import json +import time +from pathlib import Path + +from deploy_record import ( + deploy_record, + check_domain_exists, + create_domain, + load_env +) + +# 加载 .env +load_env() + +def load_config(config_file): + """加载配置文件""" + if not Path(config_file).exists(): + print(f"错误: 配置文件不存在: {config_file}") + return None + + try: + with open(config_file, 'r', encoding='utf-8') as f: + return json.load(f) + except json.JSONDecodeError as e: + print(f"错误: 配置文件格式错误: {e}") + return None + +def batch_deploy(domain, config, create_domain_flag=False, delay=0.5): + """批量部署DNS记录""" + records = config.get('records', []) + + if not records: + print("错误: 配置文件中没有记录") + return False + + print(f"\n批量部署开始") + print(f"域名: {domain}") + print(f"记录数量: {len(records)}") + print(f"延迟: {delay}s\n") + + # 检查域名是否存在 + if not check_domain_exists(domain): + if create_domain_flag: + if not create_domain(domain): + return False + else: + print(f"✗ 域名不存在: {domain}") + print(f"提示: 使用 --create-domain 自动创建域名") + return False + + # 统计 + success_count = 0 + fail_count = 0 + skip_count = 0 + + # 部署每条记录 + for idx, record_config in enumerate(records, 1): + print(f"\n[{idx}/{len(records)}] 正在部署...") + + subdomain = record_config.get('subdomain', '@') + record_type = record_config.get('type') + value = record_config.get('value') + line = record_config.get('line', '默认') + ttl = record_config.get('ttl', 600) + remark = record_config.get('remark', '') + + if not record_type or not value: + print(f"✗ 跳过: 缺少必要参数 (type或value)") + skip_count += 1 + continue + + # 部署记录 + success = deploy_record( + domain=domain, + subdomain=subdomain, + record_type=record_type, + value=value, + line=line, + ttl=ttl, + force=True, # 批量模式自动更新 + create_domain_flag=False, + remark=remark + ) + + if success: + success_count += 1 + else: + fail_count += 1 + + # API限频延迟 + if idx < len(records): + time.sleep(delay) + + # 输出统计 + print(f"\n{'='*50}") + print(f"批量部署完成") + print(f" 成功: {success_count}") + print(f" 失败: {fail_count}") + print(f" 跳过: {skip_count}") + print(f"{'='*50}\n") + + return fail_count == 0 + +def main(): + import argparse + + parser = argparse.ArgumentParser(description='DNSPod批量部署', formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=''' +配置文件格式 (JSON): +{ + "records": [ + { + "subdomain": "@", + "type": "A", + "value": "1.2.3.4", + "line": "默认", + "ttl": 600, + "remark": "主域名" + }, + { + "subdomain": "www", + "type": "A", + "value": "1.2.3.4", + "line": "默认" + }, + { + "subdomain": "api", + "type": "A", + "value": "1.2.3.5", + "line": "电信" + } + ] +} + +示例: + %(prog)s --domain example.com --config dns-config.json + %(prog)s --domain example.com --config dns-config.json --create-domain + %(prog)s --domain example.com --config dns-config.json --delay 1.0 + ''') + + parser.add_argument('--domain', required=True, help='域名(如: example.com)') + parser.add_argument('--config', required=True, help='配置文件路径(JSON格式)') + parser.add_argument('--create-domain', action='store_true', help='域名不存在时自动创建') + parser.add_argument('--delay', type=float, default=0.5, help='API调用间隔(秒, 默认: 0.5, 避免限频)') + + args = parser.parse_args() + + # 加载配置 + config = load_config(args.config) + if not config: + sys.exit(1) + + # 批量部署 + success = batch_deploy( + domain=args.domain, + config=config, + create_domain_flag=args.create_domain, + delay=args.delay + ) + + sys.exit(0 if success else 1) + +if __name__ == '__main__': + main() diff --git a/scripts/delete_record.py b/scripts/delete_record.py new file mode 100755 index 0000000..3ffbc37 --- /dev/null +++ b/scripts/delete_record.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +""" +DNS记录删除脚本 +""" +import os +import sys + +from deploy_record import ( + call_api, + find_record, + load_env +) + +# 加载 .env +load_env() + +def delete_record(domain, subdomain, record_type): + """删除DNS记录""" + print(f"\n删除DNS记录: {subdomain or '@'}.{domain} ({record_type})") + + # 查找记录 + record = find_record(domain, subdomain, record_type) + + if not record: + print(f"✗ 记录不存在") + return False + + record_id = record.get("RecordId") + current_value = record.get("Value") + + print(f" 记录ID: {record_id}") + print(f" 当前值: {current_value}") + + # 确认删除 + response = input("\n确认删除? (y/N): ") + if response.lower() != 'y': + print(" 已取消") + return False + + # 调用删除API + try: + params = { + "Domain": domain, + "RecordId": record_id + } + result = call_api("DeleteRecord", params) + print(f"✓ 记录删除成功") + return True + except Exception as e: + print(f"✗ 记录删除失败: {e}") + return False + +def main(): + import argparse + + parser = argparse.ArgumentParser(description='删除DNS记录') + + parser.add_argument('--domain', required=True, help='域名(如: example.com)') + parser.add_argument('--subdomain', default='@', help='子域名(默认: @)') + parser.add_argument('--type', required=True, help='记录类型(A/CNAME/MX等)') + + args = parser.parse_args() + + success = delete_record(args.domain, args.subdomain, args.type) + sys.exit(0 if success else 1) + +if __name__ == '__main__': + main() diff --git a/scripts/deploy_record.py b/scripts/deploy_record.py new file mode 100755 index 0000000..12bcb56 --- /dev/null +++ b/scripts/deploy_record.py @@ -0,0 +1,337 @@ +#!/usr/bin/env python3 +""" +DNSPod单条记录部署脚本 +用于快速添加或更新DNS记录 +支持 .env 文件配置敏感信息 +""" +import os +import sys +import json +import hmac +import hashlib +import time +from datetime import datetime +from pathlib import Path +from urllib.parse import urlencode, quote + +try: + import requests +except ImportError: + print("错误: 缺少 requests 库") + print("请运行: pip install -r requirements.txt") + sys.exit(1) + +# 加载 .env 文件 +def load_env(): + """加载.env文件""" + env_file = Path(__file__).parent.parent / '.env' + if env_file.exists(): + with open(env_file, 'r', encoding='utf-8') as f: + for line in f: + line = line.strip() + if line and not line.startswith('#') and '=' in line: + key, value = line.split('=', 1) + os.environ[key.strip()] = value.strip() + +# 加载 .env +load_env() + +# API配置 +API_ENDPOINT = "dnspod.tencentcloudapi.com" +API_VERSION = "2021-03-23" +REGION = "ap-guangzhou" + +def get_credentials(): + """从环境变量或.env文件获取腾讯云凭证""" + secret_id = os.getenv('TENCENT_SECRET_ID') + secret_key = os.getenv('TENCENT_SECRET_KEY') + + if not secret_id or not secret_key: + print("错误: 未找到腾讯云API密钥") + print("\n请设置环境变量或创建 .env 文件:") + print(" 方式1: 设置环境变量") + print(" export TENCENT_SECRET_ID=\"你的SecretId\"") + print(" export TENCENT_SECRET_KEY=\"你的SecretKey\"") + print("\n 方式2: 创建 .env 文件") + print(" cp .env.example .env") + print(" 然后编辑 .env 文件,填入你的密钥") + print("\n获取密钥地址: https://console.cloud.tencent.com/cam/capi") + sys.exit(1) + + return secret_id, secret_key + +def sign_request(secret_id, secret_key, action, params): + """ + 生成腾讯云API签名 + 文档: https://cloud.tencent.com/document/product/1427/56152 + """ + # 1. 构造请求体 + body = json.dumps(params) + + # 2. 构造规范请求串 + # 请求方法 + http_request_method = "POST" + # 请求URI + canonical_uri = "/" + # 请求查询字符串(空) + canonical_querystring = "" + # 请求头 + canonical_headers = f"content-type:application/json\nhost:{API_ENDPOINT}\n" + signed_headers = "content-type;host" + # 请求哈希值 + hashed_request_payload = hashlib.sha256(body.encode('utf-8')).hexdigest() + canonical_request = ( + http_request_method + "\n" + + canonical_uri + "\n" + + canonical_querystring + "\n" + + canonical_headers + "\n" + + signed_headers + "\n" + + hashed_request_payload + ) + + # 3. 构造待签名字符串 + algorithm = "TC3-HMAC-SHA256" + timestamp = int(time.time()) + date = datetime.utcfromtimestamp(timestamp).strftime('%Y-%m-%d') + credential_scope = f"{date}/{API_VERSION}/tc3_request" + hashed_canonical_request = hashlib.sha256(canonical_request.encode('utf-8')).hexdigest() + string_to_sign = ( + algorithm + "\n" + + str(timestamp) + "\n" + + credential_scope + "\n" + + hashed_canonical_request + ) + + # 4. 计算签名 + def _hmac_sha256(key, msg): + return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest() + + secret_date = _hmac_sha256(f"TC3{secret_key}".encode('utf-8'), date) + secret_service = _hmac_sha256(secret_date, API_VERSION) + secret_signing = _hmac_sha256(secret_service, "tc3_request") + signature = hmac.new(secret_signing, string_to_sign.encode('utf-8'), hashlib.sha256).hexdigest() + + # 5. 构造Authorization头 + authorization = ( + algorithm + " " + + "Credential=" + secret_id + "/" + credential_scope + ", " + + "SignedHeaders=" + signed_headers + ", " + + "Signature=" + signature + ) + + return { + "authorization": authorization, + "body": body, + "timestamp": timestamp + } + +def call_api(action, params): + """调用腾讯云API""" + secret_id, secret_key = get_credentials() + + # 生成签名 + sig = sign_request(secret_id, secret_key, action, params) + + # 构造请求头 + headers = { + "Authorization": sig["authorization"], + "Content-Type": "application/json", + "Host": API_ENDPOINT, + "X-TC-Action": action, + "X-TC-Timestamp": str(sig["timestamp"]), + "X-TC-Version": API_VERSION, + "X-TC-Region": REGION + } + + # 发送请求 + url = f"https://{API_ENDPOINT}/" + response = requests.post(url, headers=headers, data=sig["body"]) + + return response.json() + +def check_domain_exists(domain): + """检查域名是否存在""" + try: + result = call_api("DescribeDomainList", {}) + domains = [d["Name"] for d in result.get("Response", {}).get("DomainList", [])] + return domain in domains + except Exception as e: + print(f"检查域名失败: {e}") + return False + +def create_domain(domain): + """创建域名""" + print(f"正在创建域名: {domain}") + try: + result = call_api("CreateDomain", {"Domain": domain}) + print(f"✓ 域名创建成功: {domain}") + return True + except Exception as e: + print(f"✗ 域名创建失败: {e}") + return False + +def find_record(domain, subdomain, record_type): + """查找现有记录""" + try: + params = { + "Domain": domain, + "Subdomain": subdomain, + "RecordType": record_type + } + result = call_api("DescribeRecordList", params) + records = result.get("Response", {}).get("RecordList", []) + + for record in records: + if record.get("Name") == subdomain and record.get("Type") == record_type: + return record + return None + except Exception as e: + print(f"查找记录失败: {e}") + return None + +def create_record(domain, subdomain, record_type, value, line="默认", ttl=600, remark=""): + """创建DNS记录""" + params = { + "Domain": domain, + "RecordType": record_type, + "RecordLine": line, + "Value": value, + "TTL": ttl + } + + # 添加子域名 + if subdomain and subdomain != "@": + params["SubDomain"] = subdomain + + # 添加备注 + if remark: + params["Remark"] = remark + + try: + result = call_api("CreateRecord", params) + record_id = result.get("Response", {}).get("RecordId") + print(f"✓ 记录创建成功: {subdomain or '@'}.{domain} ({record_type}) → {value}") + return record_id + except Exception as e: + print(f"✗ 记录创建失败: {e}") + return None + +def modify_record(domain, record_id, subdomain, record_type, value, line="默认", ttl=600, remark=""): + """修改DNS记录""" + params = { + "Domain": domain, + "RecordId": record_id, + "RecordType": record_type, + "RecordLine": line, + "Value": value, + "TTL": ttl + } + + if subdomain and subdomain != "@": + params["SubDomain"] = subdomain + + if remark: + params["Remark"] = remark + + try: + result = call_api("ModifyRecord", params) + print(f"✓ 记录更新成功: {subdomain or '@'}.{domain} ({record_type}) → {value}") + return True + except Exception as e: + print(f"✗ 记录更新失败: {e}") + return False + +def deploy_record(domain, subdomain, record_type, value, line="默认", ttl=600, force=False, create_domain_flag=False, remark=""): + """部署DNS记录(创建或更新)""" + print(f"\n部署DNS记录: {subdomain or '@'}.{domain}") + print(f" 类型: {record_type}") + print(f" 值: {value}") + print(f" 线路: {line}") + print(f" TTL: {ttl}s") + + # 检查域名是否存在 + if not check_domain_exists(domain): + if create_domain_flag: + if not create_domain(domain): + return False + else: + print(f"✗ 域名不存在: {domain}") + print(f" 提示: 使用 --create-domain 自动创建域名") + return False + + # 查找现有记录 + existing = find_record(domain, subdomain, record_type) + + if existing: + if not force: + old_value = existing.get("Value", "") + print(f"\n记录已存在:") + print(f" 当前值: {old_value}") + print(f" 新值: {value}") + + # 检查是否需要更新 + if old_value == value: + print(" 值未变化,无需更新") + return True + + response = input("\n是否更新? (y/N): ") + if response.lower() != 'y': + print(" 已取消") + return False + + record_id = existing.get("RecordId") + return modify_record(domain, record_id, subdomain, record_type, value, line, ttl, remark) + else: + return create_record(domain, subdomain, record_type, value, line, ttl, remark) + +def main(): + import argparse + + parser = argparse.ArgumentParser(description='DNSPod记录快速部署', formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=''' +示例: + # 添加www的A记录 + %(prog)s --domain example.com --subdomain www --type A --value 1.2.3.4 + + # 添加主域名记录 + %(prog)s --domain example.com --subdomain @ --type A --value 1.2.3.4 + + # 添加CNAME记录 + %(prog)s --domain example.com --subdomain cdn --type CNAME --value cdn.example.com + + # 强制更新现有记录 + %(prog)s --domain example.com --subdomain www --type A --value 1.2.3.5 --force + + # 域名不存在时自动创建 + %(prog)s --domain example.com --subdomain www --type A --value 1.2.3.4 --create-domain + ''') + + parser.add_argument('--domain', required=True, help='域名(如: example.com)') + parser.add_argument('--subdomain', default='@', help='子域名(默认: @ 表示主域名)') + parser.add_argument('--type', required=True, choices=['A', 'AAAA', 'CNAME', 'MX', 'TXT', 'NS', 'SRV'], help='记录类型') + parser.add_argument('--value', required=True, help='记录值') + parser.add_argument('--line', default='默认', help='线路(默认: 默认)') + parser.add_argument('--ttl', type=int, default=600, help='TTL(秒, 默认: 600)') + parser.add_argument('--force', action='store_true', help='强制更新现有记录(不询问)') + parser.add_argument('--create-domain', action='store_true', help='域名不存在时自动创建') + parser.add_argument('--remark', default='', help='记录备注') + + args = parser.parse_args() + + # 部署记录 + success = deploy_record( + domain=args.domain, + subdomain=args.subdomain, + record_type=args.type, + value=args.value, + line=args.line, + ttl=args.ttl, + force=args.force, + create_domain_flag=args.create_domain, + remark=args.remark + ) + + sys.exit(0 if success else 1) + +if __name__ == '__main__': + main() diff --git a/scripts/deploy_service.py b/scripts/deploy_service.py new file mode 100755 index 0000000..aab458f --- /dev/null +++ b/scripts/deploy_service.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +""" +快速服务部署脚本 +一键部署常见服务的DNS配置(Web服务、API服务、CDN等) +""" +import os +import sys + +from deploy_record import deploy_record, load_env + +# 加载 .env +load_env() + +def deploy_web_service(domain, ip, include_wildcard=False, line="默认", ttl=600): + """部署Web服务(主域名 + www + 可选泛域名)""" + print(f"\n{'='*50}") + print(f"部署Web服务: {domain}") + print(f"目标IP: {ip}") + print(f"{'='*50}\n") + + success_count = 0 + total_count = 2 + (1 if include_wildcard else 0) + + # 1. 主域名 @ + if deploy_record(domain, '@', 'A', ip, line, ttl, force=True, remark='主域名'): + success_count += 1 + + # 2. www子域名 + if deploy_record(domain, 'www', 'A', ip, line, ttl, force=True, remark='Web服务'): + success_count += 1 + + # 3. 泛域名 *(可选) + if include_wildcard: + if deploy_record(domain, '*', 'A', ip, line, ttl, force=True, remark='泛域名'): + success_count += 1 + + print(f"\n{'='*50}") + print(f"Web服务部署完成: {success_count}/{total_count} 成功") + print(f"{'='*50}\n") + + return success_count == total_count + +def deploy_api_service(domain, ip, subdomain="api", line="默认", ttl=600): + """部署API服务""" + print(f"\n{'='*50}") + print(f"部署API服务: {subdomain}.{domain}") + print(f"目标IP: {ip}") + print(f"{'='*50}\n") + + success = deploy_record(domain, subdomain, 'A', ip, line, ttl, force=True, remark='API服务') + + print(f"\n{'='*50}") + print(f"API服务部署{'成功' if success else '失败'}") + print(f"{'='*50}\n") + + return success + +def deploy_cdn_service(domain, cdn_cname, subdomain="cdn", line="默认", ttl=600): + """部署CDN加速""" + print(f"\n{'='*50}") + print(f"部署CDN加速: {subdomain}.{domain}") + print(f"CNAME: {cdn_cname}") + print(f"{'='*50}\n") + + success = deploy_record(domain, subdomain, 'CNAME', cdn_cname, line, ttl, force=True, remark='CDN加速') + + print(f"\n{'='*50}") + print(f"CDN部署{'成功' if success else '失败'}") + print(f"{'='*50}\n") + + return success + +def deploy_mx_service(domain, mx_server, priority=10, line="默认"): + """部署邮件服务(MX记录)""" + print(f"\n{'='*50}") + print(f"部署邮件服务: {domain}") + print(f"MX服务器: {mx_server}") + print(f"优先级: {priority}") + print(f"{'='*50}\n") + + # 注意: MX记录需要特殊处理,这里简化处理 + # 实际需要调用MX专用的创建接口 + print(f"提示: MX记录部署需要手动配置") + print(f" 记录类型: MX") + print(f" 主机记录: @") + print(f" 记录值: {mx_server}") + print(f" 优先级: {priority}") + print(f" 线路: {line}\n") + + return True + +def main(): + import argparse + + parser = argparse.ArgumentParser(description='快速服务部署', formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=''' +服务类型: + web - Web服务(@ + www) + api - API服务(api子域名) + cdn - CDN加速(CNAME记录) + mx - 邮件服务(MX记录) + +示例: + # 部署Web服务 + %(prog)s --domain example.com --service web --ip 1.2.3.4 + + # 部署Web服务(含泛域名) + %(prog)s --domain example.com --service web --ip 1.2.3.4 --wildcard + + # 部署API服务 + %(prog)s --domain example.com --service api --ip 1.2.3.5 --subdomain api + + # 部署CDN + %(prog)s --domain example.com --service cdn --cname cdn.example.com.cdn.dnsv1.com + + # 部署邮件服务 + %(prog)s --domain example.com --service mx --mx-server mx.example.com --priority 10 + ''') + + parser.add_argument('--domain', required=True, help='域名(如: example.com)') + parser.add_argument('--service', required=True, choices=['web', 'api', 'cdn', 'mx'], help='服务类型') + parser.add_argument('--ip', help='目标IP地址(A记录)') + parser.add_argument('--cname', help='CNAME目标域名') + parser.add_argument('--mx-server', help='MX服务器地址') + parser.add_argument('--priority', type=int, default=10, help='MX优先级(默认: 10)') + parser.add_argument('--subdomain', default='api', help='API子域名(默认: api)') + parser.add_argument('--cdn-subdomain', default='cdn', help='CDN子域名(默认: cdn)') + parser.add_argument('--line', default='默认', help='线路(默认: 默认)') + parser.add_argument('--ttl', type=int, default=600, help='TTL(秒, 默认: 600)') + parser.add_argument('--wildcard', action='store_true', help='Web服务是否添加泛域名解析(*.domain)') + + args = parser.parse_args() + + success = False + + if args.service == 'web': + if not args.ip: + print("错误: Web服务需要指定 --ip 参数") + sys.exit(1) + success = deploy_web_service(args.domain, args.ip, args.wildcard, args.line, args.ttl) + + elif args.service == 'api': + if not args.ip: + print("错误: API服务需要指定 --ip 参数") + sys.exit(1) + success = deploy_api_service(args.domain, args.ip, args.subdomain, args.line, args.ttl) + + elif args.service == 'cdn': + if not args.cname: + print("错误: CDN服务需要指定 --cname 参数") + sys.exit(1) + success = deploy_cdn_service(args.domain, args.cname, args.cdn_subdomain, args.line, args.ttl) + + elif args.service == 'mx': + if not args.mx_server: + print("错误: 邮件服务需要指定 --mx-server 参数") + sys.exit(1) + success = deploy_mx_service(args.domain, args.mx_server, args.priority, args.line) + + sys.exit(0 if success else 1) + +if __name__ == '__main__': + main() diff --git a/scripts/list_records.py b/scripts/list_records.py new file mode 100755 index 0000000..8d0dc28 --- /dev/null +++ b/scripts/list_records.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +""" +DNS记录列表查询脚本 +""" +import os +import sys +import json + +from deploy_record import call_api, load_env + +# 加载 .env +load_env() + +def list_records(domain, record_type=None, subdomain=None): + """查询DNS记录列表""" + params = {"Domain": domain} + + if record_type: + params["RecordType"] = record_type + if subdomain: + params["Subdomain"] = subdomain + + try: + result = call_api("DescribeRecordList", params) + records = result.get("Response", {}).get("RecordList", []) + + if not records: + print(f"\n没有找到记录") + return [] + + print(f"\n{'='*80}") + print(f"DNS记录列表: {domain}") + print(f"{'='*80}\n") + + # 表头 + print(f"{'主机记录':<20} {'类型':<10} {'记录值':<30} {'线路':<10} {'TTL':<8} {'状态':<6}") + print(f"{'-'*80}") + + # 记录列表 + for record in records: + name = record.get("Name", "") + record_type = record.get("Type", "") + value = record.get("Value", "") + line = record.get("Line", "") + ttl = record.get("TTL", 0) + status = "启用" if record.get("Enabled", 1) == 1 else "禁用" + + # 截断过长的值 + if len(value) > 28: + value = value[:28] + ".." + + print(f"{name:<20} {record_type:<10} {value:<30} {line:<10} {ttl:<8} {status:<6}") + + print(f"{'-'*80}") + print(f"总计: {len(records)} 条记录\n") + + return records + + except Exception as e: + print(f"查询失败: {e}") + return [] + +def main(): + import argparse + + parser = argparse.ArgumentParser(description='查询DNS记录列表') + + parser.add_argument('--domain', required=True, help='域名(如: example.com)') + parser.add_argument('--type', help='筛选记录类型') + parser.add_argument('--subdomain', help='筛选子域名') + + args = parser.parse_args() + + list_records(args.domain, args.type, args.subdomain) + +if __name__ == '__main__': + main()