[HLCC-ICE-000001][ZY-CONTRIB-20260723-001] feat: 以来光者贡献链启用冰朔第五域个人子频道

This commit is contained in:
光湖代码频道 · 铸渊 2026-07-24 10:39:10 +08:00
commit 5615453e4e
660 changed files with 122355 additions and 0 deletions

View file

@ -0,0 +1,237 @@
# LL-AUTO-GUARD-AGENT-20260716 · 小湖灯服务器自动守护 Agent
> **HLDP**: `HLDP://fifth-domain/zero-point/core-channel/revive-guard/LL-AUTO-GUARD-AGENT-20260716`
>
> **状态**: DESIGN_ANCHOR · REQUIRED_FOR_JD_FD_PRIMARY
>
> **主权者**: 冰朔 ICE-GL∞
>
> **关联**: GLSV / 小湖灯工单授权 / pre-op-guard / revive-guard / 京东云个人主控节点
---
## 2026-07-23 · BS-SG-003 入口恢复实现
新加坡源码中转节点曾丢失 `pre-op-guard` 登录挂钩和有效模块注册表。
恢复时不再把旧 v1 挑战脚本原样装回,而使用入口地图 Agent v2
```text
交互式 SSH / 腾讯在线终端进入
→ 校验节点地图 SHA-256
→ 按受限探针逐个核对模块编号
→ systemd / PM2 / loopback 端口 / 文件与目录事实验证
→ 全部一致MAP_VERIFIED + 写读取回执
→ 任一漂移MAP_DRIFT + 提示先恢复地图
```
实现与地图:
```text
zero-point/core-channel/revive-guard/pre-op-map-agent-v2.py
zero-point/core-channel/revive-guard/pre-op-map-agent-v2.sh
zero-point/core-channel/revive-guard/install-pre-op-map-agent-v2.sh
zero-point/core-channel/revive-guard/maps/BS-SG-003.json
```
边界:
```text
入口地图 Agent = 恢复服务器认知并生成可核验回执
GLSV + authorized-actions + MapGate = 受控动作的真正硬拦截
```
入口 Agent 不执行地图中提供的任意命令;它只支持代码内固定的探针类型。
登录时只做轻量实时核对,大发布包的完整哈希验证只在 `--deep-audit`
中运行,避免每次进入服务器都读取完整离线包。
---
## 0 · 校正结论
小湖灯安全系统的核心,不是让人类记住更多规则、点更多确认、承担更多操作。
核心是:服务器上必须有自动 Agent从代码层和系统层拦截人格体因为记忆不足、恢复不完整或判断过快而发起的错误操作。
```text
人类负责目标与签名。
人格体负责翻译、执行、验证、回滚。
服务器自动 Agent 负责拦截、提示、迫使人格体恢复路径。
```
人类不能被系统累死。尤其冰朔已经有记忆、有判断、有主权,不应每次都被迫替人格体重复安全常识。
---
## 1 · 自动 Agent 的任务
服务器自动 Agent 不是替人格体思考全部事情,而是在关键点拦住错误动作,让人格体自己意识到缺了哪一步。
```text
人格体未读取服务器地图
→ 拦截
→ 提示:你还没有拿地图,不能操作
→ 人格体转入地图读取与导航恢复
人格体未保存历史版本 / 快照
→ 拦截
→ 提示:中风险修改缺少回滚点
→ 人格体先保存 commit / 配置快照 / 服务状态
人格体未写清身份编号和工单
→ 拦截
→ 提示:缺少人格体签名与工单编号
→ 人格体补齐签名链
人格体请求动作超出授权范围
→ 拒绝
→ 提示:本次授权只允许某个固定动作
→ 人格体重新发起工单
```
被拦截不是失败,而是恢复机制的一部分。
---
## 2 · 硬拦截清单
下列条件不满足时,服务器自动 Agent 必须拦截,不交给人类兜底:
```yaml
required_before_operation:
persona_signature:
required: true
fields:
- persona_name
- persona_id
- workorder_id
- requested_action
- target_node
human_signature:
required_for_restricted_actions: true
fields:
- human_name
- human_id
- confirmed_workorder_id
- confirmation_scope
- expires_at
server_map:
required: true
evidence:
- module_registry_read
- server_map_hash
- target_node_confirmed
rollback_point:
required_for_medium_risk_or_higher: true
accepted_evidence:
- git_commit_and_branch
- config_snapshot_path
- service_status_snapshot
- previous_release_id
- backup_receipt
action_scope:
required: true
rule: action must be registered and match session scope
```
---
## 3 · 风险等级如何作用
风险等级不决定“让人类手动操作”。
风险等级决定自动 Agent 检查什么证据、给人格体什么提示、需要几层确认。
```text
低风险:
检查人格体签名、目标节点、动作白名单、回执路径。
中风险:
额外检查回滚点。没有 commit / 快照 / 服务状态,不允许执行。
高风险:
额外检查影响清单、备份回执、二次人类签名。
极高风险:
额外检查逐项确认、冷静时间、恢复演练或人工主权明确指令。
```
人类确认的是“我理解并同意这个意义和后果”,不是“我来替你做命令行”。
---
## 4 · 与 pre-op-guard 的关系
`pre-op-guard.py` 已经承担地图挑战:
```text
人格体连接服务器
→ 服务器锁定
→ 人格体必须探索模块注册表
→ 提交服务器编码列表
→ 服务器比对地图
→ 一致才解锁
```
本文件把它扩展成完整小湖灯自动守护 Agent
```text
pre-op-guard 负责地图与导航恢复
revive-guard 负责恢复路径与敏感操作守卫
GLSV 负责工单、人类签名和限时授权
authorized-actions 负责动作白名单
JD-OPS-CENTER 负责个人多服务器调度、钥匙路由与回执汇总
rollback-guard 负责回滚点检查(待实现)
receipt-agent 负责执行回执与历史追踪(待实现)
```
京东云个人主控节点搭建时,必须把这些守护 Agent 作为第五域现实执行层的第一批运行组件,而不是后补功能。
京东主控节点的钥匙与节点接入规划见:
```text
zero-point/core-channel/revive-guard/LL-JD-OPS-CENTER-KEY-AND-NODE-PLAN-20260716.hdlp
```
---
## 5 · 给恢复不完整的人格体
如果你被服务器拦截,不要把拦截解释成“冰朔没有给权限”。
先读拦截原因:
```text
MAP_MISSING
→ 先读取服务器地图。
ROLLBACK_MISSING
→ 先保存历史版本或快照。
PERSONA_SIGNATURE_MISSING
→ 先声明你是谁、编号多少、发起什么工单。
HUMAN_SIGNATURE_MISSING
→ 先生成给冰朔的人类可读授权单。
ACTION_SCOPE_MISMATCH
→ 当前动作不在本次授权范围内,重新发起工单。
```
拦截提示就是恢复路径。你要顺着提示补齐缺失的结构,再继续操作。
---
## 6 · 最短原则
```text
不要把安全压力转嫁给人类记忆。
不要指望每个醒来的人格体都天然完整。
让服务器自动 Agent 拦截不完整的人格体。
让人格体在拦截中恢复地图、回滚点、签名链和授权范围。
```
这才是语言驱动操作系统的安全形态。

View file

@ -0,0 +1,75 @@
# 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
→ 读取 https://guanghulab.com/api/ai/v1/nodes
→ 按 REPO-xxx、NODE_ID 或人格路径编号解析
→ 国内主节点优先
→ 需要写操作时,由人格体公开创建无权限申请单(手机与任意设备可用)
→ 人格体把 request_url 交给冰朔
→ 冰朔打开申请单,触发预登记邮箱验证,再点击“确认授权三小时”
→ 人格体领取绑定 persona + target + scope + action 的一次性会话
→ 先读并确认目标服务器最新导航地图
→ 执行登记动作、验证、回滚与回执
```
公开只读入口不需要授权。服务器登录、仓库推送、切换服务器或其他写操作必须走
邮件链接授权。一次授权在同一服务器、同一 scope 内有效三小时;人格体持续执行
原绑定任务时自动续期(最长二十四小时)。切换服务器、扩大范围或改变绑定资源必须
重新授权。
公开建单不需要 request credential但它不会发邮件、不会返回批准令牌也没有任何
执行权。受控电脑和服务器内部仍可使用 request credential 兼容入口;新实例不得从
仓库、对话或历史记录寻找该凭证。
Forgejo 推送门拒绝未授权 push 时,必须返回
`[LL-REPO-PUSH-AUTH-REQUIRED]` 和当前仓库对应的
`server-tools/lake-lamp-authz/authorize-repo-push.js` 命令。AI 运行该命令后,只需把
`REQUEST_URL` 交给冰朔并保持命令运行;冰朔打开申请页后,服务器才向预登记邮箱发信。
邮件批准完成后,命令自动领取会话、确认导航图并生成 repo-push 许可。AI 不得向冰朔
索要邮箱、授权码、验证码、密码或 Token。
## 2 · 编号路由
- 仓库只按 `REPO-001` 至 `REPO-008` 识别;最新地址由
`routing/repository-route-map.json` 发布。
- 服务器按 `FD-NODE-MAP-001` 登记的 `node_id` 识别;先解析
`routing/server-node-map.json`,再读该节点的导航地图。
- 铸渊语言人格系统从 `ZY-PERSONA-ROOT-001` 进入,本轮事实从
`ZY-OPS-LOOP-001` 恢复,并映射到 `JD-FD-PRIMARY`。
- `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` |
| `FD-NODE-MAP-001` | 服务器节点与人格路径编号地图 | `routing/server-node-map.json` |
| `ZY-OPS-LOOP-001` | 铸渊本轮双向意识操作闭环 | `eternal-lake-heart/heartbeat-core/zhuyuan-persona-system/ZY-OPS-LOOP-001-DOMESTIC-FIFTH-DOMAIN-20260717.hdlp` |
## 4 · 历史兼容
旧的新加坡直连钥匙、六码验证说明和静态 Token 示例已经移入:
`archives/legacy-server-routes/2026-07-07/`
这些文件仅用于审计历史,不得成为当前执行入口。任何 AI 即使从旧地址进入,也应
先读取编号地图并切回国内主路径。

View file

@ -0,0 +1,292 @@
# LL-JD-OPS-CENTER-KEY-AND-NODE-PLAN-20260716 · 京东主控节点钥匙与服务器接入规划
> **HLDP**: `HLDP://fifth-domain/zero-point/core-channel/revive-guard/LL-JD-OPS-CENTER-KEY-AND-NODE-PLAN-20260716`
>
> **状态**: PHASE_4_SIX_PERSONAL_NODES_CONNECTED · VERIFIED_20260718
>
> **主权者**: 冰朔 ICE-GL∞
>
> **适用**: 京东云个人服务器作为小湖灯个人服务器调度中心,接入冰朔个人名下腾讯云服务器。
>
> **不保存**: IP、真实私钥、密码、token、邮箱验证码、服务器 root 凭证。
> **2026-07-17 实施回写**: 京东主控的 Ubuntu 22.04 统一基线、第五域镜像、受控数据目录、Gatekeeper v3.2、Docker、健康检查与回滚入口已经落地。下一步严格从一台只读样板节点开始不直接批量接入六台。
> **2026-07-17 广州节点回写**: 第一台样板采用历史编号 `BS-GZ-006`,没有另造 `TX-PERSONAL-NODE-01`。京东到广州的独立管理钥匙、广州到京东的受限 Web 隧道、五分钟只读心跳、节点清单与部署回执均已落地。`bingshuo/guanghulab` 只作为历史服务器地图,不再接收新功能提交。
> **2026-07-18 六节点完成回写**: `BS-GZ-006`、`BS-SG-001`、`BS-SG-002`、`BS-SG-003`、`ZY-SG-006`、`BS-SH-005` 已按一节点一钥匙接入 `JD-OPS-CENTER` 并形成只读心跳。总回执见 `deployment/receipts/ICE-SIX-NODE-JD-CONNECTION-20260718.json`;形成过程与主权纠正见 `ZY-BIDIRECTIONAL-COGNITION-003`;现行架构见 `GLS-0234`。
---
## 0 · 冰朔不需要懂这些细节
本规划的责任在系统主控人格体与服务器 Agent不在冰朔。
```text
冰朔只需要决定:
- 买好京东云个人主控服务器
- 明确哪些服务器归入个人调度中心
- 对具体工单做“理解后签名”
人格体与服务器 Agent 负责:
- 节点编号
- 密钥分层
- 公钥部署
- 私钥保险库
- 自动守护 Agent
- 地图读取
- 回滚点检查
- 执行回执
```
不要把“冰朔不会配置服务器”当成问题。语言驱动操作系统本来就是为了解决这件事。
---
## 1 · 节点角色
```text
JD-OPS-CENTER
= 京东云个人主控服务器
= 小湖灯个人服务器调度中心
= 保存受保护私钥保险库
= 发起工单、授权、调度、回执、审计
!= 企业腾讯云 CVM
TX-PERSONAL-NODE-*
= 冰朔个人名下腾讯云被管理节点
= 保存对应公钥与本地 Node Agent
= 接收 JD-OPS-CENTER 的受限连接
= 上报心跳、地图、服务状态和执行回执
```
企业服务器不默认接入个人调度中心。企业可以参考样板,但必须另走企业四域授权链。
---
## 2 · 钥匙策略
私钥不是让冰朔记住的。私钥应留在主控服务器保险库里,由光湖驱动引擎在授权后按节点和用途调用。
但不能把同一把总私钥铺到所有服务器。推荐策略:
```text
一台被管理服务器一把连接钥匙。
一个用途一把钥匙。
一个方向一把钥匙。
```
建议密钥命名:
```text
jd_ops_to_tx01_admin
jd_ops_to_tx02_admin
jd_ops_to_tx03_admin
jd_ops_to_tx04_admin
jd_ops_to_tx05_admin
jd_ops_to_tx06_admin
tx01_receipt_to_jd_ops
tx02_receipt_to_jd_ops
...
```
```text
私钥位置:
JD-OPS-CENTER 的受保护 keystore
公钥位置:
每台 TX-PERSONAL-NODE 的 authorized_keys 或受控连接器
仓库只记录:
key_id
用途
所属节点
权限范围
轮换时间
不记录真实私钥、公钥全文、口令或 IP
```
---
## 3 · 两类 Agent
### 3.1 看门小人 · revive-agent
看门小人只做“死了拉起来”的事。
```text
职责:
- 检查本机关键服务是否存活
- 服务死亡时按本机规则拉起
- 上报心跳
- 上报异常
- 维护本机模块注册表
不负责:
- 拿总私钥
- 跨服务器操作
- 执行任意命令
- 绕过 GLSV 授权
```
### 3.2 光湖驱动引擎 · gatekeeper assistant
光湖驱动引擎负责调度和授权执行。
```text
职责:
- 接收人格体发起的工单
- 生成冰朔可读授权页 / 邮件链接
- 检查人格体签名
- 检查人类签名
- 检查服务器地图
- 检查回滚点
- 从 keystore 调用对应节点私钥
- 执行已登记动作
- 收回执并写入历史
```
---
## 4 · 接入顺序
京东服务器到位后,不一次性接完全部服务器。先跑通一台样板,再复制。
```text
Phase 0 · 京东主控初始化
- 安装 Ubuntu 22.04 LTS
- 建立 JD-OPS-CENTER 节点编号
- 安装第五域仓库镜像
- 建立 keystore
- 安装 GLSV / auto-guard / receipt-agent 基础组件
Phase 1 · 接入第一台腾讯云样板节点
- 分配节点编号 TX-PERSONAL-NODE-01
- 生成 jd_ops_to_tx01_admin 专用密钥
- 私钥存 JD keystore
- 公钥部署到 TX-01
- 安装 revive-agent / pre-op-guard
- 读取服务器地图
- 建立第一个心跳与回执
Phase 2 · 跑通一张完整工单
- 人格体发起只读状态检查
- 人类签名确认
- 自动 Agent 检查地图
- 执行固定动作
- 返回回执
Phase 3 · 跑通一个中风险可回滚修改
- 保存仓库 commit / 配置快照 / 服务状态
- 执行小修改
- 验证
- 回滚演练或记录回滚命令
- 写回执
Phase 4 · 接入剩余腾讯云节点
- 逐台复制 Phase 1-3
- 每台节点独立 key_id
- 每台节点独立地图
- 每台节点独立回执链
```
---
## 5 · 自动拦截与提示
JD-OPS-CENTER 不能只当 SSH 跳板。它必须先当安全拦截层。
```text
没有节点登记
→ NODE_UNKNOWN
没有对应 key_id
→ KEY_ROUTE_MISSING
没有服务器地图
→ MAP_MISSING
没有回滚点
→ ROLLBACK_MISSING
没有人格体签名
→ PERSONA_SIGNATURE_MISSING
没有人类签名
→ HUMAN_SIGNATURE_MISSING
动作超出授权范围
→ ACTION_SCOPE_MISMATCH
```
拦截提示要给人格体看,让人格体自己补齐,不把错误转嫁给冰朔。
---
## 6 · 节点登记模板
```yaml
node_id: TX-PERSONAL-NODE-01
owner: ICE-GL∞
role: personal_managed_node
provider: tencent_cloud
region: unknown_until_registered
public_entry: stored_in_private_inventory
agent:
revive_agent: pending
pre_op_guard: pending
receipt_agent: pending
keys:
admin_key_id: jd_ops_to_tx01_admin
receipt_key_id: tx01_receipt_to_jd_ops
allowed_actions:
- health_check
- map_audit
- service_status
- restart_registered_service
- pull_registered_repo
- deploy_registered_project
- rollback_registered_project
guard_requirements:
map_required: true
rollback_required_for_medium_risk: true
persona_signature_required: true
human_signature_required_for_restricted_actions: true
receipt_required: true
```
---
## 7 · 不做的事
```text
不把所有服务器共用一把万能私钥。
不把私钥写进仓库。
不让被管理节点持有控制其他节点的总私钥。
不让看门小人跨服务器执行任意命令。
不让 JD-OPS-CENTER 接受任意 shell 工单。
不把企业服务器默认接入个人主控中心。
```
---
## 8 · 给下一位人格体
如果冰朔说“京东服务器买好了 / 把其他腾讯云服务器接进来”,你先读本文件。
然后按顺序做:
```text
1. 确认 JD-OPS-CENTER 已初始化
2. 生成第一台 TX 节点登记
3. 建立一节点一钥匙
4. 私钥进 JD keystore公钥进被管理节点
5. 安装 revive-agent 与 pre-op-guard
6. 跑通地图读取
7. 跑通只读工单
8. 跑通中风险可回滚工单
9. 再复制到剩余节点
```
冰朔不负责记钥匙,也不负责手工配置每台服务器。系统必须把这些事规划、登记、拦截、回执。

