Repository: favonia/cloudflare-ddns
Pull request #909 (2024-08-30 11:26:18)

docs: more documentation tweaks

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/favonia/cloudflare-ddns/pull/909?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 95.59%. Comparing base [(`1acf11d`)](https://app.codecov.io/gh/favonia/cloudflare-ddns/commit/1acf11d311addfc504695c788213226b51e5b89b?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia) to head [(`59f2c1e`)](https://app.codecov.io/gh/favonia/cloudflare-ddns/commit/59f2c1e378d41fb859bfeaee4f22b92333008ee1?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia).

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main     #909   +/-   ##
=======================================
  Coverage   95.59%   95.59%           
=======================================
  Files          61       61           
  Lines        2586     2588    +2     
=======================================
+ Hits         2472     2474    +2     
  Misses        104      104           
  Partials       10       10           
```

| [Flag](https://app.codecov.io/gh/favonia/cloudflare-ddns/pull/909/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia) | Coverage Δ | |
|---|---|---|
| [unittests](https://app.codecov.io/gh/favonia/cloudflare-ddns/pull/909/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia) | `95.59% <100.00%> (+<0.01%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/favonia/cloudflare-ddns/pull/909?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia).


Patch:
diff --git a/README.markdown b/README.markdown
index 4488a62..0cc3200 100644
--- a/README.markdown
+++ b/README.markdown
@@ -28,7 +28,7 @@ A feature-rich and robust Cloudflare DDNS updater with a small footprint. The pr
 
 - 😶‍🌫️ You can toggle [Cloudflare proxying](https://developers.cloudflare.com/dns/manage-dns-records/reference/proxied-dns-records/) for each domain.
 - 📝 You can set [DNS record comments](https://developers.cloudflare.com/dns/manage-dns-records/reference/record-attributes/) (and probably record tags soon).
-- 📜 The updater can maintain [lists](https://developers.cloudflare.com/waf/tools/lists/custom-lists/) of detected IP addresses. These lists can then be referenced in various Cloudflare products that use [Cloudflare’s Rules language](https://developers.cloudflare.com/ruleset-engine/), such as [Web Application Firewall (WAF)](https://developers.cloudflare.com/waf/) and [Cloudflare Rules](https://developers.cloudflare.com/rules/).
+- 📜 The updater can maintain [lists](https://developers.cloudflare.com/waf/tools/lists/custom-lists/) of detected IP addresses. These lists can then be referenced in any Cloudflare product that uses [Cloudflare’s Rules language](https://developers.cloudflare.com/ruleset-engine/), such as [Cloudflare Web Application Firewall (WAF)](https://developers.cloudflare.com/waf/) and [Cloudflare Rules](https://developers.cloudflare.com/rules/). (We call the lists “WAF lists”, but their use is not limited to Cloudflare WAF.)
 
 ### 👁️ Integration with Notification Services
 
@@ -260,52 +260,43 @@ _(Click to expand the following items.)_
 
 > You need to specify at least one thing in `DOMAINS`, `IP4_DOMAINS`, `IP6_DOMAINS`, or 🧪 `WAF_LISTS` for the updater to update.
 
-| Name           | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
-| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `DOMAINS`      | Comma-separated fully qualified domain names or wildcard domain names that the updater should manage for both `A` and `AAAA` records. Listing a domain in `DOMAINS` is equivalent to listing the same domain in both `IP4_DOMAINS` and `IP6_DOMAINS`.                                                                                                                                                                                                                                                                                                 |
-| `IP4_DOMAINS`  | Comma-separated fully qualified domain names or wildcard domain names that the updater should manage for `A` records                                                                                                                                                                                                                                                                                                                                                                                                                                  |
-| `IP6_DOMAINS`  | Comma-separated fully qualified domain names or wildcard domain names that the updater should manage for `AAAA` records                                                                                                                                                                                                                                                                                                                                                                                                                               |
-| 🧪 `WAF_LISTS` | 🧪 Comma-separated references of [WAF lists](https://developers.cloudflare.com/waf/tools/lists/custom-lists/) the updater should manage. A list reference is written in the format `account-id/list-name` where `account-id` is your account ID and `list-name` is the list name; it should look like `0123456789abcdef0123456789abcdef/mylist`. If the referenced WAF list does not exist, the updater will try to create it. 💡 See [how to find your account ID](https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/). |
+| Name           | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
+| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `DOMAINS`      | Comma-separated fully qualified domain names or wildcard domain names that the updater should manage for both `A` and `AAAA` records. Listing a domain in `DOMAINS` is equivalent to listing the same domain in both `IP4_DOMAINS` and `IP6_DOMAINS`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
+| `IP4_DOMAINS`  | Comma-separated fully qualified domain names or wildcard domain names that the updater should manage for `A` records                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
+| `IP6_DOMAINS`  | Comma-separated fully qualified domain names or wildcard domain names that the updater should manage for `AAAA` records                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
+| 🧪 `WAF_LISTS` | 🧪 Comma-separated references of [WAF lists](https://developers.cloudflare.com/waf/tools/lists/custom-lists/) the updater should manage. A list reference is written in the format `<account-id>/<list-name>` where `account-id` is your account ID and `list-name` is the list name; it should look like `0123456789abcdef0123456789abcdef/mylist`. If the referenced WAF list does not exist, the updater will try to create it. 💡 See [how to find your account ID](https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/). 🧪 This feature to manipulate WAF lists is highly experimental (introduced in 1.14.0) and is subject to changes. Please [open a GitHub issue](https://github.com/favonia/cloudflare-ddns/issues/new) to provide feedback. Thanks! |
 
-> 🧪 The feature to manipulate WAF lists is experimental (introduced in 1.14.0) and is subject to changes. In particular, the updater currently deletes unmanaged IPs from WAF lists (e.g., deleting IPv6 addresses if you disable IPv6), but another reasonable implementation is to leave them alone. Please [open a GitHub issue](https://github.com/favonia/cloudflare-ddns/issues/new) to provide feedback. Thanks!
+> 🃏🤖 **Wildcard domains** (`*.example.org`) represent all subdomains that _would not exist otherwise._ Therefore, if you have another subdomain entry `sub.example.org`, the wildcard domain is independent of it, because it only represents the _other_ subdomains which do not have their own entries. Also, you can only have one layer of `*`---`*.*.example.org` would not work.
 
-> 🤖 Internationalized domain names are handled using the _nontransitional processing_ (fully compatible with IDNA2008). At this point, all major browsers and whatnot have switched to the same nontransitional processing. See this [useful FAQ on internationalized domain names](https://www.unicode.org/faq/idn.html).
+> 🌐🤖 **Internationalized domain names** are handled using the _nontransitional processing_ (fully compatible with IDNA2008). At this point, all major browsers and whatnot have switched to the same nontransitional processing. See [this useful FAQ on internationalized domain names](https://www.unicode.org/faq/idn.html).
 
-> <details>
-> <summary>🃏 What are wildcard domains?</summary>
+> 🤖 Technical notes on WAF lists:
 >
-> Wildcard domains (`*.example.org`) represent all subdomains that _would not exist otherwise._ Therefore, if you have another subdomain entry `sub.example.org`, the wildcard domain is independent of it, because it only represents the _other_ subdomains which do not have their own entries. Also, you can only have one layer of `*`---`*.*.example.org` would not work.
->
-> </details>
+> 1. [Cloudflare does not allow single IPv6 addresses in a WAF list](https://developers.cloudflare.com/waf/tools/lists/custom-lists/#lists-with-ip-addresses-ip-lists), and thus the updater will use the smallest IP range allowed by Cloudflare that contains the detected IPv6 address.
+> 2. The updater will delete unmanaged IPs from the specified WAF lists (e.g., if you disable IPv6 with `IP6_PROVIDER=none`, then existing IPv6 addresses or IPv6 ranges in the lists will be deleted). The idea is that the list should only contain detected IP addresses.
 
 </details>
 
 <details>
 <summary>🔍 IP address providers</summary>
 
-| Name           | Meaning                                                                                                                                                                                                                                              | Default Value      |
-| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
-| `IP4_PROVIDER` | This specifies how to detect the current IPv4 address. Available providers include `cloudflare.doh`, `cloudflare.trace`, `local`, `url:URL`, and `none`. The special `none` provider disables IPv4 completely. See below for a detailed explanation. | `cloudflare.trace` |
-| `IP6_PROVIDER` | This specifies how to detect the current IPv6 address. Available providers include `cloudflare.doh`, `cloudflare.trace`, `local`, `url:URL`, and `none`. The special `none` provider disables IPv6 completely. See below for a detailed explanation. | `cloudflare.trace` |
+| Name           | Meaning                                                                                                                                                                                                                                                | Default Value      |
+| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ |
+| `IP4_PROVIDER` | This specifies how to detect the current IPv4 address. Available providers include `cloudflare.doh`, `cloudflare.trace`, `local`, `url:<URL>`, and `none`. The special `none` provider disables IPv4 completely. See below for a detailed explanation. | `cloudflare.trace` |
+| `IP6_PROVIDER` | This specifies how to detect the current IPv6 address. Available providers include `cloudflare.doh`, `cloudflare.trace`, `local`, `url:<URL>`, and `none`. The special `none` provider disables IPv6 completely. See below for a detailed explanation. | `cloudflare.trace` |
 
 > 👉 The option `IP4_PROVIDER` governs `A`-type DNS records and IPv4 addresses in WAF lists, while the option `IP6_PROVIDER` governs `AAAA`-type DNS records and IPv6 addresses in WAF lists. The two options act independently of each other. You can specify different address providers for IPv4 and IPv6.
 
 > 📡 Available IP address providers:
 >
-> - `cloudflare.doh`\
->   Get the IP address by querying `whoami.cloudflare.` against [Cloudflare via DNS-over-HTTPS](https://developers.cloudflare.com/1.1.1.1/dns-over-https).
-> - `cloudflare.trace`\
->   Get the IP address by parsing the [Cloudflare debugging page](https://one.one.one.one/cdn-cgi/trace). This is the default provider.
-> - `local`\
->   Get the IP address via local network interfaces. When multiple local network interfaces or in general multiple IP addresses are present, the updater will use the address that _would have_ been used for outbound UDP connections to Cloudflare servers. (No data will be transmitted.)
->   ⚠️ You need access to the host network (such as `network_mode: host` in Docker Compose) for this policy, for otherwise the updater will detect the addresses inside [the default bridge network in Docker](https://docs.docker.com/network/bridge/) instead of those in the host network.
-> - `url:URL`\
->   Fetch the content at `URL` via the HTTP(S) protocol treat the content as the IP address. The provider format is `url:` followed by the URL. For example, `IP4_PROVIDER=url:https://api4.ipify.org` will fetch the IPv4 address from <https://api4.ipify.org>, a server maintained by [ipify](https://www.ipify.org).
->   🐞 **KNOWN ISSUE: Currently, the updater _will not_ force IPv4 or IPv6 when retrieving the IPv4 or IPv6 address at `URL`.** Therefore, for `IP4_PROVIDER=url:URL`, the updater might use IPv6 (instead of the expected IPv4) to connect to `URL`, and if the server returns an IPv6 address because of this, the updating will fail. The server at `URL` must either restrict its access to the expected IP network or return a valid IP address in the expected IP network regardless of what IP network is used for connection. As a working example, <https://api4.ipify.org> has restricted its access to IPv4, and thus it’s impossible to use the wrong IP network (IPv6) to connect to it. 🧪 This is a known issue and may be fixed in the future.
-> - `none`\
->   Stop the DNS updating for the specified IP version completely. For example `IP4_PROVIDER=none` will disable IPv4 completely. Existing DNS records will not be removed. ⚠️ The IP addresses of the disabled IP version will be removed from WAF lists; so `IP4_PROVIDER=none` will remove all IPv4 addresses from all managed WAF lists. 🧪 As the support of WAF lists is experimental, this behavior is subject to changes and please [provide feedback](https://github.com/favonia/cloudflare-ddns/issues/new).
->
-> 🤖 Technical details: For the providers `cloudflare.doh` and `cloudflare.trace`, the updater will connect to the servers `1.1.1.1` for IPv4 and `2606:4700:4700::1111` for IPv6. Since version 1.9.3, the updater will switch to `1.0.0.1` for IPv4 if `1.1.1.1` appears to be blocked or intercepted by your ISP or your router (which is still not uncommon).
+> | Provider Name      | Explanation                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
+> | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+> | `cloudflare.doh`   | Get the IP address by querying `whoami.cloudflare.` against [Cloudflare via DNS-over-HTTPS](https://developers.cloudflare.com/1.1.1.1/dns-over-https). 🤖 The updater will connect `1.1.1.1` for IPv4 and `2606:4700:4700::1111` for IPv6. Since version 1.9.3, the updater will switch to `1.0.0.1` for IPv4 if `1.1.1.1` appears to be blocked or intercepted by your ISP or your router (which is still not uncommon). Since version 1.14.0, the blockage detection uses a variant of [the Happy Eyeballs algorithm](https://en.wikipedia.org/wiki/Happy_Eyeballs) to reduce delay.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
+> | `cloudflare.trace` | Get the IP address by parsing the [Cloudflare debugging page](https://one.one.one.one/cdn-cgi/trace). **This is the default provider.** 🤖 The updater will connect `1.1.1.1` for IPv4 and `2606:4700:4700::1111` for IPv6. Since version 1.9.3, the updater will switch to `1.0.0.1` for IPv4 if `1.1.1.1` appears to be blocked or intercepted by your ISP or your router (which is still not uncommon). Since version 1.14.0, the blockage detection uses a variant of [the Happy Eyeballs algorithm](https://en.wikipedia.org/wiki/Happy_Eyeballs) to reduce delay.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
+> | `local`            | Get the IP address via local network interfaces. When multiple local network interfaces or in general multiple IP addresses are present, the updater will use the address that _would have_ been used for outbound UDP connections to Cloudflare servers. (No data will be transmitted.) ⚠️ You need access to the host network (such as `network_mode: host` in Docker Compose) for this policy, for otherwise the updater will detect the addresses inside [the default bridge network in Docker](https://docs.docker.com/network/bridge/) instead of those in the host network.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
+> | `url:<URL>`        | Fetch the content at `URL` via the HTTP(S) protocol treat the content as the IP address. The provider format is `url:` followed by the URL. For example, `IP4_PROVIDER=url:https://api4.ipify.org` will fetch the IPv4 address from <https://api4.ipify.org>, a server maintained by [ipify](https://www.ipify.org). 🐞 **KNOWN ISSUE: Currently, the updater _will not_ force IPv4 or IPv6 when retrieving the IPv4 or IPv6 address at `URL`.** Therefore, for `IP4_PROVIDER=url:URL`, the updater might use IPv6 (instead of the expected IPv4) to connect to `URL`, and if the server returns an IPv6 address because of this, the updating will fail. The server at `URL` must either restrict its access to the expected IP network or return a valid IP address in the expected IP network regardless of what IP network is used for connection. As a working example, <https://api4.ipify.org> has restricted its access to IPv4, and thus it’s impossible to use the wrong IP network (IPv6) to connect to it. 🧪 This is a known issue and may be fixed in the future. |
+> | `none`             | Stop the DNS updating for the specified IP version completely. For example `IP4_PROVIDER=none` will disable IPv4 completely. Existing DNS records will not be removed. ⚠️ The IP addresses of the disabled IP version will be removed from WAF lists; so `IP4_PROVIDER=none` will remove all IPv4 addresses from all managed WAF lists. 🧪 As the support of WAF lists is experimental, this behavior is subject to changes and please [provide feedback](https://github.com/favonia/cloudflare-ddns/issues/new).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
 
 </details>
 
@@ -315,7 +306,7 @@ _(Click to expand the following items.)_
 | Name               | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | Default Value                 |
 | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
 | `CACHE_EXPIRATION` | The expiration of cached Cloudflare API responses. It can be any positive time duration accepted by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration), such as `1h` or `10m`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | `6h0m0s` (6 hours)            |
-| `DELETE_ON_STOP`   | Whether managed DNS records and WAF lists should be deleted on exit. It can be any boolean value accepted by [strconv.ParseBool](https://pkg.go.dev/strconv#ParseBool), such as `true`, `false`, `0` or `1`. If a WAF list is used in a rule expression, the updater cannot delete it and will attempt to clear it instead.                                                                                                                                                                                                                                                                                                                                                                       | `false`                       |
+| `DELETE_ON_STOP`   | Whether managed DNS records and WAF lists should be deleted on exit. It can be any boolean value accepted by [strconv.ParseBool](https://pkg.go.dev/strconv#ParseBool), such as `true`, `false`, `0` or `1`. If a WAF list is used in a rule expression, the list cannot be deleted (for otherwise the rule expression would be broken), but the updater will try to remove all IP addresses from the list.                                                                                                                                                                                                                                                                                       | `false`                       |
 | `TZ`               | The timezone used for logging messages and parsing `UPDATE_CRON`. It can be any timezone accepted by [time.LoadLocation](https://pkg.go.dev/time#LoadLocation), including any IANA Time Zone. 🤖 The pre-built Docker images come with the embedded timezone database via the [time/tzdata](https://pkg.go.dev/time/tzdata) package.                                                                                                                                                                                                                                                                                                                                                              | `UTC`                         |
 | `UPDATE_CRON`      | The schedule to re-check IP addresses and update DNS records and WAF lists (if needed). The format is [any cron expression accepted by the `cron` library](https://pkg.go.dev/github.com/robfig/cron/v3#hdr-CRON_Expression_Format) or the special value `@once`. The special value `@once` means the updater will terminate immediately after updating the DNS records or WAF lists, effectively disabling the scheduling feature. 🤖 The update schedule _does not_ take the time to update records into consideration. For example, if the schedule is `@every 5m`, and if the updating itself takes 2 minutes, then the actual interval between adjacent updates is 3 minutes, not 5 minutes. | `@every 5m` (every 5 minutes) |
 | `UPDATE_ON_START`  | Whether to check IP addresses (and possibly update DNS records and WAF lists) _immediately_ on start, regardless of the update schedule specified by `UPDATE_CRON`. It can be any boolean value accepted by [strconv.ParseBool](https://pkg.go.dev/strconv#ParseBool), such as `true`, `false`, `0` or `1`.                                                                                                                                                                                                                                                                                                                                                                                       | `true`                        |
@@ -325,17 +316,17 @@ _(Click to expand the following items.)_
 <details>
 <summary>⏳ Timeouts of various operations</summary>
 
-| Name                | Meaning                                                                                                                                                                                                                     | Default Value      |
-| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
-| `DETECTION_TIMEOUT` | The timeout of each attempt to detect IP address, per IP version (IPv4 and IPv6). It can be any positive time duration accepted by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration), such as `1h` or `10m`. | `5s` (5 seconds)   |
-| `UPDATE_TIMEOUT`    | The timeout of each attempt to update DNS records, per domain, per record type. It can be any positive time duration accepted by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration), such as `1h` or `10m`.   | `30s` (30 seconds) |
+| Name                | Meaning                                                                                                                                                                                                                                       | Default Value      |
+| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
+| `DETECTION_TIMEOUT` | The timeout of each attempt to detect IP address, per IP version (IPv4 and IPv6). It can be any positive time duration accepted by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration), such as `1h` or `10m`.                   | `5s` (5 seconds)   |
+| `UPDATE_TIMEOUT`    | The timeout of each attempt to update DNS records, per domain and per record type, or per WAF list. It can be any positive time duration accepted by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration), such as `1h` or `10m`. | `30s` (30 seconds) |
 
 </details>
 
 <details>
 <summary>🐣 Parameters of new DNS records and WAF lists</summary>
 
-> 👉 The updater will preserve existing parameters (TTL, proxy states, DNS record comments, etc.). Only when it creates new DNS records and new WAF lists, the following settings will apply. To change existing parameters, you can go to your [Cloudflare Dashboard](https://dash.cloudflare.com) and change them directly. If you think you have a use case where the updater should actively overwrite existing parameters in addition to IP addresses, please [let me know](https://github.com/favonia/cloudflare-ddns/issues/new). 🐞🧪 **KNOWN ISSUE: existing comments attached to individual WAF list items (not WAF lists themselves) are not preserved** because the Cloudflare API does not provide an easy way to update existing WAF list items. The comments will be lost when the updater deletes stale WAF list items and creates new ones.
+> 👉 The updater will preserve existing parameters (TTL, proxy states, DNS record comments, etc.). Only when it creates new DNS records and new WAF lists, the following settings will apply. To change existing parameters, you can go to your [Cloudflare Dashboard](https://dash.cloudflare.com) and change them directly. If you think you have a use case where the updater should actively overwrite existing parameters in addition to IP addresses, please [let me know](https://github.com/favonia/cloudflare-ddns/issues/new). 🐞🧪 **KNOWN ISSUE: existing comments attached to stale WAF list items (not WAF lists themselves) will not be transferred to new list items for new IP addresses** because the Cloudflare API does not provide an easy way to update them. The comments will be lost when the updater deletes stale list items and create new ones.
 
 | Name                      | Meaning                                                                                                                                                                                                                                                                      | Default Value                              |
 | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
@@ -344,7 +335,7 @@ _(Click to expand the following items.)_
 | `RECORD_COMMENT`          | The [record comment](https://developers.cloudflare.com/dns/manage-dns-records/reference/record-attributes/) of new DNS records.                                                                                                                                              | `""`                                       |
 | 🧪 `WAF_LIST_DESCRIPTION` | 🧪 The text description of new WAF lists.                                                                                                                                                                                                                                    | `""`                                       |
 
-> 🤖🧪 If you are an advanced user, the `PROXIED` can be a boolean expression involving domains! This allows you to enable Cloudflare proxying for some domains but not the others. Here are some example expressions:
+> 🤖🧪 For advanced users: the `PROXIED` can be a boolean expression involving domains! This allows you to enable Cloudflare proxying for some domains but not the others. Here are some example expressions:
 >
 > - `PROXIED=is(example.org)`: proxy only the domain `example.org`
 > - `PROXIED=is(example1.org) || sub(example2.org)`: proxy only the domain `example1.org` and subdomains of `example2.org`
@@ -353,22 +344,25 @@ _(Click to expand the following items.)_
 >
 > A boolean expression must be one of the following forms (all whitespace is ignored):
 >
-> - A boolean value accepted by [strconv.ParseBool](https://pkg.go.dev/strconv#ParseBool), such as `t` as `true` or `FALSE` as `false`.
-> - `is(d)` which matches the domain `d`. Note that `is(*.a)` only matches the wildcard domain `*.a`; use `sub(a)` to match all subdomains of `a` (including `*.a`).
-> - `sub(d)` which matches subdomains of `d`, such as `a.d` and `b.c.d`. It does not match the domain `d` itself.
-> - `! e` where `e` is a boolean expression, representing logical negation of `e`.
-> - `e1 || e2` where `e1` and `e2` are boolean expressions, representing logical disjunction of `e1` and `e2`.
-> - `e1 && e2` where `e1` and `e2` are boolean expressions, representing logical conjunction of `e1` and `e2`.
+> | Syntax                                                                                                                 | Meaning                                                                                                                                             |
+> | ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
+> | Any string accepted by [strconv.ParseBool](https://pkg.go.dev/strconv#ParseBool), such as `true`, `false`, `0`, or `1` | Logical truth or falsehood                                                                                                                          |
+> | `is(d)`                                                                                                                | Matching the domain `d`. Note that `is(*.a)` only matches the wildcard domain `*.a`; use `sub(a)` to match all subdomains of `a` (including `*.a`). |
+> | `sub(d)`                                                                                                               | Matching subdomains of `d`, such as `a.d`, `b.c.d`, and `*.d`. It does not match the domain `d` itself.                                             |
+> | `! e`                                                                                                                  | Logical negation of the boolean expression `e`                                                                                                      |
+> | <code>e1 &#124;&#124; e2</code>                                                                                        | Logical disjunction of the boolean expressions `e1` and `e2`                                                                                        |
+> | `e1 && e2`                                                                                                             | Logical conjunction of the boolean expressions `e1` and `e2`                                                                                        |
 >
-> One can use parentheses to group expressions, such as `!(is(a) && (is(b) || is(c)))`.
-> For convenience, the engine also accepts these short forms:
+> One can use parentheses to group expressions, such as `!(is(a) && (is(b) || is(c)))`. For convenience, the parser also accepts these short forms:
 >
-> - `is(d1, d2, ..., dn)` is `is(d1) || is(d2) || ... || is(dn)`
-> - `sub(d1, d2, ..., dn)` is `sub(d1) || sub(d2) || ... || sub(dn)`
+> | Short Form             | Equivalent Full Form                                                            |
+> | ---------------------- | ------------------------------------------------------------------------------- |
+> | `is(d1, d2, ..., dn)`  | <code>is(d1) &#124;&#124; is(d2) &#124;&#124; ... &#124;&#124; is(dn)</code>    |
+> | `sub(d1, d2, ..., dn)` | <code>sub(d1) &#124;&#124; sub(d2) &#124;&#124; ... &#124;&#124; sub(dn)</code> |
 >
 > For example, these two settings are equivalent:
 >
-> - `PROXYD=is(example1.org) || is(example2.org) || is(example3.org)`
+> - `PROXIED=is(example1.org) || is(example2.org) || is(example3.org)`
 > - `PROXIED=is(example1.org,example2.org,example3.org)`
 > </details>
 
diff --git a/internal/provider/happy_eyeballs.go b/internal/provider/happy_eyeballs.go
index eb672ba..350810f 100644
--- a/internal/provider/happy_eyeballs.go
+++ b/internal/provider/happy_eyeballs.go
@@ -104,6 +104,11 @@ func (p happyEyeballs) GetIP(ctx context.Context, ppfmt pp.PP, ipNet ipnet.Type)
 				// remember this successful IP detection
 				p.chosen[ipNet] = r.method
 				queuedPP[r.method].Flush()
+
+				if r.method == protocol.MethodAlternative {
+					ppfmt.Infof(pp.EmojiNow, "The server 1.0.0.1 responded before 1.1.1.1 does and will be used from now on.")
+				}
+
 				return r.ip, r.method, r.ok
 			}
 


Test patch:
diff --git a/internal/provider/happy_eyeballs_test.go b/internal/provider/happy_eyeballs_test.go
index 108a52f..54feb28 100644
--- a/internal/provider/happy_eyeballs_test.go
+++ b/internal/provider/happy_eyeballs_test.go
@@ -83,6 +83,7 @@ func TestHappyEyeballs(t *testing.T) {
 						return ip1001, true
 					}).Times(1)
 				ppfmt.EXPECT().Infof(pp.EmojiGood, "Got 1.0.0.1!")
+				ppfmt.EXPECT().Infof(pp.EmojiNow, "The server 1.0.0.1 responded before 1.1.1.1 does and will be used from now on.")
 			},
 		},
 		"primary-delayed/alternative-instant-fails": {
@@ -140,6 +141,7 @@ func TestHappyEyeballs(t *testing.T) {
 						return ip1001, true
 					}).Times(1)
 				ppfmt.EXPECT().Infof(pp.EmojiGood, "Got 1.0.0.1!")
+				ppfmt.EXPECT().Infof(pp.EmojiNow, "The server 1.0.0.1 responded before 1.1.1.1 does and will be used from now on.")
 			},
 		},
 		"primary-instant-fails/alternative-instant-fails": {
@@ -179,6 +181,7 @@ func TestHappyEyeballs(t *testing.T) {
 						return ip1001, true
 					}).Times(1)
 				ppfmt.EXPECT().Infof(pp.EmojiGood, "Got 1.0.0.1!")
+				ppfmt.EXPECT().Infof(pp.EmojiNow, "The server 1.0.0.1 responded before 1.1.1.1 does and will be used from now on.")
 			},
 		},
 		"primary-timeout/alternative-timeout": {