diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e69de29 diff --git a/Cargo.lock b/Cargo.lock index f32d465..45b927e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anstream" version = "1.0.0" @@ -38,7 +53,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -49,7 +64,67 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "arc-swap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380962b3d93b99642e50b9febf72c071e97e8b32d86eae9cf4ef582bb960ae6e" +dependencies = [ + "rustversion", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-fs" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-walkdir" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed40ca66d45a6775511920324e4ae05c81673f9133c187c4970a3089c3ae27c" +dependencies = [ + "async-fs", + "futures-lite", + "thiserror", ] [[package]] @@ -64,6 +139,99 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "aws-lc-rs" +version = "1.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faac5829c2b74c28f830747e7818ccfb684261b5f48a1118b1e2a13d36dfab13" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1622d8446a2d4b2ce0c7eefc73dd43a99779028d5ee5c2dd8073a658ba8a2bc" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + +[[package]] +name = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" +dependencies = [ + "axum-core", + "axum-macros", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-macros" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aa268c23bfbbd2c4363b9cd302a4f504fb2a9dfe7e3451d66f35dd392e20aca" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64" version = "0.23.1" @@ -82,6 +250,19 @@ version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" +[[package]] +name = "blocking" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + [[package]] name = "bstr" version = "1.13.1" @@ -129,6 +310,23 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core", +] + [[package]] name = "clap" version = "4.6.7" @@ -178,12 +376,49 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" +[[package]] +name = "clru" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197fd99cb113a8d5d9b6376f3aa817f32c1078f2343b714fff7d2ca44fdf67d5" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "combine" +version = "4.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4356eba6976435498d05a311ee9fc486da3bc81c200d3f0d7dd9703b41913f92" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -200,23 +435,127 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" +dependencies = [ + "cfg-if", +] + [[package]] name = "crete" version = "0.1.3" dependencies = [ + "async-walkdir", + "axum", + "byteorder", "clap", "clap_complete", - "gix-discover", + "futures", + "gix-discover 0.56.0", + "ipxe", "minijinja", + "netascii", + "num_enum", "optional_struct", + "regex", "reqwest", "schemars", "semver", "serde", "serde_json", "serde_yaml", + "socket-pktinfo", + "socket2", "thiserror", - "walkdir", + "tokio", +] + +[[package]] +name = "crossbeam" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e71406cd8807725f7ac2f999a4cdd32e98f829fdf65f528343cebf945e41df1e" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e8bd762f7479489c70ed6c768ddca99d7296857de437a68dcb2a94365b3fae" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" + +[[package]] +name = "dashmap" +version = "6.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", ] [[package]] @@ -259,6 +598,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "diffy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e3dc2f773b6aaa63b1a7684b8589f670a8a0146a510b74d23a401c882364b49" +dependencies = [ + "hashbrown 0.17.1", +] + [[package]] name = "displaydoc" version = "0.2.7" @@ -270,6 +618,17 @@ dependencies = [ "syn 3.0.6", ] +[[package]] +name = "dua-core" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d687862f3206e7c660daeb6ab0200c65e58fccd7e508819e4bd9949304c640a8" +dependencies = [ + "crossbeam", + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "dunce" version = "1.0.5" @@ -282,6 +641,20 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "encoding_rs" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679" +dependencies = [ + "cfg-if", + "core_detect", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -295,7 +668,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "5.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" +dependencies = [ + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", ] [[package]] @@ -316,12 +709,34 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + [[package]] name = "find-msvc-tools" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" version = "0.3.2" @@ -346,6 +761,27 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.34" @@ -362,12 +798,36 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.34" @@ -397,6 +857,7 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -407,6 +868,19 @@ dependencies = [ "slab", ] +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + [[package]] name = "getrandom" version = "0.4.3" @@ -414,8 +888,70 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", + "rand_core", + "wasm-bindgen", +] + +[[package]] +name = "gix" +version = "0.89.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2156d2e07fc3afa7632a82bf3cb0db91f995645a2852f642105c9ac9b52e1cb2" +dependencies = [ + "gix-actor 0.44.0", + "gix-attributes", + "gix-command", + "gix-commitgraph", + "gix-config", + "gix-credentials", + "gix-date 0.18.0", + "gix-diff", + "gix-dir", + "gix-discover 0.57.0", + "gix-error 0.5.1", + "gix-filter", + "gix-fs 0.24.0", + "gix-glob", + "gix-hash 0.28.0", + "gix-hashtable 0.18.0", + "gix-ignore", + "gix-index", + "gix-lock 26.0.0", + "gix-negotiate", + "gix-note", + "gix-object 0.66.0", + "gix-odb", + "gix-pack", + "gix-parallel", + "gix-path 0.14.0", + "gix-pathspec", + "gix-prompt", + "gix-protocol", + "gix-quote", + "gix-ref 0.69.0", + "gix-refspec", + "gix-revision", + "gix-revwalk", + "gix-sec 0.16.0", + "gix-shallow", + "gix-status", + "gix-submodule", + "gix-tempfile 26.0.0", + "gix-trace 0.3.0", + "gix-transport", + "gix-traverse", + "gix-url", + "gix-utils 0.5.1", + "gix-validate 0.13.0", + "gix-worktree", + "gix-worktree-state", + "gix-worktree-stream", + "gix-zlib", + "nonempty", + "smallvec", ] [[package]] @@ -425,8 +961,135 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e046e9a929e8e1f40f9a34f736408c4c079f9cf004429f3e718d270e847be96" dependencies = [ "bstr", - "gix-date", - "gix-error", + "gix-date 0.17.0", + "gix-error 0.4.0", +] + +[[package]] +name = "gix-actor" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2127da41042965ed205568a52443465806944f71c8acd73f48ec94b8cd8e798e" +dependencies = [ + "bstr", + "gix-date 0.18.0", + "gix-error 0.5.1", +] + +[[package]] +name = "gix-attributes" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0feca0b8919aba9f123b75de54062dd61eb37d2a2130f34be520da6ed8b8ef72" +dependencies = [ + "bstr", + "gix-error 0.5.1", + "gix-glob", + "gix-parallel", + "gix-path 0.14.0", + "gix-quote", + "gix-trace 0.3.0", + "smallvec", + "unicode-bom", +] + +[[package]] +name = "gix-bitmap" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "408cb9e482e0dcc5fc481850556348dda6981ec054a4a8e81a8e1bbf84f85eef" +dependencies = [ + "gix-error 0.5.1", +] + +[[package]] +name = "gix-chunk" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626335faf30ed235744642c2309aa370683b8bb2757f1c22a807e274dff60074" +dependencies = [ + "gix-error 0.5.1", +] + +[[package]] +name = "gix-command" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac560c05e19455016a4a94b33a77debe59aaed8783da32a73d31c9975947152e" +dependencies = [ + "bstr", + "gix-error 0.5.1", + "gix-path 0.14.0", + "gix-quote", + "gix-trace 0.3.0", +] + +[[package]] +name = "gix-commitgraph" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfede9936d4b789867c09722576aea4ceb0e3aacf962eef035ebab0686b41a80" +dependencies = [ + "bstr", + "gix-chunk", + "gix-error 0.5.1", + "gix-hash 0.28.0", + "memmap2", + "nonempty", +] + +[[package]] +name = "gix-config" +version = "0.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65a9c000fa9a9154f22ffc8f8d74426e9300d759c2029e8701e2ba39d5d464ea" +dependencies = [ + "bstr", + "gix-config-value", + "gix-error 0.5.1", + "gix-glob", + "gix-parallel", + "gix-path 0.14.0", + "gix-ref 0.69.0", + "gix-sec 0.16.0", + "gix-trace 0.3.0", + "gix-utils 0.5.1", + "smallvec", + "unicode-bom", +] + +[[package]] +name = "gix-config-value" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab206c2fccfd35e0de7fb1b424997cfd42912062c66dae024e08a06e22d46674" +dependencies = [ + "bitflags 2.13.2", + "bstr", + "gix-error 0.5.1", + "gix-path 0.14.0", + "gix-utils 0.5.1", + "libc", +] + +[[package]] +name = "gix-credentials" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba790d296ded4bcfe485f761a65b8659fcccad825ff59a3acc38578a86f812de" +dependencies = [ + "bstr", + "gix-command", + "gix-config-value", + "gix-date 0.18.0", + "gix-error 0.5.1", + "gix-path 0.14.0", + "gix-prompt", + "gix-quote", + "gix-sec 0.16.0", + "gix-trace 0.3.0", + "gix-url", + "percent-encoding", ] [[package]] @@ -436,11 +1099,67 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb8beceb16a77fb222923592864b94beb4ad4c2b175a7055c2cac6815ee0370a" dependencies = [ "bstr", - "gix-error", + "gix-error 0.4.0", "itoa", "jiff", ] +[[package]] +name = "gix-date" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f7eeeecc599082f32b5ea6a8c4c734e38be9730e2c09e3fe2851bb4692393e9" +dependencies = [ + "bstr", + "gix-error 0.5.1", + "itoa", + "jiff", +] + +[[package]] +name = "gix-diff" +version = "0.69.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa640963fdcbc6b4718e48593c3917f5ae764b59fb5a96313cb5a757246d041d" +dependencies = [ + "bstr", + "gix-attributes", + "gix-command", + "gix-error 0.5.1", + "gix-filter", + "gix-fs 0.24.0", + "gix-hash 0.28.0", + "gix-imara-diff", + "gix-index", + "gix-object 0.66.0", + "gix-path 0.14.0", + "gix-pathspec", + "gix-tempfile 26.0.0", + "gix-trace 0.3.0", + "gix-traverse", + "gix-worktree", +] + +[[package]] +name = "gix-dir" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84e916ef77f19161b738ae9913662bc36fed1e48256a943f40277b3a585841da" +dependencies = [ + "bstr", + "gix-discover 0.57.0", + "gix-error 0.5.1", + "gix-fs 0.24.0", + "gix-ignore", + "gix-index", + "gix-object 0.66.0", + "gix-path 0.14.0", + "gix-pathspec", + "gix-trace 0.3.0", + "gix-utils 0.5.1", + "gix-worktree", +] + [[package]] name = "gix-discover" version = "0.56.0" @@ -449,11 +1168,26 @@ checksum = "4476ba055ec5fbb05f59dd98380b596428da060beac5f9dccd7374302ad1fbfb" dependencies = [ "bstr", "dunce", - "gix-error", - "gix-fs", - "gix-path", - "gix-ref", - "gix-sec", + "gix-error 0.4.0", + "gix-fs 0.23.0", + "gix-path 0.13.0", + "gix-ref 0.68.0", + "gix-sec 0.15.0", +] + +[[package]] +name = "gix-discover" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7d0f8fd2c7e4b458eb3081362d49b54bd0131d23ecd49be802085a11f0d3cee" +dependencies = [ + "bstr", + "dunce", + "gix-error 0.5.1", + "gix-fs 0.24.0", + "gix-path 0.14.0", + "gix-ref 0.69.0", + "gix-sec 0.16.0", ] [[package]] @@ -465,20 +1199,50 @@ dependencies = [ "bstr", ] +[[package]] +name = "gix-error" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb3308a948a01b94ef13d9ee7a9b28c1303d8d9a9dc33b401ff40c2184d9e04a" +dependencies = [ + "bstr", +] + [[package]] name = "gix-features" version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb3a430dbc02d6c5e30a9f92083459593495ea22a476e837d248b22d800eed80" dependencies = [ - "gix-path", - "gix-trace", - "gix-utils", + "gix-path 0.13.0", + "gix-trace 0.2.0", + "gix-utils 0.4.0", "libc", "prodash", "walkdir", ] +[[package]] +name = "gix-filter" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d8d932ee4043f7d77d15ee58d2be2bc95accea5dd1d77fd145de1b7c504551b" +dependencies = [ + "bstr", + "encoding_rs", + "gix-attributes", + "gix-command", + "gix-error 0.5.1", + "gix-hash 0.28.0", + "gix-object 0.66.0", + "gix-packetline", + "gix-path 0.14.0", + "gix-quote", + "gix-trace 0.3.0", + "gix-utils 0.5.1", + "smallvec", +] + [[package]] name = "gix-fs" version = "0.23.0" @@ -486,10 +1250,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0105ab1cef4542d092c27766d2daf1548fcbad7e460dc2208642c120bb19b3ad" dependencies = [ "bstr", - "gix-error", + "gix-error 0.4.0", "gix-features", - "gix-path", - "gix-utils", + "gix-path 0.13.0", + "gix-utils 0.4.0", +] + +[[package]] +name = "gix-fs" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44a05d043e670c055cd8b53d078562dc0bc1e322bff6b0376b407d37cf72dcc5" +dependencies = [ + "bstr", + "gix-error 0.5.1", + "gix-parallel", + "gix-path 0.14.0", + "gix-utils 0.5.1", + "libc", + "walkdir", +] + +[[package]] +name = "gix-glob" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0d03ab50fa841621031960679caf7fe94940968d9a0736d12a580f2c03e525" +dependencies = [ + "bitflags 2.13.2", + "bstr", + "gix-fs 0.24.0", + "gix-path 0.14.0", + "gix-trace 0.3.0", + "gix-utils 0.5.1", ] [[package]] @@ -499,31 +1292,155 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a22b6febaaee8546eb0c9dd21ecdd93aca269a6cb3897e1250f0f60933c0174" dependencies = [ "faster-hex", - "gix-error", + "gix-error 0.4.0", "gix-features", "sha1dc", ] +[[package]] +name = "gix-hash" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ca156064e378d8c657084365846cedb8dc498c6ea4500965a64d74f12af2be" +dependencies = [ + "faster-hex", + "gix-error 0.5.1", + "gix-utils 0.5.1", + "sha1dc", +] + [[package]] name = "gix-hashtable" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c55bff2f32f6f5a95bdd43e5e5e01b0656250cffbc48eda19db0afa3d73adf2f" dependencies = [ - "gix-hash", - "hashbrown", + "gix-hash 0.27.0", + "hashbrown 0.17.1", "parking_lot", ] +[[package]] +name = "gix-hashtable" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916cb6b2ec42ffaca624e7867bb382a4673015cb4ffcd7330b48085d02e4c615" +dependencies = [ + "gix-hash 0.28.0", + "hashbrown 0.17.1", + "parking_lot", +] + +[[package]] +name = "gix-ignore" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb1c93f60845d493a72fde38ae237498bc4e4464f45afa4af23e53f8295a3ba1" +dependencies = [ + "bstr", + "gix-glob", + "gix-path 0.14.0", + "gix-trace 0.3.0", + "unicode-bom", +] + +[[package]] +name = "gix-imara-diff" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c48eccd894bd15555b0346470e63491ded94aefe76b3fcb1052084b5a393238" +dependencies = [ + "bstr", + "hashbrown 0.17.1", +] + +[[package]] +name = "gix-index" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6db8b20a3dba8836daf90b24199600bb27dcfce3a127ec24ae61e15a7c5b68" +dependencies = [ + "bitflags 2.13.2", + "bstr", + "filetime", + "fnv", + "gix-bitmap", + "gix-error 0.5.1", + "gix-fs 0.24.0", + "gix-hash 0.28.0", + "gix-lock 26.0.0", + "gix-object 0.66.0", + "gix-parallel", + "gix-trace 0.3.0", + "gix-traverse", + "gix-utils 0.5.1", + "gix-validate 0.13.0", + "hashbrown 0.17.1", + "itoa", + "libc", + "memmap2", + "rustix", + "smallvec", +] + [[package]] name = "gix-lock" version = "25.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fd5ddfd0cc476fef744ac544783b96a9ff5d49f8de541e6e9d6d6002aa90720" dependencies = [ - "gix-error", - "gix-tempfile", - "gix-utils", + "gix-error 0.4.0", + "gix-tempfile 25.0.0", + "gix-utils 0.4.0", +] + +[[package]] +name = "gix-lock" +version = "26.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2f7f5bd6b9fcd0b0dc58b0e6a708f3a47c3313876571dfe800c926c069aa5c" +dependencies = [ + "gix-error 0.5.1", + "gix-fs 0.24.0", + "gix-tempfile 26.0.0", + "gix-utils 0.5.1", +] + +[[package]] +name = "gix-macros" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f11f5429ca73cc3758c9f53af56a9fba100a8701c5b4bc30e29b8b0b15288991" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "gix-negotiate" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ddfb0d6da849bd938d929336e07c1f26970d9f6ec59621df74ec3f79c128ea" +dependencies = [ + "bitflags 2.13.2", + "gix-commitgraph", + "gix-date 0.18.0", + "gix-error 0.5.1", + "gix-hash 0.28.0", + "gix-object 0.66.0", + "gix-revwalk", +] + +[[package]] +name = "gix-note" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd364f8bdceaed9e7f3ae40f324b16e39f20e9c7e6a6a058f7c6ec66c3e6205c" +dependencies = [ + "gix-error 0.5.1", + "gix-hash 0.28.0", + "gix-object 0.66.0", ] [[package]] @@ -533,18 +1450,112 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a72909eeeed845721a143ef3dbeb4d6cd5df79c839fe7eefc138cb4f982eba3e" dependencies = [ "bstr", - "gix-actor", - "gix-date", - "gix-error", + "gix-actor 0.43.0", + "gix-date 0.17.0", + "gix-error 0.4.0", "gix-features", - "gix-hash", - "gix-hashtable", - "gix-utils", - "gix-validate", + "gix-hash 0.27.0", + "gix-hashtable 0.17.0", + "gix-utils 0.4.0", + "gix-validate 0.12.0", "itoa", "smallvec", ] +[[package]] +name = "gix-object" +version = "0.66.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0e38f50177e211642af154f9a7f710b7a3a637e635f9b6adb9d51973b0f9209" +dependencies = [ + "bstr", + "gix-actor 0.44.0", + "gix-command", + "gix-date 0.18.0", + "gix-error 0.5.1", + "gix-hash 0.28.0", + "gix-hashtable 0.18.0", + "gix-path 0.14.0", + "gix-tempfile 26.0.0", + "gix-utils 0.5.1", + "gix-validate 0.13.0", + "itoa", + "smallvec", +] + +[[package]] +name = "gix-odb" +version = "0.86.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca444f33f2140876ed6b5313c2b550b4af699008193c96f3bff5023afa9ef4ea" +dependencies = [ + "arc-swap", + "crc32fast", + "gix-error 0.5.1", + "gix-fs 0.24.0", + "gix-hash 0.28.0", + "gix-hashtable 0.18.0", + "gix-object 0.66.0", + "gix-pack", + "gix-parallel", + "gix-path 0.14.0", + "gix-quote", + "gix-trace 0.3.0", + "gix-utils 0.5.1", + "gix-zlib", + "memmap2", + "parking_lot", + "tempfile", +] + +[[package]] +name = "gix-pack" +version = "0.76.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5024cc503cd29461a705ae90b9eae772fa8f0e6fafc1c48513877dc374de05" +dependencies = [ + "clru", + "crc32fast", + "gix-chunk", + "gix-error 0.5.1", + "gix-hash 0.28.0", + "gix-hashtable 0.18.0", + "gix-object 0.66.0", + "gix-parallel", + "gix-path 0.14.0", + "gix-tempfile 26.0.0", + "gix-trace 0.3.0", + "gix-utils 0.5.1", + "gix-zlib", + "memmap2", + "parking_lot", + "smallvec", +] + +[[package]] +name = "gix-packetline" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830e51b227f6a925fe9308858ac91170dd00fa2880f5480fc81e8a4594fe2c35" +dependencies = [ + "bstr", + "faster-hex", + "gix-error 0.5.1", + "gix-trace 0.3.0", +] + +[[package]] +name = "gix-parallel" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e994cf1eec247ba4be7f9e7c0d58cc73f83eecb5fcb9feb595df5ba3d2078546" +dependencies = [ + "crossbeam-channel", + "gix-trace 0.3.0", + "once_cell", + "parking_lot", +] + [[package]] name = "gix-path" version = "0.13.0" @@ -552,9 +1563,85 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd051ac60c5dbce2228daabf25b6a76408ac732d416c678cfa0a4914d13f60b8" dependencies = [ "bstr", - "gix-error", - "gix-trace", - "gix-validate", + "gix-error 0.4.0", + "gix-trace 0.2.0", + "gix-validate 0.12.0", +] + +[[package]] +name = "gix-path" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6d563d4d0c975ed0df4e853f9ec962a2ba46633b1d324e1a5582541fcae0c6b" +dependencies = [ + "bstr", + "gix-error 0.5.1", + "gix-trace 0.3.0", + "gix-validate 0.13.0", +] + +[[package]] +name = "gix-pathspec" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e3b7f03e516a8435d18c3cc2d446925940c81d409145ac21edb5a576f0625e4" +dependencies = [ + "bitflags 2.13.2", + "bstr", + "gix-attributes", + "gix-config-value", + "gix-error 0.5.1", + "gix-glob", + "gix-path 0.14.0", +] + +[[package]] +name = "gix-prompt" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f26063489b415c40bd960ec488251d77d4cfe3cf5ca4331e2122c0c726bcc2d" +dependencies = [ + "gix-command", + "gix-config-value", + "gix-error 0.5.1", + "parking_lot", + "rustix", +] + +[[package]] +name = "gix-protocol" +version = "0.67.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "832b5dc46a3c0a6f6fa8f8e571c11074add4b545c6789799ae6d6c36ae1a294e" +dependencies = [ + "bstr", + "gix-credentials", + "gix-date 0.18.0", + "gix-error 0.5.1", + "gix-hash 0.28.0", + "gix-lock 26.0.0", + "gix-macros", + "gix-negotiate", + "gix-object 0.66.0", + "gix-ref 0.69.0", + "gix-refspec", + "gix-revwalk", + "gix-shallow", + "gix-trace 0.3.0", + "gix-transport", + "gix-utils 0.5.1", + "nonempty", +] + +[[package]] +name = "gix-quote" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f92bc1b13c09e88388578e8aa1e987ec35f28fca158c13b1baa7ab1a85b124" +dependencies = [ + "bstr", + "gix-error 0.5.1", + "gix-utils 0.5.1", ] [[package]] @@ -563,20 +1650,84 @@ version = "0.68.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c90ceeec67de03b737c30b1b69116ca56fec803edde022f3d25181f0f597083d" dependencies = [ - "gix-actor", - "gix-error", + "gix-actor 0.43.0", + "gix-error 0.4.0", "gix-features", - "gix-fs", - "gix-hash", - "gix-lock", - "gix-object", - "gix-path", - "gix-tempfile", - "gix-utils", - "gix-validate", + "gix-fs 0.23.0", + "gix-hash 0.27.0", + "gix-lock 25.0.0", + "gix-object 0.65.0", + "gix-path 0.13.0", + "gix-tempfile 25.0.0", + "gix-utils 0.4.0", + "gix-validate 0.12.0", "memmap2", ] +[[package]] +name = "gix-ref" +version = "0.69.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "073bc0928e1f747823b1eaed94918119dacef4c616532bc3d17e5b7457863471" +dependencies = [ + "gix-actor 0.44.0", + "gix-error 0.5.1", + "gix-fs 0.24.0", + "gix-hash 0.28.0", + "gix-lock 26.0.0", + "gix-object 0.66.0", + "gix-parallel", + "gix-path 0.14.0", + "gix-tempfile 26.0.0", + "gix-utils 0.5.1", + "gix-validate 0.13.0", + "memmap2", +] + +[[package]] +name = "gix-refspec" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc7010dae0df2900aa77cc67f8aca042cd80e35938ccc179010d863b1d42a0c" +dependencies = [ + "bstr", + "gix-error 0.5.1", + "gix-hash 0.28.0", + "gix-validate 0.13.0", + "smallvec", +] + +[[package]] +name = "gix-revision" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63210c1943b67fa700806c3c3486fc16a92b3ea871f5cf092340b8eec10a0ca1" +dependencies = [ + "bstr", + "gix-commitgraph", + "gix-date 0.18.0", + "gix-error 0.5.1", + "gix-hash 0.28.0", + "gix-object 0.66.0", + "gix-revwalk", + "nonempty", +] + +[[package]] +name = "gix-revwalk" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "800479fbbf1e7f94f663e07943a91add5499343e7aac611a81e6a0460fd44af6" +dependencies = [ + "gix-commitgraph", + "gix-date 0.18.0", + "gix-error 0.5.1", + "gix-hash 0.28.0", + "gix-hashtable 0.18.0", + "gix-object 0.66.0", + "smallvec", +] + [[package]] name = "gix-sec" version = "0.15.0" @@ -584,9 +1735,76 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d8eb983a830ba586b61f7005deaf44609e1a4186cbf13a0e8668154ca910461" dependencies = [ "bitflags 2.13.2", - "gix-path", + "gix-path 0.13.0", "libc", - "windows-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "gix-sec" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5640f642a1b471616d48230b1def7ca743cb30903441ff84800cafbab3c0fddf" +dependencies = [ + "bitflags 2.13.2", + "gix-path 0.14.0", + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "gix-shallow" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94bc0720a3d6b10f18e3ac382a7780bcfc4c6bee94a8b5857b8e4ec94c50d754" +dependencies = [ + "bstr", + "gix-error 0.5.1", + "gix-hash 0.28.0", + "gix-lock 26.0.0", + "nonempty", +] + +[[package]] +name = "gix-status" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3174a9f3fb991cf25a7a609ab6c50f19baa7d5f8ce05a48c202f7db4ce3ba" +dependencies = [ + "bstr", + "filetime", + "gix-diff", + "gix-dir", + "gix-error 0.5.1", + "gix-filter", + "gix-fs 0.24.0", + "gix-hash 0.28.0", + "gix-index", + "gix-object 0.66.0", + "gix-parallel", + "gix-path 0.14.0", + "gix-pathspec", + "gix-trace 0.3.0", + "gix-utils 0.5.1", + "gix-worktree", + "hashbrown 0.16.1", + "portable-atomic", + "windows-sys 0.61.2", +] + +[[package]] +name = "gix-submodule" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d945257bd0d624ca844a5a36c79d748df080f355b0ac92d68793875aa529a3f" +dependencies = [ + "bstr", + "gix-config", + "gix-error 0.5.1", + "gix-path 0.14.0", + "gix-pathspec", + "gix-refspec", + "gix-url", ] [[package]] @@ -595,11 +1813,25 @@ version = "25.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dd7dd8df3345933caddd6ce67821c9eec17192564f1011cf0bd0b8cb0d07ffb" dependencies = [ - "gix-fs", + "gix-fs 0.23.0", "libc", "parking_lot", "tempfile", - "windows-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "gix-tempfile" +version = "26.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9275c8ae0646d840ae0156d3f90d39b7beea6e31a398afb9ee72feb293a269c" +dependencies = [ + "dashmap", + "gix-fs 0.24.0", + "libc", + "parking_lot", + "tempfile", + "windows-sys 0.61.2", ] [[package]] @@ -608,6 +1840,64 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df45b2cf6052d9e52681130c17d67505b023e8933ad974f94a2be0b0d24f636a" +[[package]] +name = "gix-trace" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "133f2de116104d637541831b6acb157898556d5eeec363ab2eb1c583adc6bb56" + +[[package]] +name = "gix-transport" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e43d6b0097ea9f2f266d5d6c43b8f615ed3da7c686ec8771cd17c1cb33cb0de" +dependencies = [ + "base64 0.22.1", + "bstr", + "gix-command", + "gix-credentials", + "gix-error 0.5.1", + "gix-packetline", + "gix-path 0.14.0", + "gix-quote", + "gix-sec 0.16.0", + "gix-trace 0.3.0", + "gix-url", + "gix-utils 0.5.1", + "parking_lot", + "reqwest", +] + +[[package]] +name = "gix-traverse" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a668a6e7f7688e5a43eaf161f1471ffc559de1a5fd8471e861c64d59d1699e1e" +dependencies = [ + "bitflags 2.13.2", + "gix-commitgraph", + "gix-date 0.18.0", + "gix-error 0.5.1", + "gix-hash 0.28.0", + "gix-hashtable 0.18.0", + "gix-object 0.66.0", + "gix-revwalk", + "smallvec", +] + +[[package]] +name = "gix-url" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04317dea7a5dd5253bbcab060c230f3de6a57d17cc9600c6cd7030bf4795b414" +dependencies = [ + "bstr", + "gix-error 0.5.1", + "gix-path 0.14.0", + "gix-utils 0.5.1", + "percent-encoding", +] + [[package]] name = "gix-utils" version = "0.4.0" @@ -615,7 +1905,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70525766f53426ca4cd2ea4d58b0b31488dfedb416b3254e2efdd9dc184cc880" dependencies = [ "fastrand", - "getrandom", + "getrandom 0.4.3", + "unicode-normalization", +] + +[[package]] +name = "gix-utils" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8e770b8c34311044d1addc7b18957db0ac27183707e1140dc90e6f674a6354e" +dependencies = [ + "bstr", + "bytes", + "fastrand", + "getrandom 0.4.3", + "gix-error 0.5.1", + "prodash", "unicode-normalization", ] @@ -626,7 +1931,108 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30574f81fc41b2ca3582ece2bdf8696bdfe9c170aa7e8bcafb81199c86b1ceea" dependencies = [ "bstr", - "gix-error", + "gix-error 0.4.0", +] + +[[package]] +name = "gix-validate" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbcd59cc52742468bc5c8a0e7123390d154f45e6cbd37461b0dcecdc681f2179" +dependencies = [ + "bstr", + "gix-error 0.5.1", +] + +[[package]] +name = "gix-worktree" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a285c0f6b41e504e8bb81ca3f861c4da00cb3d5ced3210a4591112f22da6d38f" +dependencies = [ + "bstr", + "dua-core", + "gix-attributes", + "gix-error 0.5.1", + "gix-fs 0.24.0", + "gix-glob", + "gix-hash 0.28.0", + "gix-ignore", + "gix-index", + "gix-object 0.66.0", + "gix-path 0.14.0", + "gix-trace 0.3.0", + "gix-utils 0.5.1", + "gix-validate 0.13.0", +] + +[[package]] +name = "gix-worktree-state" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "003c1162556d4ebf51b8140b955cb04c2458d5ecbdffe50262dbbb270635208c" +dependencies = [ + "bstr", + "gix-error 0.5.1", + "gix-filter", + "gix-fs 0.24.0", + "gix-index", + "gix-object 0.66.0", + "gix-parallel", + "gix-path 0.14.0", + "gix-trace 0.3.0", + "gix-utils 0.5.1", + "gix-worktree", + "io-close", +] + +[[package]] +name = "gix-worktree-stream" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d2789610e8179d4e0e8e51170a52a66483c103a8ad3c7db4d075b571856cb6" +dependencies = [ + "gix-attributes", + "gix-error 0.5.1", + "gix-filter", + "gix-fs 0.24.0", + "gix-hash 0.28.0", + "gix-object 0.66.0", + "gix-parallel", + "gix-path 0.14.0", + "gix-trace 0.3.0", + "gix-traverse", + "gix-utils 0.5.1", + "parking_lot", +] + +[[package]] +name = "gix-zlib" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2edb206bf06f3c801f4f6789c696396171558ab9b4c7f1fe3ba4d39b00b0f4" +dependencies = [ + "gix-error 0.5.1", + "zlib-rs", +] + +[[package]] +name = "h2" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d29020232d6aa3fb1daca64c1127cf662cf97f254ae16c18c05b8ab635fc118" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", ] [[package]] @@ -638,11 +2044,33 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] [[package]] name = "heapless" @@ -699,6 +2127,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "hyper" version = "1.11.1" @@ -709,9 +2143,11 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -719,6 +2155,21 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -741,7 +2192,7 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" dependencies = [ - "base64", + "base64 0.23.1", "bytes", "futures-channel", "futures-util", @@ -863,6 +2314,25 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "include_dir" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "indexmap" version = "2.14.2" @@ -870,7 +2340,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.17.1", +] + +[[package]] +name = "io-close" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc" +dependencies = [ + "libc", + "winapi", ] [[package]] @@ -879,6 +2359,15 @@ version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" +[[package]] +name = "ipxe" +version = "0.1.0" +dependencies = [ + "diffy", + "gix", + "include_dir", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -945,13 +2434,62 @@ dependencies = [ "jiff-tzdb", ] +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + [[package]] name = "jobserver" version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" dependencies = [ - "getrandom", + "getrandom 0.4.3", "libc", ] @@ -1023,6 +2561,18 @@ version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" +[[package]] +name = "lru-slab" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + [[package]] name = "memchr" version = "2.8.3" @@ -1044,6 +2594,12 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5449c8c750f1a07ea702bbd212bd999fceece9b3d1508b17023b3e174583124b" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "minijinja" version = "2.24.0" @@ -1063,9 +2619,15 @@ checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.61.2", ] +[[package]] +name = "multiversion_no_op" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" + [[package]] name = "native-tls" version = "0.2.18" @@ -1083,6 +2645,40 @@ dependencies = [ "tempfile", ] +[[package]] +name = "netascii" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "205c45083b6725eb4671392c7c399510ad1498dd0275f1a2e68a70a34ebdf1b1" + +[[package]] +name = "nonempty" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9737e026353e5cd0736f98eddae28665118eb6f6600902a7f50db585621fecb6" + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -1159,6 +2755,12 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.5" @@ -1194,6 +2796,17 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.34" @@ -1224,6 +2837,15 @@ dependencies = [ "zerovec", ] +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.107" @@ -1242,6 +2864,63 @@ dependencies = [ "parking_lot", ] +[[package]] +name = "quinn" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e750cca55fe4f0439a15d0bb529da9651e79993e8e72c61a899a36d462befbe" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af66907df18639dcf4db56ca65490cabc4b27a97dbadd96f2926cca73298f016" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + [[package]] name = "quote" version = "1.0.47" @@ -1257,6 +2936,32 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "rand" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -1286,11 +2991,34 @@ dependencies = [ "syn 3.0.6", ] +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + [[package]] name = "regex-automata" version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "repo_path" @@ -1327,37 +3055,72 @@ version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" dependencies = [ - "base64", + "base64 0.23.1", "bytes", + "encoding_rs", "futures-channel", "futures-core", "futures-util", + "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", + "quinn", + "rustls", "rustls-pki-types", + "rustls-platform-verifier", "sync_wrapper", "tokio", "tokio-native-tls", - "tokio-util", + "tokio-rustls", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", "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.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.1.5" @@ -1368,7 +3131,33 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a073f5dc7469f984c52ad2752b63b0807745133b6de880b7b64c1ac4c48aec4" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", ] [[package]] @@ -1377,9 +3166,49 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ + "web-time", "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1167586491e2b18b8bfbb293e8180ec17c201c4f076d7cb3070ca964e7598f98" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f" + +[[package]] +name = "rustls-webpki" +version = "0.103.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.23" @@ -1407,7 +3236,7 @@ version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1529,6 +3358,29 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[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" @@ -1554,6 +3406,32 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "slab" version = "0.4.12" @@ -1566,6 +3444,17 @@ version = "1.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" +[[package]] +name = "socket-pktinfo" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612942246d0cc239cfd83af1dfd39be47f649208a3524e5e9da651910128e0ac" +dependencies = [ + "libc", + "socket2", + "windows-sys 0.61.2", +] + [[package]] name = "socket2" version = "0.6.5" @@ -1573,7 +3462,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1588,6 +3477,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[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.119" @@ -1637,10 +3532,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom", + "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1688,9 +3583,23 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", - "windows-sys", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", ] [[package]] @@ -1704,18 +3613,59 @@ dependencies = [ ] [[package]] -name = "tokio-util" -version = "0.7.19" +name = "tokio-rustls" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e464cf451ba96ebfc6f9b6542f17ee8b8956e33f1e40d9690624e59d7a7f8a4b" dependencies = [ "bytes", "futures-core", "futures-sink", + "libc", "pin-project-lite", "tokio", ] +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.15+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow", +] + [[package]] name = "tower" version = "0.5.3" @@ -1729,6 +3679,7 @@ dependencies = [ "tokio", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -1767,6 +3718,7 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ + "log", "pin-project-lite", "tracing-core", ] @@ -1786,6 +3738,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "unicode-bom" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" + [[package]] name = "unicode-ident" version = "1.0.26" @@ -1807,6 +3765,12 @@ 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.8" @@ -1918,19 +3882,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-streams" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "web-sys" version = "0.3.106" @@ -1941,21 +3892,71 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58ec80e73fd8baf57bf0d9615554c75348bda990d6d5bbeab964dd068319fecb" +dependencies = [ + "rustls-pki-types", +] + +[[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-util" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] +[[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-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[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.61.2" @@ -1965,6 +3966,79 @@ dependencies = [ "windows-link", ] +[[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" + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + [[package]] name = "writeable" version = "0.6.4" @@ -2064,6 +4138,12 @@ dependencies = [ "syn 3.0.6", ] +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + [[package]] name = "zmij" version = "1.0.23" diff --git a/Cargo.toml b/Cargo.toml index 0e7cb0f..5ebf523 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,21 +7,25 @@ publish = ["infra"] license = "GPL-3.0-or-later" [workspace] -members = ["xtask"] +members = ["ipxe", "xtask"] [workspace.dependencies] schemars = { version = "1.2.2", features = ["semver1"] } serde_json = "1.0.151" [dependencies] +async-walkdir = "2.2.0" +byteorder = "1.5.0" clap = { version = "4.6.7", features = ["derive"] } clap_complete = "4.6.11" +futures = "0.3.34" gix-discover = { version = "0.56.0", features = ["sha1"] } minijinja = { version = "2.24.0", features = ["json", "loader"] } +netascii = "0.1.0" +num_enum = "0.7.6" optional_struct = "0.5.2" +regex = "1.13.1" reqwest = { version = "0.13.5", default-features = false, features = [ - "blocking", - "stream", "native-tls", ] } schemars = { workspace = true } @@ -30,7 +34,11 @@ serde = { version = "1.0.229", features = ["derive"] } serde_json = { workspace = true } serde_yaml = "0.9.34" thiserror = "2.0.21" -walkdir = "2.5.0" +tokio = { version = "1.52.1", features = ["full"] } +ipxe = { path = "./ipxe" } +axum = { version = "0.8.9", features = ["macros"] } +socket2 = "0.6.5" +socket-pktinfo = "0.4.1" [lib] test = false diff --git a/ipxe/Cargo.toml b/ipxe/Cargo.toml new file mode 100644 index 0000000..b5e2434 --- /dev/null +++ b/ipxe/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "ipxe" +version = "0.1.0" +edition = "2024" + +[build-dependencies] +diffy = "0.5.2" +gix = { version = "0.89.0", default-features = false, features = [ + "blocking-network-client", + "worktree-mutation", + "sha1", + "blocking-http-transport-reqwest-rust-tls", +] } + +[dependencies] +include_dir = "0.7.4" diff --git a/ipxe/build.rs b/ipxe/build.rs new file mode 100644 index 0000000..e00387b --- /dev/null +++ b/ipxe/build.rs @@ -0,0 +1,180 @@ +use std::collections::HashMap; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::sync::atomic::AtomicBool; + +use diffy::apply; +use diffy::patch_set::{FileOperation, ParseOptions, PatchSet}; +use gix::Repository; +use gix::progress::Discard; + +const URL: &str = "https://github.com/ipxe/ipxe"; +const REVISION: &str = "2fa08affe9a1a1422766f54b2fb045234b571f06"; +const FILES: &[&str] = &["bin/ipxe.pxe", "bin-x86_64-efi/ipxe.efi"]; + +const SRC_DIR: &str = "src"; + +fn main() { + let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()) + .canonicalize() + .unwrap(); + let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()) + .canonicalize() + .unwrap(); + let repo_dir = out_dir.join("repo"); + + let repo = checkout(&out_dir, &repo_dir); + + apply_patches(&repo, &manifest_dir, &repo_dir); + println!("cargo::rerun-if-changed=patches"); + + build(&manifest_dir, &repo_dir); + println!("cargo::rerun-if-changed=src/embed.ipxe"); + + copy_files(&repo_dir, &out_dir); +} + +fn checkout(out_dir: &Path, repo_dir: &Path) -> Repository { + // Stamp file is used instead of comparing against HEAD to guard against incomplete clones + let stamp = out_dir.join("checkout.stamp"); + if std::fs::read_to_string(&stamp).is_ok_and(|stamp| stamp == REVISION) + && let Ok(repo) = gix::open(repo_dir) + { + return repo; + } + + // Cleanup any left-overs + std::fs::remove_file(&stamp).ok(); + std::fs::remove_dir_all(repo_dir).ok(); + + std::fs::create_dir_all(repo_dir).unwrap(); + + let mut prepare = gix::prepare_clone(URL, repo_dir) + .unwrap() + .with_revision(Some(REVISION)) + .unwrap(); + + let interrupt = AtomicBool::new(false); + let (mut checkout, _) = prepare.fetch_then_checkout(Discard, &interrupt).unwrap(); + let (repo, _) = checkout.main_worktree(Discard, &interrupt).unwrap(); + + std::fs::write(&stamp, REVISION).unwrap(); + + repo +} + +fn apply_patches(repo: &Repository, manifest_dir: &Path, repo_dir: &Path) { + let commit_id = gix::ObjectId::from_hex(REVISION.as_bytes()).unwrap(); + let tree = repo.find_commit(commit_id).unwrap().tree().unwrap(); + + // Get all the patches and sort them based on name + let mut patch_files: Vec = std::fs::read_dir(manifest_dir.join("patches")) + .unwrap() + .map(|e| e.unwrap().path()) + .filter(|e| e.extension().is_some_and(|e| e == "patch")) + .collect(); + patch_files.sort(); + + let mut patched = HashMap::new(); + for path in patch_files { + let text = std::fs::read_to_string(&path).unwrap(); + let patch_set = PatchSet::parse(&text, ParseOptions::gitdiff()); + + for patch in patch_set { + let patch = patch.unwrap(); + + match patch.operation().strip_prefix(1) { + FileOperation::Modify { original, modified } => { + assert_eq!(original, modified); + let path = original.to_string(); + + // Either get the current in-memory patched file, otherwise get the prisine file + let current = patched + .remove(&path) + .unwrap_or_else(|| pristine(&tree, &path)); + + let new = apply(¤t, patch.patch().as_text().unwrap()).unwrap(); + patched.insert(path, new); + } + _ => todo!(), + } + } + } + + // If the file was previously patched, but now isn't restore the pristine file + for path in modified_files(repo) { + if !patched.contains_key(&path) { + std::fs::write(repo_dir.join(&path), pristine(&tree, &path)).unwrap(); + } + } + + // Write all the patched files + for (path, content) in &patched { + let path = repo_dir.join(path); + let content = content.as_bytes(); + + // But only if the file is actually different from the one on disk + // This prevents changing the mtime unnecessarily + if std::fs::read(&path).ok().as_deref() != Some(content) { + std::fs::write(path, content).unwrap(); + } + } +} + +fn pristine(tree: &gix::Tree<'_>, path: &str) -> String { + let entry = tree.lookup_entry_by_path(path).unwrap().unwrap(); + + String::from_utf8(entry.object().unwrap().data.clone()).unwrap() +} + +fn modified_files(repo: &gix::Repository) -> Vec { + repo.status(Discard) + .unwrap() + .untracked_files(gix::status::UntrackedFiles::None) + .into_index_worktree_iter(Vec::::new()) + .unwrap() + .map(|item| item.unwrap().rela_path().to_string()) + .collect() +} + +fn build(manifest_dir: &Path, repo_dir: &Path) { + let src_dir = repo_dir.join(SRC_DIR); + + let mut command = Command::new("make"); + command + .current_dir(&src_dir) + .env_remove("DEBUG") + .env_remove("PROFILE") + .arg(format!( + "EMBED={}", + manifest_dir + .join("src") + .join("embed.ipxe") + .to_string_lossy() + )) + .args(FILES); + + if let Ok(flags) = std::env::var("CARGO_MAKEFLAGS") { + command.env("MAKEFLAGS", flags); + } + + let output = command.output().unwrap(); + + if !output.status.success() { + panic!("make failed:\n{}", String::from_utf8_lossy(&output.stderr)); + } +} + +fn copy_files(repo_dir: &Path, out_dir: &Path) { + let src_dir = repo_dir.join(SRC_DIR); + let bin_dir = out_dir.join("bin"); + + std::fs::create_dir_all(&bin_dir).unwrap(); + for file in FILES { + let file = Path::new(file); + let input = src_dir.join(file); + let output = bin_dir.join(file.file_name().unwrap()); + + std::fs::copy(input, output).unwrap(); + } +} diff --git a/ipxe/patches/.gitkeep b/ipxe/patches/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/ipxe/patches/0001_enable_protocols.patch b/ipxe/patches/0001_enable_protocols.patch new file mode 100644 index 0000000..39e3e21 --- /dev/null +++ b/ipxe/patches/0001_enable_protocols.patch @@ -0,0 +1,19 @@ +diff --git a/src/config/general.h b/src/config/general.h +index 43858dceb..54cb1b657 100644 +--- a/src/config/general.h ++++ b/src/config/general.h +@@ -60,10 +60,10 @@ FILE_SECBOOT ( PERMITTED ); + //#define HTTP_ENC_PEERDIST /* PeerDist content encoding */ + + /* Disable protocols not historically included in BIOS builds */ +-#if defined ( PLATFORM_pcbios ) +- #undef DOWNLOAD_PROTO_HTTPS +- #undef HTTP_AUTH_NTLM +-#endif ++// #if defined ( PLATFORM_pcbios ) ++// #undef DOWNLOAD_PROTO_HTTPS ++// #undef HTTP_AUTH_NTLM ++// #endif + + /***************************************************************************** + * diff --git a/ipxe/src/embed.ipxe b/ipxe/src/embed.ipxe new file mode 100644 index 0000000..4ff63af --- /dev/null +++ b/ipxe/src/embed.ipxe @@ -0,0 +1,4 @@ +#!ipxe + +dhcp +chain boot.ipxe diff --git a/ipxe/src/lib.rs b/ipxe/src/lib.rs new file mode 100644 index 0000000..a74d02b --- /dev/null +++ b/ipxe/src/lib.rs @@ -0,0 +1,3 @@ +use include_dir::{Dir, include_dir}; + +pub static IPXE_DIR: Dir<'_> = include_dir!("$OUT_DIR/bin"); diff --git a/src/boot.ipxe b/src/boot.ipxe new file mode 100644 index 0000000..e704798 --- /dev/null +++ b/src/boot.ipxe @@ -0,0 +1,19 @@ +#!ipxe +echo Starting ${serial} + +:start +goto node_${serial} || exit +# Default behavior (non install mode) is to exit iPXE script + +{% for cluster in clusters%} +{% for _, node in cluster.nodes|items %} +{%- if node.install.serial -%} +# {{ cluster.name }}/{{ node.hostname }} +:node_{{ node.install.serial }} +imgfree +kernel https://pxe.factory.talos.dev/image/{{ node.schematic }}/v{{ cluster.version.talos }}/kernel-{{ node.arch }} {{ node.kernelArgs|join(" ") }} {% if node.install.auto %}talos.config={{httpUrl}}/talos/{{cluster.name}}/{{node.hostname}}.yaml{% endif +%} +initrd https://pxe.factory.talos.dev/image/{{ node.schematic }}/v{{ cluster.version.talos }}/initramfs-{{ node.arch }}.xz +boot +{% endif %} +{% endfor %} +{% endfor %} diff --git a/src/cli.rs b/src/cli.rs index 4a8d76d..cfcf512 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,3 +1,4 @@ +use std::net::IpAddr; use std::path::PathBuf; use clap::{Args, Parser, Subcommand}; @@ -49,4 +50,11 @@ pub enum Commands { Generate, /// Generate completions for your current shell ShellCompletions, + /// Serve config files over tftp + Serve { + #[arg(short, long, default_value = "3000")] + port: u16, + #[arg(long, default_value = "0.0.0.0")] + ip: IpAddr, + }, } diff --git a/src/cluster.rs b/src/cluster.rs index bce3f70..7deeae8 100644 --- a/src/cluster.rs +++ b/src/cluster.rs @@ -1,18 +1,22 @@ +use std::collections::HashMap; use std::net::Ipv4Addr; use std::path::PathBuf; -use std::process::Command; use std::str::FromStr; +use async_walkdir::WalkDir; +use futures::StreamExt; +use futures::stream::FuturesUnordered; use minijinja::Environment; use optional_struct::{Applicable, optional_struct}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use walkdir::WalkDir; +use tokio::process::Command; use crate::environment::PathEnvironment; use crate::node::{Node, OptionalNodeDeserialize}; use crate::patch::Patches; -use crate::{get_configs_path, get_talos_path}; +use crate::talosconfig::TalosConfig; +use crate::{get_configs_path, get_talos_path, run_command}; #[optional_struct] #[derive(Debug, Deserialize, JsonSchema, Clone)] @@ -68,31 +72,35 @@ struct ClusterDeserialize { base: Base, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Clone)] #[serde(rename_all = "camelCase")] pub struct Cluster { pub(crate) name: String, pub(crate) version: Version, - nodes: Vec, + nodes: HashMap, cluster_env: ClusterEnv, pub(crate) control_plane_ip: Ipv4Addr, pub(crate) secrets_file: PathBuf, } impl Cluster { - pub(crate) fn get(cluster_name: &str, env: &Environment) -> Self { + pub(crate) async fn get(cluster_name: &str, env: &Environment<'static>) -> Self { let path = get_talos_path().join("clusters").join("default.yaml"); let default: OptionalClusterDeserialize = if path.exists() { - let content = std::fs::read_to_string(path).unwrap(); - serde_yaml::from_str(&content).unwrap() + let content = tokio::fs::read_to_string(path).await.unwrap(); + + // The deserialization makes a blocking web request + tokio::task::spawn_blocking(move || serde_yaml::from_str(&content).unwrap()) + .await + .unwrap() } else { Default::default() }; let mut path = get_talos_path().join("clusters").join(cluster_name); path.add_extension("yaml"); - let content = std::fs::read_to_string(path).unwrap(); + let content = tokio::fs::read_to_string(path).await.unwrap(); let mut cluster: OptionalClusterDeserialize = serde_yaml::from_str(&content).unwrap(); @@ -114,28 +122,32 @@ impl Cluster { let cluster = Self { name: cluster_name.into(), version: cluster.version, - nodes: vec![], + nodes: Default::default(), cluster_env: cluster.cluster_env, control_plane_ip: cluster.control_plane_ip, secrets_file, }; - let nodes = nodes - .iter() - .map(|name| Node::get(name, env, &cluster, &default, &base)) - .collect(); + let nodes = nodes.iter().map(|name| async { + let node = Node::get(name, env, &cluster, &default, &base).await; + (node.hostname().to_owned(), node) + }); + + let nodes = FuturesUnordered::from_iter(nodes).collect().await; Self { nodes, ..cluster } } - pub fn get_all() -> Vec { + pub async fn get_all() -> HashMap { let env = PathEnvironment::new_patches(); - WalkDir::new(get_talos_path().join("clusters")) - .into_iter() - .filter_map(|e| e.ok()) - .filter(|e| e.metadata().unwrap().is_file()) - .filter_map(|e| { + let names: Vec<_> = WalkDir::new(get_talos_path().join("clusters")) + .filter_map(|e| async { e.ok() }) + .filter(|e| { + let e = e.clone(); + async move { e.metadata().await.unwrap().is_file() } + }) + .filter_map(|e| async move { let mut path = PathBuf::from_str(&e.file_name().to_string_lossy()).unwrap(); path.set_extension(""); @@ -145,18 +157,35 @@ impl Cluster { return None; } - Some(Cluster::get(&name, &env)) + Some(name) }) .collect() + .await; + + // Get the clusters concurrently + FuturesUnordered::from_iter(names.iter().map(|name| { + let env = env.clone(); + let name = name.clone(); + + async move { + let cluster = Cluster::get(&name, &env).await; + + (name, cluster) + } + })) + .collect() + .await } - pub fn nodes(&self) -> &[Node] { - &self.nodes + pub fn nodes(&self) -> impl Iterator { + self.nodes.values() } - pub fn talosctl_gen_config_command(&self) -> Command { - let path = get_configs_path().join(&self.name).join("talosconfig"); + pub fn node(&self, node: &str) -> Option<&Node> { + self.nodes.get(node) + } + pub async fn talosconfig(&self) -> String { let mut command = Command::new("talosctl"); command.args([ "gen", @@ -168,41 +197,59 @@ impl Cluster { "--output-types", "talosconfig", "-o", - path.to_str().expect("Path should be valid utf-8"), + "-", ]); - command + run_command(command).await } - pub fn talosctl_add_endpoint_command(&self) -> Command { - let path = get_configs_path().join(&self.name).join("talosconfig"); + pub fn talosconfig_path(&self) -> PathBuf { + get_configs_path().join(&self.name).join("talosconfig") + } + async fn talosconfig_add_endpoint(&self) { let mut command = Command::new("talosctl"); command.args([ "config", "--talosconfig", - path.to_str().expect("Path should be valid utf-8"), + self.talosconfig_path() + .to_str() + .expect("Path should be valid utf-8"), "endpoint", &self.control_plane_ip.to_string(), ]); - command + run_command(command).await; } - pub fn talosctl_merge_command(&self) -> Command { - let cluster_path = get_configs_path().join(&self.name).join("talosconfig"); - let path = get_configs_path().join("talosconfig"); + pub async fn talosconfig_write(&self) { + let config = self.talosconfig().await; + let path = self.talosconfig_path(); + tokio::fs::create_dir_all(path.parent().expect("talosconfig should have parent")) + .await + .unwrap(); + + tokio::fs::write(path, config).await.unwrap(); + + self.talosconfig_add_endpoint().await + } + + pub async fn talosconfig_merge(&self) { let mut command = Command::new("talosctl"); command.args([ "config", "--talosconfig", - path.to_str().expect("Path should be valid utf-8"), + TalosConfig::merged_path() + .to_str() + .expect("Path should be valid utf-8"), "merge", - cluster_path.to_str().expect("Path should be valid utf-8"), + self.talosconfig_path() + .to_str() + .expect("Path should be valid utf-8"), ]); - command + run_command(command).await; } } diff --git a/src/environment.rs b/src/environment.rs index 904e36e..683123c 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -2,15 +2,17 @@ use std::net::Ipv4Addr; use std::ops::Deref; use std::path::{Path, PathBuf}; +use async_walkdir::WalkDir; +use futures::{Stream, StreamExt}; use minijinja::value::ViaDeserialize; use minijinja::{AutoEscape, Environment, path_loader}; -use walkdir::WalkDir; use crate::{get_configs_path, get_talos_path}; /// Transparent wrapper around minijinja::Environment that loads templates from a path and /// configures better defaults. It also implements IntoIter, making it possible to iterate over all /// the templates. +#[derive(Clone)] pub struct PathEnvironment<'a> { env: Environment<'a>, path: PathBuf, @@ -23,46 +25,50 @@ fn apply_netmask( Ok((*ip & *netmask).to_string()) } +pub fn configure_environment(env: &mut Environment) { + // Configure jinja + env.set_trim_blocks(true); + env.set_lstrip_blocks(true); + env.set_undefined_behavior(minijinja::UndefinedBehavior::Strict); + env.set_auto_escape_callback(|_| AutoEscape::None); + + // Add filters + env.add_filter("to_prefix", |netmask: ViaDeserialize| { + let mask = netmask.to_bits(); + let prefix = mask.leading_ones(); + + if mask.checked_shl(prefix).unwrap_or(0) == 0 { + Ok(prefix as u8) + } else { + Err(minijinja::Error::new( + minijinja::ErrorKind::InvalidOperation, + "invalid IP prefix length", + )) + } + }); + + env.add_function("apply_netmask", apply_netmask); + + // Helper function for getting the path to kubeconfig files + env.add_filter("kubeconfig", move |names: Vec| { + names + .iter() + .map(|name| { + get_configs_path() + .join(name) + .join("kubeconfig") + .to_string_lossy() + .to_string() + }) + .collect::>() + }); +} + impl<'a> PathEnvironment<'a> { pub fn new(path: &Path) -> Self { let mut env = Environment::new(); - // Configure jinja - env.set_trim_blocks(true); - env.set_lstrip_blocks(true); - env.set_undefined_behavior(minijinja::UndefinedBehavior::Strict); - env.set_auto_escape_callback(|_| AutoEscape::None); - - // Add filters - env.add_filter("to_prefix", |netmask: ViaDeserialize| { - let mask = netmask.to_bits(); - let prefix = mask.leading_ones(); - - if mask.checked_shl(prefix).unwrap_or(0) == 0 { - Ok(prefix as u8) - } else { - Err(minijinja::Error::new( - minijinja::ErrorKind::InvalidOperation, - "invalid IP prefix length", - )) - } - }); - - env.add_function("apply_netmask", apply_netmask); - - // Helper function for getting the path to kubeconfig files - env.add_filter("kubeconfig", move |names: Vec| { - names - .iter() - .map(|name| { - get_configs_path() - .join(name) - .join("kubeconfig") - .to_string_lossy() - .to_string() - }) - .collect::>() - }); + configure_environment(&mut env); // Add path loader env.set_loader(path_loader(&path)); @@ -76,6 +82,23 @@ impl<'a> PathEnvironment<'a> { pub(crate) fn new_patches() -> Self { Self::new(&get_talos_path().join("patches")) } + + pub fn files(&self) -> impl Stream { + // Find all templates in path + WalkDir::new(&self.path) + .filter_map(|e| async { e.ok() }) + .filter(|e| { + let e = e.clone(); + async move { e.metadata().await.unwrap().is_file() } + }) + .map(move |e| { + e.path() + .strip_prefix(&self.path) + .expect("All paths should have prefix") + .to_string_lossy() + .to_string() + }) + } } // Make PathEnvironment act like a normal Environment transparently @@ -86,25 +109,3 @@ impl<'a> Deref for PathEnvironment<'a> { &self.env } } - -// Iterate over all the files in the path -impl<'a, 'b> IntoIterator for &'b PathEnvironment<'a> { - type Item = String; - - type IntoIter = impl Iterator; - - fn into_iter(self) -> Self::IntoIter { - // Find all templates in path - WalkDir::new(&self.path) - .into_iter() - .filter_map(|e| e.ok()) - .filter(|e| e.metadata().unwrap().is_file()) - .map(|e| { - e.path() - .strip_prefix(&self.path) - .expect("All paths should have prefix") - .to_string_lossy() - .to_string() - }) - } -} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..05be665 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,10 @@ +use crete::tftp::ServerError; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error("No clusters where found")] + NoClustersFound, + #[error("Server error: {0}")] + ServerError(#[from] ServerError), +} diff --git a/src/file.rs b/src/file.rs new file mode 100644 index 0000000..3860acc --- /dev/null +++ b/src/file.rs @@ -0,0 +1,55 @@ +use crete::cluster::Cluster; +use crete::environment::configure_environment; +use ipxe::IPXE_DIR; +use minijinja::{Environment, context}; +use regex::regex; + +pub async fn get_file( + filename: impl ToString, + http_url: impl ToString, +) -> Option<(Vec, &'static str)> { + let re = regex!(r"^talos/(?.+)/(?.+)\.ya?ml$"); + + let filename: String = filename.to_string(); + let re = re.clone(); + + const IPXE_PATTERN: &str = "ipxe/"; + if filename == format!("{IPXE_PATTERN}boot.ipxe") { + let mut env = Environment::new(); + + configure_environment(&mut env); + + let template = env + .template_from_str(include_str!("./boot.ipxe")) + .expect("Template should be valid"); + + let clusters = Cluster::get_all().await; + let content = template + .render(context! { + clusters => clusters.values().collect::>(), + httpUrl => http_url.to_string() + }) + .unwrap(); + + Some((content.into(), "text/plain; charset=utf-8")) + } else if let Some(filename) = filename.strip_prefix(IPXE_PATTERN) { + Some(( + IPXE_DIR.get_file(filename)?.contents().to_owned(), + "application/octet-stream", + )) + } else if let Some(caps) = re.captures(&filename) { + let cluster = &caps["cluster"]; + let node = &caps["node"]; + + let clusters = Cluster::get_all().await; + let cluster = clusters.get(cluster)?; + + let node = cluster.node(node)?; + + let config = node.config(cluster).await; + + Some((config.into(), "application/yaml; charset=utf-8")) + } else { + None + } +} diff --git a/src/http.rs b/src/http.rs new file mode 100644 index 0000000..5d725f1 --- /dev/null +++ b/src/http.rs @@ -0,0 +1,46 @@ +use std::path::PathBuf; + +use axum::debug_handler; +use axum::extract::Request; +use axum::http::Uri; +use axum::response::IntoResponse; +use reqwest::{StatusCode, header}; + +use crate::file::get_file; + +#[debug_handler] +pub async fn handler(req: Request) -> impl IntoResponse { + let uri: Uri = req + .headers() + .iter() + .find(|(name, _)| header::HOST.eq(name)) + .map(|(_, value)| value) + .expect("Host header should exist") + .to_str() + .expect("Header value should be valid ASCII") + .parse() + .expect("Host should be valid Uri"); + + let host = uri.host().expect("Uri should have host"); + let port = uri.port().map(|port| port.as_u16()).unwrap_or(80); + + let Some(path) = req.uri().path().strip_prefix("/") else { + return StatusCode::BAD_REQUEST.into_response(); + }; + + let Some((file, content_type)) = get_file(path, format!("http://{host}:{port}")).await else { + return StatusCode::NOT_FOUND.into_response(); + }; + + let path = PathBuf::from(path); + let filename = path.file_name().unwrap(); + let filename = filename.to_str().unwrap(); + + let content_type = [(header::CONTENT_TYPE, content_type)]; + let disposition = [( + header::CONTENT_DISPOSITION, + format!("inline; filename=\"{filename}\""), + )]; + + (content_type, disposition, file).into_response() +} diff --git a/src/lib.rs b/src/lib.rs index 8b12a92..e8987b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,17 @@ -#![feature(impl_trait_in_assoc_type, path_absolute_method)] +#![feature(path_absolute_method)] use std::path::{Path, PathBuf}; use std::sync::OnceLock; +use tokio::process::Command; + pub mod cluster; pub mod environment; pub mod node; pub mod patch; pub mod schematic; pub mod secret; +pub mod talosconfig; +pub mod tftp; pub(crate) static REPO_PATH: OnceLock = OnceLock::new(); @@ -28,3 +32,14 @@ pub fn get_talos_path() -> PathBuf { pub fn get_configs_path() -> PathBuf { get_repo_path().join("configs") } + +pub async fn run_command(mut command: Command) -> String { + let output = command.output().await.unwrap(); + if !output.stderr.is_empty() { + println!("{}", String::from_utf8_lossy(&output.stderr).trim_end()); + } + + String::from_utf8_lossy(&output.stdout) + .trim_end() + .to_owned() +} diff --git a/src/main.rs b/src/main.rs index b69af7f..03627bd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,95 +1,126 @@ mod cli; +mod error; +mod file; +mod http; -use std::process::Command; +use std::net::SocketAddr; +use std::pin::pin; +use axum::Router; +use axum::routing::get; use clap::{CommandFactory, Parser}; use clap_complete::{Shell, generate as generate_complete}; use crete::cluster::Cluster; use crete::environment::PathEnvironment; -use crete::{get_configs_path, get_repo_path, set_repo_path}; +use crete::talosconfig::{get_current_context, set_context}; +use crete::{get_configs_path, get_repo_path, set_repo_path, tftp}; +use futures::StreamExt; +use futures::future::join_all; use minijinja::context; -use thiserror::Error; use crate::cli::{Cli, Commands, GlobalOpts}; +use crate::error::Error; +use crate::file::get_file; +use crate::http::handler; -#[derive(Debug, Error)] -enum Error { - #[error("No clusters where found")] - NoClustersFound, -} - -fn run_command(mut command: Command) { - let output = command.output().unwrap(); - if !output.stdout.is_empty() { - println!("{}", String::from_utf8_lossy(&output.stdout).trim_end()); - } - if !output.stderr.is_empty() { - println!("{}", String::from_utf8_lossy(&output.stderr).trim_end()); - } -} - -fn generate(opts: &GlobalOpts) -> Result<(), Error> { - set_repo_path(&opts.repo); - - let clusters = Cluster::get_all(); +async fn generate(opts: &GlobalOpts) -> Result<(), Error> { + let clusters = Cluster::get_all().await; if clusters.is_empty() { return Err(Error::NoClustersFound); } let path = opts.repo.join("rendered"); - if path.exists() { - std::fs::remove_dir_all(&path).unwrap(); - } - std::fs::create_dir(&path).unwrap(); + // We don't really care if this fails, as that likely mean that the directory did not exist + tokio::fs::remove_dir_all(&path).await.ok(); + tokio::fs::create_dir_all(&path).await.unwrap(); // Render templates let template_env = PathEnvironment::new(&get_repo_path().join("templates")); - for template_name in &template_env { + + let mut stream = pin!(template_env.files()); + while let Some(template_name) = stream.next().await { let template = template_env.get_template(&template_name).unwrap(); let content = template .render(context! { - clusters, + clusters => clusters.values().collect::>(), root => opts.repo }) .unwrap(); - std::fs::write(path.join(template_name), content).unwrap(); + tokio::fs::write(path.join(template_name), content) + .await + .unwrap(); } + let context = get_current_context().await; + // Remove existing config files and create output directory let path = get_configs_path(); - if path.exists() { - std::fs::remove_dir_all(&path).unwrap(); - } - std::fs::create_dir(&path).unwrap(); + tokio::fs::remove_dir_all(&path).await.ok(); + tokio::fs::create_dir_all(&path).await.unwrap(); // Generate config files - for cluster in clusters { - for node in cluster.nodes() { - run_command(node.talosctl_gen_config_command(&cluster)); - } + join_all(clusters.values().map(|cluster| async move { + join_all(cluster.nodes().map(|node| { + let cluster = cluster.clone(); + async move { + node.config_write(&cluster).await; + } + })) + .await; - run_command(cluster.talosctl_gen_config_command()); - run_command(cluster.talosctl_add_endpoint_command()); - run_command(cluster.talosctl_merge_command()); + // Run these commands in parallel with the the node commands + // NOTE: These do need to happen in the correct order + cluster.talosconfig_write().await; + })) + .await; + + // Merge all the cluster talosconfig files into a single file + // Since this all modifies the same output file this has to happen sequentially + for (_, cluster) in clusters { + cluster.talosconfig_merge().await; + } + + if let Some(context) = context { + set_context(context).await; } Ok(()) } -fn main() -> Result<(), Error> { +#[tokio::main] +async fn main() -> Result<(), Error> { let cli = Cli::parse(); + set_repo_path(&cli.global_opts.repo); + match cli.command { - Commands::Generate => generate(&cli.global_opts)?, + Commands::Generate => generate(&cli.global_opts).await?, Commands::ShellCompletions => generate_complete( Shell::from_env().unwrap_or(Shell::Bash), &mut Cli::command(), "crete", &mut std::io::stdout(), ), + Commands::Serve { port, ip } => { + tokio::spawn(async move { + let app = Router::new().fallback_service(get(handler)); + let addr: SocketAddr = (ip, port).into(); + let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); + + println!("Http server started on {addr}"); + axum::serve(listener, app).await.unwrap(); + }); + + tftp::serve(ip, move |filename, ip| async move { + get_file(filename, format!("http://{ip}:{port}")) + .await + .map(|(file, _)| file) + }) + .await?; + } }; Ok(()) diff --git a/src/node.rs b/src/node.rs index ab5c166..14ea4fe 100644 --- a/src/node.rs +++ b/src/node.rs @@ -1,16 +1,17 @@ use std::net::Ipv4Addr; -use std::process::Command; +use std::path::PathBuf; use minijinja::Environment; use optional_struct::{Applicable, optional_struct}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use tokio::process::Command; use crate::cluster::{Base, Cluster}; use crate::patch::{OptionalPatches, OptionalPatchesString, Patches}; use crate::schematic::Schematic; use crate::secret::Secret; -use crate::{get_configs_path, get_talos_path}; +use crate::{get_configs_path, get_talos_path, run_command}; #[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, Copy, PartialEq, Eq)] #[serde(rename_all = "camelCase")] @@ -90,7 +91,7 @@ pub(crate) struct NodeDeserialize { sops: Secret, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Clone)] #[serde(rename_all = "camelCase")] pub struct Node { hostname: String, @@ -106,9 +107,9 @@ pub struct Node { } impl Node { - pub(crate) fn get( + pub(crate) async fn get( node_name: &str, - env: &Environment, + env: &Environment<'static>, cluster: &Cluster, default: &OptionalNodeDeserialize, base: &Base, @@ -121,7 +122,7 @@ impl Node { .to_string(); path.add_extension("yaml"); - let content = std::fs::read_to_string(path).unwrap(); + let content = tokio::fs::read_to_string(path).await.unwrap(); let node: OptionalNodeDeserialize = serde_yaml::from_str(&content).unwrap(); @@ -172,15 +173,16 @@ impl Node { }; // Render patches - let patches = patches.render(env, cluster, &node); + let patches = patches.render(env, cluster, &node).await; Node { patches, ..node } } - pub fn talosctl_gen_config_command(&self, cluster: &Cluster) -> Command { - let mut path = get_configs_path().join(&cluster.name).join(&self.hostname); - path.add_extension("yaml"); + pub fn hostname(&self) -> &str { + &self.hostname + } + pub async fn config(&self, cluster: &Cluster) -> String { let mut command = Command::new("talosctl"); command.args([ "gen", @@ -204,7 +206,7 @@ impl Node { "--with-docs=false", "--with-examples=false", "-o", - path.to_str().expect("Path should be valid utf-8"), + "-", ]); for patch in &self.patches.all { @@ -218,7 +220,25 @@ impl Node { ]); } - command + run_command(command).await + } + + pub fn config_path(&self, cluster: &Cluster) -> PathBuf { + let mut path = get_configs_path().join(&cluster.name).join(&self.hostname); + path.add_extension("yaml"); + + path + } + + pub async fn config_write(&self, cluster: &Cluster) { + let config = self.config(cluster).await; + + let path = self.config_path(cluster); + tokio::fs::create_dir_all(path.parent().expect("config should have parent")) + .await + .unwrap(); + + tokio::fs::write(path, config).await.unwrap(); } } diff --git a/src/patch.rs b/src/patch.rs index 4121bdb..a0b939f 100644 --- a/src/patch.rs +++ b/src/patch.rs @@ -16,27 +16,36 @@ pub(crate) struct Patches { pub(crate) type OptionalPatchesString = OptionalPatches; -fn render( +async fn render( patches: Vec, - env: &Environment, + env: &Environment<'static>, cluster: &Cluster, node: &Node, ) -> Vec { - patches - .into_iter() - .map(|name| { - let content = env - .get_template(&name) - .unwrap() - .render(context! { - node, - cluster - }) - .unwrap(); + let env = env.clone(); + let cluster = cluster.clone(); + let node = node.clone(); - serde_yaml::from_str(&content).unwrap() - }) - .collect() + // Minijinja will still have blocking I/O internally + tokio::task::spawn_blocking(move || { + patches + .into_iter() + .map(move |name| { + let content = env + .get_template(&name) + .unwrap() + .render(context! { + node, + cluster + }) + .unwrap(); + + serde_yaml::from_str(&content).unwrap() + }) + .collect() + }) + .await + .unwrap() } impl Patches { @@ -50,15 +59,15 @@ impl Patches { } } - pub(crate) fn render( + pub(crate) async fn render( self, - env: &Environment, + env: &Environment<'static>, cluster: &Cluster, node: &Node, ) -> Patches { Patches { - all: render(self.all.clone(), env, cluster, node), - control_plane: render(self.control_plane.clone(), env, cluster, node), + all: render(self.all.clone(), env, cluster, node).await, + control_plane: render(self.control_plane.clone(), env, cluster, node).await, } } } diff --git a/src/schematic.rs b/src/schematic.rs index e6f35d7..794f4b1 100644 --- a/src/schematic.rs +++ b/src/schematic.rs @@ -1,5 +1,6 @@ use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize}; +use tokio::runtime::Handle; use crate::get_talos_path; @@ -11,7 +12,7 @@ where let path = get_talos_path().join("schematics").join(name); let content = std::fs::read_to_string(path).unwrap().trim().to_owned(); - let client = reqwest::blocking::ClientBuilder::new() + let client = reqwest::ClientBuilder::new() .user_agent(format!( "{}/{}", std::env!("CARGO_PKG_NAME"), @@ -20,19 +21,29 @@ where .build() .unwrap(); - let res = client - .post("https://factory.talos.dev/schematics") - .body(content) - .send() - .map_err(serde::de::Error::custom)?; - #[derive(Debug, Deserialize)] struct Response { id: String, } - let response: Response = serde_json::from_str(&res.text().map_err(serde::de::Error::custom)?) - .map_err(serde::de::Error::custom)?; + // We can't use async directly inside a custom deserializer, and using the blocking client also + // does not work as it will try to start tokio inside of tokio. + // So we have do this to work around it. + // NOTE: This requires that the deserialization happens inside spawn_blocking + let response = Handle::current().block_on(async { + let res = client + .post("https://factory.talos.dev/schematics") + .body(content) + .send() + .await + .map_err(serde::de::Error::custom)?; + + let response: Response = + serde_json::from_str(&res.text().await.map_err(serde::de::Error::custom)?) + .map_err(serde::de::Error::custom)?; + + Ok(response) + })?; Ok(response.id) } diff --git a/src/talosconfig.rs b/src/talosconfig.rs new file mode 100644 index 0000000..d59c20f --- /dev/null +++ b/src/talosconfig.rs @@ -0,0 +1,40 @@ +use std::path::PathBuf; + +use serde::Deserialize; +use tokio::process::Command; + +use crate::{get_configs_path, run_command}; + +#[derive(Debug, Deserialize)] +pub struct TalosConfig { + context: String, +} + +impl TalosConfig { + pub fn merged_path() -> PathBuf { + get_configs_path().join("talosconfig") + } +} + +pub async fn get_current_context() -> Option { + tokio::fs::read(&TalosConfig::merged_path()) + .await + .ok() + .and_then(|config| serde_yaml::from_slice(&config).ok()) + .map(|config: TalosConfig| config.context) +} + +pub async fn set_context(context: String) { + let mut command = Command::new("talosctl"); + command.args([ + "config", + "--talosconfig", + TalosConfig::merged_path() + .to_str() + .expect("Path should be valid utf-8"), + "context", + &context, + ]); + + run_command(command).await; +} diff --git a/src/tftp/error.rs b/src/tftp/error.rs new file mode 100644 index 0000000..8fafc2d --- /dev/null +++ b/src/tftp/error.rs @@ -0,0 +1,20 @@ +use std::io; +use std::string::FromUtf8Error; + +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error("Invalid op code '{0}'")] + InvalidOpCode(u16), + #[error("Invalid error code '{0}'")] + InvalidErrorCode(u16), + #[error("Invalid mode '{0}'")] + InvalidMode(String), + #[error("String contains invalid characters")] + InvalidString(#[from] FromUtf8Error), + #[error("Unterminated string")] + UnterminatedString, + #[error("Io error: {0}")] + Io(#[from] io::Error), +} diff --git a/src/tftp/mod.rs b/src/tftp/mod.rs new file mode 100644 index 0000000..8eab9f3 --- /dev/null +++ b/src/tftp/mod.rs @@ -0,0 +1,7 @@ +mod error; +mod packet; +mod server; + +pub use error::Error; +pub use packet::Packet; +pub use server::{Error as ServerError, serve}; diff --git a/src/tftp/packet.rs b/src/tftp/packet.rs new file mode 100644 index 0000000..1461dc1 --- /dev/null +++ b/src/tftp/packet.rs @@ -0,0 +1,355 @@ +use std::io::{BufRead, BufReader, BufWriter, Cursor, Read, Write}; +use std::net::SocketAddr; + +use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use tokio::net::UdpSocket; + +use crate::tftp::Error; + +// 2 bytes op code + 2 bytes block number + max 512 bytes data +pub(crate) const PACKET_SIZE: usize = 2 + 2 + 512; + +fn decode_null_terminated(reader: &mut BufReader) -> Result +where + R: Read + ?Sized, +{ + let mut text = Vec::new(); + reader.read_until(0, &mut text)?; + + // Make sure we ended with a null terminator + match text.pop() { + Some(0) => {} + None | Some(_) => return Err(Error::UnterminatedString), + } + + Ok(String::from_utf8(text)?) +} + +fn encode_null_terminated(writer: &mut BufWriter, text: impl AsRef) -> Result<(), Error> +where + W: Write + ?Sized, +{ + let text = text.as_ref().as_bytes(); + writer.write_all(text)?; + + // Add null terminator + writer.write_u8(0)?; + + Ok(()) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Mode { + NetAscii, + Octet, + Mail, +} + +impl Mode { + fn decode(reader: &mut BufReader) -> Result + where + R: Read + ?Sized, + { + let mut mode = decode_null_terminated(reader)?; + mode.make_ascii_lowercase(); + let mode = mode; + + Ok(match mode.as_str() { + "netascii" => Self::NetAscii, + "octet" => Self::Octet, + "mail" => Self::Mail, + _ => return Err(Error::InvalidMode(mode)), + }) + } + + fn encode(&self, writer: &mut BufWriter) -> Result<(), Error> + where + W: Write + ?Sized, + { + let mode = match &self { + Mode::NetAscii => "netascii", + Mode::Octet => "octet", + Mode::Mail => "mail", + }; + + encode_null_terminated(writer, mode)?; + + Ok(()) + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Request { + filename: String, + mode: Mode, +} + +impl Request { + pub fn filename(&self) -> &str { + &self.filename + } + + fn decode(reader: &mut BufReader) -> Result + where + R: Read + ?Sized, + { + let filename = decode_null_terminated(reader)?; + let mode = Mode::decode(reader)?; + + Ok(Self { filename, mode }) + } + + fn encode(&self, writer: &mut BufWriter) -> Result<(), Error> + where + W: Write + ?Sized, + { + encode_null_terminated(writer, &self.filename)?; + self.mode.encode(writer)?; + + Ok(()) + } + + pub fn mode(&self) -> Mode { + self.mode + } +} + +#[derive(Debug)] +pub struct Data { + block: u16, + data: Vec, +} + +impl Data { + fn decode(reader: &mut BufReader) -> Result + where + R: Read + ?Sized, + { + let block = reader.read_u16::()?; + + let mut data = Vec::new(); + reader.read_to_end(&mut data)?; + + Ok(Self { data, block }) + } + + fn encode(&self, writer: &mut BufWriter) -> Result<(), Error> + where + W: Write + ?Sized, + { + writer.write_u16::(self.block)?; + writer.write_all(&self.data)?; + + Ok(()) + } +} + +#[derive(Debug)] +pub struct Ack(u16); + +impl Ack { + pub fn block(&self) -> u16 { + self.0 + } + + fn decode(reader: &mut BufReader) -> Result + where + R: Read + ?Sized, + { + let number = reader.read_u16::()?; + + Ok(Self(number)) + } + + fn encode(&self, writer: &mut BufWriter) -> Result<(), Error> + where + W: Write + ?Sized, + { + writer.write_u16::(self.0)?; + + Ok(()) + } +} + +#[derive(Debug, TryFromPrimitive, IntoPrimitive, Clone, Copy)] +#[repr(u16)] +pub enum ErrorCode { + NotDefined, + FileNotFound, + AccessViolation, + DiskFull, + Illegal, + UnknownTransferId, + FileAlreadyExists, + NoSuchUser, +} + +impl ErrorCode { + fn decode(reader: &mut BufReader) -> Result + where + R: Read + ?Sized, + { + let code = reader.read_u16::()?; + Self::try_from_primitive(code).map_err(|err| Error::InvalidErrorCode(err.number)) + } + + fn encode(&self, writer: &mut BufWriter) -> Result<(), Error> + where + W: Write + ?Sized, + { + let code: u16 = (*self).into(); + + writer.write_u16::(code)?; + + Ok(()) + } +} + +#[derive(Debug)] +pub struct ErrorMessage { + code: ErrorCode, + message: String, +} + +impl ErrorMessage { + fn decode(reader: &mut BufReader) -> Result + where + R: Read + ?Sized, + { + let code = ErrorCode::decode(reader)?; + let message = decode_null_terminated(reader)?; + + Ok(Self { code, message }) + } + + fn encode(&self, writer: &mut BufWriter) -> Result<(), Error> + where + W: Write + ?Sized, + { + self.code.encode(writer)?; + + encode_null_terminated(writer, &self.message)?; + + Ok(()) + } +} + +#[derive(Debug)] +pub enum Packet { + Read(Request), + Write(Request), + Data(Data), + Ack(Ack), + Error(ErrorMessage), +} + +impl Packet { + pub fn data(block: u16, data: &[u8]) -> Self { + Self::Data(Data { + block, + data: data.into(), + }) + } + + pub fn ack(block: u16) -> Self { + Self::Ack(Ack(block)) + } + + pub fn error(code: ErrorCode, message: impl Into) -> Self { + Self::Error(ErrorMessage { + code, + message: message.into(), + }) + } + + fn decode(reader: &mut BufReader) -> Result + where + R: Read + ?Sized, + { + let op_code = reader.read_u16::()?; + + Ok(match op_code { + 1 => Self::Read(Request::decode(reader)?), + 2 => Self::Write(Request::decode(reader)?), + 3 => Self::Data(Data::decode(reader)?), + 4 => Self::Ack(Ack::decode(reader)?), + 5 => Self::Error(ErrorMessage::decode(reader)?), + _ => return Err(Error::InvalidOpCode(op_code)), + }) + } + + fn encode(&self, writer: &mut BufWriter) -> Result<(), Error> + where + W: Write + ?Sized, + { + match &self { + Self::Read(inner) => { + writer.write_u16::(1)?; + inner.encode(writer)?; + } + Self::Write(inner) => { + writer.write_u16::(2)?; + inner.encode(writer)?; + } + Self::Data(inner) => { + writer.write_u16::(3)?; + inner.encode(writer)?; + } + Self::Ack(inner) => { + writer.write_u16::(4)?; + inner.encode(writer)?; + } + Self::Error(inner) => { + writer.write_u16::(5)?; + inner.encode(writer)?; + } + }; + + Ok(()) + } + + pub async fn recv_from(socket: &UdpSocket) -> Result<(Self, SocketAddr), Error> { + let mut buf = [0u8; PACKET_SIZE]; + let (_, src) = socket.recv_from(&mut buf).await?; + + let packet = buf.try_into()?; + + Ok((packet, src)) + } + + pub async fn send_to(&self, socket: &UdpSocket, src: &SocketAddr) -> Result<(), Error> { + let buf: Vec<_> = self.try_into()?; + socket.send_to(&buf, src).await?; + + Ok(()) + } +} + +impl TryFrom<&Packet> for Vec { + type Error = Error; + + fn try_from(packet: &Packet) -> Result { + let mut buf = Vec::with_capacity(PACKET_SIZE); + let mut writer = BufWriter::new(Cursor::new(&mut buf)); + packet.encode(&mut writer)?; + + // Normally the writer would get dropped after we return, but since it has a mutable + // borrow in buf we have to drop it first. + drop(writer); + + Ok(buf) + } +} + +impl TryFrom<[u8; PACKET_SIZE]> for Packet { + type Error = Error; + + fn try_from( + buf: [u8; PACKET_SIZE], + ) -> Result>::Error> { + let mut reader = BufReader::new(Cursor::new(buf)); + + Packet::decode(&mut reader) + } +} diff --git a/src/tftp/server.rs b/src/tftp/server.rs new file mode 100644 index 0000000..026c1eb --- /dev/null +++ b/src/tftp/server.rs @@ -0,0 +1,188 @@ +use std::io; +use std::net::{IpAddr, SocketAddr}; +use std::time::Duration; + +use socket_pktinfo::{PktInfo, PktInfoUdpSocket}; +use socket2::Domain; +use thiserror::Error; +use tokio::io::unix::AsyncFd; +use tokio::net::UdpSocket; + +use crate::tftp::packet::{ErrorCode, Mode, PACKET_SIZE}; +use crate::tftp::{self, Packet}; + +#[derive(Debug, Error)] +pub enum Error { + #[error("{0}")] + Tftp(#[from] tftp::Error), + #[error("Io error: {0}")] + Io(#[from] io::Error), + #[error("Unsupported operation: {0:#?}")] + UnsupportedOperation(Packet), + #[error("File not found: {0}")] + FileNotFound(String), + #[error("Unsupported mode: {0:?}")] + UnsupportedMode(Mode), + #[error("Source changed: {0}")] + SourceChanged(SocketAddr), + #[error("UnexpectedPacket: {0:#?}")] + UnexpectedPacket(Packet), + #[error("Timeout")] + Timeout, +} + +pub async fn handle_connection( + info: PktInfo, + packet: Packet, + callback: F, +) -> Result<(), Error> +where + F: Fn(String, IpAddr) -> Fut, + Fut: Future>>, +{ + let src = info.addr_src; + let socket = UdpSocket::bind((info.addr_dst, 0)).await?; + println!( + "{}: Connecting back from {}", + src, + socket.local_addr().expect("Local addr should be set") + ); + + let Packet::Read(request) = packet else { + Packet::error(ErrorCode::NotDefined, "Operation not supported") + .send_to(&socket, &info.addr_src) + .await?; + + return Err(Error::UnsupportedOperation(packet)); + }; + + let filename = request.filename(); + println!("{src}: {filename} [{:?}]", request.mode()); + + let Some(data) = callback(filename.to_string(), info.addr_dst).await else { + Packet::error(ErrorCode::FileNotFound, "Unknown file") + .send_to(&socket, &src) + .await?; + + return Err(Error::FileNotFound(filename.into())); + }; + + let mode = request.mode(); + let data = match mode { + // In case of netascii mode we need to re-encode to escape certain sequences + Mode::NetAscii => netascii::Netascii::from_bytes(data).collect(), + Mode::Octet => data, + _ => { + Packet::error( + ErrorCode::NotDefined, + format!("Mode {mode:?} is not supported"), + ) + .send_to(&socket, &src) + .await?; + + return Err(Error::UnsupportedMode(mode)); + } + }; + + let mut chunks = data.chunks(512).enumerate(); + let mut chunk = chunks.next(); + + let mut timeout = 0; + + loop { + // Send the first chunk + if let Some((index, chunk)) = chunk { + Packet::data((index + 1) as u16, chunk) + .send_to(&socket, &src) + .await?; + } + + tokio::select! { + recv = Packet::recv_from(&socket) => { + timeout = 0; + let (packet, new_src) = recv?; + + // I think this is might technically be possible and fine, but I don't + // really know how to test it properly so we don't support it. + if new_src != src { + Packet::error(ErrorCode::Illegal, "Source changed") + .send_to(&socket, &src) + .await?; + + return Err(Error::SourceChanged(new_src)); + } + + match packet { + Packet::Ack(ack) => { + // If we receive an ack for the current chunk go to the next one + if let Some((index, _)) = chunk + && (index + 1) as u16 == ack.block() + { + chunk = chunks.next(); + } + + if chunk.is_none() { + break; + } + } + _ => { + Packet::error(ErrorCode::Illegal, "Unexpected packet") + .send_to(&socket, &src) + .await?; + + + return Err(Error::UnexpectedPacket(packet)); + } + } + }, + _ = tokio::time::sleep(Duration::from_secs(1)) => { + timeout += 1; + + if timeout == 5 { + return Err(Error::Timeout); + } + } + } + } + + println!("{src}: Transmission complete"); + + Ok(()) +} + +pub async fn serve(ip: IpAddr, callback: F) -> Result +where + F: Fn(String, IpAddr) -> Fut + Clone + Send + 'static, + Fut: Future>> + Send + 'static, +{ + let socket = PktInfoUdpSocket::new(Domain::IPV4)?; + socket.set_nonblocking(true)?; + + let addr: SocketAddr = (ip, 69).into(); + socket.bind(&addr.into())?; + + let async_fd = AsyncFd::new(socket)?; + let mut buf = [0u8; PACKET_SIZE]; + + println!("Tftp server started on {addr}"); + loop { + let mut guard = async_fd.readable().await?; + + let (packet, info) = match guard.try_io(|inner| inner.get_ref().recv(&mut buf)) { + Ok(Ok((_, info))) => (buf.try_into()?, info), + Ok(Err(e)) => return Err(e)?, + // Socket was not ready + Err(_would_block) => continue, + }; + + let callback = callback.clone(); + let src = info.addr_src; + tokio::spawn({ + async move { + if let Err(err) = handle_connection(info, packet, callback).await { + println!("{src}: {err}") + } + } + }); + } +} diff --git a/typos.toml b/typos.toml new file mode 100644 index 0000000..8ee3a4b --- /dev/null +++ b/typos.toml @@ -0,0 +1,2 @@ +[default.extend-words] +rela = "rela"