From 085a411860d04122d6a96f1cb1ab7e044063d6cc Mon Sep 17 00:00:00 2001 From: Rene Zander Date: Thu, 9 Apr 2026 06:48:40 +0000 Subject: [PATCH] feat: support --remote-config for loading config from URL Auto-detect URLs in the positional config argument: if the arg starts with http:// or https://, fetch the TOML content via HTTP GET (10s timeout) before parsing. Otherwise treat it as a local file path (existing behavior unchanged). Env-var expansion works on remote config content. Fixes #80 --- Cargo.lock | 25 +++++++++++++------------ Cargo.toml | 1 + src/config.rs | 37 +++++++++++++++++++++++++++++++++---- src/main.rs | 8 +++----- 4 files changed, 50 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7fe1825..ef332c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -160,12 +160,12 @@ checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "deranged" -version = "0.5.8" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", - "serde_core", + "serde", ] [[package]] @@ -749,9 +749,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "once_cell" @@ -766,6 +766,7 @@ dependencies = [ "anyhow", "rand 0.8.5", "regex", + "reqwest", "serde", "serde_json", "serenity", @@ -1451,30 +1452,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.47" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde_core", + "serde", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", diff --git a/Cargo.toml b/Cargo.toml index edfdf87..6e665d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,4 @@ uuid = { version = "1", features = ["v4"] } regex = "1" anyhow = "1" rand = "0.8" +reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] } diff --git a/src/config.rs b/src/config.rs index 719feaf..ccd7c87 100644 --- a/src/config.rs +++ b/src/config.rs @@ -150,11 +150,40 @@ fn expand_env_vars(raw: &str) -> String { .into_owned() } -pub fn load_config(path: &Path) -> anyhow::Result { - let raw = std::fs::read_to_string(path) - .map_err(|e| anyhow::anyhow!("failed to read {}: {e}", path.display()))?; +fn is_url(s: &str) -> bool { + s.starts_with("http://") || s.starts_with("https://") +} + +async fn fetch_remote_config(url: &str) -> anyhow::Result { + let client = reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(10)) + .build()?; + let response = client + .get(url) + .send() + .await + .map_err(|e| anyhow::anyhow!("failed to fetch remote config from {url}: {e}"))?; + let status = response.status(); + if !status.is_success() { + anyhow::bail!("remote config request to {url} failed with status {status}"); + } + let body = response + .text() + .await + .map_err(|e| anyhow::anyhow!("failed to read response body from {url}: {e}"))?; + Ok(body) +} + +pub async fn load_config_from_source(source: &str) -> anyhow::Result { + let raw = if is_url(source) { + fetch_remote_config(source).await? + } else { + let path = Path::new(source); + std::fs::read_to_string(path) + .map_err(|e| anyhow::anyhow!("failed to read {}: {e}", path.display()))? + }; let expanded = expand_env_vars(&raw); let config: Config = toml::from_str(&expanded) - .map_err(|e| anyhow::anyhow!("failed to parse {}: {e}", path.display()))?; + .map_err(|e| anyhow::anyhow!("failed to parse config from {source}: {e}"))?; Ok(config) } diff --git a/src/main.rs b/src/main.rs index a216b66..2171244 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,6 @@ mod reactions; use serenity::prelude::*; use std::collections::HashSet; -use std::path::PathBuf; use std::sync::Arc; use tracing::info; @@ -19,12 +18,11 @@ async fn main() -> anyhow::Result<()> { ) .init(); - let config_path = std::env::args() + let config_source = std::env::args() .nth(1) - .map(PathBuf::from) - .unwrap_or_else(|| PathBuf::from("config.toml")); + .unwrap_or_else(|| "config.toml".into()); - let cfg = config::load_config(&config_path)?; + let cfg = config::load_config_from_source(&config_source).await?; info!( agent_cmd = %cfg.agent.command, pool_max = cfg.pool.max_sessions,