Fix warmup to use VFS cache, dynamic SMB share name, smbd long flags
- warmup: read files through FUSE mount instead of rclone copy to temp dir. Files now actually land in rclone VFS SSD cache. - samba: derive share name from mount point dir name instead of hardcoded [nas-photos] (e.g. /mnt/projects → [projects]) - supervisor: use smbd long flags (--foreground, --debug-stdout, --no-process-group, --configfile) for compatibility with Samba 4.19 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5d8bf52ae9
commit
9b37c88cd5
@ -1,5 +1,10 @@
|
||||
//! `warpgate warmup` — pre-cache a remote directory to local SSD.
|
||||
//!
|
||||
//! Lists files via `rclone lsf`, then reads each through the FUSE mount
|
||||
//! to trigger VFS caching. This ensures files land in the rclone VFS
|
||||
//! SSD cache rather than being downloaded to a throwaway temp directory.
|
||||
|
||||
use std::io;
|
||||
use std::process::Command;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
@ -8,38 +13,75 @@ use crate::config::Config;
|
||||
use crate::rclone::config as rclone_config;
|
||||
|
||||
pub fn run(config: &Config, path: &str, newer_than: Option<&str>) -> Result<()> {
|
||||
let warmup_path = config.mount.point.join(path);
|
||||
let remote_src = format!("nas:{}/{}", config.connection.remote_path, path);
|
||||
let local_dest = std::env::temp_dir().join("warpgate-warmup");
|
||||
|
||||
println!("Warming up: {}", remote_src);
|
||||
println!("Warming up: {remote_src}");
|
||||
println!(" via mount: {}", warmup_path.display());
|
||||
|
||||
// Create temp destination for downloaded files
|
||||
std::fs::create_dir_all(&local_dest)
|
||||
.context("Failed to create temp directory for warmup")?;
|
||||
if !warmup_path.exists() {
|
||||
anyhow::bail!(
|
||||
"Path not found on mount: {}. Is the mount running?",
|
||||
warmup_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
// List files on remote (supports --max-age for newer_than filter)
|
||||
let mut cmd = Command::new("rclone");
|
||||
cmd.arg("copy")
|
||||
cmd.arg("lsf")
|
||||
.arg("--config")
|
||||
.arg(rclone_config::RCLONE_CONF_PATH)
|
||||
.arg(&remote_src)
|
||||
.arg(&local_dest)
|
||||
.arg("--no-traverse")
|
||||
.arg("--progress");
|
||||
.arg("--recursive")
|
||||
.arg("--files-only")
|
||||
.arg(&remote_src);
|
||||
|
||||
if let Some(age) = newer_than {
|
||||
cmd.arg("--max-age").arg(age);
|
||||
}
|
||||
|
||||
println!("Downloading from remote NAS...");
|
||||
let status = cmd.status().context("Failed to run rclone copy")?;
|
||||
let output = cmd.output().context("Failed to run rclone lsf")?;
|
||||
if !output.status.success() {
|
||||
anyhow::bail!(
|
||||
"rclone lsf failed: {}",
|
||||
String::from_utf8_lossy(&output.stderr).trim()
|
||||
);
|
||||
}
|
||||
|
||||
// Clean up temp directory
|
||||
let _ = std::fs::remove_dir_all(&local_dest);
|
||||
let file_list = String::from_utf8_lossy(&output.stdout);
|
||||
let files: Vec<&str> = file_list.lines().filter(|l| !l.is_empty()).collect();
|
||||
let total = files.len();
|
||||
|
||||
if status.success() {
|
||||
println!("Warmup complete.");
|
||||
Ok(())
|
||||
if total == 0 {
|
||||
println!("No files matched.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
println!("Found {total} files to cache.");
|
||||
|
||||
let mut cached = 0usize;
|
||||
let mut errors = 0usize;
|
||||
|
||||
for file in &files {
|
||||
let full_path = warmup_path.join(file);
|
||||
match std::fs::File::open(&full_path) {
|
||||
Ok(mut f) => {
|
||||
// Stream-read through FUSE mount → populates VFS cache
|
||||
if let Err(e) = io::copy(&mut f, &mut io::sink()) {
|
||||
eprintln!(" Warning: read failed: {file}: {e}");
|
||||
errors += 1;
|
||||
} else {
|
||||
anyhow::bail!("rclone copy exited with status {}", status);
|
||||
cached += 1;
|
||||
eprint!("\r Cached {cached}/{total}");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!(" Warning: open failed: {file}: {e}");
|
||||
errors += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!();
|
||||
println!("Warmup complete: {cached} cached, {errors} errors.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -41,8 +41,14 @@ pub fn generate(config: &Config) -> Result<String> {
|
||||
writeln!(conf, " disable spoolss = yes")?;
|
||||
writeln!(conf)?;
|
||||
|
||||
// [nas-photos] share section
|
||||
writeln!(conf, "[nas-photos]")?;
|
||||
// Share name derived from mount point directory name
|
||||
let share_name = config
|
||||
.mount
|
||||
.point
|
||||
.file_name()
|
||||
.map(|n| n.to_string_lossy())
|
||||
.unwrap_or("warpgate".into());
|
||||
writeln!(conf, "[{share_name}]")?;
|
||||
writeln!(conf, " comment = Warpgate cached NAS share")?;
|
||||
writeln!(conf, " path = {mount_point}")?;
|
||||
writeln!(conf, " browseable = yes")?;
|
||||
|
||||
@ -204,7 +204,8 @@ fn start_and_wait_mount(config: &Config, shutdown: &AtomicBool) -> Result<Child>
|
||||
/// Spawn smbd as a foreground child process.
|
||||
fn spawn_smbd() -> Result<Child> {
|
||||
Command::new("smbd")
|
||||
.args(["-F", "-S", "-N", "-s", samba::SMB_CONF_PATH])
|
||||
.args(["--foreground", "--debug-stdout", "--no-process-group",
|
||||
"--configfile", samba::SMB_CONF_PATH])
|
||||
.spawn()
|
||||
.context("Failed to spawn smbd")
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user