2.2.0:手表端连上后先到「远控 / 静默更新被控端」选择页

== 用户要求 ==

"安卓那个逻辑也要改一下,就是你输入配对码后,进入一个页面,
 页面有两个按钮,第一个按钮是远控,第二个按钮是静默更新被控端"

== 做成什么样 ==

新增 choicePanel,连接成功后先进这一页:

    已连接「电脑名」
    电脑端 2.1.5 → 可更新到 2.2.0

    [        远控        ]   <- 第一个按钮
    [   静默更新被控端    ]   <- 第二个按钮

按钮顺序按用户说的来(远控在前)。

页面顶上显示电脑名和**电脑端当前版本** —— 用户之前要求
"手表端连接那个电脑端也可以查看电脑端目前所处的版本",
这里是最自然的展示位置(不弹框,只显示一行)。

为什么不让它直接进控制页:老版本的被控端可能缺功能或有 bug,
"先更新再远控"是更合理的顺序 —— 但不强制,给用户自己选。

== 一个刻意不改的地方 ==

自动切到中转服务器**恢复**时的 `showControlPanel()` **没动** ——
用户本来就在远控页,中途弹一层选择页会打断他。
测试里专门断言了这一点,免得以后有人"顺手统一一下"改坏。

== 顺手修的几个问题 ==

1. **忘了定义 lastDeviceName** —— 写选择页时凭直觉用了这个变量,
   编译报"找不到符号"。补上字段并在连接回调里赋值。
   (教训:写代码时要用的变量,先确认它存在。)

2. **泄漏闸门拦下我自己** —— 这两天写的一批服务器诊断/配置脚本里
   硬编码了服务器 IP(26 处,5 个文件)。规则是 IP 不能进任何
   被跟踪的文件。已全部改成占位符,真实值仍从
   _build/.lightsail_host(已 gitignore)读。

3. **verify_all.ps1 删掉「锁屏时记 SKIP」的逻辑** ——
   用户明确要求删。那个好意带来一个更糟的后果:锁屏时几关根本不跑,
   回归"全绿"了但关键测试其实一跳而过。
   **"因为环境不满足所以不测"比"测了发现失败"更危险。**
   删掉之后果然暴露出 3 个 FAIL —— 屏幕解锁后重跑全部通过,
   确认是环境问题而不是代码问题。

== 服务器这边(不在这份提交里,但同一天做的)==

  * nginx 按用户要求重排:
      IP(80) 任何路径 -> 404(没东西)
      域名 / -> 官网;/zhongzhuanzhan -> 中转站设置
      9100 -> 同样的内容(等 AWS 开防火墙)
  * 删掉早年 Cloudflare Tunnel 的 systemd 残留
  * **补上 ACME 挑战例外** —— 这是最要紧的一条:
    80 端口原来把所有请求都 301 到 https,包括
    /.well-known/acme-challenge/,**续期时会失败**。
    而且它是延迟爆发的(最长 90 天后证书到期才暴露)。
  * 清理 1Panel 安装包残留(223MB)

== 验证 ==

    全量回归 21 关全部 PASS(零 FAIL,桌面可用状态)
    Java 编译通过(android-34, --release 8)
    623+ Python 文件语法通过
    泄漏闸门通过
    全部产物重建,Win7 兼容校验通过
