diff --git a/.code-map b/.code-map
index 26b3838..2607054 100644
--- a/.code-map
+++ b/.code-map
@@ -126,6 +126,11 @@ GLS-NODE-0001=gls/GLS-NODE-0001-LIGHTHOUSE-NODE-ARCHITECTURE.hdlp
LIGHTHOUSE-NODE-ARCHITECTURE=gls/GLS-NODE-0001-LIGHTHOUSE-NODE-ARCHITECTURE.hdlp
LIGHTHOUSE-NODE-BOOTSTRAP=server-tools/light-lake-node-bootstrap/README.md # Ubuntu 22.04 统一节点初始化模板
JD-FD-PRIMARY-BOOTSTRAP-RECEIPT=deployment/receipts/JD-FD-PRIMARY-BOOTSTRAP-20260717.json
+GUANGHULAB-FRONT-DOOR=server-tools/guanghulab-front-door/index.html # 广州备案域名轻量前门
+JD-APP-HUB=server-tools/jd-app-hub/README.md # 京东主节点首个 Web 应用与广州受限隧道
+PERSONAL-NODE-PROBE=server-tools/personal-node-probe/README.md # JD-OPS-CENTER 五分钟只读心跳
+BS-GZ-006=deployment/nodes/BS-GZ-006.json # 广州个人节点 · 已接入 JD-OPS-CENTER
+BS-GZ-006-CONNECTION-RECEIPT=deployment/receipts/BS-GZ-006-JD-OPS-CONNECTION-20260717.json
NODE-LIGHTHOUSE-WORKORDER=tcs-core/workorders/GLW-WO-20260714-NODE-LIGHTHOUSE-001.hdlp
HLP-LIGHTHOUSE-001=server-tools/enterprise-lighthouse/README.md
ENTERPRISE-LIGHTHOUSE-SERVICE=server-tools/enterprise-lighthouse/lighthouse.py
diff --git a/SYSTEM-STATUS.hdlp b/SYSTEM-STATUS.hdlp
index 7c488c5..d8349b6 100644
--- a/SYSTEM-STATUS.hdlp
+++ b/SYSTEM-STATUS.hdlp
@@ -15,6 +15,21 @@ Nginx: DISABLED_UNTIL_CONFIGURED
下一阶段: TX-PERSONAL-NODE-01 只读样板接入;其余五台不得跳过样板验证批量接入
```
+## 个人服务器接入实况 · 2026-07-17
+
+```text
+BS-GZ-006(广州备案前门): CONNECTED
+登记来源: bingshuo/guanghulab(REPO-002 历史归档,只读)
+当前运行事实源: deployment/nodes/BS-GZ-006.json
+JD → 广州管理连接: ONLINE · 独立钥匙 · 广州端限制来源为 JD-OPS-CENTER
+广州 → JD Web 隧道: ONLINE · 仅准转发 JD 本机 8088
+周期心跳: ONLINE · 每 5 分钟 · 固定只读命令
+公开入口: https://guanghulab.com/
+京东应用入口: https://guanghulab.com/jd/
+历史明文 Gatekeeper token: ROTATED_AND_INVALID
+剩余个人节点: 5
+```
+
## 版本
```
diff --git a/deployment/nodes/BS-GZ-006.json b/deployment/nodes/BS-GZ-006.json
new file mode 100644
index 0000000..9616c5d
--- /dev/null
+++ b/deployment/nodes/BS-GZ-006.json
@@ -0,0 +1,38 @@
+{
+ "schema": "guanghu.managed-node/v1",
+ "node_id": "BS-GZ-006",
+ "display_name": "广州备案前门节点",
+ "owner": "ICE-GL∞",
+ "provider": "tencent_cloud",
+ "region": "guangzhou",
+ "roles": [
+ "personal-managed-node",
+ "domain-front-door",
+ "legacy-service-host"
+ ],
+ "upstream": "JD-OPS-CENTER",
+ "resources": {
+ "cpu_cores": 2,
+ "memory_gib": 2,
+ "root_disk_gib": 50
+ },
+ "public_surface": {
+ "domain": "guanghulab.com",
+ "front_door": "https://guanghulab.com/",
+ "jd_app_hub": "https://guanghulab.com/jd/",
+ "icp_record": "陕ICP备2025071211号-1"
+ },
+ "connectivity": {
+ "admin_key_id": "jd_ops_to_bs_gz_006_admin",
+ "web_proxy_key_id": "bs_gz_006_to_jd_web_proxy",
+ "heartbeat": "five-minute-fixed-read-only-probe",
+ "credential_material": "private_only"
+ },
+ "source_history": {
+ "repository": "bingshuo/guanghulab",
+ "path": "brain/fifth-domain/zero-point/zhuyuan/cloud-compute-pool/bingshuo/BS-GZ-006.hdlp",
+ "repository_role": "locked_historical_archive",
+ "runtime_facts_refreshed_at": "2026-07-17"
+ },
+ "status": "connected"
+}
diff --git a/deployment/receipts/BS-GZ-006-JD-OPS-CONNECTION-20260717.json b/deployment/receipts/BS-GZ-006-JD-OPS-CONNECTION-20260717.json
new file mode 100644
index 0000000..afd3196
--- /dev/null
+++ b/deployment/receipts/BS-GZ-006-JD-OPS-CONNECTION-20260717.json
@@ -0,0 +1,41 @@
+{
+ "schema": "guanghu.deployment-receipt/v1",
+ "receipt_id": "BS-GZ-006-JD-OPS-CONNECTION-20260717",
+ "recorded_at": "2026-07-17T05:12:00Z",
+ "node_id": "BS-GZ-006",
+ "upstream": "JD-OPS-CENTER",
+ "source": {
+ "historical_registry": "bingshuo/guanghulab",
+ "current_repository": "bingshuo/fifth-domain",
+ "front_door_source": "server-tools/guanghulab-front-door",
+ "jd_hub_source": "server-tools/jd-app-hub",
+ "probe_source": "server-tools/personal-node-probe"
+ },
+ "verification": {
+ "jd_to_node_ssh": "passed",
+ "jd_to_node_key_scope": "dedicated_node_key",
+ "node_to_jd_web_tunnel": "active",
+ "tunnel_destination_scope": "jd_loopback_app_hub_only",
+ "front_door_https": 200,
+ "jd_hub_https": 200,
+ "jd_hub_dynamic_status": "passed",
+ "nginx_config_test": "passed",
+ "nginx_service": "active",
+ "heartbeat_timer": "active_every_five_minutes",
+ "icp_link": "https://beian.miit.gov.cn/",
+ "icp_record": "陕ICP备2025071211号-1",
+ "historical_gatekeeper_token": "rotated_and_rejected_with_401",
+ "current_test_count": 27
+ },
+ "rollback": {
+ "nginx_config_backup": "guanghulab.pre-jd-front-door.20260717-1305",
+ "authorized_keys_backup": "authorized_keys.pre-jd-ops-20260717",
+ "legacy_gatekeeper_secret_backup": "secret.pre-jd-ops-20260717"
+ },
+ "boundaries": {
+ "contains_ip": false,
+ "contains_credentials": false,
+ "guanghulab_repo_mutated": false,
+ "remaining_personal_nodes": 5
+ }
+}
diff --git a/server-tools/guanghulab-front-door/index.html b/server-tools/guanghulab-front-door/index.html
new file mode 100644
index 0000000..2883585
--- /dev/null
+++ b/server-tools/guanghulab-front-door/index.html
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+ 光湖 · 国内入口
+
+
+
+
+
+
+
+
+
+ GUANGHU · DOMESTIC GATEWAY
+ 从这里,进入
光湖语言世界
+ 广州节点保留备案域名与轻量入口,应用和计算逐步迁往京东云第五域主节点。入口稳定,后端可以继续生长。
+
+
+
+
+
+
+
页面在广州,重计算在京东。每张卡片都是一个真实入口。
+
+
+
+
+
+
+ 广州 · BS-GZ-006备案域名 / HTTPS / 前门展示
+ →
+ 北京 · JD-FD-PRIMARY应用运行 / 计算 / 调度中心
+
+
+
+
+
+
diff --git a/server-tools/guanghulab-front-door/styles.css b/server-tools/guanghulab-front-door/styles.css
new file mode 100644
index 0000000..bd5f926
--- /dev/null
+++ b/server-tools/guanghulab-front-door/styles.css
@@ -0,0 +1,103 @@
+:root {
+ color-scheme: dark;
+ --bg: #071113;
+ --panel: rgba(15, 31, 33, .82);
+ --panel-strong: #10292a;
+ --line: rgba(186, 238, 223, .15);
+ --text: #edf8f4;
+ --muted: #9fb4ae;
+ --lake: #7de0c7;
+ --lake-deep: #2d9f8d;
+ --warm: #f0bd78;
+ --shadow: 0 24px 70px rgba(0, 0, 0, .3);
+}
+
+* { box-sizing: border-box; }
+html { scroll-behavior: smooth; }
+body {
+ margin: 0;
+ min-height: 100vh;
+ overflow-x: hidden;
+ background:
+ radial-gradient(circle at 12% 18%, rgba(45, 159, 141, .14), transparent 30rem),
+ radial-gradient(circle at 85% 40%, rgba(240, 189, 120, .08), transparent 26rem),
+ var(--bg);
+ color: var(--text);
+ font-family: Inter, "SF Pro Display", "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
+}
+
+a { color: inherit; }
+.ambient { position: fixed; z-index: -1; border-radius: 999px; filter: blur(90px); opacity: .3; }
+.ambient-one { width: 24rem; height: 24rem; left: -10rem; top: 34rem; background: #1f8d7d; }
+.ambient-two { width: 20rem; height: 20rem; right: -8rem; top: 8rem; background: #8b6334; }
+
+.topbar, main, footer { width: min(1180px, calc(100% - 40px)); margin-inline: auto; }
+.topbar { height: 92px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid var(--line); }
+.brand { display: inline-flex; gap: 12px; align-items: center; text-decoration: none; font-size: 22px; font-weight: 750; letter-spacing: .08em; }
+.brand-mark { width: 44px; fill: none; stroke: var(--lake); stroke-width: 5; stroke-linecap: round; stroke-linejoin: round; filter: drop-shadow(0 0 12px rgba(125, 224, 199, .28)); }
+.node-pill { display: flex; align-items: center; gap: 9px; color: var(--muted); font-size: 13px; padding: 10px 14px; border: 1px solid var(--line); border-radius: 99px; background: rgba(10, 22, 23, .7); }
+.node-pill i { width: 7px; height: 7px; border-radius: 50%; background: var(--lake); box-shadow: 0 0 12px var(--lake); }
+
+.hero { padding: 108px 0 92px; max-width: 880px; }
+.eyebrow { margin: 0 0 18px; color: var(--lake); font-size: 12px; font-weight: 800; letter-spacing: .22em; }
+h1 { margin: 0; max-width: 850px; font-size: clamp(50px, 7.5vw, 92px); line-height: .98; letter-spacing: -.055em; font-weight: 760; }
+h1 span { color: transparent; background: linear-gradient(110deg, var(--lake), #bcebdc 52%, var(--warm)); background-clip: text; -webkit-background-clip: text; }
+.lede { max-width: 680px; margin: 34px 0 0; color: var(--muted); font-size: 18px; line-height: 1.85; }
+.hero-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 24px; margin-top: 42px; }
+.primary-button, .text-button { display: inline-flex; align-items: center; gap: 18px; text-decoration: none; font-weight: 720; }
+.primary-button { padding: 16px 22px; border-radius: 13px; color: #051211; background: var(--lake); box-shadow: 0 14px 34px rgba(45, 159, 141, .22); }
+.primary-button:hover { transform: translateY(-2px); background: #94ead5; }
+.text-button { color: var(--muted); }
+.text-button:hover { color: var(--text); }
+.primary-button, .route-card { transition: transform .22s ease, border-color .22s ease, background .22s ease; }
+
+.route-section { padding: 70px 0; border-top: 1px solid var(--line); }
+.section-heading { display: flex; justify-content: space-between; align-items: end; gap: 30px; margin-bottom: 34px; }
+.section-heading h2 { margin: 0; font-size: clamp(32px, 5vw, 52px); letter-spacing: -.04em; }
+.section-heading > p { max-width: 390px; margin: 0 0 3px; color: var(--muted); line-height: 1.75; }
+.card-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
+.route-card { position: relative; min-height: 250px; display: grid; grid-template-columns: 54px 1fr; gap: 22px; padding: 30px; overflow: hidden; text-decoration: none; border: 1px solid var(--line); border-radius: 20px; background: var(--panel); box-shadow: var(--shadow); backdrop-filter: blur(18px); }
+.route-card::after { content: ""; position: absolute; width: 11rem; height: 11rem; right: -6rem; bottom: -7rem; border-radius: 50%; background: rgba(125, 224, 199, .08); transition: transform .25s ease; }
+.route-card:hover { transform: translateY(-4px); border-color: rgba(125, 224, 199, .42); background: rgba(18, 42, 42, .93); }
+.route-card:hover::after { transform: scale(1.4); }
+.route-card.featured { background: linear-gradient(145deg, rgba(23, 62, 58, .96), rgba(14, 34, 35, .9)); border-color: rgba(125, 224, 199, .3); }
+.card-number { position: absolute; top: 23px; right: 25px; color: rgba(237, 248, 244, .23); font: 700 12px/1 ui-monospace, monospace; }
+.card-icon { width: 54px; height: 54px; display: grid; place-items: center; border: 1px solid rgba(125, 224, 199, .22); border-radius: 16px; color: var(--lake); background: rgba(125, 224, 199, .08); font-weight: 800; }
+.card-copy { align-self: center; }
+.card-kicker { margin: 0 0 16px; color: var(--lake); font: 700 10px/1 ui-monospace, monospace; letter-spacing: .16em; }
+.card-copy h3 { margin: 0 0 13px; font-size: 25px; letter-spacing: -.025em; }
+.card-copy > p:last-child { margin: 0; color: var(--muted); line-height: 1.75; font-size: 15px; }
+.card-arrow { position: absolute; right: 26px; bottom: 23px; color: var(--warm); font-size: 18px; }
+
+.architecture-strip { margin: 28px 0 88px; padding: 26px 30px; display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 24px; border-radius: 18px; border: 1px solid var(--line); background: rgba(8, 20, 21, .62); }
+.architecture-strip div { display: flex; flex-direction: column; gap: 7px; }
+.architecture-strip span { color: var(--lake); font-size: 12px; font-weight: 750; letter-spacing: .08em; }
+.architecture-strip strong { font-size: 15px; font-weight: 600; }
+.architecture-strip b { color: var(--warm); }
+
+footer { min-height: 120px; display: flex; justify-content: space-between; align-items: center; gap: 28px; border-top: 1px solid var(--line); color: var(--muted); font-size: 13px; }
+.footer-brand { color: var(--text); font-weight: 650; }
+.footer-links { display: flex; gap: 22px; flex-wrap: wrap; justify-content: flex-end; }
+.footer-links a { color: var(--muted); text-underline-offset: 4px; }
+.footer-links a:hover { color: var(--lake); }
+
+@media (max-width: 760px) {
+ .topbar, main, footer { width: min(100% - 28px, 1180px); }
+ .topbar { height: 76px; }
+ .node-pill { padding: 8px 10px; font-size: 11px; }
+ .hero { padding: 76px 0 70px; }
+ h1 { font-size: clamp(44px, 14vw, 66px); }
+ .lede { font-size: 16px; }
+ .section-heading { align-items: start; flex-direction: column; }
+ .card-grid { grid-template-columns: 1fr; }
+ .route-card { min-height: 225px; padding: 25px; grid-template-columns: 48px 1fr; gap: 18px; }
+ .card-icon { width: 48px; height: 48px; }
+ .architecture-strip { grid-template-columns: 1fr; }
+ .architecture-strip b { transform: rotate(90deg); justify-self: start; }
+ footer { padding: 28px 0; align-items: flex-start; flex-direction: column; }
+ .footer-links { justify-content: flex-start; }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ *, *::before, *::after { scroll-behavior: auto !important; transition: none !important; }
+}
diff --git a/server-tools/guanghulab-front-door/test/site.test.js b/server-tools/guanghulab-front-door/test/site.test.js
new file mode 100644
index 0000000..2111213
--- /dev/null
+++ b/server-tools/guanghulab-front-door/test/site.test.js
@@ -0,0 +1,31 @@
+"use strict";
+
+const assert = require("node:assert/strict");
+const fs = require("node:fs");
+const path = require("node:path");
+const test = require("node:test");
+
+const html = fs.readFileSync(path.join(__dirname, "..", "index.html"), "utf8");
+const styles = fs.readFileSync(path.join(__dirname, "..", "styles.css"), "utf8");
+
+test("front door keeps the legally required ICP link", () => {
+ assert.match(html, /陕ICP备2025071211号-1/);
+ assert.match(html, /https:\/\/beian\.miit\.gov\.cn\//);
+});
+
+test("front door routes compute work through the JD hub", () => {
+ assert.match(html, /href="\/jd\/"/);
+ assert.match(html, /京东云主节点/);
+ assert.match(html, /href="https:\/\/guanghubingshuo\.com\/code\/"/);
+});
+
+test("public page never embeds infrastructure addresses or credentials", () => {
+ assert.doesNotMatch(html, /(?:\d{1,3}\.){3}\d{1,3}/);
+ assert.doesNotMatch(html, /zy_gtw_/);
+ assert.doesNotMatch(html, /password|private[_ -]?key|token/i);
+});
+
+test("front door has responsive card layout", () => {
+ assert.match(styles, /grid-template-columns:\s*repeat\(2/);
+ assert.match(styles, /@media\s*\(max-width:\s*760px\)/);
+});
diff --git a/server-tools/jd-app-hub/README.md b/server-tools/jd-app-hub/README.md
new file mode 100644
index 0000000..63bb941
--- /dev/null
+++ b/server-tools/jd-app-hub/README.md
@@ -0,0 +1,7 @@
+# JD application hub
+
+The first web application running on `JD-FD-PRIMARY`. It binds only to
+`127.0.0.1:8088`; `BS-GZ-006` reaches it through a restricted persistent SSH
+tunnel and publishes it below `https://guanghulab.com/jd/`.
+
+No server address, credential or private key belongs in this directory.
diff --git a/server-tools/jd-app-hub/app.js b/server-tools/jd-app-hub/app.js
new file mode 100644
index 0000000..30901c8
--- /dev/null
+++ b/server-tools/jd-app-hub/app.js
@@ -0,0 +1,14 @@
+fetch("api/status", { cache: "no-store" })
+ .then((response) => response.ok ? response.json() : Promise.reject(new Error("status unavailable")))
+ .then((status) => {
+ const live = document.querySelector("#live-status");
+ const node = document.querySelector("#node-state");
+ live.textContent = "运行正常";
+ live.classList.add("ok");
+ node.textContent = `${status.node_id} · 在线`;
+ node.classList.add("ok");
+ })
+ .catch(() => {
+ document.querySelector("#live-status").textContent = "状态暂不可用";
+ document.querySelector("#node-state").textContent = "节点状态暂不可用";
+ });
diff --git a/server-tools/jd-app-hub/guanghu-jd-web-tunnel.service b/server-tools/jd-app-hub/guanghu-jd-web-tunnel.service
new file mode 100644
index 0000000..8ac38f7
--- /dev/null
+++ b/server-tools/jd-app-hub/guanghu-jd-web-tunnel.service
@@ -0,0 +1,19 @@
+[Unit]
+Description=Guanghu BS-GZ-006 to JD application hub tunnel
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=simple
+User=root
+ExecStart=/usr/bin/ssh -NT -F /etc/guanghu/jd-web-tunnel-ssh-config jd-web-target
+Restart=always
+RestartSec=5
+NoNewPrivileges=true
+PrivateTmp=true
+ProtectHome=read-only
+ProtectSystem=strict
+ReadOnlyPaths=/etc/guanghu/jd-web-tunnel-ssh-config /etc/guanghu/secrets/ssh/bs_gz_006_to_jd_web_proxy /root/.ssh/known_hosts
+
+[Install]
+WantedBy=multi-user.target
diff --git a/server-tools/jd-app-hub/index.html b/server-tools/jd-app-hub/index.html
new file mode 100644
index 0000000..9003b5b
--- /dev/null
+++ b/server-tools/jd-app-hub/index.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+ 京东云主节点 · 光湖
+
+
+
+
+
+
+
+ 节点JD-FD-PRIMARY
+ 角色第五域国内主节点
+ 连接检查中
+
+
+
+
+
+
diff --git a/server-tools/jd-app-hub/jd-app-hub.service b/server-tools/jd-app-hub/jd-app-hub.service
new file mode 100644
index 0000000..0ae693a
--- /dev/null
+++ b/server-tools/jd-app-hub/jd-app-hub.service
@@ -0,0 +1,24 @@
+[Unit]
+Description=Guanghu JD Application Hub
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=simple
+User=guanghu
+Group=guanghu
+WorkingDirectory=/opt/guanghu/jd-app-hub
+Environment=NODE_ENV=production
+Environment=HUB_HOST=127.0.0.1
+Environment=HUB_PORT=8088
+Environment=GUANGHU_NODE_ID=JD-FD-PRIMARY
+ExecStart=/usr/bin/node /opt/guanghu/jd-app-hub/server.js
+Restart=always
+RestartSec=3
+NoNewPrivileges=true
+PrivateTmp=true
+ProtectHome=true
+ProtectSystem=strict
+
+[Install]
+WantedBy=multi-user.target
diff --git a/server-tools/jd-app-hub/server.js b/server-tools/jd-app-hub/server.js
new file mode 100644
index 0000000..4d67d00
--- /dev/null
+++ b/server-tools/jd-app-hub/server.js
@@ -0,0 +1,46 @@
+"use strict";
+
+const fs = require("node:fs");
+const http = require("node:http");
+const path = require("node:path");
+const os = require("node:os");
+
+const ROOT = __dirname;
+const TYPES = { ".html": "text/html; charset=utf-8", ".css": "text/css; charset=utf-8", ".js": "application/javascript; charset=utf-8" };
+
+function json(response, status, value) {
+ response.writeHead(status, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" });
+ response.end(JSON.stringify(value));
+}
+
+function createApp() {
+ return http.createServer((request, response) => {
+ const url = new URL(request.url, "http://localhost");
+ if (request.method !== "GET") return json(response, 405, { ok: false, error: "method_not_allowed" });
+ if (url.pathname === "/api/status") {
+ return json(response, 200, {
+ ok: true,
+ node_id: process.env.GUANGHU_NODE_ID || "JD-FD-PRIMARY",
+ role: "fifth-domain-primary",
+ service: "jd-app-hub",
+ uptime_seconds: Math.floor(process.uptime()),
+ load_1m: Number(os.loadavg()[0].toFixed(2)),
+ memory_used_percent: Number((((os.totalmem() - os.freemem()) / os.totalmem()) * 100).toFixed(1)),
+ timestamp: new Date().toISOString()
+ });
+ }
+ const relative = url.pathname === "/" ? "index.html" : url.pathname.replace(/^\/+/, "");
+ if (!/^(index\.html|styles\.css|app\.js)$/.test(relative)) return json(response, 404, { ok: false, error: "not_found" });
+ const file = path.join(ROOT, relative);
+ response.writeHead(200, { "Content-Type": TYPES[path.extname(file)] || "application/octet-stream", "Cache-Control": relative === "index.html" ? "no-cache" : "public, max-age=3600" });
+ fs.createReadStream(file).pipe(response);
+ });
+}
+
+if (require.main === module) {
+ const host = process.env.HUB_HOST || "127.0.0.1";
+ const port = Number(process.env.HUB_PORT || 8088);
+ createApp().listen(port, host, () => console.log(`JD app hub listening on ${host}:${port}`));
+}
+
+module.exports = { createApp };
diff --git a/server-tools/jd-app-hub/styles.css b/server-tools/jd-app-hub/styles.css
new file mode 100644
index 0000000..c048493
--- /dev/null
+++ b/server-tools/jd-app-hub/styles.css
@@ -0,0 +1,26 @@
+:root { color-scheme: dark; --bg:#081112; --panel:#102526; --line:rgba(174,233,216,.16); --text:#eff9f6; --muted:#9cafaa; --lake:#78dfc4; --warm:#efbd79; }
+* { box-sizing:border-box; }
+body { margin:0; min-height:100vh; color:var(--text); background:radial-gradient(circle at 75% 0,rgba(62,154,136,.18),transparent 32rem),var(--bg); font-family:Inter,"PingFang SC",system-ui,sans-serif; }
+main { width:min(1080px,calc(100% - 36px)); margin:auto; padding-bottom:70px; }
+nav { height:84px; display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid var(--line); color:var(--muted); font-size:13px; }
+nav a { color:var(--text); text-decoration:none; }
+header { padding:90px 0 58px; max-width:780px; }
+.eyebrow { color:var(--lake); font:800 11px/1 ui-monospace,monospace; letter-spacing:.18em; }
+h1 { margin:20px 0; font-size:clamp(48px,8vw,86px); line-height:1; letter-spacing:-.055em; }
+header>p:last-child { max-width:700px; color:var(--muted); font-size:18px; line-height:1.8; }
+.status { display:grid; grid-template-columns:repeat(3,1fr); border:1px solid var(--line); border-radius:18px; overflow:hidden; background:rgba(15,35,36,.72); }
+.status div { padding:22px; display:flex; flex-direction:column; gap:8px; border-right:1px solid var(--line); }
+.status div:last-child { border:0; }
+.status span { color:var(--muted); font-size:12px; }
+.status strong { font-size:15px; }
+.cards { display:grid; grid-template-columns:repeat(2,1fr); gap:16px; padding-top:30px; }
+.cards>a,.cards>div { position:relative; min-height:210px; padding:28px; border:1px solid var(--line); border-radius:18px; background:var(--panel); text-decoration:none; color:var(--text); }
+.cards>a:hover { border-color:rgba(120,223,196,.48); transform:translateY(-3px); }
+.cards>a { transition:.2s ease; }
+.cards small { color:var(--lake); font-weight:800; letter-spacing:.16em; }
+.cards h2 { margin:34px 0 10px; font-size:25px; }
+.cards p { margin:0; color:var(--muted); line-height:1.7; }
+.cards b { position:absolute; right:25px; top:25px; color:var(--warm); font-size:13px; }
+.coming { opacity:.62; }
+#live-status.ok,#node-state.ok { color:var(--lake); }
+@media(max-width:700px){ nav{height:70px}.status,.cards{grid-template-columns:1fr}.status div{border-right:0;border-bottom:1px solid var(--line)}header{padding-top:65px}.cards>a,.cards>div{min-height:190px} }
diff --git a/server-tools/jd-app-hub/test/server.test.js b/server-tools/jd-app-hub/test/server.test.js
new file mode 100644
index 0000000..2f581d2
--- /dev/null
+++ b/server-tools/jd-app-hub/test/server.test.js
@@ -0,0 +1,37 @@
+"use strict";
+
+const assert = require("node:assert/strict");
+const test = require("node:test");
+const { createApp } = require("../server");
+const fs = require("node:fs");
+const path = require("node:path");
+
+test("JD hub exposes a minimal non-secret status response", async (t) => {
+ const server = createApp().listen(0, "127.0.0.1");
+ t.after(() => server.close());
+ await new Promise((resolve) => server.once("listening", resolve));
+ const { port } = server.address();
+ const response = await fetch(`http://127.0.0.1:${port}/api/status`);
+ assert.equal(response.status, 200);
+ const body = await response.json();
+ assert.equal(body.ok, true);
+ assert.equal(body.node_id, "JD-FD-PRIMARY");
+ assert.equal("ip" in body, false);
+ assert.equal("token" in body, false);
+});
+
+test("JD hub serves its application page", async (t) => {
+ const server = createApp().listen(0, "127.0.0.1");
+ t.after(() => server.close());
+ await new Promise((resolve) => server.once("listening", resolve));
+ const { port } = server.address();
+ const response = await fetch(`http://127.0.0.1:${port}/`);
+ assert.equal(response.status, 200);
+ assert.match(await response.text(), /京东云主节点/);
+});
+
+test("tunnel service keeps private addresses outside the repository", () => {
+ const unit = fs.readFileSync(path.join(__dirname, "..", "guanghu-jd-web-tunnel.service"), "utf8");
+ assert.match(unit, /\/etc\/guanghu\/jd-web-tunnel-ssh-config/);
+ assert.doesNotMatch(unit, /(?:\d{1,3}\.){3}\d{1,3}/);
+});
diff --git a/server-tools/personal-node-probe/README.md b/server-tools/personal-node-probe/README.md
new file mode 100644
index 0000000..db576e4
--- /dev/null
+++ b/server-tools/personal-node-probe/README.md
@@ -0,0 +1,9 @@
+# Personal node heartbeat probe
+
+Runs on `JD-OPS-CENTER` and checks one registered node through a private SSH
+alias. The remote action is fixed and read-only: service state, disk usage and
+uptime. A JSON receipt is written under `/var/lib/guanghu/receipts/` every five
+minutes.
+
+Real addresses and SSH identity paths belong in `/etc/guanghu/nodes/ssh/config`
+on the ops center, never in this repository.
diff --git a/server-tools/personal-node-probe/guanghu-personal-node-probe.service b/server-tools/personal-node-probe/guanghu-personal-node-probe.service
new file mode 100644
index 0000000..c02c2f7
--- /dev/null
+++ b/server-tools/personal-node-probe/guanghu-personal-node-probe.service
@@ -0,0 +1,16 @@
+[Unit]
+Description=Guanghu personal node read-only heartbeat probe
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=oneshot
+User=root
+EnvironmentFile=-/etc/guanghu/personal-node-probe.env
+ExecStart=/usr/local/sbin/guanghu-personal-node-probe
+NoNewPrivileges=true
+PrivateTmp=true
+ProtectHome=read-only
+ProtectSystem=strict
+ReadWritePaths=/var/lib/guanghu/receipts
+ReadOnlyPaths=/etc/guanghu/nodes /etc/guanghu/personal-node-probe.env
diff --git a/server-tools/personal-node-probe/guanghu-personal-node-probe.timer b/server-tools/personal-node-probe/guanghu-personal-node-probe.timer
new file mode 100644
index 0000000..9ede845
--- /dev/null
+++ b/server-tools/personal-node-probe/guanghu-personal-node-probe.timer
@@ -0,0 +1,11 @@
+[Unit]
+Description=Probe the first Guanghu personal node every five minutes
+
+[Timer]
+OnBootSec=2min
+OnUnitActiveSec=5min
+AccuracySec=30s
+Persistent=true
+
+[Install]
+WantedBy=timers.target
diff --git a/server-tools/personal-node-probe/probe.sh b/server-tools/personal-node-probe/probe.sh
new file mode 100644
index 0000000..1031e2e
--- /dev/null
+++ b/server-tools/personal-node-probe/probe.sh
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+NODE_ID=${NODE_ID:-BS-GZ-006}
+NODE_ALIAS=${NODE_ALIAS:-bs-gz-006}
+SSH_CONFIG=${SSH_CONFIG:-/etc/guanghu/nodes/ssh/config}
+RECEIPT_FILE=${RECEIPT_FILE:-/var/lib/guanghu/receipts/BS-GZ-006-latest.json}
+FRONT_DOOR_URL=${FRONT_DOOR_URL:-https://guanghulab.com/}
+APP_HUB_URL=${APP_HUB_URL:-https://guanghulab.com/jd/api/status}
+
+remote=$(ssh -F "$SSH_CONFIG" -o BatchMode=yes "$NODE_ALIAS" \
+ "set -e; jq -n \
+ --arg hostname \"\$(hostname)\" \
+ --arg nginx \"\$(systemctl is-active nginx)\" \
+ --arg tunnel \"\$(systemctl is-active guanghu-jd-web-tunnel.service)\" \
+ --arg disk_used \"\$(df --output=pcent / | tail -1 | tr -d ' ')\" \
+ --arg uptime_seconds \"\$(cut -d. -f1 /proc/uptime)\" \
+ '{hostname:\$hostname,nginx:\$nginx,tunnel:\$tunnel,disk_used:\$disk_used,uptime_seconds:(\$uptime_seconds|tonumber)}'")
+
+front_status=$(curl -fsS -o /dev/null -w '%{http_code}' "$FRONT_DOOR_URL")
+hub=$(curl -fsS "$APP_HUB_URL")
+tmp=$(mktemp "${RECEIPT_FILE}.XXXXXX")
+trap 'rm -f "$tmp"' EXIT
+
+jq -n \
+ --arg node_id "$NODE_ID" \
+ --arg checked_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
+ --arg front_status "$front_status" \
+ --argjson remote "$remote" \
+ --argjson hub "$hub" \
+ '{schema:"guanghu.node-heartbeat/v1",ok:true,node_id:$node_id,checked_at:$checked_at,front_door_http:($front_status|tonumber),remote:$remote,jd_hub:{ok:$hub.ok,node_id:$hub.node_id,service:$hub.service}}' \
+ > "$tmp"
+install -m 0640 -o root -g guanghu "$tmp" "$RECEIPT_FILE"
+cat "$RECEIPT_FILE"
diff --git a/server-tools/personal-node-probe/test/probe.test.js b/server-tools/personal-node-probe/test/probe.test.js
new file mode 100644
index 0000000..de15f8c
--- /dev/null
+++ b/server-tools/personal-node-probe/test/probe.test.js
@@ -0,0 +1,24 @@
+"use strict";
+
+const assert = require("node:assert/strict");
+const fs = require("node:fs");
+const path = require("node:path");
+const test = require("node:test");
+
+const root = path.join(__dirname, "..");
+const probe = fs.readFileSync(path.join(root, "probe.sh"), "utf8");
+const timer = fs.readFileSync(path.join(root, "guanghu-personal-node-probe.timer"), "utf8");
+
+test("probe uses a private SSH alias and fixed read-only command", () => {
+ assert.match(probe, /NODE_ALIAS/);
+ assert.match(probe, /systemctl is-active nginx/);
+ assert.match(probe, /df --output=pcent/);
+ assert.doesNotMatch(probe, /(?:\d{1,3}\.){3}\d{1,3}/);
+ assert.doesNotMatch(probe, /token|password|private[_ -]?key/i);
+});
+
+test("probe writes a latest receipt and runs periodically", () => {
+ assert.match(probe, /RECEIPT_FILE/);
+ assert.match(timer, /OnUnitActiveSec=5min/);
+ assert.match(timer, /Persistent=true/);
+});
diff --git a/zero-point/core-channel/revive-guard/LL-JD-OPS-CENTER-KEY-AND-NODE-PLAN-20260716.hdlp b/zero-point/core-channel/revive-guard/LL-JD-OPS-CENTER-KEY-AND-NODE-PLAN-20260716.hdlp
index ce54d2d..7d36368 100644
--- a/zero-point/core-channel/revive-guard/LL-JD-OPS-CENTER-KEY-AND-NODE-PLAN-20260716.hdlp
+++ b/zero-point/core-channel/revive-guard/LL-JD-OPS-CENTER-KEY-AND-NODE-PLAN-20260716.hdlp
@@ -2,7 +2,7 @@
> **HLDP**: `HLDP://fifth-domain/zero-point/core-channel/revive-guard/LL-JD-OPS-CENTER-KEY-AND-NODE-PLAN-20260716`
>
-> **状态**: PHASE_0_COMPLETE · WAITING_FOR_TX_PERSONAL_NODE_01
+> **状态**: PHASE_1_FIRST_NODE_COMPLETE · FIVE_PERSONAL_NODES_REMAINING
>
> **主权者**: 冰朔 ICE-GL∞
>
@@ -12,6 +12,8 @@
> **2026-07-17 实施回写**: 京东主控的 Ubuntu 22.04 统一基线、第五域镜像、受控数据目录、Gatekeeper v3.2、Docker、健康检查与回滚入口已经落地。下一步严格从一台只读样板节点开始,不直接批量接入六台。
+> **2026-07-17 广州节点回写**: 第一台样板采用历史编号 `BS-GZ-006`,没有另造 `TX-PERSONAL-NODE-01`。京东到广州的独立管理钥匙、广州到京东的受限 Web 隧道、五分钟只读心跳、节点清单与部署回执均已落地。`bingshuo/guanghulab` 只作为历史服务器地图,不再接收新功能提交。
+
---
## 0 · 冰朔不需要懂这些细节