131 lines
4.1 KiB
Rust
131 lines
4.1 KiB
Rust
pub mod nfs;
|
|
|
|
use clap::Parser;
|
|
use furumi_client_core::FurumiClient;
|
|
use nfsserve::tcp::{NFSTcp, NFSTcpListener};
|
|
use std::path::PathBuf;
|
|
use std::process::Command;
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
use std::sync::Arc;
|
|
use tracing::info;
|
|
|
|
#[derive(Parser, Debug)]
|
|
#[command(version, about = "Furumi-ng: mount remote filesystem via encrypted gRPC + NFS (macOS)")]
|
|
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);
|
|
}
|
|
|
|
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 { FurumiClient::connect(&full_addr, &args.token).await })?;
|
|
|
|
let furumi_nfs = nfs::FurumiNfs::new(client);
|
|
|
|
let mount_point = args.mount.clone();
|
|
let mount_point_umount = args.mount.clone();
|
|
|
|
// 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);
|
|
})?;
|
|
|
|
rt.block_on(async move {
|
|
// Bind NFS server to a random available port on localhost
|
|
let listener = NFSTcpListener::bind("127.0.0.1:0", furumi_nfs)
|
|
.await
|
|
.expect("Failed to bind NFS listener");
|
|
|
|
let port = listener.get_listen_port();
|
|
info!("NFS server listening on 127.0.0.1:{}", port);
|
|
|
|
// Start handling NFS connections BEFORE mounting —
|
|
// mount_nfs needs to talk to the server during mount.
|
|
let handle = tokio::spawn(async move {
|
|
if let Err(e) = listener.handle_forever().await {
|
|
eprintln!("NFS server error: {}", e);
|
|
}
|
|
});
|
|
|
|
// Mount via macOS native mount_nfs
|
|
let mount_path = mount_point.to_string_lossy().to_string();
|
|
let opts = format!(
|
|
"rdonly,locallocks,noresvport,nfsvers=3,tcp,rsize=1048576,port={},mountport={}",
|
|
port, port
|
|
);
|
|
|
|
let status = Command::new("mount_nfs")
|
|
.args(["-o", &opts, "127.0.0.1:/", &mount_path])
|
|
.status()
|
|
.expect("Failed to execute mount_nfs");
|
|
|
|
if !status.success() {
|
|
handle.abort();
|
|
eprintln!("Error: mount_nfs failed with status {}", status);
|
|
std::process::exit(1);
|
|
}
|
|
|
|
println!("Mounted Furumi-ng v{} to {:?}", env!("CARGO_PKG_VERSION"), mount_path);
|
|
|
|
// Wait for shutdown signal
|
|
while running.load(Ordering::SeqCst) {
|
|
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
|
|
}
|
|
|
|
// Unmount
|
|
let _ = Command::new("diskutil")
|
|
.arg("unmount")
|
|
.arg("force")
|
|
.arg(mount_point_umount.to_string_lossy().as_ref())
|
|
.status();
|
|
|
|
handle.abort();
|
|
println!("Unmounted successfully.");
|
|
});
|
|
|
|
Ok(())
|
|
}
|