Files
ykxmz/_build/fix_monitor_url.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

166 lines
6.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'''修监测单元:它现在查错了地址,在**误报网站挂了**。
== 怎么发现的 ==
把中转改成只监听 127.0.0.1(公网只能走 nginx)之后,跑状态检查看到:
公网 health 000 <- 因为 :13245 已经不对公网开了
fails=2 <- 监测单元连续两次"失败"
也就是说监测单元**误以为网站挂了**,再失败一次就会启动紧急预案 ——
而它抢救的方式是"重启中转服务",重启完它再查还是 000,再救……
**会陷入无限重启循环**。
这是我自己改监听地址时没想到的连带影响。教训:改一个服务的
暴露方式时,必须把所有"探测它"的地方一起看一遍。
== 改法 ==
监测的地址应该以"**用户实际怎么访问**"为准,现在应该是:
http://yk.xxmxxs.top/v1/health (经过 Cloudflare + nginx)
但域名还没配好(DNS 还没指过来),所以加一个**回退**:
域名不通就用 IP:80 试,只要有一个通就算健康。
这样:
* 域名配好之前 -> 走 IP:80,正常监控
* 域名配好之后 -> 走域名,监控的是用户真实路径
* 两个都不通 -> 才是真挂了
== 顺带 ==
monitor.log 的属主问题:monitor.service 以 admin 跑,
而 relay.log 之类的之前是 root 建的。已经 chown 过一次,
但 monitor.log 是后来新生成的(仍是 root)—— 这次一并修掉,
并且在 unit 里加一行让 systemd 自己保证目录属主。
'''
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, HOST as LS_HOST # noqa: E402
RELAY_DIR = "/opt/ykxmz-relay"
DOMAIN = "yk.xxmxxs.top"
FALLBACK = "http://%s:80" % LS_HOST
def main() -> int:
print("=" * 62)
print(" 修监测单元:改探测地址 + 修日志属主")
print("=" * 62)
print()
print(" ── 1. 改 monitor.sh 的健康检查(域名优先,IP 兜底)──")
# 把 PUBLIC_URL 的默认值换掉,并把 is_healthy 改成"两个都试"
old_url = 'PUBLIC_URL="${PUBLIC_URL:-http://LS_HOST:13245}"'
new_url = (
'# 探测地址:域名优先(用户实际走的路),IP:80 兜底\n'
'# (域名 DNS 还没指过来时用它,否则监测会一直误报)\n'
'PUBLIC_URL="${PUBLIC_URL:-http://%s}"\n'
'FALLBACK_URL="${FALLBACK_URL:-%s}"' % (DOMAIN, FALLBACK))
out = run(
"grep -n 'PUBLIC_URL=' %s/monitor.sh | head -3" % RELAY_DIR,
timeout=60, quiet=True)
print(" 改前:")
for ln in out[1].strip().splitlines():
print(" " + ln)
script = (
"python3 - <<'PYEOF'\n"
"from pathlib import Path\n"
"p = Path('%s/monitor.sh')\n"
"t = p.read_text(encoding='utf-8')\n"
"old = [l for l in t.splitlines() if l.startswith('PUBLIC_URL=')]\n"
"if not old:\n"
" print(' x 找不到 PUBLIC_URL 行')\n"
"else:\n"
" t = t.replace(old[0], %r, 1)\n"
" # 健康检查改成两个都试\n"
" a = 'CODE=$(curl -s -o /dev/null -w \\'%%{http_code}\\' --max-time "
"\"$CHECK_TIMEOUT\" \\\\\\n \"$PUBLIC_URL/v1/health\" 2>/dev/null)'\n"
" b = ('PROBE=\"$PUBLIC_URL\"\\n'\n"
" 'CODE=$(curl -s -o /dev/null -w \\'%%{http_code}\\' "
"--max-time \"$CHECK_TIMEOUT\" \"$PROBE/v1/health\" 2>/dev/null)\\n'\n"
" 'if [ \"$CODE\" != \"200\" ] && [ -n \"$FALLBACK_URL\" ]; then\\n'\n"
" ' PROBE=\"$FALLBACK_URL\"\\n'\n"
" ' CODE=$(curl -s -o /dev/null -w \\'%%{http_code}\\' "
"--max-time \"$CHECK_TIMEOUT\" \"$PROBE/v1/health\" 2>/dev/null)\\n'\n"
" 'fi')\n"
" if a in t:\n"
" t = t.replace(a, b, 1)\n"
" print(' ok 健康检查改成域名优先 + IP 兜底')\n"
" else:\n"
" print(' ! 健康检查那段没匹配上,手工看')\n"
" p.write_text(t, encoding='utf-8', newline='\\n')\n"
"PYEOF" % (RELAY_DIR, new_url))
out = run(script, timeout=120, quiet=True)
for ln in out[1].strip().splitlines():
print(" " + ln)
out = run("grep -n 'PUBLIC_URL=\\|FALLBACK_URL=\\|PROBE=' %s/monitor.sh "
"| head -8" % RELAY_DIR, timeout=60, quiet=True)
print(" 改后:")
for ln in out[1].strip().splitlines():
print(" " + ln)
print()
print(" ── 2. 修日志属主 + 让 systemd 保证它 ──")
out = run(
"sudo chown -R admin:admin %s && "
"sudo sed -i '/^User=admin$/i "
"# 日志文件和状态文件都要 admin 能写(服务以 admin 跑)\\n"
"# 之前 relay.log / monitor.log 是 root 建的,导致 "
"monitor.sh 报 Permission denied\\n"
"PermissionsStartOnly=true' "
"/etc/systemd/system/ykxmz-monitor.service 2>/dev/null; "
"sudo touch %s/monitor.log %s/monitor_state.txt && "
"sudo chown admin:admin %s/*.log %s/*.txt %s/*.md 2>/dev/null; "
"ls -la %s/*.log | awk '{print \" \" $3\":\"$4\" \" $9}'"
% (RELAY_DIR, RELAY_DIR, RELAY_DIR, RELAY_DIR, RELAY_DIR,
RELAY_DIR, RELAY_DIR), timeout=180, quiet=True)
for ln in out[1].strip().splitlines():
print(ln)
print()
print(" ── 3. 重置监测状态 + 跑一次 ──")
out = run(
"sudo systemctl daemon-reload; "
"printf 'fails=0\\nlast_rescue=0\\nlast_code=200\\n' > "
"%s/monitor_state.txt; "
"bash %s/monitor.sh; echo '--- 状态 ---'; "
"cat %s/monitor_state.txt; echo '--- 日志尾 ---'; "
"tail -3 %s/monitor.log" % (RELAY_DIR, RELAY_DIR, RELAY_DIR,
RELAY_DIR),
timeout=180, quiet=True)
for ln in out[1].strip().splitlines():
print(" " + ln)
print()
print(" ── 4. 验证探测链路 ──")
out = run(
'printf " %-28s %s\\n" "域名(还没配)" '
'"$(curl -s -o /dev/null -w \'%{http_code}\' --max-time 8 '
'http://%s/v1/health)"; '
'printf " %-28s %s\\n" "IP:80 (兜底)" '
'"$(curl -s -o /dev/null -w \'%{http_code}\' --max-time 8 '
'http://LS_HOST:80/v1/health)"; '
'printf " %-28s %s\\n" "IP:13245 (已关闭)" '
'"$(curl -s -o /dev/null -w \'%{http_code}\' --max-time 8 '
'http://LS_HOST:13245/v1/health)"' % DOMAIN,
timeout=120, quiet=True)
for ln in out[1].strip().splitlines():
print(ln)
return 0
if __name__ == "__main__":
sys.exit(main())