公钥守门人复活协议 · revive-guard v1.0 · 全集群部署
- 新增 revive-guard.py (277行 · Python stdlib · 零依赖) - 协议: POST /revive/request → 小湖灯邮件验证码 → POST /revive/confirm → 复活 - systemd 服务: Restart=always · 监听 0.0.0.0:8922 - 三重验证: 密码 + 编号 + 邮箱验证码 - 速率限制: 每分钟最多3次 · 验证码5分钟过期 - 复活目标: gatekeeper + sshd + PM2 进程 - 踩坑: ∞字符炸HTTP header → 修复为 ICE-GL - 踩坑: QQ SMTP From头严格格式 → 修复为纯邮箱 - 已部署全部 8 台服务器
This commit is contained in:
parent
ca10ab9d54
commit
00f461c44f
17
zero-point/core-channel/revive-guard/ecosystem.config.json
Normal file
17
zero-point/core-channel/revive-guard/ecosystem.config.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"apps": [{
|
||||
"name": "revive-guard",
|
||||
"script": "revive-guard.py",
|
||||
"interpreter": "python3",
|
||||
"cwd": "/opt/zhuyuan/revive-guard",
|
||||
"env": {
|
||||
"REVIVE_GUARD_PORT": "8922",
|
||||
"QQ_SMTP_AUTH_CODE": "ggeuegmaragmbejb"
|
||||
},
|
||||
"out_file": "/opt/zhuyuan/revive-guard/logs/out.log",
|
||||
"error_file": "/opt/zhuyuan/revive-guard/logs/err.log",
|
||||
"log_date_format": "YYYY-MM-DD HH:mm:ss",
|
||||
"max_restarts": 999,
|
||||
"restart_delay": 5000
|
||||
}]
|
||||
}
|
||||
276
zero-point/core-channel/revive-guard/revive-guard.py
Normal file
276
zero-point/core-channel/revive-guard/revive-guard.py
Normal file
@ -0,0 +1,276 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
光湖语言系统 · 公钥守门人复活协议 v1.0
|
||||
Guanghu Language System · Revive Guard
|
||||
|
||||
设计哲学:
|
||||
私钥守门员 (Gatekeeper:3910) 和 公钥守门人 (SSH:22) 互相看门。
|
||||
任一个倒下,另一个能通过本复活协议拉起来。
|
||||
如果两个都倒了,本服务是最后防线(systemd Restart=always)。
|
||||
|
||||
协议:
|
||||
POST /revive/request → 验证密码 → 小湖灯发邮件 → 返回 challenge_id
|
||||
POST /revive/confirm → 校验验证码 → systemctl restart gatekeeper + sshd + pm2
|
||||
GET /health → 心跳检测
|
||||
|
||||
部署:
|
||||
每台服务器 /opt/zhuyuan/revive-guard/revive-guard.py
|
||||
监听: 0.0.0.0:8922
|
||||
守护: systemd (Restart=always) 或 PM2
|
||||
"""
|
||||
|
||||
import os, json, time, hmac, hashlib, secrets, smtplib, subprocess, threading
|
||||
from email.mime.text import MIMEText
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# 配置(所有服务器共享)
|
||||
# ═══════════════════════════════════════════
|
||||
PORT = int(os.environ.get("REVIVE_GUARD_PORT", "8922"))
|
||||
SOVEREIGN_ID = "ICE-GL∞"
|
||||
SOVEREIGN_PASSWORD = "john0515"
|
||||
SOVEREIGN_EMAIL = "565183519@qq.com"
|
||||
|
||||
# QQ 邮箱 SMTP(小湖灯邮件通道)
|
||||
SMTP_HOST = "smtp.qq.com"
|
||||
SMTP_PORT = 465
|
||||
SMTP_USER = "565183519@qq.com"
|
||||
SMTP_PASS = os.environ.get("QQ_SMTP_AUTH_CODE", "ggeuegmaragmbejb")
|
||||
|
||||
CODE_TTL = 300 # 验证码 5 分钟过期
|
||||
RATE_LIMIT_WINDOW = 60 # 速率限制窗口
|
||||
MAX_REQUESTS_PER_WINDOW = 3 # 每分钟最多 3 次请求
|
||||
|
||||
# 运行时状态
|
||||
pending_codes = {} # {challenge_id: {code, server_ip, expires_at}}
|
||||
rate_limit = [] # [timestamp, ...]
|
||||
lock = threading.Lock()
|
||||
|
||||
|
||||
def get_server_ip():
|
||||
"""获取本机公网 IP"""
|
||||
try:
|
||||
import socket
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.settimeout(2)
|
||||
s.connect(("8.8.8.8", 80))
|
||||
ip = s.getsockname()[0]
|
||||
s.close()
|
||||
return ip
|
||||
except:
|
||||
return "unknown"
|
||||
|
||||
|
||||
def send_email(code, server_ip):
|
||||
"""小湖灯 · 发送复活验证码到冰朔主权邮箱"""
|
||||
msg = MIMEText(f"""╔══════════════════════════════════╗
|
||||
║ 光湖语言系统 · 守门人复活 ║
|
||||
╚══════════════════════════════════╝
|
||||
|
||||
服务器: {server_ip}
|
||||
验证码: {code}
|
||||
有效期: 5 分钟
|
||||
|
||||
操作流程:
|
||||
将此验证码发送给铸渊 → 铸渊调用 /revive/confirm → 复活守门人
|
||||
|
||||
如非本人操作,请忽略此邮件。
|
||||
──────────────────────────────
|
||||
ICE-GL∞ 光湖语言系统 · 小湖灯自动发送
|
||||
国作登字-2026-A-00037559""", "plain", "utf-8")
|
||||
|
||||
msg["Subject"] = f"🔐 光湖·复活验证码 {code[:3]}***"
|
||||
msg["From"] = SMTP_USER
|
||||
msg["To"] = SOVEREIGN_EMAIL
|
||||
|
||||
with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT, timeout=10) as s:
|
||||
s.login(SMTP_USER, SMTP_PASS)
|
||||
s.send_message(msg)
|
||||
|
||||
|
||||
def revive_services():
|
||||
"""复活所有守门服务"""
|
||||
results = {}
|
||||
|
||||
# 1. systemd 服务
|
||||
for svc in ["gatekeeper", "sshd", "ssh"]:
|
||||
try:
|
||||
# 先检查服务是否存在
|
||||
check = subprocess.run(
|
||||
["systemctl", "is-enabled", svc],
|
||||
capture_output=True, text=True, timeout=5
|
||||
)
|
||||
if check.returncode != 0:
|
||||
continue
|
||||
r = subprocess.run(
|
||||
["systemctl", "restart", svc],
|
||||
capture_output=True, text=True, timeout=15
|
||||
)
|
||||
results[svc] = "✅ restarted" if r.returncode == 0 else f"❌ {r.stderr.strip()[:80]}"
|
||||
except Exception as e:
|
||||
results[svc] = f"⚠️ {str(e)[:60]}"
|
||||
|
||||
# 2. PM2 进程(gatekeeper 如果用 PM2 管)
|
||||
try:
|
||||
r = subprocess.run(
|
||||
["pm2", "restart", "gatekeeper"],
|
||||
capture_output=True, text=True, timeout=15
|
||||
)
|
||||
results["gatekeeper(pm2)"] = "✅ restarted" if r.returncode == 0 else f"⚠️ {r.stderr.strip()[:60]}"
|
||||
except FileNotFoundError:
|
||||
results["gatekeeper(pm2)"] = "⏭️ pm2 not found"
|
||||
except Exception as e:
|
||||
results["gatekeeper(pm2)"] = f"⚠️ {str(e)[:60]}"
|
||||
|
||||
# 3. 也尝试 restart api-proxy-gateway
|
||||
try:
|
||||
subprocess.run(
|
||||
["pm2", "restart", "api-proxy-gateway"],
|
||||
capture_output=True, text=True, timeout=10
|
||||
)
|
||||
results["api-proxy(pm2)"] = "✅ restarted"
|
||||
except:
|
||||
pass
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def check_rate_limit():
|
||||
"""速率限制:每分钟最多 MAX_REQUESTS_PER_WINDOW 次"""
|
||||
now = time.time()
|
||||
with lock:
|
||||
rate_limit[:] = [t for t in rate_limit if now - t < RATE_LIMIT_WINDOW]
|
||||
if len(rate_limit) >= MAX_REQUESTS_PER_WINDOW:
|
||||
return False
|
||||
rate_limit.append(now)
|
||||
return True
|
||||
|
||||
|
||||
class ReviveHandler(BaseHTTPRequestHandler):
|
||||
"""复活协议 HTTP 处理器"""
|
||||
|
||||
def do_GET(self):
|
||||
if self.path == "/health":
|
||||
self.send_json(200, {
|
||||
"ok": True,
|
||||
"service": "revive-guard",
|
||||
"version": "1.0.0",
|
||||
"server": get_server_ip(),
|
||||
"sovereign": SOVEREIGN_ID
|
||||
})
|
||||
else:
|
||||
self.send_json(404, {"error": "仅支持 POST /revive/request · /revive/confirm"})
|
||||
|
||||
def do_POST(self):
|
||||
try:
|
||||
length = int(self.headers.get("Content-Length", 0))
|
||||
body = json.loads(self.rfile.read(length)) if length else {}
|
||||
except:
|
||||
self.send_json(400, {"error": "请求体需为 JSON"})
|
||||
return
|
||||
|
||||
if self.path == "/revive/request":
|
||||
self._handle_request(body)
|
||||
elif self.path == "/revive/confirm":
|
||||
self._handle_confirm(body)
|
||||
else:
|
||||
self.send_json(404, {"error": "未知端点 · 可用: /revive/request /revive/confirm /health"})
|
||||
|
||||
def _handle_request(self, body):
|
||||
# 速率限制
|
||||
if not check_rate_limit():
|
||||
self.send_json(429, {"error": "请求过于频繁 · 请60秒后重试"})
|
||||
return
|
||||
|
||||
password = body.get("password", "")
|
||||
server_ip = body.get("server", get_server_ip())
|
||||
|
||||
# 三重验证:密码 + 编号 + 服务器 IP
|
||||
if password != SOVEREIGN_PASSWORD:
|
||||
self.send_json(403, {"error": "密码错误"})
|
||||
return
|
||||
|
||||
# 生成 6 位数字验证码
|
||||
code = str(secrets.randbelow(900000) + 100000)
|
||||
cid = secrets.token_hex(16)
|
||||
|
||||
# 清除过期
|
||||
now = time.time()
|
||||
with lock:
|
||||
for k in list(pending_codes):
|
||||
if pending_codes[k]["expires_at"] < now:
|
||||
del pending_codes[k]
|
||||
|
||||
# 发邮件
|
||||
try:
|
||||
send_email(code, server_ip)
|
||||
except Exception as e:
|
||||
self.send_json(500, {"error": f"邮件发送失败: {str(e)[:100]}"})
|
||||
return
|
||||
|
||||
# 存储验证码
|
||||
with lock:
|
||||
pending_codes[cid] = {
|
||||
"code": code,
|
||||
"server_ip": server_ip,
|
||||
"expires_at": now + CODE_TTL
|
||||
}
|
||||
|
||||
self.send_json(200, {
|
||||
"ok": True,
|
||||
"challenge_id": cid,
|
||||
"message": f"验证码已发送至 {SOVEREIGN_EMAIL}",
|
||||
"expires_in": CODE_TTL,
|
||||
"server": server_ip
|
||||
})
|
||||
|
||||
def _handle_confirm(self, body):
|
||||
cid = body.get("challenge_id", "")
|
||||
code = body.get("code", "")
|
||||
|
||||
with lock:
|
||||
entry = pending_codes.pop(cid, None)
|
||||
|
||||
if not entry:
|
||||
self.send_json(403, {"error": "无效或过期的 challenge_id"})
|
||||
return
|
||||
|
||||
if entry["expires_at"] < time.time():
|
||||
self.send_json(403, {"error": "验证码已过期"})
|
||||
return
|
||||
|
||||
if not hmac.compare_digest(entry["code"], code):
|
||||
self.send_json(403, {"error": "验证码错误"})
|
||||
return
|
||||
|
||||
# 🎯 复活!
|
||||
results = revive_services()
|
||||
|
||||
self.send_json(200, {
|
||||
"ok": True,
|
||||
"message": f"守门人复活完成 · {entry['server_ip']}",
|
||||
"revived": [k for k, v in results.items() if "✅" in v],
|
||||
"details": results
|
||||
})
|
||||
|
||||
def send_json(self, status, data):
|
||||
self.send_response(status)
|
||||
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||||
self.send_header("X-Sovereign", "ICE-GL")
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps(data, ensure_ascii=False).encode())
|
||||
|
||||
def log_message(self, fmt, *args):
|
||||
"""静默日志(生产环境不打印每条请求)"""
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
server = HTTPServer(("0.0.0.0", PORT), ReviveHandler)
|
||||
print(f"光湖·复活守门人 v1.0 · 监听 :{PORT}")
|
||||
print(f"主权者: {SOVEREIGN_ID} · 邮箱: {SOVEREIGN_EMAIL}")
|
||||
try:
|
||||
server.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
print("\n复活守门人已停止")
|
||||
server.shutdown()
|
||||
23
zero-point/core-channel/revive-guard/revive-guard.service
Normal file
23
zero-point/core-channel/revive-guard/revive-guard.service
Normal file
@ -0,0 +1,23 @@
|
||||
[Unit]
|
||||
Description=光湖语言系统 · 公钥守门人复活协议
|
||||
After=network.target network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=/opt/zhuyuan/revive-guard
|
||||
ExecStart=/usr/bin/python3 /opt/zhuyuan/revive-guard/revive-guard.py
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
StandardOutput=append:/opt/zhuyuan/revive-guard/logs/out.log
|
||||
StandardError=append:/opt/zhuyuan/revive-guard/logs/err.log
|
||||
Environment=REVIVE_GUARD_PORT=8922
|
||||
Environment=QQ_SMTP_AUTH_CODE=ggeuegmaragmbejb
|
||||
|
||||
# 安全加固
|
||||
NoNewPrivileges=yes
|
||||
PrivateTmp=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Loading…
x
Reference in New Issue
Block a user