feat(cli): add snix-castore utility
This is a small utility that allows ingesting a given path or .tar file content into the snix-castore and returns the B3Digest of the root node. Another subcommand takes this hash to mount the content back as a virtiofs or FUSE drive. This works as-is, but I discovered issue #107 while working on it. Change-Id: I11df73e39ab0db6f3868effab9bde4f090eadcb5 Reviewed-on: https://cl.snix.dev/c/snix/+/30293 Tested-by: besadii Reviewed-by: Florian Klink <flokli@flokli.de>
This commit is contained in:
parent
6118142b21
commit
ff72278529
2 changed files with 228 additions and 0 deletions
|
|
@ -13623,6 +13623,13 @@ rec {
|
|||
crateName = "snix-castore";
|
||||
version = "0.1.0";
|
||||
edition = "2024";
|
||||
crateBin = [
|
||||
{
|
||||
name = "snix-castore";
|
||||
path = "src/bin/snix-castore.rs";
|
||||
requiredFeatures = [ ];
|
||||
}
|
||||
];
|
||||
src = lib.cleanSourceWith { filter = sourceFilter; src = ./castore; };
|
||||
libName = "snix_castore";
|
||||
dependencies = [
|
||||
|
|
|
|||
221
snix/castore/src/bin/snix-castore.rs
Normal file
221
snix/castore/src/bin/snix-castore.rs
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
use clap::{Parser, Subcommand};
|
||||
#[cfg(feature = "fuse")]
|
||||
use snix_castore::fs::fuse::FuseDaemon;
|
||||
#[cfg(feature = "virtiofs")]
|
||||
use snix_castore::fs::virtiofs::start_virtiofs_daemon;
|
||||
#[cfg(feature = "fs")]
|
||||
use snix_castore::fs::SnixStoreFs;
|
||||
use snix_castore::import::{archive::ingest_archive, fs::ingest_path};
|
||||
#[cfg(any(feature = "fuse", feature = "virtiofs"))]
|
||||
use snix_castore::B3Digest;
|
||||
use snix_castore::Node;
|
||||
use std::error::Error;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use tokio::fs::{self, File};
|
||||
use tokio_tar::Archive;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
/// Ingest a folder or tar archive and return its B3Digest
|
||||
Ingest {
|
||||
/// Path of the folder or tar archive to import
|
||||
#[arg(value_name = "INPUT")]
|
||||
input: PathBuf,
|
||||
|
||||
/// Address of the blob service
|
||||
#[arg(
|
||||
long,
|
||||
env = "BLOB_SERVICE_ADDR",
|
||||
default_value = "grpc+http://[::1]:8000"
|
||||
)]
|
||||
blob_service_addr: String,
|
||||
|
||||
/// Address of the directory service
|
||||
#[arg(
|
||||
long,
|
||||
env = "DIRECTORY_SERVICE_ADDR",
|
||||
default_value = "grpc+http://[::1]:8000"
|
||||
)]
|
||||
directory_service_addr: String,
|
||||
},
|
||||
|
||||
#[cfg(feature = "fuse")]
|
||||
/// Mount a folder using its B3Digest with FUSE
|
||||
Mount {
|
||||
/// B3Digest of the folder to mount (output of `snix-castore ingest`)
|
||||
#[arg(value_name = "DIGEST")]
|
||||
digest: String,
|
||||
|
||||
/// Path to the mount point for FUSE
|
||||
#[arg(value_name = "OUTPUT")]
|
||||
output: PathBuf,
|
||||
|
||||
/// Address of the blob service
|
||||
#[arg(
|
||||
long,
|
||||
env = "BLOB_SERVICE_ADDR",
|
||||
default_value = "grpc+http://[::1]:8000"
|
||||
)]
|
||||
blob_service_addr: String,
|
||||
|
||||
/// Address of the directory service
|
||||
#[arg(
|
||||
long,
|
||||
env = "DIRECTORY_SERVICE_ADDR",
|
||||
default_value = "grpc+http://[::1]:8000"
|
||||
)]
|
||||
directory_service_addr: String,
|
||||
},
|
||||
|
||||
#[cfg(feature = "virtiofs")]
|
||||
/// Expose a folder using its B3Digest through a Virtiofs daemon
|
||||
Virtiofs {
|
||||
/// B3Digest of the folder to expose (output of `snix-castore ingest`)
|
||||
#[arg(value_name = "DIGEST")]
|
||||
digest: String,
|
||||
|
||||
/// Path to the virtiofs socket
|
||||
#[arg(value_name = "OUTPUT")]
|
||||
output: PathBuf,
|
||||
|
||||
/// Address of the blob service
|
||||
#[arg(
|
||||
long,
|
||||
env = "BLOB_SERVICE_ADDR",
|
||||
default_value = "grpc+http://[::1]:8000"
|
||||
)]
|
||||
blob_service_addr: String,
|
||||
|
||||
/// Address of the directory service
|
||||
#[arg(
|
||||
long,
|
||||
env = "DIRECTORY_SERVICE_ADDR",
|
||||
default_value = "grpc+http://[::1]:8000"
|
||||
)]
|
||||
directory_service_addr: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let cli = Cli::parse();
|
||||
let tracing_handle = {
|
||||
let mut builder = snix_tracing::TracingBuilder::default();
|
||||
builder = builder.enable_progressbar();
|
||||
builder.build()?
|
||||
};
|
||||
tokio::select! {
|
||||
res = tokio::signal::ctrl_c() => {
|
||||
res?;
|
||||
if let Err(e) = tracing_handle.shutdown().await {
|
||||
eprintln!("failed to shutdown tracing: {e}");
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
res = async {
|
||||
match cli.command {
|
||||
Commands::Ingest {
|
||||
input,
|
||||
blob_service_addr,
|
||||
directory_service_addr,
|
||||
} => {
|
||||
let blob_service = snix_castore::blobservice::from_addr(&blob_service_addr).await?;
|
||||
let directory_service =
|
||||
snix_castore::directoryservice::from_addr(&directory_service_addr).await?;
|
||||
let metadata = fs::metadata(&input).await?;
|
||||
let node = if metadata.is_dir() {
|
||||
ingest_path::<_, _, _, &[u8]>(&blob_service, &directory_service, &input, None)
|
||||
.await?
|
||||
} else {
|
||||
let file = File::open(&input).await?;
|
||||
let archive_instance = Archive::new(file);
|
||||
ingest_archive(blob_service.clone(), &directory_service, archive_instance).await?
|
||||
};
|
||||
let digest = match node {
|
||||
Node::Directory { digest, .. } => digest,
|
||||
_ => return Err("Expected a directory node".into()),
|
||||
};
|
||||
let mut stdout = tracing_handle.get_stdout_writer();
|
||||
writeln!(stdout, "{digest}")?;
|
||||
}
|
||||
#[cfg(feature = "fuse")]
|
||||
Commands::Mount {
|
||||
digest,
|
||||
output,
|
||||
blob_service_addr,
|
||||
directory_service_addr,
|
||||
} => {
|
||||
let blob_service = snix_castore::blobservice::from_addr(&blob_service_addr).await?;
|
||||
let directory_service =
|
||||
snix_castore::directoryservice::from_addr(&directory_service_addr).await?;
|
||||
let digest: B3Digest = digest.parse()?;
|
||||
let root_nodes_provider = directory_service
|
||||
.get(&digest)
|
||||
.await?
|
||||
.ok_or("Root nodes provider not found")?;
|
||||
let fuse_daemon = tokio::task::spawn_blocking(move || {
|
||||
let fs = SnixStoreFs::new(
|
||||
blob_service,
|
||||
directory_service,
|
||||
root_nodes_provider,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
FuseDaemon::new(fs, &output, 4, true)
|
||||
})
|
||||
.await??;
|
||||
tokio::spawn({
|
||||
let fuse_daemon = fuse_daemon.clone();
|
||||
async move {
|
||||
tokio::signal::ctrl_c().await.unwrap();
|
||||
tokio::task::spawn_blocking(move || fuse_daemon.unmount()).await??;
|
||||
Ok::<_, std::io::Error>(())
|
||||
}
|
||||
});
|
||||
tokio::task::spawn_blocking(move || fuse_daemon.wait()).await?;
|
||||
}
|
||||
#[cfg(feature = "virtiofs")]
|
||||
Commands::Virtiofs {
|
||||
digest,
|
||||
output,
|
||||
blob_service_addr,
|
||||
directory_service_addr,
|
||||
} => {
|
||||
let blob_service = snix_castore::blobservice::from_addr(&blob_service_addr).await?;
|
||||
let directory_service =
|
||||
snix_castore::directoryservice::from_addr(&directory_service_addr).await?;
|
||||
let digest: B3Digest = digest.parse()?;
|
||||
let root_nodes_provider = directory_service
|
||||
.get(&digest)
|
||||
.await?
|
||||
.ok_or("Root nodes provider not found")?;
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let fs = SnixStoreFs::new(
|
||||
blob_service,
|
||||
directory_service,
|
||||
root_nodes_provider,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
start_virtiofs_daemon(fs, &output)
|
||||
})
|
||||
.await??;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
} => {
|
||||
if let Err(e) = tracing_handle.shutdown().await {
|
||||
eprintln!("failed to shutdown tracing: {e}");
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue