mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 10:10:43 +00:00
== 用户要求 ==
"安卓那个逻辑也要改一下,就是你输入配对码后,进入一个页面,
页面有两个按钮,第一个按钮是远控,第二个按钮是静默更新被控端"
== 做成什么样 ==
新增 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 兼容校验通过
358 lines
14 KiB
Python
358 lines
14 KiB
Python
# -*- coding: utf-8 -*-
|
||
r'''按用户要求重排 nginx:
|
||
|
||
<服务器IP> (80) -> 没东西
|
||
<服务器IP>:9100 (HTTP)-> 官网
|
||
yk.xxmxxs.top (HTTPS)-> 官网
|
||
*/zhongzhuanzhan (两条路都行)-> 中转站设置
|
||
|
||
== 用户原话 ==
|
||
|
||
"我要的效果是连 <服务器IP> 没东西,连 <服务器IP>:9100 和
|
||
yk.xxmxxs.top 是官网,连 <服务器IP>:9100/zhongzhuanzhan 和
|
||
yk.xxmxxs.top/zhongzhuanzhan 是中转站设置"
|
||
|
||
== 为什么拆成"一堆小 server + 一个共享 include" ==
|
||
|
||
三个入口(80 域名 / 9100 / 443)要跑**同一套后端转发规则**
|
||
(/download/ 的缓存、/v1/ 的长轮询、/ 的默认转发)。
|
||
|
||
如果每个 server 块各抄一遍,以后改后端端口或者改超时
|
||
就要改三处 —— 漏一处就是"某个入口偶尔 502",
|
||
而且很难查到。所以抽成 snippets/ykxmz-proxy.conf,
|
||
三个 server 块各 include 一次。
|
||
|
||
== 几个容易踩的点 ==
|
||
|
||
1. **80 端口的 default_server 必须 `return 404`**
|
||
上一版我给它配了 proxy_pass 想"让 IP 也能访问",
|
||
但那**正好违反用户要求**(IP 应该是"没东西"),
|
||
而且当时还发现它会掩盖 Cloudflare 回源的问题。
|
||
|
||
2. **certbot 管的行不能动**
|
||
443 那个 server 块的 `listen ... ssl` 和 ssl_certificate
|
||
是 certbot 写的,续期时会检查。重排时**原样保留**。
|
||
ACME 挑战走 80 端口的 `server_name yk.xxmxxs.top`,
|
||
所以那个块也要留着(certbot 自己会往里塞 location)。
|
||
|
||
3. **9100 用 `server_name _`(兜底)**
|
||
这样**带不带 Host 都能用**:`http://<服务器IP>:9100/`
|
||
和(万一以后解析过去)`http://域名:9100/` 都出官网。
|
||
|
||
4. **9100 是 HTTP**
|
||
IP 没法签证书(Let's Encrypt 不给纯 IP 签),
|
||
所以 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
|
||
|
||
# ---------------------------------------------------------------- 共享转发规则
|
||
SNIPPET = """# ======================================================================
|
||
# ykxmz 后端转发规则(80 / 9100 / 443 三个入口共用)
|
||
# ======================================================================
|
||
#
|
||
# 抽出来是为了**只维护一份**。以前这段在三个 server 块里各写一遍,
|
||
# 改个后端端口要改三处,漏一处就是"某个入口偶尔 502"。
|
||
|
||
# ---- 下载:让 Cloudflare 放心缓存 ----
|
||
#
|
||
# 文件名带版本号(ykxmz-setup-2.1.8.exe),同一个名字内容永不改变,
|
||
# 所以可以长缓存。
|
||
location /download/ {
|
||
proxy_pass http://127.0.0.1:13245;
|
||
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 CF-Connecting-IP $http_cf_connecting_ip;
|
||
|
||
proxy_buffering on;
|
||
|
||
# 这里**故意不设** Cache-Control —— relay 自己已经发了正确的
|
||
# (public, max-age=604800, immutable)。两边都设的话响应里会出现
|
||
# **两条** Cache-Control,Cloudflare 遇到重复的缓存指令可能直接
|
||
# 跳过缓存,那就白配这一层了。
|
||
proxy_cache_valid 200 7d;
|
||
}
|
||
|
||
# ---- 长轮询 ----
|
||
#
|
||
# 中转的 /v1/agent/poll 要挂 60 秒。nginx 默认 60 秒就断,
|
||
# 必须放宽,否则手表那边会一直掉线重连。
|
||
location /v1/ {
|
||
proxy_pass http://127.0.0.1:13245;
|
||
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 CF-Connecting-IP $http_cf_connecting_ip;
|
||
|
||
proxy_read_timeout 180s;
|
||
proxy_send_timeout 180s;
|
||
proxy_connect_timeout 30s;
|
||
# 长轮询不能缓冲,否则要等整个响应完才吐给客户端 —— 那就不是长轮询了
|
||
proxy_buffering off;
|
||
proxy_cache off;
|
||
}
|
||
|
||
# ---- 其余(官网 / 中转站设置 / 抢救报告 / 截图)----
|
||
location / {
|
||
proxy_pass http://127.0.0.1:13245;
|
||
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 CF-Connecting-IP $http_cf_connecting_ip;
|
||
|
||
proxy_read_timeout 180s;
|
||
proxy_connect_timeout 30s;
|
||
}
|
||
"""
|
||
|
||
# ---------------------------------------------------------------- 主配置
|
||
MAIN_CONF = """# ykxmz 中转 —— nginx 前置
|
||
#
|
||
# ======================================================================
|
||
# 入口一览(用户明确要求的效果)
|
||
# ======================================================================
|
||
#
|
||
# <服务器IP> (80) -> 没东西(404)
|
||
# <服务器IP>:9100 (HTTP)-> 官网
|
||
# yk.xxmxxs.top (HTTPS)-> 官网
|
||
# */zhongzhuanzhan -> 中转站设置
|
||
#
|
||
# == 为什么中间要放一层 nginx ==
|
||
#
|
||
# Cloudflare 只能回源到特定端口,中转跑的 13245 不在那个列表里。
|
||
# 而且下载 15MB 的安装包要靠 Cloudflare 缓存 ——
|
||
# 第二次下载就不回源了,速度取决于离用户最近的 CF 边缘节点。
|
||
#
|
||
# ======================================================================
|
||
|
||
# ----------------------------------------------------------------------
|
||
# 1. 80 端口默认站点:**没东西**
|
||
# ----------------------------------------------------------------------
|
||
#
|
||
# 用户要求:"连 <服务器IP> 没东西"。
|
||
#
|
||
# 所以这里是**显式 404**,不转发给 relay。
|
||
#
|
||
# ⚠ 上一版我在这里配了 proxy_pass(想让 IP 也能访问),
|
||
# 那违反用户要求。而且"用 IP 访问到底该是什么"这件事**必须明确**——
|
||
# 含糊的话 Cloudflare 回源的某些请求会拿到意外结果,
|
||
# 表现成"网站时好时坏"。
|
||
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;
|
||
|
||
server_tokens off;
|
||
return 404;
|
||
}
|
||
|
||
# ----------------------------------------------------------------------
|
||
# 2. 80 端口 + 域名:给 Let's Encrypt 续期用,并跳到 https
|
||
# ----------------------------------------------------------------------
|
||
#
|
||
# ⚠ 这个块**不能删** —— certbot 的 HTTP-01 挑战走这里。
|
||
# 删了的话证书到期续不上,90 天后网站会整个挂掉。
|
||
server {
|
||
listen 80;
|
||
listen [::]:80;
|
||
server_name yk.xxmxxs.top;
|
||
|
||
access_log /var/log/nginx/ykxmz-access.log;
|
||
error_log /var/log/nginx/ykxmz-error.log;
|
||
|
||
server_tokens off;
|
||
client_max_body_size 200m;
|
||
|
||
# 其余全部跳到 https(certbot --redirect 的效果)
|
||
location / {
|
||
return 301 https://$host$request_uri;
|
||
}
|
||
|
||
# certbot 会自己往这里塞 ACME 挑战的 location,别动它加的东西
|
||
}
|
||
|
||
# ----------------------------------------------------------------------
|
||
# 3. 9100:官网 + 中转站设置(直连通道,不经过 Cloudflare)
|
||
# ----------------------------------------------------------------------
|
||
#
|
||
# 用户要求:"连 <服务器IP>:9100 和 yk.xxmxxs.top 是官网,
|
||
# 连 <服务器IP>:9100/zhongzhuanzhan 是中转站设置"。
|
||
#
|
||
# server_name 用 `_`(兜底):带不带 Host 都能用 ——
|
||
# 用户可能直接敲 IP,也可能以后把某个域名解析过来。
|
||
#
|
||
# ⚠ 这是 HTTP(不是 HTTPS):**IP 没法签证书**(Let's Encrypt 不给纯 IP 签)。
|
||
# 定位是"Cloudflare 出问题时的备用通道",HTTP 就够。
|
||
#
|
||
# ⚠ 还需要 **AWS Lightsail 控制台开放 9100** —— 光配 nginx 没用。
|
||
server {
|
||
listen 9100;
|
||
listen [::]:9100;
|
||
server_name _;
|
||
|
||
access_log /var/log/nginx/ykxmz-9100-access.log;
|
||
error_log /var/log/nginx/ykxmz-9100-error.log;
|
||
|
||
server_tokens off;
|
||
client_max_body_size 200m;
|
||
|
||
include snippets/ykxmz-proxy.conf;
|
||
}
|
||
|
||
# ----------------------------------------------------------------------
|
||
# 4. 443:官网 + 中转站设置(正常入口,走 Cloudflare)
|
||
# ----------------------------------------------------------------------
|
||
#
|
||
# ⚠ `listen ... ssl` 和下面的 ssl_certificate 那几行是 **certbot 写的**,
|
||
# 续期时它会检查。重排配置时**原样保留**,别手改。
|
||
server {
|
||
server_name yk.xxmxxs.top;
|
||
|
||
access_log /var/log/nginx/ykxmz-access.log;
|
||
error_log /var/log/nginx/ykxmz-error.log;
|
||
|
||
server_tokens off;
|
||
client_max_body_size 200m;
|
||
|
||
include snippets/ykxmz-proxy.conf;
|
||
|
||
listen [::]:443 ssl ipv6only=on; # managed by Certbot
|
||
listen 443 ssl; # managed by Certbot
|
||
ssl_certificate /etc/letsencrypt/live/yk.xxmxxs.top/fullchain.pem; # managed by Certbot
|
||
ssl_certificate_key /etc/letsencrypt/live/yk.xxmxxs.top/privkey.pem; # managed by Certbot
|
||
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||
}
|
||
"""
|
||
|
||
|
||
def main() -> int:
|
||
print("=" * 62)
|
||
print(" 重排 nginx:IP 没东西 / 9100 官网 / 域名官网")
|
||
print("=" * 62)
|
||
print()
|
||
|
||
# 先把现在的 443 块里 certbot 加的行抠出来(保留原样)
|
||
#
|
||
# ⚠ 只取**真正的指令**(listen / ssl_* / include options-ssl)。
|
||
# 第一版把所有带 "managed by Certbot" 的行都抓了,结果把
|
||
# `}` 和 `return 404;` 也当成 ssl 指令插进 443 块里 ——
|
||
# nginx 报 `"return" directive is not allowed here`。
|
||
# 那些是**结构行**,属于别的 server 块。
|
||
print("── 1. 读现在的配置,抠出 certbot 的行 ──")
|
||
r = run("sudo cat /etc/nginx/sites-available/ykxmz", timeout=90, quiet=True)
|
||
old = r[1] if isinstance(r, tuple) else str(r)
|
||
cert_lines = []
|
||
for ln in old.splitlines():
|
||
s = ln.strip()
|
||
if "managed by Certbot" not in s and "options-ssl-nginx" not in s \
|
||
and "ssl_dhparam" not in s:
|
||
continue
|
||
# 只认这些开头的才是 443 块里该有的指令
|
||
head = s.split()[0] if s.split() else ""
|
||
if head in ("listen",) or head.startswith("ssl_") \
|
||
or head == "include":
|
||
cert_lines.append(s)
|
||
else:
|
||
print(" 跳过结构行: %s" % s[:60])
|
||
print(" 找到 %d 行可用的 certbot 配置" % len(cert_lines))
|
||
for ln in cert_lines:
|
||
print(" " + ln)
|
||
|
||
conf = MAIN_CONF
|
||
if not cert_lines:
|
||
print(" ⚠ 没找到 certbot 的行,用默认路径(可能不对,先跑 nginx -t 验)")
|
||
else:
|
||
# 用抓到的真实行替换模板里的那几行
|
||
conf = conf.replace(
|
||
" listen [::]:443 ssl ipv6only=on; # managed by Certbot\n"
|
||
" listen 443 ssl; # managed by Certbot\n"
|
||
" ssl_certificate /etc/letsencrypt/live/yk.xxmxxs.top/fullchain.pem; # managed by Certbot\n"
|
||
" ssl_certificate_key /etc/letsencrypt/live/yk.xxmxxs.top/privkey.pem; # managed by Certbot\n"
|
||
" include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot\n"
|
||
" ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot",
|
||
"\n".join(" " + ln.strip() for ln in cert_lines), 1)
|
||
|
||
# ---- 写 snippet ----
|
||
print()
|
||
print("── 2. 写共享转发规则 ──")
|
||
tmp = ROOT / "_build" / "_snip.conf"
|
||
tmp.write_text(SNIPPET, encoding="utf-8", newline="\n")
|
||
upload(str(tmp), "/tmp/ykxmz-proxy.conf")
|
||
tmp.unlink(missing_ok=True)
|
||
tmp2 = ROOT / "_build" / "_main.conf"
|
||
tmp2.write_text(conf, encoding="utf-8", newline="\n")
|
||
upload(str(tmp2), "/tmp/ykxmz-main.conf")
|
||
tmp2.unlink(missing_ok=True)
|
||
|
||
r = run(r'''
|
||
sudo mkdir -p /etc/nginx/snippets
|
||
sudo cp /etc/nginx/sites-available/ykxmz /etc/nginx/sites-available/ykxmz.bak.$(date +%s)
|
||
sudo mv /tmp/ykxmz-proxy.conf /etc/nginx/snippets/ykxmz-proxy.conf
|
||
sudo mv /tmp/ykxmz-main.conf /etc/nginx/sites-available/ykxmz
|
||
sudo nginx -t 2>&1 | tail -3
|
||
''', timeout=180, quiet=True)
|
||
out = r[1] if isinstance(r, tuple) else str(r)
|
||
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 && sudo nginx -t", timeout=120,
|
||
quiet=True)
|
||
return 1
|
||
print(" ✓ 写入成功")
|
||
|
||
print()
|
||
print("── 3. 重载 ──")
|
||
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())
|
||
|
||
print()
|
||
print("── 4. 复核(按用户要求逐条验)──")
|
||
r = run(r'''
|
||
echo " 要求:<服务器IP>(80)-> 没东西"
|
||
printf " %-46s " "http://127.0.0.1/"
|
||
curl -s -o /dev/null -w "%{http_code} %{size_download} 字节\n" --max-time 10 http://127.0.0.1/
|
||
echo " 要求:<服务器IP>:9100 -> 官网"
|
||
printf " %-46s " "http://127.0.0.1:9100/"
|
||
curl -s -o /dev/null -w "%{http_code} %{size_download} 字节\n" --max-time 10 http://127.0.0.1:9100/
|
||
echo " 要求:yk.xxmxxs.top -> 官网"
|
||
printf " %-46s " "https://yk.xxmxxs.top/"
|
||
curl -s -o /dev/null -w "%{http_code} %{size_download} 字节\n" --max-time 15 https://yk.xxmxxs.top/
|
||
echo " 要求::9100/zhongzhuanzhan -> 中转站设置"
|
||
printf " %-46s " "http://127.0.0.1:9100/zhongzhuanzhan"
|
||
curl -s -o /dev/null -w "%{http_code} %{size_download} 字节\n" --max-time 10 http://127.0.0.1:9100/zhongzhuanzhan
|
||
echo " 要求:yk.xxmxxs.top/zhongzhuanzhan -> 中转站设置"
|
||
printf " %-46s " "https://yk.xxmxxs.top/zhongzhuanzhan"
|
||
curl -s -o /dev/null -w "%{http_code} %{size_download} 字节\n" --max-time 15 https://yk.xxmxxs.top/zhongzhuanzhan
|
||
echo
|
||
echo " 另外验证中转接口没被弄坏:"
|
||
printf " %-46s " "https://yk.xxmxxs.top/v1/health"
|
||
curl -s -o /dev/null -w "%{http_code}\n" --max-time 15 https://yk.xxmxxs.top/v1/health
|
||
printf " %-46s " "https://yk.xxmxxs.top/download/ykxmz-setup-2.1.8.exe"
|
||
curl -s -o /dev/null -w "%{http_code} %{size_download} 字节\n" --max-time 30 -r 0-1000 https://yk.xxmxxs.top/download/ykxmz-setup-2.1.8.exe
|
||
echo
|
||
echo " 监听端口:"
|
||
sudo ss -tlnp 2>/dev/null | grep -E ':(80|443|9100) ' | awk '{print " "$4}'
|
||
''', timeout=240, quiet=True)
|
||
print(r[1] if isinstance(r, tuple) else r)
|
||
|
||
print()
|
||
print(" ⚠ 还要在 **AWS Lightsail 控制台**开放 9100 端口,外面才连得上")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|