feat: detect file changes

This commit is contained in:
2026-07-07 19:21:47 +02:00
parent d2f03dcd2f
commit df017de29d
4 changed files with 259 additions and 16 deletions
Generated
+185 -5
View File
@@ -190,7 +190,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -199,6 +199,15 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" 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]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.14.7" version = "0.14.7"
@@ -244,6 +253,26 @@ dependencies = [
"hashbrown 0.16.1", "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]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.18" version = "1.0.18"
@@ -261,6 +290,26 @@ dependencies = [
"serde", "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]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.5.0" version = "1.5.0"
@@ -310,8 +359,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda"
dependencies = [ dependencies = [
"libc", "libc",
"log",
"wasi", "wasi",
"windows-sys", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -321,19 +371,47 @@ dependencies = [
"anyhow", "anyhow",
"config", "config",
"dotenv", "dotenv",
"notify",
"serde", "serde",
"tokio", "tokio",
"tracing", "tracing",
"tracing-subscriber", "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]] [[package]]
name = "nu-ansi-term" name = "nu-ansi-term"
version = "0.50.3" version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [ dependencies = [
"windows-sys", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -498,6 +576,15 @@ dependencies = [
"ordered-multimap", "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]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.2.0" version = "1.2.0"
@@ -611,7 +698,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -657,7 +744,7 @@ dependencies = [
"signal-hook-registry", "signal-hook-registry",
"socket2", "socket2",
"tokio-macros", "tokio-macros",
"windows-sys", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -805,18 +892,46 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 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]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.1+wasi-snapshot-preview1" version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 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]] [[package]]
name = "windows-link" name = "windows-link"
version = "0.2.1" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 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]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.61.2" version = "0.61.2"
@@ -826,6 +941,71 @@ dependencies = [
"windows-link", "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]] [[package]]
name = "winnow" name = "winnow"
version = "1.0.3" version = "1.0.3"
+1
View File
@@ -7,6 +7,7 @@ edition = "2024"
anyhow = "1.0.103" anyhow = "1.0.103"
config = "0.15.25" config = "0.15.25"
dotenv = "0.15.0" dotenv = "0.15.0"
notify = "8.2.0"
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
tokio = { version = "1.52.3", features = ["full"] } tokio = { version = "1.52.3", features = ["full"] }
tracing = "0.1.44" tracing = "0.1.44"
+54 -2
View File
@@ -1,9 +1,13 @@
use std::{path::Path, sync::Arc, time::Duration};
use anyhow::{Result, ensure}; use anyhow::{Result, ensure};
use notify::{EventKind, INotifyWatcher, Watcher};
use serde::Deserialize; use serde::Deserialize;
use tokio::sync::{RwLock, mpsc};
#[derive(Deserialize)] #[derive(Deserialize)]
#[allow(clippy::upper_case_acronyms)] #[allow(clippy::upper_case_acronyms)]
pub enum EntryType { pub enum RecordType {
A, A,
AAAA, AAAA,
MX, MX,
@@ -12,7 +16,7 @@ pub enum EntryType {
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Entry { pub struct Entry {
#[serde(rename = "type")] #[serde(rename = "type")]
pub entry_type: EntryType, pub rtype: RecordType,
pub name: String, pub name: String,
pub address: String, pub address: String,
#[serde(skip)] #[serde(skip)]
@@ -77,3 +81,51 @@ pub fn load() -> Result<ConfigData> {
entries: file_config.entries, entries: file_config.entries,
}) })
} }
pub fn watch(config: Arc<RwLock<ConfigData>>) -> Result<INotifyWatcher> {
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)
}
+19 -9
View File
@@ -10,11 +10,11 @@ mod ext;
mod logging; mod logging;
mod macros; mod macros;
use std::{io::Cursor, net::SocketAddr}; use std::{io::Cursor, net::SocketAddr, sync::Arc};
use anyhow::Result; use anyhow::Result;
use dotenv::dotenv; use dotenv::dotenv;
use tokio::net::UdpSocket; use tokio::{net::UdpSocket, sync::RwLock};
use tracing::{Instrument, Level}; use tracing::{Instrument, Level};
use crate::{ use crate::{
@@ -27,15 +27,18 @@ async fn main() -> Result<()> {
dotenv().ok(); dotenv().ok();
logging::setup(); logging::setup();
let config = config::load()?; 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); tracing::info!("start server on port {}", port);
let sock = UdpSocket::bind(("0.0.0.0", config.port)).await?; let sock = UdpSocket::bind(("0.0.0.0", port)).await?;
let mut buffer = [0u8; 512]; let mut buffer = [0u8; 512];
loop { loop {
let (size, addr) = sock.recv_from(&mut buffer).await?; let (size, addr) = sock.recv_from(&mut buffer).await?;
let mut cursor = Cursor::new(&buffer[..size]); 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 { if let Err(err) = result {
tracing::error!("failed to handle datagram: {err}"); tracing::error!("failed to handle datagram: {err}");
} }
@@ -43,7 +46,7 @@ async fn main() -> Result<()> {
} }
async fn handle_datagram( async fn handle_datagram(
config: &config::ConfigData, config: Arc<RwLock<config::ConfigData>>,
sock: &UdpSocket, sock: &UdpSocket,
addr: &SocketAddr, addr: &SocketAddr,
cursor: &mut Cursor<&[u8]>, cursor: &mut Cursor<&[u8]>,
@@ -80,7 +83,7 @@ async fn handle_datagram(
match question.qtype { match question.qtype {
RECORD_TYPE_A => { RECORD_TYPE_A => {
handle_type_a_question(config, &question, &mut response); handle_type_a_question(config.clone(), &question, &mut response).await;
} }
qt => { qt => {
tracing::error!("unsupported question type, requested: {qt}"); tracing::error!("unsupported question type, requested: {qt}");
@@ -100,8 +103,15 @@ async fn handle_datagram(
result result
} }
fn handle_type_a_question(config: &ConfigData, question: &Question, response: &mut Message) { async fn handle_type_a_question(
let address = config.get_ipv4_address_by_domain(&question.domain); config: Arc<RwLock<ConfigData>>,
question: &Question,
response: &mut Message,
) {
let address = {
let config = config.read().await;
config.get_ipv4_address_by_domain(&question.domain)
};
match address { match address {
Some(address) => { Some(address) => {
let answer = Answer { let answer = Answer {