106 lines
3.3 KiB
Rust
106 lines
3.3 KiB
Rust
pub mod fs;
|
|
|
|
use clap::Parser;
|
|
use fuser::{MountOption, Session};
|
|
use std::path::PathBuf;
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
use std::sync::Arc;
|
|
use furumi_client_core::FurumiClient;
|
|
|
|
#[derive(Parser, Debug)]
|
|
#[command(version, about = "Furumi-ng: mount remote filesystem via encrypted gRPC + FUSE")]
|
|
struct Args {
|
|
/// Server address (IP:PORT, will use https:// by default unless --no-tls is specified)
|
|
#[arg(short, long, env = "FURUMI_SERVER", default_value = "0.0.0.0:50051")]
|
|
server: String,
|
|
|
|
/// Authentication Bearer token (leave empty if auth is disabled on server)
|
|
#[arg(short, long, env = "FURUMI_TOKEN", default_value = "")]
|
|
token: String,
|
|
|
|
/// Mount point directory
|
|
#[arg(short, long, env = "FURUMI_MOUNT")]
|
|
mount: PathBuf,
|
|
|
|
/// Disable TLS encryption
|
|
#[arg(long, default_value_t = false)]
|
|
no_tls: bool,
|
|
}
|
|
|
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
tracing_subscriber::fmt::init();
|
|
|
|
let args = Args::parse();
|
|
|
|
if !args.mount.exists() || !args.mount.is_dir() {
|
|
eprintln!("Error: Mount point {:?} does not exist or is not a directory", args.mount);
|
|
std::process::exit(1);
|
|
}
|
|
|
|
// Create a robust tokio runtime for the background gRPC work
|
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
|
.enable_all()
|
|
.build()?;
|
|
|
|
let full_addr = if args.no_tls {
|
|
if args.server.starts_with("http://") || args.server.starts_with("https://") {
|
|
args.server.clone()
|
|
} else {
|
|
format!("http://{}", args.server)
|
|
}
|
|
} else {
|
|
if args.server.starts_with("http://") || args.server.starts_with("https://") {
|
|
args.server.clone()
|
|
} else {
|
|
format!("https://{}", args.server)
|
|
}
|
|
};
|
|
|
|
let client = rt.block_on(async {
|
|
let c = FurumiClient::connect(&full_addr, &args.token).await?;
|
|
|
|
// Ping the server to verify connection and authentication token
|
|
if let Err(e) = c.get_attr("/").await {
|
|
return Err(format!("Failed to authenticate or connect to server: {}", e).into());
|
|
}
|
|
|
|
Ok::<_, Box<dyn std::error::Error>>(c)
|
|
})?;
|
|
|
|
let fuse_fs = fs::FurumiFuse::new(client, rt.handle().clone());
|
|
|
|
let options = vec![
|
|
MountOption::RO,
|
|
MountOption::FSName("furumi-ng".to_string()),
|
|
MountOption::NoExec, // Better security for media mount
|
|
];
|
|
|
|
println!("Mounting Furumi-ng to {:?}", args.mount);
|
|
|
|
// Use Session + BackgroundSession for graceful unmount on exit
|
|
let session = Session::new(fuse_fs, &args.mount, &options)?;
|
|
// Spawn the FUSE event loop in a background thread.
|
|
// When `bg_session` is dropped, the filesystem is automatically unmounted.
|
|
let bg_session = session.spawn()?;
|
|
|
|
// Set up signal handler for graceful shutdown
|
|
let running = Arc::new(AtomicBool::new(true));
|
|
let r = running.clone();
|
|
ctrlc::set_handler(move || {
|
|
eprintln!("\nReceived signal, unmounting...");
|
|
r.store(false, Ordering::SeqCst);
|
|
})?;
|
|
|
|
// Wait for shutdown signal
|
|
while running.load(Ordering::SeqCst) {
|
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
|
}
|
|
|
|
// Dropping bg_session automatically unmounts the filesystem
|
|
drop(bg_session);
|
|
println!("Unmounted successfully.");
|
|
|
|
Ok(())
|
|
}
|
|
|