Repository navigation
Reliability fixes: SIGTERM, failed-write reporting, fail-closed config (#302, #292) - #303
Open
timothymiller wants to merge 9 commits into
Open
timothymiller wants to merge 9 commits into
timothymiller wants to merge 9 commits into
Conversation
- Treat SIGTERM like SIGINT so docker/kubectl/systemd stops run DELETE_ON_STOP and the exit heartbeat; make all waits interruptible. - Exit with status 1 when a single-shot run fails. - Reject invalid PROXIED, managed-comment regexes, WAF_LISTS entries, TTL and timeouts at startup instead of silently falling back (an invalid regex used to mean "manage every record"). - Clamp UPDATE_CRON @every to a 30s minimum; legacy TTL auto no longer polls every second. - Strip quotes from API tokens and reject malformed ones with a clear message (#292); legacy entries without credentials are a startup error. - Substitute longest CF_DDNS_* names first and warn on unset references.
- Strip the query string Uptime Kuma's UI includes in push URLs so pings are no longer malformed (#302). - Log heartbeat and notification failures with the HTTP status or transport error; URLs are redacted to their scheme since they embed tokens. - Respect QUIET/EMOJI for notifier and heartbeat warnings. - Send Telegram messages as plain text; Markdown parse mode rejected messages containing '_' or '*'. - Redact credentials in Auth's Debug output.
- set_ips returns Failed when a create, update or delete is rejected, so heartbeats and notifications no longer report success with stale DNS. - A failed record listing is a failure instead of an empty list, which used to trigger duplicate creates. - Filter DNS record listings by name server-side; zones with more than 100 records could miss the target. - Cache zone IDs per domain instead of looking them up every cycle. - Follow cursor pagination for WAF list items; route item deletion through the shared API helper so errors are logged. - Create a missing WAF list using WAF_LIST_DESCRIPTION. - DELETE_ON_STOP reports per-domain failures and the exit heartbeat reflects them (previously two exit heartbeats were sent).
- Check create/update/delete results and report failures in heartbeats, notifications and the exit code (update_legacy always returned success). - Look up records by name server-side and match case-insensitively. - A failed listing no longer looks like a missing record. - When a deterministic provider reports no address and purgeUnknownRecords is on, delete only the configured subdomains' records instead of every A/AAAA record in the zone. - Treat a transient detection failure as an unsuccessful cycle.
Only cloudflare.trace used a family-bound client; the others could connect over the wrong family on dual-stack hosts and fail every cycle. Also reject multicast, reserved, benchmarking, 0.0.0.0/8 and IPv4-mapped addresses.
…r build - Delete TestDdnsClient from main.rs: a third copy of the legacy update logic that only tested itself; LegacyDdnsClient is covered in updater.rs. - Add tests for the interruptible shutdown sleep. - Serialize config tests that write shared env vars (intermittent failures). - Build the image with --locked.
API errors now name the failed operation instead of printing the request URL, which contains zone and account IDs; transport errors drop the URL too. WAF lists are described by name only. Addresses CodeQL cleartext-logging alerts.
There was a problem hiding this comment.
🟡 Changes recommended
Several failure paths can still report success, create WAF lists after lookup errors, accept invalid configuration, or expose notification credentials.
7 open findings
Redact credentials from all parser error messages · New Distinguish missing WAF lists from lookup failures · New Treat WAF lookup failures as unsuccessful cleanup · New Normalize placeholder tokens before API key fallback · New Enforce documented TTL range during startup validation · New Report Cloudflare purge failures in heartbeat and notifications · New Notify on failed duplicate-record deletion · New
What changed in this PR
Improves shutdown handling, configuration validation, Cloudflare update reliability, and monitoring diagnostics.
Changes:
- Adds graceful SIGTERM shutdown and interruptible scheduling.
- Fails closed on invalid configuration and reports Cloudflare write failures.
- Improves WAF handling, IP detection, heartbeat reporting, and secret redaction.
| File | Description |
|---|---|
Cargo.toml |
Enables Tokio watch channels. |
Dockerfile |
Enforces locked release builds. |
src/cloudflare.rs |
Adds caching, WAF creation/pagination, and failure propagation. |
src/config.rs |
Strengthens validation and token normalization. |
src/domain.rs |
Simplifies domain matching. |
src/main.rs |
Adds graceful shutdown and exit-status handling. |
src/notifier.rs |
Improves heartbeat errors, redaction, and Telegram delivery. |
src/pp.rs |
Makes output configuration cloneable. |
src/provider.rs |
Pins address families and rejects additional non-global ranges. |
src/updater.rs |
Improves legacy updates, deletion safety, and failure reporting. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+90
to
+94
| pub fn redact_url(url: &str) -> String { | ||
| match url.split_once("://") { | ||
| Some((scheme, _)) => format!("{scheme}://***"), | ||
| None => "***".to_string(), | ||
| } |
| ) -> SetResult { | ||
| let list_meta = match self.find_waf_list(waf_list, ppfmt).await { | ||
| Some(meta) => meta, | ||
| let (list_meta, existing_items) = match self.find_waf_list(waf_list, ppfmt).await { |
Comment on lines
876
to
+878
| let list_meta = match self.find_waf_list(waf_list, ppfmt).await { | ||
| Some(meta) => meta, | ||
| None => return, | ||
| None => return true, |
Comment on lines
+492
to
+500
| let has_token = !auth.api_token.trim().is_empty() && auth.api_token != "api_token_here"; | ||
| if has_token { | ||
| auth.api_token = | ||
| normalize_token(&auth.api_token, &format!("cloudflare[{i}].authentication.api_token"), &ppfmt)?; | ||
| } else if auth.api_key.is_none() { | ||
| return Err(format!( | ||
| "config.json: cloudflare[{i}] has no api_token or api_key credentials" | ||
| )); | ||
| } |
Comment on lines
+623
to
+628
| let ttl_val = match getenv("TTL") { | ||
| Some(s) => s | ||
| .parse::<i64>() | ||
| .map_err(|_| format!("Invalid TTL: '{s}'. Use 1 (auto) or a number of seconds."))?, | ||
| None => 1, | ||
| }; |
Comment on lines
+385
to
387
| ok &= ddns | ||
| .delete_entries(ip_type.record_type(), &legacy.cloudflare) | ||
| .await; |
| let _: Option<serde_json::Value> = self | ||
| .cf_api(&del_endpoint, "DELETE", entry, None::<&()>.as_ref()) | ||
| .await; | ||
| ok &= self.delete_record(entry, dup_id).await; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Reliability fixes for the next patch release. Fixes #302 and #292 (startup diagnostics); #294 is a docs fix in the companion docs PR.
Behavior changes
docker stop/kubectl delete pod/systemctl stopnow runDELETE_ON_STOPand send the exit heartbeat instead of being SIGKILLed after the grace period. Sleeps are interruptible.set_ipsand the legacy client return/report failure when a create, update or delete is rejected; previously monitors stayed green while DNS went stale. A failed record listing no longer looks like "no records" (which could create duplicates).PROXIED,MANAGED_RECORDS_COMMENT_REGEX,MANAGED_WAF_LIST_ITEMS_COMMENT_REGEX,WAF_LISTS,TTL,DETECTION_TIMEOUT,UPDATE_TIMEOUTare startup errors. An invalid managed-records regex used to mean "manage every record"; an invalidPROXIEDsilently disabled proxying.@everybelow 30s is clamped; legacyttl: 1+--repeatpolls every 300s instead of every second.Auth'sDebugis redacted.WAF_LIST_DESCRIPTION, previously unused) and item listing follows cursor pagination.purgeUnknownRecordson definitive IP absence deletes only the configured subdomains' records — it previously deleted every A/AAAA record in the zone._/*).Other fixes
cloudflare.doh,ipify,url:providers pinned to the requested address family; more non-global ranges rejected.TestDdnsClient(duplicate of the legacy client used only by its own tests), fixed racy env-var tests,cargo build --lockedin the Dockerfile, clippy fixes.Testing
cargo test: 406 passing (regression tests added for each fix above).cargo clippy --all-targetswarnings reduced to style lints (too-many-arguments,TTLnaming, complex types).Notes for review
tokiogains thesyncfeature (watch channel for shutdown).