use std::path::PathBuf;
use crate::storage::StoreLocation;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StorageBackend {
Duckdb,
TaelBackend,
}
impl StorageBackend {
pub fn parse(s: &str) -> Self {
match s.trim().to_lowercase().as_str() {
"duckdb" | "duck" => StorageBackend::Duckdb,
_ => StorageBackend::TaelBackend,
}
}
}
pub struct ServerConfig {
pub otlp_grpc_addr: String,
pub rest_api_addr: String,
pub rest_api_socket: Option<String>,
pub data_dir: String,
pub wal_dir: String,
pub storage: StorageBackend,
pub query_shards: Vec<String>,
pub wal_standbys: Vec<String>,
pub wal_required_acks: Option<usize>,
pub cluster: Option<ClusterSettings>,
pub object_store: ObjectStoreConfig,
pub comments: CommentsConfig,
}
#[derive(Debug, Clone)]
pub struct ObjectStoreConfig {
pub cold: StoreLocation,
pub blobs: StoreLocation,
pub cold_bucket: Option<String>,
pub blob_bucket: Option<String>,
pub blob_gc_coordinator: bool,
}
impl ObjectStoreConfig {
fn from_env() -> Self {
Self {
cold: std::env::var("TAEL_COLD_STORE")
.map(|s| StoreLocation::parse(&s))
.unwrap_or_default(),
blobs: std::env::var("TAEL_BLOB_STORE")
.map(|s| StoreLocation::parse(&s))
.unwrap_or_default(),
cold_bucket: non_empty_env("TAEL_COLD_BUCKET"),
blob_bucket: non_empty_env("TAEL_BLOB_BUCKET"),
blob_gc_coordinator: std::env::var("TAEL_BLOB_GC_ROLE")
.map(|s| s.trim().eq_ignore_ascii_case("coordinator"))
.unwrap_or(false),
}
}
pub fn blobs_shared(&self) -> bool {
self.blobs == StoreLocation::Gcs
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CommentsBackend {
#[default]
Jsonl,
Postgres,
}
impl CommentsBackend {
pub fn parse(s: &str) -> Self {
match s.trim().to_lowercase().as_str() {
"postgres" | "postgresql" | "pg" => CommentsBackend::Postgres,
_ => CommentsBackend::Jsonl,
}
}
}
#[derive(Debug, Clone)]
pub struct CommentsConfig {
pub backend: CommentsBackend,
pub database_url: Option<String>,
}
impl CommentsConfig {
fn from_env() -> Self {
Self {
backend: std::env::var("TAEL_COMMENTS_STORE")
.map(|s| CommentsBackend::parse(&s))
.unwrap_or_default(),
database_url: non_empty_env("TAEL_COMMENTS_DATABASE_URL"),
}
}
}
#[derive(Debug, Clone)]
pub struct ClusterSettings {
pub node_id: String,
pub listen_addr: String,
pub advertise_addr: String,
pub seeds: Vec<String>,
pub cluster_id: String,
}
impl ServerConfig {
pub fn from_env() -> Self {
let mut config = Self {
otlp_grpc_addr: std::env::var("TAEL_OTLP_GRPC_ADDR")
.unwrap_or_else(|_| "127.0.0.1:4317".into()),
rest_api_addr: std::env::var("TAEL_REST_API_ADDR")
.unwrap_or_else(|_| "127.0.0.1:7701".into()),
rest_api_socket: std::env::var("TAEL_REST_API_SOCKET")
.ok()
.filter(|s| !s.trim().is_empty()),
data_dir: std::env::var("TAEL_DATA_DIR").unwrap_or_else(|_| default_data_dir()),
wal_dir: std::env::var("TAEL_WAL_DIR")
.or_else(|_| std::env::var("WALRUS_DATA_DIR"))
.unwrap_or_else(|_| default_wal_dir()),
storage: std::env::var("TAEL_STORAGE")
.map(|s| StorageBackend::parse(&s))
.unwrap_or(StorageBackend::TaelBackend),
query_shards: parse_csv_env("TAEL_QUERY_SHARDS"),
wal_standbys: parse_csv_env("TAEL_WAL_STANDBYS"),
wal_required_acks: std::env::var("TAEL_WAL_REQUIRED_ACKS")
.ok()
.and_then(|s| s.trim().parse().ok()),
cluster: cluster_from_env(),
object_store: ObjectStoreConfig::from_env(),
comments: CommentsConfig::from_env(),
};
if let Some(s) = storage_flag() {
config.storage = s;
}
config
}
}
fn default_data_dir() -> String {
default_tael_home().join("data").display().to_string()
}
fn default_wal_dir() -> String {
default_tael_home().join("wal_files").display().to_string()
}
fn default_tael_home() -> PathBuf {
home_dir()
.map(|home| home.join(".tael"))
.unwrap_or_else(|| PathBuf::from(".tael"))
}
fn home_dir() -> Option<PathBuf> {
std::env::var_os("HOME")
.map(PathBuf::from)
.or_else(|| std::env::var_os("USERPROFILE").map(PathBuf::from))
.or_else(
|| match (std::env::var_os("HOMEDRIVE"), std::env::var_os("HOMEPATH")) {
(Some(drive), Some(path)) => {
let mut home = PathBuf::from(drive);
home.push(path);
Some(home)
}
_ => None,
},
)
}
fn cluster_from_env() -> Option<ClusterSettings> {
let listen_addr = std::env::var("TAEL_CLUSTER_LISTEN").ok()?;
let advertise_addr =
std::env::var("TAEL_CLUSTER_ADVERTISE").unwrap_or_else(|_| listen_addr.clone());
let node_id = std::env::var("TAEL_NODE_ID").unwrap_or_else(|_| advertise_addr.clone());
Some(ClusterSettings {
node_id,
listen_addr,
advertise_addr,
seeds: parse_csv_env("TAEL_CLUSTER_SEEDS"),
cluster_id: std::env::var("TAEL_CLUSTER_ID").unwrap_or_else(|_| "tael".to_string()),
})
}
fn non_empty_env(var: &str) -> Option<String> {
std::env::var(var).ok().filter(|s| !s.trim().is_empty())
}
fn parse_csv_env(var: &str) -> Vec<String> {
std::env::var(var)
.ok()
.map(|s| {
s.split(',')
.map(|p| p.trim().to_string())
.filter(|p| !p.is_empty())
.collect()
})
.unwrap_or_default()
}
fn storage_flag() -> Option<StorageBackend> {
let mut args = std::env::args().skip(1);
while let Some(arg) = args.next() {
if arg == "--storage" {
return args.next().map(|v| StorageBackend::parse(&v));
}
if let Some(v) = arg.strip_prefix("--storage=") {
return Some(StorageBackend::parse(v));
}
}
None
}