Skip to main content

lfsx_server/
lib.rs

1pub mod auth;
2pub mod config;
3pub mod dashboard;
4pub mod error;
5pub mod locks;
6pub mod metrics;
7pub mod model;
8pub mod namespace;
9pub mod page;
10pub mod range;
11pub mod routes;
12pub mod state;
13pub mod storage;
14
15use std::sync::Arc;
16
17use axum::Router;
18
19use crate::auth::Authorizer;
20use crate::config::Config;
21use crate::locks::LockStore;
22use crate::metrics::Metrics;
23use crate::state::AppState;
24use crate::storage::s3::{S3Config, S3Store};
25use crate::storage::{LocalStore, Store};
26
27pub fn app(config: Config) -> Router {
28    // Refusing to start beats starting without it. A server that silently wrote
29    // plaintext because a Secret failed to mount is the one failure this feature
30    // must never have: nothing downstream would notice, and the objects written
31    // in the meantime are the ones the operator believed were covered.
32    let keys = config.encryption_key_file.as_deref().map(|path| {
33        std::sync::Arc::new(
34            crate::storage::crypt::Keyring::load(path)
35                .expect("the encryption key file is not usable"),
36        )
37    });
38
39    let local = LocalStore::new(config.storage_root.clone())
40        .with_max_object_size(config.max_object_size)
41        .with_compression(config.compression)
42        .with_encryption(keys);
43
44    let store = match &config.storage {
45        crate::config::Storage::Local => Store::local(local),
46        crate::config::Storage::Bucket {
47            endpoint,
48            bucket,
49            region,
50            access_key,
51            secret_key,
52            path_style,
53            presign,
54        } => {
55            let bucket = S3Store::new(&S3Config {
56                endpoint: endpoint.clone(),
57                bucket: bucket.clone(),
58                region: region.clone(),
59                access_key: access_key.clone(),
60                secret_key: secret_key.clone(),
61                path_style: *path_style,
62                redirect: *presign,
63                lifetime: std::time::Duration::from_secs(config.action_lifetime.into()),
64            })
65            .expect("the bucket configuration is not usable");
66
67            tracing::warn!(
68                "objects are stored in a bucket: collection, deduplication, compression and                  verification answer 501, and the lfsx_objects_stored and lfsx_store_bytes gauges                  are not measured — read capacity from the bucket itself"
69            );
70
71            if *presign {
72                tracing::warn!(
73                    "LFSX_S3_PRESIGN=true — downloads are redirected to the bucket, so                      lfsx_downloaded_bytes stops counting them and the bucket serves the ranges"
74                );
75            }
76
77            if config.encryption_key_file.is_some() {
78                tracing::warn!(
79                    "LFSX_ENCRYPTION_KEY_FILE is set and objects are stored in a bucket — the                      bucket holds them as they arrive, because an encrypted object is only                      readable through the local file the codec opens"
80                );
81            }
82
83            if config.compression.is_some() {
84                tracing::warn!(
85                    "LFSX_COMPRESSION is set and objects are stored in a bucket — the bucket                      holds them uncompressed, because a compressed object is only readable                      through the local file the codec opens"
86                );
87            }
88
89            Store::bucket(bucket, local)
90        }
91    };
92    let locks = LockStore::new(config.storage_root.clone());
93    let authorizer = Authorizer::new(&config.auth);
94
95    routes::router(Arc::new(AppState {
96        store,
97        locks,
98        config,
99        authorizer,
100        metrics: Metrics::new(),
101    }))
102}