From 2913ce379c7d64c3089e184383faca8bc2659e91 Mon Sep 17 00:00:00 2001 From: Timothy Miller Date: Mon, 23 Mar 2026 20:01:29 -0400 Subject: [PATCH] Fix Shoutrrr notifications not sending in legacy config.json mode and bump to 2.0.10 Legacy mode (config.json) never set the notify flag or generated notification messages, so Shoutrrr services (Telegram, Discord, Slack, etc.) were silently skipped even when DNS records were created or updated. Propagate messages and the notify flag from the legacy update path back to update_once() so notifications fire correctly. --- Cargo.lock | 2 +- Cargo.toml | 2 +- SECURITY.md | 2 +- src/updater.rs | 56 ++++++++++++++++++++++++++++++++++++-------------- 4 files changed, 44 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f5b1831..fc7541b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -139,7 +139,7 @@ dependencies = [ [[package]] name = "cloudflare-ddns" -version = "2.0.9" +version = "2.0.10" dependencies = [ "chrono", "idna", diff --git a/Cargo.toml b/Cargo.toml index 89a8065..f2e6e11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cloudflare-ddns" -version = "2.0.9" +version = "2.0.10" edition = "2021" description = "Access your home network remotely via a custom domain name without a static IP" license = "GPL-3.0" diff --git a/SECURITY.md b/SECURITY.md index 8b71857..fba5fa2 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -47,7 +47,7 @@ This project handles **Cloudflare API tokens** that grant DNS editing privileges - The Docker image runs as a **static binary from scratch** with zero runtime dependencies, which minimizes the attack surface. - Use `security_opt: no-new-privileges:true` in Docker Compose deployments. -- Pin image tags to a specific version (e.g., `timothyjmiller/cloudflare-ddns:v2.0.9`) rather than using `latest` in production. +- Pin image tags to a specific version (e.g., `timothyjmiller/cloudflare-ddns:v2.0.10`) rather than using `latest` in production. ### Network Security diff --git a/src/updater.rs b/src/updater.rs index 059b8ef..cf64fd1 100644 --- a/src/updater.rs +++ b/src/updater.rs @@ -26,7 +26,11 @@ pub async fn update_once( let mut notify = false; // NEW: track meaningful events if config.legacy_mode { - all_ok = update_legacy(config, cf_cache, ppfmt, noop_reported, detection_client).await; + let (ok, legacy_msgs, legacy_notify) = + update_legacy(config, cf_cache, ppfmt, noop_reported, detection_client).await; + all_ok = ok; + messages = legacy_msgs; + notify = legacy_notify; } else { // Detect IPs for each provider let mut detected_ips: HashMap> = HashMap::new(); @@ -251,10 +255,10 @@ async fn update_legacy( ppfmt: &PP, noop_reported: &mut HashSet, detection_client: &Client, -) -> bool { +) -> (bool, Vec, bool) { let legacy = match &config.legacy_config { Some(l) => l, - None => return false, + None => return (false, Vec::new(), false), }; let ddns = LegacyDdnsClient { @@ -341,16 +345,17 @@ async fn update_legacy( } } - ddns.update_ips( - &ips, - &legacy.cloudflare, - legacy.ttl, - legacy.purge_unknown_records, - noop_reported, - ) - .await; + let (msgs, should_notify) = ddns + .update_ips( + &ips, + &legacy.cloudflare, + legacy.ttl, + legacy.purge_unknown_records, + noop_reported, + ) + .await; - true + (true, msgs, should_notify) } /// Delete records on stop (for env var mode). @@ -499,11 +504,19 @@ impl LegacyDdnsClient { ttl: i64, purge_unknown_records: bool, noop_reported: &mut HashSet, - ) { + ) -> (Vec, bool) { + let mut messages = Vec::new(); + let mut notify = false; for ip in ips.values() { - self.commit_record(ip, config, ttl, purge_unknown_records, noop_reported) + let (msgs, changed) = self + .commit_record(ip, config, ttl, purge_unknown_records, noop_reported) .await; + messages.extend(msgs); + if changed { + notify = true; + } } + (messages, notify) } async fn commit_record( @@ -513,7 +526,9 @@ impl LegacyDdnsClient { ttl: i64, purge_unknown_records: bool, noop_reported: &mut HashSet, - ) { + ) -> (Vec, bool) { + let mut messages = Vec::new(); + let mut changed = false; for entry in config { let zone_resp: Option> = self .cf_api( @@ -592,6 +607,7 @@ impl LegacyDdnsClient { if let Some(ref id) = identifier { if modified { noop_reported.remove(&noop_key); + changed = true; if self.dry_run { println!("[DRY RUN] Would update record {fqdn} -> {}", ip.ip); } else { @@ -602,6 +618,10 @@ impl LegacyDdnsClient { .cf_api(&update_endpoint, "PUT", entry, Some(&record)) .await; } + messages.push(Message::new_ok(&format!( + "Updated {fqdn} -> {}", + ip.ip + ))); } else if noop_reported.insert(noop_key) { if self.dry_run { println!("[DRY RUN] Record {fqdn} is up to date"); @@ -611,6 +631,7 @@ impl LegacyDdnsClient { } } else { noop_reported.remove(&noop_key); + changed = true; if self.dry_run { println!("[DRY RUN] Would add new record {fqdn} -> {}", ip.ip); } else { @@ -620,6 +641,10 @@ impl LegacyDdnsClient { .cf_api(&create_endpoint, "POST", entry, Some(&record)) .await; } + messages.push(Message::new_ok(&format!( + "Created {fqdn} -> {}", + ip.ip + ))); } if purge_unknown_records { @@ -638,6 +663,7 @@ impl LegacyDdnsClient { } } } + (messages, changed) } }