搬到 Lightsail 真服务器 + 加 nginx 给 Cloudflare 回源

== 新架构 ==

    用户 --HTTPS--> Cloudflare CDN --HTTP:80--> nginx
                    (yk.xxmxxs.top)              13.54.30.196
                                                     |
                                                     v
                                            127.0.0.1:13245 (中转)

旧架构是"Cloudflare 隧道"(cloudflared 走 7844 到那台 4G 小机器),
那条线路对 7844 是间歇性阻断的(443 通、7844 不通),
隧道每 3~10 分钟掉一次、公网跟着 530 —— 是"网站容易挂"的主因。

新架构**不用隧道**:新机器有公网 IP,Cloudflare 走标准的
反代(回源 80 端口),这是完全不同的东西,没有 7844 问题。

== 为什么要 nginx 这一层 ==

Cloudflare 只能回源到特定端口:
    HTTP:  80 / 8080 / 8880 / 2052 / 2082 / 2086 / 2095
    HTTPS: 443 / 2053 / 2083 / 2087 / 2096 / 8443
中转跑的 13245 不在列表里,所以中间放一层 nginx。

加了 nginx 之后顺便把中转**改成只监听 127.0.0.1** ——
公网只能通过 nginx 进来,少一个暴露面。实测
http://13.54.30.196:13245 现在是连不上的(000),符合预期。

== nginx 配置要点 ==

  /download/   proxy_buffering on + 显式 Cache-Control 604800 immutable
               (文件名带版本号、内容永不改变,放心长缓存;
                 Cloudflare 缓存后第二次下载不回源)
  /v1/         proxy_read_timeout 180s + **proxy_buffering off**
               (长轮询挂 60 秒,nginx 默认 60 秒就断;而且必须关缓冲,
                 否则要等整个响应完才吐给客户端,那就不是长轮询了)
  /            普通反代
  server_tokens off
  client_max_body_size 200m

== 代码修复 ==

relay.py 的 DATA_DIR 默认值原来是写死的 /root/lanremote-relay。
新机器上服务以 admin 普通用户跑(它没理由要 root),而 /root 是 0700,
部署时直接 PermissionError。改成"脚本所在目录下的 data/"。

== 客户端 ==

2.1.3 里已经让手表端支持"没有证书的中转"(裸 IP 走明文 +
usesCleartextTraffic)。等域名和 Cloudflare 接好之后就是全程 HTTPS 了。

== 新增的运维脚本 ==

  _build/lssh.py                     Lightsail 的 SSH 助手(密钥登录)
  _build/deploy_lightsail.py         首次部署中转
  _build/deploy_lightsail_monitor.py 装监测单元 + 预案
  _build/deploy_lightsail_release.py 按版本部署代码和成品
  _build/check_lightsail.py          看状态
  _build/setup_nginx.py              装配 nginx
  relay/rescue-lightsail.sh          紧急预案(针对新架构重写:
                                     没有隧道了,改成重启中转 / 强杀重拉 /
                                     查防火墙 / 重启整机[默认关])
