* feat(rust): add Web dashboard and MCP services Share configuration, status and synchronization between Web and MCP; add authenticated HTTP, stdio lifecycle support, in-process scheduling and offline regression tests. Exercise native tests on the existing Windows/macOS/Linux CI matrix. Co-authored-by: openhands <openhands@all-hands.dev> * ci(rust): allow manual cross-platform validation Enable workflow_dispatch for feature-branch CI without requiring PR creation. Preserve matrices, permissions, publishing boundaries and trusted-branch cache policy. Co-authored-by: openhands <openhands@all-hands.dev> * ci(rust): validate feature branch pushes Run the existing Rust cross-platform matrix for feat/** pushes while retaining trusted-branch-only cache writes and unchanged release triggers. Co-authored-by: openhands <openhands@all-hands.dev> * ci(rust): remove temporary branch trigger The v5 pull request now exists and runs the complete native matrix, so restore the original trusted push branches while retaining Web/MCP paths and tests. Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev>
11 KiB
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:
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
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<Option<_>> 区分记录不存在与请求失败;只有正常空列表或已知的
查询未命中错误码才允许继续查找或创建。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。
验证
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:使用 Rustregex语法,不支持 Python 环视和反向引用;不兼容模式会返回明确错误。- 复用配置中的自定义
cache路径时,Rust 写入同级<path>.ddns-rs,不会覆盖 Python 缓存。 - Python 字面量解析器只接受字典、列表/元组、字符串、数字、
True、False和None,不会执行表达式。 - 日志与错误信息将已登记的 ID、token 及其百分号编码、表单编码形式替换为固定的
***,不保留凭据首尾字符;缓存不保存凭据。 - DNS.COM 签名请求体不写入日志,API 错误消息中的当前签名会脱敏,JSON 解析错误不回显原始响应;敏感请求的 HTTP 错误只保留状态码。
- 诊断中的 URL 会隐藏用户信息、非根路径、查询值与片段,避免 Callback 或远程配置的路径凭据泄漏;实际发送的 URL 不变。
后续顺序
- 迁移 systemd、cron、launchd、schtasks。
- 验证 Web/MCP 在长期运行与平台迁移中的兼容性。
- 达到完整等价并稳定运行后再讨论改名为
ddns。