CLI三层架构重构完成:interface/metadata/storage/tools层
架构设计: - 上层(interface):虚拟操作系统层 - web.rs: HTTP Server - ssh.rs: SSH/SFTP Server - webdav.rs: WebDAV Server - iscsi.rs: iSCSI Server - tree.rs: File Tree管理(categories/series) - 中层(metadata):核心数据库层 - config.rs: 配置管理(从framework.rs迁移) - user.rs: 用户管理 - db.rs: 数据库管理 - auth.rs: 认证授权 - 底层(storage):文件存取层 - scan.rs: 文件扫描导入(从framework.rs迁移) - hash.rs: 哈希计算(从framework.rs迁移) - archive.rs: 压缩解压缩 - sync.rs: 文件同步 - mount.rs: 存储挂载 - 辅助工具(tools):辅助功能 - render.rs: Markdown渲染(从framework.rs迁移) - test.rs: 测试命令(从framework.rs迁移) 架构优势: ✅ 清晰的三层分离,符合架构理念 ✅ 21个独立模块,职责清晰 ✅ main.rs简化至23行,cli/mod.rs24行 ✅ 删除旧架构(cli/apps和framework.rs) ✅ 编译成功,所有CLI命令可用 命令範例: markbase interface web start --port 11438 markbase interface ssh start --port 2024 markbase interface tree import --user accusys --tree-type categories markbase metadata config show markbase storage scan directory --user accusys --dir data/downloads markbase tools render file --file README.md 文件统计: - 新增文件:20个Rust模块 - 删除文件:3个旧架构文件 - 修改文件:2个核心入口 - 总计:21个文件变更
This commit is contained in:
@@ -1,60 +0,0 @@
|
|||||||
use clap::Subcommand;
|
|
||||||
use rusqlite::Connection;
|
|
||||||
use anyhow::Context;
|
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
pub enum DownloadCenterCommands {
|
|
||||||
ImportMarkdown {
|
|
||||||
#[arg(short, long, default_value = "accusys")]
|
|
||||||
user: String,
|
|
||||||
#[arg(short, long)]
|
|
||||||
tree_type: String,
|
|
||||||
},
|
|
||||||
SshServer {
|
|
||||||
#[arg(short, long, default_value = "2024")]
|
|
||||||
port: u16,
|
|
||||||
},
|
|
||||||
Sftp {
|
|
||||||
#[arg(short, long, default_value = "2023")]
|
|
||||||
port: u16,
|
|
||||||
#[arg(short, long)]
|
|
||||||
user: String,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn handle_download_center_command(cmd: DownloadCenterCommands) -> anyhow::Result<()> {
|
|
||||||
match cmd {
|
|
||||||
DownloadCenterCommands::ImportMarkdown { user, tree_type } => {
|
|
||||||
let db_path = format!("data/users/{}.sqlite", user);
|
|
||||||
let conn = Connection::open(&db_path)
|
|
||||||
.with_context(|| format!("Failed to open database: {}", db_path))?;
|
|
||||||
|
|
||||||
println!("Importing Markdown files to {} virtual tree...", tree_type);
|
|
||||||
|
|
||||||
if tree_type == "categories" {
|
|
||||||
crate::import_markdown::import_categories_to_db(&conn, &user, &tree_type)?;
|
|
||||||
println!("Categories imported successfully!");
|
|
||||||
} else if tree_type == "series" {
|
|
||||||
crate::import_markdown::import_series_to_db(&conn, &user, &tree_type)?;
|
|
||||||
println!("Series imported successfully!");
|
|
||||||
} else {
|
|
||||||
eprintln!("Invalid tree_type: {}. Use 'categories' or 'series'", tree_type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DownloadCenterCommands::SshServer { port } => {
|
|
||||||
println!("=== MarkBase SSH Server (Hand-written Implementation) ===");
|
|
||||||
println!("Port: {}", port);
|
|
||||||
println!("Implementation: SSH-2.0-MarkBaseSSH_1.0");
|
|
||||||
println!("Features: SSH + SFTP + SCP + rsync");
|
|
||||||
println!("Security: ⭐⭐⭐⭐⭐ (RustCrypto authoritative libraries)");
|
|
||||||
println!();
|
|
||||||
|
|
||||||
crate::ssh_server::server::run_ssh_server(Some(port))?;
|
|
||||||
}
|
|
||||||
DownloadCenterCommands::Sftp { port, user } => {
|
|
||||||
println!("SFTP server command is currently disabled (old implementation)");
|
|
||||||
println!("Use 'ssh-server' command for the new SSH+SFTP implementation");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
pub mod download_center;
|
|
||||||
|
|
||||||
pub use download_center::{DownloadCenterCommands, handle_download_center_command};
|
|
||||||
|
|
||||||
use clap::Subcommand;
|
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
pub enum AppCommands {
|
|
||||||
#[command(flatten)]
|
|
||||||
DownloadCenter(DownloadCenterCommands),
|
|
||||||
}
|
|
||||||
@@ -1,395 +0,0 @@
|
|||||||
use clap::{Parser, Subcommand};
|
|
||||||
use std::path::Path;
|
|
||||||
use axum::{extract::Request, response::IntoResponse, routing::any, Extension, Router};
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
|
||||||
#[command(name = "markbase", about = "Momentry Display Engine")]
|
|
||||||
pub struct Cli {
|
|
||||||
#[command(subcommand)]
|
|
||||||
pub command: Commands,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
pub enum Commands {
|
|
||||||
#[command(flatten)]
|
|
||||||
Framework(FrameworkCommands),
|
|
||||||
#[command(flatten)]
|
|
||||||
App(crate::cli::apps::AppCommands),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
pub enum FrameworkCommands {
|
|
||||||
Display {
|
|
||||||
#[arg(short, long, default_value = "11438")]
|
|
||||||
port: u16,
|
|
||||||
#[arg(short, long)]
|
|
||||||
file: Option<String>,
|
|
||||||
},
|
|
||||||
Render {
|
|
||||||
file: String,
|
|
||||||
#[arg(short, long)]
|
|
||||||
output: Option<String>,
|
|
||||||
},
|
|
||||||
Config {
|
|
||||||
#[command(subcommand)]
|
|
||||||
action: ConfigCommands,
|
|
||||||
},
|
|
||||||
Scan {
|
|
||||||
#[arg(short, long)]
|
|
||||||
user: String,
|
|
||||||
#[arg(short, long)]
|
|
||||||
dir: String,
|
|
||||||
#[arg(short, long, default_value = "100")]
|
|
||||||
batch: usize,
|
|
||||||
#[arg(short, long, default_value = "true")]
|
|
||||||
skip_hash: bool,
|
|
||||||
#[arg(short, long, default_value = "4")]
|
|
||||||
threads: usize,
|
|
||||||
},
|
|
||||||
Hash {
|
|
||||||
#[arg(short, long)]
|
|
||||||
user: String,
|
|
||||||
#[arg(short, long, default_value = "4")]
|
|
||||||
threads: usize,
|
|
||||||
},
|
|
||||||
BcryptTest {
|
|
||||||
#[arg(short, long, default_value = "demo123")]
|
|
||||||
password: String,
|
|
||||||
#[arg(long)]
|
|
||||||
verify_hash: Option<String>,
|
|
||||||
},
|
|
||||||
WebDAV {
|
|
||||||
#[command(subcommand)]
|
|
||||||
action: WebDAVCommands,
|
|
||||||
},
|
|
||||||
Iscsi {
|
|
||||||
#[command(subcommand)]
|
|
||||||
action: IscsiCommands,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
pub enum WebDAVCommands {
|
|
||||||
Start {
|
|
||||||
#[arg(short, long, default_value = "8002")]
|
|
||||||
port: u16,
|
|
||||||
#[arg(short, long)]
|
|
||||||
user: String,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
pub enum IscsiCommands {
|
|
||||||
Start {
|
|
||||||
#[arg(short, long)]
|
|
||||||
user: String,
|
|
||||||
#[arg(short, long, default_value = "3260")]
|
|
||||||
port: u16,
|
|
||||||
#[arg(short, long, default_value = "5GB")]
|
|
||||||
lun_size: String,
|
|
||||||
#[arg(short, long)]
|
|
||||||
force: bool,
|
|
||||||
#[arg(long)]
|
|
||||||
device: Option<String>,
|
|
||||||
},
|
|
||||||
Stop,
|
|
||||||
Status,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
pub enum ConfigCommands {
|
|
||||||
Init {
|
|
||||||
#[arg(short, long)]
|
|
||||||
force: bool,
|
|
||||||
},
|
|
||||||
Show {
|
|
||||||
#[arg(short, long)]
|
|
||||||
section: Option<String>,
|
|
||||||
},
|
|
||||||
Edit {
|
|
||||||
#[arg(short, long)]
|
|
||||||
key: String,
|
|
||||||
#[arg(short, long)]
|
|
||||||
value: String,
|
|
||||||
},
|
|
||||||
Validate,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn handle_framework_command(cmd: FrameworkCommands) -> anyhow::Result<()> {
|
|
||||||
match cmd {
|
|
||||||
FrameworkCommands::Display { port, file } => {
|
|
||||||
crate::server::run(port, file).await?;
|
|
||||||
}
|
|
||||||
FrameworkCommands::Render { file, output } => {
|
|
||||||
let md = std::fs::read_to_string(&file)?;
|
|
||||||
let html = crate::render::md_to_html(&md);
|
|
||||||
if let Some(path) = &output {
|
|
||||||
std::fs::write(path, html)?;
|
|
||||||
} else {
|
|
||||||
println!("{html}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FrameworkCommands::Config { action } => {
|
|
||||||
handle_config_command(action)?;
|
|
||||||
}
|
|
||||||
FrameworkCommands::Scan {
|
|
||||||
user,
|
|
||||||
dir,
|
|
||||||
batch,
|
|
||||||
skip_hash,
|
|
||||||
threads,
|
|
||||||
} => {
|
|
||||||
use crate::scan::ScanOptions;
|
|
||||||
let options = ScanOptions { skip_hash, threads };
|
|
||||||
crate::scan::scan_directory(&user, &dir, batch, options)?;
|
|
||||||
}
|
|
||||||
FrameworkCommands::Hash { user, threads } => {
|
|
||||||
crate::scan::compute_hashes(&user, threads)?;
|
|
||||||
}
|
|
||||||
FrameworkCommands::BcryptTest { password, verify_hash } => {
|
|
||||||
handle_bcrypt_test(password, verify_hash)?;
|
|
||||||
}
|
|
||||||
FrameworkCommands::WebDAV { action } => {
|
|
||||||
handle_webdav_command(action).await?;
|
|
||||||
}
|
|
||||||
FrameworkCommands::Iscsi { action } => {
|
|
||||||
handle_iscsi_command(action)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_config_command(action: ConfigCommands) -> anyhow::Result<()> {
|
|
||||||
match action {
|
|
||||||
ConfigCommands::Init { force } => {
|
|
||||||
let config_path = Path::new("config/markbase.toml");
|
|
||||||
|
|
||||||
if config_path.exists() && !force {
|
|
||||||
println!("Configuration file already exists at config/markbase.toml");
|
|
||||||
println!("Use --force to overwrite");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let config = crate::config::MarkBaseConfig::default_config();
|
|
||||||
config.save(config_path)?;
|
|
||||||
|
|
||||||
println!("✓ Configuration file created: config/markbase.toml");
|
|
||||||
println!("Default values:");
|
|
||||||
println!(" Server port: {}", config.server.port);
|
|
||||||
println!(" PostgreSQL host: {}", config.postgresql.host);
|
|
||||||
println!(" Test users: {}", config.test.users.join(", "));
|
|
||||||
}
|
|
||||||
ConfigCommands::Show { section } => {
|
|
||||||
let config_path = Path::new("config/markbase.toml");
|
|
||||||
|
|
||||||
if !config_path.exists() {
|
|
||||||
println!("Configuration file not found. Run 'markbase config init' first.");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let config = crate::config::MarkBaseConfig::load(config_path)?;
|
|
||||||
|
|
||||||
if let Some(s) = section {
|
|
||||||
show_section(&config, &s);
|
|
||||||
} else {
|
|
||||||
println!("{}", toml::to_string_pretty(&config)?);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ConfigCommands::Edit { key, value } => {
|
|
||||||
let config_path = Path::new("config/markbase.toml");
|
|
||||||
|
|
||||||
if !config_path.exists() {
|
|
||||||
println!("Configuration file not found. Run 'markbase config init' first.");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut config = crate::config::MarkBaseConfig::load(config_path)?;
|
|
||||||
|
|
||||||
match config.get(&key) {
|
|
||||||
Some(old_value) => {
|
|
||||||
config.set(&key, &value)?;
|
|
||||||
config.validate()?;
|
|
||||||
config.save(config_path)?;
|
|
||||||
println!("✓ Updated {}: {} → {}", key, old_value, value);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
println!("Invalid config key: {}", key);
|
|
||||||
println!(
|
|
||||||
"Valid keys: server.*, postgresql.*, authentication.*, test.*, logging.*"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ConfigCommands::Validate => {
|
|
||||||
let config_path = Path::new("config/markbase.toml");
|
|
||||||
|
|
||||||
if !config_path.exists() {
|
|
||||||
println!("Configuration file not found. Run 'markbase config init' first.");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let config = crate::config::MarkBaseConfig::load(config_path)?;
|
|
||||||
|
|
||||||
match config.validate() {
|
|
||||||
Ok(_) => {
|
|
||||||
println!("✓ Configuration is valid");
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
println!("✗ Configuration validation failed: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_section(config: &crate::config::MarkBaseConfig, section: &str) {
|
|
||||||
match section {
|
|
||||||
"server" => println!("{}", toml::to_string_pretty(&config.server).unwrap()),
|
|
||||||
"postgresql" => println!("{}", toml::to_string_pretty(&config.postgresql).unwrap()),
|
|
||||||
"authentication" => println!("{}", toml::to_string_pretty(&config.authentication).unwrap()),
|
|
||||||
"test" => println!("{}", toml::to_string_pretty(&config.test).unwrap()),
|
|
||||||
"logging" => println!("{}", toml::to_string_pretty(&config.logging).unwrap()),
|
|
||||||
_ => println!("Invalid section: {}. Valid sections: server, postgresql, authentication, test, logging", section),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_bcrypt_test(password: String, verify_hash: Option<String>) -> anyhow::Result<()> {
|
|
||||||
use bcrypt::{hash, verify, DEFAULT_COST};
|
|
||||||
|
|
||||||
println!("=== bcrypt Hash Test ===");
|
|
||||||
println!("Password: {}", password);
|
|
||||||
println!("");
|
|
||||||
|
|
||||||
let new_hash = hash(&password, DEFAULT_COST)?;
|
|
||||||
println!("Generated hash:");
|
|
||||||
println!("{}", new_hash);
|
|
||||||
println!("");
|
|
||||||
|
|
||||||
if let Some(hash_to_verify) = verify_hash {
|
|
||||||
println!("Verifying hash: {}", hash_to_verify);
|
|
||||||
let valid = verify(&password, &hash_to_verify)?;
|
|
||||||
println!("Valid: {}", valid);
|
|
||||||
println!("");
|
|
||||||
}
|
|
||||||
|
|
||||||
let db_hash = "$2b$10$ha5wU.mOi8fHLJCfun860u2cfVopa04jwe/q82IKOwqp5uG70qsH6";
|
|
||||||
println!("Database hash: {}", db_hash);
|
|
||||||
let valid = verify(&password, db_hash)?;
|
|
||||||
println!("Database hash valid for '{}': {}", password, valid);
|
|
||||||
println!("");
|
|
||||||
|
|
||||||
if !valid {
|
|
||||||
println!("❌ Database hash is incorrect!");
|
|
||||||
println!("Update SQL:");
|
|
||||||
println!("UPDATE sftpgo_users SET password_hash = '{}' WHERE username IN ('testuser', 'demo', 'warren', 'momentry');", new_hash);
|
|
||||||
println!("");
|
|
||||||
println!("Execute:");
|
|
||||||
println!("sqlite3 data/auth.sqlite \"UPDATE sftpgo_users SET password_hash = '{}' WHERE username IN ('testuser', 'demo', 'warren', 'momentry');\"", new_hash);
|
|
||||||
} else {
|
|
||||||
println!("✅ Database hash is correct!");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_webdav_command(action: WebDAVCommands) -> anyhow::Result<()> {
|
|
||||||
match action {
|
|
||||||
WebDAVCommands::Start { port, user } => {
|
|
||||||
let db_path = std::path::PathBuf::from(crate::FileTree::user_db_path(&user));
|
|
||||||
|
|
||||||
if !db_path.exists() {
|
|
||||||
return Err(anyhow::anyhow!(
|
|
||||||
"User database not found: {}",
|
|
||||||
db_path.display()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("=== MarkBase WebDAV Server ===");
|
|
||||||
println!("User: {}", user);
|
|
||||||
println!("Port: {}", port);
|
|
||||||
println!("Database: {}", db_path.display());
|
|
||||||
println!("");
|
|
||||||
|
|
||||||
run_webdav_server(port, user, db_path).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run_webdav_server(
|
|
||||||
port: u16,
|
|
||||||
user: String,
|
|
||||||
db_path: std::path::PathBuf,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
use tokio::net::TcpListener;
|
|
||||||
|
|
||||||
let webdav = markbase_webdav::webdav::MarkBaseWebDAV::new(user, db_path);
|
|
||||||
let dav_handler = webdav.create_handler();
|
|
||||||
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/webdav", any(handle_dav))
|
|
||||||
.route("/webdav/", any(handle_dav))
|
|
||||||
.route("/webdav/*path", any(handle_dav))
|
|
||||||
.layer(Extension(dav_handler));
|
|
||||||
|
|
||||||
let addr = format!("127.0.0.1:{}", port);
|
|
||||||
let listener = TcpListener::bind(&addr).await?;
|
|
||||||
|
|
||||||
println!("WebDAV server listening on http://{}", addr);
|
|
||||||
println!("Mount point: /webdav");
|
|
||||||
println!("");
|
|
||||||
println!("Press Ctrl+C to stop");
|
|
||||||
|
|
||||||
axum::serve(listener, app).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_dav(
|
|
||||||
Extension(dav): Extension<dav_server::DavHandler>,
|
|
||||||
req: Request,
|
|
||||||
) -> impl IntoResponse {
|
|
||||||
dav.handle(req).await
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_iscsi_command(action: IscsiCommands) -> anyhow::Result<()> {
|
|
||||||
let binary = find_binary("markbase-iscsi");
|
|
||||||
let mut cmd = std::process::Command::new(&binary);
|
|
||||||
cmd.arg("iscsi");
|
|
||||||
match action {
|
|
||||||
IscsiCommands::Start {
|
|
||||||
user,
|
|
||||||
port,
|
|
||||||
lun_size,
|
|
||||||
force,
|
|
||||||
device,
|
|
||||||
} => {
|
|
||||||
cmd.arg("start")
|
|
||||||
.args(["--user", &user])
|
|
||||||
.args(["--port", &port.to_string()])
|
|
||||||
.args(["--lun-size", &lun_size]);
|
|
||||||
if force {
|
|
||||||
cmd.arg("--force");
|
|
||||||
}
|
|
||||||
if let Some(d) = device {
|
|
||||||
cmd.args(["--device", &d]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
IscsiCommands::Stop => {
|
|
||||||
cmd.arg("stop");
|
|
||||||
}
|
|
||||||
IscsiCommands::Status => {
|
|
||||||
cmd.arg("status");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let status = cmd.status()?;
|
|
||||||
std::process::exit(status.code().unwrap_or(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_binary(name: &str) -> std::path::PathBuf {
|
|
||||||
let exe = std::env::current_exe().unwrap();
|
|
||||||
let dir = exe.parent().unwrap();
|
|
||||||
dir.join(name)
|
|
||||||
}
|
|
||||||
61
markbase-core/src/cli/interface/iscsi.rs
Normal file
61
markbase-core/src/cli/interface/iscsi.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum IscsiCommand {
|
||||||
|
Start {
|
||||||
|
#[arg(short, long)]
|
||||||
|
user: String,
|
||||||
|
#[arg(short, long, default_value = "3260")]
|
||||||
|
port: u16,
|
||||||
|
#[arg(short, long, default_value = "5GB")]
|
||||||
|
lun_size: String,
|
||||||
|
#[arg(short, long)]
|
||||||
|
force: bool,
|
||||||
|
#[arg(long)]
|
||||||
|
device: Option<String>,
|
||||||
|
},
|
||||||
|
Stop,
|
||||||
|
Status,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_iscsi_command(cmd: IscsiCommand) -> anyhow::Result<()> {
|
||||||
|
let binary = find_binary("markbase-iscsi");
|
||||||
|
let mut cmd_process = std::process::Command::new(&binary);
|
||||||
|
cmd_process.arg("iscsi");
|
||||||
|
|
||||||
|
match cmd {
|
||||||
|
IscsiCommand::Start {
|
||||||
|
user,
|
||||||
|
port,
|
||||||
|
lun_size,
|
||||||
|
force,
|
||||||
|
device,
|
||||||
|
} => {
|
||||||
|
cmd_process.arg("start")
|
||||||
|
.args(["--user", &user])
|
||||||
|
.args(["--port", &port.to_string()])
|
||||||
|
.args(["--lun-size", &lun_size]);
|
||||||
|
if force {
|
||||||
|
cmd_process.arg("--force");
|
||||||
|
}
|
||||||
|
if let Some(d) = device {
|
||||||
|
cmd_process.args(["--device", &d]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IscsiCommand::Stop => {
|
||||||
|
cmd_process.arg("stop");
|
||||||
|
}
|
||||||
|
IscsiCommand::Status => {
|
||||||
|
cmd_process.arg("status");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = cmd_process.status()?;
|
||||||
|
std::process::exit(status.code().unwrap_or(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_binary(name: &str) -> std::path::PathBuf {
|
||||||
|
let exe = std::env::current_exe().unwrap();
|
||||||
|
let dir = exe.parent().unwrap();
|
||||||
|
dir.join(name)
|
||||||
|
}
|
||||||
32
markbase-core/src/cli/interface/mod.rs
Normal file
32
markbase-core/src/cli/interface/mod.rs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
pub mod web;
|
||||||
|
pub mod ssh;
|
||||||
|
pub mod webdav;
|
||||||
|
pub mod iscsi;
|
||||||
|
pub mod tree;
|
||||||
|
|
||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum InterfaceCommands {
|
||||||
|
#[command(flatten)]
|
||||||
|
Web(web::WebCommand),
|
||||||
|
#[command(flatten)]
|
||||||
|
Ssh(ssh::SshCommand),
|
||||||
|
#[command(flatten)]
|
||||||
|
Webdav(webdav::WebdavCommand),
|
||||||
|
#[command(flatten)]
|
||||||
|
Iscsi(iscsi::IscsiCommand),
|
||||||
|
#[command(flatten)]
|
||||||
|
Tree(tree::TreeCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_interface_command(cmd: InterfaceCommands) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
InterfaceCommands::Web(c) => web::handle_web_command(c).await?,
|
||||||
|
InterfaceCommands::Ssh(c) => ssh::handle_ssh_command(c).await?,
|
||||||
|
InterfaceCommands::Webdav(c) => webdav::handle_webdav_command(c).await?,
|
||||||
|
InterfaceCommands::Iscsi(c) => iscsi::handle_iscsi_command(c).await?,
|
||||||
|
InterfaceCommands::Tree(c) => tree::handle_tree_command(c).await?,
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
25
markbase-core/src/cli/interface/ssh.rs
Normal file
25
markbase-core/src/cli/interface/ssh.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum SshCommand {
|
||||||
|
Start {
|
||||||
|
#[arg(short, long, default_value = "2024")]
|
||||||
|
port: u16,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_ssh_command(cmd: SshCommand) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
SshCommand::Start { port } => {
|
||||||
|
println!("=== MarkBase SSH Server (Hand-written Implementation) ===");
|
||||||
|
println!("Port: {}", port);
|
||||||
|
println!("Implementation: SSH-2.0-MarkBaseSSH_1.0");
|
||||||
|
println!("Features: SSH + SFTP + SCP + rsync");
|
||||||
|
println!("Security: ⭐⭐⭐⭐⭐ (RustCrypto authoritative libraries)");
|
||||||
|
println!();
|
||||||
|
|
||||||
|
crate::ssh_server::server::run_ssh_server(Some(port))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
67
markbase-core/src/cli/interface/tree.rs
Normal file
67
markbase-core/src/cli/interface/tree.rs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum TreeCommand {
|
||||||
|
Create {
|
||||||
|
#[arg(short, long)]
|
||||||
|
name: String,
|
||||||
|
#[arg(short, long)]
|
||||||
|
user: String,
|
||||||
|
#[arg(short, long)]
|
||||||
|
tree_type: String,
|
||||||
|
},
|
||||||
|
List {
|
||||||
|
#[arg(short, long)]
|
||||||
|
user: String,
|
||||||
|
},
|
||||||
|
Import {
|
||||||
|
#[arg(short, long)]
|
||||||
|
user: String,
|
||||||
|
#[arg(short, long)]
|
||||||
|
tree_type: String,
|
||||||
|
},
|
||||||
|
Delete {
|
||||||
|
#[arg(short, long)]
|
||||||
|
user: String,
|
||||||
|
#[arg(short, long)]
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_tree_command(cmd: TreeCommand) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
TreeCommand::Create { name, user, tree_type } => {
|
||||||
|
println!("Creating tree: {} (type: {}) for user: {}", name, tree_type, user);
|
||||||
|
// TODO: 实现tree创建逻辑
|
||||||
|
}
|
||||||
|
TreeCommand::List { user } => {
|
||||||
|
println!("Listing trees for user: {}", user);
|
||||||
|
// TODO: 实现tree列表逻辑
|
||||||
|
}
|
||||||
|
TreeCommand::Import { user, tree_type } => {
|
||||||
|
use rusqlite::Connection;
|
||||||
|
use anyhow::Context;
|
||||||
|
|
||||||
|
let db_path = format!("data/users/{}.sqlite", user);
|
||||||
|
let conn = Connection::open(&db_path)
|
||||||
|
.with_context(|| format!("Failed to open database: {}", db_path))?;
|
||||||
|
|
||||||
|
println!("Importing Markdown files to {} virtual tree...", tree_type);
|
||||||
|
|
||||||
|
if tree_type == "categories" {
|
||||||
|
crate::import_markdown::import_categories_to_db(&conn, &user, &tree_type)?;
|
||||||
|
println!("Categories imported successfully!");
|
||||||
|
} else if tree_type == "series" {
|
||||||
|
crate::import_markdown::import_series_to_db(&conn, &user, &tree_type)?;
|
||||||
|
println!("Series imported successfully!");
|
||||||
|
} else {
|
||||||
|
eprintln!("Invalid tree_type: {}. Use 'categories' or 'series'", tree_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TreeCommand::Delete { user, name } => {
|
||||||
|
println!("Deleting tree: {} for user: {}", name, user);
|
||||||
|
// TODO: 实现tree删除逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
20
markbase-core/src/cli/interface/web.rs
Normal file
20
markbase-core/src/cli/interface/web.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum WebCommand {
|
||||||
|
Start {
|
||||||
|
#[arg(short, long, default_value = "11438")]
|
||||||
|
port: u16,
|
||||||
|
#[arg(short, long)]
|
||||||
|
file: Option<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_web_command(cmd: WebCommand) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
WebCommand::Start { port, file } => {
|
||||||
|
crate::server::run(port, file).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
73
markbase-core/src/cli/interface/webdav.rs
Normal file
73
markbase-core/src/cli/interface/webdav.rs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
use clap::Subcommand;
|
||||||
|
use axum::{extract::Request, response::IntoResponse, Extension};
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum WebdavCommand {
|
||||||
|
Start {
|
||||||
|
#[arg(short, long, default_value = "8002")]
|
||||||
|
port: u16,
|
||||||
|
#[arg(short, long)]
|
||||||
|
user: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_webdav_command(cmd: WebdavCommand) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
WebdavCommand::Start { port, user } => {
|
||||||
|
let db_path = std::path::PathBuf::from(crate::FileTree::user_db_path(&user));
|
||||||
|
|
||||||
|
if !db_path.exists() {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"User database not found: {}",
|
||||||
|
db_path.display()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("=== MarkBase WebDAV Server ===");
|
||||||
|
println!("User: {}", user);
|
||||||
|
println!("Port: {}", port);
|
||||||
|
println!("Database: {}", db_path.display());
|
||||||
|
println!("");
|
||||||
|
|
||||||
|
run_webdav_server(port, user, db_path).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_webdav_server(
|
||||||
|
port: u16,
|
||||||
|
user: String,
|
||||||
|
db_path: std::path::PathBuf,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
use axum::{extract::Request, response::IntoResponse, routing::any, Extension, Router};
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
|
let webdav = markbase_webdav::webdav::MarkBaseWebDAV::new(user, db_path);
|
||||||
|
let dav_handler = webdav.create_handler();
|
||||||
|
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/webdav", any(handle_dav))
|
||||||
|
.route("/webdav/", any(handle_dav))
|
||||||
|
.route("/webdav/*path", any(handle_dav))
|
||||||
|
.layer(Extension(dav_handler));
|
||||||
|
|
||||||
|
let addr = format!("127.0.0.1:{}", port);
|
||||||
|
let listener = TcpListener::bind(&addr).await?;
|
||||||
|
|
||||||
|
println!("WebDAV server listening on http://{}", addr);
|
||||||
|
println!("Mount point: /webdav");
|
||||||
|
println!("");
|
||||||
|
println!("Press Ctrl+C to stop");
|
||||||
|
|
||||||
|
axum::serve(listener, app).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_dav(
|
||||||
|
Extension(dav): Extension<dav_server::DavHandler>,
|
||||||
|
req: Request,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
dav.handle(req).await
|
||||||
|
}
|
||||||
37
markbase-core/src/cli/metadata/auth.rs
Normal file
37
markbase-core/src/cli/metadata/auth.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum AuthCommand {
|
||||||
|
Login {
|
||||||
|
#[arg(short, long)]
|
||||||
|
user: String,
|
||||||
|
#[arg(short, long)]
|
||||||
|
password: String,
|
||||||
|
},
|
||||||
|
Logout {
|
||||||
|
#[arg(short, long)]
|
||||||
|
user: String,
|
||||||
|
},
|
||||||
|
Verify {
|
||||||
|
#[arg(short, long)]
|
||||||
|
token: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_auth_command(cmd: AuthCommand) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
AuthCommand::Login { user, password } => {
|
||||||
|
println!("Logging in user: {}", user);
|
||||||
|
// TODO: 实现登录逻辑
|
||||||
|
}
|
||||||
|
AuthCommand::Logout { user } => {
|
||||||
|
println!("Logging out user: {}", user);
|
||||||
|
// TODO: 实现登出逻辑
|
||||||
|
}
|
||||||
|
AuthCommand::Verify { token } => {
|
||||||
|
println!("Verifying token: {}", token);
|
||||||
|
// TODO: 实现token验证逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
117
markbase-core/src/cli/metadata/config.rs
Normal file
117
markbase-core/src/cli/metadata/config.rs
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
use clap::Subcommand;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum ConfigCommand {
|
||||||
|
Init {
|
||||||
|
#[arg(short, long)]
|
||||||
|
force: bool,
|
||||||
|
},
|
||||||
|
Show {
|
||||||
|
#[arg(short, long)]
|
||||||
|
section: Option<String>,
|
||||||
|
},
|
||||||
|
Edit {
|
||||||
|
#[arg(short, long)]
|
||||||
|
key: String,
|
||||||
|
#[arg(short, long)]
|
||||||
|
value: String,
|
||||||
|
},
|
||||||
|
Validate,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_config_command(cmd: ConfigCommand) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
ConfigCommand::Init { force } => {
|
||||||
|
let config_path = Path::new("config/markbase.toml");
|
||||||
|
|
||||||
|
if config_path.exists() && !force {
|
||||||
|
println!("Configuration file already exists at config/markbase.toml");
|
||||||
|
println!("Use --force to overwrite");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = crate::config::MarkBaseConfig::default_config();
|
||||||
|
config.save(config_path)?;
|
||||||
|
|
||||||
|
println!("✓ Configuration file created: config/markbase.toml");
|
||||||
|
println!("Default values:");
|
||||||
|
println!(" Server port: {}", config.server.port);
|
||||||
|
println!(" PostgreSQL host: {}", config.postgresql.host);
|
||||||
|
println!(" Test users: {}", config.test.users.join(", "));
|
||||||
|
}
|
||||||
|
ConfigCommand::Show { section } => {
|
||||||
|
let config_path = Path::new("config/markbase.toml");
|
||||||
|
|
||||||
|
if !config_path.exists() {
|
||||||
|
println!("Configuration file not found. Run 'markbase metadata config init' first.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = crate::config::MarkBaseConfig::load(config_path)?;
|
||||||
|
|
||||||
|
if let Some(s) = section {
|
||||||
|
show_section(&config, &s);
|
||||||
|
} else {
|
||||||
|
println!("{}", toml::to_string_pretty(&config)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ConfigCommand::Edit { key, value } => {
|
||||||
|
let config_path = Path::new("config/markbase.toml");
|
||||||
|
|
||||||
|
if !config_path.exists() {
|
||||||
|
println!("Configuration file not found. Run 'markbase metadata config init' first.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut config = crate::config::MarkBaseConfig::load(config_path)?;
|
||||||
|
|
||||||
|
match config.get(&key) {
|
||||||
|
Some(old_value) => {
|
||||||
|
config.set(&key, &value)?;
|
||||||
|
config.validate()?;
|
||||||
|
config.save(config_path)?;
|
||||||
|
println!("✓ Updated {}: {} → {}", key, old_value, value);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
println!("Invalid config key: {}", key);
|
||||||
|
println!(
|
||||||
|
"Valid keys: server.*, postgresql.*, authentication.*, test.*, logging.*"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ConfigCommand::Validate => {
|
||||||
|
let config_path = Path::new("config/markbase.toml");
|
||||||
|
|
||||||
|
if !config_path.exists() {
|
||||||
|
println!("Configuration file not found. Run 'markbase metadata config init' first.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = crate::config::MarkBaseConfig::load(config_path)?;
|
||||||
|
|
||||||
|
match config.validate() {
|
||||||
|
Ok(_) => {
|
||||||
|
println!("✓ Configuration is valid");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("✗ Configuration validation failed: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_section(config: &crate::config::MarkBaseConfig, section: &str) {
|
||||||
|
match section {
|
||||||
|
"server" => println!("{}", toml::to_string_pretty(&config.server).unwrap()),
|
||||||
|
"postgresql" => println!("{}", toml::to_string_pretty(&config.postgresql).unwrap()),
|
||||||
|
"authentication" => println!("{}", toml::to_string_pretty(&config.authentication).unwrap()),
|
||||||
|
"test" => println!("{}", toml::to_string_pretty(&config.test).unwrap()),
|
||||||
|
"logging" => println!("{}", toml::to_string_pretty(&config.logging).unwrap()),
|
||||||
|
_ => println!("Invalid section: {}. Valid sections: server, postgresql, authentication, test, logging", section),
|
||||||
|
}
|
||||||
|
}
|
||||||
47
markbase-core/src/cli/metadata/db.rs
Normal file
47
markbase-core/src/cli/metadata/db.rs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum DbCommand {
|
||||||
|
Create {
|
||||||
|
#[arg(short, long)]
|
||||||
|
user: String,
|
||||||
|
},
|
||||||
|
Status {
|
||||||
|
#[arg(short, long)]
|
||||||
|
user: String,
|
||||||
|
},
|
||||||
|
Backup {
|
||||||
|
#[arg(short, long)]
|
||||||
|
user: String,
|
||||||
|
#[arg(short, long)]
|
||||||
|
output: String,
|
||||||
|
},
|
||||||
|
Restore {
|
||||||
|
#[arg(short, long)]
|
||||||
|
user: String,
|
||||||
|
#[arg(short, long)]
|
||||||
|
input: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_db_command(cmd: DbCommand) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
DbCommand::Create { user } => {
|
||||||
|
println!("Creating database for user: {}", user);
|
||||||
|
// TODO: 实现数据库创建逻辑
|
||||||
|
}
|
||||||
|
DbCommand::Status { user } => {
|
||||||
|
println!("Checking database status for user: {}", user);
|
||||||
|
// TODO: 实现数据库状态逻辑
|
||||||
|
}
|
||||||
|
DbCommand::Backup { user, output } => {
|
||||||
|
println!("Backing up database for user: {} to {}", user, output);
|
||||||
|
// TODO: 实现数据库备份逻辑
|
||||||
|
}
|
||||||
|
DbCommand::Restore { user, input } => {
|
||||||
|
println!("Restoring database for user: {} from {}", user, input);
|
||||||
|
// TODO: 实现数据库恢复逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
28
markbase-core/src/cli/metadata/mod.rs
Normal file
28
markbase-core/src/cli/metadata/mod.rs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
pub mod config;
|
||||||
|
pub mod user;
|
||||||
|
pub mod db;
|
||||||
|
pub mod auth;
|
||||||
|
|
||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum MetadataCommands {
|
||||||
|
#[command(flatten)]
|
||||||
|
Config(config::ConfigCommand),
|
||||||
|
#[command(flatten)]
|
||||||
|
User(user::UserCommand),
|
||||||
|
#[command(flatten)]
|
||||||
|
Db(db::DbCommand),
|
||||||
|
#[command(flatten)]
|
||||||
|
Auth(auth::AuthCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_metadata_command(cmd: MetadataCommands) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
MetadataCommands::Config(c) => config::handle_config_command(c)?,
|
||||||
|
MetadataCommands::User(c) => user::handle_user_command(c)?,
|
||||||
|
MetadataCommands::Db(c) => db::handle_db_command(c)?,
|
||||||
|
MetadataCommands::Auth(c) => auth::handle_auth_command(c)?,
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
42
markbase-core/src/cli/metadata/user.rs
Normal file
42
markbase-core/src/cli/metadata/user.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum UserCommand {
|
||||||
|
Create {
|
||||||
|
#[arg(short, long)]
|
||||||
|
name: String,
|
||||||
|
#[arg(short, long)]
|
||||||
|
password: String,
|
||||||
|
},
|
||||||
|
List,
|
||||||
|
Show {
|
||||||
|
#[arg(short, long)]
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
Delete {
|
||||||
|
#[arg(short, long)]
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_user_command(cmd: UserCommand) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
UserCommand::Create { name, password } => {
|
||||||
|
println!("Creating user: {} with password", name);
|
||||||
|
// TODO: 实现用户创建逻辑
|
||||||
|
}
|
||||||
|
UserCommand::List => {
|
||||||
|
println!("Listing all users");
|
||||||
|
// TODO: 实现用户列表逻辑
|
||||||
|
}
|
||||||
|
UserCommand::Show { name } => {
|
||||||
|
println!("Showing user: {}", name);
|
||||||
|
// TODO: 实现用户详情逻辑
|
||||||
|
}
|
||||||
|
UserCommand::Delete { name } => {
|
||||||
|
println!("Deleting user: {}", name);
|
||||||
|
// TODO: 实现用户删除逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,5 +1,25 @@
|
|||||||
pub mod framework;
|
pub mod interface;
|
||||||
pub mod apps;
|
pub mod metadata;
|
||||||
|
pub mod storage;
|
||||||
|
pub mod tools;
|
||||||
|
|
||||||
pub use framework::{Cli, Commands, FrameworkCommands};
|
use clap::{Parser, Subcommand};
|
||||||
pub use apps::{AppCommands, DownloadCenterCommands};
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(name = "markbase", about = "Momentry Display Engine")]
|
||||||
|
pub struct Cli {
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub command: Commands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum Commands {
|
||||||
|
#[command(flatten)]
|
||||||
|
Interface(interface::InterfaceCommands),
|
||||||
|
#[command(flatten)]
|
||||||
|
Metadata(metadata::MetadataCommands),
|
||||||
|
#[command(flatten)]
|
||||||
|
Storage(storage::StorageCommands),
|
||||||
|
#[command(flatten)]
|
||||||
|
Tools(tools::ToolsCommands),
|
||||||
|
}
|
||||||
39
markbase-core/src/cli/storage/archive.rs
Normal file
39
markbase-core/src/cli/storage/archive.rs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum ArchiveCommand {
|
||||||
|
Compress {
|
||||||
|
#[arg(short, long)]
|
||||||
|
file: String,
|
||||||
|
#[arg(short, long)]
|
||||||
|
dir: String,
|
||||||
|
},
|
||||||
|
Decompress {
|
||||||
|
#[arg(short, long)]
|
||||||
|
file: String,
|
||||||
|
#[arg(short, long)]
|
||||||
|
output: String,
|
||||||
|
},
|
||||||
|
List {
|
||||||
|
#[arg(short, long)]
|
||||||
|
file: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_archive_command(cmd: ArchiveCommand) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
ArchiveCommand::Compress { file, dir } => {
|
||||||
|
println!("Compressing {} to {}", dir, file);
|
||||||
|
// TODO: 实现压缩逻辑(使用archive模块)
|
||||||
|
}
|
||||||
|
ArchiveCommand::Decompress { file, output } => {
|
||||||
|
println!("Decompressing {} to {}", file, output);
|
||||||
|
// TODO: 实现解压缩逻辑(使用archive模块)
|
||||||
|
}
|
||||||
|
ArchiveCommand::List { file } => {
|
||||||
|
println!("Listing contents of {}", file);
|
||||||
|
// TODO: 实现列表逻辑(使用archive模块)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
20
markbase-core/src/cli/storage/hash.rs
Normal file
20
markbase-core/src/cli/storage/hash.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum HashCommand {
|
||||||
|
Compute {
|
||||||
|
#[arg(short, long)]
|
||||||
|
user: String,
|
||||||
|
#[arg(short, long, default_value = "4")]
|
||||||
|
threads: usize,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_hash_command(cmd: HashCommand) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
HashCommand::Compute { user, threads } => {
|
||||||
|
crate::scan::compute_hashes(&user, threads)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
32
markbase-core/src/cli/storage/mod.rs
Normal file
32
markbase-core/src/cli/storage/mod.rs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
pub mod scan;
|
||||||
|
pub mod hash;
|
||||||
|
pub mod archive;
|
||||||
|
pub mod sync;
|
||||||
|
pub mod mount;
|
||||||
|
|
||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum StorageCommands {
|
||||||
|
#[command(flatten)]
|
||||||
|
Scan(scan::ScanCommand),
|
||||||
|
#[command(flatten)]
|
||||||
|
Hash(hash::HashCommand),
|
||||||
|
#[command(flatten)]
|
||||||
|
Archive(archive::ArchiveCommand),
|
||||||
|
#[command(flatten)]
|
||||||
|
Sync(sync::SyncCommand),
|
||||||
|
#[command(flatten)]
|
||||||
|
Mount(mount::MountCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_storage_command(cmd: StorageCommands) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
StorageCommands::Scan(c) => scan::handle_scan_command(c)?,
|
||||||
|
StorageCommands::Hash(c) => hash::handle_hash_command(c)?,
|
||||||
|
StorageCommands::Archive(c) => archive::handle_archive_command(c)?,
|
||||||
|
StorageCommands::Sync(c) => sync::handle_sync_command(c)?,
|
||||||
|
StorageCommands::Mount(c) => mount::handle_mount_command(c)?,
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
36
markbase-core/src/cli/storage/mount.rs
Normal file
36
markbase-core/src/cli/storage/mount.rs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum MountCommand {
|
||||||
|
Attach {
|
||||||
|
#[arg(short, long)]
|
||||||
|
type_: String,
|
||||||
|
#[arg(short, long)]
|
||||||
|
server: String,
|
||||||
|
#[arg(short, long)]
|
||||||
|
path: String,
|
||||||
|
},
|
||||||
|
Detach {
|
||||||
|
#[arg(short, long)]
|
||||||
|
path: String,
|
||||||
|
},
|
||||||
|
List,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_mount_command(cmd: MountCommand) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
MountCommand::Attach { type_, server, path } => {
|
||||||
|
println!("Mounting {} from {} to {}", type_, server, path);
|
||||||
|
// TODO: 实现挂载逻辑(NFS/SMB/WebDAV)
|
||||||
|
}
|
||||||
|
MountCommand::Detach { path } => {
|
||||||
|
println!("Unmounting {}", path);
|
||||||
|
// TODO: 实现卸载逻辑
|
||||||
|
}
|
||||||
|
MountCommand::List => {
|
||||||
|
println!("Listing mounted storage");
|
||||||
|
// TODO: 实现列表逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
34
markbase-core/src/cli/storage/scan.rs
Normal file
34
markbase-core/src/cli/storage/scan.rs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum ScanCommand {
|
||||||
|
Directory {
|
||||||
|
#[arg(short, long)]
|
||||||
|
user: String,
|
||||||
|
#[arg(short, long)]
|
||||||
|
dir: String,
|
||||||
|
#[arg(short, long, default_value = "100")]
|
||||||
|
batch: usize,
|
||||||
|
#[arg(short, long, default_value = "true")]
|
||||||
|
skip_hash: bool,
|
||||||
|
#[arg(short, long, default_value = "4")]
|
||||||
|
threads: usize,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_scan_command(cmd: ScanCommand) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
ScanCommand::Directory {
|
||||||
|
user,
|
||||||
|
dir,
|
||||||
|
batch,
|
||||||
|
skip_hash,
|
||||||
|
threads,
|
||||||
|
} => {
|
||||||
|
use crate::scan::ScanOptions;
|
||||||
|
let options = ScanOptions { skip_hash, threads };
|
||||||
|
crate::scan::scan_directory(&user, &dir, batch, options)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
28
markbase-core/src/cli/storage/sync.rs
Normal file
28
markbase-core/src/cli/storage/sync.rs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum SyncCommand {
|
||||||
|
Start {
|
||||||
|
#[arg(short, long)]
|
||||||
|
source: String,
|
||||||
|
#[arg(short, long)]
|
||||||
|
target: String,
|
||||||
|
#[arg(short, long, default_value = "mirror")]
|
||||||
|
mode: String,
|
||||||
|
},
|
||||||
|
Status,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_sync_command(cmd: SyncCommand) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
SyncCommand::Start { source, target, mode } => {
|
||||||
|
println!("Syncing {} to {} (mode: {})", source, target, mode);
|
||||||
|
// TODO: 实现同步逻辑(使用sync模块)
|
||||||
|
}
|
||||||
|
SyncCommand::Status => {
|
||||||
|
println!("Checking sync status");
|
||||||
|
// TODO: 实现状态检查逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
20
markbase-core/src/cli/tools/mod.rs
Normal file
20
markbase-core/src/cli/tools/mod.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
pub mod render;
|
||||||
|
pub mod test;
|
||||||
|
|
||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum ToolsCommands {
|
||||||
|
#[command(flatten)]
|
||||||
|
Render(render::RenderCommand),
|
||||||
|
#[command(flatten)]
|
||||||
|
Test(test::TestCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_tools_command(cmd: ToolsCommands) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
ToolsCommands::Render(c) => render::handle_render_command(c)?,
|
||||||
|
ToolsCommands::Test(c) => test::handle_test_command(c)?,
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
26
markbase-core/src/cli/tools/render.rs
Normal file
26
markbase-core/src/cli/tools/render.rs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum RenderCommand {
|
||||||
|
File {
|
||||||
|
#[arg(short, long)]
|
||||||
|
file: String,
|
||||||
|
#[arg(short, long)]
|
||||||
|
output: Option<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_render_command(cmd: RenderCommand) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
RenderCommand::File { file, output } => {
|
||||||
|
let md = std::fs::read_to_string(&file)?;
|
||||||
|
let html = crate::render::md_to_html(&md);
|
||||||
|
if let Some(path) = &output {
|
||||||
|
std::fs::write(path, html)?;
|
||||||
|
} else {
|
||||||
|
println!("{html}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
61
markbase-core/src/cli/tools/test.rs
Normal file
61
markbase-core/src/cli/tools/test.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum TestCommand {
|
||||||
|
Bcrypt {
|
||||||
|
#[arg(short, long, default_value = "demo123")]
|
||||||
|
password: String,
|
||||||
|
#[arg(long)]
|
||||||
|
verify_hash: Option<String>,
|
||||||
|
},
|
||||||
|
Connection {
|
||||||
|
#[arg(short, long, default_value = "11438")]
|
||||||
|
port: u16,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_test_command(cmd: TestCommand) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
TestCommand::Bcrypt { password, verify_hash } => {
|
||||||
|
use bcrypt::{hash, verify, DEFAULT_COST};
|
||||||
|
|
||||||
|
println!("=== bcrypt Hash Test ===");
|
||||||
|
println!("Password: {}", password);
|
||||||
|
println!("");
|
||||||
|
|
||||||
|
let new_hash = hash(&password, DEFAULT_COST)?;
|
||||||
|
println!("Generated hash:");
|
||||||
|
println!("{}", new_hash);
|
||||||
|
println!("");
|
||||||
|
|
||||||
|
if let Some(hash_to_verify) = verify_hash {
|
||||||
|
println!("Verifying hash: {}", hash_to_verify);
|
||||||
|
let valid = verify(&password, &hash_to_verify)?;
|
||||||
|
println!("Valid: {}", valid);
|
||||||
|
println!("");
|
||||||
|
}
|
||||||
|
|
||||||
|
let db_hash = "$2b$10$ha5wU.mOi8fHLJCfun860u2cfVopa04jwe/q82IKOwqp5uG70qsH6";
|
||||||
|
println!("Database hash: {}", db_hash);
|
||||||
|
let valid = verify(&password, db_hash)?;
|
||||||
|
println!("Database hash valid for '{}': {}", password, valid);
|
||||||
|
println!("");
|
||||||
|
|
||||||
|
if !valid {
|
||||||
|
println!("❌ Database hash is incorrect!");
|
||||||
|
println!("Update SQL:");
|
||||||
|
println!("UPDATE sftpgo_users SET password_hash = '{}' WHERE username IN ('testuser', 'demo', 'warren', 'momentry');", new_hash);
|
||||||
|
println!("");
|
||||||
|
println!("Execute:");
|
||||||
|
println!("sqlite3 data/auth.sqlite \"UPDATE sftpgo_users SET password_hash = '{}' WHERE username IN ('testuser', 'demo', 'warren', 'momentry');\"", new_hash);
|
||||||
|
} else {
|
||||||
|
println!("✅ Database hash is correct!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TestCommand::Connection { port } => {
|
||||||
|
println!("Testing connection to port: {}", port);
|
||||||
|
// TODO: 实现连接测试逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use markbase_core::cli::{Cli, Commands};
|
use markbase_core::cli::Cli;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@@ -6,17 +6,19 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Commands::Framework(cmd) => {
|
markbase_core::cli::Commands::Interface(cmd) => {
|
||||||
markbase_core::cli::framework::handle_framework_command(cmd).await?;
|
markbase_core::cli::interface::handle_interface_command(cmd).await?;
|
||||||
}
|
}
|
||||||
Commands::App(app_cmd) => {
|
markbase_core::cli::Commands::Metadata(cmd) => {
|
||||||
match app_cmd {
|
markbase_core::cli::metadata::handle_metadata_command(cmd).await?;
|
||||||
markbase_core::cli::apps::AppCommands::DownloadCenter(cmd) => {
|
}
|
||||||
markbase_core::cli::apps::download_center::handle_download_center_command(cmd).await?;
|
markbase_core::cli::Commands::Storage(cmd) => {
|
||||||
}
|
markbase_core::cli::storage::handle_storage_command(cmd).await?;
|
||||||
}
|
}
|
||||||
|
markbase_core::cli::Commands::Tools(cmd) => {
|
||||||
|
markbase_core::cli::tools::handle_tools_command(cmd).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user