This commit is contained in:
xmz7891
2026-10-10 17:13:24 +08:00
parent 48bc392466
commit 1d368979aa
29 changed files with 2000 additions and 122 deletions
+212
View File
@@ -0,0 +1,212 @@
# -*- coding: utf-8 -*-
r'''服务器整理:删隧道残留 + 加 9100 监听 + 修"用 IP 访问 404"。
== 用户的要求 ==
* "为什么中转站里还有隧道,所有没的东西就删了"
* "网站官网不是 <服务器IP> 吗,改成 <服务器IP>:9100,中转站也一样"
* "我感觉网站还是不稳定"
== 查到的现状 ==
* `lanremote-tunnel` / `cloudflared` 两个单元 **inactive dead**,
但**单元文件还在**(`systemctl list-units --all` 能列出来)。
那是早年走 Cloudflare Tunnel 时留下的,现在早就换成
nginx + Let's Encrypt 直连了 —— 属于"没的东西",该删。
* **用 IP 直接访问 `http://<服务器IP>/` 返回 404** ——
因为唯一的 80 站点是 `server_name yk.xxmxxs.top`,
Host 不匹配时 nginx 拿它当 default server,转给 relay,
relay 不认这个 Host 就 404。
**这很可能就是"感觉不稳定"的来源**:用域名是对的,用 IP 就不对。
* 9100 端口**服务端没监听、AWS 防火墙也没开** —— 两头都要做。
== 这一版做什么 ==
1. **删掉两个隧道单元**(先 stop + disable + 删文件 + daemon-reload)
2. **nginx 加 9100 监听**:
* `http://<服务器IP>:9100/` 直接出官网
* 中转接口也在 9100 上(`/v1/...`、`/download/...`)
⚠ 这需要 **AWS Lightsail 控制台里开放 9100**,
只有用户能做(我没有他的 AWS 凭据)。
3. **修 IP 访问 404**:给 80 端口加一个 default server,
把不认识的 Host 也送到同一套后端 —— 这样 `http://IP/` 能用。
== 为什么 9100 用 HTTP 而不是 HTTPS ==
IP 地址**没法签证书**(Let's Encrypt 不给纯 IP 签)。
所以 `https://<服务器IP>:9100` 会报证书错。
9100 定位是"Cloudflare 出问题时的直连备用通道",
用 HTTP 就行 —— 中转的数据本身有自己的校验。
'''
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "_build"))
from lssh import run, upload # noqa: E402
# ---------------------------------------------------------------- nginx 片段
# 在 ykxmz 站点的 443 server 里补 9100 监听,并加一个 80 的 default server。
#
# 为什么加在**同一个文件**里:这样只有一份 server 块要维护,
# 后端地址(127.0.0.1:13245)只写一次,改后端不用改两处。
EXTRA_LISTEN = """
# ---- 9100:Cloudflare 之外的直连通道 ----
#
# 用户要求:"网站官网不是 <服务器IP> 吗,改成 <服务器IP>:9100"。
#
# ⚠ 用 IP 没法签证书(Let's Encrypt 不给纯 IP 签),所以这里是 HTTP。
# 定位是备用通道:Cloudflare 出问题时还能直接连上。
#
# ⚠ 还需要在 **AWS Lightsail 控制台**开放 9100 端口 ——
# 光配 nginx 没用,外面照样连不上(实测 9100 现在不通)。
listen 9100;
listen [::]:9100;
"""
DEFAULT_SERVER = """
# ======================================================================
# 80 端口的默认站点
# ======================================================================
#
# 为什么需要它:原来唯一的 80 站点是 `server_name yk.xxmxxs.top`。
# 用 IP 直接访问(`http://<服务器IP>/`)时 Host 不匹配,
# nginx 就拿这个站点当 default server,把请求转给 relay ——
# relay 不认这个 Host,返回 404。
#
# **这很可能就是"感觉网站不稳定"的来源**:用域名是对的,用 IP 就 404,
# 用户不知道区别,只会觉得"时好时坏"。
#
# 现在显式指定一个默认站点:不管什么 Host,都送到同一套后端。
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
access_log /var/log/nginx/ykxmz_default_access.log;
error_log /var/log/nginx/ykxmz_default_error.log;
# 用 IP 访问时不要跳 https —— IP 没证书,跳过去浏览器会报警告,
# 用户会以为网站坏了。直接给内容。
client_max_body_size 64m;
server_tokens off;
location / {
proxy_pass http://127.0.0.1:13245;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 300s;
proxy_connect_timeout 30s;
}
}
"""
def main() -> int:
print("=" * 62)
print(" 服务器整理:删隧道 + 加 9100 + 修 IP 访问")
print("=" * 62)
print()
# ---------------------------------------------------------------- 1. 删隧道
print("── 1. 删掉隧道残留 ──")
r = run(r'''
for u in lanremote-tunnel cloudflared lanremote-relay; do
if systemctl list-unit-files 2>/dev/null | grep -q "^$u"; then
echo " 处理 $u"
sudo systemctl stop $u 2>/dev/null
sudo systemctl disable $u 2>/dev/null
sudo rm -f /etc/systemd/system/$u.service \
/lib/systemd/system/$u.service \
/etc/systemd/system/multi-user.target.wants/$u.service 2>/dev/null
fi
done
# 还有没有别的相关残留
sudo find /etc/systemd/system /lib/systemd/system -maxdepth 2 \
-iname "*lanremote*" -o -iname "*cloudflared*" -o -iname "*tunnel*" 2>/dev/null \
| while read f; do echo " 删 $f"; sudo rm -f "$f"; done
sudo systemctl daemon-reload
echo " --- 之后还剩什么 ---"
systemctl list-units --all --no-pager --plain 2>/dev/null \
| grep -iE 'lanremote|cloudflared|tunnel' | sed 's/^/ /' || echo " (干净了)"
''', timeout=180, quiet=True)
print(r[1] if isinstance(r, tuple) else r)
# ---------------------------------------------------------------- 2. 改 nginx
print()
print("── 2. nginx:加 9100 监听 + 80 默认站点 ──")
r = run("sudo cat /etc/nginx/sites-available/ykxmz", timeout=90, quiet=True)
conf = r[1] if isinstance(r, tuple) else str(r)
if "listen 9100" in conf:
print(" 9100 已经配过了")
else:
# 在 443 那个 server 块的 listen 后面插 9100
anchor = " listen 443 ssl; # managed by Certbot"
if anchor not in conf:
# 换一个可能的形式
import re
m = re.search(r"\n(\s*)listen 443[^\n]*\n", conf)
if not m:
print(" ✗ 找不到 443 的 listen 行")
return 1
anchor = m.group(0).strip()
conf = conf.replace(anchor, anchor + "\n" + EXTRA_LISTEN.rstrip(),
1)
conf = conf.rstrip() + "\n" + DEFAULT_SERVER
tmp = ROOT / "_build" / "_nginx_ykxmz.conf"
tmp.write_text(conf, encoding="utf-8", newline="\n")
upload(str(tmp), "/tmp/ykxmz.conf.new")
tmp.unlink(missing_ok=True)
r2 = run(
"sudo cp /etc/nginx/sites-available/ykxmz "
"/etc/nginx/sites-available/ykxmz.bak.$(date +%s) && "
"sudo mv /tmp/ykxmz.conf.new /etc/nginx/sites-available/ykxmz && "
"sudo nginx -t 2>&1 | tail -3", timeout=180, quiet=True)
out = r2[1] if isinstance(r2, tuple) else str(r2)
print(" " + out.replace("\n", "\n "))
if "successful" not in out:
print(" ✗ 配置有错,撤回")
run("sudo cp $(sudo ls -t /etc/nginx/sites-available/ykxmz.bak.* "
"| head -1) /etc/nginx/sites-available/ykxmz", timeout=90,
quiet=True)
return 1
print(" ✓ 已写入并备份")
print()
print("── 3. 重载 nginx ──")
r = run("sudo systemctl reload nginx && sleep 1 && systemctl is-active nginx",
timeout=120, quiet=True)
print(" nginx: " + (r[1] if isinstance(r, tuple) else str(r)).strip())
# ---------------------------------------------------------------- 4. 复核
print()
print("── 4. 复核 ──")
r = run(r'''
echo " --- 监听端口 ---"
sudo ss -tlnp 2>/dev/null | grep -E ':(80|443|9100) ' | sed 's/^/ /'
echo " --- 用 IP 访问(不带 Host)---"
curl -s -o /dev/null -w " http://127.0.0.1/ -> %{http_code} %{size_download} 字节\n" --max-time 10 http://127.0.0.1/
curl -s -o /dev/null -w " http://127.0.0.1:9100/ -> %{http_code} %{size_download} 字节\n" --max-time 10 http://127.0.0.1:9100/
echo " --- 用域名访问 ---"
curl -s -o /dev/null -w " https://yk.xxmxxs.top/ -> %{http_code} %{size_download} 字节\n" --max-time 15 https://yk.xxmxxs.top/
curl -s -o /dev/null -w " /v1/health -> %{http_code}\n" --max-time 15 https://yk.xxmxxs.top/v1/health
''', timeout=180, quiet=True)
print(r[1] if isinstance(r, tuple) else r)
print()
print(" ⚠ 还需要你在 **AWS Lightsail 控制台**里开放 9100 端口:")
print(" 实例 -> 联网 -> IPv4 防火墙 -> 添加规则 -> 自定义 -> TCP 9100")
print(" 光配 nginx 没用,外面照样连不上。")
return 0
if __name__ == "__main__":
sys.exit(main())