use clap::Parser; use std::path::PathBuf; use std::process::Command; use axum::{Extension, Router, routing::any}; use tokio::net::TcpListener; use dav_server::{DavHandler, localfs::LocalFs, fakels::FakeLs}; #[derive(Parser)] struct Args { #[arg(short, long, default_value = "4932")] port: u16, #[arg(long, default_value = "data/raid_simple.sparseimage")] vdisk_path: PathBuf, #[arg(long, default_value = "RAID_AUTO")] mount_name: String, } fn main() { tokio::runtime::Builder::new_multi_thread() .enable_all() .build() .unwrap() .block_on(async_main()); } async fn async_main() { let args = Args::parse(); println!("=== RAID WebDAV Server (Auto-Mount) ==="); println!("Port: {}", args.port); println!("VDisk: {}", args.vdisk_path.display()); println!("Mount Name: {}", args.mount_name); println!(""); if !args.vdisk_path.exists() { eprintln!("Error: Virtual disk not found at {}", args.vdisk_path.display()); return; } println!("Step 1: Check if already mounted..."); let mount_point = check_or_mount(&args.vdisk_path, &args.mount_name); println!("Step 2: Verify mount point..."); if !mount_point.exists() { eprintln!("Error: Mount point does not exist: {}", mount_point.display()); return; } println!("✅ Mounted at: {}", mount_point.display()); println!(""); println!("Step 3: Starting WebDAV server..."); let dav = DavHandler::builder() .filesystem(LocalFs::new(mount_point.to_string_lossy().to_string(), false, false, false)) .locksystem(FakeLs::new()) .strip_prefix("/webdav") .build_handler(); let addr = format!("127.0.0.1:{}", args.port); let listener = TcpListener::bind(&addr).await.unwrap(); let router = Router::new() .route("/webdav", any(handle_dav)) .route("/webdav/", any(handle_dav)) .route("/webdav/{*path}", any(handle_dav)) .layer(Extension(dav)); println!("Listening on: http://{}", addr); println!("Mount with Finder:"); println!(" Cmd+K → http://localhost:{}/webdav", args.port); println!(" Guest/Guest or blank password"); println!(""); println!("Press Ctrl+C to stop..."); axum::serve(listener, router).await.unwrap(); } fn check_or_mount(vdisk_path: &PathBuf, mount_name: &str) -> PathBuf { let expected_mount = PathBuf::from("/Volumes").join(mount_name); if expected_mount.exists() { println!("✅ Already mounted at: {}", expected_mount.display()); return expected_mount; } println!("Mounting sparseimage..."); let output = Command::new("hdiutil") .args(&["attach", "-nobrowse"]) .arg(vdisk_path) .output() .expect("Failed to mount sparseimage"); if !output.status.success() { eprintln!("Mount failed: {}", String::from_utf8_lossy(&output.stderr)); return expected_mount; } println!("Mount output: {}", String::from_utf8_lossy(&output.stdout)); let mount_output = String::from_utf8_lossy(&output.stdout); for line in mount_output.lines() { if line.contains("/Volumes/") { let parts: Vec<&str> = line.split_whitespace().collect(); if let Some(mount_path) = parts.last() { println!("✅ Mounted at: {}", mount_path); return PathBuf::from(mount_path); } } } expected_mount } async fn handle_dav(Extension(dav): Extension, req: axum::extract::Request) -> impl axum::response::IntoResponse { dav.handle(req).await }