use clap::{Args, Parser};
use ipnetwork::IpNetwork;
#[derive(Clone, Debug, Parser)]
#[command(version, about, long_about = None)]
pub struct Config {
#[arg(long, env = "OBSCURA_DATABASE_URL", default_value_t = Config::default().database_url)]
pub database_url: String,
#[arg(long, env = "OBSCURA_TTL_DAYS", default_value_t = Config::default().ttl_days)]
pub ttl_days: i64,
#[command(flatten)]
pub server: ServerConfig,
#[command(flatten)]
pub auth: AuthConfig,
#[command(flatten)]
pub rate_limit: RateLimitConfig,
#[command(flatten)]
pub health: HealthConfig,
#[command(flatten)]
pub messaging: MessagingConfig,
#[command(flatten)]
pub notifications: NotificationConfig,
#[command(flatten)]
pub websocket: WsConfig,
#[command(flatten)]
pub storage: StorageConfig,
#[command(flatten)]
pub telemetry: TelemetryConfig,
}
impl Default for Config {
fn default() -> Self {
Self {
database_url: "postgres://user:password@localhost/signal_server".to_string(),
ttl_days: 30,
server: ServerConfig::default(),
auth: AuthConfig::default(),
rate_limit: RateLimitConfig::default(),
health: HealthConfig::default(),
messaging: MessagingConfig::default(),
notifications: NotificationConfig::default(),
websocket: WsConfig::default(),
storage: StorageConfig::default(),
telemetry: TelemetryConfig::default(),
}
}
}
impl Config {
pub fn load() -> Self {
Self::parse()
}
}
#[derive(Clone, Debug, Default, clap::ValueEnum)]
pub enum LogFormat {
#[default]
Text,
Json,
}
#[derive(Clone, Debug, Args)]
pub struct TelemetryConfig {
#[arg(long, env = "OBSCURA_OTLP_ENDPOINT")]
pub otlp_endpoint: Option<String>,
#[arg(long, env = "OBSCURA_LOG_FORMAT", default_value_t = TelemetryConfig::default().log_format)]
pub log_format: LogFormat,
#[arg(long, env = "OBSCURA_TRACE_SAMPLING_RATIO", default_value_t = TelemetryConfig::default().trace_sampling_ratio)]
pub trace_sampling_ratio: f64,
#[arg(long, env = "OBSCURA_TELEMETRY_METRICS_EXPORT_INTERVAL_SECS", default_value_t = TelemetryConfig::default().metrics_export_interval_secs)]
pub metrics_export_interval_secs: u64,
#[arg(long, env = "OBSCURA_TELEMETRY_EXPORT_TIMEOUT_SECS", default_value_t = TelemetryConfig::default().export_timeout_secs)]
pub export_timeout_secs: u64,
}
impl Default for TelemetryConfig {
fn default() -> Self {
Self {
otlp_endpoint: None,
log_format: LogFormat::Text,
trace_sampling_ratio: 1.0,
metrics_export_interval_secs: 60,
export_timeout_secs: 10,
}
}
}
impl std::fmt::Display for LogFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LogFormat::Text => write!(f, "text"),
LogFormat::Json => write!(f, "json"),
}
}
}
#[derive(Clone, Debug, Args)]
pub struct ServerConfig {
#[arg(long = "server-host", env = "OBSCURA_HOST", default_value_t = ServerConfig::default().host)]
pub host: String,
#[arg(long = "server-port", env = "OBSCURA_PORT", default_value_t = ServerConfig::default().port)]
pub port: u16,
#[arg(long, env = "OBSCURA_MGMT_PORT", default_value_t = ServerConfig::default().mgmt_port)]
pub mgmt_port: u16,
#[arg(long, env = "OBSCURA_SHUTDOWN_TIMEOUT_SECS", default_value_t = ServerConfig::default().shutdown_timeout_secs)]
pub shutdown_timeout_secs: u64,
#[arg(
long,
env = "OBSCURA_TRUSTED_PROXIES",
default_value = "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1/32",
value_delimiter = ','
)]
pub trusted_proxies: Vec<IpNetwork>,
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
host: "0.0.0.0".to_string(),
port: 3000,
mgmt_port: 9090,
shutdown_timeout_secs: 5,
trusted_proxies: vec![
"10.0.0.0/8".parse().unwrap(),
"172.16.0.0/12".parse().unwrap(),
"192.168.0.0/16".parse().unwrap(),
"127.0.0.1/32".parse().unwrap(),
],
}
}
}
#[derive(Clone, Debug, Args)]
pub struct AuthConfig {
#[arg(long, env = "OBSCURA_JWT_SECRET", default_value_t = AuthConfig::default().jwt_secret)]
pub jwt_secret: String,
#[arg(long, env = "OBSCURA_ACCESS_TOKEN_TTL_SECS", default_value_t = AuthConfig::default().access_token_ttl_secs)]
pub access_token_ttl_secs: u64,
#[arg(long, env = "OBSCURA_REFRESH_TOKEN_TTL_DAYS", default_value_t = AuthConfig::default().refresh_token_ttl_days)]
pub refresh_token_ttl_days: i64,
}
impl Default for AuthConfig {
fn default() -> Self {
Self {
jwt_secret: "change_me_in_production".to_string(),
access_token_ttl_secs: 900,
refresh_token_ttl_days: 30,
}
}
}
#[derive(Clone, Debug, Args)]
pub struct RateLimitConfig {
#[arg(long = "rate-limit-per-second", env = "OBSCURA_RATE_LIMIT_PER_SECOND", default_value_t = RateLimitConfig::default().per_second)]
pub per_second: u32,
#[arg(long = "rate-limit-burst", env = "OBSCURA_RATE_LIMIT_BURST", default_value_t = RateLimitConfig::default().burst)]
pub burst: u32,
#[arg(long = "auth-rate-limit-per-second", env = "OBSCURA_AUTH_RATE_LIMIT_PER_SECOND", default_value_t = RateLimitConfig::default().auth_per_second)]
pub auth_per_second: u32,
#[arg(long = "auth-rate-limit-burst", env = "OBSCURA_AUTH_RATE_LIMIT_BURST", default_value_t = RateLimitConfig::default().auth_burst)]
pub auth_burst: u32,
}
impl Default for RateLimitConfig {
fn default() -> Self {
Self {
per_second: 10,
burst: 20,
auth_per_second: 1,
auth_burst: 3,
}
}
}
#[derive(Clone, Debug, Args)]
pub struct MessagingConfig {
#[arg(long, env = "OBSCURA_MAX_INBOX_SIZE", default_value_t = MessagingConfig::default().max_inbox_size)]
pub max_inbox_size: i64,
#[arg(
long = "messaging-cleanup-interval-secs",
id = "messaging_cleanup_interval_secs",
env = "OBSCURA_MESSAGING_CLEANUP_INTERVAL_SECS",
default_value_t = MessagingConfig::default().cleanup_interval_secs
)]
pub cleanup_interval_secs: u64,
#[arg(long, env = "OBSCURA_BATCH_LIMIT", default_value_t = MessagingConfig::default().batch_limit)]
pub batch_limit: i64,
#[arg(long, env = "OBSCURA_PRE_KEY_REFILL_THRESHOLD", default_value_t = MessagingConfig::default().pre_key_refill_threshold)]
pub pre_key_refill_threshold: i32,
#[arg(long, env = "OBSCURA_MAX_PRE_KEYS", default_value_t = MessagingConfig::default().max_pre_keys)]
pub max_pre_keys: i64,
}
impl Default for MessagingConfig {
fn default() -> Self {
Self {
max_inbox_size: 1000,
cleanup_interval_secs: 300,
batch_limit: 50,
pre_key_refill_threshold: 20,
max_pre_keys: 100,
}
}
}
#[derive(Clone, Debug, Args)]
pub struct NotificationConfig {
#[arg(long, env = "OBSCURA_GC_INTERVAL_SECS", default_value_t = NotificationConfig::default().gc_interval_secs)]
pub gc_interval_secs: u64,
#[arg(long, env = "OBSCURA_CHANNEL_CAPACITY", default_value_t = NotificationConfig::default().channel_capacity)]
pub channel_capacity: usize,
}
impl Default for NotificationConfig {
fn default() -> Self {
Self {
gc_interval_secs: 60,
channel_capacity: 16,
}
}
}
#[derive(Clone, Debug, Args)]
pub struct WsConfig {
#[arg(long = "ws-outbound-buffer-size", env = "OBSCURA_WS_OUTBOUND_BUFFER_SIZE", default_value_t = WsConfig::default().outbound_buffer_size)]
pub outbound_buffer_size: usize,
#[arg(long = "ws-ack-buffer-size", env = "OBSCURA_WS_ACK_BUFFER_SIZE", default_value_t = WsConfig::default().ack_buffer_size)]
pub ack_buffer_size: usize,
#[arg(long = "ws-ack-batch-size", env = "OBSCURA_WS_ACK_BATCH_SIZE", default_value_t = WsConfig::default().ack_batch_size)]
pub ack_batch_size: usize,
#[arg(long = "ws-ack-flush-interval-ms", env = "OBSCURA_WS_ACK_FLUSH_INTERVAL_MS", default_value_t = WsConfig::default().ack_flush_interval_ms)]
pub ack_flush_interval_ms: u64,
}
impl Default for WsConfig {
fn default() -> Self {
Self {
outbound_buffer_size: 32,
ack_buffer_size: 100,
ack_batch_size: 50,
ack_flush_interval_ms: 500,
}
}
}
#[derive(Clone, Debug, Args)]
pub struct StorageConfig {
#[arg(long = "storage-bucket", env = "OBSCURA_STORAGE_BUCKET", default_value_t = StorageConfig::default().bucket)]
pub bucket: String,
#[arg(long = "storage-region", env = "OBSCURA_STORAGE_REGION", default_value_t = StorageConfig::default().region)]
pub region: String,
#[arg(long = "storage-endpoint", env = "OBSCURA_STORAGE_ENDPOINT")]
pub endpoint: Option<String>,
#[arg(long = "storage-access-key", env = "OBSCURA_STORAGE_ACCESS_KEY")]
pub access_key: Option<String>,
#[arg(long = "storage-secret-key", env = "OBSCURA_STORAGE_SECRET_KEY")]
pub secret_key: Option<String>,
#[arg(long = "storage-force-path-style", env = "OBSCURA_STORAGE_FORCE_PATH_STYLE", default_value_t = StorageConfig::default().force_path_style)]
pub force_path_style: bool,
#[arg(long = "storage-max-size-bytes", env = "OBSCURA_STORAGE_MAX_SIZE_BYTES", default_value_t = StorageConfig::default().attachment_max_size_bytes)]
pub attachment_max_size_bytes: usize,
#[arg(
long = "storage-cleanup-interval-secs",
id = "storage_cleanup_interval_secs",
env = "OBSCURA_STORAGE_CLEANUP_INTERVAL_SECS",
default_value_t = StorageConfig::default().cleanup_interval_secs
)]
pub cleanup_interval_secs: u64,
#[arg(long = "storage-cleanup-batch-size", env = "OBSCURA_STORAGE_CLEANUP_BATCH_SIZE", default_value_t = StorageConfig::default().cleanup_batch_size)]
pub cleanup_batch_size: u64,
}
impl Default for StorageConfig {
fn default() -> Self {
Self {
bucket: "obscura-attachments".to_string(),
region: "us-east-1".to_string(),
endpoint: None,
access_key: None,
secret_key: None,
force_path_style: false,
attachment_max_size_bytes: 52_428_800,
cleanup_interval_secs: 3600,
cleanup_batch_size: 100,
}
}
}
#[derive(Clone, Debug, Args)]
pub struct HealthConfig {
#[arg(long, env = "OBSCURA_HEALTH_DB_TIMEOUT_MS", default_value_t = HealthConfig::default().db_timeout_ms)]
pub db_timeout_ms: u64,
#[arg(long = "storage-timeout-ms", env = "OBSCURA_STORAGE_HEALTH_TIMEOUT_MS", default_value_t = HealthConfig::default().storage_timeout_ms)]
pub storage_timeout_ms: u64,
}
impl Default for HealthConfig {
fn default() -> Self {
Self {
db_timeout_ms: 2000,
storage_timeout_ms: 2000,
}
}
}