422 lines
16 KiB
Python
422 lines
16 KiB
Python
|
|
#!/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()
|