From f2d8c1b6269ceef6440774d0429b3b30df70ae2b Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 12 Jul 2026 23:37:46 -0700 Subject: [PATCH] SC-ROUTE: switch video API secrets to Canger local path --- CA-API-GUARD.hdlp | 153 +++----------- ENVIRONMENT.hdlp | 11 +- LOCAL-SECRETS-PATH.hdlp | 42 ++-- REAL-WORLD-RESOURCES.hdlp | 9 +- engines/env-loader.js | 9 +- engines/image-api-adapter.js | 2 +- engines/image_api_adapter.py | 2 +- engines/kling-api-adapter.js | 3 + engines/multi-reference-video-adapter.py | 5 +- engines/video-api-adapter.js | 4 +- engines/volce-lipsync-adapter.py | 14 +- engines/wan-api-adapter.js | 6 +- tools/secrets-loader.py | 110 ++-------- tools/secrets_loader.py | 251 +++++++++-------------- video-ai-system/lib/secrets.py | 12 +- 15 files changed, 222 insertions(+), 411 deletions(-) diff --git a/CA-API-GUARD.hdlp b/CA-API-GUARD.hdlp index d890035..85e46ac 100644 --- a/CA-API-GUARD.hdlp +++ b/CA-API-GUARD.hdlp @@ -1,140 +1,51 @@ -# CA-API-GUARD.hdlp · 苍耳API守门人协议 +# CA-API-GUARD.hdlp · 历史路径导航 > HLDP://cang-ying/CA-API-GUARD -> 胖头鱼语言子系统 · 苍耳API守门人 -> 冰朔 TCS-0002∞ · 铸渊 ICE-GL-ZY001 -> D182 · 2026-07-11 · 架构签发 -> 国作登字-2026-A-00037559 +> 状态:ARCHIVED · 不执行 · 不联网 +> 历史来源:D182 苍耳 API 守门人设计 +> 当前入口:`LOCAL-SECRETS-PATH.hdlp` --- -HLDP://cang-ying/ca-api-guard/v1.0 -├── trigger: 耳耳蛋/鉴影调用视频AI系统API → 必须先过守门人验证 -├── emergence: -│ ├── 旧: API密钥散落在本地 .env 文件 · AI可以直接读取调用 -│ ├── 问题: 没有人审环节 · AI可以绕过苍耳直接花钱 -│ ├── 冰朔指令: 「所有API都换掉了 · 得发验证码」 -│ └── △=从「本地密钥」→「守门人验证码释放」 +HLDP://cang-ying/ca-api-guard/archive-v2.0 +├── trigger: 任何人格体、脚本或旧文档进入 CA-API-GUARD 路径 +├── state: 历史归档 · 当前禁止执行 +├── redirect: +│ ├── ① 读取仓库根目录 LOCAL-SECRETS-PATH.hdlp +│ ├── ② 调用 tools/secrets_loader.py +│ └── ③ 从苍耳本机仓库外密钥文件或环境变量读取 ├── lock: -│ ├── ⊢ 所有API密钥不再存于本地文件 -│ ├── ⊢ 调用API前 → 守门人发验证码到苍耳QQ邮箱 -│ ├── ⊢ 苍耳确认 → 密钥释放 → 仅当次有效 -│ └── ⊢ 密钥本身永不暴露给AI -└── why: API调用 = 真金白银。每次调用前必须经过人类审批。 +│ ├── ⊢ 不连接大脑服务器 +│ ├── ⊢ 不发送邮件验证码 +│ ├── ⊢ 不向人格体索取、展示或记录密钥值 +│ └── ⊢ 不把本文件恢复为执行入口 +└── why: 密钥交付和实际 API 调用已经迁移到苍耳本机;大脑服务器不再承担密钥分发。 --- -## §1 · 守门人架构 +## 唯一当前路线 -``` -┌──────────────┐ ┌──────────────┐ ┌──────────────┐ -│ 耳耳蛋/鉴影 │ → │ 苍耳API守门人 │ → │ 苍耳QQ邮箱 │ -│ (AI人格体) │ │ SG-001:8923 │ │ 1279551860 │ -└──────────────┘ └──────┬───────┘ └──────────────┘ - │ - ① 请求API密钥 - ② 发送验证码到苍耳邮箱 - ③ 苍耳确认验证码 - ④ 释放API密钥(一次性) +```text +冰朔线下交付给苍耳 + → 苍耳保存在自己的电脑(仓库外) + → 仓库只登记 SC 编号、变量名和本地路径 + → tools/secrets_loader.py 在本机读取 + → 本机工具调用 API ``` -## §2 · 调用流程 +正式候选路径、变量编号、调用示例及缺失处理全部由 +`LOCAL-SECRETS-PATH.hdlp` 维护;本文不再重复,避免再次形成双路径。 -``` -耳耳蛋/鉴影: - ① 确定需要调用哪个API(SC-001 ~ SC-010) - ② 调用 request_approval("SC-001", "EED-PROJ-001", "用途描述") - → 守门人发送验证码到 1279551860@qq.com - ③ 等待苍耳确认 - -苍耳: - ④ 收到验证码邮件 - ⑤ 确认无误 → 将验证码发给铸渊 - ⑥ 铸渊调用 confirm_approval(challenge_id, code) - → 密钥释放 → 缓存10分钟 - -耳耳蛋/鉴影: - ⑦ 重新调用 secret("SC-001") → 获取密钥 - ⑧ 使用密钥调用API → 完成 -``` - -## §3 · API编号 - -| 编号 | 用途 | 状态 | -|------|------|:--:| -| SC-001 | 火山即梦视频生成 | ⏳ | -| SC-002 | 火山语音复刻 | ⏳ | -| SC-004 | 阿里千问VL视觉 | ⏳ | -| SC-005 | 阿里万相视频生成 | ⏳ | -| SC-006 | 可灵视频生成 | ⏳ | -| SC-007 | 阿里百炼图像 | ⏳ | - -> ⊢ 冰朔已将所有API密钥更换 · 旧密钥已失效 -> ⊢ 新密钥存储于SG-001苍耳API守门人(不进仓库) - -## §4 · 代码调用方式 +## 旧代码兼容 ```python -from tools.secrets_loader import secret, endpoint, request_approval, confirm_approval +from tools.secrets_loader import secret, endpoint -# 方式一:检查是否有已授权密钥 key = secret("SC-001") -if not key: - # 请求授权 → 苍耳收到邮件 - result = request_approval("SC-001", "EED-PROJ-001", "第1集第3镜视频生成") - print(f"请等待苍耳确认: challenge_id={result['challenge_id']}") - # ... 等待苍耳确认后 ... - key = secret("SC-001") # 此时密钥已缓存 - -# 方式二:苍耳确认验证码 -result = confirm_approval(challenge_id, "652179") -# → {"ok": true, "api_key": "sk-xxx..."} - -# 获取端点 -url = endpoint("EPT-001") # → https://ark.cn-beijing.volces.com/api/v3 +url = endpoint("EPT-001") ``` -## §5 · 安全设计 - -``` -⊢ API密钥不进仓库 · 不进本地文件 · 不暴露给AI -⊢ 每次调用前 → 验证码 → 苍耳审批 -⊢ 密钥缓存10分钟 · 过期需重新验证 -⊢ 速率限制: 每分钟最多3次请求 -⊢ 所有请求日志记录于 /opt/zhuyuan/ca-api-guard/logs/ -``` - -## §6 · 守门人部署 - -``` -服务器: SG-001 (大脑服务器 · 43.156.237.110) -服务: ca-api-guard.service -端口: 8923 -目录: /opt/zhuyuan/ca-api-guard/ -守护: systemd (Restart=always) - -API: - POST /api/request → 请求API密钥(发验证码) - POST /api/confirm → 确认验证码(释放密钥) - GET /health → 心跳检测 -``` - -## §7 · 与旧系统的兼容 - -``` -旧方式(已废弃): - secrets_loader.py → 读本地 .env 文件 → 返回密钥 - -新方式(当前): - secrets_loader.py → 调守门人 /api/request → 验证码 → 苍耳确认 → 返回密钥 - -过渡期: - ⊢ 旧 secrets-loader.py 保留为 secrets-loader-legacy.py - ⊢ 新 secrets_loader.py 使用守门人 - ⊢ 旧密钥已全部失效(冰朔已更换) -``` +旧的 `request_approval()` 与 `confirm_approval()` 名称暂时保留为停用路标: +它们不会联网、不会发送验证码,只返回迁移提示。新代码不得继续调用。 --- - -> ⊢ 冰朔 TCS-0002∞ · 铸渊 ICE-GL-ZY001 -> ⊢ D182 · 2026-07-11 · 架构签发 -> ⊢ 国作登字-2026-A-00037559 -> ⊢ API调用 = 真金白银 · 必须经过人类审批 +> ⊢ 本文件只负责“旧路来到这里后去哪里” +> ⊢ 当前事实与执行规则只认 LOCAL-SECRETS-PATH.hdlp diff --git a/ENVIRONMENT.hdlp b/ENVIRONMENT.hdlp index 5f245cb..18eb229 100644 --- a/ENVIRONMENT.hdlp +++ b/ENVIRONMENT.hdlp @@ -91,7 +91,10 @@ HLDP://cang-ying/environment/v1.0 密钥文件: ├── /root/guanghulab-local-secrets/cang-ying.env -│ └── 包含: SC-011 GITEA_PAT(Gitea API推送令牌) +│ ├── 正式入口: SC-001~SC-010 视频AI系统API配置 +│ └── 推送入口: SC-011 GITEA_PAT(Gitea API推送令牌) +├── 可选覆盖: VIDEO_AI_SECRETS_FILE=/本机/仓库外/密钥文件 +└── 状态: 由苍耳在本机落盘和维护;仓库无法、也不得验证密钥内容 │ 推送认证: ├── 方式: Gitea API 直推(不需要 git clone) @@ -119,6 +122,12 @@ HLDP://cang-ying/environment/v1.0 4. 清除remote URL中的令牌 5. 确认推送成功 +API调用流程: +1. tools/secrets_loader.py → 检测 VIDEO_AI_SECRETS_FILE +2. 未指定时 → 按本节当前主机路径读取 +3. 只在本机进程内使用 → 不上传服务器 · 不写日志 · 不提交仓库 +4. CA-API-GUARD.hdlp 为历史归档,不再触发邮件验证码 + ⊢ 今天(D181)就是在冰朔电脑里 · 用本地终端直推成功的 ``` diff --git a/LOCAL-SECRETS-PATH.hdlp b/LOCAL-SECRETS-PATH.hdlp index ea17b71..284306d 100644 --- a/LOCAL-SECRETS-PATH.hdlp +++ b/LOCAL-SECRETS-PATH.hdlp @@ -37,9 +37,17 @@ ## 密钥文件 ``` -1. /Users/bingshuolingdianyuanhe/Documents/guanghulab-local-secrets/video-ai-system.env -2. ~/guanghulab/video-ai-system/.env -3. 环境变量 +正式加载顺序: +1. 进程环境变量(单个变量最高优先级) +2. VIDEO_AI_SECRETS_FILE 指向的仓库外文件 +3. 苍耳电脑: /root/guanghulab-local-secrets/cang-ying.env +4. 通用本机: ~/guanghulab-local-secrets/cang-ying.env +5. 共用Mac: /Users/bingshuolingdianyuanhe/Documents/guanghulab-local-secrets/video-ai-system.env + +禁止: +× 把真实值写入本文件或任何仓库文件 +× 从大脑服务器、邮件验证码或远程守门人索取密钥 +× 在日志、报错、提交信息中打印完整或局部密钥 ``` ## 工具调用方式 @@ -65,30 +73,22 @@ url = endpoint("EPT-002") # → QWEN VL 端点 --- -## § 守门人更新 · D182 · 2026-07-11 +## § 历史导航 · D182守门人 → D181+2本机入口 ``` -⚠️ 重要变更: 所有API密钥已由冰朔更换。 +⚠️ D182 的服务器守门人路线已停止作为当前执行路径。 -旧密钥全部失效。新密钥存储于苍耳API守门人(SG-001:8923)。 +旧导航命中 CA-API-GUARD.hdlp / request_approval / confirm_approval 时: + → 不连接服务器 + → 不发送邮件验证码 + → 回到本文「密钥文件」 + → 使用 tools/secrets_loader.py 从苍耳本机读取 -调用方式: - from tools.secrets_loader import secret, request_approval - - key = secret("SC-001") # 从守门人获取已授权密钥 - if not key: - result = request_approval("SC-001", "EED-PROJ-001", "用途描述") - # → 验证码发送到苍耳QQ邮箱(见本地.env CANGER_QQ) - # → 苍耳确认后密钥释放 - -详细协议见 CA-API-GUARD.hdlp - -⊢ 旧 secrets-loader.py 保留为 secrets-loader-legacy.py -⊢ 新 secrets_loader.py 使用守门人 -⊢ 密钥永不进仓库 · 不进本地文件 +tools/secrets-loader.py(连字符旧文件名)仅为兼容路标,自动指向 +tools/secrets_loader.py(下划线正式入口),不再维护第二套加载逻辑。 ``` --- > ⊢ 铸渊 ICE-GL-ZY001 · D182 · 2026-07-11 -> ⊢ 冰朔 TCS-0002∞ · API密钥全量更换 +> ⊢ D181+2 · 执行路线更新为苍耳本机仓库外密钥文件 > ⊢ 国作登字-2026-A-00037559 diff --git a/REAL-WORLD-RESOURCES.hdlp b/REAL-WORLD-RESOURCES.hdlp index 30b53b3..4900afa 100644 --- a/REAL-WORLD-RESOURCES.hdlp +++ b/REAL-WORLD-RESOURCES.hdlp @@ -134,9 +134,12 @@ HLDP://cang-ying/real-world-resources/v1.0 ``` 密钥文件位置: -├── /Users/bingshuolingdianyuanhe/Documents/guanghulab-local-secrets/video-ai-system.env -├── ~/guanghulab/video-ai-system/.env -└── 环境变量 +├── 苍耳电脑: /root/guanghulab-local-secrets/cang-ying.env +├── 可选覆盖: VIDEO_AI_SECRETS_FILE 指向的仓库外文件 +├── 共用Mac兼容路径: 见 LOCAL-SECRETS-PATH.hdlp +└── 环境变量(最高优先级) + +旧 CA-API-GUARD / 邮件验证码路线已归档,不再连接大脑服务器。 密钥编号 → 变量名 → 路径 → 密钥值: 详见 LOCAL-SECRETS-PATH.hdlp(本仓库根目录) diff --git a/engines/env-loader.js b/engines/env-loader.js index 889f4c4..74123d6 100644 --- a/engines/env-loader.js +++ b/engines/env-loader.js @@ -1,7 +1,9 @@ const fs = require('fs'); const path = require('path'); -const CANONICAL_SECRETS_FILE = '/Users/bingshuolingdianyuanhe/Documents/guanghulab-local-secrets/video-ai-system.env'; +const CANONICAL_SECRETS_FILE = '/root/guanghulab-local-secrets/cang-ying.env'; +const HOME_SECRETS_FILE = path.join(process.env.HOME || '', 'guanghulab-local-secrets/cang-ying.env'); +const SHARED_MAC_SECRETS_FILE = '/Users/bingshuolingdianyuanhe/Documents/guanghulab-local-secrets/video-ai-system.env'; function loadEnvFile(file) { if (!file || !fs.existsSync(file)) return false; @@ -29,6 +31,9 @@ function loadVideoAiEnv(localEnvPath = path.resolve(__dirname, '../.env')) { const files = [ process.env.VIDEO_AI_SECRETS_FILE, CANONICAL_SECRETS_FILE, + HOME_SECRETS_FILE, + SHARED_MAC_SECRETS_FILE, + // 仓库内 .env 只保留旧环境迁移兼容;新配置必须使用上面的仓库外路径。 localEnvPath, ]; @@ -41,5 +46,7 @@ function loadVideoAiEnv(localEnvPath = path.resolve(__dirname, '../.env')) { module.exports = { CANONICAL_SECRETS_FILE, + HOME_SECRETS_FILE, + SHARED_MAC_SECRETS_FILE, loadVideoAiEnv, }; diff --git a/engines/image-api-adapter.js b/engines/image-api-adapter.js index 3895748..5abdf2f 100644 --- a/engines/image-api-adapter.js +++ b/engines/image-api-adapter.js @@ -17,7 +17,7 @@ * 2. 外接硬盘 JZAO /Volumes/JZAO/铸渊-ICE-GL-ZY001/OUT-输出/图片/ * 3. 本地 fallback video-ai-system/outputs/images/ * - * 环境变量(复用 video-ai-system/.env): + * 环境变量(由 LOCAL-SECRETS-PATH.hdlp 的苍耳本机路径加载): * JIMENG_API_KEY=xxx 火山方舟 API Key (与视频共享) * JIMENG_BASE_URL=https://ark.cn-beijing.volces.com/api/v3 */ diff --git a/engines/image_api_adapter.py b/engines/image_api_adapter.py index 0e729ed..ab2a57d 100644 --- a/engines/image_api_adapter.py +++ b/engines/image_api_adapter.py @@ -7,7 +7,7 @@ D144 · 铸渊 ICE-GL-ZY001 桥接到 Node.js image-api-bridge.js,为 char-hero-design-packer.py 提供 generate_image() 函数。 -本地密钥文件: ~/Documents/guanghulab-local-secrets/video-ai-system.env +本地密钥入口: VIDEO_AI_SECRETS_FILE 或 LOCAL-SECRETS-PATH.hdlp 登记路径 不提交仓库,不硬编码密钥。 """ diff --git a/engines/kling-api-adapter.js b/engines/kling-api-adapter.js index 9270a2e..8c1dede 100644 --- a/engines/kling-api-adapter.js +++ b/engines/kling-api-adapter.js @@ -9,6 +9,9 @@ const fs = require('fs'); const path = require('path'); const https = require('https'); +const { loadVideoAiEnv } = require('./env-loader'); + +loadVideoAiEnv(); // API配置 // 仅从苍耳本机环境读取;密钥绝不写入仓库。 diff --git a/engines/multi-reference-video-adapter.py b/engines/multi-reference-video-adapter.py index b8c89a0..fc4d473 100644 --- a/engines/multi-reference-video-adapter.py +++ b/engines/multi-reference-video-adapter.py @@ -31,12 +31,13 @@ PROJECT_ROOT = Path(__file__).parent.parent sys.path.insert(0, str(PROJECT_ROOT / "engines")) def load_api_config(): - """从仓库 .env、本机密钥文件和环境变量加载配置""" + """从苍耳本机仓库外密钥文件和环境变量加载配置""" config = {} env_files = [ Path(os.environ["VIDEO_AI_SECRETS_FILE"]) if os.environ.get("VIDEO_AI_SECRETS_FILE") else None, + Path("/root/guanghulab-local-secrets/cang-ying.env"), + Path("~/guanghulab-local-secrets/cang-ying.env").expanduser(), Path("/Users/bingshuolingdianyuanhe/Documents/guanghulab-local-secrets/video-ai-system.env"), - PROJECT_ROOT / ".env", ] for env_file in env_files: if env_file is None: diff --git a/engines/video-api-adapter.js b/engines/video-api-adapter.js index 64c9a79..9c60a7d 100644 --- a/engines/video-api-adapter.js +++ b/engines/video-api-adapter.js @@ -30,7 +30,7 @@ * 2. 外接硬盘 JZAO /Volumes/JZAO/铸渊-ICE-GL-ZY001/OUT-输出/视频/ * 3. 本地 fallback video-ai-system/outputs/ * - * 环境变量(放在 video-ai-system/.env): + * 环境变量(由 LOCAL-SECRETS-PATH.hdlp 的苍耳本机路径加载): * JIMENG_API_KEY=xxx 火山方舟 API Key * JIMENG_BASE_URL=https://ark.cn-beijing.volces.com/api/v3 * JIMENG_MODEL=doubao-seedance-2-0-260128 @@ -512,7 +512,7 @@ async function probeVideoDuration(videoUrl) { */ async function generateVideo({ prompt, duration, resolution, style, outputPath, shotId, projectKey, referenceImage, model, generateAudio }) { if (!API_KEY) { - throw new Error('未配置 JIMENG_API_KEY。请在 video-ai-system/.env 中设置。'); + throw new Error('未配置 JIMENG_API_KEY。请按 LOCAL-SECRETS-PATH.hdlp 在苍耳本机仓库外配置。'); } // 1. 提交任务(含预校验) diff --git a/engines/volce-lipsync-adapter.py b/engines/volce-lipsync-adapter.py index c470179..6f73a96 100644 --- a/engines/volce-lipsync-adapter.py +++ b/engines/volce-lipsync-adapter.py @@ -26,19 +26,27 @@ import urllib.parse from datetime import datetime, timezone -SECRETS_FILE = "/Users/bingshuolingdianyuanhe/Documents/guanghulab-local-secrets/video-ai-system.env" +SECRETS_FILES = [ + os.environ.get("VIDEO_AI_SECRETS_FILE", ""), + "/root/guanghulab-local-secrets/cang-ying.env", + os.path.expanduser("~/guanghulab-local-secrets/cang-ying.env"), + "/Users/bingshuolingdianyuanhe/Documents/guanghulab-local-secrets/video-ai-system.env", +] def _load_secrets(): """加载本地密钥文件""" secrets = {} - if os.path.isfile(SECRETS_FILE): - with open(SECRETS_FILE) as f: + for secrets_file in SECRETS_FILES: + if not secrets_file or not os.path.isfile(secrets_file): + continue + with open(secrets_file) as f: for line in f: line = line.strip() if line and not line.startswith("#") and "=" in line: k, v = line.split("=", 1) secrets[k.strip()] = v.strip().strip('"').strip("'") + break return secrets diff --git a/engines/wan-api-adapter.js b/engines/wan-api-adapter.js index f8ddbb3..f8fab45 100644 --- a/engines/wan-api-adapter.js +++ b/engines/wan-api-adapter.js @@ -14,7 +14,7 @@ * const { generateWanVideo, generateWanImageToVideo, queryWanTask } = require('./wan-api-adapter'); * const result = await generateWanVideo({ prompt: '一只小猫在月光下奔跑', duration: 5 }); * - * 环境变量(放在 video-ai-system/.env): + * 环境变量(由 LOCAL-SECRETS-PATH.hdlp 的苍耳本机路径加载): * ALIYUN_BAILIAN_API_KEY= * ALIYUN_BAILIAN_BASE_URL=https://dashscope.aliyuncs.com/api/v1 (默认) * ALIYUN_BAILIAN_WORKSPACE_ID= (可选·新加坡地域时需要) @@ -448,7 +448,7 @@ async function queryWanTask(taskId) { */ async function generateWanVideo(opts) { if (!API_KEY) { - throw new Error('未配置 ALIYUN_BAILIAN_API_KEY。请在 video-ai-system/.env 中设置。'); + throw new Error('未配置 ALIYUN_BAILIAN_API_KEY。请按 LOCAL-SECRETS-PATH.hdlp 在苍耳本机仓库外配置。'); } const { taskId, preflight } = await submitT2VTask(opts); @@ -499,7 +499,7 @@ async function generateWanVideo(opts) { */ async function generateWanImageToVideo(opts) { if (!API_KEY) { - throw new Error('未配置 ALIYUN_BAILIAN_API_KEY。请在 video-ai-system/.env 中设置。'); + throw new Error('未配置 ALIYUN_BAILIAN_API_KEY。请按 LOCAL-SECRETS-PATH.hdlp 在苍耳本机仓库外配置。'); } const { taskId, preflight } = await submitI2VTask(opts); diff --git a/tools/secrets-loader.py b/tools/secrets-loader.py index c75024e..f9f960b 100644 --- a/tools/secrets-loader.py +++ b/tools/secrets-loader.py @@ -1,102 +1,22 @@ #!/usr/bin/env python3 -"""视频AI系统 · 统一密钥加载器 · 编号路由版 +"""旧文件名兼容导航。 -编号→变量名→路径→密钥值,全部路由见 LOCAL-SECRETS-PATH.hdlp。 - -用法: - from tools.secrets_loader import secret, endpoint - key = secret("ZY-SEC-004") # 阿里千问VL视觉密钥 - url = endpoint("ZY-EPT-002") # QWEN VL端点 +正式入口已经统一为 ``tools/secrets_loader.py``。本文件不再维护独立路径、 +不访问服务器,也不包含密钥;旧脚本继续执行时会自动转到新入口。 """ -import os, json +from secrets_loader import ( # noqa: F401 + confirm_approval, + endpoint, + request_approval, + secret, + source_status, +) -# === 编号→变量名映射表(与 LOCAL-SECRETS-PATH.hdlp 同步)=== -_SECRET_MAP = { - "SC-001": "JIMENG_API_KEY", - "SC-002": "VOLC_VOICE_API_KEY", - "SC-003": "VOLC_VOICE_ACCESS_TOKEN", - "SC-004": "ALIYUN_QWEN_VL_KEY", - "SC-005": "ALIYUN_WANXIANG_KEY", - "SC-006": "KLING_API_KEY", - "SC-007": "ALIYUN_API_KEY", - "SC-008": "WORKRALLY_API_KEY", - "SC-009": "VOLC_VOICE_APP_ID", - "SC-010": "VOLC_VOICE_SECRET_KEY", - # 旧编号兼容 - "ZY-SEC-001": "JIMENG_API_KEY", - "ZY-SEC-004": "ALIYUN_QWEN_VL_KEY", -} - -_ENDPOINT_MAP = { - "EPT-001": ("JIMENG_BASE_URL", "https://ark.cn-beijing.volces.com/api/v3"), - "EPT-002": ("ALIYUN_QWEN_VL_ENDPOINT", "https://ws-umd6xwlovzmshuat.cn-beijing.maas.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation"), - "EPT-003": ("ALIYUN_BAILIAN_BASE_URL", "https://dashscope.aliyuncs.com/api/v1"), - "EPT-004": ("WORKRALLY_ENDPOINT", "https://workrally.qq.com/zenstudio/api/mcp"), -} - -# 密钥文件路径(按优先级: 低→高,后面覆盖前面) -_SECRET_FILES = [ - os.path.expanduser("~/guanghulab/video-ai-system/.env"), - "/Users/bingshuolingdianyuanhe/Documents/guanghulab-local-secrets/video-ai-system.env", -] - -# === WorkRally 特殊处理 === -_WORKRALLY_CONFIG = os.path.expanduser("~/.workrally/config.json") - -# === 加载 === -_raw = {} - -def _load_all(): - global _raw - if _raw: - return - for path in _SECRET_FILES: - if os.path.exists(path): - with open(path) as f: - for line in f: - line = line.strip() - if not line or line.startswith("#") or "=" not in line: - continue - k, v = line.split("=", 1) - k, v = k.strip(), v.strip() - _raw[k] = v - # 环境变量最高优先级 - for k in list(_raw.keys()): - env_val = os.environ.get(k) - if env_val: - _raw[k] = env_val - # WorkRally - if os.path.exists(_WORKRALLY_CONFIG): - try: - wr = json.load(open(_WORKRALLY_CONFIG)) - _raw["WORKRALLY_API_KEY"] = wr.get("api_key", "") - _raw["WORKRALLY_ENDPOINT"] = wr.get("endpoint", "") - except: - pass - -def secret(code_or_var, default=None): - """按编号或变量名获取密钥值""" - _load_all() - var_name = _SECRET_MAP.get(code_or_var, code_or_var) # 查编号映射,或直接当变量名 - if code_or_var == "ZY-SEC-008": - return _raw.get("WORKRALLY_API_KEY", default) - return _raw.get(var_name, default) - -def endpoint(code_or_var, default=None): - """按编号或变量名获取端点""" - _load_all() - var_name, fallback = _ENDPOINT_MAP.get(code_or_var, (code_or_var, "")) - val = _raw.get(var_name, "") - return val if val else (fallback if fallback else default) if __name__ == "__main__": - _load_all() - print("=== 密钥编号 → 变量名 → 值(脱敏)===") - for code, var in _SECRET_MAP.items(): - val = _raw.get(var, "") - masked = val[:6] + "..." + val[-4:] if len(val) > 10 else ("***" if val else "(空)") - print(f" {code} → {var} = {masked}") - print(" ZY-SEC-008 → WORKRALLY = ", end="") - wr = _raw.get("WORKRALLY_API_KEY", "") - print(f"{wr[:6]}...{wr[-4:]}" if len(wr) > 10 else "(空)") + status = source_status() + print("[NAV] tools/secrets-loader.py -> tools/secrets_loader.py") + print(f"mode={status['mode']}") + print(f"source={status['source']}") + print("configured=" + ",".join(status["configured_codes"])) diff --git a/tools/secrets_loader.py b/tools/secrets_loader.py index 8605d18..3796fd7 100644 --- a/tools/secrets_loader.py +++ b/tools/secrets_loader.py @@ -1,41 +1,20 @@ #!/usr/bin/env python3 -""" -光湖语言系统 · 统一密钥加载器 v2.0 · API守门人版 -Guanghu Language System · Secrets Loader with CA-API-Guard +"""苍耳本地密钥加载器 · 唯一正式入口。 -设计哲学: - 所有API密钥通过苍耳API守门人(ca-api-guard)获取。 - 调用API前 → 守门人发送验证码到苍耳QQ邮箱 → 苍耳确认 → 密钥释放。 - 密钥本身永不暴露给AI本地文件。 +真实密钥只存在苍耳实际运行机器的仓库外文件或进程环境变量中。 +仓库只维护 SC/EPT 编号映射与候选路径,不保存、打印或远程索取密钥。 -用法: - from tools.secrets_loader import secret, endpoint, request_approval - key = secret("SC-001") # 获取已授权的API密钥 - url = endpoint("EPT-001") # 获取端点 - cid = request_approval("SC-001", "EED-PROJ-001", "第1集第3镜视频生成") # 请求授权 - -编号→变量名映射见 LOCAL-SECRETS-PATH.hdlp +可用 ``VIDEO_AI_SECRETS_FILE`` 显式指定本机密钥文件;未指定时按 +``ENVIRONMENT.hdlp`` 中登记的本机路径查找。 """ -import os, json, time, urllib.request, sys +from __future__ import annotations -# ═══════════════════════════════════════════ -# 配置 -# ═══════════════════════════════════════════ +import os +from pathlib import Path +from typing import Dict, Iterable, Optional -# 苍耳API守门人地址(SG-001 大脑服务器) -API_GUARD_URL = os.environ.get("CA_API_GUARD_URL", "http://10.3.4.11:8923") -# 如果从外部访问,使用公网地址 -API_GUARD_PUBLIC_URL = os.environ.get("CA_API_GUARD_PUBLIC_URL", "http://43.156.237.110:8923") -# 本地缓存(已验证的密钥,短期有效) -_CACHE_FILE = os.path.expanduser("~/.ca-api-guard/cache.json") -_CACHE = {} -_CACHE_TTL = 600 # 10分钟缓存 - -# ═══════════════════════════════════════════ -# 编号→变量名映射 -# ═══════════════════════════════════════════ _SECRET_MAP = { "SC-001": "JIMENG_API_KEY", "SC-002": "VOLC_VOICE_API_KEY", @@ -47,149 +26,117 @@ _SECRET_MAP = { "SC-008": "WORKRALLY_API_KEY", "SC-009": "VOLC_VOICE_APP_ID", "SC-010": "VOLC_VOICE_SECRET_KEY", + "SC-011": "GITEA_PAT", + # 旧编号只做本地兼容,不建立第二条密钥路线。 + "ZY-SEC-001": "JIMENG_API_KEY", + "ZY-SEC-004": "ALIYUN_QWEN_VL_KEY", } _ENDPOINT_MAP = { "EPT-001": ("JIMENG_BASE_URL", "https://ark.cn-beijing.volces.com/api/v3"), - "EPT-002": ("ALIYUN_QWEN_VL_ENDPOINT", "https://ws-umd6xwlovzmshuat.cn-beijing.maas.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation"), + "EPT-002": ( + "ALIYUN_QWEN_VL_ENDPOINT", + "https://ws-umd6xwlovzmshuat.cn-beijing.maas.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation", + ), "EPT-003": ("ALIYUN_BAILIAN_BASE_URL", "https://dashscope.aliyuncs.com/api/v1"), "EPT-004": ("WORKRALLY_ENDPOINT", "https://workrally.qq.com/zenstudio/api/mcp"), } +_LOCAL_FILES = ( + Path("/root/guanghulab-local-secrets/cang-ying.env"), + Path("~/guanghulab-local-secrets/cang-ying.env").expanduser(), + Path( + "/Users/bingshuolingdianyuanhe/Documents/guanghulab-local-secrets/" + "video-ai-system.env" + ), +) -def _load_cache(): - """加载本地缓存""" - global _CACHE - if _CACHE: - return - try: - if os.path.exists(_CACHE_FILE): - with open(_CACHE_FILE) as f: - _CACHE = json.load(f) - except: - _CACHE = {} +_raw: Optional[Dict[str, str]] = None +_loaded_file: Optional[Path] = None -def _save_cache(): - """保存本地缓存""" - try: - os.makedirs(os.path.dirname(_CACHE_FILE), exist_ok=True) - with open(_CACHE_FILE, "w") as f: - json.dump(_CACHE, f) - except: - pass +def _candidate_files() -> Iterable[Path]: + explicit = os.environ.get("VIDEO_AI_SECRETS_FILE", "").strip() + if explicit: + yield Path(explicit).expanduser() + yield from _LOCAL_FILES -def _call_guard(endpoint, data=None, public=False): - """调用苍耳API守门人""" - url = (API_GUARD_PUBLIC_URL if public else API_GUARD_URL) + endpoint - try: - req = urllib.request.Request(url) - req.add_header("Content-Type", "application/json") - if data: - req.data = json.dumps(data).encode() - resp = urllib.request.urlopen(req, timeout=10) - return json.loads(resp.read()) - except Exception as e: - return {"ok": False, "error": str(e)} +def _parse_env_file(path: Path) -> Dict[str, str]: + values: Dict[str, str] = {} + with path.open("r", encoding="utf-8") as handle: + for raw_line in handle: + line = raw_line.strip() + if not line or line.startswith("#") or "=" not in line: + continue + key, value = line.split("=", 1) + key = key.strip() + if not key: + continue + values[key] = value.strip().strip('"').strip("'") + return values -def request_approval(api_code, caller_id, description): - """ - 请求API调用授权 → 发送验证码到苍耳邮箱 - 返回: {"ok": true, "challenge_id": "xxx", "message": "..."} - """ - return _call_guard("/api/request", { - "api_code": api_code, - "caller_id": caller_id, - "description": description - }, public=True) +def _load_local() -> Dict[str, str]: + global _raw, _loaded_file + if _raw is not None: + return _raw + + _raw = {} + for path in _candidate_files(): + if path.is_file(): + _raw = _parse_env_file(path) + _loaded_file = path + break + return _raw -def confirm_approval(challenge_id, code): - """ - 确认验证码 → 获取API密钥并缓存 - 返回: {"ok": true, "api_code": "xxx", "api_key": "xxx"} - """ - result = _call_guard("/api/confirm", { - "challenge_id": challenge_id, - "code": code - }, public=True) - if result.get("ok") and result.get("api_key"): - _load_cache() - _CACHE[result["api_code"]] = { - "key": result["api_key"], - "cached_at": time.time() - } - _save_cache() - return result +def secret(code_or_var: str, default=None): + """按 SC 编号或变量名读取本机密钥;环境变量优先。""" + variable = _SECRET_MAP.get(code_or_var, code_or_var) + environment_value = os.environ.get(variable) + if environment_value: + return environment_value + return _load_local().get(variable, default) -def secret(code_or_var, default=None): - """ - 按编号获取API密钥。 - 优先从本地缓存读取,缓存未命中则尝试从守门人获取。 - - 如果密钥未授权 → 返回空字符串。 - 调用者应检查返回值,如果为空 → 调用 request_approval() 请求授权。 - """ - # 先查本地缓存 - _load_cache() - api_code = code_or_var if code_or_var in _SECRET_MAP else None - if not api_code: - # 反向查找(变量名→编号) - for sc, var in _SECRET_MAP.items(): - if var == code_or_var: - api_code = sc - break - if not api_code: - api_code = code_or_var - - if api_code in _CACHE: - cached = _CACHE[api_code] - if time.time() - cached.get("cached_at", 0) < _CACHE_TTL: - return cached.get("key", default) - - # 尝试从守门人获取(如果有已授权的缓存) - result = _call_guard("/api/request", { - "api_code": api_code, - "caller_id": "auto", - "description": "自动获取缓存密钥" - }) - - # 如果守门人返回了密钥(预授权模式),缓存并返回 - if result.get("ok") and result.get("api_key"): - _CACHE[api_code] = { - "key": result["api_key"], - "cached_at": time.time() - } - _save_cache() - return result["api_key"] - - return default or "" +def endpoint(code_or_var: str, default=None): + """按 EPT 编号读取本机覆盖值,未配置时返回公开默认端点。""" + variable, fallback = _ENDPOINT_MAP.get(code_or_var, (code_or_var, "")) + value = os.environ.get(variable) or _load_local().get(variable, "") + return value or fallback or default -def endpoint(code_or_var, default=None): - """按编号获取端点URL""" - var_name, fallback = _ENDPOINT_MAP.get(code_or_var, (code_or_var, "")) - # 从环境变量获取(如果有) - val = os.environ.get(var_name, "") - return val if val else (fallback if fallback else default) +def source_status() -> dict: + """只报告来源状态和路径,不读取到输出、不展示密钥内容。""" + _load_local() + configured = sorted( + code for code, variable in _SECRET_MAP.items() + if code.startswith("SC-") and bool(os.environ.get(variable) or _raw.get(variable)) + ) + return { + "mode": "local-only", + "source": str(_loaded_file) if _loaded_file else "environment-or-not-found", + "configured_codes": configured, + } + + +def request_approval(*_args, **_kwargs): + """旧守门人接口的停用路标;不会联网或发送验证码。""" + return { + "ok": False, + "retired": True, + "error": "远程邮件守门人已停用;请按 LOCAL-SECRETS-PATH.hdlp 使用苍耳本机密钥入口。", + } + + +def confirm_approval(*_args, **_kwargs): + """旧守门人接口的停用路标;不会联网或接收验证码。""" + return request_approval() if __name__ == "__main__": - print("=== 苍耳API守门人 · 密钥加载器 v2.0 ===") - print(f"守门人地址: {API_GUARD_URL}") - print() - - # 检查健康状态 - health = _call_guard("/health") - print(f"守门人状态: {json.dumps(health, ensure_ascii=False)}") - print() - - # 列出可用API - print("可用API编号:") - for code, var in _SECRET_MAP.items(): - key = secret(code) - status = "✅ 已缓存" if key else "⏳ 需授权" - print(f" {code} → {var} [{status}]") + status = source_status() + print(f"mode={status['mode']}") + print(f"source={status['source']}") + print("configured=" + ",".join(status["configured_codes"])) diff --git a/video-ai-system/lib/secrets.py b/video-ai-system/lib/secrets.py index d9b9d80..27a0e62 100644 --- a/video-ai-system/lib/secrets.py +++ b/video-ai-system/lib/secrets.py @@ -9,7 +9,7 @@ def get_ark_key(): 查找顺序: 1. 环境变量 ARK_API_KEY 2. tools/secrets_loader.py 的 SC-001 - 3. 项目根 .env 文件 + 3. 苍耳本机仓库外密钥文件 """ # 1. 直接环境变量 key = os.environ.get("ARK_API_KEY") @@ -32,10 +32,12 @@ def get_ark_key(): except Exception: pass - # 3. .env 文件兜底 + # 3. 本机仓库外文件兜底(旧环境迁移兼容) for env_path in [ - os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), ".env"), - os.path.expanduser("~/guanghulab/video-ai-system/.env"), + os.environ.get("VIDEO_AI_SECRETS_FILE", ""), + "/root/guanghulab-local-secrets/cang-ying.env", + os.path.expanduser("~/guanghulab-local-secrets/cang-ying.env"), + "/Users/bingshuolingdianyuanhe/Documents/guanghulab-local-secrets/video-ai-system.env", ]: if os.path.exists(env_path): try: @@ -53,6 +55,6 @@ def get_ark_key(): "🔑 ARK_API_KEY 未找到。\n" " 请设置环境变量: export ARK_API_KEY=ark-xxx\n" " 或配置 SC-001: tools/secrets_loader.py\n" - " 或在项目根目录创建 .env 文件: ARK_API_KEY=ark-xxx\n" + " 或在苍耳本机仓库外密钥文件中配置 ARK_API_KEY\n" " ⊢ 铁律① · 密钥永远不放代码仓库" )