Files
ykxmz/_build/setup_nginx.py
T
xmz7891 892d5891ea 搬到 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          紧急预案(针对新架构重写:
                                     没有隧道了,改成重启中转 / 强杀重拉 /
                                     查防火墙 / 重启整机[默认关])
2026-10-08 22:38:57 +08:00

201 lines
7.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
r'''在 Lightsail 上装 nginx,把 yk.xxmxxs.top 反代到中转。
== 架构 ==
用户 ──HTTPS──> Cloudflare CDN ──HTTP:80──> nginx ──> 127.0.0.1:13245
(yk.xxmxxs.top) 13.54.30.196
== 为什么要加 nginx 这一层 ==
1. **Cloudflare 只能回源到特定端口**
(HTTP: 80/8080/8880/2052/2082/2086/2095,
HTTPS: 443/2053/2083/2087/2096/8443)
中转跑的 13245 **不在列表里** —— Cloudflare 没法直接回源。
2. **顺便把中转藏起来**
加了 nginx 之后,可以让 relay 只监听 127.0.0.1,
公网只能通过 nginx 进来。少一个暴露面。
3. **以后上 HTTPS 有地方挂证书**
现在先用 Cloudflare 的"灵活"模式(CF→源站走 HTTP),
以后要"完全(严格)"就把源站证书挂到 nginx 上。
== 关于端口 80 的安全 ==
nginx 监听 80 是**必须的**(Cloudflare 的开源端口)。
但要注意:如果有人知道 13.54.30.196,也能直接打 80 绕过 Cloudflare。
所以配置里加了:
* 只接受来自 Cloudflare 网段的请求(可选,见 allowlist 那段)
* 或者更简单:不启用 allowlist,但把 relay 绑到 127.0.0.1,
反正绕不绕过都是同一个网站,没有额外信息泄露
这里选后者(简单、不容易把自己锁在外面)。
'''
from __future__ import annotations
import sys
import tempfile
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "_build"))
from lssh import run, upload # noqa: E402
DOMAIN = "yk.xxmxxs.top"
RELAY_PORT = "13245"
NGINX_SITE = '''# ykxmz 中转 —— 给 Cloudflare 回源用的
#
# Cloudflare 只能回源到特定端口,中转跑的 %(port)s 不在列表里,
# 所以中间放一层 nginx(监听 80)。
#
# 客户端拿到的大文件(安装包 15MB)会被 Cloudflare 缓存 ——
# 第二次下载就不回源了,速度取决于离用户最近的 CF 边缘节点。
server {
listen 80;
listen [::]:80;
server_name %(domain)s;
# 日志:单独一份,方便排查"到底是 CF 打的还是别人直接打的"
access_log /var/log/nginx/ykxmz-access.log;
error_log /var/log/nginx/ykxmz-error.log;
# ---- 下载:让 Cloudflare 放心缓存 ----
#
# 文件名带版本号(ykxmz-setup-2.1.3.exe),同一个名字内容永不改变,
# 所以可以长缓存。中转自己也发过 Cache-Control,
# 这里显式再写一遍是为了让 CF 一定看到。
location /download/ {
proxy_pass http://127.0.0.1:%(port)s;
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;
proxy_cache_valid 200 7d;
add_header Cache-Control "public, max-age=604800, immutable" always;
}
# ---- 长轮询 ----
#
# 中转的 /v1/agent/poll 要挂 60 秒。nginx 默认 60 秒就断,
# 必须放宽,否则手表那边会一直掉线重连。
location /v1/ {
proxy_pass http://127.0.0.1:%(port)s;
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:%(port)s;
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;
}
# 别把 nginx 版本号报出去
server_tokens off;
client_max_body_size 200m;
}
'''
def main() -> int:
print("=" * 62)
print(" 装 nginx,把 %s 反代到中转" % DOMAIN)
print("=" * 62)
print()
print(" ── 1. 装 nginx ──")
out = run("which nginx >/dev/null 2>&1 && echo ALREADY || "
"(sudo apt-get update -qq && "
"sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq nginx) "
"&& echo OK; nginx -v 2>&1", timeout=600, quiet=True)
for ln in out[1].strip().splitlines()[-4:]:
print(" " + ln)
print()
print(" ── 2. 写站点配置 ──")
conf = NGINX_SITE % {"domain": DOMAIN, "port": RELAY_PORT}
tmp = Path(tempfile.gettempdir()) / "ykxmz.conf"
tmp.write_text(conf, encoding="utf-8", newline="\n")
upload(str(tmp), "/tmp/ykxmz.conf")
out = run("sudo mv /tmp/ykxmz.conf /etc/nginx/sites-available/ykxmz && "
"sudo ln -sf /etc/nginx/sites-available/ykxmz "
"/etc/nginx/sites-enabled/ykxmz && "
# 关掉默认站点,免得抢 80
"sudo rm -f /etc/nginx/sites-enabled/default && "
"sudo nginx -t 2>&1 | tail -3", timeout=180, quiet=True)
for ln in out[1].strip().splitlines():
print(" " + ln)
print()
print(" ── 3. 重启 nginx ──")
out = run("sudo systemctl enable nginx >/dev/null 2>&1; "
"sudo systemctl restart nginx; sleep 2; "
"systemctl is-active nginx", timeout=180, quiet=True)
print(" nginx: " + out[1].strip())
print()
print(" ── 4. 中转改为只监听 127.0.0.1(不再直接对公网)──")
# 公网只能通过 nginx 进来,少一个暴露面。
out = run("sudo sed -i 's/^Environment=RELAY_BIND=.*$/"
"Environment=RELAY_BIND=127.0.0.1/' "
"/etc/systemd/system/ykxmz-relay.service && "
"grep RELAY_BIND /etc/systemd/system/ykxmz-relay.service && "
"sudo systemctl daemon-reload && "
"sudo systemctl restart ykxmz-relay && sleep 3 && "
"systemctl is-active ykxmz-relay", timeout=180, quiet=True)
for ln in out[1].strip().splitlines():
print(" " + ln)
print()
print(" ── 5. 验证 ──")
script = (
'printf " %-22s %s\\n" "nginx 直连(本机:80)" '
'"$(curl -s -o /dev/null -w \'%{http_code}\' --max-time 8 '
'http://127.0.0.1:80/v1/health)"\n'
'printf " %-22s %s\\n" "带 Host 头" '
'"$(curl -s -o /dev/null -w \'%{http_code}\' --max-time 8 '
'-H \'Host: ' + DOMAIN + '\' http://127.0.0.1:80/v1/health)"\n'
'printf " %-22s %s\\n" "公网 80(IP)" '
'"$(curl -s -o /dev/null -w \'%{http_code}\' --max-time 15 '
'http://13.54.30.196:80/v1/health)"\n'
'printf " %-22s %s\\n" "中转本机(13245)" '
'"$(curl -s -o /dev/null -w \'%{http_code}\' --max-time 8 '
'http://127.0.0.1:13245/v1/health)"\n'
'printf " %-22s %s\\n" "中转是否还暴露公网" '
'"$(curl -s -o /dev/null -w \'%{http_code}\' --max-time 8 '
'http://13.54.30.196:13245/v1/health)"\n'
)
out = run(script, timeout=180, quiet=True)
for ln in out[1].strip().splitlines():
print(ln)
print()
print(" 说明:上面「中转是否还暴露公网」应该是 000(连不上)——")
print(" 那正说明它已经只认 127.0.0.1,公网只能走 nginx。")
return 0
if __name__ == "__main__":
sys.exit(main())