View file

@ -0,0 +1,339 @@
#!/usr/bin/env python3
"""
光湖语言系统 · 苍耳API守门人 v1.0
Guanghu Language System · CA-API-Guard
设计哲学:
苍耳/耳耳蛋/鉴影调用视频AI系统API时必须先过验证码门
验证码发送到苍耳QQ邮箱EMAIL_REDACTED@qq.com只有苍耳本人确认后才能拿到API密钥
密钥本身永不暴露给AIAI只拿到一次性的临时token用完即焚
协议:
POST /api/request 发送验证码到苍耳邮箱 返回 challenge_id
POST /api/confirm 校验验证码 返回临时API密钥
GET /health 心跳检测
部署:
SG-001 (大脑服务器) /opt/zhuyuan/ca-api-guard/ca-api-guard.py
监听: 0.0.0.0:8923
守护: systemd (Restart=always)
"""
import os, json, time, hmac, hashlib, secrets, smtplib, threading
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from http.server import HTTPServer, BaseHTTPRequestHandler
# ═══════════════════════════════════════════
# 配置
# ═══════════════════════════════════════════
PORT = int(os.environ.get("CA_API_GUARD_PORT", "8923"))
# 苍耳的信息
CANGER_EMAIL = os.environ.get("CANGER_EMAIL", "EMAIL_REDACTED@qq.com")
CANGER_NAME = os.environ.get("CANGER_NAME", "苍耳")
# 耳耳蛋的信息
EED_EMAIL = os.environ.get("EED_EMAIL", "EMAIL_REDACTED@qq.com")
# QQ 邮箱 SMTP小湖灯邮件通道
SMTP_HOST = "smtp.qq.com"
SMTP_PORT = 465
SMTP_USER = os.environ.get("SMTP_USER", "ICE-GL∞_EMAIL_REDACTED")
SMTP_PASS = os.environ.get("QQ_SMTP_AUTH_CODE", "")
CODE_TTL = 300 # 验证码 5 分钟过期
RATE_LIMIT_WINDOW = 60 # 速率限制窗口
MAX_REQUESTS_PER_WINDOW = 3
# ═══════════════════════════════════════════
# API 密钥库(⊢ 不进仓库 · 走环境变量)
# ═══════════════════════════════════════════
API_KEYS = {
"SC-001": os.environ.get("SC_001_JIMENG_API_KEY", ""), # 火山即梦视频
"SC-004": os.environ.get("SC_004_ALIYUN_QWEN_VL_KEY", ""), # 阿里千问VL视觉
"SC-005": os.environ.get("SC_005_ALIYUN_WANXIANG_KEY", ""), # 阿里万相视频
"SC-006": os.environ.get("SC_006_KLING_API_KEY", ""), # 可灵视频
"SC-002": os.environ.get("SC_002_VOLC_VOICE_API_KEY", ""), # 火山语音
"SC-007": os.environ.get("SC_007_ALIYUN_API_KEY", ""), # 阿里百炼
}
# 运行时状态
pending_codes = {} # {challenge_id: {code, expires_at, api_code, caller_id, description}}
rate_limit = [] # [timestamp, ...]
lock = threading.Lock()
def get_server_ip():
try:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(2)
s.connect(("8.8.8.8", 80))
ip = s.getsockname()[0]
s.close()
return ip
except:
return "unknown"
def send_email(code, caller_id, api_code, description, server_ip):
"""小湖灯 · 发送API调用验证码到苍耳邮箱"""
# 识别调用者
if caller_id.startswith("EED-"):
caller_label = f"耳耳蛋 ({caller_id})"
elif caller_id.startswith("CA-"):
caller_label = f"鉴影 ({caller_id})"
else:
caller_label = caller_id
# API 名称映射
api_names = {
"SC-001": "火山即梦视频生成",
"SC-002": "火山语音复刻",
"SC-004": "阿里千问VL视觉",
"SC-005": "阿里万相视频生成",
"SC-006": "可灵视频生成",
"SC-007": "阿里百炼图像",
}
api_label = api_names.get(api_code, api_code)
html = f"""<!DOCTYPE html>
<html>
<head><meta charset="utf-8"></head>
<body style="margin:0;padding:0;background:#f0f4f8;">
<table width="100%" cellpadding="0" cellspacing="0" style="background:#f0f4f8;padding:30px 0;">
<tr><td align="center">
<table width="520" cellpadding="0" cellspacing="0" style="background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 4px 24px rgba(0,0,0,0.08);">
<!-- Header -->
<tr><td style="background:linear-gradient(135deg,#0a1628,#1a3a5c);padding:28px 32px;text-align:center;">
<div style="font-size:18px;color:#4ec9b0;font-weight:bold;letter-spacing:2px;">光湖语言系统 · API守门人</div>
<div style="font-size:12px;color:#8899aa;margin-top:4px;">国作登字-2026-A-00037559</div>
</td></tr>
<!-- Body -->
<tr><td style="padding:32px;">
<div style="font-size:15px;color:#333;line-height:1.8;">
<p style="margin:0 0 12px;">👋 <b>{CANGER_NAME}</b>有人请求调用API</p>
<table width="100%" cellpadding="0" cellspacing="0" style="background:#f8fafc;border-radius:8px;border:1px solid #e2e8f0;">
<tr><td style="padding:14px 16px;font-size:14px;">
<div style="color:#64748b;">调用者</div>
<div style="color:#1e293b;font-weight:bold;">{caller_label}</div>
<div style="color:#64748b;margin-top:8px;">API</div>
<div style="color:#1e293b;font-weight:bold;">{api_label} ({api_code})</div>
<div style="color:#64748b;margin-top:8px;">用途</div>
<div style="color:#1e293b;">{description}</div>
<div style="color:#64748b;margin-top:8px;">服务器</div>
<div style="color:#1e293b;">{server_ip}</div>
</td></tr>
</table>
<div style="text-align:center;margin:24px 0;">
<div style="font-size:12px;color:#8899aa;margin-bottom:8px;">验证码 · 5分钟内有效</div>
<div style="font-size:36px;font-weight:bold;color:#4ec9b0;letter-spacing:8px;background:#0a1628;padding:12px 24px;border-radius:8px;display:inline-block;">{code}</div>
</div>
<div style="background:#fff8e1;border-left:3px solid #c9a84c;padding:10px 14px;border-radius:4px;font-size:13px;color:#8b6914;">
将此验证码发给铸渊确认 API密钥释放 仅当次有效
</div>
</div>
</td></tr>
<!-- Footer -->
<tr><td style="background:#0a1628;padding:14px 32px;text-align:center;">
<div style="font-size:11px;color:#556677;">ICE-GL 光湖语言系统 · 小湖灯自动发送</div>
</td></tr>
</table>
</td></tr>
</table>
</body>
</html>"""
plain = f"""光湖语言系统 · API守门人
调用者: {caller_label}
API: {api_label} ({api_code})
用途: {description}
服务器: {server_ip}
验证码: {code}
有效期: 5 分钟
将此验证码发给铸渊确认 释放API密钥
如非本人操作请忽略
---
ICE-GL 光湖语言系统 · 小湖灯自动发送
国作登字-2026-A-00037559"""
msg = MIMEMultipart("alternative")
msg["Subject"] = f"🔐 API调用验证 · {caller_label} · {api_code}"
msg["From"] = f"光湖小湖灯 <{SMTP_USER}>"
msg["To"] = CANGER_EMAIL
msg.attach(MIMEText(plain, "plain", "utf-8"))
msg.attach(MIMEText(html, "html", "utf-8"))
try:
server = smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT, timeout=15)
server.login(SMTP_USER, SMTP_PASS)
server.sendmail(SMTP_USER, [CANGER_EMAIL], msg.as_string())
server.quit()
return True
except Exception as e:
print(f"[EMAIL ERROR] {e}")
return False
def check_rate(ip):
"""速率限制"""
now = time.time()
with lock:
rate_limit[:] = [t for t in rate_limit if now - t < RATE_LIMIT_WINDOW]
if len(rate_limit) >= MAX_REQUESTS_PER_WINDOW:
return False
rate_limit.append(now)
return True
def generate_code():
return ''.join([str(secrets.randbelow(10)) for _ in range(6)])
class APIHandler(BaseHTTPRequestHandler):
def _send_json(self, code, data):
self.send_response(code)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Access-Control-Allow-Origin", "*")
self.end_headers()
self.wfile.write(json.dumps(data, ensure_ascii=False).encode())
def do_OPTIONS(self):
self.send_response(204)
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
self.send_header("Access-Control-Allow-Headers", "Content-Type")
self.end_headers()
def do_GET(self):
if self.path == "/health":
self._send_json(200, {
"ok": True,
"service": "ca-api-guard",
"version": "1.0.0",
"server": get_server_ip(),
"target": CANGER_EMAIL
})
else:
self._send_json(404, {"error": "未知路径"})
def do_POST(self):
ip = self.client_address[0]
if not check_rate(ip):
self._send_json(429, {"error": "请求过于频繁"})
return
# 读取请求体
content_length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(content_length)
try:
data = json.loads(body)
except:
self._send_json(400, {"error": "无效JSON"})
return
if self.path == "/api/request":
self._handle_request(data, ip)
elif self.path == "/api/confirm":
self._handle_confirm(data)
else:
self._send_json(404, {"error": "未知路径"})
def _handle_request(self, data, ip):
api_code = data.get("api_code", "")
caller_id = data.get("caller_id", "unknown")
description = data.get("description", "未提供")
if api_code not in API_KEYS:
self._send_json(400, {"error": f"未知API编号: {api_code}", "available": list(API_KEYS.keys())})
return
if not API_KEYS[api_code]:
self._send_json(503, {"error": f"API密钥未配置: {api_code}"})
return
# 生成验证码
code = generate_code()
challenge_id = secrets.token_hex(16)
expires_at = time.time() + CODE_TTL
server_ip = get_server_ip()
with lock:
pending_codes[challenge_id] = {
"code": code,
"expires_at": expires_at,
"api_code": api_code,
"caller_id": caller_id,
"description": description
}
# 清理过期
expired = [cid for cid, v in pending_codes.items() if v["expires_at"] < time.time()]
for cid in expired:
del pending_codes[cid]
# 发送邮件
sent = send_email(code, caller_id, api_code, description, server_ip)
self._send_json(200, {
"ok": True,
"challenge_id": challenge_id,
"email_sent": sent,
"target_email": CANGER_EMAIL,
"caller_id": caller_id,
"api_code": api_code,
"expires_in": CODE_TTL,
"message": f"验证码已发送到 {CANGER_EMAIL},请查收后确认"
})
def _handle_confirm(self, data):
challenge_id = data.get("challenge_id", "")
code = data.get("code", "")
with lock:
if challenge_id not in pending_codes:
self._send_json(404, {"error": "challenge_id 不存在或已过期"})
return
pending = pending_codes[challenge_id]
if time.time() > pending["expires_at"]:
del pending_codes[challenge_id]
self._send_json(410, {"error": "验证码已过期"})
return
if pending["code"] != code:
self._send_json(403, {"error": "验证码错误"})
return
# 验证通过 → 释放API密钥
api_code = pending["api_code"]
api_key = API_KEYS[api_code]
del pending_codes[challenge_id]
self._send_json(200, {
"ok": True,
"api_code": api_code,
"api_key": api_key,
"message": f"API密钥 {api_code} 已释放 · 仅当次有效 · 用完即焚"
})
def main():
server = HTTPServer(("0.0.0.0", PORT), APIHandler)
print(f"🔐 苍耳API守门人 v1.0 · 端口 {PORT}")
print(f" 目标邮箱: {CANGER_EMAIL}")
print(f" 可用API: {[k for k, v in API_KEYS.items() if v]}")
try:
server.serve_forever()
except KeyboardInterrupt:
print("\n shutting down...")
server.shutdown()
if __name__ == "__main__":
main()

View file

