From df017de29d7415f639ad50416914eca01b174da2 Mon Sep 17 00:00:00 2001 From: Matthias Schild Date: Tue, 7 Jul 2026 19:21:47 +0200 Subject: [PATCH] feat: detect file changes --- Cargo.lock | 190 ++++++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 1 + src/config.rs | 56 ++++++++++++++- src/main.rs | 28 +++++--- 4 files changed, 259 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3338b3a..e67c858 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -190,7 +190,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -199,6 +199,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -244,6 +253,26 @@ dependencies = [ "hashbrown 0.16.1", ] +[[package]] +name = "inotify" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd854a95a4ac672fed8c054136039fd32c22cf039ff09ead7280afe920486483" +dependencies = [ + "bitflags", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d" +dependencies = [ + "libc", +] + [[package]] name = "itoa" version = "1.0.18" @@ -261,6 +290,26 @@ dependencies = [ "serde", ] +[[package]] +name = "kqueue" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "273c0752728918e0ac4976f2b275b6fefb9ecd400585dec929419f3844cd87b5" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" +dependencies = [ + "bitflags", + "libc", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -310,8 +359,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", + "log", "wasi", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -321,19 +371,47 @@ dependencies = [ "anyhow", "config", "dotenv", + "notify", "serde", "tokio", "tracing", "tracing-subscriber", ] +[[package]] +name = "notify" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" +dependencies = [ + "bitflags", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.60.2", +] + +[[package]] +name = "notify-types" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +dependencies = [ + "bitflags", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -498,6 +576,15 @@ dependencies = [ "ordered-multimap", ] +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -611,7 +698,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -657,7 +744,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -805,18 +892,46 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -826,6 +941,71 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" version = "1.0.3" diff --git a/Cargo.toml b/Cargo.toml index dd47b45..bfca3b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2024" anyhow = "1.0.103" config = "0.15.25" dotenv = "0.15.0" +notify = "8.2.0" serde = { version = "1.0.228", features = ["derive"] } tokio = { version = "1.52.3", features = ["full"] } tracing = "0.1.44" diff --git a/src/config.rs b/src/config.rs index 600f692..c2e317d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,9 +1,13 @@ +use std::{path::Path, sync::Arc, time::Duration}; + use anyhow::{Result, ensure}; +use notify::{EventKind, INotifyWatcher, Watcher}; use serde::Deserialize; +use tokio::sync::{RwLock, mpsc}; #[derive(Deserialize)] #[allow(clippy::upper_case_acronyms)] -pub enum EntryType { +pub enum RecordType { A, AAAA, MX, @@ -12,7 +16,7 @@ pub enum EntryType { #[derive(Deserialize)] pub struct Entry { #[serde(rename = "type")] - pub entry_type: EntryType, + pub rtype: RecordType, pub name: String, pub address: String, #[serde(skip)] @@ -77,3 +81,51 @@ pub fn load() -> Result { entries: file_config.entries, }) } + +pub fn watch(config: Arc>) -> Result { + let (tx, mut rx) = mpsc::channel(100); + let mut watcher = notify::recommended_watcher(move |event| { + let _ = tx.blocking_send(event); + })?; + watcher.configure(notify::Config::default().with_poll_interval(Duration::from_secs(10)))?; + watcher.watch( + Path::new("nanodns.toml"), + notify::RecursiveMode::NonRecursive, + )?; + + tokio::spawn(async move { + loop { + let Some(event) = rx.recv().await else { + tracing::info!("watch channel closed"); + break; + }; + + match event { + Ok(event) => { + if let EventKind::Modify(_) = event.kind { + let new_config = match load() { + Ok(new_config) => new_config, + Err(err) => { + tracing::error!("Failed to load new config: {err}"); + break; + } + }; + + tracing::info!("Detected config change"); + { + let mut config = config.write().await; + *config = new_config; + } + tracing::info!("Config change applied"); + } + } + Err(err) => { + tracing::error!("error in watch channel: {err}"); + break; + } + } + } + }); + + Ok(watcher) +} diff --git a/src/main.rs b/src/main.rs index 0347425..9cd8563 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,11 +10,11 @@ mod ext; mod logging; mod macros; -use std::{io::Cursor, net::SocketAddr}; +use std::{io::Cursor, net::SocketAddr, sync::Arc}; use anyhow::Result; use dotenv::dotenv; -use tokio::net::UdpSocket; +use tokio::{net::UdpSocket, sync::RwLock}; use tracing::{Instrument, Level}; use crate::{ @@ -27,15 +27,18 @@ async fn main() -> Result<()> { dotenv().ok(); logging::setup(); let config = config::load()?; + let port = config.port; + let config = Arc::new(RwLock::new(config)); + let _watcher = config::watch(config.clone())?; - tracing::info!("start server on port {}", config.port); - let sock = UdpSocket::bind(("0.0.0.0", config.port)).await?; + tracing::info!("start server on port {}", port); + let sock = UdpSocket::bind(("0.0.0.0", port)).await?; let mut buffer = [0u8; 512]; loop { let (size, addr) = sock.recv_from(&mut buffer).await?; let mut cursor = Cursor::new(&buffer[..size]); - let result = handle_datagram(&config, &sock, &addr, &mut cursor).await; + let result = handle_datagram(config.clone(), &sock, &addr, &mut cursor).await; if let Err(err) = result { tracing::error!("failed to handle datagram: {err}"); } @@ -43,7 +46,7 @@ async fn main() -> Result<()> { } async fn handle_datagram( - config: &config::ConfigData, + config: Arc>, sock: &UdpSocket, addr: &SocketAddr, cursor: &mut Cursor<&[u8]>, @@ -80,7 +83,7 @@ async fn handle_datagram( match question.qtype { RECORD_TYPE_A => { - handle_type_a_question(config, &question, &mut response); + handle_type_a_question(config.clone(), &question, &mut response).await; } qt => { tracing::error!("unsupported question type, requested: {qt}"); @@ -100,8 +103,15 @@ async fn handle_datagram( result } -fn handle_type_a_question(config: &ConfigData, question: &Question, response: &mut Message) { - let address = config.get_ipv4_address_by_domain(&question.domain); +async fn handle_type_a_question( + config: Arc>, + question: &Question, + response: &mut Message, +) { + let address = { + let config = config.read().await; + config.get_ipv4_address_by_domain(&question.domain) + }; match address { Some(address) => { let answer = Answer {