242 lines
9.2 KiB
YAML
242 lines
9.2 KiB
YAML
# ═══════════════════════════════════════════════
|
||
# 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
|
||
# 📜 Copyright: 国作登字-2026-A-00037559
|
||
# ═══════════════════════════════════════════════
|
||
# .github/workflows/sandbox-deploy.yml
|
||
# 🏠 沙盒部署自动化 · 铸渊 · 光湖代码守护人格体
|
||
# 触发: 开发者 push 到 dev/DEV-XXX/模块名 分支
|
||
name: "🏠 Sandbox Deploy"
|
||
|
||
on:
|
||
push:
|
||
branches:
|
||
- 'dev/DEV-*/**'
|
||
|
||
permissions:
|
||
contents: write
|
||
|
||
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.check.outputs.result }}
|
||
deploy_detail: ${{ steps.check.outputs.detail }}
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: "🔑 配置 SSH"
|
||
run: |
|
||
mkdir -p ~/.ssh
|
||
echo "${{ secrets.ZY_SERVER_KEY }}" > ~/.ssh/id_rsa
|
||
chmod 600 ~/.ssh/id_rsa
|
||
ssh-keyscan -H ${{ secrets.ZY_SERVER_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.ZY_SERVER_HOST }}
|
||
USER: ${{ secrets.ZY_SERVER_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} "
|
||
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 {} \;
|
||
"
|
||
|
||
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.ZY_SERVER_HOST }}
|
||
USER: ${{ secrets.ZY_SERVER_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.ZY_SERVER_HOST }}
|
||
USER: ${{ secrets.ZY_SERVER_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"
|
||
env:
|
||
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 }}
|
||
run: |
|
||
export TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||
|
||
# 读取现有状态文件或初始化
|
||
export STATUS_FILE="data/deploy-status.json"
|
||
mkdir -p data
|
||
[ -f "$STATUS_FILE" ] || echo '{}' > "$STATUS_FILE"
|
||
|
||
# 更新状态
|
||
python3 << 'EOF'
|
||
import json, os
|
||
status_file = os.environ.get('STATUS_FILE', 'data/deploy-status.json')
|
||
dev_id = os.environ['DEV_ID']
|
||
module = os.environ['MODULE']
|
||
result = os.environ.get('RESULT', '')
|
||
detail = os.environ.get('DETAIL', '')
|
||
timestamp = os.environ.get('TIMESTAMP', '')
|
||
commit = os.environ.get('GITHUB_SHA', '')
|
||
with open(status_file, 'r') as f:
|
||
data = json.load(f)
|
||
data[f'{dev_id}/{module}'] = {
|
||
'dev_id': dev_id,
|
||
'module': module,
|
||
'result': result,
|
||
'detail': detail,
|
||
'timestamp': timestamp,
|
||
'commit': commit
|
||
}
|
||
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.ZY_SMTP_USER }}
|
||
SMTP_PASS: ${{ secrets.ZY_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: |
|
||
python3 << 'PYEOF'
|
||
import smtplib, os
|
||
from email.mime.text import MIMEText
|
||
from email.header import Header
|
||
|
||
smtp_user = os.environ['SMTP_USER']
|
||
smtp_pass = os.environ['SMTP_PASS']
|
||
to_email = os.environ['TO_EMAIL']
|
||
dev_id = os.environ['DEV_ID']
|
||
module = os.environ['MODULE']
|
||
result = os.environ.get('RESULT', '')
|
||
|
||
if result == 'passed':
|
||
subject = f'✅ 部署成功:{dev_id}/{module}'
|
||
body = f'你的模块 {module} 已成功部署到 guanghulab.com\n\n访问地址:https://guanghulab.com/{module}/\n\n铸渊 · 光湖代码守护人格体'
|
||
else:
|
||
subject = f'❌ 部署失败:{dev_id}/{module}'
|
||
body = f'你的模块 {module} 部署失败\n\n请检查你的模块是否符合「服务器部署适配协议 v1.0」\n\n错误详情请看仓库公告栏或 GitHub Actions 日志\n\n铸渊 · 光湖代码守护人格体'
|
||
|
||
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(f'✅ 邮件已发送到 {to_email}')
|
||
except Exception as e:
|
||
print(f'⚠️ 邮件发送失败: {e}')
|
||
PYEOF
|
||
|
||
- name: "📝 同步到 Notion 部署注册表"
|
||
env:
|
||
NOTION_TOKEN: ${{ secrets.ZY_NOTION_TOKEN }}
|
||
FINGERPRINT_DB_ID: ${{ secrets.ZY_NOTION_FINGERPRINT_DB }}
|
||
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 同步失败(不阻断主流程)"
|