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;
14pub mod tls;
15
16use std::sync::Arc;
17
18use axum::Router;
19
20use crate::auth::Authorizer;
21use crate::config::Config;
22use crate::locks::LockStore;
23use crate::metrics::Metrics;
24use crate::state::AppState;
25use crate::storage::s3::{S3Config, S3Store};
26use crate::storage::{LocalStore, Store};
27
28pub fn app(config: Config) -> Router {
29 let keys = config.encryption_key_file.as_deref().map(|path| {
34 std::sync::Arc::new(
35 crate::storage::crypt::Keyring::load(path)
36 .expect("the encryption key file is not usable"),
37 )
38 });
39
40 let local = LocalStore::new(config.storage_root.clone())
41 .with_max_object_size(config.max_object_size)
42 .with_compression(config.compression)
43 .with_encryption(keys);
44
45 let (store, lock_backend) = match &config.storage {
50 crate::config::Storage::Local => (
51 Store::local(local),
52 LockStore::local(config.storage_root.clone()),
53 ),
54 crate::config::Storage::Bucket {
55 endpoint,
56 bucket,
57 region,
58 access_key,
59 secret_key,
60 path_style,
61 presign,
62 } => {
63 let bucket = S3Store::new(&S3Config {
64 endpoint: endpoint.clone(),
65 bucket: bucket.clone(),
66 region: region.clone(),
67 access_key: access_key.clone(),
68 secret_key: secret_key.clone(),
69 path_style: *path_style,
70 redirect: *presign,
71 lifetime: std::time::Duration::from_secs(config.action_lifetime.into()),
72 })
73 .expect("the bucket configuration is not usable");
74
75 tracing::warn!(
76 "objects and locks 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"
77 );
78
79 if *presign {
80 tracing::warn!(
81 "LFSX_S3_PRESIGN=true — downloads are redirected to the bucket, so lfsx_downloaded_bytes stops counting them and the bucket serves the ranges"
82 );
83 }
84
85 if config.encryption_key_file.is_some() {
86 tracing::warn!(
87 "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"
88 );
89 }
90
91 if config.compression.is_some() {
92 tracing::warn!(
93 "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"
94 );
95 }
96
97 (
101 Store::bucket(bucket.clone(), local),
102 LockStore::bucket(bucket),
103 )
104 }
105 };
106 let locks = lock_backend.with_max_age(config.lock_max_age);
107 let authorizer = Authorizer::new(&config.auth);
108
109 routes::router(Arc::new(AppState {
110 store,
111 locks,
112 config,
113 authorizer,
114 metrics: Metrics::new(),
115 }))
116}