Code. Create. Rebel.
Matt
This stripped down version of https://github.com/SOVEREIGN-NET/The-Sovereign-Network
Created: 2025-10-20 04:05:31https://zhtp.anarchyspace.com/git_code/Matt/sovereign_network_node.git
Use the AnarchySpace token system or browser-based pushes.
ssh://Matt@anarchyspace.com:22/var/www/html/git_code/Matt/sovereign_network_node.git
Quick login: ssh Matt@anarchyspace.com -p 22 (restricted to /var/www/html/git_code)
Use your AnarchySpace password. Contributors can push updates to other repos, but only the creator can delete or remove them.
# Sovereign Network Node (AnarchySpace VPS Edition) This repository contains the ZHTP node running on AnarchySpace VPS infrastructure. It is a lean Axum-based service tuned for production: stable identity, atomic state, peer exchange endpoints, and Apache proxy integration. It differs from the GitHub PC/Web4 orchestrator in scope and security posture. Highlights - Stable node identity persisted at `/opt/anarchy-zhtp-node/data/identity.json` - Atomic writes for `peers.json` and `chain.json` - Peer interconnect APIs: `GET /zhtp/peers/export`, `POST /zhtp/peers/add` - UI proxied at `https://www.anarchyspace.com/zhtp` (Apache ProxyPass) - Lite client for user devices: `/zhtp/downloads/zhtp-lite-client.js` Differences vs GitHub Web4/PC version - This edition prioritizes VPS security and operational simplicity. - It omits heavy Web4 orchestration; focuses on continuity/federation endpoints. - Bridges/shims run loopback-only; public port 4820 can be restricted as needed. Deploy notes - Systemd: `anarchy-zhtp-node.service` - Sources: `/opt/anarchy-zhtp-node` - UI: `/opt/anarchy-zhtp-node/webui` (proxied under /zhtp)
name: Lint
on: [push]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '18'
- run: npm install
continue-on-error: true
- run: npm run lint
continue-on-error: true//! A tiny, in-memory, append-only blockchain for user-as-node mode.
//! Not secure; for demonstration and API plumbing only.
use serde::{Deserialize, Serialize};
use sha3::{Digest, Sha3_256};
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Block {
pub index: u64,
pub data: String,
pub prev_hash: String,
pub hash: String,
}
#[derive(Clone, Default)]
pub struct Chain(Arc<Mutex<Vec<Block>>>);
impl Chain {
pub fn new() -> Self {
let mut c = Vec::new();
let genesis = Block {
index: 0,
data: "genesis".into(),
prev_hash: "".into(),
hash: hash_block(0, "genesis", ""),
};
c.push(genesis);
Self(Arc::new(Mutex::new(c)))
}
pub fn push(&self, data: String) -> Block {
let mut g = self.0.lock().unwrap();
let index = g.len() as u64;
let prev_hash = g.last().map(|b| b.hash.clone()).unwrap_or_default();
let hash = hash_block(index, &data, &prev_hash);
let b = Block { index, data, prev_hash, hash };
g.push(b.clone());
b
}
pub fn list(&self) -> Vec<Block> {
self.0.lock().unwrap().clone()
}
}
fn hash_block(index: u64, data: &str, prev_hash: &str) -> String {
let mut h = Sha3_256::new();
h.update(index.to_le_bytes());
h.update(data.as_bytes());
h.update(prev_hash.as_bytes());
hex::encode(h.finalize())
}
[package]
name = "anarchy-blockchain"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
sha3 = { workspace = true }
hex = { workspace = true }
tracing = { workspace = true }
//! Core configuration and identity primitives for ANARCHY-ZHTP-NODE.
//! Keep this crate lightweight and dependency-minimal.
use serde::{Deserialize, Serialize};
use std::time::{Duration, SystemTime};
use hostname;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeIdentity {
pub node_id: String,
pub created_at: u64,
}
impl NodeIdentity {
pub fn new(node_id: impl Into<String>) -> Self {
let ts = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or(Duration::from_secs(0))
.as_secs();
Self { node_id: node_id.into(), created_at: ts }
}
/// Generate a node identity from host + time using SHA3-256
pub fn generate() -> Self {
let host = hostname::get().ok().and_then(|s| s.into_string().ok()).unwrap_or_else(|| "unknown".into());
let ts = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or(Duration::from_secs(0)).as_secs();
let material = format!("{}:{}", host, ts);
let node_id = external_utils::sha3_256_hex(material.as_bytes());
Self { node_id, created_at: ts }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeConfig {
pub bind_addr: String, // e.g., 0.0.0.0:4820
}
impl Default for NodeConfig {
fn default() -> Self {
Self { bind_addr: "0.0.0.0:4820".into() }
}
}
/// Lightweight helpers inspired by Sov-Net-Full (lib-crypto/sha3)
pub mod external_utils {
use sha3::{Digest, Sha3_256};
/// Compute SHA3-256 and return lowercase hex
pub fn sha3_256_hex(data: &[u8]) -> String {
let mut h = Sha3_256::new();
h.update(data);
let out = h.finalize();
hex::encode(out)
}
}[package]
name = "anarchy-core"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
sha3 = { workspace = true }
hex = { workspace = true }
hostname = "0.3"["genesis","genesis","genesis","onboard::1764065252","onboard::1764065255","onboard::1764065259","onboard::1764066860","onboard::1764066863","onboard::1764066987","onboard::1764067048","onboard::1764067053","onboard::1764067054","onboard::1764067095","onboard::1764067208","onboard::1764067214","onboard::1764067664","onboard:testuser:1764067805","onboard::1764068739","onboard::1764069565"]
{"node_id":"d5ae9904dcbb8c8e2684d38e908031c22f36ac9affd13efef0858e2c0fa2017d","created_at":1764068868}[{"addr":"node1.example.com:4820","last_seen":1764069557,"hops":1,"score":0,"failures":0,"quarantine_until":null,"backoff_secs":60},{"addr":"node2.example.com:4820","last_seen":1764069557,"hops":1,"score":0,"failures":0,"quarantine_until":null,"backoff_secs":60},{"addr":"194.164.107.6","last_seen":1764069565,"hops":1,"score":3,"failures":0,"quarantine_until":null,"backoff_secs":60}]//! Defense module: decoy IP rotation, anti-bot suspicion scoring, and simple fallbacks.
use rand::seq::SliceRandom;
use serde::Serialize;
use std::net::IpAddr;
#[derive(Debug, Clone, Serialize)]
pub struct DecoySet {
pub current: String,
pub pool: Vec<String>,
}
pub fn rotate_decoy_ip(pool: &[String]) -> DecoySet {
let mut rng = rand::thread_rng();
let mut p = pool.to_vec();
p.shuffle(&mut rng);
let current = p.first().cloned().unwrap_or_else(|| "127.0.0.1".into());
DecoySet { current, pool: p }
}
#[derive(Debug, Clone, Serialize)]
pub struct BotCheck {
pub score: u8, // 0 (good) .. 100 (bot)
pub reason: String,
}
pub fn suspicion_score(user_agent: Option<&str>, ip: Option<IpAddr>) -> BotCheck {
let mut score = 0u8;
let mut reasons = Vec::new();
if let Some(ua) = user_agent {
let ua_l = ua.to_ascii_lowercase();
if ua_l.contains("curl/") || ua_l.contains("wget/") { score = score.saturating_add(30); reasons.push("cli-agent"); }
if ua_l.contains("bot") || ua_l.contains("crawler") { score = score.saturating_add(50); reasons.push("bot-keyword"); }
} else {
score = score.saturating_add(20); reasons.push("missing-ua");
}
if let Some(ip) = ip {
match ip {
IpAddr::V4(v4) if v4.is_private() => { score = score.saturating_add(5); reasons.push("private-ip"); }
_ => {}
}
}
if score == 0 { reasons.push("ok"); }
BotCheck { score, reason: reasons.join(",") }
}
#[derive(Debug, Clone, Serialize)]
pub struct DnsHealing {
pub primary: String,
pub fallbacks: Vec<String>,
}
pub fn dns_self_heal(primary: &str, fallbacks: &[&str]) -> DnsHealing {
DnsHealing { primary: primary.to_string(), fallbacks: fallbacks.iter().map(|s| s.to_string()).collect() }
}
#[derive(Debug, Clone, Serialize, Default)]
pub struct DefenseStatus {
pub decoy_current: String,
pub decoy_pool_size: usize,
pub last_domain_ok: Option<String>,
pub last_domain_checked: Option<String>,
pub last_ip_rotation_ts: Option<u64>,
pub ua_hint: String,
}
/// Return a plausible randomized desktop/mobile User-Agent string.
pub fn random_user_agent() -> String {
let pool = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 13_6_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 16_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.7 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Mobile Safari/537.36",
];
let mut rng = rand::thread_rng();
pool.choose(&mut rng).unwrap().to_string()
}[package]
name = "anarchy-defense"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
rand = { workspace = true }
tracing = { workspace = true }
//! Mesh networking heartbeat and simple neighbor tracking.
use rand::{distributions::Alphanumeric, Rng};
use serde::Serialize;
use std::time::{Duration, SystemTime};
#[derive(Debug, Clone, Serialize)]
pub struct Heartbeat {
pub ts: u64,
pub nonce: String,
}
pub fn heartbeat() -> Heartbeat {
let ts = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or(Duration::from_secs(0))
.as_secs();
let nonce: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(8)
.map(char::from)
.collect();
Heartbeat { ts, nonce }
}
[package]
name = "anarchy-mesh"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { workspace = true }
tracing = { workspace = true }
rand = { workspace = true }
//! ANARCHY-ZHTP-NODE server
//! - Provides /status, /mesh, /blockchain, /defense, /ui endpoints on port 4820
//! - Lightweight dependencies for cheap VPS
use axum::{
body::Body,
extract::ConnectInfo,
http::{Method, Request},
middleware::Next,
response::{Html, Redirect},
routing::{get, post},
Json, Router,
};
use anarchy_blockchain as blockchain;
use anarchy_core as core;
use anarchy_defense as defense;
use anarchy_mesh as mesh;
use anarchy_webui as webui;
use zhtp_blocksync as blocksync;
use zhtp_gossip as zgossip;
use zhtp_handshake as zhello;
use zhtp_identity as zid;
use zhtp_mesh as zmesh;
use zhtp_peer as zpeer;
use serde::Serialize;
use std::{net::{SocketAddr, ToSocketAddrs}, sync::{OnceLock, RwLock}, time::{Duration, SystemTime}};
use std::path::Path;
use tower_http::{cors::{Any, CorsLayer}, services::ServeDir};
use axum::response::sse::{Sse, Event, KeepAlive};
use tokio_stream::StreamExt;
use tokio::sync::broadcast;
use tokio_stream::wrappers::BroadcastStream;
use std::fs;
use std::io::Write;
use tracing::{info, Level};
static CHAIN: OnceLock<blockchain::Chain> = OnceLock::new();
static DEFENSE_STATUS: OnceLock<RwLock<defense::DefenseStatus>> = OnceLock::new();
static NODE_ID: OnceLock<core::NodeIdentity> = OnceLock::new();
static PEERS: OnceLock<RwLock<Vec<zpeer::PeerInfo>>> = OnceLock::new();
static GOSSIP: OnceLock<RwLock<zgossip::GossipStatus>> = OnceLock::new();
static LOGS: OnceLock<RwLock<Vec<LogEntry>>> = OnceLock::new();
static LOG_TX: OnceLock<broadcast::Sender<LogEntry>> = OnceLock::new();
fn write_atomic(path: &str, contents: &str) {
let tmp = format!("{}.tmp", path);
let _ = fs::write(&tmp, contents);
let _ = fs::rename(&tmp, path);
}
#[derive(Debug, Clone, serde::Deserialize)]
struct AppConfig {
#[serde(default)] bootstrap: Bootstrap,
#[serde(default)] decoys: Decoys,
#[serde(default)] dns: Dns,
#[serde(default)] node: NodeCfg,
}
#[derive(Debug, Clone, serde::Deserialize, Default)] struct Bootstrap { #[serde(default)] peers: Vec<String> }
#[derive(Debug, Clone, serde::Deserialize, Default)] struct Decoys { #[serde(default)] ips: Vec<String> }
#[derive(Debug, Clone, serde::Deserialize, Default)] struct Dns { #[serde(default)] domains: Vec<String> }
#[derive(Debug, Clone, serde::Deserialize, Default)] struct NodeCfg { #[serde(default)] nickname: String, #[serde(default)] continuity: bool }
static APPCFG: OnceLock<AppConfig> = OnceLock::new();
static DATADIR: &str = "/opt/anarchy-zhtp-node/data";
#[derive(Clone, Serialize)]
struct LogEntry { ts: u64, path: String }
#[derive(Serialize)]
struct Status {
ok: bool,
version: &'static str,
bind_addr: String,
uptime_secs: u64,
}
#[derive(Serialize)]
struct Stats { peers: usize, onboard_events: usize, ts: u64 }
#[derive(Debug, Clone, serde::Deserialize)]
struct OnboardReq { user_id: Option<String>, nickname: Option<String>, ua: Option<String>, ip: Option<String> }
#[derive(Debug, Clone, serde::Deserialize)]
struct PeerSeenReq { addr: String }
#[tokio::main]
async fn main() {
// Logging
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env()
.add_directive(Level::INFO.into()))
.with_target(false)
.with_timer(tracing_subscriber::fmt::time::UtcTime::rfc_3339())
.init();
// Config and state
let cfg = core::NodeConfig::default();
// Persisted identity
let id_path = format!("{}/identity.json", DATADIR);
let loaded_id = fs::read_to_string(&id_path).ok().and_then(|t| serde_json::from_str::<core::NodeIdentity>(&t).ok());
let id = match loaded_id { Some(i) => i, None => { let g = core::NodeIdentity::generate(); let _ = fs::write(&id_path, serde_json::to_string(&g).unwrap_or("{}".into())); g } };
NODE_ID.set(id).ok();
CHAIN.get_or_init(blockchain::Chain::new);
DEFENSE_STATUS.get_or_init(|| RwLock::new(defense::DefenseStatus::default()));
PEERS.get_or_init(|| RwLock::new(Vec::new()));
GOSSIP.get_or_init(|| RwLock::new(zgossip::GossipStatus { messages_seen: 0, last_msg: None }));
LOGS.get_or_init(|| RwLock::new(Vec::new()));
// Init broadcast channel for SSE logs
let (tx, _rx) = broadcast::channel::<LogEntry>(200);
LOG_TX.set(tx).ok();
// Load config.toml
let cfg_path = "/opt/anarchy-zhtp-node/config.toml";
let loaded = std::fs::read_to_string(cfg_path).ok().and_then(|s| toml::from_str::<AppConfig>(&s).ok());
let appcfg = loaded.unwrap_or(AppConfig{ bootstrap: Default::default(), decoys: Default::default(), dns: Default::default(), node: Default::default()});
APPCFG.set(appcfg).ok();
// Ensure data dir exists
let _ = fs::create_dir_all(DATADIR);
// Attempt to load persisted peers
if let Ok(peers_json) = fs::read_to_string(format!("{}/peers.json", DATADIR)) {
if let Ok(list) = serde_json::from_str::<Vec<zpeer::PeerInfo>>(&peers_json) {
if let Ok(mut v) = PEERS.get().unwrap().write() { *v = list; }
}
}
// Attempt to load persisted chain (as list of data strings)
if let Ok(chain_json) = fs::read_to_string(format!("{}/chain.json", DATADIR)) {
if let Ok(datas) = serde_json::from_str::<Vec<String>>(&chain_json) {
let ch = CHAIN.get().unwrap();
for d in datas { ch.push(d); }
}
}
// Background tasks: decoy rotation and domain finder
// These are lightweight periodic tasks to maintain censorship resistance metadata.
let ds = DEFENSE_STATUS.get().unwrap();
tokio::spawn(async move {
let mut ticker = tokio::time::interval(Duration::from_secs(60));
let decoy_pool = APPCFG.get().map(|c| c.decoys.ips.clone()).unwrap_or_default();
loop {
ticker.tick().await;
let set = defense::rotate_decoy_ip(&decoy_pool);
let ts = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs();
if let Ok(mut g) = ds.write() {
g.decoy_current = set.current;
g.decoy_pool_size = set.pool.len();
g.last_ip_rotation_ts = Some(ts);
}
}
});
let ds2 = DEFENSE_STATUS.get().unwrap();
tokio::spawn(async move {
let mut ticker = tokio::time::interval(Duration::from_secs(120));
let domains = APPCFG.get().map(|c| c.dns.domains.clone()).unwrap_or_else(|| vec!["example.com".to_string()]);
loop {
ticker.tick().await;
for d in &domains {
let dom = d.to_string();
let res = tokio::task::spawn_blocking(move || {
// Try to resolve d:80 using system resolver
(dom.as_str(), 80).to_socket_addrs().ok().map(|mut it| it.next().is_some()).unwrap_or(false)
}).await.unwrap_or(false);
if let Ok(mut g) = ds2.write() {
g.last_domain_checked = Some(d.to_string());
if res { g.last_domain_ok = Some(d.to_string()); break; }
}
}
}
});
// Bootstrap peers via DNS lookups (placeholder) and keep last-seen timestamps updated
let peers_lock = PEERS.get().unwrap();
tokio::spawn(async move {
let mut ticker = tokio::time::interval(Duration::from_secs(90));
let seeds = APPCFG.get().map(|c| c.bootstrap.peers.clone()).unwrap_or_else(|| vec!["example.com:80".to_string()]);
loop {
ticker.tick().await;
let now = zpeer::now();
let mut v = peers_lock.write().unwrap();
v.clear();
for s in &seeds {
v.push(zpeer::PeerInfo { addr: s.to_string(), last_seen: now, hops: 1, score: 0, failures: 0, quarantine_until: None, backoff_secs: 60 });
}
}
});
// Periodic decay and quarantine maintenance (every 60s)
let peers_for_decay = PEERS.get().unwrap();
tokio::spawn(async move {
let mut ticker = tokio::time::interval(Duration::from_secs(60));
loop {
ticker.tick().await;
let now = zpeer::now();
let mut v = peers_for_decay.write().unwrap();
for p in v.iter_mut() {
// score decay toward zero
if p.score > 0 { p.score -= 1; } else if p.score < 0 { p.score += 1; }
// quarantine if too low
if p.score < -15 { p.quarantine_until = Some(now + 120); }
// expire quarantine
if let Some(until) = p.quarantine_until { if until <= now { p.quarantine_until = None; } }
}
}
});
// Persistence task (every 120s): write peers and chain to DATADIR
tokio::spawn(async move {
let mut ticker = tokio::time::interval(Duration::from_secs(120));
loop {
ticker.tick().await;
let peers = PEERS.get().unwrap().read().unwrap().clone();
if let Ok(mut f) = fs::File::create(format!("{}/peers.json", DATADIR)) { let _ = write!(f, "{}", serde_json::to_string(&peers).unwrap_or("[]".into())); }
let ch = CHAIN.get().unwrap();
let datas: Vec<String> = ch.list().into_iter().map(|b| b.data).collect();
if let Ok(mut f) = fs::File::create(format!("{}/chain.json", DATADIR)) { let _ = write!(f, "{}", serde_json::to_string(&datas).unwrap_or("[]".into())); }
}
});
// Routes
// CORS for cross-origin UI (e.g., anarchyspace.com -> 192.3.249.4)
let cors = CorsLayer::new()
.allow_origin(Any)
.allow_methods([Method::GET])
.allow_headers(Any);
// per-response noise header to obfuscate simple fingerprinting
async fn add_noise_header(req: Request<Body>, next: Next) -> impl axum::response::IntoResponse {
use rand::{distributions::Alphanumeric, Rng};
// Log request path into in-process ring buffer
let ts = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
if let Some(lock) = LOGS.get() {
if let Ok(mut v) = lock.write() {
v.push(LogEntry { ts, path: req.uri().path().to_string() });
let len = v.len();
if len > 200 {
let drop_n = len - 200;
let _ = v.drain(0..drop_n);
}
}
}
if let Some(tx) = LOG_TX.get() { let _ = tx.send(LogEntry { ts, path: req.uri().path().to_string() }); }
let mut res = next.run(req).await;
let noise: String = rand::thread_rng().sample_iter(&Alphanumeric).take(12).map(char::from).collect();
res.headers_mut().insert("X-Noise", axum::http::HeaderValue::from_str(&noise).unwrap());
res
}
#[derive(Debug, Clone, serde::Deserialize)]
struct AddPeerReq { addr: String }
async fn route_peers_add(Json(req): Json<AddPeerReq>) -> Json<serde_json::Value> {
if req.addr.trim().is_empty() { return Json(serde_json::json!({"ok": false, "error": "empty-addr"})); }
if let Ok(mut v) = PEERS.get().unwrap().write() {
if !v.iter().any(|p| p.addr == req.addr) {
v.push(zpeer::PeerInfo { addr: req.addr.clone(), last_seen: zpeer::now(), hops: 1, score: 0, failures: 0, quarantine_until: None, backoff_secs: 60 });
}
}
Json(serde_json::json!({"ok": true}))
}
#[derive(Serialize)]
struct PeerExport { peers: Vec<String> }
async fn route_peers_export() -> Json<PeerExport> {
let list = PEERS.get().unwrap().read().unwrap();
let addrs = list.iter().map(|p| p.addr.clone()).collect::<Vec<_>>();
Json(PeerExport { peers: addrs })
}
let app = Router::new()
.route("/status", get(route_status))
.route("/mesh", get(route_mesh))
.route("/blockchain", get(route_blockchain))
.route("/defense", get(route_defense))
// ZHTP federation endpoints
.route("/zhtp/hello", get(route_zhello))
.route("/zhtp/peers", get(route_zpeers))
β¦ truncated β¦Preview truncated for length. Use the raw link to see the full file.
//! ANARCHY-ZHTP-NODE server
//! - Provides /status, /mesh, /blockchain, /defense, /ui endpoints on port 4820
//! - Lightweight dependencies for cheap VPS
use axum::{
body::Body,
extract::ConnectInfo,
http::{Method, Request},
middleware::Next,
response::{Html, Redirect},
routing::{get, post},
Json, Router,
};
use anarchy_blockchain as blockchain;
use anarchy_core as core;
use anarchy_defense as defense;
use anarchy_mesh as mesh;
use anarchy_webui as webui;
use zhtp_blocksync as blocksync;
use zhtp_gossip as zgossip;
use zhtp_handshake as zhello;
use zhtp_identity as zid;
use zhtp_mesh as zmesh;
use zhtp_peer as zpeer;
use serde::Serialize;
use std::{net::{SocketAddr, ToSocketAddrs}, sync::{OnceLock, RwLock}, time::{Duration, SystemTime}};
use tower_http::{cors::{Any, CorsLayer}, services::ServeDir};
use axum::response::sse::{Sse, Event, KeepAlive};
use tokio_stream::StreamExt;
use tokio::sync::broadcast;
use tokio_stream::wrappers::BroadcastStream;
use std::fs;
use std::io::Write;
use tracing::{info, Level};
static CHAIN: OnceLock<blockchain::Chain> = OnceLock::new();
static DEFENSE_STATUS: OnceLock<RwLock<defense::DefenseStatus>> = OnceLock::new();
static NODE_ID: OnceLock<core::NodeIdentity> = OnceLock::new();
static PEERS: OnceLock<RwLock<Vec<zpeer::PeerInfo>>> = OnceLock::new();
static GOSSIP: OnceLock<RwLock<zgossip::GossipStatus>> = OnceLock::new();
static LOGS: OnceLock<RwLock<Vec<LogEntry>>> = OnceLock::new();
static LOG_TX: OnceLock<broadcast::Sender<LogEntry>> = OnceLock::new();
#[derive(Debug, Clone, serde::Deserialize)]
struct AppConfig {
#[serde(default)] bootstrap: Bootstrap,
#[serde(default)] decoys: Decoys,
#[serde(default)] dns: Dns,
#[serde(default)] node: NodeCfg,
}
#[derive(Debug, Clone, serde::Deserialize, Default)] struct Bootstrap { #[serde(default)] peers: Vec<String> }
#[derive(Debug, Clone, serde::Deserialize, Default)] struct Decoys { #[serde(default)] ips: Vec<String> }
#[derive(Debug, Clone, serde::Deserialize, Default)] struct Dns { #[serde(default)] domains: Vec<String> }
#[derive(Debug, Clone, serde::Deserialize, Default)] struct NodeCfg { #[serde(default)] nickname: String, #[serde(default)] continuity: bool }
static APPCFG: OnceLock<AppConfig> = OnceLock::new();
static DATADIR: &str = "/opt/anarchy-zhtp-node/data";
#[derive(Clone, Serialize)]
struct LogEntry { ts: u64, path: String }
#[derive(Serialize)]
struct Status {
ok: bool,
version: &'static str,
bind_addr: String,
uptime_secs: u64,
}
#[derive(Serialize)]
struct Stats { peers: usize, onboard_events: usize, ts: u64 }
#[derive(Debug, Clone, serde::Deserialize)]
struct OnboardReq { user_id: Option<String>, nickname: Option<String>, ua: Option<String>, ip: Option<String> }
#[derive(Debug, Clone, serde::Deserialize)]
struct PeerSeenReq { addr: String }
#[tokio::main]
async fn main() {
// Logging
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env()
.add_directive(Level::INFO.into()))
.with_target(false)
.with_timer(tracing_subscriber::fmt::time::UtcTime::rfc_3339())
.init();
// Config and state
let cfg = core::NodeConfig::default();
let id = core::NodeIdentity::generate();
NODE_ID.set(id).ok();
CHAIN.get_or_init(blockchain::Chain::new);
DEFENSE_STATUS.get_or_init(|| RwLock::new(defense::DefenseStatus::default()));
PEERS.get_or_init(|| RwLock::new(Vec::new()));
GOSSIP.get_or_init(|| RwLock::new(zgossip::GossipStatus { messages_seen: 0, last_msg: None }));
LOGS.get_or_init(|| RwLock::new(Vec::new()));
// Init broadcast channel for SSE logs
let (tx, _rx) = broadcast::channel::<LogEntry>(200);
LOG_TX.set(tx).ok();
// Load config.toml
let cfg_path = "/opt/anarchy-zhtp-node/config.toml";
let loaded = std::fs::read_to_string(cfg_path).ok().and_then(|s| toml::from_str::<AppConfig>(&s).ok());
let appcfg = loaded.unwrap_or(AppConfig{ bootstrap: Default::default(), decoys: Default::default(), dns: Default::default(), node: Default::default()});
APPCFG.set(appcfg).ok();
// Ensure data dir exists
let _ = fs::create_dir_all(DATADIR);
// Attempt to load persisted peers
if let Ok(peers_json) = fs::read_to_string(format!("{}/peers.json", DATADIR)) {
if let Ok(list) = serde_json::from_str::<Vec<zpeer::PeerInfo>>(&peers_json) {
if let Ok(mut v) = PEERS.get().unwrap().write() { *v = list; }
}
}
// Attempt to load persisted chain (as list of data strings)
if let Ok(chain_json) = fs::read_to_string(format!("{}/chain.json", DATADIR)) {
if let Ok(datas) = serde_json::from_str::<Vec<String>>(&chain_json) {
let ch = CHAIN.get().unwrap();
for d in datas { ch.push(d); }
}
}
// Background tasks: decoy rotation and domain finder
// These are lightweight periodic tasks to maintain censorship resistance metadata.
let ds = DEFENSE_STATUS.get().unwrap();
tokio::spawn(async move {
let mut ticker = tokio::time::interval(Duration::from_secs(60));
let decoy_pool = APPCFG.get().map(|c| c.decoys.ips.clone()).unwrap_or_default();
loop {
ticker.tick().await;
let set = defense::rotate_decoy_ip(&decoy_pool);
let ts = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs();
if let Ok(mut g) = ds.write() {
g.decoy_current = set.current;
g.decoy_pool_size = set.pool.len();
g.last_ip_rotation_ts = Some(ts);
}
}
});
let ds2 = DEFENSE_STATUS.get().unwrap();
tokio::spawn(async move {
let mut ticker = tokio::time::interval(Duration::from_secs(120));
let domains = APPCFG.get().map(|c| c.dns.domains.clone()).unwrap_or_else(|| vec!["example.com".to_string()]);
loop {
ticker.tick().await;
for d in &domains {
let dom = d.to_string();
let res = tokio::task::spawn_blocking(move || {
// Try to resolve d:80 using system resolver
(dom.as_str(), 80).to_socket_addrs().ok().map(|mut it| it.next().is_some()).unwrap_or(false)
}).await.unwrap_or(false);
if let Ok(mut g) = ds2.write() {
g.last_domain_checked = Some(d.to_string());
if res { g.last_domain_ok = Some(d.to_string()); break; }
}
}
}
});
// Bootstrap peers via DNS lookups (placeholder) and keep last-seen timestamps updated
let peers_lock = PEERS.get().unwrap();
tokio::spawn(async move {
let mut ticker = tokio::time::interval(Duration::from_secs(90));
let seeds = APPCFG.get().map(|c| c.bootstrap.peers.clone()).unwrap_or_else(|| vec!["example.com:80".to_string()]);
loop {
ticker.tick().await;
let now = zpeer::now();
let mut v = peers_lock.write().unwrap();
v.clear();
for s in &seeds {
v.push(zpeer::PeerInfo { addr: s.to_string(), last_seen: now, hops: 1, score: 0, failures: 0, quarantine_until: None, backoff_secs: 60 });
}
}
});
// Periodic decay and quarantine maintenance (every 60s)
let peers_for_decay = PEERS.get().unwrap();
tokio::spawn(async move {
let mut ticker = tokio::time::interval(Duration::from_secs(60));
loop {
ticker.tick().await;
let now = zpeer::now();
let mut v = peers_for_decay.write().unwrap();
for p in v.iter_mut() {
// score decay toward zero
if p.score > 0 { p.score -= 1; } else if p.score < 0 { p.score += 1; }
// quarantine if too low
if p.score < -15 { p.quarantine_until = Some(now + 120); }
// expire quarantine
if let Some(until) = p.quarantine_until { if until <= now { p.quarantine_until = None; } }
}
}
});
// Persistence task (every 120s): write peers and chain to DATADIR
tokio::spawn(async move {
let mut ticker = tokio::time::interval(Duration::from_secs(120));
loop {
ticker.tick().await;
let peers = PEERS.get().unwrap().read().unwrap().clone();
if let Ok(mut f) = fs::File::create(format!("{}/peers.json", DATADIR)) { let _ = write!(f, "{}", serde_json::to_string(&peers).unwrap_or("[]".into())); }
let ch = CHAIN.get().unwrap();
let datas: Vec<String> = ch.list().into_iter().map(|b| b.data).collect();
if let Ok(mut f) = fs::File::create(format!("{}/chain.json", DATADIR)) { let _ = write!(f, "{}", serde_json::to_string(&datas).unwrap_or("[]".into())); }
}
});
// Routes
// CORS for cross-origin UI (e.g., anarchyspace.com -> 192.3.249.4)
let cors = CorsLayer::new()
.allow_origin(Any)
.allow_methods([Method::GET])
.allow_headers(Any);
// per-response noise header to obfuscate simple fingerprinting
async fn add_noise_header(req: Request<Body>, next: Next) -> impl axum::response::IntoResponse {
use rand::{distributions::Alphanumeric, Rng};
// Log request path into in-process ring buffer
let ts = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
if let Some(lock) = LOGS.get() {
if let Ok(mut v) = lock.write() {
v.push(LogEntry { ts, path: req.uri().path().to_string() });
let len = v.len();
if len > 200 {
let drop_n = len - 200;
let _ = v.drain(0..drop_n);
}
}
}
if let Some(tx) = LOG_TX.get() { let _ = tx.send(LogEntry { ts, path: req.uri().path().to_string() }); }
let mut res = next.run(req).await;
let noise: String = rand::thread_rng().sample_iter(&Alphanumeric).take(12).map(char::from).collect();
res.headers_mut().insert("X-Noise", axum::http::HeaderValue::from_str(&noise).unwrap());
res
}
let app = Router::new()
.route("/status", get(route_status))
.route("/mesh", get(route_mesh))
.route("/blockchain", get(route_blockchain))
.route("/defense", get(route_defense))
// ZHTP federation endpoints
.route("/zhtp/hello", get(route_zhello))
.route("/zhtp/peers", get(route_zpeers))
.route("/zhtp/gossip", get(route_zgossip))
.route("/zhtp/blocksync", get(route_zblocksync))
.route("/zhtp/mesh", get(route_zmesh))
.route("/zhtp/identity", get(route_zidentity))
.route("/zhtp/logs", get(route_zlogs))
.route("/zhtp/logs/stream", get(route_zlogs_stream))
.route("/zhtp/config", get(route_config))
.route("/zhtp/onboard", post(route_onboard))
.route("/zhtp/peer/seen", post(route_peer_seen))
.route("/zhtp/stats", get(route_stats))
// Redirect /home/zhtp to /ui/index.html for public URL mapping
.route("/home/zhtp", get(|| async { Redirect::permanent("/ui/index.html") }))
// Serve static UI from /opt/anarchy-zhtp-node/webui
.nest_service("/ui", ServeDir::new("/opt/anarchy-zhtp-node/webui").append_index_html_on_directories(true))
.layer(cors)
.layer(axum::middleware::from_fn(add_noise_header));
let addr: SocketAddr = cfg.bind_addr.parse().expect("bind addr");
info!("listening on {}", addr);
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
// Provide ConnectInfo<SocketAddr> to handlers that need the peer addr
axum::serve(listener, app.into_make_service_with_connect_info::<SocketAddr>())
.await
.expect("server run");
}
async fn route_status() -> Json<Status> {
let cfg = core::NodeConfig::default
β¦ truncated β¦Preview truncated for length. Use the raw link to see the full file.
[package]
name = "anarchy-server"
version = "0.1.0"
edition = "2021"
[dependencies]
axum = { workspace = true }
tokio = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
anarchy-core = { path = "../core" }
anarchy-mesh = { path = "../mesh" }
anarchy-blockchain = { path = "../blockchain" }
anarchy-defense = { path = "../defense" }
anarchy-webui = { path = "../webui" }
tower-http = { workspace = true, features = ["fs", "cors"] }
rand = { workspace = true }
zhtp_peer = { path = "../zhtp_peer" }
zhtp_handshake = { path = "../zhtp_handshake" }
zhtp_gossip = { path = "../zhtp_gossip" }
zhtp_blocksync = { path = "../zhtp_blocksync" }
zhtp_mesh = { path = "../zhtp_mesh" }
zhtp_identity = { path = "../zhtp_identity" }
zhtp_defense = { path = "../zhtp_defense" }
toml = { workspace = true }
tokio-stream = { workspace = true, features = ["sync"] }
futures-core = { workspace = true }#!/usr/bin/env node
// Minimal ZHTP lite client for end-user devices
// - Periodically announces presence to your VPS ZHTP node
// - Optionally exposes a local status endpoint
// Usage: node zhtp-lite-client.js --server https://www.anarchyspace.com/zhtp --id USER123 --interval 30
const http = require('http');
const https = require('https');
const { URL } = require('url');
const express = require('express');
const app = express();
app.use(express.json());
const argv = require('node:process').argv.slice(2);
function arg(name, def){ const i=argv.indexOf(name); return i>=0? argv[i+1] : def; }
const SERVER = arg('--server', 'http://127.0.0.1:4820');
const USER_ID = arg('--id', null);
const INTERVAL = parseInt(arg('--interval','60'),10)||60;
const PORT = parseInt(arg('--port','4210'),10)||4210;
const client = SERVER.startsWith('https')? https : http;
function postJSON(path, body){
const u = new URL(path, SERVER);
const data = Buffer.from(JSON.stringify(body));
const opts = { method:'POST', headers:{ 'Content-Type':'application/json', 'Content-Length': data.length } };
return new Promise((resolve) => {
const req = client.request(u, opts, (res)=>{ res.resume(); res.on('end',()=>resolve(res.statusCode)); });
req.on('error', ()=>resolve(0));
req.write(data); req.end();
});
}
async function announce(){
const payload = { user_id: USER_ID, nickname: USER_ID, ip: null };
await postJSON('/zhtp/onboard', payload);
}
async function heartbeat(){
const status = await postJSON('/zhtp/peer/seen', { addr: USER_ID || 'client' });
return status;
}
let tick = 0;
setInterval(async ()=>{
try {
if (tick % 10 === 0) await announce();
await heartbeat();
} catch(_) {}
tick++;
}, INTERVAL*1000);
app.get('/status', (req,res)=>{ res.json({ ok:true, server: SERVER, id: USER_ID, interval: INTERVAL }); });
app.listen(PORT, '127.0.0.1', ()=> console.log());//! Very small embedded UI page.
pub fn index_html() -> &'static str {
r#"<!doctype html>
<html lang=\"en\">
<head>
<meta charset=\"utf-8\" />
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />
<title>ANARCHY ZHTP NODE</title>
<style>
body { font-family: system-ui, sans-serif; margin: 2rem; }
code { background: #f3f3f3; padding: 2px 4px; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
.card { border: 1px solid #ddd; padding: 1rem; border-radius: 8px; }
</style>
</head>
<body>
<h1>ANARCHY-ZHTP-NODE</h1>
<p>Minimal UI. Endpoints:
<code>/status</code> <code>/mesh</code> <code>/blockchain</code> <code>/defense</code>
</p>
<div class=\"grid\">
<div class=\"card\">
<h3>Status</h3>
<pre id=\"status\">loading...</pre>
</div>
<div class=\"card\">
<h3>Mesh</h3>
<pre id=\"mesh\">loading...</pre>
</div>
<div class=\"card\">
<h3>Blockchain</h3>
<pre id=\"chain\">loading...</pre>
</div>
<div class=\"card\">
<h3>Defense</h3>
<pre id=\"defense\">loading...</pre>
</div>
</div>
<script>
async function load(endpoint, id) {
try { const r = await fetch(endpoint); const t = await r.text(); document.getElementById(id).textContent = t; }
catch (e) { document.getElementById(id).textContent = 'error: '+e; }
}
load('/status','status'); load('/mesh','mesh'); load('/blockchain','chain'); load('/defense','defense');
</script>
</body>
</html>"#
}
(() => {
const backend = location.origin.includes('anarchyspace.com') ? 'http://192.3.249.4:4820' : '';
const qs = (id) => document.getElementById(id);
qs('backend').textContent = backend || location.origin;
async function get(endpoint) {
const url = (backend || '') + endpoint;
const res = await fetch(url, { cache: 'no-store' });
if (!res.ok) throw new Error(res.status + ' ' + res.statusText);
return res.json();
}
function heartbeat() {
const ts = new Date().toISOString();
qs('heartbeat').textContent = `UI alive β ${ts}`;
}
function setText(id, v){ qs(id).textContent = v }
function humanUptime(sec){
const s = Number(sec)||0; const h=Math.floor(s/3600), m=Math.floor((s%3600)/60), r=s%60; return `${h}h ${m}m ${r}s`;
}
async function refreshAll() {
heartbeat();
try {
const [status, idv, mesh, bs, peers, defense, gossip, logs] = await Promise.all([
get('/status'),
get('/zhtp/identity'),
get('/zhtp/mesh'),
get('/zhtp/blocksync'),
get('/zhtp/peers'),
get('/defense'),
get('/zhtp/gossip'),
get('/zhtp/logs'),
]);
setText('bind_addr', status.bind_addr);
setText('uptime', humanUptime(status.uptime_secs));
setText('version', status.version);
setText('node_id', idv.node_id);
setText('mesh_peers', mesh.peers);
setText('last_gossip', gossip.last_msg || 'n/a');
setText('height', bs.height);
setText('latest_hash', bs.latest_hash);
setText('score', (defense.bot && defense.bot.score) || 'n/a');
setText('ua_hint', (defense.status && defense.status.ua_hint) || '');
setText('decoy_ip', (defense.status && defense.status.decoy_current) || '');
setText('domain_ok', (defense.status && defense.status.last_domain_ok) || '');
const tbody = document.querySelector('#peers tbody');
tbody.innerHTML = '';
(peers.peers || []).forEach(p => {
const tr = document.createElement('tr');
tr.innerHTML = `<td>${p.addr}</td><td>${new Date(p.last_seen*1000).toISOString()}</td><td>${p.hops}</td><td>${p.score}</td>`;
tbody.appendChild(tr);
});
setText('gossip', JSON.stringify(gossip, null, 2));
setText('logs', JSON.stringify(logs, null, 2));
} catch (e) {
console.error(e);
}
}
refreshAll();
setInterval(refreshAll, 10000);
// Live logs via SSE
try {
const src = new EventSource((backend || '') + '/zhtp/logs/stream');
src.onmessage = (ev) => {
try {
const data = JSON.parse(ev.data);
const line = `${new Date(data.ts*1000).toISOString()} ${data.path}`;
const pre = qs('live');
pre.textContent += (pre.textContent ? '\n' : '') + line;
pre.scrollTop = pre.scrollHeight;
} catch {}
};
} catch (e) { console.error('SSE error', e); }
})();[package]
name = "anarchy-webui"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { workspace = true }
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>ZHTP Node Dashboard</title>
<link rel="stylesheet" href="/ui/styles.css?v=20251125094856" />
</head>
<body>
<header>
<h1>ZHTP Node Dashboard <small id="mode">Continuity Mode</small></h1>
<div class="head-right">
<span id="backend"></span>
<span id="heartbeat">Loadingβ¦</span>
</div>
</header>
<main class="grid">
<section class="card">
<h2>Status</h2>
<div class="kv">
<div><label>Bind:</label><span id="bind_addr">β¦</span></div>
<div><label>Uptime:</label><span id="uptime">β¦</span></div>
<div><label>Version:</label><span id="version">β¦</span></div>
<div><label>Node ID:</label><span id="node_id">β¦</span></div>
</div>
</section>
<section class="card">
<h2>Mesh Health</h2>
<div class="kv">
<div><label>Peers:</label><span id="mesh_peers">β¦</span></div>
<div><label>Last gossip:</label><span id="last_gossip">β¦</span></div>
</div>
</section>
<section class="card">
<h2>Blockchain</h2>
<div class="kv">
<div><label>Height:</label><span id="height">β¦</span></div>
<div><label>Latest hash:</label><span id="latest_hash">β¦</span></div>
</div>
</section>
<section class="card">
<h2>Defense</h2>
<div class="kv">
<div><label>Score:</label><span id="score">β¦</span></div>
<div><label>UA hint:</label><span id="ua_hint">β¦</span></div>
<div><label>Decoy IP:</label><span id="decoy_ip">β¦</span></div>
<div><label>Domain OK:</label><span id="domain_ok">β¦</span></div>
</div>
</section>
<section class="card span-2">
<h2>Peers</h2>
<table id="peers"><thead><tr><th>Addr</th><th>Last seen</th><th>Hops</th><th>Score</th></tr></thead><tbody></tbody></table>
</section>
<section class="card span-2">
<h2>Gossip</h2>
<pre id="gossip">Loadingβ¦</pre>
</section>
<section class="card span-2">
<h2>Logs</h2>
<pre id="logs">Loadingβ¦</pre>
<h3>Live</h3>
<pre id="live">(connectingβ¦)</pre>
</section>
</main>
<footer>
<small>Auto-refresh every 10 seconds | Resilience features active</small>
</footer>
<script src="/ui/app.js?v=20251125094856"></script>
</body>
</html>:root{--bg:#0b0e11;--card:#12161a;--text:#e6edf3;--muted:#9aa4af;--accent:#33d69f}
*{box-sizing:border-box}body{margin:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,"Helvetica Neue",sans-serif;background:var(--bg);color:var(--text)}
.grid{display:grid;gap:16px;padding:16px;grid-template-columns:repeat(auto-fit,minmax(320px,1fr))}
.card{background:var(--card);border:1px solid #1b2128;border-radius:12px;padding:12px}
.card h2{margin:0 0 8px 0;font-size:16px;color:var(--accent)}
pre{white-space:pre-wrap;word-break:break-word;margin:0;font-size:13px;line-height:1.45}
footer{padding:10px 16px;border-top:1px solid #20262c;color:var(--muted);text-align:center}
header{padding:16px 20px;border-bottom:1px solid #20262c;display:flex;align-items:center;justify-content:space-between;gap:12px}
header h1{margin:0;font-size:18px;color:var(--text);display:flex;gap:8px;align-items:center}
#mode{color:var(--muted);font-weight:400;font-size:12px;border:1px solid #2a3138;padding:2px 6px;border-radius:6px}
.head-right{display:flex;gap:12px;color:var(--muted)}
.kv{display:grid;grid-template-columns:160px 1fr;gap:6px 10px}
.kv label{color:var(--muted)}
.span-2{grid-column:1 / -1}
table{width:100%;border-collapse:collapse;font-size:13px}
th,td{border-bottom:1px solid #1b2128;padding:6px 8px;text-align:left}//! Very small embedded UI page.
pub fn index_html() -> &'static str {
r#"<!doctype html>
<html lang=\"en\">
<head>
<meta charset=\"utf-8\" />
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />
<title>ANARCHY ZHTP NODE</title>
<style>
body { font-family: system-ui, sans-serif; margin: 2rem; }
code { background: #f3f3f3; padding: 2px 4px; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
.card { border: 1px solid #ddd; padding: 1rem; border-radius: 8px; }
</style>
</head>
<body>
<h1>ANARCHY-ZHTP-NODE</h1>
<p>Minimal UI. Endpoints:
<code>/status</code> <code>/mesh</code> <code>/blockchain</code> <code>/defense</code>
</p>
<div class=\"grid\">
<div class=\"card\">
<h3>Status</h3>
<pre id=\"status\">loading...</pre>
</div>
<div class=\"card\">
<h3>Mesh</h3>
<pre id=\"mesh\">loading...</pre>
</div>
<div class=\"card\">
<h3>Blockchain</h3>
<pre id=\"chain\">loading...</pre>
</div>
<div class=\"card\">
<h3>Defense</h3>
<pre id=\"defense\">loading...</pre>
</div>
</div>
<script>
async function load(endpoint, id) {
try { const r = await fetch(endpoint); const t = await r.text(); document.getElementById(id).textContent = t; }
catch (e) { document.getElementById(id).textContent = 'error: '+e; }
}
load('/status','status'); load('/mesh','mesh'); load('/blockchain','chain'); load('/defense','defense');
</script>
</body>
</html>"#
}
[package]
name = "anarchy-webui"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { workspace = true }
use serde::Serialize;
#[derive(Debug, Clone, Serialize)]
pub struct BlockSyncInfo {
pub height: u64,
pub latest_hash: String,
}
[package]
name = "zhtp_blocksync"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { workspace = true }
tracing = { workspace = true }
use serde::Serialize;
use anarchy_defense as base;
#[derive(Debug, Clone, Serialize)]
pub struct DefenseView {
pub status: base::DefenseStatus,
}
pub fn view(status: &base::DefenseStatus) -> DefenseView {
DefenseView { status: status.clone() }
}
[package]
name = "zhtp_defense"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { workspace = true }
tracing = { workspace = true }
anarchy-defense = { path = "../defense" }
use serde::Serialize;
#[derive(Debug, Clone, Serialize)]
pub struct GossipStatus {
pub messages_seen: u64,
pub last_msg: Option<String>,
}
[package]
name = "zhtp_gossip"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { workspace = true }
tracing = { workspace = true }
use serde::Serialize;
#[derive(Debug, Clone, Serialize)]
pub struct Hello {
pub protocol: &'static str,
pub version: &'static str,
pub node_id: String,
}
[package]
name = "zhtp_handshake"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { workspace = true }
tracing = { workspace = true }
use anarchy_core::NodeIdentity;
use serde::Serialize;
#[derive(Debug, Clone, Serialize)]
pub struct IdentityView {
pub node_id: String,
pub created_at: u64,
}
pub fn view(id: &NodeIdentity) -> IdentityView {
IdentityView { node_id: id.node_id.clone(), created_at: id.created_at }
}
[package]
name = "zhtp_identity"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { workspace = true }
tracing = { workspace = true }
anarchy-core = { path = "../core" }
use serde::Serialize;
#[derive(Debug, Clone, Serialize)]
pub struct MeshHealth {
pub peers: usize,
pub last_gossip_ts: Option<u64>,
}
[package]
name = "zhtp_mesh"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { workspace = true }
tracing = { workspace = true }
use serde::{Serialize, Deserialize};
use std::time::{Duration, SystemTime};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PeerInfo {
pub addr: String,
pub last_seen: u64,
pub hops: u8,
pub score: i32,
pub failures: u32,
pub quarantine_until: Option<u64>,
pub backoff_secs: u64,
}
pub fn now() -> u64 {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or(Duration::from_secs(0))
.as_secs()
}[package]
name = "zhtp_peer"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { workspace = true }
tracing = { workspace = true }
# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "aho-corasick" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] name = "anarchy-blockchain" version = "0.1.0" dependencies = [ "hex", "serde", "serde_json", "sha3", "tracing", ] [[package]] name = "anarchy-core" version = "0.1.0" dependencies = [ "hex", "hostname", "serde", "serde_json", "sha3", "thiserror", "tracing", ] [[package]] name = "anarchy-defense" version = "0.1.0" dependencies = [ "rand", "serde", "serde_json", "tracing", ] [[package]] name = "anarchy-mesh" version = "0.1.0" dependencies = [ "rand", "serde", "tracing", ] [[package]] name = "anarchy-server" version = "0.1.0" dependencies = [ "anarchy-blockchain", "anarchy-core", "anarchy-defense", "anarchy-mesh", "anarchy-webui", "axum", "futures-core", "rand", "serde", "serde_json", "tokio", "tokio-stream", "toml", "tower-http", "tracing", "tracing-subscriber", "zhtp_blocksync", "zhtp_defense", "zhtp_gossip", "zhtp_handshake", "zhtp_identity", "zhtp_mesh", "zhtp_peer", ] [[package]] name = "anarchy-webui" version = "0.1.0" dependencies = [ "serde", ] [[package]] name = "async-trait" version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "axum" version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ "async-trait", "axum-core", "bytes", "futures-util", "http", "http-body", "http-body-util", "hyper", "hyper-util", "itoa", "matchit", "memchr", "mime", "percent-encoding", "pin-project-lite", "rustversion", "serde", "serde_json", "serde_path_to_error", "serde_urlencoded", "sync_wrapper", "tokio", "tower", "tower-layer", "tower-service", "tracing", ] [[package]] name = "axum-core" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" dependencies = [ "async-trait", "bytes", "futures-util", "http", "http-body", "http-body-util", "mime", "pin-project-lite", "rustversion", "sync_wrapper", "tower-layer", "tower-service", "tracing", ] [[package]] name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "bytes" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cpufeatures" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crypto-common" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", ] [[package]] name = "deranged" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", ] [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", ] [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "form_urlencoded" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] [[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-task", "pin-project-lite", "pin-utils", ] [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "getrandom" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hostname" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" dependencies = [ "libc", "match_cfg", "winapi", ] [[package]] name = "http" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", "itoa", ] [[package]] name = "http-body" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", ] [[package]] name = "http-body-util" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", "http", "http-body", "pin-project-lite", ] [[package]] name = "http-range-header" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" [[package]] name = "httparse" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", "http", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", "pin-utils", "smallvec", "tokio", ] [[package]] name = "hyper-util" version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" dependencies = [ "bytes", "futures-core", "http", "http-body", "hyper", "pin-project-lite", "tokio", "tower-service", ] [[package]] name = "indexmap" version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "keccak" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "log" version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "match_cfg" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" [[package]] name = "matchers" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ "regex-automata", ] [[package]] name = "matchit" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", ] [[package]] name = "mio" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", "wasi", "windows-sys 0.61.2", ] [[package]] name = "nu-ansi-term" version = "0.50.3" source = β¦ truncated β¦
Preview truncated for length. Use the raw link to see the full file.
[workspace]
members = [
"core",
"mesh",
"blockchain",
"defense",
"server",
"webui",
"zhtp_peer",
"zhtp_handshake",
"zhtp_gossip",
"zhtp_blocksync",
"zhtp_mesh",
"zhtp_identity",
"zhtp_defense",
]
[workspace.package]
edition = "2021"
version = "0.1.0"
authors = ["AnarchySpace <ops@anarchy.space>"]
[workspace.dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
rand = "0.8"
sha3 = "0.10"
hex = "0.4"
thiserror = "1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "time"] }
axum = { version = "0.7", features = ["json"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros", "signal"] }
tower-http = { version = "0.5", features = ["fs", "cors"] }
toml = "0.8"
tokio-stream = "0.1"
futures-core = "0.3"
[profile.release]
lto = "thin"
codegen-units = 1
panic = "abort"# Anarchy ZHTP Node - Ops Update Date: 2025-11-25T11:12:40Z Changes - Identity persistence: keep stable node_id in `/opt/anarchy-zhtp-node/data/identity.json`. - Atomic state writes: `peers.json` and `chain.json` now written via temp+rename. - New endpoints: - `GET /zhtp/peers/export` β returns list of peer addresses - `POST /zhtp/peers/add` β add a peer address (validates non-empty) - Apache proxy: - `https://www.anarchyspace.com/zhtp` β proxied to ZHTP UI (`127.0.0.1:4820/ui/`) - Widget link updated to `/zhtp` path - Cleanup: - Archived `/opt/zhtp-node` and `/opt/bridge` to `/opt/_archive/` - Moved bad test `/var/www/html/zhtp_test.php` into `/var/www/html/_archive/` - Lite client: downloadable script for user devices - `https://www.anarchyspace.com/zhtp/downloads/zhtp-lite-client.js` Notes - Public 4820 remains accessible; keep if nodes must connect directly. Consider restricting POSTs later if abuse is observed (optional shared secret auth can be added on request). - Nginx still running but not used for 80/443. Can disable to simplify.
[bootstrap] peers = ["node1.example.com:4820","node2.example.com:4820"] [decoys] ips = ["8.8.8.8","1.1.1.1","9.9.9.9"] [dns] domains = ["example.com","backup.net","safety-route.org"] [node] nickname = "my-node" continuity = true
[bootstrap] peers = ["node1.example.com:4820","node2.example.com:4820"] [decoys] ips = ["8.8.8.8","1.1.1.1","9.9.9.9"] [dns] domains = ["example.com","backup.net","safety-route.org"] [node] nickname = "my-node" continuity = true
//! A tiny, in-memory, append-only blockchain for user-as-node mode.
//! Not secure; for demonstration and API plumbing only.
use serde::{Deserialize, Serialize};
use anarchy_core::external_utils::sha3_256_hex;
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Block {
pub index: u64,
pub data: String,
pub prev_hash: String,
pub hash: String,
}
#[derive(Clone, Default)]
pub struct Chain(Arc<Mutex<Vec<Block>>>);
impl Chain {
pub fn new() -> Self {
let mut c = Vec::new();
let genesis = Block {
index: 0,
data: "genesis".into(),
prev_hash: "".into(),
hash: hash_block(0, "genesis", ""),
};
c.push(genesis);
Self(Arc::new(Mutex::new(c)))
}
pub fn push(&self, data: String) -> Block {
let mut g = self.0.lock().unwrap();
let index = g.len() as u64;
let prev_hash = g.last().map(|b| b.hash.clone()).unwrap_or_default();
let hash = hash_block(index, &data, &prev_hash);
let b = Block { index, data, prev_hash, hash };
g.push(b.clone());
b
}
pub fn list(&self) -> Vec<Block> {
self.0.lock().unwrap().clone()
}
}
fn hash_block(index: u64, data: &str, prev_hash: &str) -> String {
let mut bytes = Vec::with_capacity(8 + data.len() + prev_hash.len());
bytes.extend_from_slice(&index.to_le_bytes());
bytes.extend_from_slice(data.as_bytes());
bytes.extend_from_slice(prev_hash.as_bytes());
sha3_256_hex(&bytes)
}<?php
$user_id = $_POST['user_id'] ?? '';
$user_id = preg_replace('/[^a-zA-Z0-9_-]/', '', $user_id);
if (!$user_id) {
http_response_code(400);
exit('Missing or invalid user_id');
}
$key = shell_exec('php /var/www/zhtp/genkey.php ' . escapeshellarg($user_id));
if (!$key) {
http_response_code(500);
exit('Key generation failed');
}
file_put_contents(__DIR__ . "/users/{$user_id}.key", trim($key));
echo "β
{$user_id} registered to sovereign mesh.";# Anarchy ZHTP Node - Ops Update Date: 2025-11-25T11:12:40Z Changes - Identity persistence: keep stable node_id in `/opt/anarchy-zhtp-node/data/identity.json`. - Atomic state writes: `peers.json` and `chain.json` now written via temp+rename. - New endpoints: - `GET /zhtp/peers/export` β returns list of peer addresses - `POST /zhtp/peers/add` β add a peer address (validates non-empty) - Apache proxy: - `https://www.anarchyspace.com/zhtp` β proxied to ZHTP UI (`127.0.0.1:4820/ui/`) - Widget link updated to `/zhtp` path - Cleanup: - Archived `/opt/zhtp-node` and `/opt/bridge` to `/opt/_archive/` - Moved bad test `/var/www/html/zhtp_test.php` into `/var/www/html/_archive/` - Lite client: downloadable script for user devices - `https://www.anarchyspace.com/zhtp/downloads/zhtp-lite-client.js` Notes - Public 4820 remains accessible; keep if nodes must connect directly. Consider restricting POSTs later if abuse is observed (optional shared secret auth can be added on request). - Nginx still running but not used for 80/443. Can disable to simplify.
{
"zhtpPort": 4200,
"broadcastFrequency": 300,
"peers": [],
"allowAutoRegister": true
}# Sovereign Network Node (AnarchySpace VPS Edition) This repository contains the ZHTP node running on AnarchySpace VPS infrastructure. It is a lean Axum-based service tuned for production: stable identity, atomic state, peer exchange endpoints, and Apache proxy integration. It differs from the GitHub PC/Web4 orchestrator in scope and security posture. Highlights - Stable node identity persisted at `/opt/anarchy-zhtp-node/data/identity.json` - Atomic writes for `peers.json` and `chain.json` - Peer interconnect APIs: `GET /zhtp/peers/export`, `POST /zhtp/peers/add` - UI proxied at `https://www.anarchyspace.com/zhtp` (Apache ProxyPass) - Lite client for user devices: `/zhtp/downloads/zhtp-lite-client.js` Differences vs GitHub Web4/PC version - This edition prioritizes VPS security and operational simplicity. - It omits heavy Web4 orchestration; focuses on continuity/federation endpoints. - Bridges/shims run loopback-only; public port 4820 can be restricted as needed. Deploy notes - Systemd: `anarchy-zhtp-node.service` - Sources: `/opt/anarchy-zhtp-node` - UI: `/opt/anarchy-zhtp-node/webui` (proxied under /zhtp)
#!/bin/bash
set -euo pipefail
USER=${1:-}
if [[ -z "$USER" ]]; then
echo "Usage: $0 <username>" >&2
exit 1
fi
SAN_USER=$(echo "$USER" | tr -cd 'A-Za-z0-9_-')
if [[ -z "$SAN_USER" ]]; then
echo "Invalid username." >&2
exit 1
fi
KEY=$(php /var/www/zhtp/genkey.php "$SAN_USER")
echo "$KEY" > "users/${SAN_USER}.key"
echo "β
Registered ${SAN_USER} to sovereign mesh."const http = require('http');
const fs = require('fs');
const config = JSON.parse(fs.readFileSync('config.json', 'utf8'));
const PORT = config.zhtpPort || 4200;
const PEERS = config.peers || [];
function loadKeys() {
const dir = './users';
if (!fs.existsSync(dir)) return [];
return fs
.readdirSync(dir)
.filter(name => name.endsWith('.key'))
.map(name => fs.readFileSync(`${dir}/${name}`, 'utf8').trim());
}
function handleRequest(req, res) {
let body = '';
req.on('data', chunk => (body += chunk));
req.on('end', () => {
try {
const json = JSON.parse(body || '{}');
if (json.method === 'ping') {
return res.end(JSON.stringify({ jsonrpc: '2.0', id: json.id, result: 'pong' }));
}
if (json.method === 'getCitizens') {
return res.end(JSON.stringify({ jsonrpc: '2.0', id: json.id, result: loadKeys() }));
}
return res.end(JSON.stringify({ jsonrpc: '2.0', id: json.id, error: 'Unknown method' }));
} catch (err) {
return res.end(JSON.stringify({ error: 'Invalid JSON-RPC input' }));
}
});
}
http.createServer(handleRequest).listen(PORT, () => {
console.log(`β
Sovereign ZHTP node listening on port ${PORT}`);
if (PEERS.length > 0) {
console.log(`π Peers: ${PEERS.join(', ')}`);
}
});No snippets uploaded yet.
No comments yet.