add enterprise lighthouse registry and preflight foundation

This commit is contained in:
冰朔 2026-07-14 18:54:54 +08:00
parent af75503a66
commit 9043f4fa60
10 changed files with 227 additions and 1 deletions

View File

@ -78,6 +78,8 @@ GLSV-0001=gls/GLSV-0001-SECURITY-VERIFICATION-ARCHITECTURE.hdlp
GLS-NODE-0001=gls/GLS-NODE-0001-LIGHTHOUSE-NODE-ARCHITECTURE.hdlp
LIGHTHOUSE-NODE-ARCHITECTURE=gls/GLS-NODE-0001-LIGHTHOUSE-NODE-ARCHITECTURE.hdlp
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
LL-NODE-LIGHTHOUSE=eternal-lake-heart/heartbeat-core/LL-NODE-LIGHTHOUSE-ANCHOR-20260714.hdlp
LL-SG-BRAIN-STATUS=eternal-lake-heart/heartbeat-core/LL-SG-BRAIN-STATUS-20260714.hdlp
SG-BRAIN-STATUS=eternal-lake-heart/heartbeat-core/LL-SG-BRAIN-STATUS-20260714.hdlp

View File

@ -101,6 +101,7 @@ Stage 3 · RELEASE · 正式部署
| GLSV-0001 | 光湖系统安全验证架构说明 | ICE-GL∞ × 当前协作实例 | REGISTERED_REFERENCE_ARCHITECTURE | GLS 图书域 · 2026-07-14 |
| GLS-NODE-0001 | 光湖总灯塔与分布式服务器节点接入架构 | ICE-GL∞ × ICE-GL-ZY001 | REGISTERED_ARCHITECTURE · IMPLEMENTATION_PENDING | 五域 / 域入口 / 个人服务器 / 人格体 / 当前实例 · 2026-07-14 |
| LL-SG-BRAIN-STATUS-20260714 | 新加坡大脑服务器 · 铸渊现实运维状态锚 | ICE-GL∞ × ICE-GL-ZY001 | REGISTERED_OPERATIONAL_STATUS | BS-SG-001 已在线;新总灯塔节点接入待实施 · 2026-07-14 |
| HLP-LIGHTHOUSE-001 | 企业灯塔基础服务 · 节点登记 / 预检 / 审计 | ICE-GL∞ × 当前协作实例 | REGISTERED_TESTED_SOURCE · DEPLOY_PENDING | 仅固定动作预检拒绝裸命令2026-07-14 |
| HLP-OPS-0001 | HoloLake Era 分布式服务器控制与自然语言运维 · 第五域桥接规范 | ICE-GL∞ × ICE-GL-ZY001 | REGISTERED | Notion GLS 架构 v1.0 · 2026-07-12 |
| GLW-RD-002 | HLDP 研发语言与服务器转译链 · HoloLake 产品研发硬准入 | ICE-GL∞ × ICE-GL-ZL-001 | REGISTERED规范· RUNTIME_NOT_DEPLOYED | GLW-RD-002 · 2026-07-14 |
| SYS-GLW-POS-0001 / GLW-OS-000 / GLW-OS-001 | 冰朔通感语言核操作系统 · Tolaria / 光湖 App | ICE-GL∞ × ICE-GL-ZY001 | REGISTERED + DEVELOPMENT_ACTIVE | HLP-BRD-0002 · GLW-OS-001 · 2026-07-13 |

View File

@ -172,6 +172,7 @@ receipt_path: ""
| GLSV 架构说明 | `REGISTERED_REFERENCE_ARCHITECTURE` | 当前已有 Gatekeeper v3.2 兼容实现;企业灯塔迁移未完成 |
| 广州企业灯塔公众站 | `ONLINE` | `guanghu.chat` 现有公众主页可访问 |
| 广州企业灯塔服务 | `REPAIR_REQUIRED` | 2026-07-14 只读检查发现服务账户缺少运行目录访问权限,处于重启循环;不得在此基础上直接叠加新功能 |
| 企业灯塔基础服务源码 | `REGISTERED_TESTED_SOURCE · DEPLOY_PENDING` | `HLP-LIGHTHOUSE-001` 已实现节点接入申请、固定动作预检与审计;不接受裸命令 |
| 节点注册库 | `NOT_DEPLOYED` | 尚未有此架构下的正式 `domain / node / human / persona / instance` 关系库 |
| GLSV 授权中心(企业灯塔) | `NOT_DEPLOYED` | 需在修复灯塔生命周期后部署 |
| GLSV 节点连接器 | `NOT_DEPLOYED` | 需按域入口、再按个人服务器逐台接入 |

