diff --git a/.gitignore b/.gitignore index b2be92b..7d61988 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ result + + +# Added by cargo + +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f2ee9c4 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3099 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "arrayref" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-compression" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fec134f64e2bc57411226dfc4e52dec859ddfc7e711fc5e07b612584f000e4aa" +dependencies = [ + "brotli", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "zstd", + "zstd-safe", +] + +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "async-trait" +version = "0.1.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "axum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper 1.0.1", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 0.1.2", + "tower-layer", + "tower-service", +] + +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "blake3" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bstr" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +dependencies = [ + "memchr", + "regex-automata 0.4.7", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" + +[[package]] +name = "cc" +version = "1.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "clap" +version = "4.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.52.0", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enum-primitive-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba7795da175654fe16979af73f81f26a8ea27638d8d9823d317016888a63dc4c" +dependencies = [ + "num-traits", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "h2" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.5.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "handlebars" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5226a0e122dc74917f3a701484482bed3ee86d016c7356836abbaa033133a157" +dependencies = [ + "log", + "pest", + "pest_derive", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", +] + +[[package]] +name = "indicatif" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" +dependencies = [ + "console", + "futures-core", + "instant", + "number_prefix", + "portable-atomic", + "tokio", + "unicode-width", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "libmimalloc-sys" +version = "0.1.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23aa6811d3bd4deb8a84dde645f943476d13b248d818edcf8ce0b2f37f036b44" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mimalloc" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68914350ae34959d83f732418d51e2427a794055d0b9529f48259ac07af65633" +dependencies = [ + "libmimalloc-sys", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nix-compat" +version = "0.1.0" +source = "git+https://github.com/tvlfyi/tvix#10c6435ae9e11ed2fbc2fa96886cb4d17a060ac3" +dependencies = [ + "bitflags", + "bstr", + "bytes", + "data-encoding", + "ed25519", + "ed25519-dalek", + "enum-primitive-derive", + "glob", + "mimalloc", + "nix-compat-derive", + "nom", + "num-traits", + "pin-project-lite", + "serde", + "serde_json", + "sha2", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "nix-compat-derive" +version = "0.1.0" +source = "git+https://github.com/tvlfyi/tvix#10c6435ae9e11ed2fbc2fa96886cb4d17a060ac3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "nixos-channel-scripts" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-compression", + "blake3", + "bytes", + "chrono", + "clap", + "digest", + "futures", + "handlebars", + "hex", + "indicatif", + "nix-compat", + "object_store", + "opentelemetry", + "opentelemetry-otlp", + "opentelemetry_sdk", + "regex", + "reqwest", + "serde", + "serde_json", + "sha2", + "tempdir", + "termtree", + "textwrap", + "tokio", + "tokio-stream", + "tokio-util", + "toml", + "tracing", + "tracing-opentelemetry", + "tracing-subscriber", + "walkdir", + "zstd", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] +name = "object" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +dependencies = [ + "memchr", +] + +[[package]] +name = "object_store" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6da452820c715ce78221e8202ccc599b4a52f3e1eb3eedb487b680c81a8e3f3" +dependencies = [ + "async-trait", + "base64", + "bytes", + "chrono", + "futures", + "humantime", + "hyper", + "itertools", + "md-5", + "parking_lot", + "percent-encoding", + "quick-xml", + "rand 0.8.5", + "reqwest", + "ring", + "serde", + "serde_json", + "snafu", + "tokio", + "tracing", + "url", + "walkdir", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "openssl" +version = "0.10.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "opentelemetry" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c365a63eec4f55b7efeceb724f1336f26a9cf3427b70e59e2cd2a5b947fba96" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "once_cell", + "pin-project-lite", + "thiserror", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b925a602ffb916fb7421276b86756027b37ee708f9dce2dbdcc51739f07e727" +dependencies = [ + "async-trait", + "futures-core", + "http", + "opentelemetry", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost", + "thiserror", + "tokio", + "tonic", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ee9f20bff9c984511a02f082dc8ede839e4a9bf15cc2487c8d6fea5ad850d9" +dependencies = [ + "opentelemetry", + "opentelemetry_sdk", + "prost", + "tonic", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692eac490ec80f24a17828d49b40b60f5aeaccdfe6a503f939713afd22bc28df" +dependencies = [ + "async-trait", + "futures-channel", + "futures-executor", + "futures-util", + "glob", + "once_cell", + "opentelemetry", + "percent-encoding", + "rand 0.8.5", + "serde_json", + "thiserror", + "tokio", + "tokio-stream", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pest" +version = "2.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "pest_meta" +version = "2.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "portable-atomic" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2ecbe40f08db5c006b5764a2645f7f3f141ce756412ac9e1dd6087e6d32995" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "quick-xml" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a05e2e8efddfa51a84ca47cec303fac86c8541b686d37cac5efc0e094417bc" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quinn" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b22d8e7369034b9a7132bc2008cac12f2013c8132b45e0554e6e20e2617f2156" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba92fb39ec7ad06ca2582c0ca834dfeadcaf06ddfc8e635c80aa7e1c05315fdd" +dependencies = [ + "bytes", + "rand 0.8.5", + "ring", + "rustc-hash", + "rustls", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" +dependencies = [ + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "redox_syscall" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "reqwest" +version = "0.12.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +dependencies = [ + "async-compression", + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "windows-registry", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + +[[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 = "0.38.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.23.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +dependencies = [ + "base64", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" + +[[package]] +name = "rustls-webpki" +version = "0.102.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.209" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.209" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "serde_json" +version = "1.0.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "snafu" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" +dependencies = [ + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "strsim" +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 = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +dependencies = [ + "rand 0.4.6", + "remove_dir_all", +] + +[[package]] +name = "tempfile" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +dependencies = [ + "indexmap 2.5.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tonic" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f6ba989e4b2c58ae83d862d3a3e27690b6e3ae630d0deb59f3697f32aa88ad" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "socket2", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand 0.8.5", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-opentelemetry" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9784ed4da7d921bc8df6963f8c80a0e4ce34ba6ba76668acadd3edbd985ff3b" +dependencies = [ + "js-sys", + "once_cell", + "opentelemetry", + "opentelemetry_sdk", + "smallvec", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", + "web-time", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.77", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" + +[[package]] +name = "wasm-streams" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +dependencies = [ + "js-sys", + "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 = "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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +dependencies = [ + "memchr", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zstd" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.13+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..71543b8 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "nixos-channel-scripts" +version = "0.1.0" +edition = "2021" + +[dependencies] +clap = { version = "4.5.13", features = [ "derive" ] } +indicatif = { version = "0.17.8", features = ["futures", "tokio"] } +object_store = { version = "0.10.2", features = [ "aws" ] } +regex = "1.10.6" +reqwest = { version = "0.12.5", features = ["brotli"] } +serde = "1.0.204" +tempdir = "0.3.7" +textwrap = "0.16.1" +tokio = { version = "1.39.2", features = ["full"] } +toml = "0.8.19" +nix-compat = { git = "https://github.com/tvlfyi/tvix" } +futures = "0.3.30" +anyhow = "1.0.86" +bytes = "1.7.1" +tokio-util = { version = "0.7.11", features = ["io"] } +termtree = "0.5.1" +walkdir = "2.5.0" +handlebars = "6.0.0" +serde_json = "1.0.127" +tracing = { version = "0.1.40", features = ["max_level_debug", "release_max_level_info"] } +zstd = "0.13.2" +tokio-stream = "0.1.15" +async-compression = { version = "0.4.12", features = ["tokio", "zstd"] } +digest = "0.10.7" +blake3 = { version = "1.5.4", features = ["traits-preview"] } +sha2 = "0.10.8" +hex = "0.4.3" +chrono = "0.4.38" +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } +opentelemetry = "0.24.0" +opentelemetry_sdk = { version = "0.24.1", features = ["rt-tokio", "trace"] } +tracing-opentelemetry = { version = "0.25.0", features = ["metrics"] } +opentelemetry-otlp = "0.17.0" diff --git a/default.nix b/default.nix index b22e926..96c96ff 100644 --- a/default.nix +++ b/default.nix @@ -1,3 +1,11 @@ -(import (fetchTarball https://github.com/edolstra/flake-compat/archive/master.tar.gz) { - src = builtins.fetchGit ./.; -}).defaultNix +{ pkgs ? import {} }: +{ + shell = pkgs.mkShell { + buildInputs = [ + pkgs.cargo + pkgs.rustc + pkgs.openssl + pkgs.pkg-config + ]; + }; +} diff --git a/forkos.toml b/forkos.toml new file mode 100644 index 0000000..ec9e3e2 --- /dev/null +++ b/forkos.toml @@ -0,0 +1,6 @@ +hydra_uri = "https://hydra.forkos.org" +binary_cache_uri = "https://cache.forkos.org" +nixpkgs_dir = "/var/lib/nixpkgs" +s3_release_bucket_name = "bagel-channel-scripts-test" +s3_channel_bucket_name = "bagel-channel-scripts-test" +base_git_uri_for_revision = "https://cl.forkos.org/plugins/gitiles/nixpkgs/+" diff --git a/shell.nix b/shell.nix deleted file mode 100644 index db84e3d..0000000 --- a/shell.nix +++ /dev/null @@ -1,3 +0,0 @@ -(import (fetchTarball https://github.com/edolstra/flake-compat/archive/master.tar.gz) { - src = builtins.fetchGit ./.; -}).shellNix diff --git a/src/actions.rs b/src/actions.rs new file mode 100644 index 0000000..6ea71ca --- /dev/null +++ b/src/actions.rs @@ -0,0 +1,612 @@ +use std::collections::HashMap; +use std::path::PathBuf; +use std::str::FromStr; +use anyhow::Context; +use bytes::Bytes; +use digest::DynDigest; +use futures::future::join_all; +use futures::AsyncReadExt; +use sha2::Digest; +use tokio::io::{AsyncWrite, AsyncWriteExt}; +use futures::Stream; +use futures::TryStreamExt; +use indicatif::MultiProgress; +use indicatif::ProgressBar; +use indicatif::ProgressDrawTarget; +use indicatif::ProgressStyle; +use tracing::debug; +use object_store::aws::AmazonS3; +use object_store::WriteMultipart; +use tempdir::TempDir; +use tracing::trace; +use crate::html::ReleaseFileData; +use crate::html::ReleasePageData; +use crate::hydra; +use crate::config; + +const MULTIPART_CHUNK_SIZE: usize = 10 * 1024 * 1024; // 10 Mb +const ZSTD_COMPRESSION_LEVEL: i32 = 11; // Reasonable compression speed w.r.t. to the size of + // store-paths. + +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, clap::ValueEnum)] +pub enum Checksum { + Sha256, + Blake3 +} + +impl FromStr for Checksum { + type Err = anyhow::Error; + fn from_str(s: &str) -> Result { + Ok(match s { + "sha256" => Self::Sha256, + "blake3" => Self::Blake3, + _ => anyhow::bail!("unsupported checksum") + }) + } +} + +impl Checksum { + pub fn public_name(&self) -> &'static str { + match self { + Self::Sha256 => "SHA-256", + Self::Blake3 => "BLAKE-3" + } + } + + fn into_dyn_digest(&self) -> Box { + match self { + Self::Sha256 => Box::new(sha2::Sha256::new()), + Self::Blake3 => Box::new(blake3::Hasher::new()) + } + } + + fn extension(&self) -> &'static str { + match self { + Self::Sha256 => "sha256", + Self::Blake3 => "b3" + } + } +} + +#[derive(Debug, Copy, Clone)] +pub enum HydraProductType { + BrotliJson, + SourceDistribution, +} + +#[derive(Debug)] +pub enum Compression { + Zstd, + None +} + +impl Compression { + pub fn http_encoding(&self) -> Option<&str> { + Some(match self { + Self::Zstd => "zstd", + Self::None => return None, + }) + } + + pub fn file_extension(&self) -> Option<&str> { + Some(match self { + Self::Zstd => "zst", + Self::None => return None, + }) + } +} + +impl ToString for HydraProductType { + fn to_string(&self) -> String { + match self { + Self::BrotliJson => "json-br".to_string(), + Self::SourceDistribution => "source-dist".to_string(), + } + } +} + +#[derive(Debug)] +pub enum Action { + WriteFile { + dst_path: PathBuf, + contents: Vec + }, + WriteHydraProduct { + product_name: String, + dst_path: Option, + product_type: Option, + }, + WriteCompressedStorePaths { + compression: Compression + } +} + +#[derive(Debug)] +pub struct Plan { + pub actions: Vec, + pub release: hydra::Release, + pub channel: hydra::Channel, + pub target_bucket: AmazonS3, + pub revision: String +} + +#[derive(Debug)] +pub struct PlanOptions { + pub streaming: bool, + pub enable_progress_bar: bool, + pub requested_checksums: Vec +} + +macro_rules! plan_write_file { + ($plan_container:ident, $dst_path:literal, $contents:expr) => { + $plan_container.push(Action::WriteFile { + dst_path: $dst_path.into(), + contents: $contents + }); + } +} + +macro_rules! plan_write_hydra_product { + ($plan_container:ident, $pname:expr) => { + $plan_container.push(Action::WriteHydraProduct { + product_name: $pname.into(), + dst_path: None, + product_type: None + }); + }; + + ($plan_container:ident, $dst_path:literal, $pname:expr) => { + $plan_container.push(Action::WriteHydraProduct { + dst_path: Some($dst_path.into()), + product_name: $pname.into(), + product_type: None + }); + }; + + ($plan_container:ident, $dst_path:literal, $pname:expr, $ptype:expr) => { + $plan_container.push(Action::WriteHydraProduct { + dst_path: Some($dst_path.into()), + product_name: $pname.into(), + product_type: Some($ptype) + }); + }; +} + +macro_rules! plan_iso { + ($plan_container:ident, $iso_name:literal, $arch:literal) => { + plan_write_hydra_product!($plan_container, format!("nixos.iso_{}.{}", $iso_name, $arch)); + } +} + +#[tracing::instrument] +pub async fn generate_plan(config: &config::MirrorConfig, channel: &hydra::Channel, release: &hydra::Release, evaluation: &hydra::Evaluation) -> Vec { + let mut plan = vec![]; + + plan.push(Action::WriteCompressedStorePaths { + compression: Compression::Zstd + }); + + if let hydra::Input::Git { revision, .. } = evaluation.jobset_eval_inputs.get("nixpkgs").unwrap() { + plan_write_file!(plan, "src-url", release.evaluation_url(&config.hydra_uri).into()); + plan_write_file!(plan, "git-revision", revision.clone().into()); + plan_write_file!(plan, "binary-cache-url", config.binary_cache_uri.clone().into()); + + if matches!(channel.r#type(), hydra::ChannelType::NixOS) { + plan_write_hydra_product!(plan, "nixexprs.tar.xz", "nixos.channel"); + plan_write_hydra_product!(plan, "packages.json.br", "nixpkgs.tarball", + HydraProductType::BrotliJson + ); + plan_write_hydra_product!(plan, "options.json.br", "nixos.options", + HydraProductType::BrotliJson + ); + + // TODO: we don't have aarch64-linux yet + //plan_iso!(plan, "minimal", "aarch64-linux"); + //plan_iso!(plan, "minimal", "x86_64-linux"); + + if !matches!(channel.variant(), hydra::ChannelVariant::Small) { + // TODO: for some reason, those ISOs are not present in ForkOS hydra. + // TODO: implement error recovery, skipping or failure? + // maybe encode this in the plan (fallible, etc.) + // TODO: compat with nixos-20, 21, 22, 23 using plasma5? + // should we have plasma_latest? + //plan_iso!(plan, "plasma6", "aarch64-linux"); + //plan_iso!(plan, "plasma6", "x86_64-linux"); + + //plan_iso!(plan, "gnome", "aarch64-linux"); + //plan_iso!(plan, "gnome", "x86_64-linux"); + + // TODO: i686-linux ISO compat? for nixos ≤ 23.11 + + // FIXME: weird OVA appliance here, should investigate what to do + //plan_write_hydra_product!(plan, "nixos.ova.x86_64-linux"); + } + } else { + plan_write_hydra_product!(plan, "nixexprs.tar.xz", "tarball", + HydraProductType::SourceDistribution); + plan_write_hydra_product!(plan, "packages.json.br", "tarball", + HydraProductType::BrotliJson); + } + } + + // TODO: take into account the SQLite database & debuginfo + + plan +} + +#[derive(Debug)] +pub enum PlanInterpretation { + /// The plan has been streamed to the target object storage + /// It's ready to be applied by promoting atomically this temporary object store path. + Streamed { + remote_path: object_store::path::Path, + target_path: object_store::path::Path + }, + /// The plan has been prepared locally and can now be inspected or uploaded. + LocallyPlanned { + local_path: TempDir, + target_path: object_store::path::Path + } +} + +pub async fn stream_to_multipart_writer(mut input_stream: S, mut stream_callback: F, mut writer: WriteMultipart) -> std::io::Result where + S: Stream> + Unpin, + F: FnMut(&Bytes), +{ + let mut streamed = 0; + while let Some(bytes) = input_stream.try_next().await? { + streamed += bytes.len(); + stream_callback(&bytes); + writer.put(bytes); + } + + writer.finish().await?; + + Ok(streamed) +} + + +pub async fn stream_to_async_writer(mut input_stream: S, _size_hint: Option, mut stream_callback: F, mut target_file: W) -> std::io::Result where +S: Stream> + Unpin, +F: FnMut(&Bytes), +W: AsyncWrite + Unpin +{ + let mut streamed = 0; + while let Some(bytes) = input_stream.try_next().await? { + streamed += bytes.len(); + target_file.write_all(&bytes).await?; + stream_callback(&bytes); + } + + Ok(streamed) +} + +pub async fn stream_to_bucket(input_stream: S, size_hint: Option, target_path: &object_store::path::Path, mut stream_callback: F, bucket: &O) -> std::io::Result where +S: Stream> + Unpin, +F: FnMut(&Bytes), +O: object_store::ObjectStore +{ + let uses_multipart = if let Some(size) = size_hint { + size >= MULTIPART_CHUNK_SIZE + } else { true }; // Worst case is that the stream is bigger than memory. + + if uses_multipart { + // TODO: propagate error + let mpart = bucket.put_multipart(target_path).await.unwrap(); + let writer = WriteMultipart::new_with_chunk_size(mpart, MULTIPART_CHUNK_SIZE); + + stream_to_multipart_writer(input_stream, stream_callback, writer).await + } else { + let mut buffer = if let Some(size) = size_hint { + Vec::with_capacity(size) + } else { + // Constantify + Vec::with_capacity(8192) + }; + + // TODO: find a way to propagate the progress bar here. + // call the stream callback otherwise we cannot hash entire files. + input_stream.into_async_read().read_to_end(&mut buffer).await?; + let actual_length = buffer.len(); + // TODO: generalize the stream callback argument + // it's not that bad because it's usually very small here. + stream_callback(&Bytes::copy_from_slice(&buffer)); + + bucket.put(target_path, buffer.into()).await?; + + Ok(actual_length) + } +} + +pub async fn stream_to_target_nock(input_stream: S, + size_hint: Option, + key_prefix: &str, key: &str, + stream_callback: F, + bucket: &O, streaming: bool) -> std::io::Result where +S: Stream> + Unpin, +F: FnMut(&Bytes), +O: object_store::ObjectStore { + if streaming { + let bucket_path: object_store::path::Path = format!("{}/{}", key_prefix, key).into(); + stream_to_bucket(input_stream, size_hint, &bucket_path, stream_callback, bucket).await + } else { + let fs_path: PathBuf = PathBuf::from(key_prefix).join(key); + let tgt_file = tokio::fs::File::create(&fs_path).await?; + stream_to_async_writer(input_stream, size_hint, stream_callback, tgt_file).await + } +} + + +pub async fn stream_to_target(input_stream: S, + size_hint: Option, + key_prefix: &str, key: &str, + stream_callback: F, + bucket: &O, streaming: bool, + requested_checksums: Vec) -> std::io::Result<(usize, HashMap>)> where +S: Stream> + Unpin, +F: Fn(&Bytes), +O: object_store::ObjectStore { + let mut hashers: HashMap> = HashMap::from_iter( + requested_checksums.into_iter().map(|checksum| (checksum, checksum.into_dyn_digest())) + ); + + let copied = stream_to_target_nock(input_stream, size_hint, key_prefix, key, |bytes| { + hashers.iter_mut().for_each(|(_, hasher)| { + hasher.update(&bytes); + }); + + stream_callback(&bytes); + }, bucket, streaming).await?; + + // Stream all the checksums as well. + let keys: HashMap = hashers.iter().map(|(ck, _)| (ck.clone(), format!("{}.{}", key, ck.extension()))).collect(); + let checksums: HashMap> = hashers.into_iter().map(|(ck, hasher)| (ck, hasher.finalize())).collect(); + + join_all(checksums.iter().map(|(ck, digest)| { + let key = keys.get(ck).unwrap(); + let hexdigest = hex::encode(digest); + let ck_stream = tokio_stream::once( + Ok(Bytes::from_iter(hexdigest.into_bytes().into_iter())) + ); + + stream_to_target_nock(ck_stream, + Some(digest.as_ref().len()), + key_prefix, + key, + |bytes| { + trace!("streaming the checksum ({:?}); chunk size = {}", ck.clone(), bytes.len()); + }, bucket, streaming) + })).await; + + Ok((copied, checksums)) +} + +/// Interprets the plan as a list of action. +/// +/// It creates a staging directory where all actions are prepared. +/// No irreversible action is committed. +/// +/// At the end, it returns the staging directory for further post-processing, including possible +/// uploads. +/// +/// Enabling streaming will also directly stream the contents of the staging directory to the +/// target bucket in a temporary suffix of the target prefix. +/// Caller is responsible to promote that upload to a finalized upload. +#[tracing::instrument] +pub async fn interpret_plan(hydra_client: &hydra::HydraClient<'_>, plan: Plan, plan_opts: PlanOptions) -> PlanInterpretation { + // Prepare a staging directory + let staging_directory = TempDir::new(format!("staging-{}", &plan.channel.name).as_str()).expect("Failed to create temporary directory for staging directory"); + let staging_prefix = staging_directory.path().file_name() + .expect("Failed to obtain the final component of the staging directory") + .to_str() + .expect("Failed to convert the final component into string") + .to_owned(); + let streaming = plan_opts.streaming; + + let key_prefix = if streaming { + staging_prefix.to_string() + } else { + staging_directory.path().to_str().expect("Staging directory path is not valid UTF-8").to_owned() + }; + + if streaming { + debug!("Streaming the update to /{}", staging_prefix); + + } else { + debug!("Preparing locally the update in {}", staging_directory.path().display()); + } + + // Initialize multiple progress bars if we are to show progress bars. + let multi_pbar = MultiProgress::with_draw_target(if plan_opts.enable_progress_bar { + ProgressDrawTarget::stdout() + } else { + ProgressDrawTarget::hidden() + }); + + // Pending download/upload/streaming tasks. + let mut pending = Vec::new(); + + for action in plan.actions { + let task = async { + let pbar = ProgressBar::new(0); + multi_pbar.add(pbar.clone()); + pbar.set_style(ProgressStyle::with_template("[{elapsed_precise}] {bar:40.cyan/blue} {bytes:>7}/{total_bytes:>7} @ {bytes_per_sec:>7} {msg}").unwrap().progress_chars("##-")); + let requested_checksums = plan_opts.requested_checksums.clone(); + + match action { + Action::WriteHydraProduct { product_name, dst_path, product_type } => { + let dst_path = dst_path.unwrap_or_else(|| product_name.clone().into()); + assert!(dst_path.is_relative(), "Unexpected absolute path in a channel script plan, all paths needs to be relative in order for them to be prefixed by the staging directory"); + + let key = dst_path.to_str().expect("Failed to convert relative destination path to a UTF-8 key"); + let (size, stream) = hydra_client.hydra_product_bytes_stream(&plan.release, &product_name, product_type).await + .context("while preparing the stream for a Hydra product") + .expect("Failed to stream an Hydra product"); + + pbar.set_length(size); + if streaming { + pbar.set_message(format!("Downloading and streaming '{}'", product_name)); + } else { + pbar.set_message(format!("Copying '{}'", product_name)); + } + + let (copied, checksums) = stream_to_target(stream, Some(size as usize), &key_prefix, key, |bytes| { + // This is not really called when the multipart + // has completed pushing this part. + // In practice, if we are pushing as soon as we read, + // we expect the bytes chunk to be small + // and therefore, this is fairly reasonable. + trace!("multipart streaming: chunk size = {}", bytes.len()); + pbar.inc(bytes.len() as u64); + }, &plan.target_bucket, streaming, requested_checksums).await + .context( + format!("while streaming to the bucket the product {}", product_name)) + .unwrap(); + + pbar.finish_with_message(format!("Streamed '{}'", product_name)); + + debug!("Copied {} bytes", copied); + + return ReleaseFileData::new(key, size, checksums); + }, + Action::WriteFile { dst_path, contents } => { + // We don't need a progress bar here. + assert!(dst_path.is_relative(), "Unexpected absolute path in a channel script plan, all paths needs to be relative in order for them to be prefixed by the staging directory"); + let key = dst_path.to_str().expect("Failed to convert a relative path to a UTF-8 key"); + let size = contents.len(); + let stream = tokio_stream::once(Ok(Bytes::from_iter(contents.into_iter()))); + pbar.set_length(size as u64); + if streaming { + pbar.set_message(format!("Streaming '{}'", key)); + } else { + pbar.set_message(format!("Writing '{}'", key)); + } + let (copied, checksums) = stream_to_target(stream, Some(size), &key_prefix, key, |bytes| { + // This is not really called when the multipart + // has completed pushing this part. + // In practice, if we are pushing as soon as we read, + // we expect the bytes chunk to be small + // and therefore, this is fairly reasonable. + trace!("multipart streaming: chunk size = {}", bytes.len()); + pbar.inc(bytes.len() as u64); + }, &plan.target_bucket, streaming, requested_checksums).await + .context( + format!("while streaming to the bucket the following key '{}'", key) + ).unwrap(); + + pbar.finish_with_message(format!("Written '{}'", key)); + + debug!("Written {} bytes", copied); + + return ReleaseFileData::new(key, size as u64, checksums); + }, + Action::WriteCompressedStorePaths { compression } => { + let spaths = hydra_client.fetch_store_paths(&plan.release).await.context("while fetching store paths").expect("Failed to fetch store paths from Hydra"); + let mut serialized = serde_json::to_vec(&spaths).context("while serializing store paths to JSON").expect("failed to serialize"); + + let key; + + if matches!(compression, Compression::Zstd) { + let compressed_serialization = zstd::bulk::compress(&serialized, ZSTD_COMPRESSION_LEVEL).expect("Failed to compress the store-paths"); + let _ = std::mem::replace(&mut serialized, compressed_serialization); + key = "store-paths.zst"; + } else { + key = "store-paths"; + } + + let size = serialized.len(); + let stream = tokio_stream::once(Ok(Bytes::from_iter(serialized.into_iter()))); + + pbar.set_length(size as u64); + if streaming { + pbar.set_message("Streaming compressed store paths"); + } else { + pbar.set_message("Writing compressed store paths"); + } + + let (copied, checksums) = stream_to_target(stream, Some(size), &key_prefix, key, |bytes| { + // This is not really called when the multipart + // has completed pushing this part. + // In practice, if we are pushing as soon as we read, + // we expect the bytes chunk to be small + // and therefore, this is fairly reasonable. + trace!("multipart streaming: chunk size = {}", bytes.len()); + pbar.inc(bytes.len() as u64); + }, &plan.target_bucket, streaming, requested_checksums).await + .context( + format!("while streaming to the bucket the following key '{}'", key) + ).unwrap(); + + pbar.finish_with_message("Compressed store paths streamed"); + debug!("Written {} bytes", copied); + + return ReleaseFileData::new(key, size as u64, checksums); + }, + }; + }; + + pending.push(task); + } + + let files: Vec = join_all(pending).await; + let target_path = plan.release.prefix(&plan.channel); + let rendered_page = crate::html::render_release_page(ReleasePageData { + channel_name: plan.channel.name.clone(), + release_name: plan.release.nix_name.clone(), + release_date: chrono::Utc::now().format("%Y-%m-%d %H-%M UTC").to_string(), + git_commit_link: format!("{}/{}", hydra_client.config.base_git_uri_for_revision, plan.revision), + git_revision: plan.revision, + // TODO: mega evil, clean up and expose config properly. + hydra_eval_link: plan.release.evaluation_url(&hydra_client.config.hydra_uri), + hydra_eval_id: plan.release.evaluation_id(), + checksums: plan_opts.requested_checksums.iter().map(|ck| ck.public_name().to_string()).collect(), + files + }).expect("Failed to render a release page"); + + + let pbar = ProgressBar::new(0); + multi_pbar.add(pbar.clone()); + pbar.set_style(ProgressStyle::with_template("[{elapsed_precise}] {bar:40.cyan/blue} {bytes:>7}/{total_bytes:>7} @ {bytes_per_sec:>7} {msg}").unwrap().progress_chars("##-")); + let key = if streaming { + plan.release.nix_name + } else { + format!("{}.html", plan.release.nix_name) + }; + let contents = rendered_page.into_bytes(); + let size = contents.len(); + let stream = tokio_stream::once(Ok(Bytes::from_iter(contents.into_iter()))); + pbar.set_length(size as u64); + if streaming { + pbar.set_message("Streaming the release HTML page"); + } else { + pbar.set_message("Writing the release HTML page"); + } + let (copied, _) = stream_to_target(stream, Some(size), &key_prefix, &key, |bytes| { + // This is not really called when the multipart + // has completed pushing this part. + // In practice, if we are pushing as soon as we read, + // we expect the bytes chunk to be small + // and therefore, this is fairly reasonable. + trace!("multipart streaming: chunk size = {}", bytes.len()); + pbar.inc(bytes.len() as u64); + }, &plan.target_bucket, streaming, Vec::new()).await + .context( + format!("while streaming to the bucket the following key '{}'", key) + ).unwrap(); + + debug!("Release HTML page sent; copied {} bytes", copied); + pbar.finish_with_message("Release HTML page streamed"); + + if streaming { + staging_directory.close().expect("Failed to get rid of the staging directory"); + PlanInterpretation::Streamed { + remote_path: staging_prefix.into(), + target_path + } + } else { + PlanInterpretation::LocallyPlanned { + local_path: staging_directory, + target_path + } + } +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..7b15317 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,36 @@ +use object_store::aws::{AmazonS3, AmazonS3Builder}; +use serde::Deserialize; +use std::path::PathBuf; + +#[derive(Debug, Deserialize)] +pub struct MirrorConfig { + /// URI to Hydra instance + pub hydra_uri: String, + /// URI to the binary cache + pub binary_cache_uri: String, + /// Base URI to access to a web interface pertaining to a specific Git revision of the nixpkgs + /// input + pub base_git_uri_for_revision: String, + /// A path to a checkout of nixpkgs + nixpkgs_dir: PathBuf, + /// S3 releases bucket name + pub s3_release_bucket_name: String, + /// S3 channels bucket name + s3_channel_bucket_name: String, +} + +impl MirrorConfig { + pub fn release_bucket(&self) -> AmazonS3 { + AmazonS3Builder::from_env() + .with_bucket_name(&self.s3_release_bucket_name) + .build() + .expect("Failed to connect to the S3 release bucket") + } + + pub fn channel_bucket(&self) -> AmazonS3 { + AmazonS3Builder::from_env() + .with_bucket_name(&self.s3_channel_bucket_name) + .build() + .expect("Failed to connect to the S3 channel bucket") + } +} diff --git a/src/git.rs b/src/git.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/git.rs @@ -0,0 +1 @@ + diff --git a/src/html.rs b/src/html.rs new file mode 100644 index 0000000..92bddb9 --- /dev/null +++ b/src/html.rs @@ -0,0 +1,49 @@ +use std::collections::HashMap; + +use handlebars::Handlebars; +use indicatif::HumanBytes; +use serde::Serialize; + +use crate::actions::Checksum; + + +#[derive(Debug, Serialize)] +pub struct ReleaseFileData { + pub name: String, + pub size: String, + pub checksums: HashMap +} + +impl ReleaseFileData { + pub fn new(name: &str, size: u64, checksums: HashMap>) -> Self { + ReleaseFileData { + name: name.to_string(), + size: HumanBytes(size).to_string(), + checksums: checksums.into_iter().map(|(ck, digest)| { + (ck.public_name().to_string(), + hex::encode(digest)) + }).collect() + } + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ReleasePageData { + pub channel_name: String, + pub release_name: String, + pub release_date: String, + pub git_commit_link: String, + pub git_revision: String, + pub hydra_eval_link: String, + pub hydra_eval_id: u64, + pub files: Vec, + pub checksums: Vec +} + +pub fn render_release_page(page_data: ReleasePageData) -> Result { + let reg = Handlebars::new(); + + let release_template = include_str!("./release.tpl"); + reg.render_template(&release_template, &page_data) +} diff --git a/src/hydra.rs b/src/hydra.rs new file mode 100644 index 0000000..5a6aabd --- /dev/null +++ b/src/hydra.rs @@ -0,0 +1,338 @@ +use std::{collections::HashMap, path::PathBuf}; +use anyhow::Context; +use bytes::Bytes; +use futures::Stream; +use indicatif::ProgressBar; +use nix_compat::store_path::{StorePath, StorePathRef}; +use regex::Regex; + +use serde::{de::Error, Deserialize, Deserializer}; +use tokio::io::AsyncWrite; + +use crate::{actions::HydraProductType, config::MirrorConfig}; + +pub type ReleaseId = u64; +pub type EvaluationId = u64; +pub type BuildId = u64; + +fn deser_bool_as_int<'de, D>(deserializer: D) -> Result + where D: Deserializer<'de> +{ + u8::deserialize(deserializer).map(|b| if b == 1 { true } else { false }) +} + +fn deser_bool_as_string<'de, D>(deserializer: D) -> Result +where D: Deserializer<'de> +{ + let s: &str = Deserialize::deserialize(deserializer)?; + + match s { + "false" => Ok(false), + "true" => Ok(true), + _ => Err(Error::unknown_variant(s, &["false", "true"])) + } +} + +#[derive(Debug, Deserialize)] +pub struct OutputPath { + #[serde(rename="path")] + store_path: String +} + +#[derive(Debug, Deserialize)] +pub struct BuildProduct { + #[serde(rename="path")] + store_path: String, + name: String, + subtype: String, + sha256hash: String, + r#type: String, + #[serde(rename="filesize")] + file_size: u64 +} + +#[derive(Debug, Deserialize)] +pub struct BuildInfo { + jobset: String, + job: String, + project: String, + #[serde(deserialize_with = "deser_bool_as_int", rename="buildstatus")] + finished: bool, + #[serde(rename="releasename")] + release_name: Option, + #[serde(rename="stoptime")] + stop_time: u64, + #[serde(rename="starttime")] + start_time: u64, + system: String, + id: BuildId, + #[serde(rename="buildoutputs")] + build_outputs: HashMap, + #[serde(rename="buildproducts")] + build_products: HashMap, + #[serde(rename="nixname")] + nix_name: String, + #[serde(rename="drvpath")] + drv_path: String, +} + +#[derive(Debug, Clone)] +pub struct Channel { + pub name: String +} + +#[non_exhaustive] +pub enum ChannelType { + NixOS, + Nixpkgs, +} + +#[non_exhaustive] +pub enum ChannelVariant { + Normal, + Small +} + +impl Channel { + pub fn version(&self) -> String { + let re = Regex::new("([a-z]+)-(?.*)").unwrap(); + let caps = re.captures(&self.name).expect("Failed to parse the channel name"); + + caps["ver"].to_string() + } + + pub fn prefix(&self) -> String { + let re = Regex::new("(?[a-z]+)-(.*)").unwrap(); + let caps = re.captures(&self.name).expect("Failed to parse the channel name"); + + caps["name"].to_string() + } + + // TODO: just regex match? + pub fn r#type(&self) -> ChannelType { + ChannelType::NixOS + } + + pub fn variant(&self) -> ChannelVariant { + ChannelVariant::Normal + } +} + +#[derive(Deserialize, Debug)] +pub struct Release { + pub id: ReleaseId, + job: String, + #[serde(rename = "releasename")] + release_name: Option, + #[serde(rename = "starttime")] + start_time: u64, + #[serde(rename = "stoptime")] + stop_time: u64, + #[serde(rename = "nixname")] + pub nix_name: String, + #[serde(rename = "jobsetevals")] + jobset_evals: Vec, + jobset: String, + #[serde(deserialize_with = "deser_bool_as_int")] + finished: bool, + priority: u64, + system: String, + timestamp: u64, + project: String, + #[serde(rename = "drvpath")] + derivation_path: String, + // ignored: buildproducts, buildoutputs, buildmetrics, buildstatus +} + +#[derive(Debug)] +pub struct GitInput { + uri: String, + revision: String +} + +// FIXME(Raito): for some reason, #[serde(tag = "type"), rename_all = "lowercase"] doesn't behave +// correctly, and causes deserialization failures in practice. `untagged` is suboptimal but works +// because of the way responses works... +#[derive(Debug, Deserialize)] +#[serde(untagged, expecting = "An valid jobset input")] +pub enum Input { + Boolean { + #[serde(deserialize_with = "deser_bool_as_string")] + value: bool + }, + Git { uri: String, revision: String }, + Nix { value: String }, +} + +#[derive(Deserialize, Debug)] +pub struct Evaluation { + pub id: EvaluationId, + #[serde(rename="checkouttime")] + checkout_time: u64, + #[serde(rename="evaltime")] + eval_time: u64, + flake: Option, + #[serde(rename="jobsetevalinputs", default)] + pub jobset_eval_inputs: HashMap, + timestamp: u64, + builds: Vec, +} + +impl Release { + pub fn version(&self) -> String { + let re = Regex::new(".+-(?[0-9].+)").unwrap(); + let caps = re.captures(&self.nix_name).expect("Failed to parse the release name"); + + caps["ver"].to_string() + } + + pub fn evaluation_id(&self) -> u64 { + *self.jobset_evals.first().expect("Failed to obtain the corresponding evaluation, malformed release?") + } + + pub fn evaluation_url(&self, hydra_base_uri: &str) -> String { + let eval_id = self.evaluation_id(); + format!("{}/eval/{}", hydra_base_uri, eval_id) + } + + pub fn job_url(&self, hydra_base_uri: &str, job_name: &str) -> String { + let eval_id = self.evaluation_id(); + format!("{}/eval/{}/job/{}", hydra_base_uri, eval_id, job_name) + } + + pub fn store_paths_url(&self, hydra_base_uri: &str) -> String { + let eval_id = self.evaluation_id(); + format!("{}/eval/{}/store-paths", hydra_base_uri, eval_id) + } + + /// Directory related to this release. + fn directory(&self, channel: &Channel) -> String { + match channel.name.as_str() { + "nixpkgs-unstable" => "nixpkgs".to_string(), + _ => format!("{}/{}", channel.prefix(), channel.version()) + } + } + + pub fn prefix(&self, channel: &Channel) -> object_store::path::Path { + format!("{}/{}", self.directory(channel), self.nix_name).into() + } +} + +pub fn release_uri(hydra_uri: &str, job_name: &str) -> String { + format!("{}/job/{}/latest", hydra_uri, job_name) +} + +#[derive(Debug)] +pub struct HydraClient<'a> { + pub config: &'a MirrorConfig, +} + +impl HydraClient<'_> { + pub async fn fetch_release(&self, job_name: &str) -> reqwest::Result { + let client = reqwest::Client::new(); + let resp = client.get(release_uri(&self.config.hydra_uri, job_name)) + .header("Accept", "application/json") + // TODO: put a proper version + .header("User-Agent", "nixos-channel-scripts (rust)") + .send() + .await?; + + resp.json().await + } + + pub async fn fetch_evaluation(&self, release: &Release) -> reqwest::Result { + let client = reqwest::Client::new(); + let resp = client.get(release.evaluation_url(&self.config.hydra_uri)) + .header("Accept", "application/json") + .header("User-Agent", "nixos-channel-scripts (rust)") + .send() + .await?; + + resp.json().await + } + + pub async fn fetch_store_paths(&self, release: &Release) -> anyhow::Result>> { + let client = reqwest::Client::new(); + + Ok(client.get(release.store_paths_url(&self.config.hydra_uri)) + .header("Accept", "application/json") + .header("User-Agent", "nixos-channel-scripts (rust)") + .send() + .await? + .json() + .await + .context(format!("while downloading store-paths information for an evaluation"))? + ) + } + + pub async fn hydra_product_bytes_stream(&self, release: &Release, job_name: &str, product_type: Option) -> anyhow::Result<(u64, impl Stream>)> { + let client = reqwest::Client::new(); + let build_info: BuildInfo = client.get(release.job_url(&self.config.hydra_uri, job_name)) + .header("Accept", "application/json") + .header("User-Agent", "nixos-channel-scripts (rust)") + .send() + .await? + .json() + .await + .context(format!("while downloading build information from {}", release.job_url(&self.config.hydra_uri, job_name)))?; + + let mut products_by_subtype: HashMap = HashMap::new(); + for (_, product) in build_info.build_products { + if products_by_subtype.contains_key(&product.subtype) { + todo!("Job {} has multiple products of the same subtype {:?}. This is not supported yet.", job_name, product_type); + } + + products_by_subtype.insert(product.subtype.clone(), product); + } + + if products_by_subtype.len() > 1 && product_type.is_none() { + panic!("Job {} has {} build products. Select the right product via the subtypes.", job_name, products_by_subtype.len()); + } + + let product: BuildProduct = if let Some(ptype) = product_type { + products_by_subtype.remove(&ptype.to_string()).expect(&format!("Expected product type '{}' but not found in the list of products", &ptype.to_string())) + } else { + products_by_subtype.into_iter().last().expect(&format!("Expected at least one build product in job {}, found zero", job_name)).1 + }; + + // 3. FIXME: validate sha256hash during read? + let (store_path, rel_path) = StorePathRef::from_absolute_path_full(&product.store_path).expect("Failed to parse the product's store path"); + crate::nar::file_in_nar_bytes_stream(&self.config.binary_cache_uri, &nix_compat::nixbase32::encode(store_path.digest()), rel_path.to_str().unwrap()).await.context("while copying the NAR to the target") + } + + pub async fn copy_hydra_product(&self, release: &Release, job_name: &str, product_type: Option, pbar: ProgressBar, out: &mut W) -> anyhow::Result { + // TODO: dry me? + let client = reqwest::Client::new(); + let build_info: BuildInfo = client.get(release.job_url(&self.config.hydra_uri, job_name)) + .header("Accept", "application/json") + .header("User-Agent", "nixos-channel-scripts (rust)") + .send() + .await? + .json() + .await + .context(format!("while downloading build information from {}", release.job_url(&self.config.hydra_uri, job_name)))?; + + let mut products_by_subtype: HashMap = HashMap::new(); + for (_, product) in build_info.build_products { + if products_by_subtype.contains_key(&product.subtype) { + todo!("Job {} has multiple products of the same subtype {:?}. This is not supported yet.", job_name, product_type); + } + + products_by_subtype.insert(product.subtype.clone(), product); + } + + if products_by_subtype.len() > 1 && product_type.is_none() { + panic!("Job {} has {} build products. Select the right product via the subtypes.", job_name, products_by_subtype.len()); + } + + let product: BuildProduct = if let Some(ptype) = product_type { + products_by_subtype.remove(&ptype.to_string()).expect(&format!("Expected product type '{}' but not found in the list of products", &ptype.to_string())) + } else { + products_by_subtype.into_iter().last().expect(&format!("Expected at least one build product in job {}, found zero", job_name)).1 + }; + + // 3. FIXME: validate sha256hash during read? + let (store_path, rel_path) = StorePathRef::from_absolute_path_full(&product.store_path).expect("Failed to parse the product's store path"); + crate::nar::copy_file_in_nar(&self.config.binary_cache_uri, &nix_compat::nixbase32::encode(store_path.digest()), rel_path.to_str().unwrap(), pbar, out).await.context("while copying the NAR to the target") + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..0ae1bc0 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,261 @@ +mod config; +mod actions; +mod hydra; +mod git; +mod nar; +mod html; + +use std::path::PathBuf; + +use actions::{interpret_plan, Plan, PlanInterpretation, PlanOptions}; +use clap::{Subcommand, Parser, Args}; +use hydra::{Channel, Evaluation, HydraClient, Release}; +use tracing::{info, trace, warn}; +use object_store::{aws::AmazonS3, ObjectStore}; +use tempdir::TempDir; + +use tracing_opentelemetry::OpenTelemetryLayer; +use tracing_subscriber::layer::SubscriberExt; +use opentelemetry::trace::TracerProvider; + +#[derive(Debug, Args)] +struct ChannelArgs { + /// Channel name to update + channel_name: String, + /// Job name to fetch from the Hydra instance configured + job_name: String, +} + +#[derive(Debug, Args)] +struct RemoteArgs { + /// Channel name to update + channel_name: String, + /// Staging prefix + staging_prefix: String +} + +#[derive(Debug, Args)] +struct GlobalOpts { + /// TOML configuration file for channel updates + #[arg(short, long)] + config_file: PathBuf, + /// Whether to execute no remote side effects (S3 uploads, redirections), etc. + #[arg(short, long, default_value_t = false)] + dry_run: bool, + /// Whether to bypass all preflight checks + #[arg(short, long, default_value_t = false)] + bypass_preflight_checks: bool, + /// Enable or disable progress bar reports + #[arg(short, long, default_value_t = true)] + enable_progress_bar: bool, + /// Stream the plan instead of preparing it in a local directory + #[arg(short, long, default_value_t = true)] + stream: bool, + #[arg(long, default_values = &["sha256", "blake3"])] + checksum: Vec +} + +#[derive(Debug, Parser)] +#[command(version, about, long_about = None)] +struct App { + #[command(flatten)] + global_opts: GlobalOpts, + #[command(subcommand)] + command: Commands +} + +#[derive(Debug, Subcommand)] +enum Commands { + /// Print the plan for the given channel name and job name + Plan(ChannelArgs), + /// Apply the plan that would be generated for the given channel name and job name + Apply(ChannelArgs), + /// Apply an existing remote plan that was planned in the past + ApplyRemote(RemoteArgs), +} + +#[derive(Debug)] +struct PreflightCheckContext<'a> { + target_channel: &'a Channel, + new_release: &'a Release, + new_evaluation: &'a Evaluation, + current_release_path: Option, +} + +#[tracing::instrument] +async fn run_preflight_checks(channel: &Channel, release: &Release, evaluation: &Evaluation, channel_bucket: AmazonS3) -> bool { + info!("Running pre-flight checks..."); + let channel_name = object_store::path::Path::parse(&channel.name).expect("Channel name should be a valid S3 path"); + let mut context = PreflightCheckContext { + target_channel: channel, + new_release: release, + new_evaluation: evaluation, + current_release_path: None + }; + match channel_bucket.get_opts(&channel_name, object_store::GetOptions { head: true, ..Default::default() }).await { + Ok(object_store::GetResult { attributes, meta, .. }) => { + info!("Release found: {:?}", meta); + trace!("Attributes: {:#?}", attributes); + if let Some(redirection_target) = attributes.get(&object_store::Attribute::Metadata("x-amz-website-redirect-location".into())) { + context.current_release_path = Some(redirection_target.to_string().into()); + } + }, + Err(err) => { + warn!("Error while asking the channel bucket: {}", err); + todo!(); + } + } + + trace!("Preflight context check assembled: {:?}", context); + // TODO: run anti rollback protection + true +} + +enum PlanResult { + AlreadyExecuted { + remote_path: object_store::path::Path + }, + LocallyPrepared { + local_path: TempDir, + target_path: object_store::path::Path + }, + StreamedRemotely { + remote_path: object_store::path::Path, + target_path: object_store::path::Path + }, +} + +impl From for PlanResult { + fn from(value: PlanInterpretation) -> Self { + match value { + PlanInterpretation::Streamed { remote_path, target_path } => PlanResult::StreamedRemotely { + remote_path, + target_path + }, + PlanInterpretation::LocallyPlanned { local_path, target_path } => PlanResult::LocallyPrepared { + local_path, + target_path, + }, + } + } +} + +#[tracing::instrument] +async fn plan(hydra_client: &HydraClient<'_>, channel_name: &str, job_name: String, global_opts: &GlobalOpts, config: &config::MirrorConfig) -> std::io::Result { + let chan = Channel { + name: channel_name.to_string() + }; + info!("Planning for channel {} using job {}", &chan.name, &job_name); + let release = hydra_client.fetch_release(&job_name) + .await.expect("Failed to fetch release"); + trace!("{:#?}", release); + let evaluation = hydra_client.fetch_evaluation(&release) + .await.expect("Failed to fetch evaluation"); + trace!("{:?}", evaluation.jobset_eval_inputs); + + let plan_options = PlanOptions { + streaming: global_opts.stream, + enable_progress_bar: global_opts.enable_progress_bar, + requested_checksums: global_opts.checksum.clone() + }; + + if let hydra::Input::Git { revision, .. } = evaluation.jobset_eval_inputs.get("nixpkgs").expect("Expected a nixpkgs repository") { + info!("Release information:\n- Release is: {} (build {})\n- Eval is: {}\n- Prefix is: {}\n- Git commit is {}\n", + release.nix_name, release.id, evaluation.id, release.prefix(&chan), revision); + + let release_bucket = config.release_bucket(); + + // If the release already exists, skip it. + if release_bucket.head(&release.prefix(&chan)).await.is_ok() { + tracing::warn!("Release already exists, skipping"); + return Ok(PlanResult::AlreadyExecuted { + remote_path: release.prefix(&chan) + }); + } + + let channel_bucket = config.channel_bucket(); + if global_opts.bypass_preflight_checks || run_preflight_checks(&chan, &release, &evaluation, channel_bucket).await { + let actions = actions::generate_plan(&config, &chan, &release, &evaluation).await; + tracing::debug!("Action plan: {:#?}", actions); + + let interpretation = interpret_plan(&hydra_client, Plan { + actions, + release, + channel: chan.clone(), + target_bucket: config.release_bucket(), + revision: revision.to_string(), + }, plan_options).await; + info!("Plan interpreted: {:?}", interpretation); + + Ok(interpretation.into()) + } else { + tracing::error!("Preflight check failed, cannot continue, pass `--force` if you want to bypass preflight checks"); + Err(std::io::Error::other("preflight check failure")) + } + } else { + panic!("Nixpkgs input is not of type Git"); + } +} + + +#[tokio::main] +async fn main() -> std::io::Result<()> { + let args = App::parse(); + + let config: config::MirrorConfig = toml::from_str(&std::fs::read_to_string(&args.global_opts.config_file) + .expect("Failed to read the configuration file")) + .expect("Failed to deserialize the configuration file"); + + let hydra_client: HydraClient = HydraClient { + config: &config + }; + + let trace_provider = opentelemetry_otlp::new_pipeline() + .tracing() + .with_trace_config(opentelemetry_sdk::trace::Config::default().with_resource(opentelemetry_sdk::Resource::new(vec![ + opentelemetry::KeyValue::new("service.name", env!("CARGO_PKG_NAME")), + opentelemetry::KeyValue::new("service.version", env!("CARGO_PKG_VERSION")), + ]))) + .with_exporter(opentelemetry_otlp::new_exporter().tonic()) + .install_batch(opentelemetry_sdk::runtime::Tokio) + .expect("Couldn't create OTLP tracer"); + + opentelemetry::global::set_tracer_provider(trace_provider.clone()); + let fmt_layer = tracing_subscriber::fmt::layer(); + + tracing_subscriber::registry() + .with(tracing_subscriber::EnvFilter::from_default_env()) + .with(fmt_layer) + .with(OpenTelemetryLayer::new(trace_provider.tracer("channel-scripts"))); + + match args.command { + Commands::Plan(channel) => { + plan(&hydra_client, &channel.channel_name, channel.job_name, &args.global_opts, &config).await?; + }, + Commands::Apply(channel) => { + let plan = plan(&hydra_client, &channel.channel_name, channel.job_name, &args.global_opts, &config).await?; + + match plan { + PlanResult::AlreadyExecuted { .. } => { + info!("Plan was already executed, no further action required"); + }, + PlanResult::LocallyPrepared { .. } => todo!("Uploading a plan is a todo; use streaming!"), + PlanResult::StreamedRemotely { remote_path, target_path } => { + info!("Plan was streamed remotely successfully to '{}@{}', replacing {}", config.s3_release_bucket_name, remote_path, target_path); + + config.release_bucket().rename(&remote_path, &target_path) + .await.expect("Failed to atomically promote the streamed channel, aborting"); + } + } + }, + Commands::ApplyRemote(remote) => { + let release = config.release_bucket(); + // TODO: fetch the release and obtain the right name. + + release.rename(&remote.staging_prefix.try_into().unwrap(), &remote.channel_name.try_into().unwrap()) + .await.expect("Failed to atomically rename the staging prefix into the channel name"); + } + } + + Ok(()) +} diff --git a/src/nar.rs b/src/nar.rs new file mode 100644 index 0000000..a2ffa2f --- /dev/null +++ b/src/nar.rs @@ -0,0 +1,117 @@ +use std::borrow::{Borrow, BorrowMut}; + +use anyhow::{bail, Context}; +use async_compression::tokio::bufread::ZstdDecoder; +use bytes::Bytes; +use futures::{Stream, TryStreamExt}; +use indicatif::ProgressBar; +use tracing::debug; +use nix_compat::nar::listing::{Listing, ListingEntry}; +use nix_compat::narinfo::NarInfo; +use tokio::io::{AsyncReadExt, AsyncWrite}; +use tokio_util::io::{ReaderStream, StreamReader}; + +/// Returns a NarInfo from a HTTP binary cache. +#[tracing::instrument] +pub async fn get_narinfo(binary_cache_uri: &str, narinfo_hash: &str) -> reqwest::Result { + let client = reqwest::Client::new(); + let resp = client.get(format!("{}/{}.narinfo", binary_cache_uri, narinfo_hash)) + .header("User-Agent", "nixos-channel-scripts (rust)") + .send() + .await?; + + resp.text().await +} + +/// Returns a listing from a HTTP binary cache. +#[tracing::instrument] +pub async fn get_listing(binary_cache_uri: &str, narinfo_hash: &str) -> reqwest::Result { + let client = reqwest::Client::builder().brotli(true).build().unwrap(); + let resp = client.get(format!("{}/{}.ls", binary_cache_uri, narinfo_hash)) + .header("User-Agent", "nixos-channel-scripts (rust)") + .send() + .await?; + + resp.json().await +} + +/// Returns a `Content-Range`-aware HTTP body reader of the NAR. +pub async fn ranged_nar_reader(binary_cache_uri: &str, narinfo: NarInfo<'_>, start: u64, end: u64) -> reqwest::Result>> { + let client = reqwest::Client::new(); + // TODO: handle compression? + let resp = client.get(format!("{}/{}", binary_cache_uri, narinfo.url)) + .header("User-Agent", "nixos-channel-scripts (rust)") + .header("Content-Range", format!("bytes {}-{}/*", start, end)) + .send() + .await?; + + Ok(resp.bytes_stream()) +} + +#[tracing::instrument] +pub async fn nar_reader(binary_cache_uri: &str, narinfo: NarInfo<'_>) -> reqwest::Result>> { + let client = reqwest::Client::new(); + // TODO: handle compression? + let resp = client.get(format!("{}/{}", binary_cache_uri, narinfo.url)) + .header("User-Agent", "nixos-channel-scripts (rust)") + .send() + .await?; + + Ok(resp.bytes_stream()) +} + +/// Streams a binary cache file contained in a NAR +/// by using narinfo + listing information. +#[tracing::instrument] +pub async fn file_in_nar_bytes_stream(binary_cache_uri: &str, narinfo_hash: &str, path: &str) -> anyhow::Result<(u64, impl Stream>)> { + let narinfo_str: String = get_narinfo(binary_cache_uri, narinfo_hash).await?; + let narinfo: NarInfo = NarInfo::parse(narinfo_str.as_ref()).context("while parsing the narinfo").expect("Failed to parse narinfo from HTTP binary cache server"); + let listing: Listing = get_listing(binary_cache_uri, narinfo_hash).await.context("while parsing the listing of that NAR")?; + match listing { + Listing::V1 { root, .. } => { + let entry = root.locate(path) + .expect("Invalid relative path to the NAR") + .ok_or(tokio::io::Error::new(tokio::io::ErrorKind::NotFound, format!("Entry {} not found in the NAR listing", path)))?; + + if let ListingEntry::Regular { + size, nar_offset, .. + } = entry { + // TODO: this is ugly. + let mut file_reader = ZstdDecoder::new( + StreamReader::new(nar_reader(binary_cache_uri, narinfo).await?.map_err(std::io::Error::other)) + ); + + // Discard *nar_offset bytes + let discarded = tokio::io::copy(&mut file_reader.borrow_mut().take(*nar_offset), &mut tokio::io::sink()).await + .context("while discarding the start of the NAR").unwrap(); + + debug!("discarded {} bytes and taking at most {} bytes", discarded, *size); + + // Let the consumer copy at most *size bytes of data. + Ok((*size, ReaderStream::new(file_reader.take(*size)))) + } else { + bail!("Expected a file, obtained either a symlink or a directory"); + } + + }, + _ => bail!("Unsupported listing version") + } +} + +/// Hits the binary cache with a narinfo request +/// and copies a specific file to the provided writer +/// asynchronously. +pub async fn copy_file_in_nar(binary_cache_uri: &str, narinfo_hash: &str, path: &str, pbar: ProgressBar, out: &mut W) -> anyhow::Result { + let (size, stream) = file_in_nar_bytes_stream(binary_cache_uri, narinfo_hash, path).await?; + pbar.set_length(size); + + let copied = tokio::io::copy( + &mut StreamReader::new(stream), + &mut pbar.wrap_async_write(out) + ).await.context("while copying the file in the NAR").unwrap(); + + assert!(copied == size, "mismatch in the copy sizes"); + pbar.finish(); + + return Ok(copied) +} diff --git a/src/release.html b/src/release.html new file mode 100644 index 0000000..4931af6 --- /dev/null +++ b/src/release.html @@ -0,0 +1,72 @@ + + + + + + {{channelName}} - NixOS Channel + + + +
+

Channel: {{channelName}}

+

Release: {{releaseName}}

+

Date of Release: {{releaseDate}}

+ +
+ + + + + + + + + + + {{#each files}} + + + + + + {{/each}} + +
File NameSizeSHA-256 Hash
{{name}}{{size}}{{sha256}}
+ + diff --git a/src/release.tpl b/src/release.tpl new file mode 100644 index 0000000..0a1cd4d --- /dev/null +++ b/src/release.tpl @@ -0,0 +1,78 @@ + + + + + + {{channelName}} - NixOS Channel + + + +
+

Channel: {{channelName}}

+

Release: {{releaseName}}

+

Date of Release: {{releaseDate}}

+ +
+ + + + + + + {{#if checksums}} + {{#each checksums as |checksum|}} + + {{/each}} + {{/if}} + + + + {{#each files}} + + + + {{#each checksums as |checksum|}} + + {{/each}} + + {{/each}} + +
File NameSize{{checksum}} hash
{{name}}{{size}}{{this}}
+ +