# Rust V5 客户端开发 `ddns-rs` 是 DDNS 下一主版本 V5 的 Rust 实现,集成分支为 `v5`。 Python 版本继续在 `master` / `v4` 维护;Rust 功能 PR 应以 `v5` 为目标。 当前开发版本为 `5.0.0-alpha1`,支持单次同步及 Web/MCP 服务,尚未发布,也不替换稳定的 Python `ddns` 命令或默认安装方式。 ## 当前支持 | 能力 | 状态 | | --- | --- | | 全部 Python DNS Provider 及其兼容别名(Cloudflare、AliDNS/ESA、DNSPod、EdgeOne、ClouDNS、DNS.COM、HE.net、华为云、NameSilo、No-IP、Callback、West.cn、Debug) | 已支持 | | IPv4/IPv6 与全部现有 IP 规则类型 | 已支持;`regex:` 使用 Rust 语法 | | CLI、`DDNS_*`、本地/远程/多配置、v4.1 `providers` | 已支持 | | JSON 注释与受限 Python 数据字面量 | 已支持 | | 缓存、代理回退、重试、TLS 与自定义 CA | 已支持 | | Linux amd64/arm64 Docker 镜像 | 构建和离线冒烟测试;准备流程保存 OCI 工件,不推送公共镜像;`web` 提供进程内调度 | | Linux x64/arm64、macOS x64/arm64、Windows x64 二进制 | 准备流程保存 `ddns-rs-*` 与 `.sha256` Actions 工件;Linux 使用静态 musl 目标 | | Web 控制台、配置管理与进程内调度 | 已支持;单个本地配置,间隔 1..1440 分钟 | | MCP stdio、独立 HTTP、Web `/mcp` | 已支持;现代协议及旧版 stdio 兼容 | | `task` 与系统任务检测/接管 | 尚未迁移;必须手动停用旧 Python/主机任务 | ## V5 分支与发布准备 保留 `rust/` 布局、`ddns-rs` 命令及现有配置兼容边界,暂不删除 Python 实现。 `rust/Cargo.toml` 与 `rust/Cargo.lock` 的版本必须一致。`v5` 上的版本文件或 准备工作流、内嵌 Web 资源/字段模型或 Rust Dockerfile 发生变更时, `Prepare Rust V5` 自动构建,无需创建标签。它也接受 `v5` 分支上的手动准备,或与 Cargo 版本一致的规范 `v5.*` 标签,并生成经过 校验的原生工件和双架构 OCI 工件;不会创建 Release、上传发布资产或推送镜像。 准备流程仅接受带 `alpha`、`beta` 或 `rc` 后缀的 V5 预发布版本,不接受稳定版本。 V5 文档构建也不会部署覆盖稳定站点;本分支保留的 Python 发布入口拒绝 `v5.*`, 不改动稳定分支的发布配置。 当前没有可安装的 V5 发布版本,应使用源码或 CI 工件。在首次创建公开 V5 标签前, 还需隔离安装器和下载服务的 Python/Rust 版本选择,并单独确认 Rust 发布流程; 暂不要使用仓库级 `latest` / `beta` 下载作为 V5 安装入口。 ## 构建与运行 使用当前 stable Rust: ```bash cargo build --manifest-path rust/Cargo.toml --release --locked rust/target/release/ddns-rs --help rust/target/release/ddns-rs -c config.json ``` Windows 产物为 `rust\target\release\ddns-rs.exe`。 实验性 `install-rust.sh` 在移动临时二进制到安装目录前执行 `--version`; 新程序无法运行时,即使使用 `--force` 也不会覆盖已有的 `ddns-rs`。 ## Web 与 MCP ```bash ddns-rs web -c config.json --host 127.0.0.1 --port 8000 --interval 5 --open ddns-rs mcp -c config.json ddns-rs mcp -c config.json --transport http --host 127.0.0.1 --port 8001 ``` `web` 接受 `-c FILE`、`--host HOST`、`--port PORT`、`--http-token TOKEN`、 可重复的 `--http-origin ORIGIN`、`--interval MINUTES` 和 `--open`(打开浏览器)。 未指定子命令时,顶层 `--interval` 或配置根级 `interval` 会选择 Web 模式。 进程内调度间隔为 1..1440 分钟,仅在进程存活期间运行。 控制台支持读取、校验和保存完整配置,管理备份/恢复、查看状态并触发同步。 Web 与 MCP 复用同一个服务,仅接受单个本地配置文件,不接受远程或多配置来源。 Web 使用现有兼容的 Rust 运行时校验配置,不代表与 Python 运行时完全等价。 `mcp` 默认使用按行分隔的 stdio。`--transport http` 在 `/mcp` 提供独立的 Streamable HTTP;Web 监听器也提供相同端点。现代 MCP 协议版本为 `2026-07-28`;旧版 `2025-11-25` 仅通过 stdio 兼容。两种服务均提供与 Python 相同的 `get_ddns_status`、`update_dns_records` 工具。 HTTP 默认监听回环地址。非回环监听必须设置 `--http-token TOKEN`,客户端使用 `Authorization: Bearer TOKEN` 鉴权。可重复传入 `--http-origin ORIGIN` 添加允许的 浏览器来源。Web 和 MCP HTTP 均执行来源检查与鉴权。不要将明文 HTTP 暴露于 不可信网络,应使用可信的 TLS 反向代理或隧道。状态响应不包含凭据,但受监听器 鉴权保护的配置 API 会按设计返回完整配置,包括 Provider 凭据。监听 token、配置和 备份均应视为机密;即使不设置回环监听 token,本机访问仍是信任边界。 监听 token 授予敏感配置访问与 DNS 更新权限,不是只读监控凭据。Web 同步会以 进程用户权限执行经鉴权管理的本地配置中的 `cmd:`/`shell:` 规则,因此只应向可信 管理员授予访问权限。 二进制在编译时内嵌现有 `web/` 控制台资源和 `ddns/config/field-model.json`, 提供页面无需前端服务器或 Python 运行时。修改这些输入后须重新构建;源码构建需 保留仓库布局,不能只复制孤立的 `rust/` 目录。 **迁移限制:** 尚未实现 `task`、系统任务检测及自动接管。启用 Rust 定时同步前, 必须手动停用旧 Python/主机的 systemd、cron、launchd 或 schtasks 任务,避免重复运行。 Web 调度器不会安装或替代主机服务。已有 Rust `regex:` 与独立缓存限制仍然适用; `log_format`、`log_datefmt` 仅为配置兼容而接受,不保证 Python 日志格式等价。 ## 架构 - `cli.rs`:兼容参数、别名和列表规则,不依赖 CLI 框架。 - `config/`:环境变量、JSONC、受限 Python 字面量、v4.1 展平和优先级合并。 - `http.rs`:基于 `ureq`/rustls 的同步传输、代理、重试、TLS 和脱敏。 - `ip.rs`:IPv4/IPv6 地址发现与规则回退。 - `cache.rs`:独立于 Python 的版本化缓存和原子写入。 - `provider/`:公共 CRUD 编排及全部 DNS Provider。 - `update.rs`:逐配置、地址族和域名执行,继续后续项并汇总失败。 兼容性处理限制在 CLI 与配置加载边界;进入核心后,Provider 使用 `ProviderId` 枚举,别名只解析一次。Provider 通过 trait object 做运行时分发, 借用同步 HTTP 客户端;单次更新请求借用域名、地址和 Provider 扩展配置,避免 为每个域名复制整份配置。Provider 特有的 `extra` 仍保留动态 JSON 值,因为其 字段由各 DNS API 定义。 域名列表在配置层只拒绝空条目,允许 `debug`、`callback` 使用 `localhost` 等单标签; 具体域名约束由各 Provider 处理。 整数日志级别支持 `--log-level -5` 和 `--log-level=-5` 两种写法。 Cloudflare 和腾讯云查询用 `Result>` 区分记录不存在与请求失败;只有正常空列表或已知的 查询未命中错误码才允许继续查找或创建。Cloudflare 和腾讯云写入响应必须包含 有效的结果标识,畸形响应不会被当作成功写入缓存。腾讯云的 DNSPod、EdgeOne 加速和 EdgeOne DNS 模式由枚举确定服务与 API 版本,避免自由组合字符串和布尔状态。 NameSilo 和 ClouDNS 仅在域名查询返回文档定义的未命中响应时尝试下一个候选域名; 鉴权、权限、HTTP 和畸形响应错误直接上报,不会被替换为“域名不存在”。 ClouDNS、华为云与 NameSilo 的记录查询会拒绝畸形集合或缺少匹配字段的记录, 避免误建重复记录;ClouDNS 的 `{}` 和 `[]` 均保留为空集合。 华为云查询签名与实际请求使用相同的 RFC3986 查询字符串,空格编码为 `%20` 而非 `+`,发送时不再二次表单编码;其他 Provider 的表单编码保持不变。 AliDNS 和 ESA 在提供 `extra` 时仍发送更新请求,即使地址和 TTL 没有变化; 未提供扩展参数时保留原有的跳过无变化更新行为。 DNSPod 中国版和国际版在标准写入参数之后合并 `extra`,保留其覆盖优先级。 IP 文本提取使用标准库 `IpAddr` 校验完整地址,支持 `IP:2001:db8::1`、 `address:192.0.2.1` 等紧凑文本标签;无效 IPv6 不会被截断成可用片段。 `regex:` 从提取的原始地址开头匹配,保留 IPv6 的零填充、大小写和压缩写法, 例如 `^2001:0db8:` 可以匹配 `2001:0db8::1`;返回值仍为校验后的 `IpAddr`。 `local` 和数字索引先按地址族排除回环与未指定地址,再按原有顺序从 `0` 开始索引; 没有可用地址或索引越界时明确报错。私有地址和链路本地地址仍可选取。 生产代码不使用 unsafe,不引入异步运行时、通用错误框架或 mock 框架。Provider 测试通过注入 HTTP 客户端完成,集成测试只访问本机 fixture。 ## 验证 ```bash cargo fmt --manifest-path rust/Cargo.toml --check cargo clippy --manifest-path rust/Cargo.toml --locked --all-targets --all-features -- -D warnings cargo test --manifest-path rust/Cargo.toml --locked ``` CI 还会在 Linux x64/arm64、Windows x64、macOS x64/arm64 构建、测试并打包 二进制;Linux x64/arm64 还会构建和离线冒烟测试 Docker 镜像。 ## 安全边界 - `ssl=auto` 会在确认属于证书校验错误时警告并重试一次不校验证书的连接;生产环境优先使用 `ssl=true`。 - 远程配置中的 `cmd:` 与 `shell:` 会执行本机命令,只能加载可信 URL。 - `regex:` 使用 Rust `regex` 语法,不支持 Python 环视和反向引用;不兼容模式会返回明确错误。 - 复用配置中的自定义 `cache` 路径时,Rust 写入同级 `.ddns-rs`,不会覆盖 Python 缓存。 - Python 字面量解析器只接受字典、列表/元组、字符串、数字、`True`、`False` 和 `None`,不会执行表达式。 - 日志与错误信息将已登记的 ID、token 及其百分号编码、表单编码形式替换为固定的 `***`,不保留凭据首尾字符;缓存不保存凭据。 - DNS.COM 签名请求体不写入日志,API 错误消息中的当前签名会脱敏,JSON 解析错误不回显原始响应;敏感请求的 HTTP 错误只保留状态码。 - 诊断中的 URL 会隐藏用户信息、非根路径、查询值与片段,避免 Callback 或远程配置的路径凭据泄漏;实际发送的 URL 不变。 ## 后续顺序 1. 迁移 systemd、cron、launchd、schtasks。 2. 验证 Web/MCP 在长期运行与平台迁移中的兼容性。 3. 达到完整等价并稳定运行后再讨论改名为 `ddns`。