diff --git a/Cargo.lock b/Cargo.lock index b95837c1..1995fae5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3052,6 +3052,7 @@ dependencies = [ "config", "domain", "reqwest 0.13.3", + "rustls", "serde", ] @@ -3748,6 +3749,7 @@ dependencies = [ "config", "domain", "reqwest 0.13.3", + "rustls", ] [[package]] @@ -5539,6 +5541,7 @@ dependencies = [ "config", "domain", "reqwest 0.13.3", + "rustls", "serde", ] diff --git a/Cargo.toml b/Cargo.toml index 494cc871..acaa465a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -113,6 +113,9 @@ webp = "0.3" rss = { version = "2" } ratatui = "0.30.0" keyring-core = "1.0.0" +apple-native-keyring-store = { version = "1.0.0", features = ["keychain"] } +zbus-secret-service-keyring-store = { version = "1.0.0" } +windows-native-keyring-store = { version = "1.0.0" } directories = "6" api-types = { path = "crates/api-types" } diff --git a/README.md b/README.md index d3f37111..6c15ed7e 100644 --- a/README.md +++ b/README.md @@ -176,7 +176,7 @@ Build without federation with `--no-default-features`. `sqlite` and `sqlite-fede ### Build notes -- `cargo build` / `cargo run` build every crate except the deprecated `tui`, which sits outside `default-members`. Use `-p tui` for it; `make check` and CI use `--workspace`, so it is still linted and tested. +- `cargo build` / `cargo test` build every crate except the deprecated `tui`, which sits outside `default-members`. Use `-p tui` for it; `make check` and CI use `--workspace`, so it is still linted and tested. - Release builds use `codegen-units = 1` and `strip = true` (smaller binaries; thin LTO was measured and made builds slower and binaries larger, so it is off). - SQLite is compiled from source (bundled `libsqlite3-sys`), so the first clean build spends about a minute on it; incremental builds and the Docker/CI caches don't repeat that. - TLS uses rustls with the `ring` provider only; `aws-lc` and `actix` are kept out of the graph by `make check-no-second-web-stack`. diff --git a/crates/adapters/metadata/Cargo.toml b/crates/adapters/metadata/Cargo.toml index fb17a0e3..e4db888f 100644 --- a/crates/adapters/metadata/Cargo.toml +++ b/crates/adapters/metadata/Cargo.toml @@ -8,5 +8,6 @@ config = { workspace = true } anyhow = { workspace = true } async-trait = { workspace = true } reqwest = { workspace = true } +rustls = { workspace = true, features = ["ring", "std"] } serde = { workspace = true, features = ["derive"] } domain = { workspace = true } diff --git a/crates/adapters/metadata/src/omdb.rs b/crates/adapters/metadata/src/omdb.rs index b5c4f7a8..46ccc98f 100644 --- a/crates/adapters/metadata/src/omdb.rs +++ b/crates/adapters/metadata/src/omdb.rs @@ -16,6 +16,8 @@ pub(crate) struct OmdbProvider { impl OmdbProvider { pub(crate) fn new(api_key: String) -> Self { + // reqwest is built with rustls-no-provider; install ring so this works without bootstrap. + let _ = rustls::crypto::ring::default_provider().install_default(); Self { client: reqwest::Client::new(), api_key, diff --git a/crates/adapters/metadata/src/tmdb.rs b/crates/adapters/metadata/src/tmdb.rs index ec5e776a..655f966d 100644 --- a/crates/adapters/metadata/src/tmdb.rs +++ b/crates/adapters/metadata/src/tmdb.rs @@ -15,6 +15,8 @@ pub(crate) struct TmdbProvider { impl TmdbProvider { pub(crate) fn new(api_key: String) -> Self { + // reqwest is built with rustls-no-provider; install ring so this works without bootstrap. + let _ = rustls::crypto::ring::default_provider().install_default(); Self { client: reqwest::Client::new(), api_key, diff --git a/crates/adapters/metadata/tests/client_tls_omdb.rs b/crates/adapters/metadata/tests/client_tls_omdb.rs new file mode 100644 index 00000000..a2594f52 --- /dev/null +++ b/crates/adapters/metadata/tests/client_tls_omdb.rs @@ -0,0 +1,6 @@ +//! Fresh test binary, no bootstrap: constructing the client must not panic +//! for lack of a rustls CryptoProvider. +#[test] +fn omdb_client_builds_without_bootstrap() { + let _ = metadata::MetadataClientImpl::new_omdb("key".into()); +} diff --git a/crates/adapters/metadata/tests/client_tls_tmdb.rs b/crates/adapters/metadata/tests/client_tls_tmdb.rs new file mode 100644 index 00000000..9a63e670 --- /dev/null +++ b/crates/adapters/metadata/tests/client_tls_tmdb.rs @@ -0,0 +1,6 @@ +//! Fresh test binary, no bootstrap: constructing the client must not panic +//! for lack of a rustls CryptoProvider. +#[test] +fn tmdb_client_builds_without_bootstrap() { + let _ = metadata::MetadataClientImpl::new_tmdb("key".into()); +} diff --git a/crates/adapters/poster-fetcher/Cargo.toml b/crates/adapters/poster-fetcher/Cargo.toml index efbdb43f..f72c22ee 100644 --- a/crates/adapters/poster-fetcher/Cargo.toml +++ b/crates/adapters/poster-fetcher/Cargo.toml @@ -8,4 +8,5 @@ config = { workspace = true } domain = { workspace = true } async-trait = { workspace = true } reqwest = { workspace = true } +rustls = { workspace = true, features = ["ring", "std"] } anyhow = { workspace = true } diff --git a/crates/adapters/poster-fetcher/src/lib.rs b/crates/adapters/poster-fetcher/src/lib.rs index 504eae70..022a4b3f 100644 --- a/crates/adapters/poster-fetcher/src/lib.rs +++ b/crates/adapters/poster-fetcher/src/lib.rs @@ -16,6 +16,8 @@ pub struct ReqwestPosterFetcher { impl ReqwestPosterFetcher { pub fn new(config: PosterFetcherConfig) -> anyhow::Result { + // reqwest is built with rustls-no-provider; install ring so this works without bootstrap. + let _ = rustls::crypto::ring::default_provider().install_default(); let client = reqwest::Client::builder() .timeout(Duration::from_secs(config.timeout_seconds)) .build()?; diff --git a/crates/adapters/poster-fetcher/tests/client_tls.rs b/crates/adapters/poster-fetcher/tests/client_tls.rs new file mode 100644 index 00000000..74013f71 --- /dev/null +++ b/crates/adapters/poster-fetcher/tests/client_tls.rs @@ -0,0 +1,8 @@ +//! Fresh test binary, no bootstrap: constructing the client must not panic +//! for lack of a rustls CryptoProvider. +use poster_fetcher::{PosterFetcherConfig, ReqwestPosterFetcher}; + +#[test] +fn poster_fetcher_builds_without_bootstrap() { + ReqwestPosterFetcher::new(PosterFetcherConfig { timeout_seconds: 5 }).expect("client builds"); +} diff --git a/crates/adapters/tmdb-enrichment/Cargo.toml b/crates/adapters/tmdb-enrichment/Cargo.toml index 35d2275a..cce5b2fd 100644 --- a/crates/adapters/tmdb-enrichment/Cargo.toml +++ b/crates/adapters/tmdb-enrichment/Cargo.toml @@ -7,6 +7,7 @@ edition.workspace = true config = { workspace = true } domain = { workspace = true } reqwest = { workspace = true } +rustls = { workspace = true, features = ["ring", "std"] } serde = { workspace = true, features = ["derive"] } async-trait = { workspace = true } chrono = { workspace = true } diff --git a/crates/adapters/tmdb-enrichment/src/client.rs b/crates/adapters/tmdb-enrichment/src/client.rs index 09c111c5..4ae720e6 100644 --- a/crates/adapters/tmdb-enrichment/src/client.rs +++ b/crates/adapters/tmdb-enrichment/src/client.rs @@ -12,6 +12,8 @@ impl TmdbEnrichmentClient { .tmdb_api_key .clone() .ok_or_else(|| DomainError::InfrastructureError("TMDB_API_KEY is not set".into()))?; + // reqwest is built with rustls-no-provider; install ring so this works without bootstrap. + let _ = rustls::crypto::ring::default_provider().install_default(); Ok(Self { api_key, http: reqwest::Client::new(), diff --git a/crates/adapters/tmdb-enrichment/tests/client_tls.rs b/crates/adapters/tmdb-enrichment/tests/client_tls.rs new file mode 100644 index 00000000..711abe61 --- /dev/null +++ b/crates/adapters/tmdb-enrichment/tests/client_tls.rs @@ -0,0 +1,12 @@ +//! Fresh test binary, no bootstrap: constructing the client must not panic +//! for lack of a rustls CryptoProvider. +use tmdb_enrichment::TmdbEnrichmentClient; + +#[test] +fn tmdb_enrichment_builds_without_bootstrap() { + let cfg = config::MetadataConfig { + tmdb_api_key: Some("key".into()), + ..Default::default() + }; + TmdbEnrichmentClient::from_config(&cfg).expect("client builds"); +} diff --git a/crates/bootstrap/src/tls.rs b/crates/bootstrap/src/tls.rs index 2801b666..729ae316 100644 --- a/crates/bootstrap/src/tls.rs +++ b/crates/bootstrap/src/tls.rs @@ -20,8 +20,8 @@ pub fn install_crypto_provider() { mod tests { use super::*; - /// Runs alone in the lib test binary, so no other code (k-ap included) - /// has installed a provider first. + /// Only test in this crate's lib test binary, so nothing else in this + /// process installs a provider before `install_crypto_provider` runs. #[test] fn https_client_builds_after_install() { install_crypto_provider(); diff --git a/crates/tui/Cargo.toml b/crates/tui/Cargo.toml index e2e88fc7..fce7f28c 100644 --- a/crates/tui/Cargo.toml +++ b/crates/tui/Cargo.toml @@ -18,11 +18,9 @@ keyring-core = { workspace = true } directories = { workspace = true } csv = { workspace = true } -apple-native-keyring-store = { version = "1.0.0", optional = true, features = [ - "keychain", -] } -zbus-secret-service-keyring-store = { version = "1.0.0", optional = true } -windows-native-keyring-store = { version = "1.0.0", optional = true } +apple-native-keyring-store = { workspace = true, optional = true } +zbus-secret-service-keyring-store = { workspace = true, optional = true } +windows-native-keyring-store = { workspace = true, optional = true } api-types = { workspace = true } reqwest = { workspace = true }