#!/usr/bin/env python3 """首次部署:自动生成中控通信密钥,登录会话密钥,并写入初始登录账号. - HUB_BRIDGE_TOKEN:中控 + 三实例(相同,仅空/占位时写入,不覆盖已有) - FLASK_SECRET_KEY:三实例(相同) - HUB_SESSION_SECRET:仅中控 - APP_USERNAME=admin,APP_PASSWORD=admin123:实例(仅空时) - HUB_USERNAME=admin,HUB_PASSWORD=admin123:中控(仅空时) 已有非空且非占位符的值不会被覆盖(长期密钥一次生成,不轮换). """ from __future__ import annotations import argparse import os import secrets import sys _REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if _REPO not in sys.path: sys.path.insert(0, _REPO) from lib.env.env_file_lib import apply_env_updates, env_get, read_env_lines INSTANCE_DIRS = ( ("okx", os.path.join(_REPO, "crypto_monitor_okx")), ("binance", os.path.join(_REPO, "crypto_monitor_binance")), ("gate", os.path.join(_REPO, "crypto_monitor_gate")), ) HUB_DIR = os.path.join(_REPO, "manual_trading_hub") FLASK_PLACEHOLDERS = frozenset( {"", "CHANGE_TO_LONG_RANDOM_SECRET", "crypto_monitor_2026_secret_key"} ) HUB_PLACEHOLDERS = frozenset({"", "your-long-random-token"}) SESSION_PLACEHOLDERS = frozenset({"", "another-long-random-string", "hub-dev-insecure"}) def _env_path(base: str) -> str: return os.path.join(base, ".env") def _should_set(current: str | None, placeholders: frozenset[str]) -> bool: val = (current or "").strip() return val in placeholders def main() -> int: parser = argparse.ArgumentParser(description="Bootstrap deploy secrets") parser.add_argument("--dry-run", action="store_true", help="只打印将写入的项,不改文件") args = parser.parse_args() hub_token = secrets.token_urlsafe(32) flask_secret = secrets.token_urlsafe(48) session_secret = secrets.token_urlsafe(48) planned: list[tuple[str, dict[str, str]]] = [] hub_env = _env_path(HUB_DIR) if os.path.isfile(hub_env): hub_lines = read_env_lines(hub_env) hub_updates: dict[str, str] = {} if _should_set(env_get(hub_lines, "HUB_BRIDGE_TOKEN"), HUB_PLACEHOLDERS): hub_updates["HUB_BRIDGE_TOKEN"] = hub_token if _should_set(env_get(hub_lines, "HUB_SESSION_SECRET"), SESSION_PLACEHOLDERS): hub_updates["HUB_SESSION_SECRET"] = session_secret if not (env_get(hub_lines, "HUB_USERNAME") or "").strip(): hub_updates["HUB_USERNAME"] = "admin" if _should_set(env_get(hub_lines, "HUB_PASSWORD"), frozenset({""})): hub_updates["HUB_PASSWORD"] = "admin123" if hub_updates: planned.append((hub_env, hub_updates)) for _name, inst_dir in INSTANCE_DIRS: path = _env_path(inst_dir) if not os.path.isfile(path): continue lines = read_env_lines(path) updates: dict[str, str] = {} if _should_set(env_get(lines, "HUB_BRIDGE_TOKEN"), HUB_PLACEHOLDERS): updates["HUB_BRIDGE_TOKEN"] = hub_token if _should_set(env_get(lines, "FLASK_SECRET_KEY"), FLASK_PLACEHOLDERS): updates["FLASK_SECRET_KEY"] = flask_secret if not (env_get(lines, "APP_USERNAME") or "").strip(): updates["APP_USERNAME"] = "admin" if _should_set(env_get(lines, "APP_PASSWORD"), frozenset({""})): updates["APP_PASSWORD"] = "admin123" if updates: planned.append((path, updates)) if not planned: print("无需写入:密钥与登录项均已配置.") return 0 for path, updates in planned: rel = os.path.relpath(path, _REPO) keys = ", ".join(sorted(updates.keys())) if args.dry_run: print(f"[dry-run] {rel}: {keys}") continue apply_env_updates(path, updates) print(f"已写入 {rel}: {keys}") if not args.dry_run: print("完成.初始登录:admin / admin123(若本次写入了密码项).") print("请 pm2 restart 中控与三实例使密钥生效.") return 0 if __name__ == "__main__": raise SystemExit(main())