feat: 同步本地三端改动并清理上传凭证风险

整合小程序、管理端与后端的最新本地改动,补齐用户管理与首页入口相关能力;提交前已完成敏感信息扫描,并移除本地 gitea 远程 URL 中的明文凭证,避免隐私信息进入远程仓库。

Made-with: Cursor
This commit is contained in:
卡若
2026-04-06 15:59:34 +08:00
parent b1cc4be1c2
commit 6d11fb295d
126 changed files with 5789 additions and 2791 deletions

View File

@@ -0,0 +1,183 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
kr 宝塔:正式环境发布 + 宝塔面板 API 重启 Go 项目 + 线上冒烟验证。
说明(与现状一致):
- soul-api 的**文件上传与解压**仍通过 SSH/SFTP稳定**进程重启**使用宝塔「Go 项目」插件 API与 master.py --restart-method btapi 一致)。
- soul-admin 静态资源仍通过 soul-admin/master.pySSH宝塔无统一「站点文件 API」封装。
- 环境变量与 soul-api/master.py 相同DEPLOY_HOST、DEPLOY_PASSWORD、BT_PANEL_URL、BT_API_KEY、BT_GO_PROJECT_NAME 等。
用法:
python3 scripts/deploy_kr_btapi_verify.py # 部署 API+管理端 + 验证
python3 scripts/deploy_kr_btapi_verify.py --verify-only # 仅公网冒烟(不部署)
python3 scripts/deploy_kr_btapi_verify.py --skip-bt-ping # 跳过宝塔 API 探活
"""
from __future__ import print_function
import argparse
import hashlib
import os
import subprocess
import sys
import time
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
SOUL_API = os.path.join(ROOT, "soul-api")
SOUL_ADMIN = os.path.join(ROOT, "soul-admin")
def _load_api_cfg():
import importlib.util
path = os.path.join(SOUL_API, "master.py")
spec = importlib.util.spec_from_file_location("soul_api_master_mod", path)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod.get_cfg()
def bt_panel_ping(cfg):
"""调用宝塔 /system?action=GetDiskInfo 校验 API 密钥与面板可达性。"""
try:
import requests
try:
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
except Exception:
pass
except ImportError:
print("[失败] 需要 requestspip install requests")
return False
url = (cfg.get("bt_panel_url") or "").rstrip("/")
key = cfg.get("bt_api_key") or ""
if not url or not key:
print("[失败] 缺少 BT_PANEL_URL 或 BT_API_KEY")
return False
req_time = int(time.time())
sk_md5 = hashlib.md5(key.encode()).hexdigest()
req_token = hashlib.md5(("%s%s" % (req_time, sk_md5)).encode()).hexdigest()
try:
r = requests.post(
url + "/system?action=GetDiskInfo",
data={"request_time": req_time, "request_token": req_token},
timeout=20,
verify=False,
)
if r.status_code != 200:
print("[宝塔API 探活] HTTP %s" % r.status_code)
return False
j = r.json() if "application/json" in (r.headers.get("content-type") or "") else {}
# 部分面板返回 data 为列表或非标准结构,仅当明确 status=False 视为失败
if isinstance(j, dict) and j.get("status") is False:
print("[宝塔API 探活] %s" % (j.get("msg") or j))
return False
print("[宝塔API 探活] 成功GetDiskInfo", flush=True)
return True
except Exception as e:
print("[宝塔API 探活] 异常: %s" % e)
return False
def run_cmd(cwd, args, env=None):
print("", flush=True)
print(">>> %s" % " ".join(args), flush=True)
e = os.environ.copy()
if env:
e.update(env)
r = subprocess.run(args, cwd=cwd, env=e)
return r.returncode == 0
def verify_public(base_api=None, base_admin=None):
"""公网冒烟:健康检查 + 只读业务接口 + 管理端首页。"""
try:
import requests
try:
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
except Exception:
pass
except ImportError:
print("[失败] 验证阶段需要 requests")
return False
base_api = (base_api or os.environ.get("VERIFY_API_BASE", "https://soulapi.quwanzhi.com")).rstrip("/")
base_admin = (base_admin or os.environ.get("VERIFY_ADMIN_BASE", "https://souladmin.quwanzhi.com")).rstrip("/")
checks = [
("GET %s/health" % base_api, "%s/health" % base_api, lambda r: r.status_code == 200 and '"status"' in r.text),
("GET %s/api/book/parts" % base_api, "%s/api/book/parts" % base_api, lambda r: r.status_code == 200),
("GET %s/api/config" % base_api, "%s/api/config" % base_api, lambda r: r.status_code == 200),
("GET %s/" % base_admin, "%s/" % base_admin, lambda r: r.status_code == 200 and "root" in r.text),
]
print("")
print("=" * 60)
print(" 线上冒烟验证")
print("=" * 60)
all_ok = True
for name, url, pred in checks:
try:
r = requests.get(url, timeout=20, verify=True)
ok = pred(r)
print(" [%s] %s" % ("OK" if ok else "FAIL", name))
if not ok:
print(" status=%s len=%s" % (r.status_code, len(r.text or "")))
all_ok = False
except Exception as ex:
print(" [FAIL] %s%s" % (name, ex))
all_ok = False
return all_ok
def main():
parser = argparse.ArgumentParser(description="kr 宝塔BT API 重启 + 部署 + 验证")
parser.add_argument("--verify-only", action="store_true", help="仅执行公网冒烟,不部署")
parser.add_argument("--skip-bt-ping", action="store_true", help="跳过宝塔面板 API 探活")
parser.add_argument(
"--restart-method",
choices=("btapi", "auto"),
default="auto",
help="soul-api master.py 的 --restart-method默认先宝塔 Go 插件 API失败再 SSH",
)
args = parser.parse_args()
cfg = _load_api_cfg()
if not args.verify_only and not args.skip_bt_ping:
if not bt_panel_ping(cfg):
print("[失败] 宝塔 API 探活未通过,已中止(可用 --skip-bt-ping 跳过)")
return 1
if args.verify_only:
return 0 if verify_public() else 1
py = sys.executable
if not run_cmd(SOUL_API, [py, "master.py", "--restart-method", args.restart_method]):
print("[失败] soul-api 部署失败")
return 1
if not run_cmd(SOUL_ADMIN, [py, "master.py"]):
print("[失败] soul-admin 部署失败")
return 1
time.sleep(2)
if not verify_public():
print("")
print("[失败] 线上冒烟存在未通过项(部署已执行,请排查 Nginx/证书/业务)")
return 1
print("")
print(" 全部完成:已部署且冒烟通过。")
return 0
if __name__ == "__main__":
sys.exit(main() or 0)