2026-03-10 15:33:06 +00:00
|
|
|
pub mod fs;
|
|
|
|
|
|
|
|
|
|
use clap::Parser;
|
2026-03-10 15:52:16 +00:00
|
|
|
use fuser::{MountOption, Session};
|
2026-03-10 15:33:06 +00:00
|
|
|
use std::path::PathBuf;
|
2026-03-10 15:52:16 +00:00
|
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
|
|
|
use std::sync::Arc;
|
2026-03-10 15:33:06 +00:00
|
|
|
use furumi_client_core::FurumiClient;
|
|
|
|
|
|
|
|
|
|
#[derive(Parser, Debug)]
|
|
|
|
|
#[command(version, about, long_about = None)]
|
|
|
|
|
struct Args {
|
|
|
|
|
/// Server address to connect to
|
|
|
|
|
#[arg(short, long, env = "FURUMI_SERVER", default_value = "http://[::1]: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,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 client = rt.block_on(async {
|
|
|
|
|
FurumiClient::connect(&args.server, &args.token).await
|
|
|
|
|
})?;
|
|
|
|
|
|
2026-03-10 15:52:16 +00:00
|
|
|
let fuse_fs = fs::FurumiFuse::new(client, rt.handle().clone());
|
2026-03-10 15:33:06 +00:00
|
|
|
|
|
|
|
|
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);
|
2026-03-10 15:52:16 +00:00
|
|
|
|
|
|
|
|
// 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.");
|
2026-03-10 15:33:06 +00:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2026-03-10 15:52:16 +00:00
|
|
|
|