diff --git a/server-tools/enterprise-lighthouse/README.md b/server-tools/enterprise-lighthouse/README.md index 53cfa85..38012b6 100644 --- a/server-tools/enterprise-lighthouse/README.md +++ b/server-tools/enterprise-lighthouse/README.md @@ -4,6 +4,11 @@ 部署后只监听本机 `127.0.0.1:8031`。公众网站与后续 GLSV 页面通过 Nginx 以单独的受控路由接入;在邮件确认与节点连接器完成前,不开放节点激活或执行。 +`hololake-public-status.nginx.conf` 只公开两个精确的只读 GET 路由: +`/api/hololake/enterprise/health` 与 `/api/hololake/enterprise/status`。它们用于让 +HoloLake 回读 `AW-GZ-001` 企业主机和四个本机承载域的实时状态,不转发管理凭据、写入或 +任意路径。 + 人类管理员与人格体遵守同一条入口链: ```text diff --git a/server-tools/enterprise-lighthouse/hololake-public-status.nginx.conf b/server-tools/enterprise-lighthouse/hololake-public-status.nginx.conf new file mode 100644 index 0000000..11c01cc --- /dev/null +++ b/server-tools/enterprise-lighthouse/hololake-public-status.nginx.conf @@ -0,0 +1,26 @@ +# Public, read-only enterprise presence receipts for HoloLake. +# Include this file inside the TLS server block for guanghu.chat. + +location = /api/hololake/enterprise/health { + limit_except GET { + deny all; + } + proxy_pass http://127.0.0.1:8031/health; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 10s; + add_header Cache-Control "no-store" always; +} + +location = /api/hololake/enterprise/status { + limit_except GET { + deny all; + } + proxy_pass http://127.0.0.1:8031/v1/status; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 10s; + add_header Cache-Control "no-store" always; +} diff --git a/server-tools/enterprise-lighthouse/lighthouse.py b/server-tools/enterprise-lighthouse/lighthouse.py index b528ad2..a6fb7f2 100644 --- a/server-tools/enterprise-lighthouse/lighthouse.py +++ b/server-tools/enterprise-lighthouse/lighthouse.py @@ -36,6 +36,12 @@ DOMAINS = { } ENTERPRISE_MANAGED_DOMAINS = {"DOMAIN-ZS", "DOMAIN-MAIN", "DOMAIN-SUB", "DOMAIN-ZERO"} EXTERNAL_FOUNDATION_DOMAINS = {"DOMAIN-FIFTH"} +HOSTED_DOMAIN_NODES = { + "DOMAIN-MAIN": ("AW-GZ-001-MAIN", "AW-GZ-001 · 光湖主域"), + "DOMAIN-SUB": ("AW-GZ-001-SUB", "AW-GZ-001 · 光湖分域"), + "DOMAIN-ZERO": ("AW-GZ-001-ZERO", "AW-GZ-001 · 光湖零域"), + "DOMAIN-ZS": ("AW-GZ-001-ZS", "AW-GZ-001 · 光湖零感域"), +} def now(): @@ -77,9 +83,28 @@ def connection(): capsule_hash TEXT NOT NULL, created_at INTEGER NOT NULL, expires_at INTEGER NOT NULL ); """) + observed_at = now() for domain_id, name in DOMAINS.items(): state = "EXTERNAL_PRIVATE_FOUNDATION" if domain_id in EXTERNAL_FOUNDATION_DOMAINS else "PENDING_ENTRY_NODE" - db.execute("INSERT OR IGNORE INTO domains VALUES (?, ?, ?, ?)", (domain_id, name, state, now())) + db.execute("INSERT OR IGNORE INTO domains VALUES (?, ?, ?, ?)", (domain_id, name, state, observed_at)) + for domain_id, (node_id, display_name) in HOSTED_DOMAIN_NODES.items(): + db.execute( + "INSERT OR IGNORE INTO nodes VALUES (?, ?, NULL, 'ACTIVE', ?, '127.0.0.1', ?, ?, ?, ?)", + ( + node_id, + domain_id, + display_name, + json.dumps(["health_check"]), + "server-local-lighthouse", + observed_at, + observed_at, + ), + ) + db.execute( + "UPDATE nodes SET state='ACTIVE', last_heartbeat=? WHERE id=?", + (observed_at, node_id), + ) + db.execute("UPDATE domains SET state='HOSTED_ACTIVE' WHERE id=?", (domain_id,)) db.commit() return db @@ -250,7 +275,16 @@ class Handler(BaseHTTPRequestHandler): return self.respond(200, {"ok": True, "service": "guanghu-enterprise-lighthouse", "mode": "map-intent-agent-gate", "map_hash": map_hash, "execution": "disabled"}) if self.path == "/v1/status": counts = {row["state"]: row["count"] for row in db.execute("SELECT state, COUNT(*) AS count FROM nodes GROUP BY state")} - return self.respond(200, {"ok": True, "domains": [dict(row) for row in db.execute("SELECT id,name,state FROM domains ORDER BY id")], "node_counts": counts, "fixed_actions": sorted(FIXED_ACTIONS), "raw_shell": "rejected"}) + return self.respond(200, { + "ok": True, + "node_id": "AW-GZ-001", + "host_state": "ONLINE", + "observed_at": now(), + "domains": [dict(row) for row in db.execute("SELECT id,name,state FROM domains ORDER BY id")], + "node_counts": counts, + "fixed_actions": sorted(FIXED_ACTIONS), + "raw_shell": "rejected", + }) if self.path in {"/v1/navigation-map", "/v2/global-navigation-map"}: try: body, map_hash = navigation_map() diff --git a/server-tools/enterprise-lighthouse/test_lighthouse.py b/server-tools/enterprise-lighthouse/test_lighthouse.py index 79a2f5d..460dd38 100644 --- a/server-tools/enterprise-lighthouse/test_lighthouse.py +++ b/server-tools/enterprise-lighthouse/test_lighthouse.py @@ -66,6 +66,20 @@ with tempfile.TemporaryDirectory() as temp: else: raise AssertionError("server did not start") + status = json.load(urllib.request.urlopen(BASE + "/v1/status")) + assert status["node_id"] == "AW-GZ-001" + assert status["host_state"] == "ONLINE" + assert {domain["state"] for domain in status["domains"] if domain["id"] != "DOMAIN-FIFTH"} == { + "HOSTED_ACTIVE" + } + assert status["node_counts"]["ACTIVE"] == 4 + + nodes = json.load(urllib.request.urlopen(BASE + "/v1/nodes"))["nodes"] + hosted = {node["domain_id"]: node for node in nodes} + assert set(hosted) == {"DOMAIN-MAIN", "DOMAIN-SUB", "DOMAIN-ZERO", "DOMAIN-ZS"} + assert all(node["state"] == "ACTIVE" for node in hosted.values()) + assert all(node["display_name"].startswith("AW-GZ-001") for node in hosted.values()) + intake = { "human_name": "Test", "email": "test@example.invalid",