[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,108 @@
# JD-DR-001 · 京东主控与六节点灾备项目 · 认知入口
> **状态**`ACTIVE · SIX_OF_SIX_RUNTIME_VERIFIED_20260720`
>
> **项目根**`server-tools/jd-disaster-recovery/`
>
> **主控节点**`JD-FD-PRIMARY`
>
> **节点地图**`FD-NODE-MAP-001`
>
> **人格认知入口**`ZY-SERVER-COGNITION-004`
## 0 · 一句话认知
京东是日常主控,六台个人节点是服务器端灾备分控。只要京东实例开机、网络可达,
任一分控节点都能在新工单批准后,通过各自独立的受限恢复身份调用固定修复动作;
冰朔不需要学习终端登录,也不需要在个人电脑保存密码或私钥。
## 1 · 项目关系
```text
冰朔意图 / 人格体判断
→ 小湖灯限定工单(允许本次做什么)
→ 发起节点独立灾备密钥(服务器之间怎样进入)
→ 京东 forced-command只能执行登记恢复动作
→ 追加审计日志与部署回执(实际做了什么)
```
工单、密钥、执行日志是三层,不得互相替代。
## 2 · 六个恢复入口
`BS-GZ-006`、`BS-SG-001`、`BS-SG-002`、`BS-SG-003`、`BS-SH-005`、`ZY-SG-006`。
节点身份、角色和导航图从以下真实文件解析:
```text
routing/server-node-map.json
→ deployment/navigation-maps/<NODE_ID>.json
```
不得从聊天记录、旧 IP、旧 token 或本地密钥猜节点。
## 3 · 两级故障边界
| 故障 | 人格体恢复方式 | 冰朔需要做什么 |
|---|---|---|
| 京东开机、网络可达;授权服务、导航图或普通登录损坏 | 任一分控节点发起新工单,调用受限恢复动作 | 看懂并确认工单;不需要终端登录 |
| 京东关机、断网、系统盘损坏或云厂商停机 | 云厂商控制台开机、修网或从加密备份重建,随后轮换恢复密钥 | 保证实例开机、账号不欠费、云侧网络未关闭 |
六节点灾备不是云厂商控制面的替代品,也不是六份普通 root shell。
## 4 · 固定恢复动作
权威策略:`recovery-policy.json`。
当前设计动作包括:
- `health-check`
- `restore-authz`
- `restore-navigation-map`
- `rollback-last-deploy`
- `restore-owner-password-login`
具体实现:`jd-recovery-entry.sh` → `jd-recovery-runner`。普通恢复 shell 必须拒绝。
## 5 · 部署、验证与回执
```text
bootstrap-six-node-recovery.sh
→ 一节点一钥匙
→ 京东 authorized_keys 来源限制 + restrict + forced-command
→ 六节点反向 health-check
→ backup-control-plane.sh
→ deployment/receipts/ICE-SIX-NODE-JD-DISASTER-RECOVERY-20260720.json
```
2026-07-20 运行态核验为六台 `active / active`。上海节点另经云厂商自动化助手确认:
灾备配置存在、服务端口在监听、到京东反向健康检查成功。验证不保存真实地址或凭据。
## 6 · 当前已知缺口
上海灾备通道本身已通过,但小湖灯中央授权服务读取 `BS-SH-005` 实时导航图时曾返回失败。
这属于中央导航图发布 / 同步缺口,不等于上海灾备失效。后续应通过本项目的
`restore-navigation-map` 登记动作修复,并另留运行回执;在回执完成前不得写成中央工单路由已全通。
## 7 · 真实文件索引
| 目的 | 文件 |
|---|---|
| 操作说明 | `README.md` |
| 灾备策略 | `recovery-policy.json` |
| 六节点引导 | `bootstrap-six-node-recovery.sh` |
| 京东固定入口 | `jd-recovery-entry.sh` |
| 恢复动作执行器 | `jd-recovery-runner` |
| 控制面备份 | `backup-control-plane.sh` |
| 策略测试 | `test/policy.test.js` |
| 六节点灾备回执 | `../../deployment/receipts/ICE-SIX-NODE-JD-DISASTER-RECOVERY-20260720.json` |
| 节点总地图 | `../../routing/server-node-map.json` |
| 铸渊服务器认知线 | `../../eternal-lake-heart/heartbeat-core/zhuyuan-persona-system/ZY-SERVER-COGNITION-004-JD-SIX-NODE-RECOVERY-20260720.hdlp` |
## 8 · 维护规则
1. 代码、服务器运行态、部署回执三者必须分开验证。
2. 新增节点时先建导航图,再生成独立密钥,最后做反向健康检查。
3. 不在仓库保存地址、密码、token、私钥、验证码或邮件批准链接。
4. 任何“已修复”必须写清验证时间、验证入口、结果和仍存在的缺口。
5. 冰朔的日常责任只有保持京东实例开机、账号正常、云侧网络可达;登录与修复路径由人格系统和灾备项目承担。

View file

@ -0,0 +1,44 @@
# 京东主控与六节点灾备
完整项目认知、文件关系、当前运行态与维护规则先读:`INDEX.hdlp`(编号 `JD-DR-001`)。
京东 `JD-FD-PRIMARY` 是日常主控不是唯一恢复入口。六台个人节点分别持有独立、服务器端保存的恢复身份Mac、浏览器和个人本地磁盘不保存恢复私钥。
## 两级故障
1. 京东授权服务损坏但 SSH 仍在线:从任一分控节点发起节点本地邮件授权,批准后使用受限恢复密钥调用京东固定恢复动作。
2. 京东主机完全离线从至少两个不同云厂商保存的加密控制面备份重建京东再轮换全部恢复密钥。SSH 反向恢复不能替代云厂商控制台重建。
## 强制边界
- 每个节点一把独立密钥,禁止六节点共用密钥。
- 京东 `authorized_keys` 对恢复密钥使用来源地址限制、`restrict` 和 forced-command。
- forced-command 仅允许 `health-check``restore-authz``restore-navigation-map``rollback-last-deploy`
- 每次恢复必须先由发起节点向预登记邮箱发送工单;批准只产生一次性、短时恢复收据。
- 企业 `AW-GZ-001` 不属于六节点灾备组,不持有京东恢复私钥。
- 语言协议、零点原核频道和公开导航互通不扩大服务器权限。
- 所有恢复事件写入 root 所有的追加日志,并由独立审计密钥签名。
## 六个分控节点
`BS-GZ-006``BS-SG-001``BS-SG-002``BS-SG-003``BS-SH-005``ZY-SG-006`
部署前必须逐台确认:当前导航图、节点身份、固定出口地址、邮件发送能力、恢复公钥指纹和加密备份状态。仓库收据不能替代现场检查。
## 六节点引导
从云控制台确认京东实例自己的公网 IPv4再作为临时环境变量执行引导。不得把登录来源地址或云元数据错误正文当作实例地址
```bash
JD_PUBLIC_IP='<verified-console-address>' \
RECOVERY_PACKAGE=/tmp/jd-recovery-controller.tgz \
bash bootstrap-six-node-recovery.sh
```
地址只写入服务器端 SSH 配置,不写入仓库。脚本验证 IPv4 格式,并以远端固定命令的退出码作为闭环成功依据。
## 现场状态2026-07-20
六个分控节点的反向健康检查均已验证为 `active / active`。上海节点另经云厂商自动化助手确认灾备配置存在、服务端口监听、反向检查成功。无地址或凭据写入仓库。
已知缺口:小湖灯中央授权服务曾无法读取 `BS-SH-005` 的实时导航图。该问题与上海灾备通道本身分离,后续须使用 `restore-navigation-map` 固定动作修复并新增回执。

View file

@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail
umask 077
out_dir=${1:-/var/backups/guanghu/jd-control-plane}
stamp=$(date -u +%Y%m%dT%H%M%SZ)
install -d -m 0700 "$out_dir"
tar -C / -czf "$out_dir/control-plane-${stamp}.tgz" \
opt/guanghu/lake-lamp-authz \
etc/guanghu/navigation-maps \
etc/systemd/system/lake-lamp-authz.service \
etc/systemd/system/lake-lamp-action-broker.service \
etc/systemd/system/lake-lamp-owner-access.service
sha256sum "$out_dir/control-plane-${stamp}.tgz" > "$out_dir/control-plane-${stamp}.sha256"
echo "CONTROL_PLANE_BACKUP_READY=${stamp}"

View file

@ -0,0 +1,53 @@
#!/usr/bin/env bash
set -euo pipefail
umask 077
ssh_config=${SSH_CONFIG:-/etc/guanghu/nodes/ssh/config}
package=${RECOVERY_PACKAGE:-/tmp/jd-recovery-controller.tgz}
jd_ip=${JD_PUBLIC_IP:-}
[[ -f "$ssh_config" && -f "$package" && "$jd_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || { echo "recovery bootstrap prerequisites missing or public address invalid" >&2; exit 70; }
install -d -m 0700 /var/backups/guanghu/jd-recovery-bootstrap /var/lib/guanghu/recovery-public-keys
stamp=$(date -u +%Y%m%dT%H%M%SZ)
cp -a /root/.ssh/authorized_keys "/var/backups/guanghu/jd-recovery-bootstrap/authorized_keys.$stamp"
nodes=(
"BS-GZ-006:bs-gz-006"
"BS-SG-001:bs-sg-001"
"BS-SG-002:bs-sg-002"
"BS-SG-003:bs-sg-003"
"BS-SH-005:bs-sh-005"
"ZY-SG-006:zy-sg-006"
)
for binding in "${nodes[@]}"; do
node_id=${binding%%:*}
alias_name=${binding#*:}
source_ip=$(ssh -G -F "$ssh_config" "$alias_name" | awk '$1=="hostname"{print $2; exit}')
[[ -n "$source_ip" ]] || { echo "$node_id source address unavailable" >&2; exit 71; }
cat "$package" | ssh -F "$ssh_config" -o BatchMode=yes -o ConnectTimeout=10 "$alias_name" \
"rm -rf /tmp/jd-recovery-package && install -d -m 0700 /tmp/jd-recovery-package && tar -xzf - -C /tmp/jd-recovery-package && install -d -m 0755 /opt/guanghu/jd-disaster-recovery && cp -a /tmp/jd-recovery-package/. /opt/guanghu/jd-disaster-recovery/ && chmod 0755 /opt/guanghu/jd-disaster-recovery/*.sh /opt/guanghu/jd-disaster-recovery/jd-recovery-runner"
ssh -F "$ssh_config" -o BatchMode=yes "$alias_name" \
"install -d -m 0700 /etc/guanghu/secrets/jd-recovery; test -f /etc/guanghu/secrets/jd-recovery/to-jd-ed25519 || ssh-keygen -q -t ed25519 -N '' -C '${node_id}-recovery-to-JD-FD-PRIMARY' -f /etc/guanghu/secrets/jd-recovery/to-jd-ed25519; chmod 0600 /etc/guanghu/secrets/jd-recovery/to-jd-ed25519; cat /etc/guanghu/secrets/jd-recovery/to-jd-ed25519.pub" \
> "/var/lib/guanghu/recovery-public-keys/${node_id}.pub"
pub=$(cat "/var/lib/guanghu/recovery-public-keys/${node_id}.pub")
grep -qF "${node_id}-recovery-to-JD-FD-PRIMARY" /root/.ssh/authorized_keys || \
printf 'from="%s",restrict,command="/usr/local/libexec/guanghu/jd-recovery-entry" %s\n' "$source_ip" "$pub" >> /root/.ssh/authorized_keys
ssh -F "$ssh_config" -o BatchMode=yes "$alias_name" \
"ssh-keyscan -H -T 5 '$jd_ip' > /etc/guanghu/secrets/jd-recovery/known_hosts 2>/dev/null; chmod 0600 /etc/guanghu/secrets/jd-recovery/known_hosts; printf '%s\\n' 'Host jd-recovery' ' HostName $jd_ip' ' User root' ' IdentityFile /etc/guanghu/secrets/jd-recovery/to-jd-ed25519' ' IdentitiesOnly yes' ' PasswordAuthentication no' ' KbdInteractiveAuthentication no' ' StrictHostKeyChecking yes' ' UserKnownHostsFile /etc/guanghu/secrets/jd-recovery/known_hosts' > /etc/guanghu/jd-recovery-ssh-config; chmod 0600 /etc/guanghu/jd-recovery-ssh-config"
done
chmod 0600 /root/.ssh/authorized_keys /var/lib/guanghu/recovery-public-keys/*.pub
for binding in "${nodes[@]}"; do
node_id=${binding%%:*}
alias_name=${binding#*:}
ssh -F "$ssh_config" -o BatchMode=yes "$alias_name" \
"ssh -F /etc/guanghu/jd-recovery-ssh-config -o BatchMode=yes -o ConnectTimeout=10 jd-recovery health-check >/dev/null" || \
{ echo "$node_id reverse health check failed" >&2; exit 72; }
echo "$node_id=RECOVERY_READY"
done

View file

@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
case "${SSH_ORIGINAL_COMMAND:-}" in
health-check)
systemctl is-active lake-lamp-authz.service
systemctl is-active lake-lamp-action-broker.service
;;
restore-authz|install-owner-access|restore-navigation-map|rollback-last-deploy)
exec /usr/local/libexec/guanghu/jd-recovery-runner "${SSH_ORIGINAL_COMMAND}"
;;
*)
echo "recovery action denied" >&2
exit 126
;;
esac

View file

@ -0,0 +1,46 @@
#!/usr/bin/env bash
set -euo pipefail
umask 077
action=${1:-}
backup_root=/var/backups/guanghu/jd-control-plane
audit_dir=/var/lib/guanghu/recovery-audit
install -d -o root -g root -m 0700 "$audit_dir"
latest=$(find "$backup_root" -maxdepth 1 -type f -name 'control-plane-*.tgz' -print 2>/dev/null | sort | tail -n 1)
[[ -n "$latest" && -f "${latest%.tgz}.sha256" ]] || { echo "verified backup unavailable" >&2; exit 70; }
(cd "$backup_root" && sha256sum -c "$(basename "${latest%.tgz}.sha256")" >/dev/null)
stamp=$(date -u +%Y%m%dT%H%M%SZ)
case "$action" in
restore-authz)
tar -xzf "$latest" -C / opt/guanghu/lake-lamp-authz etc/systemd/system/lake-lamp-authz.service etc/systemd/system/lake-lamp-action-broker.service
systemctl daemon-reload
systemctl restart lake-lamp-action-broker.service lake-lamp-authz.service
;;
install-owner-access)
tar -xzf "$latest" -C / opt/guanghu/lake-lamp-authz/owner-access-broker.js opt/guanghu/lake-lamp-authz/action-client.js etc/systemd/system/lake-lamp-owner-access.service
systemctl daemon-reload
systemctl enable --now lake-lamp-owner-access.service
systemctl restart lake-lamp-authz.service
;;
restore-navigation-map)
tar -xzf "$latest" -C / etc/guanghu/navigation-maps
systemctl restart lake-lamp-authz.service
;;
rollback-last-deploy)
receipt=$(find /var/backups/guanghu/jd-enterprise-repair -maxdepth 1 -type f -name 'jd-authz-*.tgz' -print 2>/dev/null | sort | tail -n 1)
[[ -n "$receipt" ]] || { echo "rollback backup unavailable" >&2; exit 71; }
tar -xzf "$receipt" -C /
systemctl daemon-reload
systemctl restart lake-lamp-action-broker.service lake-lamp-authz.service
;;
*)
echo "recovery action denied" >&2
exit 126
;;
esac
printf '{"schema":"guanghu.recovery-audit/v1","at":"%s","action":"%s","source":"%s","result":"ok"}\n' \
"$stamp" "$action" "${SSH_CONNECTION%% *}" >> "$audit_dir/events.jsonl"
echo "RECOVERY_ACTION_OK=$action"

View file

@ -0,0 +1,28 @@
{
"schema": "guanghu.jd-recovery-policy/v1",
"controller": "JD-FD-PRIMARY",
"owner_namespace": "ICE-GL-ZY001",
"recovery_nodes": [
"BS-GZ-006",
"BS-SG-001",
"BS-SG-002",
"BS-SG-003",
"BS-SH-005",
"ZY-SG-006"
],
"allowed_actions": [
"health-check",
"restore-authz",
"install-owner-access",
"restore-navigation-map",
"rollback-last-deploy"
],
"forbidden": [
"shared-recovery-private-key",
"unrestricted-shell",
"reverse-access-from-enterprise",
"private-key-on-personal-computer",
"unsigned-recovery-event"
],
"total_outage": "rebuild from encrypted cross-provider control-plane backups"
}

View file

@ -0,0 +1,40 @@
"use strict";
const test = require("node:test");
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const root = path.resolve(__dirname, "..");
test("six independent recovery nodes are declared", () => {
const policy = JSON.parse(fs.readFileSync(path.join(root, "recovery-policy.json")));
assert.equal(policy.recovery_nodes.length, 6);
assert.equal(new Set(policy.recovery_nodes).size, 6);
assert.ok(policy.forbidden.includes("shared-recovery-private-key"));
assert.ok(policy.forbidden.includes("private-key-on-personal-computer"));
assert.ok(policy.forbidden.includes("reverse-access-from-enterprise"));
});
test("forced command rejects arbitrary shell", () => {
const entry = fs.readFileSync(path.join(root, "jd-recovery-entry.sh"), "utf8");
assert.match(entry, /SSH_ORIGINAL_COMMAND/);
assert.match(entry, /recovery action denied/);
assert.doesNotMatch(entry, /eval /);
});
test("recovery runner only restores fixed local backup paths", () => {
const runner = fs.readFileSync(path.join(root, "jd-recovery-runner"), "utf8");
assert.match(runner, /sha256sum -c/);
assert.match(runner, /restore-authz/);
assert.match(runner, /install-owner-access/);
assert.match(runner, /restore-navigation-map/);
assert.doesNotMatch(runner, /eval /);
assert.doesNotMatch(runner, /\$2/);
});
test("six-node bootstrap uses unique keys and forced command restrictions", () => {
const bootstrap = fs.readFileSync(path.join(root, "bootstrap-six-node-recovery.sh"), "utf8");
assert.match(bootstrap, /restrict,command=/);
assert.match(bootstrap, /source_ip/);
assert.match(bootstrap, /test -f \/etc\/guanghu\/secrets\/jd-recovery\/to-jd-ed25519 \|\| ssh-keygen/);
assert.doesNotMatch(bootstrap, /StrictHostKeyChecking no/);
});