Compare commits

...

4 commits

Author SHA1 Message Date
Jan Christian Grünhage 9a7fefc16c
chore: bump version and update changelog 2023-01-14 21:55:23 +01:00
Jan Christian Grünhage 96aa83dbfc
fix: switch to cloudflare fork that actually parses API responses 2023-01-14 21:52:51 +01:00
Jan Christian Grünhage 8cd963ad59
chore: fix clippy lints 2023-01-14 21:48:07 +01:00
Jan Christian Grünhage 3e1152edfe
chore: improve error handling 2023-01-14 21:46:14 +01:00
5 changed files with 70 additions and 35 deletions

View file

@ -2,6 +2,18 @@
All notable changes to this project will be documented in this file.
## [0.5.2] - 2023-01-14
### Bug Fixes
- Switch to cloudflare fork that actually parses API responses
### Miscellaneous Tasks
- Improve error handling
- Fix clippy lints
- Bump version and update changelog
## [0.5.1] - 2023-01-01
### Bug Fixes

9
Cargo.lock generated
View file

@ -98,8 +98,7 @@ dependencies = [
[[package]]
name = "cloudflare"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0778f99ea7ad39b49b758eb418da7117b93232a5f6a09f9b79a094b77ac88cc2"
source = "git+https://github.com/jcgruenhage/cloudflare-rs.git?branch=make-owner-fields-optional#02397fc4211886548a31a0731b240f2e17309de4"
dependencies = [
"anyhow",
"async-trait",
@ -119,7 +118,7 @@ dependencies = [
[[package]]
name = "cloudflare-ddns-service"
version = "0.5.1"
version = "0.5.2"
dependencies = [
"anyhow",
"cloudflare",
@ -1166,9 +1165,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.23.0"
version = "1.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46"
checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae"
dependencies = [
"autocfg",
"bytes",

View file

@ -1,6 +1,6 @@
[package]
name = "cloudflare-ddns-service"
version = "0.5.1"
version = "0.5.2"
authors = ["Jan Christian Grünhage <jan.christian@gruenhage.xyz>"]
edition = "2018"
description = "A daemon to use Cloudflare as a DDNS provider"
@ -18,6 +18,7 @@ serde = { version = "1.0.152", features = ["derive"] }
anyhow = "1.0.68"
env_logger = "0.10.0"
log = "0.4.17"
tokio = { version = "1.23.0", features = ["time", "macros", "rt-multi-thread"] }
tokio = { version = "1.24.1", features = ["time", "macros", "rt-multi-thread"] }
serde_yaml = "0.9.16"
cloudflare = "0.10.1"
#cloudflare = "0.10.1"
cloudflare = { git = "https://github.com/jcgruenhage/cloudflare-rs.git", branch = "make-owner-fields-optional" }

View file

@ -56,12 +56,12 @@ async fn main() -> Result<()> {
let config_string = read_to_string("/etc/cloudflare-ddns-service/config.yaml")
.context("couldn't read config file!")?;
let config: Config = from_str(&config_string)?;
let config: Config = from_str(&config_string).context("Failed to parse config file")?;
let cache_dir = PathBuf::from("/var/cache/cloudflare-ddns-service");
let cache_path = cache_dir.join("cache.yaml");
let mut cache = match read_to_string(&cache_path) {
Ok(cache) => from_str(&cache)?,
Err(_) => {
let mut cache = match read_to_string(&cache_path).map(|str| from_str(&str)) {
Ok(Ok(cache)) => cache,
_ => {
create_dir_all(cache_dir)?;
Cache::default()
}
@ -75,10 +75,13 @@ async fn main() -> Result<()> {
},
HttpApiClientConfig::default(),
Environment::Production,
)?;
let zone = get_zone(config.zone.clone(), &mut cf_client).await?;
)
.context("Failed to initiate cloudflare API client")?;
let zone = get_zone(config.zone.clone(), &mut cf_client)
.await
.context("Failed to get zone")?;
loop {
update(
if let Err(error) = update(
&config,
&mut cache,
&cache_path,
@ -86,7 +89,10 @@ async fn main() -> Result<()> {
&mut reqw_client,
&mut cf_client,
)
.await?;
.await
{
log::error!("Failed to update record: {}", error);
}
interval.tick().await;
}
}
@ -100,7 +106,9 @@ async fn update(
cf_client: &mut CfClient,
) -> Result<()> {
if config.ipv4 {
let current = get_current_ipv4(reqw_client).await?;
let current = get_current_ipv4(reqw_client)
.await
.context("Failed to query current IPv4 address")?;
log::debug!("fetched current IP: {}", current.to_string());
match cache.v4 {
Some(old) if old == current => {
@ -119,14 +127,18 @@ async fn update(
DnsContent::A { content: current },
cf_client,
)
.await?;
.await
.context("Failed to set DNS record")?;
cache.v4 = Some(current);
write_cache(cache, cache_path)?;
write_cache(cache, cache_path)
.context("Failed to write current IPv4 address to cache")?;
}
}
}
if config.ipv6 {
let current = get_current_ipv6(reqw_client).await?;
let current = get_current_ipv6(reqw_client)
.await
.context("Failed to query current IPv4 address")?;
log::debug!("fetched current IP: {}", current.to_string());
match cache.v6 {
Some(old) if old == current => {
@ -145,9 +157,11 @@ async fn update(
DnsContent::AAAA { content: current },
cf_client,
)
.await?;
.await
.context("Failed to set DNS record")?;
cache.v6 = Some(current);
write_cache(cache, cache_path)?;
write_cache(cache, cache_path)
.context("Failed to write current IPv4 address to cache")?;
}
}
}
@ -155,7 +169,11 @@ async fn update(
}
fn write_cache(cache: &mut Cache, cache_path: &PathBuf) -> Result<()> {
to_writer(File::create(cache_path)?, cache)?;
to_writer(
File::create(cache_path).context("Failed to open cache file for writing")?,
cache,
)
.context("Failed to serialize cache into file")?;
Ok(())
}

View file

@ -32,25 +32,31 @@ pub const AAAA_RECORD: DnsContent = DnsContent::AAAA {
};
pub async fn get_current_ipv4(client: &mut ReqwClient) -> Result<Ipv4Addr> {
Ok(client
client
.get("https://ipv4.icanhazip.com")
.send()
.await?
.await
.context("Failed to query current IPv4 from ipv4.icanhazip.com")?
.text()
.await?
.await
.context("Failed to read text body")?
.trim()
.parse()?)
.parse()
.context("Failed to parse IPv4 address returned by ipv4.icanhazip.com")
}
pub async fn get_current_ipv6(client: &mut ReqwClient) -> Result<Ipv6Addr> {
Ok(client
client
.get("https://ipv6.icanhazip.com")
.send()
.await?
.await
.context("Failed to query current IPv6 from ipv6.icanhazip.com")?
.text()
.await?
.await
.context("Failed to read text body")?
.trim()
.parse()?)
.parse()
.context("Failed to parse IPv6 address returned by ipv6.icanhazip.com")
}
pub async fn get_zone(domain: String, cf_client: &mut CfClient) -> Result<String> {
@ -66,7 +72,8 @@ pub async fn get_zone(domain: String, cf_client: &mut CfClient) -> Result<String
search_match: None,
},
})
.await?
.await
.context("Failed to query zone from cf_client")?
.result[0]
.id
.clone())
@ -95,9 +102,7 @@ pub async fn get_record(
.context("Couldn't fetch record")?
.result
.iter()
.find(|record| {
std::mem::discriminant(&record.content) == std::mem::discriminant(&r#type)
})
.find(|record| std::mem::discriminant(&record.content) == std::mem::discriminant(&r#type))
.context("No matching record found")?
.id
.clone())