Files

1 line
25 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "cht21-c/power-rag", "files": {"camera_sdk_agent/config.py": {"be6685690b6957a064116d162b9b4e4f7d6c1f57": {"t": 1785745451.102887, "n": 8134, "h": "0a821343e7c6891c", "c": "\"\"\"\nCamera SDK RAG Agent - Configuration Module (等保三级改造)\n\n变更:\n- 新增 _decrypt_env() 辅助函数,支持 Fernet 加密字段\n- 敏感字段(DEEPSEEK_API_KEY、MYSQL_PASSWORD)优先读取 _ENC 后缀加密值\n- 解密使用 MASTER_KEY 环境变量(不落盘)\n- print_config() 和 validate_config() 行为不变\n\"\"\"\n\nimport os\nfrom pathlib import Path\n\n# ---------------------------------------------------------------------------\n# Encryption support (等保三级 - 敏感配置加密存储)\n# ---------------------------------------------------------------------------\n\n_fernet_cache = None\n\n\ndef _get_fernet():\n \"\"\"延迟初始化 Fernet 实例(仅在需要解密时加载)。\"\"\"\n global _fernet_cache\n if _fernet_cache is not None:\n return _fernet_cache\n master_key = os.getenv(\"MASTER_KEY\", \"\")\n if not master_key:\n # 未设置 MASTER_KEY 时返回 None,调用方回退到明文\n return None\n try:\n from cryptography.fernet import Fernet\n _fernet_cache = Fernet(master_key.encode(\"utf-8\"))\n return _fernet_cache\n except ImportError:\n return None\n except Exception:\n return None\n\n\ndef _decrypt_env(env_var: str, default: str = \"\") -> str:\n \"\"\"读取环境变量,自动处理加密值。\n\n 优先级:\n 1. 直接读环境变量(非加密明文)\n 2. 如果为空,读 {env_var}_ENC 后缀的加密值,尝试解密\n 3. 回退到 default\n\n Fernet 加密值的特征是 base64 格式(gAAAAAB 前缀)。\n \"\"\"\n raw = os.getenv(env_var, \"\")\n if raw:\n return raw\n\n encrypted = os.getenv(f\"{env_var}_ENC\", \"\")\n if encrypted:\n f = _get_fernet()\n if f:\n try:\n return f.decrypt(encrypted.encode(\"utf-8\")).decode(\"utf-8\")\n except Exception:\n import warnings\n warnings.warn(f\"Failed to decrypt {env_var}_ENC. Check MASTER_KEY.\", RuntimeWarning)\n return \"\"\n else:\n import warnings\n warnings.warn(\n f\"{env_var}_ENC is set but MASTER_KEY is not. \"\n f\"Install cryptography: pip install cryptography\",\n RuntimeWarning,\n )\n return \"\"\n\n return default\n\n\n# ---------------------------------------------------------------------------\n# .env loading\n# ---------------------------------------------------------------------------\n\ntry:\n from dotenv import load_dotenv\n _env_path = Path(__file__).resolve().parent / \".env\"\n if _env_path.exists():\n load_dotenv(_env_path)\nexcept ImportError:\n pass\n\n# ---------------------------------------------------------------------------\n# Qdrant\n# ---------------------------------------------------------------------------\nQDRANT_HOST = os.getenv(\"QDRANT_HOST\", \"\")\nQDRANT_PORT = int(os.getenv(\"QDRANT_PORT\", \"6333\"))\nQDRANT_LOCAL_PATH = os.getenv(\"QDRANT_LOCAL_PATH\", str(Path(__file__).resolve().parent / \"qdrant_data\"))\nQDRANT_COLLECTION_NAME = os.getenv(\"QDRANT_COLLECTION_NAME\", \"camera_sdk_collection\")\nQDRANT_VECTOR_DIM = int(os.getenv(\"QDRANT_VECTOR_DIM\", \"1024\"))\nQDRANT_DISTANCE_METRIC = os.getenv(\"QDRANT_DISTANCE_METRIC\", \"Cosine\")\n\n# ---------------------------------------------------------------------------\n# Embedding\n# ---------------------------------------------------------------------------\nEMBED_MODEL_NAME = os.getenv(\"EMBED_MODEL_NAME\", \"BAAI/bge-m3\")\nEMBED_DEVICE = os.getenv(\"EMBED_DEVICE\", \"cpu\")\nEMBED_BATCH_SIZE = int(os.getenv(\"EMBED_BATCH_SIZE\", \"32\"))\n\n# ---------------------------------------------------------------------------\n# Chunking\n# ---------------------------------------------------------------------------\nCHUNK_SIZE_TOKENS = int(os.getenv(\"CHUNK_SIZE_TOKENS\", \"512\"))\nCHUNK_OVERLAP_TOKENS = int(os.getenv(\"CHUNK_OVERLAP_TOKENS\", \"64\"))\n\n# ---------------------------------------------------------------------------\n# Retrieval\n# ---------------------------------------------------------------------------\nVECTOR_TOP_K = int(os.getenv(\"VECTOR_TOP_K\", \"20\"))\nBM25_TOP_K = int(os.getenv(\"BM25_TOP_K\", \"20\"))\nFINAL_TOP_K = int(os.getenv(\"FINAL_TOP_K\", \"5\"))\nFUSION_K = int(os.getenv(\"FUSION_K\", \"60\"))\n\n# ---------------------------------------------------------------------------\n# P1 质量加固 —— 可配置阈值\n# ---------------------------------------------------------------------------\n# 幻觉护栏: RRF 融合分数阈值\n# 最高分低于此值 → 直接拒答(不调用 LLM)\nRAG_MIN_SCORE_STRICT = float(os.getenv(\"RAG_MIN_SCORE_STRICT\", \"0.02\"))\n# 最高分在此值和 STRICT 之间 → 生成但标注\"置信度较低\"\nRAG_MIN_SCORE_WARN = float(os.getenv(\"RAG_MIN_SCORE_WARN\", \"0.05\"))\n\n# 意图置信度阈值: LLM 分类 confidence 低于此值 → 反问用户澄清\nINTENT_CONFIDENCE_THRESHOLD = float(os.getenv(\"INTENT_CONFIDENCE_THRESHOLD\", \"0.55\"))\n\n# 图纸语义匹配: 图纸列表超过此数量时分批传入 LLM\nDRAWING_MATCH_MAX_ITEMS = int(os.getenv(\"DRAWING_MATCH_MAX_ITEMS\", \"500\"))\n\n# 二次验证开关: drawing 路由高置信度时是否启用独立复核\nVERIFY_ENABLED = os.getenv(\"VERIFY_ENABLED\", \"true\").lower() in (\"1\", \"true\", \"yes\")\n\n# ---------------------------------------------------------------------------\n# P2 工程健壮性 —— 可配置参数\n# ---------------------------------------------------------------------------\n# 模型路由: 候选模型列表(优先级从高到低)\nMODEL_CANDIDATES = os.getenv(\"MODEL_CANDIDATES\", \"deepseek-chat\").split(\",\")\n\n# 熔断器: 连续失败次数触发熔断\nCIRCUIT_BREAKER_FAIL_THRESHOLD = int(os.getenv(\"CIRCUIT_BREAKER_FAIL_THRESHOLD\", \"5\"))\n\n# 熔断器: 冷却时间(秒),OPEN -> HALF_OPEN\nCIRCUIT_BREAKER_COOLDOWN_SEC = int(os.getenv(\"CIRCUIT_BREAKER_COOLDOWN_SEC\", \"60\"))\n\n# 首包超时(秒),流式调用超过此时间未收到 token 视为失败\nSTREAM_FIRST_TOKEN_TIMEOUT_SEC = float(os.getenv(\"STREAM_FIRST_TOKEN_TIMEOUT_SEC\", \"10\"))\n\n# 记忆压缩: 保留最近 N 轮原文不压缩\nMEMORY_WINDOW_TURNS = int(os.getenv(\"MEMORY_WINDOW_TURNS\", \"3\"))\n\n# 记忆压缩: 总消息超过此 token 估算值触发压缩\nMEMORY_COMPRESS_TRIGGER_TOKENS = int(os.getenv(\"MEMORY_COMPRESS_TRIGGER_TOKENS\", \"3500\"))\n\n# 工具注册: 启用的工具名单(逗号分隔,空=全部启用)\nENABLED_TOOLS = os.getenv(\"ENABLED_TOOLS\", \"\").split(\",\") if os.getenv(\"ENABLED_TOOLS\") else None\n\n# ---------------------------------------------------------------------------\n# P2 结构化内容检测 —— 规则分块配置\n# ---------------------------------------------------------------------------\n# 结构化检测: 管道分隔字段数(如漏洞报告 4 字段: 名称|等级|IP|建议)\nSTRUCTURED_ROW_FIELD_COUNT = int(os.getenv(\"STRUCTURED_ROW_FIELD_COUNT\", \"4\"))\n\n# 结构化检测: 命中结构化模式的行数占比阈值(0.0-1.0,达到才判定为结构化块)\nSTRUCTURED_BLOCK_THRESHOLD = float(os.getenv(\"STRUCTURED_BLOCK_THRESHOLD\", \"0.6\"))\n\n# 规则分块: 规则分块时每个 chunk 最多容纳的记录行数\nMAX_RECORDS_PER_CHUNK = int(os.getenv(\"MAX_RECORDS_PER_CHUNK\", \"8\"))\n\n# LLM 上下文: 消息+检索内容的最大 token 预算\nLLM_MAX_CONTEXT_TOKENS = int(os.getenv(\"LLM_MAX_CONTEXT_TOKENS\", \"8000\"))\n\n# ---------------------------------------------------------------------------\n# DeepSeek API Configuration (等保三级:支持加密存储)\n# ---------------------------------------------------------------------------\nDEEPSEEK_API_KEY = _decrypt_env(\"DEEPSEEK_API_KEY\", \"\")\nDEEPSEEK_BASE_URL = \"https://api.deepseek.com\"\nDEEPSEEK_MODEL = os.getenv(\"DEEPSEEK_MODEL\", \"deepseek-chat\")\n\n# ---------------------------------------------------------------------------\n# MySQL (等保三级:密码支持加密存储)\n# ---------------------------------------------------------------------------\n_MYSQL_PASSWORD = _decrypt_env(\"MYSQL_PASSWORD\", \"\")\n\n# Paths\nPROJECT_ROOT = Path(__file__).resolve().parent\nSDK_DOCS_DIR = PROJECT_ROOT / \"sdk_docs\"\n\n\n# ---------------------------------------------------------------------------\n# Validation / Printing\n# ---------------------------------------------------------------------------\n\ndef validate_config() -> bool:\n if not DEEPSEEK_API_KEY:\n raise ValueError(\n \"DEEPSEEK_API_KEY is not set. \"\n \"Set it via environment variable or in a .env file.\\n\"\n \"For encrypted storage, set DEEPSEEK_API_KEY_ENC and MASTER_KEY.\"\n )\n return True\n\n\ndef print_config():\n key_masked = (\n DEEPSEEK_API_KEY[:8] + \"...\" + DEEPSEEK_API_KEY[-4:]\n if DEEPSEEK_API_KEY else \"NOT SET\"\n )\n print(f\"Qdrant: {QDRANT_HOST}:{QDRANT_PORT}\")\n print(f\"Collection: {QDRANT_COLLECTION_NAME}\")\n print(f\"Embed Model: {EMBED_MODEL_NAME}\")\n print(f\"Embed Device: {EMBED_DEVICE}\")\n print(f\"Chunk Size: {CHUNK_SIZE_TOKENS} tokens (overlap {CHUNK_OVERLAP_TOKENS})\")\n print(f\"Retrieval: vector_top{VECTOR_TOP_K} + bm25_top{BM25_TOP_K} -> fusion_top{FINAL_TOP_K}\")\n print(f\"DeepSeek Model: {DEEPSEEK_MODEL}\")\n print(f\"API Key: {key_masked}\")\n print(f\"Docs Directory: {SDK_DOCS_DIR}\")\n"}, "cd4b65f4c0dd244c17b35b3534f69a1963fc58b8": {"t": 1785753387.0677438, "n": 8134, "h": "0a821343e7c6891c", "c": "\"\"\"\nCamera SDK RAG Agent - Configuration Module (等保三级改造)\n\n变更:\n- 新增 _decrypt_env() 辅助函数,支持 Fernet 加密字段\n- 敏感字段(DEEPSEEK_API_KEY、MYSQL_PASSWORD)优先读取 _ENC 后缀加密值\n- 解密使用 MASTER_KEY 环境变量(不落盘)\n- print_config() 和 validate_config() 行为不变\n\"\"\"\n\nimport os\nfrom pathlib import Path\n\n# ---------------------------------------------------------------------------\n# Encryption support (等保三级 - 敏感配置加密存储)\n# ---------------------------------------------------------------------------\n\n_fernet_cache = None\n\n\ndef _get_fernet():\n \"\"\"延迟初始化 Fernet 实例(仅在需要解密时加载)。\"\"\"\n global _fernet_cache\n if _fernet_cache is not None:\n return _fernet_cache\n master_key = os.getenv(\"MASTER_KEY\", \"\")\n if not master_key:\n # 未设置 MASTER_KEY 时返回 None,调用方回退到明文\n return None\n try:\n from cryptography.fernet import Fernet\n _fernet_cache = Fernet(master_key.encode(\"utf-8\"))\n return _fernet_cache\n except ImportError:\n return None\n except Exception:\n return None\n\n\ndef _decrypt_env(env_var: str, default: str = \"\") -> str:\n \"\"\"读取环境变量,自动处理加密值。\n\n 优先级:\n 1. 直接读环境变量(非加密明文)\n 2. 如果为空,读 {env_var}_ENC 后缀的加密值,尝试解密\n 3. 回退到 default\n\n Fernet 加密值的特征是 base64 格式(gAAAAAB 前缀)。\n \"\"\"\n raw = os.getenv(env_var, \"\")\n if raw:\n return raw\n\n encrypted = os.getenv(f\"{env_var}_ENC\", \"\")\n if encrypted:\n f = _get_fernet()\n if f:\n try:\n return f.decrypt(encrypted.encode(\"utf-8\")).decode(\"utf-8\")\n except Exception:\n import warnings\n warnings.warn(f\"Failed to decrypt {env_var}_ENC. Check MASTER_KEY.\", RuntimeWarning)\n return \"\"\n else:\n import warnings\n warnings.warn(\n f\"{env_var}_ENC is set but MASTER_KEY is not. \"\n f\"Install cryptography: pip install cryptography\",\n RuntimeWarning,\n )\n return \"\"\n\n return default\n\n\n# ---------------------------------------------------------------------------\n# .env loading\n# ---------------------------------------------------------------------------\n\ntry:\n from dotenv import load_dotenv\n _env_path = Path(__file__).resolve().parent / \".env\"\n if _env_path.exists():\n load_dotenv(_env_path)\nexcept ImportError:\n pass\n\n# ---------------------------------------------------------------------------\n# Qdrant\n# ---------------------------------------------------------------------------\nQDRANT_HOST = os.getenv(\"QDRANT_HOST\", \"\")\nQDRANT_PORT = int(os.getenv(\"QDRANT_PORT\", \"6333\"))\nQDRANT_LOCAL_PATH = os.getenv(\"QDRANT_LOCAL_PATH\", str(Path(__file__).resolve().parent / \"qdrant_data\"))\nQDRANT_COLLECTION_NAME = os.getenv(\"QDRANT_COLLECTION_NAME\", \"camera_sdk_collection\")\nQDRANT_VECTOR_DIM = int(os.getenv(\"QDRANT_VECTOR_DIM\", \"1024\"))\nQDRANT_DISTANCE_METRIC = os.getenv(\"QDRANT_DISTANCE_METRIC\", \"Cosine\")\n\n# ---------------------------------------------------------------------------\n# Embedding\n# ---------------------------------------------------------------------------\nEMBED_MODEL_NAME = os.getenv(\"EMBED_MODEL_NAME\", \"BAAI/bge-m3\")\nEMBED_DEVICE = os.getenv(\"EMBED_DEVICE\", \"cpu\")\nEMBED_BATCH_SIZE = int(os.getenv(\"EMBED_BATCH_SIZE\", \"32\"))\n\n# ---------------------------------------------------------------------------\n# Chunking\n# ---------------------------------------------------------------------------\nCHUNK_SIZE_TOKENS = int(os.getenv(\"CHUNK_SIZE_TOKENS\", \"512\"))\nCHUNK_OVERLAP_TOKENS = int(os.getenv(\"CHUNK_OVERLAP_TOKENS\", \"64\"))\n\n# ---------------------------------------------------------------------------\n# Retrieval\n# ---------------------------------------------------------------------------\nVECTOR_TOP_K = int(os.getenv(\"VECTOR_TOP_K\", \"20\"))\nBM25_TOP_K = int(os.getenv(\"BM25_TOP_K\", \"20\"))\nFINAL_TOP_K = int(os.getenv(\"FINAL_TOP_K\", \"5\"))\nFUSION_K = int(os.getenv(\"FUSION_K\", \"60\"))\n\n# ---------------------------------------------------------------------------\n# P1 质量加固 —— 可配置阈值\n# ---------------------------------------------------------------------------\n# 幻觉护栏: RRF 融合分数阈值\n# 最高分低于此值 → 直接拒答(不调用 LLM)\nRAG_MIN_SCORE_STRICT = float(os.getenv(\"RAG_MIN_SCORE_STRICT\", \"0.02\"))\n# 最高分在此值和 STRICT 之间 → 生成但标注\"置信度较低\"\nRAG_MIN_SCORE_WARN = float(os.getenv(\"RAG_MIN_SCORE_WARN\", \"0.05\"))\n\n# 意图置信度阈值: LLM 分类 confidence 低于此值 → 反问用户澄清\nINTENT_CONFIDENCE_THRESHOLD = float(os.getenv(\"INTENT_CONFIDENCE_THRESHOLD\", \"0.55\"))\n\n# 图纸语义匹配: 图纸列表超过此数量时分批传入 LLM\nDRAWING_MATCH_MAX_ITEMS = int(os.getenv(\"DRAWING_MATCH_MAX_ITEMS\", \"500\"))\n\n# 二次验证开关: drawing 路由高置信度时是否启用独立复核\nVERIFY_ENABLED = os.getenv(\"VERIFY_ENABLED\", \"true\").lower() in (\"1\", \"true\", \"yes\")\n\n# ---------------------------------------------------------------------------\n# P2 工程健壮性 —— 可配置参数\n# ---------------------------------------------------------------------------\n# 模型路由: 候选模型列表(优先级从高到低)\nMODEL_CANDIDATES = os.getenv(\"MODEL_CANDIDATES\", \"deepseek-chat\").split(\",\")\n\n# 熔断器: 连续失败次数触发熔断\nCIRCUIT_BREAKER_FAIL_THRESHOLD = int(os.getenv(\"CIRCUIT_BREAKER_FAIL_THRESHOLD\", \"5\"))\n\n# 熔断器: 冷却时间(秒),OPEN -> HALF_OPEN\nCIRCUIT_BREAKER_COOLDOWN_SEC = int(os.getenv(\"CIRCUIT_BREAKER_COOLDOWN_SEC\", \"60\"))\n\n# 首包超时(秒),流式调用超过此时间未收到 token 视为失败\nSTREAM_FIRST_TOKEN_TIMEOUT_SEC = float(os.getenv(\"STREAM_FIRST_TOKEN_TIMEOUT_SEC\", \"10\"))\n\n# 记忆压缩: 保留最近 N 轮原文不压缩\nMEMORY_WINDOW_TURNS = int(os.getenv(\"MEMORY_WINDOW_TURNS\", \"3\"))\n\n# 记忆压缩: 总消息超过此 token 估算值触发压缩\nMEMORY_COMPRESS_TRIGGER_TOKENS = int(os.getenv(\"MEMORY_COMPRESS_TRIGGER_TOKENS\", \"3500\"))\n\n# 工具注册: 启用的工具名单(逗号分隔,空=全部启用)\nENABLED_TOOLS = os.getenv(\"ENABLED_TOOLS\", \"\").split(\",\") if os.getenv(\"ENABLED_TOOLS\") else None\n\n# ---------------------------------------------------------------------------\n# P2 结构化内容检测 —— 规则分块配置\n# ---------------------------------------------------------------------------\n# 结构化检测: 管道分隔字段数(如漏洞报告 4 字段: 名称|等级|IP|建议)\nSTRUCTURED_ROW_FIELD_COUNT = int(os.getenv(\"STRUCTURED_ROW_FIELD_COUNT\", \"4\"))\n\n# 结构化检测: 命中结构化模式的行数占比阈值(0.0-1.0,达到才判定为结构化块)\nSTRUCTURED_BLOCK_THRESHOLD = float(os.getenv(\"STRUCTURED_BLOCK_THRESHOLD\", \"0.6\"))\n\n# 规则分块: 规则分块时每个 chunk 最多容纳的记录行数\nMAX_RECORDS_PER_CHUNK = int(os.getenv(\"MAX_RECORDS_PER_CHUNK\", \"8\"))\n\n# LLM 上下文: 消息+检索内容的最大 token 预算\nLLM_MAX_CONTEXT_TOKENS = int(os.getenv(\"LLM_MAX_CONTEXT_TOKENS\", \"8000\"))\n\n# ---------------------------------------------------------------------------\n# DeepSeek API Configuration (等保三级:支持加密存储)\n# ---------------------------------------------------------------------------\nDEEPSEEK_API_KEY = _decrypt_env(\"DEEPSEEK_API_KEY\", \"\")\nDEEPSEEK_BASE_URL = \"https://api.deepseek.com\"\nDEEPSEEK_MODEL = os.getenv(\"DEEPSEEK_MODEL\", \"deepseek-chat\")\n\n# ---------------------------------------------------------------------------\n# MySQL (等保三级:密码支持加密存储)\n# ---------------------------------------------------------------------------\n_MYSQL_PASSWORD = _decrypt_env(\"MYSQL_PASSWORD\", \"\")\n\n# Paths\nPROJECT_ROOT = Path(__file__).resolve().parent\nSDK_DOCS_DIR = PROJECT_ROOT / \"sdk_docs\"\n\n\n# ---------------------------------------------------------------------------\n# Validation / Printing\n# ---------------------------------------------------------------------------\n\ndef validate_config() -> bool:\n if not DEEPSEEK_API_KEY:\n raise ValueError(\n \"DEEPSEEK_API_KEY is not set. \"\n \"Set it via environment variable or in a .env file.\\n\"\n \"For encrypted storage, set DEEPSEEK_API_KEY_ENC and MASTER_KEY.\"\n )\n return True\n\n\ndef print_config():\n key_masked = (\n DEEPSEEK_API_KEY[:8] + \"...\" + DEEPSEEK_API_KEY[-4:]\n if DEEPSEEK_API_KEY else \"NOT SET\"\n )\n print(f\"Qdrant: {QDRANT_HOST}:{QDRANT_PORT}\")\n print(f\"Collection: {QDRANT_COLLECTION_NAME}\")\n print(f\"Embed Model: {EMBED_MODEL_NAME}\")\n print(f\"Embed Device: {EMBED_DEVICE}\")\n print(f\"Chunk Size: {CHUNK_SIZE_TOKENS} tokens (overlap {CHUNK_OVERLAP_TOKENS})\")\n print(f\"Retrieval: vector_top{VECTOR_TOP_K} + bm25_top{BM25_TOP_K} -> fusion_top{FINAL_TOP_K}\")\n print(f\"DeepSeek Model: {DEEPSEEK_MODEL}\")\n print(f\"API Key: {key_masked}\")\n print(f\"Docs Directory: {SDK_DOCS_DIR}\")\n"}}, "scripts/encrypt_config.py": {"be6685690b6957a064116d162b9b4e4f7d6c1f57": {"t": 1785746870.7744243, "n": 2237, "h": "63f76472dfd7ec9b", "c": "#!/usr/bin/env python3\n\"\"\"\n敏感配置加密工具\n\n使用 Fernet 对称加密保护 .env 中的敏感字段。\n\n用法:\n # 1. 生成主密钥(一次性,输出到控制台)\n python scripts/encrypt_config.py generate-key\n\n # 2. 将主密钥设为环境变量(不落盘!)\n export MASTER_KEY=<上一步的输出>\n # Windows: set MASTER_KEY=<上一步的输出>\n\n # 3. 加密单个值\n python scripts/encrypt_config.py encrypt \"sk-your-api-key\"\n\n # 4. 解密(验证用)\n python scripts/encrypt_config.py decrypt \"<ciphered_text>\"\n\n配置方式:\n # .env 中敏感字段改用加密值:\n DEEPSEEK_API_KEY_ENC=gAAAAABl... # 加密密文\n MYSQL_PASSWORD_ENC=gAAAAABm...\n\"\"\"\n\nimport argparse\nimport os\nimport sys\n\nfrom cryptography.fernet import Fernet\n\n\ndef get_master_key() -> bytes:\n key = os.environ.get(\"MASTER_KEY\", \"\")\n if not key:\n print(\"ERROR: MASTER_KEY environment variable not set.\", file=sys.stderr)\n print(\"Set it with: export MASTER_KEY=<key>\", file=sys.stderr)\n sys.exit(1)\n try:\n return key.encode(\"utf-8\")\n except Exception:\n return key\n\n\ndef cmd_generate_key():\n \"\"\"生成一个新的 Fernet 主密钥。\"\"\"\n key = Fernet.generate_key()\n print(key.decode(\"utf-8\"))\n print(\"\\n# 请将此密钥设为环境变量(不落盘):\", file=sys.stderr)\n print(f\"# export MASTER_KEY={key.decode()}\", file=sys.stderr)\n\n\ndef cmd_encrypt(plaintext: str):\n \"\"\"加密一段明文。\"\"\"\n key = get_master_key()\n f = Fernet(key)\n token = f.encrypt(plaintext.encode(\"utf-8\"))\n print(token.decode(\"utf-8\"))\n\n\ndef cmd_decrypt(ciphertext: str):\n \"\"\"解密一段密文。\"\"\"\n key = get_master_key()\n f = Fernet(key)\n plain = f.decrypt(ciphertext.encode(\"utf-8\"))\n print(plain.decode(\"utf-8\"))\n\n\ndef main():\n parser = argparse.ArgumentParser(description=\"敏感配置加密工具\")\n sub = parser.add_subparsers(dest=\"command\", required=True)\n\n sub.add_parser(\"generate-key\", help=\"生成 Fernet 主密钥\")\n p_enc = sub.add_parser(\"encrypt\", help=\"加密明文\")\n p_enc.add_argument(\"plaintext\", help=\"要加密的明文\")\n p_dec = sub.add_parser(\"decrypt\", help=\"解密密文\")\n p_dec.add_argument(\"ciphertext\", help=\"要解密的密文\")\n\n args = parser.parse_args()\n\n if args.command == \"generate-key\":\n cmd_generate_key()\n elif args.command == \"encrypt\":\n cmd_encrypt(args.plaintext)\n elif args.command == \"decrypt\":\n cmd_decrypt(args.ciphertext)\n\n\nif __name__ == \"__main__\":\n main()\n"}, "cd4b65f4c0dd244c17b35b3534f69a1963fc58b8": {"t": 1785753388.4209394, "n": 2237, "h": "63f76472dfd7ec9b", "c": "#!/usr/bin/env python3\n\"\"\"\n敏感配置加密工具\n\n使用 Fernet 对称加密保护 .env 中的敏感字段。\n\n用法:\n # 1. 生成主密钥(一次性,输出到控制台)\n python scripts/encrypt_config.py generate-key\n\n # 2. 将主密钥设为环境变量(不落盘!)\n export MASTER_KEY=<上一步的输出>\n # Windows: set MASTER_KEY=<上一步的输出>\n\n # 3. 加密单个值\n python scripts/encrypt_config.py encrypt \"sk-your-api-key\"\n\n # 4. 解密(验证用)\n python scripts/encrypt_config.py decrypt \"<ciphered_text>\"\n\n配置方式:\n # .env 中敏感字段改用加密值:\n DEEPSEEK_API_KEY_ENC=gAAAAABl... # 加密密文\n MYSQL_PASSWORD_ENC=gAAAAABm...\n\"\"\"\n\nimport argparse\nimport os\nimport sys\n\nfrom cryptography.fernet import Fernet\n\n\ndef get_master_key() -> bytes:\n key = os.environ.get(\"MASTER_KEY\", \"\")\n if not key:\n print(\"ERROR: MASTER_KEY environment variable not set.\", file=sys.stderr)\n print(\"Set it with: export MASTER_KEY=<key>\", file=sys.stderr)\n sys.exit(1)\n try:\n return key.encode(\"utf-8\")\n except Exception:\n return key\n\n\ndef cmd_generate_key():\n \"\"\"生成一个新的 Fernet 主密钥。\"\"\"\n key = Fernet.generate_key()\n print(key.decode(\"utf-8\"))\n print(\"\\n# 请将此密钥设为环境变量(不落盘):\", file=sys.stderr)\n print(f\"# export MASTER_KEY={key.decode()}\", file=sys.stderr)\n\n\ndef cmd_encrypt(plaintext: str):\n \"\"\"加密一段明文。\"\"\"\n key = get_master_key()\n f = Fernet(key)\n token = f.encrypt(plaintext.encode(\"utf-8\"))\n print(token.decode(\"utf-8\"))\n\n\ndef cmd_decrypt(ciphertext: str):\n \"\"\"解密一段密文。\"\"\"\n key = get_master_key()\n f = Fernet(key)\n plain = f.decrypt(ciphertext.encode(\"utf-8\"))\n print(plain.decode(\"utf-8\"))\n\n\ndef main():\n parser = argparse.ArgumentParser(description=\"敏感配置加密工具\")\n sub = parser.add_subparsers(dest=\"command\", required=True)\n\n sub.add_parser(\"generate-key\", help=\"生成 Fernet 主密钥\")\n p_enc = sub.add_parser(\"encrypt\", help=\"加密明文\")\n p_enc.add_argument(\"plaintext\", help=\"要加密的明文\")\n p_dec = sub.add_parser(\"decrypt\", help=\"解密密文\")\n p_dec.add_argument(\"ciphertext\", help=\"要解密的密文\")\n\n args = parser.parse_args()\n\n if args.command == \"generate-key\":\n cmd_generate_key()\n elif args.command == \"encrypt\":\n cmd_encrypt(args.plaintext)\n elif args.command == \"decrypt\":\n cmd_decrypt(args.ciphertext)\n\n\nif __name__ == \"__main__\":\n main()\n"}}}}