mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-05-08 02:16:41 +02:00
146 lines
6.0 KiB
Python
146 lines
6.0 KiB
Python
import time
|
|
import logging
|
|
from fastapi import APIRouter, Request, Response, Query, Depends
|
|
from fastapi.responses import JSONResponse
|
|
from pydantic import BaseModel
|
|
from limiter import limiter
|
|
from auth import require_admin, require_local_operator
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/api/mesh/peers", dependencies=[Depends(require_local_operator)])
|
|
@limiter.limit("30/minute")
|
|
async def list_peers(request: Request, bucket: str = Query(None)):
|
|
"""List all peers (or filter by bucket: sync, push, bootstrap)."""
|
|
from services.mesh.mesh_peer_store import DEFAULT_PEER_STORE_PATH, PeerStore
|
|
store = PeerStore(DEFAULT_PEER_STORE_PATH)
|
|
try:
|
|
store.load()
|
|
except Exception as exc:
|
|
return {"ok": False, "detail": f"Failed to load peer store: {exc}"}
|
|
if bucket:
|
|
records = store.records_for_bucket(bucket)
|
|
else:
|
|
records = store.records()
|
|
return {"ok": True, "count": len(records), "peers": [r.to_dict() for r in records]}
|
|
|
|
|
|
@router.post("/api/mesh/peers", dependencies=[Depends(require_local_operator)])
|
|
@limiter.limit("10/minute")
|
|
async def add_peer(request: Request):
|
|
"""Add a peer to the store. Body: {peer_url, transport?, label?, role?, buckets?[]}."""
|
|
from services.mesh.mesh_crypto import normalize_peer_url
|
|
from services.mesh.mesh_peer_store import (
|
|
DEFAULT_PEER_STORE_PATH, PeerStore, PeerStoreError,
|
|
make_push_peer_record, make_sync_peer_record,
|
|
)
|
|
from services.mesh.mesh_router import peer_transport_kind
|
|
body = await request.json()
|
|
peer_url_raw = str(body.get("peer_url", "") or "").strip()
|
|
if not peer_url_raw:
|
|
return {"ok": False, "detail": "peer_url is required"}
|
|
peer_url = normalize_peer_url(peer_url_raw)
|
|
if not peer_url:
|
|
return {"ok": False, "detail": "Invalid peer_url"}
|
|
transport = str(body.get("transport", "") or "").strip().lower()
|
|
if not transport:
|
|
transport = peer_transport_kind(peer_url)
|
|
if not transport:
|
|
return {"ok": False, "detail": "Cannot determine transport for peer_url — provide transport explicitly"}
|
|
label = str(body.get("label", "") or "").strip()
|
|
role = str(body.get("role", "") or "").strip().lower() or "relay"
|
|
buckets = body.get("buckets", ["sync", "push"])
|
|
if isinstance(buckets, str):
|
|
buckets = [buckets]
|
|
if not isinstance(buckets, list):
|
|
buckets = ["sync", "push"]
|
|
store = PeerStore(DEFAULT_PEER_STORE_PATH)
|
|
try:
|
|
store.load()
|
|
except Exception:
|
|
store = PeerStore(DEFAULT_PEER_STORE_PATH)
|
|
added: list = []
|
|
try:
|
|
for b in buckets:
|
|
b = str(b).strip().lower()
|
|
if b == "sync":
|
|
store.upsert(make_sync_peer_record(peer_url=peer_url, transport=transport, role=role, label=label))
|
|
added.append("sync")
|
|
elif b == "push":
|
|
store.upsert(make_push_peer_record(peer_url=peer_url, transport=transport, role=role, label=label))
|
|
added.append("push")
|
|
store.save()
|
|
except PeerStoreError as exc:
|
|
return {"ok": False, "detail": str(exc)}
|
|
return {"ok": True, "peer_url": peer_url, "buckets": added}
|
|
|
|
|
|
@router.delete("/api/mesh/peers", dependencies=[Depends(require_local_operator)])
|
|
@limiter.limit("10/minute")
|
|
async def remove_peer(request: Request):
|
|
"""Remove a peer. Body: {peer_url, bucket?}. If bucket omitted, removes from all buckets."""
|
|
from services.mesh.mesh_crypto import normalize_peer_url
|
|
from services.mesh.mesh_peer_store import DEFAULT_PEER_STORE_PATH, PeerStore
|
|
body = await request.json()
|
|
peer_url_raw = str(body.get("peer_url", "") or "").strip()
|
|
if not peer_url_raw:
|
|
return {"ok": False, "detail": "peer_url is required"}
|
|
peer_url = normalize_peer_url(peer_url_raw)
|
|
if not peer_url:
|
|
return {"ok": False, "detail": "Invalid peer_url"}
|
|
bucket_filter = str(body.get("bucket", "") or "").strip().lower()
|
|
store = PeerStore(DEFAULT_PEER_STORE_PATH)
|
|
try:
|
|
store.load()
|
|
except Exception:
|
|
return {"ok": False, "detail": "Failed to load peer store"}
|
|
removed: list = []
|
|
for b in ["bootstrap", "sync", "push"]:
|
|
if bucket_filter and b != bucket_filter:
|
|
continue
|
|
key = f"{b}:{peer_url}"
|
|
if key in store._records:
|
|
del store._records[key]
|
|
removed.append(b)
|
|
if not removed:
|
|
return {"ok": False, "detail": "Peer not found in any bucket"}
|
|
store.save()
|
|
return {"ok": True, "peer_url": peer_url, "removed_from": removed}
|
|
|
|
|
|
@router.patch("/api/mesh/peers", dependencies=[Depends(require_local_operator)])
|
|
@limiter.limit("10/minute")
|
|
async def toggle_peer(request: Request):
|
|
"""Enable or disable a peer. Body: {peer_url, bucket, enabled: bool}."""
|
|
from services.mesh.mesh_crypto import normalize_peer_url
|
|
from services.mesh.mesh_peer_store import DEFAULT_PEER_STORE_PATH, PeerRecord, PeerStore
|
|
body = await request.json()
|
|
peer_url_raw = str(body.get("peer_url", "") or "").strip()
|
|
bucket = str(body.get("bucket", "") or "").strip().lower()
|
|
enabled = body.get("enabled")
|
|
if not peer_url_raw:
|
|
return {"ok": False, "detail": "peer_url is required"}
|
|
if not bucket:
|
|
return {"ok": False, "detail": "bucket is required"}
|
|
if enabled is None:
|
|
return {"ok": False, "detail": "enabled (true/false) is required"}
|
|
peer_url = normalize_peer_url(peer_url_raw)
|
|
if not peer_url:
|
|
return {"ok": False, "detail": "Invalid peer_url"}
|
|
store = PeerStore(DEFAULT_PEER_STORE_PATH)
|
|
try:
|
|
store.load()
|
|
except Exception:
|
|
return {"ok": False, "detail": "Failed to load peer store"}
|
|
key = f"{bucket}:{peer_url}"
|
|
record = store._records.get(key)
|
|
if not record:
|
|
return {"ok": False, "detail": f"Peer not found in {bucket} bucket"}
|
|
updated = PeerRecord(**{**record.to_dict(), "enabled": bool(enabled), "updated_at": int(time.time())})
|
|
store._records[key] = updated
|
|
store.save()
|
|
return {"ok": True, "peer_url": peer_url, "bucket": bucket, "enabled": bool(enabled)}
|