use clap::Subcommand; #[derive(Subcommand)] pub enum SmbServerCommand { #[command(name = "smb-start")] Start { #[arg(short, long, default_value = "4445")] port: u16, #[arg(short, long, default_value = "/Users/accusys/momentry/var/sftpgo/data/demo")] root: String, #[arg(short, long, default_value = "markbase")] share_name: String, #[arg(long)] read_only: bool, #[arg(short, long, value_parser = parse_user)] user: Vec<(String, String)>, #[arg(long)] s3: bool, #[arg(long)] s3_endpoint: Option, #[arg(long)] s3_bucket: Option, #[arg(long)] s3_access_key: Option, #[arg(long)] s3_secret_key: Option, #[arg(long, default_value = "us-east-1")] s3_region: String, }, } fn parse_user(s: &str) -> Result<(String, String), String> { let parts: Vec<&str> = s.split(':').collect(); if parts.len() != 2 { return Err(format!("Invalid user format '{}'. Expected 'name:password'", s)); } Ok((parts[0].to_string(), parts[1].to_string())) } pub async fn handle_smb_server_command(cmd: SmbServerCommand) -> anyhow::Result<()> { #[cfg(feature = "smb-server")] { match cmd { SmbServerCommand::Start { port, root, share_name, read_only, user, s3, s3_endpoint, s3_bucket, s3_access_key, s3_secret_key, s3_region, } => { use std::path::PathBuf; use smb_server::{Access, Share, SmbServer}; use tracing_subscriber::EnvFilter; let _ = tracing_subscriber::fmt() .with_env_filter( EnvFilter::try_from_default_env() .unwrap_or_else(|_| EnvFilter::new("info")), ) .try_init(); let addr: std::net::SocketAddr = format!("0.0.0.0:{}", port).parse()?; let root_path = PathBuf::from(&root); let vfs: Box = if s3 { let endpoint = s3_endpoint .ok_or_else(|| anyhow::anyhow!("--s3-endpoint required when --s3 is enabled"))?; let bucket = s3_bucket .ok_or_else(|| anyhow::anyhow!("--s3-bucket required when --s3 is enabled"))?; let access_key = s3_access_key .ok_or_else(|| anyhow::anyhow!("--s3-access-key required when --s3 is enabled"))?; let secret_key = s3_secret_key .ok_or_else(|| anyhow::anyhow!("--s3-secret-key required when --s3 is enabled"))?; log::info!("S3 backend: endpoint={}, bucket={}, region={}", endpoint, bucket, s3_region); Box::new(crate::vfs::s3_fs::S3Vfs::new( &endpoint, &s3_region, &bucket, &access_key, &secret_key, )?) } else { Box::new(crate::vfs::local_fs::LocalFs::new()) }; let backend = crate::vfs::smb_server_backend::VfsShareBackend::new(vfs, root_path) .read_only(read_only); let users: Vec<(String, String)> = if user.is_empty() { vec![("demo".to_string(), "demo123".to_string())] } else { user }; let mut builder = SmbServer::builder().listen(addr); for (name, password) in &users { builder = builder.user(name, password); } let mut share = Share::new(&share_name, backend); for (name, _) in &users { share = share.user(name, Access::ReadWrite); } let server = builder.share(share).build()?; let user_list: Vec<&str> = users.iter().map(|(n, _)| n.as_str()).collect(); log::info!("SMB server listening on {}", addr); log::info!("Share '{}' at root: {}", share_name, root); log::info!("Users: {}", user_list.join(", ")); server.serve().await?; } } } #[cfg(not(feature = "smb-server"))] { let _ = cmd; anyhow::bail!("SMB server support not enabled. Build with --features smb-server"); } Ok(()) }