This commit is contained in:
xmz7891
2026-10-08 22:38:57 +08:00
parent 62e67e9e26
commit 892d5891ea
3 changed files with 381 additions and 0 deletions
+119
View File
@@ -0,0 +1,119 @@
# -*- coding: utf-8 -*-
r'''把一个版本的代码和成品部署到 Lightsail。
用法:
python _build/deploy_lightsail_release.py 2.1.3
python _build/deploy_lightsail_release.py 2.1.3 --code-only
python _build/deploy_lightsail_release.py 2.1.3 --files-only
== 为什么写成文件 ==
这一轮我用 `python -c @"..."@` 传带 `$(...)` 的远端命令,
PowerShell 把 `$(systemctl ...)` 当成本机的子表达式执行了
(报 "无法将 systemctl 项识别为 cmdlet"),还吃掉引号导致
Python 语法错。凡是超过两行的远端命令,一律写成文件跑。
'''
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
RELAY_DIR = "/opt/ykxmz-relay"
PORT = "13245"
PUBLIC = "http://13.54.30.196:%s" % PORT
CODE = ["relay.py", "admin.py", "landing.py", "showcase.py"]
ARTIFACTS = ["ykxmz-setup-{v}.exe", "ykxmz-watch-{v}.apk",
"ykxmz-watch-{v}-说明.txt", "ykxmz-server-{v}.zip"]
def main() -> int:
args = [a for a in sys.argv[1:] if a]
if not args:
print(__doc__)
return 1
version = args[0]
code_only = "--code-only" in args
files_only = "--files-only" in args
print("=" * 62)
print(" 部署 %s 到 Lightsail" % version)
print("=" * 62)
if not code_only:
print()
print(" ── 1. 传成品到 downloads/ ──")
run("mkdir -p %s/downloads" % RELAY_DIR, timeout=60, quiet=True)
for tpl in ARTIFACTS:
name = tpl.format(v=version)
local = ROOT / "dist" / name
if not local.is_file():
print(" [跳过] %s 不存在" % name)
continue
upload(str(local), "%s/downloads/%s" % (RELAY_DIR, name))
if not files_only:
print()
print(" ── 2. 传服务端代码 ──")
for name in CODE:
local = ROOT / "relay" / name
if not local.is_file():
print(" [跳过] %s 不存在" % name)
continue
upload(str(local), "%s/%s" % (RELAY_DIR, name))
print()
print(" ── 3. 语法检查 + 重启 ──")
out = run(
"cd %s && for f in relay.py admin.py landing.py showcase.py; do "
"python3 -c \"import ast,sys;ast.parse(open(sys.argv[1],"
"encoding='utf-8').read())\" $f || exit 1; done && echo PY_OK"
% RELAY_DIR, timeout=120, quiet=True)
print(" " + out[1].strip().splitlines()[-1])
out = run("sudo systemctl restart ykxmz-relay; sleep 4; "
"systemctl is-active ykxmz-relay", timeout=180, quiet=True)
print(" relay 状态: " + out[1].strip())
print()
print(" ── 4. 复核 ──")
script = (
'printf " %-18s %s\\n" 本机 "'
'$(curl -s -o /dev/null -w \'%{http_code}\' --max-time 8 '
'http://127.0.0.1:' + PORT + '/v1/health)"\n'
'printf " %-18s %s\\n" 公网 "'
'$(curl -s -o /dev/null -w \'%{http_code}\' --max-time 15 '
+ PUBLIC + '/v1/health)"\n'
'printf " %-18s %s\\n" 公网首页 "'
'$(curl -s -o /dev/null -w \'%{http_code}\' --max-time 15 '
+ PUBLIC + '/)"\n'
'printf " %-18s %s\\n" 公网延迟 "'
'$(curl -s -o /dev/null -w \'%{time_total}s\' --max-time 15 '
+ PUBLIC + '/v1/health)"\n'
'printf " %-18s %s\\n" 角标 "'
'$(curl -s --max-time 10 http://127.0.0.1:' + PORT
+ '/ | grep -o "最新版 [0-9.]*" | head -1)"\n'
'printf " %-18s %s\\n" 监听 "'
'$(ss -tln 2>/dev/null | grep ":' + PORT + ' " | wc -l) 个"\n'
'printf " %-18s %s\\n" 监测 "'
'fails=$(grep -m1 ^fails= ' + RELAY_DIR
+ '/monitor_state.txt 2>/dev/null | cut -d= -f2); '
'echo ${fails:-?} 次连续失败"\n'
'printf " %-18s %s\\n" 抢救报告 "'
'$(ls ' + RELAY_DIR + '/rescue_reports 2>/dev/null | wc -l) 份"\n'
)
out = run(script, timeout=180, quiet=True)
for ln in out[1].strip().splitlines():
print(ln)
print()
print(" 公网地址:%s" % PUBLIC)
return 0
if __name__ == "__main__":
sys.exit(main())