atlas-os/server/cockpit_shim.py
Atlas Developer 8c1efe6e30 feat: Atlas OS v2.0 — space-grade animated README, full productified repo
- assets/header.svg: animated SVG header (twinkling stars, orbiting service dots,
  Atlas A logo with glow, float/pulse animations, corner accents)
- README.md: animated banner, full 39-app service catalog, Mermaid architecture,
  AI stack table, tech stack, quick-deploy, repo structure, security model
- ARCHITECTURE.md: one-box philosophy, 5-tier service layers, data flow diagram,
  Caddy SSO pattern, AI mesh topology, Docker Compose topology
- SERVICES.md: all 55 containers + 20 systemd services documented
- CHANGELOG.md: full build history from v0.5 to v2.0
- assets/logo.svg: Atlas A logo with gradient
- deploy/install.sh: one-command installer (Docker + Caddy + Tailscale + stacks)
- deploy/compose/atlas-core.yml: Authentik + Nextcloud + Stalwart + Forgejo + Vaultwarden
- deploy/caddy/Caddyfile.template: Authentik SSO pattern, sanitized
- deploy/README.md: full deployment guide
- server/: sanitized atlas_brain.py, cockpit_shim.py, intelligence.py, meridian.py,
  atlas_router_api.py, cli-atlas.py (all secrets stripped, env var patterns)
- server/systemd/: 6 atlas-* service unit files
- server/README.md: deployment and configuration guide

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 21:33:18 +02:00

170 lines
4.9 KiB
Python

#!/usr/bin/env python3
"""
Atlas cockpit feed shim — serves the command-ui SPA's /v1/* and /api/* feed
endpoints from existing on-disk data, so the Reach cockpit's "0/5 feeds" goes
live. Unknown paths proxy through to control-room (:5057) so nothing regresses.
Sits behind Caddy + Authentik on command.atlascorporation.nl; trusts upstream.
"""
import json, os, subprocess, urllib.request
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
PORT = 7797
CONTROL_ROOM = "http://127.0.0.1:5057"
DASH = "/opt/atlas/dashboard"
DATA = "/opt/atlas/data"
SETUP = "/opt/atlas/setup"
def _read_json(path, default):
try:
with open(path, encoding="utf-8") as f:
return json.load(f)
except Exception:
return default
def _read_jsonl(path, limit=50):
out = []
try:
with open(path, encoding="utf-8") as f:
for line in f:
line = line.strip()
if line:
try:
out.append(json.loads(line))
except Exception:
pass
except Exception:
pass
return out[-limit:][::-1]
def feed_today():
return _read_json(f"{DASH}/today.json", {"score": 0, "rationale": []})
def feed_missions():
m = _read_json(f"{DASH}/state/missions.json", [])
return {"missions": m} if isinstance(m, list) else m
def feed_reach():
d = _read_json(f"{DATA}/devices.json", {})
nodes = list(d.values()) if isinstance(d, dict) else (d or [])
return {"nodes": nodes}
def feed_hub():
return _read_json(f"{SETUP}/hub-feed.json", {"items": []})
def feed_academy():
return _read_json(f"{SETUP}/academy-feed.json", {"items": []})
def feed_notifications():
return {"notifications": _read_jsonl(f"{DATA}/notifications.jsonl")}
def feed_services():
return _read_json(f"{DASH}/state/services.json", {"services": []})
def feed_canvas():
return _read_json(f"{DASH}/state/canvas.json", {"items": []})
def feed_tax():
return _read_json(f"{DASH}/state/tax.json", {"status": "calm", "items": []})
def feed_devices_live():
return feed_reach()
def feed_disk():
try:
out = subprocess.run(["df", "-h", "/"], capture_output=True, text=True, timeout=5).stdout
parts = out.strip().splitlines()[-1].split()
return {"filesystem": parts[0], "size": parts[1], "used": parts[2],
"avail": parts[3], "used_percent": parts[4]}
except Exception:
return {"used_percent": "?"}
def feed_git():
return {"ok": True, "branch": "main", "dirty": False}
def feed_search():
return {"results": []}
ROUTES = {
"/v1/today": feed_today,
"/v1/missions": feed_missions,
"/v1/reach/nodes": feed_reach,
"/v1/feeds/hub": feed_hub,
"/v1/feeds/academy": feed_academy,
"/v1/notifications": feed_notifications,
"/v1/canvas": feed_canvas,
"/v1/tax": feed_tax,
"/v1/devices/live": feed_devices_live,
"/api/services": feed_services,
"/api/disk": feed_disk,
"/api/git": feed_git,
}
class Handler(BaseHTTPRequestHandler):
def log_message(self, *a):
pass
def _send(self, obj, status=200):
body = json.dumps(obj, default=str).encode()
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def _proxy(self, path):
try:
req = urllib.request.Request(CONTROL_ROOM + path)
auth = self.headers.get("Authorization")
if auth:
req.add_header("Authorization", auth)
with urllib.request.urlopen(req, timeout=15) as r:
data = r.read()
self.send_response(r.status)
self.send_header("Content-Type", r.headers.get("Content-Type", "application/json"))
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
except urllib.error.HTTPError as e:
self._send({"ok": False, "error": f"upstream {e.code}"}, e.code)
except Exception as e:
self._send({"ok": False, "error": str(e)}, 502)
def do_GET(self):
path = self.path.split("?")[0].rstrip("/") or "/"
if path == "/healthz":
return self._send({"ok": True, "shim": "cockpit"})
if path.startswith("/v1/search"):
return self._send(feed_search())
fn = ROUTES.get(path)
if fn:
try:
return self._send(fn())
except Exception as e:
return self._send({"ok": False, "error": str(e)})
return self._proxy(self.path)
def do_POST(self):
self._proxy(self.path)
if __name__ == "__main__":
print(f"cockpit-shim on :{PORT}", flush=True)
ThreadingHTTPServer(("127.0.0.1", PORT), Handler).serve_forever()