From 1470de8a447c0930f3298dd08a2d3445dfa7f713 Mon Sep 17 00:00:00 2001 From: Lach Date: Wed, 18 Jun 2025 10:04:49 +0000 Subject: [PATCH] feat: minimal rollback support --- --- a/Cargo.lock +++ b/Cargo.lock @@ -83,10 +83,10 @@ "i18n-embed", "i18n-embed-fl", "lazy_static", - "nom", + "nom 7.1.3", "num-traits", "pin-project", - "rand", + "rand 0.8.5", "rsa", "rust-embed", "scrypt", @@ -107,8 +107,8 @@ "cookie-factory", "hkdf", "io_tee", - "nom", - "rand", + "nom 7.1.3", + "rand 0.8.5", "secrecy", "sha2", ] @@ -233,18 +233,18 @@ dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] name = "async-trait" -version = "0.1.83" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -395,15 +395,15 @@ "regex", "rustc-hash", "shlex", - "syn 2.0.87", + "syn", "which", ] [[package]] name = "bitflags" -version = "2.6.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" dependencies = [ "serde", ] @@ -493,7 +493,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "nom", + "nom 7.1.3", ] [[package]] @@ -534,9 +534,9 @@ [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", @@ -544,7 +544,7 @@ "num-traits", "serde", "wasm-bindgen", - "windows-targets", + "windows-link", ] [[package]] @@ -609,10 +609,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -647,6 +647,15 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] +name = "convert_case" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" +dependencies = [ + "unicode-segmentation", +] + +[[package]] name = "cookie-factory" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -684,16 +693,18 @@ [[package]] name = "crossterm" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ "bitflags", "crossterm_winapi", + "derive_more", + "document-features", "filedescriptor", "mio", "parking_lot", - "rustix", + "rustix 1.0.7", "signal-hook", "signal-hook-mio", "winapi", @@ -715,7 +726,7 @@ checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "typenum", ] @@ -752,7 +763,7 @@ dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -781,15 +792,36 @@ [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", "serde", ] [[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -809,10 +841,19 @@ dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] +name = "document-features" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +dependencies = [ + "litrs", +] + +[[package]] name = "ed25519" version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -830,7 +871,6 @@ dependencies = [ "curve25519-dalek", "ed25519", - "rand_core", "serde", "sha2", "subtle", @@ -857,12 +897,12 @@ [[package]] name = "errno" -version = "0.3.9" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -924,10 +964,10 @@ "hostname", "human-repr", "indicatif", - "itertools 0.13.0", + "itertools 0.14.0", "nix-eval", "nixlike", - "nom", + "nom 8.0.0", "openssh", "owo-colors", "peg", @@ -958,15 +998,17 @@ "futures", "hostname", "indoc", - "itertools 0.13.0", + "itertools 0.14.0", "nix-eval", "nixlike", - "nom", + "nom 8.0.0", "openssh", - "rand", + "rand 0.9.1", "serde", "serde_json", + "tabled", "tempfile", + "time", "tokio", "tokio-util", "tracing", @@ -983,7 +1025,7 @@ "ed25519-dalek", "fleet-shared", "hex", - "rand", + "rand 0.9.1", "x25519-dalek", ] @@ -1119,7 +1161,7 @@ dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -1170,7 +1212,19 @@ dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+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]] @@ -1237,12 +1291,6 @@ version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "heck" @@ -1297,13 +1345,13 @@ [[package]] name = "hostname" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" +checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" dependencies = [ "cfg-if", "libc", - "windows", + "windows-link", ] [[package]] @@ -1463,7 +1511,7 @@ "proc-macro2", "quote", "strsim", - "syn 2.0.87", + "syn", "unic-langid", ] @@ -1477,7 +1525,7 @@ "i18n-config", "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -1620,6 +1668,15 @@ ] [[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1651,9 +1708,9 @@ [[package]] name = "libc" -version = "0.2.164" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libloading" @@ -1694,6 +1751,18 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + +[[package]] name = "lock_api" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1778,7 +1847,7 @@ "hermit-abi 0.3.9", "libc", "log", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -1790,9 +1859,9 @@ [[package]] name = "nix" -version = "0.29.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ "bitflags", "cfg-if", @@ -1807,13 +1876,13 @@ "anyhow", "better-command", "futures", - "itertools 0.13.0", + "itertools 0.14.0", "nixlike", "r2d2", "regex", "serde", "serde_json", - "thiserror 2.0.3", + "thiserror 2.0.12", "tokio", "tokio-util", "tracing", @@ -1839,7 +1908,7 @@ "serde", "serde-transcode", "serde_json", - "thiserror 2.0.3", + "thiserror 2.0.12", ] [[package]] @@ -1872,6 +1941,15 @@ ] [[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] name = "nu-ansi-term" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1893,7 +1971,7 @@ "num-integer", "num-iter", "num-traits", - "rand", + "rand 0.8.5", "smallvec", "zeroize", ] @@ -1963,15 +2041,15 @@ [[package]] name = "openssh" -version = "0.11.3" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b52987a10526b8daef7f1946b0aadfc214479f897ba624776327fd3beec2722c" +checksum = "ea0bb128ba90e86bc55dae66031935f361cda4cbc1f011547c55a7d80079bc3e" dependencies = [ "libc", "once_cell", "shell-escape", "tempfile", - "thiserror 2.0.3", + "thiserror 2.0.12", "tokio", ] @@ -1983,9 +2061,9 @@ [[package]] name = "owo-colors" -version = "4.1.0" +version = "4.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56" +checksum = "26995317201fa17f3656c36716aed4a7c81743a9634ac4c99c0eeda495db0cec" dependencies = [ "supports-color 2.1.0", "supports-color 3.0.1", @@ -1993,13 +2071,13 @@ [[package]] name = "papergrid" -version = "0.12.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7419ad52a7de9b60d33e11085a0fe3df1fbd5926aa3f93d3dd53afbc9e86725" +checksum = "6978128c8b51d8f4080631ceb2302ab51e32cc6e8615f735ee2f83fd269ae3f1" dependencies = [ "bytecount", "fnv", - "unicode-width 0.1.11", + "unicode-width 0.2.0", ] [[package]] @@ -2043,9 +2121,9 @@ [[package]] name = "peg" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "295283b02df346d1ef66052a757869b2876ac29a6bb0ac3f5f7cd44aebe40e8f" +checksum = "9928cfca101b36ec5163e70049ee5368a8a1c3c6efc9ca9c5f9cc2f816152477" dependencies = [ "peg-macros", "peg-runtime", @@ -2053,9 +2131,9 @@ [[package]] name = "peg-macros" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdad6a1d9cf116a059582ce415d5f5566aabcd4008646779dab7fdc2a9a9d426" +checksum = "6298ab04c202fa5b5d52ba03269fb7b74550b150323038878fe6c372d8280f71" dependencies = [ "peg-runtime", "proc-macro2", @@ -2064,9 +2142,9 @@ [[package]] name = "peg-runtime" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3aeb8f54c078314c2065ee649a7241f46b9d8e418e1a9581ba0546657d7aa3a" +checksum = "132dca9b868d927b35b5dd728167b2dee150eb1ad686008fc71ccb298b776fca" [[package]] name = "pem" @@ -2111,7 +2189,7 @@ dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -2204,31 +2282,7 @@ checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", - "syn 2.0.87", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", + "syn", ] [[package]] @@ -2250,7 +2304,7 @@ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -2279,7 +2333,7 @@ checksum = "0c1318b19085f08681016926435853bbf7858f9c082d0999b80550ff5d9abe15" dependencies = [ "bytes", - "heck 0.5.0", + "heck", "itertools 0.13.0", "log", "multimap", @@ -2289,7 +2343,7 @@ "prost", "prost-types", "regex", - "syn 2.0.87", + "syn", "tempfile", ] @@ -2303,7 +2357,7 @@ "itertools 0.13.0", "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -2325,6 +2379,12 @@ ] [[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] name = "r2d2" version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2342,18 +2402,38 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", ] [[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -2362,10 +2442,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", ] [[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + +[[package]] name = "rcgen" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2439,7 +2528,7 @@ dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.15", "libc", "spin", "untrusted", @@ -2481,14 +2570,15 @@ [[package]] name = "ron" -version = "0.8.1" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +checksum = "beceb6f7bf81c73e73aeef6dd1356d9a1b2b4909e1f0fc3e59b034f9572d7b7f" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "bitflags", "serde", "serde_derive", + "unicode-ident", ] [[package]] @@ -2517,7 +2607,7 @@ "num-traits", "pkcs1", "pkcs8", - "rand_core", + "rand_core 0.6.4", "signature", "spki", "subtle", @@ -2544,7 +2634,7 @@ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.87", + "syn", "walkdir", ] @@ -2588,11 +2678,24 @@ "bitflags", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.14", "windows-sys 0.52.0", ] [[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.59.0", +] + +[[package]] name = "rustls" version = "0.23.17" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2721,9 +2824,9 @@ [[package]] name = "serde" -version = "1.0.215" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] @@ -2748,20 +2851,20 @@ [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -2838,7 +2941,7 @@ checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -2920,17 +3023,6 @@ checksum = "8775305acf21c96926c900ad056abeef436701108518cf890020387236ac5a77" dependencies = [ "is_ci", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", ] [[package]] @@ -2958,37 +3050,38 @@ [[package]] name = "tabled" -version = "0.16.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c9303ee60b9bedf722012ea29ae3711ba13a67c9b9ae28993838b63057cb1b" +checksum = "e39a2ee1fbcd360805a771e1b300f78cc88fec7b8d3e2f71cd37bbf23e725c7d" dependencies = [ "papergrid", "tabled_derive", + "testing_table", ] [[package]] name = "tabled_derive" -version = "0.8.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf0fb8bfdc709786c154e24a66777493fb63ae97e3036d914c8666774c477069" +checksum = "0ea5d1b13ca6cff1f9231ffd62f15eefd72543dab5e468735f1a456728a02846" dependencies = [ - "heck 0.4.1", - "proc-macro-error", + "heck", + "proc-macro-error2", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "tempfile" -version = "3.14.0" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ - "cfg-if", "fastrand", + "getrandom 0.3.3", "once_cell", - "rustix", + "rustix 1.0.7", "windows-sys 0.59.0", ] @@ -2998,7 +3091,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" dependencies = [ - "rustix", + "rustix 0.38.40", "windows-sys 0.59.0", ] @@ -3014,6 +3107,15 @@ ] [[package]] +name = "testing_table" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f8daae29995a24f65619e19d8d31dea5b389f3d853d8bf297bbf607cd0014cc" +dependencies = [ + "unicode-width 0.2.0", +] + +[[package]] name = "text-size" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3058,11 +3160,11 @@ [[package]] name = "thiserror" -version = "2.0.3" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.3", + "thiserror-impl 2.0.12", ] [[package]] @@ -3073,18 +3175,18 @@ dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] name = "thiserror-impl" -version = "2.0.3" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -3099,9 +3201,9 @@ [[package]] name = "time" -version = "0.3.36" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "num-conv", @@ -3113,15 +3215,15 @@ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", @@ -3138,9 +3240,9 @@ [[package]] name = "tokio" -version = "1.41.1" +version = "1.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" dependencies = [ "backtrace", "bytes", @@ -3155,13 +3257,13 @@ [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -3189,9 +3291,9 @@ [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" dependencies = [ "bytes", "futures-core", @@ -3252,7 +3354,7 @@ "prost-build", "prost-types", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -3266,7 +3368,7 @@ "indexmap 1.9.3", "pin-project", "pin-project-lite", - "rand", + "rand 0.8.5", "slab", "tokio", "tokio-util", @@ -3337,7 +3439,7 @@ dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -3457,6 +3559,12 @@ checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] name = "unicode-width" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3476,9 +3584,9 @@ [[package]] name = "unindent" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" [[package]] name = "universal-hash" @@ -3573,6 +3681,15 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[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.95" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3594,7 +3711,7 @@ "once_cell", "proc-macro2", "quote", - "syn 2.0.87", + "syn", "wasm-bindgen-shared", ] @@ -3616,7 +3733,7 @@ dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3646,7 +3763,7 @@ "either", "home", "once_cell", - "rustix", + "rustix 0.38.40", ] [[package]] @@ -3681,23 +3798,19 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" +name = "windows-core" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-core", "windows-targets", ] [[package]] -name = "windows-core" -version = "0.52.0" +name = "windows-link" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets", -] +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-sys" @@ -3782,13 +3895,22 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[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 = "x25519-dalek" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ "curve25519-dalek", - "rand_core", + "rand_core 0.6.4", "serde", "zeroize", ] @@ -3804,9 +3926,9 @@ [[package]] name = "z85" -version = "3.0.5" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a599daf1b507819c1121f0bf87fa37eb19daac6aff3aefefd4e6e2e0f2020fc" +checksum = "9b3a41ce106832b4da1c065baa4c31cf640cf965fa1483816402b7f6b96f0a64" [[package]] name = "zerocopy" @@ -3826,7 +3948,7 @@ dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -3846,5 +3968,5 @@ dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] --- a/Cargo.toml +++ b/Cargo.toml @@ -15,12 +15,12 @@ anyhow = "1.0" clap = { version = "4.5", features = ["derive", "env", "unicode", "wrap_help"] } clap_complete = "4.5" -nix = { version = "0.29.0", features = ["fs", "user"] } +nix = { version = "0.30.1", features = ["fs", "user"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -tempfile = "3.10" -thiserror = "2.0.3" -tokio = { version = "1.36.0", features = ["fs", "macros", "rt", "rt-multi-thread", "sync", "time"] } -tokio-util = { version = "0.7.11", features = ["codec"] } +tempfile = "3.20" +thiserror = "2.0.12" +tokio = { version = "1.45.1", features = ["fs", "macros", "rt", "rt-multi-thread", "sync", "time"] } +tokio-util = { version = "0.7.15", features = ["codec"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] } --- a/cmds/fleet/Cargo.toml +++ b/cmds/fleet/Cargo.toml @@ -5,6 +5,7 @@ authors = ["Yaroslav Bolyukin "] edition.workspace = true rust-version.workspace = true +default-run = "fleet" [dependencies] age = { workspace = true, features = ["armor"] } @@ -27,23 +28,23 @@ async-trait = "0.1" base64 = "0.22.1" chrono = { version = "0.4", features = ["serde"] } -crossterm = { version = "0.28.0", features = ["use-dev-tty"] } +crossterm = { version = "0.29.0", features = ["use-dev-tty"] } futures = "0.3" -hostname = "0.4.0" -itertools = "0.13" +hostname = "0.4.1" +itertools = "0.14" openssh = "0.11" -owo-colors = { version = "4.0", features = ["supports-color", "supports-colors"] } +owo-colors = { version = "4.2", features = ["supports-color", "supports-colors"] } peg = "0.8" -regex = "1.10" +regex = "1.11" shlex = "1.3" -tabled = { version = "0.16" } +tabled = { version = "0.20" } time = { version = "0.3", features = ["serde"] } tokio-util = { version = "0.7", features = ["codec"] } fleet-base = { version = "0.1.0", path = "../../crates/fleet-base" } human-repr = { version = "1.1", optional = true } indicatif = { version = "0.17", optional = true } -nom = "7.1.3" +nom = "8.0.0" tracing-indicatif = { version = "0.3", optional = true } [features] --- a/cmds/fleet/src/cmds/build_systems.rs +++ b/cmds/fleet/src/cmds/build_systems.rs @@ -1,14 +1,14 @@ -use std::{env::current_dir, os::unix::fs::symlink, path::PathBuf, time::Duration}; +use std::{env::current_dir, os::unix::fs::symlink, path::PathBuf}; -use anyhow::{anyhow, bail, Context, Result}; -use clap::{Parser, ValueEnum}; +use anyhow::{anyhow, Result}; +use clap::Parser; use fleet_base::{ - host::{Config, ConfigHost, DeployKind}, + deploy::{deploy_task, upload_task, DeployAction}, + host::{Config, DeployKind, GenerationStorage}, opts::FleetOpts, }; -use itertools::Itertools as _; use nix_eval::{nix_go, NixBuildBatch}; -use tokio::{task::LocalSet, time::sleep}; +use tokio::task::LocalSet; use tracing::{error, field, info, info_span, warn, Instrument}; #[derive(Parser)] @@ -18,300 +18,16 @@ disable_rollback: bool, /// Action to execute after system is built action: DeployAction, -} - -#[derive(ValueEnum, Clone, Copy)] -enum DeployAction { - /// Upload derivation, but do not execute the update. - Upload, - /// Upload and execute the activation script, old version will be used after reboot. - Test, - /// Upload and set as current system profile, but do not execute activation script. - Boot, - /// Upload, set current profile, and execute activation script. - Switch, } -impl DeployAction { - pub(crate) fn name(&self) -> Option<&'static str> { - match self { - Self::Upload => None, - Self::Test => Some("test"), - Self::Boot => Some("boot"), - Self::Switch => Some("switch"), - } - } - pub(crate) fn should_switch_profile(&self) -> bool { - matches!(self, Self::Switch | Self::Boot) - } - pub(crate) fn should_activate(&self) -> bool { - matches!(self, Self::Switch | Self::Test | Self::Boot) - } - pub(crate) fn should_create_rollback_marker(&self) -> bool { - // Upload does nothing on the target machine, other than uploading the closure. - // In boot case we want to have rollback marker prepared, so that the system may rollback itself on the next boot. - !matches!(self, Self::Upload) - } - pub(crate) fn should_schedule_rollback_run(&self) -> bool { - matches!(self, Self::Switch | Self::Test) - } -} - #[derive(Parser, Clone)] pub struct BuildSystems { /// Attribute to build. Systems are deployed from "toplevel" attr, well-known used attributes /// are "sdImage"/"isoImage", and your configuration may include any other build attributes. #[clap(long, default_value = "toplevel")] build_attr: String, -} - -struct Generation { - id: u32, - current: bool, - datetime: String, -} - -fn parse_generation_line(g: &str) -> Option { - let mut parts = g.split_whitespace(); - let id = parts.next()?; - let id: u32 = id.parse().ok()?; - let date = parts.next()?; - let time = parts.next()?; - let current = if let Some(current) = parts.next() { - if current == "(current)" { - Some(true) - } else { - None - } - } else { - Some(false) - }; - let current = current?; - if parts.next().is_some() { - warn!("unexpected text after generation: {g}"); - } - Some(Generation { - id, - current, - datetime: format!("{date} {time}"), - }) -} - -async fn get_current_generation(host: &ConfigHost) -> Result { - let mut cmd = host.cmd("nix-env").await?; - cmd.comparg("--profile", "/nix/var/nix/profiles/system") - .arg("--list-generations"); - // Sudo is required due to --list-generations acquiring lock on the profile. - let data = cmd.sudo().run_string().await?; - let generations = data - .split('\n') - .map(|e| e.trim()) - .filter(|&l| !l.is_empty()) - .filter_map(|g| { - let gen = parse_generation_line(g); - if gen.is_none() { - warn!("bad generation: {g}"); - } - gen - }) - .collect::>(); - let current = generations - .into_iter() - .filter(|g| g.current) - .at_most_one() - .map_err(|_e| anyhow!("bad list-generations output"))? - .ok_or_else(|| anyhow!("failed to find generation"))?; - Ok(current) } - -async fn deploy_task( - action: DeployAction, - host: &ConfigHost, - built: PathBuf, - specialisation: Option, - disable_rollback: bool, -) -> Result<()> { - let deploy_kind = host.deploy_kind().await?; - if (deploy_kind == DeployKind::NixosInstall || deploy_kind == DeployKind::NixosLustrate) - && !matches!(action, DeployAction::Boot | DeployAction::Upload) - { - bail!("{deploy_kind:?} deploy kind only supports boot and upload actions"); - } - - let mut failed = false; - // TODO: Lockfile, to prevent concurrent system switch? - // TODO: If rollback target exists - bail, it should be removed. Lockfile will not work in case if rollback - // is scheduler on next boot (default behavior). On current boot - rollback activator will fail due to - // unit name conflict in systemd-run - // This code is tied to rollback.nix - if !disable_rollback && action.should_create_rollback_marker() { - let _span = info_span!("preparing").entered(); - info!("preparing for rollback"); - let generation = get_current_generation(host).await?; - info!( - "rollback target would be {} {}", - generation.id, generation.datetime - ); - { - let mut cmd = host.cmd("sh").await?; - cmd.arg("-c").arg(format!("mark=$(mktemp -p /etc -t fleet_rollback_marker.XXXXX) && echo -n {} > $mark && mv --no-clobber $mark /etc/fleet_rollback_marker", generation.id)); - if let Err(e) = cmd.sudo().run().await { - error!("failed to set rollback marker: {e}"); - failed = true; - } - } - // Activation script also starts rollback-watchdog.timer, however, it is possible that it won't be started. - // Kicking it on manually will work best. - // - // There wouldn't be conflict, because here we trigger start of the primary service, and systemd will - // only allow one instance of it. - - // TODO: We should also watch how this process is going. - // After running this command, we have less than 3 minutes to deploy everything, - // if we fail to perform generation switch in time, then we will still call the activation script, and this may break something. - // Anyway, reboot will still help in this case. - if action.should_schedule_rollback_run() { - let mut cmd = host.cmd("systemd-run").await?; - cmd.comparg("--on-active", "3min") - .comparg("--unit", "rollback-watchdog-run") - .arg("systemctl") - .arg("start") - .arg("rollback-watchdog.service"); - if let Err(e) = cmd.sudo().run().await { - error!("failed to schedule rollback run: {e}"); - failed = true; - } - } - } - if deploy_kind == DeployKind::NixosLustrate { - // Fleet could also create this file, but as this operation is potentially disruptive, - // make user do it themself. - if !host.file_exists("/etc/NIXOS_LUSTRATE").await? { - bail!("/etc/NIXOS_LUSTRATE should be created on remote host"); - } - // Wanted by NixOS to recognize the system as NixOS. - let mut cmd = host.cmd("touch").await?; - cmd.arg("/etc/NIXOS"); - cmd.sudo().run().await.context("creating /etc/NIXOS")?; - } - if deploy_kind == DeployKind::NixosInstall { - info!( - "running nixos-install to switch profile, install bootloader, and perform activation" - ); - let mut cmd = host.cmd("nixos-install").await?; - cmd.arg("--system").arg(&built).args([ - // Channels here aren't fleet host system channels, but channels embedded in installation cd, which might be old. - // It is possible to copy host channels, but I would prefer non-flake nix just to be unsupported. - "--no-channel-copy", - "--root", - "/mnt", - ]); - if let Err(e) = cmd.sudo().run().await { - error!("failed to execute nixos-install: {e}"); - failed = true; - } - } else { - if action.should_switch_profile() && !failed { - info!("switching system profile generation"); - - // To avoid even more problems, using nixos-install for now. - // // nix build is unable to work with --store argument for some reason, and nix until 2.26 didn't support copy with --profile argument, - // // falling back to using nix-env command - // // After stable NixOS starts using 2.26 - use `nix --store /mnt copy --from /mnt --profile ...` here, and instead of nix build below. - // let mut cmd = host.cmd("nix-env").await?; - // cmd.args([ - // "--store", - // "/mnt", - // "--profile", - // "/mnt/nix/var/nix/profiles/system", - // "--set", - // ]) - // .arg(&built); - // if let Err(e) = cmd.sudo().run_nix().await { - // error!("failed to switch system profile generation: {e}"); - // failed = true; - // } - // It would also be possible to update profile atomically during copy: - // https://github.com/NixOS/nix/pull/11657 - let mut cmd = host.nix_cmd().await?; - cmd.arg("build"); - cmd.comparg("--profile", "/nix/var/nix/profiles/system"); - cmd.arg(&built); - if let Err(e) = cmd.sudo().run_nix().await { - error!("failed to switch system profile generation: {e}"); - failed = true; - } - } - - // FIXME: Connection might be disconnected after activation run - - if action.should_activate() && !failed { - let _span = info_span!("activating").entered(); - info!("executing activation script"); - let specialised = if let Some(specialisation) = specialisation { - let mut specialised = built.join("specialisation"); - specialised.push(specialisation); - specialised - } else { - built.clone() - }; - let switch_script = specialised.join("bin/switch-to-configuration"); - let mut cmd = host.cmd(switch_script).in_current_span().await?; - if deploy_kind == DeployKind::NixosLustrate { - cmd.env("NIXOS_INSTALL_BOOTLOADER", "1"); - } - cmd.env("FLEET_ONLINE_ACTIVATION", "1") - .arg(action.name().expect("upload.should_activate == false")); - if let Err(e) = cmd.sudo().run().in_current_span().await { - error!("failed to activate: {e}"); - failed = true; - } - } - } - if action.should_create_rollback_marker() { - if !disable_rollback { - if failed { - if action.should_schedule_rollback_run() { - info!("executing rollback"); - if let Err(e) = host - .systemctl_start("rollback-watchdog.service") - .instrument(info_span!("rollback")) - .await - { - error!("failed to trigger rollback: {e}") - } - } - } else { - info!("trying to mark upgrade as successful"); - if let Err(e) = host - .rm_file("/etc/fleet_rollback_marker", true) - .in_current_span() - .await - { - error!("failed to remove rollback marker. This is bad, as the system will be rolled back by watchdog: {e}") - } - } - info!("disarming watchdog, just in case"); - if let Err(_e) = host.systemctl_stop("rollback-watchdog.timer").await { - // It is ok, if there was no reboot - then timer might not be running. - } - if action.should_schedule_rollback_run() { - if let Err(e) = host.systemctl_stop("rollback-watchdog-run.timer").await { - error!("failed to disarm rollback run: {e}"); - } - } - } else if let Err(_e) = host - .rm_file("/etc/fleet_rollback_marker", true) - .in_current_span() - .await - { - // Marker might not exist, yet better try to remove it. - } - } - Ok(()) -} - async fn build_task( config: Config, hostname: String, @@ -328,7 +44,8 @@ .get("out") .ok_or_else(|| anyhow!("system build should produce \"out\" output"))?; - { + // We already have system profiles for backups. + if !host.local { info!("adding gc root"); let mut cmd = config.local_host().cmd("nix").await?; cmd.arg("build") @@ -403,7 +120,6 @@ let config = config.clone(); let span = info_span!("deploy", host = field::display(&host.name)); let hostname = host.name.clone(); - let local_host = config.local_host(); let opts = opts.clone(); let batch = batch.clone(); if let Some(deploy_kind) = opts.action_attr::(&host, "deploy_kind").await? { @@ -437,51 +153,20 @@ disable_rollback = true; } - if !opts.is_local(&hostname) { - info!("uploading system closure"); + let remote_path = + match upload_task(&config, &host, GenerationStorage::Deployer, built).await { - // TODO: Move to remote_derivation method. - // Alternatively, nix store make-content-addressed can be used, - // at least for the first deployment, to provide trusted store key. - // - // It is much slower, yet doesn't require root on the deployer machine. - let Ok(mut sign) = local_host.cmd("nix").await else { - error!("failed to setup local"); + Ok(v) => v, + Err(e) => { + error!("upload failed: {e}"); return; - }; - // Private key for host machine is registered in nix-sign.nix - sign.arg("store") - .arg("sign") - .comparg("--key-file", "/etc/nix/private-key") - .arg("-r") - .arg(&built); - if let Err(e) = sign.sudo().run_nix().await { - warn!("failed to sign store paths: {e}"); - }; - } - let mut tries = 0; - loop { - match host.remote_derivation(&built).await { - Ok(remote) => { - assert!(remote == built, "CA derivations aren't implemented"); - break; - } - Err(e) if tries < 3 => { - tries += 1; - warn!("copy failure ({}/3): {}", tries, e); - sleep(Duration::from_millis(5000)).await; - } - Err(e) => { - error!("upload failed: {e}"); - return; - } } - } - } + }; + if let Err(e) = deploy_task( self.action, &host, - built, + remote_path, if let Ok(v) = opts.action_attr(&host, "specialisation").await { v } else { --- a/cmds/fleet/src/cmds/mod.rs +++ b/cmds/fleet/src/cmds/mod.rs @@ -3,3 +3,4 @@ pub mod info; pub mod secrets; pub mod tf; +pub mod rollback; \ No newline at end of file --- /dev/null +++ b/cmds/fleet/src/cmds/rollback.rs @@ -0,0 +1,127 @@ +use std::collections::HashSet; + +use anyhow::{bail, Result}; +use clap::Parser; +use fleet_base::{ + deploy::{deploy_task, upload_task, DeployAction}, + host::{Config, ConfigHost, Generation, GenerationStorage}, + opts::FleetOpts, +}; +use tabled::Table; +use tracing::{info, warn}; + +#[derive(Parser)] +pub struct RollbackSingle { + machine: String, + #[clap(subcommand)] + action: RollbackAction, +} + +#[derive(Parser, Clone)] +struct DeployOptions { + /// Rollback target to use + id: String, + /// Rollback to the current generation if rollback fails + // Automatic rollback seems to be unnecessary for manual rollback... + #[clap(long)] + enable_rollback: bool, + /// Specialization to use + #[clap(long)] + specialization: Option, +} + +#[derive(Parser, Clone)] +enum RollbackAction { + /// List available rollback targets + ListTargets, + /// Upload and execute the activation script, old version will be used after reboot. + Test(#[clap(flatten)] DeployOptions), + /// Upload, set current profile, and execute activation script. + Switch(#[clap(flatten)] DeployOptions), + /// Upload and set as current system profile, but do not execute activation script. + Boot(#[clap(flatten)] DeployOptions), +} + +pub async fn list_all_generations(host: &ConfigHost, config: &Config) -> Vec { + let stored_on_machine = host + .list_generations("system") + .await + .inspect_err(|e| { + warn!("failed to list generations available on the remote machine: {e}"); + }) + .unwrap_or_default(); + let on_machine_store_paths = stored_on_machine + .iter() + .map(|g| &g.store_path) + .collect::>(); + let mut stored_locally = config + .local_host() + .list_generations(&format!("{}-{}", config.data().gc_root_prefix, host.name)) + .await + .inspect_err(|e| { + warn!("failed to list generations available locally: {e}"); + }) + .unwrap_or_default(); + dbg!(&stored_locally); + stored_locally.retain(|g| !on_machine_store_paths.contains(&g.store_path)); + for ele in stored_locally.iter_mut() { + ele.current = false; + ele.location = GenerationStorage::Deployer; + } + stored_locally.extend(stored_on_machine); + stored_locally.sort_by_key(|v| v.datetime); + stored_locally +} + +impl RollbackSingle { + pub(crate) async fn run(&self, config: &Config, _opts: &FleetOpts) -> Result<()> { + let host = config.host(&self.machine).await?; + match &self.action { + RollbackAction::ListTargets => { + let generations = list_all_generations(&host, config).await; + if generations.is_empty() { + bail!("no available rollback targets found"); + } + info!("Generation list:\n{}", Table::new(&generations)); + Ok(()) + } + RollbackAction::Boot(o) | RollbackAction::Test(o) | RollbackAction::Switch(o) => { + let DeployOptions { + id, + enable_rollback, + specialization, + } = o; + let action: DeployAction = match self.action { + RollbackAction::Test { .. } => DeployAction::Test, + RollbackAction::Switch { .. } => DeployAction::Switch, + RollbackAction::Boot { .. } => DeployAction::Boot, + _ => unreachable!(), + }; + let generations = list_all_generations(&host, config).await; + let Some(generation) = generations.iter().find(|g| &g.rollback_id() == id) else { + bail!( + "generation by this name is not found, existing generations:\n{}", + Table::new(&generations) + ); + }; + let remote_path = upload_task( + config, + &host, + generation.location, + generation.store_path.clone(), + ) + .await?; + + deploy_task( + action, + &host, + remote_path, + specialization.clone(), + !*enable_rollback, + ) + .await?; + Ok(()) + } + } + } +} --- a/cmds/fleet/src/main.rs +++ b/cmds/fleet/src/main.rs @@ -10,6 +10,7 @@ use clap::{CommandFactory, Parser}; use cmds::{ build_systems::{BuildSystems, Deploy}, + rollback::RollbackSingle, complete::Complete, info::Info, secrets::Secret, @@ -70,6 +71,8 @@ BuildSystems(BuildSystems), /// Upload and switch system closures Deploy(Deploy), + /// Rollback remote machine by redeploying old generation as the new one + RollbackSingle(RollbackSingle), /// Secret management #[clap(subcommand)] Secret(Secret), @@ -97,6 +100,7 @@ match command { Opts::BuildSystems(c) => c.run(config, &opts).await?, Opts::Deploy(d) => d.run(config, &opts).await?, + Opts::RollbackSingle(r) => r.run(config, &opts).await?, Opts::Secret(s) => s.run(config, &opts).await?, Opts::Info(i) => i.run(config).await?, Opts::Prefetch(p) => p.run(config).await?, --- a/cmds/generator-helper/Cargo.toml +++ b/cmds/generator-helper/Cargo.toml @@ -11,7 +11,7 @@ fleet-shared.workspace = true base64 = "0.22.1" -ed25519-dalek = { version = "2.1", features = ["rand_core"] } +ed25519-dalek = { version = "2.1" } hex = "0.4.3" -rand = "0.8.5" -x25519-dalek = "2.0.1" +rand = "0.9.1" +x25519-dalek = { version = "2.0.1", features = ["getrandom"] } --- a/cmds/generator-helper/src/main.rs +++ b/cmds/generator-helper/src/main.rs @@ -11,10 +11,11 @@ }; use anyhow::{anyhow, bail, ensure, Context, Result}; use clap::{Parser, ValueEnum}; +use ed25519_dalek::SecretKey; use fleet_shared::SecretData; use rand::{ - distributions::{Alphanumeric, DistString, Distribution, Uniform}, - thread_rng, RngCore, + distr::{Alphanumeric, Distribution, SampleString, Uniform}, + rng, RngCore, }; fn write_output_file(out: &str) -> Result { @@ -224,7 +225,7 @@ fn main() -> Result<()> { let opts = Opts::parse(); // Assumed to be secure, seeded from secure OsRng+reseeded. - let mut rng = thread_rng(); + let mut rng = rng(); match opts { Opts::Public { output, encoding } => { @@ -245,7 +246,10 @@ use ed25519_dalek::SigningKey; let recipients = load_identities()?; - let key = SigningKey::generate(&mut rng).to_keypair_bytes(); + let mut secret = SecretKey::default(); + rng.fill_bytes(&mut secret); + // TODO: Use SigningKey::generate after https://github.com/dalek-cryptography/curve25519-dalek/pull/762 + let key = SigningKey::from_bytes(&secret).to_keypair_bytes(); write_public(&public, &key[32..], encoding)?; write_private( &recipients, @@ -268,7 +272,8 @@ use x25519_dalek::{PublicKey, StaticSecret}; let recipients = load_identities()?; - let key = StaticSecret::random_from_rng(rng); + // TODO: Use random_from_rng after https://github.com/dalek-cryptography/curve25519-dalek/pull/762 + let key = StaticSecret::random(); let public_key: PublicKey = (&key).into(); write_public(&public, public_key.as_bytes().as_slice(), encoding)?; write_private(&recipients, &private, key.as_bytes().as_slice(), encoding)?; @@ -289,7 +294,8 @@ } else { // Alphabet of Alphanumberic + symbols const GEN_ASCII_SYMBOLS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; - let uniform = Uniform::new(0, GEN_ASCII_SYMBOLS.len()); + let uniform = + Uniform::new(0, GEN_ASCII_SYMBOLS.len()).expect("range is valid"); (0..size) .map(|_| uniform.sample(&mut rng)) .map(|i| GEN_ASCII_SYMBOLS[i] as char) @@ -310,7 +316,9 @@ let recipients = load_identities()?; let mut bytes = vec![0u8; count]; if no_nuls { - let rand = Uniform::new_inclusive(0x1u8, 0xffu8).sample_iter(&mut rng); + let rand = Uniform::new_inclusive(0x1u8, 0xffu8) + .expect("range is valid") + .sample_iter(&mut rng); for (byte, rand) in bytes.iter_mut().zip(rand) { *byte = rand; } --- a/cmds/terraform-provider-fleet/Cargo.toml +++ b/cmds/terraform-provider-fleet/Cargo.toml @@ -9,5 +9,5 @@ serde = { workspace = true, features = ["derive"] } tokio.workspace = true -async-trait = "0.1.81" +async-trait = "0.1.88" tf-provider = "0.2.2" --- a/crates/better-command/Cargo.toml +++ b/crates/better-command/Cargo.toml @@ -5,7 +5,7 @@ rust-version.workspace = true [dependencies] -regex = "1.10" +regex = "1.11" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tracing = "0.1" --- a/crates/fleet-base/Cargo.toml +++ b/crates/fleet-base/Cargo.toml @@ -8,21 +8,23 @@ age.workspace = true anyhow.workspace = true better-command.workspace = true -chrono = "0.4.38" +chrono = "0.4.41" clap = { workspace = true, features = ["derive"] } fleet-shared.workspace = true -futures = "0.3.30" -hostname = "0.4.0" +futures = "0.3.31" +hostname = "0.4.1" indoc = "2.0.6" -itertools = "0.13.0" +itertools = "0.14.0" nix-eval.workspace = true nixlike.workspace = true -nom = "7.1.3" -openssh = "0.11.0" -rand = "0.8.5" +nom = "8.0.0" +openssh = "0.11.5" +rand = "0.9.1" serde.workspace = true -serde_json = "1.0.127" +serde_json = "1.0.140" +tabled = "0.20.0" tempfile.workspace = true +time = { version = "0.3.41", features = ["parsing"] } tokio.workspace = true -tokio-util = "0.7.11" +tokio-util = "0.7.15" tracing.workspace = true --- /dev/null +++ b/crates/fleet-base/src/deploy.rs @@ -0,0 +1,297 @@ +use std::{path::PathBuf, time::Duration}; + +use anyhow::{anyhow, bail, Context as _, Result}; +use clap::ValueEnum; +use itertools::Itertools; +use tokio::time::sleep; +use tracing::{error, info, info_span, warn, Instrument as _}; + +use crate::host::{Config, ConfigHost, DeployKind, Generation, GenerationStorage}; + +#[derive(ValueEnum, Clone, Copy)] +pub enum DeployAction { + /// Upload derivation, but do not execute the update. + Upload, + /// Upload and execute the activation script, old version will be used after reboot. + Test, + /// Upload and set as current system profile, but do not execute activation script. + Boot, + /// Upload, set current profile, and execute activation script. + Switch, +} + +impl DeployAction { + pub(crate) fn name(&self) -> Option<&'static str> { + match self { + Self::Upload => None, + Self::Test => Some("test"), + Self::Boot => Some("boot"), + Self::Switch => Some("switch"), + } + } + pub(crate) fn should_switch_profile(&self) -> bool { + matches!(self, Self::Switch | Self::Boot) + } + pub(crate) fn should_activate(&self) -> bool { + matches!(self, Self::Switch | Self::Test | Self::Boot) + } + pub(crate) fn should_create_rollback_marker(&self) -> bool { + // Upload does nothing on the target machine, other than uploading the closure. + // In boot case we want to have rollback marker prepared, so that the system may rollback itself on the next boot. + !matches!(self, Self::Upload) + } + pub(crate) fn should_schedule_rollback_run(&self) -> bool { + matches!(self, Self::Switch | Self::Test) + } +} + +async fn get_current_generation(host: &ConfigHost) -> Result { + let generations = host.list_generations("system").await?; + let current = generations + .into_iter() + .filter(|g| g.current) + .at_most_one() + .map_err(|_e| anyhow!("bad list-generations output"))? + .ok_or_else(|| anyhow!("failed to find generation"))?; + Ok(current) +} + +pub async fn deploy_task( + action: DeployAction, + host: &ConfigHost, + built: PathBuf, + specialisation: Option, + disable_rollback: bool, +) -> Result<()> { + let deploy_kind = host.deploy_kind().await?; + if (deploy_kind == DeployKind::NixosInstall || deploy_kind == DeployKind::NixosLustrate) + && !matches!(action, DeployAction::Boot | DeployAction::Upload) + { + bail!("{deploy_kind:?} deploy kind only supports boot and upload actions"); + } + + let mut failed = false; + + // TODO: Lockfile, to prevent concurrent system switch? + // TODO: If rollback target exists - bail, it should be removed. Lockfile will not work in case if rollback + // is scheduler on next boot (default behavior). On current boot - rollback activator will fail due to + // unit name conflict in systemd-run + // This code is tied to rollback.nix + if !disable_rollback && action.should_create_rollback_marker() { + let _span = info_span!("preparing").entered(); + info!("preparing for rollback"); + let generation = get_current_generation(host).await?; + info!( + "rollback target would be {} {}", + generation.id, generation.datetime + ); + { + let mut cmd = host.cmd("sh").await?; + cmd.arg("-c").arg(format!("mark=$(mktemp -p /etc -t fleet_rollback_marker.XXXXX) && echo -n {} > $mark && mv --no-clobber $mark /etc/fleet_rollback_marker", generation.id)); + if let Err(e) = cmd.sudo().run().await { + error!("failed to set rollback marker: {e}"); + failed = true; + } + } + // Activation script also starts rollback-watchdog.timer, however, it is possible that it won't be started. + // Kicking it on manually will work best. + // + // There wouldn't be conflict, because here we trigger start of the primary service, and systemd will + // only allow one instance of it. + + // TODO: We should also watch how this process is going. + // After running this command, we have less than 3 minutes to deploy everything, + // if we fail to perform generation switch in time, then we will still call the activation script, and this may break something. + // Anyway, reboot will still help in this case. + if action.should_schedule_rollback_run() { + let mut cmd = host.cmd("systemd-run").await?; + cmd.comparg("--on-active", "3min") + .comparg("--unit", "rollback-watchdog-run") + .arg("systemctl") + .arg("start") + .arg("rollback-watchdog.service"); + if let Err(e) = cmd.sudo().run().await { + error!("failed to schedule rollback run: {e}"); + failed = true; + } + } + } + if deploy_kind == DeployKind::NixosLustrate { + // Fleet could also create this file, but as this operation is potentially disruptive, + // make user do it themself. + if !host.file_exists("/etc/NIXOS_LUSTRATE").await? { + bail!("/etc/NIXOS_LUSTRATE should be created on remote host"); + } + // Wanted by NixOS to recognize the system as NixOS. + let mut cmd = host.cmd("touch").await?; + cmd.arg("/etc/NIXOS"); + cmd.sudo().run().await.context("creating /etc/NIXOS")?; + } + if deploy_kind == DeployKind::NixosInstall { + info!( + "running nixos-install to switch profile, install bootloader, and perform activation" + ); + let mut cmd = host.cmd("nixos-install").await?; + cmd.arg("--system").arg(&built).args([ + // Channels here aren't fleet host system channels, but channels embedded in installation cd, which might be old. + // It is possible to copy host channels, but I would prefer non-flake nix just to be unsupported. + "--no-channel-copy", + "--root", + "/mnt", + ]); + if let Err(e) = cmd.sudo().run().await { + error!("failed to execute nixos-install: {e}"); + failed = true; + } + } else { + if action.should_switch_profile() && !failed { + info!("switching system profile generation"); + + // To avoid even more problems, using nixos-install for now. + // // nix build is unable to work with --store argument for some reason, and nix until 2.26 didn't support copy with --profile argument, + // // falling back to using nix-env command + // // After stable NixOS starts using 2.26 - use `nix --store /mnt copy --from /mnt --profile ...` here, and instead of nix build below. + // let mut cmd = host.cmd("nix-env").await?; + // cmd.args([ + // "--store", + // "/mnt", + // "--profile", + // "/mnt/nix/var/nix/profiles/system", + // "--set", + // ]) + // .arg(&built); + // if let Err(e) = cmd.sudo().run_nix().await { + // error!("failed to switch system profile generation: {e}"); + // failed = true; + // } + // It would also be possible to update profile atomically during copy: + // https://github.com/NixOS/nix/pull/11657 + let mut cmd = host.nix_cmd().await?; + cmd.arg("build"); + cmd.comparg("--profile", "/nix/var/nix/profiles/system"); + cmd.arg(&built); + if let Err(e) = cmd.sudo().run_nix().await { + error!("failed to switch system profile generation: {e}"); + failed = true; + } + } + + // FIXME: Connection might be disconnected after activation run + + if action.should_activate() && !failed { + let _span = info_span!("activating").entered(); + info!("executing activation script"); + let specialised = if let Some(specialisation) = specialisation { + let mut specialised = built.join("specialisation"); + specialised.push(specialisation); + specialised + } else { + built.clone() + }; + let switch_script = specialised.join("bin/switch-to-configuration"); + let mut cmd = host.cmd(switch_script).in_current_span().await?; + if deploy_kind == DeployKind::NixosLustrate { + cmd.env("NIXOS_INSTALL_BOOTLOADER", "1"); + } + cmd.env("FLEET_ONLINE_ACTIVATION", "1") + .arg(action.name().expect("upload.should_activate == false")); + if let Err(e) = cmd.sudo().run().in_current_span().await { + error!("failed to activate: {e}"); + failed = true; + } + } + } + if action.should_create_rollback_marker() { + if !disable_rollback { + if failed { + if action.should_schedule_rollback_run() { + info!("executing rollback"); + if let Err(e) = host + .systemctl_start("rollback-watchdog.service") + .instrument(info_span!("rollback")) + .await + { + error!("failed to trigger rollback: {e}") + } + } + } else { + info!("trying to mark upgrade as successful"); + if let Err(e) = host + .rm_file("/etc/fleet_rollback_marker", true) + .in_current_span() + .await + { + error!("failed to remove rollback marker. This is bad, as the system will be rolled back by watchdog: {e}") + } + } + info!("disarming watchdog, just in case"); + if let Err(_e) = host.systemctl_stop("rollback-watchdog.timer").await { + // It is ok, if there was no reboot - then timer might not be running. + } + if action.should_schedule_rollback_run() { + if let Err(e) = host.systemctl_stop("rollback-watchdog-run.timer").await { + error!("failed to disarm rollback run: {e}"); + } + } + } else if let Err(_e) = host + .rm_file("/etc/fleet_rollback_marker", true) + .in_current_span() + .await + { + // Marker might not exist, yet better try to remove it. + } + } + Ok(()) +} + +pub async fn upload_task( + config: &Config, + host: &ConfigHost, + location: GenerationStorage, + generation: PathBuf, +) -> Result { + let local_host = config.local_host(); + if matches!(location, GenerationStorage::Pusher) { + bail!("pusher is not enabled in this version of fleet"); + } + if !host.local { + info!("uploading system closure"); + { + // TODO: Move to remote_derivation method. + // Alternatively, nix store make-content-addressed can be used, + // at least for the first deployment, to provide trusted store key. + // + // It is much slower, yet doesn't require root on the deployer machine. + let Ok(mut sign) = local_host.cmd("nix").await else { + bail!("failed to setup local"); + }; + // Private key for host machine is registered in nix-sign.nix + sign.arg("store") + .arg("sign") + .comparg("--key-file", "/etc/nix/private-key") + .arg("-r") + .arg(&generation); + if let Err(e) = sign.sudo().run_nix().await { + warn!("failed to sign store paths: {e}"); + }; + } + let mut tries = 0; + loop { + match host.remote_derivation(&generation).await { + Ok(remote) => { + assert!(remote == generation, "CA derivations aren't implemented"); + return Ok(remote); + } + Err(e) if tries < 3 => { + tries += 1; + warn!("copy failure ({}/3): {}", tries, e); + sleep(Duration::from_millis(5000)).await; + } + Err(e) => { + bail!("upload failed: {e}"); + } + } + } + } + Ok(generation) +} --- a/crates/fleet-base/src/fleetdata.rs +++ b/crates/fleet-base/src/fleetdata.rs @@ -7,8 +7,8 @@ use chrono::{DateTime, Utc}; use fleet_shared::SecretData; use rand::{ - distributions::{Alphanumeric, DistString}, - thread_rng, + distr::{Alphanumeric, SampleString as _}, + rng, }; use serde::{de::Error, Deserialize, Serialize}; use serde_json::Value; @@ -47,7 +47,7 @@ } fn generate_gc_prefix() -> String { - let id = Alphanumeric.sample_string(&mut thread_rng(), 8); + let id = Alphanumeric.sample_string(&mut rng(), 8); format!("fleet-gc-{id}") } --- a/crates/fleet-base/src/host.rs +++ b/crates/fleet-base/src/host.rs @@ -15,7 +15,10 @@ use nix_eval::{nix_go, nix_go_json, util::assert_warn, NixSession, Value}; use openssh::SessionBuilder; use serde::de::DeserializeOwned; +use tabled::Tabled; use tempfile::NamedTempFile; +use time::{format_description, UtcDateTime}; +use tracing::warn; use crate::{ command::MyCommand, @@ -104,8 +107,106 @@ pub local: bool, pub session: OnceLock>, } + +#[derive(Debug, Clone, Copy)] +pub enum GenerationStorage { + Deployer, + Machine, + Pusher, +} +impl GenerationStorage { + fn prefix(&self) -> &'static str { + match self { + GenerationStorage::Deployer => "deployer.", + GenerationStorage::Machine => "", + GenerationStorage::Pusher => "pusher.", + } + } +} + +#[derive(Tabled, Debug)] +pub struct Generation { + #[tabled(rename = "ID", format("{}", self.rollback_id()))] + pub id: u32, + #[tabled(rename = "Current")] + pub current: bool, + #[tabled(rename = "Created at")] + pub datetime: UtcDateTime, + #[tabled(format = "{:?}")] + pub store_path: PathBuf, + #[tabled(skip)] + pub location: GenerationStorage, +} +impl Generation { + pub fn rollback_id(&self) -> String { + format!("{}{}", self.location.prefix(), self.id) + } +} + +fn parse_generation_line(g: &str) -> Option { + let mut parts = g.split_whitespace(); + let id = parts.next()?; + let id: u32 = id.parse().ok()?; + let date = parts.next()?; + let time = parts.next()?; + let current = if let Some(current) = parts.next() { + if current == "(current)" { + Some(true) + } else { + None + } + } else { + Some(false) + }; + let current = current?; + if parts.next().is_some() { + warn!("unexpected text after generation: {g}"); + } + + let format = format_description::parse("[year]-[month]-[day] [hour]:[minute]:[second]") + .expect("valid format"); + let datetime = UtcDateTime::parse(&format!("{date} {time}"), &format).ok()?; + + Some(Generation { + id, + current, + datetime, + store_path: PathBuf::new(), + location: GenerationStorage::Machine, + }) +} // TODO: Move command helpers away with connectivity refactor impl ConfigHost { + pub async fn list_generations(&self, profile: &str) -> Result> { + let mut cmd = self.cmd("nix-env").await?; + cmd.comparg("--profile", format!("/nix/var/nix/profiles/{profile}")) + .arg("--list-generations") + .env("TZ", "UTC"); + // Sudo is required because --list-generations tries to acquire profile lock + let data = cmd.sudo().run_string().await?; + let mut generations = data + .split('\n') + .map(|e| e.trim()) + .filter(|&l| !l.is_empty()) + .filter_map(|g| { + let gen = parse_generation_line(g); + if gen.is_none() { + warn!("bad generation: {g}"); + }; + gen + }) + .collect::>(); + for ele in generations.iter_mut() { + let mut cmd = self.cmd("readlink").await?; + cmd.arg("--") + .arg(format!("/nix/var/nix/profiles/{profile}-{}-link", ele.id)); + let path = cmd.run_string().await?; + ele.store_path = PathBuf::from(path.trim_end_matches("\n")); + } + + Ok(generations) + } + pub fn set_deploy_kind(&self, kind: DeployKind) { self.deploy_kind .set(kind) --- a/crates/fleet-base/src/lib.rs +++ b/crates/fleet-base/src/lib.rs @@ -3,3 +3,4 @@ pub mod host; mod keys; pub mod opts; +pub mod deploy; \ No newline at end of file --- a/crates/fleet-base/src/opts.rs +++ b/crates/fleet-base/src/opts.rs @@ -7,7 +7,6 @@ }; use anyhow::{bail, Context, Result}; -use clap::Parser; use nix_eval::{nix_go, util::assert_warn, NixSessionPool, Value}; use nom::{ bytes::complete::take_while1, @@ -15,6 +14,7 @@ combinator::{map, opt}, multi::separated_list1, sequence::{preceded, separated_pair}, + Parser, }; use crate::{ @@ -38,11 +38,13 @@ err.to_string() } - let (input, is_tag) = map(opt(char('@')), |c| c.is_some())(input).map_err(err_to_string)?; + let (input, is_tag) = map(opt(char('@')), |c| c.is_some()) + .parse_complete(input) + .map_err(err_to_string)?; let (input, name) = map( take_while1(|v| v != ',' && v != '?' && v != '@'), str::to_owned, - )(input) + ).parse_complete(input) .map_err(err_to_string)?; let kw_item = separated_pair( @@ -55,7 +57,7 @@ }); let mut opt_kw = map(opt(preceded(char('?'), kw)), Option::unwrap_or_default); - let (input, attrs) = opt_kw(input).map_err(err_to_string)?; + let (input, attrs) = opt_kw.parse_complete(input).map_err(err_to_string)?; if !input.is_empty() { return Err(format!("unexpected trailing input: {input:?}")); @@ -68,7 +70,7 @@ } // TODO: Rename to HostSelector -#[derive(Parser, Clone)] +#[derive(clap::Parser, Clone)] pub struct FleetOpts { /// All hosts except those would be skipped #[clap(long, number_of_values = 1, value_parser = host_item_parser)] --- a/crates/fleet-shared/Cargo.toml +++ b/crates/fleet-shared/Cargo.toml @@ -6,6 +6,6 @@ [dependencies] base64 = "0.22.1" -serde = "1.0.202" +serde = "1.0.219" unicode_categories = "0.1.1" -z85 = "3.0.5" +z85 = "3.0.6" --- a/crates/nix-eval/Cargo.toml +++ b/crates/nix-eval/Cargo.toml @@ -16,11 +16,11 @@ tokio-util.workspace = true tracing.workspace = true -futures = "0.3.30" -itertools = "0.13.0" +futures = "0.3.31" +itertools = "0.14.0" r2d2 = "0.8.10" -regex = "1.10.6" -unindent = "0.2.3" +regex = "1.11.1" +unindent = "0.2.4" # [build-dependencies] # bindgen = "0.69.4" --- a/crates/nixlike/Cargo.toml +++ b/crates/nixlike/Cargo.toml @@ -9,8 +9,8 @@ alejandra = { git = "https://github.com/kamadorueda/alejandra" } linked-hash-map = "0.5.6" -peg = "0.8.2" -ron = "0.8.1" -serde = "1.0.196" +peg = "0.8.5" +ron = "0.10.1" +serde = "1.0.219" serde-transcode = "1.1.1" -serde_json = "1.0.113" +serde_json = "1.0.140" -- gitstuff