2.0.0：安装时可设中转服务器 + 手表端新增设置页 + 中转默认留空

按用户定的版本规矩，这一轮既有新内容又有重构，所以升第二位：
1.9.0 -> 2.0.0（最后一位清 0）。

== 一、安装时就能设中转服务器（用户要求）==

安装向导那一页现在是两个输入框：配对码 + 中转服务器。
装的时候是"第一次设置"，最该一次把地址填好，不然装完还得去
双击 exe 找设置窗口，很容易漏。

只填域名就行（自动补 https://），留空 = 只走局域网。
批量静默部署同样支持：
    ykxmz-setup-2.0.0.exe /VERYSILENT /CODE=246810 /RELAY=relay.你的域名

为此新增了一个 --relay-url 参数：
    --relay       连接一次、打印状态、然后退出（人工排查用）
    --relay-url   只写进配置，不连接、不退出（安装流程用）
用错那个的话，安装包的命令链会在中间就 return，后面的 --silent
根本跑不到 —— 所以必须分开。

== 二、手表端新增「设置」页（用户要求）==

用户原话："安卓端加一个设置，各种设置就在这里面调"。
以前设置散在首页（只有一个"中转设置"按钮），现在统一收进设置页：
    * 中转服务器（填写 / 一键清空）
    * 记住的配对码（显示 / 清除）
    * 已配对的电脑（看数量 / 清空，带二次确认）
    * 关于（版本号从 APK 读，不会和代码对不上）

== 三、中转地址默认留空 + 一次性迁移（用户报的问题）==

用户原话："安卓端为什么还是默认填的我的中转站，以后改成手动填"。

代码里的默认值早就是空的了，但用户看到的还是那个地址 ——
因为**老版本 1.5.0 把作者的域名硬编码成了默认值**，第一次运行就
存进了 SharedPreferences。装新 APK 不会清偏好，所以那个值一直留着。
用户其实从来没"选"过这个地址，是程序硬塞的。

所以加了 migrateLegacyRelay()：存的值如果正好等于当时内置的域名，
就当成没设过、清掉。只清完全相等的，用户手填的不会动。

== 四、两个测试自身的毛病 ==

* test_lan_optimize 那条"没有把旧公网域名当默认值"太粗 ——
  它查的是源码里任何位置，于是迁移注释里引用那行老代码就把它判红了。
  改成先剥掉注释再查（断言要测的是"有没有把它当默认值"）。

* test_image_modes 偶发红：它开真实 Tk 全屏窗口，在全量套件里跑
  偶尔起不来（单独跑 19/19 全过）。加了有界重试。
  偶发红的测试会让"全量通过"失去意义。

== 五、新增防复发断言 ==

_tests/test_relay_default_blank.py（15 项，回归阶段 3a3b）：
    * 默认值必须是空串
    * 除了迁移清单，源码里不许有写死的中转域名
    * 迁移必须真的被调用（不是只定义）
    * 迁移必须是 remove，不能换成另一个硬编码值
    * Windows 端的默认也得是空

理由：用户说过"这个中转站我就自己用，不对外" —— 值得用断言钉死。

== 六、顺手修的 ==

* release_all.py 崩在 Gitee 那一步：Gitee 的
  GET /releases/tags/{tag} 在"标签存在、但还没发行版"时返回的是
  **HTTP 200 + JSON null**（不是 404），于是 .get("id") 直接
  AttributeError。发 1.9.0 时踩到，已改成容忍 None。

* installer/build_installer.py 里 license-combined.txt 是从
  docs/ 三份文档重新生成的，所以光改生成物没用（下次构建又变回去）。
  改成在 build_license() 里统一注入当前版本。

* ExE 文件属性里的版本（version_info.txt）从 1.7.1 起就一直没跟上。

全量回归 43 个阶段全部通过。
