Skip to main content

lfsx_server/
config.rs

1use axum::http::{HeaderMap, header};
2
3use std::net::SocketAddr;
4use std::path::PathBuf;
5use std::time::Duration;
6
7use crate::model::Action;
8use crate::namespace::Namespace;
9
10#[derive(Debug, Clone)]
11pub struct Config {
12    pub bind: SocketAddr,
13    pub storage_root: PathBuf,
14    pub public_url: Option<String>,
15    pub action_lifetime: u32,
16    pub gc_grace: Duration,
17    pub auth: Auth,
18}
19
20#[derive(Debug, Clone)]
21pub enum Auth {
22    Github {
23        api_url: String,
24        cache_ttl: Duration,
25        rejection_ttl: Duration,
26    },
27    Disabled,
28}
29
30const GITHUB_API_URL: &str = "https://api.github.com";
31const CACHE_TTL: Duration = Duration::from_secs(60);
32const REJECTION_TTL: Duration = Duration::from_secs(10);
33const GC_GRACE: Duration = Duration::from_secs(14 * 24 * 60 * 60);
34
35impl Config {
36    pub fn from_env() -> Self {
37        let bind = std::env::var("LFSX_BIND")
38            .ok()
39            .and_then(|raw| raw.parse().ok())
40            .unwrap_or_else(|| SocketAddr::from(([0, 0, 0, 0], 8080)));
41
42        let storage_root = std::env::var("LFSX_STORAGE_ROOT")
43            .map(PathBuf::from)
44            .unwrap_or_else(|_| PathBuf::from("/var/lib/lfsx"));
45
46        let public_url = std::env::var("LFSX_PUBLIC_URL")
47            .ok()
48            .filter(|url| !url.is_empty())
49            .map(|url| url.trim_end_matches('/').to_owned());
50
51        Self {
52            bind,
53            storage_root,
54            public_url,
55            action_lifetime: 1800,
56            gc_grace: seconds("LFSX_GC_GRACE").unwrap_or(GC_GRACE),
57            auth: Auth::from_env(),
58        }
59    }
60
61    pub fn base_url(&self, headers: &HeaderMap) -> String {
62        if let Some(configured) = &self.public_url {
63            return configured.clone();
64        }
65
66        let scheme = headers
67            .get("x-forwarded-proto")
68            .and_then(|value| value.to_str().ok())
69            .and_then(|value| value.split(',').next())
70            .map(str::trim)
71            .filter(|scheme| !scheme.is_empty())
72            .unwrap_or("http");
73
74        let authority = headers
75            .get(header::HOST)
76            .and_then(|value| value.to_str().ok())
77            .map(str::trim)
78            .filter(|host| !host.is_empty())
79            .unwrap_or("localhost");
80
81        format!("{scheme}://{authority}")
82    }
83
84    pub fn object_url(&self, base: &str, ns: &Namespace, oid: &str) -> String {
85        format!("{base}/{ns}/objects/{oid}")
86    }
87
88    pub fn verify_url(&self, base: &str, ns: &Namespace) -> String {
89        format!("{base}/{ns}/objects/verify")
90    }
91
92    pub fn action(&self, href: String) -> Action {
93        Action {
94            href,
95            expires_in: self.action_lifetime,
96        }
97    }
98}
99
100impl Auth {
101    fn from_env() -> Self {
102        if std::env::var("LFSX_AUTH").as_deref() == Ok("disabled") {
103            tracing::warn!(
104                "LFSX_AUTH=disabled — every request is accepted, run this on a trusted network only"
105            );
106            return Self::Disabled;
107        }
108
109        let api_url = std::env::var("LFSX_GITHUB_API_URL")
110            .unwrap_or_else(|_| GITHUB_API_URL.to_owned())
111            .trim_end_matches('/')
112            .to_owned();
113
114        let cache_ttl = seconds("LFSX_AUTH_CACHE_TTL").unwrap_or(CACHE_TTL);
115
116        let rejection_ttl = seconds("LFSX_AUTH_REJECTION_TTL").unwrap_or(REJECTION_TTL);
117
118        Self::Github {
119            api_url,
120            cache_ttl,
121            rejection_ttl,
122        }
123    }
124}
125
126fn seconds(variable: &str) -> Option<Duration> {
127    std::env::var(variable)
128        .ok()
129        .and_then(|raw| raw.parse().ok())
130        .map(Duration::from_secs)
131}