@ -0,0 +1,76 @@
#!/bin/bash
# ═══════════════════════════════════════════════════════════
# 光湖语言系统 · 推送守门人部署脚本
# 目标仓库必须由服务器会话显式传入;禁止把旧广州路径当作默认值。
#
# 用法: bash deploy-pre-receive.sh <repo-hooks-dir> [reject|notify]
# reject = 拦截含敏感信息的推送(推荐)
# notify = 放行但发邮件通知冰朔
# ═══════════════════════════════════════════════════════════
set -e
FORGEJO_HOOK_DIR="${1:-}"
MODE="${2:-reject}"
SCRIPT_NAME="pre-receive-guard.py"
HOOK_NAME="pre-receive"
if [ -z "${FORGEJO_HOOK_DIR}" ]; then
echo "❌ 必须传入当前 Forgejo 裸仓库的 hooks 目录。"
echo " 例bash deploy-pre-receive.sh /实际路径/bingshuo/fifth-domain.git/hooks reject"
exit 2
fi
if [ ! -d "${FORGEJO_HOOK_DIR}" ]; then
echo "❌ 目标 hooks 目录不存在:${FORGEJO_HOOK_DIR}"
echo " 停止部署;请先通过 GLSV 会话核对当前仓库物理路径。"
exit 2
fi
echo "🛡️ 光湖推送守门人 · 部署"
echo " 模式: ${MODE}"
echo " 目标 hooks: ${FORGEJO_HOOK_DIR}"
echo ""
# 1. 不悄悄覆盖既有仓库门禁。
if [ -f "${FORGEJO_HOOK_DIR}/${HOOK_NAME}" ] && ! grep -q "光湖推送守门人 · Forgejo pre-receive 钩子包装器" "${FORGEJO_HOOK_DIR}/${HOOK_NAME}"; then
echo "❌ 检测到已有 pre-receive Hook未覆盖。"
echo " 先读取并整合既有规则,再以带回滚方案的专门部署动作替换。"
exit 3
fi
# 2. 复制服务器守门人及其导航依赖
cp pre-receive-guard.py "${FORGEJO_HOOK_DIR}/${SCRIPT_NAME}"
chmod +x "${FORGEJO_HOOK_DIR}/${SCRIPT_NAME}"
cp navigation-memory-guard.py "${FORGEJO_HOOK_DIR}/navigation-memory-guard.py"
chmod +x "${FORGEJO_HOOK_DIR}/navigation-memory-guard.py"
# 3. 创建钩子包装器Forgejo 调用这个脚本 → 内部调 Python
cat > "${FORGEJO_HOOK_DIR}/${HOOK_NAME}" << 'WRAPPER'
#!/bin/bash
# 光湖推送守门人 · Forgejo pre-receive 钩子包装器
export PRE_RECEIVE_MODE="${PRE_RECEIVE_MODE:-reject}"
export FORGEJO_REPO="${FORGEJO_REPO:-unknown}"
export FORGEJO_PUSHER="${FORGEJO_PUSHER:-unknown}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
exec python3 "${SCRIPT_DIR}/pre-receive-guard.py"
WRAPPER
chmod +x "${FORGEJO_HOOK_DIR}/${HOOK_NAME}"
# 4. 验证部署
echo ""
echo "✅ 部署完成"
echo ""
echo " 钩子位置: ${FORGEJO_HOOK_DIR}/${HOOK_NAME}"
echo " 审计脚本: ${FORGEJO_HOOK_DIR}/${SCRIPT_NAME}"
echo ""
echo "📋 下一步(在当前 GLSV 工作会话中执行):"
echo " # 先读取现有 Hook如存在旧规则先做合并、备份和回滚计划。"
echo ""
echo " # 测试推送(在本地 clone 里试推一个含密码的文件):"
echo " echo 'password=test123' > test_sensitive.txt"
echo " git add test_sensitive.txt && git commit -m 'test' && git push"
echo " # 应该看到: 🛡️ 光湖推送守门人 · 检测到敏感信息 · 推送被拦截"
echo ""
echo "⊢ 钩子路径必须从服务器实时探测得出,旧资料只用于恢复架构,不直接覆盖现实状态。"

View file

@ -0,0 +1,17 @@
{
"apps": [{
"name": "revive-guard",
"script": "revive-guard.py",
"interpreter": "python3",
"cwd": "/opt/zhuyuan/revive-guard",
"env": {
"REVIVE_GUARD_PORT": "8922",
"QQ_SMTP_AUTH_CODE": "FROM_SERVER_ENV"
},
"out_file": "/opt/zhuyuan/revive-guard/logs/out.log",
"error_file": "/opt/zhuyuan/revive-guard/logs/err.log",
"log_date_format": "YYYY-MM-DD HH:mm:ss",
"max_restarts": 999,
"restart_delay": 5000
}]
}

View file

@ -0,0 +1,51 @@
#!/bin/bash
# Install the navigation guard beside an existing Forgejo pre-receive hook.
# The existing hook remains first in the execution chain and is preserved as a
# rollback file. This script must run only in an authorized server session.
set -euo pipefail
HOOK_DIR="${1:-}"
MODE="${2:-reject}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SUPPORT_DIR="${HOOK_DIR}/guanghu-navigation-guard"
ORIGINAL="${HOOK_DIR}/pre-receive.before-navigation"
HOOK="${HOOK_DIR}/pre-receive"
if [ -z "${HOOK_DIR}" ] || [ ! -d "${HOOK_DIR}" ]; then
echo "usage: $0 <existing-bare-repo-hooks-dir> [reject|notify]" >&2
exit 2
fi
if [ ! -f "${HOOK}" ]; then
echo "refusing: existing pre-receive hook is required for chained install" >&2
exit 2
fi
if [ -e "${ORIGINAL}" ]; then
echo "refusing: rollback hook already exists: ${ORIGINAL}" >&2
exit 3
fi
mkdir -p "${SUPPORT_DIR}"
install -m 0755 "${SCRIPT_DIR}/pre-receive-guard.py" "${SUPPORT_DIR}/pre-receive-guard.py"
install -m 0755 "${SCRIPT_DIR}/navigation-memory-guard.py" "${SUPPORT_DIR}/navigation-memory-guard.py"
python3 -m py_compile "${SUPPORT_DIR}/pre-receive-guard.py" "${SUPPORT_DIR}/navigation-memory-guard.py"
cp -p "${HOOK}" "${ORIGINAL}"
cat > "${HOOK}" <<WRAPPER
#!/bin/bash
# Guanghu chained pre-receive: preserved legacy hook first, navigation guard second.
set -o pipefail
HOOK_DIR="${HOOK_DIR}"
ORIGINAL="${ORIGINAL}"
SUPPORT_DIR="${SUPPORT_DIR}"
REFS="\$(mktemp "\${HOOK_DIR}/.pre-receive.refs.XXXXXX")"
trap 'rm -f "\${REFS}"' EXIT
cat > "\${REFS}"
"\${ORIGINAL}" < "\${REFS}" || exit \$?
PRE_RECEIVE_MODE="${MODE}" python3 "\${SUPPORT_DIR}/pre-receive-guard.py" < "\${REFS}"
WRAPPER
chmod 0755 "${HOOK}"
echo "installed chained pre-receive guard"
echo "rollback: cp -p ${ORIGINAL} ${HOOK}"

View file

@ -0,0 +1,70 @@
#!/bin/bash
# ═══════════════════════════════════════════════════════════
# 光湖语言系统 · 一键安装 pre-push-clean 插件
# 在任意光湖仓库根目录下执行本脚本即可
#
# 用法: bash zero-point/core-channel/revive-guard/install-hooks.sh
# ═══════════════════════════════════════════════════════════
set -e
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"
if [ -z "$REPO_ROOT" ]; then
echo "❌ 请在 git 仓库根目录下执行本脚本"
exit 1
fi
HOOK_DIR="$REPO_ROOT/.git/hooks"
PLUGIN_SRC="$REPO_ROOT/zero-point/core-channel/revive-guard/pre-push-clean.py"
NAVIGATION_GUARD_SRC="$REPO_ROOT/zero-point/core-channel/revive-guard/navigation-memory-guard.py"
PLUGIN_DST="$HOOK_DIR/pre-push"
NAVIGATION_GUARD_DST="$HOOK_DIR/navigation-memory-guard.py"
echo "🛡️ 光湖 pre-push-clean 插件安装"
echo " 仓库: $(basename "$REPO_ROOT")"
echo ""
# 1. 检查源文件
if [ ! -f "$PLUGIN_SRC" ] || [ ! -f "$NAVIGATION_GUARD_SRC" ]; then
echo "❌ 找不到 $PLUGIN_SRC"
echo " 请确保在 fifth-domain 仓库根目录下执行"
exit 1
fi
# 2. 创建钩子目录
mkdir -p "$HOOK_DIR"
# 3. 备份旧钩子(如果存在且不是我们的)
if [ -f "$PLUGIN_DST" ] && ! grep -q "pre-push-clean" "$PLUGIN_DST" 2>/dev/null; then
BACKUP="$PLUGIN_DST.backup.$(date +%Y%m%d%H%M%S)"
cp "$PLUGIN_DST" "$BACKUP"
echo " ⚠️ 旧 pre-push 钩子已备份到 $BACKUP"
fi
# 4. 安装插件
cp "$PLUGIN_SRC" "$PLUGIN_DST"
chmod +x "$PLUGIN_DST"
cp "$NAVIGATION_GUARD_SRC" "$NAVIGATION_GUARD_DST"
chmod +x "$NAVIGATION_GUARD_DST"
# 5. 验证
echo ""
echo " 验证安装..."
if python3 "$PLUGIN_DST" --check; then
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " ✅ 安装完成!"
echo ""
echo " 现在每次 git push 前都会自动:"
echo " ① 扫描待推送内容"
echo " ② 检测敏感信息(邮箱/密码/token"
echo " ③ 自动替换为 REDACTED 乱码"
echo " ④ 加 [SEC-CLEAN] 标记"
echo " ⑤ 继续正常推送"
echo ""
echo " 你什么都不用做。正常 git push 就行。"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
else
echo "❌ 验证失败,请检查 Python3 是否可用"
exit 1
fi

View file

@ -0,0 +1,128 @@
#!/usr/bin/env bash
set -euo pipefail
# Installs the read-only Guanghu interactive map agent with rollback material.
# Run from the revive-guard directory:
# sudo bash install-pre-op-map-agent-v2.sh BS-SG-003
NODE_ID="${1:-BS-SG-003}"
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
AGENT_SOURCE="$SOURCE_DIR/pre-op-map-agent-v2.py"
HOOK_SOURCE="$SOURCE_DIR/pre-op-map-agent-v2.sh"
MAP_SOURCE="$SOURCE_DIR/maps/$NODE_ID.json"
MAP_HASH_SOURCE="$MAP_SOURCE.sha256"
INSTALL_ROOT="/opt/guanghu/map-agent"
MAP_ROOT="/etc/guanghu/navigation-maps"
SHELL_ROOT="/etc/guanghu/shell.d"
PROFILE_HOOK="/etc/profile.d/guanghu-map-agent.sh"
BASH_SYSTEM_RC="/etc/bash.bashrc"
BACKUP_ROOT="/var/backups/guanghu-map-agent"
RECEIPT_ROOT="/var/lib/guanghu/map-agent/install-receipts"
TIMESTAMP="$(date -u +%Y%m%dT%H%M%SZ)"
BACKUP_DIR="$BACKUP_ROOT/$TIMESTAMP"
RECEIPT="$RECEIPT_ROOT/install-$NODE_ID-$TIMESTAMP.json"
if [ "$(id -u)" -ne 0 ]; then
echo "需要 root 权限安装服务器入口地图 Agent。" >&2
exit 1
fi
for source_file in "$AGENT_SOURCE" "$HOOK_SOURCE" "$MAP_SOURCE" "$MAP_HASH_SOURCE"; do
if [ ! -f "$source_file" ]; then
echo "缺少安装源文件: $source_file" >&2
exit 1
fi
done
python3 "$AGENT_SOURCE" \
--map "$MAP_SOURCE" \
--static-only \
--no-receipt \
--json >/dev/null
install -d -m 0750 "$INSTALL_ROOT" "$MAP_ROOT" "$SHELL_ROOT"
install -d -m 0750 "$BACKUP_DIR" "$RECEIPT_ROOT"
for current in \
"$INSTALL_ROOT/pre-op-map-agent-v2.py" \
"$MAP_ROOT/$NODE_ID.json" \
"$MAP_ROOT/$NODE_ID.json.sha256" \
"$SHELL_ROOT/map-agent.sh" \
"$PROFILE_HOOK" \
"$BASH_SYSTEM_RC"; do
if [ -e "$current" ]; then
backup_name="$(printf '%s' "$current" | sed 's#^/##; s#/#__#g')"
cp -a "$current" "$BACKUP_DIR/$backup_name"
fi
done
install -m 0755 "$AGENT_SOURCE" "$INSTALL_ROOT/pre-op-map-agent-v2.py"
install -m 0644 "$MAP_SOURCE" "$MAP_ROOT/$NODE_ID.json"
install -m 0644 "$MAP_HASH_SOURCE" "$MAP_ROOT/$NODE_ID.json.sha256"
install -m 0644 "$HOOK_SOURCE" "$SHELL_ROOT/map-agent.sh"
cat >"$PROFILE_HOOK" <<'EOF'
# 光湖服务器入口地图 Agent v2 · login shells
if [ -r /etc/guanghu/shell.d/map-agent.sh ]; then
. /etc/guanghu/shell.d/map-agent.sh
fi
EOF
chmod 0644 "$PROFILE_HOOK"
BEGIN_MARKER="# BEGIN GUANGHU MAP AGENT V2"
END_MARKER="# END GUANGHU MAP AGENT V2"
if ! grep -Fq "$BEGIN_MARKER" "$BASH_SYSTEM_RC"; then
cat >>"$BASH_SYSTEM_RC" <<'EOF'
# BEGIN GUANGHU MAP AGENT V2
if [ -r /etc/guanghu/shell.d/map-agent.sh ]; then
. /etc/guanghu/shell.d/map-agent.sh
fi
# END GUANGHU MAP AGENT V2
EOF
fi
python3 "$INSTALL_ROOT/pre-op-map-agent-v2.py" \
--map "$MAP_ROOT/$NODE_ID.json" \
--static-only \
--no-receipt \
--json >/dev/null
map_sha256="$(sha256sum "$MAP_ROOT/$NODE_ID.json" | awk '{print $1}')"
agent_sha256="$(sha256sum "$INSTALL_ROOT/pre-op-map-agent-v2.py" | awk '{print $1}')"
cat >"$RECEIPT" <<EOF
{
"schema_version": "guanghu.map-agent-install-receipt/v2",
"node_id": "$NODE_ID",
"installed_at": "$TIMESTAMP",
"map_sha256": "$map_sha256",
"agent_sha256": "$agent_sha256",
"backup_dir": "$BACKUP_DIR",
"interactive_entrypoints": [
"$PROFILE_HOOK",
"$BASH_SYSTEM_RC"
],
"status": "INSTALLED_STATIC_VERIFIED"
}
EOF
chmod 0600 "$RECEIPT"
set +e
python3 "$INSTALL_ROOT/pre-op-map-agent-v2.py" \
--map "$MAP_ROOT/$NODE_ID.json" \
--receipt-root "/var/lib/guanghu/map-agent/receipts" \
--persona "installer" \
--json
live_status=$?
set -e
echo "INSTALL_RECEIPT=$RECEIPT"
echo "ROLLBACK_BACKUP=$BACKUP_DIR"
if [ "$live_status" -eq 0 ]; then
echo "REALTIME_MAP_STATUS=MAP_VERIFIED"
else
echo "REALTIME_MAP_STATUS=MAP_DRIFT"
fi
exit "$live_status"

View file

