commit cbddc5db182bd932c3bd67ecb1e35f70296e9992 Author: garrettmills Date: Wed Nov 6 23:41:49 2024 -0500 Initial WIP diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed78c4e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +.idea +k +*.sqlite diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..4e137dd --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4264 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[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.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +dependencies = [ + "anstyle", + "windows-sys 0.59.0", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.86", +] + +[[package]] +name = "async-trait" +version = "0.1.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.86", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "atomic" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +dependencies = [ + "bytemuck", +] + +[[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.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[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 = "bigdecimal" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f850665a0385e070b64c38d2354e6c104c8479c59868d1e48a0c13ee2c7a1c1" +dependencies = [ + "autocfg", + "libm", + "num-bigint", + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "binascii" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[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 = "borsh" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.86", + "syn_derive", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bytemuck" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" + +[[package]] +name = "cc" +version = "1.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets 0.52.6", +] + +[[package]] +name = "clap" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.86", +] + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[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.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[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 = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn 2.0.86", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.86", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "devise" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c7580b072f1c8476148f16e0a0d5dedddab787da98d86c5082c5e9ed8ab595" +dependencies = [ + "devise_codegen 0.3.1", + "devise_core 0.3.1", +] + +[[package]] +name = "devise" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1d90b0c4c777a2cad215e3c7be59ac7c15adf45cf76317009b7d096d46f651d" +dependencies = [ + "devise_codegen 0.4.2", + "devise_core 0.4.2", +] + +[[package]] +name = "devise_codegen" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "123c73e7a6e51b05c75fe1a1b2f4e241399ea5740ed810b0e3e6cacd9db5e7b2" +dependencies = [ + "devise_core 0.3.1", + "quote", +] + +[[package]] +name = "devise_codegen" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71b28680d8be17a570a2334922518be6adc3f58ecc880cbb404eaeb8624fd867" +dependencies = [ + "devise_core 0.4.2", + "quote", +] + +[[package]] +name = "devise_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841ef46f4787d9097405cac4e70fb8644fc037b526e8c14054247c0263c400d0" +dependencies = [ + "bitflags 1.3.2", + "proc-macro2", + "proc-macro2-diagnostics 0.9.1", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "devise_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b035a542cf7abf01f2e3c4d5a7acbaebfefe120ae4efc7bde3df98186e4b8af7" +dependencies = [ + "bitflags 2.6.0", + "proc-macro2", + "proc-macro2-diagnostics 0.10.1", + "quote", + "syn 2.0.86", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +dependencies = [ + "serde", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_filter" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + +[[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 = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + +[[package]] +name = "figment" +version = "0.10.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" +dependencies = [ + "atomic 0.6.0", + "pear", + "serde", + "toml", + "uncased", + "version_check", +] + +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + +[[package]] +name = "flate2" +version = "1.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[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 = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot 0.12.3", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.86", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generator" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" +dependencies = [ + "cc", + "libc", + "log", + "rustversion", + "windows", +] + +[[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.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[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 1.1.0", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "handlebars" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08485b96a0e6393e9e4d1b8d48cf74ad6c063cd905eb33f42c1ce3f0377539b" +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" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.11", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", +] + +[[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 = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[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 1.1.0", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[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 = "0.14.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.6", + "http 1.1.0", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.5.0", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.5.0", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "hyper 1.5.0", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +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 = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[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 = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown 0.15.0", + "serde", +] + +[[package]] +name = "inherent" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0122b7114117e64a63ac49f752a5ca4624d534c7b1c7de796ac196381cd2d947" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.86", +] + +[[package]] +name = "inlinable_string" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" + +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[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.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi 0.4.0", + "libc", + "windows-sys 0.52.0", +] + +[[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.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "libc" +version = "0.2.161" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" + +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", + "redox_syscall 0.5.7", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libssh2-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dc8a030b787e2119a731f1951d6a773e2280c660f8ec4b0f5e1505a386e71ee" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libz-sys" +version = "1.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[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 = "loom" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[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 = "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 = "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.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "multer" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http 1.1.0", + "httparse", + "memchr", + "mime", + "spin", + "tokio", + "tokio-util", + "version_check", +] + +[[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 = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "normpath" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8911957c4b1549ac0dc74e30db9c8b0e66ddcd6d7acc33098f4c63a64a6d7ed" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "notify" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +dependencies = [ + "bitflags 2.6.0", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio 0.8.11", + "walkdir", + "windows-sys 0.48.0", +] + +[[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-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.9", + "libc", +] + +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "openssl" +version = "0.10.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +dependencies = [ + "bitflags 2.6.0", + "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.86", +] + +[[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.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ordered-float" +version = "3.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ouroboros" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "944fa20996a25aded6b4795c6d63f10014a7a83f8be9828a11860b08c5fc4a67" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39b0deead1528fd0e5947a8546a9642a9777c25f6e1e26f34c97b204bbb465bd" +dependencies = [ + "heck 0.4.1", + "itertools", + "proc-macro2", + "proc-macro2-diagnostics 0.10.1", + "quote", + "syn 2.0.86", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "p5x" +version = "0.1.0" +dependencies = [ + "async-trait", + "env_logger", + "futures", + "log", + "proxmox-api", + "reqwest", + "rocket", + "rocket_dyn_templates", + "sea-orm", + "sea-orm-migration", + "sea-orm-rocket", + "serde", + "serde_json", + "serde_urlencoded", + "ssh2", + "tokio", + "ureq", + "uuid", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + +[[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 0.9.10", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", +] + +[[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 0.5.7", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pear" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467" +dependencies = [ + "inlinable_string", + "pear_codegen", + "yansi 1.0.1", +] + +[[package]] +name = "pear_codegen" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics 0.10.1", + "quote", + "syn 2.0.86", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[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.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.86", +] + +[[package]] +name = "pest_meta" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[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.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[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-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.86", +] + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", + "yansi 0.5.1", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.86", + "version_check", + "yansi 1.0.1", +] + +[[package]] +name = "proxmox-api" +version = "0.1.2-pre" +source = "git+https://github.com/glmdev/p5x-proxmox-api#d028ab9e1e39370c481270afd2e7bc7781b4d66f" +dependencies = [ + "log", + "parking_lot 0.12.3", + "rustls", + "serde", + "serde_json", + "serde_urlencoded", + "ureq", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[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", +] + +[[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", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "ref-cast" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.86", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "reqwest" +version = "0.12.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.6", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.5.0", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "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 = "rkyv" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rocket" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a516907296a31df7dc04310e7043b61d71954d703b603cc6867a026d7e72d73f" +dependencies = [ + "async-stream", + "async-trait", + "atomic 0.5.3", + "binascii", + "bytes", + "either", + "figment", + "futures", + "indexmap", + "log", + "memchr", + "multer", + "num_cpus", + "parking_lot 0.12.3", + "pin-project-lite", + "rand", + "ref-cast", + "rocket_codegen", + "rocket_http", + "serde", + "serde_json", + "state", + "tempfile", + "time", + "tokio", + "tokio-stream", + "tokio-util", + "ubyte", + "version_check", + "yansi 1.0.1", +] + +[[package]] +name = "rocket_codegen" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "575d32d7ec1a9770108c879fc7c47815a80073f96ca07ff9525a94fcede1dd46" +dependencies = [ + "devise 0.4.2", + "glob", + "indexmap", + "proc-macro2", + "quote", + "rocket_http", + "syn 2.0.86", + "unicode-xid", + "version_check", +] + +[[package]] +name = "rocket_dyn_templates" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bbab919c9e67df3f7ac6624a32ef897df4cd61c0969f4d66f3ced0534660d7a" +dependencies = [ + "handlebars", + "normpath", + "notify", + "rocket", + "walkdir", +] + +[[package]] +name = "rocket_http" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e274915a20ee3065f611c044bd63c40757396b6dbc057d6046aec27f14f882b9" +dependencies = [ + "cookie", + "either", + "futures", + "http 0.2.12", + "hyper 0.14.31", + "indexmap", + "log", + "memchr", + "pear", + "percent-encoding", + "pin-project-lite", + "ref-cast", + "serde", + "smallvec", + "stable-pattern", + "state", + "time", + "tokio", + "uncased", +] + +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rust_decimal" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand", + "rkyv", + "serde", + "serde_json", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.38.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.23.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + +[[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.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sea-bae" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f694a6ab48f14bc063cfadff30ab551d3c7e46d8f81836c51989d548f44a2a25" +dependencies = [ + "heck 0.4.1", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.86", +] + +[[package]] +name = "sea-orm" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4872675cc5d5d399a2a202c60f3a393ec8d3f3307c36adb166517f348e4db5" +dependencies = [ + "async-stream", + "async-trait", + "bigdecimal", + "chrono", + "futures", + "log", + "ouroboros", + "rust_decimal", + "sea-orm-macros", + "sea-query", + "sea-query-binder", + "serde", + "serde_json", + "sqlx", + "strum", + "thiserror", + "time", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "sea-orm-cli" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aefbd960c9ed7b2dfbab97b11890f5d8c314ad6e2f68c7b36c73ea0967fcc25" +dependencies = [ + "chrono", + "clap", + "dotenvy", + "glob", + "regex", + "sea-schema", + "tracing", + "tracing-subscriber", + "url", +] + +[[package]] +name = "sea-orm-macros" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85f714906b72e7265c0b2077d0ad8f235dabebda513c92f1326d5d40cef0dd01" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "sea-bae", + "syn 2.0.86", + "unicode-ident", +] + +[[package]] +name = "sea-orm-migration" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa7bbfbe3bec60b5925193acc9c98b9f8ae9853f52c8004df0c1ea5193c01ea0" +dependencies = [ + "async-trait", + "clap", + "dotenvy", + "futures", + "sea-orm", + "sea-orm-cli", + "sea-schema", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "sea-orm-rocket" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1e2cd44db4886bcaee4271e895f7d25f41e4bd4c3dffa5b4a286667593c5bb" +dependencies = [ + "rocket", + "sea-orm-rocket-codegen", +] + +[[package]] +name = "sea-orm-rocket-codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd35bf98426401976aa90ed163a1cbe9228b6037e59489b25348691437860e1" +dependencies = [ + "devise 0.3.1", + "quote", +] + +[[package]] +name = "sea-query" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff504d13b5e4b52fffcf2fb203d0352a5722fa5151696db768933e41e1e591bb" +dependencies = [ + "bigdecimal", + "chrono", + "inherent", + "ordered-float", + "rust_decimal", + "sea-query-derive", + "serde_json", + "time", + "uuid", +] + +[[package]] +name = "sea-query-binder" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0019f47430f7995af63deda77e238c17323359af241233ec768aba1faea7608" +dependencies = [ + "bigdecimal", + "chrono", + "rust_decimal", + "sea-query", + "serde_json", + "sqlx", + "time", + "uuid", +] + +[[package]] +name = "sea-query-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9834af2c4bd8c5162f00c89f1701fb6886119a88062cf76fe842ea9e232b9839" +dependencies = [ + "darling", + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.86", + "thiserror", +] + +[[package]] +name = "sea-schema" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aab1592d17860a9a8584d9b549aebcd06f7bdc3ff615f71752486ba0b05b1e6e" +dependencies = [ + "futures", + "sea-query", + "sea-schema-derive", +] + +[[package]] +name = "sea-schema-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "debdc8729c37fdbf88472f97fd470393089f997a909e535ff67c544d18cfccf0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.86", +] + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.214" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.214" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.86", +] + +[[package]] +name = "serde_json" +version = "1.0.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +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 = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[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 = [ + "digest", + "rand_core", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.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" +dependencies = [ + "serde", +] + +[[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" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" +dependencies = [ + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e" +dependencies = [ + "atoi", + "bigdecimal", + "byteorder", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.14.5", + "hashlink", + "hex", + "indexmap", + "log", + "memchr", + "native-tls", + "once_cell", + "paste", + "percent-encoding", + "rust_decimal", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror", + "time", + "tokio", + "tokio-stream", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.86", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5" +dependencies = [ + "dotenvy", + "either", + "heck 0.5.0", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 2.0.86", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" +dependencies = [ + "atoi", + "base64", + "bigdecimal", + "bitflags 2.6.0", + "byteorder", + "bytes", + "chrono", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand", + "rsa", + "rust_decimal", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "time", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" +dependencies = [ + "atoi", + "base64", + "bigdecimal", + "bitflags 2.6.0", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "num-bigint", + "once_cell", + "rand", + "rust_decimal", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "time", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "time", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "ssh2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7fe461910559f6d5604c3731d00d2aafc4a83d1665922e280f42f9a168d5455" +dependencies = [ + "bitflags 1.3.2", + "libc", + "libssh2-sys", + "parking_lot 0.11.2", +] + +[[package]] +name = "stable-pattern" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" +dependencies = [ + "memchr", +] + +[[package]] +name = "state" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b8c4a4445d81357df8b1a650d0d0d6fbbbfe99d064aa5e02f3e4022061476d8" +dependencies = [ + "loom", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" + +[[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.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89275301d38033efb81a6e60e3497e734dfcc62571f2854bf4b16690398824c" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.86", +] + +[[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 2.6.0", + "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 = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d171f59dbaa811dbbb1aee1e73db92ec2b122911a48e1390dfe327a821ddede" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b08be0f17bd307950653ce45db00cd31200d82b624b36e181337d9c7d92765b5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.86", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[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.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio 1.0.2", + "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.86", +] + +[[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.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +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.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[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 = [ + "log", + "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.86", +] + +[[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-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 = "ubyte" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f720def6ce1ee2fc44d40ac9ed6d3a59c361c80a75a7aa8e75bb9baed31cf2ea" +dependencies = [ + "serde", +] + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "uncased" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" +dependencies = [ + "serde", + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a" +dependencies = [ + "base64", + "flate2", + "log", + "once_cell", + "rustls", + "rustls-pki-types", + "url", + "webpki-roots", +] + +[[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 = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +dependencies = [ + "getrandom", + "rand", + "serde", +] + +[[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 = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.86", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.86", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" + +[[package]] +name = "web-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "whoami" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +dependencies = [ + "redox_syscall 0.5.7", + "wasite", +] + +[[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" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[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 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[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 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[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.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +dependencies = [ + "is-terminal", +] + +[[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.86", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7218000 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "p5x" +version = "0.1.0" +edition = "2021" + +[dependencies] +env_logger = "0.11.5" +log = "0.4.22" +rocket = { version = "0.5.1", features = ["json"] } +sea-orm = { version = "1.1.0", features = ["sqlx-sqlite", "runtime-tokio-native-tls", "macros"] } +sea-orm-rocket = "0.5.4" +serde = { version = "1.0.214", features = ["derive"] } +tokio = { version = "1.0.0", features = ["rt", "rt-multi-thread", "macros"] } +async-trait = "0.1.83" +rocket_dyn_templates = { version = "0.2.0", features = ["handlebars"] } +sea-orm-migration = "1.1.0" +uuid = { version = "1.11.0", features = ["v4", "fast-rng"] } +ssh2 = "0.9.4" +reqwest = "0.12.9" +futures = { version = "0.3.31", features = ["executor"] } +serde_json = "1.0.132" +serde_urlencoded = "0.7.1" +proxmox-api = { git = "https://github.com/glmdev/p5x-proxmox-api", version = "0.1.2-pre", features = ["ureq-client"] } +ureq = "2.10.1" diff --git a/Rocket.toml b/Rocket.toml new file mode 100644 index 0000000..09a93a6 --- /dev/null +++ b/Rocket.toml @@ -0,0 +1,5 @@ +[default] +template_dir = "resources/views" + +[default.databases.p5x_api] +url = "sqlite://p5x_api.sqlite?mode=rwc" diff --git a/resources/views/settings.html.hbs b/resources/views/settings.html.hbs new file mode 100644 index 0000000..ed546b5 --- /dev/null +++ b/resources/views/settings.html.hbs @@ -0,0 +1,27 @@ + + + Settings | P5x + + +

Configure P5x

+ Current config version: {{#if settings.id}}v{{settings.id}}{{^}}v0{{/if}} +
+
Proxmox VE
+ + +
+ + +
+ + +
+ + +
+ Storage pool must be network-attached (Ceph/iSCSI). NFS is discouraged because it lacks proper support for locking.
+ +
+
+ + diff --git a/src/api/cluster/carrier.rs b/src/api/cluster/carrier.rs new file mode 100644 index 0000000..b298b1e --- /dev/null +++ b/src/api/cluster/carrier.rs @@ -0,0 +1,126 @@ +use std::path::Path; +use uuid::Uuid; +use proxmox_api::nodes::node::lxc; +use proxmox_api::nodes::node::lxc::vmid; +use proxmox_api::types::VmId; +use sea_orm::{ActiveModelTrait, EntityTrait, Set, TryIntoModel}; +use crate::api::cluster::node::wait_upid; +use crate::api::entity::locks::lock_vmid; +use crate::api::entity::nodes; +use crate::api::entity::nodes::P5xError; +use crate::api::services::{ssh_run_trimmed, Services}; +use crate::api::services::SshError; + +pub async fn provision_carrier( + svc: &Services<'_>, + from_node: &nodes::Model, +) -> Result { + // Make sure the empty filesystem template exists + ensure_carrier_template(svc, from_node).await?; + + // Get the next VMID to be used by the carrier container + let _lock = lock_vmid(svc.db, Some("Provisioning carrier container")) + .await.map_err(P5xError::DbErr)?; + let pve_client = svc.pve() + .map_err(P5xError::ServiceError)?; + let pve = proxmox_api::cluster::ClusterClient::new(pve_client); + + let vm_id = pve.nextid() + .get(Default::default()) + .map_err(P5xError::PveError)?; + let vm_id = VmId::new(vm_id).unwrap(); + + // Build the new container params + let hostname = format!("carrier-{}", Uuid::new_v4().to_string()); + let storage = svc.setting_req(|s| &s.pve_storage_pool) + .map_err(P5xError::ServiceError)?; + + let mut params = lxc::PostParams::new("local:vztmpl/p5x-empty.tar.xz".to_string(), vm_id); + params.cores = Some(1); + params.description = Some("Temporary container managed by P5x".to_string()); + params.hostname = Some(hostname.to_string()); + params.memory = Some(16); // in MB, min 16 + params.start = Some(false); + params.storage = Some(storage); + params.tags = Some("p5x".to_string()); + + // Ask the PVE API to start creating the carrier node based on our empty template + let upid = svc.pve_node(&from_node.pve_host) + .map_err(P5xError::ServiceError)? + .lxc() + .post(params) + .map_err(P5xError::PveError)?; + + // Wait for the container creation task to finish + wait_upid(svc, &from_node.pve_host, &upid).await?; + + // Create a new node instance + let node = nodes::ActiveModel { + hostname: Set(hostname), + pve_id: Set(i32::try_from(vm_id.get()).unwrap()), + pve_host: Set(from_node.pve_host.to_string()), + assigned_ip: Set("0.0.0.0".to_string()), + assigned_subnet: Set(0), + is_permanent: Set(false), + ..Default::default() + } + .save(svc.db) + .await + .map_err(P5xError::DbErr)?; + + node.try_into_model().map_err(P5xError::DbErr) +} + +pub async fn ensure_carrier_template( + svc: &Services<'_>, + node: &nodes::Model, +) -> Result<(), P5xError> { + let pve_ssh = svc.pve_ssh(&node.pve_host) + .map_err(P5xError::ServiceError)?; + + // Use SFTP to check whether the file exists + let path = Path::new("/var/lib/vz/template/cache/p5x-empty.tar.xz"); + let exists = pve_ssh.sftp() + .and_then(|f| f.stat(path)) + .map_err(SshError::ClientError) + .map_err(P5xError::SshError)?; + + // The empty template already exists, so we're done + if exists.is_file() { + return Ok(()); + } + + // Initialize the template directly + // todo: we should really upload this to our p5x.image-cache during standup + ssh_run_trimmed(&pve_ssh, "mkdir -p /tmp/p5x-empty-tmp")?; + ssh_run_trimmed(&pve_ssh, "touch /tmp/p5x-empty-tmp/placeholder.txt")?; + ssh_run_trimmed(&pve_ssh, "sh -c 'cd /tmp/p5x-empty-tmp && tar cfJ /var/lib/vz/template/cache/p5x-empty.tar.xz placeholder.txt'")?; + ssh_run_trimmed(&pve_ssh, "rm -rf /tmp/p5x-empty-tmp")?; + + Ok(()) +} + +pub async fn terminate_carrier( + svc: &Services<'_>, + carrier: nodes::Model, +) -> Result<(), P5xError> { + let mut params = vmid::DeleteParams::default(); + params.purge = Some(true); + params.destroy_unreferenced_disks = Some(true); + + let upid = svc.pve_node(&carrier.pve_host) + .map_err(P5xError::ServiceError)? + .lxc() + .vmid(carrier.vm_id()) + .delete(params) + .map_err(P5xError::PveError)?; + + wait_upid(svc, &carrier.pve_host, &upid).await?; + + nodes::Entity::delete_by_id(carrier.id) + .exec(svc.db) + .await + .map_err(P5xError::DbErr)?; + + Ok(()) +} diff --git a/src/api/cluster/mod.rs b/src/api/cluster/mod.rs new file mode 100644 index 0000000..e358360 --- /dev/null +++ b/src/api/cluster/mod.rs @@ -0,0 +1,3 @@ +pub mod node; +pub mod carrier; +pub mod volume; diff --git a/src/api/cluster/node.rs b/src/api/cluster/node.rs new file mode 100644 index 0000000..92c94eb --- /dev/null +++ b/src/api/cluster/node.rs @@ -0,0 +1,67 @@ +use std::time::Duration; +use proxmox_api::nodes::node::lxc::vmid::migrate; +use proxmox_api::nodes::node::tasks::upid; +use sea_orm::{ActiveModelTrait, EntityTrait, IntoActiveModel, Set}; +use tokio::time::sleep; +use crate::api::entity::nodes; +use crate::api::entity::nodes::P5xError; +use crate::api::services::Services; + +pub async fn migrate_node( + svc: &Services<'_>, + node: nodes::Model, + to_host: &str, +) -> Result { + // Ask the PVE API to start migrating the node + let params = migrate::PostParams::new(to_host.to_string()); + let upid = svc.pve_node(&node.pve_host) + .map_err(P5xError::ServiceError)? + .lxc() + .vmid(node.vm_id()) + .migrate() + .post(params) + .map_err(P5xError::PveError)?; + + // Wait for the UPID to finish + wait_upid(svc, &node.pve_host, &upid).await?; + + // Persist the node + let mut node = node.into_active_model(); + node.pve_host = Set(to_host.to_string()); + let node = node.save(svc.db).await.map_err(P5xError::DbErr)?; + + nodes::Entity::find_by_id(node.id.unwrap()) + .one(svc.db) + .await + .map_err(P5xError::DbErr)? + .ok_or(P5xError::BadPostcondition("Could not look up node after persisting")) +} + +pub async fn wait_upid(svc: &Services<'_>, node: &str, upid: &str) -> Result<(), P5xError> { + info!("Waiting for UPID {upid} on node {node}"); + let pve = svc.pve_node(node) + .map_err(P5xError::ServiceError)?; + + loop { + let status = pve.tasks() + .upid(upid) + .status() + .get() + .map_err(P5xError::PveError)?; + + if status.status == upid::status::Status::Running { + sleep(Duration::from_secs(1)).await; + continue; + } + + if let Some(s) = status.exitstatus { + if s == "OK" { + info!("UPID {upid} on node {node} finished"); + return Ok(()); + } + + error!("UPID {upid} on node {node} failed"); + return Err(P5xError::UpidFailed(node.to_string(), upid.to_string())); + } + } +} diff --git a/src/api/cluster/volume.rs b/src/api/cluster/volume.rs new file mode 100644 index 0000000..9677fe3 --- /dev/null +++ b/src/api/cluster/volume.rs @@ -0,0 +1,453 @@ +use std::cmp::max; +use std::time::Duration; +use log::{info, debug, warn, error}; +use proxmox_api::nodes::node::lxc::vmid::config::PutParams; +use proxmox_api::nodes::node::lxc::vmid::move_volume; +use proxmox_api::types::VmId; +use proxmox_api::UreqError; +use sea_orm::*; +use sea_orm::ActiveValue::Set; +use serde::{Deserialize, Serialize}; +use tokio::time::sleep; +use crate::api::cluster::carrier::{provision_carrier, terminate_carrier}; +use crate::api::cluster::node::migrate_node; +use crate::api::entity::nodes::{lock_first_available, P5xError}; +use crate::api::entity::{nodes, volumes}; +use crate::api::services::{ssh_run_trimmed, Services}; + +#[derive(Serialize, Deserialize, FromForm)] +pub struct VolumeParams { + pub id: Option, + pub name: String, + #[serde(default)] + pub size_in_bytes: i64, + pub mountpoint: Option, +} + +impl VolumeParams { + pub async fn resolve(svc: &Services<'_>, name: &str) -> Result, DbErr> { + volumes::Entity::find() + .filter(volumes::Column::Name.eq(name)) + .one(svc.db) + .await + .map(|v| v.map(|v| v.into())) + } +} + +pub async fn create( + svc: &Services<'_>, + name: &str, + size_in_bytes: i64, +) -> Result { + info!("Creating volume {name} with size {}KiB", size_in_bytes / 1024); + + // Make sure the volume name is unique + let existing = volumes::Entity::find() + .filter(volumes::Column::Name.eq(name)) + .one(svc.db) + .await + .map_err(P5xError::DbErr)?; + + if let Some(_) = existing { + return Err(P5xError::BadPrecondition("Cannot create volume: a volume with the specified name already exists")); + } + + // Lock a node in the k8s cluster + let reason = format!("Creating volume: {name}"); + let (node, _lock) = lock_first_available(svc.db, Some(&reason)) + .await.map_err(P5xError::LockErr)?; + + // Get the next available mountpoint ID + let conf = node.config(svc)?; + let mp_id = conf.next_nth("mp"); + info!("Volume {name} will become mp{mp_id} on node {} ({})", node.hostname, node.pve_id); + + // Generate a new mountpoint entry for the node's config + let storage = svc.setting_req(|s| &s.pve_storage_pool) + .map_err(P5xError::ServiceError)?; + let size_in_gib = max((size_in_bytes as u64).div_ceil(1024 * 1024 * 1024) as i64, 1); + let line = format!("{storage}:{size_in_gib},mp=/mnt/p5x-{name},backup=1"); + debug!("Volume {name}: {line}"); + + let mut params = PutParams::default(); + params.mps.insert(mp_id, line); + + // Update the node config to create the volume + debug!("Patching PVE config for volume {name}"); + let vm_id = VmId::new(i64::from(node.pve_id)).unwrap(); + let res = svc.pve_node(&node.pve_host) + .map_err(P5xError::ServiceError)? + .lxc() + .vmid(vm_id) + .config() + .put(params); + + // This is necessary because PUT returns {data: null} on success, + // which the UreqClient flags as an unknown error. + if let Err(UreqError::EncounteredErrors(e)) = res { + return Err(P5xError::PveError(UreqError::EncounteredErrors(e))); + } + + // Stupid hack is stupid, but we don't get back a UPID to wait on the vol to be created + info!("Successfully patched PVE config. Waiting for volume {name} to appear"); + sleep(Duration::from_secs(5)).await; + + // Load the updated config + debug!("Loading updated node config for volume {name}"); + let conf = node.config(svc)?; + let mount = conf.get(&format!("mp{mp_id}")) + .ok_or(P5xError::BadPrecondition("Could not find mountpoint in config after creating volume"))?; + debug!("Found mountpoint details for volume {name}: {mount}"); + + // Parse the disk name from the config + let name_offset = format!("{storage}:{}/", node.pve_id).len() + 1; + let disk_name = mount[name_offset..].split(",").next().unwrap(); + + // Persist the volume + debug!("Inserting record into volumes table for volume {name}"); + let res = volumes::ActiveModel { + name: Set(name.to_string()), + size_in_bytes: Set(size_in_bytes), + pve_node_id: Set(node.pve_id), + mountpoint: Set(Some(format!("/mnt/p5x-{name}"))), + mountpoint_identifier: Set(Some(format!("mp{mp_id}"))), + disk_name: Set(Some(disk_name.to_string())), + ..Default::default() + } + .save(svc.db) + .await + .map_err(P5xError::DbErr)?; + + info!("Wrote new volume {name} to volumes table"); + let vol = res.try_into_model() + .map_err(P5xError::DbErr)?; + + debug!("Volume {name} creation completed successfully"); + Ok(vol) +} + +pub async fn mount( + svc: &Services<'_>, + params: &VolumeParams, +) -> Result<(), P5xError> { + info!("Mounting volume {}", params.name); + if params.mountpoint.is_none() { + return Err(P5xError::BadPrecondition("Missing required mountpoint parameter")); + } + let mountpoint = params.mountpoint.as_ref().unwrap(); + + // Look up the volume instance + let vol = volumes::resolve(svc, params).await?; + + // Make sure the volume isn't already mounted somewhere + if vol.mountpoint.is_some() { + return Err(P5xError::BadPrecondition("Tried to mount volume that is already mounted")); + } + + // Lock the volume's node + let node = vol.node(svc).await?; + debug!("Locking node {} to mount volume {} at {}", node.hostname, params.name, mountpoint); + let reason = format!("Mounting volume {} at {}", params.name, mountpoint); + let _lock = node.lock(svc, Some(&reason)); + + // Find the next available mountpoint identifier + let qualified_name = vol.qualified_name(svc).await?; + let mountpoint_identifier = node.config(svc)?.next_nth("mp"); + let mount_line = format!("{qualified_name},mp={mountpoint},backup=1"); + + // Patch the node's config to mount the volume + let pve_node = svc.pve_node(&node.pve_host) + .map_err(P5xError::ServiceError)?; + + let mut put_params = PutParams::default(); + put_params.mps.insert(mountpoint_identifier, mount_line); + + debug!("Patching node config to mount volume {}", params.name); + let res = pve_node.lxc() + .vmid(node.vm_id()) + .config() + .put(put_params); + + // This is necessary because PUT returns {data: null} on success, + // which the UreqClient flags as an unknown error. + if let Err(UreqError::EncounteredErrors(e)) = res { + return Err(P5xError::PveError(UreqError::EncounteredErrors(e))); + } + + // Persist the volume + debug!("Persisting mount changes to volume {} in database", params.name); + let mut vol = vol.into_active_model(); + vol.mountpoint_identifier = Set(Some(format!("mp{mountpoint_identifier}"))); + vol.mountpoint = Set(Some(mountpoint.to_string())); + vol.save(svc.db) + .await + .map_err(P5xError::DbErr)?; + + info!("Successfully mounted volume {} at {} on {}", params.name, mountpoint, node.hostname); + Ok(()) +} + +pub async fn unmount( + svc: &Services<'_>, + params: &VolumeParams, +) -> Result<(), P5xError> { + info!("Unmounting volume {}", params.name); + + // Look up the volume instance + let vol = volumes::resolve(svc, params).await?; + + // Check that the volume is actually mounted + if vol.mountpoint.is_none() || vol.mountpoint_identifier.is_none() { + warn!("Tried to unmount volume {}#{}, but volume is not mounted", params.name, vol.volume_id); + return Ok(()); + } + + // Make sure we have the registered disk name + if vol.disk_name.is_none() { + return Err(P5xError::BadPrecondition("Tried to unmount volume without a disk_name set")); + } + + // Unmount the disk's filesystem from within the K8s node + debug!("Unmounting volume {} ({}) from K8s host", vol.name, vol.mountpoint.as_ref().unwrap()); + let cmd = format!("umount '{}'", vol.mountpoint.as_ref().unwrap()); + let node = vol.node(svc).await?; + node.ssh_run_trimmed(svc, &cmd)?; + + // Unmount the disk's filesystem from the PVE node's shadow tree (see UNMOUNT_NOTES below) + // -- Find the PID of the LXC container itself + debug!("Attempting to identify host PID for node {}", &node.pve_host); + let pve_ssh = svc.pve_ssh(&node.pve_host) + .map_err(P5xError::ServiceError)?; + let cmd = format!("lxc-info -n {} -p", node.pve_id); + let ct_pid = ssh_run_trimmed(&pve_ssh, &cmd)?; + let ct_pid = ct_pid + .split("PID:") + .nth(1) + .ok_or(P5xError::BadPrecondition("Failed to parse PID of node's LXC container"))? + .trim(); + + // -- Find the parent PID (where the shadow tree exists) + debug!("Attempting to identify parent PID for node {} (pid: {})", &node.pve_host, ct_pid); + let cmd = format!("ps -o ppid= -p {ct_pid}"); + let parent_pid = ssh_run_trimmed(&pve_ssh, &cmd)?; + + // -- Unmount the disk's filesystem from the shadow tree's mount namespace: + let mp_id = vol.mountpoint_identifier.clone().unwrap(); + debug!("Unmounting volume {} from shadow namespace (pid: {}) {}", &vol.name, parent_pid, &mp_id); + let cmd = format!("nsenter --target {parent_pid} --mount /bin/bash -c 'umount /var/lib/lxc/.pve-staged-mounts/{}'", mp_id); + ssh_run_trimmed(&pve_ssh, &cmd)?; + + // For LVM-type storage pools, we also need to deactivate the logical volume + let pool_name = svc.setting_req(|s| &s.pve_storage_pool).map_err(P5xError::ServiceError)?; + let pool_driver = svc.setting_req(|s| &s.pve_storage_driver).map_err(P5xError::ServiceError)?; + if pool_driver == "lvm" { + let cmd = format!("lvchange -aln '/dev/{pool_name}/{}'", vol.disk_name.as_ref().unwrap()); + ssh_run_trimmed(&pve_ssh, &cmd)?; + } + + // Patch the PVE node's config to mark the volume as an unused disk + let qualified_name = vol.qualified_name(svc).await?; + let lock_reason = format!("Unmounting volume {}", vol.volume_id); + let mut vol = vol.into_active_model(); + node.mutate_config( + svc, + Some(&lock_reason), + |mut conf| { + let next_unused = conf.next_nth("unused"); + conf.replace( + |s| s.starts_with(&mp_id), + |_| Some(format!("unused{next_unused}: {qualified_name}")), + ); + + vol.mountpoint_identifier = Set(Some(format!("unused{next_unused}"))); + + conf + } + ) + .await?; + + // Persist the volume changes + vol.mountpoint = Set(None); + vol.save(svc.db).await + .map_err(P5xError::DbErr)?; + + info!("Successfully unmounted volume {} from node {}", ¶ms.name, &node.hostname); + Ok(()) +} + +// UNMOUNT_NOTES +// Okay, here's some fucky-wucky shit: +// To avoid security vulnerabilities where hosts can umount their disks and break the +// firewall between CT and host, when a disk is mounted to a CT, Proxmox opens a clone +// of the mount in a special directory in the mount namespace of the container process' +// parent (i.e. the "monitor" process). +// If we umount the disk from the container, but not the monitor process, we won't be +// able to reattach any disks to the same mpX path (mp0, mp1, mp2, &c.) +// So to get around this, we (1) look up the container process, (2) figure out the +// monitor process, then (3) umount the clone. +// This was *such* a pain in the ass to figure out, but it's a testament to open- +// source that I was able to do it at all. + +pub async fn delete( + svc: &Services<'_>, + params: &VolumeParams, +) -> Result<(), P5xError> { + info!("Deleting volume {}", params.name); + + // Look up the existing volume + let vol = volumes::Entity::find() + .filter(volumes::Column::Name.eq(¶ms.name)) + .one(svc.db) + .await + .map_err(P5xError::DbErr)? + .ok_or(P5xError::BadPrecondition("Could not delete volume: unable to find volume with that name"))?; + + // Make sure we have a mountpoint identifier + if vol.mountpoint_identifier.is_none() { + return Err(P5xError::BadPrecondition("Could not delete volume: volume has no mountpoint identifier")); + } + + // Patch the PVE config to delete the volume from the config + let node = vol.node(svc).await?; + debug!("Patching node {} config to delete volume {} ({})", node.hostname, params.name, vol.mountpoint_identifier.as_ref().unwrap()); + let pve_node = svc.pve_node(&node.pve_host) + .map_err(P5xError::ServiceError)?; + + let mut pve_params = PutParams::default(); + pve_params.delete = vol.mountpoint_identifier; + + let vm_id = VmId::new(i64::from(node.pve_id)).unwrap(); + let r = pve_node + .lxc() + .vmid(vm_id) + .config() + .put(pve_params) + .map_err(P5xError::PveError); + + if let Err(P5xError::PveError(proxmox_api::UreqError::Ureq(ureq::Error::Status(status, response)))) = r { + let json_str = response.into_string().unwrap(); + error!("Error response from PVE API (status: {status}): {json_str}"); + } + debug!("Successfully patched node {} config to delete volume {}", node.hostname, params.name); + + // Persist the volume + volumes::Entity::delete_by_id(vol.volume_id) + .exec(svc.db) + .await + .map_err(P5xError::DbErr)?; + + info!("Successfully deleted volume {}#{}", vol.name, vol.volume_id); + Ok(()) +} + +pub async fn transfer( + svc: &Services<'_>, + params: &VolumeParams, + to_node: &nodes::Model, +) -> Result<(), P5xError> { + + // Look up the volume from the params + let vol = volumes::resolve(svc, params).await?; + let from_node = vol.node(svc).await?; + + // If the volume already resides on to_node, we're done + if from_node.pve_id == to_node.pve_id { + return Ok(()); + } + + // If from_node and to_node are on the same physical host, transfer directly + if from_node.pve_host == to_node.pve_host { + transfer_directly(svc, vol, &from_node, to_node).await?; + return Ok(()); + } + + // If the nodes are on different physical hosts, we need to create a temporary + // container on shared storage to attach the volume to. We'll then migrate that + // container to the target physical host. + let carrier = provision_carrier(svc, &from_node).await?; + let vol = transfer_directly(svc, vol, &from_node, &carrier).await?; + + let carrier = migrate_node(svc, carrier, &to_node.pve_host).await?; + let _vol = transfer_directly(svc, vol, &carrier, &to_node).await?; + + terminate_carrier(svc, carrier).await?; + + Ok(()) +} + +async fn transfer_directly( + svc: &Services<'_>, + vol: volumes::Model, + from_node: &nodes::Model, + to_node: &nodes::Model, +) -> Result { + if vol.mountpoint.is_some() { + return Err(P5xError::BadPrecondition("Cannot transfer volume: volume is still mounted")); + } + + if vol.mountpoint_identifier.is_none() { + return Err(P5xError::BadPrecondition("Cannot transfer volume: volume is missing mountpoint identifier")); + } + + if from_node.pve_host != to_node.pve_host { + return Err(P5xError::BadPrecondition("Cannot transfer volume: nodes reside on different physical hosts")); + } + + // Figure out the mountpoint identifier on the new node + let _lock = to_node.lock(svc, Some("Receiving volume transfer")) + .await.map_err(P5xError::LockErr)?; + + let config = to_node.config(svc)?; + let mountpoint_identifier = config.next_nth("unused"); + let mountpoint_identifier = format!("unused{mountpoint_identifier}"); + + // Ask the PVE API to move the volume to the new node + let old_mountpoint_identifier = vol.mountpoint_identifier.as_ref().unwrap().as_str(); + let pve_from_vol = move_volume::Volume::try_from(old_mountpoint_identifier).unwrap(); + let pve_to_vol = move_volume::TargetVolume::try_from(mountpoint_identifier.as_str()).unwrap(); + let mut post_params = move_volume::PostParams::new(pve_from_vol); + post_params.target_volume = Some(pve_to_vol); + post_params.target_vmid = Some(to_node.vm_id()); + + let res = svc.pve_node(&from_node.pve_host) + .map_err(P5xError::ServiceError)? + .lxc() + .vmid(from_node.vm_id()) + .move_volume() + .post(post_params); + + // This is necessary because POST returns {data: null} on success, + // which the UreqClient flags as an unknown error. + if let Err(UreqError::EncounteredErrors(e)) = res { + return Err(P5xError::PveError(UreqError::EncounteredErrors(e))); + } + + // Wait for the volume to transfer, since we don't get back a UPID + sleep(Duration::from_secs(5)).await; + + // Verify that the volume appears in the new node's config + let config = to_node.config(svc)?; + let mount = config.get(&mountpoint_identifier) + .ok_or(P5xError::BadPostcondition("Could not find mountpoint config after transferring volume"))?; + + // Parse the disk name from the config + let storage = svc.setting_req(|s| &s.pve_storage_pool) + .map_err(P5xError::ServiceError)?; + let name_offset = format!("{storage}:{}/", to_node.pve_id).len() + 1; + let disk_name = mount[name_offset..].split(",").next().unwrap(); + + // Persist the volume + let mut vol = vol.into_active_model(); + vol.pve_node_id = Set(to_node.pve_id); + vol.mountpoint_identifier = Set(Some(mountpoint_identifier)); + vol.disk_name = Set(Some(disk_name.to_string())); + + let vol = vol.save(svc.db).await + .map_err(P5xError::DbErr)?; + + volumes::Entity::find_by_id(vol.volume_id.unwrap()) + .one(svc.db) + .await + .map_err(P5xError::DbErr)? + .ok_or(P5xError::BadPostcondition("Could not look up volume after persisting")) +} diff --git a/src/api/db/migrations/m20241101_000001_create_settings_table.rs b/src/api/db/migrations/m20241101_000001_create_settings_table.rs new file mode 100644 index 0000000..f9354e6 --- /dev/null +++ b/src/api/db/migrations/m20241101_000001_create_settings_table.rs @@ -0,0 +1,58 @@ +use async_trait::async_trait; +use sea_orm_migration::{prelude::*, schema::*}; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(Settings::Table) + .if_not_exists() + .col(pk_auto(Settings::Id)) + .col(string_null(Settings::PveMasterNode)) + .col(string_null(Settings::PveApiHost)) + .col(string_null(Settings::PveRootPassword)) + .col(string_null(Settings::PveStoragePool)) + .col(string_null(Settings::PveStorageDriver)) + .col(string_null(Settings::DnsDomain)) + .col(string_null(Settings::NodeNetworkBridge)) + .col(big_unsigned_null(Settings::NodeCpus)) + .col(big_unsigned_null(Settings::NodeRamInMib)) + .col(string_null(Settings::RootPassword)) + .col(text_null(Settings::SshPublicKey)) + .col(text_null(Settings::SshPrivateKey)) + .to_owned()) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table( + Table::drop() + .table(Settings::Table) + .to_owned()) + .await + } +} + +#[derive(DeriveIden)] +enum Settings { + Table, + Id, + PveMasterNode, + PveApiHost, + PveRootPassword, + PveStoragePool, + PveStorageDriver, + DnsDomain, + NodeNetworkBridge, + NodeCpus, + NodeRamInMib, + RootPassword, + SshPublicKey, + SshPrivateKey, +} diff --git a/src/api/db/migrations/m20241102_000001_create_nodes_table.rs b/src/api/db/migrations/m20241102_000001_create_nodes_table.rs new file mode 100644 index 0000000..a35d125 --- /dev/null +++ b/src/api/db/migrations/m20241102_000001_create_nodes_table.rs @@ -0,0 +1,46 @@ +use async_trait::async_trait; +use sea_orm_migration::{prelude::*, schema::*}; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(Nodes::Table) + .if_not_exists() + .col(pk_auto(Nodes::Id)) + .col(string(Nodes::Hostname)) + .col(integer(Nodes::PveId)) + .col(string(Nodes::PveHost)) + .col(string(Nodes::AssignedIp)) + .col(integer(Nodes::AssignedSubnet)) + .col(boolean(Nodes::IsPermanent).default(false)) + .to_owned()) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table( + Table::drop() + .table(Nodes::Table) + .to_owned()) + .await + } +} + +#[derive(DeriveIden)] +enum Nodes { + Table, + Id, + Hostname, + PveId, + PveHost, + AssignedIp, + AssignedSubnet, + IsPermanent, +} diff --git a/src/api/db/migrations/m20241102_000002_create_locks_table.rs b/src/api/db/migrations/m20241102_000002_create_locks_table.rs new file mode 100644 index 0000000..78709cc --- /dev/null +++ b/src/api/db/migrations/m20241102_000002_create_locks_table.rs @@ -0,0 +1,42 @@ +use async_trait::async_trait; +use sea_orm_migration::{prelude::*, schema::*}; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(Locks::Table) + .if_not_exists() + .col(pk_auto(Locks::Id)) + .col(string(Locks::LockType)) + .col(string(Locks::LockResource)) + .col(string_null(Locks::LockOwner)) + .col(text_null(Locks::LockReason)) + .to_owned()) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table( + Table::drop() + .table(Locks::Table) + .to_owned()) + .await + } +} + +#[derive(DeriveIden)] +enum Locks { + Table, + Id, + LockType, + LockResource, + LockOwner, + LockReason +} diff --git a/src/api/db/migrations/m20241103_000001_create_volumes_table.rs b/src/api/db/migrations/m20241103_000001_create_volumes_table.rs new file mode 100644 index 0000000..bf371e5 --- /dev/null +++ b/src/api/db/migrations/m20241103_000001_create_volumes_table.rs @@ -0,0 +1,48 @@ +use async_trait::async_trait; +use sea_orm_migration::{prelude::*, schema::*}; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(Volumes::Table) + .if_not_exists() + .col(pk_auto(Volumes::VolumeId)) + .col(string(Volumes::Name)) + .col(big_unsigned(Volumes::SizeInBytes)) + .col(integer(Volumes::PveNodeId)) + .col(string_null(Volumes::MountpointIdentifier)) + .col(string_null(Volumes::MountpointHost)) + .col(string_null(Volumes::Mountpoint)) + .col(string_null(Volumes::DiskName)) + .to_owned()) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table( + Table::drop() + .table(Volumes::Table) + .to_owned()) + .await + } +} + +#[derive(DeriveIden)] +enum Volumes { + Table, + VolumeId, + Name, + SizeInBytes, + PveNodeId, + MountpointIdentifier, + MountpointHost, + Mountpoint, + DiskName, +} diff --git a/src/api/db/migrations/mod.rs b/src/api/db/migrations/mod.rs new file mode 100644 index 0000000..6a03f53 --- /dev/null +++ b/src/api/db/migrations/mod.rs @@ -0,0 +1,35 @@ +use async_trait::async_trait; +use rocket::{fairing, Build, Rocket}; +use rocket::fairing::AdHoc; +pub use sea_orm_migration::prelude::*; +use sea_orm_rocket::Database; +use crate::api::Db; + +mod m20241101_000001_create_settings_table; +mod m20241102_000001_create_nodes_table; +mod m20241102_000002_create_locks_table; +mod m20241103_000001_create_volumes_table; + +pub struct Migrator; + +#[async_trait] +impl MigratorTrait for Migrator { + fn migrations() -> Vec> { + vec![ + Box::new(m20241101_000001_create_settings_table::Migration), + Box::new(m20241102_000001_create_nodes_table::Migration), + Box::new(m20241102_000002_create_locks_table::Migration), + Box::new(m20241103_000001_create_volumes_table::Migration), + ] + } +} + +async fn run_migrations(rocket: Rocket) -> fairing::Result { + let conn = &Db::fetch(&rocket).unwrap().conn; + let _ = Migrator::up(conn, None).await; + Ok(rocket) +} + +pub(super) fn init() -> AdHoc { + AdHoc::try_on_ignite("Applying migrations", run_migrations) +} diff --git a/src/api/db/mod.rs b/src/api/db/mod.rs new file mode 100644 index 0000000..c449fc3 --- /dev/null +++ b/src/api/db/mod.rs @@ -0,0 +1,58 @@ +mod migrations; + +use std::time::Duration; +use rocket::figment::Figment; +use sea_orm; +use sea_orm_rocket::{Config, Database, Pool}; +use async_trait::async_trait; +use rocket::fairing::AdHoc; +use sea_orm::ConnectOptions; + +#[derive(Database, Debug)] +#[database("p5x_api")] +pub struct Db(DbPool); + +#[derive(Debug, Clone)] +pub struct DbPool { + pub conn: sea_orm::DatabaseConnection, +} + +#[async_trait] +impl Pool for DbPool { + type Connection = sea_orm::DatabaseConnection; + type Error = sea_orm::DbErr; + + async fn init(figment: &Figment) -> Result { + let config = figment.extract::().unwrap(); + let mut options: ConnectOptions = config.url.into(); + + options + .max_connections(config.max_connections as u32) + .min_connections(config.min_connections.unwrap_or_default()) + .connect_timeout(Duration::from_secs(config.connect_timeout)) + .sqlx_logging(config.sqlx_logging); + + if let Some(idle_timeout) = config.idle_timeout { + options.idle_timeout(Duration::from_secs(idle_timeout)); + } + + let conn = sea_orm::Database::connect(options).await?; + Ok(DbPool { conn }) + } + + fn borrow(&self) -> &Self::Connection { + &self.conn + } +} + +// async fn run_migrations(rocket: Rocket) -> fairing::Result { +// let conn = &Db::fetch(&rocket).unwrap().conn; +// +// } + +pub fn init() -> AdHoc { + AdHoc::on_ignite("Setting up database pool", |rocket| async { + rocket.attach(Db::init()) + .attach(migrations::init()) + }) +} diff --git a/src/api/entity/locks.rs b/src/api/entity/locks.rs new file mode 100644 index 0000000..df202f5 --- /dev/null +++ b/src/api/entity/locks.rs @@ -0,0 +1,101 @@ +use std::time::Duration; +use rocket::futures; +use sea_orm::ActiveValue::Set; +use sea_orm::entity::prelude::*; +use sea_orm::{IntoActiveModel}; +use serde::{Deserialize, Serialize}; +use tokio::time::sleep; +use uuid::Uuid; + +pub async fn try_lock<'a>( + db: &'a DatabaseConnection, + lock_type: &str, + lock_resource: &str, + lock_reason: Option<&str>, +) -> Result>, DbErr> { + let owner = Uuid::new_v4(); + let mut l = ActiveModel::new(); + l.lock_owner = Set(Some(owner.to_string())); + l.lock_reason = Set(lock_reason.map(|s| s.to_string())); + + Entity::update_many() + .filter(Column::LockOwner.is_null()) + .filter(Column::LockType.eq(lock_type)) + .filter(Column::LockResource.eq(lock_resource)) + .set(l) + .exec(db) + .await?; + + Entity::find() + .filter(Column::LockOwner.eq(owner.to_string())) + .filter(Column::LockType.eq(lock_type)) + .filter(Column::LockResource.eq(lock_resource)) + .one(db) + .await + .map(|lock| + lock.map(|lock | LockHandle::new(lock, db))) +} + +pub async fn lock<'a>( + db: &'a DatabaseConnection, + lock_type: &str, + lock_resource: &str, + lock_reason: Option<&str>, +) -> Result, DbErr> { + loop { + let r = try_lock(db, lock_type, lock_resource, lock_reason).await?; + + if let Some(r) = r { + return Ok(r); + } + + sleep(Duration::from_secs(10)).await; + } +} + +pub async fn lock_vmid<'a>(db: &'a DatabaseConnection, lock_reason: Option<&str>) -> Result, DbErr> { + lock(db, "global_vmid", "0", lock_reason).await +} + +#[derive(Clone)] +pub struct LockHandle<'a> { + pub lock: Model, + db: &'a DatabaseConnection, +} + +impl<'a> LockHandle<'a> { + async fn release(&mut self) { + debug!("Releasing {} {} lock", self.lock.lock_type, self.lock.lock_resource); + + let mut l = self.lock.clone().into_active_model(); + l.lock_owner = Set(None); + l.lock_reason = Set(None); + l.save(self.db).await.unwrap(); + } + + fn new(lock: Model, db: &'a DatabaseConnection) -> LockHandle<'a> { + LockHandle { lock, db } + } +} + +impl<'a> Drop for LockHandle<'a> { + fn drop(&mut self) { + futures::executor::block_on(self.release()); + } +} + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Deserialize, Serialize, FromForm)] +#[sea_orm(table_name = "locks")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub lock_type: String, + pub lock_resource: String, + pub lock_owner: Option, + pub lock_reason: Option, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/src/api/entity/mod.rs b/src/api/entity/mod.rs new file mode 100644 index 0000000..d9ec75c --- /dev/null +++ b/src/api/entity/mod.rs @@ -0,0 +1,4 @@ +pub mod settings; +pub mod locks; +pub mod nodes; +pub mod volumes; diff --git a/src/api/entity/nodes.rs b/src/api/entity/nodes.rs new file mode 100644 index 0000000..77a92ac --- /dev/null +++ b/src/api/entity/nodes.rs @@ -0,0 +1,326 @@ +use std::fmt::Display; +use std::net::TcpStream; +use ssh2::{OpenFlags, OpenType, Session}; +use std::io::{Read, Write}; +use std::path::Path; +use std::time::Duration; +use sea_orm::entity::prelude::*; +use sea_orm::QueryOrder; +use serde::{Deserialize, Serialize}; +use tokio::time::sleep; +use proxmox_api; +use log::{warn, debug}; +use proxmox_api::types::VmId; +use crate::api::entity::{locks, settings}; +use crate::api::entity::locks::{lock, try_lock, LockHandle}; +use crate::api::services::{Services, ServiceError, SshError, ssh_run_trimmed}; + +#[derive(Debug)] +pub enum NodeLockErr { + DbErr(DbErr), + WaitTimeLimitExceeded, +} + +#[derive(Debug)] +pub enum P5xError { + DbErr(DbErr), + LockErr(NodeLockErr), + ServiceError(ServiceError), + PveError(proxmox_api::UreqError), + SshError(SshError), + InvalidNetworkInterface, + BadPrecondition(&'static str), + BadPostcondition(&'static str), + UpidFailed(String, String), +} + +pub async fn lock_first_available<'a>( + db: &'a DatabaseConnection, + lock_reason: Option<&str>, +) -> Result<(Model, locks::LockHandle<'a>), NodeLockErr> { + for _ in 0..600 { + let r = try_lock_first_available(db, lock_reason) + .await + .map_err(NodeLockErr::DbErr)?; + + if let Some(r) = r { + return Ok(r); + } + + sleep(Duration::from_secs(10)).await; + } + + warn!("Failed to acquire a lock on ANY nodes before the timeout was exceeded!"); + Err(NodeLockErr::WaitTimeLimitExceeded) +} + +pub async fn try_lock_first_available<'a>( + db: &'a DatabaseConnection, + lock_reason: Option<&str>, +) -> Result)>, DbErr> { + debug!("Trying to lock first available node. Reason: {}", lock_reason.unwrap_or("(none)")); + + // Get all permanent nodes + let nodes = Entity::find() + .filter(Column::IsPermanent.eq(true)) + .order_by_asc(Column::Id) + .all(db) + .await?; + + // Try to lock them and return the first successful one + for node in nodes { + let lock = + try_lock(db, "nodes", &node.id.to_string(), lock_reason) + .await?; + + if let Some(lock) = lock { + info!("Locked node {} ({}): {}", node.hostname, node.pve_id, lock_reason.unwrap_or("(none)")); + return Ok(Some((node, lock))) + } + } + + // No available nodes to lock + Ok(None) +} + + +pub struct PveConfig { + lines: Vec, +} + +impl PveConfig { + pub fn new(conf: &str) -> PveConfig { + let lines = conf.split("\n") + .map(|s| s.to_string()) + .collect(); + + PveConfig { lines } + } + + pub fn replace( + &mut self, + matcher: F1, + replacer: F2, + ) + where + F1: Fn(&String) -> bool, + F2: Fn(&String) -> Option + { + self.map(|s: &String| { + if matcher(s) { + return replacer(s); + } + + Some(s.to_string()) + }); + } + + pub fn map( + &mut self, + replacer: F + ) + where + F: FnMut(&String) -> Option + { + self.lines = self.lines + .iter() + .filter_map(replacer) + .collect(); + } + + pub fn get(&self, key: &str) -> Option { + let key = format!("{key}:"); + self.lines + .iter() + .filter(|line| line.starts_with(&key)) + .next() + .map(|s| s.to_string()) + } + + pub fn next_nth(&self, prefix: &str) -> u32 { + let res = self.lines + .iter() + .filter(|line| line.starts_with(prefix)) + .map(|line| + line[prefix.len()..] + .split(":") + .next() + .unwrap() + .parse::()) + .filter_map(|idx| idx.ok()) + .next(); + + if let Some(idx) = res { + return idx + 1; + } + + 0 + } +} + +impl Display for PveConfig { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.lines.join("\n")) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Deserialize, Serialize, FromForm)] +#[sea_orm(table_name = "nodes")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub hostname: String, + pub pve_id: i32, + pub pve_host: String, + pub assigned_ip: String, + pub assigned_subnet: u32, + pub is_permanent: bool, +} + +impl Model { + fn setting(&self, svc: &Services, f: F) -> Result + where + F: FnOnce(&settings::Model) -> &Option { + svc.setting_req(f).map_err(P5xError::ServiceError) + } + + pub fn vm_id(&self) -> VmId { + VmId::new(i64::from(self.pve_id)).unwrap() + } + + pub fn ssh(&self, svc: &Services) -> Result { + let addr = format!("{}:22", self.assigned_ip); + let tcp = TcpStream::connect(addr) + .map_err(SshError::IoError) + .map_err(P5xError::SshError)?; + + let mut sess = Session::new() + .map_err(SshError::ClientError) + .map_err(P5xError::SshError)?; + + sess.set_tcp_stream(tcp); + sess.handshake() + .map_err(SshError::ClientError) + .map_err(P5xError::SshError)?; + + let pubkey = self.setting(svc, |s| &s.ssh_public_key)?; + let privkey = self.setting(svc, |s| &s.ssh_private_key)?; + log::debug!("privkey: {privkey}"); + + sess.userauth_pubkey_memory("root", Some(&pubkey), &privkey, None) + .map_err(SshError::ClientError) + .map_err(P5xError::SshError)?; + + if !sess.authenticated() { + return Err(P5xError::SshError(SshError::AuthFailed)); + } + + Ok(sess) + } + + pub fn config(&self, svc: &Services) -> Result { + let pve_ssh = svc.pve_ssh(&self.pve_host) + .map_err(P5xError::ServiceError)?; + + let path = format!("/etc/pve/lxc/{}.conf", self.pve_id); + let path = Path::new(&path); + + let (mut remote_file, _) = pve_ssh.scp_recv(path) + .map_err(SshError::ClientError) + .map_err(P5xError::SshError)?; + + let mut contents = String::new(); + remote_file.read_to_string(&mut contents) + .map_err(SshError::IoError) + .map_err(P5xError::SshError)?; + + // Close the channel and wait for the whole content to be transferred + remote_file.send_eof().map_err(SshError::ClientError).map_err(P5xError::SshError)?; + remote_file.wait_eof().map_err(SshError::ClientError).map_err(P5xError::SshError)?; + remote_file.close().map_err(SshError::ClientError).map_err(P5xError::SshError)?; + remote_file.wait_close().map_err(SshError::ClientError).map_err(P5xError::SshError)?; + + Ok(PveConfig::new(&contents)) + } + + pub fn write_config(&self, svc: &Services, conf: &PveConfig) -> Result<(), P5xError> { + let conf = conf.to_string(); + let conf = conf.as_bytes(); + let pve_ssh = svc.pve_ssh(&self.pve_host) + .map_err(P5xError::ServiceError)?; + + // Write the file + let sftp = pve_ssh.sftp() + .map_err(SshError::ClientError) + .map_err(P5xError::SshError)?; + + let path = format!("/etc/pve/lxc/{}.conf", self.pve_id); + let path = Path::new(&path); + + debug!("Attempting to open file to mutate config {} on {}", path.display(), self.pve_host); + let mut f = sftp.open_mode(path, OpenFlags::WRITE, 0o640, OpenType::File) + .map_err(SshError::ClientError) + .map_err(P5xError::SshError)?; + + debug!("Attempting to write config to {} on {}", path.display(), self.pve_host); + f.write_all(conf) + .map_err(SshError::IoError) + .map_err(P5xError::SshError)?; + + /*let mut remote_file = pve_ssh.scp_send(path, 0o640, conf.len() as u64, None) + .map_err(SshError::ClientError) + .map_err(P5xError::SshError)?; + + remote_file.write_all(conf).map_err(SshError::IoError).map_err(P5xError::SshError)?;*/ + + // Close the channel and wait for the whole content to be transferred + /*remote_file.send_eof().map_err(SshError::ClientError).map_err(P5xError::SshError)?; + remote_file.wait_eof().map_err(SshError::ClientError).map_err(P5xError::SshError)?; + remote_file.close().map_err(SshError::ClientError).map_err(P5xError::SshError)?; + remote_file.wait_close().map_err(SshError::ClientError).map_err(P5xError::SshError)?;*/ + + Ok(()) + } + + pub async fn try_lock<'a>(&self, svc: &Services<'a>, lock_reason: Option<&str>) -> Result>, NodeLockErr> { + try_lock(svc.db, "nodes", &self.id.to_string(), lock_reason) + .await + .map_err(NodeLockErr::DbErr) + } + + pub async fn lock<'a>(&self, svc: &Services<'a>, lock_reason: Option<&str>) -> Result, NodeLockErr> { + lock(svc.db, "nodes", &self.id.to_string(), lock_reason) + .await + .map_err(NodeLockErr::DbErr) + } + + pub async fn mutate_config( + &self, + svc: &Services<'_>, + lock_reason: Option<&str>, + mutator: F, + ) -> Result<(), P5xError> + where + F: FnOnce(PveConfig) -> PveConfig + { + debug!("Mutating config on node {}", self.hostname); + let _lock = self.lock(svc, lock_reason) + .await.map_err(P5xError::LockErr)?; + + let config = self.config(svc)?; + debug!("Successfully loaded config for mutate on node {}", self.hostname); + let config = mutator(config); + + self.write_config(svc, &config) + } + + pub fn ssh_run_trimmed(&self, svc: &Services, cmd: &str) -> Result { + let node_ssh = self.ssh(svc)?; + ssh_run_trimmed(&node_ssh, cmd) + } +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/src/api/entity/settings.rs b/src/api/entity/settings.rs new file mode 100644 index 0000000..e69c4ee --- /dev/null +++ b/src/api/entity/settings.rs @@ -0,0 +1,29 @@ +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Deserialize, Serialize, FromForm)] +#[sea_orm(table_name = "settings")] +pub struct Model { + #[sea_orm(primary_key)] + #[field(default = 0)] + pub id: i32, + pub pve_master_node: Option, + pub pve_api_host: Option, + pub pve_root_password: Option, + pub pve_storage_pool: Option, + pub pve_storage_driver: Option, + // pub dns_domain: Option, + // pub node_network_bridge: Option, + // pub node_cpus: Option, + // pub node_ram_in_mib: Option, + // pub root_password: Option, + #[sea_orm(column_type = "Text")] + pub ssh_public_key: Option, + #[sea_orm(column_type = "Text")] + pub ssh_private_key: Option, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/src/api/entity/volumes.rs b/src/api/entity/volumes.rs new file mode 100644 index 0000000..094bd82 --- /dev/null +++ b/src/api/entity/volumes.rs @@ -0,0 +1,83 @@ +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; +use crate::api::cluster::volume::VolumeParams; +use crate::api::entity::nodes; +use crate::api::entity::nodes::P5xError; +use crate::api::services::Services; + +pub async fn resolve( + svc: &Services<'_>, + params: &VolumeParams, +) -> Result { + let mut q = Entity::find(); + + match params.id { + Some(id) => q = q.filter(Column::VolumeId.eq(id)), + None => q = q.filter(Column::Name.eq(¶ms.name)), + }; + + q.one(svc.db) + .await + .map_err(P5xError::DbErr)? + .ok_or(P5xError::BadPrecondition("Could not resolve volume from params: query matched no results")) +} + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Deserialize, Serialize, FromForm)] +#[sea_orm(table_name = "volumes")] +pub struct Model { + #[sea_orm(primary_key)] + pub volume_id: i32, + pub name: String, + pub size_in_bytes: i64, + pub pve_node_id: i32, + pub mountpoint_identifier: Option, + pub mountpoint_host: Option, + pub mountpoint: Option, + pub disk_name: Option, +} + +impl Model { + pub async fn node(&self, svc: &Services<'_>) -> Result { + nodes::Entity::find() + .filter(nodes::Column::PveId.eq(self.pve_node_id)) + .one(svc.db) + .await + .map_err(P5xError::DbErr)? + .ok_or(P5xError::BadPrecondition("Could not find node for volume: pve_node_id does not match any nodes")) + } + + pub async fn qualified_name(&self, svc: &Services<'_>) -> Result { + let node = self.node(svc).await?; + let storage = svc.setting_req(|s| &s.pve_storage_pool) + .map_err(P5xError::ServiceError)?; + + let storage_type = svc.setting_req(|s| &s.pve_storage_driver) + .map_err(P5xError::ServiceError)?; + + let disk_name = format!("pve-{}.raw", self.name); + let disk_name = self.disk_name.as_ref().unwrap_or(&disk_name); + + let qn = match storage_type.as_str() { + "lvm" => format!("{storage}:{disk_name}"), + _ => format!("{storage}:{}/{disk_name}", node.pve_id), + }; + + Ok(qn) + } +} + +impl Into for Model { + fn into(self) -> VolumeParams { + VolumeParams { + id: Some(self.volume_id), + name: self.name, + size_in_bytes: self.size_in_bytes, + mountpoint: self.mountpoint, + } + } +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/src/api/mod.rs b/src/api/mod.rs new file mode 100644 index 0000000..1e56358 --- /dev/null +++ b/src/api/mod.rs @@ -0,0 +1,16 @@ +use rocket::fairing::AdHoc; + +mod db; +mod route; +mod util; +mod cluster; +pub mod entity; +pub use db::Db; +pub mod services; + +pub fn init() -> AdHoc { + AdHoc::on_ignite("mod(db)", |rocket| async { + rocket.attach(db::init()) + .attach(route::init()) + }) +} diff --git a/src/api/route/configure.rs b/src/api/route/configure.rs new file mode 100644 index 0000000..faac92e --- /dev/null +++ b/src/api/route/configure.rs @@ -0,0 +1,51 @@ +use rocket::fairing::AdHoc; +use rocket::form::Form; +use rocket::response::{status}; +use rocket_dyn_templates::{context, Template}; +use sea_orm::*; +use sea_orm_rocket::Connection; +use crate::api; +use crate::api::entity::settings; +use crate::api::util::raise_500; + +async fn render(db: &DatabaseConnection) -> Result> { + let settings = settings::Entity::find().order_by_desc(settings::Column::Id).one(db).await.map_err(raise_500)?; + Ok(Template::render("settings", context! { + settings: settings, + })) +} + +#[get("/")] +async fn get( + conn: Connection<'_, api::Db>, +) -> Result> { + let db = conn.into_inner(); + render(db).await +} + +#[post("/", data = "")] +async fn save( + conn: Connection<'_, api::Db>, + input: Form, +) -> Result> { + let db = conn.into_inner(); + + settings::ActiveModel { + pve_master_node: Set(input.pve_master_node.to_owned()), + pve_api_host: Set(input.pve_api_host.to_owned()), + pve_root_password: Set(input.pve_root_password.to_owned()), + pve_storage_pool: Set(input.pve_storage_pool.to_owned()), + ..Default::default() + } + .save(db) + .await + .map_err(raise_500)?; + + render(db).await +} + +pub(super) fn init() -> AdHoc { + AdHoc::on_ignite("Routes: /configure", |rocket| async { + rocket.mount("/configure", routes![get, save]) + }) +} diff --git a/src/api/route/mod.rs b/src/api/route/mod.rs new file mode 100644 index 0000000..76d5415 --- /dev/null +++ b/src/api/route/mod.rs @@ -0,0 +1,11 @@ +use rocket::fairing::AdHoc; + +mod configure; +mod volume; + +pub(super) fn init() -> AdHoc { + AdHoc::on_ignite("Registering routes", |rocket| async { + rocket.attach(configure::init()) + .attach(volume::init()) + }) +} diff --git a/src/api/route/volume.rs b/src/api/route/volume.rs new file mode 100644 index 0000000..e57a575 --- /dev/null +++ b/src/api/route/volume.rs @@ -0,0 +1,130 @@ +use rocket::fairing::AdHoc; +use rocket::response::status; +use rocket::serde::json::Json; +use sea_orm_rocket::Connection; +use sea_orm::*; +use crate::api; +use crate::api::cluster; +use crate::api::cluster::volume::VolumeParams; +use crate::api::entity::nodes; +use crate::api::entity::nodes::P5xError; +use crate::api::services::Services; +use crate::api::util::raise_500; + +#[get("/")] +async fn get_vol( + conn: Connection<'_, api::Db>, + name: &str, +) -> Result, status::Custom> { + let db = conn.into_inner(); + let svc = Services::build(db).await.map_err(raise_500)?; + + let vol = VolumeParams::resolve(&svc, name) + .await.map_err(raise_500)? + .ok_or(P5xError::BadPrecondition("Could not find a volume with that name")) + .map_err(raise_500)?; + + Ok(Json(vol)) +} + +#[post("/", data = "")] +async fn create_vol( + conn: Connection<'_, api::Db>, + input: Json, +) -> Result, status::Custom> { + let input = input.into_inner(); + let db = conn.into_inner(); + let svc = Services::build(db).await.map_err(raise_500)?; + + let vol: VolumeParams = cluster::volume::create(&svc, &input.name, input.size_in_bytes) + .await + .map_err(raise_500)? + .into(); + + cluster::volume::unmount(&svc, &vol).await.map_err(raise_500)?; + + Ok(Json(vol.into())) +} + +#[delete("/")] +async fn delete_vol( + conn: Connection<'_, api::Db>, + name: &str, +) -> Result<(), status::Custom> { + let db = conn.into_inner(); + let svc = Services::build(db).await.map_err(raise_500)?; + + let vol = VolumeParams::resolve(&svc, name) + .await.map_err(raise_500)? + .ok_or(P5xError::BadPrecondition("Could not find a volume with that name")) + .map_err(raise_500)?; + + cluster::volume::unmount(&svc, &vol).await.map_err(raise_500)?; + cluster::volume::delete(&svc, &vol).await.map_err(raise_500)?; + + Ok(()) +} + +#[post("/mount", data = "")] +async fn mount_vol( + conn: Connection<'_, api::Db>, + params: Json, +) -> Result<(), status::Custom> { + let db = conn.into_inner(); + let svc = Services::build(db).await.map_err(raise_500)?; + let params = params.into_inner(); + + cluster::volume::mount(&svc, ¶ms).await.map_err(raise_500)?; + + Ok(()) +} + +#[post("/unmount/")] +async fn unmount_vol( + conn: Connection<'_, api::Db>, + name: &str, +) -> Result<(), status::Custom> { + let db = conn.into_inner(); + let svc = Services::build(db).await.map_err(raise_500)?; + + let vol = VolumeParams::resolve(&svc, name) + .await.map_err(raise_500)? + .ok_or(P5xError::BadPrecondition("Could not find a volume with that name")) + .map_err(raise_500)?; + + cluster::volume::unmount(&svc, &vol).await.map_err(raise_500)?; + + Ok(()) +} + +#[post("/transfer//to/")] +async fn transfer_vol( + conn: Connection<'_, api::Db>, + name: &str, + node: &str, +) -> Result<(), status::Custom> { + let db = conn.into_inner(); + let svc = Services::build(db).await.map_err(raise_500)?; + + let vol = VolumeParams::resolve(&svc, name) + .await.map_err(raise_500)? + .ok_or(P5xError::BadPrecondition("Could not find a volume with that name")) + .map_err(raise_500)?; + + let node = nodes::Entity::find() + .filter(nodes::Column::Hostname.eq(node)) + .one(db) + .await.map_err(raise_500)? + .ok_or(P5xError::BadPrecondition("Could not find a node with that name")) + .map_err(raise_500)?; + + cluster::volume::transfer(&svc, &vol, &node).await.map_err(raise_500)?; + + Ok(()) +} + +pub(super) fn init() -> AdHoc { + AdHoc::on_ignite("Routes: /volume", |rocket| async { + rocket.mount("/volume", routes![create_vol, delete_vol, get_vol, mount_vol, unmount_vol, transfer_vol]) + }) +} diff --git a/src/api/services.rs b/src/api/services.rs new file mode 100644 index 0000000..860963f --- /dev/null +++ b/src/api/services.rs @@ -0,0 +1,152 @@ +use std::io::Read; +use std::net::TcpStream; +use proxmox_api::nodes::node::NodeClient; +use proxmox_api::UreqClient; +use sea_orm::{DatabaseConnection, DbErr, EntityTrait, QueryOrder}; +use ssh2::Session; +use crate::api::entity::nodes::P5xError; +use crate::api::entity::settings; + +#[derive(Debug)] +pub enum SshError { + IoError(std::io::Error), + ClientError(ssh2::Error), + CommandFailed(i32, String), + AuthFailed, +} + +impl SshError { + pub fn command_failed(exit_code: i32, command: &str, output: &str) -> SshError { + SshError::CommandFailed(exit_code, format!("Command `{command}` failed: {output}")) + } +} + +#[derive(Debug)] +pub enum ServiceError { + MissingSetting, + PveError(proxmox_api::UreqError), + InvalidNetworkInterface, + SshError(SshError), +} + +pub struct Services<'a> { + pub settings: Option, + pub db: &'a DatabaseConnection, +} + +impl<'a> Services<'a> { + pub async fn build(db: &'a DatabaseConnection) -> Result, DbErr> { + let settings = settings::Entity::find().order_by_desc(settings::Column::Id).one(db).await?; + Ok(Services { + db, + settings, + }) + } + + pub fn setting(&self, f: F) -> Option + where + F: FnOnce(&settings::Model) -> &Option, + { + let model = self.settings.as_ref()?; + let res = f(model); + if let Some(res) = res { + return Some(res.clone()); + } + None + } + + pub fn setting_req(&self, f: F) -> Result + where + F: FnOnce(&settings::Model) -> &Option + { + self.setting(f).ok_or(ServiceError::MissingSetting) + } + + pub fn pve(&self) -> Result { + let host = self.setting_req(|s| &s.pve_api_host)?; + let pw = self.setting_req(|s| &s.pve_root_password)?; + + let host = format!("https://{host}:8006"); + let api = proxmox_api::UreqClient::new(&host, "root", "pam", &pw) + .map_err(ServiceError::PveError)?; + + Ok(api) + } + + pub fn pve_node(&self, host: &str) -> Result, ServiceError> { + let pve = self.pve()?; + Ok(proxmox_api::nodes::NodesClient::new(pve).node(host)) + } + + pub fn pve_addr(&self, host: &str) -> Result { + let ifaces = self.pve_node(host)? + .network() + .get(Default::default()) + .map_err(ServiceError::PveError)?; + + let addr = ifaces.iter() + .filter_map(|i| i.additional_properties.get("address")) + .next() + .ok_or(ServiceError::InvalidNetworkInterface)?; + + if !addr.is_string() { + return Err(ServiceError::InvalidNetworkInterface); + } + + Ok(addr.as_str().unwrap().to_string()) + } + + pub fn pve_ssh(&self, host: &str) -> Result { + let addr = self.pve_addr(host)?; + let addr = format!("{}:22", addr); + let tcp = TcpStream::connect(addr) + .map_err(SshError::IoError) + .map_err(ServiceError::SshError)?; + + let mut sess = Session::new() + .map_err(SshError::ClientError) + .map_err(ServiceError::SshError)?; + + sess.set_tcp_stream(tcp); + sess.handshake() + .map_err(SshError::ClientError) + .map_err(ServiceError::SshError)?; + + let pw = self.setting_req(|s| &s.pve_root_password)?; + sess.userauth_password("root", &pw) + .map_err(SshError::ClientError) + .map_err(ServiceError::SshError)?; + + if !sess.authenticated() { + return Err(ServiceError::SshError(SshError::AuthFailed)); + } + + Ok(sess) + } +} + +pub fn ssh_run_trimmed(session: &Session, cmd: &str) -> Result { + let mut channel = session.channel_session() + .map_err(SshError::ClientError) + .map_err(P5xError::SshError)?; + + channel.exec(cmd) + .map_err(SshError::ClientError) + .map_err(P5xError::SshError)?; + + let mut contents = String::new(); + channel.read_to_string(&mut contents) + .map_err(SshError::IoError) + .map_err(P5xError::SshError)?; + + let exit_code = channel.exit_status() + .map_err(SshError::ClientError) + .map_err(P5xError::SshError)?; + + let contents = contents.trim(); + if exit_code != 0 { + return Err(P5xError::SshError(SshError::command_failed(exit_code, cmd, &contents))); + } + + Ok(contents.to_string()) +} diff --git a/src/api/util.rs b/src/api/util.rs new file mode 100644 index 0000000..2adc399 --- /dev/null +++ b/src/api/util.rs @@ -0,0 +1,7 @@ +use std::fmt::Debug; +use rocket::http; +use rocket::response::status; + +pub fn raise_500(e: impl Debug) -> status::Custom { + status::Custom(http::Status::InternalServerError, format!("An unexpected error has occurred: {:?}", e)) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..70ca95e --- /dev/null +++ b/src/main.rs @@ -0,0 +1,33 @@ +pub mod api; +#[macro_use] extern crate rocket; +use rocket::{Build, Rocket}; +use log::{error, info}; +use std::{env, process}; +use rocket_dyn_templates::{ Template}; + +fn configure_rocket() -> Rocket { + rocket::build() + .attach(Template::fairing()) + .attach(api::init()) +} + +#[tokio::main] +async fn main() { + env_logger::init(); + info!(target: "p5x", "Starting p5x..."); + + let args: Vec = env::args().collect(); + if args.len() < 2 { + error!(target: "p5x", "Missing required argument. Valid modes: api-server"); + process::exit(1); + } + + let mode = &args[1]; + if mode == "api-server" { + let rocket = configure_rocket(); + if let Err(e) = rocket.launch().await { + error!(target: "p5x", "Rocket failed to launch: {:?}", e) + } + return; + } +}