1use std::path::Path;
4use std::sync::Arc;
5
6use anyhow::Context;
7
8use crate::config;
9use crate::fetcher::ssrf::SsrfLevel;
10use crate::mcp;
11use crate::storage::Db;
12
13pub struct Args {
14 pub ignore_robots: bool,
15 pub rate_limit_rpm: Option<u32>,
16 pub per_host_concurrency: Option<u32>,
17 pub global_concurrency: Option<u32>,
18 pub max_retries: Option<u8>,
19}
20
21pub async fn run(args: Args, config_path: Option<&Path>) -> anyhow::Result<()> {
22 let mut cfg = config::load_resolved(config_path).context("loading config")?;
23 cfg.apply_overrides(
24 args.rate_limit_rpm,
25 args.per_host_concurrency,
26 args.global_concurrency,
27 args.max_retries,
28 args.ignore_robots,
29 );
30
31 let ssrf_level = SsrfLevel::parse(&cfg.ssrf.level)
32 .with_context(|| format!("invalid [ssrf] level `{}` in config", cfg.ssrf.level))?;
33 let ssrf_project_root = if ssrf_level == SsrfLevel::Project {
34 let raw = &cfg.ssrf.project_root;
35 let resolved = std::fs::canonicalize(raw)
36 .with_context(|| format!("canonicalizing ssrf.project_root `{}`", raw.display()))?;
37 tracing::info!(
38 target: "rover::ssrf",
39 project_root = %resolved.display(),
40 "ssrf level=project; project_root resolved",
41 );
42 Some(resolved)
43 } else {
44 None
45 };
46
47 let har_recorder: Option<Arc<crate::fetcher::har::HarRecorder>> =
53 if !cfg.debug.har_path.is_empty() {
54 let path = std::path::PathBuf::from(&cfg.debug.har_path);
55 let r = crate::fetcher::har::HarRecorder::new(path, cfg.debug.har_body_cap)
56 .with_context(|| format!("opening har file at {}", cfg.debug.har_path))?;
57 let r = Arc::new(r);
58
59 let r_flush = r.clone();
60 tokio::spawn(async move {
61 let mut interval = tokio::time::interval(std::time::Duration::from_secs(5));
62 interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
63 loop {
64 interval.tick().await;
65 if let Err(e) = r_flush.flush().await {
66 tracing::warn!(
67 target: "rover::fetcher",
68 error = ?e,
69 "har periodic flush failed"
70 );
71 }
72 }
73 });
74
75 tracing::info!(
76 target: "rover::fetcher",
77 har_path = %cfg.debug.har_path,
78 har_body_cap = cfg.debug.har_body_cap,
79 "har recorder enabled",
80 );
81 Some(r)
82 } else {
83 None
84 };
85
86 let cfg = Arc::new(cfg);
87
88 let data_dir = crate::paths::data_dir();
89 std::fs::create_dir_all(&data_dir).context("creating data dir")?;
90 let db = Db::open(data_dir.join("rover.db"))
91 .await
92 .context("opening cache database")?;
93
94 mcp::serve_stdio(db, cfg, ssrf_level, ssrf_project_root, har_recorder).await
95}