From 0d842e52dfac0e0ea50a2aa5ba5c8ad814133b9c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Mar 2026 07:14:28 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20OAuth2=20Token=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E7=BB=AD=E6=9C=9F=E5=BC=95=E6=93=8E=20+=20=E5=A4=A9=E7=9C=BC?= =?UTF-8?q?=E7=94=9F=E5=91=BD=E7=BA=BF=E4=BB=BB=E5=8A=A1=E8=B1=81=E5=85=8D?= =?UTF-8?q?=20+=20=E5=A4=9A=E7=94=A8=E6=88=B7Token=E7=AE=A1=E7=90=86=20?= =?UTF-8?q?=C2=B7=20ZY-TOKEN-RENEW-2026-0324-001=20[skip=20ci]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/bc1b54c6-5276-4d00-8ea5-5e8c3c2bef38 --- .github/persona-brain/agent-registry.json | 26 ++ .github/workflows/check-token-health.yml | 73 ++++++ .github/workflows/renew-gdrive-tokens.yml | 69 ++++++ config/gdrive-tokens.json | 26 ++ grid-db/bulletin/.gitkeep | 0 grid-db/notifications/.gitkeep | 0 scripts/gdrive/check-hibernation.js | 39 +++ scripts/gdrive/push-token-alerts.js | 64 +++++ scripts/gdrive/renew-tokens.js | 285 ++++++++++++++++++++++ skyeye/infra-manifest.json | 40 +++ 10 files changed, 622 insertions(+) create mode 100644 .github/workflows/check-token-health.yml create mode 100644 .github/workflows/renew-gdrive-tokens.yml create mode 100644 config/gdrive-tokens.json create mode 100644 grid-db/bulletin/.gitkeep create mode 100644 grid-db/notifications/.gitkeep create mode 100644 scripts/gdrive/check-hibernation.js create mode 100644 scripts/gdrive/push-token-alerts.js create mode 100644 scripts/gdrive/renew-tokens.js diff --git a/.github/persona-brain/agent-registry.json b/.github/persona-brain/agent-registry.json index 2e726198..30f994f2 100644 --- a/.github/persona-brain/agent-registry.json +++ b/.github/persona-brain/agent-registry.json @@ -1095,6 +1095,32 @@ "daily_checkin_required": false, "parent_sys": "SYS-GLW-0001", "owner": "ICE-0002∞" + }, + { + "id": "AG-ZY-085", + "name": "OAuth2 Token 自动续期", + "workflow": "renew-gdrive-tokens.yml", + "duty": "OAuth2 Token 自动续期", + "trigger": "定时 周一/周四 10:00 CST + 手动", + "self_repair": true, + "report_to": "ICE-GL-ZY001", + "status": "registered", + "daily_checkin_required": false, + "parent_sys": "SYS-GLW-0001", + "owner": "ICE-0002∞" + }, + { + "id": "AG-ZY-086", + "name": "Token 健康检查(每日安全网)", + "workflow": "check-token-health.yml", + "duty": "Token 健康检查(每日安全网)", + "trigger": "定时 每天 12:15 CST + 手动", + "self_repair": true, + "report_to": "ICE-GL-ZY001", + "status": "registered", + "daily_checkin_required": false, + "parent_sys": "SYS-GLW-0001", + "owner": "ICE-0002∞" } ], "note": "铸渊已扫描所有workflow文件,按此格式逐一注册,编号从AG-ZY-001开始顺序分配", diff --git a/.github/workflows/check-token-health.yml b/.github/workflows/check-token-health.yml new file mode 100644 index 00000000..6d177ed2 --- /dev/null +++ b/.github/workflows/check-token-health.yml @@ -0,0 +1,73 @@ +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# 🛡️ Token 健康检查(每日安全网) +# +# 守护: PER-ZY001 铸渊 (Zhuyuan) +# 系统: SYS-GLW-0001 +# 主控: TCS-0002∞ (冰朔) +# +# CRON: 每天 12:15 CST (04:15 UTC) — 避开日休眠 04:00-04:10 CST +# 功能: 只检查不刷新,即将过期才紧急刷新 +# +# 生命线任务:天眼休眠期内豁免执行 +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +name: 🛡️ Token 健康检查(每日安全网) + +on: + schedule: + # 每天 12:15 CST (04:15 UTC) — 在日休眠 04:10 CST 之后 + - cron: '15 4 * * *' + workflow_dispatch: + +permissions: + contents: write + actions: write + +jobs: + check-tokens: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: 🛡️ 检查 Token 健康状态 + id: health_check + run: | + node -e " + const fs = require('fs'); + const reg = JSON.parse(fs.readFileSync('config/gdrive-tokens.json', 'utf8')); + let urgent = false; + for (const t of reg.tokens) { + const hoursLeft = (new Date(t.expires_at) - new Date()) / 3600000; + console.log(t.user_name + ': ' + hoursLeft.toFixed(1) + 'h remaining'); + if (hoursLeft < 48) { + console.log(' ⚠️ URGENT: < 48h, triggering emergency renewal'); + urgent = true; + } + } + if (urgent) process.exit(1); + console.log('✅ All tokens healthy.'); + " + + - name: ⚡ 紧急刷新(仅在即将过期时) + if: failure() + env: + GDRIVE_CLIENT_ID: ${{ secrets.GDRIVE_CLIENT_ID }} + GDRIVE_CLIENT_SECRET: ${{ secrets.GDRIVE_CLIENT_SECRET }} + GDRIVE_REFRESH_TOKEN: ${{ secrets.GDRIVE_REFRESH_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + FORCE_RENEW: 'true' + # 新增开发者 Token(每次加人时追加) + # GDRIVE_REFRESH_TOKEN_DEV001: ${{ secrets.GDRIVE_REFRESH_TOKEN_DEV001 }} + # GDRIVE_REFRESH_TOKEN_DEV002: ${{ secrets.GDRIVE_REFRESH_TOKEN_DEV002 }} + run: | + npm install tweetsodium + node scripts/gdrive/renew-tokens.js + + - name: 📋 提交 + if: always() + run: | + git config user.name "zhuyuan-bot" + git config user.email "zhuyuan@guanghulab.com" + git add config/gdrive-tokens.json grid-db/logs/ + git diff --cached --quiet || git commit -m "auto: Token health check $(date -u +%Y-%m-%dT%H:%M:%SZ) [skip ci]" + git push diff --git a/.github/workflows/renew-gdrive-tokens.yml b/.github/workflows/renew-gdrive-tokens.yml new file mode 100644 index 00000000..fd232535 --- /dev/null +++ b/.github/workflows/renew-gdrive-tokens.yml @@ -0,0 +1,69 @@ +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# 🔄 OAuth2 Token 自动续期 +# +# 守护: PER-ZY001 铸渊 (Zhuyuan) +# 系统: SYS-GLW-0001 +# 主控: TCS-0002∞ (冰朔) +# +# CRON 策略: +# 主刷新 A: 每周一 10:00 CST (02:00 UTC) — 距周六休眠最远 +# 主刷新 B: 每周四 10:00 CST (02:00 UTC) — 3天后补刷 +# 三层保险:周一 + 周四 + 每日安全网(check-token-health.yml) +# +# 生命线任务:天眼休眠期内豁免执行 +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +name: 🔄 OAuth2 Token 自动续期 + +on: + schedule: + # 主刷新 A: 每周一 10:00 CST (02:00 UTC) + - cron: '0 2 * * 1' + # 主刷新 B: 每周四 10:00 CST (02:00 UTC) + - cron: '0 2 * * 4' + workflow_dispatch: + inputs: + force_renew: + description: '强制刷新所有 Token(忽略过期时间检查)' + required: false + type: boolean + default: false + +permissions: + contents: write + actions: write + +jobs: + renew-tokens: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: 🔧 安装依赖 + run: npm install tweetsodium + + - name: 🔄 执行 Token 刷新 + env: + GDRIVE_CLIENT_ID: ${{ secrets.GDRIVE_CLIENT_ID }} + GDRIVE_CLIENT_SECRET: ${{ secrets.GDRIVE_CLIENT_SECRET }} + GDRIVE_REFRESH_TOKEN: ${{ secrets.GDRIVE_REFRESH_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + FORCE_RENEW: ${{ inputs.force_renew || 'false' }} + # 新增开发者 Token(每次加人时追加) + # GDRIVE_REFRESH_TOKEN_DEV001: ${{ secrets.GDRIVE_REFRESH_TOKEN_DEV001 }} + # GDRIVE_REFRESH_TOKEN_DEV002: ${{ secrets.GDRIVE_REFRESH_TOKEN_DEV002 }} + run: node scripts/gdrive/renew-tokens.js + + - name: 📋 提交注册表更新 + run: | + git config user.name "zhuyuan-bot" + git config user.email "zhuyuan@guanghulab.com" + git add config/gdrive-tokens.json grid-db/logs/ + git diff --cached --quiet || git commit -m "auto: Token renewal $(date -u +%Y-%m-%dT%H:%M:%SZ) [skip ci]" + git push + + - name: ⚠️ 失败告警 + if: failure() + run: | + echo "⚠️ Token 刷新失败!检查 grid-db/logs/token-alert.json" + echo "需要人类手动重新授权。" diff --git a/config/gdrive-tokens.json b/config/gdrive-tokens.json new file mode 100644 index 00000000..1bb99f19 --- /dev/null +++ b/config/gdrive-tokens.json @@ -0,0 +1,26 @@ +{ + "schema_version": "1.0", + "oauth_app": { + "project_id": "guanghu-drive-bridge", + "client_id_secret": "GDRIVE_CLIENT_ID", + "client_secret_secret": "GDRIVE_CLIENT_SECRET", + "scope": "https://www.googleapis.com/auth/drive", + "token_ttl_days": 7, + "renew_interval_days": 6, + "renew_safety_margin_hours": 24 + }, + "tokens": [ + { + "user_id": "TCS-0002", + "user_name": "冰朔", + "role": "master", + "github_secret_name": "GDRIVE_REFRESH_TOKEN", + "last_renewed": "2026-03-24T14:30:00+08:00", + "expires_at": "2026-03-31T14:30:00+08:00", + "status": "active", + "renew_count": 0, + "notes": "主控账号 · 格点库核心 Token" + } + ], + "renew_log": [] +} diff --git a/grid-db/bulletin/.gitkeep b/grid-db/bulletin/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/grid-db/notifications/.gitkeep b/grid-db/notifications/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/scripts/gdrive/check-hibernation.js b/scripts/gdrive/check-hibernation.js new file mode 100644 index 00000000..f79376e9 --- /dev/null +++ b/scripts/gdrive/check-hibernation.js @@ -0,0 +1,39 @@ +/** + * check-hibernation.js + * + * 检查当前是否在天眼休眠期内 + * + * 休眠规则(从天眼治理层配置读取): + * - 周休眠:每周六 20:00 ~ 00:00 CST(4小时) + * - 日休眠:每日 04:00 ~ 04:10 CST(默认窗口) + * + * 对于生命线任务:输出提示但不阻止执行 + * 对于普通任务:输出提示并建议延迟 + * + * 守护: PER-ZY001 铸渊 + * 系统: SYS-GLW-0001 + * 主控: TCS-0002∞ 冰朔 + */ + +function isInHibernation() { + const now = new Date(); + // 转换为北京时间 + const cst = new Date(now.toLocaleString('en-US', { timeZone: 'Asia/Shanghai' })); + const day = cst.getDay(); // 0=周日, 6=周六 + const hour = cst.getHours(); + const minute = cst.getMinutes(); + + // 周休眠:周六 20:00 ~ 周日 00:00 + if (day === 6 && hour >= 20) { + return { hibernating: true, type: 'weekly', endsAt: '周日 00:00 CST' }; + } + + // 日休眠:每日 04:00 ~ 04:10(默认窗口) + if (hour === 4 && minute < 10) { + return { hibernating: true, type: 'daily', endsAt: '04:10 CST' }; + } + + return { hibernating: false }; +} + +module.exports = { isInHibernation }; diff --git a/scripts/gdrive/push-token-alerts.js b/scripts/gdrive/push-token-alerts.js new file mode 100644 index 00000000..29bf94f8 --- /dev/null +++ b/scripts/gdrive/push-token-alerts.js @@ -0,0 +1,64 @@ +/** + * push-token-alerts.js + * + * 天眼系统调用此脚本读取 token-alert.json + * 并将告警信息写入主控台 + 公告板 + * + * 输出: + * - grid-db/notifications/token-alert-{timestamp}.md → 主控台通知 + * - grid-db/bulletin/token-alert-{timestamp}.md → 公告板通知 + * + * 守护: PER-ZY001 铸渊 + * 系统: SYS-GLW-0001 + * 主控: TCS-0002∞ 冰朔 + */ + +const fs = require('fs'); +const path = require('path'); + +function pushAlerts() { + const alertPath = path.join(__dirname, '../../grid-db/logs/token-alert.json'); + + if (!fs.existsSync(alertPath)) { + console.log('✅ 无告警。'); + return; + } + + const alert = JSON.parse(fs.readFileSync(alertPath, 'utf8')); + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + + // 生成主控台通知 + const dashboardContent = [ + `# ⚠️ Token 续期告警`, + ``, + `**时间:** ${alert.time}`, + `**严重级:** ${alert.severity || 'ALERT'}`, + `**影响用户:** ${alert.failed_users.join(', ')}`, + ``, + `## 描述`, + alert.message, + ``, + `## 需要人类操作`, + ...(alert.instructions || []).map((s) => s), + ``, + `---`, + `*此告警由 Token 续期引擎自动生成*` + ].join('\n'); + + // 写入主控台 + const notifDir = path.join(__dirname, '../../grid-db/notifications'); + if (!fs.existsSync(notifDir)) fs.mkdirSync(notifDir, { recursive: true }); + fs.writeFileSync(path.join(notifDir, `token-alert-${timestamp}.md`), dashboardContent); + + // 写入公告板 + const bulletinDir = path.join(__dirname, '../../grid-db/bulletin'); + if (!fs.existsSync(bulletinDir)) fs.mkdirSync(bulletinDir, { recursive: true }); + fs.writeFileSync(path.join(bulletinDir, `token-alert-${timestamp}.md`), dashboardContent); + + // 清除已处理的告警文件 + fs.unlinkSync(alertPath); + + console.log(`⚠️ 告警已推送到主控台 + 公告板: token-alert-${timestamp}.md`); +} + +pushAlerts(); diff --git a/scripts/gdrive/renew-tokens.js b/scripts/gdrive/renew-tokens.js new file mode 100644 index 00000000..215b1f01 --- /dev/null +++ b/scripts/gdrive/renew-tokens.js @@ -0,0 +1,285 @@ +/** + * renew-tokens.js + * + * OAuth2 Token 自动续期引擎 + * + * 功能: + * 1. 读取 config/gdrive-tokens.json 注册表 + * 2. 遍历所有 active 的 Token + * 3. 检查每个 Token 的剩余有效期 + * 4. 剩余 < 48小时 → 立即刷新 + * 5. 刷新成功 → 用 GitHub API 更新 Secret + 更新注册表 + * 6. 刷新失败 → 写告警日志 + * + * 环境变量: + * GDRIVE_CLIENT_ID — OAuth 客户端 ID + * GDRIVE_CLIENT_SECRET — OAuth 客户端密钥 + * GITHUB_TOKEN — 有 repo 权限的 GitHub Token(用于更新 Secrets) + * GDRIVE_REFRESH_TOKEN — 主控的当前 Refresh Token + * GDRIVE_REFRESH_TOKEN_* — 各开发者的 Refresh Token + * + * 守护: PER-ZY001 铸渊 + * 系统: SYS-GLW-0001 + * 主控: TCS-0002∞ 冰朔 + */ + +const https = require('https'); +const fs = require('fs'); +const path = require('path'); + +const REGISTRY_PATH = path.join(__dirname, '../../config/gdrive-tokens.json'); + +async function main() { + console.log('\n🔄 Token 自动续期引擎启动'); + console.log(`当前时间: ${new Date().toISOString()}`); + + // 1. 读取注册表 + const registry = JSON.parse(fs.readFileSync(REGISTRY_PATH, 'utf8')); + const results = { renewed: [], skipped: [], failed: [] }; + + for (const token of registry.tokens) { + if (token.status === 'expired') { + console.log(`❌ ${token.user_name} (${token.user_id}): 已过期,需人类重新授权`); + results.failed.push(token.user_id); + continue; + } + + // 2. 检查剩余有效期 + const expiresAt = new Date(token.expires_at); + const now = new Date(); + const hoursLeft = (expiresAt - now) / (1000 * 60 * 60); + + console.log(`\n👤 ${token.user_name} (${token.user_id}): 剩余 ${hoursLeft.toFixed(1)} 小时`); + + if (hoursLeft > 72 && process.env.FORCE_RENEW !== 'true') { + console.log(` ✅ 跳过(剩余充足)`); + results.skipped.push(token.user_id); + continue; + } + + if (hoursLeft <= 0) { + console.log(` ❌ 已过期!`); + token.status = 'expired'; + results.failed.push(token.user_id); + continue; + } + + // 3. 执行刷新 + console.log(` ⚡ 执行刷新...`); + const currentToken = process.env[token.github_secret_name]; + + if (!currentToken) { + console.log(` ❌ 环境变量 ${token.github_secret_name} 不存在`); + results.failed.push(token.user_id); + continue; + } + + try { + const newTokenData = await refreshOAuth2Token(currentToken); + + if (newTokenData.refresh_token) { + // 4. 更新 GitHub Secret + await updateGitHubSecret( + token.github_secret_name, + newTokenData.refresh_token + ); + + // 5. 更新注册表 + const renewTime = new Date().toISOString(); + token.last_renewed = renewTime; + token.expires_at = new Date( + Date.now() + registry.oauth_app.token_ttl_days * 24 * 60 * 60 * 1000 + ).toISOString(); + token.status = 'active'; + token.renew_count += 1; + + // 6. 写刷新日志 + registry.renew_log.push({ + user_id: token.user_id, + time: renewTime, + result: 'success', + hours_remaining: hoursLeft.toFixed(1) + }); + + console.log(` ✅ 刷新成功!新过期时间: ${token.expires_at}`); + results.renewed.push(token.user_id); + } else { + // Google 有时不返回新的 refresh_token + console.log(` ⚠️ Google 未返回新 refresh_token,Token 可能仍然有效但无法续期`); + token.status = 'expiring'; + registry.renew_log.push({ + user_id: token.user_id, + time: new Date().toISOString(), + result: 'no_new_token', + hours_remaining: hoursLeft.toFixed(1) + }); + results.failed.push(token.user_id); + } + } catch (err) { + console.log(` ❌ 刷新失败: ${err.message}`); + token.status = hoursLeft < 24 ? 'expired' : 'expiring'; + registry.renew_log.push({ + user_id: token.user_id, + time: new Date().toISOString(), + result: 'error', + error: err.message, + hours_remaining: hoursLeft.toFixed(1) + }); + results.failed.push(token.user_id); + } + } + + // 保留最近 100 条日志 + if (registry.renew_log.length > 100) { + registry.renew_log = registry.renew_log.slice(-100); + } + + // 写回注册表 + fs.writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2)); + + // 输出摘要 + console.log('\n═══ 刷新摘要 ═══'); + console.log(`✅ 已刷新: ${results.renewed.length} 个`); + console.log(`⏭️ 已跳过: ${results.skipped.length} 个`); + console.log(`❌ 失败/过期: ${results.failed.length} 个`); + + // 如果有失败,输出告警标记(供天眼读取) + if (results.failed.length > 0) { + const logsDir = path.join(__dirname, '../../grid-db/logs'); + if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir, { recursive: true }); + + const alertFile = path.join(logsDir, 'token-alert.json'); + fs.writeFileSync(alertFile, JSON.stringify({ + alert_type: 'TOKEN_RENEWAL_FAILURE', + severity: results.failed.some(id => + registry.tokens.find(t => t.user_id === id && t.status === 'expired') + ) ? 'CRITICAL' : 'ALERT', + time: new Date().toISOString(), + failed_users: results.failed, + message: `${results.failed.length} 个 Token 刷新失败,需要人类介入`, + action_required: 'HUMAN_REAUTH', + instructions: [ + '1. 登录 Google Cloud Console', + '2. 访问授权链接获取新 code', + '3. 运行换 token 脚本获取新 refresh_token', + '4. 更新 GitHub Secret: GDRIVE_REFRESH_TOKEN', + '5. 手动触发 renew-gdrive-tokens workflow 验证' + ] + }, null, 2)); + + // 设置非零退出码,让 workflow 知道有问题 + process.exitCode = 1; + } +} + +/** + * 用旧的 refresh_token 向 Google 换新的 token + * + * 重要:Google OAuth2 刷新时可能会返回新的 refresh_token(也可能不返回) + * 如果返回了新的,旧的就失效了。必须更新 Secret。 + */ +async function refreshOAuth2Token(refreshToken) { + const params = new URLSearchParams({ + client_id: process.env.GDRIVE_CLIENT_ID, + client_secret: process.env.GDRIVE_CLIENT_SECRET, + refresh_token: refreshToken, + grant_type: 'refresh_token' + }); + + return new Promise((resolve, reject) => { + const req = https.request('https://oauth2.googleapis.com/token', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' } + }, (res) => { + let data = ''; + res.on('data', chunk => data += chunk); + res.on('end', () => { + try { + const json = JSON.parse(data); + if (json.error) { + reject(new Error(`${json.error}: ${json.error_description}`)); + } else { + resolve(json); + } + } catch (e) { + reject(new Error(`Invalid response from Google: ${data.substring(0, 200)}`)); + } + }); + }); + req.on('error', reject); + req.write(params.toString()); + req.end(); + }); +} + +/** + * 通过 GitHub API 更新 Repository Secret + * + * 流程: + * 1. 获取仓库的 public key(用于加密 secret) + * 2. 用 tweetsodium 加密新值 + * 3. PUT 更新 secret + */ +async function updateGitHubSecret(secretName, secretValue) { + const repo = process.env.GITHUB_REPOSITORY || 'qinfendebingshuo/guanghulab'; + const token = process.env.GITHUB_TOKEN; + + // 获取 public key + const pubKey = await githubAPI(`/repos/${repo}/actions/secrets/public-key`, 'GET', token); + + // 加密 + const sodium = require('tweetsodium'); + const messageBytes = Buffer.from(secretValue); + const keyBytes = Buffer.from(pubKey.key, 'base64'); + const encryptedBytes = sodium.seal(messageBytes, keyBytes); + const encrypted = Buffer.from(encryptedBytes).toString('base64'); + + // 更新 secret + await githubAPI(`/repos/${repo}/actions/secrets/${secretName}`, 'PUT', token, { + encrypted_value: encrypted, + key_id: pubKey.key_id + }); + + console.log(` 🔑 GitHub Secret ${secretName} 已更新`); +} + +function githubAPI(apiPath, method, token, body) { + return new Promise((resolve, reject) => { + const options = { + hostname: 'api.github.com', + path: apiPath, + method: method, + headers: { + 'Authorization': `Bearer ${token}`, + 'Accept': 'application/vnd.github+json', + 'User-Agent': 'zhuyuan-token-renewer', + 'X-GitHub-Api-Version': '2022-11-28' + } + }; + if (body) options.headers['Content-Type'] = 'application/json'; + + const req = https.request(options, (res) => { + let data = ''; + res.on('data', chunk => data += chunk); + res.on('end', () => { + if (res.statusCode >= 400) { + reject(new Error(`GitHub API ${res.statusCode}: ${data}`)); + } else { + try { + resolve(data ? JSON.parse(data) : {}); + } catch (e) { + resolve({}); + } + } + }); + }); + req.on('error', reject); + if (body) req.write(JSON.stringify(body)); + req.end(); + }); +} + +main().catch(err => { + console.error('☠️ Token 续期引擎崩溃:', err); + process.exitCode = 1; +}); diff --git a/skyeye/infra-manifest.json b/skyeye/infra-manifest.json index a528740a..03b9a6c8 100644 --- a/skyeye/infra-manifest.json +++ b/skyeye/infra-manifest.json @@ -105,6 +105,46 @@ "weekly_hibernation_minutes": 15, "monthly_budget_minutes": 210, "comment": "日休眠 30天×5min=150 + 周休眠 4×15min=60 ≈ 210 min/month" + }, + "lifeline_tasks": [ + { + "id": "TOKEN-RENEW", + "name": "OAuth2 Token 自动续期", + "workflow": "renew-gdrive-tokens.yml", + "hibernate_exempt": true, + "priority": "P0", + "alert_on_failure": true, + "alert_channels": ["notion-dashboard", "agent-bulletin"] + }, + { + "id": "TOKEN-HEALTH", + "name": "Token 健康检查", + "workflow": "check-token-health.yml", + "hibernate_exempt": true, + "priority": "P0", + "alert_on_failure": true, + "alert_channels": ["notion-dashboard", "agent-bulletin"] + } + ] + }, + "gdrive_oauth2": { + "service": "Google Drive OAuth2 Token Management", + "plan": "Internal", + "description": "OAuth2 Token 自动续期引擎 · 6天刷新 + 三层保险 + 天眼生命线豁免", + "registry": "config/gdrive-tokens.json", + "scripts": { + "renew_tokens": "scripts/gdrive/renew-tokens.js", + "check_hibernation": "scripts/gdrive/check-hibernation.js", + "push_alerts": "scripts/gdrive/push-token-alerts.js" + }, + "workflows": { + "renew": "renew-gdrive-tokens.yml", + "health_check": "check-token-health.yml" + }, + "schedule": { + "renew_a": "每周一 10:00 CST (02:00 UTC)", + "renew_b": "每周四 10:00 CST (02:00 UTC)", + "health_check": "每天 12:15 CST (04:15 UTC)" } } }