mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 21:50:44 +00:00
阶段1 网络加速 - 发现隧道在跑 QUIC(ExecStart 丢了 --protocol http2), 实测 QUIC 可用率 48% vs HTTP/2 80%+,是重大性能回退。 恢复后公网延迟从 4141ms 降到 690ms(快 6 倍),可用率 92%。 - 优选 IP 结论:无效。hosts 覆盖确实生效了(日志里连的就是指定 IP), 但落点仍是 lax —— 运营商把所有 Cloudflare anycast 都路由到美西。 已清掉这段无效配置。 - BBR 不可用:设备是自定义内核 6.4.0-rc4-jsbsbxjxh66, 没编 tcp_bbr 模块也没配置项。改用 cubic + 调优: TCP 缓冲 4MB、tcp_fastopen=3、tcp_slow_start_after_idle=0 (长轮询场景关键)、tcp_tw_reuse=1。 阶段2 官网重做 - 拆出独立模块 relay/landing.py(relay.py 已两千多行, 再塞 HTML 没法看) - 信息结构参考成熟产品站:吸顶导航 / Hero / 九宫格功能 / 三步上手 / 下载 / FAQ / 页脚 - 保持白色简洁:底白、浅灰面板、细边框、大量留白 - logo 用内联 SVG(ICO 放 img 里实测是破图) - 管理入口从官网移除(管理不对外开放) 过程中被自己的测试抓到两个 bug: - 模块叫 site.py 会和 Python 标准库重名,import site 拿到的是标准库 - _logo() 里把 % 格式化和字符串拼接混用,直接抛 TypeError
213 lines
9.1 KiB
Python
213 lines
9.1 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""目标验收:逐条核对用户提出的每一项要求,每条都给证据。
|
||
|
||
用户的目标:
|
||
"在 Debian 随身WiFi 上部署中转服务器 + Cloudflare Tunnel,实现跨网络远程控制。
|
||
交付物为压缩包,内含三部分:被控端(Windows agent)、服务器端(relay)、控制端(Android APK)。
|
||
要求:单设备单配对码(设备曾设置过则复用旧码)、服务器端可管理、
|
||
面向 512MB 内存做优化。另需为电脑端所有按钮增加回退/撤销能力。"
|
||
|
||
这个脚本**不重复跑那些已经跑过的测试**,而是逐条给"这条要求达成了"的证据,
|
||
并明确指出哪一条还没有完全达成。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import subprocess
|
||
import sys
|
||
import urllib.error
|
||
import urllib.request
|
||
import zipfile
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parent.parent
|
||
sys.path.insert(0, str(ROOT / "_build"))
|
||
|
||
from devssh import run as ssh # noqa: E402
|
||
|
||
RESULTS = []
|
||
|
||
|
||
def item(requirement: str, ok: bool, evidence: str, gap: str = "") -> None:
|
||
RESULTS.append({
|
||
"requirement": requirement,
|
||
"ok": ok,
|
||
"evidence": evidence,
|
||
"gap": gap,
|
||
})
|
||
mark = "✓" if ok else ("~" if gap else "✗")
|
||
print(f" [{mark}] {requirement}")
|
||
print(f" 证据: {evidence}")
|
||
if gap:
|
||
print(f" 差距: {gap}")
|
||
|
||
|
||
def device(command: str, timeout: int = 60) -> str:
|
||
_code, out, _err = ssh(command, timeout=timeout, quiet=True)
|
||
return out.strip()
|
||
|
||
|
||
# Cloudflare 的 Browser Integrity Check 会拦 Python 默认 UA
|
||
# (403 + error code 1010),所以请求要带上这个。
|
||
_UA = {"User-Agent": "LanRemote/1.4.0"}
|
||
|
||
|
||
def http_get(url: str, timeout: int = 15) -> tuple:
|
||
try:
|
||
req = urllib.request.Request(url, headers=_UA)
|
||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||
return resp.status, resp.read().decode("utf-8", "replace")
|
||
except urllib.error.HTTPError as exc:
|
||
return exc.code, exc.read().decode("utf-8", "replace")
|
||
except Exception as exc: # noqa: BLE001
|
||
return 0, str(exc)
|
||
|
||
|
||
def main() -> int:
|
||
print("=" * 70)
|
||
print(" 目标验收:逐条核对")
|
||
print("=" * 70)
|
||
print()
|
||
|
||
# ---------------- 1. 中转服务器部署 ----------------
|
||
print("一、在随身 WiFi 上部署中转服务器")
|
||
status = device("systemctl is-active lanremote-relay")
|
||
enabled = device("systemctl is-enabled lanremote-relay 2>/dev/null")
|
||
health = device("curl -s --max-time 8 http://127.0.0.1:8080/v1/health")
|
||
mem_kb = 0
|
||
try:
|
||
mem_kb = json.loads(health).get("memoryKB", 0)
|
||
except Exception: # noqa: BLE001
|
||
pass
|
||
item("中转服务在设备上运行", status == "active",
|
||
f"systemctl={status},开机自启={enabled},Rss={mem_kb}KB ({mem_kb // 1024}MB)")
|
||
|
||
# ---------------- 2. Cloudflare Tunnel ----------------
|
||
print()
|
||
print("二、Cloudflare 隧道")
|
||
tunnel_active = device("systemctl is-active lanremote-tunnel")
|
||
url = device("cat /root/lanremote-relay/public_url.txt 2>/dev/null")
|
||
code, _body = http_get(f"{url}/v1/health") if url else (0, "")
|
||
item("Cloudflare 隧道已建立且公网可达", tunnel_active == "active" and code == 200,
|
||
f"服务={tunnel_active},地址={url or '(无)'},从公网访问 HTTP {code}")
|
||
cert = device("test -f /root/.cloudflared/cert.pem && echo yes || echo no")
|
||
item("具名隧道(固定域名 relay.xxmxxs.top)",
|
||
cert == "yes",
|
||
f"cert.pem={'存在' if cert == 'yes' else '不存在'}(授权未完成)",
|
||
gap="等待用户在浏览器完成 Cloudflare 授权。"
|
||
"已定位并修复了阻塞它的两个问题:"
|
||
"① api.cloudflare.com 被 DNS 劫持(已写 hosts 钉真实 IP);"
|
||
"② install.sh 的 CRLF 行尾(已改 LF)")
|
||
|
||
# ---------------- 3. 三件套压缩包 ----------------
|
||
print()
|
||
print("三、交付压缩包含三部分")
|
||
zip_path = ROOT / "dist" / "LanRemote-跨网络版-1.4.0.zip"
|
||
parts = {}
|
||
if zip_path.is_file():
|
||
with zipfile.ZipFile(zip_path) as archive:
|
||
names = archive.namelist()
|
||
parts = {
|
||
"被控端": any("ykxmz.exe" in n for n in names),
|
||
"服务器端": any("relay.py" in n for n in names),
|
||
"控制端": any(".apk" in n for n in names),
|
||
}
|
||
size = zip_path.stat().st_size / 1024 / 1024 if zip_path.is_file() else 0
|
||
item("压缩包含被控端 / 服务器端 / 控制端",
|
||
all(parts.values()),
|
||
f"{zip_path.name} {size:.2f} MB " +
|
||
" ".join(f"{k}={'✓' if v else '✗'}" for k, v in parts.items()))
|
||
|
||
# ---------------- 4. 单设备单配对码 ----------------
|
||
print()
|
||
print("四、单设备单配对码(设备设过就复用旧码)")
|
||
# 用真实的 relay 跑一次:同 deviceId 报新码 -> 应改回旧码;异设备用同码 -> 应拒绝
|
||
result = subprocess.run(
|
||
[sys.executable, "-u", str(ROOT / "_tests" / "test_relay_local.py")],
|
||
capture_output=True, text=True, encoding="utf-8", errors="replace")
|
||
relay_ok = "失败 0 项" in result.stdout
|
||
item("单设备单配对码 + 一个码只能一台设备用", relay_ok,
|
||
"test_relay_local.py(经真实中转):" +
|
||
(result.stdout.strip().splitlines()[-1] if result.stdout.strip() else "无输出"))
|
||
|
||
# ---------------- 5. 服务器端可管理 ----------------
|
||
print()
|
||
print("五、服务器端可管理")
|
||
if url:
|
||
plain = device("cat /root/lanremote-relay/data/admin_password.txt 2>/dev/null")
|
||
c_login, _ = http_get(f"{url}/v1/admin/devices")
|
||
c_url, url_body = http_get(f"{url}/url")
|
||
item("管理页需要登录 + 管理接口 + 可查当前地址",
|
||
c_login == 401 and c_url == 200,
|
||
f"管理接口未登录={c_login}(应为 401);"
|
||
f"/url={c_url} 返回 {url_body.strip()[:60]};"
|
||
f"管理密码={plain or '@REVAedu120830'}")
|
||
else:
|
||
item("管理页需要登录 + 管理接口", False, "没有公网地址,无法测")
|
||
|
||
# ---------------- 6. 512MB 内存优化 ----------------
|
||
print()
|
||
print("六、面向 512MB 内存做优化")
|
||
freem = device("free -m | awk 'NR==2{print $3\" \"$7}'")
|
||
queue_cap = device("grep -o 'RELAY_QUEUE_BYTES=[0-9]*' "
|
||
"/etc/systemd/system/lanremote-relay.service")
|
||
memmax = device("grep -o 'MemoryMax=[0-9A-Za-z]*' "
|
||
"/etc/systemd/system/lanremote-relay.service")
|
||
item("内存占用受控且有上限保护",
|
||
mem_kb > 0 and mem_kb < 60 * 1024,
|
||
f"中转 Rss={mem_kb // 1024}MB;系统已用/可用={freem or '?'}MB;"
|
||
f"{memmax or 'MemoryMax 未设'};{queue_cap or '队列上限未设'}")
|
||
|
||
# ---------------- 7. 所有按钮加回退 ----------------
|
||
print()
|
||
print("七、电脑端所有按钮加回退/撤销")
|
||
result = subprocess.run(
|
||
[sys.executable, "-u", str(ROOT / "_tests" / "test_undo.py")],
|
||
capture_output=True, text=True, encoding="utf-8", errors="replace")
|
||
undo_ok = "失败 0 项" in result.stdout
|
||
item("开关类 + 画面类都能回退,不可逆的不进列表", undo_ok,
|
||
"test_undo.py:" +
|
||
(result.stdout.strip().splitlines()[-1] if result.stdout.strip() else "无输出"))
|
||
|
||
# ---------------- 8. 跨网络远程控制(端到端)----------------
|
||
print()
|
||
print("八、跨网络远程控制确实能用")
|
||
if url:
|
||
admin = device("cat /root/lanremote-relay/data/admin_key.txt 2>/dev/null")
|
||
result = subprocess.run(
|
||
[sys.executable, "-u",
|
||
str(ROOT / "_tests" / "test_relay_e2e.py"), url, admin],
|
||
capture_output=True, text=True, encoding="utf-8", errors="replace")
|
||
e2e_ok = "失败 0 项" in result.stdout
|
||
item("经公网隧道完成注册/接入/收发/鉴权", e2e_ok,
|
||
"test_relay_e2e.py:" +
|
||
(result.stdout.strip().splitlines()[-1] if result.stdout.strip() else "无输出"))
|
||
else:
|
||
item("经公网隧道端到端", False, "没有公网地址")
|
||
|
||
# ---------------- 汇总 ----------------
|
||
print()
|
||
print("=" * 70)
|
||
print(" 验收汇总")
|
||
print("=" * 70)
|
||
full = sum(1 for r in RESULTS if r["ok"])
|
||
partial = sum(1 for r in RESULTS if not r["ok"] and r["gap"])
|
||
missing = sum(1 for r in RESULTS if not r["ok"] and not r["gap"])
|
||
for r in RESULTS:
|
||
mark = "✓ 达成" if r["ok"] else ("~ 部分" if r["gap"] else "✗ 未达成")
|
||
print(f" {mark} {r['requirement']}")
|
||
print()
|
||
print(f" 完全达成 {full} 项 / 部分达成 {partial} 项 / 未达成 {missing} 项")
|
||
if partial:
|
||
print()
|
||
print(" 未完全达成的:")
|
||
for r in RESULTS:
|
||
if not r["ok"] and r["gap"]:
|
||
print(f" · {r['requirement']}")
|
||
print(f" {r['gap']}")
|
||
return 0 if missing == 0 else 1
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|