difftreelog
feat minimal rollback support
in: trunk
20 files changed
Cargo.lockdiffbeforeafterboth--- 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",
]
Cargo.tomldiffbeforeafterboth--- 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"] }
cmds/fleet/Cargo.tomldiffbeforeafterboth5authors = ["Yaroslav Bolyukin <iam@lach.pw>"]5authors = ["Yaroslav Bolyukin <iam@lach.pw>"]6edition.workspace = true6edition.workspace = true7rust-version.workspace = true7rust-version.workspace = true8default-run = "fleet"899[dependencies]10[dependencies]10age = { workspace = true, features = ["armor"] }11age = { workspace = true, features = ["armor"] }27async-trait = "0.1"28async-trait = "0.1"28base64 = "0.22.1"29base64 = "0.22.1"29chrono = { version = "0.4", features = ["serde"] }30chrono = { version = "0.4", features = ["serde"] }30crossterm = { version = "0.28.0", features = ["use-dev-tty"] }31crossterm = { version = "0.29.0", features = ["use-dev-tty"] }31futures = "0.3"32futures = "0.3"32hostname = "0.4.0"33hostname = "0.4.1"33itertools = "0.13"34itertools = "0.14"34openssh = "0.11"35openssh = "0.11"35owo-colors = { version = "4.0", features = ["supports-color", "supports-colors"] }36owo-colors = { version = "4.2", features = ["supports-color", "supports-colors"] }36peg = "0.8"37peg = "0.8"37regex = "1.10"38regex = "1.11"38shlex = "1.3"39shlex = "1.3"39tabled = { version = "0.16" }40tabled = { version = "0.20" }40time = { version = "0.3", features = ["serde"] }41time = { version = "0.3", features = ["serde"] }41tokio-util = { version = "0.7", features = ["codec"] }42tokio-util = { version = "0.7", features = ["codec"] }424343fleet-base = { version = "0.1.0", path = "../../crates/fleet-base" }44fleet-base = { version = "0.1.0", path = "../../crates/fleet-base" }44human-repr = { version = "1.1", optional = true }45human-repr = { version = "1.1", optional = true }45indicatif = { version = "0.17", optional = true }46indicatif = { version = "0.17", optional = true }46nom = "7.1.3"47nom = "8.0.0"47tracing-indicatif = { version = "0.3", optional = true }48tracing-indicatif = { version = "0.3", optional = true }484949[features]50[features]cmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth--- 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<Generation> {
- 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<Generation> {
- 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::<Vec<_>>();
- 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<String>,
- 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::<DeployKind>(&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 {
cmds/fleet/src/cmds/mod.rsdiffbeforeafterboth--- 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
cmds/fleet/src/cmds/rollback.rsdiffbeforeafterboth--- /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<String>,
+}
+
+#[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<Generation> {
+ 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::<HashSet<_>>();
+ 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(())
+ }
+ }
+ }
+}
cmds/fleet/src/main.rsdiffbeforeafterboth--- 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?,
cmds/generator-helper/Cargo.tomldiffbeforeafterboth--- 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"] }
cmds/generator-helper/src/main.rsdiffbeforeafterboth--- 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<File> {
@@ -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;
}
cmds/terraform-provider-fleet/Cargo.tomldiffbeforeafterboth--- 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"
crates/better-command/Cargo.tomldiffbeforeafterboth--- 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"
crates/fleet-base/Cargo.tomldiffbeforeafterboth--- 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
crates/fleet-base/src/deploy.rsdiffbeforeafterboth--- /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<Generation> {
+ 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<String>,
+ 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<PathBuf> {
+ 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)
+}
crates/fleet-base/src/fleetdata.rsdiffbeforeafterboth--- 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}")
}
crates/fleet-base/src/host.rsdiffbeforeafterboth--- 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<Arc<openssh::Session>>,
}
+
+#[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<Generation> {
+ 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<Vec<Generation>> {
+ 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::<Vec<_>>();
+ 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)
crates/fleet-base/src/lib.rsdiffbeforeafterboth--- 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
crates/fleet-base/src/opts.rsdiffbeforeafterboth--- 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)]
crates/fleet-shared/Cargo.tomldiffbeforeafterboth--- 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"
crates/nix-eval/Cargo.tomldiffbeforeafterboth--- 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"
crates/nixlike/Cargo.tomldiffbeforeafterboth--- 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"