mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 23:00:44 +00:00
手表端
- 内置 GTS Root R4 / GlobalSign Root CA
修 SSLHandshakeException: Chain validation failed
- 连接错误翻译成人话:证书 / 时间 / 解析 / 超时分别给提示
- 经中转连上后用服务器报的地址刷新已配对记录
- 版本号 1.3.0 -> 1.5.0(AndroidManifest 之前漏改了)
服务器端
- 支持 chunked 请求体,修每 16 秒一次的连接错乱与大面积 502
- 网络调优 net-tune.sh(开机自动应用)
- 新增下载区,管理页加下载卡片
- 隧道改 HTTP/2 + 双连接
打包方式变更
- 从一个合并 zip 改成三个独立产物:
ykxmz-agent-*.zip 被控端
ykxmz-server-*.zip 服务器端
ykxmz-watch-*.apk 控制端
275 lines
9.7 KiB
Python
275 lines
9.7 KiB
Python
# -*- coding: utf-8 -*-
|
||
r"""把打包改成"三端分开出"。
|
||
|
||
用户要求:
|
||
"三端分别打包,控制端不用打包成 zip,直接上传 apk"
|
||
|
||
所以产物是三个文件(原来是一个大 zip):
|
||
dist/ykxmz-agent-1.5.0.zip 被控端(Windows)
|
||
dist/ykxmz-server-1.5.0.zip 服务器端(随身 WiFi / Linux)
|
||
dist/ykxmz-watch-1.5.0.apk 控制端(手表,直接给 APK 不打 zip)
|
||
|
||
三个包里都带上三份文档(许可协议 / 安装前必读 / 安装后须知),
|
||
这样用户拿到哪一个都知道自己在装什么、有什么红线。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
|
||
PKG = ROOT / "_build" / "package_remote.py"
|
||
|
||
# 从 "def main() -> int:" 起,到文件末尾的 if __name__ 之前,全换成新的
|
||
NEW_MAIN = '''def _zip_dir(zip_path: Path, source: Path, arc_root: str) -> int:
|
||
"""把一个目录打成一个 zip(内部带一层 arc_root 目录名)。"""
|
||
if zip_path.exists():
|
||
zip_path.unlink()
|
||
count = 0
|
||
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED,
|
||
compresslevel=6) as archive:
|
||
for item in sorted(source.rglob("*")):
|
||
if item.is_file():
|
||
archive.write(item, Path(arc_root) / item.relative_to(source))
|
||
count += 1
|
||
return count
|
||
|
||
|
||
def _copy_docs(target: Path) -> int:
|
||
"""把三份正式文档拷到目标目录(UTF-8 BOM + CRLF)。"""
|
||
done = 0
|
||
for name in ("LICENSE.txt", "BEFORE-INSTALL.txt", "AFTER-INSTALL.txt"):
|
||
src = DOCS / name
|
||
if src.is_file():
|
||
write_readme(target / name, src.read_text(encoding="utf-8"))
|
||
done += 1
|
||
else:
|
||
print(f" [警告] 缺少文档 {src}")
|
||
return done
|
||
|
||
|
||
def main() -> int:
|
||
if not AGENT_SRC.is_dir():
|
||
print(f" [失败] 找不到被控端: {AGENT_SRC}")
|
||
return 1
|
||
if not APK_SRC.is_file():
|
||
print(f" [失败] 找不到 APK: {APK_SRC}")
|
||
return 1
|
||
|
||
dist = ROOT / "dist"
|
||
dist.mkdir(parents=True, exist_ok=True)
|
||
work = dist / "_build_tmp"
|
||
if work.exists():
|
||
shutil.rmtree(work, ignore_errors=True)
|
||
|
||
results = []
|
||
|
||
# ================= 1. 被控端 =================
|
||
print(" [1/3] 被控端(Windows)")
|
||
agent_name = f"ykxmz-agent-{VERSION}"
|
||
agent_dir = work / agent_name
|
||
shutil.copytree(AGENT_SRC, agent_dir,
|
||
ignore=shutil.ignore_patterns("data", "received",
|
||
"*.idsig", "*.log"))
|
||
for tool in TOOLS:
|
||
if not (agent_dir / tool).is_file():
|
||
src = ROOT / "tools" / tool
|
||
if src.is_file():
|
||
shutil.copy2(src, agent_dir / tool)
|
||
write_readme(agent_dir / "README-agent.txt", AGENT_README)
|
||
for junk in ("先看这个.txt", "使用说明.txt"):
|
||
target = agent_dir / junk
|
||
if target.is_file():
|
||
target.unlink()
|
||
_copy_docs(agent_dir)
|
||
if not (agent_dir / "ykxmz.exe").is_file():
|
||
print(" [失败] 被控端里没有 ykxmz.exe")
|
||
return 1
|
||
agent_zip = dist / f"{agent_name}.zip"
|
||
n = _zip_dir(agent_zip, agent_dir, agent_name)
|
||
results.append(("被控端", agent_zip, n))
|
||
|
||
# ================= 2. 服务器端 =================
|
||
print(" [2/3] 服务器端(随身 WiFi / Linux)")
|
||
server_name = f"ykxmz-server-{VERSION}"
|
||
server_dir = work / server_name
|
||
server_dir.mkdir(parents=True)
|
||
# 文件清单写死容易漏 —— watchdog.sh / net-tune.sh 都漏过一次。
|
||
# 改成:relay 目录里所有 .py / .sh 都带上。
|
||
wanted = sorted(
|
||
[f for f in RELAY_SRC.iterdir()
|
||
if f.suffix in (".py", ".sh") and f.is_file()],
|
||
key=lambda f: f.name)
|
||
for src in wanted:
|
||
shutil.copy2(src, server_dir / src.name)
|
||
write_readme(server_dir / "README-server.txt", RELAY_README)
|
||
_copy_docs(server_dir)
|
||
for must in ("relay.py", "install.sh", "watchdog.sh", "net-tune.sh"):
|
||
if not (server_dir / must).is_file():
|
||
print(f" [警告] 服务器端少了 {must}")
|
||
server_zip = dist / f"{server_name}.zip"
|
||
n = _zip_dir(server_zip, server_dir, server_name)
|
||
results.append(("服务器端", server_zip, n))
|
||
|
||
# ================= 3. 控制端(直接给 APK)=================
|
||
print(" [3/3] 控制端(手表)—— 不打 zip,直接给 APK")
|
||
# 顺带把说明也放一份在旁边,方便看
|
||
watch_note = dist / f"ykxmz-watch-{VERSION}-说明.txt"
|
||
write_readme(watch_note, WATCH_README)
|
||
results.append(("控制端", APK_SRC, 1))
|
||
|
||
shutil.rmtree(work, ignore_errors=True)
|
||
|
||
# ================= 汇总 =================
|
||
print()
|
||
print(" ================ 产物 ================")
|
||
ok = True
|
||
for label, path, count in results:
|
||
if not path.is_file():
|
||
print(f" {label}: ✗ 缺失!")
|
||
ok = False
|
||
continue
|
||
size = path.stat().st_size / 1024 / 1024
|
||
extra = f"({count} 个文件)" if count > 1 else ""
|
||
print(f" {label:6} {path.name:34} {size:6.2f} MB {extra}")
|
||
if not ok:
|
||
return 1
|
||
|
||
# ================= 验证 =================
|
||
print()
|
||
print(" 验证压缩包内容:")
|
||
for label, path, _count in results:
|
||
if path.suffix != ".zip":
|
||
continue
|
||
with zipfile.ZipFile(path) as archive:
|
||
names = archive.namelist()
|
||
bad = archive.testzip()
|
||
if bad:
|
||
print(f" [失败] {path.name} 里有坏文件: {bad}")
|
||
return 1
|
||
print(f" {path.name}: {len(names)} 个条目,完整")
|
||
|
||
with zipfile.ZipFile(results[0][1]) as archive:
|
||
names = archive.namelist()
|
||
if not any("ykxmz.exe" in n for n in names):
|
||
print(" [失败] 被控端包里没有 ykxmz.exe")
|
||
return 1
|
||
with zipfile.ZipFile(results[1][1]) as archive:
|
||
names = archive.namelist()
|
||
for must in ("relay.py", "install.sh", "watchdog.sh", "net-tune.sh"):
|
||
if not any(must in n for n in names):
|
||
print(f" [失败] 服务器端包里没有 {must}")
|
||
return 1
|
||
|
||
print()
|
||
print(" 三个产物齐全")
|
||
return 0
|
||
|
||
|
||
'''
|
||
|
||
# 手表说明单独抽出来(原来嵌在 main 里)
|
||
WATCH_README_BLOCK = '''WATCH_README = f"""========================================================
|
||
控制端(安卓手表) 版本 {VERSION}
|
||
========================================================
|
||
|
||
【装】
|
||
|
||
把 ykxmz-watch-{VERSION}.apk 传到手表上安装。
|
||
手表如果提示"未知来源",在设置里允许一下。
|
||
|
||
|
||
【连】
|
||
|
||
1) 打开 App,首页有四个东西:
|
||
中转设置(跨网络用) 填服务器端给的地址
|
||
局域网连接(同一个WiFi)同一个 WiFi 时走这条,最快
|
||
输入配对码连接 经中转连
|
||
服务器:还没测 点右边「检查服务器」看延迟
|
||
|
||
2) 电脑和手表在同一个 WiFi -> 点「局域网连接」,自动搜到电脑,点一下就连
|
||
电脑在外面 -> 点「输入配对码连接」,填电脑上那个 6 位码
|
||
|
||
3) 连上后是 10 个标签页:
|
||
媒体 屏幕 文件 语音 电脑 网络 系统 教室 效率 娱乐
|
||
|
||
|
||
【连不上时看这个】
|
||
|
||
App 会把错误翻译成人话:
|
||
"证书校验失败" -> 手表系统太旧或网络被劫持
|
||
"证书时间不对" -> 把手表的日期时间调成自动获取
|
||
"连接超时" -> 网络慢或被防火墙挡
|
||
"电脑拒绝连接" -> 电脑上 ykxmz 没开
|
||
"域名解析不了" -> 手表没联网
|
||
|
||
首页的「检查服务器」能测出到中转的延迟(快/一般/慢三档配色)。
|
||
|
||
|
||
【回退】
|
||
|
||
系统页最上面三个按钮:
|
||
回退上一步 撤销刚才那一步
|
||
回退全部 把能撤的都撤了
|
||
看能回退什么 列出还能撤哪些
|
||
|
||
能撤的:所有开关、所有画面(全屏大字/投图/通知等)
|
||
撤不了的:关机、重启、删文件、杀进程 —— 这些**不会**出现在回退列表里,
|
||
免得你以为什么都能退回去。
|
||
"""
|
||
|
||
|
||
'''
|
||
|
||
WATCH_PATTERN = re.compile(r'WATCH_README = f""".*?"""\n\n\n', re.S)
|
||
|
||
|
||
def main() -> int:
|
||
text = PKG.read_text(encoding="utf-8")
|
||
|
||
if "ykxmz-agent-" in text:
|
||
print(" 已经改过")
|
||
return 0
|
||
|
||
# 1. 把 WATCH_README 提到顶层的常量
|
||
# 原来它是嵌在 main() 里 write_readme 的第三个参数
|
||
old_watch = re.search(
|
||
r' write_readme\(watch_dir / "README-watch\.txt", f"""'
|
||
r'.*?"""\)\n', text, re.S)
|
||
if old_watch:
|
||
text = text[:old_watch.start()] + text[old_watch.end():]
|
||
print(" 摘掉了 main 里内嵌的手表说明")
|
||
else:
|
||
print(" [提示] 没找到内嵌的手表说明(可能已改)")
|
||
|
||
# 2. 插入 WATCH_README 常量(放在 RELAY_README 之后)
|
||
anchor = 'def write_readme(path: Path, text: str) -> None:'
|
||
if "WATCH_README =" not in text and anchor in text:
|
||
text = text.replace(anchor, WATCH_README_BLOCK + anchor, 1)
|
||
print(" 加了 WATCH_README 常量")
|
||
|
||
# 3. 换掉 main()
|
||
start = text.find("def main() -> int:")
|
||
end = text.find('if __name__ == "__main__":')
|
||
if start < 0 or end < 0 or end < start:
|
||
print(" [失败] 找不到 main() 的边界")
|
||
return 1
|
||
text = text[:start] + NEW_MAIN + text[end:]
|
||
print(" 替换了 main()(改成出三个产物)")
|
||
|
||
PKG.write_text(text, encoding="utf-8", newline="\n")
|
||
|
||
import ast
|
||
try:
|
||
ast.parse(text)
|
||
print(" package_remote.py 语法 OK")
|
||
except SyntaxError as exc:
|
||
print(f" 语法错误: 行 {exc.lineno}: {exc.msg}")
|
||
return 1
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|