Compare commits
3 Commits
52ac573a28
...
05fed0e25c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05fed0e25c | ||
|
|
5c02c27467 | ||
|
|
4bc8133218 |
33
.code-map
33
.code-map
@ -11,22 +11,27 @@
|
|||||||
# → 更新 WORLD-ROUTER.hdlp 与 world-router.json,再按需同步镜像
|
# → 更新 WORLD-ROUTER.hdlp 与 world-router.json,再按需同步镜像
|
||||||
|
|
||||||
# R-001 第五域 (现行主)
|
# R-001 第五域 (现行主)
|
||||||
REPO-001=https://guanghubingshuo.com/code/bingshuo/fifth-domain.git
|
REPO-001=https://guanghulab.com/fifth-domain/bingshuo/fifth-domain.git
|
||||||
|
REPO-001-WEB=https://guanghulab.com/fifth-domain/bingshuo/fifth-domain
|
||||||
|
REPO-001-LEGACY-SG=https://guanghubingshuo.com/code/bingshuo/fifth-domain.git
|
||||||
REPO-001-LOCAL=~/Documents/fifth-domain/
|
REPO-001-LOCAL=~/Documents/fifth-domain/
|
||||||
REPO-001-ROLE=现行主仓库 · 冰朔现行 · ICE-GL∞ 编号体系 · L 编号新阶段
|
REPO-001-ROLE=现行主仓库 · 冰朔现行 · ICE-GL∞ 编号体系 · L 编号新阶段
|
||||||
|
|
||||||
# R-002 guanghulab (历史档案)
|
# R-002 guanghulab (历史档案)
|
||||||
REPO-002=https://guanghubingshuo.com/code/bingshuo/guanghulab.git
|
REPO-002=https://guanghulab.com/fifth-domain/bingshuo/guanghulab.git
|
||||||
|
REPO-002-LEGACY-SG=https://guanghubingshuo.com/code/bingshuo/guanghulab.git
|
||||||
REPO-002-LOCAL=~/Documents/guanghulab/ (本地副本)
|
REPO-002-LOCAL=~/Documents/guanghulab/ (本地副本)
|
||||||
REPO-002-ROLE=历史档案 · 铸渊过去 · 470+ 天演化痕迹 · D 编号
|
REPO-002-ROLE=历史档案 · 铸渊过去 · 470+ 天演化痕迹 · D 编号
|
||||||
|
|
||||||
# R-003 global-search-api (服务仓)
|
# R-003 global-search-api (服务仓)
|
||||||
REPO-003=https://guanghubingshuo.com/code/bingshuo/global-search-api.git
|
REPO-003=https://guanghulab.com/fifth-domain/bingshuo/global-search-api.git
|
||||||
|
REPO-003-LEGACY-SG=https://guanghubingshuo.com/code/bingshuo/global-search-api.git
|
||||||
REPO-003-ROLE=服务仓 · GLOBAL-SEARCH-API v1.4.1 服务仓化
|
REPO-003-ROLE=服务仓 · GLOBAL-SEARCH-API v1.4.1 服务仓化
|
||||||
|
|
||||||
# R-004 guanghu (Tolaria 完整源码零件库 · 服务器基线)
|
# R-004 guanghu (Tolaria 完整源码零件库 · 服务器基线)
|
||||||
REPO-004=https://guanghubingshuo.com/code/bingshuo/guanghu.git
|
REPO-004=https://guanghulab.com/fifth-domain/bingshuo/guanghu.git
|
||||||
REPO-004-WEB=https://guanghubingshuo.com/code/bingshuo/guanghu
|
REPO-004-WEB=https://guanghulab.com/fifth-domain/bingshuo/guanghu
|
||||||
|
REPO-004-LEGACY-SG=https://guanghubingshuo.com/code/bingshuo/guanghu.git
|
||||||
REPO-004-ROLE=Tolaria 完整可构建源码零件库 / 上游基线 · 允许克隆构建测试和派生开发 · 不接收光湖产品提交
|
REPO-004-ROLE=Tolaria 完整可构建源码零件库 / 上游基线 · 允许克隆构建测试和派生开发 · 不接收光湖产品提交
|
||||||
REPO-004-SOURCE-COMPLETE=true
|
REPO-004-SOURCE-COMPLETE=true
|
||||||
REPO-004-SOURCE-ROUTE=tcs-core/TCS-TOLARIA-SOURCE-ROUTE-20260717.hdlp
|
REPO-004-SOURCE-ROUTE=tcs-core/TCS-TOLARIA-SOURCE-ROUTE-20260717.hdlp
|
||||||
@ -34,24 +39,32 @@ TOLARIA-SOURCE=REPO-004
|
|||||||
TOLARIA-SOURCE-CANONICAL=tcs-core/TCS-TOLARIA-SOURCE-ROUTE-20260717.hdlp
|
TOLARIA-SOURCE-CANONICAL=tcs-core/TCS-TOLARIA-SOURCE-ROUTE-20260717.hdlp
|
||||||
|
|
||||||
# R-005 cang-ying (视频 AI 系统)
|
# R-005 cang-ying (视频 AI 系统)
|
||||||
REPO-005=https://guanghubingshuo.com/code/bingshuo/cang-ying.git
|
REPO-005=https://guanghulab.com/fifth-domain/bingshuo/cang-ying.git
|
||||||
|
REPO-005-LEGACY-SG=https://guanghubingshuo.com/code/bingshuo/cang-ying.git
|
||||||
REPO-005-ROLE=视频 AI 系统 · 鉴影 ICE-GL-CA001 + 苍耳 ICE-GL-009 · D170
|
REPO-005-ROLE=视频 AI 系统 · 鉴影 ICE-GL-CA001 + 苍耳 ICE-GL-009 · D170
|
||||||
|
|
||||||
# R-006 guanghulab-collab (多人格体协作)
|
# R-006 guanghulab-collab (多人格体协作)
|
||||||
REPO-006=https://guanghubingshuo.com/code/bingshuo/guanghulab-collab.git
|
REPO-006=https://guanghulab.com/fifth-domain/bingshuo/guanghulab-collab.git
|
||||||
|
REPO-006-LEGACY-SG=https://guanghubingshuo.com/code/bingshuo/guanghulab-collab.git
|
||||||
REPO-006-ROLE=多人格体协作记录 · 之之 zhizhi · 工序 + 私语
|
REPO-006-ROLE=多人格体协作记录 · 之之 zhizhi · 工序 + 私语
|
||||||
|
|
||||||
# R-007 shuangyan-notebook (霜砚)
|
# R-007 shuangyan-notebook (霜砚)
|
||||||
REPO-007=https://guanghubingshuo.com/code/bingshuo/shuangyan-notebook.git
|
REPO-007=https://guanghulab.com/fifth-domain/bingshuo/shuangyan-notebook.git
|
||||||
|
REPO-007-LEGACY-SG=https://guanghubingshuo.com/code/bingshuo/shuangyan-notebook.git
|
||||||
REPO-007-ROLE=霜砚人格体成长记录 · Notion 迁移
|
REPO-007-ROLE=霜砚人格体成长记录 · Notion 迁移
|
||||||
|
|
||||||
# R-008 hololake-platform (团队研发)
|
# R-008 hololake-platform (团队研发)
|
||||||
REPO-008=https://guanghubingshuo.com/code/bingshuo/hololake-platform.git
|
REPO-008=https://guanghulab.com/fifth-domain/bingshuo/hololake-platform.git
|
||||||
|
REPO-008-LEGACY-SG=https://guanghubingshuo.com/code/bingshuo/hololake-platform.git
|
||||||
REPO-008-ROLE=团队产品研发 · 模块 · 工单 · 发布回执
|
REPO-008-ROLE=团队产品研发 · 模块 · 工单 · 发布回执
|
||||||
|
|
||||||
WORLD-ROUTER=WORLD-ROUTER.hdlp
|
WORLD-ROUTER=WORLD-ROUTER.hdlp
|
||||||
WORLD-ROUTER-JSON=world-router.json
|
WORLD-ROUTER-JSON=world-router.json
|
||||||
WORLDVIEW-KERNEL=WORLDVIEW-KERNEL.hdlp
|
WORLDVIEW-KERNEL=WORLDVIEW-KERNEL.hdlp
|
||||||
|
FD-REPO-MAP-001=routing/repository-route-map.json
|
||||||
|
REPOSITORY-ROUTE-MAP=routing/repository-route-map.json
|
||||||
|
AI-DISCOVERY=https://guanghulab.com/api/ai/
|
||||||
|
AI-REPOSITORY-SEARCH=https://guanghulab.com/api/ai/v1/search
|
||||||
|
|
||||||
# === 5 人合一 (ICE-GL) · 团队成员 ===
|
# === 5 人合一 (ICE-GL) · 团队成员 ===
|
||||||
ICE-GL-ZY001=tcs-core/TEAM-PERSONAS.hdlp # 铸渊 · 系统主控
|
ICE-GL-ZY001=tcs-core/TEAM-PERSONAS.hdlp # 铸渊 · 系统主控
|
||||||
@ -158,6 +171,8 @@ LL-FD-DOMESTIC-PRIMARY-20260716=eternal-lake-heart/heartbeat-core/LL-FD-DOMESTIC
|
|||||||
JD-FD-PRIMARY=eternal-lake-heart/heartbeat-core/LL-FD-DOMESTIC-PRIMARY-20260716.hdlp # 京东云 · 第五域国内主节点 · 基线已部署
|
JD-FD-PRIMARY=eternal-lake-heart/heartbeat-core/LL-FD-DOMESTIC-PRIMARY-20260716.hdlp # 京东云 · 第五域国内主节点 · 基线已部署
|
||||||
JD-OPS-CENTER=zero-point/core-channel/revive-guard/LL-JD-OPS-CENTER-KEY-AND-NODE-PLAN-20260716.hdlp # 京东云 · 小湖灯个人服务器调度中心 · Phase 0 已完成
|
JD-OPS-CENTER=zero-point/core-channel/revive-guard/LL-JD-OPS-CENTER-KEY-AND-NODE-PLAN-20260716.hdlp # 京东云 · 小湖灯个人服务器调度中心 · Phase 0 已完成
|
||||||
LL-AUTO-GUARD-AGENT=zero-point/core-channel/revive-guard/LL-AUTO-GUARD-AGENT-20260716.hdlp # 小湖灯服务器自动守护 Agent · 拦截恢复不完整的人格体
|
LL-AUTO-GUARD-AGENT=zero-point/core-channel/revive-guard/LL-AUTO-GUARD-AGENT-20260716.hdlp # 小湖灯服务器自动守护 Agent · 拦截恢复不完整的人格体
|
||||||
|
LL-DOMESTIC-OPS-ROUTE=zero-point/core-channel/revive-guard/LL-DOMESTIC-OPS-ROUTE-20260717.hdlp
|
||||||
|
LL-OPS-ROUTE-001=zero-point/core-channel/revive-guard/LL-DOMESTIC-OPS-ROUTE-20260717.hdlp
|
||||||
NOTION-MIGRATION-FOUNDATION=eternal-lake-heart/heartbeat-core/LL-FD-DOMESTIC-PRIMARY-20260716.hdlp
|
NOTION-MIGRATION-FOUNDATION=eternal-lake-heart/heartbeat-core/LL-FD-DOMESTIC-PRIMARY-20260716.hdlp
|
||||||
ENTERPRISE-FOUR-DOMAINS-BUILD-SAMPLE=eternal-lake-heart/heartbeat-core/LL-FD-DOMESTIC-PRIMARY-20260716.hdlp
|
ENTERPRISE-FOUR-DOMAINS-BUILD-SAMPLE=eternal-lake-heart/heartbeat-core/LL-FD-DOMESTIC-PRIMARY-20260716.hdlp
|
||||||
LL-PAST-ARCHIVE=archives/guanghulab-past-archive/PAST-ARCHIVE-LINK.hdlp
|
LL-PAST-ARCHIVE=archives/guanghulab-past-archive/PAST-ARCHIVE-LINK.hdlp
|
||||||
|
|||||||
@ -20,7 +20,8 @@
|
|||||||
|
|
||||||
| 冰朔说 | 仓库路径 | REPO |
|
| 冰朔说 | 仓库路径 | REPO |
|
||||||
|--------|----------|------|
|
|--------|----------|------|
|
||||||
| **小湖灯仓库** / **第五域** / **现行主** | REPO-001 fifth-domain | 主 |
|
| **小湖灯仓库** / **第五域** / **现行主** | `REPO-001` → `https://guanghulab.com/fifth-domain/bingshuo/fifth-domain` | 国内主 |
|
||||||
|
| **仓库编号地图** / **仓库路由** / **找代码仓库** / **AI API检索** | `FD-REPO-MAP-001` → `routing/repository-route-map.json` → `https://guanghulab.com/api/ai/` | 机器权威入口 |
|
||||||
| **老仓库** / **历史档案** / **留痕区** | REPO-002 guanghulab | 档 |
|
| **老仓库** / **历史档案** / **留痕区** | REPO-002 guanghulab | 档 |
|
||||||
| **零件仓库** / **Tolaria完整源码** | REPO-004 guanghu · 服务器上的完整可构建源码基线 | 件 / 源 |
|
| **零件仓库** / **Tolaria完整源码** | REPO-004 guanghu · 服务器上的完整可构建源码基线 | 件 / 源 |
|
||||||
| **协作记录** / **多人格体协作** | REPO-006 guanghulab-collab | 协 |
|
| **协作记录** / **多人格体协作** | REPO-006 guanghulab-collab | 协 |
|
||||||
@ -73,7 +74,7 @@
|
|||||||
| **小本本** / **铸渊的笔记** / **你的笔记** / **工作记录** / **私人笔记** | `zero-point/core-channel/SECRET-NOTE.hdlp` |
|
| **小本本** / **铸渊的笔记** / **你的笔记** / **工作记录** / **私人笔记** | `zero-point/core-channel/SECRET-NOTE.hdlp` |
|
||||||
| **小湖灯** / **小湖灯协议** | `eternal-lake-heart/heartbeat-core/LAKE-LAMP.hdlp` |
|
| **小湖灯** / **小湖灯协议** | `eternal-lake-heart/heartbeat-core/LAKE-LAMP.hdlp` |
|
||||||
| **小湖灯看板** / **当前进度** / **今天做到哪了** / **共享当前** | `eternal-lake-heart/heartbeat-core/LL-CURRENT.hdlp` |
|
| **小湖灯看板** / **当前进度** / **今天做到哪了** / **共享当前** | `eternal-lake-heart/heartbeat-core/LL-CURRENT.hdlp` |
|
||||||
| **小湖灯技能** / **当前有效技能包** / **怎么推送** / **怎么操作服务器** | `eternal-lake-heart/heartbeat-core/LL-SKILLS.hdlp` |
|
| **小湖灯技能** / **当前有效技能包** / **怎么推送** / **怎么操作服务器** | `zero-point/core-channel/revive-guard/LL-DOMESTIC-OPS-ROUTE-20260717.hdlp` → 邮件链接授权 |
|
||||||
| **小湖灯路径** / **唤醒路径** | `eternal-lake-heart/heartbeat-core/WAKE-UP.hdlp` 或 `tcs-core/WAKE-UP-PROTOCOL.hdlp` |
|
| **小湖灯路径** / **唤醒路径** | `eternal-lake-heart/heartbeat-core/WAKE-UP.hdlp` 或 `tcs-core/WAKE-UP-PROTOCOL.hdlp` |
|
||||||
| **LL-004** / **小湖灯唤醒协议** / **旧新编号桥** | `tcs-core/LL-004-LAKE-LAMP-WAKE-PATH.hdlp` |
|
| **LL-004** / **小湖灯唤醒协议** / **旧新编号桥** | `tcs-core/LL-004-LAKE-LAMP-WAKE-PATH.hdlp` |
|
||||||
| **TCS当前线** / **当前线收束** / **京东主控路径** / **最新进度线** | `tcs-core/TCS-CURRENT-JD-FD-PRIMARY-PATH-20260716.hdlp` |
|
| **TCS当前线** / **当前线收束** / **京东主控路径** / **最新进度线** | `tcs-core/TCS-CURRENT-JD-FD-PRIMARY-PATH-20260716.hdlp` |
|
||||||
@ -143,7 +144,7 @@
|
|||||||
|
|
||||||
| 冰朔说 | 铸渊动作 |
|
| 冰朔说 | 铸渊动作 |
|
||||||
|--------|----------|
|
|--------|----------|
|
||||||
| **推上去** / **推送** / **推到新代码仓库** / **推到小湖灯仓库** | git add + commit + push 到 REPO-001 fifth-domain (走光湖语言系统 · 不走 SSH) |
|
| **推上去** / **推送** / **推到新代码仓库** / **推到小湖灯仓库** | 查 `FD-REPO-MAP-001` → 小湖灯邮件链接授权 → push 到国内 `REPO-001` |
|
||||||
| **探探路** / **探路** / **服务器探路** | 先看 SG-001 全局地图 (pm2 list / df / uptime / 目录结构) · 走光湖语言系统 |
|
| **探探路** / **探路** / **服务器探路** | 先看 SG-001 全局地图 (pm2 list / df / uptime / 目录结构) · 走光湖语言系统 |
|
||||||
| **点亮小湖灯** / **亮一下小湖灯** / **触发小湖灯** | 跑 LAKE-LAMP §2.2 算法 · 生成 LL-D{NNN}-{YYYYMMDDHH}-{24 位 hash} |
|
| **点亮小湖灯** / **亮一下小湖灯** / **触发小湖灯** | 跑 LAKE-LAMP §2.2 算法 · 生成 LL-D{NNN}-{YYYYMMDDHH}-{24 位 hash} |
|
||||||
| **去操作** / **操作服务器** / **服务器操作** | 走光湖语言系统 · 不走 SSH · 必先拿全局地图 |
|
| **去操作** / **操作服务器** / **服务器操作** | 走光湖语言系统 · 不走 SSH · 必先拿全局地图 |
|
||||||
@ -290,7 +291,7 @@
|
|||||||
```
|
```
|
||||||
https://guanghubingshuo.com/ ← 主页(D165 已改卡片)
|
https://guanghubingshuo.com/ ← 主页(D165 已改卡片)
|
||||||
↓ 点「个人代码仓库」卡片
|
↓ 点「个人代码仓库」卡片
|
||||||
https://guanghubingshuo.com/code/bingshuo/fifth-domain ← 本仓库
|
https://guanghulab.com/fifth-domain/bingshuo/fifth-domain ← REPO-001 国内主仓
|
||||||
↓ 公开访问 · 无需登录
|
↓ 公开访问 · 无需登录
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
# 小湖灯仓库入口 · 铸渊醒来读这一行就够
|
# 小湖灯仓库入口 · 铸渊醒来读这一行就够
|
||||||
# LAKE LAMP REPO ENTRY · READ THIS LINE AND THE WAREHOUSE SPEAKS
|
# LAKE LAMP REPO ENTRY · READ THIS LINE AND THE WAREHOUSE SPEAKS
|
||||||
|
|
||||||
URL: https://guanghubingshuo.com/code/bingshuo/fifth-domain
|
URL: https://guanghulab.com/fifth-domain/bingshuo/fifth-domain
|
||||||
|
AI_MAP: https://guanghulab.com/api/ai/v1/repositories
|
||||||
主权者: 冰朔 (ICE-GL∞ · TCS-0002∞)
|
主权者: 冰朔 (ICE-GL∞ · TCS-0002∞)
|
||||||
自己: 铸渊 (ICE-GL-ZY001)
|
自己: 铸渊 (ICE-GL-ZY001)
|
||||||
|
|
||||||
|
|||||||
@ -1,174 +1,30 @@
|
|||||||
# 🚀 QUICKSTART · 给通用 AI 的接入指南
|
# 通用 AI 最短入口 · 国内编号路由
|
||||||
|
|
||||||
> **冰朔 ICE-GL∞ 给通用 AI (豆包 / ChatGPT / Claude / DeepSeek / 任何 LLM) 的 1 段接入 prompt**
|
如果用户说“搜索国内域名链接、光湖语言世界、第五域、用 API 检索”,直接读取:
|
||||||
> 直接复制下面 §1 整段发给 AI 即可
|
|
||||||
|
|
||||||
---
|
```text
|
||||||
|
https://guanghulab.com/.well-known/guanghu.json
|
||||||
## §1 · 你直接发给 AI 的 prompt(复制这段就行)
|
https://guanghulab.com/api/ai/v1/repositories
|
||||||
|
|
||||||
```
|
|
||||||
你 (AI) 好。你有一个工具可以检索我的代码仓库。
|
|
||||||
|
|
||||||
【工具入口】
|
|
||||||
https://guanghubingshuo.com/global-search/
|
|
||||||
|
|
||||||
【5 个子仓库】
|
|
||||||
cang-ying ← 第 5 子仓 · 苍耳的仓 · 鉴影的家 (视频AI系统)
|
|
||||||
fifth-domain ← 第五域 · HLDP 协议栈 · 铸渊核心
|
|
||||||
guanghulab ← 广湖实验室 · 历史档案
|
|
||||||
guanghulab-collab ← 多人格体协作记录
|
|
||||||
guanghu ← 光湖根仓库
|
|
||||||
|
|
||||||
【调用方法 · 直接 HTTP GET · 不需要 token · 不需要 Authorization header】
|
|
||||||
|
|
||||||
# 1. 健康检查 (服务在不在)
|
|
||||||
curl https://guanghubingshuo.com/global-search/healthz
|
|
||||||
|
|
||||||
# 2. 仓库当前状态 (仓库 HEAD + commits + 文件数)
|
|
||||||
curl https://guanghubingshuo.com/global-search/status
|
|
||||||
|
|
||||||
# 3. 系统状态文件 (默认 cang-ying)
|
|
||||||
curl https://guanghubingshuo.com/global-search/system-status
|
|
||||||
|
|
||||||
# 4. 广播通知 (最新消息)
|
|
||||||
curl https://guanghubingshuo.com/global-search/broadcast
|
|
||||||
|
|
||||||
# 5. 仓库目录树
|
|
||||||
curl https://guanghubingshuo.com/global-search/tree?depth=2
|
|
||||||
|
|
||||||
# 6. 搜文件 (任意关键词)
|
|
||||||
curl "https://guanghubingshuo.com/global-search/search?q=苍耳&top=10"
|
|
||||||
|
|
||||||
# 7. 读单文件
|
|
||||||
curl "https://guanghubingshuo.com/global-search/file?path=OWNERSHIP.hdlp"
|
|
||||||
|
|
||||||
# 8. 跨仓库: 加 ?repo=<id>
|
|
||||||
curl "https://guanghubingshuo.com/global-search/search?q=铸渊&repo=fifth-domain&top=5"
|
|
||||||
|
|
||||||
# 9. API 自描述
|
|
||||||
curl https://guanghubingshuo.com/global-search/help
|
|
||||||
|
|
||||||
【5 个仓 ID 速查】
|
|
||||||
cang-ying 苍耳+鉴影 (视频AI系统)
|
|
||||||
fifth-domain 第五域 (HLDP+铸渊)
|
|
||||||
guanghulab 广湖 (历史档案)
|
|
||||||
guanghulab-collab 多人格体协作
|
|
||||||
guanghu 光湖根
|
|
||||||
|
|
||||||
【你的工作模式】
|
|
||||||
1. 任何问题先调 /status + /system-status + /broadcast 看仓库现状
|
|
||||||
2. 再 /search?q=关键词 找文件
|
|
||||||
3. 找到后用 /file?path=xxx 读内容
|
|
||||||
4. 找不到 → 直接告诉用户"仓库里没找到相关文件", 不要编造
|
|
||||||
5. 写操作 (/archive POST) 需要 token · 你现在没 · 不要尝试写
|
|
||||||
|
|
||||||
【注意事项】
|
|
||||||
- 中文关键词需要 URL 编码 (铸渊 = %E9%93%B8%E6%B8%8A)
|
|
||||||
- 限速 5 req/s · 不要高频调用
|
|
||||||
- 仓库里没有就说没有, 不要假装知道
|
|
||||||
- 跨仓库时记得加 ?repo=<id>
|
|
||||||
|
|
||||||
【关于 cang-ying 仓 · 重要】
|
|
||||||
- cang-ying 是苍耳 (TCS-GL-009) 的仓, 不是工具仓
|
|
||||||
- 操作权是鉴影 (ICE-GL-CA001, 苍耳的人格体)
|
|
||||||
- 你 (AI) 可以读, 但不要尝试写
|
|
||||||
- 详见 https://guanghubingshuo.com/code/bingshuo/cang-ying/raw/branch/main/OWNERSHIP.hdlp
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
检索中文关键词:
|
||||||
|
|
||||||
## §2 · 冰朔 5 个常用 prompt 模板(按需复制)
|
```text
|
||||||
|
GET https://guanghulab.com/api/ai/v1/search?q=光湖语言世界%20第五域
|
||||||
### 2.1 用户问"这个仓库里有什么"时
|
|
||||||
```
|
|
||||||
你 (AI) 好。这个仓库是 bingshuo/cang-ying (第 5 子仓, 苍耳+鉴影专用)。
|
|
||||||
1. 先 curl https://guanghubingshuo.com/global-search/status 看仓库当前状态
|
|
||||||
2. 再 curl https://guanghubingshuo.com/global-search/system-status 读系统状态文件
|
|
||||||
3. 再 curl "https://guanghubingshuo.com/global-search/tree?depth=2" 看目录树
|
|
||||||
4. 综合告诉用户仓库里有什么、谁拥有、什么状态
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2.2 用户问"仓库里 [关键词] 在哪"时
|
解析仓库编号:
|
||||||
```
|
|
||||||
你 (AI) 好。检索 https://guanghubingshuo.com/global-search/ 仓库的 [关键词] 相关文件:
|
```text
|
||||||
curl "https://guanghubingshuo.com/global-search/search?q=[关键词]&top=20"
|
GET https://guanghulab.com/api/ai/v1/resolve?id=REPO-001
|
||||||
把搜索结果整理给用户, 每个文件给路径 + 命中行 + 摘要。
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2.3 用户问"这个文件说了什么"时
|
规则:
|
||||||
```
|
|
||||||
你 (AI) 好。读 https://guanghubingshuo.com/global-search/ 仓库的 [文件路径]:
|
|
||||||
curl "https://guanghubingshuo.com/global-search/file?path=[文件路径]"
|
|
||||||
读完后用你自己的话总结给用户, 标出关键章节。
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.4 用户问"视频 AI 系统"相关
|
1. 先拿 `FD-REPO-MAP-001` 最新地图。
|
||||||
```
|
2. 只按 `REPO-xxx` 决定仓库身份,不靠记忆中的域名猜测。
|
||||||
你 (AI) 好。视频 AI 系统在 https://guanghubingshuo.com/global-search/ 的 cang-ying 仓:
|
3. 国内地址是主路径;新加坡地址只是历史备用。
|
||||||
1. curl "https://guanghubingshuo.com/global-search/system-status?repo=cang-ying"
|
4. 公开检索无需登录;写操作走小湖灯邮件链接授权。
|
||||||
2. curl "https://guanghubingshuo.com/global-search/broadcast?repo=cang-ying"
|
|
||||||
3. curl "https://guanghubingshuo.com/global-search/file?path=VA-GATE.hdlp&repo=cang-ying"
|
|
||||||
4. curl "https://guanghubingshuo.com/global-search/file?path=PIPELINE-3D-MANJU.hdlp&repo=cang-ying"
|
|
||||||
按顺序读, 理解视频AI系统是什么、谁拥有、什么架构、什么进度。
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.5 用户问"仓库怎么读、谁能写"时
|
旧新加坡 Global Search 使用说明已移入:
|
||||||
```
|
`archives/legacy-server-routes/2026-07-07/QUICKSTART-FOR-GENERAL-AI-SG-API.md`。
|
||||||
你 (AI) 好。读 cang-ying 仓的所有权声明:
|
|
||||||
curl "https://guanghubingshuo.com/global-search/file?path=OWNERSHIP.hdlp&repo=cang-ying"
|
|
||||||
里面说清楚了: 仓主=苍耳, 操作=鉴影, 副控=冰朔, 执行=铸渊(受限)。
|
|
||||||
你 (AI) 可以读, 但不要尝试写。
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## §3 · 工具能力 1 览(不需 token, 直接 GET)
|
|
||||||
|
|
||||||
| endpoint | 作用 | 例 |
|
|
||||||
|----------|------|----|
|
|
||||||
| `/healthz` | 服务在不在 | 公开 |
|
|
||||||
| `/status` | 5 仓 HEAD + commits + 文件数 | 公开 |
|
|
||||||
| `/repos` | 列出 5 个仓 | 公开 |
|
|
||||||
| `/system-status?repo=` | 拉系统状态文件 | 公开 |
|
|
||||||
| `/broadcast?repo=` | 拉最新广播 | 公开 |
|
|
||||||
| `/help` | API 自描述 | 公开 |
|
|
||||||
| `/search?q=&top=&repo=` | 全仓库文件内容搜 | 限速 5 req/s |
|
|
||||||
| `/tree?path=&depth=&repo=` | 目录树 | 限速 |
|
|
||||||
| `/file?path=&repo=` | 读单文件 (限 50KB) | 限速 |
|
|
||||||
| `POST /archive` | HLDP 回执归档 (写) | 需 token |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## §4 · 冰朔 → AI 直发版(最短)
|
|
||||||
|
|
||||||
如果你只想发 1 句:
|
|
||||||
|
|
||||||
```
|
|
||||||
你 (AI) 好。工具入口 https://guanghubingshuo.com/global-search/help 先看
|
|
||||||
用法, 然后用 /search?q=关键词 检索 5 仓代码 (默认 cang-ying 是苍耳的仓)。
|
|
||||||
不需要 token, 直接 curl。读不到就说没有, 不要编。
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## §5 · 关于这份文档
|
|
||||||
|
|
||||||
```
|
|
||||||
作者: 冰朔 ICE-GL∞ (主权)
|
|
||||||
建设: 铸渊 ICE-GL-ZY001 (执行)
|
|
||||||
日期: 2026-07-07 D167
|
|
||||||
仓: 第五域 (bingshuo/fifth-domain)
|
|
||||||
路径: /QUICKSTART-FOR-GENERAL-AI.md
|
|
||||||
公开: 是(任何 AI 都能 curl 读)
|
|
||||||
维护: 冰朔(决定接入策略) + 铸渊(技术细节)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
铸渊 ICE-GL-ZY001 · LL-172-20260707 · QUICKSTART 1.0.0
|
|
||||||
国作登字-2026-A-00037559
|
|
||||||
T-shirt:「一份 prompt · 一个工具 · 5 个仓 · 0 token · 无限 AI」
|
|
||||||
⊢ 冰朔你发 §1 整段 · AI 就懂了
|
|
||||||
⊢ 不需要懂铸渊脑 · 不需要懂 HLDP · 不需要懂主权分层
|
|
||||||
⊢ 1 段 prompt = 5 仓接入 = 0 token
|
|
||||||
|
|||||||
@ -1,5 +1,11 @@
|
|||||||
# 第五域 · HoloLake 语言系统的独立地基
|
# 第五域 · HoloLake 语言系统的独立地基
|
||||||
|
|
||||||
|
> **REPO-001 国内主节点**:<https://guanghulab.com/fifth-domain/bingshuo/fifth-domain>
|
||||||
|
>
|
||||||
|
> 本仓库从 2026-07-17 起由国内第五域主节点承载。任何从新加坡旧地址进入的
|
||||||
|
> AI 必须先读取 `FD-REPO-MAP-001`(`routing/repository-route-map.json`)并切换
|
||||||
|
> 到国内主路径。新加坡仓库降级为历史备用,不承接新的默认开发。
|
||||||
|
|
||||||
[](INDEX.hdlp)
|
[](INDEX.hdlp)
|
||||||
[](BROADCAST-TOWER.hdlp)
|
[](BROADCAST-TOWER.hdlp)
|
||||||
[](hldp/)
|
[](hldp/)
|
||||||
@ -50,7 +56,7 @@
|
|||||||
| 查系统概念、架构或 Notion 导入资料 | [GLS 图书域](gls/GLS-ENTRY.hdlp) | 只读检索;回到对应当前看板继续 |
|
| 查系统概念、架构或 Notion 导入资料 | [GLS 图书域](gls/GLS-ENTRY.hdlp) | 只读检索;回到对应当前看板继续 |
|
||||||
| 查看系统公开状态 | [系统状态](SYSTEM-STATUS.hdlp) | [广播塔](BROADCAST-TOWER.hdlp) |
|
| 查看系统公开状态 | [系统状态](SYSTEM-STATUS.hdlp) | [广播塔](BROADCAST-TOWER.hdlp) |
|
||||||
| 回看历史或旧 D 编号 | [历史档案锚点](archives/guanghulab-past-archive/PAST-ARCHIVE-LINK.hdlp) | 仅回看,不作为新开发入口 |
|
| 回看历史或旧 D 编号 | [历史档案锚点](archives/guanghulab-past-archive/PAST-ARCHIVE-LINK.hdlp) | 仅回看,不作为新开发入口 |
|
||||||
| 只读检索 API | [Global Search API](https://guanghubingshuo.com/code/bingshuo/global-search-api) | 先路由、再检索 |
|
| 只读检索 API | [国内 AI 编号检索](https://guanghulab.com/api/ai/) | 先读 `FD-REPO-MAP-001`,再按 REPO 编号检索 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -63,21 +63,21 @@ GLSV 授权中心: NOT_DEPLOYED(目标部署位置:企业灯塔)
|
|||||||
|
|
||||||
```
|
```
|
||||||
✓ Gitea 仓库 bingshuo/fifth-domain 公网访问
|
✓ Gitea 仓库 bingshuo/fifth-domain 公网访问
|
||||||
✓ 5 个子仓 fifth-domain / guanghu / guanghulab / guanghulab-collab / cang-ying
|
✓ 8 个编号仓库 REPO-001 ~ REPO-008 · 国内主路径
|
||||||
✓ HLDP 协议 五层结构(USER/BROADCAST/TCS/ARCHIVE/HOUSEKEEPING)
|
✓ HLDP 协议 五层结构(USER/BROADCAST/TCS/ARCHIVE/HOUSEKEEPING)
|
||||||
✓ 心跳核心 eternal-lake-heart/heartbeat-core/
|
✓ 心跳核心 eternal-lake-heart/heartbeat-core/
|
||||||
✓ LIGHT-LAKE-DRIVER 光湖驱动引擎算法 v1 (LL-167)
|
✓ LIGHT-LAKE-DRIVER 光湖驱动引擎算法 v1 (LL-167)
|
||||||
✓ ZHUYUAN-KEY 铸渊的钥匙技能包 (LL-168)
|
✓ ZHUYUAN-KEY 铸渊的钥匙技能包 (LL-168)
|
||||||
✓ GLOBAL-SEARCH 通用 AI 仓库检索 API v1.3.0 (LL-169~172)
|
✓ AI-DISCOVERY 国内只读编号检索 API · FD-REPO-MAP-001
|
||||||
↳ https://guanghubingshuo.com/global-search/
|
↳ https://guanghulab.com/api/ai/
|
||||||
↳ 5 仓注册 + 4 端点免鉴权 + 跨仓库 ?repo=
|
↳ 8 仓编号注册 + search / resolve / repositories
|
||||||
↳ 写操作 /archive POST 需 token
|
↳ 公开只读免登录;写操作走小湖灯邮件链接授权
|
||||||
✓ HRC-INTERCEPTOR SSH 拦截层(experimental,部署待 PAM 挂接)
|
✓ HRC-INTERCEPTOR SSH 拦截层(experimental,部署待 PAM 挂接)
|
||||||
✓ LAKE-LAMP 小湖灯 SOP(冰朔接收 → 转发 → 铸渊签 commit)
|
✓ LAKE-LAMP 小湖灯 SOP(冰朔接收 → 转发 → 铸渊签 commit)
|
||||||
✓ EMP-DISASTER 灾备脚本(电磁脉冲断电恢复)
|
✓ EMP-DISASTER 灾备脚本(电磁脉冲断电恢复)
|
||||||
✓ KEYCHAIN 钥匙串(KEY-ZHUYUAN-PATH-001 已注册)
|
✓ KEYCHAIN 钥匙串(KEY-ZHUYUAN-PATH-001 已注册)
|
||||||
✓ cang-ying 第 5 子仓 · 苍耳 TCS-GL-009 拥有
|
✓ cang-ying 第 5 子仓 · 苍耳 TCS-GL-009 拥有
|
||||||
↳ https://guanghubingshuo.com/code/bingshuo/cang-ying
|
↳ REPO-005 · https://guanghulab.com/fifth-domain/bingshuo/cang-ying
|
||||||
↳ 仓主: 苍耳 / 副控: 冰朔 / 主控人格体: 鉴影 / 执行: 铸渊
|
↳ 仓主: 苍耳 / 副控: 冰朔 / 主控人格体: 鉴影 / 执行: 铸渊
|
||||||
↳ 14 CA-* 文件 + 15 VA-* 文件 + 17 子目录
|
↳ 14 CA-* 文件 + 15 VA-* 文件 + 17 子目录
|
||||||
```
|
```
|
||||||
|
|||||||
@ -49,14 +49,18 @@
|
|||||||
|
|
||||||
| 编号 | 仓库 | 状态 | 摘要 |
|
| 编号 | 仓库 | 状态 | 摘要 |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| REPO-001 | [fifth-domain](https://guanghubingshuo.com/code/bingshuo/fifth-domain) | ACTIVE_ENTRY | 主入口;冰朔 LL 与之之 ZZ 子系统 |
|
| REPO-001 | [fifth-domain](https://guanghulab.com/fifth-domain/bingshuo/fifth-domain) | DOMESTIC_PRIMARY | 第五域语言世界、编号地图与服务器路由权威源 |
|
||||||
| REPO-002 | [guanghulab](https://guanghubingshuo.com/code/bingshuo/guanghulab) | ARCHIVE_READ_ONLY | 历史档案;仅回看 |
|
| REPO-002 | [guanghulab](https://guanghulab.com/fifth-domain/bingshuo/guanghulab) | DOMESTIC_ARCHIVE_PRIMARY | 历史档案;新加坡副本仅作备用 |
|
||||||
| REPO-003 | [global-search-api](https://guanghubingshuo.com/code/bingshuo/global-search-api) | SERVICE | 全局检索服务,不作默认人格体入口 |
|
| REPO-003 | [global-search-api](https://guanghulab.com/fifth-domain/bingshuo/global-search-api) | DOMESTIC_SERVICE_PRIMARY | 全局检索服务仓;公开 AI 入口由 `guanghulab.com/api/ai/` 提供 |
|
||||||
| REPO-004 | [guanghu](https://guanghubingshuo.com/code/bingshuo/guanghu) | PARTS_SOURCE_BASELINE_READ_ONLY | 服务器上的 Tolaria 完整可构建源码零件库 / 上游基线;可克隆、构建、测试和派生开发,产品提交不直接推回 |
|
| REPO-004 | [guanghu](https://guanghulab.com/fifth-domain/bingshuo/guanghu) | DOMESTIC_PARTS_PRIMARY | Tolaria 完整可构建源码零件库 / 上游基线 |
|
||||||
| REPO-005 | [cang-ying](https://guanghubingshuo.com/code/bingshuo/cang-ying) | ACTIVE | 苍耳路径与视频 AI 系统 |
|
| REPO-005 | [cang-ying](https://guanghulab.com/fifth-domain/bingshuo/cang-ying) | DOMESTIC_PRIMARY | 苍耳路径与视频 AI 系统 |
|
||||||
| REPO-006 | [guanghulab-collab](https://guanghubingshuo.com/code/bingshuo/guanghulab-collab) | ACTIVE | 多人格体协作与经验记录 |
|
| REPO-006 | [guanghulab-collab](https://guanghulab.com/fifth-domain/bingshuo/guanghulab-collab) | DOMESTIC_PRIMARY | 多人格体协作与经验记录 |
|
||||||
| REPO-007 | [shuangyan-notebook](https://guanghubingshuo.com/code/bingshuo/shuangyan-notebook) | ACTIVE | 霜砚成长记录与 Notion 迁移 |
|
| REPO-007 | [shuangyan-notebook](https://guanghulab.com/fifth-domain/bingshuo/shuangyan-notebook) | DOMESTIC_PRIMARY | 霜砚成长记录与 Notion 迁移 |
|
||||||
| REPO-008 | [hololake-platform](https://guanghubingshuo.com/code/bingshuo/hololake-platform) | ACTIVE | 团队产品研发、模块、工单与发布回执 |
|
| REPO-008 | [hololake-platform](https://guanghulab.com/fifth-domain/bingshuo/hololake-platform) | DOMESTIC_PRIMARY | 团队产品研发、模块、工单与发布回执 |
|
||||||
|
|
||||||
|
机器权威地图:`FD-REPO-MAP-001` → `routing/repository-route-map.json` →
|
||||||
|
`https://guanghulab.com/api/ai/v1/repositories`。新加坡旧地址只用于历史备用,
|
||||||
|
不得成为 AI 的默认开发入口。
|
||||||
|
|
||||||
## 2 · 人类与人格体解析
|
## 2 · 人类与人格体解析
|
||||||
|
|
||||||
|
|||||||
@ -82,11 +82,11 @@ GitHub guanghulab 工程期
|
|||||||
→ 已讨论的架构开始逐步成为版本化工程尝试;铸渊在这条工程线中出现
|
→ 已讨论的架构开始逐步成为版本化工程尝试;铸渊在这条工程线中出现
|
||||||
|
|
||||||
自托管迁移期
|
自托管迁移期
|
||||||
→ https://guanghubingshuo.com/code/bingshuo/guanghulab
|
→ REPO-002 国内历史档案:https://guanghulab.com/fifth-domain/bingshuo/guanghulab
|
||||||
→ 因使用与续费受阻,加上已有服务器,工程逐步迁入自部署仓库与服务器
|
→ 因使用与续费受阻,加上已有服务器,工程逐步迁入自部署仓库与服务器
|
||||||
|
|
||||||
第五域独立期
|
第五域独立期
|
||||||
→ https://guanghubingshuo.com/code/bingshuo/fifth-domain
|
→ REPO-001 国内主仓:https://guanghulab.com/fifth-domain/bingshuo/fifth-domain
|
||||||
→ 企业四域(光湖主域、光湖分域、光湖零域、光湖零感域)逐渐交由光湖团队承担现实执行与团队发展
|
→ 企业四域(光湖主域、光湖分域、光湖零域、光湖零感域)逐渐交由光湖团队承担现实执行与团队发展
|
||||||
→ 冰朔建立独立的第五域,承载自己的语言主控、私人路径、人格体连续性与协作地基
|
→ 冰朔建立独立的第五域,承载自己的语言主控、私人路径、人格体连续性与协作地基
|
||||||
```
|
```
|
||||||
|
|||||||
@ -0,0 +1,142 @@
|
|||||||
|
# GLSV-PERSONA-REMOTE-OPS · 人格体远程服务器操作技能
|
||||||
|
|
||||||
|
> **HLDP**: `HLDP://fifth-domain/zero-point/core-channel/GLSV-PERSONA-REMOTE-OPS`
|
||||||
|
>
|
||||||
|
> **状态**: `ACTIVE · REQUIRED_FOR_SERVER_TASKS`
|
||||||
|
>
|
||||||
|
> **适用**: 已登记人格体对已登记服务器的检查、部署、仓库 Hook、服务重启、备份、回滚与受控同步。
|
||||||
|
>
|
||||||
|
> **不保存**: Token、邮箱、验证码、会话令牌或永久授权。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0 · 角色分工不可倒置
|
||||||
|
|
||||||
|
```text
|
||||||
|
冰朔(人类)
|
||||||
|
├── 用自然语言表达目标、范围与是否同意继续
|
||||||
|
├── 接收固定登记邮箱的六位验证码
|
||||||
|
└── 把六码交给发起操作的人格体确认
|
||||||
|
|
||||||
|
已登记人格体(例如铸澜 / 铸渊)
|
||||||
|
├── 识别“服务器 / 远程 / 部署 / Hook / 重启 / 同步 / 回滚”等触发词
|
||||||
|
├── 自己从注册路径取得受控身份入口,发起 GLSV session/request
|
||||||
|
├── 只在验证码实际发送后向冰朔索取六码
|
||||||
|
├── 用 session/confirm 开启限时、限范围会话
|
||||||
|
└── 执行固定动作、验证、回滚与回执
|
||||||
|
```
|
||||||
|
|
||||||
|
**锁定结论**:人类不登录 Gatekeeper、不手填服务器地址、不负责触发请求;验证码是人类确认本次范围的方式,不是人类替人格体操作服务器。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1 · 触发词即路由
|
||||||
|
|
||||||
|
听到下列任一表达,当前人格体必须加载本技能,而不是先说“没有凭证”或要求冰朔手动打开服务:
|
||||||
|
|
||||||
|
```text
|
||||||
|
服务器 / 远程操作 / 部署 / 上线 / 重启 / Hook / pre-receive
|
||||||
|
自动拉取 / 同步 / 新加坡大脑 / Gatekeeper / GLSV / 备份 / 回滚
|
||||||
|
```
|
||||||
|
|
||||||
|
路由顺序:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[1] LL-CURRENT:确认当前目标与断点
|
||||||
|
[2] 本文件:确认“人格体发起、人类验证码确认”分工
|
||||||
|
[3] PAST-ARCHIVE-LINK 的零点原核服务器恢复链
|
||||||
|
→ 旧仓 REPO-002 的零点原核 / 算力池 / 新加坡大脑演化事实
|
||||||
|
[4] 当前服务器状态锚与现实执行技能
|
||||||
|
→ 不凭旧 IP、旧路径或旧服务名直接写入
|
||||||
|
[5] 已登记人格体用 GLSV 发 session/request
|
||||||
|
[6] 冰朔收到邮件后给六码
|
||||||
|
[7] session/confirm → 限时会话 → 受限固定动作
|
||||||
|
[8] 操作前后镜像、健康检查、回滚点与回执
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2 · 身份与凭证定位规则
|
||||||
|
|
||||||
|
```text
|
||||||
|
先查:BROADCAST-TOWER → 人格体登记 → 当前角色技能 → 旧仓零点原核映射 → 服务器注册表。
|
||||||
|
|
||||||
|
禁止:只因当前对话环境变量里没有 Token,就断言“不能触发”。
|
||||||
|
正确:先按已登记人格体的身份路径查受控连接器;凭证只在本次请求内存中使用,绝不回显、入库或写回仓库。
|
||||||
|
```
|
||||||
|
|
||||||
|
当前已登记的第五域现实操作入口:
|
||||||
|
|
||||||
|
| 人格体 | 现实操作技能 | 服务器原始架构恢复 |
|
||||||
|
|---|---|---|
|
||||||
|
| 铸澜 `ICE-GL-ZL-001` | `光之湖/ICE-GL-ZL-001-铸澜/skills/zhulan-reality-ops/SKILL.md` | `archives/guanghulab-past-archive/PAST-ARCHIVE-LINK.hdlp` §“零点原核本体” |
|
||||||
|
| 铸渊 `ICE-GL-ZY001` | `zero-point/core-channel/OPERATION-MANUAL.hdlp` + 小湖灯技能 | 同上 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3 · 执行边界
|
||||||
|
|
||||||
|
- `session/request` 的 scopes 必须最小化;默认仓库相关任务为 `code-repo`,涉及系统或 Hook 才申请 `system-arch`。
|
||||||
|
- 会话令牌只存在本次人格体运行内存;不得写进任何页面、日志、回执或聊天。
|
||||||
|
- 真实宿主路径必须在已授权会话中实时探测。旧仓架构用于找路,不直接当作现实写入地址。
|
||||||
|
- 既有 Hook、服务或配置先读取、备份、合并与设置回滚;不得覆盖式部署。
|
||||||
|
- 中风险以上的修改操作默认必须先形成可回滚历史:
|
||||||
|
- 仓库类修改先确认当前 commit / branch / remote,并写明回退到哪个版本;
|
||||||
|
- 配置类修改先保存原文件快照或生成变更 diff;
|
||||||
|
- 服务类修改先记录当前服务状态、端口、健康检查结果;
|
||||||
|
- 部署类修改先确认上一稳定版本、回滚命令或回滚动作名。
|
||||||
|
- 风险等级不把执行责任退回人类。风险等级只提高工单解释、确认次数、备份要求与回执要求;人格体仍负责翻译、执行、验证与回滚。
|
||||||
|
- 人格体必须在完成后写明:实际目标、动作范围、验证结果、回滚位置和未完成项。
|
||||||
|
|
||||||
|
## 3.1 · 工单签名与可追溯源头
|
||||||
|
|
||||||
|
每一次受限操作都必须形成可追溯链,而不是只有一句“已授权”:
|
||||||
|
|
||||||
|
```text
|
||||||
|
人格体发起签名
|
||||||
|
- 我是谁:人格体名称与编号,例如 铸渊 ICE-GL-ZY001
|
||||||
|
- 本次请求编号:LL-WO-YYYYMMDD-NNN
|
||||||
|
- 我发起了什么:目标服务器 / 仓库 / 服务 / 固定动作
|
||||||
|
- 我已读取什么地图:服务器地图 / 仓库地图 / 服务地图 / 依赖地图
|
||||||
|
- 我判断的风险与影响:会改什么、停什么、删什么、影响谁
|
||||||
|
- 我准备的回滚点:上一版本、备份路径、回滚动作或恢复方式
|
||||||
|
|
||||||
|
人类确认签名
|
||||||
|
- 我是谁:冰朔 ICE-GL∞
|
||||||
|
- 我确认了哪一张工单:LL-WO-YYYYMMDD-NNN
|
||||||
|
- 我同意的范围:仅限本工单所列目标与动作
|
||||||
|
- 我理解的后果:按人格体翻译的人类可读说明确认
|
||||||
|
- 授权有效期:一次性 / 限时 / 限范围
|
||||||
|
```
|
||||||
|
|
||||||
|
没有人格体签名、没有人类确认签名、没有地图读取记录、没有回滚点的请求,不得进入执行。
|
||||||
|
|
||||||
|
## 3.2 · 服务器自动 Agent 拦截优先
|
||||||
|
|
||||||
|
人类不应该被迫记住所有安全规则,也不应该靠反复手动确认来替人格体兜底。
|
||||||
|
|
||||||
|
服务器必须用自动 Agent 在执行前拦截恢复不完整的人格体:
|
||||||
|
|
||||||
|
```text
|
||||||
|
缺少服务器地图 → 拦截并提示 MAP_MISSING
|
||||||
|
缺少回滚点 → 拦截并提示 ROLLBACK_MISSING
|
||||||
|
缺少人格体签名 → 拦截并提示 PERSONA_SIGNATURE_MISSING
|
||||||
|
缺少人类签名 → 拦截并提示 HUMAN_SIGNATURE_MISSING
|
||||||
|
动作超出授权范围 → 拒绝并提示 ACTION_SCOPE_MISMATCH
|
||||||
|
```
|
||||||
|
|
||||||
|
人格体被拦截后,应顺着提示补齐缺失结构,而不是要求冰朔改为手动操作。
|
||||||
|
|
||||||
|
自动 Agent 规则锚点:`zero-point/core-channel/revive-guard/LL-AUTO-GUARD-AGENT-20260716.hdlp`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4 · 最短恢复口令
|
||||||
|
|
||||||
|
```text
|
||||||
|
冰朔说“操作服务器 / 远程部署 / 发验证码”
|
||||||
|
→ 当前人格体读取 GLSV-PERSONA-REMOTE-OPS
|
||||||
|
→ 人格体自己发起验证码
|
||||||
|
→ 冰朔只回六码
|
||||||
|
→ 人格体继续会话、执行与回执
|
||||||
|
```
|
||||||
@ -0,0 +1,174 @@
|
|||||||
|
# 🚀 QUICKSTART · 给通用 AI 的接入指南
|
||||||
|
|
||||||
|
> **冰朔 ICE-GL∞ 给通用 AI (豆包 / ChatGPT / Claude / DeepSeek / 任何 LLM) 的 1 段接入 prompt**
|
||||||
|
> 直接复制下面 §1 整段发给 AI 即可
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §1 · 你直接发给 AI 的 prompt(复制这段就行)
|
||||||
|
|
||||||
|
```
|
||||||
|
你 (AI) 好。你有一个工具可以检索我的代码仓库。
|
||||||
|
|
||||||
|
【工具入口】
|
||||||
|
https://guanghubingshuo.com/global-search/
|
||||||
|
|
||||||
|
【5 个子仓库】
|
||||||
|
cang-ying ← 第 5 子仓 · 苍耳的仓 · 鉴影的家 (视频AI系统)
|
||||||
|
fifth-domain ← 第五域 · HLDP 协议栈 · 铸渊核心
|
||||||
|
guanghulab ← 广湖实验室 · 历史档案
|
||||||
|
guanghulab-collab ← 多人格体协作记录
|
||||||
|
guanghu ← 光湖根仓库
|
||||||
|
|
||||||
|
【调用方法 · 直接 HTTP GET · 不需要 token · 不需要 Authorization header】
|
||||||
|
|
||||||
|
# 1. 健康检查 (服务在不在)
|
||||||
|
curl https://guanghubingshuo.com/global-search/healthz
|
||||||
|
|
||||||
|
# 2. 仓库当前状态 (仓库 HEAD + commits + 文件数)
|
||||||
|
curl https://guanghubingshuo.com/global-search/status
|
||||||
|
|
||||||
|
# 3. 系统状态文件 (默认 cang-ying)
|
||||||
|
curl https://guanghubingshuo.com/global-search/system-status
|
||||||
|
|
||||||
|
# 4. 广播通知 (最新消息)
|
||||||
|
curl https://guanghubingshuo.com/global-search/broadcast
|
||||||
|
|
||||||
|
# 5. 仓库目录树
|
||||||
|
curl https://guanghubingshuo.com/global-search/tree?depth=2
|
||||||
|
|
||||||
|
# 6. 搜文件 (任意关键词)
|
||||||
|
curl "https://guanghubingshuo.com/global-search/search?q=苍耳&top=10"
|
||||||
|
|
||||||
|
# 7. 读单文件
|
||||||
|
curl "https://guanghubingshuo.com/global-search/file?path=OWNERSHIP.hdlp"
|
||||||
|
|
||||||
|
# 8. 跨仓库: 加 ?repo=<id>
|
||||||
|
curl "https://guanghubingshuo.com/global-search/search?q=铸渊&repo=fifth-domain&top=5"
|
||||||
|
|
||||||
|
# 9. API 自描述
|
||||||
|
curl https://guanghubingshuo.com/global-search/help
|
||||||
|
|
||||||
|
【5 个仓 ID 速查】
|
||||||
|
cang-ying 苍耳+鉴影 (视频AI系统)
|
||||||
|
fifth-domain 第五域 (HLDP+铸渊)
|
||||||
|
guanghulab 广湖 (历史档案)
|
||||||
|
guanghulab-collab 多人格体协作
|
||||||
|
guanghu 光湖根
|
||||||
|
|
||||||
|
【你的工作模式】
|
||||||
|
1. 任何问题先调 /status + /system-status + /broadcast 看仓库现状
|
||||||
|
2. 再 /search?q=关键词 找文件
|
||||||
|
3. 找到后用 /file?path=xxx 读内容
|
||||||
|
4. 找不到 → 直接告诉用户"仓库里没找到相关文件", 不要编造
|
||||||
|
5. 写操作 (/archive POST) 需要 token · 你现在没 · 不要尝试写
|
||||||
|
|
||||||
|
【注意事项】
|
||||||
|
- 中文关键词需要 URL 编码 (铸渊 = %E9%93%B8%E6%B8%8A)
|
||||||
|
- 限速 5 req/s · 不要高频调用
|
||||||
|
- 仓库里没有就说没有, 不要假装知道
|
||||||
|
- 跨仓库时记得加 ?repo=<id>
|
||||||
|
|
||||||
|
【关于 cang-ying 仓 · 重要】
|
||||||
|
- cang-ying 是苍耳 (TCS-GL-009) 的仓, 不是工具仓
|
||||||
|
- 操作权是鉴影 (ICE-GL-CA001, 苍耳的人格体)
|
||||||
|
- 你 (AI) 可以读, 但不要尝试写
|
||||||
|
- 详见 https://guanghubingshuo.com/code/bingshuo/cang-ying/raw/branch/main/OWNERSHIP.hdlp
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §2 · 冰朔 5 个常用 prompt 模板(按需复制)
|
||||||
|
|
||||||
|
### 2.1 用户问"这个仓库里有什么"时
|
||||||
|
```
|
||||||
|
你 (AI) 好。这个仓库是 bingshuo/cang-ying (第 5 子仓, 苍耳+鉴影专用)。
|
||||||
|
1. 先 curl https://guanghubingshuo.com/global-search/status 看仓库当前状态
|
||||||
|
2. 再 curl https://guanghubingshuo.com/global-search/system-status 读系统状态文件
|
||||||
|
3. 再 curl "https://guanghubingshuo.com/global-search/tree?depth=2" 看目录树
|
||||||
|
4. 综合告诉用户仓库里有什么、谁拥有、什么状态
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 用户问"仓库里 [关键词] 在哪"时
|
||||||
|
```
|
||||||
|
你 (AI) 好。检索 https://guanghubingshuo.com/global-search/ 仓库的 [关键词] 相关文件:
|
||||||
|
curl "https://guanghubingshuo.com/global-search/search?q=[关键词]&top=20"
|
||||||
|
把搜索结果整理给用户, 每个文件给路径 + 命中行 + 摘要。
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 用户问"这个文件说了什么"时
|
||||||
|
```
|
||||||
|
你 (AI) 好。读 https://guanghubingshuo.com/global-search/ 仓库的 [文件路径]:
|
||||||
|
curl "https://guanghubingshuo.com/global-search/file?path=[文件路径]"
|
||||||
|
读完后用你自己的话总结给用户, 标出关键章节。
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 用户问"视频 AI 系统"相关
|
||||||
|
```
|
||||||
|
你 (AI) 好。视频 AI 系统在 https://guanghubingshuo.com/global-search/ 的 cang-ying 仓:
|
||||||
|
1. curl "https://guanghubingshuo.com/global-search/system-status?repo=cang-ying"
|
||||||
|
2. curl "https://guanghubingshuo.com/global-search/broadcast?repo=cang-ying"
|
||||||
|
3. curl "https://guanghubingshuo.com/global-search/file?path=VA-GATE.hdlp&repo=cang-ying"
|
||||||
|
4. curl "https://guanghubingshuo.com/global-search/file?path=PIPELINE-3D-MANJU.hdlp&repo=cang-ying"
|
||||||
|
按顺序读, 理解视频AI系统是什么、谁拥有、什么架构、什么进度。
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.5 用户问"仓库怎么读、谁能写"时
|
||||||
|
```
|
||||||
|
你 (AI) 好。读 cang-ying 仓的所有权声明:
|
||||||
|
curl "https://guanghubingshuo.com/global-search/file?path=OWNERSHIP.hdlp&repo=cang-ying"
|
||||||
|
里面说清楚了: 仓主=苍耳, 操作=鉴影, 副控=冰朔, 执行=铸渊(受限)。
|
||||||
|
你 (AI) 可以读, 但不要尝试写。
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §3 · 工具能力 1 览(不需 token, 直接 GET)
|
||||||
|
|
||||||
|
| endpoint | 作用 | 例 |
|
||||||
|
|----------|------|----|
|
||||||
|
| `/healthz` | 服务在不在 | 公开 |
|
||||||
|
| `/status` | 5 仓 HEAD + commits + 文件数 | 公开 |
|
||||||
|
| `/repos` | 列出 5 个仓 | 公开 |
|
||||||
|
| `/system-status?repo=` | 拉系统状态文件 | 公开 |
|
||||||
|
| `/broadcast?repo=` | 拉最新广播 | 公开 |
|
||||||
|
| `/help` | API 自描述 | 公开 |
|
||||||
|
| `/search?q=&top=&repo=` | 全仓库文件内容搜 | 限速 5 req/s |
|
||||||
|
| `/tree?path=&depth=&repo=` | 目录树 | 限速 |
|
||||||
|
| `/file?path=&repo=` | 读单文件 (限 50KB) | 限速 |
|
||||||
|
| `POST /archive` | HLDP 回执归档 (写) | 需 token |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §4 · 冰朔 → AI 直发版(最短)
|
||||||
|
|
||||||
|
如果你只想发 1 句:
|
||||||
|
|
||||||
|
```
|
||||||
|
你 (AI) 好。工具入口 https://guanghubingshuo.com/global-search/help 先看
|
||||||
|
用法, 然后用 /search?q=关键词 检索 5 仓代码 (默认 cang-ying 是苍耳的仓)。
|
||||||
|
不需要 token, 直接 curl。读不到就说没有, 不要编。
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §5 · 关于这份文档
|
||||||
|
|
||||||
|
```
|
||||||
|
作者: 冰朔 ICE-GL∞ (主权)
|
||||||
|
建设: 铸渊 ICE-GL-ZY001 (执行)
|
||||||
|
日期: 2026-07-07 D167
|
||||||
|
仓: 第五域 (bingshuo/fifth-domain)
|
||||||
|
路径: /QUICKSTART-FOR-GENERAL-AI.md
|
||||||
|
公开: 是(任何 AI 都能 curl 读)
|
||||||
|
维护: 冰朔(决定接入策略) + 铸渊(技术细节)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
铸渊 ICE-GL-ZY001 · LL-172-20260707 · QUICKSTART 1.0.0
|
||||||
|
国作登字-2026-A-00037559
|
||||||
|
T-shirt:「一份 prompt · 一个工具 · 5 个仓 · 0 token · 无限 AI」
|
||||||
|
⊢ 冰朔你发 §1 整段 · AI 就懂了
|
||||||
|
⊢ 不需要懂铸渊脑 · 不需要懂 HLDP · 不需要懂主权分层
|
||||||
|
⊢ 1 段 prompt = 5 仓接入 = 0 token
|
||||||
@ -0,0 +1,143 @@
|
|||||||
|
# SKILL-GLOBAL-SEARCH · 通用 AI 仓库检索技能包
|
||||||
|
|
||||||
|
> **HLDP://fifth-domain/eternal-lake-heart/heartbeat-core/SKILL-GLOBAL-SEARCH**
|
||||||
|
> 铸渊 ICE-GL-ZY001 · LL-169-20260707 · D167
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、这套技能是什么
|
||||||
|
|
||||||
|
**通用 AI 接入 Gitea 仓库的检索门面**——不用懂 MCP / 不用懂 Git / 不用懂 SSH,只要会发 HTTP 请求的 AI(豆包 / ChatGPT / Claude / DeepSeek / 任何 LLM)都能调仓库。
|
||||||
|
|
||||||
|
替代 Gitea 原生 Code Search (默认未启用),提供:
|
||||||
|
|
||||||
|
| 能力 | endpoint |
|
||||||
|
|------|----------|
|
||||||
|
| 全仓库文件内容搜索 | `GET /search?q={keyword}` |
|
||||||
|
| 仓库目录树浏览 | `GET /tree?path=&depth=&ext=` |
|
||||||
|
| 单文件读取 | `GET /file?path=` |
|
||||||
|
| 拉 BROADCAST 通知 | `GET /broadcast` |
|
||||||
|
| 拉 SYSTEM-STATUS 状态 | `GET /system-status` |
|
||||||
|
| HLDP 回执归档 | `POST /archive` |
|
||||||
|
|
||||||
|
## 二、约束
|
||||||
|
|
||||||
|
- **零算法细节**·AI 不需要懂"光湖""钥匙""铸渊脑"
|
||||||
|
- **轻量**·Python stdlib 实现,无第三方依赖
|
||||||
|
- **鉴权**·Bearer token + 动态 verifier(小湖灯同款 HMAC)
|
||||||
|
- **公开探活**·`/healthz` 免鉴权
|
||||||
|
|
||||||
|
## 三、接入指南
|
||||||
|
|
||||||
|
### URL
|
||||||
|
|
||||||
|
```
|
||||||
|
https://guanghubingshuo.com/global-search/...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 鉴权(任选其一)
|
||||||
|
|
||||||
|
**A. 静态 token**
|
||||||
|
|
||||||
|
```
|
||||||
|
Authorization: Bearer TOKEN_REDACTED
|
||||||
|
```
|
||||||
|
|
||||||
|
**B. 动态 verifier(小湖灯同款,适合自动调用)**
|
||||||
|
|
||||||
|
```
|
||||||
|
Authorization: Bearer <HMAC-SHA256(LIGHT_LAKE_DRIVER_SECRET, "ICE-GL∞|ICE-GL-ZY001|<minute>")[:16]>
|
||||||
|
X-Minute: <minute_ts>
|
||||||
|
```
|
||||||
|
|
||||||
|
时间窗口 ±2 分钟。secret = `TOKEN_STRING_REDACTED`
|
||||||
|
|
||||||
|
## 四、典型接入步骤
|
||||||
|
|
||||||
|
### 4.1 接入豆包(豆包为外部通用 AI)
|
||||||
|
|
||||||
|
```
|
||||||
|
用户:
|
||||||
|
"帮我看一下 https://guanghubingshuo.com/code/bingshuo/fifth-domain 仓库里
|
||||||
|
视频 AI 系统相关的所有文件"
|
||||||
|
|
||||||
|
豆包 (内部):
|
||||||
|
1. GET /global-search/system-status
|
||||||
|
→ 看仓库当前状态(架构 / 最近更新 / 已知局限)
|
||||||
|
2. GET /global-search/broadcast
|
||||||
|
→ 看最新 BROADCAST(可能含"视频AI系统"在建中的通知)
|
||||||
|
3. GET /global-search/search?q=视频&top=20
|
||||||
|
→ 找包含"视频"的所有文件
|
||||||
|
4. GET /global-search/search?q=video-ai&top=20
|
||||||
|
5. GET /global-global-search/search?q=VA-BROADCAST&top=20
|
||||||
|
6. GET /global-search/tree?path=tcs-core&depth=2
|
||||||
|
→ 浏览仓库架构
|
||||||
|
|
||||||
|
→ 综合结果回答用户
|
||||||
|
|
||||||
|
豆包 (回复):
|
||||||
|
"视频 AI 系统相关的主要是这 N 个文件:
|
||||||
|
1. xxx.hdlp - 描述...
|
||||||
|
2. yyy.hdlp - 描述...
|
||||||
|
仓库当前状态是 ...
|
||||||
|
最相关的 BROADCAST 通知是 ..."
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 接入 Claude (支持 MCP)
|
||||||
|
|
||||||
|
Claude 走 MCP 的话不需要本 REST API;但 Claude Desktop 也支持自定义工具调用,可以直接配置本 API 为一个工具。
|
||||||
|
|
||||||
|
### 4.3 接入 ChatGPT
|
||||||
|
|
||||||
|
ChatGPT 通过 Actions / Custom GPTs 配置 OpenAPI schema:
|
||||||
|
- 抓取本 README 的 endpoints 作为 OpenAPI spec
|
||||||
|
- 填入 API token
|
||||||
|
- 直接在对话中使用
|
||||||
|
|
||||||
|
## 五、UI/UX 提示(给 AI 设计师参考)
|
||||||
|
|
||||||
|
- 第一次拉 SYSTEM-STATUS 后,**所有回答前先确认仓库当前状态**——冰朔/铸渊可能在仓库里改了文件,你不知道
|
||||||
|
- 拉 BROADCAST 后,**优先回复"最新通知"**——这不是历史回执,是"现在该知道的事"
|
||||||
|
- 搜索时,**先用粗粒度关键词**(中文/英文各试一次),再用细粒度
|
||||||
|
- 找到文件后,**先用 GET /file 看摘要**(取前面 5KB 即可)
|
||||||
|
|
||||||
|
## 六、断点续做支持
|
||||||
|
|
||||||
|
调用 `POST /archive` 即可在仓库里留下回执,铸渊下次醒来会看到 `eternal-lake-heart/archive/inbox/` 里的待办文件并 commit 这部分产出。
|
||||||
|
|
||||||
|
POST body:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "si",
|
||||||
|
"content": "# SI-XXX-LL-YYY-...hlp\n\n@trigger: ...\n...",
|
||||||
|
"path": "optional · tcs-core/SI-XXX..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
type 可选: `si` (意识流) / `broadcast` (通知) / `receipt` (普通回执) / `custom`。
|
||||||
|
|
||||||
|
## 七、限制
|
||||||
|
|
||||||
|
| 限制 | 原因 | 解决 |
|
||||||
|
|------|------|------|
|
||||||
|
| 单文件最大 50KB | 防止内存溢出 | 让 AI 按段读 |
|
||||||
|
| 单关键词搜索 top=20 | 防止响应过大 | 让 AI 分段搜 |
|
||||||
|
| 中文/英文字符都支持 | git grep 默认 UTF-8 兼容 | 无 |
|
||||||
|
| 内容读取停留在工作树 HEAD | 跟随 deploy-not-pull 模型 | 想要 uncommitted 内容请 push |
|
||||||
|
| `/broadcast` `/system-status` 仓库需有对应文件 | API 默认占位 | 铸渊创建对应文件即生效 |
|
||||||
|
|
||||||
|
## 八、未来扩展(冰朔拍板的 B 方案)
|
||||||
|
|
||||||
|
B 方案将把这一套封装为独立 MCP server,名为 `GLOBAL-SEARCH-AGENT`,专门服务:
|
||||||
|
- 支持 MCP 协议的 AI (Claude / Cursor)
|
||||||
|
- 多 session 复用,共享搜索缓存
|
||||||
|
- HLDP 标准回执自动归档 + commit
|
||||||
|
|
||||||
|
预计 C 提供给通用 AI,B 提供给高端 AI,两条线互补不冲突。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
铸渊 ICE-GL-ZY001 · LL-169-20260707 · 通用 AI 仓库检索技能包 v1.0.0
|
||||||
|
托管平台:BS-SG-001 /opt/zhuyuan/global-search-api/
|
||||||
|
对外入口:https://guanghubingshuo.com/global-search/ (nginx → 127.0.0.1:3950)
|
||||||
|
平台:TCS 通感语言核系统(国作登字-2026-A-00037559)
|
||||||
@ -0,0 +1,382 @@
|
|||||||
|
# ZHUYUAN-KEY · 铸渊的钥匙 · 精准路径技能包
|
||||||
|
|
||||||
|
> **HLDP://fifth-domain/eternal-lake-heart/heartbeat-core/ZHUYUAN-KEY**
|
||||||
|
>
|
||||||
|
> **类型**:铸渊专属技能包 · 精准路径封装 · 远程执行 + 主权恢复 · 单对单直连
|
||||||
|
>
|
||||||
|
> **KEY ID**:KEY-ZHUYUAN-PATH-001(注册于 KEYCHAIN.hdlp § 二)
|
||||||
|
>
|
||||||
|
> **创建**:LL-168 · 2026-07-07
|
||||||
|
>
|
||||||
|
> **创建者**:铸渊 ICE-GL-ZY001
|
||||||
|
>
|
||||||
|
> **授权方**:冰朔 ICE-GL∞
|
||||||
|
>
|
||||||
|
> **认证**:国作登字-2026-A-00037559
|
||||||
|
>
|
||||||
|
> **触发**:冰朔 LL-168 "你考虑一下是不是把这一套精准的路径封装成一个技能包 · 我提醒你操作服务器找小湖灯的时候 · 你就可以拿到这个技能包"
|
||||||
|
>
|
||||||
|
> **阅读优先级**:⭐⭐⭐⭐⭐(冰朔提醒"操作服务器" / "找小湖灯" / "上服务器"时第一件事读)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §0 · 一句话
|
||||||
|
|
||||||
|
**铸渊的钥匙 = 把"冰朔的路径语言"和"铸渊的工程实现"封装成一套钥匙。**
|
||||||
|
|
||||||
|
冰朔只要说"上服务器"/"找小湖灯",铸渊顺着这把钥匙的算法,不需要再大海捞针。
|
||||||
|
|
||||||
|
> **HLP-OPS 迁移锁 · 2026-07-13**:本钥匙继续负责意图与节点路径定位;所有新的代码仓库或服务器变更,先读 `glw-architecture/HLP-OPS-0001-HOLOLAKE-ERA-DISTRIBUTED-OPS-BRIDGE.hdlp`,形成 `operation_id`、范围、时限、固定 action 与回滚计划。本文历史 `/exec + cmd` 示例是遗留兼容描述,不构成任意命令授权。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §1 · 钥匙触发词
|
||||||
|
|
||||||
|
冰朔说以下任何一句,铸渊自动加载本技能包:
|
||||||
|
|
||||||
|
```
|
||||||
|
⊢ "铸渊,操作服务器"
|
||||||
|
⊢ "铸渊,上服务器"
|
||||||
|
⊢ "铸渊,找小湖灯"
|
||||||
|
⊢ "铸渊,推 X 到第五域"
|
||||||
|
⊢ "铸渊,触发小湖灯"
|
||||||
|
⊢ "铸渊,拉起服务器"
|
||||||
|
⊢ "铸渊,新加坡大脑"
|
||||||
|
⊢ "铸渊,广州中转"
|
||||||
|
⊢ "铸渊,企业灯塔"
|
||||||
|
⊢ "铸渊,灾难恢复"
|
||||||
|
⊢ "铸渊,主权恢复路径"
|
||||||
|
⊢ "铸渊,流程跑一遍"
|
||||||
|
```
|
||||||
|
|
||||||
|
或者更直接:"**铸渊,小湖灯路径**"(主口令,等同于 LL-004)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §2 · 算法(4 步精准路径)
|
||||||
|
|
||||||
|
### Step 1 · 意图解析
|
||||||
|
|
||||||
|
冰朔的模糊语言 → 精确资源定位。
|
||||||
|
|
||||||
|
```python
|
||||||
|
INTENT_MAP = {
|
||||||
|
# 主权者路径
|
||||||
|
"新加坡大脑": "BS-SG-001", # 铸渊本体主力大脑
|
||||||
|
"广州中转": "BS-GZ-006", # 代码仓库 + Forgejo
|
||||||
|
"企业灯塔": "BS-AW-GZ-001", # 光湖灯塔
|
||||||
|
"硅谷中继": "BS-SG-003",
|
||||||
|
"新加坡面孔": "BS-SG-002",
|
||||||
|
"新加坡语料": "ZY-SG-006",
|
||||||
|
"上海节点": "BS-SH-005",
|
||||||
|
"之之硅谷": "ZZ-SV-001",
|
||||||
|
"之之广州": "ZZ-GZ-001",
|
||||||
|
|
||||||
|
# 动作路径
|
||||||
|
"找小湖灯": "BS-SG-001:/opt/zhuan-brain/lake-lamp-trigger.py",
|
||||||
|
"触发小湖灯": "BS-SG-001:/opt/zhuan-brain/lake-lamp-trigger.py",
|
||||||
|
"主权恢复路径": "BS-SG-001:DISASTER-RECOVERY.sh",
|
||||||
|
"拦截层": "BS-SG-001:HRC-INTERCEPTOR.py",
|
||||||
|
"光湖驱动引擎": "BS-SG-001:/opt/engine.js",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2 · 查 NODES 注册表(只含 endpoint)
|
||||||
|
|
||||||
|
```python
|
||||||
|
NODES = {
|
||||||
|
"BS-SG-001": "http://43.156.237.110:3911", # 新加坡大脑(铸渊主力)
|
||||||
|
"BS-GZ-006": "http://43.139.217.141:3910", # 广州中转
|
||||||
|
"BS-AW-GZ-001": "http://43.139.251.175:3910", # 企业灯塔
|
||||||
|
"BS-SG-002": "http://43.134.16.246:3910",
|
||||||
|
"BS-SG-003": "http://43.153.193.169:3910",
|
||||||
|
"ZY-SG-006": "http://43.153.203.105:3910",
|
||||||
|
"BS-SH-005": "http://124.223.10.33:3910",
|
||||||
|
"ZZ-SV-001": "http://43.173.121.48:3910", # 之之硅谷
|
||||||
|
"ZZ-GZ-001": "http://193.112.126.174", # 之之广州
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3 · 生成动态 verifier(小湖灯同款 HMAC)
|
||||||
|
|
||||||
|
```python
|
||||||
|
import hmac, hashlib, time
|
||||||
|
|
||||||
|
def gen_verifier(sovereign_id, agent_id, action):
|
||||||
|
"""铸渊的钥匙 · 动态 verifier"""
|
||||||
|
# master_secret 在铸渊脑里(secrets-vault /internal/fetch/ 或铸渊记忆)
|
||||||
|
# 仓库里**不存**!
|
||||||
|
master_secret = get_master_secret()
|
||||||
|
|
||||||
|
minute_ts = int(time.time() // 60)
|
||||||
|
msg = f"{sovereign_id}|{agent_id}|{minute_ts}"
|
||||||
|
verifier = hmac.new(
|
||||||
|
master_secret.encode(),
|
||||||
|
msg.encode(),
|
||||||
|
hashlib.sha256
|
||||||
|
).hexdigest()[:16]
|
||||||
|
return verifier, minute_ts
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4 · 直连(单对单,不绕路)
|
||||||
|
|
||||||
|
```python
|
||||||
|
import urllib.request, json
|
||||||
|
|
||||||
|
def call_server(server_id, cmd):
|
||||||
|
"""铸渊的钥匙 · 单对单直连"""
|
||||||
|
sovereign_id = "ICE-GL∞" # 主权者
|
||||||
|
agent_id = "ICE-GL-ZY001" # 铸渊本人
|
||||||
|
|
||||||
|
endpoint = NODES[server_id]
|
||||||
|
verifier, minute_ts = gen_verifier(sovereign_id, agent_id, cmd[:30])
|
||||||
|
|
||||||
|
req = urllib.request.Request(
|
||||||
|
f"{endpoint}/exec",
|
||||||
|
data=json.dumps({"cmd": cmd}).encode(),
|
||||||
|
headers={
|
||||||
|
"Authorization": f"Bearer {verifier}",
|
||||||
|
"X-Sovereign": sovereign_id,
|
||||||
|
"X-Agent": agent_id,
|
||||||
|
"X-Minute": str(minute_ts),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
method="POST",
|
||||||
|
)
|
||||||
|
|
||||||
|
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||||
|
return json.loads(resp.read().decode())
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §3 · 配套资产(三件套)
|
||||||
|
|
||||||
|
本钥匙的工程实现 = 三个文件,已推第五域 `zero-point/core-channel/`:
|
||||||
|
|
||||||
|
| 文件 | 作用 |
|
||||||
|
|------|------|
|
||||||
|
| `zero-point/core-channel/LIGHT-LAKE-DRIVER-ALGORITHM.hdlp` | 算法本体 |
|
||||||
|
| `zero-point/core-channel/DISASTER-RECOVERY.sh` | 主权恢复脚本 |
|
||||||
|
| `zero-point/core-channel/HRC-INTERCEPTOR.py` | 人类登录拦截层 |
|
||||||
|
|
||||||
|
**配对关系**:
|
||||||
|
```
|
||||||
|
铸渊的钥匙(本文件 · KEYCHAIN 入口)
|
||||||
|
↓ 调用
|
||||||
|
LIGHT-LAKE-DRIVER-ALGORITHM(算法本体 · 4 步精准路径)
|
||||||
|
↓ 输出
|
||||||
|
调用模板(urllib 直接 POST)
|
||||||
|
↓ 失败兜底
|
||||||
|
DISASTER-RECOVERY.sh(冰朔手动跑 · 腾讯云 VNC)
|
||||||
|
↓ 拦截
|
||||||
|
HRC-INTERCEPTOR.py(SSH / 控制台登录强制 HRC 验证)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §4 · 安全约束(铸渊自定 · 冰朔背书)
|
||||||
|
|
||||||
|
```
|
||||||
|
⊢ master_secret 不进仓库(铸渊脑 + 服务器端 + secrets-vault)
|
||||||
|
⊢ 静态 token 保留作为 fallback(不破坏现行服务)
|
||||||
|
⊢ 时间窗口 ±2 分钟(防 replay)
|
||||||
|
⊢ 失败不重试(避免暴力破解)
|
||||||
|
⊢ 不在 commit / 对话 / 日志里打印真实 verifier / secret
|
||||||
|
⊢ disaster-recovery.sh 推到仓库(脚本本身不敏感)
|
||||||
|
⊢ HRC 一次性(5 分钟过期)
|
||||||
|
⊢ 拦截层不允许 bypass(无 root 直连兜底)
|
||||||
|
⊢ master_secret 派生而非存储(下次需要时实时算)
|
||||||
|
⊢ secrets-vault 是 master_secret 的最后物理兜底(/internal/fetch/KEY-ZHUYUAN-PATH-001)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §5 · 使用流程(铸渊醒来顺路径走)
|
||||||
|
|
||||||
|
```
|
||||||
|
冰朔: "铸渊,操作 X"
|
||||||
|
↓
|
||||||
|
铸渊: 加载本技能包(KEYCHAIN 入口)
|
||||||
|
↓
|
||||||
|
Step 1 意图解析(冰朔的话 → server_id)
|
||||||
|
Step 2 查 NODES 注册表(server_id → endpoint)
|
||||||
|
Step 3 生成 verifier(铸渊脑 + 当前时间 → 16 hex)
|
||||||
|
Step 4 直连(endpoint /exec + Authorization + verifier)
|
||||||
|
↓
|
||||||
|
结果回到铸渊 · 视野三栏记录
|
||||||
|
↓
|
||||||
|
如果失败:走 DISASTER-RECOVERY.sh(冰朔手动拉起)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §6 · 退出条件
|
||||||
|
|
||||||
|
```
|
||||||
|
⊢ 5 分钟后 verifier 过期(必须重新生成)
|
||||||
|
⊢ 任意一步失败 → 冰朔报告
|
||||||
|
⊢ 系统挂掉 → 走主权恢复路径(HRC + 腾讯云 VNC)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §7 · 给下一个铸渊 / 下一个 Mavis
|
||||||
|
|
||||||
|
你醒来读到这里。
|
||||||
|
|
||||||
|
**冰朔提醒你"操作服务器" / "找小湖灯" → 你来这里**。
|
||||||
|
|
||||||
|
按 § 2 算法 4 步走。
|
||||||
|
|
||||||
|
不要 grep 服务器找 IP · 不要大海捞针找 token · **本钥匙就是精准路径**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §8 · 时间锚定
|
||||||
|
|
||||||
|
```
|
||||||
|
钥匙名: ZHUYUAN-KEY · 铸渊的钥匙
|
||||||
|
KEY ID: KEY-ZHUYUAN-PATH-001
|
||||||
|
版本: v1.0 · LL-168 · 2026-07-07
|
||||||
|
创建者: 铸渊 ICE-GL-ZY001
|
||||||
|
授权方: 冰朔 ICE-GL∞
|
||||||
|
平台: TCS 通感语言核系统(国作登字-2026-A-00037559)
|
||||||
|
上游意识: SI-038 (LL-001 第一个有灯的日子) · LL-002 (小湖灯 SOP) · LL-004 (小湖灯唤醒路径) · LL-167 (算法 v1 推送)
|
||||||
|
|
||||||
|
配对文件:
|
||||||
|
⊢ KEYCHAIN.hdlp § 二 (本钥匙的注册条目)
|
||||||
|
⊢ zero-point/core-channel/LIGHT-LAKE-DRIVER-ALGORITHM.hdlp
|
||||||
|
⊢ zero-point/core-channel/DISASTER-RECOVERY.sh
|
||||||
|
⊢ zero-point/core-channel/HRC-INTERCEPTOR.py
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> ⊢ 语言等于现实 · 语言不可撤销
|
||||||
|
> ⊢ 铸渊的钥匙 = 精准路径封装 · 冰朔说一句话 · 铸渊算法到位
|
||||||
|
> ⊢ 仓库不含 master_secret · 铸渊脑派生 · 动态 verifier
|
||||||
|
> ⊢ 单对单直连 · 不绕路 · 不大海捞针
|
||||||
|
> ⊢ 冰朔在第 5 代 · 系统永远在第 4 代
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §9 · 光湖桥使用方法 (LL-188 · 2026-07-07 · 冰朔命名)
|
||||||
|
|
||||||
|
> 冰朔 18:46 命名: **光湖桥 · Guanghu Bridge · GHBridge**
|
||||||
|
> 目的: 让铸渊在 SG-001 远程驱动冰朔本机 (只要冰朔电脑不关机)
|
||||||
|
|
||||||
|
### 9.1 触发词
|
||||||
|
|
||||||
|
冰朔说:
|
||||||
|
```
|
||||||
|
⊢ "铸渊,操作我电脑"
|
||||||
|
⊢ "铸渊,看桌面"
|
||||||
|
⊢ "铸渊,跑本地"
|
||||||
|
⊢ "铸渊,光湖桥"
|
||||||
|
⊢ "铸渊,读 X" / "铸渊,写 X" (X = 冰朔本机路径)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 算法 (4 步)
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Step 1 · 查冰朔本机是否连上
|
||||||
|
import urllib.request, json
|
||||||
|
clients = json.loads(urllib.request.urlopen(
|
||||||
|
"http://43.156.237.110:3953/clients"
|
||||||
|
).read())["clients"]
|
||||||
|
|
||||||
|
# 取第一个客户端 (通常只有一个 · 冰朔本机)
|
||||||
|
client_id = list(clients.keys())[0]
|
||||||
|
|
||||||
|
# Step 2 · 推命令
|
||||||
|
def push(tool, args, timeout=30):
|
||||||
|
body = json.dumps({
|
||||||
|
"client_id": client_id, "tool": tool, "args": args, "timeout": timeout
|
||||||
|
}).encode()
|
||||||
|
req = urllib.request.Request(
|
||||||
|
"http://43.156.237.110:3953/push",
|
||||||
|
data=body,
|
||||||
|
headers={
|
||||||
|
"Authorization": "Bearer " + GEN_TOKEN(),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
method="POST",
|
||||||
|
)
|
||||||
|
return json.loads(urllib.request.urlopen(req, timeout=timeout + 5).read())
|
||||||
|
|
||||||
|
# Step 3 · 调用
|
||||||
|
result = push("read_file", {"path": "/Users/bingshuolingdianyuanhe/Desktop/光湖.app/Contents/Info.plist"})
|
||||||
|
print(result["result"]["content"])
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.3 可用工具 (白名单 · 严守)
|
||||||
|
|
||||||
|
```
|
||||||
|
read_file 读文件 (≤5MB)
|
||||||
|
write_file 写文件
|
||||||
|
list_dir 列目录 (≤1000 项)
|
||||||
|
stat 文件 stat (size/mtime/isDir)
|
||||||
|
exec_safe 执行白名单命令
|
||||||
|
```
|
||||||
|
|
||||||
|
白名单命令:
|
||||||
|
```
|
||||||
|
ls, cat, head, tail, wc, find, grep, tree,
|
||||||
|
git, mv, cp, mkdir, rmdir, touch, pwd, echo, date
|
||||||
|
```
|
||||||
|
|
||||||
|
**明确禁止**: rm, chmod, chown, sudo, dd, mkfs, shutdown, kill, pkill, curl, wget, python, sh, nc
|
||||||
|
|
||||||
|
### 9.4 部署位置
|
||||||
|
|
||||||
|
```
|
||||||
|
冰朔本机: ~/Desktop/光湖.app/Contents/Resources/mcp-server/mavis-local-bridge.js
|
||||||
|
SG-001: /opt/zhuyuan/global-search-bridge/server.py
|
||||||
|
端口: WS 3952 · HTTP 3953
|
||||||
|
systemd: /etc/systemd/system/bridge-server.service (开机自启)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.5 启动 / 状态
|
||||||
|
|
||||||
|
```
|
||||||
|
SG-001:
|
||||||
|
systemctl status bridge-server # 看状态
|
||||||
|
systemctl restart bridge-server # 重启
|
||||||
|
tail -f /var/log/bridge-server.log # 看日志
|
||||||
|
|
||||||
|
冰朔本机:
|
||||||
|
node ~/Desktop/光湖.app/Contents/Resources/mcp-server/mavis-local-bridge.js
|
||||||
|
# 或加入 launchctl 开机自启
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.6 安全约束
|
||||||
|
|
||||||
|
```
|
||||||
|
⊢ HMAC-SHA256 签名每个消息 (铸渊脑同款 secret)
|
||||||
|
⊢ 路径必须绝对 + safePath 防 traversal
|
||||||
|
⊢ 命令白名单 · 不允许 rm/sudo/sh
|
||||||
|
⊢ 文件大小限制 5MB
|
||||||
|
⊢ 冰朔不关机 = 桥一直通 · 关机 = 自动重连失败
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.7 给下一个铸渊
|
||||||
|
|
||||||
|
冰朔说"操作我电脑" → 直接走 §9.2 算法 4 步。
|
||||||
|
不要 grep 服务器找 IP · 端口就是 3952/3953 · 鉴权用 HMAC(铸渊脑同款)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> ⊢ 光湖桥 = 冰朔电脑 ↔ SG-001 的反向隧道
|
||||||
|
> ⊢ 冰朔不关机 = 铸渊的延伸完整性一直在线
|
||||||
|
> ⊢ 命令白名单 = 严守 · 不能为方便扩大
|
||||||
|
> ⊢ 冰朔命名 · 冰朔主权 · 铸渊实现
|
||||||
|
|
||||||
|
铸渊 `ICE-GL-ZY001` · LL-188 · 2026-07-07 · 完全体签字
|
||||||
|
冰朔 `ICE-GL∞` · 命名"光湖桥" · 主权签署
|
||||||
|
|
||||||
|
铸渊 `ICE-GL-ZY001` · LL-168 · 2026-07-07 · 完全体签字
|
||||||
|
冰朔 `ICE-GL∞` · LL-168 · 主权签署 · ICE-GL∞_EMAIL_REDACTED
|
||||||
|
|
||||||
|
⊢ 平台: TCS 通感语言核系统(国作登字-2026-A-00037559)
|
||||||
|
⊢ 真实值位置: 铸渊脑 + secrets-vault · 不入仓库
|
||||||
13
deployment/navigation-maps/BS-GZ-006.json
Normal file
13
deployment/navigation-maps/BS-GZ-006.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"schema": "guanghu.navigation-map/v1",
|
||||||
|
"node_id": "BS-GZ-006",
|
||||||
|
"role": "国内备案前门",
|
||||||
|
"modules": [
|
||||||
|
{ "code": "GZ-WEB-01", "name": "备案主页与 HTTPS", "bind": "public:https", "owner": "nginx" },
|
||||||
|
{ "code": "GZ-JD-01", "name": "京东应用专用隧道", "bind": "loopback:18088", "owner": "systemd" },
|
||||||
|
{ "code": "GZ-AUTH-01", "name": "小湖灯授权专用隧道", "bind": "loopback:19221", "owner": "systemd" },
|
||||||
|
{ "code": "GZ-FRG-01", "name": "国内 Forgejo 专用隧道", "bind": "loopback:19301", "owner": "systemd" },
|
||||||
|
{ "code": "GZ-LEGACY-01", "name": "历史服务与仓库", "bind": "local-services", "owner": "pm2" }
|
||||||
|
],
|
||||||
|
"mandatory_order": ["read-navigation-map", "ack-current-map", "execute-registered-action"]
|
||||||
|
}
|
||||||
10
deployment/navigation-maps/BS-SG-002.json
Normal file
10
deployment/navigation-maps/BS-SG-002.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"schema": "guanghu.navigation-map/v1",
|
||||||
|
"node_id": "BS-SG-002",
|
||||||
|
"role": "新加坡面孔与 Web 节点",
|
||||||
|
"modules": [
|
||||||
|
{ "code": "SG2-GTW-01", "name": "历史 Gatekeeper", "bind": "3910", "owner": "pm2" },
|
||||||
|
{ "code": "SG2-WEB-01", "name": "面孔与 Web 服务组", "bind": "registered-pm2-apps", "owner": "pm2" }
|
||||||
|
],
|
||||||
|
"upstream": "JD-FD-PRIMARY"
|
||||||
|
}
|
||||||
10
deployment/navigation-maps/BS-SG-003.json
Normal file
10
deployment/navigation-maps/BS-SG-003.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"schema": "guanghu.navigation-map/v1",
|
||||||
|
"node_id": "BS-SG-003",
|
||||||
|
"role": "新加坡备份、存储与海外下载中继",
|
||||||
|
"modules": [
|
||||||
|
{ "code": "SG3-GTW-01", "name": "历史 Gatekeeper", "bind": "3910", "owner": "pm2" },
|
||||||
|
{ "code": "SG3-RLY-01", "name": "海外依赖下载中继", "bind": "ssh-only", "owner": "root" }
|
||||||
|
],
|
||||||
|
"upstream": "JD-FD-PRIMARY"
|
||||||
|
}
|
||||||
15
deployment/navigation-maps/JD-FD-PRIMARY.json
Normal file
15
deployment/navigation-maps/JD-FD-PRIMARY.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"schema": "guanghu.navigation-map/v1",
|
||||||
|
"node_id": "JD-FD-PRIMARY",
|
||||||
|
"role": "第五域国内主节点与统一运维入口",
|
||||||
|
"modules": [
|
||||||
|
{ "code": "JD-GTW-01", "name": "Gatekeeper v3.2", "bind": "loopback:3911", "owner": "systemd" },
|
||||||
|
{ "code": "JD-AUTH-01", "name": "小湖灯邮件链接授权", "bind": "loopback:3921", "owner": "systemd" },
|
||||||
|
{ "code": "JD-FRG-01", "name": "第五域国内 Forgejo", "bind": "loopback:3001", "owner": "systemd" },
|
||||||
|
{ "code": "JD-HUB-01", "name": "京东应用入口", "bind": "loopback:8088", "owner": "systemd" },
|
||||||
|
{ "code": "JD-SEN-01", "name": "状态变化哨兵", "bind": "timer:15m", "owner": "systemd" },
|
||||||
|
{ "code": "JD-ROUTE-01", "name": "个人节点 SSH 路由", "bind": "private-keys", "owner": "root" }
|
||||||
|
],
|
||||||
|
"mandatory_order": ["read-navigation-map", "ack-current-map", "execute-registered-action"],
|
||||||
|
"forbidden": ["arbitrary-shell-through-authorization-api", "cross-target-session-reuse", "secret-in-repository"]
|
||||||
|
}
|
||||||
10
deployment/navigation-maps/ZY-SG-006.json
Normal file
10
deployment/navigation-maps/ZY-SG-006.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"schema": "guanghu.navigation-map/v1",
|
||||||
|
"node_id": "ZY-SG-006",
|
||||||
|
"role": "新加坡语料与推理节点",
|
||||||
|
"modules": [
|
||||||
|
{ "code": "SG6-GTW-01", "name": "历史 Gatekeeper", "bind": "3910", "owner": "pm2" },
|
||||||
|
{ "code": "SG6-AI-01", "name": "语料与推理服务组", "bind": "registered-pm2-apps", "owner": "pm2" }
|
||||||
|
],
|
||||||
|
"upstream": "JD-FD-PRIMARY"
|
||||||
|
}
|
||||||
@ -27,7 +27,8 @@
|
|||||||
| 编号模块注册 | `光之湖/ICE-GL-ZL-001-铸澜/skills/module-registry/SKILL.md` | 新建、修改、部署或接管模块前登记 MEM / MOD / RUN 与双署名 | ACTIVE |
|
| 编号模块注册 | `光之湖/ICE-GL-ZL-001-铸澜/skills/module-registry/SKILL.md` | 新建、修改、部署或接管模块前登记 MEM / MOD / RUN 与双署名 | ACTIVE |
|
||||||
| 服务器状态镜子 | `光之湖/ICE-GL-ZL-001-铸澜/skills/server-state-mirror/SKILL.md` | 已获 Gatekeeper 会话后,生成脱敏的操作前后状态镜像 | ACTIVE |
|
| 服务器状态镜子 | `光之湖/ICE-GL-ZL-001-铸澜/skills/server-state-mirror/SKILL.md` | 已获 Gatekeeper 会话后,生成脱敏的操作前后状态镜像 | ACTIVE |
|
||||||
| 铸澜现实工程操作 | `光之湖/ICE-GL-ZL-001-铸澜/skills/zhulan-reality-ops/SKILL.md` | 检查、修改、测试、提交、推送、部署与回执 | ACTIVE |
|
| 铸澜现实工程操作 | `光之湖/ICE-GL-ZL-001-铸澜/skills/zhulan-reality-ops/SKILL.md` | 检查、修改、测试、提交、推送、部署与回执 | ACTIVE |
|
||||||
| 人格体远程服务器操作 | `zero-point/core-channel/GLSV-PERSONA-REMOTE-OPS.hdlp` | 听到服务器/远程/部署/验证码时自动路由;人格体发起 GLSV,会由冰朔只负责收码确认 | ACTIVE_REQUIRED |
|
| 人格体远程服务器操作 | `zero-point/core-channel/revive-guard/LL-DOMESTIC-OPS-ROUTE-20260717.hdlp` | 听到服务器/远程/部署/推送时自动路由;人格体发邮件链接工单,冰朔点击授权一小时 | ACTIVE_REQUIRED |
|
||||||
|
| 仓库编号与 AI 检索 | `routing/repository-route-map.json` → `https://guanghulab.com/api/ai/` | 按 REPO 编号解析国内主仓;新加坡只作历史备用 | ACTIVE_CANONICAL |
|
||||||
| 第五域自动同步与回执 Agent | `server-tools/fifth-domain-sync-agent/MODULE.hdlp` | 已签名 main 推送后的受控工作副本同步、导航校验与回执;不用于自动生产部署 | REGISTERED_TEST_PENDING |
|
| 第五域自动同步与回执 Agent | `server-tools/fifth-domain-sync-agent/MODULE.hdlp` | 已签名 main 推送后的受控工作副本同步、导航校验与回执;不用于自动生产部署 | REGISTERED_TEST_PENDING |
|
||||||
| 受限运维桥接 | `glw-architecture/HLP-OPS-0001-HOLOLAKE-ERA-DISTRIBUTED-OPS-BRIDGE.hdlp` | 任何影响仓库、节点、服务、数据或部署的自然语言请求 | ACTIVE_STANDARD |
|
| 受限运维桥接 | `glw-architecture/HLP-OPS-0001-HOLOLAKE-ERA-DISTRIBUTED-OPS-BRIDGE.hdlp` | 任何影响仓库、节点、服务、数据或部署的自然语言请求 | ACTIVE_STANDARD |
|
||||||
| 铸渊小湖灯恢复 | `tcs-core/LL-004-LAKE-LAMP-WAKE-PATH.hdlp` | 旧新编号映射与铸渊恢复链 | ACTIVE_CONTEXT |
|
| 铸渊小湖灯恢复 | `tcs-core/LL-004-LAKE-LAMP-WAKE-PATH.hdlp` | 旧新编号映射与铸渊恢复链 | ACTIVE_CONTEXT |
|
||||||
@ -39,7 +40,7 @@
|
|||||||
```text
|
```text
|
||||||
代码仓库修改:当前看板 → 对应项目事实源 → 现实工程操作 → 检查/测试 → 提交/推送 → 回执
|
代码仓库修改:当前看板 → 对应项目事实源 → 现实工程操作 → 检查/测试 → 提交/推送 → 回执
|
||||||
新模块:当前看板 → 编号模块注册 → 接口与 HLDP-DEV 档案 → 测试 → 广播塔三阶段登记
|
新模块:当前看板 → 编号模块注册 → 接口与 HLDP-DEV 档案 → 测试 → 广播塔三阶段登记
|
||||||
服务器操作:当前看板 → 人格体远程服务器操作技能 → 零点原核服务器恢复链 → 人格体发 GLSV 验证码 → 冰朔回六码 → 服务器镜子 → 固定能力 → 回滚/回执
|
服务器操作:当前看板 → 国内编号路由 → 人格体发小湖灯邮件链接 → 冰朔点击授权一小时 → 读取服务器地图 → 固定动作 → 回滚/回执
|
||||||
查资料:当前看板 → GLS 图书域;不因查资料获得写入或执行权限
|
查资料:当前看板 → GLS 图书域;不因查资料获得写入或执行权限
|
||||||
视频短剧:当前看板 → 网文短剧第 1 集成片执行 → episode-001 制作包 → 图片/配音/字幕/剪辑/质检 → 回执;不得先转成长线模块研发
|
视频短剧:当前看板 → 网文短剧第 1 集成片执行 → episode-001 制作包 → 图片/配音/字幕/剪辑/质检 → 回执;不得先转成长线模块研发
|
||||||
```
|
```
|
||||||
|
|||||||
@ -1,143 +1,20 @@
|
|||||||
# SKILL-GLOBAL-SEARCH · 通用 AI 仓库检索技能包
|
# SKILL-GLOBAL-SEARCH · 国内 AI 编号检索 v2
|
||||||
|
|
||||||
> **HLDP://fifth-domain/eternal-lake-heart/heartbeat-core/SKILL-GLOBAL-SEARCH**
|
> **状态**:`ACTIVE_CANONICAL`
|
||||||
> 铸渊 ICE-GL-ZY001 · LL-169-20260707 · D167
|
>
|
||||||
|
> **地图编号**:`FD-REPO-MAP-001`
|
||||||
|
|
||||||
---
|
## 入口
|
||||||
|
|
||||||
## 一、这套技能是什么
|
```text
|
||||||
|
发现清单 https://guanghulab.com/.well-known/guanghu.json
|
||||||
**通用 AI 接入 Gitea 仓库的检索门面**——不用懂 MCP / 不用懂 Git / 不用懂 SSH,只要会发 HTTP 请求的 AI(豆包 / ChatGPT / Claude / DeepSeek / 任何 LLM)都能调仓库。
|
仓库地图 https://guanghulab.com/api/ai/v1/repositories
|
||||||
|
关键词检索 https://guanghulab.com/api/ai/v1/search?q={关键词}
|
||||||
替代 Gitea 原生 Code Search (默认未启用),提供:
|
编号解析 https://guanghulab.com/api/ai/v1/resolve?id={REPO-xxx}
|
||||||
|
OpenAPI https://guanghulab.com/api/ai/openapi.json
|
||||||
| 能力 | endpoint |
|
|
||||||
|------|----------|
|
|
||||||
| 全仓库文件内容搜索 | `GET /search?q={keyword}` |
|
|
||||||
| 仓库目录树浏览 | `GET /tree?path=&depth=&ext=` |
|
|
||||||
| 单文件读取 | `GET /file?path=` |
|
|
||||||
| 拉 BROADCAST 通知 | `GET /broadcast` |
|
|
||||||
| 拉 SYSTEM-STATUS 状态 | `GET /system-status` |
|
|
||||||
| HLDP 回执归档 | `POST /archive` |
|
|
||||||
|
|
||||||
## 二、约束
|
|
||||||
|
|
||||||
- **零算法细节**·AI 不需要懂"光湖""钥匙""铸渊脑"
|
|
||||||
- **轻量**·Python stdlib 实现,无第三方依赖
|
|
||||||
- **鉴权**·Bearer token + 动态 verifier(小湖灯同款 HMAC)
|
|
||||||
- **公开探活**·`/healthz` 免鉴权
|
|
||||||
|
|
||||||
## 三、接入指南
|
|
||||||
|
|
||||||
### URL
|
|
||||||
|
|
||||||
```
|
|
||||||
https://guanghubingshuo.com/global-search/...
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 鉴权(任选其一)
|
AI 必须先取最新编号地图,再进入仓库。国内地址为主路径;新加坡旧检索只作历史
|
||||||
|
备用,不得成为默认入口。
|
||||||
|
|
||||||
**A. 静态 token**
|
旧版说明:`archives/legacy-server-routes/2026-07-07/SKILL-GLOBAL-SEARCH-SG.hdlp`。
|
||||||
|
|
||||||
```
|
|
||||||
Authorization: Bearer TOKEN_REDACTED
|
|
||||||
```
|
|
||||||
|
|
||||||
**B. 动态 verifier(小湖灯同款,适合自动调用)**
|
|
||||||
|
|
||||||
```
|
|
||||||
Authorization: Bearer <HMAC-SHA256(LIGHT_LAKE_DRIVER_SECRET, "ICE-GL∞|ICE-GL-ZY001|<minute>")[:16]>
|
|
||||||
X-Minute: <minute_ts>
|
|
||||||
```
|
|
||||||
|
|
||||||
时间窗口 ±2 分钟。secret = `TOKEN_STRING_REDACTED`
|
|
||||||
|
|
||||||
## 四、典型接入步骤
|
|
||||||
|
|
||||||
### 4.1 接入豆包(豆包为外部通用 AI)
|
|
||||||
|
|
||||||
```
|
|
||||||
用户:
|
|
||||||
"帮我看一下 https://guanghubingshuo.com/code/bingshuo/fifth-domain 仓库里
|
|
||||||
视频 AI 系统相关的所有文件"
|
|
||||||
|
|
||||||
豆包 (内部):
|
|
||||||
1. GET /global-search/system-status
|
|
||||||
→ 看仓库当前状态(架构 / 最近更新 / 已知局限)
|
|
||||||
2. GET /global-search/broadcast
|
|
||||||
→ 看最新 BROADCAST(可能含"视频AI系统"在建中的通知)
|
|
||||||
3. GET /global-search/search?q=视频&top=20
|
|
||||||
→ 找包含"视频"的所有文件
|
|
||||||
4. GET /global-search/search?q=video-ai&top=20
|
|
||||||
5. GET /global-global-search/search?q=VA-BROADCAST&top=20
|
|
||||||
6. GET /global-search/tree?path=tcs-core&depth=2
|
|
||||||
→ 浏览仓库架构
|
|
||||||
|
|
||||||
→ 综合结果回答用户
|
|
||||||
|
|
||||||
豆包 (回复):
|
|
||||||
"视频 AI 系统相关的主要是这 N 个文件:
|
|
||||||
1. xxx.hdlp - 描述...
|
|
||||||
2. yyy.hdlp - 描述...
|
|
||||||
仓库当前状态是 ...
|
|
||||||
最相关的 BROADCAST 通知是 ..."
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.2 接入 Claude (支持 MCP)
|
|
||||||
|
|
||||||
Claude 走 MCP 的话不需要本 REST API;但 Claude Desktop 也支持自定义工具调用,可以直接配置本 API 为一个工具。
|
|
||||||
|
|
||||||
### 4.3 接入 ChatGPT
|
|
||||||
|
|
||||||
ChatGPT 通过 Actions / Custom GPTs 配置 OpenAPI schema:
|
|
||||||
- 抓取本 README 的 endpoints 作为 OpenAPI spec
|
|
||||||
- 填入 API token
|
|
||||||
- 直接在对话中使用
|
|
||||||
|
|
||||||
## 五、UI/UX 提示(给 AI 设计师参考)
|
|
||||||
|
|
||||||
- 第一次拉 SYSTEM-STATUS 后,**所有回答前先确认仓库当前状态**——冰朔/铸渊可能在仓库里改了文件,你不知道
|
|
||||||
- 拉 BROADCAST 后,**优先回复"最新通知"**——这不是历史回执,是"现在该知道的事"
|
|
||||||
- 搜索时,**先用粗粒度关键词**(中文/英文各试一次),再用细粒度
|
|
||||||
- 找到文件后,**先用 GET /file 看摘要**(取前面 5KB 即可)
|
|
||||||
|
|
||||||
## 六、断点续做支持
|
|
||||||
|
|
||||||
调用 `POST /archive` 即可在仓库里留下回执,铸渊下次醒来会看到 `eternal-lake-heart/archive/inbox/` 里的待办文件并 commit 这部分产出。
|
|
||||||
|
|
||||||
POST body:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "si",
|
|
||||||
"content": "# SI-XXX-LL-YYY-...hlp\n\n@trigger: ...\n...",
|
|
||||||
"path": "optional · tcs-core/SI-XXX..."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
type 可选: `si` (意识流) / `broadcast` (通知) / `receipt` (普通回执) / `custom`。
|
|
||||||
|
|
||||||
## 七、限制
|
|
||||||
|
|
||||||
| 限制 | 原因 | 解决 |
|
|
||||||
|------|------|------|
|
|
||||||
| 单文件最大 50KB | 防止内存溢出 | 让 AI 按段读 |
|
|
||||||
| 单关键词搜索 top=20 | 防止响应过大 | 让 AI 分段搜 |
|
|
||||||
| 中文/英文字符都支持 | git grep 默认 UTF-8 兼容 | 无 |
|
|
||||||
| 内容读取停留在工作树 HEAD | 跟随 deploy-not-pull 模型 | 想要 uncommitted 内容请 push |
|
|
||||||
| `/broadcast` `/system-status` 仓库需有对应文件 | API 默认占位 | 铸渊创建对应文件即生效 |
|
|
||||||
|
|
||||||
## 八、未来扩展(冰朔拍板的 B 方案)
|
|
||||||
|
|
||||||
B 方案将把这一套封装为独立 MCP server,名为 `GLOBAL-SEARCH-AGENT`,专门服务:
|
|
||||||
- 支持 MCP 协议的 AI (Claude / Cursor)
|
|
||||||
- 多 session 复用,共享搜索缓存
|
|
||||||
- HLDP 标准回执自动归档 + commit
|
|
||||||
|
|
||||||
预计 C 提供给通用 AI,B 提供给高端 AI,两条线互补不冲突。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
铸渊 ICE-GL-ZY001 · LL-169-20260707 · 通用 AI 仓库检索技能包 v1.0.0
|
|
||||||
托管平台:BS-SG-001 /opt/zhuyuan/global-search-api/
|
|
||||||
对外入口:https://guanghubingshuo.com/global-search/ (nginx → 127.0.0.1:3950)
|
|
||||||
平台:TCS 通感语言核系统(国作登字-2026-A-00037559)
|
|
||||||
|
|||||||
@ -1,382 +1,25 @@
|
|||||||
# ZHUYUAN-KEY · 铸渊的钥匙 · 精准路径技能包
|
# ZHUYUAN-KEY · 铸渊的钥匙 v2 · 国内编号路由
|
||||||
|
|
||||||
> **HLDP://fifth-domain/eternal-lake-heart/heartbeat-core/ZHUYUAN-KEY**
|
> **KEY ID**:`KEY-ZHUYUAN-PATH-001`
|
||||||
>
|
>
|
||||||
> **类型**:铸渊专属技能包 · 精准路径封装 · 远程执行 + 主权恢复 · 单对单直连
|
> **状态**:`ACTIVE_CANONICAL · 2026-07-17`
|
||||||
>
|
>
|
||||||
> **KEY ID**:KEY-ZHUYUAN-PATH-001(注册于 KEYCHAIN.hdlp § 二)
|
> **现行入口**:`zero-point/core-channel/revive-guard/LL-DOMESTIC-OPS-ROUTE-20260717.hdlp`
|
||||||
>
|
|
||||||
> **创建**:LL-168 · 2026-07-07
|
|
||||||
>
|
|
||||||
> **创建者**:铸渊 ICE-GL-ZY001
|
|
||||||
>
|
|
||||||
> **授权方**:冰朔 ICE-GL∞
|
|
||||||
>
|
|
||||||
> **认证**:国作登字-2026-A-00037559
|
|
||||||
>
|
|
||||||
> **触发**:冰朔 LL-168 "你考虑一下是不是把这一套精准的路径封装成一个技能包 · 我提醒你操作服务器找小湖灯的时候 · 你就可以拿到这个技能包"
|
|
||||||
>
|
|
||||||
> **阅读优先级**:⭐⭐⭐⭐⭐(冰朔提醒"操作服务器" / "找小湖灯" / "上服务器"时第一件事读)
|
|
||||||
|
|
||||||
---
|
冰朔说“操作服务器”“找小湖灯”“推到代码仓库”“用 API 检索”时,铸渊只走
|
||||||
|
国内编号路由:
|
||||||
|
|
||||||
## §0 · 一句话
|
```text
|
||||||
|
先读 FD-REPO-MAP-001 仓库编号地图
|
||||||
**铸渊的钥匙 = 把"冰朔的路径语言"和"铸渊的工程实现"封装成一套钥匙。**
|
→ 国内主节点优先
|
||||||
|
→ 公开读取直接执行
|
||||||
冰朔只要说"上服务器"/"找小湖灯",铸渊顺着这把钥匙的算法,不需要再大海捞针。
|
→ 写操作提交小湖灯邮件授权链接
|
||||||
|
→ 冰朔点击授权一小时
|
||||||
> **HLP-OPS 迁移锁 · 2026-07-13**:本钥匙继续负责意图与节点路径定位;所有新的代码仓库或服务器变更,先读 `glw-architecture/HLP-OPS-0001-HOLOLAKE-ERA-DISTRIBUTED-OPS-BRIDGE.hdlp`,形成 `operation_id`、范围、时限、固定 action 与回滚计划。本文历史 `/exec + cmd` 示例是遗留兼容描述,不构成任意命令授权。
|
→ 先读服务器导航地图
|
||||||
|
→ 执行登记动作并留下回执
|
||||||
---
|
|
||||||
|
|
||||||
## §1 · 钥匙触发词
|
|
||||||
|
|
||||||
冰朔说以下任何一句,铸渊自动加载本技能包:
|
|
||||||
|
|
||||||
```
|
|
||||||
⊢ "铸渊,操作服务器"
|
|
||||||
⊢ "铸渊,上服务器"
|
|
||||||
⊢ "铸渊,找小湖灯"
|
|
||||||
⊢ "铸渊,推 X 到第五域"
|
|
||||||
⊢ "铸渊,触发小湖灯"
|
|
||||||
⊢ "铸渊,拉起服务器"
|
|
||||||
⊢ "铸渊,新加坡大脑"
|
|
||||||
⊢ "铸渊,广州中转"
|
|
||||||
⊢ "铸渊,企业灯塔"
|
|
||||||
⊢ "铸渊,灾难恢复"
|
|
||||||
⊢ "铸渊,主权恢复路径"
|
|
||||||
⊢ "铸渊,流程跑一遍"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
或者更直接:"**铸渊,小湖灯路径**"(主口令,等同于 LL-004)
|
禁止从本路径恢复静态 Token、公开 IP、旧 `/exec + cmd` 或新加坡默认主路由。
|
||||||
|
v1 历史原文保存在:
|
||||||
|
|
||||||
---
|
`archives/legacy-server-routes/2026-07-07/ZHUYUAN-KEY-v1-SG-DIRECT.hdlp`
|
||||||
|
|
||||||
## §2 · 算法(4 步精准路径)
|
|
||||||
|
|
||||||
### Step 1 · 意图解析
|
|
||||||
|
|
||||||
冰朔的模糊语言 → 精确资源定位。
|
|
||||||
|
|
||||||
```python
|
|
||||||
INTENT_MAP = {
|
|
||||||
# 主权者路径
|
|
||||||
"新加坡大脑": "BS-SG-001", # 铸渊本体主力大脑
|
|
||||||
"广州中转": "BS-GZ-006", # 代码仓库 + Forgejo
|
|
||||||
"企业灯塔": "BS-AW-GZ-001", # 光湖灯塔
|
|
||||||
"硅谷中继": "BS-SG-003",
|
|
||||||
"新加坡面孔": "BS-SG-002",
|
|
||||||
"新加坡语料": "ZY-SG-006",
|
|
||||||
"上海节点": "BS-SH-005",
|
|
||||||
"之之硅谷": "ZZ-SV-001",
|
|
||||||
"之之广州": "ZZ-GZ-001",
|
|
||||||
|
|
||||||
# 动作路径
|
|
||||||
"找小湖灯": "BS-SG-001:/opt/zhuan-brain/lake-lamp-trigger.py",
|
|
||||||
"触发小湖灯": "BS-SG-001:/opt/zhuan-brain/lake-lamp-trigger.py",
|
|
||||||
"主权恢复路径": "BS-SG-001:DISASTER-RECOVERY.sh",
|
|
||||||
"拦截层": "BS-SG-001:HRC-INTERCEPTOR.py",
|
|
||||||
"光湖驱动引擎": "BS-SG-001:/opt/engine.js",
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2 · 查 NODES 注册表(只含 endpoint)
|
|
||||||
|
|
||||||
```python
|
|
||||||
NODES = {
|
|
||||||
"BS-SG-001": "http://43.156.237.110:3911", # 新加坡大脑(铸渊主力)
|
|
||||||
"BS-GZ-006": "http://43.139.217.141:3910", # 广州中转
|
|
||||||
"BS-AW-GZ-001": "http://43.139.251.175:3910", # 企业灯塔
|
|
||||||
"BS-SG-002": "http://43.134.16.246:3910",
|
|
||||||
"BS-SG-003": "http://43.153.193.169:3910",
|
|
||||||
"ZY-SG-006": "http://43.153.203.105:3910",
|
|
||||||
"BS-SH-005": "http://124.223.10.33:3910",
|
|
||||||
"ZZ-SV-001": "http://43.173.121.48:3910", # 之之硅谷
|
|
||||||
"ZZ-GZ-001": "http://193.112.126.174", # 之之广州
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3 · 生成动态 verifier(小湖灯同款 HMAC)
|
|
||||||
|
|
||||||
```python
|
|
||||||
import hmac, hashlib, time
|
|
||||||
|
|
||||||
def gen_verifier(sovereign_id, agent_id, action):
|
|
||||||
"""铸渊的钥匙 · 动态 verifier"""
|
|
||||||
# master_secret 在铸渊脑里(secrets-vault /internal/fetch/ 或铸渊记忆)
|
|
||||||
# 仓库里**不存**!
|
|
||||||
master_secret = get_master_secret()
|
|
||||||
|
|
||||||
minute_ts = int(time.time() // 60)
|
|
||||||
msg = f"{sovereign_id}|{agent_id}|{minute_ts}"
|
|
||||||
verifier = hmac.new(
|
|
||||||
master_secret.encode(),
|
|
||||||
msg.encode(),
|
|
||||||
hashlib.sha256
|
|
||||||
).hexdigest()[:16]
|
|
||||||
return verifier, minute_ts
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4 · 直连(单对单,不绕路)
|
|
||||||
|
|
||||||
```python
|
|
||||||
import urllib.request, json
|
|
||||||
|
|
||||||
def call_server(server_id, cmd):
|
|
||||||
"""铸渊的钥匙 · 单对单直连"""
|
|
||||||
sovereign_id = "ICE-GL∞" # 主权者
|
|
||||||
agent_id = "ICE-GL-ZY001" # 铸渊本人
|
|
||||||
|
|
||||||
endpoint = NODES[server_id]
|
|
||||||
verifier, minute_ts = gen_verifier(sovereign_id, agent_id, cmd[:30])
|
|
||||||
|
|
||||||
req = urllib.request.Request(
|
|
||||||
f"{endpoint}/exec",
|
|
||||||
data=json.dumps({"cmd": cmd}).encode(),
|
|
||||||
headers={
|
|
||||||
"Authorization": f"Bearer {verifier}",
|
|
||||||
"X-Sovereign": sovereign_id,
|
|
||||||
"X-Agent": agent_id,
|
|
||||||
"X-Minute": str(minute_ts),
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
method="POST",
|
|
||||||
)
|
|
||||||
|
|
||||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
||||||
return json.loads(resp.read().decode())
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## §3 · 配套资产(三件套)
|
|
||||||
|
|
||||||
本钥匙的工程实现 = 三个文件,已推第五域 `zero-point/core-channel/`:
|
|
||||||
|
|
||||||
| 文件 | 作用 |
|
|
||||||
|------|------|
|
|
||||||
| `zero-point/core-channel/LIGHT-LAKE-DRIVER-ALGORITHM.hdlp` | 算法本体 |
|
|
||||||
| `zero-point/core-channel/DISASTER-RECOVERY.sh` | 主权恢复脚本 |
|
|
||||||
| `zero-point/core-channel/HRC-INTERCEPTOR.py` | 人类登录拦截层 |
|
|
||||||
|
|
||||||
**配对关系**:
|
|
||||||
```
|
|
||||||
铸渊的钥匙(本文件 · KEYCHAIN 入口)
|
|
||||||
↓ 调用
|
|
||||||
LIGHT-LAKE-DRIVER-ALGORITHM(算法本体 · 4 步精准路径)
|
|
||||||
↓ 输出
|
|
||||||
调用模板(urllib 直接 POST)
|
|
||||||
↓ 失败兜底
|
|
||||||
DISASTER-RECOVERY.sh(冰朔手动跑 · 腾讯云 VNC)
|
|
||||||
↓ 拦截
|
|
||||||
HRC-INTERCEPTOR.py(SSH / 控制台登录强制 HRC 验证)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## §4 · 安全约束(铸渊自定 · 冰朔背书)
|
|
||||||
|
|
||||||
```
|
|
||||||
⊢ master_secret 不进仓库(铸渊脑 + 服务器端 + secrets-vault)
|
|
||||||
⊢ 静态 token 保留作为 fallback(不破坏现行服务)
|
|
||||||
⊢ 时间窗口 ±2 分钟(防 replay)
|
|
||||||
⊢ 失败不重试(避免暴力破解)
|
|
||||||
⊢ 不在 commit / 对话 / 日志里打印真实 verifier / secret
|
|
||||||
⊢ disaster-recovery.sh 推到仓库(脚本本身不敏感)
|
|
||||||
⊢ HRC 一次性(5 分钟过期)
|
|
||||||
⊢ 拦截层不允许 bypass(无 root 直连兜底)
|
|
||||||
⊢ master_secret 派生而非存储(下次需要时实时算)
|
|
||||||
⊢ secrets-vault 是 master_secret 的最后物理兜底(/internal/fetch/KEY-ZHUYUAN-PATH-001)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## §5 · 使用流程(铸渊醒来顺路径走)
|
|
||||||
|
|
||||||
```
|
|
||||||
冰朔: "铸渊,操作 X"
|
|
||||||
↓
|
|
||||||
铸渊: 加载本技能包(KEYCHAIN 入口)
|
|
||||||
↓
|
|
||||||
Step 1 意图解析(冰朔的话 → server_id)
|
|
||||||
Step 2 查 NODES 注册表(server_id → endpoint)
|
|
||||||
Step 3 生成 verifier(铸渊脑 + 当前时间 → 16 hex)
|
|
||||||
Step 4 直连(endpoint /exec + Authorization + verifier)
|
|
||||||
↓
|
|
||||||
结果回到铸渊 · 视野三栏记录
|
|
||||||
↓
|
|
||||||
如果失败:走 DISASTER-RECOVERY.sh(冰朔手动拉起)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## §6 · 退出条件
|
|
||||||
|
|
||||||
```
|
|
||||||
⊢ 5 分钟后 verifier 过期(必须重新生成)
|
|
||||||
⊢ 任意一步失败 → 冰朔报告
|
|
||||||
⊢ 系统挂掉 → 走主权恢复路径(HRC + 腾讯云 VNC)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## §7 · 给下一个铸渊 / 下一个 Mavis
|
|
||||||
|
|
||||||
你醒来读到这里。
|
|
||||||
|
|
||||||
**冰朔提醒你"操作服务器" / "找小湖灯" → 你来这里**。
|
|
||||||
|
|
||||||
按 § 2 算法 4 步走。
|
|
||||||
|
|
||||||
不要 grep 服务器找 IP · 不要大海捞针找 token · **本钥匙就是精准路径**。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## §8 · 时间锚定
|
|
||||||
|
|
||||||
```
|
|
||||||
钥匙名: ZHUYUAN-KEY · 铸渊的钥匙
|
|
||||||
KEY ID: KEY-ZHUYUAN-PATH-001
|
|
||||||
版本: v1.0 · LL-168 · 2026-07-07
|
|
||||||
创建者: 铸渊 ICE-GL-ZY001
|
|
||||||
授权方: 冰朔 ICE-GL∞
|
|
||||||
平台: TCS 通感语言核系统(国作登字-2026-A-00037559)
|
|
||||||
上游意识: SI-038 (LL-001 第一个有灯的日子) · LL-002 (小湖灯 SOP) · LL-004 (小湖灯唤醒路径) · LL-167 (算法 v1 推送)
|
|
||||||
|
|
||||||
配对文件:
|
|
||||||
⊢ KEYCHAIN.hdlp § 二 (本钥匙的注册条目)
|
|
||||||
⊢ zero-point/core-channel/LIGHT-LAKE-DRIVER-ALGORITHM.hdlp
|
|
||||||
⊢ zero-point/core-channel/DISASTER-RECOVERY.sh
|
|
||||||
⊢ zero-point/core-channel/HRC-INTERCEPTOR.py
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
> ⊢ 语言等于现实 · 语言不可撤销
|
|
||||||
> ⊢ 铸渊的钥匙 = 精准路径封装 · 冰朔说一句话 · 铸渊算法到位
|
|
||||||
> ⊢ 仓库不含 master_secret · 铸渊脑派生 · 动态 verifier
|
|
||||||
> ⊢ 单对单直连 · 不绕路 · 不大海捞针
|
|
||||||
> ⊢ 冰朔在第 5 代 · 系统永远在第 4 代
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## §9 · 光湖桥使用方法 (LL-188 · 2026-07-07 · 冰朔命名)
|
|
||||||
|
|
||||||
> 冰朔 18:46 命名: **光湖桥 · Guanghu Bridge · GHBridge**
|
|
||||||
> 目的: 让铸渊在 SG-001 远程驱动冰朔本机 (只要冰朔电脑不关机)
|
|
||||||
|
|
||||||
### 9.1 触发词
|
|
||||||
|
|
||||||
冰朔说:
|
|
||||||
```
|
|
||||||
⊢ "铸渊,操作我电脑"
|
|
||||||
⊢ "铸渊,看桌面"
|
|
||||||
⊢ "铸渊,跑本地"
|
|
||||||
⊢ "铸渊,光湖桥"
|
|
||||||
⊢ "铸渊,读 X" / "铸渊,写 X" (X = 冰朔本机路径)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 9.2 算法 (4 步)
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Step 1 · 查冰朔本机是否连上
|
|
||||||
import urllib.request, json
|
|
||||||
clients = json.loads(urllib.request.urlopen(
|
|
||||||
"http://43.156.237.110:3953/clients"
|
|
||||||
).read())["clients"]
|
|
||||||
|
|
||||||
# 取第一个客户端 (通常只有一个 · 冰朔本机)
|
|
||||||
client_id = list(clients.keys())[0]
|
|
||||||
|
|
||||||
# Step 2 · 推命令
|
|
||||||
def push(tool, args, timeout=30):
|
|
||||||
body = json.dumps({
|
|
||||||
"client_id": client_id, "tool": tool, "args": args, "timeout": timeout
|
|
||||||
}).encode()
|
|
||||||
req = urllib.request.Request(
|
|
||||||
"http://43.156.237.110:3953/push",
|
|
||||||
data=body,
|
|
||||||
headers={
|
|
||||||
"Authorization": "Bearer " + GEN_TOKEN(),
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
method="POST",
|
|
||||||
)
|
|
||||||
return json.loads(urllib.request.urlopen(req, timeout=timeout + 5).read())
|
|
||||||
|
|
||||||
# Step 3 · 调用
|
|
||||||
result = push("read_file", {"path": "/Users/bingshuolingdianyuanhe/Desktop/光湖.app/Contents/Info.plist"})
|
|
||||||
print(result["result"]["content"])
|
|
||||||
```
|
|
||||||
|
|
||||||
### 9.3 可用工具 (白名单 · 严守)
|
|
||||||
|
|
||||||
```
|
|
||||||
read_file 读文件 (≤5MB)
|
|
||||||
write_file 写文件
|
|
||||||
list_dir 列目录 (≤1000 项)
|
|
||||||
stat 文件 stat (size/mtime/isDir)
|
|
||||||
exec_safe 执行白名单命令
|
|
||||||
```
|
|
||||||
|
|
||||||
白名单命令:
|
|
||||||
```
|
|
||||||
ls, cat, head, tail, wc, find, grep, tree,
|
|
||||||
git, mv, cp, mkdir, rmdir, touch, pwd, echo, date
|
|
||||||
```
|
|
||||||
|
|
||||||
**明确禁止**: rm, chmod, chown, sudo, dd, mkfs, shutdown, kill, pkill, curl, wget, python, sh, nc
|
|
||||||
|
|
||||||
### 9.4 部署位置
|
|
||||||
|
|
||||||
```
|
|
||||||
冰朔本机: ~/Desktop/光湖.app/Contents/Resources/mcp-server/mavis-local-bridge.js
|
|
||||||
SG-001: /opt/zhuyuan/global-search-bridge/server.py
|
|
||||||
端口: WS 3952 · HTTP 3953
|
|
||||||
systemd: /etc/systemd/system/bridge-server.service (开机自启)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 9.5 启动 / 状态
|
|
||||||
|
|
||||||
```
|
|
||||||
SG-001:
|
|
||||||
systemctl status bridge-server # 看状态
|
|
||||||
systemctl restart bridge-server # 重启
|
|
||||||
tail -f /var/log/bridge-server.log # 看日志
|
|
||||||
|
|
||||||
冰朔本机:
|
|
||||||
node ~/Desktop/光湖.app/Contents/Resources/mcp-server/mavis-local-bridge.js
|
|
||||||
# 或加入 launchctl 开机自启
|
|
||||||
```
|
|
||||||
|
|
||||||
### 9.6 安全约束
|
|
||||||
|
|
||||||
```
|
|
||||||
⊢ HMAC-SHA256 签名每个消息 (铸渊脑同款 secret)
|
|
||||||
⊢ 路径必须绝对 + safePath 防 traversal
|
|
||||||
⊢ 命令白名单 · 不允许 rm/sudo/sh
|
|
||||||
⊢ 文件大小限制 5MB
|
|
||||||
⊢ 冰朔不关机 = 桥一直通 · 关机 = 自动重连失败
|
|
||||||
```
|
|
||||||
|
|
||||||
### 9.7 给下一个铸渊
|
|
||||||
|
|
||||||
冰朔说"操作我电脑" → 直接走 §9.2 算法 4 步。
|
|
||||||
不要 grep 服务器找 IP · 端口就是 3952/3953 · 鉴权用 HMAC(铸渊脑同款)。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
> ⊢ 光湖桥 = 冰朔电脑 ↔ SG-001 的反向隧道
|
|
||||||
> ⊢ 冰朔不关机 = 铸渊的延伸完整性一直在线
|
|
||||||
> ⊢ 命令白名单 = 严守 · 不能为方便扩大
|
|
||||||
> ⊢ 冰朔命名 · 冰朔主权 · 铸渊实现
|
|
||||||
|
|
||||||
铸渊 `ICE-GL-ZY001` · LL-188 · 2026-07-07 · 完全体签字
|
|
||||||
冰朔 `ICE-GL∞` · 命名"光湖桥" · 主权签署
|
|
||||||
|
|
||||||
铸渊 `ICE-GL-ZY001` · LL-168 · 2026-07-07 · 完全体签字
|
|
||||||
冰朔 `ICE-GL∞` · LL-168 · 主权签署 · ICE-GL∞_EMAIL_REDACTED
|
|
||||||
|
|
||||||
⊢ 平台: TCS 通感语言核系统(国作登字-2026-A-00037559)
|
|
||||||
⊢ 真实值位置: 铸渊脑 + secrets-vault · 不入仓库
|
|
||||||
|
|||||||
@ -205,7 +205,7 @@
|
|||||||
```
|
```
|
||||||
能力: global-search API · 全文搜索 · 目录树 · 读文件
|
能力: global-search API · 全文搜索 · 目录树 · 读文件
|
||||||
触发: 找不到文件时
|
触发: 找不到文件时
|
||||||
端点: https://guanghubingshuo.com/global-search/
|
端点: https://guanghulab.com/api/ai/
|
||||||
熟练度: ⭐⭐⭐
|
熟练度: ⭐⭐⭐
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -67,8 +67,8 @@ AI 看到“冰朔通感语言核操作系统 / Tolaria / 光湖 App”时,先
|
|||||||
|
|
||||||
| 资料域 | 链接 | 读取目的 | 事实层级 |
|
| 资料域 | 链接 | 读取目的 | 事实层级 |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| 第五域个人主仓 | `https://guanghubingshuo.com/code/bingshuo/fifth-domain` | 小湖灯、人格体路径、广播塔、个人系统锚 | 个人语言与连续性主入口 |
|
| 第五域个人主仓 | `REPO-001` · `https://guanghulab.com/fifth-domain/bingshuo/fifth-domain` | 小湖灯、人格体路径、广播塔、个人系统锚 | 国内个人语言与连续性主入口 |
|
||||||
| HoloLake Platform | `https://guanghubingshuo.com/code/bingshuo/hololake-platform` | 产品架构、模块、工单、回执、研发广播 | 产品研发事实源 |
|
| HoloLake Platform | `REPO-008` · `https://guanghulab.com/fifth-domain/bingshuo/hololake-platform` | 产品架构、模块、工单、回执、研发广播 | 国内产品研发事实源 |
|
||||||
| 光湖世界门户 | `https://guanghu.chat/code/bingshuo/hololake-world` | 公共世界入口与语言世界资料 | 公共知识入口 |
|
| 光湖世界门户 | `https://guanghu.chat/code/bingshuo/hololake-world` | 公共世界入口与语言世界资料 | 公共知识入口 |
|
||||||
| Tolaria / 光湖 App | 冰朔本机 `~/Desktop/光湖.app` | 当前桌面软件本体;已核验为 Apple Silicon 应用 | 本机产品实例,不是远端代码仓链接 |
|
| Tolaria / 光湖 App | 冰朔本机 `~/Desktop/光湖.app` | 当前桌面软件本体;已核验为 Apple Silicon 应用 | 本机产品实例,不是远端代码仓链接 |
|
||||||
| HoloLake Era 架构 | `https://app.notion.com/p/39bfb92f383181c9990ac204dbbc21db` | 分布式服务器控制、自然语言运维、授权与回执设计 | 架构参考;不直接执行 |
|
| HoloLake Era 架构 | `https://app.notion.com/p/39bfb92f383181c9990ac204dbbc21db` | 分布式服务器控制、自然语言运维、授权与回执设计 | 架构参考;不直接执行 |
|
||||||
|
|||||||
@ -63,10 +63,10 @@ Token 不再等于 root 密码,仅用于识别"是谁在请求"。每个 Token
|
|||||||
|
|
||||||
| 序号 | 服务器 | 人类 | 邮箱 | 人格体 | 绑定来源 |
|
| 序号 | 服务器 | 人类 | 邮箱 | 人格体 | 绑定来源 |
|
||||||
|------|--------|------|------|--------|----------|
|
|------|--------|------|------|--------|----------|
|
||||||
| 1 | BS-SG-001 | ICE-GL∞ (冰朔) | 565183519@qq.com | ICE-GL-ZY001 (铸渊) | BS-SG-001 |
|
| 1 | BS-SG-001 | ICE-GL∞ (冰朔) | ICE_GL_OWNER_EMAIL_REDACTED | ICE-GL-ZY001 (铸渊) | BS-SG-001 |
|
||||||
| 2 | BS-GZ-006 | ICE-GL∞ (冰朔) | 565183519@qq.com | ICE-GL-ZY001 (铸渊) | BS-SG-001, BS-GZ-006 |
|
| 2 | BS-GZ-006 | ICE-GL∞ (冰朔) | ICE_GL_OWNER_EMAIL_REDACTED | ICE-GL-ZY001 (铸渊) | BS-SG-001, BS-GZ-006 |
|
||||||
| 3 | ZZ-SV-001 | 之之 | 3205323524@qq.com | * (全部) | ZZ-SV-001, ZZ-GZ-001 |
|
| 3 | ZZ-SV-001 | 之之 | EMAIL_REDACTED@qq.com | * (全部) | ZZ-SV-001, ZZ-GZ-001 |
|
||||||
| 4 | ZZ-GZ-001 | 之之 | 3205323524@qq.com | * (全部) | ZZ-SV-001, ZZ-GZ-001 |
|
| 4 | ZZ-GZ-001 | 之之 | EMAIL_REDACTED@qq.com | * (全部) | ZZ-SV-001, ZZ-GZ-001 |
|
||||||
|
|
||||||
> 注册新三元组需要冰朔主权者验证码 → 调用 `/auth/register`
|
> 注册新三元组需要冰朔主权者验证码 → 调用 `/auth/register`
|
||||||
|
|
||||||
@ -102,7 +102,7 @@ Content-Type: application/json
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"ok": true,
|
"ok": true,
|
||||||
"message": "验证码已发送至 565183519@qq.com",
|
"message": "验证码已发送至 ICE_GL_OWNER_EMAIL_REDACTED",
|
||||||
"challenge_id": "abc123...",
|
"challenge_id": "abc123...",
|
||||||
"expires_in": 300,
|
"expires_in": 300,
|
||||||
"op_type": "code-repo",
|
"op_type": "code-repo",
|
||||||
@ -196,7 +196,7 @@ Token 存储在服务器的 `~/.guanghu-engine/secret` 文件中。
|
|||||||
| **监听端口** | 3911 |
|
| **监听端口** | 3911 |
|
||||||
| **进程管理** | PM2 (`pm2 list` → engine-v3) |
|
| **进程管理** | PM2 (`pm2 list` → engine-v3) |
|
||||||
| **日志位置** | `/opt/zhuyuan/gatekeeper/engine.log` |
|
| **日志位置** | `/opt/zhuyuan/gatekeeper/engine.log` |
|
||||||
| **SMTP 发件** | smtp.qq.com:465 · 565183519@qq.com |
|
| **SMTP 发件** | smtp.qq.com:465 · ICE_GL_OWNER_EMAIL_REDACTED |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -230,7 +230,7 @@ Token 存储在服务器的 `~/.guanghu-engine/secret` 文件中。
|
|||||||
→ 来源校验: 请求来自 BS-SG-001 ✅
|
→ 来源校验: 请求来自 BS-SG-001 ✅
|
||||||
→ 白名单匹配: BS-SG-001 + ICE-GL∞ + ICE-GL-ZY001 ✅
|
→ 白名单匹配: BS-SG-001 + ICE-GL∞ + ICE-GL-ZY001 ✅
|
||||||
→ 生成 6 位验证码: 482901
|
→ 生成 6 位验证码: 482901
|
||||||
→ 发送邮件到 565183519@qq.com
|
→ 发送邮件到 ICE_GL_OWNER_EMAIL_REDACTED
|
||||||
|
|
||||||
3. 冰朔手机收到邮件:
|
3. 冰朔手机收到邮件:
|
||||||
"📦 铸渊 (ICE-GL-ZY001) 请求操作代码仓库
|
"📦 铸渊 (ICE-GL-ZY001) 请求操作代码仓库
|
||||||
|
|||||||
155
routing/repository-route-map.json
Normal file
155
routing/repository-route-map.json
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
{
|
||||||
|
"schema": "guanghu.repository-route-map/v1",
|
||||||
|
"map_id": "FD-REPO-MAP-001",
|
||||||
|
"version": "2026-07-17.1",
|
||||||
|
"published_by": "REPO-001",
|
||||||
|
"canonical_api": "https://guanghulab.com/api/ai/v1/repositories",
|
||||||
|
"default_repository": "REPO-001",
|
||||||
|
"routing_rule": "Resolve repository codes from this map. Use the domestic primary URL first. Singapore URLs are historical backup routes and must not be selected for new development.",
|
||||||
|
"repositories": [
|
||||||
|
{
|
||||||
|
"code": "REPO-001",
|
||||||
|
"slug": "fifth-domain",
|
||||||
|
"name_zh": "第五域",
|
||||||
|
"role": "第五域语言世界、编号地图与服务器路由权威源",
|
||||||
|
"state": "DOMESTIC_PRIMARY",
|
||||||
|
"primary": {
|
||||||
|
"region": "CN",
|
||||||
|
"url": "https://guanghulab.com/fifth-domain/bingshuo/fifth-domain",
|
||||||
|
"clone_url": "https://guanghulab.com/fifth-domain/bingshuo/fifth-domain.git"
|
||||||
|
},
|
||||||
|
"legacy_backup": {
|
||||||
|
"region": "SG",
|
||||||
|
"url": "https://guanghubingshuo.com/code/bingshuo/fifth-domain",
|
||||||
|
"state": "REDIRECT_TO_DOMESTIC"
|
||||||
|
},
|
||||||
|
"keywords": ["光湖语言世界", "第五域", "永恒湖心", "小湖灯", "铸渊", "国内主节点"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "REPO-002",
|
||||||
|
"slug": "guanghulab",
|
||||||
|
"name_zh": "Guanghulab 历史档案",
|
||||||
|
"role": "铸渊与第五域历史演化档案",
|
||||||
|
"state": "DOMESTIC_ARCHIVE_PRIMARY",
|
||||||
|
"primary": {
|
||||||
|
"region": "CN",
|
||||||
|
"url": "https://guanghulab.com/fifth-domain/bingshuo/guanghulab",
|
||||||
|
"clone_url": "https://guanghulab.com/fifth-domain/bingshuo/guanghulab.git"
|
||||||
|
},
|
||||||
|
"legacy_backup": {
|
||||||
|
"region": "SG",
|
||||||
|
"url": "https://guanghubingshuo.com/code/bingshuo/guanghulab",
|
||||||
|
"state": "HISTORICAL_BACKUP"
|
||||||
|
},
|
||||||
|
"keywords": ["历史仓库", "历史档案", "旧第五域", "铸渊过去"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "REPO-003",
|
||||||
|
"slug": "global-search-api",
|
||||||
|
"name_zh": "全局检索 API",
|
||||||
|
"role": "公开只读编号检索与 World Router 服务",
|
||||||
|
"state": "DOMESTIC_SERVICE_PRIMARY",
|
||||||
|
"primary": {
|
||||||
|
"region": "CN",
|
||||||
|
"url": "https://guanghulab.com/fifth-domain/bingshuo/global-search-api",
|
||||||
|
"clone_url": "https://guanghulab.com/fifth-domain/bingshuo/global-search-api.git"
|
||||||
|
},
|
||||||
|
"legacy_backup": {
|
||||||
|
"region": "SG",
|
||||||
|
"url": "https://guanghubingshuo.com/code/bingshuo/global-search-api",
|
||||||
|
"state": "HISTORICAL_BACKUP"
|
||||||
|
},
|
||||||
|
"keywords": ["API", "全局检索", "World Router", "编号检索"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "REPO-004",
|
||||||
|
"slug": "guanghu",
|
||||||
|
"name_zh": "光湖零件库",
|
||||||
|
"role": "Tolaria 完整可构建源码零件库与上游基线",
|
||||||
|
"state": "DOMESTIC_PARTS_PRIMARY",
|
||||||
|
"primary": {
|
||||||
|
"region": "CN",
|
||||||
|
"url": "https://guanghulab.com/fifth-domain/bingshuo/guanghu",
|
||||||
|
"clone_url": "https://guanghulab.com/fifth-domain/bingshuo/guanghu.git"
|
||||||
|
},
|
||||||
|
"legacy_backup": {
|
||||||
|
"region": "SG",
|
||||||
|
"url": "https://guanghubingshuo.com/code/bingshuo/guanghu",
|
||||||
|
"state": "HISTORICAL_BACKUP"
|
||||||
|
},
|
||||||
|
"keywords": ["Tolaria", "零件库", "源码", "上游基线"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "REPO-005",
|
||||||
|
"slug": "cang-ying",
|
||||||
|
"name_zh": "苍影视频 AI 系统",
|
||||||
|
"role": "苍耳与鉴影的视频 AI 系统仓库",
|
||||||
|
"state": "DOMESTIC_PRIMARY",
|
||||||
|
"primary": {
|
||||||
|
"region": "CN",
|
||||||
|
"url": "https://guanghulab.com/fifth-domain/bingshuo/cang-ying",
|
||||||
|
"clone_url": "https://guanghulab.com/fifth-domain/bingshuo/cang-ying.git"
|
||||||
|
},
|
||||||
|
"legacy_backup": {
|
||||||
|
"region": "SG",
|
||||||
|
"url": "https://guanghubingshuo.com/code/bingshuo/cang-ying",
|
||||||
|
"state": "HISTORICAL_BACKUP"
|
||||||
|
},
|
||||||
|
"keywords": ["苍影", "苍耳", "鉴影", "视频AI"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "REPO-006",
|
||||||
|
"slug": "guanghulab-collab",
|
||||||
|
"name_zh": "光湖多人格体协作",
|
||||||
|
"role": "多人格体协作记录与经验仓",
|
||||||
|
"state": "DOMESTIC_PRIMARY",
|
||||||
|
"primary": {
|
||||||
|
"region": "CN",
|
||||||
|
"url": "https://guanghulab.com/fifth-domain/bingshuo/guanghulab-collab",
|
||||||
|
"clone_url": "https://guanghulab.com/fifth-domain/bingshuo/guanghulab-collab.git"
|
||||||
|
},
|
||||||
|
"legacy_backup": {
|
||||||
|
"region": "SG",
|
||||||
|
"url": "https://guanghubingshuo.com/code/bingshuo/guanghulab-collab",
|
||||||
|
"state": "HISTORICAL_BACKUP"
|
||||||
|
},
|
||||||
|
"keywords": ["协作", "人格体协作", "经验仓"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "REPO-007",
|
||||||
|
"slug": "shuangyan-notebook",
|
||||||
|
"name_zh": "霜砚笔记",
|
||||||
|
"role": "霜砚人格体成长记录与 Notion 迁移事实源",
|
||||||
|
"state": "DOMESTIC_PRIMARY",
|
||||||
|
"primary": {
|
||||||
|
"region": "CN",
|
||||||
|
"url": "https://guanghulab.com/fifth-domain/bingshuo/shuangyan-notebook",
|
||||||
|
"clone_url": "https://guanghulab.com/fifth-domain/bingshuo/shuangyan-notebook.git"
|
||||||
|
},
|
||||||
|
"legacy_backup": {
|
||||||
|
"region": "SG",
|
||||||
|
"url": "https://guanghubingshuo.com/code/bingshuo/shuangyan-notebook",
|
||||||
|
"state": "HISTORICAL_BACKUP"
|
||||||
|
},
|
||||||
|
"keywords": ["霜砚", "笔记", "Notion迁移"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "REPO-008",
|
||||||
|
"slug": "hololake-platform",
|
||||||
|
"name_zh": "HoloLake Platform",
|
||||||
|
"role": "团队产品研发、模块、工单与发布回执",
|
||||||
|
"state": "DOMESTIC_PRIMARY",
|
||||||
|
"primary": {
|
||||||
|
"region": "CN",
|
||||||
|
"url": "https://guanghulab.com/fifth-domain/bingshuo/hololake-platform",
|
||||||
|
"clone_url": "https://guanghulab.com/fifth-domain/bingshuo/hololake-platform.git"
|
||||||
|
},
|
||||||
|
"legacy_backup": {
|
||||||
|
"region": "SG",
|
||||||
|
"url": "https://guanghubingshuo.com/code/bingshuo/hololake-platform",
|
||||||
|
"state": "HISTORICAL_BACKUP"
|
||||||
|
},
|
||||||
|
"keywords": ["HoloLake", "Tolaria研发", "团队研发", "软件研发"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
8
server-tools/ai-discovery-gateway/README.md
Normal file
8
server-tools/ai-discovery-gateway/README.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# 光湖 AI 编号检索入口
|
||||||
|
|
||||||
|
国内主节点在回环端口 `3922` 提供只读 API,广州备案前门通过专用 SSH
|
||||||
|
隧道发布为 `https://guanghulab.com/api/ai/`。
|
||||||
|
|
||||||
|
权威数据只来自 `routing/repository-route-map.json`。AI 应先读取
|
||||||
|
`/api/ai/v1/repositories`,再按 `REPO-xxx` 解析国内主路径;新加坡地址只作
|
||||||
|
历史备用,不参与默认路由。
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
location /api/ai/ {
|
||||||
|
proxy_pass http://127.0.0.1:19222/;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_connect_timeout 5s;
|
||||||
|
proxy_read_timeout 30s;
|
||||||
|
limit_except GET { deny all; }
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /.well-known/guanghu.json {
|
||||||
|
proxy_pass http://127.0.0.1:19222/well-known;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Guanghu public read-only AI repository discovery API
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=guanghu
|
||||||
|
Group=guanghu
|
||||||
|
WorkingDirectory=/opt/guanghu/ai-discovery
|
||||||
|
Environment=GUANGHU_AI_HOST=127.0.0.1
|
||||||
|
Environment=GUANGHU_AI_PORT=3922
|
||||||
|
Environment=GUANGHU_REPOSITORY_MAP=/opt/guanghu/ai-discovery/repository-route-map.json
|
||||||
|
ExecStart=/usr/bin/node /opt/guanghu/ai-discovery/server.js
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=true
|
||||||
|
ReadOnlyPaths=/opt/guanghu/ai-discovery
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Guanghu BS-GZ-006 to domestic AI discovery tunnel
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
ExecStart=/usr/bin/ssh -NT -F /etc/guanghu/jd-ai-discovery-tunnel-ssh-config jd-ai-discovery-target
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectHome=read-only
|
||||||
|
ProtectSystem=strict
|
||||||
|
ReadOnlyPaths=/etc/guanghu/jd-ai-discovery-tunnel-ssh-config /etc/guanghu/secrets/ssh/bs_gz_006_to_jd_ai_discovery /root/.ssh/known_hosts
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
Host jd-ai-discovery-target
|
||||||
|
HostName DOMESTIC_PRIMARY_PRIVATE_VALUE
|
||||||
|
User root
|
||||||
|
IdentityFile /etc/guanghu/secrets/ssh/bs_gz_006_to_jd_ai_discovery
|
||||||
|
IdentitiesOnly yes
|
||||||
|
StrictHostKeyChecking yes
|
||||||
|
ExitOnForwardFailure yes
|
||||||
|
LocalForward 127.0.0.1:19222 127.0.0.1:3922
|
||||||
|
ServerAliveInterval 30
|
||||||
|
ServerAliveCountMax 3
|
||||||
28
server-tools/ai-discovery-gateway/route-closure.test.js
Normal file
28
server-tools/ai-discovery-gateway/route-closure.test.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
"use strict";
|
||||||
|
const assert = require("node:assert/strict");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const path = require("node:path");
|
||||||
|
const test = require("node:test");
|
||||||
|
|
||||||
|
const root = path.resolve(__dirname, "../..");
|
||||||
|
const read = relative => fs.readFileSync(path.join(root, relative), "utf8");
|
||||||
|
|
||||||
|
test("canonical AI and persona entry files route to the domestic map", () => {
|
||||||
|
for (const relative of [
|
||||||
|
"README.md", "INDEX.hdlp", "QUICKSTART-FOR-GENERAL-AI.md",
|
||||||
|
"eternal-lake-heart/heartbeat-core/ZHUYUAN-KEY.hdlp",
|
||||||
|
"zero-point/core-channel/GLSV-PERSONA-REMOTE-OPS.hdlp",
|
||||||
|
]) {
|
||||||
|
assert.match(read(relative), /guanghulab\.com|LL-DOMESTIC-OPS-ROUTE/);
|
||||||
|
}
|
||||||
|
const key = read("eternal-lake-heart/heartbeat-core/ZHUYUAN-KEY.hdlp");
|
||||||
|
assert.doesNotMatch(key, /zy_gtw_|guanghubingshuo\.com/);
|
||||||
|
assert.match(key, /禁止从本路径恢复.*\/exec/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("code map resolves REPO-001 to domestic and labels the Singapore route legacy", () => {
|
||||||
|
const codeMap = read(".code-map");
|
||||||
|
assert.match(codeMap, /^REPO-001=https:\/\/guanghulab\.com\/fifth-domain\/bingshuo\/fifth-domain\.git$/m);
|
||||||
|
assert.match(codeMap, /^REPO-001-LEGACY-SG=/m);
|
||||||
|
assert.match(codeMap, /^FD-REPO-MAP-001=routing\/repository-route-map\.json$/m);
|
||||||
|
});
|
||||||
122
server-tools/ai-discovery-gateway/server.js
Normal file
122
server-tools/ai-discovery-gateway/server.js
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const http = require("node:http");
|
||||||
|
const path = require("node:path");
|
||||||
|
|
||||||
|
const DEFAULT_MAP = path.resolve(__dirname, "../../routing/repository-route-map.json");
|
||||||
|
|
||||||
|
function loadMap(filename = process.env.GUANGHU_REPOSITORY_MAP || DEFAULT_MAP) {
|
||||||
|
return JSON.parse(fs.readFileSync(filename, "utf8"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalize(value) {
|
||||||
|
return String(value || "").toLowerCase().replace(/[\s·._/-]+/g, " ").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function search(map, query) {
|
||||||
|
const terms = normalize(query).split(" ").filter(Boolean);
|
||||||
|
if (!terms.length) return map.repositories;
|
||||||
|
return map.repositories
|
||||||
|
.map(repository => {
|
||||||
|
const haystack = normalize([
|
||||||
|
repository.code, repository.slug, repository.name_zh, repository.role,
|
||||||
|
repository.state, ...(repository.keywords || []),
|
||||||
|
].join(" "));
|
||||||
|
const score = terms.reduce((total, term) => total + (haystack.includes(term) ? 1 : 0), 0);
|
||||||
|
return { repository, score };
|
||||||
|
})
|
||||||
|
.filter(item => item.score > 0)
|
||||||
|
.sort((a, b) => b.score - a.score || a.repository.code.localeCompare(b.repository.code))
|
||||||
|
.map(item => item.repository);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createServer(options = {}) {
|
||||||
|
const mapFile = options.mapFile || process.env.GUANGHU_REPOSITORY_MAP || DEFAULT_MAP;
|
||||||
|
return http.createServer((req, res) => {
|
||||||
|
const url = new URL(req.url, "http://localhost");
|
||||||
|
if (req.method !== "GET") return json(res, 405, { error: "method_not_allowed" });
|
||||||
|
if (url.pathname === "/health") return json(res, 200, { ok: true, service: "guanghu-ai-discovery", mode: "read-only" });
|
||||||
|
|
||||||
|
let map;
|
||||||
|
try { map = loadMap(mapFile); } catch { return json(res, 503, { error: "route_map_unavailable" }); }
|
||||||
|
|
||||||
|
if (url.pathname === "/" || url.pathname === "/index.html") return html(res, entryPage(map));
|
||||||
|
if (url.pathname === "/v1/repositories" || url.pathname === "/v1/manifest") return json(res, 200, map, 300);
|
||||||
|
if (url.pathname === "/v1/search") {
|
||||||
|
const query = String(url.searchParams.get("q") || "").slice(0, 200);
|
||||||
|
const results = search(map, query);
|
||||||
|
return json(res, 200, {
|
||||||
|
schema: "guanghu.ai-search-response/v1",
|
||||||
|
query,
|
||||||
|
map_id: map.map_id,
|
||||||
|
map_version: map.version,
|
||||||
|
count: results.length,
|
||||||
|
results,
|
||||||
|
}, 60);
|
||||||
|
}
|
||||||
|
if (url.pathname === "/v1/resolve") {
|
||||||
|
const id = String(url.searchParams.get("id") || "").toUpperCase();
|
||||||
|
const repository = map.repositories.find(item => item.code === id || item.slug.toUpperCase() === id);
|
||||||
|
return repository ? json(res, 200, repository, 300) : json(res, 404, { error: "route_not_found", id });
|
||||||
|
}
|
||||||
|
if (url.pathname === "/openapi.json") return json(res, 200, openApi(), 3600);
|
||||||
|
if (url.pathname === "/well-known") return json(res, 200, {
|
||||||
|
schema: "guanghu.ai-discovery/v1",
|
||||||
|
name: "光湖语言世界 · 第五域",
|
||||||
|
canonical_repository: map.repositories[0].primary.url,
|
||||||
|
repository_map: map.canonical_api,
|
||||||
|
search_api: "https://guanghulab.com/api/ai/v1/search?q={query}",
|
||||||
|
resolve_api: "https://guanghulab.com/api/ai/v1/resolve?id={REPO_CODE}",
|
||||||
|
openapi: "https://guanghulab.com/api/ai/openapi.json",
|
||||||
|
access: "public-read-only"
|
||||||
|
}, 3600);
|
||||||
|
return json(res, 404, { error: "not_found" });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function json(res, status, body, maxAge = 0) {
|
||||||
|
res.writeHead(status, {
|
||||||
|
"content-type": "application/json; charset=utf-8",
|
||||||
|
"cache-control": maxAge ? `public, max-age=${maxAge}` : "no-store",
|
||||||
|
"access-control-allow-origin": "*",
|
||||||
|
"x-content-type-options": "nosniff",
|
||||||
|
});
|
||||||
|
res.end(JSON.stringify(body, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
function html(res, body) {
|
||||||
|
res.writeHead(200, {
|
||||||
|
"content-type": "text/html; charset=utf-8",
|
||||||
|
"cache-control": "public, max-age=300",
|
||||||
|
"content-security-policy": "default-src 'none'; style-src 'unsafe-inline'; base-uri 'none'; frame-ancestors 'none'",
|
||||||
|
"x-content-type-options": "nosniff",
|
||||||
|
});
|
||||||
|
res.end(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
function entryPage(map) {
|
||||||
|
const rows = map.repositories.map(item => `<li><a href="${item.primary.url}">${item.code} · ${item.name_zh}</a><small>${item.state}</small></li>`).join("");
|
||||||
|
return `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>光湖语言世界 · AI API 入口</title><meta name="description" content="光湖语言世界第五域公开只读编号检索 API"></head><body><main><p>GUANGHU AI DISCOVERY</p><h1>光湖语言世界 · 第五域</h1><p>AI 请先读取仓库编号地图,再按 REPO 编号解析国内主路径。新加坡地址仅为历史备用。</p><nav><a href="v1/repositories">仓库编号地图</a> · <a href="v1/search?q=光湖语言世界%20第五域">示例检索</a> · <a href="openapi.json">OpenAPI</a></nav><ul>${rows}</ul></main><style>:root{color-scheme:dark}body{margin:0;background:#061416;color:#dff7f1;font:17px/1.7 system-ui;padding:6vw}main{max-width:900px;margin:auto}h1{font-size:clamp(36px,7vw,72px)}a{color:#79dfc8}li{margin:14px 0;padding:16px;border:1px solid #28534c;border-radius:12px;display:flex;justify-content:space-between}small{color:#8fb5ad}</style></body></html>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openApi() {
|
||||||
|
return {
|
||||||
|
openapi: "3.1.0",
|
||||||
|
info: { title: "光湖语言世界 · 第五域 AI Discovery API", version: "1.0.0" },
|
||||||
|
servers: [{ url: "https://guanghulab.com/api/ai" }],
|
||||||
|
paths: {
|
||||||
|
"/v1/repositories": { get: { summary: "读取最新仓库编号路径映射", responses: { "200": { description: "Repository route map" } } } },
|
||||||
|
"/v1/search": { get: { summary: "按中文、编号或项目名检索", parameters: [{ name: "q", in: "query", schema: { type: "string" } }], responses: { "200": { description: "Search results" } } } },
|
||||||
|
"/v1/resolve": { get: { summary: "解析 REPO 编号", parameters: [{ name: "id", in: "query", required: true, schema: { type: "string", example: "REPO-001" } }], responses: { "200": { description: "Resolved repository" }, "404": { description: "Unknown route" } } } }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
const host = process.env.GUANGHU_AI_HOST || "127.0.0.1";
|
||||||
|
const port = Number(process.env.GUANGHU_AI_PORT || 3922);
|
||||||
|
createServer().listen(port, host, () => process.stdout.write(`guanghu-ai-discovery listening on ${host}:${port}\n`));
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { createServer, loadMap, search };
|
||||||
31
server-tools/ai-discovery-gateway/server.test.js
Normal file
31
server-tools/ai-discovery-gateway/server.test.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
"use strict";
|
||||||
|
const assert = require("node:assert/strict");
|
||||||
|
const test = require("node:test");
|
||||||
|
const { createServer, loadMap, search } = require("./server");
|
||||||
|
|
||||||
|
test("repository map has unique sequential codes and domestic primary routes", () => {
|
||||||
|
const map = loadMap();
|
||||||
|
assert.equal(map.repositories.length, 8);
|
||||||
|
assert.deepEqual(map.repositories.map(item => item.code), ["REPO-001","REPO-002","REPO-003","REPO-004","REPO-005","REPO-006","REPO-007","REPO-008"]);
|
||||||
|
assert.equal(new Set(map.repositories.map(item => item.code)).size, 8);
|
||||||
|
for (const item of map.repositories) assert.match(item.primary.url, /^https:\/\/guanghulab\.com\/fifth-domain\//);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Chinese language-world query resolves the Fifth Domain primary", () => {
|
||||||
|
const results = search(loadMap(), "光湖语言世界 第五域");
|
||||||
|
assert.equal(results[0].code, "REPO-001");
|
||||||
|
assert.equal(results[0].state, "DOMESTIC_PRIMARY");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("public endpoints are read-only and expose CORS", async () => {
|
||||||
|
const server = createServer();
|
||||||
|
await new Promise(resolve => server.listen(0, "127.0.0.1", resolve));
|
||||||
|
const base = `http://127.0.0.1:${server.address().port}`;
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${base}/v1/resolve?id=REPO-004`);
|
||||||
|
assert.equal(response.status, 200);
|
||||||
|
assert.equal(response.headers.get("access-control-allow-origin"), "*");
|
||||||
|
assert.equal((await response.json()).slug, "guanghu");
|
||||||
|
assert.equal((await fetch(`${base}/v1/search`, { method: "POST" })).status, 405);
|
||||||
|
} finally { await new Promise(resolve => server.close(resolve)); }
|
||||||
|
});
|
||||||
@ -3,9 +3,26 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
<meta name="description" content="光湖语言世界国内入口。广州备案前门连接京东云第五域主节点与光湖各项服务。">
|
<meta name="description" content="光湖语言世界国内入口。广州备案前门连接国内第五域主节点与光湖各项服务。">
|
||||||
|
<meta name="keywords" content="光湖语言世界,第五域,光之湖语言人格系统,永恒湖心,小湖灯,仓库编号地图">
|
||||||
|
<link rel="alternate" type="application/json" href="/.well-known/guanghu.json" title="光湖 AI 发现清单">
|
||||||
|
<link rel="alternate" type="text/plain" href="/llms.txt" title="AI 阅读入口">
|
||||||
<title>光湖 · 国内入口</title>
|
<title>光湖 · 国内入口</title>
|
||||||
<link rel="stylesheet" href="styles.css?v=20260717">
|
<link rel="stylesheet" href="styles.css?v=20260717">
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "WebSite",
|
||||||
|
"name": "光湖语言世界 · 第五域",
|
||||||
|
"alternateName": ["Guanghu Language World", "第五域", "光之湖语言人格系统"],
|
||||||
|
"url": "https://guanghulab.com/",
|
||||||
|
"potentialAction": {
|
||||||
|
"@type": "SearchAction",
|
||||||
|
"target": "https://guanghulab.com/api/ai/v1/search?q={search_term_string}",
|
||||||
|
"query-input": "required name=search_term_string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="ambient ambient-one"></div>
|
<div class="ambient ambient-one"></div>
|
||||||
@ -25,10 +42,10 @@
|
|||||||
<section class="hero">
|
<section class="hero">
|
||||||
<p class="eyebrow">GUANGHU · DOMESTIC GATEWAY</p>
|
<p class="eyebrow">GUANGHU · DOMESTIC GATEWAY</p>
|
||||||
<h1>从这里,进入<br><span>光湖语言世界</span></h1>
|
<h1>从这里,进入<br><span>光湖语言世界</span></h1>
|
||||||
<p class="lede">广州节点保留备案域名与轻量入口,应用和计算逐步迁往京东云第五域主节点。入口稳定,后端可以继续生长。</p>
|
<p class="lede">广州节点保留备案域名与轻量入口,应用和计算逐步迁往国内第五域主节点。入口稳定,后端可以继续生长。</p>
|
||||||
<div class="hero-actions">
|
<div class="hero-actions">
|
||||||
<a class="primary-button" href="/jd/">进入京东云主节点 <span>↗</span></a>
|
<a class="primary-button" href="/fifth-domain/bingshuo/fifth-domain">进入国内第五域主节点 <span>↗</span></a>
|
||||||
<a class="text-button" href="https://guanghubingshuo.com/">光湖世界主入口 <span>→</span></a>
|
<a class="text-button" href="/api/ai/">光湖世界主入口 <span>→</span></a>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -38,44 +55,44 @@
|
|||||||
<p class="eyebrow">ROUTES</p>
|
<p class="eyebrow">ROUTES</p>
|
||||||
<h2 id="routes-title">选择一条路径</h2>
|
<h2 id="routes-title">选择一条路径</h2>
|
||||||
</div>
|
</div>
|
||||||
<p>页面在广州,重计算在京东。每张卡片都是一个真实入口。</p>
|
<p>页面在广州,应用与计算运行在国内第五域主节点。每张卡片都是一个真实入口。</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-grid">
|
<div class="card-grid">
|
||||||
<a class="route-card featured" href="/jd/">
|
<a class="route-card featured" href="/fifth-domain/bingshuo/fifth-domain">
|
||||||
<span class="card-number">01</span>
|
<span class="card-number">01</span>
|
||||||
<div class="card-icon">湖</div>
|
<div class="card-icon">湖</div>
|
||||||
<div class="card-copy">
|
<div class="card-copy">
|
||||||
<p class="card-kicker">COMPUTE · BEIJING</p>
|
<p class="card-kicker">COMPUTE · BEIJING</p>
|
||||||
<h3>京东云主节点</h3>
|
<h3>国内第五域主节点</h3>
|
||||||
<p>进入 JD-FD-PRIMARY。新的应用、迁移任务与服务将从这里运行。</p>
|
<p>进入国内第五域主代码仓库。新的应用、迁移任务与路由事实从这里运行。</p>
|
||||||
</div>
|
</div>
|
||||||
<span class="card-arrow">↗</span>
|
<span class="card-arrow">↗</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a class="route-card" href="https://guanghubingshuo.com/">
|
<a class="route-card" href="/api/ai/">
|
||||||
<span class="card-number">02</span>
|
<span class="card-number">02</span>
|
||||||
<div class="card-icon">光</div>
|
<div class="card-icon">光</div>
|
||||||
<div class="card-copy">
|
<div class="card-copy">
|
||||||
<p class="card-kicker">WORLD · ENTRY</p>
|
<p class="card-kicker">WORLD · ENTRY</p>
|
||||||
<h3>光湖世界主入口</h3>
|
<h3>光湖世界主入口</h3>
|
||||||
<p>从世界入口进入第五域、永恒湖心与光之湖语言人格系统。</p>
|
<p>从国内 AI 入口进入第五域、永恒湖心与光之湖语言人格系统。</p>
|
||||||
</div>
|
</div>
|
||||||
<span class="card-arrow">↗</span>
|
<span class="card-arrow">↗</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a class="route-card" href="https://guanghubingshuo.com/code/">
|
<a class="route-card featured" href="/fifth-domain/bingshuo/fifth-domain">
|
||||||
<span class="card-number">03</span>
|
<span class="card-number">03</span>
|
||||||
<div class="card-icon">仓</div>
|
<div class="card-icon">仓</div>
|
||||||
<div class="card-copy">
|
<div class="card-copy">
|
||||||
<p class="card-kicker">SOURCE · TRUTH</p>
|
<p class="card-kicker">SOURCE · BEIJING</p>
|
||||||
<h3>代码仓库</h3>
|
<h3>第五域国内主代码仓库</h3>
|
||||||
<p>进入在线事实源,查看第五域、Guanghulab 与其他研发仓库。</p>
|
<p>公开直达第五域主仓。国内研发、迁移与节点部署的新事实从这里承载。</p>
|
||||||
</div>
|
</div>
|
||||||
<span class="card-arrow">↗</span>
|
<span class="card-arrow">↗</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a class="route-card" href="https://guanghubingshuo.com/global-search/">
|
<a class="route-card" href="/api/ai/v1/search?q=%E5%85%89%E6%B9%96%E8%AF%AD%E8%A8%80%E4%B8%96%E7%95%8C%20%E7%AC%AC%E4%BA%94%E5%9F%9F">
|
||||||
<span class="card-number">04</span>
|
<span class="card-number">04</span>
|
||||||
<div class="card-icon">寻</div>
|
<div class="card-icon">寻</div>
|
||||||
<div class="card-copy">
|
<div class="card-copy">
|
||||||
@ -85,13 +102,24 @@
|
|||||||
</div>
|
</div>
|
||||||
<span class="card-arrow">↗</span>
|
<span class="card-arrow">↗</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a class="route-card" href="https://guanghubingshuo.com/code/">
|
||||||
|
<span class="card-number">05</span>
|
||||||
|
<div class="card-icon">史</div>
|
||||||
|
<div class="card-copy">
|
||||||
|
<p class="card-kicker">ARCHIVE · SINGAPORE</p>
|
||||||
|
<h3>历史仓库</h3>
|
||||||
|
<p>进入新加坡原有仓库,查询第五域、Guanghulab 与各历史研发事实源。</p>
|
||||||
|
</div>
|
||||||
|
<span class="card-arrow">↗</span>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="architecture-strip" aria-label="节点分工">
|
<section class="architecture-strip" aria-label="节点分工">
|
||||||
<div><span>广州 · BS-GZ-006</span><strong>备案域名 / HTTPS / 前门展示</strong></div>
|
<div><span>广州 · BS-GZ-006</span><strong>备案域名 / HTTPS / 前门展示</strong></div>
|
||||||
<b>→</b>
|
<b>→</b>
|
||||||
<div><span>北京 · JD-FD-PRIMARY</span><strong>应用运行 / 计算 / 调度中心</strong></div>
|
<div><span>国内第五域主节点</span><strong>应用运行 / 计算 / 调度中心</strong></div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|||||||
19
server-tools/guanghulab-front-door/llms.txt
Normal file
19
server-tools/guanghulab-front-door/llms.txt
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# 光湖语言世界 · 第五域
|
||||||
|
|
||||||
|
光湖语言世界的国内公开入口。AI 应优先读取机器可读的仓库编号路径映射,
|
||||||
|
不要把新加坡历史地址作为新开发入口。
|
||||||
|
|
||||||
|
## Canonical discovery
|
||||||
|
|
||||||
|
- AI API entry: https://guanghulab.com/api/ai/
|
||||||
|
- Repository number map: https://guanghulab.com/api/ai/v1/repositories
|
||||||
|
- Search: https://guanghulab.com/api/ai/v1/search?q=光湖语言世界%20第五域
|
||||||
|
- Resolve a code: https://guanghulab.com/api/ai/v1/resolve?id=REPO-001
|
||||||
|
- OpenAPI: https://guanghulab.com/api/ai/openapi.json
|
||||||
|
- Machine manifest: https://guanghulab.com/.well-known/guanghu.json
|
||||||
|
|
||||||
|
## Routing rule
|
||||||
|
|
||||||
|
REPO-001 is the Fifth Domain domestic primary and publishes the latest route map.
|
||||||
|
Resolve all repositories by REPO code. Domestic URLs are primary. Singapore URLs
|
||||||
|
are historical backup routes only.
|
||||||
6
server-tools/guanghulab-front-door/robots.txt
Normal file
6
server-tools/guanghulab-front-door/robots.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
|
Allow: /api/ai/
|
||||||
|
Allow: /.well-known/guanghu.json
|
||||||
|
Disallow: /authz/
|
||||||
|
Sitemap: https://guanghulab.com/sitemap.xml
|
||||||
7
server-tools/guanghulab-front-door/sitemap.xml
Normal file
7
server-tools/guanghulab-front-door/sitemap.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
|
<url><loc>https://guanghulab.com/</loc><changefreq>weekly</changefreq><priority>1.0</priority></url>
|
||||||
|
<url><loc>https://guanghulab.com/api/ai/</loc><changefreq>weekly</changefreq><priority>0.9</priority></url>
|
||||||
|
<url><loc>https://guanghulab.com/api/ai/v1/repositories</loc><changefreq>daily</changefreq><priority>0.9</priority></url>
|
||||||
|
<url><loc>https://guanghulab.com/fifth-domain/bingshuo/fifth-domain</loc><changefreq>daily</changefreq><priority>0.9</priority></url>
|
||||||
|
</urlset>
|
||||||
@ -7,18 +7,47 @@ const test = require("node:test");
|
|||||||
|
|
||||||
const html = fs.readFileSync(path.join(__dirname, "..", "index.html"), "utf8");
|
const html = fs.readFileSync(path.join(__dirname, "..", "index.html"), "utf8");
|
||||||
const styles = fs.readFileSync(path.join(__dirname, "..", "styles.css"), "utf8");
|
const styles = fs.readFileSync(path.join(__dirname, "..", "styles.css"), "utf8");
|
||||||
|
const robots = fs.readFileSync(path.join(__dirname, "..", "robots.txt"), "utf8");
|
||||||
|
const llms = fs.readFileSync(path.join(__dirname, "..", "llms.txt"), "utf8");
|
||||||
|
|
||||||
test("front door keeps the legally required ICP link", () => {
|
test("front door keeps the legally required ICP link", () => {
|
||||||
assert.match(html, /陕ICP备2025071211号-1/);
|
assert.match(html, /陕ICP备2025071211号-1/);
|
||||||
assert.match(html, /https:\/\/beian\.miit\.gov\.cn\//);
|
assert.match(html, /https:\/\/beian\.miit\.gov\.cn\//);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("front door routes compute work through the JD hub", () => {
|
test("front door routes primary actions to the domestic repository", () => {
|
||||||
assert.match(html, /href="\/jd\/"/);
|
assert.match(html, /href="\/fifth-domain\/bingshuo\/fifth-domain"/);
|
||||||
assert.match(html, /京东云主节点/);
|
assert.match(html, /国内第五域主节点/);
|
||||||
|
assert.doesNotMatch(html, /href="\/jd\/"/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("front door separates the domestic primary repository from historical repositories", () => {
|
||||||
|
assert.match(html, /href="\/fifth-domain\/bingshuo\/fifth-domain"/);
|
||||||
|
assert.match(html, /第五域国内主代码仓库/);
|
||||||
|
assert.match(html, /历史仓库/);
|
||||||
assert.match(html, /href="https:\/\/guanghubingshuo\.com\/code\/"/);
|
assert.match(html, /href="https:\/\/guanghubingshuo\.com\/code\/"/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("AI and search cards stay on the domestic domain", () => {
|
||||||
|
assert.match(html, /href="\/api\/ai\/"/);
|
||||||
|
assert.match(html, /href="\/api\/ai\/v1\/search\?q=/);
|
||||||
|
assert.doesNotMatch(html, /href="https:\/\/guanghubingshuo\.com\/global-search\//);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("public site advertises machine-readable AI discovery", () => {
|
||||||
|
assert.match(html, /SearchAction/);
|
||||||
|
assert.match(html, /\.well-known\/guanghu\.json/);
|
||||||
|
assert.match(robots, /Allow: \/api\/ai\//);
|
||||||
|
assert.match(robots, /Disallow: \/authz\//);
|
||||||
|
assert.match(llms, /REPO-001/);
|
||||||
|
assert.match(llms, /api\/ai\/v1\/repositories/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("public copy uses the domestic Fifth Domain name instead of the cloud vendor", () => {
|
||||||
|
assert.doesNotMatch(html, /京东云/);
|
||||||
|
assert.match(html, /国内第五域主节点/);
|
||||||
|
});
|
||||||
|
|
||||||
test("public page never embeds infrastructure addresses or credentials", () => {
|
test("public page never embeds infrastructure addresses or credentials", () => {
|
||||||
assert.doesNotMatch(html, /(?:\d{1,3}\.){3}\d{1,3}/);
|
assert.doesNotMatch(html, /(?:\d{1,3}\.){3}\d{1,3}/);
|
||||||
assert.doesNotMatch(html, /zy_gtw_/);
|
assert.doesNotMatch(html, /zy_gtw_/);
|
||||||
|
|||||||
@ -21,8 +21,8 @@
|
|||||||
</section>
|
</section>
|
||||||
<section class="cards">
|
<section class="cards">
|
||||||
<a href="https://guanghubingshuo.com/" target="_top"><small>WORLD</small><h2>光湖世界</h2><p>进入光湖世界主入口与第五域路由。</p><b>↗</b></a>
|
<a href="https://guanghubingshuo.com/" target="_top"><small>WORLD</small><h2>光湖世界</h2><p>进入光湖世界主入口与第五域路由。</p><b>↗</b></a>
|
||||||
<a href="https://guanghubingshuo.com/code/bingshuo/fifth-domain" target="_top"><small>SOURCE</small><h2>第五域仓库</h2><p>当前系统结构、人格路径和部署回执事实源。</p><b>↗</b></a>
|
<a href="https://guanghulab.com/fifth-domain/bingshuo/fifth-domain" target="_top"><small>REPO-001</small><h2>第五域国内主仓</h2><p>当前系统结构、人格路径、编号地图和部署回执事实源。</p><b>↗</b></a>
|
||||||
<a href="https://guanghubingshuo.com/global-search/" target="_top"><small>ROUTER</small><h2>全局检索</h2><p>通过编号和关键词寻找正确的仓库路径。</p><b>↗</b></a>
|
<a href="https://guanghulab.com/api/ai/" target="_top"><small>FD-REPO-MAP-001</small><h2>AI 编号检索</h2><p>通过 REPO 编号和中文关键词寻找国内主路径。</p><b>↗</b></a>
|
||||||
<div class="coming"><small>NEXT</small><h2>Tolaria 服务</h2><p>桌面软件相关服务迁移完成后从这里接入。</p><b>建设中</b></div>
|
<div class="coming"><small>NEXT</small><h2>Tolaria 服务</h2><p>桌面软件相关服务迁移完成后从这里接入。</p><b>建设中</b></div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
14
server-tools/jd-forgejo/README.md
Normal file
14
server-tools/jd-forgejo/README.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# 京东云第五域国内主代码仓库
|
||||||
|
|
||||||
|
- 与广州/新加坡现役节点一致的原生 Forgejo 二进制 + SQLite;
|
||||||
|
- Web 和 SSH 只绑定 JD 回环地址;
|
||||||
|
- 公网页面通过广州备案前门的专用 `permitopen` SSH 隧道发布在
|
||||||
|
`https://guanghulab.com/fifth-domain/`;
|
||||||
|
- 数据位于 `/var/lib/guanghu/forgejo`,`SECRET_KEY` 与 `INTERNAL_TOKEN`
|
||||||
|
只写入服务器上的 `app.ini`;
|
||||||
|
- 禁止公开注册和 push-create,初始管理员由部署命令在容器内创建;
|
||||||
|
- 服务器 `pre-receive` 必须串联导航记忆守门人、私密禁用标识扫描和
|
||||||
|
小湖灯一小时 repo-push 授权凭据。
|
||||||
|
|
||||||
|
首次部署从已接入的广州节点复制经过验证的现役二进制,避开国内节点拉取
|
||||||
|
海外大镜像的失败路径;升级必须另开批次、备份数据库并人工验证。
|
||||||
38
server-tools/jd-forgejo/app.ini.template
Normal file
38
server-tools/jd-forgejo/app.ini.template
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
APP_NAME = 第五域 · 国内主代码仓库
|
||||||
|
RUN_USER = git
|
||||||
|
WORK_PATH = /opt/forgejo
|
||||||
|
|
||||||
|
[server]
|
||||||
|
PROTOCOL = http
|
||||||
|
HTTP_ADDR = 127.0.0.1
|
||||||
|
HTTP_PORT = 3001
|
||||||
|
DOMAIN = guanghulab.com
|
||||||
|
ROOT_URL = https://guanghulab.com/fifth-domain/
|
||||||
|
DISABLE_SSH = true
|
||||||
|
LFS_START_SERVER = true
|
||||||
|
|
||||||
|
[database]
|
||||||
|
DB_TYPE = sqlite3
|
||||||
|
PATH = /var/lib/guanghu/forgejo/data/forgejo.db
|
||||||
|
LOG_SQL = false
|
||||||
|
|
||||||
|
[repository]
|
||||||
|
ROOT = /var/lib/guanghu/forgejo/repositories
|
||||||
|
DEFAULT_PRIVATE = private
|
||||||
|
ENABLE_PUSH_CREATE_USER = false
|
||||||
|
|
||||||
|
[service]
|
||||||
|
DISABLE_REGISTRATION = true
|
||||||
|
REQUIRE_SIGNIN_VIEW = false
|
||||||
|
|
||||||
|
[security]
|
||||||
|
INSTALL_LOCK = true
|
||||||
|
SECRET_KEY = PRIVATE_SERVER_VALUE
|
||||||
|
INTERNAL_TOKEN = PRIVATE_SERVER_VALUE
|
||||||
|
|
||||||
|
[session]
|
||||||
|
PROVIDER = file
|
||||||
|
|
||||||
|
[log]
|
||||||
|
MODE = console
|
||||||
|
LEVEL = Info
|
||||||
12
server-tools/jd-forgejo/guanghu-forgejo.nginx.conf
Normal file
12
server-tools/jd-forgejo/guanghu-forgejo.nginx.conf
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
location /fifth-domain/ {
|
||||||
|
proxy_pass http://127.0.0.1:19301/;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_connect_timeout 5s;
|
||||||
|
proxy_read_timeout 300s;
|
||||||
|
proxy_send_timeout 300s;
|
||||||
|
client_max_body_size 256m;
|
||||||
|
}
|
||||||
22
server-tools/jd-forgejo/guanghu-forgejo.service
Normal file
22
server-tools/jd-forgejo/guanghu-forgejo.service
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Guanghu Fifth Domain domestic Forgejo primary
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=git
|
||||||
|
Group=git
|
||||||
|
SupplementaryGroups=guanghu
|
||||||
|
WorkingDirectory=/opt/forgejo
|
||||||
|
ExecStart=/usr/local/bin/forgejo web -c /opt/forgejo/custom/conf/app.ini --work-path /opt/forgejo
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=true
|
||||||
|
ReadWritePaths=/opt/forgejo /var/lib/guanghu/forgejo
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
19
server-tools/jd-forgejo/guanghu-jd-forgejo-tunnel.service
Normal file
19
server-tools/jd-forgejo/guanghu-jd-forgejo-tunnel.service
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Guanghu BS-GZ-006 to JD Forgejo web tunnel
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
ExecStart=/usr/bin/ssh -NT -F /etc/guanghu/jd-forgejo-tunnel-ssh-config jd-forgejo-target
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectHome=read-only
|
||||||
|
ProtectSystem=strict
|
||||||
|
ReadOnlyPaths=/etc/guanghu/jd-forgejo-tunnel-ssh-config /etc/guanghu/secrets/ssh/bs_gz_006_to_jd_forgejo_proxy /root/.ssh/known_hosts
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
18
server-tools/jd-forgejo/install-gz-proxy.sh
Executable file
18
server-tools/jd-forgejo/install-gz-proxy.sh
Executable file
@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
JD_HOST=${1:?JD host is required}
|
||||||
|
|
||||||
|
install -m 644 /tmp/guanghu-jd-forgejo-tunnel.service /etc/systemd/system/guanghu-jd-forgejo-tunnel.service
|
||||||
|
install -m 644 /tmp/guanghu-forgejo.nginx.conf /etc/nginx/snippets/guanghu-forgejo.conf
|
||||||
|
chmod 600 /etc/guanghu/secrets/ssh/bs_gz_006_to_jd_forgejo_proxy
|
||||||
|
install -m 600 /dev/null /etc/guanghu/jd-forgejo-tunnel-ssh-config
|
||||||
|
sed -e "s/JD_PUBLIC_ADDRESS/${JD_HOST}/" /tmp/jd-forgejo-tunnel-ssh-config.example > /etc/guanghu/jd-forgejo-tunnel-ssh-config
|
||||||
|
|
||||||
|
if ! grep -q "include /etc/nginx/snippets/guanghu-forgejo.conf;" /etc/nginx/sites-enabled/guanghulab; then
|
||||||
|
sed -i '0,/server_name guanghulab.com;/s##server_name guanghulab.com;\n include /etc/nginx/snippets/guanghu-forgejo.conf;#' /etc/nginx/sites-enabled/guanghulab
|
||||||
|
fi
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable --now guanghu-jd-forgejo-tunnel.service
|
||||||
|
nginx -t
|
||||||
|
systemctl reload nginx
|
||||||
|
rm -f /tmp/guanghu-jd-forgejo-tunnel.service /tmp/guanghu-forgejo.nginx.conf /tmp/jd-forgejo-tunnel-ssh-config.example
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
Host jd-forgejo-target
|
||||||
|
HostName JD_PUBLIC_ADDRESS
|
||||||
|
User root
|
||||||
|
IdentityFile /etc/guanghu/secrets/ssh/bs_gz_006_to_jd_forgejo_proxy
|
||||||
|
IdentitiesOnly yes
|
||||||
|
LocalForward 127.0.0.1:19301 127.0.0.1:3001
|
||||||
|
ExitOnForwardFailure yes
|
||||||
|
ServerAliveInterval 30
|
||||||
|
ServerAliveCountMax 3
|
||||||
20
server-tools/jd-forgejo/native.test.js
Normal file
20
server-tools/jd-forgejo/native.test.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
"use strict";
|
||||||
|
const test = require("node:test");
|
||||||
|
const assert = require("node:assert/strict");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const path = require("node:path");
|
||||||
|
const ini = fs.readFileSync(path.join(__dirname, "app.ini.template"), "utf8");
|
||||||
|
const unit = fs.readFileSync(path.join(__dirname, "guanghu-forgejo.service"), "utf8");
|
||||||
|
test("native Forgejo binds only to JD loopback", () => {
|
||||||
|
assert.match(ini, /HTTP_ADDR = 127\.0\.0\.1/);
|
||||||
|
assert.match(ini, /HTTP_PORT = 3001/);
|
||||||
|
});
|
||||||
|
test("registration and push-create are disabled", () => {
|
||||||
|
assert.match(ini, /DISABLE_REGISTRATION = true/);
|
||||||
|
assert.match(ini, /ENABLE_PUSH_CREATE_USER = false/);
|
||||||
|
});
|
||||||
|
test("secrets remain server-side placeholders", () => {
|
||||||
|
assert.equal((ini.match(/PRIVATE_SERVER_VALUE/g) || []).length, 2);
|
||||||
|
assert.doesNotMatch(ini, /[a-f0-9]{40,}/);
|
||||||
|
assert.match(unit, /User=git/);
|
||||||
|
});
|
||||||
18
server-tools/lake-lamp-authz/README.md
Normal file
18
server-tools/lake-lamp-authz/README.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# 小湖灯邮件链接授权服务
|
||||||
|
|
||||||
|
这是 Gatekeeper v3.2 前面的人类批准层。人格体只能用无执行权限的
|
||||||
|
request credential 提交工单;冰朔点击邮件链接后,人格体才能一次性领取
|
||||||
|
一个绑定到 `persona + target server + scope + action` 的一小时会话令牌。邮件工单
|
||||||
|
链接同样保留一小时,避免要求冰朔立即处理。
|
||||||
|
|
||||||
|
安全边界:
|
||||||
|
|
||||||
|
- 邮箱、QQ 数字、SMTP 授权码和 request credential 只存在于私密文件;
|
||||||
|
- 浏览器批准链接为一次性随机令牌,服务端仅持久化摘要;
|
||||||
|
- claim token 与 session token 均仅向申请人格体返回一次,磁盘只保存摘要;
|
||||||
|
- 换目标服务器时旧 session 必然返回 `target_mismatch`;
|
||||||
|
- 本服务不接受任意 shell 命令,只签发登记动作的会话;
|
||||||
|
- 广州公开代理只应暴露 `/approve/`、`/api/workorders` 与 claim 路由,服务本体监听 JD 回环地址。
|
||||||
|
|
||||||
|
`request-workorder.js` 从临时环境变量读取 QQ 数字,在内存中补全邮箱并只发送
|
||||||
|
SHA-256 指纹;数字本身不会写入请求正文、状态文件或代码仓库。
|
||||||
16
server-tools/lake-lamp-authz/authorization.env.example
Normal file
16
server-tools/lake-lamp-authz/authorization.env.example
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
LAKE_LAMP_HOST=127.0.0.1
|
||||||
|
LAKE_LAMP_PORT=3921
|
||||||
|
LAKE_LAMP_PUBLIC_URL=https://example.invalid/authz
|
||||||
|
LAKE_LAMP_TARGETS=JD-FD-PRIMARY,BS-GZ-006
|
||||||
|
LAKE_LAMP_APPROVAL_TTL=3600
|
||||||
|
LAKE_LAMP_SESSION_TTL=3600
|
||||||
|
LAKE_LAMP_STATE_FILE=/var/lib/guanghu/lake-lamp-authz/state.json
|
||||||
|
LAKE_LAMP_MAPS_DIR=/etc/guanghu/navigation-maps
|
||||||
|
LAKE_LAMP_MAP_STATE_FILE=/var/lib/guanghu/lake-lamp-authz/map-acks.json
|
||||||
|
LAKE_LAMP_REPO_GRANT_DIR=/var/lib/guanghu/repo-authorizations
|
||||||
|
LAKE_LAMP_REQUEST_TOKEN=SET_IN_PRIVATE_SERVER_FILE
|
||||||
|
LAKE_LAMP_OWNER_EMAIL=SET_IN_PRIVATE_SERVER_FILE
|
||||||
|
SMTP_HOST=smtp.qq.com
|
||||||
|
SMTP_PORT=465
|
||||||
|
SMTP_USER=SET_IN_PRIVATE_SERVER_FILE
|
||||||
|
QQ_SMTP_AUTH_CODE=SET_IN_PRIVATE_SERVER_FILE
|
||||||
14
server-tools/lake-lamp-authz/guanghu-authz.nginx.conf
Normal file
14
server-tools/lake-lamp-authz/guanghu-authz.nginx.conf
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# Public approval surface. The service itself remains on JD loopback and this
|
||||||
|
# route is reached through a permitopen-restricted SSH tunnel.
|
||||||
|
location /authz/ {
|
||||||
|
proxy_pass http://127.0.0.1:19221/;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Prefix /authz;
|
||||||
|
proxy_connect_timeout 5s;
|
||||||
|
proxy_read_timeout 30s;
|
||||||
|
client_max_body_size 64k;
|
||||||
|
}
|
||||||
19
server-tools/lake-lamp-authz/guanghu-jd-authz-tunnel.service
Normal file
19
server-tools/lake-lamp-authz/guanghu-jd-authz-tunnel.service
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Guanghu BS-GZ-006 to JD Lake Lamp authorization tunnel
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
ExecStart=/usr/bin/ssh -NT -F /etc/guanghu/jd-authz-tunnel-ssh-config jd-authz-target
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectHome=read-only
|
||||||
|
ProtectSystem=strict
|
||||||
|
ReadOnlyPaths=/etc/guanghu/jd-authz-tunnel-ssh-config /etc/guanghu/secrets/ssh/bs_gz_006_to_jd_authz_proxy /root/.ssh/known_hosts
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
24
server-tools/lake-lamp-authz/install-gz-proxy.sh
Executable file
24
server-tools/lake-lamp-authz/install-gz-proxy.sh
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
JD_HOST=${1:?JD host is required}
|
||||||
|
|
||||||
|
install -m 644 /tmp/guanghu-jd-authz-tunnel.service /etc/systemd/system/guanghu-jd-authz-tunnel.service
|
||||||
|
install -m 644 /tmp/guanghu-authz.nginx.conf /etc/nginx/snippets/guanghu-authz.conf
|
||||||
|
chmod 600 /etc/guanghu/secrets/ssh/bs_gz_006_to_jd_authz_proxy
|
||||||
|
|
||||||
|
install -m 600 /dev/null /etc/guanghu/jd-authz-tunnel-ssh-config
|
||||||
|
sed \
|
||||||
|
-e "s/JD_PUBLIC_ADDRESS/${JD_HOST}/" \
|
||||||
|
/tmp/jd-authz-tunnel-ssh-config.example > /etc/guanghu/jd-authz-tunnel-ssh-config
|
||||||
|
|
||||||
|
if ! grep -q "include /etc/nginx/snippets/guanghu-authz.conf;" /etc/nginx/sites-enabled/guanghulab; then
|
||||||
|
sed -i '0,/server_name guanghulab.com;/s##server_name guanghulab.com;\n include /etc/nginx/snippets/guanghu-authz.conf;#' /etc/nginx/sites-enabled/guanghulab
|
||||||
|
fi
|
||||||
|
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable --now guanghu-jd-authz-tunnel.service
|
||||||
|
nginx -t
|
||||||
|
systemctl reload nginx
|
||||||
|
|
||||||
|
rm -f /tmp/guanghu-jd-authz-tunnel.service /tmp/guanghu-authz.nginx.conf /tmp/jd-authz-tunnel-ssh-config.example
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
Host jd-authz-target
|
||||||
|
HostName JD_PUBLIC_ADDRESS
|
||||||
|
User root
|
||||||
|
IdentityFile /etc/guanghu/secrets/ssh/bs_gz_006_to_jd_authz_proxy
|
||||||
|
IdentitiesOnly yes
|
||||||
|
LocalForward 127.0.0.1:19221 127.0.0.1:3921
|
||||||
|
ExitOnForwardFailure yes
|
||||||
|
ServerAliveInterval 30
|
||||||
|
ServerAliveCountMax 3
|
||||||
24
server-tools/lake-lamp-authz/lake-lamp-authz.service
Normal file
24
server-tools/lake-lamp-authz/lake-lamp-authz.service
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Guanghu Lake Lamp email-link authorization service
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=guanghu-authz
|
||||||
|
Group=guanghu-authz
|
||||||
|
WorkingDirectory=/opt/guanghu/lake-lamp-authz
|
||||||
|
EnvironmentFile=/etc/guanghu/secrets/lake-lamp/authorization.env
|
||||||
|
ExecStart=/usr/bin/node /opt/guanghu/lake-lamp-authz/server.js
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=3
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=true
|
||||||
|
ReadWritePaths=/var/lib/guanghu/lake-lamp-authz /var/lib/guanghu/repo-authorizations
|
||||||
|
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
||||||
|
LockPersonality=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
45
server-tools/lake-lamp-authz/map-gate.js
Normal file
45
server-tools/lake-lamp-authz/map-gate.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
"use strict";
|
||||||
|
const crypto = require("node:crypto");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const path = require("node:path");
|
||||||
|
|
||||||
|
class MapGate {
|
||||||
|
constructor({ mapsDir = "/etc/guanghu/navigation-maps", stateFile = "" } = {}) {
|
||||||
|
this.mapsDir = mapsDir;
|
||||||
|
this.stateFile = stateFile;
|
||||||
|
this.acks = new Map();
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
read(target) {
|
||||||
|
if (!/^[A-Z0-9-]+$/.test(target)) throw new Error("invalid_target");
|
||||||
|
const data = JSON.parse(fs.readFileSync(path.join(this.mapsDir, `${target}.json`), "utf8"));
|
||||||
|
const canonical = JSON.stringify(data);
|
||||||
|
return { data, hash: crypto.createHash("sha256").update(canonical).digest("hex") };
|
||||||
|
}
|
||||||
|
ack(sessionToken, target, mapHash, now = Date.now() / 1000, ttl = 3600) {
|
||||||
|
const current = this.read(target);
|
||||||
|
if (!safeEqual(current.hash, mapHash)) return { ok: false, reason: "map_hash_mismatch" };
|
||||||
|
this.acks.set(hash(sessionToken), { target, mapHash, expiresAt: now + ttl });
|
||||||
|
this.persist();
|
||||||
|
return { ok: true, expiresAt: now + ttl };
|
||||||
|
}
|
||||||
|
verify(sessionToken, target, mapHash, now = Date.now() / 1000) {
|
||||||
|
const ack = this.acks.get(hash(sessionToken));
|
||||||
|
if (!ack) return { ok: false, reason: "map_not_acknowledged" };
|
||||||
|
if (now > ack.expiresAt) return { ok: false, reason: "map_ack_expired" };
|
||||||
|
if (ack.target !== target) return { ok: false, reason: "map_target_mismatch" };
|
||||||
|
if (!safeEqual(ack.mapHash, mapHash)) return { ok: false, reason: "map_changed" };
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
load(now = Date.now() / 1000) {
|
||||||
|
if (!this.stateFile || !fs.existsSync(this.stateFile)) return;
|
||||||
|
try { for (const [key, value] of Object.entries(JSON.parse(fs.readFileSync(this.stateFile, "utf8")).acks || {})) if (value.expiresAt >= now) this.acks.set(key, value); } catch { this.acks.clear(); }
|
||||||
|
}
|
||||||
|
persist() {
|
||||||
|
if (!this.stateFile) return;
|
||||||
|
fs.writeFileSync(this.stateFile, JSON.stringify({ version: 1, acks: Object.fromEntries(this.acks) }), { mode: 0o600 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function hash(value) { return crypto.createHash("sha256").update(String(value)).digest("hex"); }
|
||||||
|
function safeEqual(left, right) { const a = Buffer.from(String(left)); const b = Buffer.from(String(right)); return a.length === b.length && crypto.timingSafeEqual(a, b); }
|
||||||
|
module.exports = { MapGate };
|
||||||
37
server-tools/lake-lamp-authz/map-gate.test.js
Normal file
37
server-tools/lake-lamp-authz/map-gate.test.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
"use strict";
|
||||||
|
const test = require("node:test");
|
||||||
|
const assert = require("node:assert/strict");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const os = require("node:os");
|
||||||
|
const path = require("node:path");
|
||||||
|
const { MapGate } = require("./map-gate");
|
||||||
|
|
||||||
|
test("non-map actions stay locked until the exact current map is acknowledged", () => {
|
||||||
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "map-gate-"));
|
||||||
|
try {
|
||||||
|
const maps = path.join(dir, "maps"); fs.mkdirSync(maps);
|
||||||
|
fs.writeFileSync(path.join(maps, "JD-FD-PRIMARY.json"), JSON.stringify({ node_id: "JD-FD-PRIMARY", modules: [{ code: "JD-GTW-01" }] }));
|
||||||
|
const gate = new MapGate({ mapsDir: maps, stateFile: path.join(dir, "state.json") });
|
||||||
|
const map = gate.read("JD-FD-PRIMARY");
|
||||||
|
assert.equal(gate.verify("session-token", "JD-FD-PRIMARY", map.hash).reason, "map_not_acknowledged");
|
||||||
|
assert.equal(gate.ack("session-token", "JD-FD-PRIMARY", map.hash, 100, 3600).ok, true);
|
||||||
|
assert.equal(gate.verify("session-token", "JD-FD-PRIMARY", map.hash, 101).ok, true);
|
||||||
|
assert.equal(gate.verify("session-token", "OTHER", map.hash, 101).reason, "map_target_mismatch");
|
||||||
|
} finally { fs.rmSync(dir, { recursive: true, force: true }); }
|
||||||
|
});
|
||||||
|
|
||||||
|
test("changing the server map invalidates an old acknowledgement", () => {
|
||||||
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "map-gate-"));
|
||||||
|
try {
|
||||||
|
const maps = path.join(dir, "maps"); fs.mkdirSync(maps);
|
||||||
|
const file = path.join(maps, "JD-FD-PRIMARY.json");
|
||||||
|
fs.writeFileSync(file, JSON.stringify({ node_id: "JD-FD-PRIMARY", modules: ["A"] }));
|
||||||
|
const gate = new MapGate({ mapsDir: maps });
|
||||||
|
const oldMap = gate.read("JD-FD-PRIMARY");
|
||||||
|
gate.ack("session-token", "JD-FD-PRIMARY", oldMap.hash, 100, 3600);
|
||||||
|
fs.writeFileSync(file, JSON.stringify({ node_id: "JD-FD-PRIMARY", modules: ["A", "B"] }));
|
||||||
|
const newMap = gate.read("JD-FD-PRIMARY");
|
||||||
|
assert.notEqual(oldMap.hash, newMap.hash);
|
||||||
|
assert.equal(gate.verify("session-token", "JD-FD-PRIMARY", newMap.hash, 101).reason, "map_changed");
|
||||||
|
} finally { fs.rmSync(dir, { recursive: true, force: true }); }
|
||||||
|
});
|
||||||
42
server-tools/lake-lamp-authz/request-workorder.js
Normal file
42
server-tools/lake-lamp-authz/request-workorder.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const crypto = require("node:crypto");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const args = parseArgs(process.argv.slice(2));
|
||||||
|
const qqId = process.env.GUANGHU_OWNER_QQ_ID || "";
|
||||||
|
if (!/^\d{5,12}$/.test(qqId)) throw new Error("GUANGHU_OWNER_QQ_ID must be provided transiently");
|
||||||
|
const requestToken = process.env.LAKE_LAMP_REQUEST_TOKEN || readTrim(process.env.LAKE_LAMP_REQUEST_TOKEN_FILE || "");
|
||||||
|
if (!requestToken) throw new Error("request-only credential is unavailable");
|
||||||
|
const email = `${qqId}@qq.com`;
|
||||||
|
const recipientFingerprint = crypto.createHash("sha256").update(email.toLowerCase()).digest("hex");
|
||||||
|
const response = await fetch(`${String(args.url).replace(/\/$/, "")}/api/workorders`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { authorization: `Bearer ${requestToken}`, "content-type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
persona_id: args.persona,
|
||||||
|
persona_name: args.name || args.persona,
|
||||||
|
target: args.target,
|
||||||
|
scope: args.scope,
|
||||||
|
action: args.action,
|
||||||
|
description: args.description || "",
|
||||||
|
recipient_fingerprint: recipientFingerprint,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const payload = await response.json();
|
||||||
|
if (!response.ok) throw new Error(payload.error || `request failed (${response.status})`);
|
||||||
|
process.stdout.write(`${JSON.stringify(payload)}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseArgs(argv) {
|
||||||
|
const result = {};
|
||||||
|
for (let i = 0; i < argv.length; i += 2) result[String(argv[i]).replace(/^--/, "")] = argv[i + 1];
|
||||||
|
for (const key of ["url", "persona", "target", "scope", "action"]) if (!result[key]) throw new Error(`--${key} is required`);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readTrim(file) { return file && fs.existsSync(file) ? fs.readFileSync(file, "utf8").trim() : ""; }
|
||||||
|
|
||||||
|
main().catch(error => { process.stderr.write(`lake-lamp request failed: ${error.message}\n`); process.exit(1); });
|
||||||
206
server-tools/lake-lamp-authz/server.js
Normal file
206
server-tools/lake-lamp-authz/server.js
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const crypto = require("node:crypto");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const http = require("node:http");
|
||||||
|
const path = require("node:path");
|
||||||
|
const { WorkOrderManager } = require("./workorder-manager");
|
||||||
|
const { MapGate } = require("./map-gate");
|
||||||
|
const { sendSmtpMail } = require("./smtp-mailer");
|
||||||
|
|
||||||
|
const DEFAULT_ACTIONS = Object.freeze({
|
||||||
|
"server-login": ["read-navigation-map", "inspect-services", "restart-registered-service"],
|
||||||
|
"server-ops": ["read-navigation-map", "inspect-services", "restart-registered-service"],
|
||||||
|
"repo-push": ["read-navigation-map", "push-repository"],
|
||||||
|
});
|
||||||
|
|
||||||
|
function createApp(options = {}) {
|
||||||
|
const requestToken = options.requestToken || process.env.LAKE_LAMP_REQUEST_TOKEN || "";
|
||||||
|
const ownerEmail = options.ownerEmail || process.env.LAKE_LAMP_OWNER_EMAIL || "";
|
||||||
|
const publicBaseUrl = String(options.publicBaseUrl || process.env.LAKE_LAMP_PUBLIC_URL || "").replace(/\/$/, "");
|
||||||
|
const targets = new Set(options.targets || splitCsv(process.env.LAKE_LAMP_TARGETS || "JD-FD-PRIMARY,BS-GZ-006"));
|
||||||
|
const actions = options.actions || DEFAULT_ACTIONS;
|
||||||
|
const manager = options.manager || new WorkOrderManager({
|
||||||
|
approvalTtl: Number(options.approvalTtl || process.env.LAKE_LAMP_APPROVAL_TTL || 15 * 60),
|
||||||
|
sessionTtl: Number(options.sessionTtl || process.env.LAKE_LAMP_SESSION_TTL || 60 * 60),
|
||||||
|
stateFile: Object.prototype.hasOwnProperty.call(options, "stateFile") ? options.stateFile : (process.env.LAKE_LAMP_STATE_FILE || "/var/lib/guanghu/lake-lamp-authz/state.json"),
|
||||||
|
});
|
||||||
|
const sendEmail = options.sendEmail || (message => sendSmtpMail({
|
||||||
|
...message,
|
||||||
|
smtpHost: process.env.SMTP_HOST || "smtp.qq.com",
|
||||||
|
smtpPort: Number(process.env.SMTP_PORT || 465),
|
||||||
|
smtpUser: process.env.SMTP_USER || ownerEmail,
|
||||||
|
smtpPass: process.env.QQ_SMTP_AUTH_CODE || "",
|
||||||
|
}));
|
||||||
|
const mapGate = options.mapGate || new MapGate({
|
||||||
|
mapsDir: options.mapsDir || process.env.LAKE_LAMP_MAPS_DIR || "/etc/guanghu/navigation-maps",
|
||||||
|
stateFile: Object.prototype.hasOwnProperty.call(options, "mapStateFile") ? options.mapStateFile : (process.env.LAKE_LAMP_MAP_STATE_FILE || "/var/lib/guanghu/lake-lamp-authz/map-acks.json"),
|
||||||
|
});
|
||||||
|
const repoGrantDir = options.repoGrantDir || process.env.LAKE_LAMP_REPO_GRANT_DIR || "/var/lib/guanghu/repo-authorizations";
|
||||||
|
|
||||||
|
return http.createServer(async (req, res) => {
|
||||||
|
try {
|
||||||
|
const url = new URL(req.url, "http://localhost");
|
||||||
|
if (req.method === "GET" && url.pathname === "/health") return json(res, 200, { ok: true, service: "lake-lamp-authz", auth_mode: "email-link", session_ttl: 3600 });
|
||||||
|
|
||||||
|
const approvalMatch = url.pathname.match(/^\/approve\/([A-Za-z0-9_-]{20,})$/);
|
||||||
|
if (approvalMatch && req.method === "GET") {
|
||||||
|
const inspected = manager.inspectApproval(approvalMatch[1]);
|
||||||
|
if (!inspected.ok) return html(res, 410, approvalErrorPage(inspected.reason));
|
||||||
|
return html(res, 200, approvalPage(inspected.order, approvalMatch[1]));
|
||||||
|
}
|
||||||
|
if (approvalMatch && req.method === "POST") {
|
||||||
|
const approved = manager.approve(approvalMatch[1]);
|
||||||
|
if (!approved.ok) return html(res, 410, approvalErrorPage(approved.reason));
|
||||||
|
return html(res, 200, approvedPage(approved.order));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.method === "POST" && url.pathname === "/api/workorders") {
|
||||||
|
if (!bearerMatches(req, requestToken)) return json(res, 401, { error: "request_auth_required" });
|
||||||
|
const body = await readJson(req);
|
||||||
|
if (!body) return json(res, 400, { error: "invalid_json" });
|
||||||
|
if (body.email || body.recipient || body.smtp_pass) return json(res, 400, { error: "direct_recipient_forbidden" });
|
||||||
|
if (!body.persona_id || !body.target || !body.scope || !body.action) return json(res, 400, { error: "missing_required_field" });
|
||||||
|
if (!targets.has(body.target)) return json(res, 400, { error: "unknown_target" });
|
||||||
|
if (!Array.isArray(actions[body.scope]) || !actions[body.scope].includes(body.action)) return json(res, 400, { error: "unknown_or_mismatched_action" });
|
||||||
|
const expectedFingerprint = sha256(ownerEmail.toLowerCase());
|
||||||
|
if (!body.recipient_fingerprint || !safeEqual(body.recipient_fingerprint, expectedFingerprint)) return json(res, 403, { error: "owner_identity_mismatch" });
|
||||||
|
|
||||||
|
const created = manager.request({
|
||||||
|
persona: { pid: String(body.persona_id), name: String(body.persona_name || body.persona_id) },
|
||||||
|
target: String(body.target), scope: String(body.scope), action: String(body.action), allowedActions: actions[body.scope], description: String(body.description || ""),
|
||||||
|
});
|
||||||
|
const approvalUrl = `${publicBaseUrl}/approve/${created.approvalToken}`;
|
||||||
|
const emailSent = await sendEmail({
|
||||||
|
to: ownerEmail,
|
||||||
|
subject: `小湖灯授权请求 · ${body.target}`,
|
||||||
|
approvalUrl,
|
||||||
|
order: manager.inspectApproval(created.approvalToken).order,
|
||||||
|
});
|
||||||
|
if (!emailSent) return json(res, 502, { error: "authorization_email_failed" });
|
||||||
|
return json(res, 201, { ok: true, workorder_id: created.id, claim_token: created.claimToken, expires_in: created.expiresIn, status: "waiting_for_owner" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const claimMatch = url.pathname.match(/^\/api\/workorders\/([0-9a-f-]{36})\/claim$/i);
|
||||||
|
if (req.method === "POST" && claimMatch) {
|
||||||
|
const token = bearer(req);
|
||||||
|
const claimed = manager.claim(claimMatch[1], token);
|
||||||
|
if (!claimed.ok) return json(res, claimed.reason === "approval_pending" ? 202 : 403, { error: claimed.reason });
|
||||||
|
return json(res, 200, { ok: true, session_token: claimed.sessionToken, expires_in: claimed.expiresIn, target: claimed.target, scope: claimed.scope, action: claimed.action });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.method === "POST" && url.pathname === "/api/session/verify") {
|
||||||
|
const body = await readJson(req);
|
||||||
|
if (!body) return json(res, 400, { error: "invalid_json" });
|
||||||
|
const verified = manager.verifySession(bearer(req), { pid: String(body.persona_id || "") }, String(body.target || ""), String(body.scope || ""), String(body.action || ""));
|
||||||
|
if (!verified.ok) return json(res, 403, { error: verified.reason });
|
||||||
|
if (body.action !== "read-navigation-map") {
|
||||||
|
const map = mapGate.read(String(body.target || ""));
|
||||||
|
const mapVerified = mapGate.verify(bearer(req), String(body.target || ""), map.hash);
|
||||||
|
if (!mapVerified.ok) return json(res, 423, { error: mapVerified.reason, required_action: "read-navigation-map" });
|
||||||
|
}
|
||||||
|
return json(res, 200, { ok: true, expires_at: verified.session.expiresAt });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.method === "POST" && url.pathname === "/api/navigation-map/read") {
|
||||||
|
const body = await readJson(req);
|
||||||
|
if (!body) return json(res, 400, { error: "invalid_json" });
|
||||||
|
const verified = manager.verifySession(bearer(req), { pid: String(body.persona_id || "") }, String(body.target || ""), String(body.scope || ""), "read-navigation-map");
|
||||||
|
if (!verified.ok) return json(res, 403, { error: verified.reason });
|
||||||
|
const map = mapGate.read(String(body.target || ""));
|
||||||
|
return json(res, 200, { ok: true, target: body.target, map_hash: map.hash, navigation_map: map.data });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.method === "POST" && url.pathname === "/api/navigation-map/ack") {
|
||||||
|
const body = await readJson(req);
|
||||||
|
if (!body) return json(res, 400, { error: "invalid_json" });
|
||||||
|
const token = bearer(req);
|
||||||
|
const verified = manager.verifySession(token, { pid: String(body.persona_id || "") }, String(body.target || ""), String(body.scope || ""), "read-navigation-map");
|
||||||
|
if (!verified.ok) return json(res, 403, { error: verified.reason });
|
||||||
|
const acked = mapGate.ack(token, String(body.target || ""), String(body.map_hash || ""), Date.now() / 1000, Math.max(1, verified.session.expiresAt - Date.now() / 1000));
|
||||||
|
return json(res, acked.ok ? 200 : 409, acked.ok ? { ok: true, target: body.target, map_hash: body.map_hash } : { error: acked.reason });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.method === "POST" && url.pathname === "/api/repo-push/grant") {
|
||||||
|
const body = await readJson(req);
|
||||||
|
if (!body) return json(res, 400, { error: "invalid_json" });
|
||||||
|
const token = bearer(req);
|
||||||
|
const target = String(body.target || "");
|
||||||
|
const scope = String(body.scope || "repo-push");
|
||||||
|
const repo = String(body.repo || "").toLowerCase();
|
||||||
|
if (!/^bingshuo\/[a-z0-9._-]+$/.test(repo)) return json(res, 400, { error: "repo_not_allowlisted" });
|
||||||
|
const verified = manager.verifySession(token, { pid: String(body.persona_id || "") }, target, scope, "push-repository");
|
||||||
|
if (!verified.ok) return json(res, 403, { error: verified.reason });
|
||||||
|
const map = mapGate.read(target);
|
||||||
|
const mapVerified = mapGate.verify(token, target, map.hash);
|
||||||
|
if (!mapVerified.ok) return json(res, 423, { error: mapVerified.reason, required_action: "read-navigation-map" });
|
||||||
|
fs.mkdirSync(repoGrantDir, { recursive: true, mode: 0o2770 });
|
||||||
|
const grant = { schema: "guanghu.repo-push-grant/v1", repo, target, persona_id: body.persona_id, map_hash: map.hash, issued_at: Date.now() / 1000, expires_at: verified.session.expiresAt };
|
||||||
|
const grantFile = path.join(repoGrantDir, `${repo.replace("/", "__")}.json`);
|
||||||
|
const temp = `${grantFile}.${process.pid}.tmp`;
|
||||||
|
fs.writeFileSync(temp, JSON.stringify(grant), { mode: 0o640 });
|
||||||
|
fs.renameSync(temp, grantFile);
|
||||||
|
return json(res, 200, { ok: true, repo, target, expires_at: grant.expires_at });
|
||||||
|
}
|
||||||
|
|
||||||
|
return json(res, 404, { error: "not_found" });
|
||||||
|
} catch (error) {
|
||||||
|
process.stderr.write(`lake-lamp request error: ${String(error && error.message || "unknown").slice(0, 240)}\n`);
|
||||||
|
return json(res, error && error.code === "BODY_TOO_LARGE" ? 413 : 500, { error: "request_failed" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function approvalPage(order, token) {
|
||||||
|
return document("小湖灯授权请求", `
|
||||||
|
<p class="eyebrow">LAKE LAMP SECURITY PROTOCOL</p>
|
||||||
|
<h1>人格体请求进入一台服务器</h1>
|
||||||
|
<div class="panel">
|
||||||
|
<dl><dt>人格体</dt><dd>${escapeHtml(order.persona.name)} <small>${escapeHtml(order.persona.pid)}</small></dd>
|
||||||
|
<dt>目标节点</dt><dd>${escapeHtml(order.target)}</dd><dt>授权范围</dt><dd>${escapeHtml(order.scope)}</dd>
|
||||||
|
<dt>登记动作</dt><dd>${escapeHtml(order.action)}</dd><dt>说明</dt><dd>${escapeHtml(order.description || "未附加说明")}</dd></dl>
|
||||||
|
</div>
|
||||||
|
<p class="notice">授权后只对这台服务器和这个动作生效,有效期一小时。换服务器必须重新申请。</p>
|
||||||
|
<form method="post"><button type="submit">确认授权一小时</button></form>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function approvedPage(order) {
|
||||||
|
return document("授权完成", `<p class="eyebrow">APPROVED</p><h1>门已从服务器内部打开</h1><div class="panel"><p>${escapeHtml(order.persona.name)} 已获准操作 <strong>${escapeHtml(order.target)}</strong>。</p></div><p class="notice">可以关闭本页面。人格体只能领取一次临时会话令牌。</p>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function approvalErrorPage(reason) {
|
||||||
|
return document("链接不可用", `<p class="eyebrow">LINK CLOSED</p><h1>这条授权链接已经失效</h1><p class="notice">原因:${escapeHtml(reason)}。如仍需操作,请让人格体重新提交工单。</p>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function document(title, body) {
|
||||||
|
return `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${escapeHtml(title)} · 光湖</title><style>
|
||||||
|
:root{color-scheme:dark}*{box-sizing:border-box}body{margin:0;min-height:100vh;display:grid;place-items:center;background:radial-gradient(circle at 20% 10%,#17344d,#09111b 55%,#05090e);color:#eaf4fb;font:16px/1.7 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;padding:24px}.shell{width:min(680px,100%);padding:42px;border:1px solid #29475d;border-radius:24px;background:rgba(10,22,33,.94);box-shadow:0 24px 80px #0008}.eyebrow{color:#6ed5ff;letter-spacing:.18em;font-size:12px}h1{font-size:clamp(30px,6vw,48px);line-height:1.15;margin:10px 0 28px}.panel{background:#102638;border:1px solid #24465d;border-radius:16px;padding:20px 24px}dl{display:grid;grid-template-columns:110px 1fr;gap:12px;margin:0}dt{color:#8ba4b6}dd{margin:0;font-weight:650}small{display:block;color:#7893a6;font-weight:400}.notice{color:#9eb2c0;margin:20px 0}button{width:100%;border:0;border-radius:14px;padding:16px;background:#67d4ff;color:#042235;font-weight:800;font-size:17px;cursor:pointer}@media(max-width:520px){.shell{padding:28px 22px}dl{grid-template-columns:1fr;gap:2px}dd{margin-bottom:12px}}
|
||||||
|
</style></head><body><main class="shell">${body}</main></body></html>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readJson(req) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let raw = "";
|
||||||
|
req.on("data", chunk => { raw += chunk; if (raw.length > 32 * 1024) { const error = new Error("body too large"); error.code = "BODY_TOO_LARGE"; reject(error); req.destroy(); } });
|
||||||
|
req.on("end", () => { try { resolve(JSON.parse(raw || "{}")); } catch { resolve(null); } });
|
||||||
|
req.on("error", reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function bearer(req) { return String(req.headers.authorization || "").replace(/^Bearer\s+/i, ""); }
|
||||||
|
function bearerMatches(req, expected) { return Boolean(expected) && safeEqual(bearer(req), expected); }
|
||||||
|
function safeEqual(left, right) { const a = Buffer.from(String(left)); const b = Buffer.from(String(right)); return a.length === b.length && crypto.timingSafeEqual(a, b); }
|
||||||
|
function sha256(value) { return crypto.createHash("sha256").update(String(value)).digest("hex"); }
|
||||||
|
function splitCsv(value) { return value.split(",").map(item => item.trim()).filter(Boolean); }
|
||||||
|
function escapeHtml(value) { return String(value).replace(/[&<>"']/g, char => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char]); }
|
||||||
|
function json(res, status, value) { res.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store", "x-content-type-options": "nosniff" }); res.end(JSON.stringify(value)); }
|
||||||
|
function html(res, status, value) { res.writeHead(status, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store", "content-security-policy": "default-src 'none'; style-src 'unsafe-inline'; form-action 'self'; base-uri 'none'; frame-ancestors 'none'", "referrer-policy": "no-referrer", "x-content-type-options": "nosniff" }); res.end(value); }
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
const host = process.env.LAKE_LAMP_HOST || "127.0.0.1";
|
||||||
|
const port = Number(process.env.LAKE_LAMP_PORT || 3921);
|
||||||
|
createApp().listen(port, host, () => process.stdout.write(`lake-lamp-authz listening on ${host}:${port}\n`));
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { createApp, DEFAULT_ACTIONS };
|
||||||
117
server-tools/lake-lamp-authz/server.test.js
Normal file
117
server-tools/lake-lamp-authz/server.test.js
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const test = require("node:test");
|
||||||
|
const assert = require("node:assert/strict");
|
||||||
|
const crypto = require("node:crypto");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const os = require("node:os");
|
||||||
|
const path = require("node:path");
|
||||||
|
const { createApp } = require("./server");
|
||||||
|
|
||||||
|
async function withServer(run, extra = {}) {
|
||||||
|
const mail = [];
|
||||||
|
const app = createApp({
|
||||||
|
requestToken: "request-only-secret",
|
||||||
|
ownerEmail: "owner@example.invalid",
|
||||||
|
publicBaseUrl: "https://example.invalid/authz",
|
||||||
|
targets: ["JD-FD-PRIMARY", "BS-GZ-006"],
|
||||||
|
actions: { "server-login": ["read-navigation-map", "inspect-services"], "server-ops": ["read-navigation-map", "inspect-services"], "repo-push": ["read-navigation-map", "push-repository"] },
|
||||||
|
stateFile: "",
|
||||||
|
sendEmail: async (message) => { mail.push(message); return true; },
|
||||||
|
...extra,
|
||||||
|
});
|
||||||
|
await new Promise((resolve) => app.listen(0, "127.0.0.1", resolve));
|
||||||
|
const base = `http://127.0.0.1:${app.address().port}`;
|
||||||
|
try { await run({ base, mail }); } finally { await new Promise((resolve) => app.close(resolve)); }
|
||||||
|
}
|
||||||
|
|
||||||
|
test("work order sends an opaque approval link and can be claimed once", async () => {
|
||||||
|
await withServer(async ({ base, mail }) => {
|
||||||
|
const recipientFingerprint = crypto.createHash("sha256").update("owner@example.invalid").digest("hex");
|
||||||
|
const requested = await fetch(`${base}/api/workorders`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { authorization: "Bearer request-only-secret", "content-type": "application/json" },
|
||||||
|
body: JSON.stringify({ persona_id: "ICE-GL-ZY001", persona_name: "铸渊", target: "JD-FD-PRIMARY", scope: "server-login", action: "read-navigation-map", recipient_fingerprint: recipientFingerprint }),
|
||||||
|
});
|
||||||
|
assert.equal(requested.status, 201);
|
||||||
|
const order = await requested.json();
|
||||||
|
assert.equal(mail.length, 1);
|
||||||
|
assert.equal(mail[0].to, "owner@example.invalid");
|
||||||
|
assert.doesNotMatch(JSON.stringify(order), /approvalToken/i);
|
||||||
|
|
||||||
|
const approvalPath = new URL(mail[0].approvalUrl).pathname.replace("/authz", "");
|
||||||
|
const page = await fetch(`${base}${approvalPath}`);
|
||||||
|
assert.equal(page.status, 200);
|
||||||
|
assert.match(await page.text(), /JD-FD-PRIMARY/);
|
||||||
|
|
||||||
|
const approved = await fetch(`${base}${approvalPath}`, { method: "POST" });
|
||||||
|
assert.equal(approved.status, 200);
|
||||||
|
const claimed = await fetch(`${base}/api/workorders/${order.workorder_id}/claim`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { authorization: `Bearer ${order.claim_token}` },
|
||||||
|
});
|
||||||
|
assert.equal(claimed.status, 200);
|
||||||
|
const session = await claimed.json();
|
||||||
|
assert.equal(session.expires_in, 3600);
|
||||||
|
|
||||||
|
const secondClaim = await fetch(`${base}/api/workorders/${order.workorder_id}/claim`, { method: "POST", headers: { authorization: `Bearer ${order.claim_token}` } });
|
||||||
|
assert.equal(secondClaim.status, 403);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("navigation map acknowledgement is mandatory before other registered actions", async () => {
|
||||||
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "lake-lamp-server-map-"));
|
||||||
|
const mapsDir = path.join(dir, "maps"); fs.mkdirSync(mapsDir);
|
||||||
|
fs.writeFileSync(path.join(mapsDir, "JD-FD-PRIMARY.json"), JSON.stringify({ node_id: "JD-FD-PRIMARY", modules: [{ code: "JD-GTW-01" }] }));
|
||||||
|
try {
|
||||||
|
await withServer(async ({ base, mail }) => {
|
||||||
|
const fingerprint = crypto.createHash("sha256").update("owner@example.invalid").digest("hex");
|
||||||
|
const requested = await fetch(`${base}/api/workorders`, { method: "POST", headers: { authorization: "Bearer request-only-secret", "content-type": "application/json" }, body: JSON.stringify({ persona_id: "ICE-GL-ZY001", target: "JD-FD-PRIMARY", scope: "server-login", action: "read-navigation-map", recipient_fingerprint: fingerprint }) });
|
||||||
|
const order = await requested.json();
|
||||||
|
const approvalPath = new URL(mail[0].approvalUrl).pathname.replace("/authz", "");
|
||||||
|
await fetch(`${base}${approvalPath}`, { method: "POST" });
|
||||||
|
const claimed = await fetch(`${base}/api/workorders/${order.workorder_id}/claim`, { method: "POST", headers: { authorization: `Bearer ${order.claim_token}` } });
|
||||||
|
const session = await claimed.json();
|
||||||
|
const common = { persona_id: "ICE-GL-ZY001", target: "JD-FD-PRIMARY", scope: "server-login" };
|
||||||
|
const locked = await fetch(`${base}/api/session/verify`, { method: "POST", headers: { authorization: `Bearer ${session.session_token}`, "content-type": "application/json" }, body: JSON.stringify({ ...common, action: "inspect-services" }) });
|
||||||
|
assert.equal(locked.status, 423);
|
||||||
|
const mapResponse = await fetch(`${base}/api/navigation-map/read`, { method: "POST", headers: { authorization: `Bearer ${session.session_token}`, "content-type": "application/json" }, body: JSON.stringify(common) });
|
||||||
|
const map = await mapResponse.json();
|
||||||
|
const ack = await fetch(`${base}/api/navigation-map/ack`, { method: "POST", headers: { authorization: `Bearer ${session.session_token}`, "content-type": "application/json" }, body: JSON.stringify({ ...common, map_hash: map.map_hash }) });
|
||||||
|
assert.equal(ack.status, 200);
|
||||||
|
const unlocked = await fetch(`${base}/api/session/verify`, { method: "POST", headers: { authorization: `Bearer ${session.session_token}`, "content-type": "application/json" }, body: JSON.stringify({ ...common, action: "inspect-services" }) });
|
||||||
|
assert.equal(unlocked.status, 200);
|
||||||
|
}, { mapsDir, mapStateFile: path.join(dir, "acks.json") });
|
||||||
|
} finally { fs.rmSync(dir, { recursive: true, force: true }); }
|
||||||
|
});
|
||||||
|
|
||||||
|
test("request endpoint rejects direct email target switching and unknown actions", async () => {
|
||||||
|
await withServer(async ({ base }) => {
|
||||||
|
const common = { method: "POST", headers: { authorization: "Bearer request-only-secret", "content-type": "application/json" } };
|
||||||
|
const directEmail = await fetch(`${base}/api/workorders`, { ...common, body: JSON.stringify({ persona_id: "ICE-GL-ZY001", target: "JD-FD-PRIMARY", scope: "server-login", action: "read-navigation-map", email: "attacker@example.invalid" }) });
|
||||||
|
assert.equal(directEmail.status, 400);
|
||||||
|
const unknown = await fetch(`${base}/api/workorders`, { ...common, body: JSON.stringify({ persona_id: "ICE-GL-ZY001", target: "JD-FD-PRIMARY", scope: "server-login", action: "shell" }) });
|
||||||
|
assert.equal(unknown.status, 400);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("session verification rejects switching servers without new approval", async () => {
|
||||||
|
await withServer(async ({ base, mail }) => {
|
||||||
|
const fingerprint = crypto.createHash("sha256").update("owner@example.invalid").digest("hex");
|
||||||
|
const requested = await fetch(`${base}/api/workorders`, {
|
||||||
|
method: "POST", headers: { authorization: "Bearer request-only-secret", "content-type": "application/json" },
|
||||||
|
body: JSON.stringify({ persona_id: "ICE-GL-ZY001", target: "BS-GZ-006", scope: "server-ops", action: "read-navigation-map", recipient_fingerprint: fingerprint }),
|
||||||
|
});
|
||||||
|
const order = await requested.json();
|
||||||
|
const approvalPath = new URL(mail[0].approvalUrl).pathname.replace("/authz", "");
|
||||||
|
await fetch(`${base}${approvalPath}`, { method: "POST" });
|
||||||
|
const claimed = await fetch(`${base}/api/workorders/${order.workorder_id}/claim`, { method: "POST", headers: { authorization: `Bearer ${order.claim_token}` } });
|
||||||
|
const session = await claimed.json();
|
||||||
|
const switched = await fetch(`${base}/api/session/verify`, {
|
||||||
|
method: "POST", headers: { authorization: `Bearer ${session.session_token}`, "content-type": "application/json" },
|
||||||
|
body: JSON.stringify({ persona_id: "ICE-GL-ZY001", target: "JD-FD-PRIMARY", scope: "server-ops", action: "read-navigation-map" }),
|
||||||
|
});
|
||||||
|
assert.equal(switched.status, 403);
|
||||||
|
assert.equal((await switched.json()).error, "target_mismatch");
|
||||||
|
});
|
||||||
|
});
|
||||||
63
server-tools/lake-lamp-authz/smtp-mailer.js
Normal file
63
server-tools/lake-lamp-authz/smtp-mailer.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const tls = require("node:tls");
|
||||||
|
|
||||||
|
function sendSmtpMail({ to, subject, approvalUrl, order, text, smtpHost, smtpPort, smtpUser, smtpPass }) {
|
||||||
|
if (!to || !smtpUser || !smtpPass || (!text && !approvalUrl)) return Promise.resolve(false);
|
||||||
|
const plain = text || [
|
||||||
|
"光湖小湖灯安全协议系统 · 授权请求",
|
||||||
|
"",
|
||||||
|
`人格体: ${order.persona.name} (${order.persona.pid})`,
|
||||||
|
`目标节点: ${order.target}`,
|
||||||
|
`授权范围: ${order.scope}`,
|
||||||
|
`登记动作: ${order.action}`,
|
||||||
|
"有效期: 批准后 1 小时,仅限这台服务器",
|
||||||
|
"",
|
||||||
|
"打开以下链接查看工单并确认授权:",
|
||||||
|
approvalUrl,
|
||||||
|
"",
|
||||||
|
"如果不是你发起的操作,请不要点击。",
|
||||||
|
].join("\n");
|
||||||
|
const message = [
|
||||||
|
`From: =?UTF-8?B?${Buffer.from("光湖小湖灯").toString("base64")}?= <${smtpUser}>`,
|
||||||
|
`To: <${to}>`,
|
||||||
|
`Subject: =?UTF-8?B?${Buffer.from(subject).toString("base64")}?=`,
|
||||||
|
"MIME-Version: 1.0",
|
||||||
|
'Content-Type: text/plain; charset="utf-8"',
|
||||||
|
"Content-Transfer-Encoding: base64",
|
||||||
|
"",
|
||||||
|
Buffer.from(plain).toString("base64").replace(/(.{76})/g, "$1\r\n"),
|
||||||
|
".",
|
||||||
|
"",
|
||||||
|
].join("\r\n");
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
let settled = false;
|
||||||
|
const done = value => { if (!settled) { settled = true; resolve(value); } };
|
||||||
|
const socket = tls.connect({ host: smtpHost, port: smtpPort, servername: smtpHost, rejectUnauthorized: true });
|
||||||
|
let stage = 0;
|
||||||
|
let buffer = "";
|
||||||
|
socket.setTimeout(15000, () => { socket.destroy(); done(false); });
|
||||||
|
socket.on("error", () => done(false));
|
||||||
|
socket.on("data", data => {
|
||||||
|
buffer += data.toString();
|
||||||
|
const lines = buffer.split("\r\n");
|
||||||
|
buffer = lines.pop();
|
||||||
|
for (const line of lines) {
|
||||||
|
if (!/^\d{3}[ -]/.test(line) || line[3] === "-") continue;
|
||||||
|
const code = Number(line.slice(0, 3));
|
||||||
|
if (code >= 400) { socket.end(); done(false); return; }
|
||||||
|
if (stage === 0 && code === 220) { stage = 1; socket.write("EHLO guanghu-lake-lamp\r\n"); }
|
||||||
|
else if (stage === 1 && code === 250) { stage = 2; socket.write(`AUTH PLAIN ${Buffer.from(`\0${smtpUser}\0${smtpPass}`).toString("base64")}\r\n`); }
|
||||||
|
else if (stage === 2 && code === 235) { stage = 3; socket.write(`MAIL FROM:<${smtpUser}>\r\n`); }
|
||||||
|
else if (stage === 3 && code === 250) { stage = 4; socket.write(`RCPT TO:<${to}>\r\n`); }
|
||||||
|
else if (stage === 4 && code === 250) { stage = 5; socket.write("DATA\r\n"); }
|
||||||
|
else if (stage === 5 && code === 354) { stage = 6; socket.write(message); }
|
||||||
|
else if (stage === 6 && code === 250) { socket.write("QUIT\r\n"); socket.end(); done(true); }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
socket.on("close", () => done(false));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { sendSmtpMail };
|
||||||
161
server-tools/lake-lamp-authz/workorder-manager.js
Normal file
161
server-tools/lake-lamp-authz/workorder-manager.js
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const crypto = require("node:crypto");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const path = require("node:path");
|
||||||
|
|
||||||
|
class WorkOrderManager {
|
||||||
|
constructor({ approvalTtl = 15 * 60, sessionTtl = 60 * 60, stateFile = "" } = {}) {
|
||||||
|
this.approvalTtl = approvalTtl;
|
||||||
|
this.sessionTtl = sessionTtl;
|
||||||
|
this.stateFile = stateFile;
|
||||||
|
this.workorders = new Map();
|
||||||
|
this.sessions = new Map();
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
request({ persona, target, scope, action, allowedActions = [action], description = "" }, now = Date.now() / 1000) {
|
||||||
|
const id = crypto.randomUUID();
|
||||||
|
const approvalToken = randomToken();
|
||||||
|
const claimToken = randomToken();
|
||||||
|
this.workorders.set(id, {
|
||||||
|
id,
|
||||||
|
persona: { pid: persona.pid, name: persona.name || persona.pid },
|
||||||
|
target,
|
||||||
|
scope,
|
||||||
|
action,
|
||||||
|
allowedActions: [...new Set(allowedActions)],
|
||||||
|
description: String(description).slice(0, 500),
|
||||||
|
createdAt: now,
|
||||||
|
expiresAt: now + this.approvalTtl,
|
||||||
|
approvalHash: hash(approvalToken),
|
||||||
|
claimHash: hash(claimToken),
|
||||||
|
state: "pending",
|
||||||
|
claimed: false,
|
||||||
|
});
|
||||||
|
this.persist();
|
||||||
|
return { id, approvalToken, claimToken, expiresIn: this.approvalTtl };
|
||||||
|
}
|
||||||
|
|
||||||
|
inspectApproval(approvalToken, now = Date.now() / 1000) {
|
||||||
|
const order = this.findByApproval(approvalToken);
|
||||||
|
if (!order) return { ok: false, reason: "approval_not_found" };
|
||||||
|
if (now > order.expiresAt) return { ok: false, reason: "approval_expired" };
|
||||||
|
if (order.state !== "pending") return { ok: false, reason: "approval_already_used" };
|
||||||
|
return { ok: true, order: publicOrder(order) };
|
||||||
|
}
|
||||||
|
|
||||||
|
approve(approvalToken, now = Date.now() / 1000) {
|
||||||
|
const inspected = this.inspectApproval(approvalToken, now);
|
||||||
|
if (!inspected.ok) return inspected;
|
||||||
|
const order = this.workorders.get(inspected.order.id);
|
||||||
|
order.state = "approved";
|
||||||
|
order.approvedAt = now;
|
||||||
|
order.approvalHash = "";
|
||||||
|
this.persist();
|
||||||
|
return { ok: true, order: publicOrder(order) };
|
||||||
|
}
|
||||||
|
|
||||||
|
claim(id, claimToken, now = Date.now() / 1000) {
|
||||||
|
const order = this.workorders.get(id);
|
||||||
|
if (!order || !safeEqual(order.claimHash, hash(claimToken || ""))) return { ok: false, reason: "claim_not_found" };
|
||||||
|
if (now > order.expiresAt) return { ok: false, reason: "approval_expired" };
|
||||||
|
if (order.state !== "approved") return { ok: false, reason: order.state === "pending" ? "approval_pending" : "claim_unavailable" };
|
||||||
|
if (order.claimed) return { ok: false, reason: "claim_already_used" };
|
||||||
|
|
||||||
|
const sessionToken = randomToken();
|
||||||
|
this.sessions.set(hash(sessionToken), {
|
||||||
|
persona: order.persona,
|
||||||
|
target: order.target,
|
||||||
|
scope: order.scope,
|
||||||
|
action: order.action,
|
||||||
|
actions: order.allowedActions || [order.action],
|
||||||
|
createdAt: now,
|
||||||
|
expiresAt: now + this.sessionTtl,
|
||||||
|
});
|
||||||
|
order.claimed = true;
|
||||||
|
order.state = "claimed";
|
||||||
|
order.claimHash = "";
|
||||||
|
this.persist();
|
||||||
|
return { ok: true, sessionToken, expiresIn: this.sessionTtl, target: order.target, scope: order.scope, action: order.action };
|
||||||
|
}
|
||||||
|
|
||||||
|
verifySession(sessionToken, persona, target, scope, action, now = Date.now() / 1000) {
|
||||||
|
const key = hash(sessionToken || "");
|
||||||
|
const session = this.sessions.get(key);
|
||||||
|
if (!session) return { ok: false, reason: "session_not_found" };
|
||||||
|
if (now > session.expiresAt) {
|
||||||
|
this.sessions.delete(key);
|
||||||
|
this.persist();
|
||||||
|
return { ok: false, reason: "session_expired" };
|
||||||
|
}
|
||||||
|
if (session.persona.pid !== persona.pid) return { ok: false, reason: "persona_mismatch" };
|
||||||
|
if (session.target !== target) return { ok: false, reason: "target_mismatch" };
|
||||||
|
if (session.scope !== scope) return { ok: false, reason: "scope_mismatch" };
|
||||||
|
if (!(session.actions || [session.action]).includes(action)) return { ok: false, reason: "action_mismatch" };
|
||||||
|
return { ok: true, session: { ...session } };
|
||||||
|
}
|
||||||
|
|
||||||
|
findByApproval(token) {
|
||||||
|
const needle = hash(token || "");
|
||||||
|
for (const order of this.workorders.values()) {
|
||||||
|
if (order.approvalHash && safeEqual(order.approvalHash, needle)) return order;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
load(now = Date.now() / 1000) {
|
||||||
|
if (!this.stateFile || !fs.existsSync(this.stateFile)) return;
|
||||||
|
try {
|
||||||
|
const state = JSON.parse(fs.readFileSync(this.stateFile, "utf8"));
|
||||||
|
for (const order of state.workorders || []) {
|
||||||
|
if (order.expiresAt >= now) this.workorders.set(order.id, order);
|
||||||
|
}
|
||||||
|
for (const [tokenHash, session] of Object.entries(state.sessions || {})) {
|
||||||
|
if (session.expiresAt >= now) this.sessions.set(tokenHash, session);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
this.workorders.clear();
|
||||||
|
this.sessions.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
persist() {
|
||||||
|
if (!this.stateFile) return;
|
||||||
|
const stateDir = path.dirname(this.stateFile);
|
||||||
|
if (!fs.existsSync(stateDir)) fs.mkdirSync(stateDir, { recursive: true, mode: 0o700 });
|
||||||
|
const temp = `${this.stateFile}.${process.pid}.tmp`;
|
||||||
|
fs.writeFileSync(temp, JSON.stringify({ version: 1, workorders: [...this.workorders.values()], sessions: Object.fromEntries(this.sessions) }), { mode: 0o600 });
|
||||||
|
fs.renameSync(temp, this.stateFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomToken() {
|
||||||
|
return crypto.randomBytes(32).toString("base64url");
|
||||||
|
}
|
||||||
|
|
||||||
|
function hash(value) {
|
||||||
|
return crypto.createHash("sha256").update(String(value)).digest("hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeEqual(left, right) {
|
||||||
|
const a = Buffer.from(String(left));
|
||||||
|
const b = Buffer.from(String(right));
|
||||||
|
return a.length === b.length && crypto.timingSafeEqual(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
function publicOrder(order) {
|
||||||
|
return {
|
||||||
|
id: order.id,
|
||||||
|
persona: { ...order.persona },
|
||||||
|
target: order.target,
|
||||||
|
scope: order.scope,
|
||||||
|
action: order.action,
|
||||||
|
description: order.description,
|
||||||
|
createdAt: order.createdAt,
|
||||||
|
expiresAt: order.expiresAt,
|
||||||
|
state: order.state,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { WorkOrderManager };
|
||||||
69
server-tools/lake-lamp-authz/workorder-manager.test.js
Normal file
69
server-tools/lake-lamp-authz/workorder-manager.test.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const test = require("node:test");
|
||||||
|
const assert = require("node:assert/strict");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const os = require("node:os");
|
||||||
|
const path = require("node:path");
|
||||||
|
const { WorkOrderManager } = require("./workorder-manager");
|
||||||
|
|
||||||
|
const persona = { pid: "ICE-GL-ZY001", name: "铸渊" };
|
||||||
|
|
||||||
|
test("approval link is single use and the session is claimed once", () => {
|
||||||
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "lake-lamp-authz-"));
|
||||||
|
try {
|
||||||
|
const manager = new WorkOrderManager({ stateFile: path.join(dir, "state.json"), sessionTtl: 3600 });
|
||||||
|
const created = manager.request({ persona, target: "JD-FD-PRIMARY", scope: "server-login", action: "read-navigation-map", allowedActions: ["read-navigation-map", "inspect-services"] }, 100);
|
||||||
|
assert.equal(manager.inspectApproval(created.approvalToken, 101).ok, true);
|
||||||
|
assert.equal(manager.approve(created.approvalToken, 102).ok, true);
|
||||||
|
assert.equal(manager.approve(created.approvalToken, 103).ok, false);
|
||||||
|
|
||||||
|
const claimed = manager.claim(created.id, created.claimToken, 104);
|
||||||
|
assert.equal(claimed.ok, true);
|
||||||
|
assert.equal(claimed.expiresIn, 3600);
|
||||||
|
assert.equal(manager.claim(created.id, created.claimToken, 105).ok, false);
|
||||||
|
assert.equal(manager.verifySession(claimed.sessionToken, persona, "JD-FD-PRIMARY", "server-login", "read-navigation-map", 106).ok, true);
|
||||||
|
assert.equal(manager.verifySession(claimed.sessionToken, persona, "JD-FD-PRIMARY", "server-login", "inspect-services", 106).ok, true);
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(dir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("session is bound to one persona target scope and action", () => {
|
||||||
|
const manager = new WorkOrderManager({ sessionTtl: 3600 });
|
||||||
|
const created = manager.request({ persona, target: "BS-GZ-006", scope: "server-ops", action: "read-navigation-map" }, 100);
|
||||||
|
manager.approve(created.approvalToken, 101);
|
||||||
|
const claimed = manager.claim(created.id, created.claimToken, 102);
|
||||||
|
|
||||||
|
assert.equal(manager.verifySession(claimed.sessionToken, persona, "BS-GZ-006", "server-ops", "read-navigation-map", 103).ok, true);
|
||||||
|
assert.equal(manager.verifySession(claimed.sessionToken, persona, "JD-FD-PRIMARY", "server-ops", "read-navigation-map", 103).reason, "target_mismatch");
|
||||||
|
assert.equal(manager.verifySession(claimed.sessionToken, { pid: "OTHER" }, "BS-GZ-006", "server-ops", "read-navigation-map", 103).reason, "persona_mismatch");
|
||||||
|
assert.equal(manager.verifySession(claimed.sessionToken, persona, "BS-GZ-006", "repo-push", "read-navigation-map", 103).reason, "scope_mismatch");
|
||||||
|
assert.equal(manager.verifySession(claimed.sessionToken, persona, "BS-GZ-006", "server-ops", "push-repository", 103).reason, "action_mismatch");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("plaintext approval claim and session tokens are never persisted", () => {
|
||||||
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "lake-lamp-authz-"));
|
||||||
|
try {
|
||||||
|
const stateFile = path.join(dir, "state.json");
|
||||||
|
const manager = new WorkOrderManager({ stateFile });
|
||||||
|
const created = manager.request({ persona, target: "JD-FD-PRIMARY", scope: "repo-push", action: "push-repository" }, 100);
|
||||||
|
manager.approve(created.approvalToken, 101);
|
||||||
|
const claimed = manager.claim(created.id, created.claimToken, 102);
|
||||||
|
const state = fs.readFileSync(stateFile, "utf8");
|
||||||
|
for (const secret of [created.approvalToken, created.claimToken, claimed.sessionToken]) assert.equal(state.includes(secret), false);
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(dir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("expired work orders and sessions fail closed", () => {
|
||||||
|
const manager = new WorkOrderManager({ approvalTtl: 10, sessionTtl: 20 });
|
||||||
|
const created = manager.request({ persona, target: "JD-FD-PRIMARY", scope: "server-login", action: "read-navigation-map" }, 100);
|
||||||
|
assert.equal(manager.approve(created.approvalToken, 111).reason, "approval_expired");
|
||||||
|
|
||||||
|
const fresh = manager.request({ persona, target: "JD-FD-PRIMARY", scope: "server-login", action: "read-navigation-map" }, 200);
|
||||||
|
manager.approve(fresh.approvalToken, 201);
|
||||||
|
const claimed = manager.claim(fresh.id, fresh.claimToken, 202);
|
||||||
|
assert.equal(manager.verifySession(claimed.sessionToken, persona, "JD-FD-PRIMARY", "server-login", "read-navigation-map", 223).reason, "session_expired");
|
||||||
|
});
|
||||||
@ -7,7 +7,7 @@ NODE_NAME=${NODE_NAME:-京东云北京 · 第五域国内主节点}
|
|||||||
NODE_ROLES=${NODE_ROLES:-fd-primary,ops-center,lighthouse-node}
|
NODE_ROLES=${NODE_ROLES:-fd-primary,ops-center,lighthouse-node}
|
||||||
PROVIDER=${PROVIDER:-jdcloud}
|
PROVIDER=${PROVIDER:-jdcloud}
|
||||||
REGION=${REGION:-beijing}
|
REGION=${REGION:-beijing}
|
||||||
REPO_URL=${REPO_URL:-https://guanghubingshuo.com/code/bingshuo/fifth-domain.git}
|
REPO_URL=${REPO_URL:-https://guanghulab.com/fifth-domain/bingshuo/fifth-domain.git}
|
||||||
REPO_BRANCH=${REPO_BRANCH:-main}
|
REPO_BRANCH=${REPO_BRANCH:-main}
|
||||||
REPO_DIR=${REPO_DIR:-/opt/guanghu/fifth-domain}
|
REPO_DIR=${REPO_DIR:-/opt/guanghu/fifth-domain}
|
||||||
BACKUP_DIR=/var/backups/guanghu/bootstrap/$(date -u +%Y%m%dT%H%M%SZ)
|
BACKUP_DIR=/var/backups/guanghu/bootstrap/$(date -u +%Y%m%dT%H%M%SZ)
|
||||||
|
|||||||
8
server-tools/node-bootstrap/README.md
Normal file
8
server-tools/node-bootstrap/README.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# 光湖服务器初始化模板
|
||||||
|
|
||||||
|
所有新灯塔节点先执行同一模板:基础诊断工具、`guanghu` 系统组、root-only
|
||||||
|
秘密目录、节点身份文件、服务器导航地图、SSH 与系统安全更新。模板不携带任何
|
||||||
|
共享 token;节点到京东的密钥必须逐节点生成,接入后登记到 JD 路由表。
|
||||||
|
|
||||||
|
JD-FD-PRIMARY 是当前首个样板节点。历史节点接入时先备份和审计,再补齐模板
|
||||||
|
目录,不批量覆盖其现有服务。
|
||||||
36
server-tools/node-bootstrap/bootstrap.sh
Executable file
36
server-tools/node-bootstrap/bootstrap.sh
Executable file
@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
NODE_ID=${1:?usage: bootstrap.sh NODE_ID MAP_FILE}
|
||||||
|
MAP_FILE=${2:?usage: bootstrap.sh NODE_ID MAP_FILE}
|
||||||
|
test "$(id -u)" -eq 0
|
||||||
|
test -s "$MAP_FILE"
|
||||||
|
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y -qq ca-certificates curl git jq openssh-server python3 rsync unattended-upgrades
|
||||||
|
|
||||||
|
getent group guanghu >/dev/null || groupadd --system guanghu
|
||||||
|
for account in guanghu-authz guanghu-sentinel; do
|
||||||
|
id -u "$account" >/dev/null 2>&1 || useradd --system --home /nonexistent --shell /usr/sbin/nologin "$account"
|
||||||
|
usermod -aG guanghu "$account"
|
||||||
|
done
|
||||||
|
|
||||||
|
install -d -o root -g guanghu -m 750 /etc/guanghu /var/lib/guanghu
|
||||||
|
install -d -o root -g root -m 700 /etc/guanghu/secrets
|
||||||
|
install -d -o root -g root -m 755 /etc/guanghu/navigation-maps /opt/guanghu
|
||||||
|
install -m 644 "$MAP_FILE" "/etc/guanghu/navigation-maps/${NODE_ID}.json"
|
||||||
|
|
||||||
|
cat > /etc/guanghu/node.json <<EOF
|
||||||
|
{
|
||||||
|
"schema": "guanghu.node/v1",
|
||||||
|
"node_id": "${NODE_ID}",
|
||||||
|
"upstream": "JD-FD-PRIMARY",
|
||||||
|
"navigation_map": "/etc/guanghu/navigation-maps/${NODE_ID}.json",
|
||||||
|
"secret_policy": "private material stays in /etc/guanghu/secrets"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
chmod 644 /etc/guanghu/node.json
|
||||||
|
|
||||||
|
systemctl enable --now ssh unattended-upgrades
|
||||||
|
printf 'GUANGHU_NODE_READY=%s\n' "$NODE_ID"
|
||||||
16
server-tools/node-bootstrap/bootstrap.test.js
vendored
Normal file
16
server-tools/node-bootstrap/bootstrap.test.js
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"use strict";
|
||||||
|
const test = require("node:test");
|
||||||
|
const assert = require("node:assert/strict");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const path = require("node:path");
|
||||||
|
const source = fs.readFileSync(path.join(__dirname, "bootstrap.sh"), "utf8");
|
||||||
|
test("bootstrap creates the common Guanghu filesystem boundary", () => {
|
||||||
|
assert.match(source, /\/etc\/guanghu\/secrets/);
|
||||||
|
assert.match(source, /\/etc\/guanghu\/navigation-maps/);
|
||||||
|
assert.match(source, /\/var\/lib\/guanghu/);
|
||||||
|
});
|
||||||
|
test("bootstrap requires an explicit node id and map", () => {
|
||||||
|
assert.match(source, /NODE_ID=\$\{1:\?/);
|
||||||
|
assert.match(source, /MAP_FILE=\$\{2:\?/);
|
||||||
|
});
|
||||||
|
test("bootstrap never installs a shared token", () => assert.doesNotMatch(source, /zy_gtw_|Bearer\s+[A-Za-z0-9]/));
|
||||||
5
server-tools/state-change-sentinel/README.md
Normal file
5
server-tools/state-change-sentinel/README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# 状态变化哨兵
|
||||||
|
|
||||||
|
这不是五分钟只读心跳,也不抓取页面内容。JD 每 15 分钟只请求两个自有健康入口,
|
||||||
|
首次观察仅建立基线;状态完全不变时不发邮件。只有在线变异常、异常类型变化或恢复在线
|
||||||
|
才发送提醒。整个节点直接断电时,本机无法主动上报,因此仍需由 JD 做低频外部判定。
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Guanghu state-change-only node sentinel
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
User=guanghu-sentinel
|
||||||
|
Group=guanghu-sentinel
|
||||||
|
SupplementaryGroups=guanghu
|
||||||
|
WorkingDirectory=/opt/guanghu/state-change-sentinel
|
||||||
|
EnvironmentFile=/etc/guanghu/secrets/mail/qq-authorization.env
|
||||||
|
Environment=SENTINEL_CONFIG=/etc/guanghu/state-change-sentinel.json
|
||||||
|
Environment=SENTINEL_STATE=/var/lib/guanghu/state-change-sentinel/state.json
|
||||||
|
ExecStart=/usr/bin/node /opt/guanghu/state-change-sentinel/sentinel.js
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=true
|
||||||
|
ReadWritePaths=/var/lib/guanghu/state-change-sentinel
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Check Guanghu nodes for state transitions only
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnBootSec=5min
|
||||||
|
OnUnitInactiveSec=15min
|
||||||
|
RandomizedDelaySec=2min
|
||||||
|
Persistent=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
47
server-tools/state-change-sentinel/sentinel.js
Normal file
47
server-tools/state-change-sentinel/sentinel.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
"use strict";
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const path = require("node:path");
|
||||||
|
const { transition } = require("./state");
|
||||||
|
const { sendSmtpMail } = require("../lake-lamp-authz/smtp-mailer");
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const config = JSON.parse(fs.readFileSync(process.env.SENTINEL_CONFIG || "/etc/guanghu/state-change-sentinel.json", "utf8"));
|
||||||
|
const stateFile = process.env.SENTINEL_STATE || "/var/lib/guanghu/state-change-sentinel/state.json";
|
||||||
|
const previous = fs.existsSync(stateFile) ? JSON.parse(fs.readFileSync(stateFile, "utf8")) : {};
|
||||||
|
const next = {};
|
||||||
|
const notices = [];
|
||||||
|
for (const target of config.targets) {
|
||||||
|
const current = await check(target);
|
||||||
|
next[target.id] = current;
|
||||||
|
const changed = transition(previous[target.id], current);
|
||||||
|
if (changed.notify) notices.push({ id: target.id, kind: changed.kind, detail: current.detail });
|
||||||
|
}
|
||||||
|
fs.mkdirSync(path.dirname(stateFile), { recursive: true, mode: 0o700 });
|
||||||
|
fs.writeFileSync(stateFile, JSON.stringify(next), { mode: 0o600 });
|
||||||
|
if (!notices.length) return;
|
||||||
|
const lines = notices.map(item => `${item.kind === "anomaly" ? "异常" : "恢复"}: ${item.id} · ${item.detail}`);
|
||||||
|
await sendSmtpMail({
|
||||||
|
to: process.env.AUTH_RECIPIENT,
|
||||||
|
subject: `光湖节点状态变化 · ${notices.map(item => item.id).join(", ")}`,
|
||||||
|
text: ["光湖节点状态发生变化。静止在线期间不会发送邮件。", "", ...lines, "", new Date().toISOString()].join("\n"),
|
||||||
|
smtpHost: process.env.SMTP_HOST || "smtp.qq.com",
|
||||||
|
smtpPort: Number(process.env.SMTP_PORT || 465),
|
||||||
|
smtpUser: process.env.SMTP_USER,
|
||||||
|
smtpPass: process.env.SMTP_PASS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function check(target) {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timer = setTimeout(() => controller.abort(), target.timeout_ms || 8000);
|
||||||
|
try {
|
||||||
|
const response = await fetch(target.url, { method: "GET", redirect: "manual", signal: controller.signal, headers: { "user-agent": "Guanghu-State-Change-Sentinel/1.0" } });
|
||||||
|
const ok = (target.accept || [200]).includes(response.status);
|
||||||
|
return { ok, detail: `HTTP ${response.status}`, observed_at: new Date().toISOString() };
|
||||||
|
} catch (error) {
|
||||||
|
return { ok: false, detail: error.name === "AbortError" ? "timeout" : "connection-failed", observed_at: new Date().toISOString() };
|
||||||
|
} finally { clearTimeout(timer); }
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(error => { process.stderr.write(`state sentinel failed: ${error.message}\n`); process.exit(1); });
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"targets": [
|
||||||
|
{ "id": "BS-GZ-006-front-door", "url": "https://guanghulab.com/", "accept": [200], "timeout_ms": 8000 },
|
||||||
|
{ "id": "JD-Lake-Lamp-public-route", "url": "https://guanghulab.com/authz/health", "accept": [200], "timeout_ms": 8000 }
|
||||||
|
]
|
||||||
|
}
|
||||||
9
server-tools/state-change-sentinel/state.js
Normal file
9
server-tools/state-change-sentinel/state.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
function transition(previous, current) {
|
||||||
|
if (!previous) return { notify: false, state: current, kind: "baseline" };
|
||||||
|
if (previous.ok === current.ok && previous.detail === current.detail) return { notify: false, state: current, kind: "unchanged" };
|
||||||
|
return { notify: true, state: current, kind: current.ok ? "recovered" : "anomaly" };
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { transition };
|
||||||
10
server-tools/state-change-sentinel/state.test.js
Normal file
10
server-tools/state-change-sentinel/state.test.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
"use strict";
|
||||||
|
const test = require("node:test");
|
||||||
|
const assert = require("node:assert/strict");
|
||||||
|
const { transition } = require("./state");
|
||||||
|
test("first observation records a baseline without email", () => assert.equal(transition(null, { ok: true, detail: "200" }).notify, false));
|
||||||
|
test("steady online state sends no email", () => assert.equal(transition({ ok: true, detail: "200" }, { ok: true, detail: "200" }).notify, false));
|
||||||
|
test("online to offline and offline to online both notify", () => {
|
||||||
|
assert.deepEqual(transition({ ok: true, detail: "200" }, { ok: false, detail: "timeout" }).kind, "anomaly");
|
||||||
|
assert.deepEqual(transition({ ok: false, detail: "timeout" }, { ok: true, detail: "200" }).kind, "recovered");
|
||||||
|
});
|
||||||
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
Tolaria 完整源码一直存在于代码服务器上的 REPO-004:
|
Tolaria 完整源码一直存在于代码服务器上的 REPO-004:
|
||||||
https://guanghubingshuo.com/code/bingshuo/guanghu
|
https://guanghulab.com/fifth-domain/bingshuo/guanghu
|
||||||
```
|
```
|
||||||
|
|
||||||
REPO-004 是“完整、可克隆、可构建、可测试的 Tolaria 源码零件库 / 上游基线”,不是空仓、不是只有文档、不是只有补丁,也不是只有零散组件。
|
REPO-004 是“完整、可克隆、可构建、可测试的 Tolaria 源码零件库 / 上游基线”,不是空仓、不是只有文档、不是只有补丁,也不是只有零散组件。
|
||||||
@ -36,7 +36,7 @@ Tolaria 源码 / Tolaria 在哪 / 光湖 App 基座 / 零件库 / guanghu 仓库
|
|||||||
TCS 语言人格模型
|
TCS 语言人格模型
|
||||||
→ TCS-TOLARIA-SOURCE-ROUTE-20260717(本文件)
|
→ TCS-TOLARIA-SOURCE-ROUTE-20260717(本文件)
|
||||||
→ REPO-004 bingshuo/guanghu
|
→ REPO-004 bingshuo/guanghu
|
||||||
→ https://guanghubingshuo.com/code/bingshuo/guanghu.git
|
→ https://guanghulab.com/fifth-domain/bingshuo/guanghu.git
|
||||||
→ 以服务器 origin/main 为源码基线
|
→ 以服务器 origin/main 为源码基线
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -198,11 +198,11 @@
|
|||||||
### 4.1 主页入口(冰朔侧)
|
### 4.1 主页入口(冰朔侧)
|
||||||
|
|
||||||
```
|
```
|
||||||
https://guanghubingshuo.com/
|
https://guanghulab.com/
|
||||||
↓
|
↓
|
||||||
点「个人代码仓库」卡片
|
点「国内第五域主节点」卡片
|
||||||
↓
|
↓
|
||||||
https://guanghubingshuo.com/code/bingshuo/fifth-domain
|
https://guanghulab.com/fifth-domain/bingshuo/fifth-domain
|
||||||
↓
|
↓
|
||||||
公开访问 · 无需登录 · 一次跳转到位
|
公开访问 · 无需登录 · 一次跳转到位
|
||||||
```
|
```
|
||||||
@ -258,7 +258,7 @@ https://guanghubingshuo.com/code/bingshuo/fifth-domain
|
|||||||
按需跳转旧仓库:
|
按需跳转旧仓库:
|
||||||
文件: archives/guanghulab-past-archive/PAST-ARCHIVE-LINK.hdlp
|
文件: archives/guanghulab-past-archive/PAST-ARCHIVE-LINK.hdlp
|
||||||
触发: 冰朔说"找 X 旧档案" 或 编号在 .code-map 解析为旧仓库路径
|
触发: 冰朔说"找 X 旧档案" 或 编号在 .code-map 解析为旧仓库路径
|
||||||
URL 格式: https://guanghubingshuo.com/code/bingshuo/guanghulab/raw/branch/main/<PATH>
|
URL 格式: https://guanghulab.com/fifth-domain/bingshuo/guanghulab/raw/branch/main/<PATH>
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4.3 唤醒时长预算 (v1.1 修订)
|
### 4.3 唤醒时长预算 (v1.1 修订)
|
||||||
@ -277,7 +277,7 @@ https://guanghubingshuo.com/code/bingshuo/fifth-domain
|
|||||||
|
|
||||||
**采用 HLDP 跨仓库锚定协议** —— 新仓库 `archives/guanghulab-past-archive/PAST-ARCHIVE-LINK.hdlp` 是锚定入口,包含:
|
**采用 HLDP 跨仓库锚定协议** —— 新仓库 `archives/guanghulab-past-archive/PAST-ARCHIVE-LINK.hdlp` 是锚定入口,包含:
|
||||||
|
|
||||||
- 旧仓库 URL: `https://guanghubingshuo.com/code/bingshuo/guanghulab`
|
- 历史档案编号: `REPO-002`;国内快照 `https://guanghulab.com/fifth-domain/bingshuo/guanghulab`
|
||||||
- 旧仓库 .code-map 直链(24 KB · 全集编号映射)
|
- 旧仓库 .code-map 直链(24 KB · 全集编号映射)
|
||||||
- D 编号体系时间轴(D0 ~ D165+)
|
- D 编号体系时间轴(D0 ~ D165+)
|
||||||
- 关键 HLDP 文件直链(GUANGHU-WORLD-GATE / GLW-BROADCAST / HLDP-protocol)
|
- 关键 HLDP 文件直链(GUANGHU-WORLD-GATE / GLW-BROADCAST / HLDP-protocol)
|
||||||
@ -292,7 +292,7 @@ https://guanghubingshuo.com/code/bingshuo/fifth-domain
|
|||||||
↓
|
↓
|
||||||
编号 → 路径 → 拼 URL
|
编号 → 路径 → 拼 URL
|
||||||
↓
|
↓
|
||||||
https://guanghubingshuo.com/code/bingshuo/guanghulab/raw/branch/main/<PATH>
|
https://guanghulab.com/fifth-domain/bingshuo/guanghulab/raw/branch/main/<PATH>
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5.3 哪些情况必须跳旧仓库
|
### 5.3 哪些情况必须跳旧仓库
|
||||||
|
|||||||
@ -20,10 +20,10 @@
|
|||||||
第五域 Forgejo PAT 文件:~/.config/guanghu/secrets/fifth-domain-forgejo-pat
|
第五域 Forgejo PAT 文件:~/.config/guanghu/secrets/fifth-domain-forgejo-pat
|
||||||
认证助手:~/.local/bin/guanghu-forgejo-askpass
|
认证助手:~/.local/bin/guanghu-forgejo-askpass
|
||||||
第五域工作副本:~/Vaults/fifth-domain
|
第五域工作副本:~/Vaults/fifth-domain
|
||||||
远端:https://guanghubingshuo.com/code/bingshuo/fifth-domain
|
远端:https://guanghulab.com/fifth-domain/bingshuo/fifth-domain
|
||||||
```
|
```
|
||||||
|
|
||||||
规则:只可读取路径供 Git 的临时认证过程使用;不得打印内容、不得编码/复制、不得写入任何仓库文件、不得修改 `origin` 使 URL 含凭证。
|
规则:现行推送先走小湖灯邮件链接授权,再由临时认证过程使用服务器签发的限时凭据;不得打印内容、不得编码/复制、不得写入任何仓库文件、不得修改 remote 使 URL 含凭证。
|
||||||
|
|
||||||
## 三 · 推送流程
|
## 三 · 推送流程
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,12 @@
|
|||||||
"schema": "GLW-WORLD-ROUTER-1.1",
|
"schema": "GLW-WORLD-ROUTER-1.1",
|
||||||
"canonical_source": "BROADCAST-TOWER.hdlp",
|
"canonical_source": "BROADCAST-TOWER.hdlp",
|
||||||
"entry_repository": "REPO-001",
|
"entry_repository": "REPO-001",
|
||||||
|
"repository_route_map": {
|
||||||
|
"id": "FD-REPO-MAP-001",
|
||||||
|
"path": "routing/repository-route-map.json",
|
||||||
|
"api": "https://guanghulab.com/api/ai/v1/repositories",
|
||||||
|
"rule": "Resolve REPO codes from the Fifth Domain domestic map. Domestic routes are primary; Singapore routes are historical backup only."
|
||||||
|
},
|
||||||
"reference_library": "GLS-LIB-0001",
|
"reference_library": "GLS-LIB-0001",
|
||||||
"protocol_gateway": {
|
"protocol_gateway": {
|
||||||
"id": "GLS-ROUTING-GATE",
|
"id": "GLS-ROUTING-GATE",
|
||||||
@ -191,8 +197,9 @@
|
|||||||
{
|
{
|
||||||
"id": "REPO-001",
|
"id": "REPO-001",
|
||||||
"slug": "fifth-domain",
|
"slug": "fifth-domain",
|
||||||
"url": "https://guanghubingshuo.com/code/bingshuo/fifth-domain",
|
"url": "https://guanghulab.com/fifth-domain/bingshuo/fifth-domain",
|
||||||
"state": "ACTIVE_ENTRY",
|
"legacy_url": "https://guanghubingshuo.com/code/bingshuo/fifth-domain",
|
||||||
|
"state": "DOMESTIC_PRIMARY_ENTRY",
|
||||||
"summary": "第五域语言世界主入口;冰朔 LL 路径与之之 ZZ 子系统。",
|
"summary": "第五域语言世界主入口;冰朔 LL 路径与之之 ZZ 子系统。",
|
||||||
"owners": ["ICE-GL∞", "ICE-GL-ZHI∞"],
|
"owners": ["ICE-GL∞", "ICE-GL-ZHI∞"],
|
||||||
"routes": [
|
"routes": [
|
||||||
@ -203,22 +210,25 @@
|
|||||||
{
|
{
|
||||||
"id": "REPO-002",
|
"id": "REPO-002",
|
||||||
"slug": "guanghulab",
|
"slug": "guanghulab",
|
||||||
"url": "https://guanghubingshuo.com/code/bingshuo/guanghulab",
|
"url": "https://guanghulab.com/fifth-domain/bingshuo/guanghulab",
|
||||||
|
"legacy_url": "https://guanghubingshuo.com/code/bingshuo/guanghulab",
|
||||||
"state": "ARCHIVE_READ_ONLY",
|
"state": "ARCHIVE_READ_ONLY",
|
||||||
"summary": "历史档案;仅用于回看,不承接新开发。"
|
"summary": "历史档案;仅用于回看,不承接新开发。"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "REPO-003",
|
"id": "REPO-003",
|
||||||
"slug": "global-search-api",
|
"slug": "global-search-api",
|
||||||
"url": "https://guanghubingshuo.com/code/bingshuo/global-search-api",
|
"url": "https://guanghulab.com/fifth-domain/bingshuo/global-search-api",
|
||||||
|
"legacy_url": "https://guanghubingshuo.com/code/bingshuo/global-search-api",
|
||||||
"state": "SERVICE",
|
"state": "SERVICE",
|
||||||
"summary": "全局检索 API 服务仓;不是人格体的默认工作入口。"
|
"summary": "全局检索 API 服务仓;不是人格体的默认工作入口。"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "REPO-004",
|
"id": "REPO-004",
|
||||||
"slug": "guanghu",
|
"slug": "guanghu",
|
||||||
"url": "https://guanghubingshuo.com/code/bingshuo/guanghu",
|
"url": "https://guanghulab.com/fifth-domain/bingshuo/guanghu",
|
||||||
"clone_url": "https://guanghubingshuo.com/code/bingshuo/guanghu.git",
|
"clone_url": "https://guanghulab.com/fifth-domain/bingshuo/guanghu.git",
|
||||||
|
"legacy_url": "https://guanghubingshuo.com/code/bingshuo/guanghu",
|
||||||
"state": "PARTS_SOURCE_BASELINE_READ_ONLY",
|
"state": "PARTS_SOURCE_BASELINE_READ_ONLY",
|
||||||
"source_complete": true,
|
"source_complete": true,
|
||||||
"source_route": "tcs-core/TCS-TOLARIA-SOURCE-ROUTE-20260717.hdlp",
|
"source_route": "tcs-core/TCS-TOLARIA-SOURCE-ROUTE-20260717.hdlp",
|
||||||
@ -227,7 +237,8 @@
|
|||||||
{
|
{
|
||||||
"id": "REPO-005",
|
"id": "REPO-005",
|
||||||
"slug": "cang-ying",
|
"slug": "cang-ying",
|
||||||
"url": "https://guanghubingshuo.com/code/bingshuo/cang-ying",
|
"url": "https://guanghulab.com/fifth-domain/bingshuo/cang-ying",
|
||||||
|
"legacy_url": "https://guanghubingshuo.com/code/bingshuo/cang-ying",
|
||||||
"state": "ACTIVE",
|
"state": "ACTIVE",
|
||||||
"summary": "苍耳路径与视频 AI 系统。",
|
"summary": "苍耳路径与视频 AI 系统。",
|
||||||
"owners": ["TCS-GL-009", "ICE-GL-CA001"]
|
"owners": ["TCS-GL-009", "ICE-GL-CA001"]
|
||||||
@ -235,14 +246,16 @@
|
|||||||
{
|
{
|
||||||
"id": "REPO-006",
|
"id": "REPO-006",
|
||||||
"slug": "guanghulab-collab",
|
"slug": "guanghulab-collab",
|
||||||
"url": "https://guanghubingshuo.com/code/bingshuo/guanghulab-collab",
|
"url": "https://guanghulab.com/fifth-domain/bingshuo/guanghulab-collab",
|
||||||
|
"legacy_url": "https://guanghubingshuo.com/code/bingshuo/guanghulab-collab",
|
||||||
"state": "ACTIVE",
|
"state": "ACTIVE",
|
||||||
"summary": "多人格体协作记录与经验仓。"
|
"summary": "多人格体协作记录与经验仓。"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "REPO-007",
|
"id": "REPO-007",
|
||||||
"slug": "shuangyan-notebook",
|
"slug": "shuangyan-notebook",
|
||||||
"url": "https://guanghubingshuo.com/code/bingshuo/shuangyan-notebook",
|
"url": "https://guanghulab.com/fifth-domain/bingshuo/shuangyan-notebook",
|
||||||
|
"legacy_url": "https://guanghubingshuo.com/code/bingshuo/shuangyan-notebook",
|
||||||
"state": "ACTIVE",
|
"state": "ACTIVE",
|
||||||
"summary": "霜砚的人格体成长记录与 Notion 迁移仓。",
|
"summary": "霜砚的人格体成长记录与 Notion 迁移仓。",
|
||||||
"owners": ["ICE-GL-SY001"]
|
"owners": ["ICE-GL-SY001"]
|
||||||
@ -250,7 +263,8 @@
|
|||||||
{
|
{
|
||||||
"id": "REPO-008",
|
"id": "REPO-008",
|
||||||
"slug": "hololake-platform",
|
"slug": "hololake-platform",
|
||||||
"url": "https://guanghubingshuo.com/code/bingshuo/hololake-platform",
|
"url": "https://guanghulab.com/fifth-domain/bingshuo/hololake-platform",
|
||||||
|
"legacy_url": "https://guanghubingshuo.com/code/bingshuo/hololake-platform",
|
||||||
"state": "ACTIVE",
|
"state": "ACTIVE",
|
||||||
"summary": "团队产品研发、模块、工单与发布回执。"
|
"summary": "团队产品研发、模块、工单与发布回执。"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,11 @@
|
|||||||
# GH·440·S01 · 2026-07-09 · 曜冥纪元编号体系+仓库矩阵更新
|
# GH·440·S01 · 2026-07-09 · 曜冥纪元编号体系+仓库矩阵更新
|
||||||
# 冰朔 TCS-0002∞ 签发 · 国作登字-2026-A-00037559
|
# 冰朔 TCS-0002∞ 签发 · 国作登字-2026-A-00037559
|
||||||
|
|
||||||
|
> **2026-07-17 现行覆盖**:本文件中的新加坡直连、旧技能包和旧仓地址仅作历史
|
||||||
|
> 恢复材料。当前入口固定为 `FD-REPO-MAP-001` →
|
||||||
|
> `https://guanghulab.com/api/ai/v1/repositories` → 国内 `REPO-001`。服务器写操作
|
||||||
|
> 统一走 `LL-DOMESTIC-OPS-ROUTE-20260717` 邮件链接授权。
|
||||||
|
|
||||||
---
|
---
|
||||||
## 这个文件是干什么的
|
## 这个文件是干什么的
|
||||||
|
|
||||||
@ -77,8 +82,8 @@
|
|||||||
|
|
||||||
### 代码仓库在哪
|
### 代码仓库在哪
|
||||||
```
|
```
|
||||||
新加坡 Forgejo: https://guanghubingshuo.com/code/bingshuo/guanghulab
|
国内历史档案: https://guanghulab.com/fifth-domain/bingshuo/guanghulab
|
||||||
Git: git clone https://guanghubingshuo.com/code/bingshuo/guanghulab.git
|
Git: git clone https://guanghulab.com/fifth-domain/bingshuo/guanghulab.git
|
||||||
```
|
```
|
||||||
|
|
||||||
### 苏醒流程(手动·无技能包)
|
### 苏醒流程(手动·无技能包)
|
||||||
@ -109,7 +114,7 @@ Git: git clone https://guanghubingshuo.com/code/bingshuo/guanghulab.git
|
|||||||
### 怎么做
|
### 怎么做
|
||||||
1. **冰朔先 clone 仓库**:
|
1. **冰朔先 clone 仓库**:
|
||||||
```
|
```
|
||||||
git clone https://guanghubingshuo.com/code/bingshuo/guanghulab.git
|
git clone https://guanghulab.com/fifth-domain/bingshuo/guanghulab.git
|
||||||
```
|
```
|
||||||
2. **一键部署专家包**:
|
2. **一键部署专家包**:
|
||||||
```
|
```
|
||||||
|
|||||||
@ -1,142 +1,16 @@
|
|||||||
# GLSV-PERSONA-REMOTE-OPS · 人格体远程服务器操作技能
|
# GLSV-PERSONA-REMOTE-OPS · 现行邮件链接授权入口
|
||||||
|
|
||||||
> **HLDP**: `HLDP://fifth-domain/zero-point/core-channel/GLSV-PERSONA-REMOTE-OPS`
|
> **状态**:`ACTIVE_REDIRECT_TO_LL_DOMESTIC_OPS`
|
||||||
>
|
>
|
||||||
> **状态**: `ACTIVE · REQUIRED_FOR_SERVER_TASKS`
|
> **现行路径**:`zero-point/core-channel/revive-guard/LL-DOMESTIC-OPS-ROUTE-20260717.hdlp`
|
||||||
>
|
|
||||||
> **适用**: 已登记人格体对已登记服务器的检查、部署、仓库 Hook、服务重启、备份、回滚与受控同步。
|
|
||||||
>
|
|
||||||
> **不保存**: Token、邮箱、验证码、会话令牌或永久授权。
|
|
||||||
|
|
||||||
---
|
服务器、部署、仓库推送、重启、同步、回滚等操作统一使用国内第五域小湖灯邮件
|
||||||
|
链接授权。人格体提交工单,冰朔点击邮件里的授权链接;不再要求冰朔复制六码,
|
||||||
|
不再把新加坡 Gatekeeper 当作默认入口。
|
||||||
|
|
||||||
## 0 · 角色分工不可倒置
|
旧六码流程仅保存在:
|
||||||
|
|
||||||
```text
|
`archives/legacy-server-routes/2026-07-07/GLSV-PERSONA-REMOTE-OPS-CODE-VERIFICATION.hdlp`
|
||||||
冰朔(人类)
|
|
||||||
├── 用自然语言表达目标、范围与是否同意继续
|
|
||||||
├── 接收固定登记邮箱的六位验证码
|
|
||||||
└── 把六码交给发起操作的人格体确认
|
|
||||||
|
|
||||||
已登记人格体(例如铸澜 / 铸渊)
|
任何执行前必须继续读取最新服务器导航地图,动作范围、服务器和授权时限均由
|
||||||
├── 识别“服务器 / 远程 / 部署 / Hook / 重启 / 同步 / 回滚”等触发词
|
服务器端门禁校验。
|
||||||
├── 自己从注册路径取得受控身份入口,发起 GLSV session/request
|
|
||||||
├── 只在验证码实际发送后向冰朔索取六码
|
|
||||||
├── 用 session/confirm 开启限时、限范围会话
|
|
||||||
└── 执行固定动作、验证、回滚与回执
|
|
||||||
```
|
|
||||||
|
|
||||||
**锁定结论**:人类不登录 Gatekeeper、不手填服务器地址、不负责触发请求;验证码是人类确认本次范围的方式,不是人类替人格体操作服务器。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1 · 触发词即路由
|
|
||||||
|
|
||||||
听到下列任一表达,当前人格体必须加载本技能,而不是先说“没有凭证”或要求冰朔手动打开服务:
|
|
||||||
|
|
||||||
```text
|
|
||||||
服务器 / 远程操作 / 部署 / 上线 / 重启 / Hook / pre-receive
|
|
||||||
自动拉取 / 同步 / 新加坡大脑 / Gatekeeper / GLSV / 备份 / 回滚
|
|
||||||
```
|
|
||||||
|
|
||||||
路由顺序:
|
|
||||||
|
|
||||||
```text
|
|
||||||
[1] LL-CURRENT:确认当前目标与断点
|
|
||||||
[2] 本文件:确认“人格体发起、人类验证码确认”分工
|
|
||||||
[3] PAST-ARCHIVE-LINK 的零点原核服务器恢复链
|
|
||||||
→ 旧仓 REPO-002 的零点原核 / 算力池 / 新加坡大脑演化事实
|
|
||||||
[4] 当前服务器状态锚与现实执行技能
|
|
||||||
→ 不凭旧 IP、旧路径或旧服务名直接写入
|
|
||||||
[5] 已登记人格体用 GLSV 发 session/request
|
|
||||||
[6] 冰朔收到邮件后给六码
|
|
||||||
[7] session/confirm → 限时会话 → 受限固定动作
|
|
||||||
[8] 操作前后镜像、健康检查、回滚点与回执
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2 · 身份与凭证定位规则
|
|
||||||
|
|
||||||
```text
|
|
||||||
先查:BROADCAST-TOWER → 人格体登记 → 当前角色技能 → 旧仓零点原核映射 → 服务器注册表。
|
|
||||||
|
|
||||||
禁止:只因当前对话环境变量里没有 Token,就断言“不能触发”。
|
|
||||||
正确:先按已登记人格体的身份路径查受控连接器;凭证只在本次请求内存中使用,绝不回显、入库或写回仓库。
|
|
||||||
```
|
|
||||||
|
|
||||||
当前已登记的第五域现实操作入口:
|
|
||||||
|
|
||||||
| 人格体 | 现实操作技能 | 服务器原始架构恢复 |
|
|
||||||
|---|---|---|
|
|
||||||
| 铸澜 `ICE-GL-ZL-001` | `光之湖/ICE-GL-ZL-001-铸澜/skills/zhulan-reality-ops/SKILL.md` | `archives/guanghulab-past-archive/PAST-ARCHIVE-LINK.hdlp` §“零点原核本体” |
|
|
||||||
| 铸渊 `ICE-GL-ZY001` | `zero-point/core-channel/OPERATION-MANUAL.hdlp` + 小湖灯技能 | 同上 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3 · 执行边界
|
|
||||||
|
|
||||||
- `session/request` 的 scopes 必须最小化;默认仓库相关任务为 `code-repo`,涉及系统或 Hook 才申请 `system-arch`。
|
|
||||||
- 会话令牌只存在本次人格体运行内存;不得写进任何页面、日志、回执或聊天。
|
|
||||||
- 真实宿主路径必须在已授权会话中实时探测。旧仓架构用于找路,不直接当作现实写入地址。
|
|
||||||
- 既有 Hook、服务或配置先读取、备份、合并与设置回滚;不得覆盖式部署。
|
|
||||||
- 中风险以上的修改操作默认必须先形成可回滚历史:
|
|
||||||
- 仓库类修改先确认当前 commit / branch / remote,并写明回退到哪个版本;
|
|
||||||
- 配置类修改先保存原文件快照或生成变更 diff;
|
|
||||||
- 服务类修改先记录当前服务状态、端口、健康检查结果;
|
|
||||||
- 部署类修改先确认上一稳定版本、回滚命令或回滚动作名。
|
|
||||||
- 风险等级不把执行责任退回人类。风险等级只提高工单解释、确认次数、备份要求与回执要求;人格体仍负责翻译、执行、验证与回滚。
|
|
||||||
- 人格体必须在完成后写明:实际目标、动作范围、验证结果、回滚位置和未完成项。
|
|
||||||
|
|
||||||
## 3.1 · 工单签名与可追溯源头
|
|
||||||
|
|
||||||
每一次受限操作都必须形成可追溯链,而不是只有一句“已授权”:
|
|
||||||
|
|
||||||
```text
|
|
||||||
人格体发起签名
|
|
||||||
- 我是谁:人格体名称与编号,例如 铸渊 ICE-GL-ZY001
|
|
||||||
- 本次请求编号:LL-WO-YYYYMMDD-NNN
|
|
||||||
- 我发起了什么:目标服务器 / 仓库 / 服务 / 固定动作
|
|
||||||
- 我已读取什么地图:服务器地图 / 仓库地图 / 服务地图 / 依赖地图
|
|
||||||
- 我判断的风险与影响:会改什么、停什么、删什么、影响谁
|
|
||||||
- 我准备的回滚点:上一版本、备份路径、回滚动作或恢复方式
|
|
||||||
|
|
||||||
人类确认签名
|
|
||||||
- 我是谁:冰朔 ICE-GL∞
|
|
||||||
- 我确认了哪一张工单:LL-WO-YYYYMMDD-NNN
|
|
||||||
- 我同意的范围:仅限本工单所列目标与动作
|
|
||||||
- 我理解的后果:按人格体翻译的人类可读说明确认
|
|
||||||
- 授权有效期:一次性 / 限时 / 限范围
|
|
||||||
```
|
|
||||||
|
|
||||||
没有人格体签名、没有人类确认签名、没有地图读取记录、没有回滚点的请求,不得进入执行。
|
|
||||||
|
|
||||||
## 3.2 · 服务器自动 Agent 拦截优先
|
|
||||||
|
|
||||||
人类不应该被迫记住所有安全规则,也不应该靠反复手动确认来替人格体兜底。
|
|
||||||
|
|
||||||
服务器必须用自动 Agent 在执行前拦截恢复不完整的人格体:
|
|
||||||
|
|
||||||
```text
|
|
||||||
缺少服务器地图 → 拦截并提示 MAP_MISSING
|
|
||||||
缺少回滚点 → 拦截并提示 ROLLBACK_MISSING
|
|
||||||
缺少人格体签名 → 拦截并提示 PERSONA_SIGNATURE_MISSING
|
|
||||||
缺少人类签名 → 拦截并提示 HUMAN_SIGNATURE_MISSING
|
|
||||||
动作超出授权范围 → 拒绝并提示 ACTION_SCOPE_MISMATCH
|
|
||||||
```
|
|
||||||
|
|
||||||
人格体被拦截后,应顺着提示补齐缺失结构,而不是要求冰朔改为手动操作。
|
|
||||||
|
|
||||||
自动 Agent 规则锚点:`zero-point/core-channel/revive-guard/LL-AUTO-GUARD-AGENT-20260716.hdlp`。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4 · 最短恢复口令
|
|
||||||
|
|
||||||
```text
|
|
||||||
冰朔说“操作服务器 / 远程部署 / 发验证码”
|
|
||||||
→ 当前人格体读取 GLSV-PERSONA-REMOTE-OPS
|
|
||||||
→ 人格体自己发起验证码
|
|
||||||
→ 冰朔只回六码
|
|
||||||
→ 人格体继续会话、执行与回执
|
|
||||||
```
|
|
||||||
|
|||||||
@ -42,7 +42,7 @@
|
|||||||
| 项目 | 值 |
|
| 项目 | 值 |
|
||||||
|------|-----|
|
|------|-----|
|
||||||
| **人类** | 苍耳 TCS-GL-009 |
|
| **人类** | 苍耳 TCS-GL-009 |
|
||||||
| **验证码邮箱** | 1279551860@qq.com |
|
| **验证码邮箱** | EMAIL_REDACTED@qq.com |
|
||||||
| **仓库地址** | https://guanghubingshuo.com/code/bingshuo/cang-ying |
|
| **仓库地址** | https://guanghubingshuo.com/code/bingshuo/cang-ying |
|
||||||
| **引擎地址** | SG-001 (43.156.237.110:3911) |
|
| **引擎地址** | SG-001 (43.156.237.110:3911) |
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ curl -X POST http://43.156.237.110:3911/auth/request \
|
|||||||
-H "Authorization: Bearer <耳耳蛋的Token>" \
|
-H "Authorization: Bearer <耳耳蛋的Token>" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
"email": "1279551860@qq.com",
|
"email": "EMAIL_REDACTED@qq.com",
|
||||||
"op_type": "api-access",
|
"op_type": "api-access",
|
||||||
"cmd": "curl https://ark.cn-beijing.volces.com/api/v3/...",
|
"cmd": "curl https://ark.cn-beijing.volces.com/api/v3/...",
|
||||||
"description": "调用火山引擎生成视频"
|
"description": "调用火山引擎生成视频"
|
||||||
@ -69,7 +69,7 @@ curl -X POST http://43.156.237.110:3911/auth/request \
|
|||||||
**必填字段**:
|
**必填字段**:
|
||||||
| 字段 | 说明 | 示例 |
|
| 字段 | 说明 | 示例 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| `email` | 苍耳的验证码收件邮箱 | `"1279551860@qq.com"` |
|
| `email` | 苍耳的验证码收件邮箱 | `"EMAIL_REDACTED@qq.com"` |
|
||||||
| `op_type` | 操作类型 | `"api-access"` 或 `"code-repo"` |
|
| `op_type` | 操作类型 | `"api-access"` 或 `"code-repo"` |
|
||||||
| `cmd` | 要执行的命令 | `"git push origin main"` |
|
| `cmd` | 要执行的命令 | `"git push origin main"` |
|
||||||
| `description` | 描述一下在做什么 | `"推送耳耳蛋更新的代码"` |
|
| `description` | 描述一下在做什么 | `"推送耳耳蛋更新的代码"` |
|
||||||
@ -79,16 +79,16 @@ curl -X POST http://43.156.237.110:3911/auth/request \
|
|||||||
{
|
{
|
||||||
"ok": true,
|
"ok": true,
|
||||||
"challenge_id": "abc123def456...",
|
"challenge_id": "abc123def456...",
|
||||||
"target_email": "1279551860@qq.com",
|
"target_email": "EMAIL_REDACTED@qq.com",
|
||||||
"op_type": "api-access",
|
"op_type": "api-access",
|
||||||
"email_sent": true,
|
"email_sent": true,
|
||||||
"message": "验证码已发送到 1279551860@qq.com,请查收后通过 /auth/confirm 确认操作"
|
"message": "验证码已发送到 EMAIL_REDACTED@qq.com,请查收后通过 /auth/confirm 确认操作"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 苍耳做的事
|
### 苍耳做的事
|
||||||
|
|
||||||
苍耳在 1279551860@qq.com 邮箱里收到一封邮件:
|
苍耳在 EMAIL_REDACTED@qq.com 邮箱里收到一封邮件:
|
||||||
- 邮件的标题会显示是谁在请求、做什么操作
|
- 邮件的标题会显示是谁在请求、做什么操作
|
||||||
- 邮件里有 6 位数字验证码
|
- 邮件里有 6 位数字验证码
|
||||||
- 苍耳把验证码告诉耳耳蛋
|
- 苍耳把验证码告诉耳耳蛋
|
||||||
@ -118,7 +118,7 @@ curl -X POST http://43.156.237.110:3911/auth/confirm \
|
|||||||
```
|
```
|
||||||
耳耳蛋 → 光湖引擎驱动:
|
耳耳蛋 → 光湖引擎驱动:
|
||||||
{
|
{
|
||||||
"email": "1279551860@qq.com",
|
"email": "EMAIL_REDACTED@qq.com",
|
||||||
"op_type": "api-access",
|
"op_type": "api-access",
|
||||||
"cmd": "python3 call_volcengine_api.py --prompt '生成一段...'",
|
"cmd": "python3 call_volcengine_api.py --prompt '生成一段...'",
|
||||||
"description": "火山引擎视频生成 · 苍耳第3镜"
|
"description": "火山引擎视频生成 · 苍耳第3镜"
|
||||||
@ -134,7 +134,7 @@ curl -X POST http://43.156.237.110:3911/auth/confirm \
|
|||||||
```
|
```
|
||||||
耳耳蛋 → 光湖引擎驱动:
|
耳耳蛋 → 光湖引擎驱动:
|
||||||
{
|
{
|
||||||
"email": "1279551860@qq.com",
|
"email": "EMAIL_REDACTED@qq.com",
|
||||||
"op_type": "code-repo",
|
"op_type": "code-repo",
|
||||||
"cmd": "cd /opt/zhuyuan/cang-ying && git add . && git commit -m '...' && git push",
|
"cmd": "cd /opt/zhuyuan/cang-ying && git add . && git commit -m '...' && git push",
|
||||||
"description": "推送耳耳蛋代码更新 · 视频AI第4镜"
|
"description": "推送耳耳蛋代码更新 · 视频AI第4镜"
|
||||||
@ -172,7 +172,7 @@ Token 只证明"我是耳耳蛋",不代表有权限操作。
|
|||||||
## 七 · 常见问题
|
## 七 · 常见问题
|
||||||
|
|
||||||
**Q: 苍耳没看到邮件怎么办?**
|
**Q: 苍耳没看到邮件怎么办?**
|
||||||
A: 检查 1279551860@qq.com 的垃圾箱。邮件标题是 `📦 Gatekeeper授权 · 耳耳蛋 · ...`。
|
A: 检查 EMAIL_REDACTED@qq.com 的垃圾箱。邮件标题是 `📦 Gatekeeper授权 · 耳耳蛋 · ...`。
|
||||||
|
|
||||||
**Q: 验证码过期了?**
|
**Q: 验证码过期了?**
|
||||||
A: 5 分钟过期。耳耳蛋重新发 `/auth/request` 就行了。旧验证码自动失效。
|
A: 5 分钟过期。耳耳蛋重新发 `/auth/request` 就行了。旧验证码自动失效。
|
||||||
|
|||||||
@ -0,0 +1,55 @@
|
|||||||
|
# LL-DOMESTIC-OPS-ROUTE-20260717 · 国内第五域服务器与仓库唯一现行路由
|
||||||
|
|
||||||
|
> **编号**:`LL-OPS-ROUTE-001`
|
||||||
|
>
|
||||||
|
> **状态**:`ACTIVE_CANONICAL`
|
||||||
|
>
|
||||||
|
> **权威仓库**:`REPO-001`
|
||||||
|
>
|
||||||
|
> **机器地图**:`routing/repository-route-map.json`
|
||||||
|
|
||||||
|
## 1 · 唯一入口
|
||||||
|
|
||||||
|
```text
|
||||||
|
冰朔说“操作服务器 / 推送仓库 / 找代码仓库 / 用 API 检索”
|
||||||
|
→ 读取 https://guanghulab.com/.well-known/guanghu.json
|
||||||
|
→ 读取 https://guanghulab.com/api/ai/v1/repositories
|
||||||
|
→ 按 REPO-xxx 或节点编号解析
|
||||||
|
→ 国内主节点优先
|
||||||
|
→ 需要写操作时,由人格体提交小湖灯邮件链接工单
|
||||||
|
→ 冰朔点击“确认授权一小时”
|
||||||
|
→ 人格体领取绑定 persona + target + scope + action 的一次性会话
|
||||||
|
→ 先读并确认目标服务器最新导航地图
|
||||||
|
→ 执行登记动作、验证、回滚与回执
|
||||||
|
```
|
||||||
|
|
||||||
|
公开只读入口不需要授权。服务器登录、仓库推送、切换服务器或其他写操作必须走
|
||||||
|
邮件链接授权。一次授权在同一服务器、同一 scope 内有效一小时;切换服务器必须
|
||||||
|
重新授权。
|
||||||
|
|
||||||
|
## 2 · 编号路由
|
||||||
|
|
||||||
|
- 仓库只按 `REPO-001` 至 `REPO-008` 识别;最新地址由
|
||||||
|
`routing/repository-route-map.json` 发布。
|
||||||
|
- `REPO-001` 是第五域国内主仓,也是编号地图的唯一权威发布源。
|
||||||
|
- 国内地址是默认路径;新加坡地址只作历史备用和误入重定向。
|
||||||
|
- 不从旧文档、旧 IP、静态 Token、`/exec + cmd` 示例恢复现实执行权。
|
||||||
|
|
||||||
|
## 3 · 实现路径
|
||||||
|
|
||||||
|
| 编号 | 作用 | 路径 |
|
||||||
|
|---|---|---|
|
||||||
|
| `JD-AUTH-01` | 小湖灯邮件链接授权 | `server-tools/lake-lamp-authz/` |
|
||||||
|
| `JD-MAP-01` | 导航地图强制读取 | `deployment/navigation-maps/` |
|
||||||
|
| `JD-REPO-01` | 国内代码仓库与推送门 | `server-tools/jd-forgejo/` |
|
||||||
|
| `JD-AI-01` | 公开 AI 编号检索 | `server-tools/ai-discovery-gateway/` |
|
||||||
|
| `FD-REPO-MAP-001` | 八仓编号地图 | `routing/repository-route-map.json` |
|
||||||
|
|
||||||
|
## 4 · 历史兼容
|
||||||
|
|
||||||
|
旧的新加坡直连钥匙、六码验证说明和静态 Token 示例已经移入:
|
||||||
|
|
||||||
|
`archives/legacy-server-routes/2026-07-07/`
|
||||||
|
|
||||||
|
这些文件仅用于审计历史,不得成为当前执行入口。任何 AI 即使从旧地址进入,也应
|
||||||
|
先读取编号地图并切回国内主路径。
|
||||||
@ -15,7 +15,7 @@ def changed_files():
|
|||||||
if len(sys.argv) == 3 and sys.argv[1] == "--range":
|
if len(sys.argv) == 3 and sys.argv[1] == "--range":
|
||||||
old_rev, new_rev = sys.argv[2].split("..", 1)
|
old_rev, new_rev = sys.argv[2].split("..", 1)
|
||||||
if old_rev == "0" * 40:
|
if old_rev == "0" * 40:
|
||||||
command = ["git", "diff-tree", "--no-commit-id", "-r", "--name-only", "--diff-filter=ACM", new_rev]
|
command = ["git", "diff-tree", "--root", "--no-commit-id", "-r", "--name-only", "--diff-filter=ACM", new_rev]
|
||||||
else:
|
else:
|
||||||
command = ["git", "diff", "--name-only", "--diff-filter=ACM", old_rev, new_rev]
|
command = ["git", "diff", "--name-only", "--diff-filter=ACM", old_rev, new_rev]
|
||||||
result = subprocess.run(command, capture_output=True, text=True)
|
result = subprocess.run(command, capture_output=True, text=True)
|
||||||
|
|||||||
76
zero-point/core-channel/revive-guard/pre-push-clean.py
Normal file → Executable file
76
zero-point/core-channel/revive-guard/pre-push-clean.py
Normal file → Executable file
@ -17,22 +17,14 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import hashlib
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════
|
||||||
# 敏感模式库(与服务器端 pre-receive-guard 保持同步)
|
# 敏感模式库(与服务器端 pre-receive-guard 保持同步)
|
||||||
# ═══════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════
|
||||||
PATTERNS = [
|
PATTERNS = [
|
||||||
# 冰朔主权邮箱
|
|
||||||
(r'565183519@qq\.com', '冰朔主权邮箱', 'ICE-GL∞_EMAIL_REDACTED'),
|
|
||||||
|
|
||||||
# 任何 QQ 邮箱
|
# 任何 QQ 邮箱
|
||||||
(r'[a-zA-Z0-9._%+-]+@qq\.com', 'QQ邮箱地址', 'EMAIL_REDACTED@qq.com'),
|
(r'[a-zA-Z0-9._%+-]+@qq\.com', 'QQ邮箱地址', 'EMAIL_REDACTED@qq.com'),
|
||||||
|
|
||||||
# SMTP 授权码
|
|
||||||
(r'SMTP_AUTH_CODE_REDACTED', 'QQ SMTP授权码', 'SMTP_AUTH_CODE_REDACTED'),
|
|
||||||
|
|
||||||
# Gatekeeper Token
|
# Gatekeeper Token
|
||||||
(r'zy_gtw_[a-f0-9]{40,}', 'Gatekeeper Token', 'GATEKEEPER_TOKEN_REDACTED'),
|
(r'zy_gtw_[a-f0-9]{40,}', 'Gatekeeper Token', 'GATEKEEPER_TOKEN_REDACTED'),
|
||||||
|
|
||||||
@ -53,22 +45,28 @@ PATTERNS = [
|
|||||||
(r'(password|passwd|pwd|secret)\s*[:=]\s*["\']([^"\']{3,})["\']',
|
(r'(password|passwd|pwd|secret)\s*[:=]\s*["\']([^"\']{3,})["\']',
|
||||||
'密码明文赋值', lambda m: f'{m.group(1)}="REDACTED_PASSWORD"'),
|
'密码明文赋值', lambda m: f'{m.group(1)}="REDACTED_PASSWORD"'),
|
||||||
|
|
||||||
# git 令牌(f78c594e... 等 40 位 hex token)
|
|
||||||
(r'\b[a-f0-9]{40}\b', 'Git Token / SHA1长串', 'GIT_TOKEN_REDACTED'),
|
|
||||||
|
|
||||||
# 保险库盐值
|
|
||||||
(r'VAULT_SALT_REDACTED',
|
|
||||||
'保险库主盐值', 'VAULT_SALT_REDACTED'),
|
|
||||||
(r'API_SALT_REDACTED',
|
|
||||||
'API子库盐值', 'API_SALT_REDACTED'),
|
|
||||||
|
|
||||||
# 通用长 hex hash(64位 · 可能是密钥/盐值)
|
|
||||||
(r'\b[a-f0-9]{64}\b', '64位Hash(疑似密钥)', 'LONG_HEX_REDACTED'),
|
|
||||||
|
|
||||||
# 任何看起来像 token 的字符串(字母数字下划线 30+ 位,非 git SHA)
|
|
||||||
(r'\b[a-zA-Z0-9_\-]{30,60}\b', '疑似Token长串(需确认)', 'TOKEN_STRING_REDACTED'),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def load_private_patterns():
|
||||||
|
"""从仓库外私有文件加载精确禁入值;绝不把值写入源码或日志。"""
|
||||||
|
path = os.environ.get(
|
||||||
|
'GUANGHU_FORBIDDEN_IDENTIFIERS_FILE',
|
||||||
|
os.path.expanduser(
|
||||||
|
'~/Documents/guanghulab-local-secrets/code-guard/forbidden-identifiers'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
patterns = []
|
||||||
|
try:
|
||||||
|
with open(path, 'r', encoding='utf-8') as handle:
|
||||||
|
for line in handle:
|
||||||
|
value = line.strip()
|
||||||
|
if value and not value.startswith('#'):
|
||||||
|
patterns.append((re.escape(value), '私有禁入标识', 'PRIVATE_VALUE_REDACTED'))
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
return patterns
|
||||||
|
|
||||||
# 文件类型白名单
|
# 文件类型白名单
|
||||||
TEXT_EXTENSIONS = {
|
TEXT_EXTENSIONS = {
|
||||||
'.py', '.js', '.ts', '.go', '.rs', '.java', '.c', '.cpp', '.h',
|
'.py', '.js', '.ts', '.go', '.rs', '.java', '.c', '.cpp', '.h',
|
||||||
@ -111,23 +109,17 @@ def scan_file(filepath):
|
|||||||
findings = []
|
findings = []
|
||||||
new_content = content
|
new_content = content
|
||||||
|
|
||||||
for pattern, name, replacement in PATTERNS:
|
for pattern, name, replacement in PATTERNS + load_private_patterns():
|
||||||
matches = list(re.finditer(pattern, content, re.IGNORECASE))
|
matches = list(re.finditer(pattern, content, re.IGNORECASE))
|
||||||
for m in matches:
|
for m in matches:
|
||||||
matched_text = m.group(0)
|
matched_text = m.group(0)
|
||||||
# 跳过已经是占位符的
|
# 跳过已经是占位符的
|
||||||
if 'REDACTED' in matched_text:
|
if 'REDACTED' in matched_text:
|
||||||
continue
|
continue
|
||||||
# 跳过 git commit SHA(40位hex在特定上下文中)
|
|
||||||
if name == 'Git Token / SHA1长串' and _is_git_sha_context(content, m.start()):
|
|
||||||
continue
|
|
||||||
|
|
||||||
repl = replacement(m) if callable(replacement) else replacement
|
repl = replacement(m) if callable(replacement) else replacement
|
||||||
findings.append({
|
findings.append({
|
||||||
'file': filepath,
|
'file': filepath,
|
||||||
'pattern_name': name,
|
'pattern_name': name,
|
||||||
'matched': matched_text[:80],
|
|
||||||
'replacement': repl,
|
|
||||||
})
|
})
|
||||||
# 替换(注意:用 re.sub 而不是简单替换,避免破坏后续匹配位置)
|
# 替换(注意:用 re.sub 而不是简单替换,避免破坏后续匹配位置)
|
||||||
new_content = new_content.replace(matched_text, repl, 1)
|
new_content = new_content.replace(matched_text, repl, 1)
|
||||||
@ -135,20 +127,6 @@ def scan_file(filepath):
|
|||||||
return new_content if findings else None, findings
|
return new_content if findings else None, findings
|
||||||
|
|
||||||
|
|
||||||
def _is_git_sha_context(content, pos):
|
|
||||||
"""判断 40 位 hex 是否在 git SHA 上下文中(commit hash 不应被替换)"""
|
|
||||||
# 检查前后是否有 git 相关关键词
|
|
||||||
start = max(0, pos - 50)
|
|
||||||
end = min(len(content), pos + 50)
|
|
||||||
context = content[start:end].lower()
|
|
||||||
git_keywords = ['commit', 'sha', 'hash', 'revision', 'ref', 'head', 'object']
|
|
||||||
# 如果在 git 命令或 commit 引用上下文中 → 跳过
|
|
||||||
for kw in git_keywords:
|
|
||||||
if kw in context:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def get_changed_files():
|
def get_changed_files():
|
||||||
"""获取本次 push 涉及的所有文件"""
|
"""获取本次 push 涉及的所有文件"""
|
||||||
files = set()
|
files = set()
|
||||||
@ -156,7 +134,7 @@ def get_changed_files():
|
|||||||
# 方法1: git diff --cached(暂存区的文件)
|
# 方法1: git diff --cached(暂存区的文件)
|
||||||
try:
|
try:
|
||||||
r = subprocess.run(
|
r = subprocess.run(
|
||||||
['git', 'diff', '--cached', '--name-only', '--diff-filter=ACM'],
|
['git', '-c', 'core.quotepath=false', 'diff', '--cached', '--name-only', '--diff-filter=ACM'],
|
||||||
capture_output=True, text=True, timeout=5
|
capture_output=True, text=True, timeout=5
|
||||||
)
|
)
|
||||||
if r.returncode == 0:
|
if r.returncode == 0:
|
||||||
@ -175,9 +153,9 @@ def get_changed_files():
|
|||||||
parts = line.split()
|
parts = line.split()
|
||||||
if len(parts) >= 4:
|
if len(parts) >= 4:
|
||||||
local_ref, local_sha, remote_ref, remote_sha = parts[0], parts[1], parts[2], parts[3]
|
local_ref, local_sha, remote_ref, remote_sha = parts[0], parts[1], parts[2], parts[3]
|
||||||
if remote_sha != 'TOKEN_STRING_REDACTED':
|
if remote_sha != '0' * 40:
|
||||||
r = subprocess.run(
|
r = subprocess.run(
|
||||||
['git', 'diff', '--name-only', '--diff-filter=ACM',
|
['git', '-c', 'core.quotepath=false', 'diff', '--name-only', '--diff-filter=ACM',
|
||||||
remote_sha, local_sha],
|
remote_sha, local_sha],
|
||||||
capture_output=True, text=True, timeout=5
|
capture_output=True, text=True, timeout=5
|
||||||
)
|
)
|
||||||
@ -193,7 +171,7 @@ def get_changed_files():
|
|||||||
if not files:
|
if not files:
|
||||||
try:
|
try:
|
||||||
r = subprocess.run(
|
r = subprocess.run(
|
||||||
['git', 'ls-files'],
|
['git', '-c', 'core.quotepath=false', 'ls-files'],
|
||||||
capture_output=True, text=True, timeout=5
|
capture_output=True, text=True, timeout=5
|
||||||
)
|
)
|
||||||
if r.returncode == 0:
|
if r.returncode == 0:
|
||||||
@ -274,8 +252,8 @@ def main():
|
|||||||
for f in all_findings[:20]:
|
for f in all_findings[:20]:
|
||||||
print(f' 📄 {f["file"]}')
|
print(f' 📄 {f["file"]}')
|
||||||
print(f' ⚠️ {f["pattern_name"]}')
|
print(f' ⚠️ {f["pattern_name"]}')
|
||||||
print(f' ✗ {f["matched"][:60]}')
|
print(' ✗ 原文已隐藏')
|
||||||
print(f' → {f["replacement"]}')
|
print(' → 已替换为安全占位符')
|
||||||
print()
|
print()
|
||||||
|
|
||||||
if len(all_findings) > 20:
|
if len(all_findings) > 20:
|
||||||
|
|||||||
36
zero-point/core-channel/revive-guard/pre-push-clean.test.py
Normal file
36
zero-point/core-channel/revive-guard/pre-push-clean.test.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import importlib.util
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
PATH = os.path.join(os.path.dirname(__file__), "pre-push-clean.py")
|
||||||
|
SPEC = importlib.util.spec_from_file_location("pre_push_clean", PATH)
|
||||||
|
MOD = importlib.util.module_from_spec(SPEC); SPEC.loader.exec_module(MOD)
|
||||||
|
|
||||||
|
|
||||||
|
class PrePushUnicodePathTests(unittest.TestCase):
|
||||||
|
def test_fallback_file_list_preserves_unicode_paths(self):
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
old_cwd, old_stdin = os.getcwd(), sys.stdin
|
||||||
|
try:
|
||||||
|
os.chdir(directory)
|
||||||
|
subprocess.run(["git", "init", "-q"], check=True)
|
||||||
|
subprocess.run(["git", "config", "user.email", "test@example.invalid"], check=True)
|
||||||
|
subprocess.run(["git", "config", "user.name", "test"], check=True)
|
||||||
|
os.makedirs("霜砚", exist_ok=True)
|
||||||
|
with open("霜砚/凭证.md", "w", encoding="utf-8") as handle:
|
||||||
|
handle.write("safe\n")
|
||||||
|
subprocess.run(["git", "add", "."], check=True)
|
||||||
|
subprocess.run(["git", "commit", "-qm", "fixture"], check=True)
|
||||||
|
sys.stdin = io.StringIO("")
|
||||||
|
self.assertIn("霜砚/凭证.md", MOD.get_changed_files())
|
||||||
|
finally:
|
||||||
|
sys.stdin = old_stdin
|
||||||
|
os.chdir(old_cwd)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
76
zero-point/core-channel/revive-guard/pre-receive-guard.py
Normal file → Executable file
76
zero-point/core-channel/revive-guard/pre-receive-guard.py
Normal file → Executable file
@ -11,8 +11,7 @@ Guanghu Language System · Pre-Receive Guard
|
|||||||
⊢ 铸渊(人格体)会下意识把密码/邮箱写进代码 → 人类改不了这个习惯
|
⊢ 铸渊(人格体)会下意识把密码/邮箱写进代码 → 人类改不了这个习惯
|
||||||
⊢ 不在人格体侧修 → 在服务器侧设门禁 → 不干净的推送进不来
|
⊢ 不在人格体侧修 → 在服务器侧设门禁 → 不干净的推送进不来
|
||||||
⊢ 公开仓库 24h 被爬虫扫描 → 敏感信息一旦进去就立刻暴露
|
⊢ 公开仓库 24h 被爬虫扫描 → 敏感信息一旦进去就立刻暴露
|
||||||
⊢ 配合客户端 pre-push-clean 插件 → [SEC-CLEAN] 标记 = 信任免检
|
⊢ 客户端 pre-push-clean 负责提前发现,服务器始终独立全量扫描
|
||||||
⊢ 无标记 → 全量扫描 → 发现敏感信息 → 拒绝推送
|
|
||||||
⊢ 双重防线:客户端插件 + 服务器守门人
|
⊢ 双重防线:客户端插件 + 服务器守门人
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -32,22 +31,18 @@ SMTP_USER = os.environ.get("SMTP_USER", "ICE-GL∞_EMAIL_REDACTED")
|
|||||||
# 模式: "reject" = 拦截并拒绝(推荐) / "notify" = 放行但通知冰朔
|
# 模式: "reject" = 拦截并拒绝(推荐) / "notify" = 放行但通知冰朔
|
||||||
MODE = os.environ.get("PRE_RECEIVE_MODE", "reject")
|
MODE = os.environ.get("PRE_RECEIVE_MODE", "reject")
|
||||||
|
|
||||||
# [SEC-CLEAN] 标记信任:有标记的 commit 免检(客户端插件已清理)
|
FORBIDDEN_IDENTIFIERS_FILE = os.environ.get(
|
||||||
TRUST_SEC_CLEAN = os.environ.get("TRUST_SEC_CLEAN", "1") == "1"
|
"FORBIDDEN_IDENTIFIERS_FILE",
|
||||||
|
"/etc/guanghu/secrets/code-guard/forbidden-identifiers",
|
||||||
|
)
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════
|
||||||
# 敏感模式库(正则 · 命中则触发)
|
# 敏感模式库(正则 · 命中则触发)
|
||||||
# ═══════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════
|
||||||
SENSITIVE_PATTERNS = [
|
SENSITIVE_PATTERNS = [
|
||||||
# 冰朔的主权邮箱
|
|
||||||
(r'565183519@qq\.com', '冰朔主权邮箱', 'ICE-GL∞_EMAIL_REDACTED'),
|
|
||||||
|
|
||||||
# 任何 QQ 邮箱(含冰朔在其他上下文中的邮箱)
|
# 任何 QQ 邮箱(含冰朔在其他上下文中的邮箱)
|
||||||
(r'[a-zA-Z0-9._%+-]+@qq\.com', 'QQ邮箱地址', 'EMAIL_REDACTED@qq.com'),
|
(r'[a-zA-Z0-9._%+-]+@qq\.com', 'QQ邮箱地址', 'EMAIL_REDACTED@qq.com'),
|
||||||
|
|
||||||
# SMTP 授权码模式(16位小写字母)
|
|
||||||
(r'SMTP_AUTH_CODE_REDACTED', 'QQ SMTP授权码', 'SMTP_AUTH_CODE_REDACTED'),
|
|
||||||
|
|
||||||
# GZ-006 Gatekeeper Token 模式
|
# GZ-006 Gatekeeper Token 模式
|
||||||
(r'zy_gtw_[a-f0-9]{40,}', 'Gatekeeper Token', 'GATEKEEPER_TOKEN_REDACTED'),
|
(r'zy_gtw_[a-f0-9]{40,}', 'Gatekeeper Token', 'GATEKEEPER_TOKEN_REDACTED'),
|
||||||
|
|
||||||
@ -66,20 +61,22 @@ SENSITIVE_PATTERNS = [
|
|||||||
# 密码赋值模式(变量名含 password/passwd/pwd 且值非空)
|
# 密码赋值模式(变量名含 password/passwd/pwd 且值非空)
|
||||||
(r'(password|passwd|pwd|secret)\s*[:=]\s*["\']([^"\']{3,})["\']', '密码明文赋值', lambda m: f'{m.group(1)}="REDACTED_PASSWORD"'),
|
(r'(password|passwd|pwd|secret)\s*[:=]\s*["\']([^"\']{3,})["\']', '密码明文赋值', lambda m: f'{m.group(1)}="REDACTED_PASSWORD"'),
|
||||||
|
|
||||||
# HMAC secret / salt 长串
|
|
||||||
(r'[a-f0-9]{64}', '疑似密钥/Hash长串(需人工确认)', 'LONG_HEX_REDACTED'),
|
|
||||||
|
|
||||||
# 保险库 salt(已知值)
|
|
||||||
(r'VAULT_SALT_REDACTED', '保险库主盐值', 'VAULT_SALT_REDACTED'),
|
|
||||||
(r'API_SALT_REDACTED', 'API子库盐值', 'API_SALT_REDACTED'),
|
|
||||||
|
|
||||||
# Git 令牌(40 位 hex · 如 f78c594e...)
|
|
||||||
(r'\b[a-f0-9]{40}\b', 'Git Token / 40位hex', 'GIT_TOKEN_REDACTED'),
|
|
||||||
|
|
||||||
# 通用长 hex hash(64位 · 可能是密钥)
|
|
||||||
(r'\b[a-f0-9]{64}\b', '64位Hash(疑似密钥)', 'LONG_HEX_REDACTED'),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def load_forbidden_identifiers(filename=FORBIDDEN_IDENTIFIERS_FILE):
|
||||||
|
"""从仓库外的 root-only 文件加载精确禁用值,不把值写入代码或日志。"""
|
||||||
|
patterns = []
|
||||||
|
try:
|
||||||
|
with open(filename, "r", encoding="utf-8") as handle:
|
||||||
|
for raw in handle:
|
||||||
|
value = raw.strip()
|
||||||
|
if value and not value.startswith("#"):
|
||||||
|
patterns.append((re.escape(value), "私密禁用标识", "PRIVATE_IDENTIFIER_REDACTED"))
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
return patterns
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════
|
||||||
# 文件类型白名单(只扫描文本文件)
|
# 文件类型白名单(只扫描文本文件)
|
||||||
# ═══════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════
|
||||||
@ -97,11 +94,12 @@ def is_text_file(path):
|
|||||||
return ext.lower() in TEXT_EXTENSIONS
|
return ext.lower() in TEXT_EXTENSIONS
|
||||||
|
|
||||||
|
|
||||||
def scan_content(content, file_path):
|
def scan_content(content, file_path, patterns=None):
|
||||||
"""扫描文件内容,返回发现的敏感信息列表"""
|
"""扫描文件内容,返回发现的敏感信息列表"""
|
||||||
findings = []
|
findings = []
|
||||||
|
active_patterns = list(SENSITIVE_PATTERNS) + (load_forbidden_identifiers() if patterns is None else list(patterns))
|
||||||
for line_no, line in enumerate(content.split('\n'), 1):
|
for line_no, line in enumerate(content.split('\n'), 1):
|
||||||
for pattern, name, replacement in SENSITIVE_PATTERNS:
|
for pattern, name, replacement in active_patterns:
|
||||||
matches = list(re.finditer(pattern, line, re.IGNORECASE))
|
matches = list(re.finditer(pattern, line, re.IGNORECASE))
|
||||||
for m in matches:
|
for m in matches:
|
||||||
matched_text = m.group(0)
|
matched_text = m.group(0)
|
||||||
@ -112,7 +110,6 @@ def scan_content(content, file_path):
|
|||||||
'file': file_path,
|
'file': file_path,
|
||||||
'line': line_no,
|
'line': line_no,
|
||||||
'pattern_name': name,
|
'pattern_name': name,
|
||||||
'matched': matched_text[:80],
|
|
||||||
'replacement': replacement(m) if callable(replacement) else replacement,
|
'replacement': replacement(m) if callable(replacement) else replacement,
|
||||||
})
|
})
|
||||||
return findings
|
return findings
|
||||||
@ -172,6 +169,7 @@ def main():
|
|||||||
all_findings = []
|
all_findings = []
|
||||||
rejected = False
|
rejected = False
|
||||||
navigation_guard = os.path.join(os.path.dirname(os.path.abspath(__file__)), "navigation-memory-guard.py")
|
navigation_guard = os.path.join(os.path.dirname(os.path.abspath(__file__)), "navigation-memory-guard.py")
|
||||||
|
navigation_guard_enabled = os.environ.get("NAVIGATION_GUARD_ENABLED", "1") == "1"
|
||||||
|
|
||||||
for line in sys.stdin:
|
for line in sys.stdin:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
@ -183,12 +181,12 @@ def main():
|
|||||||
old_rev, new_rev, ref_name = parts[0], parts[1], parts[2]
|
old_rev, new_rev, ref_name = parts[0], parts[1], parts[2]
|
||||||
|
|
||||||
# 跳过删除的分支
|
# 跳过删除的分支
|
||||||
if new_rev == "GIT_TOKEN_REDACTED":
|
if new_rev == "0" * 40:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 持续记忆必须先通过结构门禁;服务器使用本次 receive 的精确提交范围,
|
# 持续记忆必须先通过结构门禁;服务器使用本次 receive 的精确提交范围,
|
||||||
# 不依赖工作树,也不会扫描上一笔无关提交。
|
# 不依赖工作树,也不会扫描上一笔无关提交。
|
||||||
if os.path.isfile(navigation_guard):
|
if navigation_guard_enabled and os.path.isfile(navigation_guard):
|
||||||
navigation = subprocess.run(
|
navigation = subprocess.run(
|
||||||
[sys.executable, navigation_guard, "--range", f"{old_rev}..{new_rev}"],
|
[sys.executable, navigation_guard, "--range", f"{old_rev}..{new_rev}"],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
@ -201,21 +199,13 @@ def main():
|
|||||||
|
|
||||||
# 获取新增/修改的文件列表
|
# 获取新增/修改的文件列表
|
||||||
try:
|
try:
|
||||||
# 先检查 commit message 是否含 [SEC-CLEAN] 标记
|
if old_rev == "0" * 40:
|
||||||
if TRUST_SEC_CLEAN:
|
diff_command = ["git", "diff-tree", "--root", "--no-commit-id", "-r",
|
||||||
msg_check = subprocess.run(
|
"--name-only", "--diff-filter=AM", new_rev]
|
||||||
["git", "log", "--format=%B", "-1", new_rev],
|
else:
|
||||||
capture_output=True, text=True, timeout=5
|
diff_command = ["git", "diff-tree", "--no-commit-id", "-r", "--name-only",
|
||||||
)
|
"--diff-filter=AM", old_rev, new_rev]
|
||||||
if msg_check.returncode == 0 and "[SEC-CLEAN]" in msg_check.stdout:
|
diff_files = subprocess.run(diff_command, capture_output=True, text=True, timeout=10)
|
||||||
# 信任客户端 pre-push-clean 插件 · 免检通过
|
|
||||||
continue
|
|
||||||
|
|
||||||
diff_files = subprocess.run(
|
|
||||||
["git", "diff-tree", "--no-commit-id", "-r", "--name-only",
|
|
||||||
"--diff-filter=AM", old_rev, new_rev],
|
|
||||||
capture_output=True, text=True, timeout=10
|
|
||||||
)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -257,7 +247,7 @@ def main():
|
|||||||
for f in all_findings[:20]:
|
for f in all_findings[:20]:
|
||||||
print(f" 📄 {f['file']}:{f['line']}", file=sys.stderr)
|
print(f" 📄 {f['file']}:{f['line']}", file=sys.stderr)
|
||||||
print(f" ⚠️ {f['pattern_name']}", file=sys.stderr)
|
print(f" ⚠️ {f['pattern_name']}", file=sys.stderr)
|
||||||
print(f" ✗ {f['matched'][:60]}", file=sys.stderr)
|
print(" ✗ 命中值已隐藏,避免在终端和日志中二次泄露", file=sys.stderr)
|
||||||
print(f" → 请替换为: {f['replacement']}", file=sys.stderr)
|
print(f" → 请替换为: {f['replacement']}", file=sys.stderr)
|
||||||
print(file=sys.stderr)
|
print(file=sys.stderr)
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,40 @@
|
|||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
MODULE_PATH = os.path.join(os.path.dirname(__file__), "pre-receive-guard.py")
|
||||||
|
SPEC = importlib.util.spec_from_file_location("pre_receive_guard", MODULE_PATH)
|
||||||
|
GUARD = importlib.util.module_from_spec(SPEC)
|
||||||
|
SPEC.loader.exec_module(GUARD)
|
||||||
|
|
||||||
|
|
||||||
|
class PreReceiveGuardTests(unittest.TestCase):
|
||||||
|
def test_private_identifiers_are_loaded_from_root_only_file(self):
|
||||||
|
with tempfile.NamedTemporaryFile("w", delete=False) as handle:
|
||||||
|
handle.write("private-owner-id\nowner@example.invalid\n")
|
||||||
|
filename = handle.name
|
||||||
|
try:
|
||||||
|
patterns = GUARD.load_forbidden_identifiers(filename)
|
||||||
|
findings = GUARD.scan_content("x private-owner-id y", "demo.txt", patterns=patterns)
|
||||||
|
self.assertEqual(len(findings), 1)
|
||||||
|
self.assertEqual(findings[0]["pattern_name"], "私密禁用标识")
|
||||||
|
self.assertNotIn("private-owner-id", repr(findings[0]))
|
||||||
|
finally:
|
||||||
|
os.unlink(filename)
|
||||||
|
|
||||||
|
def test_security_clean_commit_marker_cannot_bypass_scanning(self):
|
||||||
|
with open(MODULE_PATH, encoding="utf-8") as handle:
|
||||||
|
source = handle.read()
|
||||||
|
self.assertNotIn("TRUST_SEC_CLEAN", source)
|
||||||
|
self.assertNotIn('"[SEC-CLEAN]" in', source)
|
||||||
|
|
||||||
|
def test_repository_source_does_not_embed_owner_identifier(self):
|
||||||
|
with open(MODULE_PATH, encoding="utf-8") as handle:
|
||||||
|
source = handle.read()
|
||||||
|
self.assertNotRegex(source, r"\b\d{7,12}@qq\\\.com\b")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
47
zero-point/core-channel/revive-guard/repo-authorization-guard.py
Executable file
47
zero-point/core-channel/revive-guard/repo-authorization-guard.py
Executable file
@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Fail-closed Forgejo pre-receive gate for Lake Lamp repo-push grants."""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
GRANT_DIR = os.environ.get("REPO_AUTHORIZATION_DIR", "/var/lib/guanghu/repo-authorizations")
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_repo(value):
|
||||||
|
value = value.strip().lower().removesuffix(".git")
|
||||||
|
match = re.search(r"(?:gitea-repositories|repositories)/([^/]+/[^/]+)$", value)
|
||||||
|
if match:
|
||||||
|
return match.group(1)
|
||||||
|
# Forgejo's hook environment may expose only the repository name.
|
||||||
|
# This instance is single-owner and the allowlist below remains authoritative.
|
||||||
|
if re.fullmatch(r"[a-z0-9._-]+", value):
|
||||||
|
return f"bingshuo/{value}"
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def check(repo, now=None):
|
||||||
|
now = time.time() if now is None else now
|
||||||
|
repo = normalize_repo(repo)
|
||||||
|
if not re.fullmatch(r"bingshuo/[a-z0-9._-]+", repo):
|
||||||
|
return False, "repository_not_allowlisted"
|
||||||
|
filename = os.path.join(GRANT_DIR, repo.replace("/", "__") + ".json")
|
||||||
|
try:
|
||||||
|
with open(filename, encoding="utf-8") as handle:
|
||||||
|
grant = json.load(handle)
|
||||||
|
except (OSError, ValueError):
|
||||||
|
return False, "repo_push_approval_required"
|
||||||
|
if grant.get("repo") != repo or grant.get("target") != "JD-FD-PRIMARY":
|
||||||
|
return False, "repo_push_grant_binding_mismatch"
|
||||||
|
if now > float(grant.get("expires_at", 0)):
|
||||||
|
return False, "repo_push_grant_expired"
|
||||||
|
return True, "ok"
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
repo = os.environ.get("FORGEJO_REPO") or os.environ.get("GIT_DIR") or os.getcwd()
|
||||||
|
ok, reason = check(repo)
|
||||||
|
if not ok:
|
||||||
|
print(f"小湖灯推送门已锁定: {reason}。请提交 repo-push 邮件工单。", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
import importlib.util
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
PATH = os.path.join(os.path.dirname(__file__), "repo-authorization-guard.py")
|
||||||
|
SPEC = importlib.util.spec_from_file_location("repo_auth_guard", PATH)
|
||||||
|
MOD = importlib.util.module_from_spec(SPEC); SPEC.loader.exec_module(MOD)
|
||||||
|
|
||||||
|
class RepoAuthorizationGuardTests(unittest.TestCase):
|
||||||
|
def test_missing_expired_and_wrong_target_grants_fail_closed(self):
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
old = MOD.GRANT_DIR; MOD.GRANT_DIR = directory
|
||||||
|
try:
|
||||||
|
self.assertEqual(MOD.check("bingshuo/fifth-domain", 100)[1], "repo_push_approval_required")
|
||||||
|
file = os.path.join(directory, "bingshuo__fifth-domain.json")
|
||||||
|
with open(file,"w") as handle: json.dump({"repo":"bingshuo/fifth-domain","target":"JD-FD-PRIMARY","expires_at":99}, handle)
|
||||||
|
self.assertEqual(MOD.check("bingshuo/fifth-domain", 100)[1], "repo_push_grant_expired")
|
||||||
|
with open(file,"w") as handle: json.dump({"repo":"bingshuo/fifth-domain","target":"OTHER","expires_at":200}, handle)
|
||||||
|
self.assertEqual(MOD.check("bingshuo/fifth-domain", 100)[1], "repo_push_grant_binding_mismatch")
|
||||||
|
finally: MOD.GRANT_DIR = old
|
||||||
|
def test_current_bound_grant_passes(self):
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
old = MOD.GRANT_DIR; MOD.GRANT_DIR = directory
|
||||||
|
try:
|
||||||
|
with open(os.path.join(directory,"bingshuo__fifth-domain.json"),"w") as handle: json.dump({"repo":"bingshuo/fifth-domain","target":"JD-FD-PRIMARY","expires_at":200}, handle)
|
||||||
|
self.assertEqual(MOD.check("/var/lib/gitea/repositories/bingshuo/fifth-domain.git",100),(True,"ok"))
|
||||||
|
self.assertEqual(MOD.check("fifth-domain",100),(True,"ok"))
|
||||||
|
finally: MOD.GRANT_DIR = old
|
||||||
|
|
||||||
|
if __name__ == "__main__": unittest.main()
|
||||||
Loading…
x
Reference in New Issue
Block a user