mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-05-13 12:04:56 +02:00
Stabilize Infonet private sync and selected telemetry
This commit is contained in:
+61
-5
@@ -1084,6 +1084,7 @@ _WORMHOLE_PUBLIC_PROFILE_FIELDS = {"profile", "wormhole_enabled"}
|
||||
_PRIVATE_LANE_CONTROL_FIELDS = {"private_lane_tier", "private_lane_policy"}
|
||||
_PUBLIC_RNS_STATUS_FIELDS = {"enabled", "ready", "configured_peers", "active_peers"}
|
||||
_NODE_PUBLIC_EVENT_HOOK_REGISTERED = False
|
||||
_INFONET_PRIVATE_TRANSPORT_LOCK = threading.Lock()
|
||||
|
||||
|
||||
def _current_node_mode() -> str:
|
||||
@@ -1172,6 +1173,50 @@ def _filter_infonet_sync_records(records: list[Any]) -> list[Any]:
|
||||
]
|
||||
|
||||
|
||||
def _ensure_infonet_private_transport_ready(reason: str = "") -> bool:
|
||||
"""Warm the local onion transport before private Infonet sync.
|
||||
|
||||
Infonet may know about an onion seed before the Wormhole UI is opened. The
|
||||
sync worker still needs Arti marked enabled and a ready SOCKS listener, so
|
||||
do that lazily in the worker instead of making users manually open another
|
||||
panel just to participate in the Infonet.
|
||||
"""
|
||||
if not _infonet_private_transport_required():
|
||||
return True
|
||||
|
||||
try:
|
||||
from services.wormhole_supervisor import _check_arti_ready
|
||||
|
||||
if bool(get_settings().MESH_ARTI_ENABLED) and _check_arti_ready():
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not _INFONET_PRIVATE_TRANSPORT_LOCK.acquire(blocking=False):
|
||||
return False
|
||||
try:
|
||||
from routers.ai_intel import _write_env_value
|
||||
from services.tor_hidden_service import tor_service
|
||||
from services.wormhole_supervisor import _check_arti_ready
|
||||
|
||||
label = f" ({reason})" if reason else ""
|
||||
logger.info("Infonet private transport warmup starting%s", label)
|
||||
tor_result = tor_service.start(target_port=8000)
|
||||
if tor_result.get("ok"):
|
||||
_write_env_value("MESH_ARTI_ENABLED", "true")
|
||||
get_settings.cache_clear()
|
||||
if _check_arti_ready():
|
||||
logger.info("Infonet private transport ready%s", label)
|
||||
return True
|
||||
logger.warning("Infonet private transport warmup incomplete%s: %s", label, tor_result)
|
||||
return False
|
||||
except Exception as exc:
|
||||
logger.warning("Infonet private transport warmup failed: %s", exc)
|
||||
return False
|
||||
finally:
|
||||
_INFONET_PRIVATE_TRANSPORT_LOCK.release()
|
||||
|
||||
|
||||
def _configured_bootstrap_seed_peer_urls() -> list[str]:
|
||||
settings = get_settings()
|
||||
primary = str(getattr(settings, "MESH_BOOTSTRAP_SEED_PEERS", "") or "").strip()
|
||||
@@ -1487,6 +1532,12 @@ def _run_public_sync_cycle() -> SyncWorkerState:
|
||||
set_sync_state(updated)
|
||||
return updated
|
||||
|
||||
if _infonet_private_transport_required() and any(
|
||||
str(getattr(record, "transport", "") or "").strip().lower() == "onion"
|
||||
for record in peers
|
||||
):
|
||||
_ensure_infonet_private_transport_ready("sync")
|
||||
|
||||
last_error = "sync failed"
|
||||
for record in peers:
|
||||
started = begin_sync(
|
||||
@@ -2295,7 +2346,13 @@ async def lifespan(app: FastAPI):
|
||||
_refresh_node_peer_store()
|
||||
if _node_runtime_supported():
|
||||
if not _participant_node_enabled():
|
||||
set_sync_state(_set_node_sync_disabled_state())
|
||||
logger.info("Infonet participant auto-enabled for private seed sync")
|
||||
_set_participant_node_enabled(True)
|
||||
threading.Thread(
|
||||
target=lambda: _ensure_infonet_private_transport_ready("startup"),
|
||||
daemon=True,
|
||||
name="infonet-private-transport-warmup",
|
||||
).start()
|
||||
_NODE_SYNC_STOP.clear()
|
||||
threading.Thread(target=_public_infonet_sync_loop, daemon=True).start()
|
||||
_kick_public_sync_background("startup")
|
||||
@@ -9662,10 +9719,9 @@ async def api_wormhole_leave(request: Request):
|
||||
updated = write_wormhole_settings(enabled=False)
|
||||
state = disconnect_wormhole(reason="leave_wormhole")
|
||||
|
||||
# Disable node participation when the user leaves the Wormhole.
|
||||
from services.node_settings import write_node_settings
|
||||
|
||||
write_node_settings(enabled=False)
|
||||
# Leaving private DM mode must not disable Infonet participation. Infonet
|
||||
# sync has its own private transport warmup and can remain connected to
|
||||
# seed/peer nodes while MeshChat stays separately opt-in.
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
|
||||
@@ -929,10 +929,9 @@ async def api_wormhole_leave(request: Request):
|
||||
updated = write_wormhole_settings(enabled=False)
|
||||
state = disconnect_wormhole(reason="leave_wormhole")
|
||||
|
||||
# Disable node participation when the user leaves the Wormhole.
|
||||
from services.node_settings import write_node_settings
|
||||
|
||||
write_node_settings(enabled=False)
|
||||
# Leaving private DM mode must not disable Infonet participation. Infonet
|
||||
# sync has its own private transport warmup and can remain connected to
|
||||
# seed/peer nodes while MeshChat stays separately opt-in.
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
|
||||
@@ -256,7 +256,7 @@ PRIVATE_JET_TYPES = {
|
||||
# Flight trails state
|
||||
flight_trails = {} # {icao_hex: {points: [[lat, lng, alt, ts], ...], last_seen: ts}}
|
||||
_trails_lock = threading.Lock()
|
||||
_MAX_TRACKED_TRAILS = 2000
|
||||
_MAX_TRACKED_TRAILS = 20000
|
||||
|
||||
|
||||
def get_flight_trail(icao24: str) -> list:
|
||||
@@ -624,7 +624,7 @@ def _classify_and_publish(all_adsb_flights):
|
||||
# --- Trail Accumulation ---
|
||||
_TRAIL_INTERVAL_S = 60 # selected trails need enough resolution to show where unknown-route traffic came from
|
||||
|
||||
def _accumulate_trail(f, now_ts, check_route=True):
|
||||
def _accumulate_trail(f, now_ts, attach_known_route_trail=False):
|
||||
hex_id = f.get("icao24", "").lower()
|
||||
if not hex_id:
|
||||
return 0, None
|
||||
@@ -637,12 +637,9 @@ def _classify_and_publish(all_adsb_flights):
|
||||
(f.get("origin_loc") and f.get("dest_loc"))
|
||||
or (_known_route_name(f.get("origin_name")) and _known_route_name(f.get("dest_name")))
|
||||
)
|
||||
if check_route and has_known_route:
|
||||
f["trail"] = []
|
||||
return 0, hex_id
|
||||
lat, lng, alt = f.get("lat"), f.get("lng"), f.get("alt", 0)
|
||||
if lat is None or lng is None:
|
||||
f["trail"] = flight_trails.get(hex_id, {}).get("points", [])
|
||||
f["trail"] = [] if has_known_route and not attach_known_route_trail else flight_trails.get(hex_id, {}).get("points", [])
|
||||
return 0, hex_id
|
||||
point = [round(lat, 5), round(lng, 5), round(alt, 1), round(now_ts)]
|
||||
if hex_id not in flight_trails:
|
||||
@@ -663,7 +660,10 @@ def _classify_and_publish(all_adsb_flights):
|
||||
trail_data["last_seen"] = now_ts
|
||||
if len(trail_data["points"]) > 200:
|
||||
trail_data["points"] = trail_data["points"][-200:]
|
||||
f["trail"] = trail_data["points"]
|
||||
# Keep known-route flights visually clean in the main payload; selected
|
||||
# detail panels can still fetch this server-side trail to compute
|
||||
# observed fuel/CO2 burn.
|
||||
f["trail"] = [] if has_known_route and not attach_known_route_trail else trail_data["points"]
|
||||
return 1, hex_id
|
||||
|
||||
now_ts = datetime.utcnow().timestamp()
|
||||
@@ -675,7 +675,9 @@ def _classify_and_publish(all_adsb_flights):
|
||||
tracked_snapshot = copy.deepcopy(latest_data.get("tracked_flights", []))
|
||||
raw_flights_snapshot = list(latest_data.get("flights", []))
|
||||
|
||||
# Skip trails for any flight with a known route; the route line replaces historical trail.
|
||||
# Accumulate trails for every aircraft so selected details can estimate
|
||||
# observed fuel/CO2 burn. Known-route flights keep an empty payload trail so
|
||||
# the route line, not historical breadcrumbs, remains the visible map path.
|
||||
route_check_lists = [commercial_snapshot, private_jets_snapshot, private_ga_snapshot]
|
||||
always_trail_lists = [tracked_snapshot, military_snapshot]
|
||||
seen_hexes = set()
|
||||
@@ -683,14 +685,14 @@ def _classify_and_publish(all_adsb_flights):
|
||||
with _trails_lock:
|
||||
for flist in route_check_lists:
|
||||
for f in flist:
|
||||
count, hex_id = _accumulate_trail(f, now_ts, check_route=True)
|
||||
count, hex_id = _accumulate_trail(f, now_ts, attach_known_route_trail=False)
|
||||
trail_count += count
|
||||
if hex_id:
|
||||
seen_hexes.add(hex_id)
|
||||
|
||||
for flist in always_trail_lists:
|
||||
for f in flist:
|
||||
count, hex_id = _accumulate_trail(f, now_ts, check_route=True)
|
||||
count, hex_id = _accumulate_trail(f, now_ts, attach_known_route_trail=False)
|
||||
trail_count += count
|
||||
if hex_id:
|
||||
seen_hexes.add(hex_id)
|
||||
|
||||
@@ -6,6 +6,7 @@ import time
|
||||
import requests
|
||||
from services.network_utils import fetch_with_curl
|
||||
from services.fetchers._store import latest_data, _data_lock, _mark_fresh
|
||||
from services.fetchers.emissions import get_emissions_info
|
||||
from services.fetchers.plane_alert import enrich_with_plane_alert
|
||||
|
||||
logger = logging.getLogger("services.data_fetcher")
|
||||
@@ -289,6 +290,13 @@ def fetch_military_flights():
|
||||
remaining_mil = []
|
||||
for mf in military_flights:
|
||||
enrich_with_plane_alert(mf)
|
||||
model = mf.get("model")
|
||||
if not model or str(model).strip().lower() in {"", "unknown"}:
|
||||
model = mf.get("alert_type") or ""
|
||||
if model:
|
||||
emissions = get_emissions_info(model)
|
||||
if emissions:
|
||||
mf["emissions"] = emissions
|
||||
if mf.get("alert_category"):
|
||||
mf["type"] = "tracked_flight"
|
||||
tracked_mil.append(mf)
|
||||
|
||||
@@ -61,7 +61,7 @@ export default function NetworkStats() {
|
||||
const nodeColor = stats.syncOutcome === 'ok' ? 'text-green-400'
|
||||
: stats.syncOutcome === 'running' ? 'text-amber-400'
|
||||
: stats.nodeEnabled ? 'text-amber-400' : 'text-gray-600';
|
||||
const nodeLabel = stats.syncOutcome === 'ok' ? 'CONNECTED'
|
||||
const nodeLabel = stats.syncOutcome === 'ok' ? 'SEED SYNCED'
|
||||
: stats.syncOutcome === 'running' ? 'SYNCING'
|
||||
: stats.syncOutcome === 'error' || stats.syncOutcome === 'fork' ? 'RETRYING'
|
||||
: stats.nodeEnabled ? 'WAITING' : 'OFFLINE';
|
||||
|
||||
@@ -274,7 +274,7 @@ function hasKnownRouteName(value?: string | null): boolean {
|
||||
|
||||
function flightHasKnownRoute(entity: ReturnType<typeof findSelectedEntity>, dynamicRoute: DynamicRoute | null): boolean {
|
||||
if (!entity) return false;
|
||||
if (dynamicRoute?.orig_loc || dynamicRoute?.dest_loc) return true;
|
||||
if (dynamicRoute?.orig_loc && dynamicRoute?.dest_loc) return true;
|
||||
if (!('origin_loc' in entity) && !('origin_name' in entity)) return false;
|
||||
const flight = entity as Flight;
|
||||
return Boolean(
|
||||
@@ -683,19 +683,24 @@ const MaplibreViewer = ({
|
||||
const endpoint = isShip
|
||||
? `${API_BASE}/api/trail/ship/${encodeURIComponent(trailId)}`
|
||||
: `${API_BASE}/api/trail/flight/${encodeURIComponent(trailId)}`;
|
||||
fetch(endpoint)
|
||||
.then((res) => (res.ok ? res.json() : null))
|
||||
.then((payload) => {
|
||||
if (cancelled || !payload) return;
|
||||
const points = parseTrailPoints(payload.trail, kind);
|
||||
setSelectedTrailPoints(points.length >= 2 ? points : fallback);
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setSelectedTrailPoints(fallback);
|
||||
});
|
||||
const refreshSelectedTrail = () => {
|
||||
fetch(endpoint, { cache: 'no-store' })
|
||||
.then((res) => (res.ok ? res.json() : null))
|
||||
.then((payload) => {
|
||||
if (cancelled || !payload) return;
|
||||
const points = parseTrailPoints(payload.trail, kind);
|
||||
setSelectedTrailPoints(points.length >= 2 ? points : fallback);
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setSelectedTrailPoints(fallback);
|
||||
});
|
||||
};
|
||||
refreshSelectedTrail();
|
||||
const trailRefreshTimer = window.setInterval(refreshSelectedTrail, 30000);
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
window.clearInterval(trailRefreshTimer);
|
||||
};
|
||||
}, [selectedEntity, data, dynamicRoute]);
|
||||
|
||||
|
||||
@@ -5953,7 +5953,7 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
PARTICIPANT NODE
|
||||
</div>
|
||||
<div className="mt-1 text-sm leading-5 text-slate-400">
|
||||
Backend bootstrap is configured; activate the participant node to sync the public testnet seed without Wormhole.
|
||||
Backend bootstrap is configured; the participant node syncs the testnet seed over the private seed lane.
|
||||
</div>
|
||||
</div>
|
||||
<div className="border border-cyan-500/20 bg-cyan-500/8 px-3 py-1.5 text-[13px] tracking-[0.22em] text-cyan-200">
|
||||
@@ -6008,10 +6008,10 @@ export default function MeshTerminal({ isOpen, launchToken = 0, onClose, onDmCou
|
||||
|
||||
<div className="border border-amber-400/16 bg-amber-400/6 px-4 py-3 text-sm leading-6 text-amber-100/85">
|
||||
<div className="text-[13px] font-mono tracking-[0.24em] text-amber-300">
|
||||
WORMHOLE OPTIONAL FOR NODE SYNC
|
||||
PRIVATE SEED LANE
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
Participant-node bootstrap, sync, and public chain hosting run on the backend lane without Wormhole.
|
||||
Participant-node bootstrap, sync, and public chain hosting use the backend private seed lane.
|
||||
</div>
|
||||
<div className="mt-2 text-amber-200/75">
|
||||
Turn Wormhole on for gates, obfuscated inbox, and the stronger obfuscated lane only.
|
||||
|
||||
@@ -480,19 +480,24 @@ function NewsFeedInner({ selectedEntity, regionDossier, regionDossierLoading, on
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
fetch(`${API_BASE}/api/trail/flight/${encodeURIComponent(trailId)}`, { cache: 'no-store' })
|
||||
.then((res) => (res.ok ? res.json() : null))
|
||||
.then((payload) => {
|
||||
if (cancelled) return;
|
||||
const trail = Array.isArray(payload?.trail) ? payload.trail as FlightTrailPoint[] : [];
|
||||
setSelectedFlightTrail(trail);
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setSelectedFlightTrail([]);
|
||||
});
|
||||
const refreshSelectedFlightTrail = () => {
|
||||
fetch(`${API_BASE}/api/trail/flight/${encodeURIComponent(trailId)}`, { cache: 'no-store' })
|
||||
.then((res) => (res.ok ? res.json() : null))
|
||||
.then((payload) => {
|
||||
if (cancelled) return;
|
||||
const trail = Array.isArray(payload?.trail) ? payload.trail as FlightTrailPoint[] : [];
|
||||
setSelectedFlightTrail(trail);
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setSelectedFlightTrail([]);
|
||||
});
|
||||
};
|
||||
refreshSelectedFlightTrail();
|
||||
const trailRefreshTimer = window.setInterval(refreshSelectedFlightTrail, 30000);
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
window.clearInterval(trailRefreshTimer);
|
||||
};
|
||||
}, [selectedEntity?.id, selectedEntity?.type]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user