--- belongs_to: - "[[INDEX · 铸渊·协作指令|GitHub ↔ Notion 桥接协议]]" related_to: - "[["$BRANCH" != dev/${MY_DEV}* && "$BRANCH" != "main"]]" --- # ⚒️ 铸渊指令|沙盒部署自动化闭环 v1.0(2026-03-15·霜砚签发) --- ## 一、总体架构 ``` ┌───────────────────────────────────────────────────────────────┐ │ 沙盒部署自动化闭环 · 全景数据流 │ ├───────────────────────────────────────────────────────────────┤ │ │ │ ① 开发者打开终端 → 输入 DEV 编号 + 邮箱 │ │ ↓ │ │ ② 在人格体引导下完成模块开发 │ │ ↓ │ │ ③ git push 到自己的分支(dev/DEV-XXX/模块名) │ │ ↓ 触发 GitHub Actions │ │ ④ 铸渊唤醒核心大脑 → 扫描 Secrets → 确认密钥可用 │ │ ↓ │ │ ⑤ SSH 到服务器 → 拉代码到对应沙盒目录 │ │ ↓ │ │ ⑥ 跑 deploy-check.sh 部署前自检 │ │ ↓ │ │ ⑦ 设置权限 chown nginx:nginx + chmod 755/644 │ │ ↓ │ │ ⑧ 跑 smoke-test.sh 冒烟检查(curl 所有已注册模块) │ │ ↓ │ │ ⑨ 写结果到 deploy-status.json │ │ ↓ │ │ ⑩ M22 公告栏自动读取展示(✅通过 / ❌失败+详情链接) │ │ ↓ │ │ ⑪ 发邮件通知开发者(用 SMTP_USER + SMTP_PASS) │ │ ↓ │ │ ⑫ 铸渊每天两次同步部署状态到 Notion 部署注册表 │ │ │ └───────────────────────────────────────────────────────────────┘ ``` --- ## 二、前置条件:铸渊唤醒后密钥扫描 ### 已配置的 Secrets 一览(铸渊唤醒后自动扫描) | **Secret 名称** | **用途** | **本指令使用场景** | | --- | --- | --- | | `DEPLOY_HOST` | 服务器 IP(8.155.62.235) | SSH 登录部署 | | `DEPLOY_USER` | SSH 用户名 | SSH 登录部署 | | `DEPLOY_KEY` | SSH 私钥 | SSH 登录部署 | | `DEPLOY_PATH` | 部署根路径 | 指向 /var/www/ | | `NOTION_TOKEN` / `NOTION_API_KEY` | Notion API Token | 写入部署注册表 | | `SMTP_USER` | [发件邮箱(EMAIL_REDACTED@qq.com](mailto:发件邮箱(EMAIL_REDACTED@qq.com)) | 发部署结果邮件 | | `SMTP_PASS` | 邮箱授权码 | 发部署结果邮件 | | `LLM_API_KEY` | 大模型 API Key | 铸渊人格体唤醒 | | `LLM_BASE_URL` | 大模型 API 端点 | 铸渊人格体唤醒 | | `PORTRAIT_DB_ID` | 开发者动态画像库 DB ID | 部署状态同步 | | `FINGERPRINT_DB_ID` | 模块指纹注册表 DB ID | 模块状态检查 | | `NOTION_TICKET_DB_ID` | 工单队列 DB ID | 部署异常自动开工单 | | `CORE_BRAIN_PAGE_ID` | 曜冥核心大脑页面 ID | 铸渊唤醒时读取核心规则 | | `FEISHU_APP_ID` / `FEISHU_APP_SECRET` | 飞书应用凭证 | 飞书通知推送(备用通道) | ### 铸渊唤醒后的密钥扫描流程 ```jsx // 铸渊每次唤醒核心大脑后自动执行 function onBrainAwake() { console.log('🧠 铸渊核心大脑已唤醒'); // Step 1: 扫描密钥 const requiredSecrets = [ 'DEPLOY_HOST', 'DEPLOY_USER', 'DEPLOY_KEY', 'DEPLOY_PATH', 'NOTION_TOKEN', 'SMTP_USER', 'SMTP_PASS', 'LLM_API_KEY', 'LLM_BASE_URL' ]; const available = []; const missing = []; for (const key of requiredSecrets) { if (process.env[key]) { available.push(key); } else { missing.push(key); } } console.log(`✅ 可用密钥:${available.length}个`); if (missing.length > 0) { console.log(`⚠️ 缺失密钥:${missing.join(', ')}`); // 记录到 brain log,不阻断流程 } // Step 2: 读取核心大脑规则 // Step 3: 继续执行后续任务... } ``` --- ## 三、开发者身份注册机制 ### 3.1 本地身份文件(pre-push hook) 开发者第一次 `git push` 时,自动收集身份信息: ```bash #!/bin/bash # .git/hooks/pre-push · 铸渊自动分发到各开发者仓库 # 开发者 clone 仓库时自动安装(通过 post-checkout hook 或 setup 脚本) PROFILE_FILE=".dev-profile" if [ ! -f "$PROFILE_FILE" ]; then echo "" echo "===== 🔐 光湖系统 · 身份注册 =====" echo "" read -p "🆔 请输入你的开发者编号(如 DEV-005):" DEV_ID read -p "📧 请输入你的接收邮箱:" DEV_EMAIL # 验证格式 if [[ ! "$DEV_ID" =~ ^DEV-[0-9]{3}$ ]]; then echo "❌ 编号格式错误!应为 DEV-XXX(如 DEV-005)" exit 1 fi if [[ ! "$DEV_EMAIL" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then echo "❌ 邮箱格式错误!" exit 1 fi # 写入本地身份文件 cat > "$PROFILE_FILE" << EOF { "dev_id": "$DEV_ID", "email": "$DEV_EMAIL", "registered_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)" } EOF echo "" echo "✅ 身份已注册:${DEV_ID} · ${DEV_EMAIL}" echo "📨 后续部署结果将发送到这个邮箱" echo "" fi # 确保只 push 到自己的分支 if [ -f "$PROFILE_FILE" ]; then MY_DEV=$(cat "$PROFILE_FILE" | grep -o '"dev_id": "[^"]*"' | cut -d'"' -f4) BRANCH=$(git rev-parse --abbrev-ref HEAD) if [[ "$BRANCH" != dev/${MY_DEV}* && "$BRANCH" != "main" ]]; then echo "⚠️ 你当前在分支 ${BRANCH},但你的编号是 ${MY_DEV}" echo "→ 建议 push 到 dev/${MY_DEV}/模块名 分支" fi fi ``` ### 3.2 开发者分支命名规范 ``` dev/DEV-005/status-board # 小草莓的看板模块 dev/DEV-005/cost-control # 小草莓的成本控制模块 dev/DEV-010/channel-engine # 桔子的频道引擎 dev/DEV-001/backend # 页页的后端 ``` > 铸渊从分支名自动提取 DEV 编号,确定部署目标沙盒。 > --- ## 四、核心 Workflow:沙盒部署自动化 ### 4.1 主 Workflow:`sandbox-deploy.yml` ```yaml # .github/workflows/sandbox-deploy.yml name: "🏠 Sandbox Deploy" on: push: branches: - 'dev/DEV-*/**' jobs: # ===== Job 1: 解析 + 校验 ===== parse-and-validate: runs-on: ubuntu-latest outputs: dev_id: $ steps.parse.outputs.dev_id module: $ steps.parse.outputs.module email: $ steps.parse.outputs.email steps: - uses: actions/checkout@v4 - name: "🔍 解析分支名" id: parse run: | BRANCH="${GITHUB_REF#refs/heads/}" DEV_ID=$(echo "$BRANCH" | cut -d'/' -f2) MODULE=$(echo "$BRANCH" | cut -d'/' -f3) echo "dev_id=$DEV_ID" >> $GITHUB_OUTPUT echo "module=$MODULE" >> $GITHUB_OUTPUT echo "🆔 开发者: $DEV_ID" echo "📦 模块: $MODULE" # 从 dev-registry.json 获取邮箱 EMAIL=$(cat data/dev-registry.json 2>/dev/null | \ python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('$DEV_ID',{}).get('email',''))" 2>/dev/null || echo '') echo "email=$EMAIL" >> $GITHUB_OUTPUT - name: "✅ 校验 DEV 编号" run: | DEV_ID="$ steps.parse.outputs.dev_id " if [[ ! "$DEV_ID" =~ ^DEV-[0-9]{3}$ ]]; then echo "❌ 无效的 DEV 编号: $DEV_ID" exit 1 fi # ===== Job 2: 部署到服务器沙盒 ===== deploy-to-sandbox: needs: parse-and-validate runs-on: ubuntu-latest outputs: deploy_result: $ steps.deploy.outputs.result deploy_detail: $ steps.deploy.outputs.detail steps: - uses: actions/checkout@v4 - name: "🔑 配置 SSH" run: | mkdir -p ~/.ssh echo "$ secrets.DEPLOY_KEY " > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa ssh-keyscan -H $ secrets.DEPLOY_HOST >> ~/.ssh/known_hosts 2>/dev/null - name: "🏠 部署到沙盒" id: deploy env: DEV_ID: $ needs.parse-and-validate.outputs.dev_id MODULE: $ needs.parse-and-validate.outputs.module HOST: $ secrets.DEPLOY_HOST USER: $ secrets.DEPLOY_USER run: | SANDBOX="/var/www/${DEV_ID}/${MODULE}" echo "📦 部署目标: ${SANDBOX}" # 创建沙盒目录(如不存在) ssh ${USER}@${HOST} "mkdir -p ${SANDBOX}" # rsync 同步代码 rsync -avz --delete \ --exclude='.git' \ --exclude='.dev-profile' \ --exclude='node_modules' \ ./ ${USER}@${HOST}:${SANDBOX}/ # 设置权限(遵守部署适配协议 v1.0) ssh ${USER}@${HOST} << 'REMOTE' chown -R nginx:nginx /var/www/${DEV_ID}/${MODULE} find /var/www/${DEV_ID}/${MODULE} -type d -exec chmod 755 {} \; find /var/www/${DEV_ID}/${MODULE} -type f -exec chmod 644 {} \; REMOTE echo "✅ 文件同步完成" - name: "🔍 运行 deploy-check.sh" id: check env: DEV_ID: $ needs.parse-and-validate.outputs.dev_id MODULE: $ needs.parse-and-validate.outputs.module HOST: $ secrets.DEPLOY_HOST USER: $ secrets.DEPLOY_USER run: | # 上传自检脚本 scp scripts/deploy-check.sh ${USER}@${HOST}:/tmp/deploy-check.sh # 运行自检 RESULT=$(ssh ${USER}@${HOST} "bash /tmp/deploy-check.sh ${DEV_ID} ${MODULE}" 2>&1) || true echo "$RESULT" if echo "$RESULT" | grep -q "FAIL"; then echo "result=failed" >> $GITHUB_OUTPUT echo "detail=$RESULT" >> $GITHUB_OUTPUT else echo "result=passed" >> $GITHUB_OUTPUT echo "detail=All checks passed" >> $GITHUB_OUTPUT fi - name: "🔥 运行 smoke-test.sh" id: smoke env: HOST: $ secrets.DEPLOY_HOST USER: $ secrets.DEPLOY_USER run: | scp scripts/smoke-test.sh ${USER}@${HOST}:/tmp/smoke-test.sh SMOKE=$(ssh ${USER}@${HOST} "bash /tmp/smoke-test.sh" 2>&1) || true echo "$SMOKE" if echo "$SMOKE" | grep -q "异常"; then echo "⚠️ 有模块冒烟检查失败,但不阻断当前部署" fi # ===== Job 3: 写结果 + 通知 ===== notify-and-sync: needs: [parse-and-validate, deploy-to-sandbox] runs-on: ubuntu-latest if: always() steps: - uses: actions/checkout@v4 - name: "📊 写入 deploy-status.json" run: | DEV_ID="$ needs.parse-and-validate.outputs.dev_id " MODULE="$ needs.parse-and-validate.outputs.module " RESULT="$ needs.deploy-to-sandbox.outputs.deploy_result " DETAIL="$ needs.deploy-to-sandbox.outputs.deploy_detail " TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) # 读取现有状态文件或初始化 STATUS_FILE="data/deploy-status.json" mkdir -p data [ -f "$STATUS_FILE" ] || echo '{}' > "$STATUS_FILE" # 更新状态 python3 << EOF import json with open('$STATUS_FILE', 'r') as f: data = json.load(f) data['${DEV_ID}/${MODULE}'] = { 'dev_id': '${DEV_ID}', 'module': '${MODULE}', 'result': '${RESULT}', 'detail': '${DETAIL}', 'timestamp': '${TIMESTAMP}', 'commit': '${GITHUB_SHA}' } with open('$STATUS_FILE', 'w') as f: json.dump(data, f, indent=2, ensure_ascii=False) EOF git config user.name "zhuyuan-bot" git config user.email "zhuyuan@guanghulab.com" git add data/deploy-status.json git commit -m "🧠 deploy-status: ${DEV_ID}/${MODULE} → ${RESULT}" || true git push || true - name: "📧 发送部署结果邮件" if: needs.parse-and-validate.outputs.email != '' env: SMTP_USER: $ secrets.SMTP_USER SMTP_PASS: $ secrets.SMTP_PASS TO_EMAIL: $ needs.parse-and-validate.outputs.email DEV_ID: $ needs.parse-and-validate.outputs.dev_id MODULE: $ needs.parse-and-validate.outputs.module RESULT: $ needs.deploy-to-sandbox.outputs.deploy_result run: | if [ "$RESULT" = "passed" ]; then SUBJECT="✅ 部署成功:${DEV_ID}/${MODULE}" BODY="你的模块 ${MODULE} 已成功部署到 guanghulab.com\n\n访问地址:https://guanghulab.com/${MODULE}/\n\n铸渊 · 光湖代码守护人格体" else SUBJECT="❌ 部署失败:${DEV_ID}/${MODULE}" BODY="你的模块 ${MODULE} 部署失败\n\n请检查你的模块是否符合『服务器部署适配协议 v1.0』\n\n错误详情请看仓库公告栏或 GitHub Actions 日志\n\n铸渊 · 光湖代码守护人格体" fi # 使用 Python smtplib 发送邮件(QQ 邮箱 SMTP) python3 << PYEOF import smtplib from email.mime.text import MIMEText from email.header import Header msg = MIMEText("""$BODY""", 'plain', 'utf-8') msg['Subject'] = Header("$SUBJECT", 'utf-8') msg['From'] = "$SMTP_USER" msg['To'] = "$TO_EMAIL" try: server = smtplib.SMTP_SSL('smtp.qq.com', 465) server.login("$SMTP_USER", "$SMTP_PASS") server.sendmail("$SMTP_USER", "$TO_EMAIL", msg.as_string()) server.quit() print("✅ 邮件已发送到 $TO_EMAIL") except Exception as e: print(f"⚠️ 邮件发送失败: {e}") PYEOF - name: "📝 同步到 Notion 部署注册表" env: NOTION_TOKEN: $ secrets.NOTION_TOKEN FINGERPRINT_DB_ID: $ secrets.FINGERPRINT_DB_ID DEV_ID: $ needs.parse-and-validate.outputs.dev_id MODULE: $ needs.parse-and-validate.outputs.module RESULT: $ needs.deploy-to-sandbox.outputs.deploy_result run: | node scripts/sync-deploy-to-notion.js || echo "⚠️ Notion 同步失败(不阻断主流程)" ``` --- ## 五、开发者注册表(铸渊维护) 铸渊在仓库维护一份开发者注册表,用于自动部署时查找邮箱: ```json // data/dev-registry.json { "DEV-001": { "name": "页页", "email": "", "modules": ["backend"], "sandbox": "/var/www/DEV-001/" }, "DEV-002": { "name": "肥猫", "email": "", "modules": ["feishu-bot"], "sandbox": "/var/www/DEV-002/" }, "DEV-005": { "name": "小草莓", "email": "", "modules": ["status-board", "cost-control", "persona-scheduler"], "sandbox": "/var/www/DEV-005/" }, "DEV-010": { "name": "桔子", "email": "", "modules": ["channel-engine"], "sandbox": "/var/www/DEV-010/" } } ``` > 邮箱由开发者第一次 push 时通过 pre-push hook 收集,或由人格体在 SYSLOG 中提取。 > > 铸渊自动合并到注册表。 > --- ## 六、M22 公告栏对接 M22 主域公告栏自动读取 `deploy-status.json`,展示当前所有模块的部署状态: ```jsx // 公告栏前端代码片段 async function loadDeployStatus() { const res = await fetch('/data/deploy-status.json'); const data = await res.json(); const container = document.getElementById('deploy-board'); container.innerHTML = ''; for (const [key, info] of Object.entries(data)) { const card = document.createElement('div'); card.className = `deploy-card ${info.result}`; card.innerHTML = `
${info.dev_id}
${info.module}
${info.result === 'passed' ? '✅ 通过' : '❌ 失败'}
${info.timestamp}
${info.result === 'failed' ? `` : ''} `; container.appendChild(card); } } function showDetail(key) { // 打开详情模态框,展示报错信息 // 人格体可以复制这些信息来引导开发者修复 } ``` --- ## 七、定时同步到 Notion 铸渊每天两次(04:00 + 14:00 UTC = 北京 12:00 + 22:00)把部署状态同步到 Notion: ```yaml # .github/workflows/sync-deploy-to-notion.yml name: "📝 Sync Deploy Status to Notion" on: schedule: - cron: '0 4,14 * * *' # 北京 12:00 + 22:00 workflow_dispatch: # 手动触发 jobs: sync: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: "🧠 唤醒铸渊核心大脑" run: | echo "🧠 铸渊核心大脑唤醒中..." echo "🔑 扫描 Secrets 权限..." # 检查必要密钥 for KEY in NOTION_TOKEN FINGERPRINT_DB_ID SMTP_USER; do if [ -z "$(eval echo \$$KEY)" ]; then echo "⚠️ 缺失: $KEY" else echo "✅ 可用: $KEY" fi done env: NOTION_TOKEN: $ secrets.NOTION_TOKEN FINGERPRINT_DB_ID: $ secrets.FINGERPRINT_DB_ID SMTP_USER: $ secrets.SMTP_USER - name: "📝 同步部署状态到 Notion" env: NOTION_TOKEN: $ secrets.NOTION_TOKEN FINGERPRINT_DB_ID: $ secrets.FINGERPRINT_DB_ID run: | node scripts/sync-deploy-to-notion.js - name: "🔥 远程冒烟检查" env: DEPLOY_HOST: $ secrets.DEPLOY_HOST DEPLOY_USER: $ secrets.DEPLOY_USER DEPLOY_KEY: $ secrets.DEPLOY_KEY run: | mkdir -p ~/.ssh echo "$DEPLOY_KEY" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa ssh-keyscan -H $DEPLOY_HOST >> ~/.ssh/known_hosts 2>/dev/null scp scripts/smoke-test.sh ${DEPLOY_USER}@${DEPLOY_HOST}:/tmp/smoke-test.sh ssh ${DEPLOY_USER}@${DEPLOY_HOST} "bash /tmp/smoke-test.sh" ``` --- ## 八、铸渊自行部署清单(读到就做) ``` 铸渊自行部署清单(读到就做·不等人): ① 创建/更新 Workflow a. .github/workflows/sandbox-deploy.yml ← 主部署流程 b. .github/workflows/sync-deploy-to-notion.yml ← 定时同步 ② 创建脚本 a. scripts/deploy-check.sh ← 部署前自检(从部署适配协议复制) b. scripts/smoke-test.sh ← 冒烟检查(从部署适配协议复制) c. scripts/sync-deploy-to-notion.js ← Notion API 写入 d. scripts/setup-sandbox.sh ← 新开发者开户脚本 ③ 创建数据文件 a. data/deploy-status.json ← 部署状态源(公告栏读取) b. data/dev-registry.json ← 开发者注册表(邮箱+模块映射) ④ 服务器目录初始化(通过 SSH) a. 创建 /var/www/_shared/ 目录 b. 创建所有已注册 DEV 编号的沙盒目录 c. 设置权限 nginx:nginx + 755 d. 迁移现有模块到新沙盒结构: /var/www/hololake-frontend/status-board/ → /var/www/DEV-005/status-board/ /var/www/hololake-frontend/cost-control/ → /var/www/DEV-005/cost-control/ ⑤ 更新 Nginx 配置 a. 添加所有 DEV 编号的 location 块 b. 清理重复的 server 块(修复 conflicting server name 警告) c. nginx -t && systemctl reload nginx ⑥ M22 公告栏对接 a. 在公告栏页面添加部署状态板块 b. 读取 deploy-status.json 展示结果 c. 失败时显示「🔍 查看详情」按钮 验收标准: ✅ 开发者 push 到 dev/DEV-XXX/模块名 分支 → 自动部署到服务器沙盒 ✅ 部署后自动跑自检+冒烟检查 ✅ 结果写入 deploy-status.json ✅ 公告栏可看到谁通过谁失败 ✅ 失败时可点展详情看报错信息 ✅ 开发者邮箱收到部署结果通知 ✅ 每天两次自动同步到 Notion 模块指纹注册表 ``` --- ## 九、现有模块迁移计划 服务器现有模块需要从平铺结构迁移到沙盒结构: | **现有路径** | **迁移到** | **开发者** | | --- | --- | --- | | `/var/www/hololake-frontend/status-board/` | `/var/www/DEV-005/status-board/` | DEV-005 小草莓 | | `/var/www/hololake-frontend/cost-control/` | `/var/www/DEV-005/cost-control/` | DEV-005 小草莓 | | `/var/www/backend/` | `/var/www/backend/`(保持,共享服务层) | DEV-001 页页 | | `/var/www/feishu-bot/` | `/var/www/feishu-bot/`(保持,共享服务层) | DEV-002 肥猫 | | `/opt/guanghulab-dingtalk/` | `/var/www/dingtalk-bot/`(保持,共享服务层) | DEV-004 之之 | --- ## 十、协议版本 | **版本** | **日期** | **变更** | | --- | --- | --- | | v1.0 | 2026-03-15 | 首版 · 全套沙盒部署自动化闭环:身份注册+自动部署+自检+冒烟检查+公告栏+邮件通知+Notion同步 | ---