Introduces a ScheduledTask mechanism that periodically calls rclone RC vfs/refresh to keep directory listing caches warm (no file downloads), with two-level config (global default + per-share override). Adds dir-refresh status badges and timestamps to the web UI shares tab and CLI status output, following the same pattern as warmup/warmed. - src/scheduler.rs: New generic ScheduledTask runner with generation-based cancellation and parse_interval() helper - src/rclone/rc.rs: Add vfs_refresh() RC API call - src/config.rs: Add DirRefreshConfig, per-share dir_refresh_interval override, effective_dir_refresh_interval() resolution method - src/config_diff.rs: Track dir_refresh_changed for hot-reload - src/daemon.rs: Track per-share last_dir_refresh timestamps (HashMap), add dir_refresh_ago_for() helper and format_ago() - src/supervisor.rs: spawn_dir_refresh() per-share background threads, called on startup and config reload - src/web/api.rs: Expose dir_refresh_active + last_dir_refresh_ago in ShareStatusResponse - src/web/pages.rs: Populate dir_refresh_active + last_dir_refresh_ago in ShareView and ShareDetailView - templates/web/tabs/shares.html: DIR-REFRESH badge (yellow=pending, green=N ago) in health column; Dir Refresh row in detail panel - templates/web/tabs/config.html: Dir Refresh section and per-share interval field in interactive config editor - src/cli/status.rs: Append Dir-Refresh suffix to mount status lines Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
133 lines
4.0 KiB
Rust
133 lines
4.0 KiB
Rust
mod cli;
|
|
mod config;
|
|
mod config_diff;
|
|
mod daemon;
|
|
mod deploy;
|
|
mod rclone;
|
|
mod scheduler;
|
|
mod services;
|
|
mod supervisor;
|
|
mod web;
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use anyhow::Result;
|
|
use clap::{Parser, Subcommand};
|
|
|
|
use config::Config;
|
|
|
|
/// Warpgate — Make your NAS feel local.
|
|
///
|
|
/// SSD read-write caching proxy for remote NAS access.
|
|
#[derive(Parser)]
|
|
#[command(name = "warpgate", version, about)]
|
|
struct Cli {
|
|
/// Path to config file.
|
|
#[arg(short, long, default_value = config::DEFAULT_CONFIG_PATH)]
|
|
config: PathBuf,
|
|
|
|
#[command(subcommand)]
|
|
command: Commands,
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
enum Commands {
|
|
/// One-click deploy: install deps, generate configs, enable services.
|
|
Deploy,
|
|
/// Show service status, cache stats, write-back queue, bandwidth.
|
|
Status,
|
|
/// List files currently in the SSD cache.
|
|
CacheList,
|
|
/// Clean cached files (only evicts clean files, never dirty).
|
|
CacheClean {
|
|
/// Remove all clean files (default: only expired).
|
|
#[arg(long)]
|
|
all: bool,
|
|
},
|
|
/// Pre-cache a remote directory to local SSD.
|
|
Warmup {
|
|
/// Name of the share to warm up.
|
|
#[arg(long)]
|
|
share: String,
|
|
/// Path within the share to warm up.
|
|
path: String,
|
|
/// Only files newer than this duration (e.g. "7d", "24h").
|
|
#[arg(long)]
|
|
newer_than: Option<String>,
|
|
},
|
|
/// View or adjust bandwidth limits at runtime.
|
|
Bwlimit {
|
|
/// Upload limit (e.g. "10M", "0" for unlimited).
|
|
#[arg(long)]
|
|
up: Option<String>,
|
|
/// Download limit (e.g. "50M", "0" for unlimited).
|
|
#[arg(long)]
|
|
down: Option<String>,
|
|
},
|
|
/// Stream service logs in real time.
|
|
Log {
|
|
/// Number of recent lines to show.
|
|
#[arg(short, long, default_value = "50")]
|
|
lines: u32,
|
|
/// Follow log output (like tail -f).
|
|
#[arg(short, long)]
|
|
follow: bool,
|
|
},
|
|
/// Test network speed to remote NAS.
|
|
SpeedTest,
|
|
/// Run all services under a single supervisor process.
|
|
Run,
|
|
/// Generate a default config file.
|
|
ConfigInit {
|
|
/// Output path (default: /etc/warpgate/config.toml).
|
|
#[arg(short, long)]
|
|
output: Option<PathBuf>,
|
|
},
|
|
}
|
|
|
|
fn main() -> Result<()> {
|
|
let cli = Cli::parse();
|
|
|
|
match cli.command {
|
|
// config-init doesn't need an existing config file
|
|
Commands::ConfigInit { output } => cli::config_init::run(output),
|
|
// deploy loads config if it exists, or generates one
|
|
Commands::Deploy => {
|
|
let config = load_config_or_default(&cli.config)?;
|
|
deploy::setup::run(&config)
|
|
}
|
|
// all other commands require a valid config
|
|
cmd => {
|
|
let config = Config::load(&cli.config)?;
|
|
match cmd {
|
|
Commands::Status => cli::status::run(&config),
|
|
Commands::CacheList => cli::cache::list(&config),
|
|
Commands::CacheClean { all } => cli::cache::clean(&config, all),
|
|
Commands::Warmup { share, path, newer_than } => {
|
|
cli::warmup::run(&config, &share, &path, newer_than.as_deref())
|
|
}
|
|
Commands::Bwlimit { up, down } => {
|
|
cli::bwlimit::run(&config, up.as_deref(), down.as_deref())
|
|
}
|
|
Commands::Log { lines, follow } => cli::log::run(&config, lines, follow),
|
|
Commands::SpeedTest => cli::speed_test::run(&config),
|
|
Commands::Run => supervisor::run(&config, cli.config.clone()),
|
|
// already handled above
|
|
Commands::ConfigInit { .. } | Commands::Deploy => unreachable!(),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Load config from file, or return a useful error.
|
|
fn load_config_or_default(path: &std::path::Path) -> Result<Config> {
|
|
if path.exists() {
|
|
Config::load(path)
|
|
} else {
|
|
anyhow::bail!(
|
|
"Config file not found: {}. Run `warpgate config-init` to generate one.",
|
|
path.display()
|
|
)
|
|
}
|
|
}
|