docs(memory): Uptime Kuma 第一批监控上线 + git 证书更新记录

This commit is contained in:
夏犀麟 2026-09-13 17:27:06 +08:00
parent f2bbcd28d1
commit b7246c4d47
8 changed files with 266 additions and 0 deletions

View File

@ -1,5 +1,8 @@
# 2026-09-13
2026-09-13 | CHG | Uptime Kuma 第一批监控上线9 个站点 HTTP 监控60/120/300s 三档 + 证书到期通知),因官方 API 不支持创建监控,采用「直写 SQLite monitor 表 + 重启容器」批量添加
2026-09-13 | FIX | 修正 service.xpcool.com 监控误报:后端纯 POST 无根路由,可接受状态码放宽为 ["200-399","404"](进程挂掉时 nginx 返 502仍判 DOWN
2026-09-13 | CFG | git.xpcool.com 证书更新(至 2026-12-12旧证书备份为 fullchain.pem.bak.202609131719;注意该站证书文件名是 fullchain.pem/privkey.pem与别的站不一致
2026-09-13 | CFG | status.xpcool.com 切换 HTTPS腾讯云 DV 证书落到 /data/nginx/ssl/status.xpcool.com/key 60080→301→443 生效,公网验证 ssl_verify=0、证书有效期至 2026-12-12
2026-09-13 | FIX | 沉淀 SSH 远程部署两个坑:多行脚本经 ssh.exe 传参会因括号导致 bash 语法错误;多文件 scp 静默丢文件 → 改为「本地 .sh + 单文件 scp + 幂等脚本」
2026-09-13 | CFG | status.xpcool.com新增 nginx 反代配置(先 HTTP含 WebSocket 透传nginx -t 通过并已 reload

View File

@ -58,3 +58,13 @@
- **证书**:腾讯云免费 DVCN/SAN 均为 `status.xpcool.com`**有效期仅 3 个月(至 2026-12-12到期必须重新申请并替换**;落点 `/data/nginx/ssl/status.xpcool.com/`crt/pem/csr 644、key 600本地源 `E:\CentOS\ssl\status.xpcool.com_nginx\`。部署时用 `openssl x509 -pubkey | md5``openssl pkey -pubout | md5` 比对,确认证书与私钥配套。
- 访问形态:`http:// → 301 → https://`;公网实测 `ssl_verify=0`、响应约 0.45s2026-09-13
- nginx 配置模板见 `.workbuddy/tmp/status.xpcool.com.conf`
- **监控的添加方式2026-09-13 实测)**Uptime Kuma 官方 REST API **不支持创建监控**(只能读/暂停/恢复),只能走 Socket.IO需面板账号密码社区库 `uptime-kuma-api`)或**直接写 SQLite**。本项目采用后者:向 `monitor` 表 INSERT字段均有默认值只需给 `name/type/url/interval/retry_interval/maxretries/weight/description/accepted_statuscodes_json``user_id=1` 即 `xpcool`),再 `docker restart uptime-kuma` 让服务端重新加载并开始探测。脚本:`.workbuddy/tmp/add-monitors.sh`(幂等,按 name 去重);**改库前先备份** `/tmp/kuma.db.backup.<时间戳>`
- **heartbeat.status 含义**`0=DOWN`、`1=UP`、`2=PENDING`、`3=MAINTENANCE``monitor_tls_info` 表会自动填充各站点证书信息(可用于证书到期监控)。
- **第一批监控9 个,均 HTTP(s)、`accepted_statuscodes_json=["200-399"]`、开启证书到期通知)**
| 站点 | 间隔 | 重试 | 权重 |
|---|---|---|---|
| admin / service / git | 60s | 3 | 1000 |
| file / read / tool / sub2api | 120s | 2 | 2000 |
| code / status | 300s | 2 | 3000 |
- **`service.xpcool.com` 单独放宽为 `["200-399","404"]`**:该后端是纯 POST 接口(遵循本项目"接口全 POST"规范),任何 GET 都 404返回 404 说明 nginx + GoFrame 存活,**后端进程真挂时 nginx 返回 502仍会判 DOWN**。
- **待办**:通知渠道尚未配置(建议接 Bark可复用现有 `bark` 容器及 `service.xpcool.com/bark/` 反代);「证书到期提前几天通知」需在面板设置里确认。