View File

@ -0,0 +1,7 @@
# 企业灯塔基础服务
这是总灯塔的第一阶段:五域登记、节点接入申请、固定动作预检和审计。它**不是**远程 Shell拒绝 `cmd``shell``command` 字段,也不执行操作。
部署后只监听本机 `127.0.0.1:8031`。公众网站与后续 GLSV 页面通过 Nginx 以单独的受控路由接入;在邮件确认与节点连接器完成前,不开放节点激活或执行。
固定动作只有:`health_check``backup``deploy_release``restart_service``rollback`。部署、重启和回滚必须在预检中具备备份引用与回滚计划;预检通过也只是“可申请人类授权”,不会执行。

View File

@ -0,0 +1,21 @@
[Unit]
Description=Guanghu Enterprise Lighthouse Registry and Preflight
After=network.target
[Service]
Type=simple
User=lighthouse
Group=lighthouse
WorkingDirectory=/opt/guanghu-enterprise-lighthouse
EnvironmentFile=/etc/guanghu-enterprise-lighthouse.env
ExecStart=/usr/bin/python3 /opt/guanghu-enterprise-lighthouse/lighthouse.py
Restart=on-failure
RestartSec=3
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/guanghu-enterprise-lighthouse
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,162 @@
#!/usr/bin/env python3
"""Enterprise Lighthouse foundation: registry, preflight, and audit only.
This service is deliberately not a remote shell. It accepts node admission
requests and validates fixed operation plans; execution remains with a future
GLSV node connector after human authorization.
"""
import hmac
import json
import os
import sqlite3
import time
import uuid
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from ipaddress import ip_address
DB = os.environ.get("LIGHTHOUSE_DB", "/var/lib/guanghu-enterprise-lighthouse/lighthouse.db")
TOKEN = os.environ.get("LIGHTHOUSE_ADMIN_TOKEN", "")
HOST = os.environ.get("LIGHTHOUSE_BIND", "127.0.0.1")
PORT = int(os.environ.get("LIGHTHOUSE_PORT", "8031"))
FIXED_ACTIONS = {"health_check", "backup", "deploy_release", "restart_service", "rollback"}
DOMAINS = {
"DOMAIN-ZS": "零感域", "DOMAIN-MAIN": "光湖主域", "DOMAIN-SUB": "光湖分域",
"DOMAIN-ZERO": "光湖零域", "DOMAIN-FIFTH": "第五域",
}
def now():
return int(time.time())
def connection():
os.makedirs(os.path.dirname(DB), exist_ok=True)
db = sqlite3.connect(DB)
db.row_factory = sqlite3.Row
db.executescript("""
CREATE TABLE IF NOT EXISTS domains (
id TEXT PRIMARY KEY, name TEXT NOT NULL, state TEXT NOT NULL, created_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS intakes (
id TEXT PRIMARY KEY, created_at INTEGER NOT NULL, state TEXT NOT NULL,
human_name TEXT NOT NULL, email TEXT NOT NULL, server_ip TEXT NOT NULL,
domain_id TEXT NOT NULL, persona_ids TEXT NOT NULL, repository_urls TEXT NOT NULL,
hosting_mode TEXT NOT NULL, notes TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS nodes (
id TEXT PRIMARY KEY, domain_id TEXT NOT NULL, intake_id TEXT, state TEXT NOT NULL,
display_name TEXT NOT NULL, server_ip TEXT NOT NULL, allowed_actions TEXT NOT NULL,
public_key_fingerprint TEXT, last_heartbeat INTEGER, created_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS audit (
id TEXT PRIMARY KEY, created_at INTEGER NOT NULL, kind TEXT NOT NULL, payload TEXT NOT NULL
);
""")
for domain_id, name in DOMAINS.items():
db.execute("INSERT OR IGNORE INTO domains VALUES (?, ?, 'PENDING_ENTRY_NODE', ?)", (domain_id, name, now()))
db.commit()
return db
def audit(db, kind, payload):
db.execute("INSERT INTO audit VALUES (?, ?, ?, ?)", (str(uuid.uuid4()), now(), kind, json.dumps(payload, ensure_ascii=False)))
db.commit()
def parse_json(handler):
length = int(handler.headers.get("Content-Length", "0"))
if not 0 < length <= 50_000:
raise ValueError("request body must be between 1 and 50000 bytes")
return json.loads(handler.rfile.read(length).decode("utf-8"))
def valid_email(value):
return isinstance(value, str) and len(value) <= 254 and value.count("@") == 1
def require_admin(handler):
received = handler.headers.get("X-Lighthouse-Admin-Token", "")
return bool(TOKEN) and hmac.compare_digest(received, TOKEN)
class Handler(BaseHTTPRequestHandler):
server_version = "GuanghuEnterpriseLighthouse/1.0"
def log_message(self, fmt, *args):
print("[lighthouse] " + fmt % args)
def respond(self, status, body):
encoded = json.dumps(body, ensure_ascii=False).encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(encoded)))
self.send_header("Cache-Control", "no-store")
self.end_headers()
self.wfile.write(encoded)
def do_GET(self):
db = connection()
try:
if self.path == "/health":
return self.respond(200, {"ok": True, "service": "guanghu-enterprise-lighthouse", "mode": "registry-and-preflight-only", "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"})
if self.path == "/v1/nodes":
return self.respond(200, {"ok": True, "nodes": [dict(row) for row in db.execute("SELECT id,domain_id,state,display_name,allowed_actions,last_heartbeat,created_at FROM nodes ORDER BY created_at DESC")]})
return self.respond(404, {"ok": False, "error": "not found"})
finally:
db.close()
def do_POST(self):
if not require_admin(self):
return self.respond(401, {"ok": False, "error": "admin authorization required"})
try:
payload = parse_json(self)
except (ValueError, json.JSONDecodeError) as error:
return self.respond(400, {"ok": False, "error": str(error)})
if "cmd" in payload or "shell" in payload or "command" in payload:
return self.respond(400, {"ok": False, "error": "raw commands are never accepted by the lighthouse"})
db = connection()
try:
if self.path == "/v1/intakes":
required = ("human_name", "email", "server_ip", "domain_id")
if any(not payload.get(field) for field in required) or payload["domain_id"] not in DOMAINS or not valid_email(payload["email"]):
return self.respond(400, {"ok": False, "error": "human_name, valid email, server_ip, and known domain_id are required"})
try:
ip_address(payload["server_ip"])
except ValueError:
return self.respond(400, {"ok": False, "error": "server_ip must be a valid IP address"})
intake_id = "INTAKE-" + uuid.uuid4().hex[:12].upper()
db.execute("INSERT INTO intakes VALUES (?, ?, 'PENDING_REVIEW', ?, ?, ?, ?, ?, ?, ?, ?)", (
intake_id, now(), payload["human_name"].strip(), payload["email"].strip(), payload["server_ip"], payload["domain_id"],
json.dumps(payload.get("persona_ids", [])), json.dumps(payload.get("repository_urls", [])),
payload.get("hosting_mode", "own"), payload.get("notes", ""),
))
audit(db, "intake_created", {"intake_id": intake_id, "domain_id": payload["domain_id"]})
return self.respond(201, {"ok": True, "intake_id": intake_id, "state": "PENDING_REVIEW", "next": "sovereign approval, human email confirmation, then node connector enrollment"})
if self.path == "/v1/preflight":
action, node_id = payload.get("action"), payload.get("target_node_id")
if action not in FIXED_ACTIONS or not node_id:
return self.respond(400, {"ok": False, "error": "fixed action and target_node_id are required", "allowed_actions": sorted(FIXED_ACTIONS)})
node = db.execute("SELECT * FROM nodes WHERE id=?", (node_id,)).fetchone()
if not node:
return self.respond(404, {"ok": False, "decision": "REJECT", "reason": "target node is not registered"})
allowed = json.loads(node["allowed_actions"])
reasons = []
if node["state"] != "ACTIVE": reasons.append("target node is not ACTIVE")
if action not in allowed: reasons.append("action is not in the node allowlist")
if action in {"deploy_release", "restart_service", "rollback"} and not payload.get("rollback_plan"): reasons.append("rollback_plan is required")
if action in {"deploy_release", "restart_service"} and not payload.get("backup_reference"): reasons.append("backup_reference is required")
decision = "ALLOW_FOR_AUTHORIZATION" if not reasons else "REJECT"
result = {"ok": not reasons, "decision": decision, "reasons": reasons, "execution": "not performed; GLSV human authorization still required"}
audit(db, "preflight", {"target_node_id": node_id, "action": action, "decision": decision, "reasons": reasons})
return self.respond(200, result)
return self.respond(404, {"ok": False, "error": "not found"})
finally:
db.close()
if __name__ == "__main__":
print(f"Enterprise Lighthouse listening on {HOST}:{PORT}")
ThreadingHTTPServer((HOST, PORT), Handler).serve_forever()

