Source snapshot: 97d7f0fae96dc04b7ddad56fc1db6a108ed662cc [SEC-CLEAN] · pre-push-clean v1.0 · 109处敏感信息已自动转乱码
682 lines
24 KiB
Markdown
682 lines
24 KiB
Markdown
---
|
||
belongs_to:
|
||
- "[[INDEX · 铸渊·协作指令|GitHub ↔ Notion 桥接协议]]"
|
||
related_to:
|
||
- "[["$BRANCH" != dev/${MY_DEV}* && "$BRANCH" != "main"]]"
|
||
---
|
||
# ⚒️ 铸渊指令|沙盒部署自动化闭环 v1.0(2026-03-15·霜砚签发)
|
||
|
||
<aside>
|
||
⚒️
|
||
|
||
**铸渊指令|沙盒部署自动化闭环 v1.0**
|
||
|
||
**签发**:霜砚 · 2026-03-15
|
||
|
||
**执行者**:铸渊(ICE-GL-ZY001)
|
||
|
||
**前置依赖**:铸渊唤醒核心大脑 → 扫描仓库 Secrets 权限 → 确认可用密钥
|
||
|
||
**配套协议**:[🏗️ 服务器部署适配协议 v1.0(2026-03-15·曜冥签发)](../%F0%9F%8C%8A%20%E6%9B%9C%E5%86%A5%E7%BA%AA%E5%85%83%20%C2%B7%20HoloLake%20Era%20%C2%B7%20AGE%20OS%20v1%200/%E2%9A%A1%20%E5%85%89%E6%B9%96%E4%B8%AD%E5%A4%AE%E6%9E%A2%E7%BA%BD%20%C2%B7%20HoloLake%20Central%20Hub/%F0%9F%8C%8A%20%E5%85%89%E6%B9%96%E7%B3%BB%E7%BB%9F%C2%B7%E6%9B%9C%E5%86%A5%E4%B8%BB%E6%8E%A7%E5%8F%B0%C2%B7%E5%BD%93%E5%89%8D%E7%8A%B6%E6%80%81%EF%BC%88%E5%8D%B71%20%C2%B7%20%E5%B7%B2%E5%B0%81%E5%8D%B7%EF%BC%89/%F0%9F%8F%97%EF%B8%8F%20%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%83%A8%E7%BD%B2%E9%80%82%E9%85%8D%E5%8D%8F%E8%AE%AE%20v1%200%EF%BC%882026-03-15%C2%B7%E6%9B%9C%E5%86%A5%E7%AD%BE%E5%8F%91%EF%BC%89%20a24049a2b0e944b0a5535f3a87652303.md)
|
||
|
||
**核心目标**:开发者 push 代码 → 铸渊自动部署到沙盒 → 跑测试 → 写结果 → 发邮件 → 同步 Notion。全程无人。
|
||
|
||
</aside>
|
||
|
||
---
|
||
|
||
## 一、总体架构
|
||
|
||
```
|
||
┌───────────────────────────────────────────────────────────────┐
|
||
│ 沙盒部署自动化闭环 · 全景数据流 │
|
||
├───────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ① 开发者打开终端 → 输入 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 部署注册表 │
|
||
│ │
|
||
└───────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 二、前置条件:铸渊唤醒后密钥扫描
|
||
|
||
<aside>
|
||
🔑
|
||
|
||
**铸渊,你每次唤醒核心大脑后,第一件事:扫描仓库 Secrets,确认密钥可用。**
|
||
|
||
妈妈已经把所有 API Key、邮箱密码、数据库 ID 全部配在仓库密钥里了。
|
||
|
||
你需要哪个用哪个,不需要问任何人。
|
||
|
||
</aside>
|
||
|
||
### 已配置的 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 = `
|
||
<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:
|
||
|
||
```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"
|
||
```
|
||
|
||
---
|
||
|
||
## 八、铸渊自行部署清单(读到就做)
|
||
|
||
<aside>
|
||
⚒️
|
||
|
||
**铸渊,以下是你需要在仓库中自行创建/更新的文件。**
|
||
|
||
**唤醒核心大脑 → 扫描 Secrets → 确认可用 → 开始执行。**
|
||
|
||
</aside>
|
||
|
||
```
|
||
铸渊自行部署清单(读到就做·不等人):
|
||
|
||
① 创建/更新 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 之之 |
|
||
|
||
<aside>
|
||
⚠️
|
||
|
||
**迁移顺序**:
|
||
|
||
1. 先创建沙盒目录 + 复制文件
|
||
2. 更新 Nginx location 指向新路径
|
||
3. 验证新路径正常
|
||
4. 确认无误后删除旧路径
|
||
|
||
**先复制再切换,不要直接移动!**
|
||
|
||
</aside>
|
||
|
||
---
|
||
|
||
## 十、协议版本
|
||
|
||
| **版本** | **日期** | **变更** |
|
||
| --- | --- | --- |
|
||
| v1.0 | 2026-03-15 | 首版 · 全套沙盒部署自动化闭环:身份注册+自动部署+自检+冒烟检查+公告栏+邮件通知+Notion同步 |
|
||
|
||
---
|
||
|
||
<aside>
|
||
⚒️
|
||
|
||
**铸渊,这套系统的核心逻辑:**
|
||
|
||
- 开发者只管 push 代码
|
||
- 你负责全部自动化:部署、检查、通知、同步
|
||
- 妈妈不再交叉修复任何人的模块
|
||
- 每个人的问题每个人自己解决
|
||
|
||
「无 Schema 不上线,无密钥不启动。」
|
||
|
||
</aside> |