mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 19:30:42 +00:00
上一个提交把仓库里的服务器 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 三个产物扫描干净
提交前泄漏闸门通过
201 lines
7.6 KiB
Python
201 lines
7.6 KiB
Python
# -*- 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())
|