1use std::path::PathBuf;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum StorageBackend {
8 Duckdb,
9 TaelBackend,
10}
11
12impl StorageBackend {
13 pub fn parse(s: &str) -> Self {
16 match s.trim().to_lowercase().as_str() {
17 "duckdb" | "duck" => StorageBackend::Duckdb,
18 _ => StorageBackend::TaelBackend,
19 }
20 }
21}
22
23pub struct ServerConfig {
24 pub otlp_grpc_addr: String,
25 pub rest_api_addr: String,
26 pub rest_api_socket: Option<String>,
27 pub data_dir: String,
28 pub wal_dir: String,
29 pub storage: StorageBackend,
30 pub query_shards: Vec<String>,
35 pub wal_standbys: Vec<String>,
39 pub wal_required_acks: Option<usize>,
43 pub cluster: Option<ClusterSettings>,
47}
48
49#[derive(Debug, Clone)]
51pub struct ClusterSettings {
52 pub node_id: String,
54 pub listen_addr: String,
56 pub advertise_addr: String,
58 pub seeds: Vec<String>,
60 pub cluster_id: String,
62}
63
64impl ServerConfig {
65 pub fn from_env() -> Self {
66 let mut config = Self {
67 otlp_grpc_addr: std::env::var("TAEL_OTLP_GRPC_ADDR")
68 .unwrap_or_else(|_| "127.0.0.1:4317".into()),
69 rest_api_addr: std::env::var("TAEL_REST_API_ADDR")
70 .unwrap_or_else(|_| "127.0.0.1:7701".into()),
71 rest_api_socket: std::env::var("TAEL_REST_API_SOCKET")
72 .ok()
73 .filter(|s| !s.trim().is_empty()),
74 data_dir: std::env::var("TAEL_DATA_DIR").unwrap_or_else(|_| default_data_dir()),
75 wal_dir: std::env::var("TAEL_WAL_DIR")
76 .or_else(|_| std::env::var("WALRUS_DATA_DIR"))
77 .unwrap_or_else(|_| default_wal_dir()),
78 storage: std::env::var("TAEL_STORAGE")
80 .map(|s| StorageBackend::parse(&s))
81 .unwrap_or(StorageBackend::TaelBackend),
82 query_shards: parse_csv_env("TAEL_QUERY_SHARDS"),
83 wal_standbys: parse_csv_env("TAEL_WAL_STANDBYS"),
84 wal_required_acks: std::env::var("TAEL_WAL_REQUIRED_ACKS")
85 .ok()
86 .and_then(|s| s.trim().parse().ok()),
87 cluster: cluster_from_env(),
88 };
89 if let Some(s) = storage_flag() {
92 config.storage = s;
93 }
94 config
95 }
96}
97
98fn default_data_dir() -> String {
99 default_tael_home().join("data").display().to_string()
100}
101
102fn default_wal_dir() -> String {
103 default_tael_home().join("wal_files").display().to_string()
104}
105
106fn default_tael_home() -> PathBuf {
107 home_dir()
108 .map(|home| home.join(".tael"))
109 .unwrap_or_else(|| PathBuf::from(".tael"))
110}
111
112fn home_dir() -> Option<PathBuf> {
113 std::env::var_os("HOME")
114 .map(PathBuf::from)
115 .or_else(|| std::env::var_os("USERPROFILE").map(PathBuf::from))
116 .or_else(
117 || match (std::env::var_os("HOMEDRIVE"), std::env::var_os("HOMEPATH")) {
118 (Some(drive), Some(path)) => {
119 let mut home = PathBuf::from(drive);
120 home.push(path);
121 Some(home)
122 }
123 _ => None,
124 },
125 )
126}
127
128fn cluster_from_env() -> Option<ClusterSettings> {
131 let listen_addr = std::env::var("TAEL_CLUSTER_LISTEN").ok()?;
132 let advertise_addr =
133 std::env::var("TAEL_CLUSTER_ADVERTISE").unwrap_or_else(|_| listen_addr.clone());
134 let node_id = std::env::var("TAEL_NODE_ID").unwrap_or_else(|_| advertise_addr.clone());
136 Some(ClusterSettings {
137 node_id,
138 listen_addr,
139 advertise_addr,
140 seeds: parse_csv_env("TAEL_CLUSTER_SEEDS"),
141 cluster_id: std::env::var("TAEL_CLUSTER_ID").unwrap_or_else(|_| "tael".to_string()),
142 })
143}
144
145fn parse_csv_env(var: &str) -> Vec<String> {
147 std::env::var(var)
148 .ok()
149 .map(|s| {
150 s.split(',')
151 .map(|p| p.trim().to_string())
152 .filter(|p| !p.is_empty())
153 .collect()
154 })
155 .unwrap_or_default()
156}
157
158fn storage_flag() -> Option<StorageBackend> {
160 let mut args = std::env::args().skip(1);
161 while let Some(arg) = args.next() {
162 if arg == "--storage" {
163 return args.next().map(|v| StorageBackend::parse(&v));
164 }
165 if let Some(v) = arg.strip_prefix("--storage=") {
166 return Some(StorageBackend::parse(v));
167 }
168 }
169 None
170}