Files
furumi-ng/furumi-mount-linux/src/main.rs

106 lines
3.4 KiB
Rust
Raw Normal View History

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)]
2026-03-10 16:20:19 +00:00
#[command(version, about = "Furumi-ng: mount remote filesystem via encrypted gRPC + FUSE")]
struct Args {
2026-03-10 16:52:13 +00:00
/// 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,
2026-03-10 16:52:13 +00:00
/// 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()?;
2026-03-10 16:52:13 +00:00
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 {
2026-03-13 15:52:17 +00:00
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
];
2026-03-17 15:03:36 +00:00
println!("Mounting Furumi-ng v{} to {:?}", env!("CARGO_PKG_VERSION"), 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(())
}