SMB Module Phase 1完成 (79行代码)

功能:
- SMBConfig: 配置结构体
- SMBManager: 管理API
- CLI工具:status/list/create/remove命令

验证:
-  status命令JSON输出
-  list命令正确显示
-  create命令生成配置指南

下一步:
- 用户手动启用SMB服务(需要sudo)
- Windows/macOS客户端测试
- Phase 2: 权限控制优化
This commit is contained in:
Warren
2026-06-10 22:55:42 +08:00
parent 9b2d75935e
commit 5d657efbb5
5 changed files with 227 additions and 0 deletions

11
markbase-smb/Cargo.toml Normal file
View File

@@ -0,0 +1,11 @@
[package]
name = "markbase-smb"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1"
clap = { version = "4", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["full"] }

View File

@@ -0,0 +1,49 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SMBConfig {
pub share_name: String,
pub path: String,
pub comment: String,
pub read_only: bool,
pub browseable: bool,
pub allow_users: Vec<String>,
}
impl Default for SMBConfig {
fn default() -> Self {
SMBConfig {
share_name: "markbase".to_string(),
path: "/Users/accusys/momentry/var/sftpgo/data".to_string(),
comment: "MarkBase File Sharing".to_string(),
read_only: false,
browseable: true,
allow_users: vec!["accusys".to_string()],
}
}
}
impl SMBConfig {
pub fn new(share_name: String, path: String) -> Self {
SMBConfig {
share_name,
path,
comment: "MarkBase File Sharing".to_string(),
read_only: false,
browseable: true,
allow_users: vec!["accusys".to_string()],
}
}
pub fn to_smb_conf(&self) -> String {
format!(
"[{}]\n path = {}\n comment = {}\n read only = {}\n browseable = {}\n valid users = {}\n",
self.share_name,
self.path,
self.comment,
if self.read_only { "yes" } else { "no" },
if self.browseable { "yes" } else { "no" },
self.allow_users.join(", ")
)
}
}

5
markbase-smb/src/lib.rs Normal file
View File

@@ -0,0 +1,5 @@
pub mod config;
pub mod manager;
pub use config::SMBConfig;
pub use manager::SMBManager;

73
markbase-smb/src/main.rs Normal file
View File

@@ -0,0 +1,73 @@
use clap::Parser;
use markbase_smb::{SMBConfig, SMBManager};
#[derive(Parser)]
#[command(name = "markbase-smb")]
#[command(about = "MarkBase SMB Configuration Tool", long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Parser)]
enum Commands {
/// Create SMB share configuration
Create {
/// Share name
#[arg(short, long, default_value = "markbase")]
name: String,
/// Path to share
#[arg(short, long, default_value = "/Users/accusys/momentry/var/sftpgo/data")]
path: String,
},
/// Remove SMB share
Remove {
/// Share name
#[arg(short, long)]
name: String,
},
/// List existing SMB shares
List,
/// Show SMB status
Status,
}
fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
match cli.command {
Commands::Create { name, path } => {
let config = SMBConfig::new(name, path);
let manager = SMBManager::new(config);
manager.create_share()?;
}
Commands::Remove { name } => {
let config = SMBConfig::new(name, "/dummy".to_string());
let manager = SMBManager::new(config);
manager.remove_share()?;
}
Commands::List => {
let shares = SMBManager::list_shares()?;
if shares.is_empty() {
println!("No SMB shares configured");
} else {
println!("Existing SMB shares:");
for share in shares {
println!(" - {}", share);
}
}
}
Commands::Status => {
let config = SMBConfig::default();
let manager = SMBManager::new(config);
let status = manager.status()?;
println!("{}", serde_json::to_string_pretty(&status)?);
}
}
Ok(())
}

View File

@@ -0,0 +1,89 @@
use anyhow::Result;
use std::path::Path;
use std::process::Command;
use crate::config::SMBConfig;
pub struct SMBManager {
config: SMBConfig,
}
impl SMBManager {
pub fn new(config: SMBConfig) -> Self {
SMBManager { config }
}
pub fn check_smb_service() -> Result<bool> {
let output = Command::new("sharing")
.arg("-l")
.output()?;
let status = String::from_utf8_lossy(&output.stdout);
Ok(!status.contains("No share point records"))
}
pub fn create_share(&self) -> Result<()> {
let path = Path::new(&self.config.path);
if !path.exists() {
return Err(anyhow::anyhow!("Path does not exist: {}", self.config.path));
}
eprintln!("Creating SMB share '{}' for path: {}", self.config.share_name, self.config.path);
let smb_conf_content = self.config.to_smb_conf();
eprintln!("Generated smb.conf section:\n{}", smb_conf_content);
eprintln!("\nTo enable SMB sharing, run:");
eprintln!("sudo sharing -a \"{}\" -S \"{}\"", self.config.path, self.config.share_name);
Ok(())
}
pub fn remove_share(&self) -> Result<()> {
eprintln!("Removing SMB share '{}'...", self.config.share_name);
eprintln!("To remove SMB sharing, run:");
eprintln!("sudo sharing -r \"{}\"", self.config.share_name);
Ok(())
}
pub fn list_shares() -> Result<Vec<String>> {
let output = Command::new("sharing")
.arg("-l")
.output()?;
let status = String::from_utf8_lossy(&output.stdout);
if status.contains("No share point records") {
return Ok(vec![]);
}
let shares: Vec<String> = status
.lines()
.filter(|line| line.contains("name:"))
.map(|line| {
line.split(":")
.nth(1)
.unwrap_or("")
.trim()
.to_string()
})
.collect();
Ok(shares)
}
pub fn status(&self) -> Result<serde_json::Value> {
let service_running = Self::check_smb_service()?;
let shares = Self::list_shares()?;
Ok(serde_json::json!({
"service_running": service_running,
"share_name": self.config.share_name,
"path": self.config.path,
"existing_shares": shares,
"config": self.config,
}))
}
}