Skip to main content

nar_bridge/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3use axum::http::StatusCode;
4use axum::response::IntoResponse;
5use axum::routing::{head, put};
6use axum::{Router, routing::get};
7use lru::LruCache;
8use nix_compat::nix_http;
9use parking_lot::RwLock;
10use snix_castore::Node;
11use snix_castore::blobservice::BlobService;
12use snix_castore::directoryservice::DirectoryService;
13use snix_store::pathinfoservice::PathInfoService;
14use std::num::NonZeroUsize;
15use std::sync::Arc;
16
17mod nar;
18mod outhash;
19
20#[derive(Clone)]
21pub struct AppState {
22    blob_service: Arc<dyn BlobService>,
23    directory_service: Arc<dyn DirectoryService>,
24    path_info_service: Arc<dyn PathInfoService>,
25
26    /// Lookup table from NarHash to [Node], necessary to populate the root_node
27    /// field of the PathInfo when processing the narinfo upload.
28    root_nodes: Arc<RwLock<LruCache<[u8; 32], Node>>>,
29}
30
31impl AppState {
32    pub fn new(
33        blob_service: Arc<dyn BlobService>,
34        directory_service: Arc<dyn DirectoryService>,
35        path_info_service: Arc<dyn PathInfoService>,
36        root_nodes_cache_capacity: NonZeroUsize,
37    ) -> Self {
38        Self {
39            blob_service,
40            directory_service,
41            path_info_service,
42            root_nodes: Arc::new(RwLock::new(LruCache::new(root_nodes_cache_capacity))),
43        }
44    }
45}
46
47pub fn gen_router(priority: u64) -> Router<AppState> {
48    #[cfg(feature = "otlp")]
49    let metrics_meter = opentelemetry::global::meter("nar-bridge");
50    #[cfg(feature = "otlp")]
51    let metrics_layer = opentelemetry_instrumentation_tower::HTTPMetricsLayerBuilder::builder()
52        .with_meter(metrics_meter)
53        .build()
54        .unwrap();
55
56    let router = Router::new()
57        .route("/", get(root))
58        .route("/nar/{nar_str}", get(StatusCode::NOT_FOUND))
59        .route("/nar/{nar_str}", head(nar::head_root_nodes))
60        .route("/nar/{nar_str}", put(nar::put))
61        .route("/nar/snix-castore/{root_node_enc}", get(nar::get_head))
62        .route("/nar/snix-castore/{root_node_enc}", head(nar::get_head))
63        .route("/{outhash_str}", get(outhash::get))
64        .route("/{outhash_str}", head(outhash::head))
65        .route("/{outhash_str}", put(outhash::put))
66        .route("/nix-cache-info", get(move || nix_cache_info(priority)));
67
68    let router = router.layer(tower_http::compression::CompressionLayer::new());
69
70    #[cfg(feature = "otlp")]
71    return router.layer(metrics_layer);
72    #[cfg(not(feature = "otlp"))]
73    return router;
74}
75
76async fn root() -> &'static str {
77    "Hello from nar-bridge"
78}
79
80async fn nix_cache_info(priority: u64) -> impl IntoResponse {
81    (
82        [("Content-Type", nix_http::MIME_TYPE_CACHE_INFO)],
83        format!("StoreDir: /nix/store\nWantMassQuery: 1\nPriority: {priority}\n"),
84    )
85}