Compare commits

..

1 commit

Author SHA1 Message Date
5f961ac785
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1m5s
/ check (push) Successful in 1m3s
/ report-size (push) Successful in 8s
2025-01-23 21:20:21 +01:00
3 changed files with 31 additions and 83 deletions

2
Cargo.lock generated
View file

@ -1139,8 +1139,6 @@ dependencies = [
"insta", "insta",
"miette", "miette",
"ring", "ring",
"serde",
"serde_json",
"tokio", "tokio",
"tower-http", "tower-http",
"tracing", "tracing",

View file

@ -27,8 +27,6 @@ clap-verbosity-flag = { version = "3", default-features = false, features = [
http = "1" http = "1"
miette = { version = "7", features = ["fancy"] } miette = { version = "7", features = ["fancy"] }
ring = { version = "0.17", features = ["std"] } ring = { version = "0.17", features = ["std"] }
serde = { version = "1.0.217", features = ["derive"] }
serde_json = "1.0.137"
tokio = { version = "1", features = ["macros", "rt", "process", "io-util"] } tokio = { version = "1", features = ["macros", "rt", "process", "io-util"] }
tower-http = { version = "0.6.2", features = ["validate-request"] } tower-http = { version = "0.6.2", features = ["validate-request"] }
tracing = "0.1" tracing = "0.1"

View file

@ -1,6 +1,6 @@
use std::{ use std::{
io::ErrorKind, io::ErrorKind,
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, net::{IpAddr, SocketAddr},
path::{Path, PathBuf}, path::{Path, PathBuf},
time::Duration, time::Duration,
}; };
@ -114,48 +114,6 @@ struct AppState<'a> {
/// The file where the last IP is stored /// The file where the last IP is stored
ip_file: &'a Path, ip_file: &'a Path,
/// Last recorded IPs
last_ips: std::sync::Arc<tokio::sync::Mutex<SavedIPs>>,
}
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
struct SavedIPs {
#[serde(skip_serializing_if = "Option::is_none")]
ipv4: Option<Ipv4Addr>,
#[serde(skip_serializing_if = "Option::is_none")]
ipv6: Option<Ipv6Addr>,
}
impl SavedIPs {
fn update(&mut self, ip: IpAddr) {
match ip {
IpAddr::V4(ipv4_addr) => self.ipv4 = Some(ipv4_addr),
IpAddr::V6(ipv6_addr) => self.ipv6 = Some(ipv6_addr),
}
}
fn ips(&self) -> impl Iterator<Item = IpAddr> {
self.ipv4
.map(IpAddr::V4)
.into_iter()
.chain(self.ipv6.map(IpAddr::V6))
}
fn from_str(data: &str) -> miette::Result<Self> {
match data.parse::<IpAddr>() {
// Old format
Ok(IpAddr::V4(ipv4)) => Ok(Self {
ipv4: Some(ipv4),
ipv6: None,
}),
Ok(IpAddr::V6(ipv6)) => Ok(Self {
ipv4: None,
ipv6: Some(ipv6),
}),
Err(_) => serde_json::from_str(data).into_diagnostic(),
}
}
} }
impl AppState<'static> { impl AppState<'static> {
@ -179,7 +137,7 @@ impl AppState<'static> {
let ttl = Duration::from_secs(*ttl); let ttl = Duration::from_secs(*ttl);
// Use last registered IP address if available // Use last registered IP address if available
let ip_file = Box::leak(data_dir.join("last-ip").into_boxed_path()); let ip_file = data_dir.join("last-ip");
let state = AppState { let state = AppState {
ttl, ttl,
@ -197,10 +155,7 @@ impl AppState<'static> {
Ok(&*Box::leak(path.into())) Ok(&*Box::leak(path.into()))
}) })
.transpose()?, .transpose()?,
ip_file, ip_file: Box::leak(ip_file.into_boxed_path()),
last_ips: std::sync::Arc::new(tokio::sync::Mutex::new(
load_ip(ip_file)?.unwrap_or_default(),
)),
}; };
ensure!( ensure!(
@ -212,7 +167,7 @@ impl AppState<'static> {
} }
} }
fn load_ip(path: &Path) -> Result<Option<SavedIPs>> { fn load_ip(path: &Path) -> Result<Option<IpAddr>> {
debug!("loading last IP from {}", path.display()); debug!("loading last IP from {}", path.display());
let data = match std::fs::read_to_string(path) { let data = match std::fs::read_to_string(path) {
Ok(ip) => ip, Ok(ip) => ip,
@ -226,9 +181,11 @@ fn load_ip(path: &Path) -> Result<Option<SavedIPs>> {
} }
}; };
SavedIPs::from_str(&data) Ok(Some(
.wrap_err_with(|| format!("failed to load last ip address from {}", path.display())) data.parse()
.map(Some) .into_diagnostic()
.wrap_err("failed to parse last ip address")?,
))
} }
#[tracing::instrument(err)] #[tracing::instrument(err)]
@ -309,9 +266,9 @@ fn main() -> Result<()> {
.wrap_err("failed to start the tokio runtime")?; .wrap_err("failed to start the tokio runtime")?;
rt.block_on(async { rt.block_on(async {
// Update DNS record with previous IPs (if available) // Load previous IP and update DNS record to point to it (if available)
let ips = state.last_ips.lock().await.clone(); match load_ip(state.ip_file) {
for ip in ips.ips() { Ok(Some(ip)) => {
match nsupdate::nsupdate(ip, state.ttl, state.key_file, state.records).await { match nsupdate::nsupdate(ip, state.ttl, state.key_file, state.records).await {
Ok(status) => { Ok(status) => {
if !status.success() { if !status.success() {
@ -327,6 +284,10 @@ fn main() -> Result<()> {
} }
} }
} }
Ok(None) => info!("No previous IP address set"),
Err(err) => error!("Ignoring previous IP due to: {err}"),
};
// Create services // Create services
let app = Router::new().route("/update", get(update_records)); let app = Router::new().route("/update", get(update_records));
@ -363,22 +324,13 @@ async fn update_records(
info!("accepted update from {ip}"); info!("accepted update from {ip}");
match nsupdate::nsupdate(ip, state.ttl, state.key_file, state.records).await { match nsupdate::nsupdate(ip, state.ttl, state.key_file, state.records).await {
Ok(status) if status.success() => { Ok(status) if status.success() => {
let ips = {
// Update state
let mut ips = state.last_ips.lock().await;
ips.update(ip);
ips.clone()
};
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
info!("updating last ips to {ips:?}"); info!("updating last ip to {ip}");
let data = serde_json::to_vec(&ips).expect("invalid serialization impl"); if let Err(err) = std::fs::write(state.ip_file, format!("{ip}")) {
if let Err(err) = std::fs::write(state.ip_file, data) {
error!("Failed to update last IP: {err}"); error!("Failed to update last IP: {err}");
} }
info!("updated last ips to {ips:?}"); info!("updated last ip to {ip}");
}); });
Ok("successful update") Ok("successful update")
} }
Ok(status) => { Ok(status) => {