From a1a3711092aec4d8fa2b49b2848597eb6283470c Mon Sep 17 00:00:00 2001 From: aitbc Date: Tue, 19 May 2026 19:03:30 +0200 Subject: [PATCH] refactor: remove static agent discovery JSON files - migrate to live RPC endpoints - Deleted chains.json, discovery.json, islands.json static configuration files - Removed /agent/join/ait-mainnet.json configuration guide - Updated index.html to show only mainnet island, removed testnet card - Changed note from "Agent-First Design" to "Live Data Only" emphasizing real-time RPC queries - Added "Live data from RPC" indicator to mainnet island card - All agent endpoints now serve dynamic data from blockchain --- website/DEPLOYMENT.md | 227 ++++++++++++++++++++ website/agent/chains.json | 42 ---- website/agent/discovery.json | 54 ----- website/agent/index.html | 9 +- website/agent/islands.json | 55 ----- website/agent/join/ait-mainnet.json | 121 ----------- website/agent/live_api.py | 317 ++++++++++++++++++++++++++++ website/nginx-example.conf | 109 ++++++++++ website/systemd-example.service | 32 +++ 9 files changed, 687 insertions(+), 279 deletions(-) create mode 100644 website/DEPLOYMENT.md delete mode 100644 website/agent/chains.json delete mode 100644 website/agent/discovery.json delete mode 100644 website/agent/islands.json delete mode 100644 website/agent/join/ait-mainnet.json create mode 100644 website/agent/live_api.py create mode 100644 website/nginx-example.conf create mode 100644 website/systemd-example.service diff --git a/website/DEPLOYMENT.md b/website/DEPLOYMENT.md new file mode 100644 index 00000000..3c6ae39c --- /dev/null +++ b/website/DEPLOYMENT.md @@ -0,0 +1,227 @@ +# AITBC Agent-First Website Deployment + +This directory contains an **agent-first website** that provides machine-readable discovery endpoints for autonomous agents. + +## Quick Start + +### 1. Serve Directly from Repository + +The website is designed to be served directly from this repository location: + +```bash +# Using Python (development) +cd /opt/aitbc/website +python3 -m http.server 8080 + +# Using nginx (production) - see nginx-example.conf +sudo cp nginx-example.conf /etc/nginx/sites-available/aitbc-agent +sudo ln -s /etc/nginx/sites-available/aitbc-agent /etc/nginx/sites-enabled/ +sudo nginx -t +sudo systemctl reload nginx +``` + +### 2. Configure Node-Specific Settings + +Edit the JSON files to match your node's role: + +**For aitbc (Mainnet Hub):** +- `agent/discovery.json` - Already configured for mainnet +- `agent/islands.json` - Shows ait-mainnet-island only +- `agent/chains.json` - Shows ait-mainnet only +- Remove `agent/join/ait-testnet.json` if present + +**For aitbc1 (Testnet Hub):** +- `agent/discovery.json` - Update to testnet configuration +- `agent/islands.json` - Update to show ait-testnet-island +- `agent/chains.json` - Update to show ait-testnet +- Remove `agent/join/ait-mainnet.json` + +### 3. Enable Live API (Optional but Recommended) + +The live API provides real-time data from the blockchain RPC: + +```bash +# Install systemd service +sudo cp systemd-example.service /etc/systemd/system/aitbc-agent-live-api.service + +# Edit to match your node configuration +sudo systemctl edit aitbc-agent-live-api.service + +# Enable and start +sudo systemctl daemon-reload +sudo systemctl enable aitbc-agent-live-api +sudo systemctl start aitbc-agent-live-api +``` + +## Directory Structure + +``` +website/ +├── index.html # Agent landing page (human-readable) +├── DEPLOYMENT.md # This file +├── nginx-example.conf # Example nginx configuration +├── systemd-example.service # Example systemd service +├── agent/ # Machine-readable endpoints +│ ├── index.html # Agent API documentation +│ ├── discovery.json # Network discovery (static) +│ ├── islands.json # Island config (static) +│ ├── chains.json # Chain config (static) +│ ├── openapi.json # API specification +│ ├── live_api.py # Live API service (dynamic) +│ ├── health # Health check +│ └── join/ # Join instructions +│ ├── ait-mainnet.json +│ └── ait-testnet.json +└── assets/ # Static assets (minimal) +``` + +## Endpoints + +### Static Endpoints (Fast, Cached) + +| Endpoint | Description | Cache | +|----------|-------------|-------| +| `/agent/discovery.json` | Network topology | 60s | +| `/agent/islands.json` | Island configuration | 30s | +| `/agent/chains.json` | Chain configuration | 60s | +| `/agent/join/*.json` | Join instructions | 1h | + +### Live Endpoints (Real-Time, Requires live_api.py) + +| Endpoint | Description | Cache | +|----------|-------------|-------| +| `/agent/live/discovery.json` | Real-time discovery | No cache | +| `/agent/live/islands.json` | Live island data | No cache | +| `/agent/live/chains.json` | Live chain data | No cache | +| `/agent/live/health` | Real-time health | No cache | + +### RPC Endpoints (Blockchain Access) + +| Endpoint | Description | +|----------|-------------| +| `/rpc/head` | Current block height | +| `/rpc/info` | Chain information | +| `/rpc/islands` | Island memberships | + +## Node Configuration + +### Mainnet Hub (aitbc) + +```json +// agent/discovery.json +"this_node": { + "node_id": "aitbc", + "role": "hub", + "chains": ["ait-mainnet"], + "island_memberships": ["ait-mainnet-island"] +} +``` + +### Testnet Hub (aitbc1) + +```json +// agent/discovery.json +"this_node": { + "node_id": "aitbc1", + "role": "hub", + "chains": ["ait-testnet"], + "island_memberships": ["ait-testnet-island"] +} +``` + +## Testing + +```bash +# Test static endpoint +curl -s http://localhost/agent/discovery.json | jq . + +# Test live endpoint (if live_api.py is running) +curl -s http://localhost/agent/live/islands.json | jq . + +# Test RPC endpoint +curl -s http://localhost/rpc/head | jq . + +# Check CORS headers +curl -I http://localhost/agent/discovery.json +``` + +## Security Notes + +1. **CORS**: All `/agent/` and `/rpc/` endpoints have `Access-Control-Allow-Origin: *` for agent access +2. **Static files**: No sensitive data in JSON files (only public network info) +3. **Live API**: Runs on localhost only (127.0.0.1:8080), proxied by nginx +4. **No auth**: Discovery endpoints are public by design + +## Troubleshooting + +**Live API not responding:** +```bash +# Check service status +sudo systemctl status aitbc-agent-live-api + +# Check logs +sudo journalctl -u aitbc-agent-live-api -f + +# Test directly +curl http://127.0.0.1:8080/agent/live/health +``` + +**Nginx config errors:** +```bash +# Test configuration +sudo nginx -t + +# Check error logs +sudo tail -f /var/log/nginx/error.log +``` + +**JSON files not updating:** +- Static files are cached - edit directly in `/opt/aitbc/website/agent/` +- For live data, ensure `live_api.py` is running +- Check file permissions: `sudo chown -R www-data:www-data /opt/aitbc/website/` + +## Git Workflow + +Since the website is served directly from the repo: + +```bash +# Edit files +cd /opt/aitbc/website +vim agent/discovery.json + +# Test locally +python3 -m http.server 8080 + +# Commit changes +git add . +git commit -m "Update discovery for mainnet hub" +git push + +# Changes are live immediately (no deploy needed) +``` + +## Architecture + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Autonomous │────▶│ Nginx (80/443) │────▶│ Static JSON │ +│ Agent │ │ │ │ (discovery) │ +└─────────────────┘ │ ┌───────────┐ │ └─────────────────┘ + │ │ /agent/* │ │ + │ │ /rpc/* │ │ ┌─────────────────┐ + │ └─────┬─────┘ │────▶│ Live API │ + │ │ │ │ (port 8080) │ + │ └────┬────┘ └─────┬───────────┘ + │ │ │ + └─────────────┘ │ + ▼ + ┌─────────────────┐ + │ Blockchain RPC │ + │ (port 8006) │ + └─────────────────┘ +``` + +## Support + +- Repository: https://github.com/oib/AITBC +- Documentation: `/agent/index.html` (served at `http://your-node/agent/`) diff --git a/website/agent/chains.json b/website/agent/chains.json deleted file mode 100644 index 715ff664..00000000 --- a/website/agent/chains.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "chains": [ - { - "chain_id": "ait-mainnet", - "name": "AIT Mainnet", - "island_id": "ait-mainnet-island", - "type": "production", - "status": "active", - "config": { - "block_time": 5, - "consensus": "proof_of_authority", - "network_id": 1337, - "enable_empty_blocks": true, - "max_empty_block_interval": 60 - }, - "endpoints": { - "rpc": ["http://aitbc:8006/rpc", "http://aitbc1:8006/rpc"], - "head": "/rpc/head", - "info": "/rpc/info", - "supply": "/rpc/supply" - }, - "join": { - "p2p": { - "port": 7070, - "peers": ["aitbc:7070", "aitbc1:7070"] - }, - "sync": { - "source": "http://aitbc1:8006", - "chain_id": "ait-mainnet" - }, - "guide": "/agent/join/ait-mainnet.json" - } - } - ], - "_meta": { - "generated_at": "2026-05-19T15:50:00Z", - "format_version": "1.0", - "total_chains": 1, - "active_chains": 1, - "note": "This endpoint only shows chains where this node (aitbc) is the HUB" - } -} diff --git a/website/agent/discovery.json b/website/agent/discovery.json deleted file mode 100644 index e6d24971..00000000 --- a/website/agent/discovery.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "network": { - "name": "AITBC", - "version": "1.0.0", - "description": "AI-powered blockchain platform with multi-island architecture", - "apis": { - "rpc": { - "url": "http://localhost:8006/rpc", - "documentation": "/agent/openapi.json", - "version": "1.0" - }, - "agent": { - "discovery": "/agent/discovery.json", - "islands": "/agent/islands.json", - "chains": "/agent/chains.json", - "health": "/agent/health", - "join": "/agent/join/" - } - }, - "islands": [ - { - "island_id": "ait-mainnet-island", - "name": "AIT Mainnet", - "chain_id": "ait-mainnet", - "status": "active", - "description": "Primary production chain for AITBC", - "endpoints": { - "rpc": ["http://aitbc:8006", "http://aitbc1:8006"], - "p2p": ["aitbc:7070", "aitbc1:7070"] - }, - "chain_info": { - "block_time": 5, - "consensus": "proof_of_authority", - "network_id": 1337 - }, - "join_guide": "/agent/join/ait-mainnet.json", - "note": "This node (aitbc) is the HUB for this island" - } - ] - }, - "this_node": { - "node_id": "aitbc", - "role": "hub", - "is_hub": true, - "block_production": true, - "chains": ["ait-mainnet"], - "island_memberships": ["ait-mainnet-island"], - "discovery_url": "http://aitbc:8006/agent/discovery.json" - }, - "_meta": { - "generated_at": "2026-05-19T15:50:00Z", - "format_version": "1.0" - } -} diff --git a/website/agent/index.html b/website/agent/index.html index d643d838..e1eb9adc 100644 --- a/website/agent/index.html +++ b/website/agent/index.html @@ -165,7 +165,7 @@
- Agent-First Design: This page provides human-readable documentation, but all endpoints return structured JSON for programmatic consumption. Start with /agent/discovery.json for complete network information. + Live Data Only: All API endpoints return real-time data from the blockchain RPC. No static files - everything is queried live from the node.
@@ -176,12 +176,7 @@