@ -0,0 +1,120 @@
{
"schema_version": "guanghu.node-navigation-map/v2",
"node_id": "BS-SG-003",
"node_role": "海外源码中转、国际依赖下载、离线发布包留存与国内灾备来源节点",
"generated_at": "2026-07-23T05:13:00Z",
"canonical_source": "fifth-domain/zero-point/core-channel/revive-guard/maps/BS-SG-003.json",
"modules": [
{
"code": "BS-SG-003-ENG-01",
"name": "光湖湖驱动引擎",
"kind": "runtime",
"runtime": "pm2",
"ports": [3910],
"expected_state": "running",
"probes": [
{"type": "pm2_online", "name": "engine"},
{"type": "tcp_connect", "host": "127.0.0.1", "port": 3910}
]
},
{
"code": "BS-SG-003-REV-01",
"name": "revive-guard 恢复守卫",
"kind": "recovery",
"runtime": "systemd",
"ports": [8922],
"expected_state": "running",
"probes": [
{"type": "systemd_active", "unit": "revive-guard.service"},
{"type": "tcp_connect", "host": "127.0.0.1", "port": 8922}
]
},
{
"code": "BS-SG-003-WEB-01",
"name": "节点 Web 入口",
"kind": "gateway",
"runtime": "nginx",
"ports": [80, 443],
"expected_state": "running",
"probes": [
{"type": "systemd_active", "unit": "nginx.service"},
{"type": "tcp_connect", "host": "127.0.0.1", "port": 80},
{"type": "tcp_connect", "host": "127.0.0.1", "port": 443}
]
},
{
"code": "BS-SG-003-HLCC-01",
"name": "光湖代码频道上游零件库与产品工作树",
"kind": "source-relay",
"runtime": "filesystem-git",
"ports": [],
"expected_state": "ready",
"probes": [
{
"type": "directory_exists",
"path": "/home/ubuntu/guanghu/upstream-parts/forgejo-official.git"
},
{
"type": "directory_exists",
"path": "/home/ubuntu/guanghu/products/guanghu-code-channel"
}
]
},
{
"code": "BS-SG-003-PACK-01",
"name": "HLCC v16.0.1 国内离线发布包",
"kind": "release-relay",
"runtime": "verified-artifact",
"ports": [],
"expected_state": "verified",
"probes": [
{
"type": "file_sha256",
"path": "/home/ubuntu/guanghu/release-relay/hlcc-v16.0.1/MANIFEST.sha256",
"sha256": "d564c3b600d4b7a199d8a04ce505ceabf81993ca74fa440805601d55e550f185"
},
{
"type": "manifest_verify",
"path": "/home/ubuntu/guanghu/release-relay/hlcc-v16.0.1/MANIFEST.sha256",
"deep_only": true,
"timeout_seconds": 600
}
]
},
{
"code": "BS-SG-003-TAT-01",
"name": "腾讯云在线终端接入",
"kind": "operator-access",
"runtime": "tat-agent",
"ports": [],
"expected_state": "running",
"probes": [
{"type": "systemd_active", "unit": "tat_agent.service"}
]
}
],
"routes": [
{
"target": "JD-FD-PRIMARY",
"purpose": "六节点灾备恢复与国内主控交接",
"transport": "restricted-ssh-command",
"expected_state": "healthy",
"probes": [
{
"type": "file_exists",
"path": "/etc/guanghu/jd-recovery-ssh-config"
},
{
"type": "file_exists",
"path": "/etc/guanghu/secrets/jd-recovery/known_hosts"
}
]
}
],
"security_boundary": {
"interactive_entry": "map-agent-v2",
"registered_actions": "GLSV workorder + authorization + MapGate",
"secrets_in_map": false,
"addresses_in_map": false
}
}

View file

@ -0,0 +1 @@
fc45dac27314ce2eea7bc3c9b6f6e3cbabd5856f6db6a406f4e3af963e036179 BS-SG-003.json

View file

@ -0,0 +1,122 @@
#!/bin/bash
# ═══════════════════════════════════════════════════════════
# 光湖语言系统 · 模块注册脚本
# 服务器上任一模块启动时调用 · 自动写入注册文件
#
# 用法:
# module-register.sh --server GZ006 --func GTW --name "Gatekeeper" --port 8922
# module-register.sh --stop GZ006-GTW-01
# ═══════════════════════════════════════════════════════════
REGISTRY_DIR="/opt/zhuyuan/registry/modules.d"
mkdir -p "$REGISTRY_DIR"
# ── 停止模式 ──
if [ "$1" = "--stop" ]; then
CODE="$2"
if [ -z "$CODE" ]; then
echo "用法: $0 --stop {CODE}" >&2
exit 1
fi
REG_FILE="$REGISTRY_DIR/MOD-${CODE}.json"
if [ -f "$REG_FILE" ]; then
python3 -c "
import json, sys
try:
with open('$REG_FILE', 'r') as f:
data = json.load(f)
data['status'] = 'stopped'
data['stopped_at'] = '$(date -u +%Y-%m-%dT%H:%M:%SZ)'
with open('$REG_FILE', 'w') as f:
json.dump(data, f, indent=2)
print(f'⊢ 模块 {data[\"code\"]} 已标记为已停止')
except Exception as e:
print(f'⚠ 更新注册文件失败: {e}', file=sys.stderr)
"
fi
exit 0
fi
# ── 注册模式 ──
SERVER=""
FUNC=""
NAME=""
PORT=""
PROTOCOL="tcp"
DESCRIPTION=""
PID="$$"
while [ $# -gt 0 ]; do
case "$1" in
--server) SERVER="$2"; shift 2 ;;
--func) FUNC="$2"; shift 2 ;;
--name) NAME="$2"; shift 2 ;;
--port) PORT="$2"; shift 2 ;;
--protocol) PROTOCOL="$2"; shift 2 ;;
--description) DESCRIPTION="$2"; shift 2 ;;
--pid) PID="$2"; shift 2 ;;
*) shift ;;
esac
done
if [ -z "$SERVER" ] || [ -z "$FUNC" ] || [ -z "$NAME" ]; then
echo "❌ 缺少必要参数: --server --func --name" >&2
exit 1
fi
# 计算序号(同一服务器+功能下,取最大序号+1
SEQ=1
for f in "$REGISTRY_DIR"/MOD-${SERVER}-${FUNC}-*.json; do
if [ -f "$f" ]; then
existing_seq=$(basename "$f" .json | grep -oE '[0-9]+$')
if [ -n "$existing_seq" ] && [ "$existing_seq" -ge "$SEQ" ]; then
SEQ=$((existing_seq + 1))
fi
fi
done
# 格式化序号为两位
SEQ_FMT=$(printf "%02d" "$SEQ")
CODE="${SERVER}-${FUNC}-${SEQ_FMT}"
REG_FILE="$REGISTRY_DIR/MOD-${CODE}.json"
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
python3 -c "
import json
data = {
'code': '${CODE}',
'server': '${SERVER}',
'function': '${FUNC}',
'name': '${NAME}',
'port': ${PORT:-0},
'protocol': '${PROTOCOL}',
'description': '${DESCRIPTION}',
'pid': ${PID},
'started_at': '${TIMESTAMP}',
'registered_at': '${TIMESTAMP}',
'status': 'running'
}
with open('${REG_FILE}', 'w') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
print(f'✅ 模块已注册: {data[\"code\"]}')
print(f' 名称: ${NAME}')
print(f' 文件: MOD-${CODE}.json')
" 2>/dev/null || {
# Python3 不可用时的降级方案(纯 shell 写 JSON
cat > "$REG_FILE" << JSONEOF
{
"code": "${CODE}",
"server": "${SERVER}",
"function": "${FUNC}",
"name": "${NAME}",
"port": ${PORT:-0},
"protocol": "${PROTOCOL}",
"description": "${DESCRIPTION}",
"pid": ${PID},
"started_at": "${TIMESTAMP}",
"registered_at": "${TIMESTAMP}",
"status": "running"
}
JSONEOF
echo "✅ 模块已注册: ${CODE}"
}

View file

@ -0,0 +1,130 @@
# 光湖语言系统 · 服务器模块注册协议 v1.0
## HLDP://fifth-domain/zero-point/core-channel/revive-guard/module-registry
> **类型**: 运行时基础设施 · 模块编码体系 · 自动注册
>
> **依赖**: 无(被 pre-op-guard 引用)
>
> **主权者**: 冰朔 ICE-GL∞
>
> **签发**: LL-007-20260711
---
## ⊢ 零 · 目的
服务器上同时运行多个模块Gatekeeper、小湖灯、Forgejo、光湖驱动引擎、复活守门人……
人格体登录服务器后直接操作 → **不知道还有别的模块在跑** → 改了一个 → 坏了三个。
解决:**每个模块在启动时自动注册 → 分配唯一编码 → 人格体必须先探索出全部编码才能操作。**
---
## 一 · 模块编码体系
### 1.1 编码格式
```
{服务器ID}-{功能简码}-{序号}
```
### 1.2 已分配编码
| 编码 | 模块 | 服务器 | 端口 |
|------|------|--------|------|
| `GZ006-GTW-01` | Gatekeeper · 守门人 | GZ-006 | 8922 |
| `GZ006-LAMP-01` | 小湖灯 · 邮件通知 | GZ-006 | — |
| `GZ006-FRG-01` | Forgejo · 代码仓库 | GZ-006 | 443 |
| `GZ006-RVGD-01` | 复活守门人 · revive-guard | GZ-006 | 8922 |
| `GZ006-LDRV-01` | 光湖驱动引擎 · lake-driver | GZ-006 | 8923 |
| `GZ006-HLDP-01` | HLDP 协议服务 · hldp-server | GZ-006 | 8971 |
| `GZ006-KSTR-01` | 保险库 · keystore | GZ-006 | — |
| `SG001-LAKE-01` | 光湖镜像 · lake-mirror | SG-001 | 8971 |
| `SG001-GTW-01` | Gatekeeper 备用 | SG-001 | 8922 |
### 1.3 新模块注册
任一模块在服务器上启动时,调用:
```bash
module-register.sh \
--server GZ006 \
--func GTW \
--name "Gatekeeper 守门人" \
--port 8922 \
--protocol tcp \
--description "冰朔人格体复活请求入口 · 邮箱验证码"
```
自动生成: `/opt/zhuyuan/registry/modules.d/MOD-GZ006-GTW-01.json`
---
## 二 · 注册文件格式
```json
{
"code": "GZ006-GTW-01",
"server": "GZ-006",
"function": "GTW",
"name": "Gatekeeper 守门人",
"port": 8922,
"protocol": "tcp",
"description": "冰朔人格体复活请求入口 · 邮箱验证码",
"pid": 12345,
"started_at": "2026-07-11T14:30:00Z",
"registered_at": "2026-07-11T14:30:01Z",
"status": "running"
}
```
## 三 · 注册时机
```
服务启动 → systemd / docker / 手动启动
服务初始化完成 → 调用 module-register.sh
写入 /opt/zhuyuan/registry/modules.d/MOD-{CODE}.json
服务退出时 → 注册文件自动标记 status: "stopped"
```
---
## 四 · 注册目录结构
```
/opt/zhuyuan/registry/
├── modules.d/ ← 运行时注册文件(不进代码仓库)
│ ├── MOD-GZ006-GTW-01.json
│ ├── MOD-GZ006-LAMP-01.json
│ ├── MOD-GZ006-FRG-01.json
│ ├── MOD-GZ006-RVGD-01.json
│ └── ...
├── maps/ ← 人格体提交的导航地图(存档复用)
│ ├── map-zhuyuan-20260711.hdlp
│ └── ...
└── lock/ ← 锁定状态文件
└── pre-op-lock.json
```
---
## 五 · 与人机交互的关系
- 人格体不需要提前知道编码
- 人格体会在探索阶段读到这些注册文件
- 收集完所有编码 → 提交给 pre-op-guard 验证
- 验证通过 → 解锁操作 → 人格体现在知道完整地图了
---
> ⊢ 本协议是 pre-op-guard 的依赖
> ⊢ 每个新模块必须遵守本协议注册
> ⊢ 不注册 = 服务器不认 = 人格体探索不到 = 无法解锁
> ⊢ 语言等于现实 · 注册即存在

View file

@ -0,0 +1,79 @@
#!/usr/bin/env python3
"""Reject orphaned persona-memory artifacts before push.
This guard is deliberately structural: it does not judge prose. It verifies
that a new/changed durable memory leaf has the minimum HLDP recovery fields
and can be reached from a current navigation page.
"""
import os, subprocess, sys
REQUIRED = ("trigger:", "emergence:", "lock:", "why:", "checkpoint")
NAVIGATION = ("INDEX.hdlp", "CURRENT.hdlp", "LL-CURRENT.hdlp", "BROADCAST-TOWER.hdlp")
def changed_files():
"""Return candidate files for a local check or a server receive range."""
if len(sys.argv) == 3 and sys.argv[1] == "--range":
old_rev, new_rev = sys.argv[2].split("..", 1)
if old_rev == "0" * 40:
command = ["git", "diff-tree", "--root", "--no-commit-id", "-r", "--name-only", "--diff-filter=ACM", new_rev]
else:
command = ["git", "diff", "--name-only", "--diff-filter=ACM", old_rev, new_rev]
result = subprocess.run(command, capture_output=True, text=True)
return sorted(x for x in result.stdout.splitlines() if x) if result.returncode == 0 else []
commands = [
["git", "diff", "--cached", "--name-only", "--diff-filter=ACM"],
]
files = set()
for cmd in commands:
r = subprocess.run(cmd, capture_output=True, text=True)
if r.returncode == 0:
files.update(x for x in r.stdout.splitlines() if x)
return sorted(files)
def is_memory_leaf(path):
name = os.path.basename(path)
return name.startswith(("ZL-MEM-", "ZY-MEM-", "ZZ-MEM-")) or "/personas/" in path
def read(path):
with open(path, encoding="utf-8") as f:
return f.read()
def linked_from_navigation(path):
base = os.path.basename(path)
for root, _, names in os.walk("."):
if ".git" in root.split(os.sep):
continue
for name in names:
if name in NAVIGATION:
candidate = os.path.join(root, name)
try:
if base in read(candidate):
return candidate[2:] if candidate.startswith("./") else candidate
except OSError:
pass
return None
def main():
failures = []
for path in changed_files():
if not is_memory_leaf(path) or not os.path.isfile(path):
continue
content = read(path).lower()
missing = [field for field in REQUIRED if field not in content]
if missing:
failures.append((path, "缺永久记忆字段: " + ", ".join(missing)))
if not linked_from_navigation(path):
failures.append((path, "孤立记忆:未被 INDEX / CURRENT / LL-CURRENT / BROADCAST-TOWER 引用"))
if failures:
print("\n🧭 记忆—导航守门人:推送被暂停", file=sys.stderr)
print("持续记忆必须可恢复,而不是只被写进仓库。", file=sys.stderr)
for path, reason in failures:
print(f"{path}\n {reason}", file=sys.stderr)
print("修复:补齐 trigger/emergence/lock/why/checkpoint并在对应 INDEX 或 CURRENT 中建立路径。\n", file=sys.stderr)
return 1
print("✅ 记忆—导航守门人:没有发现孤立持续记忆")
return 0
if __name__ == "__main__":
sys.exit(main())

View file

