Skip to content

Commit 2446c1d

Browse files
committed
Bump crate to 2.0.8 and refine updater behavior
Deduplicate up-to-date messages by tracking noop keys and move logging to the updater so callers only log the first noop. Reuse a single reqwest Client for IP detection instead of rebuilding it for each call. Always ping heartbeat even when there are no meaningful changes. Fix Pushover shoutrrr parsing (token@user order) and update tests
1 parent 9b8aba5 commit 2446c1d

6 files changed

Lines changed: 219 additions & 97 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cloudflare-ddns"
3-
version = "2.0.7"
3+
version = "2.0.8"
44
edition = "2021"
55
description = "Access your home network remotely via a custom domain name without a static IP"
66
license = "GPL-3.0"

src/cloudflare.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ impl CloudflareHandle {
467467
self.update_record(zone_id, &record.id, &payload, ppfmt).await;
468468
}
469469
} else {
470-
ppfmt.infof(pp::EMOJI_SKIP, &format!("Record {fqdn} is up to date ({ip_str})"));
470+
// Caller handles "up to date" logging based on SetResult::Noop
471471
}
472472
} else {
473473
// Find an existing managed record to update, or create new
@@ -668,10 +668,7 @@ impl CloudflareHandle {
668668
.collect();
669669

670670
if to_add.is_empty() && ids_to_delete.is_empty() {
671-
ppfmt.infof(
672-
pp::EMOJI_SKIP,
673-
&format!("WAF list {} is up to date", waf_list.describe()),
674-
);
671+
// Caller handles "up to date" logging based on SetResult::Noop
675672
return SetResult::Noop;
676673
}
677674

src/main.rs

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ use crate::cloudflare::{Auth, CloudflareHandle};
1111
use crate::config::{AppConfig, CronSchedule};
1212
use crate::notifier::{CompositeNotifier, Heartbeat, Message};
1313
use crate::pp::PP;
14+
use std::collections::HashSet;
1415
use std::sync::atomic::{AtomicBool, Ordering};
1516
use std::sync::Arc;
17+
use reqwest::Client;
1618
use tokio::signal;
1719
use tokio::time::{sleep, Duration};
1820

@@ -117,13 +119,17 @@ async fn main() {
117119
heartbeat.start().await;
118120

119121
let mut cf_cache = cf_ip_filter::CachedCloudflareFilter::new();
122+
let detection_client = Client::builder()
123+
.timeout(app_config.detection_timeout)
124+
.build()
125+
.unwrap_or_default();
120126

121127
if app_config.legacy_mode {
122128
// --- Legacy mode (original cloudflare-ddns behavior) ---
123-
run_legacy_mode(&app_config, &handle, &notifier, &heartbeat, &ppfmt, running, &mut cf_cache).await;
129+
run_legacy_mode(&app_config, &handle, &notifier, &heartbeat, &ppfmt, running, &mut cf_cache, &detection_client).await;
124130
} else {
125131
// --- Env var mode (cf-ddns behavior) ---
126-
run_env_mode(&app_config, &handle, &notifier, &heartbeat, &ppfmt, running, &mut cf_cache).await;
132+
run_env_mode(&app_config, &handle, &notifier, &heartbeat, &ppfmt, running, &mut cf_cache, &detection_client).await;
127133
}
128134

129135
// On shutdown: delete records if configured
@@ -146,12 +152,15 @@ async fn run_legacy_mode(
146152
ppfmt: &PP,
147153
running: Arc<AtomicBool>,
148154
cf_cache: &mut cf_ip_filter::CachedCloudflareFilter,
155+
detection_client: &Client,
149156
) {
150157
let legacy = match &config.legacy_config {
151158
Some(l) => l,
152159
None => return,
153160
};
154161

162+
let mut noop_reported = HashSet::new();
163+
155164
if config.repeat {
156165
match (legacy.a, legacy.aaaa) {
157166
(true, true) => println!(
@@ -168,7 +177,7 @@ async fn run_legacy_mode(
168177
}
169178

170179
while running.load(Ordering::SeqCst) {
171-
updater::update_once(config, handle, notifier, heartbeat, cf_cache, ppfmt).await;
180+
updater::update_once(config, handle, notifier, heartbeat, cf_cache, ppfmt, &mut noop_reported, detection_client).await;
172181

173182
for _ in 0..legacy.ttl {
174183
if !running.load(Ordering::SeqCst) {
@@ -178,7 +187,7 @@ async fn run_legacy_mode(
178187
}
179188
}
180189
} else {
181-
updater::update_once(config, handle, notifier, heartbeat, cf_cache, ppfmt).await;
190+
updater::update_once(config, handle, notifier, heartbeat, cf_cache, ppfmt, &mut noop_reported, detection_client).await;
182191
}
183192
}
184193

@@ -190,11 +199,14 @@ async fn run_env_mode(
190199
ppfmt: &PP,
191200
running: Arc<AtomicBool>,
192201
cf_cache: &mut cf_ip_filter::CachedCloudflareFilter,
202+
detection_client: &Client,
193203
) {
204+
let mut noop_reported = HashSet::new();
205+
194206
match &config.update_cron {
195207
CronSchedule::Once => {
196208
if config.update_on_start {
197-
updater::update_once(config, handle, notifier, heartbeat, cf_cache, ppfmt).await;
209+
updater::update_once(config, handle, notifier, heartbeat, cf_cache, ppfmt, &mut noop_reported, detection_client).await;
198210
}
199211
}
200212
schedule => {
@@ -210,7 +222,7 @@ async fn run_env_mode(
210222

211223
// Update on start if configured
212224
if config.update_on_start {
213-
updater::update_once(config, handle, notifier, heartbeat, cf_cache, ppfmt).await;
225+
updater::update_once(config, handle, notifier, heartbeat, cf_cache, ppfmt, &mut noop_reported, detection_client).await;
214226
}
215227

216228
// Main loop
@@ -237,7 +249,7 @@ async fn run_env_mode(
237249
return;
238250
}
239251

240-
updater::update_once(config, handle, notifier, heartbeat, cf_cache, ppfmt).await;
252+
updater::update_once(config, handle, notifier, heartbeat, cf_cache, ppfmt, &mut noop_reported, detection_client).await;
241253
}
242254
}
243255
}
@@ -386,6 +398,7 @@ mod tests {
386398
config: &[LegacyCloudflareEntry],
387399
ttl: i64,
388400
purge_unknown_records: bool,
401+
noop_reported: &mut std::collections::HashSet<String>,
389402
) {
390403
for entry in config {
391404
#[derive(serde::Deserialize)]
@@ -487,8 +500,10 @@ mod tests {
487500
}
488501
}
489502

503+
let noop_key = format!("{fqdn}:{record_type}");
490504
if let Some(ref id) = identifier {
491505
if modified {
506+
noop_reported.remove(&noop_key);
492507
if self.dry_run {
493508
println!("[DRY RUN] Would update record {fqdn} -> {ip}");
494509
} else {
@@ -504,23 +519,30 @@ mod tests {
504519
)
505520
.await;
506521
}
507-
} else if self.dry_run {
508-
println!("[DRY RUN] Record {fqdn} is up to date ({ip})");
522+
} else if noop_reported.insert(noop_key) {
523+
if self.dry_run {
524+
println!("[DRY RUN] Record {fqdn} is up to date");
525+
} else {
526+
println!("Record {fqdn} is up to date");
527+
}
509528
}
510-
} else if self.dry_run {
511-
println!("[DRY RUN] Would add new record {fqdn} -> {ip}");
512529
} else {
513-
println!("Adding new record {fqdn} -> {ip}");
514-
let create_endpoint =
515-
format!("zones/{}/dns_records", entry.zone_id);
516-
let _: Option<serde_json::Value> = self
517-
.cf_api(
518-
&create_endpoint,
519-
"POST",
520-
&entry.authentication.api_token,
521-
Some(&record),
522-
)
523-
.await;
530+
noop_reported.remove(&noop_key);
531+
if self.dry_run {
532+
println!("[DRY RUN] Would add new record {fqdn} -> {ip}");
533+
} else {
534+
println!("Adding new record {fqdn} -> {ip}");
535+
let create_endpoint =
536+
format!("zones/{}/dns_records", entry.zone_id);
537+
let _: Option<serde_json::Value> = self
538+
.cf_api(
539+
&create_endpoint,
540+
"POST",
541+
&entry.authentication.api_token,
542+
Some(&record),
543+
)
544+
.await;
545+
}
524546
}
525547

526548
if purge_unknown_records {
@@ -640,7 +662,7 @@ mod tests {
640662

641663
let ddns = TestDdnsClient::new(&mock_server.uri());
642664
let config = test_config(zone_id);
643-
ddns.commit_record("198.51.100.7", "A", &config.cloudflare, 300, false)
665+
ddns.commit_record("198.51.100.7", "A", &config.cloudflare, 300, false, &mut std::collections::HashSet::new())
644666
.await;
645667
}
646668

@@ -689,7 +711,7 @@ mod tests {
689711

690712
let ddns = TestDdnsClient::new(&mock_server.uri());
691713
let config = test_config(zone_id);
692-
ddns.commit_record("198.51.100.7", "A", &config.cloudflare, 300, false)
714+
ddns.commit_record("198.51.100.7", "A", &config.cloudflare, 300, false, &mut std::collections::HashSet::new())
693715
.await;
694716
}
695717

@@ -732,7 +754,7 @@ mod tests {
732754

733755
let ddns = TestDdnsClient::new(&mock_server.uri());
734756
let config = test_config(zone_id);
735-
ddns.commit_record("198.51.100.7", "A", &config.cloudflare, 300, false)
757+
ddns.commit_record("198.51.100.7", "A", &config.cloudflare, 300, false, &mut std::collections::HashSet::new())
736758
.await;
737759
}
738760

@@ -766,7 +788,7 @@ mod tests {
766788

767789
let ddns = TestDdnsClient::new(&mock_server.uri()).dry_run();
768790
let config = test_config(zone_id);
769-
ddns.commit_record("198.51.100.7", "A", &config.cloudflare, 300, false)
791+
ddns.commit_record("198.51.100.7", "A", &config.cloudflare, 300, false, &mut std::collections::HashSet::new())
770792
.await;
771793
}
772794

@@ -823,7 +845,7 @@ mod tests {
823845
ip4_provider: None,
824846
ip6_provider: None,
825847
};
826-
ddns.commit_record("198.51.100.7", "A", &config.cloudflare, 300, true)
848+
ddns.commit_record("198.51.100.7", "A", &config.cloudflare, 300, true, &mut std::collections::HashSet::new())
827849
.await;
828850
}
829851

@@ -925,7 +947,7 @@ mod tests {
925947
ip6_provider: None,
926948
};
927949

928-
ddns.commit_record("203.0.113.99", "A", &config.cloudflare, 300, false)
950+
ddns.commit_record("203.0.113.99", "A", &config.cloudflare, 300, false, &mut std::collections::HashSet::new())
929951
.await;
930952
}
931953
}

src/notifier.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ fn parse_shoutrrr_url(url_str: &str) -> Result<ShoutrrrService, String> {
406406
service_type: ShoutrrrServiceType::Pushover,
407407
webhook_url: format!(
408408
"https://api.pushover.net/1/messages.json?token={}&user={}",
409-
parts[1], parts[0]
409+
parts[0], parts[1]
410410
),
411411
});
412412
}
@@ -868,7 +868,7 @@ mod tests {
868868

869869
#[test]
870870
fn test_parse_pushover() {
871-
let result = parse_shoutrrr_url("pushover://userkey@apitoken").unwrap();
871+
let result = parse_shoutrrr_url("pushover://apitoken@userkey").unwrap();
872872
assert_eq!(
873873
result.webhook_url,
874874
"https://api.pushover.net/1/messages.json?token=apitoken&user=userkey"
@@ -1307,7 +1307,8 @@ mod tests {
13071307
#[test]
13081308
fn test_pushover_url_query_parsing() {
13091309
// Verify that the pushover webhook URL format contains the right params
1310-
let service = parse_shoutrrr_url("pushover://myuser@mytoken").unwrap();
1310+
// shoutrrr format: pushover://token@user
1311+
let service = parse_shoutrrr_url("pushover://mytoken@myuser").unwrap();
13111312
let parsed = url::Url::parse(&service.webhook_url).unwrap();
13121313
let params: std::collections::HashMap<_, _> = parsed.query_pairs().collect();
13131314
assert_eq!(params.get("token").unwrap().as_ref(), "mytoken");

0 commit comments

Comments
 (0)