1use dirs;
2use dotenv::dotenv;
3use once_cell::sync::Lazy;
4use serde::{Deserialize, Serialize};
5use std::env;
6use std::fs;
7use std::path::PathBuf;
8
9#[derive(Serialize, Deserialize, Default, Debug)]
10pub struct GlobalConfig {
11 pub servers: Vec<ServerConfig>,
12 pub current_server: Option<String>,
13}
14
15#[derive(Serialize, Deserialize, Default, Clone, Debug)]
16pub struct ServerConfig {
17 pub name: Option<String>,
19 pub api_key: Option<String>,
20 pub server: Option<String>,
21 pub auth_server: Option<String>,
22}
23
24impl GlobalConfig {
25 pub fn read() -> Result<Self, Box<dyn std::error::Error>> {
29 let config_path = get_config_file_path()?;
30 let path_exists = config_path.exists();
31
32 let mut config = if path_exists {
34 let yaml = fs::read_to_string(&config_path)?;
35 serde_yaml::from_str::<GlobalConfig>(&yaml)?
36 } else {
37 GlobalConfig::default()
38 };
39
40 let env_api_key = env::var("NEBU_API_KEY")
42 .or_else(|_| env::var("NEBULOUS_API_KEY"))
43 .or_else(|_| env::var("AGENTSEA_API_KEY"))
44 .ok();
45 let env_server = env::var("NEBU_SERVER")
46 .or_else(|_| env::var("NEBULOUS_SERVER"))
47 .or_else(|_| env::var("AGENTSEA_SERVER"))
48 .ok();
49 let env_auth_server = env::var("NEBU_AUTH_SERVER")
50 .or_else(|_| env::var("NEBULOUS_AUTH_SERVER"))
51 .or_else(|_| env::var("AGENTSEA_AUTH_SERVER"))
52 .ok();
53
54 if let (Some(env_api_key), Some(env_server), Some(env_auth_server)) =
56 (env_api_key, env_server, env_auth_server)
57 {
58 let found_server = config.servers.iter_mut().find(|srv| {
60 srv.api_key.as_deref() == Some(&env_api_key)
61 && srv.server.as_deref() == Some(&env_server)
62 && srv.auth_server.as_deref() == Some(&env_auth_server)
63 });
64
65 let server_name = "env-based-server".to_string();
67 let chosen_name = if let Some(srv) = found_server {
68 if srv.name.is_none() {
70 srv.name = Some(server_name.clone());
71 }
72 srv.name.clone().unwrap()
73 } else {
74 let new_server = ServerConfig {
76 name: Some(server_name.clone()),
77 api_key: Some(env_api_key),
78 server: Some(env_server),
79 auth_server: Some(env_auth_server),
80 };
81 config.servers.push(new_server);
82 server_name
83 };
84
85 config.current_server = Some(chosen_name);
87 }
88
89 if !path_exists {
91 config.write()?;
92 }
93
94 Ok(config)
95 }
96
97 pub fn write(&self) -> Result<(), Box<dyn std::error::Error>> {
99 let config_path = get_config_file_path()?;
100
101 if let Some(parent) = config_path.parent() {
103 fs::create_dir_all(parent)?;
104 }
105
106 let yaml = serde_yaml::to_string(self)?;
107 fs::write(config_path, yaml)?;
108
109 Ok(())
110 }
111
112 pub fn get_current_server_config(&self) -> Option<&ServerConfig> {
116 self.current_server.as_deref().and_then(|name| {
117 self.servers
118 .iter()
119 .find(|srv| srv.name.as_deref() == Some(name))
120 })
121 }
122
123 pub fn get_auth_server(&self) -> Option<&str> {
124 self.get_current_server_config()
125 .and_then(|cfg| cfg.auth_server.as_deref())
126 .or_else(|| Some(CONFIG.auth_server.as_str()))
127 }
128}
129
130fn get_config_file_path() -> Result<PathBuf, Box<dyn std::error::Error>> {
131 let home_dir = dirs::home_dir().ok_or("Could not determine home directory")?;
132 let config_dir = home_dir.join(".agentsea");
133 let config_path = config_dir.join("nebu.yaml");
134 Ok(config_path)
135}
136
137#[derive(Debug, Clone)]
138pub struct Config {
139 pub message_queue_type: String,
140 pub kafka_bootstrap_servers: String,
141 pub kafka_timeout_ms: String,
142 pub redis_host: String,
143 pub redis_port: String,
144 pub redis_password: Option<String>,
145 pub redis_url: Option<String>,
146 pub redis_publish_url: Option<String>,
147 pub database_url: String,
148 pub tailscale_api_key: Option<String>,
149 pub tailscale_tailnet: Option<String>,
150 pub bucket_name: String,
151 pub bucket_region: String,
152 pub root_owner: String,
153 pub auth_server: String,
154 pub publish_url: Option<String>,
155}
156
157impl Config {
158 pub fn new() -> Self {
159 dotenv().ok();
160
161 Self {
162 message_queue_type: env::var("MESSAGE_QUEUE_TYPE")
163 .unwrap_or_else(|_| "redis".to_string()),
164 kafka_bootstrap_servers: env::var("KAFKA_BOOTSTRAP_SERVERS")
165 .unwrap_or_else(|_| "localhost:9092".to_string()),
166 kafka_timeout_ms: env::var("KAFKA_TIMEOUT_MS").unwrap_or_else(|_| "5000".to_string()),
167 redis_host: env::var("REDIS_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()),
168 redis_port: env::var("REDIS_PORT").unwrap_or_else(|_| "6379".to_string()),
169 redis_password: env::var("REDIS_PASSWORD").ok(),
170 redis_url: env::var("REDIS_URL").ok(),
171 redis_publish_url: env::var("REDIS_PUBLISH_URL").ok(),
172 database_url: env::var("DATABASE_URL")
173 .unwrap_or_else(|_| "sqlite://.data/data.db".to_string()),
174 tailscale_api_key: env::var("TAILSCALE_API_KEY").ok(),
175 tailscale_tailnet: env::var("TAILSCALE_TAILNET").ok(),
176 bucket_name: env::var("NEBU_BUCKET_NAME")
177 .or_else(|_| env::var("NEBULOUS_BUCKET_NAME"))
178 .unwrap_or_else(|_| panic!("NEBU_BUCKET_NAME or NEBULOUS_BUCKET_NAME environment variable must be set")),
179 bucket_region: env::var("NEBU_BUCKET_REGION")
180 .or_else(|_| env::var("NEBULOUS_BUCKET_REGION"))
181 .unwrap_or_else(|_| panic!("NEBU_BUCKET_REGION or NEBULOUS_BUCKET_REGION environment variable must be set")),
182 root_owner: env::var("NEBU_ROOT_OWNER")
183 .or_else(|_| env::var("NEBULOUS_ROOT_OWNER"))
184 .unwrap_or_else(|_| panic!("NEBU_ROOT_OWNER or NEBULOUS_ROOT_OWNER environment variable must be set")),
185 auth_server: env::var("NEBU_AUTH_SERVER")
186 .or_else(|_| env::var("NEBULOUS_AUTH_SERVER"))
187 .or_else(|_| env::var("AGENTSEA_AUTH_SERVER"))
188 .or_else(|_| env::var("AGENTSEA_AUTH_URL"))
189 .unwrap_or_else(|_| "https://auth.hub.agentlabs.xyz".to_string()),
190 publish_url: env::var("NEBU_PUBLISH_URL")
191 .or_else(|_| env::var("NEBULOUS_PUBLISH_URL"))
192 .ok(),
193 }
194 }
195}
196pub static CONFIG: Lazy<Config> = Lazy::new(Config::new);