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 to connect to (use https:// for encrypted connection) #[arg(short, long, env = "FURUMI_SERVER", default_value = "https://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, /// Path to server's TLS CA certificate PEM file (required for https:// connections) #[arg(long, env = "FURUMI_TLS_CA")] tls_ca: Option, } fn main() -> Result<(), Box> { 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); } // Load TLS CA certificate if provided let tls_ca_pem = if let Some(ref ca_path) = args.tls_ca { let pem = std::fs::read(ca_path).unwrap_or_else(|e| { eprintln!("Error: Failed to read TLS CA cert {:?}: {}", ca_path, e); std::process::exit(1); }); Some(pem) } else { None }; // Create a robust tokio runtime for the background gRPC work let rt = tokio::runtime::Builder::new_multi_thread() .enable_all() .build()?; let client = rt.block_on(async { FurumiClient::connect(&args.server, &args.token, tls_ca_pem).await })?; 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(()) }