Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,16 @@
]
},
{
"description": "Analog's Vite plugin reaches into @angular/build internals, so each only works with the release of the other it was built against: analog 2.7.5 needs a hash module that @angular/build 22.2 introduced, and @angular/build 22.2 asserts an initialisation analog 2.7.2 never does. Updated apart, both PRs fail the site build (LFSX#463, LFSX#465). One branch for both.",
"description": "Analog's Vite plugin reaches into @angular/build internals, so each only works with the release of the other it was built against: analog 2.7.5 needs a hash module that @angular/build 22.2 introduced, and @angular/build 22.2 asserts an initialisation analog 2.7.2 never does. Updated apart, both PRs fail the site build (LFSX#463, LFSX#465). One branch for both. Majors stay in the same branch too, since an Analog minor can be the one that needs the next @angular/build major.",
"matchPackageNames": [
"@analogjs/**",
"@angular/build",
"@angular/cli",
"@angular-devkit/**",
"@schematics/angular"
],
"groupName": "Angular build and Analog"
"groupName": "Angular build and Analog",
"separateMajorMinor": false
}
]
}
4 changes: 1 addition & 3 deletions server/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -530,9 +530,7 @@ fn is_set(value: Option<&str>) -> bool {
}

fn allowed(value: Option<&str>) -> Option<Namespaces> {
value
.filter(|value| is_set(Some(value)))
.map(|value| Namespaces::parse("LFSX_ALLOWED", Some(value)))
is_set(value).then(|| Namespaces::parse("LFSX_ALLOWED", value))
}

// Both variables or neither. One without the other is a configuration that
Expand Down
93 changes: 50 additions & 43 deletions server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ pub fn app(config: Config) -> Router {
}

announce_access(&config);
announce_storage(&config);
let (store, locks) = backends(&config);
let authorizer = Authorizer::new(&config.auth);
let transfers = (config.max_concurrent_transfers > 0)
Expand Down Expand Up @@ -322,6 +323,55 @@ fn announce_access(config: &Config) {
}
}

fn announce_storage(config: &Config) {
let crate::config::Storage::Bucket { presign, cache, .. } = &config.storage else {
return;
};

tracing::warn!(
"objects and locks are stored in a bucket: deduplication, rewriting and \
verification answer 501, and the lfsx_objects_stored and lfsx_store_bytes \
gauges are not measured: read capacity from the bucket itself"
);

if *presign {
if config.encryption_key.is_some() || config.compression.is_some() {
tracing::warn!(
"LFSX_S3_PRESIGN=true, but a codec is configured, so downloads keep \
streaming through this server: what sits in the bucket is a frame under \
the plaintext digest, and a client handed that directly would hash it \
and reject the object"
);
} else {
tracing::warn!(
"LFSX_S3_PRESIGN=true, downloads are redirected to the bucket, so \
lfsx_downloaded_bytes stops counting them and the bucket serves the ranges"
);
}

if config.encryption_key.is_some() {
tracing::warn!(
"an encryption key is configured, so uploads keep coming through this \
server rather than going straight to the bucket: an object a client \
writes itself would arrive unencrypted"
);
} else if config.compression.is_some() {
tracing::warn!(
"LFSX_COMPRESSION is set, and objects clients upload straight to the \
bucket arrive uncompressed: only what passes through this server is \
compressed"
);
}
}

if cache.is_some() && *presign {
tracing::warn!(
"LFSX_S3_CACHE_DIR is set with LFSX_S3_PRESIGN=true, so downloads go straight to the \
bucket and the cache never sees them: the two settings pull in opposite directions"
);
}
}

fn backends(config: &Config) -> (Store, LockStore) {
// Refusing to start beats starting without it. A server that silently wrote
// plaintext because a Secret failed to mount is the one failure this feature
Expand Down Expand Up @@ -360,42 +410,6 @@ fn backends(config: &Config) -> (Store, LockStore) {
// reaches into the other to get at them.
let keys = keyspace(config).expect("a bucket keyspace for a bucket store");

tracing::warn!(
"objects and locks are stored in a bucket: deduplication, rewriting and \
verification answer 501, and the lfsx_objects_stored and lfsx_store_bytes \
gauges are not measured: read capacity from the bucket itself"
);

if *presign {
if config.encryption_key.is_some() || config.compression.is_some() {
tracing::warn!(
"LFSX_S3_PRESIGN=true, but a codec is configured, so downloads keep \
streaming through this server: what sits in the bucket is a frame under \
the plaintext digest, and a client handed that directly would hash it \
and reject the object"
);
} else {
tracing::warn!(
"LFSX_S3_PRESIGN=true, downloads are redirected to the bucket, so \
lfsx_downloaded_bytes stops counting them and the bucket serves the ranges"
);
}

if config.encryption_key.is_some() {
tracing::warn!(
"an encryption key is configured, so uploads keep coming through this \
server rather than going straight to the bucket: an object a client \
writes itself would arrive unencrypted"
);
} else if config.compression.is_some() {
tracing::warn!(
"LFSX_COMPRESSION is set, and objects clients upload straight to the \
bucket arrive uncompressed: only what passes through this server is \
compressed"
);
}
}

// The locks go with the objects. Left on the volume they would make
// the bucket a half measure: capacity would be shared and the one
// piece of state a second replica must agree on would not be.
Expand All @@ -407,13 +421,6 @@ fn backends(config: &Config) -> (Store, LockStore) {
.expect("the cache directory is not usable")
});

if disk.is_some() && *presign {
tracing::warn!(
"LFSX_S3_CACHE_DIR is set with LFSX_S3_PRESIGN=true, so downloads go straight to the \
bucket and the cache never sees them: the two settings pull in opposite directions"
);
}

(
Store::bucket(S3Store::new(keys.clone(), *presign), local).with_cache(disk),
LockStore::bucket(keys).with_conditional_writes(*locking),
Expand Down
35 changes: 14 additions & 21 deletions server/tests/boot_log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,28 +30,21 @@ async fn a_boot_says_what_access_is_configured_once() {
let (api_url, _forge) = forge().await;
let config = config(&root, &api_url);

let mut logged = String::new();
for _ in 0..5 {
let captured = Captured::default();
{
let _guard = tracing::subscriber::set_default(
tracing_subscriber::fmt()
.with_writer(captured.clone())
.with_max_level(tracing::Level::INFO)
.with_ansi(false)
.finish(),
);

lfsx_server::reclaim(&config).await;
lfsx_server::store(&config);
let _app = lfsx_server::app(config.clone());
}

logged = String::from_utf8(captured.0.lock().unwrap().clone()).unwrap();
if !logged.is_empty() {
break;
}
let captured = Captured::default();
{
let _guard = tracing::subscriber::set_default(
tracing_subscriber::fmt()
.with_writer(captured.clone())
.with_max_level(tracing::Level::INFO)
.with_ansi(false)
.finish(),
);

lfsx_server::reclaim(&config).await;
lfsx_server::store(&config);
let _app = lfsx_server::app(config.clone());
}
let logged = String::from_utf8(captured.0.lock().unwrap().clone()).unwrap();

assert_eq!(
logged.matches("LFSX_ALLOWED is unset").count(),
Expand Down
Loading