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    Forge {
23        provider: Provider,
24        api_url: String,
25        cache_ttl: Duration,
26        rejection_ttl: Duration,
27    },
28    Disabled,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum Provider {
33    Github,
34    Gitlab,
35}
36
37impl Provider {
38    fn default_api_url(self) -> &'static str {
39        match self {
40            Self::Github => "https://api.github.com",
41            Self::Gitlab => "https://gitlab.com/api/v4",
42        }
43    }
44
45    fn api_url_variable(self) -> &'static str {
46        match self {
47            Self::Github => "LFSX_GITHUB_API_URL",
48            Self::Gitlab => "LFSX_GITLAB_API_URL",
49        }
50    }
51}
52
53const CACHE_TTL: Duration = Duration::from_secs(60);
54const REJECTION_TTL: Duration = Duration::from_secs(10);
55const GC_GRACE: Duration = Duration::from_secs(14 * 24 * 60 * 60);
56
57impl Config {
58    pub fn from_env() -> Self {
59        let bind = std::env::var("LFSX_BIND")
60            .ok()
61            .and_then(|raw| raw.parse().ok())
62            .unwrap_or_else(|| SocketAddr::from(([0, 0, 0, 0], 8080)));
63
64        let storage_root = std::env::var("LFSX_STORAGE_ROOT")
65            .map(PathBuf::from)
66            .unwrap_or_else(|_| PathBuf::from("/var/lib/lfsx"));
67
68        let public_url = std::env::var("LFSX_PUBLIC_URL")
69            .ok()
70            .filter(|url| !url.is_empty())
71            .map(|url| url.trim_end_matches('/').to_owned());
72
73        Self {
74            bind,
75            storage_root,
76            public_url,
77            action_lifetime: 1800,
78            gc_grace: seconds("LFSX_GC_GRACE").unwrap_or(GC_GRACE),
79            auth: Auth::from_env(),
80        }
81    }
82
83    pub fn base_url(&self, headers: &HeaderMap) -> String {
84        if let Some(configured) = &self.public_url {
85            return configured.clone();
86        }
87
88        let scheme = headers
89            .get("x-forwarded-proto")
90            .and_then(|value| value.to_str().ok())
91            .and_then(|value| value.split(',').next())
92            .map(str::trim)
93            .filter(|scheme| !scheme.is_empty())
94            .unwrap_or("http");
95
96        let authority = headers
97            .get(header::HOST)
98            .and_then(|value| value.to_str().ok())
99            .map(str::trim)
100            .filter(|host| !host.is_empty())
101            .unwrap_or("localhost");
102
103        format!("{scheme}://{authority}")
104    }
105
106    pub fn object_url(&self, base: &str, ns: &Namespace, oid: &str) -> String {
107        format!("{base}/{ns}/objects/{oid}")
108    }
109
110    pub fn verify_url(&self, base: &str, ns: &Namespace) -> String {
111        format!("{base}/{ns}/objects/verify")
112    }
113
114    pub fn action(&self, href: String) -> Action {
115        Action {
116            href,
117            expires_in: self.action_lifetime,
118        }
119    }
120}
121
122impl Auth {
123    fn from_env() -> Self {
124        if std::env::var("LFSX_AUTH").as_deref() == Ok("disabled") {
125            tracing::warn!(
126                "LFSX_AUTH=disabled — every request is accepted, run this on a trusted network only"
127            );
128            return Self::Disabled;
129        }
130
131        let provider = match std::env::var("LFSX_AUTH").as_deref() {
132            Ok("gitlab") => Provider::Gitlab,
133            _ => Provider::Github,
134        };
135
136        let api_url = std::env::var(provider.api_url_variable())
137            .unwrap_or_else(|_| provider.default_api_url().to_owned())
138            .trim_end_matches('/')
139            .to_owned();
140
141        Self::Forge {
142            provider,
143            api_url,
144            cache_ttl: seconds("LFSX_AUTH_CACHE_TTL").unwrap_or(CACHE_TTL),
145            rejection_ttl: seconds("LFSX_AUTH_REJECTION_TTL").unwrap_or(REJECTION_TTL),
146        }
147    }
148}
149
150fn seconds(variable: &str) -> Option<Duration> {
151    std::env::var(variable)
152        .ok()
153        .and_then(|raw| raw.parse().ok())
154        .map(Duration::from_secs)
155}