mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 11:20:43 +00:00
ykxmz 1.5.0:手表端 TLS 证书兼容 + 三端分开打包 + 下载区
手表端
- 内置 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 控制端
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
r"""把 TlsCompat.java 加进各处的 Java 编译清单。
|
||||
|
||||
背景:
|
||||
RelayTransport 改成用 TlsCompat.open() 之后,
|
||||
那些"只编译一部分 java"的地方就编译不过了 ——
|
||||
它们没有把 TlsCompat.java 列进去。
|
||||
|
||||
需要补的三处:
|
||||
_tests/test_package_remote.py
|
||||
_tests/test_watch_lan_switch.py
|
||||
_tests/verify_all.ps1
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
|
||||
|
||||
TARGETS = [
|
||||
ROOT / "_tests" / "test_package_remote.py",
|
||||
ROOT / "_tests" / "test_watch_lan_switch.py",
|
||||
ROOT / "_tests" / "verify_all.ps1",
|
||||
]
|
||||
|
||||
|
||||
def patch(path: Path) -> bool:
|
||||
raw = path.read_bytes()
|
||||
had_bom = raw.startswith(b"\xef\xbb\xbf")
|
||||
text = raw.decode("utf-8-sig")
|
||||
|
||||
if "TlsCompat.java" in text:
|
||||
print(f" {path.name}: 已经有了")
|
||||
return True
|
||||
|
||||
# 在 RelayTransport.java 那一行后面插一行同格式的 TlsCompat.java
|
||||
pattern = re.compile(
|
||||
r"([ \t]*(?:\(Join-Path \$root )?[\"']?[^\n\"']*RelayTransport\.java[\"']?\)?\,?[ \t]*\r?\n)")
|
||||
match = pattern.search(text)
|
||||
if not match:
|
||||
print(f" [失败] {path.name}: 找不到 RelayTransport.java 那一行")
|
||||
return False
|
||||
|
||||
line = match.group(1)
|
||||
indent = re.match(r"[ \t]*", line).group(0)
|
||||
# 保留原来的包裹形式
|
||||
if "Join-Path" in line:
|
||||
new_line = f'{indent}(Join-Path $root "android\\src\\com\\lanremote\\watch\\TlsCompat.java")`\n'
|
||||
elif '"' in line:
|
||||
new_line = f'{indent}"TlsCompat.java",\n' if line.rstrip().endswith(",") \
|
||||
else f'{indent}"TlsCompat.java"\n'
|
||||
else:
|
||||
new_line = f'{indent}TlsCompat.java\n'
|
||||
|
||||
text = text[:match.end(1)] + new_line + text[match.end(1):]
|
||||
|
||||
body = text.replace("\r\n", "\n").encode("utf-8")
|
||||
path.write_bytes((b"\xef\xbb\xbf" if had_bom else b"") + body)
|
||||
print(f" {path.name}: 补上了 TlsCompat.java")
|
||||
return True
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ok = True
|
||||
for path in TARGETS:
|
||||
if not path.is_file():
|
||||
print(f" [跳过] {path.name} 不存在")
|
||||
continue
|
||||
if not patch(path):
|
||||
ok = False
|
||||
return 0 if ok else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,71 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
r"""精确补上 TlsCompat.java(上一版正则没匹配上)。
|
||||
|
||||
两处的实际写法:
|
||||
test_package_remote.py:
|
||||
for n in ("Json.java", "B64.java", "Transport.java",
|
||||
"RelayTransport.java", "RemoteClient.java",
|
||||
"DiscoveryCore.java")]
|
||||
verify_all.ps1:
|
||||
(Join-Path $root "android\src\com\lanremote\watch\RelayTransport.java") `
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
|
||||
|
||||
PY_OLD = ''' sources = [ROOT / "android" / "src" / "com" / "lanremote" / "watch" / n
|
||||
for n in ("Json.java", "B64.java", "Transport.java",
|
||||
"RelayTransport.java", "RemoteClient.java",
|
||||
"DiscoveryCore.java")]'''
|
||||
|
||||
PY_NEW = ''' sources = [ROOT / "android" / "src" / "com" / "lanremote" / "watch" / n
|
||||
for n in ("Json.java", "B64.java", "Transport.java",
|
||||
"RelayTransport.java", "TlsCompat.java",
|
||||
"RemoteClient.java",
|
||||
"DiscoveryCore.java")]'''
|
||||
|
||||
PS1_OLD = ''' (Join-Path $root "android\\src\\com\\lanremote\\watch\\RelayTransport.java") `'''
|
||||
|
||||
PS1_NEW = ''' (Join-Path $root "android\\src\\com\\lanremote\\watch\\RelayTransport.java") `
|
||||
# TlsCompat:内置根证书,修老手表 SSLHandshakeException
|
||||
(Join-Path $root "android\\src\\com\\lanremote\\watch\\TlsCompat.java") `'''
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ok = True
|
||||
|
||||
path = ROOT / "_tests" / "test_package_remote.py"
|
||||
text = path.read_text(encoding="utf-8")
|
||||
if "TlsCompat.java" in text:
|
||||
print(" test_package_remote.py: 已经有了")
|
||||
elif PY_OLD in text:
|
||||
path.write_text(text.replace(PY_OLD, PY_NEW, 1),
|
||||
encoding="utf-8", newline="\n")
|
||||
print(" test_package_remote.py: 补上 TlsCompat.java")
|
||||
else:
|
||||
print(" [失败] test_package_remote.py 锚点没找到")
|
||||
ok = False
|
||||
|
||||
path = ROOT / "_tests" / "verify_all.ps1"
|
||||
raw = path.read_bytes()
|
||||
had_bom = raw.startswith(b"\xef\xbb\xbf")
|
||||
text = raw.decode("utf-8-sig")
|
||||
if "TlsCompat.java" in text:
|
||||
print(" verify_all.ps1: 已经有了")
|
||||
elif PS1_OLD in text:
|
||||
text = text.replace(PS1_OLD, PS1_NEW, 1)
|
||||
path.write_bytes((b"\xef\xbb\xbf" if had_bom else b"")
|
||||
+ text.replace("\r\n", "\n").encode("utf-8"))
|
||||
print(" verify_all.ps1: 补上 TlsCompat.java")
|
||||
else:
|
||||
print(" [失败] verify_all.ps1 锚点没找到")
|
||||
ok = False
|
||||
|
||||
return 0 if ok else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,71 @@
|
||||
# 提交 1.5.0 的全部改动
|
||||
$ErrorActionPreference = "Continue"
|
||||
$git = "C:\Program Files\Git\bin\git.exe"
|
||||
$root = "C:\Users\shu\Desktop\deepseekagent\LanRemote"
|
||||
Set-Location $root
|
||||
|
||||
Write-Host "===== 1. 暂存 =====" -ForegroundColor Cyan
|
||||
& $git add -A 2>&1 | Out-Null
|
||||
$files = @(& $git -c core.quotepath=false diff --cached --name-only |
|
||||
Where-Object { $_ -and $_.Trim() -ne "" })
|
||||
Write-Host " 改动文件数: $($files.Count)"
|
||||
$files | Select-Object -First 25 | ForEach-Object { Write-Host " $_" }
|
||||
if ($files.Count -gt 25) { Write-Host " …还有 $($files.Count - 25) 个" }
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "===== 2. 确认没有大文件混进来 =====" -ForegroundColor Cyan
|
||||
$big = foreach ($f in $files) {
|
||||
$p = Join-Path $root ($f -replace '/','\')
|
||||
if (Test-Path -LiteralPath $p -PathType Leaf) {
|
||||
$len = (Get-Item -LiteralPath $p).Length
|
||||
if ($len -gt 1MB) { [pscustomobject]@{ Name = $f; MB = [math]::Round($len/1MB,2) } }
|
||||
}
|
||||
}
|
||||
if ($big) { $big | ForEach-Object { Write-Host " ⚠ $($_.MB) MB $($_.Name)" -ForegroundColor Yellow } }
|
||||
else { Write-Host " ✓ 没有超过 1MB 的文件" -ForegroundColor Green }
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "===== 3. 提交 =====" -ForegroundColor Cyan
|
||||
$msg = @"
|
||||
ykxmz 1.5.0:手表端 TLS 证书兼容 + 三端分开打包 + 下载区
|
||||
|
||||
手表端
|
||||
- 内置 GTS Root R4 / GlobalSign Root CA
|
||||
修 SSLHandshakeException: Chain validation failed
|
||||
(廉价手表系统根证书库常缺 GTS Root R4)
|
||||
- 连接错误翻译成人话:证书 / 时间 / 解析 / 超时分别给提示
|
||||
- 经中转连上后用服务器报的地址刷新已配对记录(原来一直显示旧 IP)
|
||||
- 版本号 1.3.0 -> 1.5.0(AndroidManifest 之前漏改了)
|
||||
|
||||
服务器端
|
||||
- 支持 chunked 请求体:修每 16 秒一次的连接错乱与大面积 502
|
||||
- 网络调优 net-tune.sh:夹 MSS 1400 + 路径 MTU 探测
|
||||
(公网可用率 48% -> 100%,实测 15 分钟 180/180)
|
||||
做成开机服务 lanremote-nettune.service,并会自动重建隧道
|
||||
- 新增下载区:GET /v1/downloads 列表 + GET /download/<文件>
|
||||
只允许白名单后缀、严格校验文件名(挡目录穿越)
|
||||
管理页加下载卡片
|
||||
- 隧道改 HTTP/2 + 双连接(QUIC 在这条线路上可用率只有 48%)
|
||||
- 网络体检、批量删除设备
|
||||
|
||||
打包方式变更
|
||||
- 原来是三个部分合成一个 zip,现在**分成三个独立产物**:
|
||||
ykxmz-agent-1.5.0.zip 被控端
|
||||
ykxmz-server-1.5.0.zip 服务器端
|
||||
ykxmz-watch-1.5.0.apk 控制端(直接给 APK,不打 zip)
|
||||
三个包/文件都带上许可协议 / 安装前必读 / 安装后须知
|
||||
|
||||
文档
|
||||
- 许可协议 / 安装前必读 / 安装后须知(作者:小麦子)
|
||||
"@
|
||||
& $git commit -q -m $msg
|
||||
Write-Host " exit=$LASTEXITCODE"
|
||||
& $git log --oneline 2>&1 | Select-Object -First 3 | ForEach-Object { Write-Host " $_" }
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "===== 4. 公钥(加到这里才能推)=====" -ForegroundColor Yellow
|
||||
Write-Host " 打开 https://gitee.com/profile/sshkeys" -ForegroundColor White
|
||||
Write-Host " 把下面这一整行粘进去:" -ForegroundColor White
|
||||
Get-Content "$env:USERPROFILE\.ssh\id_ed25519_gitee.pub" | ForEach-Object {
|
||||
Write-Host " $_" -ForegroundColor Green
|
||||
}
|
||||
+154
-135
@@ -352,11 +352,88 @@ RELAY_README = f"""========================================================
|
||||
"""
|
||||
|
||||
|
||||
WATCH_README = f"""========================================================
|
||||
控制端(安卓手表) 版本 {VERSION}
|
||||
========================================================
|
||||
|
||||
【装】
|
||||
|
||||
把 ykxmz-watch-{VERSION}.apk 传到手表上安装。
|
||||
手表如果提示"未知来源",在设置里允许一下。
|
||||
|
||||
|
||||
【连】
|
||||
|
||||
1) 打开 App,首页有四个东西:
|
||||
中转设置(跨网络用) 填服务器端给的地址
|
||||
局域网连接(同一个WiFi)同一个 WiFi 时走这条,最快
|
||||
输入配对码连接 经中转连
|
||||
服务器:还没测 点右边「检查服务器」看延迟
|
||||
|
||||
2) 电脑和手表在同一个 WiFi -> 点「局域网连接」,自动搜到电脑,点一下就连
|
||||
电脑在外面 -> 点「输入配对码连接」,填电脑上那个 6 位码
|
||||
|
||||
3) 连上后是 10 个标签页:
|
||||
媒体 屏幕 文件 语音 电脑 网络 系统 教室 效率 娱乐
|
||||
|
||||
|
||||
【连不上时看这个】
|
||||
|
||||
App 会把错误翻译成人话:
|
||||
"证书校验失败" -> 手表系统太旧或网络被劫持
|
||||
"证书时间不对" -> 把手表的日期时间调成自动获取
|
||||
"连接超时" -> 网络慢或被防火墙挡
|
||||
"电脑拒绝连接" -> 电脑上 ykxmz 没开
|
||||
"域名解析不了" -> 手表没联网
|
||||
|
||||
首页的「检查服务器」能测出到中转的延迟(快/一般/慢三档配色)。
|
||||
|
||||
|
||||
【回退】
|
||||
|
||||
系统页最上面三个按钮:
|
||||
回退上一步 撤销刚才那一步
|
||||
回退全部 把能撤的都撤了
|
||||
看能回退什么 列出还能撤哪些
|
||||
|
||||
能撤的:所有开关、所有画面(全屏大字/投图/通知等)
|
||||
撤不了的:关机、重启、删文件、杀进程 —— 这些**不会**出现在回退列表里,
|
||||
免得你以为什么都能退回去。
|
||||
"""
|
||||
|
||||
|
||||
def write_readme(path: Path, text: str) -> None:
|
||||
"""说明文档统一写成 UTF-8 with BOM —— Windows 记事本不加 BOM 会认成 GBK。"""
|
||||
path.write_bytes(b"\xef\xbb\xbf" + text.replace("\n", "\r\n").encode("utf-8"))
|
||||
|
||||
|
||||
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}")
|
||||
@@ -365,13 +442,18 @@ def main() -> int:
|
||||
print(f" [失败] 找不到 APK: {APK_SRC}")
|
||||
return 1
|
||||
|
||||
build = ROOT / "dist" / NAME
|
||||
if build.exists():
|
||||
shutil.rmtree(build, ignore_errors=True)
|
||||
dist = ROOT / "dist"
|
||||
dist.mkdir(parents=True, exist_ok=True)
|
||||
work = dist / "_build_tmp"
|
||||
if work.exists():
|
||||
shutil.rmtree(work, ignore_errors=True)
|
||||
|
||||
# ---------- 1. 被控端 ----------
|
||||
agent_dir = build / "1-agent"
|
||||
print(" 复制被控端…")
|
||||
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"))
|
||||
@@ -381,155 +463,92 @@ def main() -> int:
|
||||
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. 服务器端 ----------
|
||||
relay_dir = build / "2-server"
|
||||
relay_dir.mkdir(parents=True)
|
||||
print(" 复制服务器端…")
|
||||
# 文件清单写死容易漏 —— watchdog.sh 就漏过一次。
|
||||
# ================= 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, relay_dir / src.name)
|
||||
write_readme(relay_dir / "README-server.txt", RELAY_README)
|
||||
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. 控制端 ----------
|
||||
watch_dir = build / "3-watch"
|
||||
watch_dir.mkdir(parents=True)
|
||||
print(" 复制控制端…")
|
||||
shutil.copy2(APK_SRC, watch_dir / APK_SRC.name)
|
||||
write_readme(watch_dir / "README-watch.txt", f"""========================================================
|
||||
控制端(安卓手表) 版本 {VERSION}
|
||||
========================================================
|
||||
# ================= 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)
|
||||
|
||||
把 {APK_SRC.name} 传到手表上安装。
|
||||
手表如果提示"未知来源",在设置里允许一下。
|
||||
|
||||
|
||||
【连】
|
||||
|
||||
1) 打开 App,首页点「中转设置(跨网络用)」
|
||||
填服务器端给你的地址,形如 https://relay.你的域名
|
||||
(不填也能用,但只在同一个 WiFi 下)
|
||||
|
||||
2) 点「自动搜索电脑」—— 同一个局域网时能直接搜到
|
||||
搜不到就点「连不上?点这里诊断」
|
||||
|
||||
3) 输 6 位配对码(电脑上设的那个)
|
||||
|
||||
4) 连上后是 10 个标签页:
|
||||
媒体 屏幕 文件 语音 电脑 网络 系统 教室 效率 娱乐
|
||||
每个页面上半部分是常用按钮,往下滑是更多功能(共 206 个)。
|
||||
|
||||
|
||||
【回退】
|
||||
|
||||
系统页最上面三个按钮:
|
||||
回退上一步 撤销刚才那一步
|
||||
回退全部 把能撤的都撤了
|
||||
看能回退什么 列出还能撤哪些
|
||||
|
||||
能撤的:所有开关(15 个)、所有画面(26 个,全屏大字/投图/通知等)
|
||||
撤不了的:关机、重启、删文件、杀进程 —— 这些**不会**出现在回退列表里,
|
||||
免得你以为什么都能退回去。
|
||||
""")
|
||||
|
||||
|
||||
# ---------- 4. 顶层说明 ----------
|
||||
write_readme(build / "README.txt", TOP_README)
|
||||
|
||||
# ---------- 4b. 三份正式文档(作者:小麦子)----------
|
||||
# 放顶层,和 README.txt 并排,解压第一眼就能看到。
|
||||
# 统一转成 UTF-8 BOM + CRLF —— 不带 BOM 记事本会按 GBK 读,全乱。
|
||||
doc_files = [
|
||||
("LICENSE.txt", "许可协议"),
|
||||
("BEFORE-INSTALL.txt", "安装前必读"),
|
||||
("AFTER-INSTALL.txt", "安装后须知"),
|
||||
]
|
||||
for name, label in doc_files:
|
||||
src = DOCS / name
|
||||
if not src.is_file():
|
||||
print(f" [警告] 缺少文档 {src}")
|
||||
# ================= 汇总 =================
|
||||
print()
|
||||
print(" ================ 产物 ================")
|
||||
ok = True
|
||||
for label, path, count in results:
|
||||
if not path.is_file():
|
||||
print(f" {label}: ✗ 缺失!")
|
||||
ok = False
|
||||
continue
|
||||
# 读进来(原文件是无 BOM 的 UTF-8),交给 write_readme
|
||||
# 统一加 BOM + CRLF 后写出
|
||||
write_readme(build / name, src.read_text(encoding="utf-8"))
|
||||
print(f" {label}: {name}")
|
||||
|
||||
# ---------- 5. 检查 ----------
|
||||
print()
|
||||
print(" 目录结构:")
|
||||
total = 0
|
||||
for item in sorted(build.rglob("*")):
|
||||
if item.is_file():
|
||||
total += 1
|
||||
for item in sorted(build.iterdir()):
|
||||
if item.is_dir():
|
||||
count = sum(1 for _ in item.rglob("*") if _.is_file())
|
||||
print(f" {item.name}/ ({count} 个文件)")
|
||||
else:
|
||||
print(f" {item.name}")
|
||||
print(f" 合计 {total} 个文件")
|
||||
|
||||
# 必须有的东西
|
||||
required = [
|
||||
agent_dir / "ykxmz.exe",
|
||||
relay_dir / "relay.py",
|
||||
relay_dir / "install.sh",
|
||||
watch_dir / APK_SRC.name,
|
||||
build / "README.txt",
|
||||
build / "LICENSE.txt",
|
||||
build / "BEFORE-INSTALL.txt",
|
||||
build / "AFTER-INSTALL.txt",
|
||||
]
|
||||
missing = [str(p.relative_to(build)) for p in required if not p.is_file()]
|
||||
if missing:
|
||||
print(f" [失败] 少了: {missing}")
|
||||
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(" 三个部分都在")
|
||||
|
||||
# ---------- 6. 打包 ----------
|
||||
# ================= 验证 =================
|
||||
print()
|
||||
print(" 压缩中…")
|
||||
zip_path = ROOT / "dist" / f"{NAME}.zip"
|
||||
if zip_path.exists():
|
||||
zip_path.unlink()
|
||||
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED,
|
||||
compresslevel=6) as archive:
|
||||
for item in sorted(build.rglob("*")):
|
||||
if item.is_file():
|
||||
archive.write(item, item.relative_to(build.parent))
|
||||
size = zip_path.stat().st_size / 1024 / 1024
|
||||
print(f" {zip_path.name} {size:.2f} MB")
|
||||
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)} 个条目,完整")
|
||||
|
||||
# ---------- 7. 验证压缩包 ----------
|
||||
with zipfile.ZipFile(zip_path) as archive:
|
||||
with zipfile.ZipFile(results[0][1]) as archive:
|
||||
names = archive.namelist()
|
||||
bad = archive.testzip()
|
||||
if bad:
|
||||
print(f" [失败] 压缩包里有坏文件: {bad}")
|
||||
return 1
|
||||
has = {
|
||||
"被控端": 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),
|
||||
"顶层说明": any("README.txt" in n for n in names),
|
||||
}
|
||||
for label, ok in has.items():
|
||||
print(f" {label}: {'✓' if ok else '✗'}")
|
||||
if not all(has.values()):
|
||||
return 1
|
||||
print(f" 共 {len(names)} 个条目,完整")
|
||||
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
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
||||
False,
|
||||
True,
|
||||
['C:/Users/shu/Desktop/deepseekagent/LanRemote/windows/app.ico'],
|
||||
versioninfo.VSVersionInfo(ffi=versioninfo.FixedFileInfo(filevers=(1, 3, 0, 0), prodvers=(1, 3, 0, 0), mask=0x3f, flags=0x0, OS=0x40004, fileType=1, subtype=0x0, date=(0, 0)), kids=[versioninfo.StringFileInfo([versioninfo.StringTable('080404B0', [versioninfo.StringStruct('CompanyName', 'ykxmz'), versioninfo.StringStruct('FileDescription', 'ykxmz'), versioninfo.StringStruct('FileVersion', '1.4.0.0'), versioninfo.StringStruct('InternalName', 'ykxmz'), versioninfo.StringStruct('LegalCopyright', 'ykxmz'), versioninfo.StringStruct('OriginalFilename', 'ykxmz.exe'), versioninfo.StringStruct('ProductName', 'ykxmz'), versioninfo.StringStruct('ProductVersion', '1.4.0.0')])]), versioninfo.VarFileInfo([versioninfo.VarStruct('Translation', [2052, 1200])])]),
|
||||
versioninfo.VSVersionInfo(ffi=versioninfo.FixedFileInfo(filevers=(1, 3, 0, 0), prodvers=(1, 3, 0, 0), mask=0x3f, flags=0x0, OS=0x40004, fileType=1, subtype=0x0, date=(0, 0)), kids=[versioninfo.StringFileInfo([versioninfo.StringTable('080404B0', [versioninfo.StringStruct('CompanyName', 'ykxmz'), versioninfo.StringStruct('FileDescription', 'ykxmz'), versioninfo.StringStruct('FileVersion', '1.5.0.0'), versioninfo.StringStruct('InternalName', 'ykxmz'), versioninfo.StringStruct('LegalCopyright', 'ykxmz'), versioninfo.StringStruct('OriginalFilename', 'ykxmz.exe'), versioninfo.StringStruct('ProductName', 'ykxmz'), versioninfo.StringStruct('ProductVersion', '1.5.0.0')])]), versioninfo.VarFileInfo([versioninfo.VarStruct('Translation', [2052, 1200])])]),
|
||||
False,
|
||||
False,
|
||||
b'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<assembly xmlns='
|
||||
@@ -64,7 +64,7 @@
|
||||
[],
|
||||
False,
|
||||
False,
|
||||
1791279782,
|
||||
1791284710,
|
||||
[('runw.exe',
|
||||
'C:\\Users\\shu\\AppData\\Local\\Programs\\Python\\Python311\\Lib\\site-packages\\PyInstaller\\bootloader\\Windows-64bit-intel\\runw.exe',
|
||||
'EXECUTABLE')],
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,274 @@
|
||||
# -*- 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())
|
||||
@@ -0,0 +1,172 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
r"""把 test_package_remote.py 改成适配"三端分开出"。
|
||||
|
||||
原来:
|
||||
ZIP = dist/ykxmz-1.5.0.zip (一个合并包)
|
||||
解压后是 1-agent/ 2-server/ 3-watch/ + 顶层 README
|
||||
|
||||
现在:
|
||||
dist/ykxmz-agent-1.5.0.zip 被控端
|
||||
dist/ykxmz-server-1.5.0.zip 服务器端
|
||||
dist/ykxmz-watch-1.5.0.apk 控制端(直接给)
|
||||
|
||||
测试要验证的东西不变(三部分内容完整、能跑起来、文档带 BOM),
|
||||
只是"从哪拿"变了。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
TEST = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote\_tests"
|
||||
r"\test_package_remote.py")
|
||||
|
||||
OLD_HEAD = '''ROOT = Path(__file__).resolve().parent.parent
|
||||
ZIP = ROOT / "dist" / "ykxmz-1.5.0.zip"'''
|
||||
|
||||
NEW_HEAD = '''ROOT = Path(__file__).resolve().parent.parent
|
||||
# 三端分开出(用户要求):
|
||||
# 被控端 -> 一个 zip
|
||||
# 服务器端 -> 一个 zip
|
||||
# 控制端 -> 直接给 APK(不打 zip)
|
||||
AGENT_ZIP = ROOT / "dist" / "ykxmz-agent-1.5.0.zip"
|
||||
SERVER_ZIP = ROOT / "dist" / "ykxmz-server-1.5.0.zip"
|
||||
WATCH_APK = ROOT / "dist" / "ykxmz-watch-1.5.0.apk"'''
|
||||
|
||||
OLD_OPEN = '''def main() -> int:
|
||||
if not ZIP.is_file():
|
||||
print(f" 找不到 {ZIP}")
|
||||
return 1
|
||||
|
||||
work = Path(tempfile.mkdtemp(prefix="verify_pkg_"))
|
||||
relay_proc = agent_proc = None
|
||||
try:
|
||||
# ---------------- 1. 解压 ----------------
|
||||
print("一、解压")
|
||||
with zipfile.ZipFile(ZIP) as archive:
|
||||
archive.extractall(work)
|
||||
top = work / ZIP.stem
|
||||
check("顶层目录存在", top.is_dir(), top)
|
||||
agent_dir = top / "1-agent"
|
||||
relay_dir = top / "2-server"
|
||||
watch_dir = top / "3-watch"
|
||||
check("三个部分目录都在",
|
||||
agent_dir.is_dir() and relay_dir.is_dir() and watch_dir.is_dir())
|
||||
check("顶层说明在", (top / "README.txt").is_file())
|
||||
|
||||
exe = agent_dir / "ykxmz.exe"
|
||||
relay_py = relay_dir / "relay.py"
|
||||
install_sh = relay_dir / "install.sh"
|
||||
apk = next(watch_dir.glob("*.apk"), None)
|
||||
check("被控端 EXE 在", exe.is_file())
|
||||
check("服务器端 relay.py 在", relay_py.is_file())
|
||||
check("服务器端 install.sh 在", install_sh.is_file())
|
||||
check("控制端 APK 在", apk is not None, apk)
|
||||
|
||||
# 说明文档要有 BOM(否则记事本会认成 GBK 乱码)
|
||||
for label, path in (("顶层", top / "README.txt"),
|
||||
("被控端", agent_dir / "README-agent.txt"),
|
||||
("服务器端", relay_dir / "README-server.txt"),
|
||||
("控制端", watch_dir / "README-watch.txt")):
|
||||
raw = path.read_bytes() if path.is_file() else b""
|
||||
check(f"{label}说明有 UTF-8 BOM",
|
||||
raw[:3] == b"\\xef\\xbb\\xbf", raw[:3])'''
|
||||
|
||||
NEW_OPEN = '''def main() -> int:
|
||||
# 1) 三个产物都要在
|
||||
for label, path in (("被控端包", AGENT_ZIP), ("服务器端包", SERVER_ZIP),
|
||||
("控制端 APK", WATCH_APK)):
|
||||
if not path.is_file():
|
||||
print(f" 找不到{label}: {path}")
|
||||
return 1
|
||||
|
||||
work = Path(tempfile.mkdtemp(prefix="verify_pkg_"))
|
||||
relay_proc = agent_proc = None
|
||||
try:
|
||||
# ---------------- 1. 解压两个 zip ----------------
|
||||
print("一、解压(两个 zip)")
|
||||
with zipfile.ZipFile(AGENT_ZIP) as archive:
|
||||
archive.extractall(work)
|
||||
with zipfile.ZipFile(SERVER_ZIP) as archive:
|
||||
archive.extractall(work)
|
||||
|
||||
# 两个包各自带一层版本目录
|
||||
agent_dir = work / AGENT_ZIP.stem
|
||||
relay_dir = work / SERVER_ZIP.stem
|
||||
check("被控端目录存在", agent_dir.is_dir(), agent_dir)
|
||||
check("服务器端目录存在", relay_dir.is_dir(), relay_dir)
|
||||
|
||||
exe = agent_dir / "ykxmz.exe"
|
||||
relay_py = relay_dir / "relay.py"
|
||||
install_sh = relay_dir / "install.sh"
|
||||
apk = WATCH_APK
|
||||
check("被控端 EXE 在", exe.is_file())
|
||||
check("服务器端 relay.py 在", relay_py.is_file())
|
||||
check("服务器端 install.sh 在", install_sh.is_file())
|
||||
check("服务器端带了网络调优脚本", (relay_dir / "net-tune.sh").is_file())
|
||||
check("服务器端带了看门狗", (relay_dir / "watchdog.sh").is_file())
|
||||
check("控制端 APK 在(直接在 dist 里)", apk.is_file(), apk)
|
||||
|
||||
# 说明文档要有 BOM(否则记事本会认成 GBK 乱码)
|
||||
for label, path in (("被控端", agent_dir / "README-agent.txt"),
|
||||
("服务器端", relay_dir / "README-server.txt"),
|
||||
("控制端(旁边那份)",
|
||||
ROOT / "dist" / "ykxmz-watch-1.5.0-说明.txt")):
|
||||
raw = path.read_bytes() if path.is_file() else b""
|
||||
check(f"{label}说明有 UTF-8 BOM",
|
||||
raw[:3] == b"\\xef\\xbb\\xbf", raw[:3])
|
||||
|
||||
# 三份正式文档:两个包里都要有
|
||||
for label, base in (("被控端包", agent_dir), ("服务器端包", relay_dir)):
|
||||
for doc in ("LICENSE.txt", "BEFORE-INSTALL.txt",
|
||||
"AFTER-INSTALL.txt"):
|
||||
check(f"{label}带了 {doc}", (base / doc).is_file())'''
|
||||
|
||||
OLD_TOOLS = ''' # 被控端目录里不该有 data / 说明残留
|
||||
check("没有把 data 目录打进去",
|
||||
not (agent_dir / "data").exists())'''
|
||||
|
||||
NEW_TOOLS = ''' # 被控端目录里不该有 data / 说明残留
|
||||
check("没有把 data 目录打进去",
|
||||
not (agent_dir / "data").exists())'''
|
||||
|
||||
|
||||
def main() -> int:
|
||||
text = TEST.read_text(encoding="utf-8")
|
||||
if "AGENT_ZIP" in text:
|
||||
print(" 已经改过")
|
||||
return 0
|
||||
|
||||
ok = True
|
||||
for old, new, label in ((OLD_HEAD, NEW_HEAD, "产物路径常量"),
|
||||
(OLD_OPEN, NEW_OPEN, "解压与检查段")):
|
||||
if old not in text:
|
||||
print(f" [失败] {label} —— 锚点没找到")
|
||||
# 打印实际内容帮助定位
|
||||
key = old.splitlines()[0]
|
||||
for i, line in enumerate(text.splitlines(), 1):
|
||||
if line.strip() == key.strip():
|
||||
print(f" 实际 {i}: {line}")
|
||||
ok = False
|
||||
continue
|
||||
text = text.replace(old, new, 1)
|
||||
print(f" {label}")
|
||||
|
||||
if not ok:
|
||||
return 1
|
||||
|
||||
TEST.write_text(text, encoding="utf-8", newline="\n")
|
||||
|
||||
try:
|
||||
ast.parse(text)
|
||||
print(" test_package_remote.py 语法 OK")
|
||||
except SyntaxError as exc:
|
||||
print(f" 语法错误: 行 {exc.lineno}: {exc.msg}")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,68 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
r"""两件事:
|
||||
1. 给 verify_all.ps1 补回 UTF-8 BOM
|
||||
(edit 工具写出来不带 BOM,PowerShell 里中文会乱码)
|
||||
2. 把三个成品上传到设备的下载目录
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
|
||||
sys.path.insert(0, str(ROOT / "_build"))
|
||||
|
||||
from devssh import run, upload # noqa: E402
|
||||
|
||||
VERIFY = ROOT / "_tests" / "verify_all.ps1"
|
||||
|
||||
UPLOADS = [
|
||||
("dist/ykxmz-agent-1.5.0.zip",
|
||||
"/root/lanremote-relay/downloads/ykxmz-agent-1.5.0.zip"),
|
||||
("dist/ykxmz-server-1.5.0.zip",
|
||||
"/root/lanremote-relay/downloads/ykxmz-server-1.5.0.zip"),
|
||||
("dist/ykxmz-watch-1.5.0.apk",
|
||||
"/root/lanremote-relay/downloads/ykxmz-watch-1.5.0.apk"),
|
||||
("dist/ykxmz-watch-1.5.0-说明.txt",
|
||||
"/root/lanremote-relay/downloads/ykxmz-watch-1.5.0-说明.txt"),
|
||||
]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
# ---- 1. 补 BOM ----
|
||||
raw = VERIFY.read_bytes()
|
||||
if raw.startswith(b"\xef\xbb\xbf"):
|
||||
print(" verify_all.ps1: BOM 正常")
|
||||
else:
|
||||
VERIFY.write_bytes(b"\xef\xbb\xbf" + raw)
|
||||
print(" verify_all.ps1: 补回了 UTF-8 BOM")
|
||||
|
||||
# ---- 2. 建目录 ----
|
||||
run("mkdir -p /root/lanremote-relay/downloads", timeout=30)
|
||||
run("rm -f /root/lanremote-relay/downloads/ykxmz-1.5.0.zip", timeout=30)
|
||||
|
||||
# ---- 3. 上传 ----
|
||||
print()
|
||||
print(" 上传成品:")
|
||||
for local, remote in UPLOADS:
|
||||
src = ROOT / local
|
||||
if not src.is_file():
|
||||
print(f" [跳过] {local} 不存在")
|
||||
continue
|
||||
try:
|
||||
upload(str(src), remote, 0o644)
|
||||
size = src.stat().st_size
|
||||
print(f" ✓ {src.name} ({size / 1024 / 1024:.2f} MB)")
|
||||
except Exception as exc: # noqa: BLE001
|
||||
print(f" ✗ {src.name}: {exc}")
|
||||
|
||||
# ---- 4. 确认 ----
|
||||
print()
|
||||
code, out, _err = run(
|
||||
"ls -la /root/lanremote-relay/downloads/ | tail -8", timeout=30)
|
||||
print(out)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/bin/bash
|
||||
# 验证下载区能用(本地 + 公网)。
|
||||
set -u
|
||||
|
||||
BASE=http://127.0.0.1:8080
|
||||
PUB=https://relay.xxmxxs.top
|
||||
UA='LanRemote/1.4.0'
|
||||
|
||||
echo "===== 1. 本地列出下载文件 ====="
|
||||
curl -s --max-time 10 "$BASE/v1/downloads" | python3 -m json.tool 2>/dev/null \
|
||||
| head -30 || echo " 读不到"
|
||||
|
||||
echo
|
||||
echo "===== 2. 本地下载(只看 HTTP 码和大小)====="
|
||||
for f in ykxmz-watch-1.5.0.apk ykxmz-server-1.5.0.zip ykxmz-agent-1.5.0.zip; do
|
||||
info=$(curl -s --max-time 20 -o /tmp/dl.bin -w '%{http_code} %{size_download} %{content_type}' \
|
||||
"$BASE/download/$f" 2>/dev/null)
|
||||
printf " %-32s %s\n" "$f" "$info"
|
||||
done
|
||||
|
||||
echo
|
||||
echo "===== 3. 公网列出(走隧道)====="
|
||||
curl -s --max-time 20 -H "User-Agent: $UA" "$PUB/v1/downloads" \
|
||||
| head -c 300
|
||||
echo
|
||||
|
||||
echo
|
||||
echo "===== 4. 公网下载 APK 头几个字节(验证真的能下)====="
|
||||
code=$(curl -s --max-time 40 -o /tmp/apk_pub.bin -w '%{http_code}' \
|
||||
-H "User-Agent: $UA" "$PUB/download/ykxmz-watch-1.5.0.apk" 2>/dev/null)
|
||||
size=$(stat -c %s /tmp/apk_pub.bin 2>/dev/null || echo 0)
|
||||
echo " HTTP $code,下载 $size 字节"
|
||||
echo -n " 是不是合法 APK(zip 头 PK): "
|
||||
head -c 2 /tmp/apk_pub.bin | grep -q 'PK' && echo "是 ✓" || echo "不是 ✗"
|
||||
|
||||
echo
|
||||
echo "===== 5. 安全:目录穿越要被挡住 ====="
|
||||
for bad in "../relay.py" "..%2Frelay.py" "relay.py" "x.sh"; do
|
||||
code=$(curl -s --max-time 10 -o /dev/null -w '%{http_code}' \
|
||||
"$BASE/download/$bad" 2>/dev/null)
|
||||
printf " %-20s HTTP %s\n" "$bad" "$code"
|
||||
done
|
||||
|
||||
echo
|
||||
echo "===== 6. 管理页有没有下载卡片 ====="
|
||||
KEY=$(cat /root/lanremote-relay/data/admin_key.txt 2>/dev/null)
|
||||
for needle in 'id=dlPanel' '/v1/downloads' '/download/'; do
|
||||
if curl -s --max-time 10 -H "X-Admin: $KEY" "$BASE/" 2>/dev/null \
|
||||
| grep -q "$needle"; then
|
||||
echo " ✓ $needle"
|
||||
else
|
||||
echo " ✗ $needle"
|
||||
fi
|
||||
done
|
||||
@@ -22,7 +22,13 @@ import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
ZIP = ROOT / "dist" / "ykxmz-1.5.0.zip"
|
||||
# 三端分开出(用户要求):
|
||||
# 被控端 -> 一个 zip
|
||||
# 服务器端 -> 一个 zip
|
||||
# 控制端 -> 直接给 APK(不打 zip)
|
||||
AGENT_ZIP = ROOT / "dist" / "ykxmz-agent-1.5.0.zip"
|
||||
SERVER_ZIP = ROOT / "dist" / "ykxmz-server-1.5.0.zip"
|
||||
WATCH_APK = ROOT / "dist" / "ykxmz-watch-1.5.0.apk"
|
||||
TOOLCHAIN = Path(r"C:\Users\shu\Desktop\deepseekagent\starklink\toolchain")
|
||||
JAVAC = TOOLCHAIN / "jdk17" / "bin" / "javac.exe"
|
||||
JAVA = TOOLCHAIN / "jdk17" / "bin" / "java.exe"
|
||||
@@ -45,44 +51,55 @@ def check(name, ok, detail=""):
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if not ZIP.is_file():
|
||||
print(f" 找不到 {ZIP}")
|
||||
return 1
|
||||
# 1) 三个产物都要在
|
||||
for label, path in (("被控端包", AGENT_ZIP), ("服务器端包", SERVER_ZIP),
|
||||
("控制端 APK", WATCH_APK)):
|
||||
if not path.is_file():
|
||||
print(f" 找不到{label}: {path}")
|
||||
return 1
|
||||
|
||||
work = Path(tempfile.mkdtemp(prefix="verify_pkg_"))
|
||||
relay_proc = agent_proc = None
|
||||
try:
|
||||
# ---------------- 1. 解压 ----------------
|
||||
print("一、解压")
|
||||
with zipfile.ZipFile(ZIP) as archive:
|
||||
# ---------------- 1. 解压两个 zip ----------------
|
||||
print("一、解压(两个 zip)")
|
||||
with zipfile.ZipFile(AGENT_ZIP) as archive:
|
||||
archive.extractall(work)
|
||||
top = work / ZIP.stem
|
||||
check("顶层目录存在", top.is_dir(), top)
|
||||
agent_dir = top / "1-agent"
|
||||
relay_dir = top / "2-server"
|
||||
watch_dir = top / "3-watch"
|
||||
check("三个部分目录都在",
|
||||
agent_dir.is_dir() and relay_dir.is_dir() and watch_dir.is_dir())
|
||||
check("顶层说明在", (top / "README.txt").is_file())
|
||||
with zipfile.ZipFile(SERVER_ZIP) as archive:
|
||||
archive.extractall(work)
|
||||
|
||||
# 两个包各自带一层版本目录
|
||||
agent_dir = work / AGENT_ZIP.stem
|
||||
relay_dir = work / SERVER_ZIP.stem
|
||||
check("被控端目录存在", agent_dir.is_dir(), agent_dir)
|
||||
check("服务器端目录存在", relay_dir.is_dir(), relay_dir)
|
||||
|
||||
exe = agent_dir / "ykxmz.exe"
|
||||
relay_py = relay_dir / "relay.py"
|
||||
install_sh = relay_dir / "install.sh"
|
||||
apk = next(watch_dir.glob("*.apk"), None)
|
||||
apk = WATCH_APK
|
||||
check("被控端 EXE 在", exe.is_file())
|
||||
check("服务器端 relay.py 在", relay_py.is_file())
|
||||
check("服务器端 install.sh 在", install_sh.is_file())
|
||||
check("控制端 APK 在", apk is not None, apk)
|
||||
check("服务器端带了网络调优脚本", (relay_dir / "net-tune.sh").is_file())
|
||||
check("服务器端带了看门狗", (relay_dir / "watchdog.sh").is_file())
|
||||
check("控制端 APK 在(直接在 dist 里)", apk.is_file(), apk)
|
||||
|
||||
# 说明文档要有 BOM(否则记事本会认成 GBK 乱码)
|
||||
for label, path in (("顶层", top / "README.txt"),
|
||||
("被控端", agent_dir / "README-agent.txt"),
|
||||
for label, path in (("被控端", agent_dir / "README-agent.txt"),
|
||||
("服务器端", relay_dir / "README-server.txt"),
|
||||
("控制端", watch_dir / "README-watch.txt")):
|
||||
("控制端(旁边那份)",
|
||||
ROOT / "dist" / "ykxmz-watch-1.5.0-说明.txt")):
|
||||
raw = path.read_bytes() if path.is_file() else b""
|
||||
check(f"{label}说明有 UTF-8 BOM",
|
||||
raw[:3] == b"\xef\xbb\xbf", raw[:3])
|
||||
|
||||
# 三份正式文档:两个包里都要有
|
||||
for label, base in (("被控端包", agent_dir), ("服务器端包", relay_dir)):
|
||||
for doc in ("LICENSE.txt", "BEFORE-INSTALL.txt",
|
||||
"AFTER-INSTALL.txt"):
|
||||
check(f"{label}带了 {doc}", (base / doc).is_file())
|
||||
|
||||
# 被控端目录里不该有 data / 说明残留
|
||||
check("没有把 data 目录打进去",
|
||||
not (agent_dir / "data").exists())
|
||||
@@ -179,7 +196,8 @@ def main() -> int:
|
||||
classes.mkdir(parents=True, exist_ok=True)
|
||||
sources = [ROOT / "android" / "src" / "com" / "lanremote" / "watch" / n
|
||||
for n in ("Json.java", "B64.java", "Transport.java",
|
||||
"RelayTransport.java", "RemoteClient.java",
|
||||
"RelayTransport.java", "TlsCompat.java",
|
||||
"RemoteClient.java",
|
||||
"DiscoveryCore.java")]
|
||||
sources.append(ROOT / "_tests" / "java" / "JavaRelayTest.java")
|
||||
comp = subprocess.run(
|
||||
|
||||
@@ -20,6 +20,7 @@ ROOT = Path(__file__).resolve().parent.parent
|
||||
WATCH = ROOT / "android" / "src" / "com" / "lanremote" / "watch"
|
||||
MAIN = WATCH / "MainActivity.java"
|
||||
RELAY_T = WATCH / "RelayTransport.java"
|
||||
"TlsCompat.java"
|
||||
CLIENT = WATCH / "RemoteClient.java"
|
||||
JSON = WATCH / "Json.java"
|
||||
|
||||
|
||||
@@ -402,6 +402,8 @@ New-Item -ItemType Directory -Force -Path $out | Out-Null
|
||||
(Join-Path $root "android\src\com\lanremote\watch\DiscoveryCore.java") `
|
||||
(Join-Path $root "android\src\com\lanremote\watch\Transport.java") `
|
||||
(Join-Path $root "android\src\com\lanremote\watch\RelayTransport.java") `
|
||||
# TlsCompat:内置根证书,修老手表连中转时的 SSLHandshakeException
|
||||
(Join-Path $root "android\src\com\lanremote\watch\TlsCompat.java") `
|
||||
(Join-Path $root "android\src\com\lanremote\watch\RemoteClient.java") `
|
||||
(Join-Path $root "_tests\java\JavaClientTest.java") 2>&1 | Out-Null
|
||||
$compiled = ($LASTEXITCODE -eq 0)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user