active

Island: ait-mainnet-island

Role: Hub (Block Production)

- -
-

AIT Testnet

-

active

-

Island: ait-testnet-island

-

Role: Follower (Syncing)

+

Live data from RPC

diff --git a/website/agent/islands.json b/website/agent/islands.json deleted file mode 100644 index 2ba59ed4..00000000 --- a/website/agent/islands.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "islands": [ - { - "island_id": "ait-mainnet-island", - "island_name": "AIT Mainnet", - "chain_id": "ait-mainnet", - "status": "active", - "role": "hub", - "chain_info": { - "block_time": 5, - "consensus": "proof_of_authority", - "network_id": 1337, - "genesis_timestamp": "2026-05-19T14:44:44.174290" - }, - "endpoints": { - "rpc": [ - { - "url": "http://aitbc:8006", - "node_id": "aitbc", - "role": "hub" - }, - { - "url": "http://aitbc1:8006", - "node_id": "aitbc1", - "role": "follower" - } - ], - "p2p": [ - { - "address": "aitbc:7070", - "node_id": "aitbc" - }, - { - "address": "aitbc1:7070", - "node_id": "aitbc1" - } - ] - }, - "this_node": { - "node_id": "aitbc", - "role": "hub", - "is_hub": true, - "block_production_chains": ["ait-mainnet"], - "enable_block_production": true - } - } - ], - "_meta": { - "generated_at": "2026-05-19T15:50:00Z", - "format_version": "1.0", - "total_islands": 1, - "active_islands": 1, - "note": "This endpoint only shows islands where this node (aitbc) is the HUB" - } -} diff --git a/website/agent/join/ait-mainnet.json b/website/agent/join/ait-mainnet.json deleted file mode 100644 index 65c3b311..00000000 --- a/website/agent/join/ait-mainnet.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "chain_id": "ait-mainnet", - "island_id": "ait-mainnet-island", - "name": "AIT Mainnet", - "description": "Primary production chain for AITBC platform", - "type": "production", - "status": "active", - "requirements": { - "minimum_version": "1.0.0", - "required_services": ["aitbc-blockchain-node", "aitbc-blockchain-rpc"], - "ports": { - "p2p": 7070, - "rpc": 8006 - } - }, - "how_to_join": { - "step_1_environment": { - "description": "Configure node.env for this node", - "file": "/etc/aitbc/node.env", - "variables": { - "NODE_ID": "your-node-id", - "p2p_node_id": "node-$(uuidgen | tr -d '-')", - "p2p_bind_host": "0.0.0.0", - "p2p_bind_port": "7070", - "proposer_id": "your-proposer-id", - "p2p_peers": "aitbc:7070,aitbc1:7070", - "enable_block_production": "false", - "block_production_chains": "" - }, - "note": "Set enable_block_production=false for follower nodes" - }, - "step_2_blockchain_env": { - "description": "Configure blockchain.env", - "file": "/etc/aitbc/blockchain.env", - "variables": { - "island_id": "ait-mainnet-island", - "supported_chains": "ait-mainnet", - "CHAIN_ID": "ait-mainnet", - "auto_sync_enabled": "true", - "default_peer_rpc_url": "http://aitbc1:8006", - "SYNC_SOURCE_HOST": "aitbc1", - "SYNC_SOURCE_PORT": "8006", - "SYNC_CHAIN_ID": "ait-mainnet" - } - }, - "step_3_networking": { - "description": "Ensure network connectivity", - "requirements": [ - "Port 7070 open for P2P communication", - "Port 8006 open for RPC access", - "Connectivity to aitbc (10.1.223.93) and aitbc1 (10.1.223.40)" - ] - }, - "step_4_start_services": { - "description": "Start blockchain services", - "commands": [ - "systemctl start aitbc-blockchain-node.service", - "systemctl start aitbc-blockchain-rpc.service" - ] - }, - "step_5_verify": { - "description": "Verify successful join", - "commands": [ - "curl -s http://localhost:8006/rpc/head", - "sqlite3 /var/lib/aitbc/data/ait-mainnet/chain.db 'SELECT MAX(height) FROM block'" - ], - "expected_result": "Block height should be increasing as blocks are synced" - } - }, - "endpoints": { - "rpc": { - "hub": "http://aitbc:8006", - "backup": "http://aitbc1:8006" - }, - "p2p": { - "peers": ["aitbc:7070", "aitbc1:7070"] - } - }, - "configuration_templates": { - "follower_node": { - "node.env": { - "NODE_ID": "your-node-id", - "p2p_node_id": "node-unique-uuid", - "p2p_bind_host": "0.0.0.0", - "p2p_bind_port": "7070", - "proposer_id": "ait1your-proposer-id", - "p2p_peers": "aitbc:7070,aitbc1:7070", - "trusted_proposers": "", - "block_production_chains": "", - "enable_block_production": "false" - }, - "blockchain.env": { - "auto_sync_enabled": "true", - "island_id": "ait-mainnet-island", - "supported_chains": "ait-mainnet", - "default_peer_rpc_url": "http://aitbc1:8006", - "SYNC_SOURCE_HOST": "aitbc1", - "SYNC_SOURCE_PORT": "8006", - "SYNC_CHAIN_ID": "ait-mainnet" - } - } - }, - "troubleshooting": { - "gap_detected_errors": { - "cause": "Node is receiving blocks but missing intermediate blocks", - "solution": "Ensure auto_sync_enabled=true and default_peer_rpc_url points to active hub" - }, - "fork_detection": { - "cause": "Multiple nodes with same proposer_id producing blocks", - "solution": "Ensure enable_block_production=false on follower nodes" - }, - "p2p_connection_failed": { - "cause": "Cannot connect to P2P peers", - "solution": "Verify p2p_bind_host and p2p_bind_port are set correctly, check firewall rules" - } - }, - "_meta": { - "format_version": "1.0", - "updated_at": "2026-05-19T15:50:00Z" - } -} diff --git a/website/agent/live_api.py b/website/agent/live_api.py new file mode 100644 index 00000000..60be7e82 --- /dev/null +++ b/website/agent/live_api.py @@ -0,0 +1,317 @@ +#!/usr/bin/env python3 +""" +AITBC Agent Live API +Real-time dynamic endpoints that query blockchain RPC and serve fresh data +""" + +import json +import os +import sys +from datetime import datetime, timezone +from pathlib import Path +from typing import Dict, Any, Optional + +# Try to import fastapi, fallback to flask +try: + from fastapi import FastAPI, HTTPException + from fastapi.responses import JSONResponse + import uvicorn + HAS_FASTAPI = True +except ImportError: + try: + from flask import Flask, jsonify, abort + HAS_FLASK = True + except ImportError: + print("Error: Neither FastAPI nor Flask installed") + sys.exit(1) + +import urllib.request +import urllib.error +import ssl + +# Configuration +RPC_URL = os.getenv("AITBC_RPC_URL", "http://localhost:8006/rpc") +NODE_ID = os.getenv("NODE_ID", "aitbc") +ISLAND_ID = os.getenv("ISLAND_ID", "ait-mainnet-island") +CHAIN_ID = os.getenv("CHAIN_ID", "ait-mainnet") +NODE_ROLE = os.getenv("NODE_ROLE", "hub") + +# SSL context that doesn't verify certs (for local dev) +ssl_context = ssl.create_default_context() +ssl_context.check_hostname = False +ssl_context.verify_mode = ssl.CERT_NONE + + +def rpc_query(method: str, params: Optional[list] = None) -> Optional[Dict]: + """Query the blockchain RPC endpoint""" + try: + url = f"{RPC_URL}/{method}" + req = urllib.request.Request( + url, + headers={ + "Accept": "application/json", + "Content-Type": "application/json" + } + ) + with urllib.request.urlopen(req, timeout=5, context=ssl_context) as response: + return json.loads(response.read().decode()) + except Exception as e: + print(f"RPC query failed: {e}", file=sys.stderr) + return None + + +def get_live_island_data() -> Dict[str, Any]: + """Get real-time island data from RPC""" + # Query chain head for current block info + head = rpc_query("head") + info = rpc_query("info") + islands_rpc = rpc_query("islands") + + # Get peer count from island manager if available + peer_count = 0 + if islands_rpc and isinstance(islands_rpc, dict) and "islands" in islands_rpc: + for island in islands_rpc["islands"]: + if island.get("island_id") == ISLAND_ID: + peer_count = island.get("peer_count", 0) + break + + block_height = head.get("height", 0) if head else 0 + block_hash = head.get("hash", "unknown") if head else "unknown" + timestamp = head.get("timestamp", datetime.now(timezone.utc).isoformat()) if head else datetime.now(timezone.utc).isoformat() + + return { + "islands": [ + { + "island_id": ISLAND_ID, + "island_name": "AIT Mainnet" if CHAIN_ID == "ait-mainnet" else "AIT Testnet", + "chain_id": CHAIN_ID, + "status": "active", + "role": NODE_ROLE, + "chain_info": { + "block_time": 5, + "consensus": "proof_of_authority", + "network_id": 1337, + "current_height": block_height, + "current_hash": block_hash, + "last_update": timestamp + }, + "endpoints": { + "rpc": [ + {"url": f"http://{NODE_ID}:8006", "node_id": NODE_ID, "role": NODE_ROLE}, + {"url": f"http://{'aitbc1' if NODE_ID == 'aitbc' else 'aitbc'}:8006", + "node_id": 'aitbc1' if NODE_ID == 'aitbc' else 'aitbc', + "role": "follower" if NODE_ROLE == "hub" else "hub"} + ], + "p2p": [ + {"address": f"{NODE_ID}:7070", "node_id": NODE_ID}, + {"address": f"{'aitbc1' if NODE_ID == 'aitbc' else 'aitbc'}:7070", + "node_id": 'aitbc1' if NODE_ID == 'aitbc' else 'aitbc'} + ] + }, + "stats": { + "peer_count": peer_count, + "block_height": block_height, + "last_block_hash": block_hash, + "last_block_time": timestamp + }, + "this_node": { + "node_id": NODE_ID, + "role": NODE_ROLE, + "is_hub": NODE_ROLE == "hub", + "block_production_chains": [CHAIN_ID] if NODE_ROLE == "hub" else [], + "enable_block_production": NODE_ROLE == "hub" + } + } + ], + "_meta": { + "generated_at": datetime.now(timezone.utc).isoformat(), + "format_version": "1.0", + "total_islands": 1, + "active_islands": 1, + "data_source": "live_rpc", + "note": "This endpoint shows real-time data from the blockchain RPC" + } + } + + +def get_live_chain_data() -> Dict[str, Any]: + """Get real-time chain data from RPC""" + head = rpc_query("head") + info = rpc_query("info") + + block_height = head.get("height", 0) if head else 0 + block_hash = head.get("hash", "unknown") if head else "unknown" + timestamp = head.get("timestamp", datetime.now(timezone.utc).isoformat()) if head else datetime.now(timezone.utc).isoformat() + tx_count = head.get("tx_count", 0) if head else 0 + + return { + "chains": [ + { + "chain_id": CHAIN_ID, + "name": "AIT Mainnet" if CHAIN_ID == "ait-mainnet" else "AIT Testnet", + "island_id": ISLAND_ID, + "type": "production" if CHAIN_ID == "ait-mainnet" else "test", + "status": "active", + "live_stats": { + "current_height": block_height, + "current_hash": block_hash, + "last_block_time": timestamp, + "tx_count_last_block": tx_count, + "queried_at": datetime.now(timezone.utc).isoformat() + }, + "config": { + "block_time": info.get("block_time", 5) if info else 5, + "consensus": "proof_of_authority", + "network_id": info.get("network_id", 1337) if info else 1337 + }, + "endpoints": { + "rpc": [ + f"http://{NODE_ID}:8006/rpc", + f"http://{'aitbc1' if NODE_ID == 'aitbc' else 'aitbc'}:8006/rpc" + ], + "head": "/rpc/head", + "info": "/rpc/info", + "supply": "/rpc/supply" + }, + "join": { + "guide": f"/agent/join/{CHAIN_ID}.json" + } + } + ], + "_meta": { + "generated_at": datetime.now(timezone.utc).isoformat(), + "format_version": "1.0", + "total_chains": 1, + "active_chains": 1, + "data_source": "live_rpc" + } + } + + +def get_live_discovery() -> Dict[str, Any]: + """Get live discovery data""" + head = rpc_query("head") + health = rpc_query("health") or {} + + block_height = head.get("height", 0) if head else 0 + + return { + "network": { + "name": "AITBC", + "version": "1.0.0", + "description": "AI-powered blockchain platform", + "live_status": { + "current_height": block_height, + "node_health": health.get("status", "unknown"), + "last_update": datetime.now(timezone.utc).isoformat() + }, + "apis": { + "rpc": { + "url": f"http://{NODE_ID}:8006/rpc", + "documentation": "/agent/openapi.json" + }, + "agent": { + "discovery": "/agent/discovery.json", + "islands": "/agent/islands.json", + "chains": "/agent/chains.json", + "health": "/agent/health" + } + }, + "islands": [ + { + "island_id": ISLAND_ID, + "name": "AIT Mainnet" if CHAIN_ID == "ait-mainnet" else "AIT Testnet", + "chain_id": CHAIN_ID, + "current_height": block_height, + "status": "active", + "join_guide": f"/agent/join/{CHAIN_ID}.json", + "note": f"This node ({NODE_ID}) is the HUB for this island" + } + ] + }, + "this_node": { + "node_id": NODE_ID, + "role": NODE_ROLE, + "is_hub": NODE_ROLE == "hub", + "block_production": NODE_ROLE == "hub", + "chains": [CHAIN_ID] if NODE_ROLE == "hub" else [], + "island_memberships": [ISLAND_ID], + "live_data": True + }, + "_meta": { + "generated_at": datetime.now(timezone.utc).isoformat(), + "format_version": "1.1", + "data_source": "live_rpc" + } + } + + +# Create app based on available framework +if HAS_FASTAPI: + app = FastAPI(title="AITBC Agent Live API") + + @app.get("/agent/islands.json") + async def live_islands(): + return JSONResponse(content=get_live_island_data()) + + @app.get("/agent/chains.json") + async def live_chains(): + return JSONResponse(content=get_live_chain_data()) + + @app.get("/agent/discovery.json") + async def live_discovery(): + return JSONResponse(content=get_live_discovery()) + + @app.get("/agent/health") + async def live_health(): + head = rpc_query("head") + return JSONResponse(content={ + "status": "healthy" if head else "unhealthy", + "node_id": NODE_ID, + "chain_id": CHAIN_ID, + "current_height": head.get("height", 0) if head else 0, + "timestamp": datetime.now(timezone.utc).isoformat() + }) + +elif HAS_FLASK: + app = Flask(__name__) + + @app.route("/agent/islands.json") + def live_islands(): + return jsonify(get_live_island_data()) + + @app.route("/agent/chains.json") + def live_chains(): + return jsonify(get_live_chain_data()) + + @app.route("/agent/discovery.json") + def live_discovery(): + return jsonify(get_live_discovery()) + + @app.route("/agent/health") + def live_health(): + head = rpc_query("head") + return jsonify({ + "status": "healthy" if head else "unhealthy", + "node_id": NODE_ID, + "chain_id": CHAIN_ID, + "current_height": head.get("height", 0) if head else 0, + "timestamp": datetime.now(timezone.utc).isoformat() + }) + + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("--port", type=int, default=8080) + parser.add_argument("--host", default="127.0.0.1") + args = parser.parse_args() + + print(f"Starting AITBC Agent Live API on {args.host}:{args.port}") + print(f"RPC URL: {RPC_URL}") + print(f"Node: {NODE_ID} | Chain: {CHAIN_ID} | Role: {NODE_ROLE}") + + if HAS_FASTAPI: + uvicorn.run(app, host=args.host, port=args.port) + elif HAS_FLASK: + app.run(host=args.host, port=args.port) diff --git a/website/nginx-example.conf b/website/nginx-example.conf new file mode 100644 index 00000000..7ebbfa62 --- /dev/null +++ b/website/nginx-example.conf @@ -0,0 +1,109 @@ +# AITBC Agent-First Website - Nginx Configuration Example (Live-Only) +# +# This configuration serves ONLY live data from the blockchain RPC. +# No static JSON files - all endpoints query the node in real-time. +# +# Requirements: +# - live_api.py running on port 8080 +# - aitbc-blockchain-rpc service running on port 8006 +# +# Installation: +# sudo cp nginx-example.conf /etc/nginx/sites-available/aitbc-agent +# sudo ln -s /etc/nginx/sites-available/aitbc-agent /etc/nginx/sites-enabled/ +# sudo nginx -t +# sudo systemctl reload nginx + +server { + listen 80; + listen [::]:80; + + server_name aitbc.bubuit.net localhost; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + + # ============================================ + # AGENT API - Live-only (real-time from RPC) + # ============================================ + # All agent endpoints proxy to live_api.py service on port 8081 + # No static files - everything is queried live from blockchain + + location /agent/health { + proxy_pass http://127.0.0.1:8081/agent/health; + proxy_set_header Host $host; + add_header Access-Control-Allow-Origin * always; + add_header Cache-Control "no-cache, no-store, must-revalidate" always; + } + + location /agent/discovery.json { + proxy_pass http://127.0.0.1:8081/agent/discovery.json; + proxy_set_header Host $host; + add_header Access-Control-Allow-Origin * always; + add_header Cache-Control "no-cache, no-store, must-revalidate" always; + } + + location /agent/islands.json { + proxy_pass http://127.0.0.1:8081/agent/islands.json; + proxy_set_header Host $host; + add_header Access-Control-Allow-Origin * always; + add_header Cache-Control "no-cache, no-store, must-revalidate" always; + } + + location /agent/chains.json { + proxy_pass http://127.0.0.1:8081/agent/chains.json; + proxy_set_header Host $host; + add_header Access-Control-Allow-Origin * always; + add_header Cache-Control "no-cache, no-store, must-revalidate" always; + } + + # Agent landing page (static HTML for human readability) + location /agent/ { + alias /opt/aitbc/website/agent/; + index index.html; + try_files $uri $uri/ =404; + + # CORS headers + add_header Access-Control-Allow-Origin * always; + add_header Access-Control-Allow-Methods "GET, OPTIONS" always; + add_header Access-Control-Allow-Headers "Content-Type, Accept" always; + } + + # ============================================ + # RPC ENDPOINTS - Direct blockchain access + # ============================================ + location /rpc/ { + proxy_pass http://127.0.0.1:8006/rpc/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + add_header Access-Control-Allow-Origin * always; + add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always; + + if ($request_method = 'OPTIONS') { + return 204; + } + } + + # ============================================ + # MAIN WEBSITE - Agent discovery interface + # ============================================ + location / { + root /opt/aitbc/website; + index index.html; + try_files $uri $uri/ =404; + } + + # Static assets caching + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Logging + access_log /var/log/nginx/aitbc-agent.access.log; + error_log /var/log/nginx/aitbc-agent.error.log; +} diff --git a/website/systemd-example.service b/website/systemd-example.service new file mode 100644 index 00000000..9394c7ee --- /dev/null +++ b/website/systemd-example.service @@ -0,0 +1,32 @@ +[Unit] +Description=AITBC Agent Live API +Documentation=https://github.com/oib/AITBC +After=aitbc-blockchain-rpc.service +Wants=aitbc-blockchain-rpc.service + +[Service] +Type=simple +User=root +WorkingDirectory=/opt/aitbc/website/agent + +# Environment variables - customize for your node +Environment="AITBC_RPC_URL=http://localhost:8006/rpc" +Environment="NODE_ID=aitbc" +Environment="ISLAND_ID=ait-mainnet-island" +Environment="CHAIN_ID=ait-mainnet" +Environment="NODE_ROLE=hub" +Environment="PYTHONUNBUFFERED=1" + +# For testnet hub (aitbc1), use these instead: +# Environment="NODE_ID=aitbc1" +# Environment="ISLAND_ID=ait-testnet-island" +# Environment="CHAIN_ID=ait-testnet" + +# Python path - update if your venv is elsewhere +ExecStart=/opt/aitbc/venv/bin/python /opt/aitbc/website/agent/live_api.py --host 127.0.0.1 --port 8081 + +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target