commit 8078234ada01ca746106fcbab1b854f93ccc6b00 Author: Maxime Van Hees Date: Thu Jul 17 20:08:48 2025 +0200 first commit: added functionality for server listing, ssh key management and boot configuration management diff --git a/.env b/.env new file mode 100644 index 0000000..838aa5d --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +HETZNER_USERNAME="#ws+JdQtGCdL" +HETZNER_PASSWORD="Kds007kds!" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f115879 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2040 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "const-random", + "getrandom 0.3.3", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "tiny-keccak", +] + +[[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 = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "csv" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" +dependencies = [ + "memchr", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[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 = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-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-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[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-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "h2" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-uring" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is-terminal" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libredox" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "prettytable" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46480520d1b77c9a3482d39939fcf96831537a250ec62d4fd8fbdf8e0302e781" +dependencies = [ + "csv", + "encode_unicode", + "is-terminal", + "lazy_static", + "term", + "unicode-width", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "redox_syscall" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "reqwest" +version = "0.12.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "rhai" +version = "1.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2780e813b755850e50b178931aaf94ed24f6817f46aaaf5d21c13c12d939a249" +dependencies = [ + "ahash", + "bitflags", + "instant", + "num-traits", + "once_cell", + "rhai_codegen", + "serde", + "smallvec", + "smartstring", + "thin-vec", +] + +[[package]] +name = "rhai_codegen" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "robot_hetzner_rhai" +version = "0.1.0" +dependencies = [ + "dotenv", + "prettytable", + "reqwest", + "rhai", + "serde", + "serde_json", + "thiserror 2.0.12", + "tokio", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" + +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.60.2", +] + +[[package]] +name = "rustls" +version = "0.23.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_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 = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "smartstring" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" +dependencies = [ + "autocfg", + "serde", + "static_assertions", + "version_check", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "thin-vec" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d" +dependencies = [ + "serde", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "slab", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 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-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + +[[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 0.52.6", + "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-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[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_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[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_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[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_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[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_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[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_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7956203 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "robot_hetzner_rhai" +version = "0.1.0" +edition = "2024" + +[dependencies] +dotenv = "0.15.0" +prettytable = "0.10.0" +reqwest = { version = "0.12.22", features = ["json", "blocking"] } +rhai = { version = "1.22.2", features = ["serde"] } +serde = { version = "1.0.219", features = ["derive"] } +serde_json = "1.0.140" +thiserror = "2.0.12" +tokio = { version = "1.46.1", features = ["full"] } diff --git a/examples/boot_management.rhai b/examples/boot_management.rhai new file mode 100644 index 0000000..2d06531 --- /dev/null +++ b/examples/boot_management.rhai @@ -0,0 +1,21 @@ +// Get the boot configuration for a server +// Replace 1825193 with the server number you want to fetch +let boot_config = hetzner.get_boot_configuration(1825193); +print(boot_config); + +// Get the rescue boot configuration for a server +// Replace 1825193 with the server number you want to fetch +let rescue_config = hetzner.get_rescue_boot_configuration(1825193); +print(rescue_config); + +// Enable rescue mode +// Replace 1825193 with the server number you want to enable rescue mode on +// Replace "linux" with the desired OS +// Replace the fingerprint with your SSH key fingerprint +//let enabled_rescue = hetzner.enable_rescue_mode(1825193, "linux", ["13:dc:a2:1e:a9:d2:1d:a9:39:f4:44:c5:f1:00:ec:c7"]); +//print(enabled_rescue); + +// Disable rescue mode +// Replace 1825193 with the server number you want to disable rescue mode on +//let disabled_rescue = hetzner.disable_rescue_mode(1825193); +//print(disabled_rescue); \ No newline at end of file diff --git a/examples/server_management.rhai b/examples/server_management.rhai new file mode 100644 index 0000000..c244f80 --- /dev/null +++ b/examples/server_management.rhai @@ -0,0 +1,9 @@ +// Get all servers and print them in a table +let servers = hetzner.get_servers(); +print(servers); +print_servers_table(servers); + +// Get a specific server and print its details +// Replace 1825193 with the server number you want to fetch +let server = hetzner.get_server(1825193); +print_server_details(server); \ No newline at end of file diff --git a/examples/ssh_key_management.rhai b/examples/ssh_key_management.rhai new file mode 100644 index 0000000..7b3e48b --- /dev/null +++ b/examples/ssh_key_management.rhai @@ -0,0 +1,22 @@ +// Get all SSH keys and print them in a table +let keys = hetzner.get_ssh_keys(); +print_ssh_keys_table(keys); + +// Get a specific SSH key +// Replace "13:dc:a2:1e:a9:d2:1d:a9:39:f4:44:c5:f1:00:ec:c7" with the fingerprint of the key you want to fetch +let key = hetzner.get_ssh_key("13:dc:a2:1e:a9:d2:1d:a9:39:f4:44:c5:f1:00:ec:c7"); +print(key); + +// Add a new SSH key +// Replace "my-new-key" with the desired name and "ssh-rsa ..." with your public key data +let new_key = hetzner.add_ssh_key("my-new-key", "ssh-rsa ..."); +print(new_key); + +// Update an SSH key's name +// Replace "cb:8b:ef:a7:fe:04:87:3f:e5:55:cd:12:e3:e8:9f:99" with the fingerprint of the key you want to update +let updated_key = hetzner.update_ssh_key_name("cb:8b:ef:a7:fe:04:87:3f:e5:55:cd:12:e3:e8:9f:99", "my-updated-key-name"); +print(updated_key); + +// Delete an SSH key +// Replace "cb:8b:ef:a7:fe:04:87:3f:e5:55:cd:12:e3:e8:9f:99" with the fingerprint of the key you want to delete +hetzner.delete_ssh_key("cb:8b:ef:a7:fe:04:87:3f:e5:55:cd:12:e3:e8:9f:99"); \ No newline at end of file diff --git a/src/api/error.rs b/src/api/error.rs new file mode 100644 index 0000000..e7c0786 --- /dev/null +++ b/src/api/error.rs @@ -0,0 +1,12 @@ +use crate::api::models::ApiError; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum AppError { + #[error("Request failed: {0}")] + RequestError(#[from] reqwest::Error), + #[error("API error: {0:?}")] + ApiError(ApiError), + #[error("Deserialization failed: {0}\nResponse body: {1}")] + DeserializationError(String, String), +} \ No newline at end of file diff --git a/src/api/mod.rs b/src/api/mod.rs new file mode 100644 index 0000000..5983742 --- /dev/null +++ b/src/api/mod.rs @@ -0,0 +1,204 @@ +pub mod error; +pub mod models; + +use self::models::{ + Boot, BootWrapper, ErrorResponse, Rescue, RescueWrapper, Server, ServerWrapper, SshKey, + SshKeyWrapper, +}; +use crate::config::Config; +use error::AppError; +use reqwest::blocking::Client as HttpClient; +use reqwest::StatusCode; + +#[derive(Clone)] +pub struct Client { + http_client: HttpClient, + config: Config, +} + +impl Client { + pub fn new(config: Config) -> Self { + Self { + http_client: HttpClient::new(), + config, + } + } + + fn handle_response(&self, response: reqwest::blocking::Response) -> Result + where + T: serde::de::DeserializeOwned, + { + match response.status() { + StatusCode::OK => { + // Read the body as text first, then try to deserialize from that + let text = response.text().unwrap_or_else(|_| "".to_string()); + let result = serde_json::from_str(&text); + match result { + Ok(val) => Ok(val), + Err(e) => { + let deser_err = format!("{:?}", e); + Err(AppError::DeserializationError(deser_err, text)) + } + } + }, + _status => { + let error_response: ErrorResponse = response.json()?; + Err(AppError::ApiError(error_response.error)) + } + } + } + + pub fn get_server(&self, server_number: i32) -> Result { + let response = self + .http_client + .get(format!( + "{}/server/{}", + self.config.api_url, server_number + )) + .basic_auth(&self.config.username, Some(&self.config.password)) + .send()?; + + let server_wrapper: ServerWrapper = self.handle_response(response)?; + Ok(server_wrapper.server) + } + + pub fn get_servers(&self) -> Result, AppError> { + let response = self + .http_client + .get(format!("{}/server", self.config.api_url)) + .basic_auth(&self.config.username, Some(&self.config.password)) + .send()?; + + let server_wrappers: Vec = self.handle_response(response)?; + let servers = server_wrappers.into_iter().map(|sw| sw.server).collect(); + Ok(servers) + } + pub fn get_ssh_keys(&self) -> Result, AppError> { + let response = self + .http_client + .get(format!("{}/key", self.config.api_url)) + .basic_auth(&self.config.username, Some(&self.config.password)) + .send()?; + + let ssh_key_wrappers: Vec = self.handle_response(response)?; + let ssh_keys = ssh_key_wrappers.into_iter().map(|sw| sw.key).collect(); + Ok(ssh_keys) + } + + pub fn get_ssh_key(&self, fingerprint: &str) -> Result { + let response = self + .http_client + .get(format!("{}/key/{}", self.config.api_url, fingerprint)) + .basic_auth(&self.config.username, Some(&self.config.password)) + .send()?; + + let ssh_key_wrapper: SshKeyWrapper = self.handle_response(response)?; + Ok(ssh_key_wrapper.key) + } + + pub fn add_ssh_key(&self, name: &str, data: &str) -> Result { + let params = [("name", name), ("data", data)]; + let response = self + .http_client + .post(format!("{}/key", self.config.api_url)) + .basic_auth(&self.config.username, Some(&self.config.password)) + .form(¶ms) + .send()?; + + let ssh_key_wrapper: SshKeyWrapper = self.handle_response(response)?; + Ok(ssh_key_wrapper.key) + } + + pub fn update_ssh_key_name( + &self, + fingerprint: &str, + name: &str, + ) -> Result { + let params = [("name", name)]; + let response = self + .http_client + .post(format!("{}/key/{}", self.config.api_url, fingerprint)) + .basic_auth(&self.config.username, Some(&self.config.password)) + .form(¶ms) + .send()?; + + let ssh_key_wrapper: SshKeyWrapper = self.handle_response(response)?; + Ok(ssh_key_wrapper.key) + } + + pub fn delete_ssh_key(&self, fingerprint: &str) -> Result<(), AppError> { + self.http_client + .delete(format!("{}/key/{}", self.config.api_url, fingerprint)) + .basic_auth(&self.config.username, Some(&self.config.password)) + .send()?; + + Ok(()) + } + pub fn get_boot_configuration(&self, server_number: i32) -> Result { + let response = self + .http_client + .get(format!("{}/boot/{}", self.config.api_url, server_number)) + .basic_auth(&self.config.username, Some(&self.config.password)) + .send()?; + + let boot_wrapper: BootWrapper = self.handle_response(response)?; + Ok(boot_wrapper.boot) + } + + pub fn get_rescue_boot_configuration( + &self, + server_number: i32, + ) -> Result { + let response = self + .http_client + .get(format!( + "{}/boot/{}/rescue", + self.config.api_url, server_number + )) + .basic_auth(&self.config.username, Some(&self.config.password)) + .send()?; + + let rescue_wrapper: RescueWrapper = self.handle_response(response)?; + Ok(rescue_wrapper.rescue) + } + + pub fn enable_rescue_mode( + &self, + server_number: i32, + os: &str, + authorized_keys: Option<&[String]>, + ) -> Result { + let mut params = vec![("os", os)]; + if let Some(keys) = authorized_keys { + for key in keys { + params.push(("authorized_key[]", key)); + } + } + let response = self + .http_client + .post(format!( + "{}/boot/{}/rescue", + self.config.api_url, server_number + )) + .basic_auth(&self.config.username, Some(&self.config.password)) + .form(¶ms) + .send()?; + + let rescue_wrapper: RescueWrapper = self.handle_response(response)?; + Ok(rescue_wrapper.rescue) + } + + pub fn disable_rescue_mode(&self, server_number: i32) -> Result { + let response = self + .http_client + .delete(format!( + "{}/boot/{}/rescue", + self.config.api_url, server_number + )) + .basic_auth(&self.config.username, Some(&self.config.password)) + .send()?; + + let rescue_wrapper: RescueWrapper = self.handle_response(response)?; + Ok(rescue_wrapper.rescue) + } +} \ No newline at end of file diff --git a/src/api/models.rs b/src/api/models.rs new file mode 100644 index 0000000..69e0d0f --- /dev/null +++ b/src/api/models.rs @@ -0,0 +1,382 @@ +use rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Deserializer}; +use serde_json::Value; + +#[derive(Debug, Deserialize, Clone)] +pub struct ServerWrapper { + pub server: Server, +} + +#[derive(Debug, Deserialize, Clone, CustomType)] +#[rhai_type(extra = Self::build_rhai_type)] +pub struct Server { + pub server_ip: String, + pub server_ipv6_net: String, + pub server_number: i32, + pub server_name: String, + pub product: String, + pub dc: String, + pub traffic: String, + pub status: String, + pub cancelled: bool, + pub paid_until: String, + pub ip: Vec, + pub subnet: Option>, + pub reset: Option, + pub rescue: Option, + pub vnc: Option, + pub windows: Option, + pub plesk: Option, + pub cpanel: Option, + pub wol: Option, + pub hot_swap: Option, + pub linked_storagebox: Option, +} + +impl Server { + fn build_rhai_type(builder: &mut TypeBuilder) { + builder + .with_name("Server") + .with_get("server_ip", |s: &mut Server| s.server_ip.clone()) + .with_get("server_ipv6_net", |s: &mut Server| s.server_ipv6_net.clone()) + .with_get("server_number", |s: &mut Server| s.server_number) + .with_get("server_name", |s: &mut Server| s.server_name.clone()) + .with_get("product", |s: &mut Server| s.product.clone()) + .with_get("dc", |s: &mut Server| s.dc.clone()) + .with_get("traffic", |s: &mut Server| s.traffic.clone()) + .with_get("status", |s: &mut Server| s.status.clone()) + .with_get("cancelled", |s: &mut Server| s.cancelled) + .with_get("paid_until", |s: &mut Server| s.paid_until.clone()); + } +} + + +#[derive(Debug, Deserialize, Clone, CustomType)] +#[rhai_type(extra = Self::build_rhai_type)] +pub struct Subnet { + pub ip: String, + pub mask: String, +} + +impl Subnet { + fn build_rhai_type(builder: &mut TypeBuilder) { + builder + .with_name("Subnet") + .with_get("ip", |s: &mut Subnet| s.ip.clone()) + .with_get("mask", |s: &mut Subnet| s.mask.clone()); + } +} +#[derive(Debug, Deserialize, Clone)] +pub struct SshKeyWrapper { + pub key: SshKey, +} + +#[derive(Debug, Deserialize, Clone, CustomType)] +#[rhai_type(extra = Self::build_rhai_type)] +pub struct SshKey { + pub name: String, + pub fingerprint: String, + #[serde(rename = "type")] + pub key_type: String, + pub size: i32, + pub data: String, + pub created_at: String, +} + +impl SshKey { + fn build_rhai_type(builder: &mut TypeBuilder) { + builder + .with_name("SshKey") + .with_get("name", |s: &mut SshKey| s.name.clone()) + .with_get("fingerprint", |s: &mut SshKey| s.fingerprint.clone()) + .with_get("key_type", |s: &mut SshKey| s.key_type.clone()) + .with_get("size", |s: &mut SshKey| s.size) + .with_get("data", |s: &mut SshKey| s.data.clone()) + .with_get("created_at", |s: &mut SshKey| s.created_at.clone()); + } +} +#[derive(Debug, Deserialize, Clone)] +pub struct BootWrapper { + pub boot: Boot, +} + +#[derive(Debug, Deserialize, Clone, CustomType)] +#[rhai_type(extra = Self::build_rhai_type)] +pub struct Boot { + pub rescue: Rescue, + pub linux: Linux, + pub vnc: Vnc, + pub windows: Option, + pub plesk: Option, + pub cpanel: Option, +} + +impl Boot { + fn build_rhai_type(builder: &mut TypeBuilder) { + builder + .with_name("Boot") + .with_get("rescue", |b: &mut Boot| b.rescue.clone()) + .with_get("linux", |b: &mut Boot| b.linux.clone()) + .with_get("vnc", |b: &mut Boot| b.vnc.clone()) + .with_get("windows", |b: &mut Boot| b.windows.clone()) + .with_get("plesk", |b: &mut Boot| b.plesk.clone()) + .with_get("cpanel", |b: &mut Boot| b.cpanel.clone()); + } +} + +#[derive(Debug, Deserialize, Clone)] +pub struct RescueWrapper { + pub rescue: Rescue, +} + +#[derive(Debug, Deserialize, Clone, CustomType)] +#[rhai_type(extra = Self::build_rhai_type)] +pub struct Rescue { + pub server_ip: String, + pub server_ipv6_net: String, + pub server_number: i32, + #[serde(deserialize_with = "string_or_seq_string")] + pub os: Vec, + pub active: bool, + pub password: Option, + pub authorized_key: Vec, + pub host_key: Vec, +} + +impl Rescue { + fn build_rhai_type(builder: &mut TypeBuilder) { + builder + .with_name("Rescue") + .with_get("server_ip", |r: &mut Rescue| r.server_ip.clone()) + .with_get("server_ipv6_net", |r: &mut Rescue| { + r.server_ipv6_net.clone() + }) + .with_get("server_number", |r: &mut Rescue| r.server_number) + .with_get("os", |r: &mut Rescue| r.os.clone()) + .with_get("active", |r: &mut Rescue| r.active) + .with_get("password", |r: &mut Rescue| r.password.clone()) + .with_get("authorized_key", |r: &mut Rescue| { + r.authorized_key.clone() + }) + .with_get("host_key", |r: &mut Rescue| r.host_key.clone()); + } +} + +#[derive(Debug, Deserialize, Clone, CustomType)] +#[rhai_type(extra = Self::build_rhai_type)] +pub struct Linux { + pub server_ip: String, + pub server_ipv6_net: String, + pub server_number: i32, + #[serde(deserialize_with = "string_or_seq_string")] + pub dist: Vec, + #[serde(deserialize_with = "string_or_seq_string")] + pub lang: Vec, + pub active: bool, + pub password: Option, + pub authorized_key: Vec, + pub host_key: Vec, +} + +impl Linux { + fn build_rhai_type(builder: &mut TypeBuilder) { + builder + .with_name("Linux") + .with_get("server_ip", |l: &mut Linux| l.server_ip.clone()) + .with_get("server_ipv6_net", |l: &mut Linux| { + l.server_ipv6_net.clone() + }) + .with_get("server_number", |l: &mut Linux| l.server_number) + .with_get("dist", |l: &mut Linux| l.dist.clone()) + .with_get("lang", |l: &mut Linux| l.lang.clone()) + .with_get("active", |l: &mut Linux| l.active) + .with_get("password", |l: &mut Linux| l.password.clone()) + .with_get("authorized_key", |l: &mut Linux| { + l.authorized_key.clone() + }) + .with_get("host_key", |l: &mut Linux| l.host_key.clone()); + } +} + +#[derive(Debug, Deserialize, Clone, CustomType)] +#[rhai_type(extra = Self::build_rhai_type)] +pub struct Vnc { + pub server_ip: String, + pub server_ipv6_net: String, + pub server_number: i32, + #[serde(deserialize_with = "string_or_seq_string")] + pub dist: Vec, + #[serde(deserialize_with = "string_or_seq_string")] + pub lang: Vec, + pub active: bool, + pub password: Option, +} + +impl Vnc { + fn build_rhai_type(builder: &mut TypeBuilder) { + builder + .with_name("Vnc") + .with_get("server_ip", |v: &mut Vnc| v.server_ip.clone()) + .with_get("server_ipv6_net", |v: &mut Vnc| v.server_ipv6_net.clone()) + .with_get("server_number", |v: &mut Vnc| v.server_number) + .with_get("dist", |v: &mut Vnc| v.dist.clone()) + .with_get("lang", |v: &mut Vnc| v.lang.clone()) + .with_get("active", |v: &mut Vnc| v.active) + .with_get("password", |v: &mut Vnc| v.password.clone()); + } +} + +#[derive(Debug, Deserialize, Clone, CustomType)] +#[rhai_type(extra = Self::build_rhai_type)] +pub struct Windows { + pub server_ip: String, + pub server_ipv6_net: String, + pub server_number: i32, + #[serde(deserialize_with = "option_string_or_seq_string")] + pub dist: Option>, + #[serde(deserialize_with = "option_string_or_seq_string")] + pub lang: Option>, + pub active: bool, + pub password: Option, +} + +impl Windows { + fn build_rhai_type(builder: &mut TypeBuilder) { + builder + .with_name("Windows") + .with_get("server_ip", |w: &mut Windows| w.server_ip.clone()) + .with_get("server_ipv6_net", |w: &mut Windows| { + w.server_ipv6_net.clone() + }) + .with_get("server_number", |w: &mut Windows| w.server_number) + .with_get("dist", |w: &mut Windows| w.dist.clone()) + .with_get("lang", |w: &mut Windows| w.lang.clone()) + .with_get("active", |w: &mut Windows| w.active) + .with_get("password", |w: &mut Windows| w.password.clone()); + } +} + +#[derive(Debug, Deserialize, Clone, CustomType)] +#[rhai_type(extra = Self::build_rhai_type)] +pub struct Plesk { + pub server_ip: String, + pub server_ipv6_net: String, + pub server_number: i32, + #[serde(deserialize_with = "string_or_seq_string")] + pub dist: Vec, + #[serde(deserialize_with = "string_or_seq_string")] + pub lang: Vec, + pub active: bool, + pub password: Option, + pub hostname: Option, +} + +impl Plesk { + fn build_rhai_type(builder: &mut TypeBuilder) { + builder + .with_name("Plesk") + .with_get("server_ip", |p: &mut Plesk| p.server_ip.clone()) + .with_get("server_ipv6_net", |p: &mut Plesk| { + p.server_ipv6_net.clone() + }) + .with_get("server_number", |p: &mut Plesk| p.server_number) + .with_get("dist", |p: &mut Plesk| p.dist.clone()) + .with_get("lang", |p: &mut Plesk| p.lang.clone()) + .with_get("active", |p: &mut Plesk| p.active) + .with_get("password", |p: &mut Plesk| p.password.clone()) + .with_get("hostname", |p: &mut Plesk| p.hostname.clone()); + } +} + +#[derive(Debug, Deserialize, Clone, CustomType)] +#[rhai_type(extra = Self::build_rhai_type)] +pub struct Cpanel { + pub server_ip: String, + pub server_ipv6_net: String, + pub server_number: i32, + #[serde(deserialize_with = "string_or_seq_string")] + pub dist: Vec, + #[serde(deserialize_with = "string_or_seq_string")] + pub lang: Vec, + pub active: bool, + pub password: Option, + pub hostname: Option, +} + +impl Cpanel { + fn build_rhai_type(builder: &mut TypeBuilder) { + builder + .with_name("Cpanel") + .with_get("server_ip", |c: &mut Cpanel| c.server_ip.clone()) + .with_get("server_ipv6_net", |c: &mut Cpanel| { + c.server_ipv6_net.clone() + }) + .with_get("server_number", |c: &mut Cpanel| c.server_number) + .with_get("dist", |c: &mut Cpanel| c.dist.clone()) + .with_get("lang", |c: &mut Cpanel| c.lang.clone()) + .with_get("active", |c: &mut Cpanel| c.active) + .with_get("password", |c: &mut Cpanel| c.password.clone()) + .with_get("hostname", |c: &mut Cpanel| c.hostname.clone()); + } +} + +fn string_or_seq_string<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let value = Value::deserialize(deserializer)?; + match value { + Value::String(s) => Ok(vec![s]), + Value::Array(a) => a + .into_iter() + .map(|v| { + v.as_str() + .map(ToString::to_string) + .ok_or(serde::de::Error::custom("expected string")) + }) + .collect(), + _ => Err(serde::de::Error::custom( + "expected string or array of strings", + )), + } +} + +fn option_string_or_seq_string<'de, D>( + deserializer: D, +) -> Result>, D::Error> +where + D: Deserializer<'de>, +{ + let value = Value::deserialize(deserializer)?; + match value { + Value::Null => Ok(None), + Value::String(s) => Ok(Some(vec![s])), + Value::Array(a) => Ok(Some( + a.into_iter() + .map(|v| { + v.as_str() + .map(ToString::to_string) + .ok_or(serde::de::Error::custom("expected string")) + }) + .collect::, _>>()?, + )), + _ => Err(serde::de::Error::custom( + "expected string or array of strings", + )), + } +} + +#[derive(Debug, Deserialize)] +pub struct ApiError { + #[allow(dead_code)] + pub status: i32, + #[allow(dead_code)] + pub code: String, + #[allow(dead_code)] + pub message: String, +} + +#[derive(Debug, Deserialize)] +pub struct ErrorResponse { + pub error: ApiError, +} \ No newline at end of file diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..75e2950 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,25 @@ +use std::env; + +#[derive(Clone)] +pub struct Config { + pub username: String, + pub password: String, + pub api_url: String, +} + +impl Config { + pub fn from_env() -> Result { + let username = env::var("HETZNER_USERNAME") + .map_err(|_| "HETZNER_USERNAME environment variable not set".to_string())?; + let password = env::var("HETZNER_PASSWORD") + .map_err(|_| "HETZNER_PASSWORD environment variable not set".to_string())?; + let api_url = env::var("HETZNER_API_URL") + .unwrap_or_else(|_| "https://robot-ws.your-server.de".to_string()); + + Ok(Config { + username, + password, + api_url, + }) + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..ffdca10 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,44 @@ +mod api; +mod config; +mod scripting; + +use crate::api::Client; +use crate::config::Config; +use crate::scripting::setup_engine; +use dotenv::dotenv; +use std::env; +use std::fs; +use std::process; + +fn main() { + dotenv().ok(); + + if let Err(e) = run() { + eprintln!("Error: {}", e); + let mut source = e.source(); + while let Some(s) = source { + eprintln!("Caused by: {}", s); + source = s.source(); + } + process::exit(1); + } +} + +fn run() -> Result<(), Box> { + let args: Vec = env::args().collect(); + if args.len() != 2 { + eprintln!("Usage: {} ", args[0]); + return Ok(()); + } + + let config = Config::from_env()?; + let client = Client::new(config.clone()); + + let (engine, mut scope) = setup_engine(client); + + let script = fs::read_to_string(&args[1])?; + + engine.run_with_scope(&mut scope, &script)?; + + Ok(()) +} diff --git a/src/scripting/boot.rs b/src/scripting/boot.rs new file mode 100644 index 0000000..2c1340a --- /dev/null +++ b/src/scripting/boot.rs @@ -0,0 +1,63 @@ +use crate::api::{ + models::{Boot, Rescue}, + Client, +}; +use rhai::{plugin::*, Engine}; + +pub fn register(engine: &mut Engine) { + let boot_module = exported_module!(boot_api); + engine.register_global_module(boot_module.into()); +} + +#[export_module] +pub mod boot_api { + use super::*; + use rhai::EvalAltResult; + + #[rhai_fn(name = "get_boot_configuration", return_raw)] + pub fn get_boot_configuration( + client: &mut Client, + server_number: i64, + ) -> Result> { + client + .get_boot_configuration(server_number as i32) + .map_err(|e| e.to_string().into()) + } + + #[rhai_fn(name = "get_rescue_boot_configuration", return_raw)] + pub fn get_rescue_boot_configuration( + client: &mut Client, + server_number: i64, + ) -> Result> { + client + .get_rescue_boot_configuration(server_number as i32) + .map_err(|e| e.to_string().into()) + } + + #[rhai_fn(name = "enable_rescue_mode", return_raw)] + pub fn enable_rescue_mode( + client: &mut Client, + server_number: i64, + os: &str, + authorized_keys: rhai::Array, + ) -> Result> { + let keys: Vec = authorized_keys + .into_iter() + .map(|k| k.into_string().unwrap()) + .collect(); + + client + .enable_rescue_mode(server_number as i32, os, Some(&keys)) + .map_err(|e| e.to_string().into()) + } + + #[rhai_fn(name = "disable_rescue_mode", return_raw)] + pub fn disable_rescue_mode( + client: &mut Client, + server_number: i64, + ) -> Result> { + client + .disable_rescue_mode(server_number as i32) + .map_err(|e| e.to_string().into()) + } +} \ No newline at end of file diff --git a/src/scripting/mod.rs b/src/scripting/mod.rs new file mode 100644 index 0000000..f762ba5 --- /dev/null +++ b/src/scripting/mod.rs @@ -0,0 +1,30 @@ +use crate::api::Client; +use crate::api::models::{Server, SshKey}; +use rhai::{Engine, Scope}; + +pub mod server; +pub mod ssh_keys; +pub mod boot; + +pub fn setup_engine(client: Client) -> (Engine, Scope<'static>) { + let mut engine = Engine::new(); + let mut scope = Scope::new(); + + engine.build_type::(); + engine.build_type::(); + engine.build_type::(); + engine.build_type::(); + engine.build_type::(); + engine.build_type::(); + engine.build_type::(); + engine.build_type::(); + engine.build_type::(); + + server::register(&mut engine); + ssh_keys::register(&mut engine); + boot::register(&mut engine); + + scope.push("hetzner", client); + + (engine, scope) +} \ No newline at end of file diff --git a/src/scripting/server.rs b/src/scripting/server.rs new file mode 100644 index 0000000..a532085 --- /dev/null +++ b/src/scripting/server.rs @@ -0,0 +1,92 @@ +use crate::api::{models::Server, Client}; +use prettytable::{row, Table}; +use rhai::{plugin::*, Array, Dynamic}; + +pub fn register(engine: &mut Engine) { + let server_module = exported_module!(server_api); + engine.register_global_module(server_module.into()); +} + +#[export_module] +pub mod server_api { + use super::*; + use rhai::EvalAltResult; + + #[rhai_fn(name = "get_server", return_raw)] + pub fn get_server( + client: &mut Client, + server_number: i64, + ) -> Result> { + client + .get_server(server_number as i32) + .map_err(|e| e.to_string().into()) + } + + #[rhai_fn(name = "get_servers", return_raw)] + pub fn get_servers(client: &mut Client) -> Result> { + let servers = client + .get_servers() + .map_err(|e| Into::>::into(e.to_string()))?; + Ok(servers.into_iter().map(Dynamic::from).collect()) + } + + #[rhai_fn(name = "print_servers_table")] + pub fn print_servers_table(servers: Array) { + let mut table = Table::new(); + table.add_row(row![ + "Server Number", + "Server Name", + "Server IP", + "Product", + "Datacenter", + "Status" + ]); + + for server_dynamic in servers { + let server: Server = server_dynamic + .try_cast::() + .expect("could not cast to server"); + table.add_row(row![ + server.server_number, + server.server_name, + server.server_ip, + server.product, + server.dc, + server.status + ]); + } + + table.printstd(); + } + + #[rhai_fn(name = "print_server_details")] + pub fn print_server_details(server: Server) { + let mut table = Table::new(); + table.add_row(row!["Property", "Value"]); + table.add_row(row!["Server Number", server.server_number]); + table.add_row(row!["Server Name", server.server_name]); + table.add_row(row!["Server IP", server.server_ip]); + table.add_row(row!["IPv6 Network", server.server_ipv6_net]); + table.add_row(row!["Product", server.product]); + table.add_row(row!["Datacenter", server.dc]); + table.add_row(row!["Traffic", server.traffic]); + table.add_row(row!["Status", server.status]); + table.add_row(row!["Cancelled", server.cancelled]); + table.add_row(row!["Paid Until", server.paid_until]); + table.add_row(row!["Reset", server.reset.unwrap_or(false)]); + table.add_row(row!["Rescue", server.rescue.unwrap_or(false)]); + table.add_row(row!["VNC", server.vnc.unwrap_or(false)]); + table.add_row(row!["Windows", server.windows.unwrap_or(false)]); + table.add_row(row!["Plesk", server.plesk.unwrap_or(false)]); + table.add_row(row!["cPanel", server.cpanel.unwrap_or(false)]); + table.add_row(row!["WOL", server.wol.unwrap_or(false)]); + table.add_row(row!["Hot Swap", server.hot_swap.unwrap_or(false)]); + table.add_row(row![ + "Linked Storagebox", + server + .linked_storagebox + .map_or("N/A".to_string(), |id| id.to_string()) + ]); + table.printstd(); + } +} \ No newline at end of file diff --git a/src/scripting/ssh_keys.rs b/src/scripting/ssh_keys.rs new file mode 100644 index 0000000..bb52514 --- /dev/null +++ b/src/scripting/ssh_keys.rs @@ -0,0 +1,91 @@ +use crate::api::{models::SshKey, Client}; +use prettytable::{row, Table}; +use rhai::{plugin::*, Array, Dynamic, Engine}; + +pub fn register(engine: &mut Engine) { + let ssh_keys_module = exported_module!(ssh_keys_api); + engine.register_global_module(ssh_keys_module.into()); +} + +#[export_module] +pub mod ssh_keys_api { + use super::*; + use rhai::EvalAltResult; + + #[rhai_fn(name = "get_ssh_keys", return_raw)] + pub fn get_ssh_keys(client: &mut Client) -> Result> { + let ssh_keys = client + .get_ssh_keys() + .map_err(|e| Into::>::into(e.to_string()))?; + Ok(ssh_keys.into_iter().map(Dynamic::from).collect()) + } + + #[rhai_fn(name = "get_ssh_key", return_raw)] + pub fn get_ssh_key( + client: &mut Client, + fingerprint: &str, + ) -> Result> { + client + .get_ssh_key(fingerprint) + .map_err(|e| e.to_string().into()) + } + + #[rhai_fn(name = "add_ssh_key", return_raw)] + pub fn add_ssh_key( + client: &mut Client, + name: &str, + data: &str, + ) -> Result> { + client + .add_ssh_key(name, data) + .map_err(|e| e.to_string().into()) + } + + #[rhai_fn(name = "update_ssh_key_name", return_raw)] + pub fn update_ssh_key_name( + client: &mut Client, + fingerprint: &str, + name: &str, + ) -> Result> { + client + .update_ssh_key_name(fingerprint, name) + .map_err(|e| e.to_string().into()) + } + + #[rhai_fn(name = "delete_ssh_key", return_raw)] + pub fn delete_ssh_key( + client: &mut Client, + fingerprint: &str, + ) -> Result<(), Box> { + client + .delete_ssh_key(fingerprint) + .map_err(|e| e.to_string().into()) + } + + #[rhai_fn(name = "print_ssh_keys_table")] + pub fn print_ssh_keys_table(keys: Array) { + let mut table = Table::new(); + table.add_row(row![ + "Name", + "Fingerprint", + "Type", + "Size", + "Created At" + ]); + + for key_dynamic in keys { + let key: SshKey = key_dynamic + .try_cast::() + .expect("could not cast to SshKey"); + table.add_row(row![ + key.name, + key.fingerprint, + key.key_type, + key.size, + key.created_at + ]); + } + + table.printstd(); + } +} \ No newline at end of file