From a4a9634ba830b97ea6da33b08f8ca574052a9c9f Mon Sep 17 00:00:00 2001 From: dekun Date: Thu, 1 Oct 2026 11:16:20 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=AD=E6=8E=A7=20Bridge=20Token=20=E4=B8=BA?= =?UTF-8?q?=E7=A9=BA=E6=97=B6=E8=87=AA=E5=8A=A8=E7=94=9F=E6=88=90=EF=BC=8C?= =?UTF-8?q?Origin=20=E6=94=B9=E4=B8=BA=E9=80=89=E5=A1=AB=E8=AF=B4=E6=98=8E?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 打开 env 配置或首次部署即可写入 Token,便于复制到中控登记。 Co-authored-by: Cursor --- .env.example | 7 +++---- lib/env/env_ui_manifest.py | 11 +++++++++-- lib/hub/hub_bridge_auth.py | 26 ++++++++++++++++++++++++++ scripts/bootstrap_deploy_secrets.py | 10 +++++++++- 4 files changed, 47 insertions(+), 7 deletions(-) diff --git a/.env.example b/.env.example index 8434e33..e397116 100644 --- a/.env.example +++ b/.env.example @@ -276,10 +276,9 @@ APP_TIMEZONE=Asia/Shanghai # TRADING_DAY_RESET_HOUR 现在表示「北京时间」整点,默认 8 点起算新交易日;开仓整点限制见 TRADING_DAY_RESET_OPEN_GUARD_ENABLED # ============================================================================= -# 中控对接(crypto_okx_hub 拉取持仓 / SSO 打开;也可在「系统设置 → env 配置」改) +# 中控对接(crypto_okx_hub;也可在「系统设置 → env 配置 → 中控对接」查看) # ============================================================================= -# 与中控登记的 Token 一致;留空则 /api/hub/* 拒绝访问 +# 首次部署/打开 env 配置时若为空会自动生成;复制到中控「添加实例」的 Bridge Token HUB_BRIDGE_TOKEN= -# 允许嵌入本站的中控 Origin(空格分隔);例: http://192.168.1.10:5100 https://hub.example.com -# 设为 * 允许任意父页(仅内网调试) +# 选填。允许嵌入本站的中控 Origin(空格分隔);一般留空即可;iframe 被挡时再填,调试可 * HUB_EMBED_FRAME_ANCESTORS= diff --git a/lib/env/env_ui_manifest.py b/lib/env/env_ui_manifest.py index beceb3e..d0c6c4f 100644 --- a/lib/env/env_ui_manifest.py +++ b/lib/env/env_ui_manifest.py @@ -64,12 +64,12 @@ _SHARED_SECTIONS: list[dict[str, Any]] = [ ( "HUB_BRIDGE_TOKEN", "中控 Bridge Token", - "与 crypto_okx_hub「添加实例」里的 Bridge Token 一致;可自拟长随机串,两边填相同值;留空则中控无法拉持仓", + "首次打开本页自动生成;复制到 crypto_okx_hub「添加实例」即可;也可手动改后保存", ), ( "HUB_EMBED_FRAME_ANCESTORS", "允许嵌入的中控 Origin", - "空格分隔,例: http://中控IP:5100 https://hub.example.com;调试可填 *", + "选填。一般可留空;仅当中控 iframe 打不开时再填中控地址(空格分隔),或调试填 *", ), ], }, @@ -279,6 +279,13 @@ def build_env_ui_payload( example_path: str, env_path: str, ) -> list[dict[str, Any]]: + # 首次打开 env 配置时若 Token 为空则自动生成,便于复制到中控 + try: + from lib.hub.hub_bridge_auth import ensure_hub_bridge_token + + ensure_hub_bridge_token(env_path) + except Exception: + pass schema = _schema_field_map(example_path) env_lines = read_env_lines(env_path) values = env_get_all(env_lines) diff --git a/lib/hub/hub_bridge_auth.py b/lib/hub/hub_bridge_auth.py index 493d53e..7748dbe 100644 --- a/lib/hub/hub_bridge_auth.py +++ b/lib/hub/hub_bridge_auth.py @@ -4,6 +4,7 @@ from __future__ import annotations import hashlib import hmac import os +import secrets import time from typing import Optional from urllib.parse import urlencode @@ -94,3 +95,28 @@ def frame_ancestors_header_value() -> Optional[str]: return "*" parts = [p.strip() for p in raw.split() if p.strip()] return " ".join(parts) if parts else None + + +def ensure_hub_bridge_token(env_path: str) -> str: + """若 .env 中 HUB_BRIDGE_TOKEN 为空则生成并写入;返回当前 token.""" + from lib.env.env_file_lib import apply_env_updates, env_get, read_env_lines + + path = (env_path or "").strip() + if not path or not os.path.isfile(path): + existing = hub_bridge_token() + if existing: + return existing + token = secrets.token_urlsafe(32) + os.environ["HUB_BRIDGE_TOKEN"] = token + return token + + lines = read_env_lines(path) + cur = (env_get(lines, "HUB_BRIDGE_TOKEN") or "").strip() + if cur: + os.environ["HUB_BRIDGE_TOKEN"] = cur + return cur + + token = secrets.token_urlsafe(32) + apply_env_updates(path, {"HUB_BRIDGE_TOKEN": token}) + os.environ["HUB_BRIDGE_TOKEN"] = token + return token diff --git a/scripts/bootstrap_deploy_secrets.py b/scripts/bootstrap_deploy_secrets.py index 2a8417a..fb30501 100644 --- a/scripts/bootstrap_deploy_secrets.py +++ b/scripts/bootstrap_deploy_secrets.py @@ -3,6 +3,7 @@ - FLASK_SECRET_KEY - APP_USERNAME=admin / APP_PASSWORD=admin123(仅空时) +- HUB_BRIDGE_TOKEN(仅空时,供中控对接) """ from __future__ import annotations @@ -47,6 +48,9 @@ def main() -> int: if (cur_pass or "").strip() in PASSWORD_PLACEHOLDERS: updates["APP_PASSWORD"] = "admin123" + if not (env_get(lines, "HUB_BRIDGE_TOKEN") or "").strip(): + updates["HUB_BRIDGE_TOKEN"] = secrets.token_urlsafe(32) + if not updates: print(" 密钥已存在,无需写入") return 0 @@ -54,7 +58,11 @@ def main() -> int: print(f" 将更新 {env_path}: {', '.join(updates.keys())}") if args.dry_run: for k, v in updates.items(): - shown = v if k != "APP_PASSWORD" and "SECRET" not in k else "***" + shown = ( + "***" + if k in ("APP_PASSWORD", "HUB_BRIDGE_TOKEN") or "SECRET" in k + else v + ) print(f" {k}={shown}") return 0