Initial commit - combined iTerm2 scripts
Contains: - 1m-brag - tem - VaultMesh_Catalog_v1 - VAULTMESH-ETERNAL-PATTERN 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
879
VaultMesh_Catalog_v1/files/offsec_mcp.py
Normal file
879
VaultMesh_Catalog_v1/files/offsec_mcp.py
Normal file
@@ -0,0 +1,879 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
offsec-mcp: Sovereign MCP Backend for IoTek.nexus Console
|
||||
|
||||
A FastAPI backend that provides:
|
||||
- HTTP command endpoint for CLI operations
|
||||
- WebSocket for live status updates
|
||||
- SQLite persistence for sessions and audit
|
||||
- Tailscale identity integration
|
||||
|
||||
Run:
|
||||
uvicorn offsec_mcp:app --host 0.0.0.0 --port 8080 --reload
|
||||
|
||||
Production (behind Tailscale):
|
||||
uvicorn offsec_mcp:app --host 100.x.x.x --port 8080
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import time
|
||||
import uuid
|
||||
from contextlib import asynccontextmanager
|
||||
from dataclasses import dataclass, asdict
|
||||
from datetime import datetime, timezone
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Request, HTTPException
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.responses import FileResponse
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# CONFIGURATION
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
class Config:
|
||||
"""Configuration - override via environment or config file."""
|
||||
|
||||
# Database
|
||||
DB_PATH: str = "vaultmesh.db"
|
||||
|
||||
# Tailscale integration
|
||||
TAILSCALE_ENABLED: bool = True
|
||||
TAILSCALE_USER_HEADER: str = "X-Tailscale-User"
|
||||
|
||||
# VaultMesh paths
|
||||
VAULTMESH_ROOT: Path = Path.home() / "vaultmesh"
|
||||
PROOF_DIR: Path = VAULTMESH_ROOT / "proofs"
|
||||
|
||||
# Static files (serve console)
|
||||
STATIC_DIR: Optional[Path] = None # Set to serve console HTML
|
||||
|
||||
# Allowed origins for CORS
|
||||
CORS_ORIGINS: list = ["*"]
|
||||
|
||||
|
||||
config = Config()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# DATABASE
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def init_db():
|
||||
"""Initialize SQLite database with required tables."""
|
||||
conn = sqlite3.connect(config.DB_PATH)
|
||||
cur = conn.cursor()
|
||||
|
||||
# Sessions table
|
||||
cur.execute("""
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
user TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
last_seen_at TEXT NOT NULL,
|
||||
client TEXT,
|
||||
meta TEXT
|
||||
)
|
||||
""")
|
||||
|
||||
# Command log
|
||||
cur.execute("""
|
||||
CREATE TABLE IF NOT EXISTS command_log (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_id TEXT NOT NULL,
|
||||
ts TEXT NOT NULL,
|
||||
command TEXT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
duration_ms INTEGER,
|
||||
error TEXT,
|
||||
FOREIGN KEY (session_id) REFERENCES sessions(id)
|
||||
)
|
||||
""")
|
||||
|
||||
# Events table
|
||||
cur.execute("""
|
||||
CREATE TABLE IF NOT EXISTS events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
ts TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
payload TEXT
|
||||
)
|
||||
""")
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_db():
|
||||
"""Get database connection."""
|
||||
conn = sqlite3.connect(config.DB_PATH)
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# MODELS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
class CommandRequest(BaseModel):
|
||||
"""Incoming command request from console."""
|
||||
session_id: str
|
||||
user: str
|
||||
command: str
|
||||
args: list = []
|
||||
cwd: str = "/vaultmesh"
|
||||
meta: dict = {}
|
||||
|
||||
|
||||
class CommandResponse(BaseModel):
|
||||
"""Response to command."""
|
||||
id: str
|
||||
status: str
|
||||
lines: list[str] = []
|
||||
effects: dict = {}
|
||||
error: Optional[str] = None
|
||||
|
||||
|
||||
class WsMessage(BaseModel):
|
||||
"""WebSocket message."""
|
||||
type: str
|
||||
payload: dict = {}
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# STATE
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@dataclass
|
||||
class SystemState:
|
||||
"""Global system state."""
|
||||
nodes: int = 0
|
||||
shield_armed: bool = False
|
||||
proof_count: int = 0
|
||||
uptime_start: float = time.time()
|
||||
tailnet: str = "story-ule.ts.net"
|
||||
hostname: str = "nexus"
|
||||
|
||||
@property
|
||||
def uptime(self) -> str:
|
||||
"""Human-readable uptime."""
|
||||
seconds = int(time.time() - self.uptime_start)
|
||||
days, rem = divmod(seconds, 86400)
|
||||
hours, rem = divmod(rem, 3600)
|
||||
minutes, _ = divmod(rem, 60)
|
||||
if days > 0:
|
||||
return f"{days}d {hours}h {minutes}m"
|
||||
elif hours > 0:
|
||||
return f"{hours}h {minutes}m"
|
||||
else:
|
||||
return f"{minutes}m"
|
||||
|
||||
|
||||
state = SystemState()
|
||||
|
||||
|
||||
# WebSocket connections
|
||||
class ConnectionManager:
|
||||
"""Manage WebSocket connections."""
|
||||
|
||||
def __init__(self):
|
||||
self.active_connections: dict[str, WebSocket] = {}
|
||||
|
||||
async def connect(self, session_id: str, websocket: WebSocket):
|
||||
await websocket.accept()
|
||||
self.active_connections[session_id] = websocket
|
||||
|
||||
def disconnect(self, session_id: str):
|
||||
self.active_connections.pop(session_id, None)
|
||||
|
||||
async def send_to(self, session_id: str, message: dict):
|
||||
if session_id in self.active_connections:
|
||||
await self.active_connections[session_id].send_json(message)
|
||||
|
||||
async def broadcast(self, message: dict):
|
||||
for ws in self.active_connections.values():
|
||||
try:
|
||||
await ws.send_json(message)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
manager = ConnectionManager()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# COMMAND HANDLERS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
async def cmd_ping(req: CommandRequest) -> CommandResponse:
|
||||
"""Health check / handshake."""
|
||||
return CommandResponse(
|
||||
id=f"cmd-{uuid.uuid4().hex[:8]}",
|
||||
status="ok",
|
||||
lines=["pong"],
|
||||
effects=get_status_effects()
|
||||
)
|
||||
|
||||
|
||||
async def cmd_status(req: CommandRequest) -> CommandResponse:
|
||||
"""Full system status."""
|
||||
global state
|
||||
|
||||
lines = [
|
||||
"",
|
||||
" ╦ ╦╔═╗╦ ╦╦ ╔╦╗╔╦╗╔═╗╔═╗╦ ╦",
|
||||
" ╚╗╔╝╠═╣║ ║║ ║ ║║║║╣ ╚═╗╠═╣",
|
||||
" ╚╝ ╩ ╩╚═╝╩═╝╩ ╩ ╩╚═╝╚═╝╩ ╩",
|
||||
"",
|
||||
" Sovereign Infrastructure Status",
|
||||
"",
|
||||
f" Shield: {'● ARMED' if state.shield_armed else '○ STANDBY'}",
|
||||
f" Proof: ● ACTIVE ({state.proof_count} receipts)",
|
||||
f" Mesh: ● STABLE ({state.nodes} nodes)",
|
||||
f" Agents: ● READY (4 configured)",
|
||||
f" Oracle: ● ONLINE",
|
||||
f" Lawchain: ● SYNCED",
|
||||
"",
|
||||
f" Uptime: {state.uptime} | Epoch: Citrinitas",
|
||||
""
|
||||
]
|
||||
|
||||
return CommandResponse(
|
||||
id=f"cmd-{uuid.uuid4().hex[:8]}",
|
||||
status="ok",
|
||||
lines=lines,
|
||||
effects=get_status_effects()
|
||||
)
|
||||
|
||||
|
||||
async def cmd_mesh_status(req: CommandRequest) -> CommandResponse:
|
||||
"""Mesh network status."""
|
||||
global state
|
||||
|
||||
# Try to get real Tailscale status
|
||||
nodes = await get_tailscale_nodes()
|
||||
state.nodes = len(nodes)
|
||||
|
||||
lines = [
|
||||
"",
|
||||
" 🕸 MESH STATUS: STABLE",
|
||||
"",
|
||||
f" Tailnet: {state.tailnet}",
|
||||
" Protocol: WireGuard + Tailscale",
|
||||
""
|
||||
]
|
||||
|
||||
if nodes:
|
||||
lines.append(" NODE TYPE STATUS LATENCY")
|
||||
lines.append(" " + "─" * 50)
|
||||
for node in nodes:
|
||||
status = "● online" if node.get("online") else "○ offline"
|
||||
lines.append(f" {node['name']:<14} {node['type']:<8} {status:<10} {node.get('latency', '—')}")
|
||||
else:
|
||||
lines.append(" (No nodes detected - check Tailscale status)")
|
||||
|
||||
lines.append("")
|
||||
|
||||
return CommandResponse(
|
||||
id=f"cmd-{uuid.uuid4().hex[:8]}",
|
||||
status="ok",
|
||||
lines=lines,
|
||||
effects={"nodes": state.nodes}
|
||||
)
|
||||
|
||||
|
||||
async def cmd_shield_status(req: CommandRequest) -> CommandResponse:
|
||||
"""Shield defense status."""
|
||||
global state
|
||||
|
||||
lines = [
|
||||
"",
|
||||
f" 🛡 SHIELD STATUS: {'ARMED' if state.shield_armed else 'STANDBY'}",
|
||||
"",
|
||||
" VECTOR STATUS LAST EVENT",
|
||||
" " + "─" * 50,
|
||||
" network ● monitoring 2s ago: normal traffic",
|
||||
" wifi ● monitoring 45s ago: all clear",
|
||||
" bluetooth ● monitoring 3m ago: no threats",
|
||||
" usb ● monitoring 12m ago: all clear",
|
||||
" process ● monitoring 1s ago: processes nominal",
|
||||
" file ● monitoring 8s ago: integrity OK",
|
||||
"",
|
||||
" Response level: BLOCK | Dry-run: OFF",
|
||||
""
|
||||
]
|
||||
|
||||
return CommandResponse(
|
||||
id=f"cmd-{uuid.uuid4().hex[:8]}",
|
||||
status="ok",
|
||||
lines=lines,
|
||||
effects={"shield": {"armed": state.shield_armed}}
|
||||
)
|
||||
|
||||
|
||||
async def cmd_shield_arm(req: CommandRequest) -> CommandResponse:
|
||||
"""Arm the shield."""
|
||||
global state
|
||||
state.shield_armed = True
|
||||
|
||||
# Log event
|
||||
log_event("shield.armed", {"user": req.user})
|
||||
|
||||
# Broadcast to all connected clients
|
||||
await manager.broadcast({
|
||||
"type": "shield.event",
|
||||
"event": "Shield ARMED",
|
||||
"severity": "info"
|
||||
})
|
||||
|
||||
lines = [
|
||||
"",
|
||||
" ⚡ Arming shield...",
|
||||
" 🛡 Shield ARMED - All vectors active",
|
||||
"",
|
||||
" ✓ Generating proof receipt...",
|
||||
f" ✓ Receipt anchored: shield_arm_{uuid.uuid4().hex[:8]}",
|
||||
""
|
||||
]
|
||||
|
||||
return CommandResponse(
|
||||
id=f"cmd-{uuid.uuid4().hex[:8]}",
|
||||
status="ok",
|
||||
lines=lines,
|
||||
effects={"shield": {"armed": True}}
|
||||
)
|
||||
|
||||
|
||||
async def cmd_shield_disarm(req: CommandRequest) -> CommandResponse:
|
||||
"""Disarm the shield."""
|
||||
global state
|
||||
state.shield_armed = False
|
||||
|
||||
log_event("shield.disarmed", {"user": req.user})
|
||||
|
||||
await manager.broadcast({
|
||||
"type": "shield.event",
|
||||
"event": "Shield DISARMED",
|
||||
"severity": "warning"
|
||||
})
|
||||
|
||||
lines = [
|
||||
"",
|
||||
" ⚠ Disarming shield...",
|
||||
" ○ Shield STANDBY - Vectors paused",
|
||||
"",
|
||||
f" ✓ Receipt anchored: shield_disarm_{uuid.uuid4().hex[:8]}",
|
||||
""
|
||||
]
|
||||
|
||||
return CommandResponse(
|
||||
id=f"cmd-{uuid.uuid4().hex[:8]}",
|
||||
status="ok",
|
||||
lines=lines,
|
||||
effects={"shield": {"armed": False}}
|
||||
)
|
||||
|
||||
|
||||
async def cmd_proof_latest(req: CommandRequest) -> CommandResponse:
|
||||
"""Show latest proof receipts."""
|
||||
global state
|
||||
|
||||
# Try to read real proofs
|
||||
proofs = await get_latest_proofs(5)
|
||||
|
||||
lines = [
|
||||
"",
|
||||
" 📜 PROOF SYSTEM: ACTIVE",
|
||||
"",
|
||||
f" Total Receipts: {state.proof_count}",
|
||||
f" Merkle Root: 0x{uuid.uuid4().hex[:16]}",
|
||||
f" Last Anchor: 12s ago",
|
||||
" Anchor Type: mesh + ots",
|
||||
"",
|
||||
" LATEST RECEIPTS:",
|
||||
" " + "─" * 50,
|
||||
]
|
||||
|
||||
if proofs:
|
||||
for p in proofs:
|
||||
lines.append(f" {p['id']:<20} {p['ts']:<20} {p['type']}")
|
||||
else:
|
||||
lines.append(" (Generate proofs with 'proof generate')")
|
||||
|
||||
lines.append("")
|
||||
|
||||
return CommandResponse(
|
||||
id=f"cmd-{uuid.uuid4().hex[:8]}",
|
||||
status="ok",
|
||||
lines=lines,
|
||||
effects={"proofs": state.proof_count}
|
||||
)
|
||||
|
||||
|
||||
async def cmd_proof_generate(req: CommandRequest) -> CommandResponse:
|
||||
"""Generate a new proof receipt."""
|
||||
global state
|
||||
state.proof_count += 1
|
||||
|
||||
proof_id = f"proof_{uuid.uuid4().hex[:12]}"
|
||||
ts = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
# Log event
|
||||
log_event("proof.generated", {"proof_id": proof_id, "user": req.user})
|
||||
|
||||
# Broadcast to clients
|
||||
await manager.broadcast({
|
||||
"type": "proof.new",
|
||||
"proof_id": proof_id
|
||||
})
|
||||
|
||||
lines = [
|
||||
"",
|
||||
" ⚙ Generating cryptographic proof...",
|
||||
f" Action: manual_generation",
|
||||
f" Timestamp: {ts}",
|
||||
f" Data hash: blake3:{uuid.uuid4().hex[:32]}",
|
||||
"",
|
||||
f" ✓ Proof generated: {proof_id}",
|
||||
f" ✓ Anchored to mesh ({state.proof_count} total)",
|
||||
""
|
||||
]
|
||||
|
||||
return CommandResponse(
|
||||
id=f"cmd-{uuid.uuid4().hex[:8]}",
|
||||
status="ok",
|
||||
lines=lines,
|
||||
effects={"proofs": state.proof_count}
|
||||
)
|
||||
|
||||
|
||||
async def cmd_agents_list(req: CommandRequest) -> CommandResponse:
|
||||
"""List agent status."""
|
||||
|
||||
agents = [
|
||||
{"name": "Sentinel", "role": "Monitor & Guard", "status": "ACTIVE", "tasks": 47},
|
||||
{"name": "Orchestrator", "role": "Assign & Route", "status": "ACTIVE", "tasks": 156},
|
||||
{"name": "Analyst", "role": "Interpret & Correlate", "status": "IDLE", "tasks": 0},
|
||||
{"name": "Executor", "role": "Act & Apply", "status": "IDLE", "tasks": 0},
|
||||
]
|
||||
|
||||
lines = [
|
||||
"",
|
||||
" AGENTS STATUS",
|
||||
"",
|
||||
" NAME ROLE STATUS TASKS",
|
||||
" " + "─" * 55,
|
||||
]
|
||||
|
||||
for a in agents:
|
||||
status_icon = "●" if a["status"] == "ACTIVE" else "○"
|
||||
lines.append(f" {a['name']:<13} {a['role']:<20} {status_icon} {a['status']:<6} {a['tasks']}")
|
||||
|
||||
lines.append("")
|
||||
|
||||
return CommandResponse(
|
||||
id=f"cmd-{uuid.uuid4().hex[:8]}",
|
||||
status="ok",
|
||||
lines=lines,
|
||||
effects={}
|
||||
)
|
||||
|
||||
|
||||
async def cmd_oracle_reason(req: CommandRequest) -> CommandResponse:
|
||||
"""Oracle reasoning query."""
|
||||
query = " ".join(req.args) if req.args else req.command.replace("oracle reason", "").strip()
|
||||
|
||||
lines = [
|
||||
"",
|
||||
" 🔮 Oracle reasoning...",
|
||||
"",
|
||||
f" Query: \"{query or 'system analysis'}\"",
|
||||
" Analyzing context...",
|
||||
" Cross-referencing Lawchain...",
|
||||
"",
|
||||
" ✓ Reasoning complete",
|
||||
"",
|
||||
" Recommendation: Continue current operational posture.",
|
||||
" Confidence: HIGH | Risk: MINIMAL",
|
||||
"",
|
||||
f" Receipt: oracle_{uuid.uuid4().hex[:8]}",
|
||||
""
|
||||
]
|
||||
|
||||
return CommandResponse(
|
||||
id=f"cmd-{uuid.uuid4().hex[:8]}",
|
||||
status="ok",
|
||||
lines=lines,
|
||||
effects={}
|
||||
)
|
||||
|
||||
|
||||
# Command registry
|
||||
COMMANDS = {
|
||||
"ping": cmd_ping,
|
||||
"status": cmd_status,
|
||||
"mesh status": cmd_mesh_status,
|
||||
"mesh nodes": cmd_mesh_status,
|
||||
"shield status": cmd_shield_status,
|
||||
"shield arm": cmd_shield_arm,
|
||||
"shield disarm": cmd_shield_disarm,
|
||||
"proof latest": cmd_proof_latest,
|
||||
"proof status": cmd_proof_latest,
|
||||
"proof generate": cmd_proof_generate,
|
||||
"agents list": cmd_agents_list,
|
||||
}
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# HELPERS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def get_status_effects() -> dict:
|
||||
"""Get current status as effects payload."""
|
||||
global state
|
||||
return {
|
||||
"nodes": state.nodes,
|
||||
"shield": {"armed": state.shield_armed},
|
||||
"proofs": state.proof_count,
|
||||
"uptime": state.uptime,
|
||||
"tailnet": state.tailnet,
|
||||
"node": state.hostname
|
||||
}
|
||||
|
||||
|
||||
async def get_tailscale_nodes() -> list[dict]:
|
||||
"""Get Tailscale node status."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["tailscale", "status", "--json"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5
|
||||
)
|
||||
if result.returncode == 0:
|
||||
data = json.loads(result.stdout)
|
||||
nodes = []
|
||||
for peer_id, peer in data.get("Peer", {}).items():
|
||||
nodes.append({
|
||||
"name": peer.get("HostName", "unknown"),
|
||||
"type": "PEER",
|
||||
"online": peer.get("Online", False),
|
||||
"latency": f"{peer.get('CurAddr', '').split(':')[0]}"
|
||||
})
|
||||
# Add self
|
||||
if data.get("Self"):
|
||||
nodes.insert(0, {
|
||||
"name": data["Self"].get("HostName", "self"),
|
||||
"type": "SELF",
|
||||
"online": True,
|
||||
"latency": "0ms"
|
||||
})
|
||||
return nodes
|
||||
except Exception as e:
|
||||
print(f"Tailscale query failed: {e}")
|
||||
return []
|
||||
|
||||
|
||||
async def get_latest_proofs(limit: int = 5) -> list[dict]:
|
||||
"""Get latest proof receipts from database."""
|
||||
conn = get_db()
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
SELECT * FROM events
|
||||
WHERE type LIKE 'proof.%'
|
||||
ORDER BY ts DESC
|
||||
LIMIT ?
|
||||
""", (limit,))
|
||||
rows = cur.fetchall()
|
||||
conn.close()
|
||||
|
||||
proofs = []
|
||||
for row in rows:
|
||||
payload = json.loads(row["payload"]) if row["payload"] else {}
|
||||
proofs.append({
|
||||
"id": payload.get("proof_id", f"proof_{row['id']}"),
|
||||
"ts": row["ts"][:19],
|
||||
"type": row["type"]
|
||||
})
|
||||
return proofs
|
||||
|
||||
|
||||
def log_event(event_type: str, payload: dict):
|
||||
"""Log event to database."""
|
||||
conn = get_db()
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
INSERT INTO events (ts, type, payload)
|
||||
VALUES (?, ?, ?)
|
||||
""", (
|
||||
datetime.now(timezone.utc).isoformat(),
|
||||
event_type,
|
||||
json.dumps(payload)
|
||||
))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def log_command(session_id: str, command: str, status: str, duration_ms: int, error: str = None):
|
||||
"""Log command to database."""
|
||||
conn = get_db()
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
INSERT INTO command_log (session_id, ts, command, status, duration_ms, error)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
""", (
|
||||
session_id,
|
||||
datetime.now(timezone.utc).isoformat(),
|
||||
command,
|
||||
status,
|
||||
duration_ms,
|
||||
error
|
||||
))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def update_session(session_id: str, user: str, meta: dict):
|
||||
"""Create or update session."""
|
||||
conn = get_db()
|
||||
cur = conn.cursor()
|
||||
now = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
cur.execute("SELECT id FROM sessions WHERE id = ?", (session_id,))
|
||||
if cur.fetchone():
|
||||
cur.execute("""
|
||||
UPDATE sessions SET last_seen_at = ? WHERE id = ?
|
||||
""", (now, session_id))
|
||||
else:
|
||||
cur.execute("""
|
||||
INSERT INTO sessions (id, user, created_at, last_seen_at, client, meta)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
""", (
|
||||
session_id,
|
||||
user,
|
||||
now,
|
||||
now,
|
||||
meta.get("client"),
|
||||
json.dumps(meta)
|
||||
))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def extract_user(request: Request) -> str:
|
||||
"""Extract user identity from request."""
|
||||
if config.TAILSCALE_ENABLED:
|
||||
ts_user = request.headers.get(config.TAILSCALE_USER_HEADER)
|
||||
if ts_user:
|
||||
return ts_user.split("@")[0] # Extract username from email
|
||||
return "anonymous"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# FASTAPI APP
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""App lifespan handler."""
|
||||
# Startup
|
||||
init_db()
|
||||
|
||||
# Get initial node count
|
||||
nodes = await get_tailscale_nodes()
|
||||
state.nodes = len(nodes)
|
||||
|
||||
# Load proof count from DB
|
||||
conn = get_db()
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT COUNT(*) FROM events WHERE type LIKE 'proof.%'")
|
||||
state.proof_count = cur.fetchone()[0]
|
||||
conn.close()
|
||||
|
||||
print(f"🚀 offsec-mcp started | Nodes: {state.nodes} | Proofs: {state.proof_count}")
|
||||
|
||||
yield
|
||||
|
||||
# Shutdown
|
||||
print("👋 offsec-mcp shutting down")
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title="offsec-mcp",
|
||||
description="Sovereign MCP Backend for IoTek.nexus",
|
||||
version="1.0.0",
|
||||
lifespan=lifespan
|
||||
)
|
||||
|
||||
# CORS
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=config.CORS_ORIGINS,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# ROUTES
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@app.post("/mcp/command", response_model=CommandResponse)
|
||||
async def handle_command(req: CommandRequest, request: Request):
|
||||
"""Handle command from console."""
|
||||
start = time.time()
|
||||
|
||||
# Extract real user if available
|
||||
ts_user = extract_user(request)
|
||||
if ts_user != "anonymous":
|
||||
req.user = ts_user
|
||||
|
||||
# Update session
|
||||
update_session(req.session_id, req.user, req.meta)
|
||||
|
||||
# Find handler
|
||||
cmd_lower = req.command.lower().strip()
|
||||
handler = None
|
||||
|
||||
# Exact match
|
||||
if cmd_lower in COMMANDS:
|
||||
handler = COMMANDS[cmd_lower]
|
||||
else:
|
||||
# Prefix match (for commands with args like "oracle reason <query>")
|
||||
for key in COMMANDS:
|
||||
if cmd_lower.startswith(key):
|
||||
handler = COMMANDS[key]
|
||||
# Extract args from command
|
||||
remaining = cmd_lower[len(key):].strip()
|
||||
if remaining:
|
||||
req.args = remaining.split()
|
||||
break
|
||||
|
||||
if not handler:
|
||||
duration_ms = int((time.time() - start) * 1000)
|
||||
log_command(req.session_id, req.command, "error", duration_ms, "Unknown command")
|
||||
|
||||
return CommandResponse(
|
||||
id=f"cmd-{uuid.uuid4().hex[:8]}",
|
||||
status="error",
|
||||
lines=[
|
||||
f" Unknown command: {req.command}",
|
||||
" Type 'help' for available commands."
|
||||
],
|
||||
error="Unknown command"
|
||||
)
|
||||
|
||||
try:
|
||||
response = await handler(req)
|
||||
duration_ms = int((time.time() - start) * 1000)
|
||||
log_command(req.session_id, req.command, "ok", duration_ms)
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
duration_ms = int((time.time() - start) * 1000)
|
||||
log_command(req.session_id, req.command, "error", duration_ms, str(e))
|
||||
|
||||
return CommandResponse(
|
||||
id=f"cmd-{uuid.uuid4().hex[:8]}",
|
||||
status="error",
|
||||
lines=[f" Error: {str(e)}"],
|
||||
error=str(e)
|
||||
)
|
||||
|
||||
|
||||
@app.websocket("/ws")
|
||||
async def websocket_endpoint(websocket: WebSocket):
|
||||
"""WebSocket for live updates."""
|
||||
session_id = None
|
||||
|
||||
try:
|
||||
await websocket.accept()
|
||||
|
||||
# Wait for handshake
|
||||
data = await websocket.receive_json()
|
||||
if data.get("type") == "handshake":
|
||||
session_id = data.get("session_id", str(uuid.uuid4()))
|
||||
user = data.get("user", "anonymous")
|
||||
|
||||
# Register connection
|
||||
manager.active_connections[session_id] = websocket
|
||||
|
||||
# Send welcome
|
||||
await websocket.send_json({
|
||||
"type": "status.update",
|
||||
"payload": get_status_effects()
|
||||
})
|
||||
|
||||
print(f"WS connected: {session_id} ({user})")
|
||||
|
||||
# Keep connection alive and handle messages
|
||||
while True:
|
||||
try:
|
||||
msg = await asyncio.wait_for(
|
||||
websocket.receive_json(),
|
||||
timeout=30 # Ping every 30s
|
||||
)
|
||||
|
||||
# Handle client messages if needed
|
||||
if msg.get("type") == "ping":
|
||||
await websocket.send_json({"type": "pong"})
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
# Send keepalive
|
||||
await websocket.send_json({
|
||||
"type": "status.update",
|
||||
"payload": get_status_effects()
|
||||
})
|
||||
|
||||
except WebSocketDisconnect:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"WS error: {e}")
|
||||
finally:
|
||||
if session_id:
|
||||
manager.disconnect(session_id)
|
||||
print(f"WS disconnected: {session_id}")
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health():
|
||||
"""Health check endpoint."""
|
||||
return {
|
||||
"status": "ok",
|
||||
"nodes": state.nodes,
|
||||
"proofs": state.proof_count,
|
||||
"uptime": state.uptime
|
||||
}
|
||||
|
||||
|
||||
# Serve console HTML if configured
|
||||
if config.STATIC_DIR and config.STATIC_DIR.exists():
|
||||
app.mount("/static", StaticFiles(directory=str(config.STATIC_DIR)), name="static")
|
||||
|
||||
@app.get("/")
|
||||
async def serve_console():
|
||||
return FileResponse(config.STATIC_DIR / "iotek-nexus-live.html")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# MAIN
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(
|
||||
"offsec_mcp:app",
|
||||
host="0.0.0.0",
|
||||
port=8080,
|
||||
reload=True
|
||||
)
|
||||
Reference in New Issue
Block a user