- delete internal infra docs: system-of-record.md, sitemap.html status board
(exposed live attack surface: subdomains, up/down status, client names)
- parameterize OS desktop: derive DOMAIN from location.hostname, u() helper
builds service URLs so a friend deploying at their domain just works
- scrub all live service links to service.{DOMAIN} placeholders
- remove client domains (bicos, hkintercare, etc.) everywhere
- collapse brand/client site tiles to one generic Website tile
- single public CTA -> atlascorporation.nl (Get a workspace)
- relative SSO sign-out; sanitized server scripts to example.com
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
170 lines
4.9 KiB
Python
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.example.com; 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()
|