use clap::Subcommand; use data_encoding::BASE64; use nix_compat::derivation::Derivation; use nix_compat::derivation::Output; use nix_compat::nixhash::HashAlgo; use nix_compat::nixhash::NixHash; use nix_compat::nixhash::NixHashWithMode; use std::path::PathBuf; use tracing_subscriber::prelude::*; use tvix_store::blobservice::SledBlobService; use tvix_store::directoryservice::SledDirectoryService; use tvix_store::import::ingest_path; use tvix_store::nar::NARCalculationService; use tvix_store::nar::NonCachingNARCalculationService; use tvix_store::pathinfoservice::SledPathInfoService; use tvix_store::proto::blob_service_server::BlobServiceServer; use tvix_store::proto::directory_service_server::DirectoryServiceServer; use tvix_store::proto::path_info_service_server::PathInfoServiceServer; use tvix_store::proto::GRPCBlobServiceWrapper; use tvix_store::proto::GRPCDirectoryServiceWrapper; use tvix_store::proto::GRPCPathInfoServiceWrapper; #[cfg(feature = "reflection")] use tvix_store::proto::FILE_DESCRIPTOR_SET; use clap::Parser; use tonic::{transport::Server, Result}; use tracing::{info, Level}; #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Cli { /// Whether to log in JSON #[arg(long)] json: bool, #[arg(long)] log_level: Option, #[command(subcommand)] command: Commands, } #[derive(Subcommand)] enum Commands { /// Runs the tvix-store daemon. Daemon { #[arg(long, short = 'l')] listen_address: Option, }, /// Imports a list of paths into the store (not using the daemon) Import { #[clap(value_name = "PATH")] paths: Vec, }, } #[tokio::main] async fn main() -> Result<(), Box> { let cli = Cli::parse(); // configure log settings let level = cli.log_level.unwrap_or(Level::INFO); let subscriber = tracing_subscriber::registry() .with(if cli.json { Some( tracing_subscriber::fmt::Layer::new() .with_writer(std::io::stdout.with_max_level(level)) .json(), ) } else { None }) .with(if !cli.json { Some( tracing_subscriber::fmt::Layer::new() .with_writer(std::io::stdout.with_max_level(level)) .pretty(), ) } else { None }); tracing::subscriber::set_global_default(subscriber).expect("Unable to set global subscriber"); // initialize stores let mut blob_service = SledBlobService::new("blobs.sled".into())?; let mut directory_service = SledDirectoryService::new("directories.sled".into())?; let path_info_service = SledPathInfoService::new("pathinfo.sled".into())?; match cli.command { Commands::Daemon { listen_address } => { let listen_address = listen_address .unwrap_or_else(|| "[::]:8000".to_string()) .parse() .unwrap(); let mut server = Server::builder(); let nar_calculation_service = NonCachingNARCalculationService::new( blob_service.clone(), directory_service.clone(), ); #[allow(unused_mut)] let mut router = server .add_service(BlobServiceServer::new(GRPCBlobServiceWrapper::from( blob_service, ))) .add_service(DirectoryServiceServer::new( GRPCDirectoryServiceWrapper::from(directory_service), )) .add_service(PathInfoServiceServer::new(GRPCPathInfoServiceWrapper::new( path_info_service, nar_calculation_service, ))); #[cfg(feature = "reflection")] { let reflection_svc = tonic_reflection::server::Builder::configure() .register_encoded_file_descriptor_set(FILE_DESCRIPTOR_SET) .build()?; router = router.add_service(reflection_svc); } info!("tvix-store listening on {}", listen_address); router.serve(listen_address).await?; } Commands::Import { paths } => { let nar_calculation_service = NonCachingNARCalculationService::new( blob_service.clone(), directory_service.clone(), ); for path in paths { let root_node = ingest_path(&mut blob_service, &mut directory_service, &path)?; let nar_hash = NixHashWithMode::Recursive(NixHash::new( HashAlgo::Sha256, nar_calculation_service .calculate_nar(&root_node)? .1 .to_vec(), )); let mut drv = Derivation::default(); drv.outputs.insert( "out".to_string(), Output { path: "".to_string(), hash_with_mode: Some(nar_hash), }, ); drv.calculate_output_paths( path.file_name() .expect("path must not be ..") .to_str() .expect("path must be valid unicode"), // Note the derivation_or_fod_hash argument is *unused* for FODs, so it doesn't matter what we pass here. &NixHash::new(HashAlgo::Sha256, vec![]), )?; println!("{}", drv.outputs.get("out").unwrap().path); match root_node { tvix_store::proto::node::Node::Directory(directory_node) => { info!( path = ?path, name = directory_node.name, digest = BASE64.encode(&directory_node.digest), "import successful", ) } tvix_store::proto::node::Node::File(file_node) => { info!( path = ?path, name = file_node.name, digest = BASE64.encode(&file_node.digest), "import successful" ) } tvix_store::proto::node::Node::Symlink(symlink_node) => { info!( path = ?path, name = symlink_node.name, target = symlink_node.target, "import successful" ) } } } } }; Ok(()) }