juiceback/
config.rs

1//! Config loaded from environment variables.
2
3use std::path::PathBuf;
4
5/// Holds every setting juiceback needs to run.
6///
7/// All values come from environment variables with sensible fallbacks.
8/// Check out Config::load to see what variables are available.
9#[derive(Debug)]
10pub struct Config {
11    pub host: String,
12    pub port: u16,
13    pub files_dir: PathBuf,
14    pub database_path: String,
15    pub default_ttl_hours: u32,
16    pub max_ttl_hours: u32,
17    pub max_file_size_bytes: u64,
18    pub rate_limit_per_minute: u32,
19    pub db_pool_size: u32,
20    pub public_base_url: String,
21    pub log_level: String,
22    pub cleanup_interval_minutes: u64,
23    pub juicehost_api_key: String,
24    pub juicehost_url: String,
25    pub juiceback_origin: String,
26    pub jwt_secret: String,
27}
28
29impl Config {
30    /// Load settings from the environment.
31    ///
32    /// Here are the environment variables it reads:
33    /// HOST (default 127.0.0.1) -- TCP bind address
34    /// PORT (default 6401) -- TCP bind port, QUIC on UDP 6402
35    /// FILES_DIR (default ./files) -- temp file directory
36    /// DATABASE_PATH (default ./delta.db) -- SQLite database file
37    /// DEFAULT_TTL_HOURS (default 72) -- how long files live
38    /// MAX_TTL_HOURS (default 168) -- max retention a user can pick
39    /// MAX_FILE_SIZE_MB (default 500) -- max upload size
40    /// RATE_LIMIT_PER_MINUTE (default 10) -- uploads per IP per minute
41    /// DB_POOL_SIZE (default 8) -- connection pool size
42    /// PUBLIC_BASE_URL (default http://localhost:6402) -- public juicehost URL
43    /// LOG_LEVEL (default info) -- log filter
44    /// CLEANUP_INTERVAL_MINUTES (default 30) -- how often expired files get cleaned
45    /// JUICEHOST_API_KEY -- shared secret for juicehost auth
46    /// JUICEHOST_URL (default http://127.0.0.1:6402) -- internal juicehost URL
47    /// JUICEBACK_ORIGIN -- origin header sent to juicehost
48    /// JWT_SECRET -- JWT signing key, auto-generates if you leave it unset
49    pub fn load() -> Result<Self, String> {
50        let host = std::env::var("HOST").unwrap_or_else(|_| "127.0.0.1".to_string());
51        let port = std::env::var("PORT")
52            .unwrap_or_else(|_| "6401".to_string())
53            .parse::<u16>()
54            .map_err(|e| format!("Invalid PORT: {}", e))?;
55
56        let files_dir = PathBuf::from(std::env::var("FILES_DIR").unwrap_or_else(|_| "./files".to_string()));
57
58        let database_path = std::env::var("DATABASE_PATH").unwrap_or_else(|_| "./delta.db".to_string());
59
60        let default_ttl_hours = std::env::var("DEFAULT_TTL_HOURS")
61            .unwrap_or_else(|_| "72".to_string())
62            .parse::<u32>()
63            .map_err(|e| format!("Invalid DEFAULT_TTL_HOURS: {}", e))?;
64
65        let max_ttl_hours = std::env::var("MAX_TTL_HOURS")
66            .unwrap_or_else(|_| "168".to_string())
67            .parse::<u32>()
68            .map_err(|e| format!("Invalid MAX_TTL_HOURS: {}", e))?;
69
70        let max_file_size_mb = std::env::var("MAX_FILE_SIZE_MB")
71            .unwrap_or_else(|_| "500".to_string())
72            .parse::<u64>()
73            .map_err(|e| format!("Invalid MAX_FILE_SIZE_MB: {}", e))?;
74        let max_file_size_bytes = max_file_size_mb * 1024 * 1024;
75
76        let rate_limit_per_minute = std::env::var("RATE_LIMIT_PER_MINUTE")
77            .unwrap_or_else(|_| "10".to_string())
78            .parse::<u32>()
79            .map_err(|e| format!("Invalid RATE_LIMIT_PER_MINUTE: {}", e))?;
80
81        let db_pool_size = std::env::var("DB_POOL_SIZE")
82            .unwrap_or_else(|_| "8".to_string())
83            .parse::<u32>()
84            .map_err(|e| format!("Invalid DB_POOL_SIZE: {}", e))?;
85
86        let public_base_url = std::env::var("PUBLIC_BASE_URL")
87            .unwrap_or_else(|_| "http://localhost:6402".to_string())
88            .trim_end_matches('/')
89            .to_string();
90
91        let log_level = std::env::var("LOG_LEVEL").unwrap_or_else(|_| "info".to_string());
92
93        let cleanup_interval_minutes = std::env::var("CLEANUP_INTERVAL_MINUTES")
94            .unwrap_or_else(|_| "30".to_string())
95            .parse::<u64>()
96            .map_err(|e| format!("Invalid CLEANUP_INTERVAL_MINUTES: {}", e))?;
97
98        let juicehost_api_key = std::env::var("JUICEHOST_API_KEY").unwrap_or_else(|_| {
99            tracing::warn!("JUICEHOST_API_KEY not set, internal API endpoints are unprotected");
100            String::new()
101        });
102
103        let juicehost_url = std::env::var("JUICEHOST_URL")
104            .unwrap_or_else(|_| "http://127.0.0.1:6402".to_string())
105            .trim_end_matches('/')
106            .to_string();
107
108        let juiceback_origin = std::env::var("JUICEBACK_ORIGIN")
109            .unwrap_or_else(|_| format!("http://{}:{}", host, port))
110            .trim_end_matches('/')
111            .to_string();
112
113        let jwt_secret = std::env::var("JWT_SECRET").unwrap_or_else(|_| {
114            let generated = uuid::Uuid::new_v4().to_string();
115            tracing::warn!(
116                "JWT_SECRET not set, using generated random secret (admins will be invalidated on restart)"
117            );
118            generated
119        });
120
121        Ok(Self {
122            host,
123            port,
124            files_dir,
125            database_path,
126            default_ttl_hours,
127            max_ttl_hours,
128            max_file_size_bytes,
129            rate_limit_per_minute,
130            db_pool_size,
131            public_base_url,
132            log_level,
133            cleanup_interval_minutes,
134            juicehost_api_key,
135            juicehost_url,
136            juiceback_origin,
137            jwt_secret,
138        })
139    }
140}