Files
drouter/scripts
liuzhuohua aab35d48a7 feat(ddns): 真实下发 + 「检测间隔」终于有消费方
原状:DDNS 页面「立即更新」只打印一份更新计划,DNS 记录纹丝不动;
「检测间隔(秒)」字段全仓没有任何定时器消费,纯装饰。

本次做两件事:

1) 真实下发
   - 新增 _http_open:区分「HTTP 层失败」与「业务失败」。
     这类接口的共同点是**用 HTTP 200 回一句 error 表达失败**,
     只判 HTTP 层的话页面显示「成功」而记录没动。
   - 接入 7 家:custom / dnspod / aliyun / cloudflare / noip /
     dyndns / dyndns2。各自的成功判据不同(DNSPod 看 status.code,
     阿里云看 Code,Cloudflare 看 success)。
   - huaweicloud / dnspod_tencent **如实报「暂未接入」**并给替代建议,
     不写「看起来对」的实现 —— 未验证过的签名算法不能假装能用。
   - update 分支三态:全成功 / 全失败(DDNS_UPDATE_FAILED) /
     部分成功(DDNS_UPDATE_PARTIAL)。部分成功不显示成全成功。
   - 下发器内部异常被兜住并如实上报,不把动作打断成 500。

2) 检测间隔有消费方
   - 新增 backend/drouter-ddnsd.py:读 interval → 未启用直接跳过 →
     失败退避 → 探测公网地址 → **只在地址变化时才下发**
     (IP 没变不敲 API,避免触发服务商频率限制)。
   - 失败退避 3 倍递增、上限 1 小时。退避期内秒退,不探测外网
     (一次完整探测 5 秒以上,失败时反复探测是自伤)。
   - _write_ddns_timer 由 save / on / off 三处调用,改了间隔立刻
     重写 timer,不会出现「页面显示已保存、实际还是旧周期」。
   - 守护不依赖 init:容器形态下由常驻守护代跑。

真机验收(192.168.7.3,Debian 13):
  - timer generated/enabled/active,OnUnitActiveSec=120s,跟随用户设定
  - 地址未变 → last_result='unchanged',不下发
  - 篡改 last_ip4 制造「地址变了」→ 真下发 → 失败如实记录并退避
  - 退避内第二次执行 0.136s 返回(首次 4.3s),确认没探测外网
  - HTTP 层失败与业务失败文案可区分
  - 验收后已恢复出厂默认(停用),timer 单元与退避文件已清理

顺带修掉 3 个真缺陷:
  - 阿里云更新参数对新建 dict 做 del up['Action'] → KeyError。
    UpdateDomainRecord 少了 Action/Format/Version 会被阿里云拒。
  - 自定义 URL 失败词表漏 badauth:返回 badauth 被判成功。
  - DNSPod 错误文本在 status.message 而非顶层 message,
    只读顶层时用户只看到「失败」没有原因。

判据:
  - 新增 _dev/t-111-ddns-push.py(44 项,12 组),全绿。
    真实验签不靠网络:_http_open 换成假实现按剧本返回不同正文,
    并在启动时掐死 socket.connect —— 判据测试打真实网络既慢又不确定,
    还会污染对方日志,且失败时看起来像「产品有 bug」。
  - t-packaging 补 2 条:deploy.sh 里每个后端模块都要出现。
    本次就踩到 —— build-deb 用 backend/*.py 通配复制(ddnsd 自动进包、
    判据全绿),可 deploy.sh 是**逐个 install 硬名单**,ddnsd 漏在里面,
    表现为「本地全绿、真机没有文件」,timer 起来报 203/EXEC。
    部署才是真正生效的那一步,两份清单都得查。已反向验证会红。

全量静态回归 43 个检查器 0 失败。
2026-10-03 21:45:43 +08:00
..