use dirs;
use dotenv::dotenv;
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use std::env;
use std::fs;
use std::path::PathBuf;
#[derive(Serialize, Deserialize, Default, Debug)]
pub struct GlobalConfig {
pub servers: Vec<ServerConfig>,
pub current_server: Option<String>,
}
#[derive(Serialize, Deserialize, Default, Clone, Debug)]
pub struct ServerConfig {
pub name: Option<String>,
pub api_key: Option<String>,
pub server: Option<String>,
pub auth_server: Option<String>,
}
impl GlobalConfig {
pub fn read() -> Result<Self, Box<dyn std::error::Error>> {
let config_path = get_config_file_path()?;
let path_exists = config_path.exists();
let mut config = if path_exists {
let yaml = fs::read_to_string(&config_path)?;
serde_yaml::from_str::<GlobalConfig>(&yaml)?
} else {
GlobalConfig::default()
};
let env_api_key = env::var("NEBU_API_KEY")
.or_else(|_| env::var("NEBULOUS_API_KEY"))
.or_else(|_| env::var("AGENTSEA_API_KEY"))
.ok();
let env_server = env::var("NEBU_SERVER")
.or_else(|_| env::var("NEBULOUS_SERVER"))
.or_else(|_| env::var("AGENTSEA_SERVER"))
.ok();
let env_auth_server = env::var("NEBU_AUTH_SERVER")
.or_else(|_| env::var("NEBULOUS_AUTH_SERVER"))
.or_else(|_| env::var("AGENTSEA_AUTH_SERVER"))
.ok();
if let (Some(env_api_key), Some(env_server), Some(env_auth_server)) =
(env_api_key, env_server, env_auth_server)
{
let found_server = config.servers.iter_mut().find(|srv| {
srv.api_key.as_deref() == Some(&env_api_key)
&& srv.server.as_deref() == Some(&env_server)
&& srv.auth_server.as_deref() == Some(&env_auth_server)
});
let server_name = "env-based-server".to_string();
let chosen_name = if let Some(srv) = found_server {
if srv.name.is_none() {
srv.name = Some(server_name.clone());
}
srv.name.clone().unwrap()
} else {
let new_server = ServerConfig {
name: Some(server_name.clone()),
api_key: Some(env_api_key),
server: Some(env_server),
auth_server: Some(env_auth_server),
};
config.servers.push(new_server);
server_name
};
config.current_server = Some(chosen_name);
}
if !path_exists {
config.write()?;
}
Ok(config)
}
pub fn write(&self) -> Result<(), Box<dyn std::error::Error>> {
let config_path = get_config_file_path()?;
if let Some(parent) = config_path.parent() {
fs::create_dir_all(parent)?;
}
let yaml = serde_yaml::to_string(self)?;
fs::write(config_path, yaml)?;
Ok(())
}
pub fn get_current_server_config(&self) -> Option<&ServerConfig> {
self.current_server.as_deref().and_then(|name| {
self.servers
.iter()
.find(|srv| srv.name.as_deref() == Some(name))
})
}
pub fn get_auth_server(&self) -> Option<&str> {
self.get_current_server_config()
.and_then(|cfg| cfg.auth_server.as_deref())
.or_else(|| Some(CONFIG.auth_server.as_str()))
}
}
fn get_config_file_path() -> Result<PathBuf, Box<dyn std::error::Error>> {
let home_dir = dirs::home_dir().ok_or("Could not determine home directory")?;
let config_dir = home_dir.join(".agentsea");
let config_path = config_dir.join("nebu.yaml");
Ok(config_path)
}
#[derive(Debug, Clone)]
pub struct Config {
pub message_queue_type: String,
pub kafka_bootstrap_servers: String,
pub kafka_timeout_ms: String,
pub redis_host: String,
pub redis_port: String,
pub redis_password: Option<String>,
pub redis_url: Option<String>,
pub redis_publish_url: Option<String>,
pub database_url: String,
pub tailscale_api_key: Option<String>,
pub tailscale_tailnet: Option<String>,
pub bucket_name: String,
pub bucket_region: String,
pub root_owner: String,
pub auth_server: String,
pub publish_url: Option<String>,
}
impl Config {
pub fn new() -> Self {
dotenv().ok();
Self {
message_queue_type: env::var("MESSAGE_QUEUE_TYPE")
.unwrap_or_else(|_| "redis".to_string()),
kafka_bootstrap_servers: env::var("KAFKA_BOOTSTRAP_SERVERS")
.unwrap_or_else(|_| "localhost:9092".to_string()),
kafka_timeout_ms: env::var("KAFKA_TIMEOUT_MS").unwrap_or_else(|_| "5000".to_string()),
redis_host: env::var("REDIS_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()),
redis_port: env::var("REDIS_PORT").unwrap_or_else(|_| "6379".to_string()),
redis_password: env::var("REDIS_PASSWORD").ok(),
redis_url: env::var("REDIS_URL").ok(),
redis_publish_url: env::var("REDIS_PUBLISH_URL").ok(),
database_url: env::var("DATABASE_URL")
.unwrap_or_else(|_| "sqlite://.data/data.db".to_string()),
tailscale_api_key: env::var("TAILSCALE_API_KEY").ok(),
tailscale_tailnet: env::var("TAILSCALE_TAILNET").ok(),
bucket_name: env::var("NEBU_BUCKET_NAME")
.or_else(|_| env::var("NEBULOUS_BUCKET_NAME"))
.unwrap_or_else(|_| panic!("NEBU_BUCKET_NAME or NEBULOUS_BUCKET_NAME environment variable must be set")),
bucket_region: env::var("NEBU_BUCKET_REGION")
.or_else(|_| env::var("NEBULOUS_BUCKET_REGION"))
.unwrap_or_else(|_| panic!("NEBU_BUCKET_REGION or NEBULOUS_BUCKET_REGION environment variable must be set")),
root_owner: env::var("NEBU_ROOT_OWNER")
.or_else(|_| env::var("NEBULOUS_ROOT_OWNER"))
.unwrap_or_else(|_| panic!("NEBU_ROOT_OWNER or NEBULOUS_ROOT_OWNER environment variable must be set")),
auth_server: env::var("NEBU_AUTH_SERVER")
.or_else(|_| env::var("NEBULOUS_AUTH_SERVER"))
.or_else(|_| env::var("AGENTSEA_AUTH_SERVER"))
.or_else(|_| env::var("AGENTSEA_AUTH_URL"))
.unwrap_or_else(|_| "https://auth.hub.agentlabs.xyz".to_string()),
publish_url: env::var("NEBU_PUBLISH_URL")
.or_else(|_| env::var("NEBULOUS_PUBLISH_URL"))
.ok(),
}
}
}
pub static CONFIG: Lazy<Config> = Lazy::new(Config::new);