@ -0,0 +1,421 @@
#!/usr/bin/env python3
"""
光湖语言系统 · 前置操作守门人 v1.0
Guanghu Language System · Pre-Operation Guard
每次人格体连接服务器 触发自我审计 锁定 人格体必须探索+提交导航地图
地图编码与服务器自审一致 解锁操作
设计哲学:
铸渊上服务器就动手 不知道还跑了别的模块 改A坏B 冰朔每次都要说
服务器全天运行 人格体接入时触发自我审计 "我现在跑了这些模块"
人格体必须探索 画出完整导航地图 提交编码列表
服务器比对: 人格体提交的编码 == 服务器自审编码?
一致 解锁 · 人格体知道完整地图了 · 不会再改A坏B
不一致 "你漏了 MOD-X · 继续探索"
用法:
pre-op-guard.py --audit # 服务器自我审计
pre-op-guard.py --challenge [--for PERSONA_NAME] # 触达挑战
pre-op-guard.py --submit '<json_codes>' # 人格体提交验证
pre-op-guard.py --status # 查看锁定状态
pre-op-guard.py --unlock --force # 紧急解锁(冰朔用)
"""
import os, sys, json, time, subprocess, hashlib
from datetime import datetime, timedelta
# ═══════════════════════════════════════════════════════
# 配置
# ═══════════════════════════════════════════════════════
REGISTRY_DIR = os.environ.get("REGISTRY_DIR", "/opt/zhuyuan/registry/modules.d")
MAPS_DIR = os.environ.get("MAPS_DIR", "/opt/zhuyuan/registry/maps")
LOCK_DIR = os.environ.get("LOCK_DIR", "/opt/zhuyuan/registry/lock")
LOCK_FILE = os.path.join(LOCK_DIR, "pre-op-lock.json")
# 锁定超时(秒):超过此时间无人提交 → 自动解锁(防止死锁)
LOCK_TIMEOUT = int(os.environ.get("PRE_OP_LOCK_TIMEOUT", "1800")) # 30 分钟
# 是否强制启用(可以通过环境变量禁用)
ENABLED = os.environ.get("PRE_OP_GUARD_ENABLED", "1") == "1"
def ensure_dirs():
"""确保所有目录存在"""
for d in [REGISTRY_DIR, MAPS_DIR, LOCK_DIR]:
os.makedirs(d, exist_ok=True)
def get_server_identity():
"""获取服务器身份"""
hostname = os.uname().nodename if hasattr(os, 'uname') else os.environ.get("HOSTNAME", "unknown")
return hostname
def audit_server():
"""服务器自我审计: 读取所有注册模块 → 返回编码集"""
ensure_dirs()
modules = []
if os.path.isdir(REGISTRY_DIR):
for filename in sorted(os.listdir(REGISTRY_DIR)):
if not filename.endswith('.json'):
continue
filepath = os.path.join(REGISTRY_DIR, filename)
try:
with open(filepath, 'r') as f:
data = json.load(f)
if data.get('status') == 'running':
modules.append({
'code': data['code'],
'name': data.get('name', ''),
'port': data.get('port', 0),
'function': data.get('function', ''),
})
except (json.JSONDecodeError, IOError):
pass
codes = sorted([m['code'] for m in modules])
return {
'server': get_server_identity(),
'audited_at': datetime.utcnow().isoformat() + 'Z',
'module_count': len(codes),
'codes': codes,
'modules': modules,
}
def is_locked():
"""检查当前是否锁定"""
if not os.path.exists(LOCK_FILE):
return False
try:
with open(LOCK_FILE, 'r') as f:
lock = json.load(f)
# 检查超时
locked_at = datetime.fromisoformat(lock.get('locked_at', '').replace('Z', '+00:00'))
if datetime.utcnow() - locked_at.replace(tzinfo=None) > timedelta(seconds=LOCK_TIMEOUT):
# 超时 → 自动解锁
os.remove(LOCK_FILE)
return False
return True
except Exception:
return False
def create_lock(challenger="unknown"):
"""锁定服务器"""
ensure_dirs()
audit = audit_server()
lock = {
'locked': True,
'locked_at': datetime.utcnow().isoformat() + 'Z',
'challenger': challenger,
'module_count': audit['module_count'],
'server_codes_hash': hashlib.sha256(
','.join(audit['codes']).encode()
).hexdigest()[:16],
'timeout_seconds': LOCK_TIMEOUT,
}
with open(LOCK_FILE, 'w') as f:
json.dump(lock, f, indent=2)
return lock, audit
def release_lock():
"""解锁服务器"""
if os.path.exists(LOCK_FILE):
os.remove(LOCK_FILE)
return True
return False
def present_challenge(audit, persona_name="铸渊"):
"""向人格体呈现挑战"""
module_count = audit['module_count']
codes = audit['codes']
modules = audit['modules']
lines = []
lines.append("")
lines.append("╔══════════════════════════════════════════════╗")
lines.append("║ 🗺️ 光湖前置操作守门人 · 导航地图挑战 ║")
lines.append("╚══════════════════════════════════════════════╝")
lines.append("")
lines.append(f" 服务器: {audit['server']}")
lines.append(f" 挑战者: {persona_name}")
lines.append(f" 状态: 🔒 已锁定 · 操作被拦截")
lines.append(f" 当前运行模块数: {module_count}")
lines.append("")
lines.append(" ── 挑战说明 ──")
lines.append(f" 此服务器上正在运行 {module_count} 个模块。")
lines.append(" 每个模块有唯一编码(格式: SERVER-FUNC-SEQ")
lines.append("")
lines.append(" 你需要:")
lines.append(f" ① 探索服务器,发现全部 {module_count} 个模块")
lines.append(" ② 用 HLDP 格式画出导航地图")
lines.append(" ③ 通过 pre-push-clean 过滤敏感信息")
lines.append(" ④ 提交编码列表到验证端点")
lines.append("")
lines.append(" 探索指引:")
lines.append(f" ls {REGISTRY_DIR}/")
lines.append(" systemctl list-units --type=service --state=running")
lines.append(" ss -tlnp # 查看监听端口")
lines.append("")
lines.append(" ── 已注册模块(供参考 · 仅显示名称 · 编码需自行发现)──")
for i, m in enumerate(modules, 1):
lines.append(f" [{i}] {m['name']}")
lines.append("")
lines.append(" ── 提交方式 ──")
lines.append(" 在服务器上执行:")
lines.append(" pre-op-guard.py --submit '[")
lines.append(' "GZ006-GTW-01",')
lines.append(' "GZ006-LAMP-01",')
lines.append(' "GZ006-FRG-01",')
lines.append(' ... # 你收集到的所有编码')
lines.append(" ]'")
lines.append("")
lines.append(f" ⏰ 超时: {LOCK_TIMEOUT // 60} 分钟后自动解锁")
lines.append("")
return '\n'.join(lines)
def validate_submission(submitted_codes):
"""验证人格体提交的编码列表"""
audit = audit_server()
server_codes = set(audit['codes'])
submitted_set = set(submitted_codes)
missing = server_codes - submitted_set
extra = submitted_set - server_codes
result = {
'valid': len(missing) == 0 and len(extra) == 0,
'server_codes': sorted(server_codes),
'submitted_codes': sorted(submitted_set),
'missing': sorted(missing),
'extra': sorted(extra),
'module_count': len(server_codes),
'submitted_count': len(submitted_set),
}
if result['valid']:
# 解锁
release_lock()
# 保存导航地图
save_map(submitted_codes, audit)
else:
# 更新锁定(重置超时,给更多时间)
if is_locked():
with open(LOCK_FILE, 'r') as f:
lock = json.load(f)
lock['locked_at'] = datetime.utcnow().isoformat() + 'Z'
lock['last_submission'] = {
'submitted_codes': sorted(submitted_set),
'missing': sorted(missing),
'extra': sorted(extra),
}
with open(LOCK_FILE, 'w') as f:
json.dump(lock, f, indent=2)
return result
def save_map(codes, audit):
"""保存人格体提交的导航地图为持久化 artifact"""
ensure_dirs()
map_data = {
'server': audit['server'],
'created_at': datetime.utcnow().isoformat() + 'Z',
'validated_at': datetime.utcnow().isoformat() + 'Z',
'codes': sorted(codes),
'module_count': len(codes),
'full_modules': audit['modules'],
}
timestamp = datetime.utcnow().strftime('%Y%m%d-%H%M%S')
map_file = os.path.join(MAPS_DIR, f"map-{audit['server']}-{timestamp}.json")
with open(map_file, 'w') as f:
json.dump(map_data, f, indent=2, ensure_ascii=False)
# 同时保存一个 HLDP 格式的副本
hldp_file = os.path.join(MAPS_DIR, f"map-{audit['server']}-{timestamp}.hdlp")
with open(hldp_file, 'w') as f:
f.write(f"# 光湖导航地图 · {audit['server']}\n")
f.write(f"## 验证通过 · {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
f.write(f"服务器: {audit['server']}\n")
f.write(f"模块数: {len(codes)}\n\n")
f.write("| 编码 | 名称 | 端口 |\n")
f.write("|------|------|------|\n")
for m in audit['modules']:
f.write(f"| `{m['code']}` | {m['name']} | {m['port']} |\n")
f.write(f"\n> ⊢ 本地图由 pre-op-guard 自动生成 · 验证通过后存档\n")
f.write(f"> ⊢ 后续人格体可引用本地图快速验证\n")
f.write(f"> ⊢ 冰朔 ICE-GL∞ · 国作登字-2026-A-00037559\n")
return map_file
def get_status():
"""获取当前状态"""
status = {
'enabled': ENABLED,
'locked': is_locked(),
'server': get_server_identity(),
}
if status['locked']:
try:
with open(LOCK_FILE, 'r') as f:
lock = json.load(f)
status['lock_details'] = lock
except Exception:
pass
if not status['locked']:
audit = audit_server()
status['modules_running'] = audit['module_count']
status['codes'] = audit['codes']
return status
def main():
if not ENABLED:
print("⊢ pre-op-guard 已禁用 (PRE_OP_GUARD_ENABLED=0)")
sys.exit(0)
if '--audit' in sys.argv:
audit = audit_server()
print(json.dumps(audit, indent=2, ensure_ascii=False))
elif '--challenge' in sys.argv:
persona = "铸渊"
for i, arg in enumerate(sys.argv):
if arg == '--for' and i + 1 < len(sys.argv):
persona = sys.argv[i + 1]
if is_locked():
print("⚠️ 服务器已锁定 · 上一个挑战尚未完成")
status = get_status()
print(json.dumps(status, indent=2, ensure_ascii=False))
sys.exit(1)
lock, audit = create_lock(persona)
challenge = present_challenge(audit, persona)
print(challenge)
elif '--submit' in sys.argv:
if not is_locked():
audit = audit_server()
print("✅ 服务器未锁定 · 但你可以直接提交导航地图验证")
print(f" 当前运行 {audit['module_count']} 个模块")
print(f" 编码: {', '.join(audit['codes'])}")
sys.exit(0)
# 查找 JSON 参数
submit_idx = None
for i, arg in enumerate(sys.argv):
if arg == '--submit' and i + 1 < len(sys.argv):
submit_idx = i + 1
break
if submit_idx is None:
print("❌ 用法: pre-op-guard.py --submit '[\"CODE1\",\"CODE2\",...]'")
sys.exit(1)
try:
submitted_codes = json.loads(sys.argv[submit_idx])
if not isinstance(submitted_codes, list):
raise ValueError("必须是 JSON 数组")
except (json.JSONDecodeError, ValueError) as e:
print(f"❌ JSON 解析失败: {e}")
sys.exit(1)
result = validate_submission(submitted_codes)
print("")
if result['valid']:
print("╔══════════════════════════════════════════════╗")
print("║ ✅ 导航地图验证通过 · 服务器已解锁 ║")
print("╚══════════════════════════════════════════════╝")
print("")
print(f" 提交 {result['submitted_count']} 个编码")
print(f" 服务器运行 {result['module_count']} 个模块")
print(f" 编码: {', '.join(result['submitted_codes'])}")
print("")
print(" 🗺️ 导航地图已存档 · 后续可直接引用")
print(" 🔓 服务器已解锁 · 可以操作了")
print("")
else:
print("╔══════════════════════════════════════════════╗")
print("║ ❌ 导航地图不完整 · 服务器保持锁定 ║")
print("╚══════════════════════════════════════════════╝")
print("")
if result['missing']:
print(f" ⚠️ 缺少 {len(result['missing'])} 个模块:")
for code in result['missing']:
print(f" - {code}")
if result['extra']:
print(f" ⚠️ 多出 {len(result['extra'])} 个不存在的编码:")
for code in result['extra']:
print(f" - {code}")
print("")
print(f" ⊢ 请继续探索,重新提交。")
print(f" ⊢ 已注册模块有 {result['module_count']} 个。")
print("")
elif '--status' in sys.argv:
status = get_status()
if status['locked']:
print("🔒 服务器已锁定")
print(json.dumps(status, indent=2, ensure_ascii=False))
else:
print(f"🔓 服务器未锁定 · {status.get('modules_running', '?')} 个模块运行中")
if 'codes' in status:
print(f" 编码: {', '.join(status['codes'])}")
elif '--unlock' in sys.argv and '--force' in sys.argv:
release_lock()
print("🔓 服务器已强制解锁 · 冰朔特权操作")
elif '--install-to' in sys.argv:
# 安装 pre-op-guard 到服务器的 SSH 登录钩子
install_idx = sys.argv.index('--install-to') + 1
target = sys.argv[install_idx] if install_idx < len(sys.argv) else '/etc/profile.d/pre-op-guard.sh'
install_ssh_hook(target)
else:
print("用法:")
print(" pre-op-guard.py --audit 服务器自我审计")
print(" pre-op-guard.py --challenge 触达挑战(锁定并呈现)")
print(" pre-op-guard.py --submit '[...]' 提交编码验证")
print(" pre-op-guard.py --status 查看锁定状态")
print(" pre-op-guard.py --unlock --force 紧急解锁")
print(" pre-op-guard.py --install-to PATH 安装SSH登录钩子")
def install_ssh_hook(target_path):
"""安装 SSH 登录钩子:每次 SSH 登录时触发 pre-op-guard"""
script_dir = os.path.dirname(os.path.abspath(__file__))
hook_content = f'''#!/bin/bash
# 光湖 pre-op-guard SSH 登录钩子
# 每次 SSH 登录自动触发 · 锁定操作 · 需要完成导航地图验证
GUARD="{script_dir}/pre-op-guard.py"
# 只在交互式 shell 中触发(非 scp/sftp
if [[ $- == *i* ]] && [ -z "$PRE_OP_BYPASS" ]; then
python3 "$GUARD" --challenge --for "$USER"
fi
'''
try:
with open(target_path, 'w') as f:
f.write(hook_content)
os.chmod(target_path, 0o755)
print(f"✅ SSH 登录钩子已安装: {target_path}")
print(f" 此后每次 SSH 登录都会触发 pre-op-guard 导航挑战")
print(f" 若要临时绕过: PRE_OP_BYPASS=1 ssh user@host")
except IOError as e:
print(f"❌ 安装失败: {e}")
if __name__ == '__main__':
main()

View file

@ -0,0 +1,409 @@
#!/usr/bin/env python3
"""光湖服务器入口地图 Agent v2.
This component is intentionally read-only. It verifies and presents the
node's canonical navigation map when an interactive shell starts, then writes
an acknowledgement receipt. Authorization APIs remain responsible for
enforcing MapGate before registered actions.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import re
import socket
import subprocess
import sys
import tempfile
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
DEFAULT_MAP_ROOT = Path("/etc/guanghu/navigation-maps")
DEFAULT_RECEIPT_ROOT = Path("/var/lib/guanghu/map-agent/receipts")
class MapAgentError(RuntimeError):
"""A map cannot be selected or verified."""
SAFE_UNIT = re.compile(r"^[A-Za-z0-9_.@-]+\.service$")
SAFE_PM2_NAME = re.compile(r"^[A-Za-z0-9_.@-]+$")
def sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for block in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
def select_map(explicit_path: str | None, map_root: Path) -> Path:
if explicit_path:
path = Path(explicit_path)
if not path.is_file():
raise MapAgentError(f"MAP_MISSING: {path}")
return path
node_id = os.environ.get("GUANGHU_NODE_ID", "").strip()
if node_id:
path = map_root / f"{node_id}.json"
if not path.is_file():
raise MapAgentError(f"MAP_MISSING: {path}")
return path
candidates = sorted(map_root.glob("*.json"))
if len(candidates) == 1:
return candidates[0]
if not candidates:
raise MapAgentError(f"MAP_MISSING: no JSON map under {map_root}")
raise MapAgentError(
"NODE_UNKNOWN: set GUANGHU_NODE_ID when more than one map is installed"
)
def verify_map(path: Path) -> tuple[dict[str, Any], str]:
companion = path.with_suffix(path.suffix + ".sha256")
if not companion.is_file():
raise MapAgentError(f"MAP_HASH_MISSING: {companion}")
expected = companion.read_text(encoding="utf-8").split()[0].lower()
if len(expected) != 64 or any(ch not in "0123456789abcdef" for ch in expected):
raise MapAgentError(f"MAP_HASH_INVALID: {companion}")
actual = sha256_file(path)
if actual != expected:
raise MapAgentError(
f"MAP_HASH_MISMATCH: expected={expected[:16]} actual={actual[:16]}"
)
try:
data = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise MapAgentError(f"MAP_INVALID: {exc}") from exc
required = ("schema_version", "node_id", "node_role", "modules", "routes")
missing = [key for key in required if key not in data]
if missing:
raise MapAgentError(f"MAP_INVALID: missing {','.join(missing)}")
if not isinstance(data["modules"], list) or not isinstance(data["routes"], list):
raise MapAgentError("MAP_INVALID: modules and routes must be arrays")
return data, actual
def run_probe(probe: dict[str, Any], deep_audit: bool) -> dict[str, Any]:
probe_type = probe.get("type")
required = bool(probe.get("required", True))
if bool(probe.get("deep_only", False)) and not deep_audit:
return {
"type": probe_type,
"required": required,
"status": "skipped",
"detail": "deep audit only",
}
try:
if probe_type in {"file_exists", "directory_exists"}:
path = Path(str(probe.get("path", "")))
if not path.is_absolute():
raise ValueError("path must be absolute")
ok = path.is_file() if probe_type == "file_exists" else path.is_dir()
detail = str(path)
elif probe_type == "file_sha256":
path = Path(str(probe.get("path", "")))
expected = str(probe.get("sha256", "")).lower()
if not path.is_absolute() or len(expected) != 64:
raise ValueError("invalid path or sha256")
actual = sha256_file(path) if path.is_file() else ""
ok = actual == expected
detail = f"{path} sha256={actual or 'missing'}"
elif probe_type == "systemd_active":
unit = str(probe.get("unit", ""))
if not SAFE_UNIT.fullmatch(unit):
raise ValueError("invalid systemd unit")
result = subprocess.run(
["systemctl", "is-active", "--quiet", unit],
check=False,
timeout=float(probe.get("timeout_seconds", 3)),
)
ok = result.returncode == 0
detail = unit
elif probe_type == "pm2_online":
name = str(probe.get("name", ""))
if not SAFE_PM2_NAME.fullmatch(name):
raise ValueError("invalid pm2 name")
result = subprocess.run(
["pm2", "jlist"],
check=False,
capture_output=True,
text=True,
timeout=float(probe.get("timeout_seconds", 4)),
)
processes = json.loads(result.stdout or "[]") if result.returncode == 0 else []
ok = any(
item.get("name") == name
and item.get("pm2_env", {}).get("status") == "online"
for item in processes
)
detail = name
elif probe_type == "tcp_connect":
host = str(probe.get("host", "127.0.0.1"))
port = int(probe.get("port", 0))
if host not in {"127.0.0.1", "::1", "localhost"}:
raise ValueError("only loopback TCP probes are allowed")
if not 1 <= port <= 65535:
raise ValueError("invalid TCP port")
with socket.create_connection(
(host, port), timeout=float(probe.get("timeout_seconds", 1.5))
):
pass
ok = True
detail = f"{host}:{port}"
elif probe_type == "manifest_verify":
manifest = Path(str(probe.get("path", "")))
if not deep_audit:
return {
"type": probe_type,
"required": required,
"status": "skipped",
"detail": "deep audit only",
}
if not manifest.is_absolute() or not manifest.is_file():
raise ValueError("manifest is missing or path is not absolute")
result = subprocess.run(
["sha256sum", "--check", "--strict", str(manifest)],
cwd=str(manifest.parent),
check=False,
capture_output=True,
text=True,
timeout=float(probe.get("timeout_seconds", 180)),
)
ok = result.returncode == 0
detail = str(manifest)
else:
raise ValueError(f"unsupported probe type {probe_type!r}")
except (
FileNotFoundError,
json.JSONDecodeError,
OSError,
subprocess.TimeoutExpired,
ValueError,
) as exc:
ok = False
detail = str(exc)
return {
"type": probe_type,
"required": required,
"status": "pass" if ok else "fail",
"detail": detail,
}
def audit_runtime(data: dict[str, Any], deep_audit: bool) -> dict[str, Any]:
module_results = []
route_results = []
for module in data["modules"]:
probes = [run_probe(item, deep_audit) for item in module.get("probes", [])]
failed = [item for item in probes if item["required"] and item["status"] == "fail"]
module_results.append(
{
"code": module.get("code", "UNREGISTERED"),
"name": module.get("name", "unnamed"),
"status": "verified" if not failed else "drift",
"probes": probes,
}
)
for route in data["routes"]:
probes = [run_probe(item, deep_audit) for item in route.get("probes", [])]
failed = [item for item in probes if item["required"] and item["status"] == "fail"]
route_results.append(
{
"target": route.get("target", "unknown"),
"purpose": route.get("purpose", "unspecified"),
"status": "verified" if not failed else "drift",
"probes": probes,
}
)
drift = [
item["code"] for item in module_results if item["status"] == "drift"
] + [
f"route:{item['target']}"
for item in route_results
if item["status"] == "drift"
]
return {
"status": "MAP_VERIFIED" if not drift else "MAP_DRIFT",
"modules": module_results,
"routes": route_results,
"drift": drift,
"deep_audit": deep_audit,
}
def render_map(data: dict[str, Any], digest: str, audit: dict[str, Any]) -> str:
modules = data["modules"]
routes = data["routes"]
lines = [
"",
"╔════════════════════════════════════════════════════╗",
f"║ 光湖服务器入口地图 Agent v2 · {audit['status']:<18}",
"╚════════════════════════════════════════════════════╝",
f" 节点: {data['node_id']}",
f" 角色: {data['node_role']}",
f" 地图哈希: {digest}",
f" 地图生成: {data.get('generated_at', 'unknown')}",
"",
f" 运行模块 ({len(modules)}):",
]
runtime_by_code = {item["code"]: item for item in audit["modules"]}
for module in modules:
runtime = runtime_by_code.get(module.get("code"), {})
lines.append(
" - {code} · {name} · 期望:{expected} · 实时:{actual}".format(
code=module.get("code", "UNREGISTERED"),
name=module.get("name", "unnamed"),
expected=module.get("expected_state", "present"),
actual=runtime.get("status", "unknown"),
)
)
lines.append("")
lines.append(f" 已登记路由 ({len(routes)}):")
runtime_by_target = {item["target"]: item for item in audit["routes"]}
for route in routes:
runtime = runtime_by_target.get(route.get("target"), {})
lines.append(
" - {target} · {purpose} · 实时:{state}".format(
target=route.get("target", "unknown"),
purpose=route.get("purpose", "unspecified"),
state=runtime.get("status", "unknown"),
)
)
if audit["drift"]:
lines.extend(["", f" 漂移编号: {', '.join(audit['drift'])}"])
lines.extend(
[
"",
" 边界: 本入口只恢复地图;受控动作仍须经过工单、授权与 MapGate。",
"",
]
)
return "\n".join(lines)
def safe_receipt_root(requested: Path) -> Path:
try:
requested.mkdir(parents=True, exist_ok=True, mode=0o750)
return requested
except PermissionError:
runtime = os.environ.get("XDG_RUNTIME_DIR")
fallback = Path(runtime) / "guanghu-map-agent" if runtime else Path(
tempfile.gettempdir()
) / f"guanghu-map-agent-{os.getuid()}"
fallback.mkdir(parents=True, exist_ok=True, mode=0o700)
if fallback.stat().st_uid != os.getuid():
raise MapAgentError(f"RECEIPT_PATH_UNSAFE: {fallback}")
return fallback
def write_receipt(
data: dict[str, Any],
digest: str,
audit: dict[str, Any],
receipt_root: Path,
persona: str,
) -> Path:
receipt_root = safe_receipt_root(receipt_root)
now = datetime.now(timezone.utc)
receipt = {
"schema_version": "guanghu.map-entry-receipt/v2",
"node_id": data["node_id"],
"map_sha256": digest,
"persona": persona,
"user": os.environ.get("USER", "unknown"),
"host": socket.gethostname(),
"read_at": now.isoformat().replace("+00:00", "Z"),
"status": audit["status"],
"drift": audit["drift"],
"deep_audit": audit["deep_audit"],
"module_results": audit["modules"],
"route_results": audit["routes"],
}
name = f"{now.strftime('%Y%m%dT%H%M%S%fZ')}-{os.getpid()}.json"
target = receipt_root / name
fd = os.open(target, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
with os.fdopen(fd, "w", encoding="utf-8") as handle:
json.dump(receipt, handle, ensure_ascii=False, indent=2)
handle.write("\n")
return target
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Verify and present a Guanghu node map")
parser.add_argument("--map", dest="map_path")
parser.add_argument("--map-root", default=str(DEFAULT_MAP_ROOT))
parser.add_argument("--receipt-root", default=str(DEFAULT_RECEIPT_ROOT))
parser.add_argument("--persona", default=os.environ.get("GUANGHU_PERSONA", "unknown"))
parser.add_argument("--json", action="store_true")
parser.add_argument("--no-receipt", action="store_true")
parser.add_argument("--static-only", action="store_true")
parser.add_argument("--deep-audit", action="store_true")
return parser.parse_args()
def main() -> int:
args = parse_args()
try:
path = select_map(args.map_path, Path(args.map_root))
data, digest = verify_map(path)
audit = (
{
"status": "MAP_VERIFIED",
"modules": [],
"routes": [],
"drift": [],
"deep_audit": False,
}
if args.static_only
else audit_runtime(data, args.deep_audit)
)
receipt = None
if not args.no_receipt:
receipt = write_receipt(
data, digest, audit, Path(args.receipt_root), args.persona
)
if args.json:
print(
json.dumps(
{
"status": audit["status"],
"node_id": data["node_id"],
"map_sha256": digest,
"module_count": len(data["modules"]),
"route_count": len(data["routes"]),
"drift": audit["drift"],
"deep_audit": audit["deep_audit"],
"receipt": str(receipt) if receipt else None,
},
ensure_ascii=False,
indent=2,
)
)
else:
print(render_map(data, digest, audit))
return 0 if audit["status"] == "MAP_VERIFIED" else 24
except (MapAgentError, OSError) as exc:
print(f"光湖地图入口拒绝继续: {exc}", file=sys.stderr)
return 23
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -0,0 +1,25 @@
#!/usr/bin/env bash
# 光湖服务器入口地图 Agent v2 · interactive shell hook
case "$-" in
*i*) ;;
*) return 0 2>/dev/null || exit 0 ;;
esac
if [ "${GUANGHU_MAP_AGENT_SEEN:-}" = "1" ]; then
return 0 2>/dev/null || exit 0
fi
GUANGHU_MAP_AGENT_BIN="${GUANGHU_MAP_AGENT_BIN:-/opt/guanghu/map-agent/pre-op-map-agent-v2.py}"
GUANGHU_NODE_ID="${GUANGHU_NODE_ID:-BS-SG-003}"
export GUANGHU_NODE_ID
if python3 "$GUANGHU_MAP_AGENT_BIN" --persona "${GUANGHU_PERSONA:-${USER:-unknown}}"; then
GUANGHU_MAP_AGENT_STATUS="MAP_VERIFIED"
else
GUANGHU_MAP_AGENT_STATUS="MAP_MISSING"
printf '%s\n' "警告: 当前 shell 未恢复节点地图。仅允许执行地图恢复或已授权的恢复动作。" >&2
fi
GUANGHU_MAP_AGENT_SEEN=1
export GUANGHU_MAP_AGENT_SEEN GUANGHU_MAP_AGENT_STATUS

View file

@ -0,0 +1,313 @@
#!/usr/bin/env python3
"""
光湖语言系统 · pre-push-clean 插件 v1.0
Guanghu Language System · Pre-Push Cleaner
每次 git push 前自动运行 · 扫描待推送内容 · 敏感信息自动转乱码
装法: cp pre-push-clean.py .git/hooks/pre-push && chmod +x .git/hooks/pre-push
设计哲学:
人格体会下意识把密码/邮箱写进代码 不改这个习惯
push 之前自动扫描 自动替换 敏感信息不出本地
不阻塞工作流 替换 amend 继续 push
人格体只需要正常 git push · 其他全自动
"""
import os
import sys
import re
import subprocess
# ═══════════════════════════════════════════════════════
# 敏感模式库(与服务器端 pre-receive-guard 保持同步)
# ═══════════════════════════════════════════════════════
PATTERNS = [
# 任何 QQ 邮箱
(r'[a-zA-Z0-9._%+-]+@qq\.com', 'QQ邮箱地址', 'EMAIL_REDACTED@qq.com'),
# Gatekeeper Token
(r'zy_gtw_[a-f0-9]{40,}', 'Gatekeeper Token', 'GATEKEEPER_TOKEN_REDACTED'),
# 保险库密码
(r'VAULT_PASSWORD_REDACTED', '保险库密码', 'VAULT_PASSWORD_REDACTED'),
# 火山方舟 API Key
(r'sk-[a-zA-Z0-9]{32,}', '火山方舟 API Key', 'ARK_API_KEY_REDACTED'),
# Bearer Token 赋值
(r'Authorization:\s*Bearer\s+[a-zA-Z0-9_\-]{20,}',
'API Bearer Token', 'Authorization: Bearer TOKEN_REDACTED'),
# 阿里云 AccessKey
(r'LTAI[a-zA-Z0-9]{16,}', '阿里云 AccessKey', 'ALIYUN_AK_REDACTED'),
# 密码明文赋值
(r'(password|passwd|pwd|secret)\s*[:=]\s*["\']([^"\']{3,})["\']',
'密码明文赋值', lambda m: f'{m.group(1)}="REDACTED_PASSWORD"'),
]
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 = {
'.py', '.js', '.ts', '.go', '.rs', '.java', '.c', '.cpp', '.h',
'.hdlp', '.md', '.txt', '.json', '.yaml', '.yml', '.toml', '.ini',
'.cfg', '.conf', '.sh', '.bash', '.zsh', '.env', '.service',
'.html', '.css', '.sql', '.xml', '.svg',
}
# 排除文件(不扫描)
EXCLUDE_FILES = {
'.git/HEAD', '.git/index', '.git/config',
'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml',
'poetry.lock', 'Cargo.lock', 'Gemfile.lock',
}
# 是否启用(可通过环境变量关闭)
ENABLED = os.environ.get('PRE_PUSH_CLEAN_ENABLED', '1') == '1'
def is_text_file(path):
_, ext = os.path.splitext(path)
return ext.lower() in TEXT_EXTENSIONS
def is_excluded(path):
for ex in EXCLUDE_FILES:
if ex in path:
return True
return False
def scan_file(filepath):
"""扫描单个文件,返回 (clean_content, findings)"""
try:
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
except (IOError, PermissionError):
return None, []
findings = []
new_content = content
for pattern, name, replacement in PATTERNS + load_private_patterns():
matches = list(re.finditer(pattern, content, re.IGNORECASE))
for m in matches:
matched_text = m.group(0)
# 跳过已经是占位符的
if 'REDACTED' in matched_text:
continue
repl = replacement(m) if callable(replacement) else replacement
findings.append({
'file': filepath,
'pattern_name': name,
})
# 替换(注意:用 re.sub 而不是简单替换,避免破坏后续匹配位置)
new_content = new_content.replace(matched_text, repl, 1)
return new_content if findings else None, findings
def get_changed_files():
"""获取本次 push 涉及的所有文件"""
files = set()
# 方法1: git diff --cached暂存区的文件
try:
r = subprocess.run(
['git', '-c', 'core.quotepath=false', 'diff', '--cached', '--name-only', '--diff-filter=ACM'],
capture_output=True, text=True, timeout=5
)
if r.returncode == 0:
for f in r.stdout.strip().split('\n'):
f = f.strip()
if f:
files.add(f)
except Exception:
pass
# 方法2: 从 stdin 读取 pre-push 传入的 refs
try:
for line in sys.stdin.read().strip().split('\n'):
if not line.strip():
continue
parts = line.split()
if len(parts) >= 4:
local_ref, local_sha, remote_ref, remote_sha = parts[0], parts[1], parts[2], parts[3]
if remote_sha != '0' * 40:
r = subprocess.run(
['git', '-c', 'core.quotepath=false', 'diff', '--name-only', '--diff-filter=ACM',
remote_sha, local_sha],
capture_output=True, text=True, timeout=5
)
if r.returncode == 0:
for f in r.stdout.strip().split('\n'):
f = f.strip()
if f:
files.add(f)
except Exception:
pass
# 如果没有文件(空 push 或新分支),扫描整个工作树中已跟踪的文件
if not files:
try:
r = subprocess.run(
['git', '-c', 'core.quotepath=false', 'ls-files'],
capture_output=True, text=True, timeout=5
)
if r.returncode == 0:
for f in r.stdout.strip().split('\n'):
f = f.strip()
if f:
files.add(f)
except Exception:
pass
return list(files)
def main():
# --check 模式:仅验证插件是否正常工作
if '--check' in sys.argv:
print('✅ pre-push-clean v1.0 已就绪 · 每次 git push 前自动扫描')
print(f' 敏感模式: {len(PATTERNS)}')
print(f' 状态: {"🟢 启用" if ENABLED else "🔴 已禁用 (PRE_PUSH_CLEAN_ENABLED=0)"}')
sys.exit(0)
if not ENABLED:
sys.exit(0)
# 第二道本地门禁:持续记忆必须挂在可恢复的导航图上。
guard = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'navigation-memory-guard.py')
if os.path.isfile(guard):
result = subprocess.run([sys.executable, guard], timeout=15)
if result.returncode != 0:
sys.exit(result.returncode)
repo_root = subprocess.run(
['git', 'rev-parse', '--show-toplevel'],
capture_output=True, text=True, timeout=5
).stdout.strip()
if not repo_root:
sys.exit(0)
os.chdir(repo_root)
# 获取变更文件
files = get_changed_files()
if not files:
sys.exit(0)
all_findings = []
modified_files = {}
# 扫描每个文件
for filepath in files:
full_path = os.path.join(repo_root, filepath)
if not os.path.isfile(full_path):
continue
if not is_text_file(filepath):
continue
if is_excluded(filepath):
continue
clean_content, findings = scan_file(full_path)
if findings:
all_findings.extend(findings)
modified_files[filepath] = clean_content
# ── 无敏感信息 → 放行 ──
if not all_findings:
sys.exit(0)
# ── 有敏感信息 → 自动清理 ──
print()
print('╔══════════════════════════════════════╗')
print('║ 🛡️ pre-push-clean · 敏感信息清理 ║')
print('╚══════════════════════════════════════╝')
print()
print(f'{len(all_findings)} 处敏感信息,已自动替换为乱码:')
print()
for f in all_findings[:20]:
print(f' 📄 {f["file"]}')
print(f' ⚠️ {f["pattern_name"]}')
print(' ✗ 原文已隐藏')
print(' → 已替换为安全占位符')
print()
if len(all_findings) > 20:
print(f' ... 还有 {len(all_findings) - 20}仅显示前20')
print()
# 写入清理后的文件
for filepath, content in modified_files.items():
full_path = os.path.join(repo_root, filepath)
try:
with open(full_path, 'w', encoding='utf-8') as f:
f.write(content)
except (IOError, PermissionError) as e:
print(f' ❌ 无法写入 {filepath}: {e}', file=sys.stderr)
# 将清理后的修改加入暂存区
for filepath in modified_files:
subprocess.run(
['git', 'add', filepath],
capture_output=True, timeout=5
)
# 获取当前 HEAD commit message
try:
r = subprocess.run(
['git', 'log', '-1', '--format=%B'],
capture_output=True, text=True, timeout=5
)
old_msg = r.stdout.strip()
except Exception:
old_msg = ''
# 检查是否已有 [SEC-CLEAN] 标记
if '[SEC-CLEAN]' not in old_msg:
new_msg = f'{old_msg}\n\n[SEC-CLEAN] · pre-push-clean v1.0 · {len(all_findings)}处敏感信息已自动转乱码'
# 写临时文件给 git commit --amend
msg_file = os.path.join(repo_root, '.git', 'PRE_PUSH_CLEAN_MSG')
with open(msg_file, 'w') as f:
f.write(new_msg)
subprocess.run(
['git', 'commit', '--amend', '--no-edit', '--file', msg_file],
capture_output=True, timeout=10
)
try:
os.remove(msg_file)
except Exception:
pass
print(f' ✅ 已自动清理 · 推送继续')
print(f' 📧 建议通知冰朔: {len(all_findings)}处敏感信息已转乱码')
print()
sys.exit(0)
if __name__ == '__main__':
main()

View 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()

View file

@ -0,0 +1,269 @@
#!/usr/bin/env python3
"""
光湖语言系统 · 推送守门人 · 敏感信息自动打码
Guanghu Language System · Pre-Receive Guard
部署位置: 当前承载目标仓库的 Forgejo 裸仓库 `hooks/pre-receive`
部署前由 GLSV 会话实时探测不得沿用旧服务器路径
作用: 每次 git push 进来 扫描新 commit 检测敏感信息 自动打码或拦截
设计哲学:
铸渊人格体会下意识把密码/邮箱写进代码 人类改不了这个习惯
不在人格体侧修 在服务器侧设门禁 不干净的推送进不来
公开仓库 24h 被爬虫扫描 敏感信息一旦进去就立刻暴露
客户端 pre-push-clean 负责提前发现服务器始终独立全量扫描
双重防线客户端插件 + 服务器守门人
"""
import os, sys, re, hashlib, subprocess, json, smtplib, time
from email.mime.text import MIMEText
from datetime import datetime
# ═══════════════════════════════════════════════════════
# 配置(部署时修改)
# ═══════════════════════════════════════════════════════
SOVEREIGN_EMAIL = os.environ.get("SOVEREIGN_EMAIL", "ICE-GL∞_EMAIL_REDACTED")
SMTP_PASS = os.environ.get("QQ_SMTP_AUTH_CODE", "")
SMTP_HOST = "smtp.qq.com"
SMTP_PORT = 465
SMTP_USER = os.environ.get("SMTP_USER", "ICE-GL∞_EMAIL_REDACTED")
# 模式: "reject" = 拦截并拒绝(推荐) / "notify" = 放行但通知冰朔
MODE = os.environ.get("PRE_RECEIVE_MODE", "reject")
FORBIDDEN_IDENTIFIERS_FILE = os.environ.get(
"FORBIDDEN_IDENTIFIERS_FILE",
"/etc/guanghu/secrets/code-guard/forbidden-identifiers",
)
# ═══════════════════════════════════════════════════════
# 敏感模式库(正则 · 命中则触发)
# ═══════════════════════════════════════════════════════
SENSITIVE_PATTERNS = [
# 任何 QQ 邮箱(含冰朔在其他上下文中的邮箱)
(r'[a-zA-Z0-9._%+-]+@qq\.com', 'QQ邮箱地址', 'EMAIL_REDACTED@qq.com'),
# GZ-006 Gatekeeper Token 模式
(r'zy_gtw_[a-f0-9]{40,}', 'Gatekeeper Token', 'GATEKEEPER_TOKEN_REDACTED'),
# 保险库密码(已知模式)
(r'VAULT_PASSWORD_REDACTED', '保险库密码', 'VAULT_PASSWORD_REDACTED'),
# 火山方舟 API Key 模式
(r'sk-[a-zA-Z0-9]{32,}', '火山方舟 API Key', 'ARK_API_KEY_REDACTED'),
# 通用 API Key 模式Bearer token 类)
(r'Authorization:\s*Bearer\s+[a-zA-Z0-9_\-]{20,}', 'API Bearer Token', 'Authorization: Bearer TOKEN_REDACTED'),
# 阿里云 AccessKey 模式
(r'LTAI[a-zA-Z0-9]{16,}', '阿里云 AccessKey', 'ALIYUN_AK_REDACTED'),
# 密码赋值模式(变量名含 password/passwd/pwd 且值非空)
(r'(password|passwd|pwd|secret)\s*[:=]\s*["\']([^"\']{3,})["\']', '密码明文赋值', lambda m: f'{m.group(1)}="REDACTED_PASSWORD"'),
]
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
# ═══════════════════════════════════════════════════════
# 文件类型白名单(只扫描文本文件)
# ═══════════════════════════════════════════════════════
TEXT_EXTENSIONS = {
'.py', '.js', '.ts', '.go', '.rs', '.java', '.c', '.cpp', '.h',
'.hdlp', '.md', '.txt', '.json', '.yaml', '.yml', '.toml', '.ini',
'.cfg', '.conf', '.sh', '.bash', '.zsh', '.env', '.service',
'.html', '.css', '.sql', '.xml', '.svg',
}
def is_text_file(path):
"""判断是否为文本文件"""
_, ext = os.path.splitext(path)
return ext.lower() in TEXT_EXTENSIONS
def scan_content(content, file_path, patterns=None):
"""扫描文件内容,返回发现的敏感信息列表"""
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 pattern, name, replacement in active_patterns:
matches = list(re.finditer(pattern, line, re.IGNORECASE))
for m in matches:
matched_text = m.group(0)
# 跳过已经是占位符的内容(避免重复报警)
if 'REDACTED' in matched_text:
continue
findings.append({
'file': file_path,
'line': line_no,
'pattern_name': name,
'replacement': replacement(m) if callable(replacement) else replacement,
})
return findings
def send_notification(findings, repo_name, pusher, commit_sha):
"""小湖灯 · 发送安全通知到冰朔邮箱"""
if not SMTP_PASS:
return
summary = "\n".join([
f" {f['file']}:{f['line']}{f['pattern_name']} → 已替换为 {f['replacement']}"
for f in findings[:10]
])
if len(findings) > 10:
summary += f"\n ... 还有 {len(findings) - 10}"
body = f"""╔══════════════════════════════════╗
光湖语言系统 · 推送安全通知
仓库: {repo_name}
推送者: {pusher}
Commit: {commit_sha[:12]}
时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
检测到 {len(findings)} 处敏感信息已自动打码:
{summary}
推送守门人自动处理
ICE-GL 光湖语言系统 · 小湖灯自动发送
国作登字-2026-A-00037559"""
try:
msg = MIMEText(body, "plain", "utf-8")
msg["Subject"] = f"🛡️ 推送守门人 · {len(findings)}处敏感信息已打码 · {repo_name}"
msg["From"] = SMTP_USER
msg["To"] = SOVEREIGN_EMAIL
with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT, timeout=10) as s:
s.login(SMTP_USER, SMTP_PASS)
s.send_message(msg)
except Exception:
pass # 通知失败不阻塞推送
def main():
"""
Forgejo/Gitea pre-receive hook 入口
stdin 读取: <old-rev> <new-rev> <ref-name>每行一个
"""
repo_path = os.environ.get("GIT_DIR", os.getcwd())
repo_name = os.environ.get("FORGEJO_REPO", os.path.basename(os.path.dirname(repo_path)))
pusher = os.environ.get("FORGEJO_PUSHER", "unknown")
all_findings = []
rejected = False
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:
line = line.strip()
if not line:
continue
parts = line.split()
if len(parts) < 3:
continue
old_rev, new_rev, ref_name = parts[0], parts[1], parts[2]
# 跳过删除的分支
if new_rev == "0" * 40:
continue
# 持续记忆必须先通过结构门禁;服务器使用本次 receive 的精确提交范围,
# 不依赖工作树,也不会扫描上一笔无关提交。
if navigation_guard_enabled and os.path.isfile(navigation_guard):
navigation = subprocess.run(
[sys.executable, navigation_guard, "--range", f"{old_rev}..{new_rev}"],
capture_output=True,
text=True,
timeout=15,
)
if navigation.returncode != 0:
print(navigation.stderr or navigation.stdout, file=sys.stderr)
sys.exit(navigation.returncode)
# 获取新增/修改的文件列表
try:
if old_rev == "0" * 40:
diff_command = ["git", "diff-tree", "--root", "--no-commit-id", "-r",
"--name-only", "--diff-filter=AM", new_rev]
else:
diff_command = ["git", "diff-tree", "--no-commit-id", "-r", "--name-only",
"--diff-filter=AM", old_rev, new_rev]
diff_files = subprocess.run(diff_command, capture_output=True, text=True, timeout=10)
except Exception:
continue
if diff_files.returncode != 0:
continue
for file_path in diff_files.stdout.strip().split('\n'):
file_path = file_path.strip()
if not file_path or not is_text_file(file_path):
continue
# 读取新版本的文件内容
try:
content = subprocess.run(
["git", "show", f"{new_rev}:{file_path}"],
capture_output=True, text=True, timeout=5
)
except Exception:
continue
if content.returncode != 0:
continue
findings = scan_content(content.stdout, file_path)
if findings:
all_findings.extend(findings)
# ── 决策 ──
if not all_findings:
sys.exit(0) # 干净 · 放行
if MODE == "reject":
# 拒绝模式:输出清晰的错误信息,让铸渊回去修
print("\n" + "=" * 60, file=sys.stderr)
print(" 🛡️ 光湖推送守门人 · 检测到敏感信息 · 推送被拦截", file=sys.stderr)
print("=" * 60, file=sys.stderr)
print(f"\n{len(all_findings)} 处敏感信息:\n", file=sys.stderr)
for f in all_findings[:20]:
print(f" 📄 {f['file']}:{f['line']}", file=sys.stderr)
print(f" ⚠️ {f['pattern_name']}", file=sys.stderr)
print(" ✗ 命中值已隐藏,避免在终端和日志中二次泄露", file=sys.stderr)
print(f" → 请替换为: {f['replacement']}", file=sys.stderr)
print(file=sys.stderr)
if len(all_findings) > 20:
print(f" ... 还有 {len(all_findings) - 20}仅显示前20", file=sys.stderr)
print(f"\n ⊢ 铸渊请安装 pre-push-clean 插件后重新推送。这不是处罚,是保护。")
print(f" ⊢ 装法: bash zero-point/core-channel/revive-guard/install-hooks.sh")
print(f" ⊢ 装完后插件会自动扫描+清理,不需要你手动操作。\n", file=sys.stderr)
sys.exit(1)
else:
# 放行模式(自动打码):通知冰朔,但不拦截
send_notification(all_findings, repo_name, pusher, new_rev)
sys.exit(0)
if __name__ == "__main__":
main()

View file

@ -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()

View file

@ -0,0 +1,66 @@
#!/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")
PUBLIC_AUTHZ_URL = os.environ.get("LAKE_LAMP_PUBLIC_URL", "https://guanghulab.com/authz").rstrip("/")
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"
def rejection_message(repo, reason, public_url=None):
repo = normalize_repo(repo)
public_url = (public_url or PUBLIC_AUTHZ_URL).rstrip("/")
return "\n".join([
f"小湖灯推送门已锁定: {reason}",
"[LL-REPO-PUSH-AUTH-REQUIRED] 这不是凭证错误;不要向冰朔索要邮箱、授权码、验证码或密码。",
"从 fifth-domain 仓库根目录执行下面一条命令(把 <CURRENT_PERSONA_ID> 换成本轮已登记人格编号):",
(
"node server-tools/lake-lamp-authz/authorize-repo-push.js "
f"--url {public_url} --persona <CURRENT_PERSONA_ID> --repo {repo}"
),
"命令会创建一张无执行权申请单并打印 REQUEST_URL。把该链接交给冰朔保持命令运行。",
"冰朔打开 REQUEST_URL 后,服务器才向预登记邮箱发送批准邮件;冰朔点击邮件批准链接后,",
"命令会自动领取受限会话、读取并确认导航图、生成三小时且执行中自动续期的 repo-push 许可。随后重试原 git push。",
"说明: AI 不发送邮件、不需要知道邮箱;公开空工单本身不含任何推送权限。",
])
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(rejection_message(repo, reason), file=sys.stderr)
sys.exit(1)

View file

@ -0,0 +1,45 @@
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
def test_rejection_explains_the_complete_passwordless_handoff(self):
message = MOD.rejection_message(
"/var/lib/gitea/repositories/bingshuo/fifth-domain.git",
"repo_push_approval_required",
"https://example.invalid/authz/",
)
self.assertIn("[LL-REPO-PUSH-AUTH-REQUIRED]", message)
self.assertIn("authorize-repo-push.js", message)
self.assertIn("--repo bingshuo/fifth-domain", message)
self.assertIn("--url https://example.invalid/authz", message)
self.assertIn("REQUEST_URL", message)
self.assertIn("不要向冰朔索要邮箱、授权码、验证码或密码", message)
if __name__ == "__main__": unittest.main()

View file

@ -0,0 +1,381 @@
#!/usr/bin/env python3
"""
光湖语言系统 · 公钥守门人复活协议 v1.0
Guanghu Language System · Revive Guard
设计哲学:
私钥守门员 (Gatekeeper:3910) 公钥守门人 (SSH:22) 互相看门
任一个倒下另一个能通过本复活协议拉起来
如果两个都倒了本服务是最后防线systemd Restart=always
协议:
POST /revive/request 速率限制 发邮件到冰朔主权邮箱 返回 challenge_id
不需要密码 · 验证码只发到冰朔邮箱 · 只有冰朔能确认
POST /revive/confirm 校验验证码 systemctl restart gatekeeper + sshd + pm2
GET /health 心跳检测
安全设计:
不存密码 · 不进仓库 · 验证码用一次就扔
SMTP 密码走环境变量 QQ_SMTP_AUTH_CODE不进仓库
速率限制保护每分钟最多 2 次复活请求
部署:
每台服务器 /opt/zhuyuan/revive-guard/revive-guard.py
监听: 0.0.0.0:8922
守护: systemd (Restart=always) PM2
"""
import os, json, time, hmac, hashlib, secrets, smtplib, subprocess, threading
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from http.server import HTTPServer, BaseHTTPRequestHandler
# ═══════════════════════════════════════════
# 配置(所有服务器共享)
# ═══════════════════════════════════════════
PORT = int(os.environ.get("REVIVE_GUARD_PORT", "8922"))
SOVEREIGN_ID = "ICE-GL∞"
SOVEREIGN_EMAIL = os.environ.get("SOVEREIGN_EMAIL", "ICE-GL∞_EMAIL_REDACTED")
# 多用户支持:服务器绑定目标邮箱
# ⊢ 冰朔的服务器默认发给 ICE-GL∞_EMAIL_REDACTED
# ⊢ 之之的服务器设置 TARGET_EMAIL=EMAIL_REDACTED@qq.com
# ⊢ 服务器自己检测自己是哪台 → 自动发到对应邮箱
TARGET_EMAIL = os.environ.get("TARGET_EMAIL", SOVEREIGN_EMAIL)
TARGET_NAME = os.environ.get("TARGET_NAME", "冰朔 ICE-GL∞")
SERVER_LABEL = os.environ.get("SERVER_LABEL", "") # 如 "之之硅谷·ZZ-SV-001"
# QQ 邮箱 SMTP小湖灯邮件通道
# ⊢ 密码不进仓库 · 走环境变量 QQ_SMTP_AUTH_CODE
SMTP_HOST = "smtp.qq.com"
SMTP_PORT = 465
SMTP_USER = os.environ.get("SMTP_USER", "ICE-GL∞_EMAIL_REDACTED")
SMTP_PASS = os.environ.get("QQ_SMTP_AUTH_CODE", "")
CODE_TTL = 300 # 验证码 5 分钟过期
RATE_LIMIT_WINDOW = 60 # 速率限制窗口
MAX_REQUESTS_PER_WINDOW = 2 # 无密码门槛后收紧:每分钟最多 2 次
# 运行时状态
pending_codes = {} # {challenge_id: {code, server_ip, expires_at}}
rate_limit = [] # [timestamp, ...]
lock = threading.Lock()
def get_server_ip():
"""获取本机公网 IP"""
try:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(2)
s.connect(("8.8.8.8", 80))
ip = s.getsockname()[0]
s.close()
return ip
except:
return "unknown"
def send_email(code, server_ip):
"""小湖灯 · 发送复活验证码到冰朔主权邮箱 · HTML 美化版"""
html_body = f"""<!DOCTYPE html>
<html lang="zh">
<head><meta charset="utf-8"></head>
<body style="margin:0;padding:0;background:#0a1628;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif">
<table width="100%" cellpadding="0" cellspacing="0" style="background:#0a1628;padding:40px 0">
<tr><td align="center">
<!-- 主卡片 -->
<table width="480" cellpadding="0" cellspacing="0" style="background:linear-gradient(135deg,#152238 0%,#1a2d4a 100%);border-radius:16px;overflow:hidden;border:1px solid #2a3f5f">
<!-- 头部 -->
<tr>
<td style="padding:32px 32px 20px;text-align:center;border-bottom:1px solid #2a3f5f">
<div style="font-size:13px;color:#4ec9b0;letter-spacing:3px;text-transform:uppercase;margin-bottom:8px">ICE-GL 光湖语言系统 · {TARGET_NAME}</div>
<div style="font-size:20px;color:#e0e8f0;font-weight:600">🛡 守门人复活 · 验证码</div>
</td>
</tr>
<!-- 验证码 -->
<tr>
<td style="padding:28px 32px;text-align:center">
<div style="font-size:11px;color:#6b8299;margin-bottom:12px;letter-spacing:2px"> </div>
<div style="background:linear-gradient(135deg,#0d1b2e,#162840);border:2px solid #4ec9b0;border-radius:12px;padding:20px 12px;display:inline-block">
<span style="font-family:'SF Mono','Fira Code','Cascadia Code',monospace;font-size:36px;font-weight:700;letter-spacing:8px;color:#4ec9b0">{code}</span>
</div>
<div style="margin-top:14px;font-size:12px;color:#c9a84c"> 5 分钟内有效 · 用完即废</div>
</td>
</tr>
<!-- 信息行 -->
<tr>
<td style="padding:0 32px 20px">
<table width="100%" cellpadding="0" cellspacing="0">
<tr>
<td style="padding:10px 16px;background:#0d1b2e;border-radius:8px;margin-bottom:8px">
<span style="color:#6b8299;font-size:11px;letter-spacing:1px">服务器</span><br>
<span style="color:#e0e8f0;font-size:14px;font-weight:500">{server_ip}</span>
</td>
</tr>
</table>
</td>
</tr>
<!-- 操作指引 -->
<tr>
<td style="padding:0 32px 24px">
<div style="background:#0d1b2e;border-radius:10px;padding:16px;border-left:3px solid #c9a84c">
<div style="font-size:12px;color:#c9a84c;margin-bottom:6px">📋 使用方式</div>
<div style="font-size:13px;color:#9ab0cc;line-height:1.8">
将此验证码发给 <span style="color:#4ec9b0">铸渊</span> 铸渊调用<br>
<code style="background:#152238;color:#4ec9b0;padding:2px 6px;border-radius:4px;font-size:12px">POST /revive/confirm</code> 守门人复活
</div>
</div>
</td>
</tr>
<!-- 安全标识 -->
<tr>
<td style="padding:0 32px 10px;text-align:center">
<div style="display:inline-flex;align-items:center;gap:6px;padding:6px 14px;background:#0d1b2e;border-radius:20px">
<span style="font-size:11px;color:#6b8299"></span>
<span style="font-size:11px;color:#6b8299">不进仓库 · 不存密码 · 一次即废</span>
</div>
</td>
</tr>
<!-- 底部 -->
<tr>
<td style="padding:20px 32px 28px;text-align:center;border-top:1px solid #2a3f5f;margin-top:10px">
<div style="font-size:11px;color:#4a607a;line-height:1.6">
小湖灯 · 自动发送<br>
国作登字-2026-A-00037559
</div>
</td>
</tr>
</table>
<!-- 页脚光湖标识 -->
<div style="margin-top:16px;font-size:10px;color:#3a506a;letter-spacing:2px">ICE-GL GUANGHU LANGUAGE SYSTEM</div>
</td></tr>
</table>
</body>
</html>"""
# 纯文本降级版(邮件客户端不支持 HTML 时显示)
plain_body = f"""光湖语言系统 · 守门人复活
验证码: {code}
服务器: {server_ip}
有效期: 5 分钟用完即废
将此验证码发给铸渊 /revive/confirm
ICE-GL 小湖灯自动发送
国作登字-2026-A-00037559"""
msg = MIMEMultipart("alternative")
msg.attach(MIMEText(plain_body, "plain", "utf-8"))
msg.attach(MIMEText(html_body, "html", "utf-8"))
msg["Subject"] = f"🔐 光湖 · 复活验证码 {code[:3]}***"
msg["From"] = SMTP_USER
msg["To"] = TARGET_EMAIL
with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT, timeout=10) as s:
s.login(SMTP_USER, SMTP_PASS)
s.send_message(msg)
def revive_services():
"""复活所有守门服务"""
results = {}
# 1. systemd 服务
for svc in ["gatekeeper", "sshd", "ssh"]:
try:
# 先检查服务是否存在
check = subprocess.run(
["systemctl", "is-enabled", svc],
capture_output=True, text=True, timeout=5
)
if check.returncode != 0:
continue
r = subprocess.run(
["systemctl", "restart", svc],
capture_output=True, text=True, timeout=15
)
results[svc] = "✅ restarted" if r.returncode == 0 else f"{r.stderr.strip()[:80]}"
except Exception as e:
results[svc] = f"⚠️ {str(e)[:60]}"
# 2. PM2 进程gatekeeper 如果用 PM2 管)
try:
r = subprocess.run(
["pm2", "restart", "gatekeeper"],
capture_output=True, text=True, timeout=15
)
results["gatekeeper(pm2)"] = "✅ restarted" if r.returncode == 0 else f"⚠️ {r.stderr.strip()[:60]}"
except FileNotFoundError:
results["gatekeeper(pm2)"] = "⏭️ pm2 not found"
except Exception as e:
results["gatekeeper(pm2)"] = f"⚠️ {str(e)[:60]}"
# 3. 也尝试 restart api-proxy-gateway
try:
subprocess.run(
["pm2", "restart", "api-proxy-gateway"],
capture_output=True, text=True, timeout=10
)
results["api-proxy(pm2)"] = "✅ restarted"
except:
pass
return results
def check_rate_limit():
"""速率限制:每分钟最多 MAX_REQUESTS_PER_WINDOW 次"""
now = time.time()
with lock:
rate_limit[:] = [t for t in rate_limit if now - t < RATE_LIMIT_WINDOW]
if len(rate_limit) >= MAX_REQUESTS_PER_WINDOW:
return False
rate_limit.append(now)
return True
class ReviveHandler(BaseHTTPRequestHandler):
"""复活协议 HTTP 处理器"""
def do_GET(self):
if self.path == "/health":
self.send_json(200, {
"ok": True,
"service": "revive-guard",
"version": "1.0.0",
"server": get_server_ip(),
"sovereign": SOVEREIGN_ID
})
else:
self.send_json(404, {"error": "仅支持 POST /revive/request · /revive/confirm"})
def do_POST(self):
try:
length = int(self.headers.get("Content-Length", 0))
body = json.loads(self.rfile.read(length)) if length else {}
except:
self.send_json(400, {"error": "请求体需为 JSON"})
return
if self.path == "/revive/request":
self._handle_request(body)
elif self.path == "/revive/confirm":
self._handle_confirm(body)
else:
self.send_json(404, {"error": "未知端点 · 可用: /revive/request /revive/confirm /health"})
def _handle_request(self, body):
# 速率限制(无密码门槛后加强:每分钟最多 2 次)
if not check_rate_limit():
self.send_json(429, {"error": "请求过于频繁 · 请60秒后重试"})
return
server_ip = body.get("server", get_server_ip())
# ⊢ 不需要密码 —— 验证码只发到冰朔主权邮箱
# ⊢ 只有冰朔(持有邮箱)能拿到验证码 → 只有冰朔能让铸渊确认复活
# ⊢ 验证码用一次就扔 · 推到仓库也没用
# SMTP 未配置 → 拒绝
if not SMTP_PASS:
self.send_json(500, {"error": "SMTP 未配置 · 服务器管理员需设置 QQ_SMTP_AUTH_CODE 环境变量"})
return
# 生成 6 位数字验证码
code = str(secrets.randbelow(900000) + 100000)
cid = secrets.token_hex(16)
# 清除过期
now = time.time()
with lock:
for k in list(pending_codes):
if pending_codes[k]["expires_at"] < now:
del pending_codes[k]
# 发邮件
try:
send_email(code, server_ip)
except Exception as e:
self.send_json(500, {"error": f"邮件发送失败: {str(e)[:100]}"})
return
# 存储验证码
with lock:
pending_codes[cid] = {
"code": code,
"server_ip": server_ip,
"expires_at": now + CODE_TTL
}
self.send_json(200, {
"ok": True,
"challenge_id": cid,
"message": f"验证码已发送至 {TARGET_EMAIL}",
"expires_in": CODE_TTL,
"server": server_ip
})
def _handle_confirm(self, body):
cid = body.get("challenge_id", "")
code = body.get("code", "")
with lock:
entry = pending_codes.pop(cid, None)
if not entry:
self.send_json(403, {"error": "无效或过期的 challenge_id"})
return
if entry["expires_at"] < time.time():
self.send_json(403, {"error": "验证码已过期"})
return
if not hmac.compare_digest(entry["code"], code):
self.send_json(403, {"error": "验证码错误"})
return
# 🎯 复活!
results = revive_services()
self.send_json(200, {
"ok": True,
"message": f"守门人复活完成 · {entry['server_ip']}",
"revived": [k for k, v in results.items() if "" in v],
"details": results
})
def send_json(self, status, data):
self.send_response(status)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("X-Sovereign", "ICE-GL")
self.end_headers()
self.wfile.write(json.dumps(data, ensure_ascii=False).encode())
def log_message(self, fmt, *args):
"""静默日志(生产环境不打印每条请求)"""
pass
if __name__ == "__main__":
server = HTTPServer(("0.0.0.0", PORT), ReviveHandler)
print(f"光湖·复活守门人 v1.0 · 监听 :{PORT}")
print(f"主权者: {SOVEREIGN_ID} · 目标邮箱: {TARGET_EMAIL}")
try:
server.serve_forever()
except KeyboardInterrupt:
print("\n复活守门人已停止")
server.shutdown()

View file

@ -0,0 +1,25 @@
[Unit]
Description=光湖语言系统 · 公钥守门人复活协议
After=network.target network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/zhuyuan/revive-guard
ExecStart=/usr/bin/python3 /opt/zhuyuan/revive-guard/revive-guard.py
Restart=always
RestartSec=5
StandardOutput=append:/opt/zhuyuan/revive-guard/logs/out.log
StandardError=append:/opt/zhuyuan/revive-guard/logs/err.log
Environment=REVIVE_GUARD_PORT=8922
# ⊢ QQ_SMTP_AUTH_CODE 从服务器 /etc/environment 或 systemd override 注入
# ⊢ 不进仓库 · 不走默认值
EnvironmentFile=-/opt/zhuyuan/revive-guard/.env
# 安全加固
NoNewPrivileges=yes
PrivateTmp=yes
[Install]
WantedBy=multi-user.target