View File

@ -0,0 +1,82 @@
#!/bin/bash
# 批量添加 Uptime Kuma 监控2026-09-13 第一批)
# 说明Uptime Kuma 官方 REST API 不支持创建监控,这里直接写入 SQLite字段均有默认值
# 写完后重启容器,服务端会从 monitor 表重新加载并开始探测。
set -e
DB=/data/uptime-kuma/kuma.db
echo "===== insert monitors ====="
sudo python3 - <<'PY'
import sqlite3, shutil, datetime
DB = "/data/uptime-kuma/kuma.db"
USER_ID = 1
# 先备份数据库(含 WAL便于回滚
stamp = datetime.datetime.now().strftime("%Y%m%d%H%M")
for ext in ("", "-wal", "-shm"):
try:
shutil.copy(DB + ext, "/tmp/kuma.db.backup.%s%s" % (stamp, ext))
except Exception:
pass
print("backup -> /tmp/kuma.db.backup.%s" % stamp)
# (名称, URL, 探测间隔秒, 最大重试, 排序权重(越小越前), 描述)
MONITORS = [
("管理后台 · admin.xpcool.com", "https://admin.xpcool.com", 60, 3, 1000, "Vue 管理后台前端"),
("后端服务 · service.xpcool.com", "https://service.xpcool.com", 60, 3, 1000, "Go 后端服务"),
("代码托管 · git.xpcool.com", "https://git.xpcool.com", 60, 3, 1000, "Gitea 代码托管"),
("文件下载 · file.xpcool.com", "https://file.xpcool.com", 120, 2, 2000, "FileBrowser Quantum 只读下载"),
("阅读 · read.xpcool.com", "https://read.xpcool.com", 120, 2, 2000, "legado 自托管阅读"),
("工具站 · tool.xpcool.com", "https://tool.xpcool.com", 120, 2, 2000, "前端工具站"),
("sub2api · sub2api.xpcool.com", "https://sub2api.xpcool.com", 120, 2, 2000, "sub2api 服务"),
("网页 IDE · code.xpcool.com", "https://code.xpcool.com", 300, 2, 3000, "code-server 网页 IDE"),
("监控面板 · status.xpcool.com", "https://status.xpcool.com", 300, 2, 3000, "Uptime Kuma 自身可用性"),
]
con = sqlite3.connect(DB, timeout=30)
cur = con.cursor()
existing = {r[0] for r in cur.execute("select name from monitor")}
added = 0
for name, url, interval, retries, weight, desc in MONITORS:
if name in existing:
print("SKIP exists:", name)
continue
cur.execute(
"INSERT INTO monitor ("
" name, active, user_id, interval, retry_interval, maxretries, url, type, method,"
" weight, description, accepted_statuscodes_json, expiry_notification,"
" domain_expiry_notification, upside_down, maxredirects, invert_keyword"
") VALUES (?, 1, ?, ?, ?, ?, ?, 'http', 'GET', ?, ?, ?, 1, 0, 0, 10, 0)",
(name, USER_ID, interval, interval, retries, url, weight, desc, '["200-399"]'),
)
print("ADD:", name, "-> id", cur.lastrowid)
added += 1
con.commit()
print("added =", added, " total =", cur.execute("select count(*) from monitor").fetchone()[0])
con.close()
PY
echo "===== restart uptime-kuma (reload monitors) ====="
sudo docker restart uptime-kuma >/dev/null
sleep 50
echo "===== verify ====="
sudo python3 - <<'PY'
import sqlite3
con = sqlite3.connect("file:/data/uptime-kuma/kuma.db?mode=ro", uri=True)
print("monitors:")
for row in con.execute("select id, name, type, interval, active from monitor order by weight, id"):
print(" ", row)
print("heartbeat rows:", con.execute("select count(*) from heartbeat").fetchone()[0])
print("latest beats (monitor_id, status, time):")
for row in con.execute("select monitor_id, status, time from heartbeat order by id desc limit 15"):
print(" ", row)
PY
echo "===== container ====="
sudo docker ps --filter name=uptime-kuma --format '{{.Names}} {{.Status}}'
echo DONE

