diff --git a/Cargo.lock b/Cargo.lock index 5329607..336257a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,9 +30,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.4" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +checksum = "628a8f9bd1e24b4e0db2b4bc2d000b001e7dd032d54afa60a68836aeec5aa54a" dependencies = [ "anstyle", "anstyle-parse", @@ -78,9 +78,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" [[package]] name = "async-trait" @@ -90,7 +90,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.48", ] [[package]] @@ -120,15 +120,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "cc" -version = "1.0.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -137,9 +128,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.4.6" +version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" +checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" dependencies = [ "clap_builder", "clap_derive", @@ -147,9 +138,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.6" +version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" +checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" dependencies = [ "anstream", "anstyle", @@ -159,38 +150,39 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.4.3" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ae8ba90b9d8b007efe66e55e48fb936272f5ca00349b5b0e89877520d35ea7" +checksum = "dfb0d4825b75ff281318c393e8e1b80c4da9fb75a6b1d98547d389d6fe1f48d2" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.4.2" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.48", ] [[package]] name = "clap_lex" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "cmd_lib" -version = "1.3.0" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ba0f413777386d37f85afa5242f277a7b461905254c1af3c339d4af06800f62" +checksum = "e5f4cbdcab51ca635c5b19c85ece4072ea42e0d2360242826a6fc96fb11f0d40" dependencies = [ "cmd_lib_macros", + "env_logger", "faccess", "lazy_static", "log", @@ -199,9 +191,9 @@ dependencies = [ [[package]] name = "cmd_lib_macros" -version = "1.3.0" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e66605092ff6c6e37e0246601ae6c3f62dc1880e0599359b5f303497c112dc0" +checksum = "ae881960f7e2a409f91ef0b1c09558cf293031a1d6e8b45f908311f2a43f5fdf" dependencies = [ "proc-macro-error", "proc-macro2", @@ -238,9 +230,9 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "config" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7" +checksum = "23738e11972c7643e4ec947840fc463b6a571afcd3e735bdfce7d03c7a784aca" dependencies = [ "async-trait", "json5", @@ -338,24 +330,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] -name = "errno" -version = "0.3.4" +name = "env_logger" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480" +checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", ] [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "errno" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ - "cc", "libc", + "windows-sys 0.52.0", ] [[package]] @@ -389,9 +383,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "miniz_oxide", @@ -433,15 +427,27 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + [[package]] name = "home" -version = "0.5.5" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "indicatif" version = "0.17.7" @@ -464,6 +470,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "is-terminal" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "itoa" version = "1.0.9" @@ -489,9 +506,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.148" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "linked-hash-map" @@ -501,9 +518,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.8" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3852614a3bd9ca9804678ba6be5e3b8ce76dfc902cae004e3e0c44051b6e88db" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "log" @@ -577,12 +594,12 @@ dependencies = [ [[package]] name = "os_pipe" -version = "0.9.2" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb233f06c2307e1f5ce2ecad9f8121cffbbee2c95428f44ea85222e460d0d213" +checksum = "57119c3b893986491ec9aa85056780d3a0f3cf4da7cc09dd3650dbd6c6738fb9" dependencies = [ "libc", - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -622,7 +639,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.48", ] [[package]] @@ -668,18 +685,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -695,9 +712,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.6" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", @@ -707,9 +724,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.9" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", @@ -718,9 +735,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "ron" @@ -745,15 +762,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.15" +version = "0.38.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f9da0cbd88f9f09e7814e388301c8414c51c62aa6ce1e4b5c551d49d96e531" +checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" dependencies = [ "bitflags 2.4.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -764,29 +781,29 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "serde" -version = "1.0.188" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.48", ] [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" dependencies = [ "itoa", "ryu", @@ -816,6 +833,16 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "sudo" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88bd84d4c082e18e37fef52c0088e4407dabcef19d23a607fb4b5ee03b7d5b83" +dependencies = [ + "libc", + "log", +] + [[package]] name = "syn" version = "1.0.109" @@ -829,9 +856,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.37" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -862,6 +889,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.49" @@ -879,7 +915,7 @@ checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.48", ] [[package]] @@ -935,14 +971,15 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "which" -version = "4.4.2" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +checksum = "7fa5e0c10bf77f44aac573e498d1a82d5fbd5e91f6fc0a99e7be4b38e85e101c" dependencies = [ "either", "home", "once_cell", "rustix", + "windows-sys 0.52.0", ] [[package]] @@ -961,6 +998,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -985,6 +1031,15 @@ 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.0", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -1015,6 +1070,21 @@ dependencies = [ "windows_x86_64_msvc 0.48.5", ] +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -1027,6 +1097,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -1039,6 +1115,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -1051,6 +1133,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -1063,6 +1151,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -1075,6 +1169,12 @@ 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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -1087,6 +1187,12 @@ 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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -1099,9 +1205,15 @@ 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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "wrestic" -version = "1.3.0" +version = "1.5.0" dependencies = [ "anyhow", "clap", @@ -1117,6 +1229,7 @@ dependencies = [ "regex", "serde", "serde_json", + "sudo", "tar", "which", ] diff --git a/Cargo.toml b/Cargo.toml index f10a85d..5505900 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wrestic" -version = "1.3.0" +version = "1.5.0" authors = ["alvaro17f"] description = "Restic wrapper built in Rust" homepage = "https://wrestic.com/" @@ -10,19 +10,20 @@ edition = "2021" keywords = ["restic", "wrapper", "rust", "backup", "tool"] [dependencies] -anyhow = "1.0.72" -clap = { version = "4.4.6", features = ["derive"] } -clap_complete = "4.4.1" -cmd_lib = "1.3.0" -color-print = "0.3.4" -config = "0.13.3" +anyhow = "1.0.79" +clap = { version = "4.4.18", features = ["derive"] } +clap_complete = "4.4.7" +cmd_lib = "1.9.3" +color-print = "0.3.5" +config = "0.13.4" dialoguer = "0.11.0" -flate2 = "1.0.27" -indicatif = "0.17.6" +flate2 = "1.0.28" +indicatif = "0.17.7" lazy_static = "1.4.0" nix = { version = "0.27.1", features = ["user"] } -regex = "1.9.3" -serde = "1.0.183" -serde_json = "1.0.104" +regex = "1.10.2" +serde = "1.0.195" +serde_json = "1.0.111" +sudo = "0.6.0" tar = "0.4.40" -which = "4.4.0" +which = "6.0.0" diff --git a/src/main.rs b/src/main.rs index f6e47d9..997e968 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,10 @@ mod modules; mod utils; -use crate::utils::{completions::set_completions, tools::clear}; +use crate::utils::{ + completions::set_completions, set_environment_variables::set_environment_variables, + tools::clear, +}; use anyhow::Result; use clap::{CommandFactory, Parser, Subcommand}; use clap_complete::Shell; @@ -12,10 +15,9 @@ use modules::{ initialize::initialize, repair::repair, restore::restore, selector::selector, snapshots::snapshots, update::update, }; -use std::{env, process::exit}; +use std::process::exit; use utils::{ completions::print_completions, get_config::get_config, restic_checker::restic_checker, - root_checker::root_checker, }; #[derive(Parser, Debug, PartialEq)] @@ -61,23 +63,22 @@ enum Commands { Custom { args: Vec }, } -fn main() -> Result<()> { - restic_checker()?; - root_checker()?; - - let cli = Cli::parse(); - if let Some(generator) = cli.generator { +fn handle_completions(cli: &Cli) -> Result<()> { + if let Some(generator) = cli.generator.as_ref() { let mut cmd = Cli::command(); - if generator == Shell::Zsh || generator == Shell::Bash { - set_completions(generator, &mut cmd); + if generator == &Shell::Zsh || generator == &Shell::Bash || generator == &Shell::Fish { + set_completions(*generator, &mut cmd)?; cprintln!("{} completions are set", generator); exit(0) } else { - print_completions(generator, &mut cmd); + print_completions(*generator, &mut cmd); exit(0) } } + Ok(()) +} +fn handle_commands(cli: &Cli) -> Result<()> { match &cli.commands { Some(Commands::Backup) => { backup(true)?; @@ -98,40 +99,13 @@ fn main() -> Result<()> { check(true)?; } Some(Commands::Repair) => { - let settings = get_config()?; - clear()?; - cprintln!("REPAIR"); - println!(); - let selection = if settings.len() > 1 { - let selections: Vec = settings.iter().map(|x| x.name.to_owned()).collect(); - Select::with_theme(&ColorfulTheme::default()) - .with_prompt(cformat!("Where do you want to perform a repair?")) - .default(0) - .max_length(10) - .items(&selections[..]) - .interact()? - } else { - 0 - }; - - env::set_var("USER", &settings[selection].user); - env::set_var("RESTIC_PASSWORD", &settings[selection].restic_password); - for env in &settings[selection].env { - for (key, value) in env { - env::set_var(key, value); - } - } - - let backend = &settings[selection].backend; - let repository = &settings[selection].repository; - - repair(backend, repository, true)?; + handle_repair()?; } Some(Commands::Cache) => { cache(true)?; } Some(Commands::Update) => { - update(true)?; + update()?; } Some(Commands::Custom { args }) => { custom(args)?; @@ -140,6 +114,43 @@ fn main() -> Result<()> { selector()?; } } + Ok(()) +} + +fn handle_repair() -> Result<()> { + clear()?; + cprintln!("REPAIR"); + println!(); + + let settings = get_config()?; + + let selection = if settings.len() > 1 { + let selections: Vec = settings.iter().map(|x| x.name.to_owned()).collect(); + Select::with_theme(&ColorfulTheme::default()) + .with_prompt(cformat!("Where do you want to perform a repair?")) + .default(0) + .max_length(10) + .items(&selections[..]) + .interact()? + } else { + 0 + }; + + set_environment_variables(&settings[selection])?; + + let backend = &settings[selection].backend; + let repository = &settings[selection].repository; + + repair(backend, repository, true)?; + Ok(()) +} + +fn main() -> Result<()> { + restic_checker()?; + + let cli = Cli::parse(); + handle_completions(&cli)?; + handle_commands(&cli)?; Ok(()) } diff --git a/src/modules/backup.rs b/src/modules/backup.rs index 03cf700..078de83 100644 --- a/src/modules/backup.rs +++ b/src/modules/backup.rs @@ -1,7 +1,9 @@ use crate::{ modules::{repair::repair, selector::selector}, utils::{ - get_config::get_config, + get_config::{get_config, Settings}, + root_checker::root_checker, + set_environment_variables::set_environment_variables, tools::{clear, pause}, }, }; @@ -9,23 +11,29 @@ use anyhow::Result; use cmd_lib::run_cmd; use color_print::{cformat, cprintln}; use dialoguer::{theme::ColorfulTheme, Confirm, Select}; -use std::env; -fn do_backup(backend: &str, repository: &str, backup_folder: &str, keep_last: &str) -> Result<()> { +fn do_backup(setting: &Settings) -> Result<()> { + root_checker()?; + + let backend = &setting.backend; + let repository = &setting.repository; + let backup_folder = &setting.backup_folder; + let keep_last = &setting.keep_last; + if run_cmd!( - restic -r $backend:$repository --verbose --verbose backup $backup_folder; + sudo -E restic -r $backend:$repository --verbose --verbose backup $backup_folder 2>/dev/null; ) .is_err() { - cprintln!("Failed to backup"); + cprintln!("\nFailed to backup\n"); } if run_cmd!( - restic -r $backend:$repository --verbose --verbose forget --keep-last $keep_last; + sudo -E restic -r $backend:$repository --verbose --verbose forget --keep-last $keep_last 2>/dev/null; ) .is_err() { - cprintln!("Failed to delete keeping last {keep_last} snapshots."); + cprintln!("\nFailed to delete old snapshots keeping last {keep_last}\n"); if Confirm::with_theme(&ColorfulTheme::default()) .with_prompt(cformat!("Do you want to repair? (Y/n):")) .default(true) @@ -34,13 +42,13 @@ fn do_backup(backend: &str, repository: &str, backup_folder: &str, keep_last: &s repair(backend, repository, true)?; if run_cmd!( - restic -r $backend:$repository --verbose --verbose forget --keep-last $keep_last; + sudo -E restic -r $backend:$repository --verbose --verbose forget --keep-last $keep_last 2>/dev/null; ) .is_err() { cprintln!( - "Houston, we have a problem! Failed to delete keeping last {keep_last} snapshots AGAIN." - ) + "\nHouston, we have a problem! Failed to delete old snapshots keeping last {keep_last} AGAIN\n" + ) } } } @@ -49,27 +57,16 @@ fn do_backup(backend: &str, repository: &str, backup_folder: &str, keep_last: &s } pub fn backup(noconfirm: bool) -> Result<()> { - let settings = get_config()?; clear()?; cprintln!("BACKUP"); println!(); - if noconfirm { - for conf in settings { - let backend = &conf.backend; - let repository = &conf.repository; - let keep_last = &conf.keep_last; - let backup_folder = &conf.backup_folder; - - env::set_var("USER", &conf.user); - env::set_var("RESTIC_PASSWORD", &conf.restic_password); - for env in &conf.env { - for (key, value) in env { - env::set_var(key, value); - } - } + let settings = get_config()?; - do_backup(backend, repository, backup_folder, keep_last)?; + if noconfirm { + for setting in settings { + set_environment_variables(&setting)?; + do_backup(&setting)?; } } else { let selection = if settings.len() > 1 { @@ -84,27 +81,19 @@ pub fn backup(noconfirm: bool) -> Result<()> { 0 }; - let name = &settings[selection].name; - let backend = &settings[selection].backend; - let repository = &settings[selection].repository; - let keep_last = &settings[selection].keep_last; - let backup_folder = &settings[selection].backup_folder; + let setting = &settings[selection]; + + set_environment_variables(setting)?; - env::set_var("USER", &settings[selection].user); - env::set_var("RESTIC_PASSWORD", &settings[selection].restic_password); - for env in &settings[selection].env { - for (key, value) in env { - env::set_var(key, value); - } - } if Confirm::with_theme(&ColorfulTheme::default()) .with_prompt(cformat!( - "Do you want to perform a backup for {name}? (Y/n): " + "Do you want to perform a backup for {}? (Y/n): ", + setting.name )) .default(true) .interact()? { - do_backup(backend, repository, backup_folder, keep_last)?; + do_backup(setting)?; pause()?; } if !noconfirm { diff --git a/src/modules/cache.rs b/src/modules/cache.rs index d39f1da..30c4283 100644 --- a/src/modules/cache.rs +++ b/src/modules/cache.rs @@ -1,29 +1,42 @@ use crate::{ modules::selector::selector, - utils::tools::{clear, pause}, + utils::{ + root_checker::root_checker, + tools::{clear, pause}, + }, }; use anyhow::Result; use cmd_lib::run_cmd; use color_print::{cformat, cprintln}; use dialoguer::{theme::ColorfulTheme, Confirm}; +fn clean_cache() -> Result<()> { + root_checker()?; + + if run_cmd!( + sudo -E restic cache --cleanup + ) + .is_err() + { + cprintln!("\nFailed to clean cache\n"); + } + + Ok(()) +} + pub fn cache(noconfirm: bool) -> Result<()> { clear()?; cprintln!("CACHE"); println!(); + if noconfirm || Confirm::with_theme(&ColorfulTheme::default()) .with_prompt(cformat!("Do you want to clean cache? (Y/n): ")) .default(true) .interact()? { - if run_cmd!( - restic cache --cleanup - ) - .is_err() - { - cprintln!("Failed to clean cache"); - } + clean_cache()?; + if !noconfirm { pause()?; selector()?; @@ -31,5 +44,6 @@ pub fn cache(noconfirm: bool) -> Result<()> { } else { selector()?; } + Ok(()) } diff --git a/src/modules/check.rs b/src/modules/check.rs index 3801f83..8f2715c 100644 --- a/src/modules/check.rs +++ b/src/modules/check.rs @@ -2,6 +2,8 @@ use crate::{ modules::{repair::repair, selector::selector}, utils::{ get_config::get_config, + root_checker::root_checker, + set_environment_variables::set_environment_variables, tools::{clear, pause}, }, }; @@ -9,15 +11,16 @@ use anyhow::Result; use cmd_lib::run_cmd; use color_print::{cformat, cprintln}; use dialoguer::{theme::ColorfulTheme, Confirm, Select}; -use std::env; fn do_check(backend: &str, repository: &str) -> Result<()> { + root_checker()?; + if run_cmd!( - restic -r $backend:$repository check; + sudo -E restic -r $backend:$repository check; ) .is_err() { - cprintln!("Failed to check."); + cprintln!("\nFailed to check\n"); if Confirm::with_theme(&ColorfulTheme::default()) .with_prompt(cformat!("Do you want to repair? (Y/n):")) .default(true) @@ -31,10 +34,12 @@ fn do_check(backend: &str, repository: &str) -> Result<()> { } pub fn check(noconfirm: bool) -> Result<()> { - let settings = get_config()?; clear()?; cprintln!("CHECK"); println!(); + + let settings = get_config()?; + let selection = if settings.len() > 1 { let selections: Vec = settings.iter().map(|x| x.name.to_owned()).collect(); Select::with_theme(&ColorfulTheme::default()) @@ -47,13 +52,7 @@ pub fn check(noconfirm: bool) -> Result<()> { 0 }; - env::set_var("USER", &settings[selection].user); - env::set_var("RESTIC_PASSWORD", &settings[selection].restic_password); - for env in &settings[selection].env { - for (key, value) in env { - env::set_var(key, value); - } - } + set_environment_variables(&settings[selection])?; let backend = &settings[selection].backend; let repository = &settings[selection].repository; diff --git a/src/modules/custom.rs b/src/modules/custom.rs index 9a6259d..36806fb 100644 --- a/src/modules/custom.rs +++ b/src/modules/custom.rs @@ -1,16 +1,44 @@ -use std::{env, process::Command, time::Duration}; +use std::{process::Command, time::Duration}; -use crate::utils::{get_config::get_config, macros::error, tools::clear}; +use crate::utils::{ + get_config::get_config, macros::error, root_checker::root_checker, + set_environment_variables::set_environment_variables, tools::clear, +}; use anyhow::{Context, Result}; use color_print::{cformat, cprintln}; use dialoguer::{theme::ColorfulTheme, Select}; use indicatif::ProgressBar; +fn run_custom_command(backend: &str, repository: &str, args: &Vec) -> Result<()> { + root_checker()?; + + let pb = ProgressBar::new_spinner(); + pb.enable_steady_tick(Duration::from_millis(120)); + pb.set_message("Loading custom command..."); + + let out = Command::new("sudo") + .arg("-E") + .arg("restic") + .arg("-r") + .arg(format!("{}:{}", &backend, &repository)) + .args(args) + .output() + .context(error!("Failed to run custom command!"))?; + + pb.finish_and_clear(); + + println!("{}", String::from_utf8(out.stdout)?); + + Ok(()) +} + pub fn custom(args: &Vec) -> Result<()> { - let settings = get_config()?; clear()?; cprintln!("CUSTOM"); println!(); + + let settings = get_config()?; + let selection = if settings.len() > 1 { let selections: Vec = settings.iter().map(|x| x.name.to_owned()).collect(); Select::with_theme(&ColorfulTheme::default()) @@ -23,31 +51,12 @@ pub fn custom(args: &Vec) -> Result<()> { 0 }; - env::set_var("USER", &settings[selection].user); - env::set_var("RESTIC_PASSWORD", &settings[selection].restic_password); - for env in &settings[selection].env { - for (key, value) in env { - env::set_var(key, value); - } - } + set_environment_variables(&settings[selection])?; let backend = &settings[selection].backend; let repository = &settings[selection].repository; - let pb = ProgressBar::new_spinner(); - pb.enable_steady_tick(Duration::from_millis(120)); - pb.set_message("Loading custom command..."); - - let out = Command::new("restic") - .arg("-r") - .arg(format!("{}:{}", &backend, &repository)) - .args(args) - .output() - .context(error!("Failed to run custom command!"))?; - - pb.finish_and_clear(); - - println!("{}", String::from_utf8(out.stdout)?); + run_custom_command(backend, repository, args)?; Ok(()) } diff --git a/src/modules/delete.rs b/src/modules/delete.rs index 6b4bf1f..a7a74b1 100644 --- a/src/modules/delete.rs +++ b/src/modules/delete.rs @@ -1,9 +1,9 @@ -#![allow(clippy::collapsible_if)] - use crate::{ modules::{repair::repair, selector::selector}, utils::{ get_config::get_config, + root_checker::root_checker, + set_environment_variables::set_environment_variables, snapshots_selector::snapshots_selector, tools::{clear, pause}, }, @@ -12,13 +12,44 @@ use anyhow::Result; use cmd_lib::run_cmd; use color_print::{cformat, cprintln}; use dialoguer::{theme::ColorfulTheme, Confirm, Select}; -use std::env; + +fn delete_snapshot(backend: &str, repository: &str, delete_snapshots: &str) -> Result<()> { + root_checker()?; + + if run_cmd!( + sudo -E restic -r $backend:$repository forget $delete_snapshots; + ) + .is_err() + { + cprintln!("\nFailed to delete snapshot!\n"); + + if Confirm::with_theme(&ColorfulTheme::default()) + .with_prompt(cformat!("Do you want to repair? (Y/n):")) + .default(true) + .interact()? + { + repair(backend, repository, true)?; + if run_cmd!( + sudo -E restic -r $backend:$repository forget $delete_snapshots; + ) + .is_err() + { + cprintln!("\nHouston, we have a problem! Failed to delete the snapshot AGAIN\n"); + } + } + pause()?; + } + + Ok(()) +} pub fn delete(noconfirm: bool) -> Result<()> { - let settings = get_config()?; clear()?; cprintln!("DELETE"); println!(); + + let settings = get_config()?; + let selection = if settings.len() > 1 { let selections: Vec = settings.iter().map(|x| x.name.to_owned()).collect(); Select::with_theme(&ColorfulTheme::default()) @@ -31,13 +62,8 @@ pub fn delete(noconfirm: bool) -> Result<()> { 0 }; - env::set_var("USER", &settings[selection].user); - env::set_var("RESTIC_PASSWORD", &settings[selection].restic_password); - for env in &settings[selection].env { - for (key, value) in env { - env::set_var(key, value); - } - } + set_environment_variables(&settings[selection])?; + let backend = &settings[selection].backend; let repository = &settings[selection].repository; let delete_snapshots = snapshots_selector(backend, repository)?; @@ -49,30 +75,7 @@ pub fn delete(noconfirm: bool) -> Result<()> { .default(true) .interact()? { - if run_cmd!( - restic -r $backend:$repository forget $delete_snapshots; - ) - .is_err() - { - cprintln!("Failed to delete snapshots!"); - if Confirm::with_theme(&ColorfulTheme::default()) - .with_prompt(cformat!("Do you want to repair? (Y/n):")) - .default(true) - .interact()? - { - repair(backend, repository, true)?; - if run_cmd!( - restic -r $backend:$repository forget $delete_snapshots; - ) - .is_err() - { - cprintln!( - "Houston, we have a problem! Failed to delete the snapshot AGAIN." - ); - } - } - pause()?; - } + delete_snapshot(backend, repository, &delete_snapshots)?; } if !noconfirm { diff --git a/src/modules/initialize.rs b/src/modules/initialize.rs index d8e019d..2470080 100644 --- a/src/modules/initialize.rs +++ b/src/modules/initialize.rs @@ -2,6 +2,8 @@ use crate::{ modules::selector::selector, utils::{ get_config::get_config, + root_checker::root_checker, + set_environment_variables::set_environment_variables, tools::{clear, pause}, }, }; @@ -9,14 +11,37 @@ use anyhow::Result; use cmd_lib::run_cmd; use color_print::{cformat, cprintln}; use dialoguer::{theme::ColorfulTheme, Confirm}; -use std::env; +use indicatif::ProgressBar; +use std::time::Duration; + +fn initialize_repository(backend: &str, repository: &str) -> Result<()> { + root_checker()?; + + let pb = ProgressBar::new_spinner(); + pb.enable_steady_tick(Duration::from_millis(120)); + pb.set_message("Initializing repositories..."); + + if run_cmd!( + sudo -E restic -r $backend:$repository init 2>/dev/null; + ) + .is_err() + { + pb.finish_and_clear(); + cprintln!("\nRepository: {repository} already exists\n"); + } + + pb.finish_and_clear(); + + Ok(()) +} pub fn initialize(noconfirm: bool) -> Result<()> { - let settings = get_config()?; clear()?; cprintln!("INITIALIZE REPOSITORIES"); println!(); + let settings = get_config()?; + if Confirm::with_theme(&ColorfulTheme::default()) .with_prompt(cformat!( "Do you want to initialize all repositories? (Y/n): " @@ -25,24 +50,12 @@ pub fn initialize(noconfirm: bool) -> Result<()> { .interact()? { for conf in settings { + set_environment_variables(&conf)?; + let backend = &conf.backend; let repository = &conf.repository; - env::set_var("USER", &conf.user); - env::set_var("RESTIC_PASSWORD", &conf.restic_password); - for env in &conf.env { - for (key, value) in env { - env::set_var(key, value); - } - } - - if run_cmd!( - restic -r $backend:$repository init; - ) - .is_err() - { - cprintln!("Repository: {repository} already exists"); - } + initialize_repository(backend, repository)?; } pause()?; } diff --git a/src/modules/repair.rs b/src/modules/repair.rs index 7a6e05e..80462dd 100644 --- a/src/modules/repair.rs +++ b/src/modules/repair.rs @@ -1,9 +1,29 @@ -use crate::{modules::selector::selector, utils::tools::pause}; +use crate::{ + modules::selector::selector, + utils::{root_checker::root_checker, tools::pause}, +}; use anyhow::Result; use cmd_lib::run_cmd; use color_print::{cformat, cprintln}; use dialoguer::{theme::ColorfulTheme, Confirm}; +fn repair_repository(backend: &str, repository: &str) -> Result<()> { + root_checker()?; + + if run_cmd!( + sudo -E restic -r $backend:$repository unlock; + sudo -E restic -r $backend:$repository rebuild-index; + sudo -E restic -r $backend:$repository prune; + sudo -E restic -r $backend:$repository check; + ) + .is_err() + { + cprintln!("\nFailed to repair\n"); + } + + Ok(()) +} + pub fn repair(backend: &str, repository: &str, noconfirm: bool) -> Result<()> { if noconfirm || Confirm::with_theme(&ColorfulTheme::default()) @@ -13,16 +33,7 @@ pub fn repair(backend: &str, repository: &str, noconfirm: bool) -> Result<()> { .default(true) .interact()? { - if run_cmd!( - restic -r $backend:$repository unlock; - restic -r $backend:$repository rebuild-index; - restic -r $backend:$repository prune; - restic -r $backend:$repository check; - ) - .is_err() - { - cprintln!("Failed to repair"); - } + repair_repository(backend, repository)?; if !noconfirm { pause()?; diff --git a/src/modules/restore.rs b/src/modules/restore.rs index 1f1483b..67aaacd 100644 --- a/src/modules/restore.rs +++ b/src/modules/restore.rs @@ -2,6 +2,8 @@ use crate::{ modules::selector::selector, utils::{ get_config::get_config, + root_checker::root_checker, + set_environment_variables::set_environment_variables, snapshots_selector::snapshots_selector, tools::{clear, pause}, }, @@ -10,7 +12,6 @@ use anyhow::Result; use cmd_lib::run_cmd; use color_print::{cformat, cprintln}; use dialoguer::{theme::ColorfulTheme, Confirm, Select}; -use std::env; fn do_restore( backend: &str, @@ -19,25 +20,29 @@ fn do_restore( restore_snapshot: &str, user: &str, ) -> Result<()> { + root_checker()?; + if run_cmd!( - restic -r $backend:$repository --verbose --verbose restore $restore_snapshot --target $restore_folder; + sudo -E restic -r $backend:$repository --verbose --verbose restore $restore_snapshot --target $restore_folder; ) .is_err() { cprintln!("Failed to restore snapshot: {restore_snapshot} into: {restore_folder}"); } - if run_cmd!(chown -R $user:$user $restore_folder).is_err() { - cprintln!("Failed to change ownership of: {restore_folder}"); + if run_cmd!(sudo -E chown -R $user:$user $restore_folder 2>/dev/null).is_err() { + cprintln!("\nFailed to change ownership of: {restore_folder}\n"); } Ok(()) } pub fn restore(noconfirm: bool) -> Result<()> { - let settings = get_config()?; clear()?; cprintln!("RESTORE"); println!(); + + let settings = get_config()?; + let selection = if settings.len() > 1 { let selections: Vec = settings.iter().map(|x| x.name.to_owned()).collect(); Select::with_theme(&ColorfulTheme::default()) @@ -50,19 +55,15 @@ pub fn restore(noconfirm: bool) -> Result<()> { 0 }; - env::set_var("USER", &settings[selection].user); - env::set_var("RESTIC_PASSWORD", &settings[selection].restic_password); - for env in &settings[selection].env { - for (key, value) in env { - env::set_var(key, value); - } - } + let setting = &settings[selection]; + + set_environment_variables(setting)?; - let backend = &settings[selection].backend; - let repository = &settings[selection].repository; - let restore_folder = &settings[selection].restore_folder; + let backend = &setting.backend; + let repository = &setting.repository; + let restore_folder = &setting.restore_folder; let restore_snapshot = snapshots_selector(backend, repository)?; - let user = &settings[selection].user; + let user = &setting.user; if Confirm::with_theme(&ColorfulTheme::default()) .with_prompt(cformat!( diff --git a/src/modules/selector.rs b/src/modules/selector.rs index a3fc263..5398025 100644 --- a/src/modules/selector.rs +++ b/src/modules/selector.rs @@ -1,14 +1,60 @@ use crate::{ modules::{ backup::backup, cache::cache, check::check, delete::delete, initialize::initialize, - repair::repair, restore::restore, snapshots::snapshots, update::update, + repair::repair, restore::restore, snapshots::snapshots, + }, + utils::{ + get_config::get_config, set_environment_variables::set_environment_variables, tools::clear, }, - utils::{get_config::get_config, tools::clear}, }; use anyhow::Result; use color_print::{cformat, cprintln}; use dialoguer::{theme::ColorfulTheme, Select}; -use std::{env, process::exit}; +use std::process::exit; + +fn handle_selection(selection: &str) -> Result<()> { + match selection { + "Backup" => backup(false), + "Restore" => restore(false), + "Snapshots" => snapshots(false), + "Delete" => delete(false), + "Initialize" => initialize(false), + "Check" => check(false), + "Repair" => handle_repair(), + "Cache" => cache(false), + "Exit" => { + exit(0); + } + _ => exit(0), + } +} + +fn handle_repair() -> Result<()> { + clear()?; + cprintln!("REPAIR"); + println!(); + + let settings = get_config()?; + + let selection = if settings.len() > 1 { + let selections: Vec = settings.iter().map(|x| x.name.to_owned()).collect(); + Select::with_theme(&ColorfulTheme::default()) + .with_prompt(cformat!("Where do you want to perform a repair?")) + .default(0) + .max_length(10) + .items(&selections[..]) + .interact()? + } else { + 0 + }; + + set_environment_variables(&settings[selection])?; + + let backend = &settings[selection].backend; + let repository = &settings[selection].repository; + + repair(backend, repository, false) +} pub fn selector() -> Result<()> { clear()?; @@ -22,7 +68,6 @@ pub fn selector() -> Result<()> { "Check", "Repair", "Cache", - "Update", exit_str.as_str(), ]; let selection = Select::with_theme(&ColorfulTheme::default()) @@ -32,66 +77,5 @@ pub fn selector() -> Result<()> { .items(&selections[..]) .interact()?; - match selections[selection] { - "Backup" => { - backup(false)?; - } - "Restore" => { - restore(false)?; - } - "Snapshots" => { - snapshots(false)?; - } - "Delete" => { - delete(false)?; - } - "Initialize" => { - initialize(false)?; - } - "Check" => { - check(false)?; - } - "Repair" => { - let settings = get_config()?; - clear()?; - cprintln!("REPAIR"); - println!(); - let selection = if settings.len() > 1 { - let selections: Vec = settings.iter().map(|x| x.name.to_owned()).collect(); - Select::with_theme(&ColorfulTheme::default()) - .with_prompt(cformat!("Where do you want to perform a repair?")) - .default(0) - .max_length(10) - .items(&selections[..]) - .interact()? - } else { - 0 - }; - - env::set_var("USER", &settings[selection].user); - env::set_var("RESTIC_PASSWORD", &settings[selection].restic_password); - for env in &settings[selection].env { - for (key, value) in env { - env::set_var(key, value); - } - } - - let backend = &settings[selection].backend; - let repository = &settings[selection].repository; - - repair(backend, repository, false)?; - } - "Cache" => { - cache(false)?; - } - "Update" => { - update(false)?; - } - "Exit" => { - exit(0); - } - _ => exit(0), - } - - Ok(()) + handle_selection(selections[selection]) } diff --git a/src/modules/snapshots.rs b/src/modules/snapshots.rs index 0625a09..67500f9 100644 --- a/src/modules/snapshots.rs +++ b/src/modules/snapshots.rs @@ -1,43 +1,47 @@ -use std::{env, time::Duration}; - use crate::{ modules::selector::selector, utils::{ get_config::get_config, + root_checker::root_checker, + set_environment_variables::set_environment_variables, tools::{clear, pause}, }, }; use anyhow::Result; +use cmd_lib::run_cmd; use color_print::{cformat, cprintln}; use dialoguer::{theme::ColorfulTheme, Select}; use indicatif::ProgressBar; -use std::process::Command; +use std::time::Duration; fn get_snapshots(backend: &str, repository: &str) -> Result<()> { + root_checker()?; + let pb = ProgressBar::new_spinner(); pb.enable_steady_tick(Duration::from_millis(120)); pb.set_message("Loading snapshots..."); - let out = Command::new("restic") - .arg("-r") - .arg(format!("{}:{}", &backend, &repository)) - .arg("--verbose") - .arg("--verbose") - .arg("snapshots") - .output()?; + if run_cmd!( + sudo -E restic -r $backend:$repository --verbose --verbose snapshots; + ) + .is_err() + { + pb.finish_and_clear(); + cprintln!("Failed to list snapshots"); + } pb.finish_and_clear(); - println!("{}", String::from_utf8(out.stdout)?); Ok(()) } pub fn snapshots(noconfirm: bool) -> Result<()> { - let settings = get_config()?; clear()?; cprintln!("SNAPSHOTS"); println!(); + let settings = get_config()?; + let selection = if settings.len() > 1 { let selections: Vec = settings.iter().map(|x| x.name.to_owned()).collect(); Select::with_theme(&ColorfulTheme::default()) @@ -50,16 +54,12 @@ pub fn snapshots(noconfirm: bool) -> Result<()> { 0 }; - env::set_var("USER", &settings[selection].user); - env::set_var("RESTIC_PASSWORD", &settings[selection].restic_password); - for env in &settings[selection].env { - for (key, value) in env { - env::set_var(key, value); - } - } + let setting = &settings[selection]; + + set_environment_variables(setting)?; - let backend = &settings[selection].backend; - let repository = &settings[selection].repository; + let backend = &setting.backend; + let repository = &setting.repository; get_snapshots(backend, repository)?; pause()?; diff --git a/src/modules/update.rs b/src/modules/update.rs index 8b36a33..a05366a 100644 --- a/src/modules/update.rs +++ b/src/modules/update.rs @@ -1,10 +1,8 @@ -use crate::{ - modules::selector::selector, - utils::{ - get_current_shell::get_current_shell, - macros::error, - tools::{clear, pause}, - }, +use crate::utils::{ + get_current_shell::get_current_shell, + macros::error, + root_checker::root_checker, + tools::{clear, pause}, }; use anyhow::{Context, Result}; use cmd_lib::run_cmd; @@ -14,7 +12,8 @@ use indicatif::ProgressBar; use std::{ env::current_exe, fs::{remove_file, File}, - io::BufReader, + io::{BufReader, ErrorKind}, + os::unix::process::CommandExt, path::Path, process::Command, time::Duration, @@ -25,6 +24,7 @@ fn get_current_version() -> Result { let version = env!("CARGO_PKG_VERSION").to_string(); Ok(version) } + fn get_latest_version(url: &str) -> Result { let shell = get_current_shell()?; let output = Command::new(shell) @@ -50,11 +50,77 @@ fn extract_wrestic(file_path: &str, extract_path: &str) -> Result<()> { let tar = BufReader::new(gz); let mut archive = Archive::new(tar); let extract_path_parent = Path::new(extract_path).parent().unwrap().to_owned(); - archive.unpack(extract_path_parent)?; + + match archive.unpack(&extract_path_parent) { + Ok(_) => Ok(()), + Err(e) => { + if e.kind() == std::io::ErrorKind::PermissionDenied { + Command::new("sudo") + .arg("-E") + .arg("tar") + .arg("-xvf") + .arg(file_path) + .arg("-C") + .arg(extract_path_parent.to_str().unwrap()) + .exec(); + Ok(()) + } else { + Err(e.into()) + } + } + } +} + +fn download_latest_version(shell: &str, url: &str, tmp_path: &str) -> Result<()> { + let command = format!( + r#"curl -sL $(curl -s "{url}" | grep browser_download_url | cut -d '"' -f 4) -o {tmp_path}"# + ); + + let command_result = run_cmd!( + $shell -c $command 2>/dev/null; + ); + + if let Err(e) = command_result { + if format!("{}", e).contains("Permission denied") { + root_checker()?; + + if run_cmd!( + sudo -E $shell -c $command 2>/dev/null; + ) + .is_err() + { + Err(error!("Failed downloading the latest version of wrestic"))?; + } + } else { + Err(error!("Failed downloading the latest version of wrestic"))?; + } + } + + Ok(()) +} + +fn remove_file_with_permission_check(file_path: &str) -> Result<()> { + if let Err(e) = remove_file(file_path) { + if e.kind() == ErrorKind::PermissionDenied { + let output = Command::new("sudo") + .arg("-E") + .arg("rm") + .arg(file_path) + .output() + .expect("Failed to execute command"); + + if !output.status.success() { + return Err(error!("Failed removing file")); + } + } else { + return Err(error!("Failed removing file")); + } + } + Ok(()) } -pub fn update(noconfirm: bool) -> Result<()> { +pub fn update() -> Result<()> { clear()?; cprintln!("UPDATER"); println!(); @@ -64,10 +130,6 @@ pub fn update(noconfirm: bool) -> Result<()> { let tmp_path = "/tmp/wrestic.tar.gz"; let url = "https://api.github.com/repos/alvaro17f/wrestic/releases/latest"; - let command = format!( - r#"curl -sL $(curl -s "{url}" | grep browser_download_url | cut -d '"' -f 4) -o {tmp_path}"# - ); - if get_current_version()? >= get_latest_version(url)? { cprintln!("Wrestic is already up to date!\n"); pause()? @@ -84,38 +146,25 @@ pub fn update(noconfirm: bool) -> Result<()> { let shell = get_current_shell()?; - if run_cmd!( - $shell -c $command; - ) - .is_err() - { - pb.finish_and_clear(); - Err(error!("Failed downloading the latest version of wrestic"))?; - } + download_latest_version(&shell, url, tmp_path)?; - if remove_file(bin_path).is_err() { - pb.finish_and_clear(); - Err(error!("Failed removing the old wrestic version"))?; - } + pb.finish_and_clear(); - if extract_wrestic(tmp_path, bin_path).is_err() { - pb.finish_and_clear(); - Err(error!(format!("Failed extracting wrestic into {bin_path}")))?; - }; + remove_file_with_permission_check(bin_path)?; - if remove_file(tmp_path).is_err() { - pb.finish_and_clear(); - Err(error!("Failed removing tmp files"))?; - } else { - pb.finish_and_clear(); - cprintln!("Wrestic was successfully updated\n"); + pb.finish_and_clear(); + + if extract_wrestic(tmp_path, bin_path).is_err() { + Err(error!("Failed extracting the latest version of wrestic"))?; } - pause()?; - } + pb.finish_and_clear(); - if !noconfirm { - selector()?; + cprintln!( + "Wrestic has been updated to version {}!", + get_latest_version(url)? + ); } + Ok(()) } diff --git a/src/utils/completions.rs b/src/utils/completions.rs index bb03527..b8cde10 100644 --- a/src/utils/completions.rs +++ b/src/utils/completions.rs @@ -1,41 +1,115 @@ -use clap::Command; +#![allow(clippy::single_char_pattern)] +use crate::utils::get_user::get_user; +use crate::utils::macros::error; +use anyhow::Result; use clap_complete::{generate, Generator}; +use std::io::ErrorKind; use std::{ fs::{self, File}, io::{self, Write}, path::Path, + process::{Command, Stdio}, }; -pub fn print_completions(gen: G, cmd: &mut Command) { + +fn create_completions_file(file_path: &str, output: &[u8]) -> Result<()> { + let path = Path::new(file_path); + + if !path.exists() { + if let Some(parent) = path.parent() { + match fs::create_dir_all(parent) { + Ok(_) => {} + Err(e) if e.kind() == ErrorKind::PermissionDenied => { + let status = Command::new("sudo") + .arg("mkdir") + .arg("-p") + .arg(parent.to_str().unwrap()) + .status()?; + + if !status.success() { + return Err(error!( + "Failed to create directory. Please run this program with 'sudo'." + )); + } + } + Err(_) => return Err(error!("Failed to create directory.")), + } + } + + match File::create(path) { + Ok(_) => {} + Err(e) if e.kind() == ErrorKind::PermissionDenied => { + let status = Command::new("sudo") + .arg("touch") + .arg(path.to_str().unwrap()) + .status()?; + + if !status.success() { + return Err(error!( + "Failed to create file. Please run this program with 'sudo'." + )); + } + } + Err(_) => return Err(error!("Failed to create file.")), + } + } + + match fs::write(path, output) { + Ok(_) => {} + Err(e) if e.kind() == ErrorKind::PermissionDenied => { + let mut child = Command::new("sudo") + .arg("sh") + .arg("-c") + .arg(format!( + "echo '{}' > {}", + String::from_utf8_lossy(output).replace("'", "'\\''"), + path.to_str().unwrap() + )) + .stdin(Stdio::piped()) + .spawn()?; + + child.stdin.as_mut().unwrap().write_all(output)?; + + let status = child.wait()?; + + if !status.success() { + return Err(error!( + "Failed to write to file. Please run this program with 'sudo'." + )); + } + } + Err(_) => return Err(error!("Failed to write to file.")), + } + + Ok(()) +} + +pub fn print_completions(gen: G, cmd: &mut clap::Command) { generate(gen, cmd, cmd.get_name().to_string(), &mut io::stdout()); } -pub fn set_completions(gen: G, cmd: &mut Command) { +pub fn set_completions( + gen: G, + cmd: &mut clap::Command, +) -> Result<()> { let mut output = Vec::new(); generate(gen, cmd, cmd.get_name().to_string(), &mut output); let shell = format!("{:#?}", gen); + let user = get_user; - let file_path = if shell.to_lowercase().contains("zsh") { - "/usr/local/share/zsh/site-functions/_wrestic" - } else if shell.to_lowercase().contains("bash") { - "/etc/bash_completion.d/wrestic" + let file_path = if shell.to_lowercase().contains("bash") { + format!( + "/home/{}/.local/share/bash-completion/completions/wrestic", + user()? + ) + } else if shell.to_lowercase().contains("zsh") { + "/usr/local/share/zsh/site-functions/_wrestic".to_string() + } else if shell.to_lowercase().contains("fish") { + format!("/home/{}/.config/fish/completions/wrestic.fish", user()?) } else { panic!("{:?}", "Shell not supported") }; - let path = Path::new(file_path); - - if !path.exists() { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent).unwrap(); - } - File::create(path).unwrap(); - } - - if let Ok(mut file) = File::create(path) { - file.write_all(&output).unwrap(); - } else { - io::stdout().write_all(&output).unwrap(); - } + create_completions_file(&file_path, &output) } diff --git a/src/utils/get_user.rs b/src/utils/get_user.rs new file mode 100644 index 0000000..95beca4 --- /dev/null +++ b/src/utils/get_user.rs @@ -0,0 +1,39 @@ +use crate::utils::macros::error; +use anyhow::Result; +use color_print::cformat; +use dialoguer::theme::ColorfulTheme; +use dialoguer::Select; +use std::fs; + +pub fn get_user() -> Result { + let mut users = Vec::new(); + + for entry in fs::read_dir("/home")? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + let config_path = path.join(".config/wrestic/wrestic.toml"); + if config_path.exists() { + if let Some(user) = path.file_name().and_then(|os_str| os_str.to_str()) { + users.push(user.to_string()); + } + } + } + } + + match users.len() { + 0 => Err(error!( + "No users found. Please create a config file at ~/.config/wrestic/wrestic.toml for a user." + )), + 1 => Ok(users[0].to_string()), + _ => { + let selection = Select::with_theme(&ColorfulTheme::default()) + .with_prompt(cformat!("Who are you?")) + .default(0) + .max_length(10) + .items(&users) + .interact()?; + Ok(users[selection].to_string()) + } + } +} diff --git a/src/utils/macros.rs b/src/utils/macros.rs index de83ba4..8b8f38b 100644 --- a/src/utils/macros.rs +++ b/src/utils/macros.rs @@ -22,5 +22,6 @@ macro_rules! error { anyhow::anyhow!(color_print::cformat!("{}", $err)) }; } + pub(crate) use error; pub(crate) use error_detail; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 46a570a..926371a 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,8 +1,10 @@ pub mod completions; pub mod get_config; pub mod get_current_shell; +pub mod get_user; pub mod macros; pub mod restic_checker; pub mod root_checker; +pub mod set_environment_variables; pub mod snapshots_selector; pub mod tools; diff --git a/src/utils/restic_checker.rs b/src/utils/restic_checker.rs index ed5b07a..494f037 100644 --- a/src/utils/restic_checker.rs +++ b/src/utils/restic_checker.rs @@ -2,6 +2,7 @@ use crate::{ utils::macros::error, utils::{ get_current_shell::get_current_shell, + root_checker::root_checker, tools::{clear, pause}, }, }; @@ -12,6 +13,22 @@ use dialoguer::{theme::ColorfulTheme, Confirm}; use std::process::exit; use which::which; +fn install_restic(shell: &str, command: &str) -> Result<()> { + root_checker()?; + + if run_cmd!( + sudo -E $shell -c $command; + ) + .is_err() + { + Err(error!("Failed to install Restic")) + } else { + cprintln!("Restic installed successfully!"); + pause()?; + Ok(()) + } +} + pub fn restic_checker() -> Result<()> { let url = "https://api.github.com/repos/restic/restic/releases/latest"; let command = format!( @@ -30,17 +47,7 @@ pub fn restic_checker() -> Result<()> { .interact()? { let shell = get_current_shell()?; - if run_cmd!( - $shell -c $command; - ) - .is_err() - { - Err(error!("Failed to install Restic"))? - } else { - cprintln!("Restic installed successfully!"); - pause()?; - Ok(()) - } + Ok(install_restic(&shell, &command)?) } else { exit(0); } diff --git a/src/utils/root_checker.rs b/src/utils/root_checker.rs index 9c76ccf..83786b1 100644 --- a/src/utils/root_checker.rs +++ b/src/utils/root_checker.rs @@ -1,11 +1,16 @@ use crate::utils::macros::error; + use anyhow::Result; use nix::unistd::geteuid; +use std::process::Command; pub fn root_checker() -> Result<()> { if geteuid().is_root() { Ok(()) } else { - Err(error!("Please run as root"))? + if Command::new("sudo").arg("-v").status().is_err() { + Err(error!("You need to be root to run this command"))?; + }; + Ok(()) } } diff --git a/src/utils/set_environment_variables.rs b/src/utils/set_environment_variables.rs new file mode 100644 index 0000000..1151465 --- /dev/null +++ b/src/utils/set_environment_variables.rs @@ -0,0 +1,14 @@ +use crate::utils::get_config::Settings; +use anyhow::Result; +use std::env; + +pub fn set_environment_variables(setting: &Settings) -> Result<()> { + env::set_var("USER", &setting.user); + env::set_var("RESTIC_PASSWORD", &setting.restic_password); + for env in &setting.env { + for (key, value) in env { + env::set_var(key, value); + } + } + Ok(()) +} diff --git a/src/utils/snapshots_selector.rs b/src/utils/snapshots_selector.rs index 3e3ffa2..c88a0e8 100644 --- a/src/utils/snapshots_selector.rs +++ b/src/utils/snapshots_selector.rs @@ -5,21 +5,25 @@ use indicatif::ProgressBar; use regex::Regex; use std::{process::Command, time::Duration}; -pub fn snapshots_selector(backend: &str, repository: &str) -> Result { - let pb = ProgressBar::new_spinner(); - pb.enable_steady_tick(Duration::from_millis(120)); - pb.set_message("Loading snapshots..."); - let restic = Command::new("restic") +use crate::utils::root_checker::root_checker; + +fn get_snapshots(backend: &str, repository: &str) -> Result { + let restic = Command::new("sudo") + .arg("-E") + .arg("restic") .arg("-r") .arg(format!("{}:{}", backend, repository)) .arg("--verbose") .arg("--verbose") .arg("snapshots") .output()?; - pb.finish_and_clear(); let restic = String::from_utf8(restic.stdout)?; + Ok(restic) +} + +fn parse_snapshots(restic: &str) -> Result> { let restic_rev = restic .lines() .rev() @@ -32,6 +36,22 @@ pub fn snapshots_selector(backend: &str, repository: &str) -> Result { .map(|cap| format!("[{}] - {}", &cap[1], &cap[2])) .collect::>(); + Ok(selections) +} + +pub fn snapshots_selector(backend: &str, repository: &str) -> Result { + root_checker()?; + + let pb = ProgressBar::new_spinner(); + pb.enable_steady_tick(Duration::from_millis(120)); + pb.set_message("Loading snapshots..."); + + let restic = get_snapshots(backend, repository)?; + + pb.finish_and_clear(); + + let selections = parse_snapshots(&restic)?; + let selection = Select::with_theme(&ColorfulTheme::default()) .with_prompt(cformat!("Snapshots:")) .default(0) @@ -40,7 +60,7 @@ pub fn snapshots_selector(backend: &str, repository: &str) -> Result { .interact()?; let selection = Regex::new(r"(\w+)\s+(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})")? - .captures_iter(&restic_rev) + .captures_iter(&restic) .map(|cap| (cap[1]).to_string()) .collect::>()[selection] .to_owned();