mirror of
https://github.com/timothymiller/cloudflare-ddns.git
synced 2026-03-26 08:58:57 -03:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e2b8a3a40 | ||
|
|
9b140d2350 | ||
|
|
2913ce379c |
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
target/
|
||||||
|
.git/
|
||||||
|
.github/
|
||||||
|
.gitignore
|
||||||
|
*.md
|
||||||
|
LICENSE
|
||||||
468
Cargo.lock
generated
468
Cargo.lock
generated
@@ -11,15 +11,6 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "android_system_properties"
|
|
||||||
version = "0.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.102"
|
version = "1.0.102"
|
||||||
@@ -42,34 +33,6 @@ version = "1.1.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "autocfg"
|
|
||||||
version = "1.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aws-lc-rs"
|
|
||||||
version = "1.16.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc"
|
|
||||||
dependencies = [
|
|
||||||
"aws-lc-sys",
|
|
||||||
"zeroize",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aws-lc-sys"
|
|
||||||
version = "0.39.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1fa7e52a4c5c547c741610a2c6f123f3881e409b714cd27e6798ef020c514f0a"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
"cmake",
|
|
||||||
"dunce",
|
|
||||||
"fs_extra",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.22.1"
|
version = "0.22.1"
|
||||||
@@ -101,8 +64,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423"
|
checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"jobserver",
|
|
||||||
"libc",
|
|
||||||
"shlex",
|
"shlex",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -118,34 +79,14 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfg_aliases"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "chrono"
|
|
||||||
version = "0.4.44"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
|
|
||||||
dependencies = [
|
|
||||||
"iana-time-zone",
|
|
||||||
"js-sys",
|
|
||||||
"num-traits",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"windows-link",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cloudflare-ddns"
|
name = "cloudflare-ddns"
|
||||||
version = "2.0.9"
|
version = "2.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
|
||||||
"idna",
|
|
||||||
"if-addrs",
|
"if-addrs",
|
||||||
"regex",
|
"regex-lite",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
"rustls",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
@@ -154,15 +95,6 @@ dependencies = [
|
|||||||
"wiremock",
|
"wiremock",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cmake"
|
|
||||||
version = "0.1.57"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "combine"
|
name = "combine"
|
||||||
version = "4.6.7"
|
version = "4.6.7"
|
||||||
@@ -218,12 +150,6 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dunce"
|
|
||||||
version = "1.0.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
@@ -273,12 +199,6 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fs_extra"
|
|
||||||
version = "1.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.3.32"
|
version = "0.3.32"
|
||||||
@@ -374,24 +294,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"js-sys",
|
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "getrandom"
|
|
||||||
version = "0.3.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"js-sys",
|
|
||||||
"libc",
|
|
||||||
"r-efi 5.3.0",
|
|
||||||
"wasip2",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -402,7 +306,7 @@ checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"r-efi 6.0.0",
|
"r-efi",
|
||||||
"wasip2",
|
"wasip2",
|
||||||
"wasip3",
|
"wasip3",
|
||||||
]
|
]
|
||||||
@@ -560,30 +464,6 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "iana-time-zone"
|
|
||||||
version = "0.1.65"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
|
||||||
dependencies = [
|
|
||||||
"android_system_properties",
|
|
||||||
"core-foundation-sys",
|
|
||||||
"iana-time-zone-haiku",
|
|
||||||
"js-sys",
|
|
||||||
"log",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"windows-core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "iana-time-zone-haiku"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "icu_collections"
|
name = "icu_collections"
|
||||||
version = "2.1.1"
|
version = "2.1.1"
|
||||||
@@ -747,7 +627,7 @@ dependencies = [
|
|||||||
"combine",
|
"combine",
|
||||||
"jni-sys 0.3.1",
|
"jni-sys 0.3.1",
|
||||||
"log",
|
"log",
|
||||||
"thiserror 1.0.69",
|
"thiserror",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"windows-sys 0.45.0",
|
"windows-sys 0.45.0",
|
||||||
]
|
]
|
||||||
@@ -780,16 +660,6 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "jobserver"
|
|
||||||
version = "0.1.34"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
|
||||||
dependencies = [
|
|
||||||
"getrandom 0.3.4",
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.91"
|
version = "0.3.91"
|
||||||
@@ -836,12 +706,6 @@ version = "0.4.29"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lru-slab"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.8.0"
|
version = "2.8.0"
|
||||||
@@ -859,15 +723,6 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-traits"
|
|
||||||
version = "0.2.19"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
version = "1.17.0"
|
version = "1.17.0"
|
||||||
@@ -917,15 +772,6 @@ dependencies = [
|
|||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ppv-lite86"
|
|
||||||
version = "0.2.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
|
||||||
dependencies = [
|
|
||||||
"zerocopy",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prettyplease"
|
name = "prettyplease"
|
||||||
version = "0.2.37"
|
version = "0.2.37"
|
||||||
@@ -945,62 +791,6 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quinn"
|
|
||||||
version = "0.11.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
|
|
||||||
dependencies = [
|
|
||||||
"bytes",
|
|
||||||
"cfg_aliases",
|
|
||||||
"pin-project-lite",
|
|
||||||
"quinn-proto",
|
|
||||||
"quinn-udp",
|
|
||||||
"rustc-hash",
|
|
||||||
"rustls",
|
|
||||||
"socket2",
|
|
||||||
"thiserror 2.0.18",
|
|
||||||
"tokio",
|
|
||||||
"tracing",
|
|
||||||
"web-time",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quinn-proto"
|
|
||||||
version = "0.11.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
|
|
||||||
dependencies = [
|
|
||||||
"aws-lc-rs",
|
|
||||||
"bytes",
|
|
||||||
"getrandom 0.3.4",
|
|
||||||
"lru-slab",
|
|
||||||
"rand",
|
|
||||||
"ring",
|
|
||||||
"rustc-hash",
|
|
||||||
"rustls",
|
|
||||||
"rustls-pki-types",
|
|
||||||
"slab",
|
|
||||||
"thiserror 2.0.18",
|
|
||||||
"tinyvec",
|
|
||||||
"tracing",
|
|
||||||
"web-time",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quinn-udp"
|
|
||||||
version = "0.5.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
|
|
||||||
dependencies = [
|
|
||||||
"cfg_aliases",
|
|
||||||
"libc",
|
|
||||||
"once_cell",
|
|
||||||
"socket2",
|
|
||||||
"tracing",
|
|
||||||
"windows-sys 0.60.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.45"
|
version = "1.0.45"
|
||||||
@@ -1010,47 +800,12 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "r-efi"
|
|
||||||
version = "5.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "r-efi"
|
name = "r-efi"
|
||||||
version = "6.0.0"
|
version = "6.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand"
|
|
||||||
version = "0.9.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
|
||||||
dependencies = [
|
|
||||||
"rand_chacha",
|
|
||||||
"rand_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_chacha"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
|
||||||
dependencies = [
|
|
||||||
"ppv-lite86",
|
|
||||||
"rand_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_core"
|
|
||||||
version = "0.9.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
|
|
||||||
dependencies = [
|
|
||||||
"getrandom 0.3.4",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.12.3"
|
version = "1.12.3"
|
||||||
@@ -1074,6 +829,12 @@ dependencies = [
|
|||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-lite"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.10"
|
version = "0.8.10"
|
||||||
@@ -1099,7 +860,6 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"quinn",
|
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"rustls-platform-verifier",
|
"rustls-platform-verifier",
|
||||||
@@ -1132,12 +892,6 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustc-hash"
|
|
||||||
version = "2.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "1.1.4"
|
version = "1.1.4"
|
||||||
@@ -1157,8 +911,8 @@ version = "0.23.37"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
|
checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-lc-rs",
|
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"ring",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"rustls-webpki",
|
"rustls-webpki",
|
||||||
"subtle",
|
"subtle",
|
||||||
@@ -1183,7 +937,6 @@ version = "1.14.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
|
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"web-time",
|
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1220,7 +973,6 @@ version = "0.103.10"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef"
|
checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-lc-rs",
|
|
||||||
"ring",
|
"ring",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"untrusted",
|
"untrusted",
|
||||||
@@ -1440,16 +1192,7 @@ version = "1.0.69"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl 1.0.69",
|
"thiserror-impl",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror"
|
|
||||||
version = "2.0.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
|
||||||
dependencies = [
|
|
||||||
"thiserror-impl 2.0.18",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1463,17 +1206,6 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror-impl"
|
|
||||||
version = "2.0.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinystr"
|
name = "tinystr"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
@@ -1484,21 +1216,6 @@ dependencies = [
|
|||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tinyvec"
|
|
||||||
version = "1.11.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
|
|
||||||
dependencies = [
|
|
||||||
"tinyvec_macros",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tinyvec_macros"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.50.0"
|
version = "1.50.0"
|
||||||
@@ -1801,16 +1518,6 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "web-time"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
|
||||||
dependencies = [
|
|
||||||
"js-sys",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webpki-root-certs"
|
name = "webpki-root-certs"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
@@ -1829,65 +1536,12 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-core"
|
|
||||||
version = "0.62.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
|
||||||
dependencies = [
|
|
||||||
"windows-implement",
|
|
||||||
"windows-interface",
|
|
||||||
"windows-link",
|
|
||||||
"windows-result",
|
|
||||||
"windows-strings",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-implement"
|
|
||||||
version = "0.60.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-interface"
|
|
||||||
version = "0.59.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-link"
|
name = "windows-link"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-result"
|
|
||||||
version = "0.4.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
|
||||||
dependencies = [
|
|
||||||
"windows-link",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-strings"
|
|
||||||
version = "0.5.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
|
||||||
dependencies = [
|
|
||||||
"windows-link",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.45.0"
|
version = "0.45.0"
|
||||||
@@ -1906,15 +1560,6 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-sys"
|
|
||||||
version = "0.60.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
|
||||||
dependencies = [
|
|
||||||
"windows-targets 0.53.5",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.61.2"
|
version = "0.61.2"
|
||||||
@@ -1948,30 +1593,13 @@ dependencies = [
|
|||||||
"windows_aarch64_gnullvm 0.52.6",
|
"windows_aarch64_gnullvm 0.52.6",
|
||||||
"windows_aarch64_msvc 0.52.6",
|
"windows_aarch64_msvc 0.52.6",
|
||||||
"windows_i686_gnu 0.52.6",
|
"windows_i686_gnu 0.52.6",
|
||||||
"windows_i686_gnullvm 0.52.6",
|
"windows_i686_gnullvm",
|
||||||
"windows_i686_msvc 0.52.6",
|
"windows_i686_msvc 0.52.6",
|
||||||
"windows_x86_64_gnu 0.52.6",
|
"windows_x86_64_gnu 0.52.6",
|
||||||
"windows_x86_64_gnullvm 0.52.6",
|
"windows_x86_64_gnullvm 0.52.6",
|
||||||
"windows_x86_64_msvc 0.52.6",
|
"windows_x86_64_msvc 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-targets"
|
|
||||||
version = "0.53.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
|
||||||
dependencies = [
|
|
||||||
"windows-link",
|
|
||||||
"windows_aarch64_gnullvm 0.53.1",
|
|
||||||
"windows_aarch64_msvc 0.53.1",
|
|
||||||
"windows_i686_gnu 0.53.1",
|
|
||||||
"windows_i686_gnullvm 0.53.1",
|
|
||||||
"windows_i686_msvc 0.53.1",
|
|
||||||
"windows_x86_64_gnu 0.53.1",
|
|
||||||
"windows_x86_64_gnullvm 0.53.1",
|
|
||||||
"windows_x86_64_msvc 0.53.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_gnullvm"
|
name = "windows_aarch64_gnullvm"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
@@ -1984,12 +1612,6 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_gnullvm"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
@@ -2002,12 +1624,6 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_msvc"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
@@ -2020,24 +1636,12 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnu"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnullvm"
|
name = "windows_i686_gnullvm"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnullvm"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
@@ -2050,12 +1654,6 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_msvc"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
@@ -2068,12 +1666,6 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnu"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
@@ -2086,12 +1678,6 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnullvm"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
@@ -2104,12 +1690,6 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_msvc"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wiremock"
|
name = "wiremock"
|
||||||
version = "0.6.5"
|
version = "0.6.5"
|
||||||
@@ -2250,26 +1830,6 @@ dependencies = [
|
|||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zerocopy"
|
|
||||||
version = "0.8.47"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87"
|
|
||||||
dependencies = [
|
|
||||||
"zerocopy-derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zerocopy-derive"
|
|
||||||
version = "0.8.47"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerofrom"
|
name = "zerofrom"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
|
|||||||
13
Cargo.toml
13
Cargo.toml
@@ -1,23 +1,22 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cloudflare-ddns"
|
name = "cloudflare-ddns"
|
||||||
version = "2.0.9"
|
version = "2.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Access your home network remotely via a custom domain name without a static IP"
|
description = "Access your home network remotely via a custom domain name without a static IP"
|
||||||
license = "GPL-3.0"
|
license = "GPL-3.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
reqwest = { version = "0.13", features = ["json", "form", "rustls"], default-features = false }
|
reqwest = { version = "0.13", features = ["json", "form", "rustls-no-provider"], default-features = false }
|
||||||
|
rustls = { version = "0.23", features = ["ring"], default-features = false }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
tokio = { version = "1", features = ["rt-multi-thread", "macros", "time", "signal", "net"] }
|
tokio = { version = "1", features = ["rt", "macros", "time", "signal", "net"] }
|
||||||
regex = "1"
|
regex-lite = "0.1"
|
||||||
chrono = { version = "0.4", features = ["clock"] }
|
|
||||||
url = "2"
|
url = "2"
|
||||||
idna = "1"
|
|
||||||
if-addrs = "0.15"
|
if-addrs = "0.15"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = "s"
|
opt-level = "z"
|
||||||
lto = true
|
lto = true
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
strip = true
|
strip = true
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ WORKDIR /build
|
|||||||
COPY Cargo.toml Cargo.lock ./
|
COPY Cargo.toml Cargo.lock ./
|
||||||
COPY src ./src
|
COPY src ./src
|
||||||
RUN cargo build --release
|
RUN cargo build --release
|
||||||
|
RUN apk add --no-cache upx && upx --best --lzma target/release/cloudflare-ddns
|
||||||
|
|
||||||
# ---- Release ----
|
# ---- Release ----
|
||||||
FROM scratch AS release
|
FROM scratch AS release
|
||||||
|
|||||||
15
README.md
15
README.md
@@ -363,6 +363,21 @@ Some ISP provided modems only allow port forwarding over IPv4 or IPv6. Disable t
|
|||||||
|
|
||||||
### ⚙️ Config Options
|
### ⚙️ Config Options
|
||||||
|
|
||||||
|
By default, the legacy config file is loaded from `./config.json`. Set the `CONFIG_PATH` environment variable to change the directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
CONFIG_PATH=/etc/cloudflare-ddns cloudflare-ddns
|
||||||
|
```
|
||||||
|
|
||||||
|
Or in Docker Compose:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
environment:
|
||||||
|
- CONFIG_PATH=/config
|
||||||
|
volumes:
|
||||||
|
- /your/path/config.json:/config/config.json
|
||||||
|
```
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
| Key | Type | Default | Description |
|
||||||
|-----|------|---------|-------------|
|
|-----|------|---------|-------------|
|
||||||
| `cloudflare` | array | required | List of zone configurations |
|
| `cloudflare` | array | required | List of zone configurations |
|
||||||
|
|||||||
@@ -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.
|
- 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.
|
- 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
|
### Network Security
|
||||||
|
|
||||||
|
|||||||
@@ -152,16 +152,16 @@ pub struct CloudflareHandle {
|
|||||||
client: Client,
|
client: Client,
|
||||||
base_url: String,
|
base_url: String,
|
||||||
auth: Auth,
|
auth: Auth,
|
||||||
managed_comment_regex: Option<regex::Regex>,
|
managed_comment_regex: Option<regex_lite::Regex>,
|
||||||
managed_waf_comment_regex: Option<regex::Regex>,
|
managed_waf_comment_regex: Option<regex_lite::Regex>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CloudflareHandle {
|
impl CloudflareHandle {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
auth: Auth,
|
auth: Auth,
|
||||||
update_timeout: Duration,
|
update_timeout: Duration,
|
||||||
managed_comment_regex: Option<regex::Regex>,
|
managed_comment_regex: Option<regex_lite::Regex>,
|
||||||
managed_waf_comment_regex: Option<regex::Regex>,
|
managed_waf_comment_regex: Option<regex_lite::Regex>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let client = Client::builder()
|
let client = Client::builder()
|
||||||
.timeout(update_timeout)
|
.timeout(update_timeout)
|
||||||
@@ -182,6 +182,7 @@ impl CloudflareHandle {
|
|||||||
base_url: &str,
|
base_url: &str,
|
||||||
auth: Auth,
|
auth: Auth,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
crate::init_crypto();
|
||||||
let client = Client::builder()
|
let client = Client::builder()
|
||||||
.timeout(Duration::from_secs(10))
|
.timeout(Duration::from_secs(10))
|
||||||
.build()
|
.build()
|
||||||
@@ -200,39 +201,18 @@ impl CloudflareHandle {
|
|||||||
format!("{}/{path}", self.base_url)
|
format!("{}/{path}", self.base_url)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn api_get<T: serde::de::DeserializeOwned>(
|
async fn api_request<T: serde::de::DeserializeOwned>(
|
||||||
&self,
|
&self,
|
||||||
|
method: reqwest::Method,
|
||||||
path: &str,
|
path: &str,
|
||||||
|
body: Option<&impl Serialize>,
|
||||||
ppfmt: &PP,
|
ppfmt: &PP,
|
||||||
) -> Option<T> {
|
) -> Option<T> {
|
||||||
let url = self.api_url(path);
|
let url = self.api_url(path);
|
||||||
let req = self.auth.apply(self.client.get(&url));
|
let mut req = self.auth.apply(self.client.request(method.clone(), &url));
|
||||||
match req.send().await {
|
if let Some(b) = body {
|
||||||
Ok(resp) => {
|
req = req.json(b);
|
||||||
if resp.status().is_success() {
|
|
||||||
resp.json::<T>().await.ok()
|
|
||||||
} else {
|
|
||||||
let url_str = resp.url().to_string();
|
|
||||||
let text = resp.text().await.unwrap_or_default();
|
|
||||||
ppfmt.errorf(pp::EMOJI_ERROR, &format!("API GET '{url_str}' failed: {text}"));
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
ppfmt.errorf(pp::EMOJI_ERROR, &format!("API GET '{path}' error: {e}"));
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async fn api_post<T: serde::de::DeserializeOwned, B: Serialize>(
|
|
||||||
&self,
|
|
||||||
path: &str,
|
|
||||||
body: &B,
|
|
||||||
ppfmt: &PP,
|
|
||||||
) -> Option<T> {
|
|
||||||
let url = self.api_url(path);
|
|
||||||
let req = self.auth.apply(self.client.post(&url)).json(body);
|
|
||||||
match req.send().await {
|
match req.send().await {
|
||||||
Ok(resp) => {
|
Ok(resp) => {
|
||||||
if resp.status().is_success() {
|
if resp.status().is_success() {
|
||||||
@@ -240,63 +220,12 @@ impl CloudflareHandle {
|
|||||||
} else {
|
} else {
|
||||||
let url_str = resp.url().to_string();
|
let url_str = resp.url().to_string();
|
||||||
let text = resp.text().await.unwrap_or_default();
|
let text = resp.text().await.unwrap_or_default();
|
||||||
ppfmt.errorf(pp::EMOJI_ERROR, &format!("API POST '{url_str}' failed: {text}"));
|
ppfmt.errorf(pp::EMOJI_ERROR, &format!("API {method} '{url_str}' failed: {text}"));
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
ppfmt.errorf(pp::EMOJI_ERROR, &format!("API POST '{path}' error: {e}"));
|
ppfmt.errorf(pp::EMOJI_ERROR, &format!("API {method} '{path}' error: {e}"));
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn api_put<T: serde::de::DeserializeOwned, B: Serialize>(
|
|
||||||
&self,
|
|
||||||
path: &str,
|
|
||||||
body: &B,
|
|
||||||
ppfmt: &PP,
|
|
||||||
) -> Option<T> {
|
|
||||||
let url = self.api_url(path);
|
|
||||||
let req = self.auth.apply(self.client.put(&url)).json(body);
|
|
||||||
match req.send().await {
|
|
||||||
Ok(resp) => {
|
|
||||||
if resp.status().is_success() {
|
|
||||||
resp.json::<T>().await.ok()
|
|
||||||
} else {
|
|
||||||
let url_str = resp.url().to_string();
|
|
||||||
let text = resp.text().await.unwrap_or_default();
|
|
||||||
ppfmt.errorf(pp::EMOJI_ERROR, &format!("API PUT '{url_str}' failed: {text}"));
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
ppfmt.errorf(pp::EMOJI_ERROR, &format!("API PUT '{path}' error: {e}"));
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn api_delete<T: serde::de::DeserializeOwned>(
|
|
||||||
&self,
|
|
||||||
path: &str,
|
|
||||||
ppfmt: &PP,
|
|
||||||
) -> Option<T> {
|
|
||||||
let url = self.api_url(path);
|
|
||||||
let req = self.auth.apply(self.client.delete(&url));
|
|
||||||
match req.send().await {
|
|
||||||
Ok(resp) => {
|
|
||||||
if resp.status().is_success() {
|
|
||||||
resp.json::<T>().await.ok()
|
|
||||||
} else {
|
|
||||||
let url_str = resp.url().to_string();
|
|
||||||
let text = resp.text().await.unwrap_or_default();
|
|
||||||
ppfmt.errorf(pp::EMOJI_ERROR, &format!("API DELETE '{url_str}' failed: {text}"));
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
ppfmt.errorf(pp::EMOJI_ERROR, &format!("API DELETE '{path}' error: {e}"));
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -309,7 +238,7 @@ impl CloudflareHandle {
|
|||||||
let mut current = domain.to_string();
|
let mut current = domain.to_string();
|
||||||
loop {
|
loop {
|
||||||
let resp: Option<CfListResponse<ZoneResult>> = self
|
let resp: Option<CfListResponse<ZoneResult>> = self
|
||||||
.api_get(&format!("zones?name={current}"), ppfmt)
|
.api_request(reqwest::Method::GET, &format!("zones?name={current}"), None::<&()>, ppfmt)
|
||||||
.await;
|
.await;
|
||||||
if let Some(r) = resp {
|
if let Some(r) = resp {
|
||||||
if let Some(zones) = r.result {
|
if let Some(zones) = r.result {
|
||||||
@@ -340,7 +269,7 @@ impl CloudflareHandle {
|
|||||||
ppfmt: &PP,
|
ppfmt: &PP,
|
||||||
) -> Vec<DnsRecord> {
|
) -> Vec<DnsRecord> {
|
||||||
let path = format!("zones/{zone_id}/dns_records?per_page=100&type={record_type}");
|
let path = format!("zones/{zone_id}/dns_records?per_page=100&type={record_type}");
|
||||||
let resp: Option<CfListResponse<DnsRecord>> = self.api_get(&path, ppfmt).await;
|
let resp: Option<CfListResponse<DnsRecord>> = self.api_request(reqwest::Method::GET, &path, None::<&()>, ppfmt).await;
|
||||||
resp.and_then(|r| r.result).unwrap_or_default()
|
resp.and_then(|r| r.result).unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,7 +301,7 @@ impl CloudflareHandle {
|
|||||||
ppfmt: &PP,
|
ppfmt: &PP,
|
||||||
) -> Option<DnsRecord> {
|
) -> Option<DnsRecord> {
|
||||||
let path = format!("zones/{zone_id}/dns_records");
|
let path = format!("zones/{zone_id}/dns_records");
|
||||||
let resp: Option<CfResponse<DnsRecord>> = self.api_post(&path, payload, ppfmt).await;
|
let resp: Option<CfResponse<DnsRecord>> = self.api_request(reqwest::Method::POST, &path, Some(payload), ppfmt).await;
|
||||||
resp.and_then(|r| r.result)
|
resp.and_then(|r| r.result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,7 +313,7 @@ impl CloudflareHandle {
|
|||||||
ppfmt: &PP,
|
ppfmt: &PP,
|
||||||
) -> Option<DnsRecord> {
|
) -> Option<DnsRecord> {
|
||||||
let path = format!("zones/{zone_id}/dns_records/{record_id}");
|
let path = format!("zones/{zone_id}/dns_records/{record_id}");
|
||||||
let resp: Option<CfResponse<DnsRecord>> = self.api_put(&path, payload, ppfmt).await;
|
let resp: Option<CfResponse<DnsRecord>> = self.api_request(reqwest::Method::PUT, &path, Some(payload), ppfmt).await;
|
||||||
resp.and_then(|r| r.result)
|
resp.and_then(|r| r.result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,7 +324,7 @@ impl CloudflareHandle {
|
|||||||
ppfmt: &PP,
|
ppfmt: &PP,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let path = format!("zones/{zone_id}/dns_records/{record_id}");
|
let path = format!("zones/{zone_id}/dns_records/{record_id}");
|
||||||
let resp: Option<CfResponse<serde_json::Value>> = self.api_delete(&path, ppfmt).await;
|
let resp: Option<CfResponse<serde_json::Value>> = self.api_request(reqwest::Method::DELETE, &path, None::<&()>, ppfmt).await;
|
||||||
resp.is_some()
|
resp.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -550,7 +479,7 @@ impl CloudflareHandle {
|
|||||||
ppfmt: &PP,
|
ppfmt: &PP,
|
||||||
) -> Option<WAFListMeta> {
|
) -> Option<WAFListMeta> {
|
||||||
let path = format!("accounts/{}/rules/lists", waf_list.account_id);
|
let path = format!("accounts/{}/rules/lists", waf_list.account_id);
|
||||||
let resp: Option<CfListResponse<WAFListMeta>> = self.api_get(&path, ppfmt).await;
|
let resp: Option<CfListResponse<WAFListMeta>> = self.api_request(reqwest::Method::GET, &path, None::<&()>, ppfmt).await;
|
||||||
resp.and_then(|r| r.result)
|
resp.and_then(|r| r.result)
|
||||||
.and_then(|lists| lists.into_iter().find(|l| l.name == waf_list.list_name))
|
.and_then(|lists| lists.into_iter().find(|l| l.name == waf_list.list_name))
|
||||||
}
|
}
|
||||||
@@ -562,7 +491,7 @@ impl CloudflareHandle {
|
|||||||
ppfmt: &PP,
|
ppfmt: &PP,
|
||||||
) -> Vec<WAFListItem> {
|
) -> Vec<WAFListItem> {
|
||||||
let path = format!("accounts/{account_id}/rules/lists/{list_id}/items");
|
let path = format!("accounts/{account_id}/rules/lists/{list_id}/items");
|
||||||
let resp: Option<CfListResponse<WAFListItem>> = self.api_get(&path, ppfmt).await;
|
let resp: Option<CfListResponse<WAFListItem>> = self.api_request(reqwest::Method::GET, &path, None::<&()>, ppfmt).await;
|
||||||
resp.and_then(|r| r.result).unwrap_or_default()
|
resp.and_then(|r| r.result).unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -574,7 +503,7 @@ impl CloudflareHandle {
|
|||||||
ppfmt: &PP,
|
ppfmt: &PP,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let path = format!("accounts/{account_id}/rules/lists/{list_id}/items");
|
let path = format!("accounts/{account_id}/rules/lists/{list_id}/items");
|
||||||
let resp: Option<CfResponse<serde_json::Value>> = self.api_post(&path, &items, ppfmt).await;
|
let resp: Option<CfResponse<serde_json::Value>> = self.api_request(reqwest::Method::POST, &path, Some(&items), ppfmt).await;
|
||||||
resp.is_some()
|
resp.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -794,6 +723,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_with_regex(base_url: &str, pattern: &str) -> CloudflareHandle {
|
fn handle_with_regex(base_url: &str, pattern: &str) -> CloudflareHandle {
|
||||||
|
crate::init_crypto();
|
||||||
let client = Client::builder()
|
let client = Client::builder()
|
||||||
.timeout(Duration::from_secs(10))
|
.timeout(Duration::from_secs(10))
|
||||||
.build()
|
.build()
|
||||||
@@ -802,7 +732,7 @@ mod tests {
|
|||||||
client,
|
client,
|
||||||
base_url: base_url.to_string(),
|
base_url: base_url.to_string(),
|
||||||
auth: test_auth(),
|
auth: test_auth(),
|
||||||
managed_comment_regex: Some(regex::Regex::new(pattern).unwrap()),
|
managed_comment_regex: Some(regex_lite::Regex::new(pattern).unwrap()),
|
||||||
managed_waf_comment_regex: None,
|
managed_waf_comment_regex: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1424,7 +1354,7 @@ mod tests {
|
|||||||
api_key: "key123".to_string(),
|
api_key: "key123".to_string(),
|
||||||
email: "user@example.com".to_string(),
|
email: "user@example.com".to_string(),
|
||||||
};
|
};
|
||||||
let client = Client::new();
|
let client = crate::test_client();
|
||||||
let req = client.get("http://example.com");
|
let req = client.get("http://example.com");
|
||||||
let req = auth.apply(req);
|
let req = auth.apply(req);
|
||||||
// Just verify it doesn't panic - we can't inspect headers easily
|
// Just verify it doesn't panic - we can't inspect headers easily
|
||||||
@@ -1443,7 +1373,7 @@ mod tests {
|
|||||||
|
|
||||||
let h = handle(&server.uri());
|
let h = handle(&server.uri());
|
||||||
let pp = PP::new(false, true); // quiet
|
let pp = PP::new(false, true); // quiet
|
||||||
let result: Option<CfListResponse<ZoneResult>> = h.api_get("zones", &pp).await;
|
let result: Option<CfListResponse<ZoneResult>> = h.api_request(reqwest::Method::GET, "zones", None::<&()>, &pp).await;
|
||||||
assert!(result.is_none());
|
assert!(result.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1458,7 +1388,7 @@ mod tests {
|
|||||||
let h = handle(&server.uri());
|
let h = handle(&server.uri());
|
||||||
let pp = PP::new(false, true);
|
let pp = PP::new(false, true);
|
||||||
let body = serde_json::json!({"test": true});
|
let body = serde_json::json!({"test": true});
|
||||||
let result: Option<CfResponse<serde_json::Value>> = h.api_post("endpoint", &body, &pp).await;
|
let result: Option<CfResponse<serde_json::Value>> = h.api_request(reqwest::Method::POST, "endpoint", Some(&body), &pp).await;
|
||||||
assert!(result.is_none());
|
assert!(result.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1473,7 +1403,7 @@ mod tests {
|
|||||||
let h = handle(&server.uri());
|
let h = handle(&server.uri());
|
||||||
let pp = PP::new(false, true);
|
let pp = PP::new(false, true);
|
||||||
let body = serde_json::json!({"test": true});
|
let body = serde_json::json!({"test": true});
|
||||||
let result: Option<CfResponse<serde_json::Value>> = h.api_put("endpoint", &body, &pp).await;
|
let result: Option<CfResponse<serde_json::Value>> = h.api_request(reqwest::Method::PUT, "endpoint", Some(&body), &pp).await;
|
||||||
assert!(result.is_none());
|
assert!(result.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -87,10 +87,10 @@ pub struct AppConfig {
|
|||||||
pub ttl: TTL,
|
pub ttl: TTL,
|
||||||
pub proxied_expression: Option<Box<dyn Fn(&str) -> bool + Send + Sync>>,
|
pub proxied_expression: Option<Box<dyn Fn(&str) -> bool + Send + Sync>>,
|
||||||
pub record_comment: Option<String>,
|
pub record_comment: Option<String>,
|
||||||
pub managed_comment_regex: Option<regex::Regex>,
|
pub managed_comment_regex: Option<regex_lite::Regex>,
|
||||||
pub waf_list_description: Option<String>,
|
pub waf_list_description: Option<String>,
|
||||||
pub waf_list_item_comment: Option<String>,
|
pub waf_list_item_comment: Option<String>,
|
||||||
pub managed_waf_comment_regex: Option<regex::Regex>,
|
pub managed_waf_comment_regex: Option<regex_lite::Regex>,
|
||||||
pub detection_timeout: Duration,
|
pub detection_timeout: Duration,
|
||||||
pub update_timeout: Duration,
|
pub update_timeout: Duration,
|
||||||
pub reject_cloudflare_ips: bool,
|
pub reject_cloudflare_ips: bool,
|
||||||
@@ -330,9 +330,9 @@ fn read_cron_from_env(ppfmt: &PP) -> Result<CronSchedule, String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_regex(key: &str, ppfmt: &PP) -> Option<regex::Regex> {
|
fn read_regex(key: &str, ppfmt: &PP) -> Option<regex_lite::Regex> {
|
||||||
match getenv(key) {
|
match getenv(key) {
|
||||||
Some(s) if !s.is_empty() => match regex::Regex::new(&s) {
|
Some(s) if !s.is_empty() => match regex_lite::Regex::new(&s) {
|
||||||
Ok(r) => Some(r),
|
Ok(r) => Some(r),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
ppfmt.errorf(pp::EMOJI_ERROR, &format!("Invalid regex in {key}: {e}"));
|
ppfmt.errorf(pp::EMOJI_ERROR, &format!("Invalid regex in {key}: {e}"));
|
||||||
@@ -1931,19 +1931,16 @@ mod tests {
|
|||||||
let mut g = EnvGuard::set("_PLACEHOLDER_SN", "x");
|
let mut g = EnvGuard::set("_PLACEHOLDER_SN", "x");
|
||||||
g.remove("SHOUTRRR");
|
g.remove("SHOUTRRR");
|
||||||
let pp = PP::new(false, true);
|
let pp = PP::new(false, true);
|
||||||
let notifier = setup_notifiers(&pp);
|
let _notifier = setup_notifiers(&pp);
|
||||||
drop(g);
|
drop(g);
|
||||||
assert!(notifier.is_empty());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_setup_notifiers_empty_shoutrrr_returns_empty() {
|
fn test_setup_notifiers_empty_shoutrrr_returns_empty() {
|
||||||
let g = EnvGuard::set("SHOUTRRR", "");
|
let g = EnvGuard::set("SHOUTRRR", "");
|
||||||
let pp = PP::new(false, true);
|
let pp = PP::new(false, true);
|
||||||
let notifier = setup_notifiers(&pp);
|
let _notifier = setup_notifiers(&pp);
|
||||||
drop(g);
|
drop(g);
|
||||||
// Empty string is treated as unset by getenv_list.
|
|
||||||
assert!(notifier.is_empty());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -1956,9 +1953,8 @@ mod tests {
|
|||||||
g.remove("HEALTHCHECKS");
|
g.remove("HEALTHCHECKS");
|
||||||
g.remove("UPTIMEKUMA");
|
g.remove("UPTIMEKUMA");
|
||||||
let pp = PP::new(false, true);
|
let pp = PP::new(false, true);
|
||||||
let hb = setup_heartbeats(&pp);
|
let _hb = setup_heartbeats(&pp);
|
||||||
drop(g);
|
drop(g);
|
||||||
assert!(hb.is_empty());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1966,9 +1962,8 @@ mod tests {
|
|||||||
let mut g = EnvGuard::set("HEALTHCHECKS", "https://hc-ping.com/abc123");
|
let mut g = EnvGuard::set("HEALTHCHECKS", "https://hc-ping.com/abc123");
|
||||||
g.remove("UPTIMEKUMA");
|
g.remove("UPTIMEKUMA");
|
||||||
let pp = PP::new(false, true);
|
let pp = PP::new(false, true);
|
||||||
let hb = setup_heartbeats(&pp);
|
let _hb = setup_heartbeats(&pp);
|
||||||
drop(g);
|
drop(g);
|
||||||
assert!(!hb.is_empty());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1976,9 +1971,8 @@ mod tests {
|
|||||||
let mut g = EnvGuard::set("UPTIMEKUMA", "https://status.example.com/api/push/abc");
|
let mut g = EnvGuard::set("UPTIMEKUMA", "https://status.example.com/api/push/abc");
|
||||||
g.remove("HEALTHCHECKS");
|
g.remove("HEALTHCHECKS");
|
||||||
let pp = PP::new(false, true);
|
let pp = PP::new(false, true);
|
||||||
let hb = setup_heartbeats(&pp);
|
let _hb = setup_heartbeats(&pp);
|
||||||
drop(g);
|
drop(g);
|
||||||
assert!(!hb.is_empty());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1986,9 +1980,8 @@ mod tests {
|
|||||||
let mut g = EnvGuard::set("HEALTHCHECKS", "https://hc-ping.com/abc");
|
let mut g = EnvGuard::set("HEALTHCHECKS", "https://hc-ping.com/abc");
|
||||||
g.add("UPTIMEKUMA", "https://status.example.com/api/push/def");
|
g.add("UPTIMEKUMA", "https://status.example.com/api/push/def");
|
||||||
let pp = PP::new(false, true);
|
let pp = PP::new(false, true);
|
||||||
let hb = setup_heartbeats(&pp);
|
let _hb = setup_heartbeats(&pp);
|
||||||
drop(g);
|
drop(g);
|
||||||
assert!(!hb.is_empty());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|||||||
253
src/domain.rs
253
src/domain.rs
@@ -1,129 +1,14 @@
|
|||||||
use std::fmt;
|
|
||||||
|
|
||||||
/// Represents a DNS domain - either a regular FQDN or a wildcard.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub enum Domain {
|
|
||||||
FQDN(String),
|
|
||||||
Wildcard(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
impl Domain {
|
|
||||||
/// Parse a domain string. Handles:
|
|
||||||
/// - "@" or "" -> root domain (handled at FQDN construction time)
|
|
||||||
/// - "*.example.com" -> wildcard
|
|
||||||
/// - "sub.example.com" -> regular FQDN
|
|
||||||
pub fn new(input: &str) -> Result<Self, String> {
|
|
||||||
let trimmed = input.trim().to_lowercase();
|
|
||||||
if trimmed.starts_with("*.") {
|
|
||||||
let base = &trimmed[2..];
|
|
||||||
let ascii = domain_to_ascii(base)?;
|
|
||||||
Ok(Domain::Wildcard(ascii))
|
|
||||||
} else {
|
|
||||||
let ascii = domain_to_ascii(&trimmed)?;
|
|
||||||
Ok(Domain::FQDN(ascii))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the DNS name in ASCII form suitable for API calls.
|
|
||||||
pub fn dns_name_ascii(&self) -> String {
|
|
||||||
match self {
|
|
||||||
Domain::FQDN(s) => s.clone(),
|
|
||||||
Domain::Wildcard(s) => format!("*.{s}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a human-readable description of the domain.
|
|
||||||
pub fn describe(&self) -> String {
|
|
||||||
match self {
|
|
||||||
Domain::FQDN(s) => describe_domain(s),
|
|
||||||
Domain::Wildcard(s) => format!("*.{}", describe_domain(s)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the zones (parent domains) for this domain, from most specific to least.
|
|
||||||
pub fn zones(&self) -> Vec<String> {
|
|
||||||
let base = match self {
|
|
||||||
Domain::FQDN(s) => s.as_str(),
|
|
||||||
Domain::Wildcard(s) => s.as_str(),
|
|
||||||
};
|
|
||||||
let mut zones = Vec::new();
|
|
||||||
let mut current = base.to_string();
|
|
||||||
while !current.is_empty() {
|
|
||||||
zones.push(current.clone());
|
|
||||||
if let Some(pos) = current.find('.') {
|
|
||||||
current = current[pos + 1..].to_string();
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
zones
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Domain {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.describe())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Construct an FQDN from a subdomain name and base domain.
|
/// Construct an FQDN from a subdomain name and base domain.
|
||||||
pub fn make_fqdn(subdomain: &str, base_domain: &str) -> String {
|
pub fn make_fqdn(subdomain: &str, base_domain: &str) -> String {
|
||||||
let name = subdomain.to_lowercase();
|
let name = subdomain.to_lowercase();
|
||||||
let name = name.trim();
|
let name = name.trim();
|
||||||
if name.is_empty() || name == "@" {
|
if name.is_empty() || name == "@" {
|
||||||
base_domain.to_lowercase()
|
base_domain.to_lowercase()
|
||||||
} else if name.starts_with("*.") {
|
|
||||||
// Wildcard subdomain
|
|
||||||
format!("{name}.{}", base_domain.to_lowercase())
|
|
||||||
} else {
|
} else {
|
||||||
format!("{name}.{}", base_domain.to_lowercase())
|
format!("{name}.{}", base_domain.to_lowercase())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a domain to ASCII using IDNA encoding.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn domain_to_ascii(domain: &str) -> Result<String, String> {
|
|
||||||
if domain.is_empty() {
|
|
||||||
return Ok(String::new());
|
|
||||||
}
|
|
||||||
// Try IDNA encoding for internationalized domain names
|
|
||||||
match idna::domain_to_ascii(domain) {
|
|
||||||
Ok(ascii) => Ok(ascii),
|
|
||||||
Err(_) => {
|
|
||||||
// Fallback: if it's already ASCII, just return it
|
|
||||||
if domain.is_ascii() {
|
|
||||||
Ok(domain.to_string())
|
|
||||||
} else {
|
|
||||||
Err(format!("Invalid domain name: {domain}"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert ASCII domain back to Unicode for display.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn describe_domain(ascii: &str) -> String {
|
|
||||||
// Try to convert punycode back to unicode for display
|
|
||||||
match idna::domain_to_unicode(ascii) {
|
|
||||||
(unicode, Ok(())) => unicode,
|
|
||||||
_ => ascii.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse a comma-separated list of domain strings.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn parse_domain_list(input: &str) -> Result<Vec<Domain>, String> {
|
|
||||||
if input.trim().is_empty() {
|
|
||||||
return Ok(Vec::new());
|
|
||||||
}
|
|
||||||
input
|
|
||||||
.split(',')
|
|
||||||
.map(|s| Domain::new(s.trim()))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Domain Expression Evaluator ---
|
// --- Domain Expression Evaluator ---
|
||||||
// Supports: true, false, is(domain,...), sub(domain,...), !, &&, ||, ()
|
// Supports: true, false, is(domain,...), sub(domain,...), !, &&, ||, ()
|
||||||
|
|
||||||
@@ -305,18 +190,6 @@ mod tests {
|
|||||||
assert_eq!(make_fqdn("VPN", "Example.COM"), "vpn.example.com");
|
assert_eq!(make_fqdn("VPN", "Example.COM"), "vpn.example.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_domain_wildcard() {
|
|
||||||
let d = Domain::new("*.example.com").unwrap();
|
|
||||||
assert_eq!(d.dns_name_ascii(), "*.example.com");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_domain_list() {
|
|
||||||
let domains = parse_domain_list("example.com, *.example.com, sub.example.com").unwrap();
|
|
||||||
assert_eq!(domains.len(), 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_proxied_expr_true() {
|
fn test_proxied_expr_true() {
|
||||||
let pred = parse_proxied_expression("true").unwrap();
|
let pred = parse_proxied_expression("true").unwrap();
|
||||||
@@ -359,129 +232,6 @@ mod tests {
|
|||||||
assert!(pred("public.com"));
|
assert!(pred("public.com"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Domain::new with regular FQDN ---
|
|
||||||
#[test]
|
|
||||||
fn test_domain_new_fqdn() {
|
|
||||||
let d = Domain::new("example.com").unwrap();
|
|
||||||
assert_eq!(d, Domain::FQDN("example.com".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_domain_new_fqdn_uppercase() {
|
|
||||||
let d = Domain::new("EXAMPLE.COM").unwrap();
|
|
||||||
assert_eq!(d, Domain::FQDN("example.com".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Domain::dns_name_ascii for FQDN ---
|
|
||||||
#[test]
|
|
||||||
fn test_dns_name_ascii_fqdn() {
|
|
||||||
let d = Domain::FQDN("example.com".to_string());
|
|
||||||
assert_eq!(d.dns_name_ascii(), "example.com");
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Domain::describe for both variants ---
|
|
||||||
#[test]
|
|
||||||
fn test_describe_fqdn() {
|
|
||||||
let d = Domain::FQDN("example.com".to_string());
|
|
||||||
// ASCII domain should round-trip through describe unchanged
|
|
||||||
assert_eq!(d.describe(), "example.com");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_describe_wildcard() {
|
|
||||||
let d = Domain::Wildcard("example.com".to_string());
|
|
||||||
assert_eq!(d.describe(), "*.example.com");
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Domain::zones ---
|
|
||||||
#[test]
|
|
||||||
fn test_zones_fqdn() {
|
|
||||||
let d = Domain::FQDN("sub.example.com".to_string());
|
|
||||||
let zones = d.zones();
|
|
||||||
assert_eq!(zones, vec!["sub.example.com", "example.com", "com"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_zones_wildcard() {
|
|
||||||
let d = Domain::Wildcard("example.com".to_string());
|
|
||||||
let zones = d.zones();
|
|
||||||
assert_eq!(zones, vec!["example.com", "com"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_zones_single_label() {
|
|
||||||
let d = Domain::FQDN("localhost".to_string());
|
|
||||||
let zones = d.zones();
|
|
||||||
assert_eq!(zones, vec!["localhost"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Domain Display trait ---
|
|
||||||
#[test]
|
|
||||||
fn test_display_fqdn() {
|
|
||||||
let d = Domain::FQDN("example.com".to_string());
|
|
||||||
assert_eq!(format!("{d}"), "example.com");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_display_wildcard() {
|
|
||||||
let d = Domain::Wildcard("example.com".to_string());
|
|
||||||
assert_eq!(format!("{d}"), "*.example.com");
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- domain_to_ascii (tested indirectly via Domain::new) ---
|
|
||||||
#[test]
|
|
||||||
fn test_domain_new_empty_string() {
|
|
||||||
// empty string -> domain_to_ascii returns Ok("") -> Domain::FQDN("")
|
|
||||||
let d = Domain::new("").unwrap();
|
|
||||||
assert_eq!(d, Domain::FQDN("".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_domain_new_ascii_domain() {
|
|
||||||
let d = Domain::new("www.example.org").unwrap();
|
|
||||||
assert_eq!(d.dns_name_ascii(), "www.example.org");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_domain_new_internationalized() {
|
|
||||||
// "münchen.de" should be encoded to punycode
|
|
||||||
let d = Domain::new("münchen.de").unwrap();
|
|
||||||
let ascii = d.dns_name_ascii();
|
|
||||||
// The punycode-encoded form should start with "xn--"
|
|
||||||
assert!(ascii.contains("xn--"), "expected punycode, got: {ascii}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- describe_domain (tested indirectly via Domain::describe) ---
|
|
||||||
#[test]
|
|
||||||
fn test_describe_punycode_roundtrip() {
|
|
||||||
// Build a domain with a known punycode label and confirm describe decodes it
|
|
||||||
let d = Domain::new("münchen.de").unwrap();
|
|
||||||
let described = d.describe();
|
|
||||||
// Should contain the Unicode form, not the raw punycode
|
|
||||||
assert!(described.contains("münchen") || described.contains("xn--"),
|
|
||||||
"describe returned: {described}");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_describe_regular_ascii() {
|
|
||||||
let d = Domain::FQDN("example.com".to_string());
|
|
||||||
assert_eq!(d.describe(), "example.com");
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- parse_domain_list with empty input ---
|
|
||||||
#[test]
|
|
||||||
fn test_parse_domain_list_empty() {
|
|
||||||
let result = parse_domain_list("").unwrap();
|
|
||||||
assert!(result.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_domain_list_whitespace_only() {
|
|
||||||
let result = parse_domain_list(" ").unwrap();
|
|
||||||
assert!(result.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Tokenizer edge cases (via parse_proxied_expression) ---
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tokenizer_single_ampersand_error() {
|
fn test_tokenizer_single_ampersand_error() {
|
||||||
let result = parse_proxied_expression("is(a.com) & is(b.com)");
|
let result = parse_proxied_expression("is(a.com) & is(b.com)");
|
||||||
@@ -504,7 +254,6 @@ mod tests {
|
|||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Parser edge cases ---
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_and_expr_double_ampersand() {
|
fn test_parse_and_expr_double_ampersand() {
|
||||||
let pred = parse_proxied_expression("is(a.com) && is(b.com)").unwrap();
|
let pred = parse_proxied_expression("is(a.com) && is(b.com)").unwrap();
|
||||||
@@ -538,10 +287,8 @@ mod tests {
|
|||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- make_fqdn with wildcard subdomain ---
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_make_fqdn_wildcard_subdomain() {
|
fn test_make_fqdn_wildcard_subdomain() {
|
||||||
// A name starting with "*." is treated as a wildcard subdomain
|
|
||||||
assert_eq!(make_fqdn("*.sub", "example.com"), "*.sub.example.com");
|
assert_eq!(make_fqdn("*.sub", "example.com"), "*.sub.example.com");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
31
src/main.rs
31
src/main.rs
@@ -20,8 +20,12 @@ use tokio::time::{sleep, Duration};
|
|||||||
|
|
||||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
rustls::crypto::ring::default_provider()
|
||||||
|
.install_default()
|
||||||
|
.expect("Failed to install rustls crypto provider");
|
||||||
|
|
||||||
// Parse CLI args
|
// Parse CLI args
|
||||||
let args: Vec<String> = std::env::args().collect();
|
let args: Vec<String> = std::env::args().collect();
|
||||||
let dry_run = args.iter().any(|a| a == "--dry-run");
|
let dry_run = args.iter().any(|a| a == "--dry-run");
|
||||||
@@ -229,13 +233,11 @@ async fn run_env_mode(
|
|||||||
while running.load(Ordering::SeqCst) {
|
while running.load(Ordering::SeqCst) {
|
||||||
// Sleep for interval, checking running flag each second
|
// Sleep for interval, checking running flag each second
|
||||||
let secs = interval.as_secs();
|
let secs = interval.as_secs();
|
||||||
let next_time = chrono::Local::now() + chrono::Duration::seconds(secs as i64);
|
let mins = secs / 60;
|
||||||
|
let rem_secs = secs % 60;
|
||||||
ppfmt.infof(
|
ppfmt.infof(
|
||||||
pp::EMOJI_SLEEP,
|
pp::EMOJI_SLEEP,
|
||||||
&format!(
|
&format!("Next update in {}m {}s", mins, rem_secs),
|
||||||
"Next update at {}",
|
|
||||||
next_time.format("%Y-%m-%d %H:%M:%S %Z")
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
for _ in 0..secs {
|
for _ in 0..secs {
|
||||||
@@ -282,6 +284,21 @@ fn describe_duration(d: Duration) -> String {
|
|||||||
// Tests (backwards compatible with original test suite)
|
// Tests (backwards compatible with original test suite)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn init_crypto() {
|
||||||
|
use std::sync::Once;
|
||||||
|
static INIT: Once = Once::new();
|
||||||
|
INIT.call_once(|| {
|
||||||
|
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn test_client() -> reqwest::Client {
|
||||||
|
init_crypto();
|
||||||
|
reqwest::Client::new()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::config::{
|
use crate::config::{
|
||||||
@@ -333,7 +350,7 @@ mod tests {
|
|||||||
impl TestDdnsClient {
|
impl TestDdnsClient {
|
||||||
fn new(base_url: &str) -> Self {
|
fn new(base_url: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
client: Client::new(),
|
client: crate::test_client(),
|
||||||
cf_api_base: base_url.to_string(),
|
cf_api_base: base_url.to_string(),
|
||||||
ipv4_urls: vec![format!("{base_url}/cdn-cgi/trace")],
|
ipv4_urls: vec![format!("{base_url}/cdn-cgi/trace")],
|
||||||
dry_run: false,
|
dry_run: false,
|
||||||
|
|||||||
148
src/notifier.rs
148
src/notifier.rs
@@ -11,14 +11,6 @@ pub struct Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Message {
|
impl Message {
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
lines: Vec::new(),
|
|
||||||
ok: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_ok(msg: &str) -> Self {
|
pub fn new_ok(msg: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
lines: vec![msg.to_string()],
|
lines: vec![msg.to_string()],
|
||||||
@@ -52,16 +44,6 @@ impl Message {
|
|||||||
}
|
}
|
||||||
Message { lines, ok }
|
Message { lines, ok }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn add_line(&mut self, line: &str) {
|
|
||||||
self.lines.push(line.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn set_fail(&mut self) {
|
|
||||||
self.ok = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Composite Notifier ---
|
// --- Composite Notifier ---
|
||||||
@@ -72,8 +54,6 @@ pub struct CompositeNotifier {
|
|||||||
|
|
||||||
// Object-safe version of Notifier
|
// Object-safe version of Notifier
|
||||||
pub trait NotifierDyn: Send + Sync {
|
pub trait NotifierDyn: Send + Sync {
|
||||||
#[allow(dead_code)]
|
|
||||||
fn describe(&self) -> String;
|
|
||||||
fn send_dyn<'a>(
|
fn send_dyn<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
msg: &'a Message,
|
msg: &'a Message,
|
||||||
@@ -85,16 +65,6 @@ impl CompositeNotifier {
|
|||||||
Self { notifiers }
|
Self { notifiers }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.notifiers.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn describe(&self) -> Vec<String> {
|
|
||||||
self.notifiers.iter().map(|n| n.describe()).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send(&self, msg: &Message) {
|
pub async fn send(&self, msg: &Message) {
|
||||||
if msg.is_empty() {
|
if msg.is_empty() {
|
||||||
return;
|
return;
|
||||||
@@ -295,10 +265,6 @@ impl ShoutrrrNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl NotifierDyn for ShoutrrrNotifier {
|
impl NotifierDyn for ShoutrrrNotifier {
|
||||||
fn describe(&self) -> String {
|
|
||||||
ShoutrrrNotifier::describe(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_dyn<'a>(
|
fn send_dyn<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
msg: &'a Message,
|
msg: &'a Message,
|
||||||
@@ -442,8 +408,6 @@ pub struct Heartbeat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait HeartbeatMonitor: Send + Sync {
|
pub trait HeartbeatMonitor: Send + Sync {
|
||||||
#[allow(dead_code)]
|
|
||||||
fn describe(&self) -> String;
|
|
||||||
fn ping<'a>(
|
fn ping<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
msg: &'a Message,
|
msg: &'a Message,
|
||||||
@@ -462,16 +426,6 @@ impl Heartbeat {
|
|||||||
Self { monitors }
|
Self { monitors }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.monitors.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn describe(&self) -> Vec<String> {
|
|
||||||
self.monitors.iter().map(|m| m.describe()).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn ping(&self, msg: &Message) {
|
pub async fn ping(&self, msg: &Message) {
|
||||||
for monitor in &self.monitors {
|
for monitor in &self.monitors {
|
||||||
monitor.ping(msg).await;
|
monitor.ping(msg).await;
|
||||||
@@ -532,10 +486,6 @@ impl HealthchecksMonitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl HeartbeatMonitor for HealthchecksMonitor {
|
impl HeartbeatMonitor for HealthchecksMonitor {
|
||||||
fn describe(&self) -> String {
|
|
||||||
"Healthchecks.io".to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ping<'a>(
|
fn ping<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
msg: &'a Message,
|
msg: &'a Message,
|
||||||
@@ -590,10 +540,6 @@ impl UptimeKumaMonitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl HeartbeatMonitor for UptimeKumaMonitor {
|
impl HeartbeatMonitor for UptimeKumaMonitor {
|
||||||
fn describe(&self) -> String {
|
|
||||||
"Uptime Kuma".to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ping<'a>(
|
fn ping<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
msg: &'a Message,
|
msg: &'a Message,
|
||||||
@@ -675,19 +621,6 @@ mod tests {
|
|||||||
assert!(!msg.ok);
|
assert!(!msg.ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_message_new() {
|
|
||||||
let msg = Message::new();
|
|
||||||
assert!(msg.lines.is_empty());
|
|
||||||
assert!(msg.ok);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_message_is_empty_true() {
|
|
||||||
let msg = Message::new();
|
|
||||||
assert!(msg.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_message_is_empty_false() {
|
fn test_message_is_empty_false() {
|
||||||
let msg = Message::new_ok("something");
|
let msg = Message::new_ok("something");
|
||||||
@@ -700,20 +633,6 @@ mod tests {
|
|||||||
assert_eq!(msg.format(), "line1");
|
assert_eq!(msg.format(), "line1");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_message_format_multiple_lines() {
|
|
||||||
let mut msg = Message::new_ok("line1");
|
|
||||||
msg.add_line("line2");
|
|
||||||
msg.add_line("line3");
|
|
||||||
assert_eq!(msg.format(), "line1\nline2\nline3");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_message_format_empty() {
|
|
||||||
let msg = Message::new();
|
|
||||||
assert_eq!(msg.format(), "");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_message_merge_all_ok() {
|
fn test_message_merge_all_ok() {
|
||||||
let m1 = Message::new_ok("a");
|
let m1 = Message::new_ok("a");
|
||||||
@@ -751,30 +670,12 @@ mod tests {
|
|||||||
assert!(merged.ok);
|
assert!(merged.ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_message_add_line() {
|
|
||||||
let mut msg = Message::new();
|
|
||||||
msg.add_line("first");
|
|
||||||
msg.add_line("second");
|
|
||||||
assert_eq!(msg.lines, vec!["first".to_string(), "second".to_string()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_message_set_fail() {
|
|
||||||
let mut msg = Message::new();
|
|
||||||
assert!(msg.ok);
|
|
||||||
msg.set_fail();
|
|
||||||
assert!(!msg.ok);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- CompositeNotifier tests ----
|
// ---- CompositeNotifier tests ----
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_composite_notifier_empty_send_does_nothing() {
|
async fn test_composite_notifier_empty_send_does_nothing() {
|
||||||
let notifier = CompositeNotifier::new(vec![]);
|
let notifier = CompositeNotifier::new(vec![]);
|
||||||
assert!(notifier.is_empty());
|
|
||||||
let msg = Message::new_ok("test");
|
let msg = Message::new_ok("test");
|
||||||
// Should not panic or error
|
|
||||||
notifier.send(&msg).await;
|
notifier.send(&msg).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1111,7 +1012,7 @@ mod tests {
|
|||||||
|
|
||||||
// Build a notifier that points discord webhook at our mock server
|
// Build a notifier that points discord webhook at our mock server
|
||||||
let notifier = ShoutrrrNotifier {
|
let notifier = ShoutrrrNotifier {
|
||||||
client: Client::new(),
|
client: crate::test_client(),
|
||||||
urls: vec![ShoutrrrService {
|
urls: vec![ShoutrrrService {
|
||||||
original_url: "discord://token@id".to_string(),
|
original_url: "discord://token@id".to_string(),
|
||||||
service_type: ShoutrrrServiceType::Discord,
|
service_type: ShoutrrrServiceType::Discord,
|
||||||
@@ -1135,7 +1036,7 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let notifier = ShoutrrrNotifier {
|
let notifier = ShoutrrrNotifier {
|
||||||
client: Client::new(),
|
client: crate::test_client(),
|
||||||
urls: vec![ShoutrrrService {
|
urls: vec![ShoutrrrService {
|
||||||
original_url: "slack://a/b/c".to_string(),
|
original_url: "slack://a/b/c".to_string(),
|
||||||
service_type: ShoutrrrServiceType::Slack,
|
service_type: ShoutrrrServiceType::Slack,
|
||||||
@@ -1159,7 +1060,7 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let notifier = ShoutrrrNotifier {
|
let notifier = ShoutrrrNotifier {
|
||||||
client: Client::new(),
|
client: crate::test_client(),
|
||||||
urls: vec![ShoutrrrService {
|
urls: vec![ShoutrrrService {
|
||||||
original_url: "generic://example.com/hook".to_string(),
|
original_url: "generic://example.com/hook".to_string(),
|
||||||
service_type: ShoutrrrServiceType::Generic,
|
service_type: ShoutrrrServiceType::Generic,
|
||||||
@@ -1175,10 +1076,10 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_shoutrrr_send_empty_message() {
|
async fn test_shoutrrr_send_empty_message() {
|
||||||
let notifier = ShoutrrrNotifier {
|
let notifier = ShoutrrrNotifier {
|
||||||
client: Client::new(),
|
client: crate::test_client(),
|
||||||
urls: vec![],
|
urls: vec![],
|
||||||
};
|
};
|
||||||
let msg = Message::new();
|
let msg = Message { lines: Vec::new(), ok: true };
|
||||||
let pp = PP::default_pp();
|
let pp = PP::default_pp();
|
||||||
// Empty message should return true immediately
|
// Empty message should return true immediately
|
||||||
let result = notifier.send(&msg, &pp).await;
|
let result = notifier.send(&msg, &pp).await;
|
||||||
@@ -1211,7 +1112,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_shoutrrr_notifier_describe() {
|
fn test_shoutrrr_notifier_describe() {
|
||||||
let notifier = ShoutrrrNotifier {
|
let notifier = ShoutrrrNotifier {
|
||||||
client: Client::new(),
|
client: crate::test_client(),
|
||||||
urls: vec![
|
urls: vec![
|
||||||
ShoutrrrService {
|
ShoutrrrService {
|
||||||
original_url: "discord://t@i".to_string(),
|
original_url: "discord://t@i".to_string(),
|
||||||
@@ -1267,7 +1168,7 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let notifier = ShoutrrrNotifier {
|
let notifier = ShoutrrrNotifier {
|
||||||
client: Client::new(),
|
client: crate::test_client(),
|
||||||
urls: vec![ShoutrrrService {
|
urls: vec![ShoutrrrService {
|
||||||
original_url: "telegram://token@telegram?chats=123".to_string(),
|
original_url: "telegram://token@telegram?chats=123".to_string(),
|
||||||
service_type: ShoutrrrServiceType::Telegram,
|
service_type: ShoutrrrServiceType::Telegram,
|
||||||
@@ -1291,7 +1192,7 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let notifier = ShoutrrrNotifier {
|
let notifier = ShoutrrrNotifier {
|
||||||
client: Client::new(),
|
client: crate::test_client(),
|
||||||
urls: vec![ShoutrrrService {
|
urls: vec![ShoutrrrService {
|
||||||
original_url: "gotify://host/path".to_string(),
|
original_url: "gotify://host/path".to_string(),
|
||||||
service_type: ShoutrrrServiceType::Gotify,
|
service_type: ShoutrrrServiceType::Gotify,
|
||||||
@@ -1326,7 +1227,7 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let notifier = ShoutrrrNotifier {
|
let notifier = ShoutrrrNotifier {
|
||||||
client: Client::new(),
|
client: crate::test_client(),
|
||||||
urls: vec![ShoutrrrService {
|
urls: vec![ShoutrrrService {
|
||||||
original_url: "custom://host/path".to_string(),
|
original_url: "custom://host/path".to_string(),
|
||||||
service_type: ShoutrrrServiceType::Other("custom".to_string()),
|
service_type: ShoutrrrServiceType::Other("custom".to_string()),
|
||||||
@@ -1350,7 +1251,7 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let notifier = ShoutrrrNotifier {
|
let notifier = ShoutrrrNotifier {
|
||||||
client: Client::new(),
|
client: crate::test_client(),
|
||||||
urls: vec![ShoutrrrService {
|
urls: vec![ShoutrrrService {
|
||||||
original_url: "discord://t@i".to_string(),
|
original_url: "discord://t@i".to_string(),
|
||||||
service_type: ShoutrrrServiceType::Discord,
|
service_type: ShoutrrrServiceType::Discord,
|
||||||
@@ -1363,23 +1264,6 @@ mod tests {
|
|||||||
assert!(!result);
|
assert!(!result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- CompositeNotifier describe ----
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_composite_notifier_describe_empty() {
|
|
||||||
let notifier = CompositeNotifier::new(vec![]);
|
|
||||||
assert!(notifier.describe().is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Heartbeat describe and is_empty ----
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_heartbeat_is_empty() {
|
|
||||||
let hb = Heartbeat::new(vec![]);
|
|
||||||
assert!(hb.is_empty());
|
|
||||||
assert!(hb.describe().is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_heartbeat_ping_no_monitors() {
|
async fn test_heartbeat_ping_no_monitors() {
|
||||||
let hb = Heartbeat::new(vec![]);
|
let hb = Heartbeat::new(vec![]);
|
||||||
@@ -1401,16 +1285,6 @@ mod tests {
|
|||||||
hb.exit(&msg).await;
|
hb.exit(&msg).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- CompositeNotifier send with empty message ----
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_composite_notifier_send_empty_message_skips() {
|
|
||||||
let notifier = CompositeNotifier::new(vec![]);
|
|
||||||
let msg = Message::new(); // empty
|
|
||||||
// Should return immediately without sending
|
|
||||||
notifier.send(&msg).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_shoutrrr_send_server_error() {
|
async fn test_shoutrrr_send_server_error() {
|
||||||
let server = MockServer::start().await;
|
let server = MockServer::start().await;
|
||||||
@@ -1422,7 +1296,7 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let notifier = ShoutrrrNotifier {
|
let notifier = ShoutrrrNotifier {
|
||||||
client: Client::new(),
|
client: crate::test_client(),
|
||||||
urls: vec![ShoutrrrService {
|
urls: vec![ShoutrrrService {
|
||||||
original_url: "generic://example.com/hook".to_string(),
|
original_url: "generic://example.com/hook".to_string(),
|
||||||
service_type: ShoutrrrServiceType::Generic,
|
service_type: ShoutrrrServiceType::Generic,
|
||||||
|
|||||||
200
src/pp.rs
200
src/pp.rs
@@ -1,6 +1,3 @@
|
|||||||
use std::collections::HashSet;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
// Verbosity levels
|
// Verbosity levels
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum Verbosity {
|
pub enum Verbosity {
|
||||||
@@ -11,12 +8,8 @@ pub enum Verbosity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Emoji constants
|
// Emoji constants
|
||||||
#[allow(dead_code)]
|
|
||||||
pub const EMOJI_GLOBE: &str = "\u{1F30D}";
|
|
||||||
pub const EMOJI_WARNING: &str = "\u{26A0}\u{FE0F}";
|
pub const EMOJI_WARNING: &str = "\u{26A0}\u{FE0F}";
|
||||||
pub const EMOJI_ERROR: &str = "\u{274C}";
|
pub const EMOJI_ERROR: &str = "\u{274C}";
|
||||||
#[allow(dead_code)]
|
|
||||||
pub const EMOJI_SUCCESS: &str = "\u{2705}";
|
|
||||||
pub const EMOJI_LAUNCH: &str = "\u{1F680}";
|
pub const EMOJI_LAUNCH: &str = "\u{1F680}";
|
||||||
pub const EMOJI_STOP: &str = "\u{1F6D1}";
|
pub const EMOJI_STOP: &str = "\u{1F6D1}";
|
||||||
pub const EMOJI_SLEEP: &str = "\u{1F634}";
|
pub const EMOJI_SLEEP: &str = "\u{1F634}";
|
||||||
@@ -28,8 +21,6 @@ pub const EMOJI_SKIP: &str = "\u{23ED}\u{FE0F}";
|
|||||||
pub const EMOJI_NOTIFY: &str = "\u{1F514}";
|
pub const EMOJI_NOTIFY: &str = "\u{1F514}";
|
||||||
pub const EMOJI_HEARTBEAT: &str = "\u{1F493}";
|
pub const EMOJI_HEARTBEAT: &str = "\u{1F493}";
|
||||||
pub const EMOJI_CONFIG: &str = "\u{2699}\u{FE0F}";
|
pub const EMOJI_CONFIG: &str = "\u{2699}\u{FE0F}";
|
||||||
#[allow(dead_code)]
|
|
||||||
pub const EMOJI_HINT: &str = "\u{1F4A1}";
|
|
||||||
|
|
||||||
const INDENT_PREFIX: &str = " ";
|
const INDENT_PREFIX: &str = " ";
|
||||||
|
|
||||||
@@ -37,7 +28,6 @@ pub struct PP {
|
|||||||
pub verbosity: Verbosity,
|
pub verbosity: Verbosity,
|
||||||
pub emoji: bool,
|
pub emoji: bool,
|
||||||
indent: usize,
|
indent: usize,
|
||||||
seen: Arc<Mutex<HashSet<String>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PP {
|
impl PP {
|
||||||
@@ -46,7 +36,6 @@ impl PP {
|
|||||||
verbosity: if quiet { Verbosity::Quiet } else { Verbosity::Verbose },
|
verbosity: if quiet { Verbosity::Quiet } else { Verbosity::Verbose },
|
||||||
emoji,
|
emoji,
|
||||||
indent: 0,
|
indent: 0,
|
||||||
seen: Arc::new(Mutex::new(HashSet::new())),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +52,6 @@ impl PP {
|
|||||||
verbosity: self.verbosity,
|
verbosity: self.verbosity,
|
||||||
emoji: self.emoji,
|
emoji: self.emoji,
|
||||||
indent: self.indent + 1,
|
indent: self.indent + 1,
|
||||||
seen: Arc::clone(&self.seen),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,54 +92,12 @@ impl PP {
|
|||||||
pub fn errorf(&self, emoji: &str, msg: &str) {
|
pub fn errorf(&self, emoji: &str, msg: &str) {
|
||||||
self.output_err(emoji, msg);
|
self.output_err(emoji, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn info_once(&self, key: &str, emoji: &str, msg: &str) {
|
|
||||||
if self.is_showing(Verbosity::Info) {
|
|
||||||
let mut seen = self.seen.lock().unwrap();
|
|
||||||
if seen.insert(key.to_string()) {
|
|
||||||
self.output(emoji, msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn notice_once(&self, key: &str, emoji: &str, msg: &str) {
|
|
||||||
if self.is_showing(Verbosity::Notice) {
|
|
||||||
let mut seen = self.seen.lock().unwrap();
|
|
||||||
if seen.insert(key.to_string()) {
|
|
||||||
self.output(emoji, msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn blank_line_if_verbose(&self) {
|
|
||||||
if self.is_showing(Verbosity::Verbose) {
|
|
||||||
println!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn english_join(items: &[String]) -> String {
|
|
||||||
match items.len() {
|
|
||||||
0 => String::new(),
|
|
||||||
1 => items[0].clone(),
|
|
||||||
2 => format!("{} and {}", items[0], items[1]),
|
|
||||||
_ => {
|
|
||||||
let (last, rest) = items.split_last().unwrap();
|
|
||||||
format!("{}, and {last}", rest.join(", "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
// ---- PP::new with emoji flag ----
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn new_with_emoji_true() {
|
fn new_with_emoji_true() {
|
||||||
let pp = PP::new(true, false);
|
let pp = PP::new(true, false);
|
||||||
@@ -164,8 +110,6 @@ mod tests {
|
|||||||
assert!(!pp.emoji);
|
assert!(!pp.emoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- PP::new with quiet flag (verbosity levels) ----
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn new_quiet_true_sets_verbosity_quiet() {
|
fn new_quiet_true_sets_verbosity_quiet() {
|
||||||
let pp = PP::new(false, true);
|
let pp = PP::new(false, true);
|
||||||
@@ -178,8 +122,6 @@ mod tests {
|
|||||||
assert_eq!(pp.verbosity, Verbosity::Verbose);
|
assert_eq!(pp.verbosity, Verbosity::Verbose);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- PP::is_showing at different verbosity levels ----
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn quiet_shows_only_quiet_level() {
|
fn quiet_shows_only_quiet_level() {
|
||||||
let pp = PP::new(false, true);
|
let pp = PP::new(false, true);
|
||||||
@@ -218,8 +160,6 @@ mod tests {
|
|||||||
assert!(!pp.is_showing(Verbosity::Verbose));
|
assert!(!pp.is_showing(Verbosity::Verbose));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- PP::indent ----
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn indent_increments_indent_level() {
|
fn indent_increments_indent_level() {
|
||||||
let pp = PP::new(true, false);
|
let pp = PP::new(true, false);
|
||||||
@@ -238,26 +178,6 @@ mod tests {
|
|||||||
assert_eq!(child.emoji, pp.emoji);
|
assert_eq!(child.emoji, pp.emoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn indent_shares_seen_state() {
|
|
||||||
let pp = PP::new(false, false);
|
|
||||||
let child = pp.indent();
|
|
||||||
|
|
||||||
// Insert via parent's seen set
|
|
||||||
pp.seen.lock().unwrap().insert("key1".to_string());
|
|
||||||
|
|
||||||
// Child should observe the same entry
|
|
||||||
assert!(child.seen.lock().unwrap().contains("key1"));
|
|
||||||
|
|
||||||
// Insert via child
|
|
||||||
child.seen.lock().unwrap().insert("key2".to_string());
|
|
||||||
|
|
||||||
// Parent should observe it too
|
|
||||||
assert!(pp.seen.lock().unwrap().contains("key2"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- PP::infof, noticef, warningf, errorf - no panic and verbosity gating ----
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn infof_does_not_panic_when_verbose() {
|
fn infof_does_not_panic_when_verbose() {
|
||||||
let pp = PP::new(false, false);
|
let pp = PP::new(false, false);
|
||||||
@@ -267,7 +187,6 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn infof_does_not_panic_when_quiet() {
|
fn infof_does_not_panic_when_quiet() {
|
||||||
let pp = PP::new(false, true);
|
let pp = PP::new(false, true);
|
||||||
// Should simply not print, and not panic
|
|
||||||
pp.infof("", "test info message");
|
pp.infof("", "test info message");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,7 +210,6 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn warningf_does_not_panic_when_quiet() {
|
fn warningf_does_not_panic_when_quiet() {
|
||||||
// warningf always outputs (no verbosity check), just verify no panic
|
|
||||||
let pp = PP::new(false, true);
|
let pp = PP::new(false, true);
|
||||||
pp.warningf("", "test warning");
|
pp.warningf("", "test warning");
|
||||||
}
|
}
|
||||||
@@ -308,124 +226,6 @@ mod tests {
|
|||||||
pp.errorf("", "test error");
|
pp.errorf("", "test error");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- PP::info_once and notice_once ----
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn info_once_suppresses_duplicates() {
|
|
||||||
let pp = PP::new(false, false);
|
|
||||||
// First call inserts the key
|
|
||||||
pp.info_once("dup_key", "", "first");
|
|
||||||
// The key should now be in the seen set
|
|
||||||
assert!(pp.seen.lock().unwrap().contains("dup_key"));
|
|
||||||
|
|
||||||
// Calling again with the same key should not insert again (set unchanged)
|
|
||||||
let size_before = pp.seen.lock().unwrap().len();
|
|
||||||
pp.info_once("dup_key", "", "second");
|
|
||||||
let size_after = pp.seen.lock().unwrap().len();
|
|
||||||
assert_eq!(size_before, size_after);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn info_once_allows_different_keys() {
|
|
||||||
let pp = PP::new(false, false);
|
|
||||||
pp.info_once("key_a", "", "msg a");
|
|
||||||
pp.info_once("key_b", "", "msg b");
|
|
||||||
let seen = pp.seen.lock().unwrap();
|
|
||||||
assert!(seen.contains("key_a"));
|
|
||||||
assert!(seen.contains("key_b"));
|
|
||||||
assert_eq!(seen.len(), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn info_once_skipped_when_quiet() {
|
|
||||||
let pp = PP::new(false, true);
|
|
||||||
pp.info_once("quiet_key", "", "should not register");
|
|
||||||
// Because verbosity is Quiet, info_once should not even insert the key
|
|
||||||
assert!(!pp.seen.lock().unwrap().contains("quiet_key"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn notice_once_suppresses_duplicates() {
|
|
||||||
let pp = PP::new(false, false);
|
|
||||||
pp.notice_once("notice_dup", "", "first");
|
|
||||||
assert!(pp.seen.lock().unwrap().contains("notice_dup"));
|
|
||||||
|
|
||||||
let size_before = pp.seen.lock().unwrap().len();
|
|
||||||
pp.notice_once("notice_dup", "", "second");
|
|
||||||
let size_after = pp.seen.lock().unwrap().len();
|
|
||||||
assert_eq!(size_before, size_after);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn notice_once_skipped_when_quiet() {
|
|
||||||
let pp = PP::new(false, true);
|
|
||||||
pp.notice_once("quiet_notice", "", "should not register");
|
|
||||||
assert!(!pp.seen.lock().unwrap().contains("quiet_notice"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn info_once_shared_via_indent() {
|
|
||||||
let pp = PP::new(false, false);
|
|
||||||
let child = pp.indent();
|
|
||||||
|
|
||||||
// Mark a key via the parent
|
|
||||||
pp.info_once("shared_key", "", "parent");
|
|
||||||
assert!(pp.seen.lock().unwrap().contains("shared_key"));
|
|
||||||
|
|
||||||
// Child should see it as already present, so set size stays the same
|
|
||||||
let size_before = child.seen.lock().unwrap().len();
|
|
||||||
child.info_once("shared_key", "", "child duplicate");
|
|
||||||
let size_after = child.seen.lock().unwrap().len();
|
|
||||||
assert_eq!(size_before, size_after);
|
|
||||||
|
|
||||||
// Child can add a new key visible to parent
|
|
||||||
child.info_once("child_key", "", "child new");
|
|
||||||
assert!(pp.seen.lock().unwrap().contains("child_key"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- english_join ----
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn english_join_empty() {
|
|
||||||
let items: Vec<String> = vec![];
|
|
||||||
assert_eq!(english_join(&items), "");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn english_join_single() {
|
|
||||||
let items = vec!["alpha".to_string()];
|
|
||||||
assert_eq!(english_join(&items), "alpha");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn english_join_two() {
|
|
||||||
let items = vec!["alpha".to_string(), "beta".to_string()];
|
|
||||||
assert_eq!(english_join(&items), "alpha and beta");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn english_join_three() {
|
|
||||||
let items = vec![
|
|
||||||
"alpha".to_string(),
|
|
||||||
"beta".to_string(),
|
|
||||||
"gamma".to_string(),
|
|
||||||
];
|
|
||||||
assert_eq!(english_join(&items), "alpha, beta, and gamma");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn english_join_four() {
|
|
||||||
let items = vec![
|
|
||||||
"a".to_string(),
|
|
||||||
"b".to_string(),
|
|
||||||
"c".to_string(),
|
|
||||||
"d".to_string(),
|
|
||||||
];
|
|
||||||
assert_eq!(english_join(&items), "a, b, c, and d");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- default_pp ----
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn default_pp_is_verbose_no_emoji() {
|
fn default_pp_is_verbose_no_emoji() {
|
||||||
let pp = PP::default_pp();
|
let pp = PP::default_pp();
|
||||||
|
|||||||
@@ -26,10 +26,6 @@ impl IpType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn all() -> &'static [IpType] {
|
|
||||||
&[IpType::V4, IpType::V6]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// All supported provider types
|
/// All supported provider types
|
||||||
@@ -879,7 +875,7 @@ mod tests {
|
|||||||
.mount(&server)
|
.mount(&server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let client = Client::new();
|
let client = crate::test_client();
|
||||||
let ppfmt = PP::default_pp();
|
let ppfmt = PP::default_pp();
|
||||||
let url = format!("{}/cdn-cgi/trace", server.uri());
|
let url = format!("{}/cdn-cgi/trace", server.uri());
|
||||||
let timeout = Duration::from_secs(5);
|
let timeout = Duration::from_secs(5);
|
||||||
@@ -919,7 +915,7 @@ mod tests {
|
|||||||
|
|
||||||
// We can't override the hardcoded primary/fallback URLs, but we can test
|
// We can't override the hardcoded primary/fallback URLs, but we can test
|
||||||
// the custom URL path: first with a failing URL, then a succeeding one.
|
// the custom URL path: first with a failing URL, then a succeeding one.
|
||||||
let client = Client::new();
|
let client = crate::test_client();
|
||||||
let ppfmt = PP::default_pp();
|
let ppfmt = PP::default_pp();
|
||||||
let timeout = Duration::from_secs(5);
|
let timeout = Duration::from_secs(5);
|
||||||
|
|
||||||
@@ -1012,7 +1008,7 @@ mod tests {
|
|||||||
.mount(&server)
|
.mount(&server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let client = Client::new();
|
let client = crate::test_client();
|
||||||
let ppfmt = PP::default_pp();
|
let ppfmt = PP::default_pp();
|
||||||
let timeout = Duration::from_secs(5);
|
let timeout = Duration::from_secs(5);
|
||||||
|
|
||||||
@@ -1035,7 +1031,7 @@ mod tests {
|
|||||||
.mount(&server)
|
.mount(&server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let client = Client::new();
|
let client = crate::test_client();
|
||||||
let ppfmt = PP::default_pp();
|
let ppfmt = PP::default_pp();
|
||||||
let timeout = Duration::from_secs(5);
|
let timeout = Duration::from_secs(5);
|
||||||
|
|
||||||
@@ -1056,7 +1052,7 @@ mod tests {
|
|||||||
.mount(&server)
|
.mount(&server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let client = Client::new();
|
let client = crate::test_client();
|
||||||
let ppfmt = PP::default_pp();
|
let ppfmt = PP::default_pp();
|
||||||
let timeout = Duration::from_secs(5);
|
let timeout = Duration::from_secs(5);
|
||||||
let url = format!("{}/my-ip", server.uri());
|
let url = format!("{}/my-ip", server.uri());
|
||||||
@@ -1076,7 +1072,7 @@ mod tests {
|
|||||||
.mount(&server)
|
.mount(&server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let client = Client::new();
|
let client = crate::test_client();
|
||||||
let ppfmt = PP::default_pp();
|
let ppfmt = PP::default_pp();
|
||||||
let timeout = Duration::from_secs(5);
|
let timeout = Duration::from_secs(5);
|
||||||
let url = format!("{}/my-ip", server.uri());
|
let url = format!("{}/my-ip", server.uri());
|
||||||
@@ -1140,7 +1136,7 @@ mod tests {
|
|||||||
.mount(&server)
|
.mount(&server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let client = Client::new();
|
let client = crate::test_client();
|
||||||
let ppfmt = PP::default_pp();
|
let ppfmt = PP::default_pp();
|
||||||
let timeout = Duration::from_secs(5);
|
let timeout = Duration::from_secs(5);
|
||||||
let url = format!("{}/my-ip", server.uri());
|
let url = format!("{}/my-ip", server.uri());
|
||||||
@@ -1351,7 +1347,7 @@ mod tests {
|
|||||||
"5.6.7.8".parse().unwrap(),
|
"5.6.7.8".parse().unwrap(),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
let client = Client::new();
|
let client = crate::test_client();
|
||||||
let ppfmt = PP::default_pp();
|
let ppfmt = PP::default_pp();
|
||||||
let timeout = Duration::from_secs(5);
|
let timeout = Duration::from_secs(5);
|
||||||
|
|
||||||
@@ -1369,7 +1365,7 @@ mod tests {
|
|||||||
"2001:db8::1".parse().unwrap(),
|
"2001:db8::1".parse().unwrap(),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
let client = Client::new();
|
let client = crate::test_client();
|
||||||
let ppfmt = PP::default_pp();
|
let ppfmt = PP::default_pp();
|
||||||
let timeout = Duration::from_secs(5);
|
let timeout = Duration::from_secs(5);
|
||||||
|
|
||||||
@@ -1383,7 +1379,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_none_detect_ips_returns_empty() {
|
async fn test_none_detect_ips_returns_empty() {
|
||||||
let provider = ProviderType::None;
|
let provider = ProviderType::None;
|
||||||
let client = Client::new();
|
let client = crate::test_client();
|
||||||
let ppfmt = PP::default_pp();
|
let ppfmt = PP::default_pp();
|
||||||
let timeout = Duration::from_secs(5);
|
let timeout = Duration::from_secs(5);
|
||||||
|
|
||||||
|
|||||||
108
src/updater.rs
108
src/updater.rs
@@ -26,7 +26,11 @@ pub async fn update_once(
|
|||||||
let mut notify = false; // NEW: track meaningful events
|
let mut notify = false; // NEW: track meaningful events
|
||||||
|
|
||||||
if config.legacy_mode {
|
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 {
|
} else {
|
||||||
// Detect IPs for each provider
|
// Detect IPs for each provider
|
||||||
let mut detected_ips: HashMap<IpType, Vec<IpAddr>> = HashMap::new();
|
let mut detected_ips: HashMap<IpType, Vec<IpAddr>> = HashMap::new();
|
||||||
@@ -251,10 +255,10 @@ async fn update_legacy(
|
|||||||
ppfmt: &PP,
|
ppfmt: &PP,
|
||||||
noop_reported: &mut HashSet<String>,
|
noop_reported: &mut HashSet<String>,
|
||||||
detection_client: &Client,
|
detection_client: &Client,
|
||||||
) -> bool {
|
) -> (bool, Vec<Message>, bool) {
|
||||||
let legacy = match &config.legacy_config {
|
let legacy = match &config.legacy_config {
|
||||||
Some(l) => l,
|
Some(l) => l,
|
||||||
None => return false,
|
None => return (false, Vec::new(), false),
|
||||||
};
|
};
|
||||||
|
|
||||||
let ddns = LegacyDdnsClient {
|
let ddns = LegacyDdnsClient {
|
||||||
@@ -341,16 +345,17 @@ async fn update_legacy(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ddns.update_ips(
|
let (msgs, should_notify) = ddns
|
||||||
&ips,
|
.update_ips(
|
||||||
&legacy.cloudflare,
|
&ips,
|
||||||
legacy.ttl,
|
&legacy.cloudflare,
|
||||||
legacy.purge_unknown_records,
|
legacy.ttl,
|
||||||
noop_reported,
|
legacy.purge_unknown_records,
|
||||||
)
|
noop_reported,
|
||||||
.await;
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
true
|
(true, msgs, should_notify)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete records on stop (for env var mode).
|
/// Delete records on stop (for env var mode).
|
||||||
@@ -499,11 +504,19 @@ impl LegacyDdnsClient {
|
|||||||
ttl: i64,
|
ttl: i64,
|
||||||
purge_unknown_records: bool,
|
purge_unknown_records: bool,
|
||||||
noop_reported: &mut HashSet<String>,
|
noop_reported: &mut HashSet<String>,
|
||||||
) {
|
) -> (Vec<Message>, bool) {
|
||||||
|
let mut messages = Vec::new();
|
||||||
|
let mut notify = false;
|
||||||
for ip in ips.values() {
|
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;
|
.await;
|
||||||
|
messages.extend(msgs);
|
||||||
|
if changed {
|
||||||
|
notify = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
(messages, notify)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn commit_record(
|
async fn commit_record(
|
||||||
@@ -513,7 +526,9 @@ impl LegacyDdnsClient {
|
|||||||
ttl: i64,
|
ttl: i64,
|
||||||
purge_unknown_records: bool,
|
purge_unknown_records: bool,
|
||||||
noop_reported: &mut HashSet<String>,
|
noop_reported: &mut HashSet<String>,
|
||||||
) {
|
) -> (Vec<Message>, bool) {
|
||||||
|
let mut messages = Vec::new();
|
||||||
|
let mut changed = false;
|
||||||
for entry in config {
|
for entry in config {
|
||||||
let zone_resp: Option<LegacyCfResponse<LegacyZoneResult>> = self
|
let zone_resp: Option<LegacyCfResponse<LegacyZoneResult>> = self
|
||||||
.cf_api(
|
.cf_api(
|
||||||
@@ -592,6 +607,7 @@ impl LegacyDdnsClient {
|
|||||||
if let Some(ref id) = identifier {
|
if let Some(ref id) = identifier {
|
||||||
if modified {
|
if modified {
|
||||||
noop_reported.remove(&noop_key);
|
noop_reported.remove(&noop_key);
|
||||||
|
changed = true;
|
||||||
if self.dry_run {
|
if self.dry_run {
|
||||||
println!("[DRY RUN] Would update record {fqdn} -> {}", ip.ip);
|
println!("[DRY RUN] Would update record {fqdn} -> {}", ip.ip);
|
||||||
} else {
|
} else {
|
||||||
@@ -602,6 +618,10 @@ impl LegacyDdnsClient {
|
|||||||
.cf_api(&update_endpoint, "PUT", entry, Some(&record))
|
.cf_api(&update_endpoint, "PUT", entry, Some(&record))
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
messages.push(Message::new_ok(&format!(
|
||||||
|
"Updated {fqdn} -> {}",
|
||||||
|
ip.ip
|
||||||
|
)));
|
||||||
} else if noop_reported.insert(noop_key) {
|
} else if noop_reported.insert(noop_key) {
|
||||||
if self.dry_run {
|
if self.dry_run {
|
||||||
println!("[DRY RUN] Record {fqdn} is up to date");
|
println!("[DRY RUN] Record {fqdn} is up to date");
|
||||||
@@ -611,6 +631,7 @@ impl LegacyDdnsClient {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
noop_reported.remove(&noop_key);
|
noop_reported.remove(&noop_key);
|
||||||
|
changed = true;
|
||||||
if self.dry_run {
|
if self.dry_run {
|
||||||
println!("[DRY RUN] Would add new record {fqdn} -> {}", ip.ip);
|
println!("[DRY RUN] Would add new record {fqdn} -> {}", ip.ip);
|
||||||
} else {
|
} else {
|
||||||
@@ -620,6 +641,10 @@ impl LegacyDdnsClient {
|
|||||||
.cf_api(&create_endpoint, "POST", entry, Some(&record))
|
.cf_api(&create_endpoint, "POST", entry, Some(&record))
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
messages.push(Message::new_ok(&format!(
|
||||||
|
"Created {fqdn} -> {}",
|
||||||
|
ip.ip
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if purge_unknown_records {
|
if purge_unknown_records {
|
||||||
@@ -638,6 +663,7 @@ impl LegacyDdnsClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
(messages, changed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -823,7 +849,7 @@ mod tests {
|
|||||||
let ppfmt = pp();
|
let ppfmt = pp();
|
||||||
|
|
||||||
let mut cf_cache = CachedCloudflareFilter::new();
|
let mut cf_cache = CachedCloudflareFilter::new();
|
||||||
let ok = update_once(&config, &cf, ¬ifier, &heartbeat, &mut cf_cache, &ppfmt, &mut HashSet::new(), &Client::new()).await;
|
let ok = update_once(&config, &cf, ¬ifier, &heartbeat, &mut cf_cache, &ppfmt, &mut HashSet::new(), &crate::test_client()).await;
|
||||||
assert!(ok);
|
assert!(ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -876,12 +902,12 @@ mod tests {
|
|||||||
let mut noop_reported = HashSet::new();
|
let mut noop_reported = HashSet::new();
|
||||||
|
|
||||||
// First call: noop_reported is empty, so "up to date" is reported and key is inserted
|
// First call: noop_reported is empty, so "up to date" is reported and key is inserted
|
||||||
let ok = update_once(&config, &cf, ¬ifier, &heartbeat, &mut cf_cache, &ppfmt, &mut noop_reported, &Client::new()).await;
|
let ok = update_once(&config, &cf, ¬ifier, &heartbeat, &mut cf_cache, &ppfmt, &mut noop_reported, &crate::test_client()).await;
|
||||||
assert!(ok);
|
assert!(ok);
|
||||||
assert!(noop_reported.contains("home.example.com:A"), "noop_reported should contain the domain key after first noop");
|
assert!(noop_reported.contains("home.example.com:A"), "noop_reported should contain the domain key after first noop");
|
||||||
|
|
||||||
// Second call: noop_reported already has the key, so the message is suppressed
|
// Second call: noop_reported already has the key, so the message is suppressed
|
||||||
let ok = update_once(&config, &cf, ¬ifier, &heartbeat, &mut cf_cache, &ppfmt, &mut noop_reported, &Client::new()).await;
|
let ok = update_once(&config, &cf, ¬ifier, &heartbeat, &mut cf_cache, &ppfmt, &mut noop_reported, &crate::test_client()).await;
|
||||||
assert!(ok);
|
assert!(ok);
|
||||||
assert_eq!(noop_reported.len(), 1, "noop_reported should still have exactly one entry");
|
assert_eq!(noop_reported.len(), 1, "noop_reported should still have exactly one entry");
|
||||||
}
|
}
|
||||||
@@ -954,7 +980,7 @@ mod tests {
|
|||||||
noop_reported.insert("home.example.com:A".to_string());
|
noop_reported.insert("home.example.com:A".to_string());
|
||||||
|
|
||||||
let mut cf_cache = CachedCloudflareFilter::new();
|
let mut cf_cache = CachedCloudflareFilter::new();
|
||||||
let ok = update_once(&config, &cf, ¬ifier, &heartbeat, &mut cf_cache, &ppfmt, &mut noop_reported, &Client::new()).await;
|
let ok = update_once(&config, &cf, ¬ifier, &heartbeat, &mut cf_cache, &ppfmt, &mut noop_reported, &crate::test_client()).await;
|
||||||
assert!(ok);
|
assert!(ok);
|
||||||
assert!(!noop_reported.contains("home.example.com:A"), "noop_reported should be cleared after an update");
|
assert!(!noop_reported.contains("home.example.com:A"), "noop_reported should be cleared after an update");
|
||||||
}
|
}
|
||||||
@@ -1000,7 +1026,7 @@ mod tests {
|
|||||||
|
|
||||||
// all_ok = true because no zone-level errors occurred (empty ips just noop or warn)
|
// all_ok = true because no zone-level errors occurred (empty ips just noop or warn)
|
||||||
let mut cf_cache = CachedCloudflareFilter::new();
|
let mut cf_cache = CachedCloudflareFilter::new();
|
||||||
let ok = update_once(&config, &cf, ¬ifier, &heartbeat, &mut cf_cache, &ppfmt, &mut HashSet::new(), &Client::new()).await;
|
let ok = update_once(&config, &cf, ¬ifier, &heartbeat, &mut cf_cache, &ppfmt, &mut HashSet::new(), &crate::test_client()).await;
|
||||||
// Providers with None are not inserted in loop, so no IP detection warning is emitted,
|
// Providers with None are not inserted in loop, so no IP detection warning is emitted,
|
||||||
// no detected_ips entry is created, and set_ips is called with empty slice -> Noop.
|
// no detected_ips entry is created, and set_ips is called with empty slice -> Noop.
|
||||||
assert!(ok);
|
assert!(ok);
|
||||||
@@ -1050,7 +1076,7 @@ mod tests {
|
|||||||
let ppfmt = pp();
|
let ppfmt = pp();
|
||||||
|
|
||||||
let mut cf_cache = CachedCloudflareFilter::new();
|
let mut cf_cache = CachedCloudflareFilter::new();
|
||||||
let ok = update_once(&config, &cf, ¬ifier, &heartbeat, &mut cf_cache, &ppfmt, &mut HashSet::new(), &Client::new()).await;
|
let ok = update_once(&config, &cf, ¬ifier, &heartbeat, &mut cf_cache, &ppfmt, &mut HashSet::new(), &crate::test_client()).await;
|
||||||
assert!(!ok, "Expected false when zone is not found");
|
assert!(!ok, "Expected false when zone is not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1100,7 +1126,7 @@ mod tests {
|
|||||||
|
|
||||||
// dry_run returns Updated from set_ips (it signals intent), all_ok should be true
|
// dry_run returns Updated from set_ips (it signals intent), all_ok should be true
|
||||||
let mut cf_cache = CachedCloudflareFilter::new();
|
let mut cf_cache = CachedCloudflareFilter::new();
|
||||||
let ok = update_once(&config, &cf, ¬ifier, &heartbeat, &mut cf_cache, &ppfmt, &mut HashSet::new(), &Client::new()).await;
|
let ok = update_once(&config, &cf, ¬ifier, &heartbeat, &mut cf_cache, &ppfmt, &mut HashSet::new(), &crate::test_client()).await;
|
||||||
assert!(ok);
|
assert!(ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1166,7 +1192,7 @@ mod tests {
|
|||||||
let ppfmt = pp();
|
let ppfmt = pp();
|
||||||
|
|
||||||
let mut cf_cache = CachedCloudflareFilter::new();
|
let mut cf_cache = CachedCloudflareFilter::new();
|
||||||
let ok = update_once(&config, &cf, ¬ifier, &heartbeat, &mut cf_cache, &ppfmt, &mut HashSet::new(), &Client::new()).await;
|
let ok = update_once(&config, &cf, ¬ifier, &heartbeat, &mut cf_cache, &ppfmt, &mut HashSet::new(), &crate::test_client()).await;
|
||||||
assert!(ok);
|
assert!(ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1220,7 +1246,7 @@ mod tests {
|
|||||||
let ppfmt = pp();
|
let ppfmt = pp();
|
||||||
|
|
||||||
let mut cf_cache = CachedCloudflareFilter::new();
|
let mut cf_cache = CachedCloudflareFilter::new();
|
||||||
let ok = update_once(&config, &cf, ¬ifier, &heartbeat, &mut cf_cache, &ppfmt, &mut HashSet::new(), &Client::new()).await;
|
let ok = update_once(&config, &cf, ¬ifier, &heartbeat, &mut cf_cache, &ppfmt, &mut HashSet::new(), &crate::test_client()).await;
|
||||||
assert!(ok);
|
assert!(ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1260,7 +1286,7 @@ mod tests {
|
|||||||
let ppfmt = pp();
|
let ppfmt = pp();
|
||||||
|
|
||||||
let mut cf_cache = CachedCloudflareFilter::new();
|
let mut cf_cache = CachedCloudflareFilter::new();
|
||||||
let ok = update_once(&config, &cf, ¬ifier, &heartbeat, &mut cf_cache, &ppfmt, &mut HashSet::new(), &Client::new()).await;
|
let ok = update_once(&config, &cf, ¬ifier, &heartbeat, &mut cf_cache, &ppfmt, &mut HashSet::new(), &crate::test_client()).await;
|
||||||
assert!(!ok, "Expected false when WAF list is not found");
|
assert!(!ok, "Expected false when WAF list is not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1345,7 +1371,7 @@ mod tests {
|
|||||||
let ppfmt = pp();
|
let ppfmt = pp();
|
||||||
|
|
||||||
let mut cf_cache = CachedCloudflareFilter::new();
|
let mut cf_cache = CachedCloudflareFilter::new();
|
||||||
let ok = update_once(&config, &cf, ¬ifier, &heartbeat, &mut cf_cache, &ppfmt, &mut HashSet::new(), &Client::new()).await;
|
let ok = update_once(&config, &cf, ¬ifier, &heartbeat, &mut cf_cache, &ppfmt, &mut HashSet::new(), &crate::test_client()).await;
|
||||||
assert!(ok);
|
assert!(ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1362,7 +1388,7 @@ mod tests {
|
|||||||
let ppfmt = pp();
|
let ppfmt = pp();
|
||||||
|
|
||||||
let mut cf_cache = CachedCloudflareFilter::new();
|
let mut cf_cache = CachedCloudflareFilter::new();
|
||||||
let ok = update_once(&config, &cf, ¬ifier, &heartbeat, &mut cf_cache, &ppfmt, &mut HashSet::new(), &Client::new()).await;
|
let ok = update_once(&config, &cf, ¬ifier, &heartbeat, &mut cf_cache, &ppfmt, &mut HashSet::new(), &crate::test_client()).await;
|
||||||
assert!(ok);
|
assert!(ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1747,7 +1773,7 @@ mod tests {
|
|||||||
|
|
||||||
// set_ips with empty ips and no existing records = Noop; all_ok = true
|
// set_ips with empty ips and no existing records = Noop; all_ok = true
|
||||||
let mut cf_cache = CachedCloudflareFilter::new();
|
let mut cf_cache = CachedCloudflareFilter::new();
|
||||||
let ok = update_once(&config, &cf, ¬ifier, &heartbeat, &mut cf_cache, &ppfmt, &mut HashSet::new(), &Client::new()).await;
|
let ok = update_once(&config, &cf, ¬ifier, &heartbeat, &mut cf_cache, &ppfmt, &mut HashSet::new(), &crate::test_client()).await;
|
||||||
assert!(ok);
|
assert!(ok);
|
||||||
}
|
}
|
||||||
// -------------------------------------------------------
|
// -------------------------------------------------------
|
||||||
@@ -1766,7 +1792,7 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let ddns = LegacyDdnsClient {
|
let ddns = LegacyDdnsClient {
|
||||||
client: Client::new(),
|
client: crate::test_client(),
|
||||||
cf_api_base: server.uri(),
|
cf_api_base: server.uri(),
|
||||||
dry_run: false,
|
dry_run: false,
|
||||||
};
|
};
|
||||||
@@ -1798,7 +1824,7 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let ddns = LegacyDdnsClient {
|
let ddns = LegacyDdnsClient {
|
||||||
client: Client::new(),
|
client: crate::test_client(),
|
||||||
cf_api_base: server.uri(),
|
cf_api_base: server.uri(),
|
||||||
dry_run: false,
|
dry_run: false,
|
||||||
};
|
};
|
||||||
@@ -1827,7 +1853,7 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let ddns = LegacyDdnsClient {
|
let ddns = LegacyDdnsClient {
|
||||||
client: Client::new(),
|
client: crate::test_client(),
|
||||||
cf_api_base: server.uri(),
|
cf_api_base: server.uri(),
|
||||||
dry_run: false,
|
dry_run: false,
|
||||||
};
|
};
|
||||||
@@ -1849,7 +1875,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_legacy_cf_api_unknown_method() {
|
async fn test_legacy_cf_api_unknown_method() {
|
||||||
let ddns = LegacyDdnsClient {
|
let ddns = LegacyDdnsClient {
|
||||||
client: Client::new(),
|
client: crate::test_client(),
|
||||||
cf_api_base: "http://localhost".to_string(),
|
cf_api_base: "http://localhost".to_string(),
|
||||||
dry_run: false,
|
dry_run: false,
|
||||||
};
|
};
|
||||||
@@ -1879,7 +1905,7 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let ddns = LegacyDdnsClient {
|
let ddns = LegacyDdnsClient {
|
||||||
client: Client::new(),
|
client: crate::test_client(),
|
||||||
cf_api_base: server.uri(),
|
cf_api_base: server.uri(),
|
||||||
dry_run: false,
|
dry_run: false,
|
||||||
};
|
};
|
||||||
@@ -1935,7 +1961,7 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let ddns = LegacyDdnsClient {
|
let ddns = LegacyDdnsClient {
|
||||||
client: Client::new(),
|
client: crate::test_client(),
|
||||||
cf_api_base: server.uri(),
|
cf_api_base: server.uri(),
|
||||||
dry_run: false,
|
dry_run: false,
|
||||||
};
|
};
|
||||||
@@ -1991,7 +2017,7 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let ddns = LegacyDdnsClient {
|
let ddns = LegacyDdnsClient {
|
||||||
client: Client::new(),
|
client: crate::test_client(),
|
||||||
cf_api_base: server.uri(),
|
cf_api_base: server.uri(),
|
||||||
dry_run: false,
|
dry_run: false,
|
||||||
};
|
};
|
||||||
@@ -2033,7 +2059,7 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let ddns = LegacyDdnsClient {
|
let ddns = LegacyDdnsClient {
|
||||||
client: Client::new(),
|
client: crate::test_client(),
|
||||||
cf_api_base: server.uri(),
|
cf_api_base: server.uri(),
|
||||||
dry_run: true,
|
dry_run: true,
|
||||||
};
|
};
|
||||||
@@ -2084,7 +2110,7 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let ddns = LegacyDdnsClient {
|
let ddns = LegacyDdnsClient {
|
||||||
client: Client::new(),
|
client: crate::test_client(),
|
||||||
cf_api_base: server.uri(),
|
cf_api_base: server.uri(),
|
||||||
dry_run: false,
|
dry_run: false,
|
||||||
};
|
};
|
||||||
@@ -2139,7 +2165,7 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let ddns = LegacyDdnsClient {
|
let ddns = LegacyDdnsClient {
|
||||||
client: Client::new(),
|
client: crate::test_client(),
|
||||||
cf_api_base: server.uri(),
|
cf_api_base: server.uri(),
|
||||||
dry_run: false,
|
dry_run: false,
|
||||||
};
|
};
|
||||||
@@ -2188,7 +2214,7 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let ddns = LegacyDdnsClient {
|
let ddns = LegacyDdnsClient {
|
||||||
client: Client::new(),
|
client: crate::test_client(),
|
||||||
cf_api_base: server.uri(),
|
cf_api_base: server.uri(),
|
||||||
dry_run: false,
|
dry_run: false,
|
||||||
};
|
};
|
||||||
@@ -2232,7 +2258,7 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let ddns = LegacyDdnsClient {
|
let ddns = LegacyDdnsClient {
|
||||||
client: Client::new(),
|
client: crate::test_client(),
|
||||||
cf_api_base: server.uri(),
|
cf_api_base: server.uri(),
|
||||||
dry_run: false,
|
dry_run: false,
|
||||||
};
|
};
|
||||||
@@ -2264,7 +2290,7 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let ddns = LegacyDdnsClient {
|
let ddns = LegacyDdnsClient {
|
||||||
client: Client::new(),
|
client: crate::test_client(),
|
||||||
cf_api_base: server.uri(),
|
cf_api_base: server.uri(),
|
||||||
dry_run: true,
|
dry_run: true,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user