View File

@ -0,0 +1,43 @@
#!/bin/bash
# 修正 service.xpcool.com 监控的判定规则
# 背景:该后端为纯 POST 接口,任何 GET 都返回 404404 说明 nginx+GoFrame 存活,
# 真正故障(后端进程挂掉)时 nginx 会返回 502故把 404 纳入可接受状态码。
set -e
echo "===== update monitor #2 ====="
sudo python3 - <<'PY'
import sqlite3
con = sqlite3.connect("/data/uptime-kuma/kuma.db", timeout=30)
cur = con.cursor()
cur.execute(
"update monitor set accepted_statuscodes_json = ?, description = ? where id = 2",
('["200-399","404"]', "Go 后端服务无根路由404 即存活502/超时才算故障)"),
)
con.commit()
row = cur.execute("select id, name, accepted_statuscodes_json from monitor where id = 2").fetchone()
print("updated:", row[0], row[2])
con.close()
PY
echo "===== restart to reload ====="
sudo docker restart uptime-kuma >/dev/null
sleep 55
echo "===== final status ====="
sudo python3 - <<'PY'
import sqlite3
con = sqlite3.connect("file:/data/uptime-kuma/kuma.db?mode=ro", uri=True)
label = {0: "DOWN", 1: "UP", 2: "PENDING", 3: "MAINTENANCE"}
q = """
select m.id, m.name, m.interval, m.accepted_statuscodes_json, h.status, h.msg
from monitor m
left join heartbeat h on h.id = (select max(id) from heartbeat where monitor_id = m.id)
order by m.weight, m.id
"""
for mid, name, interval, codes, status, msg in con.execute(q):
print(" id=%s %-4ss [%-11s] %-34s %s" % (mid, interval, label.get(status, status), name[:34], (msg or "")[:40]))
print()
print("heartbeat total:", con.execute("select count(*) from heartbeat").fetchone()[0])
PY
sudo docker ps --filter name=uptime-kuma --format '{{.Names}} {{.Status}}'
echo DONE

View File

@ -0,0 +1,30 @@
#!/bin/bash
# 读取 Uptime Kuma 数据库结构(只读),评估批量添加监控的可行路径
python3 - <<'PY'
import sqlite3
con = sqlite3.connect("file:/data/uptime-kuma/kuma.db?mode=ro", uri=True)
cur = con.cursor()
print("===== all tables =====")
for (n,) in cur.execute("select name from sqlite_master where type='table' order by name"):
print(n)
for name in ("user", "monitor", "monitor_notification", "monitor_tag", "tag", "notification"):
r = cur.execute("select sql from sqlite_master where type='table' and name=?", (name,)).fetchone()
print("\n===== schema: %s =====" % name)
print(r[0] if r else "NOT FOUND")
print("\n===== users =====")
try:
for row in cur.execute("select id, username, active from user"):
print(row)
except Exception as e:
print("err:", e)
print("\n===== monitor count =====")
print(cur.execute("select count(*) from monitor").fetchone())
print("\n===== monitor columns (pragma) =====")
for row in cur.execute("pragma table_info(monitor)"):
print(row)
PY

View File

