refactor: remove static agent discovery JSON files - migrate to live RPC endpoints
All checks were successful
Cross-Node Transaction Testing / transaction-test (push) Successful in 3s
Deploy to Testnet / deploy-testnet (push) Successful in 1m19s
Multi-Node Stress Testing / stress-test (push) Successful in 3s

- 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
This commit is contained in:
aitbc
2026-05-19 19:03:30 +02:00
parent 001f1bc9d9
commit a1a3711092
9 changed files with 687 additions and 279 deletions

227
website/DEPLOYMENT.md Normal file
View File

@@ -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/`)

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -165,7 +165,7 @@
</header> </header>
<div class="note"> <div class="note">
<strong>Agent-First Design:</strong> This page provides human-readable documentation, but all endpoints return structured JSON for programmatic consumption. Start with <a href="/agent/discovery.json">/agent/discovery.json</a> for complete network information. <strong>Live Data Only:</strong> All API endpoints return real-time data from the blockchain RPC. No static files - everything is queried live from the node.
</div> </div>
<section class="section"> <section class="section">
@@ -176,12 +176,7 @@
<p><span class="status-indicator"></span>active</p> <p><span class="status-indicator"></span>active</p>
<p class="description">Island: ait-mainnet-island</p> <p class="description">Island: ait-mainnet-island</p>
<p class="description">Role: Hub (Block Production)</p> <p class="description">Role: Hub (Block Production)</p>
</div> <p class="description" style="font-size: 0.75rem; color: var(--warning);">Live data from RPC</p>
<div class="island-card">
<h3>AIT Testnet</h3>
<p><span class="status-indicator"></span>active</p>
<p class="description">Island: ait-testnet-island</p>
<p class="description">Role: Follower (Syncing)</p>
</div> </div>
</div> </div>
</section> </section>

View File

@@ -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"
}
}

View File

@@ -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"
}
}

317
website/agent/live_api.py Normal file
View File

@@ -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)

109
website/nginx-example.conf Normal file
View File

@@ -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;
}

View File

@@ -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