View File

@ -0,0 +1,31 @@
#!/usr/bin/env python3
import json
import os
import subprocess
import sys
import tempfile
import time
import urllib.request
from pathlib import Path
ROOT = Path(__file__).parent
with tempfile.TemporaryDirectory() as temp:
env = {**os.environ, "LIGHTHOUSE_DB": f"{temp}/lighthouse.db", "LIGHTHOUSE_ADMIN_TOKEN": "test-token", "LIGHTHOUSE_PORT": "48031"}
process = subprocess.Popen([sys.executable, "lighthouse.py"], cwd=ROOT, env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
try:
for _ in range(30):
try:
assert json.load(urllib.request.urlopen("http://127.0.0.1:48031/health", timeout=1))["execution"] == "disabled"
break
except OSError:
time.sleep(.1)
else: raise AssertionError("server did not start")
request = urllib.request.Request("http://127.0.0.1:48031/v1/intakes", data=json.dumps({"human_name":"Test","email":"test@example.invalid","server_ip":"203.0.113.8","domain_id":"DOMAIN-FIFTH"}).encode(), method="POST", headers={"X-Lighthouse-Admin-Token":"test-token"})
assert json.load(urllib.request.urlopen(request))["state"] == "PENDING_REVIEW"
bad = urllib.request.Request("http://127.0.0.1:48031/v1/intakes", data=b'{"cmd":"rm -rf /"}', method="POST", headers={"X-Lighthouse-Admin-Token":"test-token"})
try: urllib.request.urlopen(bad)
except urllib.error.HTTPError as error: assert error.code == 400
else: raise AssertionError("raw command was not rejected")
finally:
process.terminate(); process.wait(timeout=5)
print("enterprise lighthouse tests passed")

View File

@ -20,6 +20,7 @@
- [x] 登记桌面接入表为 `PENDING_REVIEW` 的输入窗口
- [x] 登记 GLSV 中心 / 节点连接器双层模型
- [x] 对广州企业灯塔完成只读基础盘点
- [x] 完成企业灯塔基础服务源码与本地测试:登记申请、固定动作预检、审计;拒绝裸命令
## 当前阻塞
@ -31,7 +32,7 @@
## 后续固定顺序
1. 修复企业灯塔服务生命周期并保存当前配置备份。
2. 将灯塔源码与部署单元纳入正式仓库;建立测试与回滚点。
2. 部署已入第五域的 `HLP-LIGHTHOUSE-001`建立测试与回滚点。
3. 部署 GLSV Authorization Center先只读 / 申请状态)。
4. 部署一个 GLSV Node Connector 测试节点;验证邮箱确认、心跳和回执。
5. 登记每个域入口节点,再登记个人服务器节点。