From f450ddfa158387721568e8a27f6ac58cd2c38d77 Mon Sep 17 00:00:00 2001 From: Nadu_Dev Date: Mon, 11 May 2026 13:23:56 +0530 Subject: [PATCH 01/19] feat: add metrics and metrics-exporter-prometheus dependencies for enhanced monitoring --- Cargo.lock | 407 ++++++++++++++++++++++++++++++++++++++++++++++++----- Cargo.toml | 2 + 2 files changed, 372 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5bcec6e..30f9cb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,18 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -94,10 +106,10 @@ dependencies = [ "bytes", "form_urlencoded", "futures-util", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.9.0", "hyper-util", "itoa", "matchit", @@ -125,8 +137,8 @@ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", @@ -147,6 +159,12 @@ dependencies = [ "syn", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -236,6 +254,22 @@ version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "crc32fast" version = "1.5.0" @@ -245,6 +279,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -351,6 +394,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -379,6 +428,21 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -553,7 +617,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http", + "http 1.4.0", "indexmap", "slab", "tokio", @@ -566,6 +630,9 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", +] [[package]] name = "hashbrown" @@ -590,6 +657,17 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.4.0" @@ -600,6 +678,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.1" @@ -607,7 +696,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.4.0", ] [[package]] @@ -618,8 +707,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "pin-project-lite", ] @@ -641,6 +730,29 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper" version = "1.9.0" @@ -652,8 +764,8 @@ dependencies = [ "futures-channel", "futures-core", "h2", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "httparse", "httpdate", "itoa", @@ -669,8 +781,8 @@ version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "http", - "hyper", + "http 1.4.0", + "hyper 1.9.0", "hyper-util", "rustls", "tokio", @@ -685,31 +797,44 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper", + "hyper 1.9.0", "hyper-util", "pin-project-lite", "tokio", "tower-service", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper 0.14.32", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "hyper-util" version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-channel", "futures-util", - "http", - "http-body", - "hyper", + "http 1.4.0", + "http-body 1.0.1", + "hyper 1.9.0", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.3", "tokio", "tower-service", "tracing", @@ -890,6 +1015,12 @@ version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + [[package]] name = "litemap" version = "0.8.2" @@ -938,6 +1069,50 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "metrics" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d05972e8cbac2671e85aa9d04d9160d193f8bebd1a5c1a2f4542c62e65d1d0" +dependencies = [ + "ahash", + "portable-atomic", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bf4e7146e30ad172c42c39b3246864bd2d3c6396780711a1baf749cfe423e21" +dependencies = [ + "base64 0.21.7", + "hyper 0.14.32", + "hyper-tls", + "indexmap", + "ipnet", + "metrics", + "metrics-util", + "quanta", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "metrics-util" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b07a5eb561b8cbc16be2d216faf7757f9baf3bfb94dbb0fae3df8387a5bb47f" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.14.5", + "metrics", + "num_cpus", + "quanta", + "sketches-ddsketch", +] + [[package]] name = "mime" version = "0.3.17" @@ -985,6 +1160,8 @@ dependencies = [ "dotenvy", "governor", "http-body-util", + "metrics", + "metrics-exporter-prometheus", "regex", "reqwest", "serde", @@ -1002,6 +1179,23 @@ dependencies = [ "wiremock", ] +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nonempty" version = "0.7.0" @@ -1039,6 +1233,49 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "openssl" +version = "0.10.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf0b434746ee2832f4f0baf10137e1cabb18cbe6912c69e2e33263c45250f542" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "158fe5b292746440aa6e7a7e690e55aeb72d41505e2804c23c6973ad0e9c9781" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -1183,7 +1420,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2", + "socket2 0.6.3", "thiserror 2.0.18", "tokio", "tracing", @@ -1220,7 +1457,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.6.3", "tracing", "windows-sys 0.60.2", ] @@ -1322,13 +1559,13 @@ version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-core", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.9.0", "hyper-rustls", "hyper-util", "js-sys", @@ -1374,6 +1611,19 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + [[package]] name = "rustls" version = "0.23.40" @@ -1421,12 +1671,44 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.228" @@ -1524,6 +1806,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +[[package]] +name = "sketches-ddsketch" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c" + [[package]] name = "slab" version = "0.4.12" @@ -1536,6 +1824,16 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "socket2" version = "0.6.3" @@ -1604,6 +1902,19 @@ dependencies = [ "syn", ] +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -1690,7 +2001,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.6.3", "tokio-macros", "windows-sys 0.61.2", ] @@ -1706,6 +2017,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.4" @@ -1748,18 +2069,18 @@ checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" dependencies = [ "async-trait", "axum", - "base64", + "base64 0.22.1", "bytes", "h2", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.9.0", "hyper-timeout", "hyper-util", "percent-encoding", "pin-project", - "socket2", + "socket2 0.6.3", "sync_wrapper", "tokio", "tokio-stream", @@ -1799,8 +2120,8 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", "http-range-header", "httpdate", @@ -1838,7 +2159,7 @@ dependencies = [ "axum", "forwarded-header-value", "governor", - "http", + "http 1.4.0", "pin-project", "thiserror 2.0.18", "tonic", @@ -1986,6 +2307,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "want" version = "0.3.1" @@ -2285,12 +2618,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08db1edfb05d9b3c1542e521aea074442088292f00b5f28e435c714a98f85031" dependencies = [ "assert-json-diff", - "base64", + "base64 0.22.1", "deadpool", "futures", - "http", + "http 1.4.0", "http-body-util", - "hyper", + "hyper 1.9.0", "hyper-util", "log", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index c463f0f..7a596f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,8 @@ axum = { version = "0.8", features = ["macros"] } dashmap = "6.1.0" dotenvy = "0.15" governor = "0.10" +metrics = "0.22" +metrics-exporter-prometheus = "0.13" reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls", "socks"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" From 81ec3055e306e01327e716806459723694590d96 Mon Sep 17 00:00:00 2001 From: Nadu_Dev Date: Mon, 11 May 2026 13:25:14 +0530 Subject: [PATCH 02/19] feat: integrate telemetry for enhanced observability and metrics tracking --- src/app.rs | 29 +++++--------------- src/lib.rs | 2 ++ src/services/ytdlp/mod.rs | 19 ++++++++++++- src/telemetry.rs | 56 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 24 deletions(-) create mode 100644 src/telemetry.rs diff --git a/src/app.rs b/src/app.rs index 576642a..c29b3ee 100644 --- a/src/app.rs +++ b/src/app.rs @@ -7,18 +7,15 @@ use crate::{ routes, services::ytdlp::YtdlpManager, state::AppState, + telemetry, }; use axum::{middleware, serve}; use dotenvy::dotenv; use std::net::SocketAddr; use std::sync::Arc; use tokio::net::TcpListener; -use tower_http::{ - compression::CompressionLayer, - trace::{DefaultMakeSpan, DefaultOnRequest, DefaultOnResponse, TraceLayer}, -}; -use tracing::{Level, error, info}; -use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; +use tower_http::compression::CompressionLayer; +use tracing::{error, info}; /// Application entry point. pub async fn run() { @@ -26,25 +23,10 @@ pub async fn run() { dotenv().ok(); // 2. Initialize structured logging and environment-based log filtering - #[allow(clippy::expect_used)] - let env_filter = tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| "info".into()) - .add_directive( - "tower_http=info" - .parse() - .expect("static directive should parse"), - ); - - tracing_subscriber::registry() - .with(env_filter) - .with(tracing_subscriber::fmt::layer()) - .init(); + telemetry::init_tracing(); // 3. Configure request/response tracing middleware - let trace_layer = TraceLayer::new_for_http() - .make_span_with(DefaultMakeSpan::new().level(Level::INFO)) - .on_request(DefaultOnRequest::new().level(Level::INFO)) - .on_response(DefaultOnResponse::new().level(Level::INFO)); + let trace_layer = telemetry::build_trace_layer(); // 4. Load application config and build shared app state let config = match AppConfig::from_env() { @@ -84,6 +66,7 @@ pub async fn run() { // 6. Compose router, state, and middleware stack (including rate limiter) let app = routes::create_router(state.clone()) + .merge(telemetry::setup_metrics_router()) .with_state(state.clone()) .layer(middleware::from_fn_with_state( state.clone(), diff --git a/src/lib.rs b/src/lib.rs index 6eb3b09..ffbe584 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,3 +21,5 @@ pub mod routes; pub mod services; /// Global application state. pub mod state; +/// Observability and tracing configuration. +pub mod telemetry; diff --git a/src/services/ytdlp/mod.rs b/src/services/ytdlp/mod.rs index 47d6153..97a2244 100644 --- a/src/services/ytdlp/mod.rs +++ b/src/services/ytdlp/mod.rs @@ -7,12 +7,13 @@ use crate::{ }, }; use dashmap::DashMap; +use metrics::{counter, gauge, histogram}; use regex::Regex; use std::{ path::{Component, Path, PathBuf}, process::Stdio, sync::{Arc, OnceLock}, - time::SystemTime, + time::{Instant, SystemTime}, }; use tokio::{ fs, @@ -150,6 +151,9 @@ impl YtdlpManager { .expect("job should exist"); } + counter!("ytdlp_jobs_enqueued_total").increment(1); + gauge!("ytdlp_active_jobs").increment(1.0); + let manager = self.clone(); tokio::spawn(async move { manager @@ -221,6 +225,8 @@ impl YtdlpManager { format_flag: String, sort_flag: Option, ) { + let start_time = Instant::now(); + #[allow(clippy::expect_used)] let _permit = self.semaphore.acquire().await.expect("semaphore closed"); @@ -330,6 +336,8 @@ impl YtdlpManager { let stderr_output = stderr_task.await.unwrap_or_else(|_| String::new()); let combined_output = combine_outputs(stdout_output, stderr_output); + let mut job_success = false; + match wait_result { Ok(Ok(status)) if status.success() => { let temp_dir_str = temp_dir.to_string_lossy(); @@ -365,6 +373,7 @@ impl YtdlpManager { }), ); } else { + job_success = true; self.mark_job_finished(&id, moved_files); info!("finished ytdlp job id={id}"); } @@ -387,6 +396,14 @@ impl YtdlpManager { Self::cleanup_failed_files(&temp_dir.to_string_lossy(), &id, false).await; } } + + histogram!("ytdlp_job_duration_seconds").record(start_time.elapsed().as_secs_f64()); + if job_success { + counter!("ytdlp_jobs_completed_total", "status" => "success").increment(1); + } else { + counter!("ytdlp_jobs_completed_total", "status" => "error").increment(1); + } + gauge!("ytdlp_active_jobs").decrement(1.0); } async fn cleanup_failed_files(temp_dir: &str, _id: &str, _is_base_dir: bool) { diff --git a/src/telemetry.rs b/src/telemetry.rs new file mode 100644 index 0000000..67afdbc --- /dev/null +++ b/src/telemetry.rs @@ -0,0 +1,56 @@ +use crate::state::AppState; +use axum::{Router, routing::get}; +use metrics_exporter_prometheus::PrometheusBuilder; +use tower_http::{ + classify::{ServerErrorsAsFailures, SharedClassifier}, + trace::{DefaultMakeSpan, DefaultOnRequest, DefaultOnResponse, TraceLayer}, +}; +use tracing::Level; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; + +/// Initializes structured logging and environment-based log filtering. +pub fn init_tracing() { + #[allow(clippy::expect_used)] + let env_filter = tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| "info".into()) + .add_directive( + "tower_http=info" + .parse() + .expect("static directive should parse"), + ); + + tracing_subscriber::registry() + .with(env_filter) + .with(tracing_subscriber::fmt::layer()) + .init(); +} + +pub type AppTraceLayer = TraceLayer< + SharedClassifier, + DefaultMakeSpan, + DefaultOnRequest, + DefaultOnResponse, +>; + +/// Configures request/response tracing middleware. +#[must_use] +pub fn build_trace_layer() -> AppTraceLayer { + TraceLayer::new_for_http() + .make_span_with(DefaultMakeSpan::new().level(Level::INFO)) + .on_request(DefaultOnRequest::new().level(Level::INFO)) + .on_response(DefaultOnResponse::new().level(Level::INFO)) +} + +/// Initializes the Prometheus metrics registry and returns the router. +pub fn setup_metrics_router() -> Router { + let builder = PrometheusBuilder::new(); + + // Install the global recorder + #[allow(clippy::expect_used)] + let handle = builder + .install_recorder() + .expect("Failed to install Prometheus recorder"); + + // Route that Prometheus will scrape + Router::new().route("/metrics", get(move || std::future::ready(handle.render()))) +} From 41c12c2480651e3a74614a9c4c118e219b98cfda Mon Sep 17 00:00:00 2001 From: Nadu_Dev Date: Mon, 11 May 2026 13:25:28 +0530 Subject: [PATCH 03/19] feat: enhance Caddyfile configuration for improved routing and access control --- Caddyfile | 88 ++++++++++++++++++++++++++++++++----------------- Caddyfile.local | 27 +++++++++------ 2 files changed, 75 insertions(+), 40 deletions(-) diff --git a/Caddyfile b/Caddyfile index d1b4833..2a3969c 100644 --- a/Caddyfile +++ b/Caddyfile @@ -1,34 +1,62 @@ -# Production Caddyfile. +{ + servers { + protocols h1 + trusted_proxies static ${CADDY_CLOUDFLARE_TRUSTED_PROXIES} + trusted_proxies_strict + client_ip_headers CF-Connecting-IP X-Forwarded-For + } +} + :80 { - # note: Redirect all HTTP traffic to HTTPS - redir https://api.nadzu.me{uri} + redir https://${PRODUCTION_DOMAIN}{uri} } -api.nadzu.me:443 { - tls /etc/caddy/certs/api.nadzu.me.pem /etc/caddy/certs/api.nadzu.me.key - - @notallowed { - path /nadun/fs/* - not remote_ip - } - # note: Only Allow MY Personal VPN IP - abort @notallowed - - route /nadun/fs/* { - uri strip_prefix /nadun/fs - - @mp4 path *.mp4 - header @mp4 { - Content-Type "video/mp4" - Content-Disposition "inline" - Access-Control-Allow-Origin "*" - } - - file_server browse { - root /home/app/downloads - hide lost+found - } - } - # note: DockerCompose Native - reverse_proxy app:${APP_PORT} +${PRODUCTION_DOMAIN}:443 { + tls /etc/caddy/certs/${PRODUCTION_DOMAIN}.pem /etc/caddy/certs/${PRODUCTION_DOMAIN}.key + + # Global Matcher: Only your SSH/VPN IPs can pass + @internal_only { + not client_ip ${SSH_ALLOWED_IPS} + } + + # 1. Protected File System + handle /nadun/fs/* { + respond @internal_only "Forbidden" 403 + + uri strip_prefix /nadun/fs + @mp4 path *.mp4 + header @mp4 { + Content-Type "video/mp4" + Content-Disposition "inline" + Access-Control-Allow-Origin "*" + } + + file_server { + browse /etc/caddy/browse.html + root /home/app/downloads + hide lost+found + } + } + + # 2. Protected Grafana Dashboard + handle /nadun/grafana/* { + respond @internal_only "Forbidden" 403 + reverse_proxy grafana:3000 + } + + # 3. Protected Metrics (Internal Telemetry) + handle /metrics { + respond @internal_only "Forbidden" 403 + reverse_proxy app:8080 + } + + # 4. Public API + handle { + reverse_proxy app:${APP_PORT} { + header_up Host {host} + transport http { + versions 1.1 + } + } + } } \ No newline at end of file diff --git a/Caddyfile.local b/Caddyfile.local index d0d6332..0573bf5 100644 --- a/Caddyfile.local +++ b/Caddyfile.local @@ -1,32 +1,39 @@ { debug admin :2026 - email admin@nadzu.me auto_https disable_redirects } -# HTTP Protocol +# HTTP Protocol - Direct API Access http://:8080 { - handle { - reverse_proxy app:8080 - } + reverse_proxy app:8080 } -# HTTPS Protocol +# HTTPS Protocol - Full Stack Access nadzu.localhost { tls internal - handle_path /downloads/* { + # 1. File System Access + handle_path /nadun/fs/* { root * /var/www/downloads - file_server { browse /etc/caddy/browse.html hide lost+found } } - # Proxy everything else to the Rust backend + # 2. Local Grafana Access + handle /nadun/grafana/* { + reverse_proxy nadzu-grafana:3000 + } + + # 3. Local Metrics Access + handle /metrics { + reverse_proxy app:8080 + } + + # 4. Default API Proxy handle { reverse_proxy app:8080 } -} +} \ No newline at end of file From 1cdf52dbfddb0ad685c48e9c1a65ddc28bfc65d9 Mon Sep 17 00:00:00 2001 From: Nadu_Dev Date: Mon, 11 May 2026 13:25:46 +0530 Subject: [PATCH 04/19] feat: update Dockerfile and docker-compose for improved service configuration and add Prometheus datasource --- Dockerfile | 2 +- docker-compose.dev.yml | 32 ++++++++++++++++--- .../provisioning/datasources/prometheus.yml | 7 ++++ 3 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 infra/common/grafana/provisioning/datasources/prometheus.yml diff --git a/Dockerfile b/Dockerfile index 7b61ec4..8bba8d2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ ARG BIN=nadzu # --- STAGE 1: Rust Chef --- FROM --platform=$BUILDPLATFORM ${RUST_IMAGE} AS chef -RUN apk add --no-cache build-base musl-dev zstd-dev pkgconfig ca-certificates +RUN apk add --no-cache build-base musl-dev zstd-dev pkgconfig ca-certificates openssl-dev openssl-libs-static WORKDIR /app # --- STAGE 2: Rust Planner --- diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 803f422..beb29c4 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -2,10 +2,8 @@ name: nadzu-dev services: app: - build: - context: . - dockerfile: Dockerfile.dev - container_name: nadzu-app-dev + # Build Docker image from local Dockerfile (make bd) + image: nadzu:local environment: APP_HOST: "0.0.0.0" APP_PORT: "8080" @@ -15,6 +13,8 @@ services: RUST_LOG: "debug" ALLOWED_ORIGINS: "https://nadzu.localhost, http://localhost:8080" CAPTCHA_SECRET_KEY: "bypass" + MASTER_API_KEY: "${MASTER_API_KEY}" + volumes: - ./:/app - app-target:/app/target_docker @@ -36,6 +36,30 @@ services: depends_on: - app + prometheus: + image: prom/prometheus:latest + container_name: nadzu-prometheus + volumes: + - ./infra/common/prometheus.yml:/etc/prometheus/prometheus.yml + ports: + - "9090:9090" + + grafana: + image: grafana/grafana:latest + container_name: nadzu-grafana + environment: + - GF_AUTH_ANONYMOUS_ENABLED=true + - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin + - GF_AUTH_DISABLE_LOGIN_FORM=true + - GF_SERVER_ROOT_URL=https://nadzu.localhost/nadun/grafana/ + - GF_SERVER_SERVE_FROM_SUB_PATH=true + ports: + - "3000:3000" + volumes: + - ./infra/common/grafana/provisioning:/etc/grafana/provisioning + depends_on: + - prometheus + volumes: app-target: app-cargo: diff --git a/infra/common/grafana/provisioning/datasources/prometheus.yml b/infra/common/grafana/provisioning/datasources/prometheus.yml new file mode 100644 index 0000000..0eddf26 --- /dev/null +++ b/infra/common/grafana/provisioning/datasources/prometheus.yml @@ -0,0 +1,7 @@ +apiVersion: 1 +datasources: + - name: Prometheus + type: prometheus + access: proxy + url: http://prometheus:9090 + isDefault: true From d6939bc6d5072d18bf780bd3eb4189bedd6b6349 Mon Sep 17 00:00:00 2001 From: Nadu_Dev Date: Mon, 11 May 2026 13:26:12 +0530 Subject: [PATCH 05/19] feat: add Prometheus and Grafana configuration files to S3 in Terraform setup --- Makefile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Makefile b/Makefile index 6b6370e..bfbae08 100644 --- a/Makefile +++ b/Makefile @@ -181,6 +181,14 @@ tf: ## use this to spawn a loaded shell set +a && \ aws s3 cp infra/common/browse.html \"s3://\$$AWS_S3_BUCKET_NAME/terraform/data/browse.html\" --endpoint-url \"\$$AWS_ENDPOINT_URL_S3\" && \ export TF_VAR_CADDY_CUSTOM_BROWSE_FILE_URL=\$$(aws s3 presign \"s3://\$$AWS_S3_BUCKET_NAME/terraform/data/browse.html\" --endpoint-url \"\$$AWS_ENDPOINT_URL_S3\" --expires-in 3600 | tr -d '\r') && \ + aws s3 cp infra/common/prometheus.yml \"s3://\$$AWS_S3_BUCKET_NAME/terraform/data/prometheus.yml\" --endpoint-url \"\$$AWS_ENDPOINT_URL_S3\" && \ + export TF_VAR_PROMETHEUS_CONFIG_URL=\$$(aws s3 presign \"s3://\$$AWS_S3_BUCKET_NAME/terraform/data/prometheus.yml\" --endpoint-url \"\$$AWS_ENDPOINT_URL_S3\" --expires-in 3600 | tr -d '\r') && \ + aws s3 cp infra/common/grafana/provisioning/datasources/prometheus.yml \"s3://\$$AWS_S3_BUCKET_NAME/terraform/data/grafana_datasource.yml\" --endpoint-url \"\$$AWS_ENDPOINT_URL_S3\" && \ + export TF_VAR_GRAFANA_DATASOURCE_URL=\$$(aws s3 presign \"s3://\$$AWS_S3_BUCKET_NAME/terraform/data/grafana_datasource.yml\" --endpoint-url \"\$$AWS_ENDPOINT_URL_S3\" --expires-in 3600 | tr -d '\r') && \ + aws s3 cp infra/common/grafana/provisioning/dashboards/default.yml \"s3://\$$AWS_S3_BUCKET_NAME/terraform/data/grafana_provider.yml\" --endpoint-url \"\$$AWS_ENDPOINT_URL_S3\" && \ + export TF_VAR_GRAFANA_PROVIDER_URL=\$$(aws s3 presign \"s3://\$$AWS_S3_BUCKET_NAME/terraform/data/grafana_provider.yml\" --endpoint-url \"\$$AWS_ENDPOINT_URL_S3\" --expires-in 3600 | tr -d '\r') && \ + aws s3 cp infra/common/grafana/provisioning/dashboards/ytdlp-health.json \"s3://\$$AWS_S3_BUCKET_NAME/terraform/data/ytdlp-health.json\" --endpoint-url \"\$$AWS_ENDPOINT_URL_S3\" && \ + export TF_VAR_YTDLP_DASHBOARD_URL=\$$(aws s3 presign \"s3://\$$AWS_S3_BUCKET_NAME/terraform/data/ytdlp-health.json\" --endpoint-url \"\$$AWS_ENDPOINT_URL_S3\" --expires-in 3600 | tr -d '\r') && \ export MSYS_NO_PATHCONV=1 && \ cd $(TF_STACK_DIR) && \ unset PROMPT_COMMAND && \ From 9e364cdb41b8b808a429eff3aabd19f7b5244c1c Mon Sep 17 00:00:00 2001 From: Nadu_Dev Date: Mon, 11 May 2026 13:26:45 +0530 Subject: [PATCH 06/19] feat: add Prometheus and Grafana configuration variables for enhanced monitoring --- .../digitalocean/accounts/naduns-team/main.tf | 4 ++++ .../accounts/naduns-team/variables.tf | 21 +++++++++++++++++++ infra/digitalocean/components/locals.tf | 6 +++++- infra/digitalocean/components/variables.tf | 20 ++++++++++++++++++ 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/infra/digitalocean/accounts/naduns-team/main.tf b/infra/digitalocean/accounts/naduns-team/main.tf index 2eb5dcf..1d020df 100644 --- a/infra/digitalocean/accounts/naduns-team/main.tf +++ b/infra/digitalocean/accounts/naduns-team/main.tf @@ -32,4 +32,8 @@ module "digitalocean_stack" { SSH_ALLOWED_IPS = var.SSH_ALLOWED_IPS MASTER_API_KEY = var.MASTER_API_KEY CADDY_CUSTOM_BROWSE_FILE_URL = var.CADDY_CUSTOM_BROWSE_FILE_URL + PROMETHEUS_CONFIG_URL = var.PROMETHEUS_CONFIG_URL + GRAFANA_DATASOURCE_URL = var.GRAFANA_DATASOURCE_URL + GRAFANA_PROVIDER_URL = var.GRAFANA_PROVIDER_URL + YTDLP_DASHBOARD_URL = var.YTDLP_DASHBOARD_URL } diff --git a/infra/digitalocean/accounts/naduns-team/variables.tf b/infra/digitalocean/accounts/naduns-team/variables.tf index bdca09e..d8f0d27 100644 --- a/infra/digitalocean/accounts/naduns-team/variables.tf +++ b/infra/digitalocean/accounts/naduns-team/variables.tf @@ -199,6 +199,27 @@ variable "MASTER_API_KEY" { sensitive = true } + +variable "PROMETHEUS_CONFIG_URL" { + description = "Presigned URL to download prometheus.yml" + type = string +} + +variable "GRAFANA_DATASOURCE_URL" { + description = "Presigned URL to download prometheus data source config" + type = string +} + +variable "GRAFANA_PROVIDER_URL" { + description = "Presigned URL to download grafana provider config for dashboard" + type = string +} + +variable "YTDLP_DASHBOARD_URL" { + description = "Presigned URL to download ytdlp dashboard json" + type = string +} + variable "CADDY_CUSTOM_BROWSE_FILE_URL" { //VAR: Declare on terraform.tfvars description = "Presigned URL to download custom browse.html" diff --git a/infra/digitalocean/components/locals.tf b/infra/digitalocean/components/locals.tf index 3305daf..7016f4c 100644 --- a/infra/digitalocean/components/locals.tf +++ b/infra/digitalocean/components/locals.tf @@ -29,7 +29,11 @@ locals { MASTER_API_KEY = coalesce(var.MASTER_API_KEY, "NOT-SET") CERT_PEM = file("${path.module}/../../common/certificates/api.nadzu.me.pem") CERT_KEY = file("${path.module}/../../common/certificates/api.nadzu.me.key") - CADDY_CUSTOM_BROWSE_FILE_URL = var.CADDY_CUSTOM_BROWSE_FILE_URL + CADDY_CUSTOM_BROWSE_FILE_URL = var.CADDY_CUSTOM_BROWSE_FILE_URL + PROMETHEUS_CONFIG_URL = var.PROMETHEUS_CONFIG_URL + GRAFANA_DATASOURCE_URL = var.GRAFANA_DATASOURCE_URL + GRAFANA_PROVIDER_URL = var.GRAFANA_PROVIDER_URL + YTDLP_DASHBOARD_URL = var.YTDLP_DASHBOARD_URL SSH_ALLOWED_IPS = join(" ", var.SSH_ALLOWED_IPS) PRODUCTION_DOMAIN = join(".", [var.CLOUDFLARE_RECORD_NAME, var.CLOUDFLARE_ZONE_NAME]) CADDY_CLOUDFLARE_TRUSTED_PROXIES = join(" ", concat( diff --git a/infra/digitalocean/components/variables.tf b/infra/digitalocean/components/variables.tf index 678fe69..1314f6c 100644 --- a/infra/digitalocean/components/variables.tf +++ b/infra/digitalocean/components/variables.tf @@ -166,3 +166,23 @@ variable "CADDY_CUSTOM_BROWSE_FILE_URL" { description = "Presigned URL to download custom browse.html" type = string } + +variable "PROMETHEUS_CONFIG_URL" { + description = "Presigned URL to download prometheus.yml" + type = string +} + +variable "GRAFANA_DATASOURCE_URL" { + description = "Presigned URL to download prometheus data source config" + type = string +} + +variable "GRAFANA_PROVIDER_URL" { + description = "Presigned URL to download grafana provider config for dashboard" + type = string +} + +variable "YTDLP_DASHBOARD_URL" { + description = "Presigned URL to download ytdlp dashboard json" + type = string +} From e94dd25c1daf5ec03ecd8b6018c946d4a30de514 Mon Sep 17 00:00:00 2001 From: Nadu_Dev Date: Mon, 11 May 2026 13:26:58 +0530 Subject: [PATCH 07/19] feat: add Grafana dashboard and Prometheus configuration for monitoring Nadzu API --- .../provisioning/dashboards/default.yml | 7 + .../provisioning/dashboards/ytdlp-health.json | 137 ++++++++++++++++++ infra/common/prometheus.yml | 6 + 3 files changed, 150 insertions(+) create mode 100644 infra/common/grafana/provisioning/dashboards/default.yml create mode 100644 infra/common/grafana/provisioning/dashboards/ytdlp-health.json create mode 100644 infra/common/prometheus.yml diff --git a/infra/common/grafana/provisioning/dashboards/default.yml b/infra/common/grafana/provisioning/dashboards/default.yml new file mode 100644 index 0000000..3c1d355 --- /dev/null +++ b/infra/common/grafana/provisioning/dashboards/default.yml @@ -0,0 +1,7 @@ +apiVersion: 1 +providers: + - name: 'Default' + orgId: 1 + type: file + options: + path: /etc/grafana/provisioning/dashboards diff --git a/infra/common/grafana/provisioning/dashboards/ytdlp-health.json b/infra/common/grafana/provisioning/dashboards/ytdlp-health.json new file mode 100644 index 0000000..14041d0 --- /dev/null +++ b/infra/common/grafana/provisioning/dashboards/ytdlp-health.json @@ -0,0 +1,137 @@ +{ + "title": "Nadzu API - Worker Health", + "timezone": "browser", + "refresh": "5s", + "schemaVersion": 39, + "panels": [ + { + "title": "Current Success Rate", + "type": "stat", + "gridPos": { "h": 6, "w": 8, "x": 0, "y": 0 }, + "targets": [ + { + "datasource": "Prometheus", + "expr": "(sum(rate(ytdlp_jobs_completed_total{status=\"success\"}[5m])) / sum(rate(ytdlp_jobs_completed_total[5m]))) * 100", + "legendFormat": "Success Rate" + } + ], + "fieldConfig": { + "defaults": { + "unit": "percent", + "noValue": "100", + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "red", "value": null }, + { "color": "orange", "value": 80 }, + { "color": "green", "value": 95 } + ] + } + } + } + }, + { + "title": "Active Workers (Saturation)", + "type": "gauge", + "gridPos": { "h": 6, "w": 8, "x": 8, "y": 0 }, + "targets": [ + { + "datasource": "Prometheus", + "expr": "ytdlp_active_jobs" + } + ], + "fieldConfig": { + "defaults": { + "min": 0, + "max": 10, + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "green", "value": null }, + { "color": "orange", "value": 5 }, + { "color": "red", "value": 8 } + ] + } + } + } + }, + { + "title": "Recent Error Count (1h)", + "type": "stat", + "gridPos": { "h": 6, "w": 8, "x": 16, "y": 0 }, + "targets": [ + { + "datasource": "Prometheus", + "expr": "sum(increase(ytdlp_jobs_completed_total{status=\"error\"}[1h]))" + } + ], + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "green", "value": null }, + { "color": "red", "value": 1 } + ] + } + } + } + }, + { + "title": "Traffic Volume (Success vs Error)", + "type": "timeseries", + "gridPos": { "h": 9, "w": 24, "x": 0, "y": 6 }, + "targets": [ + { + "datasource": "Prometheus", + "expr": "sum by (status) (rate(ytdlp_jobs_completed_total[5m]))", + "legendFormat": "{{status}}" + } + ], + "fieldConfig": { + "defaults": { + "custom": { + "drawStyle": "line", + "fillOpacity": 20, + "lineInterpolation": "smooth", + "stacking": { "group": "A", "mode": "normal" } + } + }, + "overrides": [ + { + "matcher": { "id": "byName", "options": "success" }, + "properties": [{ "id": "color", "value": { "fixedColor": "green", "mode": "fixed" } }] + }, + { + "matcher": { "id": "byName", "options": "error" }, + "properties": [{ "id": "color", "value": { "fixedColor": "red", "mode": "fixed" } }] + } + ] + } + }, + { + "title": "Processing Latency (P50 vs P99)", + "type": "timeseries", + "gridPos": { "h": 9, "w": 24, "x": 0, "y": 15 }, + "targets": [ + { + "datasource": "Prometheus", + "expr": "ytdlp_job_duration_seconds{quantile=\"0.5\"}", + "legendFormat": "P50 (Median)" + }, + { + "datasource": "Prometheus", + "expr": "ytdlp_job_duration_seconds{quantile=\"0.99\"}", + "legendFormat": "P99 (Worst Case)" + } + ], + "fieldConfig": { + "defaults": { + "unit": "s", + "custom": { "lineInterpolation": "smooth" } + } + } + } + ] +} \ No newline at end of file diff --git a/infra/common/prometheus.yml b/infra/common/prometheus.yml new file mode 100644 index 0000000..b4b1ccc --- /dev/null +++ b/infra/common/prometheus.yml @@ -0,0 +1,6 @@ +global: + scrape_interval: 10s +scrape_configs: + - job_name: 'nadzu-api' + static_configs: + - targets: ['app:8080'] From d28fa0afad33bd916096bbb3fd48197eb6ee43c4 Mon Sep 17 00:00:00 2001 From: Nadu_Dev Date: Mon, 11 May 2026 13:27:04 +0530 Subject: [PATCH 08/19] feat: add Prometheus and Grafana services with configuration for observability --- infra/common/cloud-init.template | 75 ++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 14 deletions(-) diff --git a/infra/common/cloud-init.template b/infra/common/cloud-init.template index 788153a..246b113 100644 --- a/infra/common/cloud-init.template +++ b/infra/common/cloud-init.template @@ -40,8 +40,7 @@ write_files: - HTTP_PROXY=socks5h://warp:40000 - HTTPS_PROXY=socks5h://warp:40000 - ALL_PROXY=socks5h://warp:40000 - - NO_PROXY=google.com,www.google.com,recaptcha.net,localhost,127.0.0.1,app,caddy,warp - + - NO_PROXY=localhost,127.0.0.1,app,warp,caddy,prometheus,grafana caddy: image: caddy:2-alpine restart: ${DOCKER_RESTART_POLICY} @@ -56,6 +55,25 @@ write_files: depends_on: - app + prometheus: + image: prom/prometheus:latest + restart: ${DOCKER_RESTART_POLICY} + volumes: + - /opt/app/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml + + grafana: + image: grafana/grafana:latest + restart: ${DOCKER_RESTART_POLICY} + environment: + - GF_AUTH_ANONYMOUS_ENABLED=true + - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin + - GF_SERVER_SERVE_FROM_SUB_PATH=true + - GF_SERVER_ROOT_URL=https://${PRODUCTION_DOMAIN}/nadun/grafana/ + volumes: + - /opt/app/grafana/provisioning:/etc/grafana/provisioning + depends_on: + - prometheus + - path: /opt/app/Caddyfile permissions: '0644' owner: root:root @@ -76,15 +94,16 @@ write_files: ${PRODUCTION_DOMAIN}:443 { tls /etc/caddy/certs/${PRODUCTION_DOMAIN}.pem /etc/caddy/certs/${PRODUCTION_DOMAIN}.key - route /nadun/fs/* { - @blocked { - path /nadun/fs/* - not client_ip ${SSH_ALLOWED_IPS} - } - respond @blocked "Forbidden" 403 + # Global Matcher for Internal Access + @internal_only { + not client_ip ${SSH_ALLOWED_IPS} + } + # 1. Protected File System + handle /nadun/fs/* { + respond @internal_only "Forbidden" 403 + uri strip_prefix /nadun/fs - @mp4 path *.mp4 header @mp4 { Content-Type "video/mp4" @@ -99,10 +118,25 @@ write_files: } } - reverse_proxy app:${APP_PORT} { - header_up Host {host} - transport http { - versions 1.1 + # 2. Protected Grafana Dashboard + handle /nadun/grafana/* { + respond @internal_only "Forbidden" 403 + reverse_proxy grafana:3000 + } + + # 3. Protected Metrics Endpoint (App) + handle /metrics { + respond @internal_only "Forbidden" 403 + reverse_proxy app:8080 + } + + # 4. Public API Endpoints + handle { + reverse_proxy app:${APP_PORT} { + header_up Host {host} + transport http { + versions 1.1 + } } } } @@ -194,13 +228,26 @@ write_files: chown 65532:65532 "${MOUNT_PATH}" chmod 775 "${MOUNT_PATH}" - # Download browse.html via presigned URL securely if [ -n "${CADDY_CUSTOM_BROWSE_FILE_URL}" ]; then curl -sSfL "${CADDY_CUSTOM_BROWSE_FILE_URL}" -o /opt/app/browse.html chmod 644 /opt/app/browse.html chown root:root /opt/app/browse.html fi + # Create Observability Directories + mkdir -p /opt/app/prometheus + mkdir -p /opt/app/grafana/provisioning/datasources + mkdir -p /opt/app/grafana/provisioning/dashboards + + # Fetch Artifacts from S3 Presigned URLs + curl -sSfL "${PROMETHEUS_CONFIG_URL}" -o /opt/app/prometheus/prometheus.yml + curl -sSfL "${GRAFANA_DATASOURCE_URL}" -o /opt/app/grafana/provisioning/datasources/prometheus.yml + curl -sSfL "${GRAFANA_PROVIDER_URL}" -o /opt/app/grafana/provisioning/dashboards/default.yml + curl -sSfL "${YTDLP_DASHBOARD_URL}" -o /opt/app/grafana/provisioning/dashboards/ytdlp-health.json + + # Set Permissions + chmod -R 644 /opt/app/prometheus /opt/app/grafana/provisioning + cat /root/.ghcr_token | docker login ghcr.io -u "${GITHUB_USERNAME}" --password-stdin cd /opt/app docker compose pull From 0d3dc0a4d47f775ccd640b67ae8a6e797e549e13 Mon Sep 17 00:00:00 2001 From: Nadu_Dev Date: Tue, 12 May 2026 08:15:06 +0530 Subject: [PATCH 09/19] feat: add Grafana admin user and password variables for enhanced security --- .env.example | 2 ++ docker-compose.dev.yml | 6 ++-- infra/common/cloud-init.template | 28 +++++++++---------- .../provisioning/dashboards/ytdlp-health.json | 6 ++-- .../digitalocean/accounts/naduns-team/main.tf | 2 ++ .../accounts/naduns-team/variables.tf | 9 ++++++ infra/digitalocean/components/locals.tf | 2 ++ infra/digitalocean/components/variables.tf | 11 ++++++++ 8 files changed, 47 insertions(+), 19 deletions(-) diff --git a/.env.example b/.env.example index 65c0e89..d73593e 100644 --- a/.env.example +++ b/.env.example @@ -46,3 +46,5 @@ TF_VAR_YTDLP_PATH=REPLACE_WITH_TF_VAR_YTDLP_PATH TF_VAR_DOWNLOAD_DIR=REPLACE_WITH_TF_VAR_DOWNLOAD_DIR TF_VAR_MASTER_API_KEY=REPLACE_WITH_TF_VAR_MASTER_API_KEY TF_VAR_WARP_LICENSE_KEY=REPLACE_WITH_TF_VAR_WARP_LICENSE_KEY +TF_VAR_GRAFANA_ADMIN_USER=REPLACE_WITH_GRAFANA_ADMIN_USER +TF_VAR_GRAFANA_ADMIN_PASSWORD=REPLACE_WITH_GRAFANA_ADMIN_PASSWORD diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index beb29c4..77a26d5 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -49,8 +49,10 @@ services: container_name: nadzu-grafana environment: - GF_AUTH_ANONYMOUS_ENABLED=true - - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin - - GF_AUTH_DISABLE_LOGIN_FORM=true + - GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer + - GF_SECURITY_ADMIN_USER=admin + - GF_SECURITY_ADMIN_PASSWORD=admin + - GF_USERS_ALLOW_SIGN_UP=false - GF_SERVER_ROOT_URL=https://nadzu.localhost/nadun/grafana/ - GF_SERVER_SERVE_FROM_SUB_PATH=true ports: diff --git a/infra/common/cloud-init.template b/infra/common/cloud-init.template index 246b113..9fa4257 100644 --- a/infra/common/cloud-init.template +++ b/infra/common/cloud-init.template @@ -66,7 +66,10 @@ write_files: restart: ${DOCKER_RESTART_POLICY} environment: - GF_AUTH_ANONYMOUS_ENABLED=true - - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin + - GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer + - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USER} + - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD} + - GF_USERS_ALLOW_SIGN_UP=false - GF_SERVER_SERVE_FROM_SUB_PATH=true - GF_SERVER_ROOT_URL=https://${PRODUCTION_DOMAIN}/nadun/grafana/ volumes: @@ -94,12 +97,12 @@ write_files: ${PRODUCTION_DOMAIN}:443 { tls /etc/caddy/certs/${PRODUCTION_DOMAIN}.pem /etc/caddy/certs/${PRODUCTION_DOMAIN}.key - # Global Matcher for Internal Access + # Global: Internal Access @internal_only { not client_ip ${SSH_ALLOWED_IPS} } - # 1. Protected File System + # 1.internal: File System RO handle /nadun/fs/* { respond @internal_only "Forbidden" 403 @@ -118,16 +121,16 @@ write_files: } } - # 2. Protected Grafana Dashboard + # 2. internal: Grafana Dashboard handle /nadun/grafana/* { respond @internal_only "Forbidden" 403 reverse_proxy grafana:3000 } - # 3. Protected Metrics Endpoint (App) + # 3. internal: Prometheus Metrics handle /metrics { respond @internal_only "Forbidden" 403 - reverse_proxy app:8080 + reverse_proxy app:${APP_PORT} } # 4. Public API Endpoints @@ -177,7 +180,7 @@ write_files: ${GHCR_PAT} - path: /opt/app/startup.sh - permissions: '0755' + permissions: '0700' owner: root:root content: | #!/bin/bash @@ -227,12 +230,6 @@ write_files: chown 65532:65532 "${MOUNT_PATH}" chmod 775 "${MOUNT_PATH}" - - if [ -n "${CADDY_CUSTOM_BROWSE_FILE_URL}" ]; then - curl -sSfL "${CADDY_CUSTOM_BROWSE_FILE_URL}" -o /opt/app/browse.html - chmod 644 /opt/app/browse.html - chown root:root /opt/app/browse.html - fi # Create Observability Directories mkdir -p /opt/app/prometheus @@ -240,13 +237,16 @@ write_files: mkdir -p /opt/app/grafana/provisioning/dashboards # Fetch Artifacts from S3 Presigned URLs + curl -sSfL "${CADDY_CUSTOM_BROWSE_FILE_URL}" -o /opt/app/browse.html curl -sSfL "${PROMETHEUS_CONFIG_URL}" -o /opt/app/prometheus/prometheus.yml curl -sSfL "${GRAFANA_DATASOURCE_URL}" -o /opt/app/grafana/provisioning/datasources/prometheus.yml curl -sSfL "${GRAFANA_PROVIDER_URL}" -o /opt/app/grafana/provisioning/dashboards/default.yml curl -sSfL "${YTDLP_DASHBOARD_URL}" -o /opt/app/grafana/provisioning/dashboards/ytdlp-health.json # Set Permissions - chmod -R 644 /opt/app/prometheus /opt/app/grafana/provisioning + chmod 644 /opt/app/browse.html + chown root:root /opt/app/browse.html + chmod -R u=rwX,go=rX /opt/app/prometheus /opt/app/grafana/provisioning cat /root/.ghcr_token | docker login ghcr.io -u "${GITHUB_USERNAME}" --password-stdin cd /opt/app diff --git a/infra/common/grafana/provisioning/dashboards/ytdlp-health.json b/infra/common/grafana/provisioning/dashboards/ytdlp-health.json index 14041d0..84657b0 100644 --- a/infra/common/grafana/provisioning/dashboards/ytdlp-health.json +++ b/infra/common/grafana/provisioning/dashboards/ytdlp-health.json @@ -18,7 +18,7 @@ "fieldConfig": { "defaults": { "unit": "percent", - "noValue": "100", + "noValue": 100, "thresholds": { "mode": "absolute", "steps": [ @@ -117,12 +117,12 @@ "targets": [ { "datasource": "Prometheus", - "expr": "ytdlp_job_duration_seconds{quantile=\"0.5\"}", + "expr": "histogram_quantile(0.5, sum by (le) (rate(ytdlp_job_duration_seconds_bucket[5m])))", "legendFormat": "P50 (Median)" }, { "datasource": "Prometheus", - "expr": "ytdlp_job_duration_seconds{quantile=\"0.99\"}", + "expr": "histogram_quantile(0.99, sum by (le) (rate(ytdlp_job_duration_seconds_bucket[5m])))", "legendFormat": "P99 (Worst Case)" } ], diff --git a/infra/digitalocean/accounts/naduns-team/main.tf b/infra/digitalocean/accounts/naduns-team/main.tf index 1d020df..bb4c353 100644 --- a/infra/digitalocean/accounts/naduns-team/main.tf +++ b/infra/digitalocean/accounts/naduns-team/main.tf @@ -35,5 +35,7 @@ module "digitalocean_stack" { PROMETHEUS_CONFIG_URL = var.PROMETHEUS_CONFIG_URL GRAFANA_DATASOURCE_URL = var.GRAFANA_DATASOURCE_URL GRAFANA_PROVIDER_URL = var.GRAFANA_PROVIDER_URL + GRAFANA_ADMIN_USER = var.GRAFANA_ADMIN_USER + GRAFANA_ADMIN_PASSWORD = var.GRAFANA_ADMIN_PASSWORD YTDLP_DASHBOARD_URL = var.YTDLP_DASHBOARD_URL } diff --git a/infra/digitalocean/accounts/naduns-team/variables.tf b/infra/digitalocean/accounts/naduns-team/variables.tf index d8f0d27..4809c28 100644 --- a/infra/digitalocean/accounts/naduns-team/variables.tf +++ b/infra/digitalocean/accounts/naduns-team/variables.tf @@ -214,7 +214,16 @@ variable "GRAFANA_PROVIDER_URL" { description = "Presigned URL to download grafana provider config for dashboard" type = string } +variable "GRAFANA_ADMIN_USER" { + description = "Admin username for Grafana" + type = string +} +variable "GRAFANA_ADMIN_PASSWORD" { + description = "Admin password for Grafana" + type = string + sensitive = true +} variable "YTDLP_DASHBOARD_URL" { description = "Presigned URL to download ytdlp dashboard json" type = string diff --git a/infra/digitalocean/components/locals.tf b/infra/digitalocean/components/locals.tf index 7016f4c..7ee519e 100644 --- a/infra/digitalocean/components/locals.tf +++ b/infra/digitalocean/components/locals.tf @@ -33,6 +33,8 @@ locals { PROMETHEUS_CONFIG_URL = var.PROMETHEUS_CONFIG_URL GRAFANA_DATASOURCE_URL = var.GRAFANA_DATASOURCE_URL GRAFANA_PROVIDER_URL = var.GRAFANA_PROVIDER_URL + GRAFANA_ADMIN_USER = var.GRAFANA_ADMIN_USER + GRAFANA_ADMIN_PASSWORD = var.GRAFANA_ADMIN_PASSWORD YTDLP_DASHBOARD_URL = var.YTDLP_DASHBOARD_URL SSH_ALLOWED_IPS = join(" ", var.SSH_ALLOWED_IPS) PRODUCTION_DOMAIN = join(".", [var.CLOUDFLARE_RECORD_NAME, var.CLOUDFLARE_ZONE_NAME]) diff --git a/infra/digitalocean/components/variables.tf b/infra/digitalocean/components/variables.tf index 1314f6c..806244a 100644 --- a/infra/digitalocean/components/variables.tf +++ b/infra/digitalocean/components/variables.tf @@ -182,6 +182,17 @@ variable "GRAFANA_PROVIDER_URL" { type = string } +variable "GRAFANA_ADMIN_USER" { + description = "Admin username for Grafana" + type = string +} + +variable "GRAFANA_ADMIN_PASSWORD" { + description = "Admin password for Grafana" + type = string + sensitive = true +} + variable "YTDLP_DASHBOARD_URL" { description = "Presigned URL to download ytdlp dashboard json" type = string From 667071c5998ed61dbdff6f1b436589741bf1c001 Mon Sep 17 00:00:00 2001 From: Nadu_Dev Date: Tue, 12 May 2026 08:34:40 +0530 Subject: [PATCH 10/19] feat: update Docker Compose and cloud-init template for improved configuration and security --- docker-compose.dev.yml | 5 +++-- docker-compose.yml | 1 + infra/common/cloud-init.template | 20 ++++++++++++++------ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 77a26d5..7ab5c09 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -2,7 +2,8 @@ name: nadzu-dev services: app: - # Build Docker image from local Dockerfile (make bd) + # run `make bd' to build the local image for development.image name 'nadzu:local`. + # For production deployments, use the published image `ghcr.io/nxdun/nadzu:latest` or an appropriate tagged release. image: nadzu:local environment: APP_HOST: "0.0.0.0" @@ -12,7 +13,7 @@ services: DOWNLOAD_DIR: "/app/downloads" RUST_LOG: "debug" ALLOWED_ORIGINS: "https://nadzu.localhost, http://localhost:8080" - CAPTCHA_SECRET_KEY: "bypass" + CAPTCHA_SECRET_KEY: "nl-ultra-secret-key-dev" MASTER_API_KEY: "${MASTER_API_KEY}" volumes: diff --git a/docker-compose.yml b/docker-compose.yml index 35da45a..8a8fdc1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,3 +1,4 @@ +# Intergrated WARP Proxy. name: nadzu services: warp: diff --git a/infra/common/cloud-init.template b/infra/common/cloud-init.template index 9fa4257..cd3e792 100644 --- a/infra/common/cloud-init.template +++ b/infra/common/cloud-init.template @@ -12,7 +12,7 @@ packages: write_files: - path: /opt/app/docker-compose.yml - permissions: '0644' + permissions: '0600' owner: root:root content: | name: nadzu @@ -237,11 +237,19 @@ write_files: mkdir -p /opt/app/grafana/provisioning/dashboards # Fetch Artifacts from S3 Presigned URLs - curl -sSfL "${CADDY_CUSTOM_BROWSE_FILE_URL}" -o /opt/app/browse.html - curl -sSfL "${PROMETHEUS_CONFIG_URL}" -o /opt/app/prometheus/prometheus.yml - curl -sSfL "${GRAFANA_DATASOURCE_URL}" -o /opt/app/grafana/provisioning/datasources/prometheus.yml - curl -sSfL "${GRAFANA_PROVIDER_URL}" -o /opt/app/grafana/provisioning/dashboards/default.yml - curl -sSfL "${YTDLP_DASHBOARD_URL}" -o /opt/app/grafana/provisioning/dashboards/ytdlp-health.json + fetch_url() { + for i in {1..5}; do + echo "Attempt $i: fetching $2" + if curl -sSfL "$1" -o "$2"; then return 0; fi + sleep $((2**i)) + done + return 1 + } + fetch_url "${CADDY_CUSTOM_BROWSE_FILE_URL}" /opt/app/browse.html + fetch_url "${PROMETHEUS_CONFIG_URL}" /opt/app/prometheus/prometheus.yml + fetch_url "${GRAFANA_DATASOURCE_URL}" /opt/app/grafana/provisioning/datasources/prometheus.yml + fetch_url "${GRAFANA_PROVIDER_URL}" /opt/app/grafana/provisioning/dashboards/default.yml + fetch_url "${YTDLP_DASHBOARD_URL}" /opt/app/grafana/provisioning/dashboards/ytdlp-health.json # Set Permissions chmod 644 /opt/app/browse.html From 1a8eb73124da4f64daa6bd3c82b904554a9e402f Mon Sep 17 00:00:00 2001 From: Nadu_Dev Date: Tue, 12 May 2026 09:58:28 +0530 Subject: [PATCH 11/19] feat: update environment variable for CAPTCHA secret key and modify Prometheus query expressions in Grafana dashboard --- docker-compose.dev.yml | 2 +- infra/common/cloud-init.template | 3 +++ .../provisioning/dashboards/ytdlp-health.json | 4 ++-- .../collections/Nadzu API/Metrics.request.yaml | 16 ++++++++++++++++ 4 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 postman/collections/Nadzu API/Metrics.request.yaml diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 7ab5c09..84cd6ed 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -13,7 +13,7 @@ services: DOWNLOAD_DIR: "/app/downloads" RUST_LOG: "debug" ALLOWED_ORIGINS: "https://nadzu.localhost, http://localhost:8080" - CAPTCHA_SECRET_KEY: "nl-ultra-secret-key-dev" + CAPTCHA_SECRET_KEY: ${CAPTCHA_SECRET_KEY} MASTER_API_KEY: "${MASTER_API_KEY}" volumes: diff --git a/infra/common/cloud-init.template b/infra/common/cloud-init.template index cd3e792..468e723 100644 --- a/infra/common/cloud-init.template +++ b/infra/common/cloud-init.template @@ -251,6 +251,9 @@ write_files: fetch_url "${GRAFANA_PROVIDER_URL}" /opt/app/grafana/provisioning/dashboards/default.yml fetch_url "${YTDLP_DASHBOARD_URL}" /opt/app/grafana/provisioning/dashboards/ytdlp-health.json + # APP_PORT: Dynamic + sed -i "s/app:[0-9]*/app:${APP_PORT}/g" /opt/app/prometheus/prometheus.yml + # Set Permissions chmod 644 /opt/app/browse.html chown root:root /opt/app/browse.html diff --git a/infra/common/grafana/provisioning/dashboards/ytdlp-health.json b/infra/common/grafana/provisioning/dashboards/ytdlp-health.json index 84657b0..d2659e8 100644 --- a/infra/common/grafana/provisioning/dashboards/ytdlp-health.json +++ b/infra/common/grafana/provisioning/dashboards/ytdlp-health.json @@ -117,12 +117,12 @@ "targets": [ { "datasource": "Prometheus", - "expr": "histogram_quantile(0.5, sum by (le) (rate(ytdlp_job_duration_seconds_bucket[5m])))", + "expr": "ytdlp_job_duration_seconds{quantile=\"0.5\"}", "legendFormat": "P50 (Median)" }, { "datasource": "Prometheus", - "expr": "histogram_quantile(0.99, sum by (le) (rate(ytdlp_job_duration_seconds_bucket[5m])))", + "expr": "ytdlp_job_duration_seconds{quantile=\"0.99\"}", "legendFormat": "P99 (Worst Case)" } ], diff --git a/postman/collections/Nadzu API/Metrics.request.yaml b/postman/collections/Nadzu API/Metrics.request.yaml new file mode 100644 index 0000000..acbf4db --- /dev/null +++ b/postman/collections/Nadzu API/Metrics.request.yaml @@ -0,0 +1,16 @@ +$kind: http-request +name: Metrics +url: "{{base_url}}/metrics" +method: GET +scripts: + - type: afterResponse + code: >- + pm.test('Status is 200', function () { pm.response.to.have.status(200); + }); + + pm.test('Response contains Prometheus metrics', function () { + pm.expect(pm.response.text()).to.include('ytdlp_jobs_completed_total'); + }); + + language: text/javascript +order: 3000 From af71da8b4440536079ff21b0a3ec0e64f9e7da48 Mon Sep 17 00:00:00 2001 From: Nadu_Dev Date: Tue, 12 May 2026 10:32:34 +0530 Subject: [PATCH 12/19] feat: update redirection status code in Caddyfile and improve success rate expression in Grafana dashboard --- infra/common/cloud-init.template | 2 +- infra/common/grafana/provisioning/dashboards/ytdlp-health.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/infra/common/cloud-init.template b/infra/common/cloud-init.template index 468e723..7776a99 100644 --- a/infra/common/cloud-init.template +++ b/infra/common/cloud-init.template @@ -91,7 +91,7 @@ write_files: } :80 { - redir https://${PRODUCTION_DOMAIN}{uri} + redir https://${PRODUCTION_DOMAIN}{uri} 308 } ${PRODUCTION_DOMAIN}:443 { diff --git a/infra/common/grafana/provisioning/dashboards/ytdlp-health.json b/infra/common/grafana/provisioning/dashboards/ytdlp-health.json index d2659e8..6b02448 100644 --- a/infra/common/grafana/provisioning/dashboards/ytdlp-health.json +++ b/infra/common/grafana/provisioning/dashboards/ytdlp-health.json @@ -11,7 +11,7 @@ "targets": [ { "datasource": "Prometheus", - "expr": "(sum(rate(ytdlp_jobs_completed_total{status=\"success\"}[5m])) / sum(rate(ytdlp_jobs_completed_total[5m]))) * 100", + "expr": "(((sum(ytdlp_jobs_completed_total{status=\"success\"}) or vector(0)) / (sum(ytdlp_jobs_completed_total) > 0)) * 100) or vector(100)", "legendFormat": "Success Rate" } ], From b4791c68a4e089050d4db24d6b9d2f2a3b238620 Mon Sep 17 00:00:00 2001 From: Nadu_Dev Date: Tue, 12 May 2026 13:36:25 +0530 Subject: [PATCH 13/19] feat: enhance telemetry with structured logging and metrics tracking for CAPTCHA verification --- src/app.rs | 1 + src/middleware/captcha.rs | 44 ++++++++++++++++++++++++++++++++------- src/telemetry.rs | 1 + 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/app.rs b/src/app.rs index c29b3ee..eae03b1 100644 --- a/src/app.rs +++ b/src/app.rs @@ -24,6 +24,7 @@ pub async fn run() { // 2. Initialize structured logging and environment-based log filtering telemetry::init_tracing(); + tracing::info!("nadzu app::run() starting"); // 3. Configure request/response tracing middleware let trace_layer = telemetry::build_trace_layer(); diff --git a/src/middleware/captcha.rs b/src/middleware/captcha.rs index 3663ae7..51c4808 100644 --- a/src/middleware/captcha.rs +++ b/src/middleware/captcha.rs @@ -4,7 +4,9 @@ use axum::{ middleware::Next, response::Response, }; +use metrics::{counter, histogram}; use serde::Deserialize; +use std::time::Instant; use crate::{ error::AppError, @@ -43,6 +45,7 @@ pub async fn verify_captcha_token( if has_valid_master_api_key(req.headers(), state.config.as_ref()) { tracing::debug!("Bypassing captcha check due to valid x-api-key"); + counter!("captcha_check_total", "status" => "bypass").increment(1); return Ok(next.run(req).await); } @@ -51,36 +54,61 @@ pub async fn verify_captcha_token( .get(HEADER_CAPTCHA_NAME) .and_then(|value| value.to_str().ok()) .map(str::trim) - .filter(|value| !value.is_empty()) - .ok_or_else(|| AppError::Validation("x-captcha-token header is required".to_string()))?; + .filter(|value| !value.is_empty()); + + if captcha_token.is_none() { + counter!("captcha_check_total", "status" => "failure", "reason" => "missing").increment(1); + return Err(AppError::Validation( + "x-captcha-token header is required".to_string(), + )); + } + let captcha_token = captcha_token.unwrap_or_default(); let secret_key = state .config .captcha_secret_key() - .filter(|s| !s.trim().is_empty()) - .ok_or_else(|| { - AppError::ServiceUnavailable("CAPTCHA_SECRET_KEY is not configured".to_string()) - })?; + .filter(|s| !s.trim().is_empty()); + + if secret_key.is_none() { + counter!("captcha_check_total", "status" => "error", "reason" => "config").increment(1); + return Err(AppError::ServiceUnavailable( + "CAPTCHA_SECRET_KEY is not configured".to_string(), + )); + } + let secret_key = secret_key.unwrap_or_default(); + let start = Instant::now(); let response = state .http_client .post("https://www.google.com/recaptcha/api/siteverify") .timeout(std::time::Duration::from_secs(CAPTCHA_VERIFY_TIMEOUT_SECS)) .form(&[("secret", secret_key), ("response", captcha_token)]) .send() - .await - .map_err(|err| AppError::UpstreamError(format!("Failed to verify captcha: {err}")))?; + .await; + + let latency = start.elapsed(); + histogram!("captcha_verify_duration_seconds").record(latency.as_secs_f64()); + + let response = response.map_err(|err| { + counter!("captcha_check_total", "status" => "failure", "reason" => "upstream_error") + .increment(1); + AppError::UpstreamError(format!("Failed to verify captcha: {err}")) + })?; let body = response .json::() .await .map_err(|err| { + counter!("captcha_check_total", "status" => "failure", "reason" => "parse_error") + .increment(1); AppError::UpstreamError(format!("Failed to parse captcha response: {err}")) })?; if !body.success { + counter!("captcha_check_total", "status" => "failure", "reason" => "invalid").increment(1); return Err(AppError::Validation("Invalid captcha token".to_string())); } + counter!("captcha_check_total", "status" => "success").increment(1); Ok(next.run(req).await) } diff --git a/src/telemetry.rs b/src/telemetry.rs index 67afdbc..d4349f8 100644 --- a/src/telemetry.rs +++ b/src/telemetry.rs @@ -43,6 +43,7 @@ pub fn build_trace_layer() -> AppTraceLayer { /// Initializes the Prometheus metrics registry and returns the router. pub fn setup_metrics_router() -> Router { + tracing::info!("Initializing Prometheus metrics recorder"); let builder = PrometheusBuilder::new(); // Install the global recorder From 55ee69ca4c61cfb6bb969dbe15458aea7e374a1d Mon Sep 17 00:00:00 2001 From: Nadu_Dev Date: Tue, 12 May 2026 13:36:35 +0530 Subject: [PATCH 14/19] docs: update 'Things I Learned' section in README.md for clarity and additional insights --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ff321be..520ada6 100644 --- a/README.md +++ b/README.md @@ -488,11 +488,12 @@ infra/ ## Things I Learned -- Rust: The initial learning curve is steep, but the long-term benefits in performance, safety, and low-level control are worth it. -- Terraform: cloud-init is excellent for bootstrapping a server, but it has provider-specific size limits. -- Terraform: The Cloudflare provider only supports R2 buckets; use the AWS Terraform provider for object uploads to R2. +- Rust: The initial learning curve is steep, but the long-term benefits in performance, safety, and low-level control are worth it. Very low RAM usage. +- Terraform: cloud-init is excellent for bootstrapping a server, but it has provider-specific size limits. +- Terraform: The Cloudflare provider only supports R2 buckets; use the AWS Terraform provider for object uploads to R2. +- Terraform: It's challenging to maintain local > S3 > cloud-init userdata passing when the project becomes complex over time. -## Acknowledgements +## Acknowledgements * [**yt-dlp**][yt-dlp-repo] From a1c5b46ee64404719137baecfe01acbf65db027f Mon Sep 17 00:00:00 2001 From: Nadu_Dev Date: Tue, 12 May 2026 13:36:48 +0530 Subject: [PATCH 15/19] feat: add CAPTCHA security dashboard URL variable for improved telemetry --- infra/digitalocean/accounts/naduns-team/main.tf | 1 + infra/digitalocean/accounts/naduns-team/variables.tf | 5 +++++ infra/digitalocean/components/locals.tf | 1 + infra/digitalocean/components/variables.tf | 5 +++++ 4 files changed, 12 insertions(+) diff --git a/infra/digitalocean/accounts/naduns-team/main.tf b/infra/digitalocean/accounts/naduns-team/main.tf index bb4c353..3e2e489 100644 --- a/infra/digitalocean/accounts/naduns-team/main.tf +++ b/infra/digitalocean/accounts/naduns-team/main.tf @@ -38,4 +38,5 @@ module "digitalocean_stack" { GRAFANA_ADMIN_USER = var.GRAFANA_ADMIN_USER GRAFANA_ADMIN_PASSWORD = var.GRAFANA_ADMIN_PASSWORD YTDLP_DASHBOARD_URL = var.YTDLP_DASHBOARD_URL + CAPTCHA_SECURITY_DASHBOARD_URL = var.CAPTCHA_SECURITY_DASHBOARD_URL } diff --git a/infra/digitalocean/accounts/naduns-team/variables.tf b/infra/digitalocean/accounts/naduns-team/variables.tf index 4809c28..d98cb1a 100644 --- a/infra/digitalocean/accounts/naduns-team/variables.tf +++ b/infra/digitalocean/accounts/naduns-team/variables.tf @@ -229,6 +229,11 @@ variable "YTDLP_DASHBOARD_URL" { type = string } +variable "CAPTCHA_SECURITY_DASHBOARD_URL" { + description = "Presigned URL to download captcha security dashboard json" + type = string +} + variable "CADDY_CUSTOM_BROWSE_FILE_URL" { //VAR: Declare on terraform.tfvars description = "Presigned URL to download custom browse.html" diff --git a/infra/digitalocean/components/locals.tf b/infra/digitalocean/components/locals.tf index 7ee519e..bf9682b 100644 --- a/infra/digitalocean/components/locals.tf +++ b/infra/digitalocean/components/locals.tf @@ -36,6 +36,7 @@ locals { GRAFANA_ADMIN_USER = var.GRAFANA_ADMIN_USER GRAFANA_ADMIN_PASSWORD = var.GRAFANA_ADMIN_PASSWORD YTDLP_DASHBOARD_URL = var.YTDLP_DASHBOARD_URL + CAPTCHA_SECURITY_DASHBOARD_URL = var.CAPTCHA_SECURITY_DASHBOARD_URL SSH_ALLOWED_IPS = join(" ", var.SSH_ALLOWED_IPS) PRODUCTION_DOMAIN = join(".", [var.CLOUDFLARE_RECORD_NAME, var.CLOUDFLARE_ZONE_NAME]) CADDY_CLOUDFLARE_TRUSTED_PROXIES = join(" ", concat( diff --git a/infra/digitalocean/components/variables.tf b/infra/digitalocean/components/variables.tf index 806244a..36eac4d 100644 --- a/infra/digitalocean/components/variables.tf +++ b/infra/digitalocean/components/variables.tf @@ -197,3 +197,8 @@ variable "YTDLP_DASHBOARD_URL" { description = "Presigned URL to download ytdlp dashboard json" type = string } + +variable "CAPTCHA_SECURITY_DASHBOARD_URL" { + description = "Presigned URL to download captcha security dashboard json" + type = string +} From 55cb301bf30109a6097f503a38e4588b09c590d2 Mon Sep 17 00:00:00 2001 From: Nadu_Dev Date: Tue, 12 May 2026 13:37:00 +0530 Subject: [PATCH 16/19] feat: add HTTPS redirection for nadzu.localhost and update Grafana anonymous access settings --- Caddyfile.local | 5 +++++ Dockerfile.dev | 1 + docker-compose.dev.yml | 6 +----- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Caddyfile.local b/Caddyfile.local index 0573bf5..ddb07d0 100644 --- a/Caddyfile.local +++ b/Caddyfile.local @@ -9,6 +9,11 @@ http://:8080 { reverse_proxy app:8080 } +# dev only: Redirect plain HTTP for nadzu.localhost to HTTPS permanently +http://nadzu.localhost { + redir https://{host}{uri} 301 +} + # HTTPS Protocol - Full Stack Access nadzu.localhost { tls internal diff --git a/Dockerfile.dev b/Dockerfile.dev index d881b39..88c335f 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -7,6 +7,7 @@ RUN apk add --no-cache \ build-base \ musl-dev \ zstd-dev \ + openssl-dev \ pkgconfig \ ca-certificates \ curl \ diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 84cd6ed..c8a4be6 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -17,9 +17,6 @@ services: MASTER_API_KEY: "${MASTER_API_KEY}" volumes: - - ./:/app - - app-target:/app/target_docker - - app-cargo:/usr/local/cargo/registry - ./downloads:/app/downloads caddy: @@ -49,8 +46,7 @@ services: image: grafana/grafana:latest container_name: nadzu-grafana environment: - - GF_AUTH_ANONYMOUS_ENABLED=true - - GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer + - GF_AUTH_ANONYMOUS_ENABLED=false - GF_SECURITY_ADMIN_USER=admin - GF_SECURITY_ADMIN_PASSWORD=admin - GF_USERS_ALLOW_SIGN_UP=false From 36afb346e163a3ab4305d70d08d9c76cdf7ff30d Mon Sep 17 00:00:00 2001 From: Nadu_Dev Date: Tue, 12 May 2026 13:37:41 +0530 Subject: [PATCH 17/19] feat: add captcha security dashboard and update related configurations --- .gemini/settings.json | 14 ++ Makefile | 4 +- infra/common/cloud-init.template | 1 + .../dashboards/captcha-security.json | 172 ++++++++++++++++++ .../Nadzu API/YT-DLP Enqueue.request.yaml | 18 +- .../environments/Nadzu Local.environment.yaml | 8 +- 6 files changed, 204 insertions(+), 13 deletions(-) create mode 100644 .gemini/settings.json create mode 100644 infra/common/grafana/provisioning/dashboards/captcha-security.json diff --git a/.gemini/settings.json b/.gemini/settings.json new file mode 100644 index 0000000..876afb0 --- /dev/null +++ b/.gemini/settings.json @@ -0,0 +1,14 @@ +{ + "mcpServers": { + "grafana": { + "command": "uvx", + "args": [ + "mcp-grafana" + ], + "env": { + "GRAFANA_URL": "http://localhost:3000/", + "GRAFANA_SERVICE_ACCOUNT_TOKEN": "" + } + } + } +} \ No newline at end of file diff --git a/Makefile b/Makefile index bfbae08..4a0ec13 100644 --- a/Makefile +++ b/Makefile @@ -188,7 +188,9 @@ tf: ## use this to spawn a loaded shell aws s3 cp infra/common/grafana/provisioning/dashboards/default.yml \"s3://\$$AWS_S3_BUCKET_NAME/terraform/data/grafana_provider.yml\" --endpoint-url \"\$$AWS_ENDPOINT_URL_S3\" && \ export TF_VAR_GRAFANA_PROVIDER_URL=\$$(aws s3 presign \"s3://\$$AWS_S3_BUCKET_NAME/terraform/data/grafana_provider.yml\" --endpoint-url \"\$$AWS_ENDPOINT_URL_S3\" --expires-in 3600 | tr -d '\r') && \ aws s3 cp infra/common/grafana/provisioning/dashboards/ytdlp-health.json \"s3://\$$AWS_S3_BUCKET_NAME/terraform/data/ytdlp-health.json\" --endpoint-url \"\$$AWS_ENDPOINT_URL_S3\" && \ - export TF_VAR_YTDLP_DASHBOARD_URL=\$$(aws s3 presign \"s3://\$$AWS_S3_BUCKET_NAME/terraform/data/ytdlp-health.json\" --endpoint-url \"\$$AWS_ENDPOINT_URL_S3\" --expires-in 3600 | tr -d '\r') && \ + export TF_VAR_YTDLP_DASHBOARD_URL=\$$(aws s3 presign \"s3://\$$AWS_S3_BUCKET_NAME/terraform/data/ytdlp-health.json\" --endpoint-url \"\$$AWS_ENDPOINT_URL_S3\" --expires-in 3600 | tr -d '\\r') && \ + aws s3 cp infra/common/grafana/provisioning/dashboards/captcha-security.json \"s3://\$$AWS_S3_BUCKET_NAME/terraform/data/captcha-security.json\" --endpoint-url \"\$$AWS_ENDPOINT_URL_S3\" && \ + export TF_VAR_CAPTCHA_SECURITY_DASHBOARD_URL=\$$(aws s3 presign \"s3://\$$AWS_S3_BUCKET_NAME/terraform/data/captcha-security.json\" --endpoint-url \"\$$AWS_ENDPOINT_URL_S3\" --expires-in 3600 | tr -d '\\r') && \ export MSYS_NO_PATHCONV=1 && \ cd $(TF_STACK_DIR) && \ unset PROMPT_COMMAND && \ diff --git a/infra/common/cloud-init.template b/infra/common/cloud-init.template index 7776a99..8bc9341 100644 --- a/infra/common/cloud-init.template +++ b/infra/common/cloud-init.template @@ -250,6 +250,7 @@ write_files: fetch_url "${GRAFANA_DATASOURCE_URL}" /opt/app/grafana/provisioning/datasources/prometheus.yml fetch_url "${GRAFANA_PROVIDER_URL}" /opt/app/grafana/provisioning/dashboards/default.yml fetch_url "${YTDLP_DASHBOARD_URL}" /opt/app/grafana/provisioning/dashboards/ytdlp-health.json + fetch_url "${CAPTCHA_SECURITY_DASHBOARD_URL}" /opt/app/grafana/provisioning/dashboards/captcha-security.json # APP_PORT: Dynamic sed -i "s/app:[0-9]*/app:${APP_PORT}/g" /opt/app/prometheus/prometheus.yml diff --git a/infra/common/grafana/provisioning/dashboards/captcha-security.json b/infra/common/grafana/provisioning/dashboards/captcha-security.json new file mode 100644 index 0000000..2beec11 --- /dev/null +++ b/infra/common/grafana/provisioning/dashboards/captcha-security.json @@ -0,0 +1,172 @@ +{ + "title": "Captcha & Security Dashboard", + "description": "Monitoring for Captcha verification hits, errors, and API key bypasses.", + "panels": [ + { + "title": "Total Captcha Requests (5m)", + "type": "stat", + "gridPos": { "h": 4, "w": 6, "x": 0, "y": 0 }, + "datasource": "Prometheus", + "targets": [ + { + "expr": "sum(increase(captcha_check_total[5m]))", + "instant": true, + "refId": "A" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "reduceOptions": { "calcs": ["lastNotNull"] } + }, + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] } + } + } + }, + { + "title": "Success Rate (%)", + "type": "stat", + "gridPos": { "h": 4, "w": 6, "x": 6, "y": 0 }, + "datasource": "Prometheus", + "targets": [ + { + "expr": "sum(increase(captcha_check_total{status=\"success\"}[5m])) / sum(increase(captcha_check_total[5m])) * 100", + "instant": true, + "refId": "A" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "reduceOptions": { "calcs": ["lastNotNull"] } + }, + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] } + } + } + }, + { + "title": "Bypass Rate (%)", + "type": "stat", + "gridPos": { "h": 4, "w": 6, "x": 12, "y": 0 }, + "datasource": "Prometheus", + "targets": [ + { + "expr": "sum(increase(captcha_check_total{status=\"bypass\"}[5m])) / sum(increase(captcha_check_total[5m])) * 100", + "instant": true, + "refId": "A" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "reduceOptions": { "calcs": ["lastNotNull"] } + }, + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] } + } + } + }, + { + "title": "Failure Rate (%)", + "type": "stat", + "gridPos": { "h": 4, "w": 6, "x": 18, "y": 0 }, + "datasource": "Prometheus", + "targets": [ + { + "expr": "sum(increase(captcha_check_total{status=\"failure\"}[5m])) / sum(increase(captcha_check_total[5m])) * 100", + "instant": true, + "refId": "A" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "reduceOptions": { "calcs": ["lastNotNull"] } + }, + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "thresholds": { + "mode": "absolute", + "steps": [{ "color": "green", "value": null }, { "color": "red", "value": 1 }] + } + } + } + }, + { + "title": "Captcha Traffic Status", + "type": "timeseries", + "gridPos": { "h": 8, "w": 12, "x": 0, "y": 4 }, + "datasource": "Prometheus", + "targets": [ + { + "expr": "sum by (status) (rate(captcha_check_total[1m]))", + "legendFormat": "{{status}}", + "refId": "A" + } + ], + "options": { + "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, + "tooltip": { "mode": "multi", "sort": "none" } + } + }, + { + "title": "Failure Reasons (1h)", + "type": "piechart", + "gridPos": { "h": 8, "w": 12, "x": 12, "y": 4 }, + "datasource": "Prometheus", + "targets": [ + { + "expr": "sum by (reason) (increase(captcha_check_total{status=\"failure\"}[1h]))", + "legendFormat": "{{reason}}", + "refId": "A" + } + ], + "options": { + "displayLabels": ["percent"], + "legend": { "displayMode": "list", "placement": "right", "showLegend": true }, + "pieType": "donut", + "tooltip": { "mode": "single", "sort": "none" } + } + }, + { + "title": "Verification Latency (seconds)", + "type": "timeseries", + "gridPos": { "h": 8, "w": 24, "x": 0, "y": 12 }, + "datasource": "Prometheus", + "targets": [ + { + "expr": "histogram_quantile(0.99, sum by (le) (rate(captcha_verify_duration_seconds_bucket[5m])))", + "legendFormat": "P99 Latency", + "refId": "A" + }, + { + "expr": "histogram_quantile(0.95, sum by (le) (rate(captcha_verify_duration_seconds_bucket[5m])))", + "legendFormat": "P95 Latency", + "refId": "B" + }, + { + "expr": "sum(rate(captcha_verify_duration_seconds_sum[5m])) / sum(rate(captcha_verify_duration_seconds_count[5m]))", + "legendFormat": "Avg Latency", + "refId": "C" + } + ], + "options": { + "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, + "tooltip": { "mode": "multi", "sort": "none" } + } + } + ] +} diff --git a/postman/collections/Nadzu API/YT-DLP Enqueue.request.yaml b/postman/collections/Nadzu API/YT-DLP Enqueue.request.yaml index 40569cc..167e2fc 100644 --- a/postman/collections/Nadzu API/YT-DLP Enqueue.request.yaml +++ b/postman/collections/Nadzu API/YT-DLP Enqueue.request.yaml @@ -3,9 +3,13 @@ name: YT-DLP Enqueue url: "{{base_url}}/api/v1/ytdlp" method: POST headers: - Content-Type: application/json - x-captcha-token: "{{captcha_token}}" - x-api-key: "{{api_key}}" + - key: Content-Type + value: application/json + - key: x-captcha-token + value: "{{captcha_token}}" + disabled: true + - key: x-api-key + value: "{{api_key}}" body: type: json content: |- @@ -17,17 +21,13 @@ body: scripts: - type: afterResponse - code: >- - pm.test('Status is 202', function () { pm.response.to.have.status(202); - }); - + code: |- + pm.test('Status is 202', function () { pm.response.to.have.status(202); }); var jsonData = pm.response.json(); - pm.test('Response has job id', function () { pm.expect(jsonData).to.have.property('job'); pm.expect(jsonData.job).to.have.property('id'); }); - pm.collectionVariables.set('latest_job_id', jsonData.job.id); language: text/javascript order: 4000 diff --git a/postman/environments/Nadzu Local.environment.yaml b/postman/environments/Nadzu Local.environment.yaml index 144089b..4ce857e 100644 --- a/postman/environments/Nadzu Local.environment.yaml +++ b/postman/environments/Nadzu Local.environment.yaml @@ -2,8 +2,10 @@ name: Nadzu Local values: - key: base_url value: 'http://127.0.0.1:8080' - enabled: true - key: base_url_tls value: 'https://nadzu.localhost' - enabled: true -color: 0 + - key: api_key + value: '' + - key: video_url + value: '' +color: 120 From bdcb37f317ea3d654f8fe69af35891d9fb291f67 Mon Sep 17 00:00:00 2001 From: Nadu_Dev Date: Tue, 12 May 2026 15:21:07 +0530 Subject: [PATCH 18/19] fix: correct typo in WARP Proxy description in docker-compose.yml --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 8a8fdc1..b72f6da 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -# Intergrated WARP Proxy. +# Integrated WARP Proxy. name: nadzu services: warp: From 538fdd8c17779be24c852bebcbd0c635529c2709 Mon Sep 17 00:00:00 2001 From: Nadu_Dev Date: Tue, 12 May 2026 15:21:50 +0530 Subject: [PATCH 19/19] feat: update nadzu version to 0.4.0 in Cargo.toml and Cargo.lock --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 30f9cb3..672508e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1152,7 +1152,7 @@ dependencies = [ [[package]] name = "nadzu" -version = "0.3.1" +version = "0.4.0" dependencies = [ "anyhow", "axum", diff --git a/Cargo.toml b/Cargo.toml index 7a596f6..696d27a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nadzu" -version = "0.3.1" +version = "0.4.0" edition = "2024" authors = ["Nadun "] description = "A high-performance YouTube downloader backend built with Axum and Rust."