1use clap::Parser;
6
7#[derive(Parser, Debug)]
8#[command(name = "agentmux-srv", about = "AgentMux Rust backend server")]
9pub struct CliArgs {
10 #[arg(long = "wavedata")]
12 pub wavedata: Option<String>,
13
14 #[arg(long = "instance", default_value = "default")]
16 pub instance: String,
17
18}
19
20#[derive(Debug, Clone)]
21pub struct Config {
22 pub auth_key: String,
23 pub data_home: String,
24 pub config_home: String,
25 pub app_path: String,
26 #[allow(dead_code)]
27 pub is_dev: bool,
28 pub version: &'static str,
29 pub build_time: &'static str,
30 pub instance_id: String,
31}
32
33impl Config {
34 pub fn from_env_and_args(args: &CliArgs) -> Result<Self, String> {
37 let auth_key = std::env::var("AGENTMUX_AUTH_KEY")
38 .map_err(|_| "AGENTMUX_AUTH_KEY environment variable is required".to_string())?;
39
40 if auth_key.is_empty() {
41 return Err("AGENTMUX_AUTH_KEY must not be empty".to_string());
42 }
43
44 std::env::remove_var("AGENTMUX_AUTH_KEY");
46
47 let data_home = args
54 .wavedata
55 .clone()
56 .or_else(|| std::env::var("AGENTMUX_DATA_DIR").ok())
57 .unwrap_or_default();
58
59 let config_home = std::env::var("AGENTMUX_CONFIG_DIR").unwrap_or_default();
60 let app_path = std::env::var("AGENTMUX_APP_PATH").unwrap_or_default();
61 let is_dev = matches!(
65 agentmux_common::RuntimeMode::from_env(),
66 Some(agentmux_common::RuntimeMode::Dev { .. })
67 );
68
69 Ok(Config {
70 auth_key,
71 data_home,
72 config_home,
73 app_path,
74 is_dev,
75 version: env!("CARGO_PKG_VERSION"),
76 build_time: option_env!("BUILD_TIME").unwrap_or("dev"),
77 instance_id: args.instance.clone(),
78 })
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 use std::sync::Mutex;
86
87 static ENV_LOCK: Mutex<()> = Mutex::new(());
89
90 fn lock() -> std::sync::MutexGuard<'static, ()> {
95 ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner())
96 }
97
98 fn clear_env() {
101 for k in [
102 "AGENTMUX_AUTH_KEY",
103 "AGENTMUX_DATA_DIR",
104 "AGENTMUX_DATA_HOME",
105 "AGENTMUX_CONFIG_DIR",
106 "AGENTMUX_CONFIG_HOME",
107 "AGENTMUX_APP_PATH",
108 "AGENTMUX_RUNTIME_MODE",
109 "AGENTMUX_DEV",
110 ] {
111 std::env::remove_var(k);
112 }
113 }
114
115 #[test]
116 fn missing_auth_key_errors() {
117 let _lock = lock();
118 clear_env();
119 let args = CliArgs { wavedata: None, instance: "default".to_string() };
120 let result = Config::from_env_and_args(&args);
121 assert!(result.is_err());
122 assert!(result.unwrap_err().contains("AGENTMUX_AUTH_KEY"));
123 }
124
125 #[test]
126 fn empty_auth_key_errors() {
127 let _lock = lock();
128 clear_env();
129 std::env::set_var("AGENTMUX_AUTH_KEY", "");
130 let args = CliArgs { wavedata: None, instance: "default".to_string() };
131 let result = Config::from_env_and_args(&args);
132 assert!(result.is_err());
133 clear_env();
134 }
135
136 #[test]
137 fn cli_wavedata_overrides_env() {
138 let _lock = lock();
139 clear_env();
140 std::env::set_var("AGENTMUX_AUTH_KEY", "test-key-12345");
141 std::env::set_var("AGENTMUX_DATA_DIR", "/from/env");
142 let args = CliArgs {
143 wavedata: Some("/from/cli".to_string()),
144 instance: "default".to_string(),
145 };
146 let config = Config::from_env_and_args(&args).unwrap();
147 assert_eq!(config.data_home, "/from/cli");
148 assert!(std::env::var("AGENTMUX_AUTH_KEY").is_err());
149 clear_env();
150 }
151
152 #[test]
153 fn env_var_parsing() {
154 let _lock = lock();
155 clear_env();
156 std::env::set_var("AGENTMUX_AUTH_KEY", "test-key-67890");
157 std::env::set_var("AGENTMUX_DATA_DIR", "/data");
158 std::env::set_var("AGENTMUX_CONFIG_DIR", "/config");
159 std::env::set_var("AGENTMUX_APP_PATH", "/app");
160 std::env::set_var("AGENTMUX_RUNTIME_MODE", "dev:main");
161 let args = CliArgs { wavedata: None, instance: "default".to_string() };
162 let config = Config::from_env_and_args(&args).unwrap();
163 assert_eq!(config.data_home, "/data");
164 assert_eq!(config.config_home, "/config");
165 assert_eq!(config.app_path, "/app");
166 assert!(config.is_dev);
167 clear_env();
168 }
169}