diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..c7b955639c9978e32b3078072a2f1ddf4333aa06 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +.vscode/ +/healer/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000000000000000000000000000000000000..6c4a3c4067853189157c8176f4bd156f3ca01b5e --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2786 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.59.0", +] + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aya" +version = "0.13.1" +source = "git+https://github.com/aya-rs/aya#ab182be622acb245db0adef58591978208bcdb2c" +dependencies = [ + "assert_matches", + "aya-obj", + "bitflags", + "bytes", + "hashbrown", + "libc", + "log", + "object 0.37.1", + "once_cell", + "thiserror 2.0.12", + "tokio", +] + +[[package]] +name = "aya-build" +version = "0.1.2" +source = "git+https://github.com/aya-rs/aya#ab182be622acb245db0adef58591978208bcdb2c" +dependencies = [ + "anyhow", + "cargo_metadata", +] + +[[package]] +name = "aya-ebpf" +version = "0.1.1" +source = "git+https://github.com/aya-rs/aya#ab182be622acb245db0adef58591978208bcdb2c" +dependencies = [ + "aya-ebpf-bindings", + "aya-ebpf-cty", + "aya-ebpf-macros", + "rustversion", +] + +[[package]] +name = "aya-ebpf-bindings" +version = "0.1.1" +source = "git+https://github.com/aya-rs/aya#ab182be622acb245db0adef58591978208bcdb2c" +dependencies = [ + "aya-ebpf-cty", +] + +[[package]] +name = "aya-ebpf-cty" +version = "0.2.2" +source = "git+https://github.com/aya-rs/aya#ab182be622acb245db0adef58591978208bcdb2c" + +[[package]] +name = "aya-ebpf-macros" +version = "0.1.1" +source = "git+https://github.com/aya-rs/aya#ab182be622acb245db0adef58591978208bcdb2c" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn", +] + +[[package]] +name = "aya-log" +version = "0.2.1" +source = "git+https://github.com/aya-rs/aya#ab182be622acb245db0adef58591978208bcdb2c" +dependencies = [ + "aya", + "aya-log-common", + "log", + "thiserror 2.0.12", +] + +[[package]] +name = "aya-log-common" +version = "0.1.15" +source = "git+https://github.com/aya-rs/aya#ab182be622acb245db0adef58591978208bcdb2c" +dependencies = [ + "num_enum", +] + +[[package]] +name = "aya-log-ebpf" +version = "0.1.1" +source = "git+https://github.com/aya-rs/aya#ab182be622acb245db0adef58591978208bcdb2c" +dependencies = [ + "aya-ebpf", + "aya-log-common", + "aya-log-ebpf-macros", +] + +[[package]] +name = "aya-log-ebpf-macros" +version = "0.1.0" +source = "git+https://github.com/aya-rs/aya#ab182be622acb245db0adef58591978208bcdb2c" +dependencies = [ + "aya-log-common", + "aya-log-parser", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "aya-log-parser" +version = "0.1.13" +source = "git+https://github.com/aya-rs/aya#ab182be622acb245db0adef58591978208bcdb2c" +dependencies = [ + "aya-log-common", +] + +[[package]] +name = "aya-obj" +version = "0.2.1" +source = "git+https://github.com/aya-rs/aya#ab182be622acb245db0adef58591978208bcdb2c" +dependencies = [ + "bytes", + "hashbrown", + "log", + "object 0.37.1", + "thiserror 2.0.12", +] + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object 0.36.7", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "camino" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84982c6c0ae343635a3a4ee6dedef965513735c8b183caa7289fa6e27399ebd4" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-util-schemas" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e63d2780ac94487eb9f1fea7b0d56300abc9eb488800854ca217f102f5caccca" +dependencies = [ + "semver", + "serde", + "serde-untagged", + "serde-value", + "thiserror 1.0.69", + "toml", + "unicode-xid", + "url", +] + +[[package]] +name = "cargo_metadata" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f7835cfc6135093070e95eb2b53e5d9b5c403dc3a6be6040ee026270aa82502" +dependencies = [ + "camino", + "cargo-platform", + "cargo-util-schemas", + "semver", + "serde", + "serde_json", + "thiserror 2.0.12", +] + +[[package]] +name = "cc" +version = "1.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[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.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +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.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "daemonize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8bfdaacb3c887a54d41bdf48d3af8873b3f5566469f8ba21b92057509f116e" +dependencies = [ + "libc", +] + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_home" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" +dependencies = [ + "serde", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "h2" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "healer" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "aya", + "aya-build", + "aya-log", + "bytes", + "chrono", + "daemonize", + "env_logger", + "futures", + "healer-common", + "healer-ebpf", + "nix", + "reqwest", + "serde", + "serde_yaml", + "sysinfo", + "tokio", + "tracing", + "tracing-appender", + "tracing-subscriber", + "users", +] + +[[package]] +name = "healer-common" +version = "0.1.0" +dependencies = [ + "aya", +] + +[[package]] +name = "healer-ebpf" +version = "0.1.0" +dependencies = [ + "aya-ebpf", + "aya-log-ebpf", + "healer-common", + "which", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +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]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "io-uring" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jiff" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +dependencies = [ + "bitflags", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a" +dependencies = [ + "libc", + "objc2-core-foundation", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "object" +version = "0.37.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03fd943161069e1768b4b3d050890ba48730e590f57e56d4aa04e7e090e61b4a" +dependencies = [ + "crc32fast", + "hashbrown", + "indexmap", + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "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.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "redox_syscall" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "reqwest" +version = "0.12.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.23.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "299d9c19d7d466db4ab10addd5703e4c615dec2a5a16dbbafe191045e87ee66e" +dependencies = [ + "erased-serde", + "serde", + "typeid", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "smallvec" +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 = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sysinfo" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aab138f5c1bb35231de19049060a87977ad23e04f2303e953bc5c2947ac7dec4" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "slab", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-appender" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" +dependencies = [ + "crossbeam-channel", + "thiserror 1.0.69", + "time", + "tracing-subscriber", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "time", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "users" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" +dependencies = [ + "libc", + "log", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "which" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d" +dependencies = [ + "env_home", + "rustix", + "winsafe", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +dependencies = [ + "memchr", +] + +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..2adbd8096c112edc6d8bbfbb16a60a2357079fa5 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[workspace] +members = [ + "healer", + "healer-ebpf", + "healer-common", +] +resolver = "2" +default-members = ["healer", "healer-common"] + +[profile.release.package.healer-ebpf] +debug = 2 +codegen-units = 1 \ No newline at end of file diff --git a/README.md b/README.md index 100d35b605c7e7d487382dd610c098f377405d33..f730ba65fd360b31d34ef97a21b8fb72f4d4dc80 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,112 @@ # process-healer - -#### 介绍 +## 介绍 A high-performance daemon leveraging eBPF for reliable, low-overhead monitoring and automatic recovery of critical processes to ensure service continuity. -#### 软件架构 -软件架构说明 +## 软件架构 +Healer 是一个面向关键进程自愈场景的轻量守护进程,当前已实现的核心要点: + +1. 统一事件总线(broadcast)串联 “监控 → 恢复”。 +2. 可插拔监控插件(PID 文件 / 网络探测 / eBPF )。 +3. 熔断与退避的恢复控制(ProcessHealer)。 +4. 守护进程模式运行,带信号管理(SIGHUP 热加载 / SIGTERM 优雅退出)与僵尸进程回收。 + + +``` ++------------------+ +main ------>+ daemonize parent |----> parent退出 / 子进程常驻 + +---------+--------+ + | + v + +-----------------------------+ + | Core Runtime (tokio runtime)| + +-----------------------------+ + init: load config -> init logger -> create event bus + | spawn monitors + | spawn healer subscriber + | enter signal loop (SIGHUP reload / SIGTERM shutdown) +``` + + +## 模块与职责概览 +核心模块按 “监控 → 恢复” 主链路与支撑层次划分(协调层尚未实现,后续加入)。 + +### 事件主链路 +- `monitor/*` (PID / Network / eBPF 退出事件):采集进程或服务健康信号,发布事件(如 `ProcessDown`, `ProcessDisconnected`)到广播通道。 +- `subscriber/process_healer.rs`(ProcessHealer):执行真正的重启 / 恢复动作;实现熔断控制(`retries` / `retry_window_secs` / `cooldown_secs`;状态 Closed → Open → HalfOpen),并输出日志。 + +### 配置与运行时 +- `config.rs` / `config_manager.rs`:加载、验证、热更新(SIGHUP)配置;定义监控与恢复策略结构体。 +- `core_logic.rs`:启动顺序(配置→日志→事件通道→监控→订阅者),托管 tokio runtime 主循环。 +- `service_manager.rs`:统一拉起 Healer 等长期任务(后续可扩展其他订阅者)。 +- `monitor_manager.rs`:按配置集管理 / 重建各监控实例。 +- `daemon_handler.rs`:守护进程化(fork + 父进程退出)。 +- `signal_handler.rs`:处理 `SIGHUP`(重载)、`SIGTERM` / `SIGINT`(优雅退出)、回收僵尸进程。 +- `logger.rs`:初始化 tracing/log 目录与等级(支持配置与 `RUST_LOG` 覆盖)。 +- `event_bus.rs`:定义 `ProcessEvent` 枚举与 broadcast 通道(monitors → healer)。 + +### 监控插件 (Monitors) +- `pid_monitor.rs`:根据 PID 文件轮询存活状态。 +- `network_monitor.rs`:网络连通 / 端口可达检测(输出断连事件)。 +- `ebpf_monitor.rs`:已实现的 eBPF 监控:加载内置 BPF 对象,附加 tracepoint `sched:sched_process_exit`,使用 perf ring buffer 读取 `ProcessExitEvent`,并通过 `PROCESS_NAMES_TO_MONITOR` 映射(截断进程名 -> 配置名)筛选关注的进程,向事件总线发送 `ProcessDown { pid, name }`。 + + +### 工具与辅助 +- `utils.rs`:通用帮助函数(路径、时间、权限等后续可放入)。 +- `tests/integration`:端到端场景验证(计划:依赖阻塞 → 延迟 → 释放;熔断路径;配置热加载)。 + + +### 事件流简述 +Monitors → broadcast → ProcessHealer(执行恢复 / 熔断)。 -#### 安装教程 +(依赖图 / 延迟恢复等“协调”能力尚未实现,未来若加入,将插入在 Monitors 与 Healer 之间。) -1. xxxx -2. xxxx -3. xxxx +--- +以上列表可随实现推进持续更新,保持 “新增模块 = 更新职责表” 的维护约定。 -#### 使用说明 +## 未来计划(未实现) +以下能力仍处于设计阶段,当前仓库不含对应代码,仅作为路线参考: +- 依赖关系自动发现与建模(系统d unit / 命令行启发式 / 配置融合)。 +- 延迟 / 条件性恢复(等待关键依赖 ready 后再放行重启)。 +- eBPF 深度扩展(在现有退出事件基础上继续增加退出原因、异常 syscall / 资源异常 统计等更细粒度信号)。 -1. xxxx -2. xxxx -3. xxxx -#### 参与贡献 -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request +## 配置文件使用指南 +配置文件在根目录下的`config.yaml` +配置文件采用了`serde`库进行解析。支持 PID eBPF Network 三种工作模式,具体的使用实例可以参考yaml中已有的样例。 +```YAML + - name: "simple_counter" #名字 用于日志 + enabled: true #启用开关 + command: "/home/lxq/ospp/simple_test_process/target/debug/simple_test_process" #恢复命令,在无pid文件时作为进程的主键来识别 + args: [] #恢复命令的参数 + run_as_root: false #进程是否已root进行恢复重启 + run_as_user: "lxq" #如果非root,则以某个用户的身份重启 + monitor: + type: "pid" # 使用 PID 文件进行监控 + pid_file_path: "/var/run/healer/simple_counter.pid" # pid监控模式应该有对应的pid文件 + interval_secs: 3 # 轮询间隔,单位秒 + # 恢复/重启策略配置 + recovery: + type: "regular" # 恢复策略,目前只有regular,regular默认实现了熔断,后续可以考虑分为两种恢复模式 + retries: 3 # 60秒内最多重试3次 + retry_window_secs: 60 + cooldown_secs: 180 # 如果发生熔断,冷却3分钟(180秒) +``` +配置文件支持热加载,可以给守护进程发送信号sigup来实现更新。 -#### 特技 +## 编译使用 +``` +RUST_LOG=info cargo run --config 'target."cfg(all())".runner="sudo -E"' +``` +用非root权限进行cargo build,并以root权限执行二进制 -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) +日志的位置可以由用户自己在 `config.yaml`中定义: +```YAML +# 全局配置 +log_level: "info" #日志输出等级,可以调整为debug/tracing发现更多信息,不过会被RUST_LOG环境变量覆盖 +log_directory: "/var/log/healer" #日志文件地址,本地址需要root权限,用户可以放在自己定义的位置下。 +pid_file_directory: "/var/run/healer" # healer 守护进程自己的 PID 文件目录,用户可以放在自己定义的位置下。 +working_directory: "/" #工作目录,默认是根目录 +``` diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f3c93986a0fe464071784563d9370c0d63e700ba --- /dev/null +++ b/config.yaml @@ -0,0 +1,61 @@ +# healer/config.yaml + +# 全局配置 +log_level: "info" +log_directory: "/var/log/healer" +pid_file_directory: "/var/run/healer" # healer 守护进程自己的 PID 文件目录 +working_directory: "/" + +# 需要监控和自愈的进程列表 +processes: + + # - name: "simple_counter" + # enabled: true + # command: "/home/lxq/ospp/simple_test_process/target/debug/simple_test_process" + # args: [] + # run_as_root: false + # run_as_user: "lxq" + # monitor: + # type: "pid" # 使用 PID 文件进行监控 + + # pid_file_path: "/var/run/healer/simple_counter.pid" + + # interval_secs: 3 + + # # 恢复/重启策略配置 + # recovery: + # type: "regular" + # retries: 3 # 60秒内最多重试3次 + # retry_window_secs: 60 + # cooldown_secs: 180 # 如果发生熔断,冷却3分钟 + + # - name: "simple_test_process" + # enabled: true + # command: "/home/lxq/ospp/simple_test_process/target/debug/simple_test_process" + # args: [] + # run_as_root: false + # run_as_user: "lxq" + # monitor: + # type: "ebpf" + # recovery: + # type: "regular" + # retries: 3 # 60秒内最多重试3次 + # retry_window_secs: 60 + # cooldown_secs: 180 # 如果发生熔断,冷却3分钟 + + + - name: "simple_net_process" + enabled: true + command: "/home/lxq/anaconda3/bin/python" + args: ["/home/lxq/ospp/net-simple-test/main.py"] + run_as_root: false + run_as_user: "lxq" + monitor: + type: "network" + target_url: "http://127.0.0.1:8080/health" + interval_secs: 5 + recovery: + type: "regular" + retries: 3 # 60秒内最多重试3次 + retry_window_secs: 60 + cooldown_secs: 180 # 如果发生熔断,冷却3分钟 \ No newline at end of file diff --git a/healer-common/.gitignore b/healer-common/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..ea8c4bf7f35f6f77f75d92ad8ce8349f6e81ddba --- /dev/null +++ b/healer-common/.gitignore @@ -0,0 +1 @@ +/target diff --git a/healer-common/Cargo.toml b/healer-common/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..bb97b1c037e6a7182cedf752663fe204b4200daf --- /dev/null +++ b/healer-common/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "healer-common" +version = "0.1.0" +edition = "2024" + +[features] +default = [] +user = ["aya"] + +[dependencies] +aya = { git = "https://github.com/aya-rs/aya", optional = true } + +[lib] +path = "src/lib.rs" diff --git a/healer-common/src/lib.rs b/healer-common/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..758d9ad378920021f4218f05bdebc9e6315130c0 --- /dev/null +++ b/healer-common/src/lib.rs @@ -0,0 +1,11 @@ +#![no_std] + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct ProcessExitEvent { + pub pid: u32, + pub comm: [u8; 16], // 内核进程名,最多16字节(包括null终止符) +} + +#[cfg(feature = "user")] +unsafe impl aya::Pod for ProcessExitEvent {} diff --git a/healer-ebpf/.gitignore b/healer-ebpf/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..ea8c4bf7f35f6f77f75d92ad8ce8349f6e81ddba --- /dev/null +++ b/healer-ebpf/.gitignore @@ -0,0 +1 @@ +/target diff --git a/healer-ebpf/Cargo.toml b/healer-ebpf/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..498ba1c836a39697f9dde6b91907f073191926e8 --- /dev/null +++ b/healer-ebpf/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "healer-ebpf" +version = "0.1.0" +edition = "2024" + +[dependencies] +aya-ebpf = { git = "https://github.com/aya-rs/aya" } +aya-log-ebpf = { git = "https://github.com/aya-rs/aya" } +healer-common = { path = "../healer-common" } + +[build-dependencies] +which = { version = "8.0.0", default-features = false, features = ["real-sys"] } + + +[[bin]] +name = "healer" +path = "src/main.rs" \ No newline at end of file diff --git a/healer-ebpf/build.rs b/healer-ebpf/build.rs new file mode 100644 index 0000000000000000000000000000000000000000..f83c317a1fb277f5a322fd2cb7372c4302ad443e --- /dev/null +++ b/healer-ebpf/build.rs @@ -0,0 +1,17 @@ +use which::which; + +/// Building this crate has an undeclared dependency on the `bpf-linker` binary. This would be +/// better expressed by [artifact-dependencies][bindeps] but issues such as +/// https://github.com/rust-lang/cargo/issues/12385 make their use impractical for the time being. +/// +/// This file implements an imperfect solution: it causes cargo to rebuild the crate whenever the +/// mtime of `which bpf-linker` changes. Note that possibility that a new bpf-linker is added to +/// $PATH ahead of the one used as the cache key still exists. Solving this in the general case +/// would require rebuild-if-changed-env=PATH *and* rebuild-if-changed={every-directory-in-PATH} +/// which would likely mean far too much cache invalidation. +/// +/// [bindeps]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html?highlight=feature#artifact-dependencies +fn main() { + let bpf_linker = which("bpf-linker").unwrap(); + println!("cargo:rerun-if-changed={}", bpf_linker.to_str().unwrap()); +} diff --git a/healer-ebpf/src/main.rs b/healer-ebpf/src/main.rs new file mode 100644 index 0000000000000000000000000000000000000000..ef06ee7e9452b1b48127f270224b7a513d223dcc --- /dev/null +++ b/healer-ebpf/src/main.rs @@ -0,0 +1,57 @@ +#![no_std] +#![no_main] + +use aya_ebpf::{ + EbpfContext, + macros::{map, tracepoint}, + maps::{HashMap, PerfEventArray}, + programs::TracePointContext, +}; +use aya_log_ebpf::info; +use healer_common::ProcessExitEvent; + +// 存储要监控的进程名(截断到15个字符) +#[map] +static PROCESS_NAMES_TO_MONITOR: HashMap<[u8; 16], u8> = HashMap::with_max_entries(1024, 0); + +#[map] +static EVENTS: PerfEventArray = PerfEventArray::new(0); + +#[tracepoint] +pub fn healer_exit(ctx: TracePointContext) -> u32 { + match try_healer_exit(ctx) { + Ok(ret) => ret, + Err(ret) => ret, + } +} + +fn try_healer_exit(ctx: TracePointContext) -> Result { + let pid = ctx.pid(); + let tgid = ctx.tgid(); + + if pid != tgid { + return Ok(0); + } + + let comm = match aya_ebpf::helpers::bpf_get_current_comm() { + Ok(comm_array) => comm_array, + Err(_) => return Ok(0), + }; + + // 检查这个进程名是否在监控列表中 + if unsafe { PROCESS_NAMES_TO_MONITOR.get(&comm) }.is_some() { + info!(&ctx, "Monitored process detected"); + + // 发送包含进程名的事件 + let event = ProcessExitEvent { pid, comm }; + EVENTS.output(&ctx, &event, 0); + } + + Ok(0) +} + +#[cfg(not(test))] +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} diff --git a/healer/Cargo.lock b/healer/Cargo.lock new file mode 100644 index 0000000000000000000000000000000000000000..3c203c0eaedb6821a3387001022513834ce3b624 --- /dev/null +++ b/healer/Cargo.lock @@ -0,0 +1,1021 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[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.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "daemonize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8bfdaacb3c887a54d41bdf48d3af8873b3f5566469f8ba21b92057509f116e" +dependencies = [ + "libc", +] + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "hashbrown" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" + +[[package]] +name = "healer" +version = "0.1.0" +dependencies = [ + "async-trait", + "chrono", + "daemonize", + "nix", + "serde", + "serde_yaml", + "tokio", + "tracing", + "tracing-appender", + "tracing-subscriber", + "users", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +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]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "socket2" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tokio" +version = "1.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-appender" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" +dependencies = [ + "crossbeam-channel", + "thiserror", + "time", + "tracing-subscriber", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "time", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "users" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" +dependencies = [ + "libc", + "log", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46ec44dc15085cea82cf9c78f85a9114c463a369786585ad2882d1ff0b0acf40" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-result" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b895b5356fc36103d0f64dd1e94dfa7ac5633f1c9dd6e80fe9ec4adef69e09d" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a7ab927b2637c19b3dbe0965e75d8f2d30bdd697a1516191cad2ec4df8fb28a" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/healer/Cargo.toml b/healer/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..1798ead0a4fb4dc71ac3afcdee69b917bd7d7d73 --- /dev/null +++ b/healer/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "healer" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] +aya = { git = "https://github.com/aya-rs/aya", features = ["async_tokio"] } +aya-log = { git = "https://github.com/aya-rs/aya" } +healer-common = {path = "../healer-common", features = ["user"] } +anyhow = "1" +tokio = { version = "1", features = ["full"] } +daemonize = "0.5" +reqwest = "0.12" +serde = { version = "1.0", features = ["derive"] } +serde_yaml = "0.9" +chrono = "0.4" +tracing = "0.1" +async-trait = "0.1" +tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter", "json", "time"] } +tracing-appender = "0.2" +nix = { version = "0.29.0", features = ["signal", "process"] } +users = "0.11.0" +bytes = "1.10.1" +futures = "0.3.31" +sysinfo = "0.36.0" +env_logger = "0.11.8" + + +[build-dependencies] +aya-build = { git = "https://github.com/aya-rs/aya" } +anyhow = "1" +healer-ebpf = { path = "../healer-ebpf" } + +[[bin]] +name = "healer" +path = "src/main.rs" diff --git a/healer/build.rs b/healer/build.rs new file mode 100644 index 0000000000000000000000000000000000000000..666f802bb67ec9992ab20d189e3eeea66d36fb00 --- /dev/null +++ b/healer/build.rs @@ -0,0 +1,14 @@ +use anyhow::{Context as _, anyhow}; +use aya_build::{Toolchain, cargo_metadata}; + +fn main() -> anyhow::Result<()> { + let cargo_metadata::Metadata { packages, .. } = cargo_metadata::MetadataCommand::new() + .no_deps() + .exec() + .context("MetadataCommand::exec")?; + let ebpf_package = packages + .into_iter() + .find(|cargo_metadata::Package { name, .. }| name.as_str() == "healer-ebpf") + .ok_or_else(|| anyhow!("healer-ebpf package not found"))?; + aya_build::build_ebpf([ebpf_package], Toolchain::default()) +} diff --git a/healer/src/config.rs b/healer/src/config.rs new file mode 100644 index 0000000000000000000000000000000000000000..7aa0505950459e6310cc58506004cddda20e4aa5 --- /dev/null +++ b/healer/src/config.rs @@ -0,0 +1,182 @@ +use crate::daemon_handler::DaemonConfig; +use crate::monitor::ebpf_monitor; +use nix::libc::SKF_NET_OFF; +use serde::Deserialize; +use std::fs; +use std::ops::Not; +use std::path::{Path, PathBuf}; + +// 顶层配置结构体 + +#[derive(Debug, Clone, Deserialize)] +pub struct AppConfig { + pub log_level: Option, + pub log_directory: Option, + pub pid_file_directory: Option, + pub processes: Vec, + pub working_directory: Option, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct ProcessConfig { + pub name: String, + pub enabled: bool, + pub command: String, + pub args: Vec, + pub run_as_user: Option, + pub run_as_root: bool, + #[serde(default)] + pub working_dir: Option, + pub monitor: MonitorConfig, + #[serde(default)] + pub recovery: RecoveryConfig, +} + +#[derive(Deserialize, Debug, Clone)] +#[serde(tag = "type", rename_all = "lowercase")] +pub enum MonitorConfig { + Pid(PidMonitorFields), + Ebpf(EbpfMonitorFields), + Network(NetworkMonitorFields), +} + +#[derive(Deserialize, Debug, Clone)] +pub struct PidMonitorFields { + pub pid_file_path: PathBuf, + pub interval_secs: u64, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct NetworkMonitorFields { + pub target_url: String, + pub interval_secs: u64, +} +#[derive(Deserialize, Debug, Clone)] +pub struct EbpfMonitorFields {} + +// #[derive(Deserialize, Debug, Clone)] +// pub struct RecoveryConfig { +// pub retries: u32, +// pub retry_window_secs: u64, +// pub cooldown_secs: u64, +// } + +#[derive(Deserialize, Debug, Clone)] +#[serde(tag = "type", rename_all = "lowercase")] +pub enum RecoveryConfig { + Regular(RegularHealerFields), + NotRegular(NotREgularHealerFields), +} + +#[derive(Deserialize, Debug, Clone)] +pub struct RegularHealerFields { + pub retries: u32, + pub retry_window_secs: u64, + pub cooldown_secs: u64, +} +// 占位以后没有也可以删掉 +#[derive(Deserialize, Debug, Clone)] +pub struct NotREgularHealerFields {} + +#[derive(Debug, Clone)] +pub struct PidMonitorConfig { + pub name: String, + pub pid_file_path: PathBuf, + pub interval_secs: u64, +} +#[derive(Debug, Clone)] +pub struct EbpfMonitorConfig { + pub name: String, + pub command: String, +} +#[derive(Debug, Clone)] +pub struct NetworkMonitorConfig { + pub name: String, + // pub pid_file_path: PathBuf, + pub target_url: String, // 目标URL + pub interval_secs: u64, //检查的频率间隔 +} +impl Default for RecoveryConfig { + fn default() -> Self { + Self::Regular(RegularHealerFields::default()) + } +} +impl Default for RegularHealerFields { + fn default() -> Self { + Self { + retries: 3, + retry_window_secs: 60, + cooldown_secs: 180, + } + } +} +impl ProcessConfig { + pub fn get_pid_monitor_config(&self) -> Option { + if let MonitorConfig::Pid(pid_fields) = &self.monitor { + Some(PidMonitorConfig { + name: self.name.clone(), + pid_file_path: pid_fields.pid_file_path.clone(), + interval_secs: pid_fields.interval_secs, + }) + } else { + None + } + } + + pub fn get_ebpf_monitor_config(&self) -> Option { + if let MonitorConfig::Ebpf(ebpf_fields) = &self.monitor { + Some(EbpfMonitorConfig { + name: self.name.clone(), + command: self.command.clone(), + }) + } else { + None + } + } + + pub fn get_network_monitor_config(&self) -> Option { + if let MonitorConfig::Network(net_fields) = &self.monitor { + Some(NetworkMonitorConfig { + name: self.name.clone(), + target_url: net_fields.target_url.clone(), + interval_secs: net_fields.interval_secs, + }) + } else { + None + } + } +} + +impl AppConfig { + pub fn load_from_file(config_file_path: &Path) -> Result> { + let config_content = fs::read_to_string(config_file_path)?; + let loaded_config: AppConfig = serde_yaml::from_str(&config_content)?; + Ok(loaded_config) + } + + pub fn get_process_config_for(&self, process_name: &str) -> Option<&ProcessConfig> { + self.processes + .iter() + .find(|&p| p.name == process_name) + .map(|p_config| p_config) + } + + pub fn to_daemonize_config(&self) -> DaemonConfig { + let daemon_config = DaemonConfig { + pid_file: self + .pid_file_directory + .as_ref() + .map(|pid_file| pid_file.join("healer.pid")) + .unwrap_or_else(|| PathBuf::from("/tmp/healer.pid")), + log_directory: self + .log_directory + .clone() + .unwrap_or_else(|| PathBuf::from("/tmp/healer")), + working_dir: self + .working_directory + .clone() + .unwrap_or_else(|| PathBuf::from("/")), + }; + daemon_config + } +} diff --git a/healer/src/config_manager.rs b/healer/src/config_manager.rs new file mode 100644 index 0000000000000000000000000000000000000000..136cb3cd855d75d1af281f7d98fcb5141f044728 --- /dev/null +++ b/healer/src/config_manager.rs @@ -0,0 +1,46 @@ +use crate::config::AppConfig; +use anyhow::Result; +use std::sync::Arc; +use tokio::sync::RwLock; +use tracing::{error, info}; + +// 配置管理器,负责配置的加载和热更新 +pub struct ConfigManager { + config: Arc>, + config_path: std::path::PathBuf, +} + +impl ConfigManager { + pub fn new(config: Arc>, config_path: std::path::PathBuf) -> Self { + Self { + config, + config_path, + } + } + + // 重新加载配置文件 + pub async fn reload_config(&self) -> Result<()> { + info!( + "ConfigManager: Reloading configuration from {:?}", + self.config_path + ); + + match AppConfig::load_from_file(&self.config_path) { + Ok(new_config) => { + let mut config_guard = self.config.write().await; + *config_guard = new_config; + info!("ConfigManager: Configuration reloaded successfully."); + Ok(()) + } + Err(e) => { + error!("ConfigManager: Failed to reload configuration: {}", e); + Err(anyhow::anyhow!("Failed to reload configuration: {}", e)) + } + } + } + + // 获取当前配置的只读引用 + pub fn get_config(&self) -> Arc> { + Arc::clone(&self.config) + } +} diff --git a/healer/src/core_logic.rs b/healer/src/core_logic.rs new file mode 100644 index 0000000000000000000000000000000000000000..aac45accc1103ebacdd7f277cc01afbe193dccb7 --- /dev/null +++ b/healer/src/core_logic.rs @@ -0,0 +1,112 @@ +use crate::{ + config::AppConfig, + config_manager::ConfigManager, + event_bus, + monitor_manager::MonitorManager, + service_manager::ServiceManager, + signal_handler::{SignalEvent, SignalHandler}, +}; +use anyhow::Result; +use std::{path::PathBuf, sync::Arc}; +use tokio::sync::RwLock; +use tracing::{debug, error, info}; + +pub fn async_runtime(app_config: Arc>, config_path: PathBuf) { + println!("Async runtime: Starting process monitoring"); + + let rt = match tokio::runtime::Builder::new_multi_thread() + .enable_all() + .worker_threads(4) + .thread_name("healer") + .build() + { + Ok(r) => r, + Err(e) => { + eprintln!("Async runtime: Error from {}", e); + std::process::exit(1); + } + }; + + rt.block_on(async { + if let Err(e) = daemon_core_logic(app_config, config_path).await { + error!("Core logic error: {}", e); + std::process::exit(1); + } + }); +} + +async fn daemon_core_logic(config: Arc>, config_path: PathBuf) -> Result<()> { + info!("Application Core Logic: Starting up and initializing components..."); + + // 1. 创建事件总线 + let event_sender = event_bus::create_event_sender(); + info!("Application Core Logic: Event bus created."); + + // 2. 初始化各个管理器,包括配置管理器喝监视器管理器 + let config_manager = ConfigManager::new(Arc::clone(&config), config_path); + let mut monitor_manager = MonitorManager::new(event_sender.clone()).await?; + + // 3. 启动持久性后台服务 + ServiceManager::spawn_persistent_services(&event_sender, &config); + info!("Application Core Logic: Persistent services started."); + + // 4. 进行初始配置协调 + { + let config_guard = config.read().await; + debug!( + "Loaded {} process configurations:", + config_guard.processes.len() + ); + for (i, process_config) in config_guard.processes.iter().enumerate() { + debug!( + "Process {}: name='{}', command='{}'", + i + 1, + process_config.name, + process_config.command + ); + if let Some(ebpf_config) = process_config.get_ebpf_monitor_config() { + let executable_name = crate::utils::extract_executable_name(&ebpf_config.command); + let truncated_name = crate::utils::truncate_process_name(&executable_name); + debug!( + " eBPF monitoring enabled: executable='{}', truncated='{}'", + executable_name, truncated_name + ); + } + } + monitor_manager.reconcile(&config_guard.processes).await?; + } + info!("Application Core Logic: Initial reconciliation completed."); + + // 5. 主事件循环 - 等待信号并处理 + loop { + match SignalHandler::wait_for_signal().await? { + SignalEvent::ConfigReload => { + info!("Core Logic: Processing configuration reload..."); + + // 重新加载配置 + if let Err(e) = config_manager.reload_config().await { + error!("Core Logic: Failed to reload config: {}", e); + continue; + } + + // 重新协调监控器 + let config_guard = config.read().await; + if let Err(e) = monitor_manager.reconcile(&config_guard.processes).await { + error!("Core Logic: Failed to reconcile monitors: {}", e); + } + } + SignalEvent::Shutdown => { + info!("Core Logic: Initiating graceful shutdown..."); + break; + } + } + } + + // 6. 关闭 + monitor_manager.shutdown().await; + info!("Application Core Logic: Shutdown completed."); + + // 7. 确保进程正确退出 + info!("Application Core Logic: Exiting process..."); + std::process::exit(0); +} diff --git a/healer/src/daemon_handler.rs b/healer/src/daemon_handler.rs new file mode 100644 index 0000000000000000000000000000000000000000..efa7d4a100580fbe711a82a4a62c6898c5380241 --- /dev/null +++ b/healer/src/daemon_handler.rs @@ -0,0 +1,88 @@ +use crate::config::AppConfig; +use crate::logger; +use daemonize::Daemonize; +use std::path::PathBuf; + +#[derive(Debug)] +pub enum DaemonError { + Io(std::io::Error), + Daemonize(daemonize::Error), +} +impl From for DaemonError { + fn from(err: std::io::Error) -> DaemonError { + DaemonError::Io(err) + } +} +impl From for DaemonError { + fn from(err: daemonize::Error) -> DaemonError { + DaemonError::Daemonize(err) + } +} + +#[derive(Clone, Debug)] +pub struct DaemonConfig { + pub pid_file: PathBuf, + pub log_directory: PathBuf, + pub working_dir: PathBuf, +} +impl Default for DaemonConfig { + fn default() -> Self { + DaemonConfig { + pid_file: PathBuf::from("/tmp/healer.pid"), + log_directory: PathBuf::from("/tmp/healer"), + working_dir: PathBuf::from("/"), + } + } +} + +pub fn run_as_daemon( + config: std::sync::Arc>, + core_logic_fn: F, +) -> Result<(), DaemonError> +where + F: FnOnce() + Send + 'static, +{ + let config_guard = config.blocking_read(); + let daemon_config = config_guard.to_daemonize_config(); + + println!("Starting the daemon process"); + println!("PID file: {:?}", daemon_config.pid_file); + println!( + "Healer-Process Log Directory: {:?}", + daemon_config.log_directory + ); + let daemonizer = Daemonize::new() + .pid_file(&daemon_config.pid_file) + .chown_pid_file(false) + .working_directory(&daemon_config.working_dir) + .umask(0o027) + .privileged_action(|| { + //Todo + println!("Child process: Successfully set the hook fn."); + }); + match daemonizer.start() { + Ok(_) => { + let log_file_path = &daemon_config.log_directory; + let log_guard = match logger::init_daemon_logging(log_file_path) { + Ok(guard) => guard, + Err(e) => { + tracing::error!("Failed to initialize logging: {}. Exiting.", e); + std::process::exit(1); + } + }; + + // 执行核心逻辑 + core_logic_fn(); + + // 在进程退出前,显式地等待日志系统完成 + std::mem::drop(log_guard); + std::thread::sleep(std::time::Duration::from_millis(100)); + + Ok(()) + } + Err(e) => { + eprintln!("Parents process: Failed to begin the daemon process: {}", e); + Err(DaemonError::Daemonize(e)) + } + } +} diff --git a/healer/src/event_bus.rs b/healer/src/event_bus.rs new file mode 100644 index 0000000000000000000000000000000000000000..f0460fa9f097724c391f234e7ec975b9c6e772b0 --- /dev/null +++ b/healer/src/event_bus.rs @@ -0,0 +1,21 @@ +use crate::config::ProcessConfig; +use std::path::PathBuf; +use tokio::sync::broadcast; + +const CHANNEL_CAPACITY: usize = 128; + +#[derive(Clone, Debug)] +pub enum ProcessEvent { + ProcessDown { name: String, pid: u32 }, + ProcessDisconnected { name: String, url: String }, +} +pub struct RestartProcessConfig { + pub name: String, + pub command: String, + pub args: Vec, + pub workding_dir: Option, +} +pub fn create_event_sender() -> broadcast::Sender { + let (tx, _rx_initial) = broadcast::channel(CHANNEL_CAPACITY); + tx +} diff --git a/healer/src/logger.rs b/healer/src/logger.rs new file mode 100644 index 0000000000000000000000000000000000000000..f867cd4b492b8334f2e0c053c2b124469749f855 --- /dev/null +++ b/healer/src/logger.rs @@ -0,0 +1,31 @@ +use std::path::Path; +use tracing_appender::non_blocking::WorkerGuard; +use tracing_subscriber::EnvFilter; + +pub fn init_daemon_logging( + log_directory: &Path, +) -> Result> { + let log_file_name_prefix = "healer.log"; + + let file_appender = tracing_appender::rolling::daily(log_directory, log_file_name_prefix); + let (non_blocking_writer, guard) = tracing_appender::non_blocking(file_appender); + + let env_filter_str = std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()); + let env_filter = EnvFilter::try_new(&env_filter_str) + .map_err(|e| format!("Failed to parse RUST_LOG value '{}': {}", env_filter_str, e))?; + + tracing_subscriber::fmt() + .with_env_filter(env_filter) + .with_writer(non_blocking_writer) + .with_ansi(false) + .try_init() // try_init() 返回 Result, init() 会 panic on error + .map_err(|e| format!("Failed to initialize tracing subscriber: {}", e))?; + + tracing::info!( + "Logging system initialized. Log directory: {}", + log_directory.display() + ); + tracing::info!("Log level configured via RUST_LOG='{}'", env_filter_str); + + Ok(guard) +} diff --git a/healer/src/main.rs b/healer/src/main.rs new file mode 100644 index 0000000000000000000000000000000000000000..e47ce2457ce0a40d9c7d41a76ec314df336cbab4 --- /dev/null +++ b/healer/src/main.rs @@ -0,0 +1,46 @@ +mod config; +mod config_manager; +mod core_logic; +mod daemon_handler; +mod event_bus; +mod logger; +mod monitor; +mod monitor_manager; +mod service_manager; +mod signal_handler; +mod subscriber; +mod utils; +use config::AppConfig; +use daemon_handler::run_as_daemon; +use tokio::sync::RwLock; + +fn main() { + println!("Attempting to load the config"); + + // let config_file_path = Path::new("config.yaml"); + let config_file_path_str = "config.yaml"; + let absolue_config_path = match std::fs::canonicalize(config_file_path_str) { + Ok(path) => path, + Err(e) => { + eprintln!( + "Error: No such file or directory about configure '{}': {}", + config_file_path_str, e + ); + std::process::exit(1); + } + }; + let initial_config = AppConfig::load_from_file(&absolue_config_path).expect("初始配置加载失败"); + let shared_config = std::sync::Arc::new(RwLock::new(initial_config)); + let config_for_closure = std::sync::Arc::clone(&shared_config); + let path_for_closure = absolue_config_path.clone(); + let core_logic_closure = + move || core_logic::async_runtime(config_for_closure, path_for_closure); + match run_as_daemon(shared_config, core_logic_closure) { + Ok(_) => { + println!("Main program: Core logic quit"); + } + Err(e) => { + println!("Main program: Core logic error with {:?}", e); + } + } +} diff --git a/healer/src/monitor.rs b/healer/src/monitor.rs new file mode 100644 index 0000000000000000000000000000000000000000..570ef1f7c5346fe851cdc7d27c9c460a8428662c --- /dev/null +++ b/healer/src/monitor.rs @@ -0,0 +1,15 @@ +use async_trait::async_trait; +pub mod ebpf_monitor; +pub mod network_monitor; +pub mod pid_monitor; +use sysinfo::{RefreshKind, System}; +#[async_trait] +pub trait Monitor: Send + Sync { + // 启动并运行监控任务。 + // 这个方法需要包含一个无限循环,因此它本身不会返回。 + // 它应该被作为一个独立的并发任务来 spawn。 + // 但是目前eBPF不需要run。后续考虑重新抽象Monitor trait (Todo) + async fn run(self); + + fn name(&self) -> String; +} diff --git a/healer/src/monitor/ebpf_monitor.rs b/healer/src/monitor/ebpf_monitor.rs new file mode 100644 index 0000000000000000000000000000000000000000..93c4efc51fd809e0accbb557f0dddff358f60a92 --- /dev/null +++ b/healer/src/monitor/ebpf_monitor.rs @@ -0,0 +1,359 @@ +use super::Monitor; +use crate::{config::EbpfMonitorConfig, event_bus::ProcessEvent, utils}; +use anyhow::Result; +use anyhow::anyhow; +use async_trait::async_trait; +use aya::{Ebpf, maps::PerfEventArray, programs::TracePoint, util::online_cpus}; +use bytes::BytesMut; +use healer_common::ProcessExitEvent; +use std::time::Duration; +use std::{ + collections, + sync::{Arc, Mutex}, +}; +use std::{ + os::unix::io::AsRawFd, + sync::atomic::{AtomicBool, Ordering}, +}; +use tokio::{io::unix::AsyncFd, sync::broadcast, time::timeout}; +use tracing::{debug, error, info, warn}; + +pub struct EbpfMonitor { + bpf: Arc>, + process_name_mapping: Arc>>, // truncated_name -> full_config_name + task_handles: Vec>, // 保存后台任务句柄 + shutdown_flag: Arc, // 关闭标志 +} + +impl EbpfMonitor { + pub async fn new(event_tx: broadcast::Sender) -> Result { + info!("[EbpfMonitor] Initializing and launching the global eBPF monitor..."); + + let mut bpf = aya::Ebpf::load(aya::include_bytes_aligned!(concat!( + env!("OUT_DIR"), + "/healer" + )))?; + let program: &mut TracePoint = bpf + .program_mut("healer_exit") + .ok_or_else(|| anyhow!("Program 'healer_exit' not found"))? + .try_into()?; + program.load()?; + program.attach("sched", "sched_process_exit")?; + info!("[EbpfMonitor] Tracepoint attached successfully."); + + let events_map = bpf + .take_map("EVENTS") + .ok_or_else(|| anyhow!("Failed to take ownership of 'EVENTS' map"))?; + let mut events = PerfEventArray::try_from(events_map)?; + let mut task_handles = Vec::new(); + let shutdown_flag = Arc::new(AtomicBool::new(false)); + + // 创建进程名映射的共享引用 + let process_name_mapping = Arc::new(Mutex::new(collections::HashMap::new())); + for cpu_id in online_cpus().unwrap() { + let perf_buf = events.open(cpu_id, None)?; + let tx_clone = event_tx.clone(); + let fd = perf_buf.as_raw_fd(); + let async_fd = AsyncFd::new(fd)?; + let shutdown_flag_clone = shutdown_flag.clone(); + let mapping_clone = Arc::clone(&process_name_mapping); + + let handle = tokio::spawn(async move { + info!("[Worker] Listener task for CPU {} started.", cpu_id); + let mut local_perf_buf = perf_buf; + + while !shutdown_flag_clone.load(Ordering::SeqCst) { + let readable_result = + timeout(Duration::from_secs(1), async_fd.readable()).await; + match readable_result { + Ok(Ok(mut guard)) => { + // 确保在作用域结束时清理可读状态 + let mut should_break = false; + let mut bufs: [BytesMut; 1] = [BytesMut::with_capacity(1024)]; + match local_perf_buf.read_events(&mut bufs) { + Ok(events_read) => { + if events_read.read > 0 { + for buf in bufs.iter().take(events_read.read) { + let event = unsafe { + (buf.as_ptr() as *const ProcessExitEvent) + .read_unaligned() + }; + + let comm_str = std::str::from_utf8( + &event.comm[..event + .comm + .iter() + .position(|&x| x == 0) + .unwrap_or(16)], + ) + .unwrap_or("unknown"); + let process_name = { + let mapping = mapping_clone.lock().unwrap(); + mapping + .get(comm_str) + .cloned() + .unwrap_or_else(|| comm_str.to_string()) + }; + + info!( + "(CPU {}) Received Event: PID {} (comm: {}) has exited.", + cpu_id, event.pid, comm_str + ); + let send_result = + tx_clone.send(ProcessEvent::ProcessDown { + name: process_name.clone(), + pid: event.pid, + }); + + match send_result { + Ok(_) => { + debug!( + "(CPU {}) Sent ProcessDown event for '{}'", + cpu_id, process_name + ); + } + Err(e) => { + warn!( + "(CPU {}) Failed to send event: {} - continuing", + cpu_id, e + ); + } + } + } + } + } + Err(e) => { + debug!( + "[Worker] Perf buffer read error on CPU {}: {}, continuing", + cpu_id, e + ); + let error_str = e.to_string(); + if error_str.contains("broken pipe") + || error_str.contains("connection") + { + warn!( + "[Worker] Perf buffer connection issue on CPU {}, exiting", + cpu_id + ); + should_break = true; + } + } + } + // 明确清理可读状态 + guard.clear_ready(); + if should_break { + break; + } + } + Ok(Err(e)) => { + warn!( + "[Worker] AsyncFd error on CPU {}: {}, continuing", + cpu_id, e + ); + tokio::time::sleep(Duration::from_millis(100)).await; + } + Err(_) => { + // 超时是预料之中的,会继续进行shutdown_falg_clone的检查。用于在外界关闭本守护进程可以在1s内响应 + continue; + } + } + } + info!("[Worker] Listener task for CPU {} shutting down.", cpu_id); + }); + task_handles.push(handle); + } + + info!( + "[EbpfMonitor] All {} worker tasks have been dispatched.", + task_handles.len() + ); + + Ok(Self { + bpf: Arc::new(Mutex::new(bpf)), + process_name_mapping, + task_handles, + shutdown_flag, + }) + } + pub async fn wait_and_publish(&mut self) {} + pub async fn watch_config(&mut self, ebpf_config: EbpfMonitorConfig) -> anyhow::Result<()> { + // 从命令路径中提取可执行文件名 + let executable_name = utils::extract_executable_name(&ebpf_config.command); + let truncated_name = utils::truncate_process_name(&executable_name); + + info!( + "[EbpfMonitor] Adding process '{}' (truncated: '{}') to watch list.", + executable_name, truncated_name + ); + + // 更新进程名映射 + let mut mapping = self.process_name_mapping.lock().unwrap(); + mapping.insert(truncated_name.clone(), ebpf_config.name.clone()); + drop(mapping); + + // 将进程名添加到 eBPF map 中 + let mut process_name_bytes = [0u8; 16]; + let truncated_bytes = truncated_name.as_bytes(); + let copy_len = truncated_bytes.len().min(15); // 保留一个字节作为null终止符 + process_name_bytes[..copy_len].copy_from_slice(&truncated_bytes[..copy_len]); + + let bpf_clone = Arc::clone(&self.bpf); + let insert_result = tokio::task::spawn_blocking(move || -> anyhow::Result<()> { + let mut bpf_guard = bpf_clone + .lock() + .map_err(|e| anyhow!("Mutex was poisoned: {}", e))?; + + let map_handle = bpf_guard + .map_mut("PROCESS_NAMES_TO_MONITOR") + .ok_or_else(|| anyhow!("eBPF map 'PROCESS_NAMES_TO_MONITOR' not found"))?; + + let mut names_map: aya::maps::HashMap<_, [u8; 16], u8> = + aya::maps::HashMap::try_from(map_handle) + .map_err(|e| anyhow!("Failed to create HashMap view from eBPF map: {}", e))?; + + names_map.insert(process_name_bytes, 1, 0).map_err(|e| { + anyhow!( + "Failed to insert process name {:?} into eBPF map: {}", + process_name_bytes, + e + ) + })?; + + Ok(()) + }) + .await; + + match insert_result { + Ok(inner_result) => match inner_result { + Ok(()) => { + info!( + "Successfully added process name '{}' to eBPF monitoring.", + truncated_name + ); + Ok(()) + } + Err(e) => { + error!(error = ?e, process_name = %truncated_name, "Task to update eBPF map failed."); + Err(e) + } + }, + Err(e) => { + error!(error = ?e, "The spawned blocking task itself failed."); + Err(e.into()) + } + } + } + + pub async fn unwatch_config(&mut self, ebpf_config: EbpfMonitorConfig) -> anyhow::Result<()> { + let executable_name = utils::extract_executable_name(&ebpf_config.command); + let truncated_name = utils::truncate_process_name(&executable_name); + + info!( + "[EbpfMonitor] Removing process '{}' (truncated: '{}') from watch list.", + executable_name, truncated_name + ); + + // 移除内部状态 + let mut mapping = self.process_name_mapping.lock().unwrap(); + mapping.remove(&truncated_name); + drop(mapping); + + // 从 eBPF map 中移除进程名 + let mut process_name_bytes = [0u8; 16]; + let truncated_bytes = truncated_name.as_bytes(); + let copy_len = truncated_bytes.len().min(15); // 保留一个字节作为null终止符 + process_name_bytes[..copy_len].copy_from_slice(&truncated_bytes[..copy_len]); + + let bpf_clone = Arc::clone(&self.bpf); + let remove_result = tokio::task::spawn_blocking(move || -> anyhow::Result<()> { + let mut bpf_guard = bpf_clone + .lock() + .map_err(|e| anyhow!("Mutex was poisoned: {}", e))?; + + let map_handle = bpf_guard + .map_mut("PROCESS_NAMES_TO_MONITOR") + .ok_or_else(|| anyhow!("eBPF map 'PROCESS_NAMES_TO_MONITOR' not found"))?; + + let mut names_map: aya::maps::HashMap<_, [u8; 16], u8> = + aya::maps::HashMap::try_from(map_handle) + .map_err(|e| anyhow!("Failed to create HashMap view from eBPF map: {}", e))?; + + names_map.remove(&process_name_bytes).map_err(|e| { + anyhow!( + "Failed to remove process name {:?} from eBPF map: {}", + process_name_bytes, + e + ) + })?; + + Ok(()) + }) + .await; + + match remove_result { + Ok(inner_result) => match inner_result { + Ok(()) => { + info!( + "Successfully removed process name '{}' from eBPF monitoring.", + truncated_name + ); + Ok(()) + } + Err(e) => { + error!(error = ?e, process_name = %truncated_name, "Task to remove from eBPF map failed."); + Err(e) + } + }, + Err(e) => { + error!(error = ?e, "The spawned blocking task itself failed."); + Err(e.into()) + } + } + } + // 关闭 eBPF 监控器 + pub async fn shutdown(&mut self) { + info!("[EbpfMonitor] Initiating shutdown..."); + + // 设置关闭标志,使循环内的定期超时检查可以关闭对应线程 + self.shutdown_flag.store(true, Ordering::SeqCst); + + // 等待所有任务完成,设置超时 + let mut completed_tasks = 0; + for handle in self.task_handles.drain(..) { + match tokio::time::timeout(Duration::from_secs(3), handle).await { + Ok(_) => { + completed_tasks += 1; + } + Err(_) => { + warn!("[EbpfMonitor] Task did not complete within timeout, force stopping."); + } + } + } + + info!( + "[EbpfMonitor] Shutdown completed. {} tasks stopped gracefully.", + completed_tasks + ); + } +} + +impl Drop for EbpfMonitor { + fn drop(&mut self) { + // 设置关闭标志,即使 shutdown 没有被调用 + self.shutdown_flag.store(true, Ordering::SeqCst); + info!("[EbpfMonitor] Monitor dropped, shutdown flag set."); + } +} +#[async_trait] +impl Monitor for EbpfMonitor { + // eBPF的monitor的run方法是空的,不应该被调用 + // eBPF的执行逻辑应该是在new创建函数里就进行了 + async fn run(mut self) { + self.wait_and_publish().await; + } + // 监控器的名字或标识 + fn name(&self) -> String { + // self.config.name.clone() + "EbpfMonitor".to_string() + } +} diff --git a/healer/src/monitor/network_monitor.rs b/healer/src/monitor/network_monitor.rs new file mode 100644 index 0000000000000000000000000000000000000000..688572ab898e7d0dc2a93e099c902e9a9252cad3 --- /dev/null +++ b/healer/src/monitor/network_monitor.rs @@ -0,0 +1,103 @@ +use crate::{ + config::{self, NetworkMonitorConfig}, + event_bus::ProcessEvent, + monitor::Monitor, +}; +use async_trait::async_trait; +use reqwest::Response; +use std::{io::Read, result}; +use tokio::{io::AsyncReadExt, sync::broadcast}; +use tokio::{net::TcpStream, time}; +use tracing::{debug, error, info, warn}; +pub struct NetworkMonitor { + config: NetworkMonitorConfig, + event_tx: broadcast::Sender, +} +impl NetworkMonitor { + pub fn new(config: NetworkMonitorConfig, event_tx: broadcast::Sender) -> Self { + Self { + config, + event_tx, + } + } + pub fn check_interval(&self) -> u64 { + self.config.interval_secs + } + async fn check_and_publish(&self) { + let client = reqwest::Client::new(); + let check_result = client.get(&self.config.target_url).send().await; + match check_result { + Ok(response) => match response.status().is_success() { + true => { + debug!("[NetMonitor] {} is healthy", self.config.name); + } + false => { + warn!( + "[NetMonitor] {} is unhealthy, status: {}", + self.config.name, + response.status() + ); + } + }, + Err(e) => { + if e.is_connect() { + warn!("[NetMonitor] {} is unreachable: {}", self.config.name, e); + } else if e.is_timeout() { + warn!("[NetMonitor] {} request timed out: {}", self.config.name, e); + } else { + warn!( + "[NetMonitor] {} encountered an error: {}", + self.config.name, e + ); + } + self.publish_process_disconnected(); + //TODO 不能确定这几个事件究竟是否是需要重连,考虑设置成多个不同event发送 + } + } + } + async fn monitor_task_loop(&self) { + let mut interval = time::interval(time::Duration::from_secs(self.check_interval())); + + info!("[NetMonitor] Task for '{}' started.", self.config.name); + loop { + interval.tick().await; + self.check_and_publish().await; + } + } + fn publish_process_disconnected(&self) { + let event = ProcessEvent::ProcessDisconnected { + name: self.config.name.clone(), //name是被检测的进程的name + url: self.config.target_url.clone(), + //和PidMonitor比起,稍微不太一样的是Pid的config内部含的是pid file的地址。需要去读取才可以用,而target_url是可以直接使用的 + }; + debug!( + "[{}] Publishing ProcessDisconnected event for HTTP {}", + self.config.name, self.config.target_url + ); + + match self.event_tx.send(event) { + Ok(receiver_count) => { + debug!( + "[{}] Sent ProcessDisconnected event for HTTP {} to {} receivers", + self.config.name, self.config.target_url, receiver_count + ); + } + Err(_) => { + warn!( + "[{}] Failed to publish ProcessDisconnected event for HTTP {}: no active subscribers", + self.config.name, self.config.target_url + ); + } + } + } +} + +#[async_trait] +impl Monitor for NetworkMonitor { + async fn run(self) { + self.monitor_task_loop().await; + } + fn name(&self) -> String { + self.config.name.clone() + } +} diff --git a/healer/src/monitor/pid_monitor.rs b/healer/src/monitor/pid_monitor.rs new file mode 100644 index 0000000000000000000000000000000000000000..558ecfc66c42f693ea5ced569d0c2b24817ef81c --- /dev/null +++ b/healer/src/monitor/pid_monitor.rs @@ -0,0 +1,136 @@ +// src/monitor/pid_monitor.rs + +use async_trait::async_trait; +use nix::Error as NixError; +use nix::errno::Errno; +use nix::sys::signal::{Signal as NixSignal, kill}; +use nix::unistd::Pid; +use tokio::fs; +use tokio::sync::broadcast; +use tokio::time::{self, Duration as TokioDuration}; +use tracing::{debug, warn}; +// 从 config 模块引入 PidMonitor 所需的、具体的配置结构体 +use super::Monitor; +use crate::config::PidMonitorConfig; +use crate::event_bus::ProcessEvent; +use tracing::info; +pub struct PidMonitor { + config: PidMonitorConfig, + event_tx: broadcast::Sender, +} + +impl PidMonitor { + pub fn new(config: PidMonitorConfig, event_tx: broadcast::Sender) -> Self { + Self { config, event_tx } + } + pub fn check_interval(&self) -> u64 { + self.config.interval_secs + } + fn publish_process_down(&self, pid: u32) { + let event = ProcessEvent::ProcessDown { + name: self.config.name.clone(), //name是被检测的进程的name + pid, + }; + debug!( + "[{}] Publishing ProcessDown event for PID {}", + self.config.name, pid + ); + + match self.event_tx.send(event) { + Ok(receiver_count) => { + debug!( + "[{}] Sent ProcessDown event for PID {} to {} receivers", + self.config.name, pid, receiver_count + ); + } + Err(_) => { + warn!( + "[{}] Failed to publish ProcessDown event for PID {}: no active subscribers", + self.config.name, pid + ); + } + } + } + + async fn monitor_task_loop(&self) { + let monitor_name = self.name(); + let interval_secs = self.check_interval(); + let mut interval = time::interval(TokioDuration::from_secs(interval_secs)); + info!( + "[Monitor] Task for '{}' started with a {}s interval.", + monitor_name, interval_secs + ); + loop { + interval.tick().await; + self.check_and_publish().await; + } + } + + async fn check_and_publish(&self) { + let monitor_name = &self.config.name; + let pid_file_path = &self.config.pid_file_path; + debug!( + "[{}] Performing health check on PID file: {}", + monitor_name, + pid_file_path.display() + ); + // 异步读取 PID 文件内容 + let pid_str = match fs::read_to_string(pid_file_path).await { + Ok(content) => content, + Err(e) => { + warn!( + "[{}] Failed to read PID file {}: {}. Assuming process is down.", + monitor_name, + pid_file_path.display(), + e + ); + return; + } + }; + + // 解析 PID + let pid = match pid_str.trim().parse::() { + Ok(p) if p > 0 => p, + _ => { + warn!( + "[{}] Failed to parse a valid PID from file {}. Content: '{}'. Assuming process is down.", + monitor_name, + pid_file_path.display(), + pid_str + ); + return; + } + }; + + // 使用信号检查进程是否存在 + let process_pid = Pid::from_raw(pid); + match kill(process_pid, None) { + Ok(_) => { + debug!("[{}] Process (PID: {}) is alive.", monitor_name, pid); + } + Err(Errno::ESRCH) => { + info!( + "[{}] Process (PID: {}) not found (ESRCH). Process has exited.", + monitor_name, pid + ); + self.publish_process_down(pid as u32); + } + Err(e) => { + warn!( + "[{}] Error checking process (PID: {}): {}. Unable to determine status.", + monitor_name, pid, e + ); + } + } + } +} + +#[async_trait] +impl Monitor for PidMonitor { + fn name(&self) -> String { + self.config.name.clone() + } + async fn run(self) { + self.monitor_task_loop().await; + } +} diff --git a/healer/src/monitor_manager.rs b/healer/src/monitor_manager.rs new file mode 100644 index 0000000000000000000000000000000000000000..795d4fde36e57148ee01eed40e47f059921c6927 --- /dev/null +++ b/healer/src/monitor_manager.rs @@ -0,0 +1,225 @@ +use crate::{ + config::{NetworkMonitorConfig, ProcessConfig}, + event_bus::ProcessEvent, + monitor::{ + Monitor, ebpf_monitor::EbpfMonitor, network_monitor::NetworkMonitor, + pid_monitor::PidMonitor, + }, +}; +use anyhow::Result; +use std::collections::HashMap; +use std::time::Duration; +use sysinfo::NetworkData; +use tokio::sync::broadcast; +use tokio::task::JoinHandle; +use tracing::{error, info, warn}; + +// 监控器管理器,负责统一管理不同类型的监控器 +pub struct MonitorManager { + // eBPF 监控器 - 全局单例,始终运行 + ebpf_monitor: Option, + // 当前被 eBPF 监控的进程配置 + watched_ebpf_configs: HashMap, + // PID 监控器 - 按需启停 + running_monitors: HashMap>, + // 网络监控器 - 按需启停 + // running_network_monitors: HashMap>, + // 事件发送器 + event_sender: broadcast::Sender, +} + +impl MonitorManager { + pub async fn new(event_sender: broadcast::Sender) -> Result { + // 初始化全局 eBPF 监控器 + let ebpf_monitor = match EbpfMonitor::new(event_sender.clone()).await { + Ok(monitor) => { + info!("MonitorManager: eBPF Monitor initialized successfully."); + Some(monitor) + } + Err(e) => { + error!("MonitorManager: Failed to initialize eBPF Monitor: {}", e); + None + } + }; + + Ok(Self { + ebpf_monitor, + watched_ebpf_configs: HashMap::new(), + running_monitors: HashMap::new(), + // running_network_monitors: HashMap::new(), + event_sender, + }) + } + + // 根据新的配置更新所有监控器 + pub async fn reconcile(&mut self, processes: &[ProcessConfig]) -> Result<()> { + info!("MonitorManager: Starting reconciliation..."); + + // 分离不同类型的监控配置, ebpf和其他的pid network监视器都略有不同 + let (ebpf_configs, not_ebpf_configs): (Vec<_>, Vec<_>) = processes + .iter() + .filter(|p| p.enabled) + .partition(|p| p.get_ebpf_monitor_config().is_some()); + + // 更新 eBPF 监控器 + self.reconcile_ebpf_monitors(ebpf_configs).await?; + + // 更新 PID 监控器 + self.reconcile_monitors(not_ebpf_configs).await?; + + info!("MonitorManager: Reconciliation completed."); + Ok(()) + } + + // 更新 eBPF 监控器的监控列表 + async fn reconcile_ebpf_monitors( + &mut self, + desired_configs: Vec<&ProcessConfig>, + ) -> Result<()> { + let Some(ref mut ebpf_monitor) = self.ebpf_monitor else { + warn!("MonitorManager: eBPF monitor not available, skipping eBPF reconciliation."); + return Ok(()); + }; + + // 构建期望的配置映射 + let desired_configs_map: HashMap = desired_configs + .into_iter() + .map(|config| (config.name.clone(), config.clone())) + .collect(); + + // 移除不再需要的监控 + let configs_to_remove: Vec = self + .watched_ebpf_configs + .keys() + .filter(|name| !desired_configs_map.contains_key(*name)) + .cloned() + .collect(); + + for name in configs_to_remove { + if let Some(config) = self.watched_ebpf_configs.remove(&name) { + if let Some(ebpf_config) = config.get_ebpf_monitor_config() { + info!("MonitorManager: Removing eBPF watch for process '{}'", name); + if let Err(e) = ebpf_monitor.unwatch_config(ebpf_config).await { + error!( + "MonitorManager: Failed to unwatch config for '{}': {}", + name, e + ); + } + } + } + } + + // 添加新的监控 + for (name, config) in desired_configs_map { + if !self.watched_ebpf_configs.contains_key(&name) { + if let Some(ebpf_config) = config.get_ebpf_monitor_config() { + info!("MonitorManager: Adding eBPF watch for process '{}'", name); + match ebpf_monitor.watch_config(ebpf_config).await { + Ok(()) => { + self.watched_ebpf_configs.insert(name, config); + } + Err(e) => { + error!( + "MonitorManager: Failed to watch config for '{}': {}", + name, e + ); + } + } + } + } + } + + Ok(()) + } + + // 更新 PID 监控器的启停状态 + async fn reconcile_monitors(&mut self, desired_configs: Vec<&ProcessConfig>) -> Result<()> { + // 构建期望的配置映射 + let desired_configs_map: HashMap = desired_configs + .into_iter() + .map(|config| (config.name.clone(), config)) + .collect(); + + // 停止不再需要的监控器 + let monitors_to_stop: Vec = self + .running_monitors + .keys() + .filter(|name| !desired_configs_map.contains_key(*name)) + .cloned() + .collect(); + + for name in monitors_to_stop { + info!( + "MonitorManager: Stopping not-ebpf monitor for process '{}'", + name + ); + if let Some(handle) = self.running_monitors.remove(&name) { + // 取消任务并等待一小段时间 + handle.abort(); + // 给任务一些时间来清理 + tokio::time::sleep(Duration::from_millis(100)).await; + } + } + + // 启动新的监控器或重启已结束的监控器 + for (name, process_config) in desired_configs_map { + let should_start = match self.running_monitors.get(&name) { + Some(handle) => handle.is_finished(), + None => true, + }; + + if should_start { + if let Some(pid_config) = process_config.get_pid_monitor_config() { + info!( + "MonitorManager: Starting PID monitor for process '{}'", + name + ); + let monitor = PidMonitor::new(pid_config, self.event_sender.clone()); + let handle = tokio::spawn(monitor.run()); + self.running_monitors.insert(name.clone(), handle); + } else if let Some(network_config) = process_config.get_network_monitor_config() { + info!( + "MonitorManager: Starting Network monitor for process '{}'", + name + ); + let monitor = NetworkMonitor::new(network_config, self.event_sender.clone()); + let handle = tokio::spawn(monitor.run()); + self.running_monitors.insert(name.clone(), handle); + } + } + } + + Ok(()) + } + + // 关闭所有监控器 + pub async fn shutdown(&mut self) { + info!("MonitorManager: Shutting down all monitors..."); + + // 停止所有 非ebpf 监控器 + let mut handles_to_wait = Vec::new(); + for (name, handle) in self.running_monitors.drain() { + info!("MonitorManager: Stopping monitor for '{}'", name); + handle.abort(); + handles_to_wait.push(handle); + } + + // 等待所有任务完成,但设置超时 + for handle in handles_to_wait { + if let Err(e) = tokio::time::timeout(Duration::from_secs(2), handle).await { + warn!( + "MonitorManager: not-ebpf monitor task did not complete within timeout: {:?}", + e + ); + } + } + + // 关闭 eBPF 监控器,否则守护进程杀不死 + if let Some(mut ebpf_monitor) = self.ebpf_monitor.take() { + info!("MonitorManager: Shutting down eBPF monitor..."); + ebpf_monitor.shutdown().await; + } + + info!("MonitorManager: All monitors stopped."); + } +} diff --git a/healer/src/service_manager.rs b/healer/src/service_manager.rs new file mode 100644 index 0000000000000000000000000000000000000000..fe04c797feb46ff6756c32f013b72f6b2b33a996 --- /dev/null +++ b/healer/src/service_manager.rs @@ -0,0 +1,109 @@ +use crate::{ + config::AppConfig, + event_bus::ProcessEvent, + subscriber::{Subscriber, process_healer::ProcessHealer}, +}; +use nix::errno::Errno; +use nix::sys::wait::{WaitPidFlag, WaitStatus, waitpid}; +use std::sync::Arc; +use tokio::signal::unix::{self, SignalKind}; +use tokio::sync::{RwLock, broadcast}; +use tracing::{debug, error, info, warn}; + +/// 服务管理器,负责管理持久性后台任务 +pub struct ServiceManager; + +impl ServiceManager { + /// 启动所有持久性后台服务 + pub fn spawn_persistent_services( + event_sender: &broadcast::Sender, + config: &Arc>, + ) { + Self::spawn_process_healer(event_sender, config); + Self::spawn_zombie_reaper(); + } + + /// 启动进程自愈服务 + fn spawn_process_healer( + event_sender: &broadcast::Sender, + config: &Arc>, + ) { + let healer_receiver = event_sender.subscribe(); + let healer_config = Arc::clone(config); + + tokio::spawn(async move { + let mut healer = ProcessHealer::new(healer_receiver, healer_config).await; + info!("ServiceManager: ProcessHealer service started."); + loop { + match healer.event_rx.recv().await { + Ok(event) => { + healer.handle_event(event).await; + } + Err(broadcast::error::RecvError::Lagged(n)) => { + warn!( + "ServiceManager: ProcessHealer lagged, missed {} messages", + n + ); + } + Err(_) => { + error!("ServiceManager: ProcessHealer event channel closed, exiting."); + break; + } + } + } + }); + } + + /// 启动僵尸进程清理服务 + fn spawn_zombie_reaper() { + tokio::spawn(async { + info!("ServiceManager: Zombie reaper service started, listening for SIGCHLD."); + + match unix::signal(SignalKind::child()) { + Ok(mut stream) => loop { + stream.recv().await; + Self::reap_zombies(); + }, + Err(e) => { + error!( + "ServiceManager: Failed to register SIGCHLD signal handler: {}", + e + ); + } + } + }); + } + + /// 清理僵尸进程 + fn reap_zombies() { + loop { + match waitpid(None, Some(WaitPidFlag::WNOHANG)) { + Ok(WaitStatus::Exited(pid, status)) => { + info!( + "ServiceManager: Reaped child {} which exited with status {}", + pid, status + ); + } + Ok(WaitStatus::Signaled(pid, signal, _)) => { + info!( + "ServiceManager: Reaped child {} which was killed by signal {}", + pid, signal + ); + } + Ok(WaitStatus::StillAlive) | Err(Errno::ECHILD) => { + break; // 没有更多已终止的子进程了 + } + Ok(other) => { + debug!( + "ServiceManager: Reaped child with other status: {:?}", + other + ); + } + Err(e) => { + error!("ServiceManager: waitpid failed: {}", e); + break; + } + } + } + } +} diff --git a/healer/src/signal_handler.rs b/healer/src/signal_handler.rs new file mode 100644 index 0000000000000000000000000000000000000000..9527fb63d626c9d54cbcf84b4a8a2160ece986e9 --- /dev/null +++ b/healer/src/signal_handler.rs @@ -0,0 +1,38 @@ +use anyhow::Result; +use tokio::signal::unix::{self, SignalKind}; +use tracing::info; + +/// 信号处理器,负责处理系统信号 +pub struct SignalHandler; + +#[derive(Debug)] +pub enum SignalEvent { + /// 配置重载信号 (SIGHUP) + ConfigReload, + /// 关闭信号 (SIGTERM, SIGINT) + Shutdown, +} + +impl SignalHandler { + /// 等待下一个信号事件 + pub async fn wait_for_signal() -> Result { + let mut hup_signal = unix::signal(SignalKind::hangup())?; + let mut term_signal = unix::signal(SignalKind::terminate())?; + let mut int_signal = unix::signal(SignalKind::interrupt())?; + + tokio::select! { + _ = hup_signal.recv() => { + info!("SignalHandler: Received SIGHUP, triggering configuration reload."); + Ok(SignalEvent::ConfigReload) + } + _ = term_signal.recv() => { + info!("SignalHandler: Received SIGTERM, initiating graceful shutdown."); + Ok(SignalEvent::Shutdown) + } + _ = int_signal.recv() => { + info!("SignalHandler: Received SIGINT (Ctrl+C), initiating graceful shutdown."); + Ok(SignalEvent::Shutdown) + } + } + } +} diff --git a/healer/src/subscriber.rs b/healer/src/subscriber.rs new file mode 100644 index 0000000000000000000000000000000000000000..299454e3f0a0a3845f8cfada7ede434309f81376 --- /dev/null +++ b/healer/src/subscriber.rs @@ -0,0 +1,7 @@ +use crate::event_bus; +use async_trait::async_trait; +pub mod process_healer; +#[async_trait] +pub trait Subscriber: Send + Sync { + async fn handle_event(&mut self, event: event_bus::ProcessEvent); +} diff --git a/healer/src/subscriber/process_healer.rs b/healer/src/subscriber/process_healer.rs new file mode 100644 index 0000000000000000000000000000000000000000..994ad4abf66dba27ed17114fe60950be5215cc41 --- /dev/null +++ b/healer/src/subscriber/process_healer.rs @@ -0,0 +1,305 @@ +use super::Subscriber; +use crate::config::{self, AppConfig, ProcessConfig, RecoveryConfig, RegularHealerFields}; +use crate::event_bus::ProcessEvent; +use async_trait::async_trait; +use serde::de::value::StrDeserializer; +use std::collections::{HashMap, VecDeque}; +use std::iter::StepBy; +use std::os::unix::process::{self, CommandExt}; +use std::process::{Command, Stdio}; +use std::{default, fs}; +use std::{sync::Arc, time::Instant}; +use tokio::sync::RwLock; +use tokio::sync::{Mutex, broadcast}; +use tracing::{debug, info, warn}; +use users::get_user_by_name; + +#[derive(PartialEq)] +enum State { + Closed, + Open, + HalfOpen, +} +struct ProcessRecoveryStats { + recovery_session_starts: VecDeque, + recovery_state: State, + in_cooldown_until: Option, + half_open_safe_until: Option, + // half_open_retry_flag: Option, +} +impl Default for ProcessRecoveryStats { + fn default() -> Self { + Self { + recovery_session_starts: VecDeque::new(), + recovery_state: State::Closed, + in_cooldown_until: None, + half_open_safe_until: None, + } + } +} +pub struct ProcessHealer { + pub event_rx: broadcast::Receiver, + pub app_config: Arc>, + process_recovery_windows: Mutex>, +} + +impl ProcessHealer { + pub async fn new( + rx: broadcast::Receiver, + config: Arc>, + ) -> Self { + let recover_map = { + let config_guard = config.read().await; + config_guard + .processes + .iter() + .map(|p| (p.name.clone(), ProcessRecoveryStats::default())) + .collect::>() + }; // 读锁在这个作用域结束时自动释放 + + Self { + event_rx: rx, + app_config: config, + process_recovery_windows: Mutex::new(recover_map), + } + } + + pub async fn heal_process(&mut self, name: &String) { + // 使用超时机制获取配置锁,避免无限期阻塞 + //breaker 返回true,说明仍在熔断;返回false说明可以执行 + if self.check_circuit_breaker(&name).await { + warn!( + target = "healer_action", + process_name = %name, + "Circuit breaker is open, skipping recovery for process {}.", + name + ); + return; + } + let config_guard = + match tokio::time::timeout(std::time::Duration::from_secs(5), self.app_config.read()) + .await + { + Ok(guard) => guard, + Err(_) => { + warn!( + target = "healer_action", + process_name = %name, + "Failed to acquire config lock within timeout, skipping recovery." + ); + return; + } + }; + + if let Some(process_config) = config_guard.get_process_config_for(&name) { + let command_to_run = &process_config.command; + let args = &process_config.args; + info!(target = "healer_event", process_name = %name, "Parsed the restart command. Conducting recovery."); + + // 创建日志目录(如果不存在) + if let Err(e) = std::fs::create_dir_all("/var/log/healer") { + warn!(target = "healer_action", process_name = %name, error = %e, "Failed to create log directory, using /tmp"); + } + + let child_log_path = format!("/var/log/healer/{}.restarted.log", name); + let child_output_file = match fs::File::create(&child_log_path) { + Ok(file) => file, + Err(e) => { + warn!( + target = "healer_action", + process_name = %name, + error = %e, + "Failed to create log file, trying /tmp" + ); + // 尝试在/tmp创建日志文件 + let fallback_path = format!("/tmp/healer_{}.restarted.log", name); + match fs::File::create(&fallback_path) { + Ok(file) => file, + Err(e2) => { + tracing::error!( + target = "healer_action", + process_name = %name, + error = %e2, + "Failed to create fallback log file, aborting recovery." + ); + return; + } + } + } + }; + + let mut command = Command::new(command_to_run); + command.args(args); + + // 改进的权限处理 + if !process_config.run_as_root { + if let Some(username) = &process_config.run_as_user { + match get_user_by_name(username) { + Some(user) => { + command.uid(user.uid()); + command.gid(user.primary_group_id()); + info!(target: "healer_action", process_name = %name, user = %username, uid = %user.uid(), "Dropping privileges to run as specified user."); + } + None => { + warn!(target: "healer_action", process_name = %name, user = %username, "Specified user not found. Process will run as root. This is a security risk."); + } + } + } else { + warn!(target: "healer_action", process_name = %name, "run_as_root is false but no run_as_user specified. Process will run as root."); + } + } + + // 被恢复的进程重定向io + command.stdout(Stdio::from(child_output_file.try_clone().unwrap())); + command.stderr(Stdio::from(child_output_file)); + + match command.spawn() { + Ok(child) => { + info!(target = "healer_event", process_name = %name, process_pid = %child.id(), "Successfully restarted process."); + } + Err(e) => { + tracing::error!(target = "healer_action", + process_name = %name, + error = %e, + "Failed to restart process. This might be due to permission issues or invalid command path."); + } + } + } else { + warn!( + target = "healer_action", + process_name = %name, + "No configuration found for process." + ); + } + } + async fn check_circuit_breaker(&mut self, name: &String) -> bool { + match self.process_recovery_windows.lock().await.get_mut(name) { + Some(stats) => { + debug!("[{}] Checking circuit breaker state.", name); + match stats.recovery_state { + State::Closed => { + let config_guard = self.app_config.read().await; + if let Some(process_config) = config_guard.get_process_config_for(name) { + if let RecoveryConfig::Regular(regular_healer_fields) = + &process_config.recovery + { + //遍历一遍,把时间超过窗口期的恢复次数删除掉 + //Rust不用for和迭代器遍历,retain 来判断窗口期时间 + stats.recovery_session_starts.retain(|start_time| { + start_time.elapsed().as_secs() + < regular_healer_fields.retry_window_secs + }); + + if stats.recovery_session_starts.len() + == regular_healer_fields.retries as usize + { + //熔断了,切换为开路,设置好冷却时间,同时重置历史累计的回复次数 + stats.recovery_state = State::Open; + stats.in_cooldown_until = Some( + Instant::now() + + std::time::Duration::from_secs( + regular_healer_fields.cooldown_secs, + ), + ); + stats.recovery_session_starts.clear(); + return true; + } else { + stats.recovery_session_starts.push_back(Instant::now()); + debug!( + "Process {} has tried {} times", + &name, + stats.recovery_session_starts.len() + ); + return false; + } + } else if let RecoveryConfig::NotRegular(_) = &process_config.recovery { + warn!("Shouldn'be here, NotRegular is not implemented yet"); + return false; // 开还是关的返回值还未确定,健壮性有待商榷 (Todo) + } else { + warn!("No recovery configuration found for process {}", name); + return false; // 开还是关的返回值还未确定,健壮性有待商榷 + } + } else { + warn!("No configuration found for process {}", name); + return false; + } + } + State::Open => { + if let Some(cooldown_until) = stats.in_cooldown_until { + if Instant::now() < cooldown_until { + return true; // 熔断器仍然处于开启状态 + } else { + // 如果半开重试标志为true,说明可以尝试恢复 + stats.recovery_state = State::HalfOpen; + stats.recovery_session_starts.clear(); + stats.in_cooldown_until = None; // 清除冷却时间 + stats.half_open_safe_until = + Some(Instant::now() + std::time::Duration::from_secs(2)); //Todo: 这里的2秒是个写死的,实际应该根据配置来设置或者给出默认值 + return false; + } + } else { + // in_cool_down_until为None也处理为可以半开路, 但是不应该走到这里 + warn!( + "Shouldn't be here, in_cooldown_until is None for process {}", + name + ); + warn!( + "No cooldown time set for process {}, assuming it can be recovered.", + name + ); + // 如果半开重试标志为true,说明 + stats.recovery_state = State::HalfOpen; + stats.recovery_session_starts.clear(); + stats.in_cooldown_until = None; // 清除冷却时间 + return false; + } + } + State::HalfOpen => { + // 半开状态,尝试恢复 + if let Some(safe_until) = stats.half_open_safe_until { + if Instant::now() < safe_until { + //切换成Open + //可以考虑在后续实现为指数回避 (Todo) + warn!( + "Process {} is in half-open state, but safe time has not passed yet.", + name + ); + stats.recovery_state = State::Open; + stats.half_open_safe_until = None; // 清除半开安全时间 + stats.recovery_session_starts.clear(); + return true; // 半开状态,不能恢复 + } else { + // 半开状态结束,重置为关闭状态 + stats.recovery_state = State::Closed; + stats.half_open_safe_until = None; + stats.recovery_session_starts.clear(); + return false; // 可以恢复 + } + } else { + warn!("Half-open state without safe time set for process {}", name); + stats.recovery_state = State::Closed; + stats.half_open_safe_until = None; + stats.recovery_session_starts.clear(); + return false; // 可以恢复 + } + } + } + } + None => true, + } + } +} + +#[async_trait] +impl Subscriber for ProcessHealer { + async fn handle_event(&mut self, event: ProcessEvent) { + //heal_process + if let ProcessEvent::ProcessDown { name, pid } = &event { + info!(target = "healer_event", process_name = %name, process_pid = %pid, "Received ProcessDown event. Initiating recovery process."); + self.heal_process(name).await + } else if let ProcessEvent::ProcessDisconnected { name, url } = &event { + info!(target = "healer_event", process_name = %name, url = %url, "Received ProcessDisconnected event. Initiating recovery process."); + self.heal_process(name).await; + } + } +} diff --git a/healer/src/utils.rs b/healer/src/utils.rs new file mode 100644 index 0000000000000000000000000000000000000000..72ccbcd9a525e7b73227d89fa9d34b3885781499 --- /dev/null +++ b/healer/src/utils.rs @@ -0,0 +1,99 @@ +use std::collections::HashMap; +use std::default::Default; +use sysinfo::{ProcessRefreshKind, RefreshKind, System, UpdateKind}; +use tracing::debug; + +pub fn find_pid_by_exe_path(path: &str) -> Option { + let process_kind = ProcessRefreshKind::default().with_exe(UpdateKind::Always); + let rk = RefreshKind::nothing().with_processes(process_kind); + let sys = System::new_with_specifics(rk); + for process in sys.processes().values() { + debug!("the exe for pid {} is {:?}", process.pid(), process.exe()); + if let Some(exe_path) = process.exe() { + if exe_path.to_str() == Some(path) { + let pid_as_usize = usize::from(process.pid()); + return Some(pid_as_usize as u32); + } + } + } + None +} + +/// 截断进程名到内核限制的16个字符(包括null终止符,所以实际是15个字符) +pub fn truncate_process_name(name: &str) -> String { + const MAX_COMM_LEN: usize = 15; // 内核中TASK_COMM_LEN - 1 + if name.len() <= MAX_COMM_LEN { + name.to_string() + } else { + name.chars().take(MAX_COMM_LEN).collect() + } +} + +/// 根据截断的进程名查找完整的进程配置名 +/// 返回可能匹配的进程配置名列表 +pub fn find_process_configs_by_truncated_name( + truncated_name: &str, + process_names: &[String], +) -> Vec { + let mut matches = Vec::new(); + + for process_name in process_names { + let truncated_config_name = truncate_process_name(process_name); + if truncated_config_name == truncated_name { + matches.push(process_name.clone()); + } + } + + matches +} + +/// 构建进程名到配置名的映射表 +/// 用于快速查找截断名对应的完整配置 +pub fn build_process_name_mapping(process_names: &[String]) -> HashMap> { + let mut mapping = HashMap::new(); + + for process_name in process_names { + let truncated = truncate_process_name(process_name); + mapping + .entry(truncated) + .or_insert_with(Vec::new) + .push(process_name.clone()); + } + + mapping +} + +/// 从进程名获取可执行文件名部分 +/// 例如:"/usr/bin/nginx" -> "nginx" +pub fn extract_executable_name(command: &str) -> String { + std::path::Path::new(command) + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or(command) + .to_string() +} + +/// 匹配进程名:处理截断名可能对应多个配置的情况 +/// 优先返回精确匹配,如果有多个匹配则返回第一个 +pub fn smart_match_process_name( + truncated_name: &str, + name_mapping: &HashMap>, +) -> Option { + if let Some(matches) = name_mapping.get(truncated_name) { + if matches.len() == 1 { + Some(matches[0].clone()) + } else if matches.len() > 1 { + tracing::warn!( + "Multiple process configs match truncated name '{}': {:?}. Using first match: '{}'", + truncated_name, + matches, + matches[0] + ); + Some(matches[0].clone()) + } else { + None + } + } else { + None + } +}