Files
ykxmz/_build/setup_nginx.py
T
xmz7891 0ad3f648c6 修上一轮清理留下的副作用:占位符把运维脚本弄连不上了
上一个提交把仓库里的服务器 IP 换成了占位符,但**那些脚本本身要用这个 IP
去连服务器** —— 换掉之后它们就跑不起来了。这个提交把取值方式理清楚。

== 取值统一走 _build/lssh.py ==

    YKXMZ_LS_HOST 环境变量
    或 _build/.lightsail_host(一行 IP,已 gitignore)
    都没有就报错,并告诉你怎么设

十个运维脚本改成 `from lssh import ... HOST as LS_HOST`,
公开仓库里一个具体地址都不留。

== 顺带修的两个自己造的 bug ==

  * 用正则往 `from lssh import ...` 后面追加东西时,
    追加到了行尾的 `# noqa: E402` **注释里面** ——
    十处导入全是废的,脚本一跑就 NameError。
    改成先剥掉注释、拼好导入、再把 noqa 加回去。

  * 同一个正则还把 `from lssh import HOST as LS_HOST` 加进了
    **lssh.py 自己**,造成自我导入。

教训:**正则批量改导入语句很危险**(注释、行尾、顺序都能坑到),
这种地方宁可逐个 edit。

== 为什么要重建产物 ==

docs/AFTER-INSTALL.txt 会被打进服务端 zip,而它上一轮改过
(去掉了写死的旧管理员密码)—— 不重打的话发出去的还是旧的。

重打后确认:三个产物里都没有旧密码、没有服务器 IP。

== 验证 ==

    全量回归退出码 0,41 个阶段全 PASS
    603 个 Python 文件语法通过
    ykxmz-agent / server / watch 三个产物扫描干净
    提交前泄漏闸门通过
2026-10-09 00:02:32 +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) LS_HOST
== 为什么要加 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 的开源端口)。
但要注意:如果有人知道 LS_HOST,也能直接打 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, HOST as LS_HOST # 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://LS_HOST: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://LS_HOST: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())