From 18fd154ab0649f74cf2013097ea9dcf28283c38a Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Sun, 2 Nov 2025 23:51:22 +0900 Subject: [PATCH] feat: support realtime config+block+allow files reloading --- proxy-bin/Cargo.toml | 2 +- proxy-bin/src/config/target_config.rs | 47 ++++++++++++++++++++++++++- proxy-bin/src/main.rs | 5 +-- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/proxy-bin/Cargo.toml b/proxy-bin/Cargo.toml index 8617d09..ac2db92 100644 --- a/proxy-bin/Cargo.toml +++ b/proxy-bin/Cargo.toml @@ -39,7 +39,7 @@ toml = { version = "0.9.8", default-features = false, features = [ "parse", "serde", ] } -hot_reload = { version = "0.3.4", features = ["file-reloader"] } +hot_reload = { version = "0.3.5", features = ["file-reloader"] } # logging tracing = { version = "0.1.41" } diff --git a/proxy-bin/src/config/target_config.rs b/proxy-bin/src/config/target_config.rs index 0a28b88..275e702 100644 --- a/proxy-bin/src/config/target_config.rs +++ b/proxy-bin/src/config/target_config.rs @@ -1,9 +1,15 @@ use super::{toml::ConfigToml, utils_dns_proto::parse_proto_sockaddr_str, utils_verifier::*}; use crate::{constants::*, error::*, log::*}; +use async_trait::async_trait; use doh_auth_proxy_lib::{ AuthenticationConfig, NextHopRelayConfig, ProxyConfig, QueryManipulationConfig, SubseqRelayConfig, TokenConfig, }; -use std::{env, path::PathBuf, sync::Arc}; +use hot_reload::AsyncFileLoad; +use std::{ + env, + path::{Path, PathBuf}, + sync::Arc, +}; use tokio::time::Duration; pub type ConfigReloader = hot_reload::file_reloader::FileReloader; @@ -25,6 +31,45 @@ impl TryFrom<&PathBuf> for TargetConfig { } } +#[async_trait] +impl AsyncFileLoad for TargetConfig { + type Error = String; + + async fn async_load_from(path: T) -> Result + where + T: AsRef + Send, + { + let config_str = tokio::fs::read_to_string(path) + .await + .map_err(|e| format!("Failed to read config file: {}", e))?; + let config_toml: ConfigToml = toml::from_str(&config_str).map_err(|e| format!("Failed to parse toml: {}", e))?; + let query_manipulation_config: Option = (&config_toml) + .try_into() + .map_err(|e| format!("Failed to parse query manipulation config: {}", e))?; + Ok(Self { + config_toml, + query_manipulation_config: query_manipulation_config.map(Arc::new), + }) + } + + fn dependent_paths(&self) -> Vec { + let mut paths = Vec::new(); + + let Some(plugins) = &self.config_toml.plugins else { + return paths; + }; + + if let Some(blocked_file) = &plugins.domains_blocked_file { + paths.push(PathBuf::from(blocked_file)); + } + if let Some(overridden_file) = &plugins.domains_overridden_file { + paths.push(PathBuf::from(overridden_file)); + } + + paths + } +} + impl TargetConfig { /// build new target config by loading query manipulation plugin configs pub fn new(config_file: &PathBuf) -> anyhow::Result { diff --git a/proxy-bin/src/main.rs b/proxy-bin/src/main.rs index c2eb308..f562b6d 100644 --- a/proxy-bin/src/main.rs +++ b/proxy-bin/src/main.rs @@ -35,14 +35,15 @@ fn main() { std::process::exit(1); } } else { - let reloader_config = ReloaderConfig::polling(CONFIG_WATCH_DELAY_SECS); + // With config file watcher in hybrid mode + let reloader_config = ReloaderConfig::hybrid(CONFIG_WATCH_DELAY_SECS); let (config_service, config_rx) = ReloaderService::::new(&parsed_opts.config_file_path, reloader_config) .await .unwrap(); tokio::select! { - Err(e) = config_service.start() => { + Err(e) = config_service.start_with_realtime() => { error!("config reloader service exited: {e}"); std::process::exit(1); }