@ -0,0 +1,18 @@
#!/bin/bash
# 探测 service.xpcool.com 可用的健康检查路径
echo "=== service nginx conf ==="
cat /data/nginx/conf.d/service.xpcool.com.conf
echo ""
echo "=== path probe (via local nginx with Host header) ==="
for p in / /health /healthz /api/health /api/service/health /api/service/admin /api/service/open/health /index.html /favicon.ico; do
code=$(curl -s -o /dev/null -w "%{http_code}" -k -H "Host: service.xpcool.com" "https://127.0.0.1$p")
echo " $p -> $code"
done
echo ""
echo "=== direct backend probe (127.0.0.1:10100) ==="
for p in / /health /api/health; do
code=$(curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:10100$p")
echo " $p -> $code"
done

View File

@ -0,0 +1,52 @@
#!/bin/bash
# git.xpcool.com 证书更新2026-09-13
# 自适应:从 nginx 配置里读出实际引用的证书路径,覆盖该路径,避免文件名不一致踩空
set -e
DOMAIN=git.xpcool.com
CONF=/data/nginx/conf.d/$DOMAIN.conf
echo "=== conf ssl lines ==="
grep -n "listen\|server_name\|ssl_certificate" "$CONF"
echo "=== ssl dir (before) ==="
sudo ls -l /data/nginx/ssl/$DOMAIN/
# 从配置解析真实路径ssl_certificate\s 不会误匹配 ssl_certificate_key
CRT=$(grep -oP "ssl_certificate\s+\K[^;]+" "$CONF" | head -1 | tr -d ' \r')
KEY=$(grep -oP "ssl_certificate_key\s+\K[^;]+" "$CONF" | head -1 | tr -d ' \r')
echo "target crt = $CRT"
echo "target key = $KEY"
echo "=== old cert ==="
sudo openssl x509 -in "$CRT" -noout -subject -enddate
echo -n "old sha="; sudo sha256sum "$CRT" | cut -c1-16
# 备份旧证书(可回滚)
STAMP=$(date +%Y%m%d%H%M)
sudo cp -a "$CRT" "${CRT}.bak.$STAMP"
sudo cp -a "$KEY" "${KEY}.bak.$STAMP"
echo "backup -> ${CRT}.bak.$STAMP"
# 覆盖为新证书
sudo cp /tmp/${DOMAIN}_bundle.crt "$CRT"
sudo cp /tmp/${DOMAIN}.key "$KEY"
sudo chmod 644 "$CRT"
sudo chmod 600 "$KEY"
echo "=== key/cert match check ==="
c=$(sudo openssl x509 -in "$CRT" -noout -pubkey | openssl md5)
k=$(sudo openssl pkey -in "$KEY" -pubout 2>/dev/null | openssl md5)
echo "cert=$c"
echo "key =$k"
if [ "$c" = "$k" ]; then echo "MATCH_OK"; else echo "MATCH_FAIL"; exit 1; fi
echo "=== new cert ==="
sudo openssl x509 -in "$CRT" -noout -subject -enddate
sudo nginx -t
sudo systemctl reload nginx
echo "=== verify served cert ==="
echo | openssl s_client -connect 127.0.0.1:443 -servername $DOMAIN 2>/dev/null | openssl x509 -noout -subject -enddate
echo "DONE"

View File

@ -0,0 +1,28 @@
#!/bin/bash
# 复核监控状态:状态码含义 0=DOWN 1=UP 2=PENDING 3=MAINTENANCE
sleep 25
sudo python3 - <<'PY'
import sqlite3
con = sqlite3.connect("file:/data/uptime-kuma/kuma.db?mode=ro", uri=True)
print("=== name encoding check (chars, first bytes hex) ===")
for row in con.execute("select id, name, length(name), hex(substr(name,1,4)) from monitor order by id limit 3"):
print(" ", row)
print()
print("=== latest status per monitor ===")
q = """
select m.id, m.name, h.status, h.time, h.msg
from monitor m
left join heartbeat h on h.id = (select max(id) from heartbeat where monitor_id = m.id)
order by m.weight, m.id
"""
label = {0: "DOWN", 1: "UP", 2: "PENDING", 3: "MAINTENANCE"}
for mid, name, status, time, msg in con.execute(q):
st = label.get(status, status)
print(" id=%s [%-11s] %s (%s) %s" % (mid, st, name, time, (msg or "")[:60]))
print()
print("heartbeat total:", con.execute("select count(*) from heartbeat").fetchone()[0])
print("tls_info rows:", con.execute("select count(*) from monitor_tls_info").fetchone()[0])
PY