mirror of
https://github.com/house-of-vanity/yggman.git
synced 2025-10-23 20:59:08 +00:00
first commit
This commit is contained in:
222
src/node_manager.rs
Normal file
222
src/node_manager.rs
Normal file
@@ -0,0 +1,222 @@
|
||||
use crate::yggdrasil::{Node, YggdrasilConfig};
|
||||
use crate::database::entities::node as node_entity;
|
||||
use ed25519_dalek::{SigningKey, VerifyingKey};
|
||||
use sea_orm::{DatabaseConnection, EntityTrait, ActiveModelTrait};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct NodeManager {
|
||||
db: DatabaseConnection,
|
||||
}
|
||||
|
||||
impl NodeManager {
|
||||
pub fn new(db: DatabaseConnection) -> Self {
|
||||
Self { db }
|
||||
}
|
||||
|
||||
pub async fn add_node(&self, name: String, listen: Vec<String>, addresses: Vec<String>) -> Result<(), crate::error::AppError> {
|
||||
let signing_key = SigningKey::from_bytes(&rand::random());
|
||||
let verifying_key: VerifyingKey = signing_key.verifying_key();
|
||||
|
||||
let private_seed = signing_key.to_bytes();
|
||||
let public_key_bytes = verifying_key.to_bytes();
|
||||
|
||||
// Yggdrasil expects a 64-byte private key (32-byte seed + 32-byte public key)
|
||||
let mut full_private_key = Vec::with_capacity(64);
|
||||
full_private_key.extend_from_slice(&private_seed);
|
||||
full_private_key.extend_from_slice(&public_key_bytes);
|
||||
|
||||
let private_key = hex::encode(full_private_key);
|
||||
let public_key = hex::encode(public_key_bytes);
|
||||
|
||||
let node = Node {
|
||||
id: format!("node-{}", uuid_simple()),
|
||||
name: name.clone(),
|
||||
public_key: public_key.clone(),
|
||||
private_key,
|
||||
listen,
|
||||
addresses,
|
||||
};
|
||||
|
||||
// Save to database
|
||||
let active_model = node_entity::ActiveModel::from(&node);
|
||||
active_model.insert(&self.db).await
|
||||
.map_err(|e| crate::error::AppError::Config(format!("Database error: {}", e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_node(&self, node_id: &str, name: String, listen: Vec<String>, addresses: Vec<String>) -> Result<(), crate::error::AppError> {
|
||||
// Check if node exists
|
||||
let existing_node = node_entity::Entity::find_by_id(node_id)
|
||||
.one(&self.db)
|
||||
.await
|
||||
.map_err(|e| crate::error::AppError::Config(format!("Database error: {}", e)))?;
|
||||
|
||||
if existing_node.is_none() {
|
||||
return Err(crate::error::AppError::Config("Node not found".to_string()));
|
||||
}
|
||||
|
||||
// Update the node
|
||||
let mut active_model: node_entity::ActiveModel = existing_node.unwrap().into();
|
||||
active_model.name = sea_orm::Set(name);
|
||||
active_model.listen = sea_orm::Set(serde_json::to_string(&listen).unwrap_or_default());
|
||||
active_model.addresses = sea_orm::Set(serde_json::to_string(&addresses).unwrap_or_default());
|
||||
|
||||
active_model.update(&self.db).await
|
||||
.map_err(|e| crate::error::AppError::Config(format!("Database error: {}", e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn remove_node(&self, node_id: &str) -> Result<(), crate::error::AppError> {
|
||||
let result = node_entity::Entity::delete_by_id(node_id)
|
||||
.exec(&self.db)
|
||||
.await
|
||||
.map_err(|e| crate::error::AppError::Config(format!("Database error: {}", e)))?;
|
||||
|
||||
if result.rows_affected == 0 {
|
||||
return Err(crate::error::AppError::Config("Node not found".to_string()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_node_by_id(&self, node_id: &str) -> Option<Node> {
|
||||
match node_entity::Entity::find_by_id(node_id).one(&self.db).await {
|
||||
Ok(Some(model)) => Some(Node::from(model)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_node_by_name(&self, name: &str) -> Option<Node> {
|
||||
use sea_orm::{ColumnTrait, QueryFilter};
|
||||
match node_entity::Entity::find()
|
||||
.filter(node_entity::Column::Name.eq(name))
|
||||
.one(&self.db).await {
|
||||
Ok(Some(model)) => Some(Node::from(model)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub async fn get_all_nodes(&self) -> Vec<Node> {
|
||||
match node_entity::Entity::find().all(&self.db).await {
|
||||
Ok(models) => models.into_iter().map(Node::from).collect(),
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to fetch nodes from database: {}", e);
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn generate_configs(&self) -> HashMap<String, YggdrasilConfig> {
|
||||
let nodes = self.get_all_nodes().await;
|
||||
let mut configs = HashMap::new();
|
||||
|
||||
let all_public_keys: Vec<String> = nodes
|
||||
.iter()
|
||||
.map(|n| n.public_key.clone())
|
||||
.collect();
|
||||
|
||||
for node in &nodes {
|
||||
let mut config = YggdrasilConfig::default();
|
||||
|
||||
config.private_key = node.private_key.clone();
|
||||
config.listen = node.listen.clone();
|
||||
|
||||
let mut other_keys = all_public_keys.clone();
|
||||
other_keys.retain(|k| k != &node.public_key);
|
||||
config.allowed_public_keys = other_keys;
|
||||
|
||||
// Build peers from other nodes' listen endpoints
|
||||
let mut peers: Vec<String> = Vec::new();
|
||||
for other_node in &nodes {
|
||||
if other_node.id != node.id {
|
||||
// For each listen endpoint, create peers for all node addresses
|
||||
for listen_addr in &other_node.listen {
|
||||
// If no addresses provided, use localhost
|
||||
let addresses_to_use = if other_node.addresses.is_empty() {
|
||||
vec!["127.0.0.1".to_string()]
|
||||
} else {
|
||||
other_node.addresses.clone()
|
||||
};
|
||||
|
||||
for address in &addresses_to_use {
|
||||
if let Some(peer_addr) = convert_listen_to_peer_with_address(listen_addr, &other_node.public_key, address) {
|
||||
peers.push(peer_addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
config.peers = peers;
|
||||
|
||||
let mut node_info = HashMap::new();
|
||||
node_info.insert("name".to_string(), serde_json::Value::String(node.name.clone()));
|
||||
config.node_info = node_info;
|
||||
|
||||
configs.insert(node.id.clone(), config);
|
||||
}
|
||||
|
||||
configs
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn uuid_simple() -> String {
|
||||
use rand::Rng;
|
||||
let mut rng = rand::thread_rng();
|
||||
let bytes: Vec<u8> = (0..16).map(|_| rng.r#gen()).collect();
|
||||
hex::encode(bytes)
|
||||
}
|
||||
|
||||
fn convert_listen_to_peer_with_address(listen_addr: &str, public_key: &str, address: &str) -> Option<String> {
|
||||
// Parse the listen address and convert to peer format
|
||||
// Listen format: tcp://[::]:1234 or tcp://0.0.0.0:1234
|
||||
// Peer format: tcp://REAL_IP:1234?key=PUBLIC_KEY
|
||||
|
||||
if listen_addr.contains("unix://") {
|
||||
// Unix sockets are local only, skip
|
||||
return None;
|
||||
}
|
||||
|
||||
// Extract protocol and port
|
||||
let parts: Vec<&str> = listen_addr.split("://").collect();
|
||||
if parts.len() != 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let protocol = parts[0];
|
||||
let addr_part = parts[1];
|
||||
|
||||
// Extract port from address
|
||||
let port = if addr_part.contains("]:") {
|
||||
// IPv6 format [::]:port
|
||||
addr_part.split("]:").nth(1)
|
||||
} else {
|
||||
// IPv4 format 0.0.0.0:port
|
||||
addr_part.split(':').nth(1)
|
||||
};
|
||||
|
||||
let port = port?;
|
||||
|
||||
// Check for query parameters in the original listen address
|
||||
let (port_clean, params) = if port.contains('?') {
|
||||
let parts: Vec<&str> = port.split('?').collect();
|
||||
(parts[0], Some(parts[1]))
|
||||
} else {
|
||||
(port, None)
|
||||
};
|
||||
|
||||
// Build the peer address with the specified IP
|
||||
let mut peer_addr = format!("{}://{}:{}?key={}", protocol, address, port_clean, public_key);
|
||||
|
||||
// Add any additional parameters from the listen address
|
||||
if let Some(params) = params {
|
||||
peer_addr.push('&');
|
||||
peer_addr.push_str(params);
|
||||
}
|
||||
|
||||
Some(peer_addr)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user