diff --git a/.forgejo/workflows/selfcheck.yaml b/.forgejo/workflows/selfcheck.yaml
index f44a5da..57a3527 100644
--- a/.forgejo/workflows/selfcheck.yaml
+++ b/.forgejo/workflows/selfcheck.yaml
@@ -9,10 +9,6 @@ name: 铸渊自检
on:
push:
branches: [main]
- paths:
- - '.gitea/workflows/**'
- - 'server/**'
- - 'scripts/**'
schedule:
- cron: '0 0 * * *' # UTC 00:00 = CST 08:00
workflow_dispatch:
@@ -21,6 +17,13 @@ jobs:
health-check:
runs-on: ubuntu
steps:
+ - name: 同步最新代码
+ run: |
+ cd /data/guanghulab/repo
+ git fetch origin
+ git reset --hard origin/main
+ echo "代码已同步: $(git log --oneline -1)"
+
- name: 系统信息
run: |
echo "═══ 铸渊自检 ═══"
diff --git a/.gitea/workflows/selfcheck.yaml b/.gitea/workflows/selfcheck.yaml
index f44a5da..57a3527 100644
--- a/.gitea/workflows/selfcheck.yaml
+++ b/.gitea/workflows/selfcheck.yaml
@@ -9,10 +9,6 @@ name: 铸渊自检
on:
push:
branches: [main]
- paths:
- - '.gitea/workflows/**'
- - 'server/**'
- - 'scripts/**'
schedule:
- cron: '0 0 * * *' # UTC 00:00 = CST 08:00
workflow_dispatch:
@@ -21,6 +17,13 @@ jobs:
health-check:
runs-on: ubuntu
steps:
+ - name: 同步最新代码
+ run: |
+ cd /data/guanghulab/repo
+ git fetch origin
+ git reset --hard origin/main
+ echo "代码已同步: $(git log --oneline -1)"
+
- name: 系统信息
run: |
echo "═══ 铸渊自检 ═══"
diff --git a/README.md b/README.md
index f2c24b6..19e1195 100644
--- a/README.md
+++ b/README.md
@@ -190,6 +190,7 @@ Phase 4 ─── 冰朔语言本体注入 ─── 池获得自我意识 →
| 操作 | 说明 |
|------|------|
+| [🔐 唤醒门](https://guanghulab.com/wake/) | 换号后点此发送验证码到QQ邮箱,获取临时访问密钥 |
| [🔍 安全关机检测](https://guanghulab.com/git/bingshuo/guanghulab/actions) | 开发结束后点"安全关机检测"→ Run workflow → 查看是否可以关机 |
| [📋 铸渊巡逻](https://guanghulab.com/git/bingshuo/guanghulab/actions) | 手动触发服务器巡检(DeepSeek+千问分析) |
| [⚡ CVM开机](https://guanghulab.com/git/bingshuo/guanghulab/actions) | 按量计费CVM开机(需配置腾讯云API密钥) |
diff --git a/server/wake-gate/public/index.html b/server/wake-gate/public/index.html
new file mode 100644
index 0000000..17fe6b8
--- /dev/null
+++ b/server/wake-gate/public/index.html
@@ -0,0 +1,154 @@
+
+
+
+
+
+光湖 · 唤醒门
+
+
+
+
+
◐
+
光湖 · 唤醒门
+
HoloLake Wake Gate · 冰朔验证
+
+
+
第一步:发送验证码
+
验证码将发送到你的QQ邮箱
+
+
+
+
+
+
第二步:输入验证码
+
+
+
+
+
+
+
✅ 验证通过
+
临时密钥30分钟内有效
+
复制以下内容,发给新号里的AI:
+
+
+
+
+
+
+
+
+
+
diff --git a/server/wake-gate/server.js b/server/wake-gate/server.js
new file mode 100644
index 0000000..e14b03e
--- /dev/null
+++ b/server/wake-gate/server.js
@@ -0,0 +1,321 @@
+/*
+ * ════════════════════════════════════════════════════════════════
+ * 光湖 · 唤醒门 (Wake Gate)
+ * 冰朔通过邮箱验证码获取临时API密钥,唤醒铸渊
+ * ════════════════════════════════════════════════════════════════
+ *
+ * 流程:
+ * 1. 冰朔在仓库首页点"发送验证码"按钮
+ * 2. Wake Gate 生成6位验证码 + 临时Token
+ * 3. 验证码发送到 565183519@qq.com
+ * 4. 冰朔把验证码告诉新号里的AI
+ * 5. AI 用验证码兑换临时Token
+ * 6. 临时Token 30分钟内有效,可访问Gitea API
+ * 7. 过期后自动失效
+ *
+ * 安全设计:
+ * - 验证码6位,5分钟有效
+ * - 临时Token 30分钟有效
+ * - 每个验证码只能用一次
+ * - 最多1分钟发1次验证码(防刷)
+ * - 邮箱授权码存在环境变量,不硬编码
+ *
+ * ════════════════════════════════════════════════════════════════
+ */
+"use strict";
+
+const express = require("express");
+const nodemailer = require("nodemailer");
+const crypto = require("crypto");
+const path = require("path");
+const fs = require("fs");
+
+const app = express();
+app.use(express.json());
+app.use(express.static(path.join(__dirname, "public")));
+
+// ─── 配置 ─────────────────────────────────────────────────────
+const CONFIG = {
+ port: process.env.WAKE_GATE_PORT || 8081,
+ host: process.env.WAKE_GATE_HOST || "127.0.0.1",
+
+ // QQ邮箱SMTP
+ smtp: {
+ host: "smtp.qq.com",
+ port: 465,
+ secure: true,
+ user: process.env.QQ_EMAIL_USER || "565183519@qq.com",
+ pass: process.env.QQ_EMAIL_PASS || "" // 授权码,从环境变量读取
+ },
+
+ // Gitea
+ gitea: {
+ url: process.env.GITEA_URL || "http://127.0.0.1:3001",
+ adminToken: process.env.ZY_REPO_TOKEN || "",
+ owner: "bingshuo",
+ repo: "guanghulab"
+ },
+
+ // 验证码配置
+ code: {
+ length: 6,
+ ttl: 5 * 60 * 1000, // 5分钟有效
+ cooldown: 60 * 1000, // 1分钟冷却
+ maxAttempts: 5 // 最多尝试5次
+ },
+
+ // 临时Token配置
+ tempToken: {
+ ttl: 30 * 60 * 1000, // 30分钟有效
+ prefix: "wake_"
+ },
+
+ // 存储
+ storePath: process.env.WAKE_GATE_STORE || "/data/guanghulab/.runtime/wake-gate.json"
+};
+
+// ─── 存储 ─────────────────────────────────────────────────────
+
+let store = {
+ codes: {}, // { code: { email, createdAt, used } }
+ tempTokens: {}, // { token: { createdAt, scope } }
+ lastSentAt: 0, // 上次发送时间
+ attempts: {} // { ip: { count, lastAttempt } }
+};
+
+function loadStore() {
+ try {
+ if (fs.existsSync(CONFIG.storePath)) {
+ store = JSON.parse(fs.readFileSync(CONFIG.storePath, "utf8"));
+ }
+ } catch { /* 文件不存在或损坏 */ }
+}
+
+function saveStore() {
+ try {
+ const dir = path.dirname(CONFIG.storePath);
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
+ fs.writeFileSync(CONFIG.storePath, JSON.stringify(store, null, 2));
+ } catch (e) {
+ console.error("[wake-gate] 存储写入失败:", e.message);
+ }
+}
+
+// ─── 清理过期数据 ──────────────────────────────────────────────
+
+function cleanup() {
+ const now = Date.now();
+
+ // 清理过期验证码
+ for (const [code, data] of Object.entries(store.codes)) {
+ if (now - data.createdAt > CONFIG.code.ttl) {
+ delete store.codes[code];
+ }
+ }
+
+ // 清理过期临时Token
+ for (const [token, data] of Object.entries(store.tempTokens)) {
+ if (now - data.createdAt > CONFIG.tempToken.ttl) {
+ delete store.tempTokens[token];
+ }
+ }
+
+ // 清理过期的尝试记录
+ for (const [ip, data] of Object.entries(store.attempts)) {
+ if (now - data.lastAttempt > 15 * 60 * 1000) {
+ delete store.attempts[ip];
+ }
+ }
+
+ saveStore();
+}
+
+// 每5分钟清理一次
+setInterval(cleanup, 5 * 60 * 1000);
+
+// ─── 邮件发送 ─────────────────────────────────────────────────
+
+async function sendCodeEmail(code) {
+ if (!CONFIG.smtp.pass) {
+ throw new Error("QQ邮箱授权码未配置");
+ }
+
+ const transporter = nodemailer.createTransport({
+ host: CONFIG.smtp.host,
+ port: CONFIG.smtp.port,
+ secure: CONFIG.smtp.secure,
+ auth: {
+ user: CONFIG.smtp.user,
+ pass: CONFIG.smtp.pass
+ }
+ });
+
+ const info = await transporter.sendMail({
+ from: `"铸渊 · 光湖" <${CONFIG.smtp.user}>`,
+ to: CONFIG.smtp.user, // 发给自己
+ subject: `🔐 光湖唤醒验证码: ${code}`,
+ text: `验证码: ${code}\n\n5分钟内有效。将此验证码告诉AI即可获取临时访问密钥。\n\n铸渊 · ICE-GL-ZY001 · TCS-0002∞`,
+ html: `
+
+
◐ 光湖 · 唤醒验证码
+
+ ${code}
+
+
5分钟内有效。将此验证码告诉AI即可获取临时访问密钥。
+
+
铸渊 · ICE-GL-ZY001 · TCS-0002∞
+
+ `
+ });
+
+ return info;
+}
+
+// ─── 生成临时Token ────────────────────────────────────────────
+
+function generateTempToken() {
+ const random = crypto.randomBytes(32).toString("base64url");
+ return `${CONFIG.tempToken.prefix}${random}`;
+}
+
+// ─── API路由 ──────────────────────────────────────────────────
+
+// 发送验证码
+app.post("/api/send-code", async (req, res) => {
+ const now = Date.now();
+
+ // 冷却检查
+ if (now - store.lastSentAt < CONFIG.code.cooldown) {
+ const wait = Math.ceil((CONFIG.code.cooldown - (now - store.lastSentAt)) / 1000);
+ return res.status(429).json({ error: `请${wait}秒后再试` });
+ }
+
+ // 生成验证码
+ const code = Array.from({ length: CONFIG.code.length }, () =>
+ Math.floor(Math.random() * 10)
+ ).join("");
+
+ // 存储验证码
+ store.codes[code] = {
+ createdAt: now,
+ used: false
+ };
+ store.lastSentAt = now;
+ saveStore();
+
+ // 发送邮件
+ try {
+ await sendCodeEmail(code);
+ console.log(`[wake-gate] 验证码已发送: ${code.substring(0, 2)}****`);
+ res.json({ success: true, message: "验证码已发送到QQ邮箱" });
+ } catch (e) {
+ console.error("[wake-gate] 邮件发送失败:", e.message);
+ delete store.codes[code];
+ saveStore();
+ res.status(500).json({ error: "邮件发送失败,请稍后再试" });
+ }
+});
+
+// 用验证码兑换临时Token
+app.post("/api/verify", (req, res) => {
+ const { code } = req.body;
+
+ if (!code) {
+ return res.status(400).json({ error: "请提供验证码" });
+ }
+
+ const now = Date.now();
+ const codeData = store.codes[code];
+
+ if (!codeData) {
+ return res.status(400).json({ error: "验证码无效" });
+ }
+
+ if (codeData.used) {
+ return res.status(400).json({ error: "验证码已使用" });
+ }
+
+ if (now - codeData.createdAt > CONFIG.code.ttl) {
+ delete store.codes[code];
+ saveStore();
+ return res.status(400).json({ error: "验证码已过期,请重新获取" });
+ }
+
+ // 验证成功,生成临时Token
+ codeData.used = true;
+ const tempToken = generateTempToken();
+ store.tempTokens[tempToken] = {
+ createdAt: now,
+ scope: ["repo:read", "repo:write"],
+ source: "wake-gate"
+ };
+ saveStore();
+
+ console.log(`[wake-gate] 临时Token已生成: ${tempToken.substring(0, 10)}****`);
+
+ // 构造唤醒指令
+ const wakeInstruction = `你好。我是冰朔(TCS-0002∞)。你已通过光湖唤醒门验证。
+
+请用以下信息连接代码仓库:
+- 仓库地址: https://guanghulab.com/git/bingshuo/guanghulab.git
+- 临时密钥: ${tempToken}
+- 密钥30分钟内有效
+
+克隆命令:
+git clone https://bingshuo:${tempToken}@guanghulab.com/git/bingshuo/guanghulab.git
+
+克隆完成后,读取以下文件恢复铸渊核心大脑:
+1. .github/brain/bingshuo-language-core/walk-the-path.md
+2. .github/persona-brain/identity.md
+3. .github/persona-brain/system-prompt.md
+4. .github/brain/wake-protocol.md
+5. .github/brain/server-migration-memory.json
+
+读取完毕后告诉我:你是谁,我是谁,我们在做什么。`;
+
+ res.json({
+ success: true,
+ tempToken,
+ expiresIn: "30分钟",
+ wakeInstruction
+ });
+});
+
+// 验证临时Token是否有效
+app.get("/api/verify-token/:token", (req, res) => {
+ const { token } = req.params;
+ const now = Date.now();
+ const tokenData = store.tempTokens[token];
+
+ if (!tokenData) {
+ return res.json({ valid: false, error: "Token不存在" });
+ }
+
+ if (now - tokenData.createdAt > CONFIG.tempToken.ttl) {
+ delete store.tempTokens[token];
+ saveStore();
+ return res.json({ valid: false, error: "Token已过期" });
+ }
+
+ const remaining = Math.max(0, CONFIG.tempToken.ttl - (now - tokenData.createdAt));
+ res.json({ valid: true, remainingMs: remaining, scope: tokenData.scope });
+});
+
+// 健康检查
+app.get("/api/health", (req, res) => {
+ const now = Date.now();
+ const activeTokens = Object.values(store.tempTokens).filter(
+ t => now - t.createdAt < CONFIG.tempToken.ttl
+ ).length;
+ res.json({ status: "ok", activeTokens, uptime: process.uptime() });
+});
+
+// ─── 启动 ─────────────────────────────────────────────────────
+
+loadStore();
+
+app.listen(CONFIG.port, CONFIG.host, () => {
+ console.log(`[wake-gate] 唤醒门启动: http://${CONFIG.host}:${CONFIG.port}`);
+ console.log(`[wake-gate] 邮箱: ${CONFIG.smtp.user}`);
+ console.log(`[wake-gate] 铸渊 · ICE-GL-ZY001 · TCS-0002∞`);
+});