Source snapshot: 97d7f0fae96dc04b7ddad56fc1db6a108ed662cc [SEC-CLEAN] · pre-push-clean v1.0 · 109处敏感信息已自动转乱码
24 KiB
24 KiB
belongs_to:
- "INDEX · 铸渊·协作指令|GitHub ↔ Notion 桥接协议"
related_to:
- ""
4.1 主 Workflow:
⚒️ 铸渊指令|沙盒部署自动化闭环 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) | 发部署结果邮件 |
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 |
飞书应用凭证 | 飞书通知推送(备用通道) |
铸渊唤醒后的密钥扫描流程
// 铸渊每次唤醒核心大脑后自动执行
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 时,自动收集身份信息:
#!/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
# .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 同步失败(不阻断主流程)"
五、开发者注册表(铸渊维护)
铸渊在仓库维护一份开发者注册表,用于自动部署时查找邮箱:
// 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,展示当前所有模块的部署状态:
// 公告栏前端代码片段
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 = `
<div class="deploy-dev">${info.dev_id}</div>
<div class="deploy-module">${info.module}</div>
<div class="deploy-status">
${info.result === 'passed' ? '✅ 通过' : '❌ 失败'}
</div>
<div class="deploy-time">${info.timestamp}</div>
${info.result === 'failed'
? `<button onclick="showDetail('${key}')"
class="deploy-detail-btn">🔍 查看详情</button>`
: ''}
`;
container.appendChild(card);
}
}
function showDetail(key) {
// 打开详情模态框,展示报错信息
// 人格体可以复制这些信息来引导开发者修复
}
七、定时同步到 Notion
铸渊每天两次(04:00 + 14:00 UTC = 北京 12:00 + 22:00)把部署状态同步到 Notion:
# .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同步 |