--- a/Cargo.lock +++ b/Cargo.lock @@ -3,6 +3,15 @@ version = 3 [[package]] +name = "abort-on-drop" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd6d700ad9af641490c1f7d67980d2de4d1433016e5b12f819448d3c832142a" +dependencies = [ + "tokio", +] + +[[package]] name = "addr2line" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -141,10 +150,29 @@ ] [[package]] +name = "ansi-str" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cf4578926a981ab0ca955dc023541d19de37112bc24c1a197bd806d3d86ad1d" +dependencies = [ + "ansitok", +] + +[[package]] +name = "ansitok" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "220044e6a1bb31ddee4e3db724d29767f352de47445a6cd75e1a173142136c83" +dependencies = [ + "nom", + "vte 0.10.1", +] + +[[package]] name = "anstream" -version = "0.5.0" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" dependencies = [ "anstyle", "anstyle-parse", @@ -156,15 +184,15 @@ [[package]] name = "anstyle" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" dependencies = [ "utf8parse", ] @@ -180,9 +208,9 @@ [[package]] name = "anstyle-wincon" -version = "2.1.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" dependencies = [ "anstyle", "windows-sys 0.48.0", @@ -202,22 +230,39 @@ [[package]] name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "arrayvec" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "async-trait" -version = "0.1.73" +version = "0.1.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -246,9 +291,9 @@ [[package]] name = "base64" -version = "0.21.4" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "base64ct" @@ -281,9 +326,9 @@ [[package]] name = "bitflags" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" dependencies = [ "serde", ] @@ -323,6 +368,12 @@ checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] +name = "bytecount" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" + +[[package]] name = "byteorder" version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -419,9 +470,9 @@ [[package]] name = "clap" -version = "4.4.4" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d7b8d5ec32af0fadc644bf1fd509a688c2103b185644bb1e29d164e0703136" +checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b" dependencies = [ "clap_builder", "clap_derive", @@ -429,9 +480,9 @@ [[package]] name = "clap_builder" -version = "4.4.4" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5179bb514e4d7c2051749d8fcefa2ed6d06a9f4e6d69faf3805f5d80b8cf8d56" +checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663" dependencies = [ "anstream", "anstyle", @@ -444,21 +495,21 @@ [[package]] name = "clap_derive" -version = "4.4.2" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] name = "clap_lex" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "colorchoice" @@ -552,7 +603,7 @@ checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown 0.14.0", + "hashbrown 0.14.1", "lock_api", "once_cell", "parking_lot_core", @@ -570,10 +621,11 @@ [[package]] name = "deranged" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" dependencies = [ + "powerfmt", "serde", ] @@ -606,7 +658,7 @@ dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -642,30 +694,19 @@ [[package]] name = "errno" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" dependencies = [ - "errno-dragonfly", "libc", "windows-sys 0.48.0", ] [[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - -[[package]] name = "fastrand" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "find-crate" @@ -680,11 +721,12 @@ name = "fleet" version = "0.1.0" dependencies = [ + "abort-on-drop", "age", "age-core", "anyhow", "async-trait", - "base64 0.21.4", + "base64 0.21.5", "chrono", "clap", "futures", @@ -693,9 +735,13 @@ "itertools", "nixlike", "once_cell", + "owo-colors", "peg", + "r2d2", "serde", "serde_json", + "shlex", + "tabled", "tempfile", "time", "tokio", @@ -703,6 +749,7 @@ "tracing", "tracing-indicatif", "tracing-subscriber", + "unindent", "z85", ] @@ -767,10 +814,16 @@ ] [[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] name = "futures" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" dependencies = [ "futures-channel", "futures-core", @@ -783,9 +836,9 @@ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", "futures-sink", @@ -793,15 +846,15 @@ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" dependencies = [ "futures-core", "futures-task", @@ -810,38 +863,38 @@ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-channel", "futures-core", @@ -911,9 +964,9 @@ [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" [[package]] name = "heck" @@ -923,6 +976,15 @@ [[package]] name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" @@ -1014,7 +1076,7 @@ "proc-macro2", "quote", "strsim", - "syn 2.0.37", + "syn 2.0.38", "unic-langid", ] @@ -1028,7 +1090,7 @@ "i18n-config", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1056,12 +1118,12 @@ [[package]] name = "indexmap" -version = "2.0.0" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.14.1", ] [[package]] @@ -1128,12 +1190,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.3", "rustix", "windows-sys 0.48.0", ] [[package]] +name = "is_ci" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" + +[[package]] name = "itertools" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1168,9 +1236,9 @@ [[package]] name = "libc" -version = "0.2.148" +version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "libm" @@ -1196,9 +1264,9 @@ [[package]] name = "linux-raw-sys" -version = "0.4.7" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" [[package]] name = "lock_api" @@ -1233,24 +1301,15 @@ [[package]] name = "memchr" -version = "2.6.3" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memoffset" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] - -[[package]] -name = "memoffset" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" dependencies = [ "autocfg", ] @@ -1292,15 +1351,13 @@ [[package]] name = "nix" -version = "0.26.4" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.1", "cfg-if", "libc", - "memoffset 0.7.1", - "pin-utils", ] [[package]] @@ -1310,7 +1367,6 @@ "alejandra", "linked-hash-map", "peg", - "rnix", "ron", "serde", "serde-transcode", @@ -1378,9 +1434,9 @@ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", "libm", @@ -1392,7 +1448,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.3", "libc", ] @@ -1430,6 +1486,28 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +dependencies = [ + "supports-color", +] + +[[package]] +name = "papergrid" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ccbe15f2b6db62f9a9871642746427e297b0ceb85f9a7f1ee5ff47d184d0c8" +dependencies = [ + "ansi-str", + "ansitok", + "bytecount", + "fnv", + "unicode-width", +] + +[[package]] name = "parking_lot" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1447,7 +1525,7 @@ dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "smallvec", "windows-targets 0.48.5", ] @@ -1463,9 +1541,9 @@ [[package]] name = "peg" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a07f2cafdc3babeebc087e499118343442b742cc7c31b4d054682cc598508554" +checksum = "400bcab7d219c38abf8bd7cc2054eb9bbbd4312d66f6a5557d572a203f646f61" dependencies = [ "peg-macros", "peg-runtime", @@ -1473,9 +1551,9 @@ [[package]] name = "peg-macros" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a90084dc05cf0428428e3d12399f39faad19b0909f64fb9170c9fdd6d9cd49b" +checksum = "46e61cce859b76d19090f62da50a9fe92bab7c2a5f09e183763559a2ac392c90" dependencies = [ "peg-runtime", "proc-macro2", @@ -1484,9 +1562,9 @@ [[package]] name = "peg-runtime" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa00462b37ead6d11a82c9d568b26682d78e0477dc02d1966c013af80969739" +checksum = "36bae92c60fa2398ce4678b98b2c4b5a7c61099961ca1fa305aec04a9ad28922" [[package]] name = "pin-project" @@ -1505,7 +1583,7 @@ dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1572,6 +1650,12 @@ checksum = "31114a898e107c51bb1609ffaf55a0e011cf6a4d7f1170d0015a165082c0338b" [[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1603,9 +1687,9 @@ [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] @@ -1620,6 +1704,17 @@ ] [[package]] +name = "r2d2" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" +dependencies = [ + "log", + "parking_lot", + "scheduled-thread-pool", +] + +[[package]] name = "rand" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1700,6 +1795,15 @@ ] [[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] name = "regex" version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1760,8 +1864,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ - "base64 0.21.4", - "bitflags 2.4.0", + "base64 0.21.5", + "bitflags 2.4.1", "serde", "serde_derive", ] @@ -1774,7 +1878,7 @@ dependencies = [ "countme", "hashbrown 0.9.1", - "memoffset 0.6.5", + "memoffset", "rustc-hash", "text-size", ] @@ -1820,7 +1924,7 @@ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.37", + "syn 2.0.38", "walkdir", ] @@ -1848,11 +1952,11 @@ [[package]] name = "rustix" -version = "0.38.14" +version = "0.38.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f" +checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", @@ -1884,6 +1988,15 @@ ] [[package]] +name = "scheduled-thread-pool" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" +dependencies = [ + "parking_lot", +] + +[[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1918,9 +2031,9 @@ [[package]] name = "serde" -version = "1.0.188" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" dependencies = [ "serde_derive", ] @@ -1936,13 +2049,13 @@ [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1967,9 +2080,9 @@ [[package]] name = "sha2" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -1986,6 +2099,12 @@ ] [[package]] +name = "shlex" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" + +[[package]] name = "signal-hook-registry" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2067,20 +2186,31 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] +name = "supports-color" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ba6faf2ca7ee42fdd458f4347ae0a9bd6bcc445ad7cb57ad82b383f18870d6f" +dependencies = [ + "atty", + "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]] name = "syn" -version = "2.0.37" +version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ "proc-macro2", "quote", @@ -2088,14 +2218,40 @@ ] [[package]] +name = "tabled" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfe9c3632da101aba5131ed63f9eed38665f8b3c68703a6bb18124835c1a5d22" +dependencies = [ + "ansi-str", + "ansitok", + "papergrid", + "tabled_derive", + "unicode-width", +] + +[[package]] +name = "tabled_derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99f688a08b54f4f02f0a3c382aefdb7884d3d69609f785bd253dc033243e3fe4" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] name = "tempfile" -version = "3.8.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", + "redox_syscall 0.4.1", "rustix", "windows-sys 0.48.0", ] @@ -2127,22 +2283,22 @@ [[package]] name = "thiserror" -version = "1.0.48" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.48" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -2157,11 +2313,12 @@ [[package]] name = "time" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ "deranged", + "powerfmt", "serde", "time-core", "time-macros", @@ -2169,15 +2326,15 @@ [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] @@ -2193,9 +2350,9 @@ [[package]] name = "tokio" -version = "1.32.0" +version = "1.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" dependencies = [ "backtrace", "bytes", @@ -2218,14 +2375,14 @@ dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] name = "tokio-util" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", @@ -2298,7 +2455,7 @@ dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -2408,6 +2565,12 @@ checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] +name = "unindent" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" + +[[package]] name = "universal-hash" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2444,16 +2607,27 @@ "itoa", "log", "unicode-width", - "vte", + "vte 0.11.1", ] [[package]] name = "vte" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983" +dependencies = [ + "arrayvec 0.5.2", + "utf8parse", + "vte_generate_state_changes", +] + +[[package]] +name = "vte" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" dependencies = [ - "arrayvec", + "arrayvec 0.7.4", "utf8parse", "vte_generate_state_changes", ] @@ -2511,7 +2685,7 @@ "once_cell", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", "wasm-bindgen-shared", ] @@ -2533,7 +2707,7 @@ dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2759,5 +2933,5 @@ dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] --- a/README.adoc +++ b/README.adoc @@ -6,3 +6,4 @@ - Modules can configure multiple hosts at once (I.e for wireguard/kubernetes installation) - Secrets can be securely stored in Git (No one except target hosts can decrypt them), automatically regenerated, reencrypted, etc. +- Automatic rollback on deployment failure, which will work, as long as system is passing initrd stage (So still be carefull with root filesystem mount) --- a/cmds/fleet/Cargo.toml +++ b/cmds/fleet/Cargo.toml @@ -9,29 +9,35 @@ anyhow = "1.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -time = { version = "0.3.2", features = ["serde"] } -tempfile = "3.2" -once_cell = "1.5" +time = { version = "0.3.30", features = ["serde"] } +tempfile = "3.8" +once_cell = "1.18" hostname = "0.3.1" age-core = "0.9.0" -peg = "0.8.0" +peg = "0.8.2" nixlike = { path = "../../crates/nixlike" } -age = { version = "0.9.0", features = ["ssh", "armor"] } -base64 = "0.21.0" -chrono = { version = "0.4.19", features = ["serde"] } -z85 = "3.0.3" -clap = { version = "4.0.29", features = [ +age = { version = "0.9.2", features = ["ssh", "armor"] } +base64 = "0.21.5" +chrono = { version = "0.4.31", features = ["serde"] } +z85 = "3.0.5" +clap = { version = "4.4.7", features = [ "derive", "env", "wrap_help", "unicode", ] } -tokio = { version = "1.14.0", features = ["full"] } +tokio = { version = "1.33.0", features = ["full"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] } -tokio-util = { version = "0.7.0", features = ["codec"] } -async-trait = "0.1.52" -futures = "0.3.17" +tokio-util = { version = "0.7.10", features = ["codec"] } +async-trait = "0.1.74" +futures = "0.3.29" tracing-indicatif = "0.3.5" indicatif = "0.17.7" itertools = "0.11.0" +shlex = "1.2.0" +tabled = { version = "0.14.0", features = ["color"] } +owo-colors = { version = "3.5.0", features = ["supports-color", "supports-colors"] } +r2d2 = "0.8.10" +abort-on-drop = "0.2.2" +unindent = "0.2.3" --- a/cmds/fleet/src/cmds/build_systems.rs +++ b/cmds/fleet/src/cmds/build_systems.rs @@ -121,11 +121,11 @@ 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 = config.run_string_on(&host, cmd, true).await?; + let data = config.run_string_on(host, cmd, true).await?; let generations = data .split('\n') .map(|e| e.trim()) - .filter(|&l| l != "") + .filter(|&l| !l.is_empty()) .filter_map(|g| { let gen: Option = try { let mut parts = g.split_whitespace(); @@ -170,13 +170,13 @@ async fn systemctl_stop(config: &Config, host: &str, unit: &str) -> Result<()> { let mut cmd = MyCommand::new("systemctl"); cmd.arg("stop").arg(unit); - config.run_on(&host, cmd, true).await + config.run_on(host, cmd, true).await } async fn systemctl_start(config: &Config, host: &str, unit: &str) -> Result<()> { let mut cmd = MyCommand::new("systemctl"); cmd.arg("start").arg(unit); - config.run_on(&host, cmd, true).await + config.run_on(host, cmd, true).await } async fn execute_upload( @@ -195,7 +195,7 @@ if !build.disable_rollback { let _span = info_span!("preparing").entered(); info!("preparing for rollback"); - let generation = get_current_generation(&config, &host).await?; + let generation = get_current_generation(config, host).await?; info!( "rollback target would be {} {}", generation.id, generation.datetime @@ -203,7 +203,7 @@ { let mut cmd = MyCommand::new("sh"); 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) = config.run_on(&host, cmd, true).await { + if let Err(e) = config.run_on(host, cmd, true).await { error!("failed to set rollback marker: {e}"); failed = true; } @@ -225,7 +225,7 @@ .arg("systemctl") .arg("start") .arg("rollback-watchdog.service"); - if let Err(e) = config.run_on(&host, cmd, true).await { + if let Err(e) = config.run_on(host, cmd, true).await { error!("failed to schedule rollback run: {e}"); failed = true; } @@ -236,7 +236,7 @@ let mut cmd = MyCommand::new("nix-env"); cmd.comparg("--profile", "/nix/var/nix/profiles/system") .comparg("--set", &built); - if let Err(e) = config.run_on(&host, cmd, true).await { + if let Err(e) = config.run_on(host, cmd, true).await { error!("failed to switch generation: {e}"); failed = true; } @@ -249,7 +249,7 @@ switch_script.push("switch-to-configuration"); let mut cmd = MyCommand::new(switch_script); cmd.arg(action.name()); - if let Err(e) = config.run_on(&host, cmd, true).in_current_span().await { + if let Err(e) = config.run_on(host, cmd, true).in_current_span().await { error!("failed to activate: {e}"); failed = true; } @@ -257,7 +257,7 @@ if !build.disable_rollback { if failed { info!("executing rollback"); - if let Err(e) = systemctl_start(&config, &host, "rollback-watchdog.service") + if let Err(e) = systemctl_start(config, host, "rollback-watchdog.service") .instrument(info_span!("rollback")) .await { @@ -267,23 +267,23 @@ info!("trying to mark upgrade as successful"); let mut cmd = MyCommand::new("rm"); cmd.arg("-f").arg("/etc/fleet_rollback_marker"); - if let Err(e) = config.run_on(&host, cmd, true).in_current_span().await { + if let Err(e) = config.run_on(host, cmd, 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) = systemctl_stop(&config, &host, "rollback-watchdog.timer").await { + if let Err(_e) = systemctl_stop(config, host, "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) = systemctl_stop(&config, &host, "rollback-watchdog-run.timer").await { + if let Err(e) = systemctl_stop(config, host, "rollback-watchdog-run.timer").await { error!("failed to disarm rollback run: {e}"); } } } else { let mut cmd = MyCommand::new("rm"); cmd.arg("-f").arg("/etc/fleet_rollback_marker"); - if let Err(_e) = config.run_on(&host, cmd, true).in_current_span().await { + if let Err(_e) = config.run_on(host, cmd, true).in_current_span().await { // Marker might not exist, yet better try to remove it. } } @@ -341,7 +341,7 @@ sign.arg("nix") .arg("store") .arg("sign") - .comparg("-k", "/etc/nix/private-key") + .comparg("--key-file", "/etc/nix/private-key") .arg("-r") .arg(&built); if let Err(e) = sign.run_nix().await { @@ -353,7 +353,7 @@ let mut nix = MyCommand::new("nix"); nix.arg("copy") .arg("--substitute-on-destination") - .comparg("--to", format!("ssh-ng://root@{host}")) + .comparg("--to", format!("ssh-ng://{host}")) .arg(&built); match nix.run_nix().await { Ok(()) => break, @@ -423,17 +423,17 @@ let hosts = config.list_hosts().await?; let set = LocalSet::new(); let this = &self; - for host in hosts.iter() { - if config.should_skip(host) { + for host in hosts.into_iter() { + if config.should_skip(&host.name) { continue; } let config = config.clone(); - let host = host.clone(); let this = this.clone(); - let span = info_span!("deployment", host = field::display(&host)); + let span = info_span!("deployment", host = field::display(&host.name)); + let hostname = host.name; set.spawn_local( (async move { - match this.build_task(config, host).await { + match this.build_task(config, hostname).await { Ok(_) => {} Err(e) => { error!("failed to deploy host: {}", e) --- a/cmds/fleet/src/cmds/info.rs +++ b/cmds/fleet/src/cmds/info.rs @@ -36,14 +36,19 @@ InfoCmd::ListHosts { ref tagged } => { 'host: for host in config.list_hosts().await? { if !tagged.is_empty() { - let tags: Vec = config.config_attr(&host, "tags").await?; + let tags: Vec = config + .fleet_field + .get_field_deep(["configuredSystems", &host.name, "config", "tags"]) + .await? + .as_json() + .await?; for tag in tagged { if !tags.contains(tag) { continue 'host; } } } - data.push(host); + data.push(host.name); } } InfoCmd::HostIps { @@ -56,17 +61,20 @@ "at leas one of --external or --internal must be set" ); let mut out = >::new(); + let host = config.system_config(&host).await?; if external { out.extend( - config - .config_attr::>(&host, "network.externalIps") + host.get_field_deep(["network", "externalIps"]) + .await? + .as_json::>() .await?, ); } if internal { out.extend( - config - .config_attr::>(&host, "network.internalIps") + host.get_field_deep(["network", "internalIps"]) + .await? + .as_json::>() .await?, ); } --- a/cmds/fleet/src/cmds/secrets/mod.rs +++ b/cmds/fleet/src/cmds/secrets/mod.rs @@ -3,15 +3,18 @@ host::Config, }; use anyhow::{bail, ensure, Context, Result}; +use chrono::Utc; use clap::Parser; use futures::{StreamExt, TryStreamExt}; +use owo_colors::OwoColorize; use std::{ collections::HashSet, io::{self, Cursor, Read}, path::PathBuf, }; +use tabled::{Table, Tabled}; use tokio::fs::read_to_string; -use tracing::{error, info, warn}; +use tracing::{error, info, info_span, warn}; #[derive(Parser)] pub enum Secrets { @@ -73,6 +76,7 @@ #[clap(long)] prefer_identities: Vec, }, + List {}, } impl Secrets { @@ -80,10 +84,10 @@ match self { Secrets::ForceKeys => { for host in config.list_hosts().await? { - if config.should_skip(&host) { + if config.should_skip(&host.name) { continue; } - config.key(&host).await?; + config.key(&host.name).await?; } } Secrets::AddShared { @@ -128,7 +132,8 @@ FleetSharedSecret { owners: machines, secret: FleetSecret { - expire_at: None, + created_at: Utc::now(), + expires_at: None, secret, public: match (public, public_file) { (Some(v), None) => Some(v), @@ -175,7 +180,8 @@ &machine, name, FleetSecret { - expire_at: None, + created_at: Utc::now(), + expires_at: None, secret, public: match (public, public_file) { (Some(v), None) => Some(v), @@ -291,7 +297,7 @@ target_recipients.into_iter().collect::>>()?; let encrypted = config - .reencrypt_on_host(&identity_holder, secret.secret.secret, target_recipients) + .reencrypt_on_host(identity_holder, secret.secret.secret, target_recipients) .await?; secret.owners = target_machines; @@ -300,13 +306,14 @@ } Secrets::Regenerate { prefer_identities } => { { - let expected_shared_set = - config.shared_config_attr_names("sharedSecrets").await?; - let expected_shared_set = expected_shared_set.iter().collect::>(); - let shared_set = config.list_shared(); - let shared_set = shared_set.iter().collect::>(); + let expected_shared_set = config + .list_configured_shared() + .await? + .into_iter() + .collect::>(); + let shared_set = config.list_shared().into_iter().collect::>(); for removed in expected_shared_set.difference(&shared_set) { - warn!("secret needs to be generated: {removed}") + error!("secret needs to be generated: {removed}") } } let mut to_remove = Vec::new(); @@ -314,7 +321,8 @@ info!("updating secret: {name}"); let mut data = config.shared_secret(name)?; let expected_owners: Vec = config - .shared_config_attr(&format!("sharedSecrets.\"{name}\".expectedOwners")) + .config_field + .get_json_deep(["sharedSecrets", name, "expectedOwners"]) .await?; if expected_owners.is_empty() { warn!("secret was removed from fleet config: {name}, removing from data"); @@ -326,7 +334,8 @@ let should_remove = set.difference(&expected_set).next().is_some(); if set != expected_set { let owner_dependent: bool = config - .shared_config_attr(&format!("sharedSecrets.\"{name}\".ownerDependent")) + .config_field + .get_json_deep(["sharedSecrets", name, "ownerDependent"]) .await?; if !owner_dependent { warn!("reencrypting secret '{name}' for new owner set"); @@ -355,7 +364,7 @@ let encrypted = config .reencrypt_on_host( - &identity_holder, + identity_holder, data.secret.secret, target_recipients, ) @@ -364,13 +373,6 @@ data.secret.secret = encrypted; data.owners = expected_owners; config.replace_shared(name.to_owned(), data); - } else if let Some(generator) = config - .shared_config_attr::>(&format!( - "sharedSecrets.\"{name}\".generator" - )) - .await? - { - todo!("regenerate secret {name} with {generator}"); } else { error!("secret '{name}' should be regenerated manually"); } @@ -382,6 +384,39 @@ config.remove_shared(&k); } } + Secrets::List {} => { + let _span = info_span!("loading secrets").entered(); + let configured = config.list_configured_shared().await?; + #[derive(Tabled)] + struct SecretDisplay { + #[tabled(rename = "Name")] + name: String, + #[tabled(rename = "Owners")] + owners: String, + } + let mut table = vec![]; + for name in configured.iter().cloned() { + let config = config.clone(); + let expected_owners = config.shared_secret_expected_owners(&name).await?; + let data = config.shared_secret(&name)?; + let owners = data + .owners + .iter() + .map(|o| { + if expected_owners.contains(o) { + o.green().to_string() + } else { + o.red().to_string() + } + }) + .collect::>(); + table.push(SecretDisplay { + owners: owners.join(", "), + name, + }) + } + info!("loaded\n{}", Table::new(table).to_string()) + } } Ok(()) } --- a/cmds/fleet/src/command.rs +++ b/cmds/fleet/src/command.rs @@ -1,11 +1,14 @@ -use std::{collections::HashMap, ffi::OsStr, process::Stdio, task::Poll}; +use std::{ + collections::HashMap, + ffi::OsStr, + process::Stdio, + sync::{Arc, Mutex}, + task::Poll, +}; -use anyhow::{Context, Result}; +use anyhow::Result; use futures::StreamExt; -use serde::{ - de::{DeserializeOwned, Visitor}, - Deserialize, -}; +use serde::{de::Visitor, Deserialize}; use tokio::{io::AsyncRead, process::Command, select}; use tokio_util::codec::{BytesCodec, FramedRead, LinesCodec}; use tracing::{info, info_span, warn, Span}; @@ -49,12 +52,12 @@ if !self.env.is_empty() { out.push("env".to_owned()); for (k, v) in self.env { - assert!(!k.contains("=")); + assert!(!k.contains('=')); out.push(format!("{k}={v}")); } } out.push(self.command); - out.extend(self.args.into_iter()); + out.extend(self.args); out } fn into_string(self) -> String { @@ -63,7 +66,7 @@ out.push_str("env"); for (k, v) in self.env { out.push(' '); - assert!(!k.contains("=")); + assert!(!k.contains('=')); escape_bash(&k, &mut out); out.push('='); escape_bash(&v, &mut out); @@ -135,10 +138,6 @@ let cmd = self.into_command(); let v = run_nix_inner_stdout(str, cmd, &mut PlainHandler).await?; Ok(v) - } - pub async fn run_nix_json(self) -> Result { - let str = self.run_nix_string().await?; - serde_json::from_str(&str).with_context(|| format!("{:?}", str)) } pub async fn run_nix_string(self) -> Result { @@ -172,38 +171,55 @@ cmd: Command, handler: &mut dyn Handler, ) -> Result { - Ok(run_nix_inner_raw(str, cmd, true, handler) + Ok(run_nix_inner_raw(str, cmd, true, handler, None) .await? .expect("has out")) } async fn run_nix_inner(str: String, cmd: Command, handler: &mut dyn Handler) -> Result<()> { - let v = run_nix_inner_raw(str, cmd, false, handler).await?; + let v = run_nix_inner_raw(str, cmd, false, handler, None).await?; assert!(v.is_none()); Ok(()) } -trait Handler { - fn handle_err(&mut self, e: &str); - fn handle_info(&mut self, e: &str); +pub trait Handler: Send { + fn handle_line(&mut self, e: &str); +} + +pub struct ClonableHandler(Arc>); +impl Clone for ClonableHandler { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} +impl ClonableHandler { + pub fn new(inner: H) -> Self { + Self(Arc::new(Mutex::new(inner))) + } +} +impl Handler for ClonableHandler { + fn handle_line(&mut self, e: &str) { + self.0.lock().unwrap().handle_line(e) + } } struct PlainHandler; impl Handler for PlainHandler { - fn handle_err(&mut self, e: &str) { + fn handle_line(&mut self, e: &str) { info!(target: "log", "{e}"); } +} - fn handle_info(&mut self, e: &str) { - info!(target: "log", "{e}"); - } +pub struct NoopHandler; +impl Handler for NoopHandler { + fn handle_line(&mut self, _e: &str) {} } #[derive(Default)] -struct NixHandler { +pub struct NixHandler { spans: HashMap, } impl Handler for NixHandler { - fn handle_err(&mut self, e: &str) { + fn handle_line(&mut self, e: &str) { if let Some(e) = e.strip_prefix("@nix ") { let log: NixLog = match serde_json::from_str(e) { Ok(l) => l, @@ -214,6 +230,7 @@ }; match log { NixLog::Msg { msg, raw_msg, .. } => { + #[allow(clippy::nonminimal_bool)] if !(msg.starts_with("\u{1b}[35;1mwarning:\u{1b}[0m Git tree '") && msg.ends_with("' is dirty")) && !msg.starts_with("\u{1b}[35;1mwarning:\u{1b}[0m not writing modified lock file of flake") && msg != "\u{1b}[35;1mwarning:\u{1b}[0m \u{1b}[31;1merror:\u{1b}[0m SQLite database '\u{1b}[35;1m/nix/var/nix/db/db.sqlite\u{1b}[0m' is busy" { @@ -397,11 +414,12 @@ _ => warn!("unknown log: {:?}", log), }; } else { - warn!(target = "nix", "unknown: {}", e.trim()) + let e = e.trim(); + if e.starts_with("Failed tcsetattr(TCSADRAIN): ") { + return; + } + info!("{e}") } - } - fn handle_info(&mut self, o: &str) { - self.handle_err(o) } } @@ -409,9 +427,9 @@ str: String, mut cmd: Command, want_stdout: bool, - handler: &mut dyn Handler, + err_handler: &mut dyn Handler, + mut out_handler: Option<&mut dyn Handler>, ) -> Result> { - info!("running {str}"); cmd.stderr(Stdio::piped()); cmd.stdout(Stdio::piped()); let mut child = cmd.spawn()?; @@ -436,7 +454,7 @@ e = err.next() => { if let Some(e) = e { let e = e?; - handler.handle_err(&e); + err_handler.handle_line(&e); } }, o = ob.next() => { @@ -447,7 +465,12 @@ o = ol.next() => { if let Some(o) = o { let o = o?; - handler.handle_info(&o); + if let Some(out) = out_handler.as_mut() { + out.handle_line(&o) + } else { + err_handler.handle_line(&o) + } + // out_handler.handle_info(&o); } }, code = child.wait() => { @@ -463,6 +486,11 @@ Ok(out_buf.map(String::from_utf8).transpose()?) } +pub trait ErrorRecorder: Send { + /// Return true to discard message from logging + fn push_message(&mut self, msg: &str) -> bool; +} + #[derive(Debug)] enum LogField { String(String), --- a/cmds/fleet/src/extra_args.rs +++ b/cmds/fleet/src/extra_args.rs @@ -12,7 +12,7 @@ }) .collect()) } -pub fn parse(s: &str) -> Result> { - let osstr = OsString::try_from(s)?; - parse_os(&osstr) -} +// pub fn parse(s: &str) -> Result> { +// let osstr = OsString::try_from(s)?; +// parse_os(&osstr) +// } --- a/cmds/fleet/src/fleetdata.rs +++ b/cmds/fleet/src/fleetdata.rs @@ -44,9 +44,11 @@ #[serde(rename_all = "camelCase")] #[must_use] pub struct FleetSecret { + #[serde(default = "Utc::now")] + pub created_at: DateTime, #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] - pub expire_at: Option>, + #[serde(skip_serializing_if = "Option::is_none", alias = "expire_at")] + pub expires_at: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub public: Option, #[serde( --- a/cmds/fleet/src/host.rs +++ b/cmds/fleet/src/host.rs @@ -1,19 +1,18 @@ use std::{ - cell::{Ref, RefCell, RefMut}, env::current_dir, ffi::OsString, io::Write, ops::Deref, path::PathBuf, - sync::Arc, + sync::{Arc, Mutex, MutexGuard}, }; use anyhow::{bail, Context, Result}; use clap::{ArgGroup, Parser}; -use serde::de::DeserializeOwned; use tempfile::NamedTempFile; use crate::{ + better_nix_eval::{Field, NixSessionPool}, command::MyCommand, fleetdata::{FleetData, FleetSecret, FleetSharedSecret}, }; @@ -22,8 +21,12 @@ pub local_system: String, pub directory: PathBuf, pub opts: FleetOpts, - pub data: RefCell, + pub data: Mutex, pub nix_args: Vec, + // fleetConfigurations. + pub fleet_field: Field, + // fleet_config.configUnchecked + pub config_field: Field, } #[derive(Clone)] @@ -37,6 +40,10 @@ } } +pub struct ConfigHost { + pub name: String, +} + impl Config { pub fn should_skip(&self, host: &str) -> bool { if !self.opts.skip.is_empty() { @@ -60,7 +67,6 @@ } command.run().await } - #[must_use] pub async fn run_string_on( &self, host: &str, @@ -86,52 +92,39 @@ str } - pub async fn list_hosts(&self) -> Result> { - let mut cmd = MyCommand::new("nix"); - cmd.arg("eval") - .arg(self.configuration_attr_name("configuredHosts")) - .args(["--apply", "builtins.attrNames", "--json", "--show-trace"]) - .args(&self.nix_args); - cmd.run_nix_json().await - } - pub async fn shared_config_attr(&self, attr: &str) -> Result { - let mut cmd = MyCommand::new("nix"); - cmd.arg("eval") - .arg(self.configuration_attr_name(&format!("configUnchecked.{}", attr))) - .args(["--json", "--show-trace"]) - .args(&self.nix_args); - cmd.run_nix_json().await - } - pub async fn shared_config_attr_names(&self, attr: &str) -> Result> { - let mut cmd = MyCommand::new("nix"); - cmd.arg("eval") - .arg(self.configuration_attr_name(&format!("configUnchecked.{}", attr))) - .args(["--apply", "builtins.attrNames"]) - .args(["--json", "--show-trace"]) - .args(&self.nix_args); - cmd.run_nix_json().await + pub async fn list_hosts(&self) -> Result> { + let names = self.fleet_field + .get_field_deep(["configuredHosts"]) + .await? + .list_fields() + .await?; + let mut out = vec![]; + for name in names { + out.push(ConfigHost { + name, + }) + } + Ok(out) } - pub async fn config_attr(&self, host: &str, attr: &str) -> Result { - let mut cmd = MyCommand::new("nix"); - cmd.arg("eval") - .arg( - self.configuration_attr_name(&format!( - "configuredSystems.{}.config.{}", - host, attr - )), - ) - .args(["--json", "--show-trace"]) - .args(&self.nix_args); - cmd.run_nix_json().await + pub async fn system_config(&self, host: &str) -> Result { + self.fleet_field.get_field_deep(["configuredSystems", host, "config"]).await } - pub(super) fn data(&self) -> Ref { - self.data.borrow() + pub(super) fn data(&self) -> MutexGuard { + self.data.lock().unwrap() + } + pub(super) fn data_mut(&self) -> MutexGuard { + self.data.lock().unwrap() } - pub(super) fn data_mut(&self) -> RefMut { - self.data.borrow_mut() + /// Shared secrets configured in fleet.nix or in flake + pub async fn list_configured_shared(&self) -> Result> { + self.config_field + .get_field("sharedSecrets") + .await? + .list_fields() + .await } - + /// Shared secrets configured in fleet.nix pub fn list_shared(&self) -> Vec { let data = self.data(); data.shared_secrets.keys().cloned().collect() @@ -149,13 +142,6 @@ data.shared_secrets.remove(secret); } - pub fn list_secrets(&self, host: &str) -> Vec { - let data = self.data(); - let Some(host_secrets) = data.host_secrets.get(host) else { - return Vec::new(); - }; - host_secrets.keys().cloned().collect() - } pub fn has_secret(&self, host: &str, secret: &str) -> bool { let data = self.data(); let Some(host_secrets) = data.host_secrets.get(host) else { @@ -180,7 +166,7 @@ .context("failed to call remote host for decrypt")? .trim() .to_owned(); - Ok(z85::decode(encoded).context("bad encoded data? outdated host?")?) + z85::decode(encoded).context("bad encoded data? outdated host?") } pub async fn reencrypt_on_host( &self, @@ -201,10 +187,9 @@ .context("failed to call remote host for decrypt")? .trim() .to_owned(); - Ok(z85::decode(encoded).context("bad encoded data? outdated host?")?) + z85::decode(encoded).context("bad encoded data? outdated host?") } - #[must_use] pub fn host_secret(&self, host: &str, secret: &str) -> Result { let data = self.data(); let Some(host_secrets) = data.host_secrets.get(host) else { @@ -215,7 +200,6 @@ }; Ok(secret.clone()) } - #[must_use] pub fn shared_secret(&self, secret: &str) -> Result { let data = self.data(); let Some(secret) = data.shared_secrets.get(secret) else { @@ -223,13 +207,20 @@ }; Ok(secret.clone()) } + pub async fn shared_secret_expected_owners(&self, secret: &str) -> Result> { + self.config_field + .get_field_deep(["sharedSecrets", secret, "expectedOwners"]) + .await? + .as_json() + .await + } pub fn save(&self) -> Result<()> { let mut tempfile = NamedTempFile::new_in(self.directory.clone())?; let data = nixlike::serialize(&self.data() as &FleetData)?; tempfile.write_all( format!( - "# This file contains fleet state and shouldn't be edited by hand\n\n{}\n", + "# This file contains fleet state and shouldn't be edited by hand\n\n{}\n\n# vim: ts=2 et nowrap\n", data ) .as_bytes(), @@ -259,19 +250,35 @@ // TODO: unhardcode x86_64-linux /// Override detected system for host, to perform builds via /// binfmt-declared qemu instead of trying to crosscompile - #[clap(long, default_value = "x86_64-linux")] + #[clap(long, default_value = "detect")] pub local_system: String, } impl FleetOpts { pub async fn build(mut self, nix_args: Vec) -> Result { - let local_system = self.local_system.clone(); if self.localhost.is_none() { self.localhost .replace(hostname::get().unwrap().to_str().unwrap().to_owned()); } let directory = current_dir()?; + let pool = NixSessionPool::new(directory.as_os_str().to_owned(), nix_args.clone()).await?; + let root_field = pool.get().await?; + + if self.local_system == "detect" { + let builtins_field = Field::field(root_field.clone(), "builtins").await?; + let system = builtins_field.get_field("currentSystem").await?; + self.local_system = system.as_json().await?; + } + let local_system = self.local_system.clone(); + + let fleet_root = Field::field(root_field, "fleetConfigurations").await?; + + let fleet_field = fleet_root + .get_field_deep(["default", &local_system]) + .await?; + let config_field = fleet_field.get_field("configUnchecked").await?; + let mut fleet_data_path = directory.clone(); fleet_data_path.push("fleet.nix"); let bytes = std::fs::read_to_string(fleet_data_path)?; @@ -283,6 +290,8 @@ data, local_system, nix_args, + fleet_field, + config_field, }))) } } --- a/cmds/fleet/src/keys.rs +++ b/cmds/fleet/src/keys.rs @@ -3,6 +3,7 @@ use crate::command::MyCommand; use crate::host::Config; use anyhow::{anyhow, Result}; +use itertools::Itertools; use tracing::warn; impl Config { @@ -40,9 +41,15 @@ age::ssh::Recipient::from_str(&key).map_err(|e| anyhow!("parse recipient error: {:?}", e)) } + #[allow(dead_code)] pub async fn orphaned_data(&self) -> Result> { let mut out = Vec::new(); - let host_names = self.list_hosts().await?; + let host_names = self + .list_hosts() + .await? + .into_iter() + .map(|h| h.name) + .collect_vec(); for hostname in self .data() .hosts --- a/cmds/fleet/src/main.rs +++ b/cmds/fleet/src/main.rs @@ -1,9 +1,12 @@ #![feature(try_blocks)] -pub mod cmds; -pub mod command; -pub mod host; -pub mod keys; +pub(crate) mod cmds; +pub(crate) mod command; +pub(crate) mod host; +pub(crate) mod keys; + +pub(crate) mod extra_args; +pub(crate) mod better_nix_eval; mod fleetdata; @@ -14,13 +17,18 @@ use clap::Parser; use cmds::{build_systems::BuildSystems, info::Info, secrets::Secrets}; +use futures::future::LocalBoxFuture; +use futures::stream::FuturesUnordered; +use futures::TryStreamExt; use host::{Config, FleetOpts}; use indicatif::{ProgressState, ProgressStyle}; -use tokio::process::Command; use tracing::{info, metadata::LevelFilter}; +use tracing::{info_span, Instrument}; use tracing_indicatif::IndicatifLayer; use tracing_subscriber::{prelude::*, EnvFilter}; +use crate::command::MyCommand; + #[derive(Parser)] struct Prefetch {} impl Prefetch { @@ -31,20 +39,28 @@ info!("nothing to prefetch: no prefetch directory"); return Ok(()); } + let tasks = >>>::new(); for entry in std::fs::read_dir(&prefetch_dir)? { - let entry = entry?; - if !entry.metadata()?.is_file() { - bail!("only files should exist in prefetch directory"); - } - info!("prefetching {:?}", entry.file_name()); - let mut path = OsString::new(); - path.push("file://"); - path.push(entry.path()); - let status = Command::new("nix-prefetch-url").arg(path).status().await?; - if !status.success() { - bail!("failed with {status}"); - } + tasks.push(Box::pin(async { + let entry = entry?; + if !entry.metadata()?.is_file() { + bail!("only files should exist in prefetch directory"); + } + let span = info_span!( + "prefetching", + name = entry.file_name().to_string_lossy().as_ref() + ); + let mut path = OsString::new(); + path.push("file://"); + path.push(entry.path()); + + let mut status = MyCommand::new("nix"); + status.arg("store").arg("prefetch-file").arg(path); + status.run_nix_string().instrument(span).await?; + Ok(()) + })); } + tasks.try_collect::>().await?; Ok(()) } } @@ -81,8 +97,28 @@ Ok(()) } -#[tokio::main] -async fn main() -> Result<()> { +// fn main() -> Result<()> { +// let pool = r2d2::Builder::::new() +// .min_idle(Some(1)) +// .max_lifetime(Some(Duration::from_secs(10))) +// .build(NixSessionPool { +// flake: ".".to_owned(), +// nix_args: vec![], +// })?; +// let conn = pool.get()?; +// let field = Field::root(conn); +// // let builtins = field.get_field("builtins")?; +// let cur_sys: String = field.get_field("builtins")?.as_json()?; +// eprintln!("current system = {cur_sys}"); +// let v = field.get_field("fleetConfigurations")?; +// eprintln!("configs = {:?}", v.list_fields()?); +// let d = v.get_field("default")?; +// dbg!(d.list_fields()); +// Ok(()) +// } +// + +fn setup_logging() { let indicatif_layer = IndicatifLayer::new().with_progress_style( ProgressStyle::with_template( "{color_start}{span_child_prefix} {span_name}{{{span_fields}}}{color_end} {wide_msg} {color_start}{pos:>7}/{len:7}{elapsed}{color_end}", @@ -124,10 +160,19 @@ ) .with(indicatif_layer) .init(); - info!("Starting"); - let mut os_args = std::env::args_os(); - let opts = RootOpts::parse_from((&mut os_args).take_while(|v| v != "--")); - let config = opts.fleet_opts.build(os_args.collect()).await?; +} + +#[tokio::main] +async fn main() -> Result<()> { + setup_logging(); + let _ = better_nix_eval::TOKIO_RUNTIME.set(tokio::runtime::Handle::current()); + + let nix_args = std::env::var_os("NIX_ARGS") + .map(|a| extra_args::parse_os(&a)) + .transpose()? + .unwrap_or_default(); + let opts = RootOpts::parse(); + let config = opts.fleet_opts.build(nix_args).await?; match run_command(&config, opts.command).await { Ok(()) => { --- a/cmds/fleet/src/nix_eval.rs +++ /dev/null @@ -1,256 +0,0 @@ -//! Calling nix eval for everything is slow, it is not easy to link nix evaluator itself, -//! and tvix-nix doesn't have proper flake support. Fleets solution: automating nix repl calls. -//! -//! Api is synchronous, yet it is good enough with pooling, and in environment without IFDs for using -//! those blocking calls from async code. - -use std::borrow::Cow; -use std::sync::{Arc, Mutex}; -use std::time::Instant; - -use anyhow::{anyhow, bail, ensure, Context, Result}; -use itertools::Itertools; -use r2d2::PooledConnection; -use rexpect::session::{PtyReplSession, PtySession}; -use serde::de::DeserializeOwned; -use std::ffi::OsString; -use tracing::info_span; - -fn parse_error(res: &str) -> Option { - let res = if let Some(v) = res.strip_prefix("error: ") { - if let Some((first_line, next)) = v.split_once('\n') { - format!("{first_line}\n{}", unindent::unindent(next)) - } else { - v.trim_start().to_owned() - } - } else if let Some(v) = res.strip_prefix("error:\n") { - let mut v = v.to_owned(); - v.insert(0, '\n'); - unindent::unindent(&v).trim_start().to_owned() - } else { - return None; - }; - let res = res.trim_end(); - Some( - res.replace('Â', "") - .split('\n') - .map(|l| l.strip_prefix("â\u{80}¦ ").unwrap_or(l)) - .join("\n"), - ) -} -pub struct NixSessionPool { - pub flake: OsString, - pub nix_args: Vec, -} - -#[derive(Debug)] -pub struct NixPoolError(anyhow::Error); -impl From for NixPoolError { - fn from(value: anyhow::Error) -> Self { - Self(value) - } -} -impl std::error::Error for NixPoolError {} -impl std::fmt::Display for NixPoolError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) - } -} - -impl r2d2::ManageConnection for NixSessionPool { - type Connection = NixSession; - type Error = NixPoolError; - - fn connect(&self) -> std::result::Result { - Ok(NixSession::new(&self.flake, &self.nix_args, None)?) - } - - fn is_valid(&self, conn: &mut Self::Connection) -> std::result::Result<(), Self::Error> { - let res = conn.expression_result("2 + 2")?; - if res != "4" { - return Err(anyhow!("basic expression failed").into()); - } - Ok(()) - } - - fn has_broken(&self, conn: &mut Self::Connection) -> bool { - conn.finished - } -} - -pub struct NixSession { - session: PtyReplSession, - next_id: u32, - free_list: Vec, - finished: bool, -} -impl NixSession { - fn new(flake: &OsString, args: &[OsString], timeout: Option) -> Result { - let mut cmd = std::process::Command::new("nix"); - cmd.arg("repl"); - cmd.arg(flake); - for arg in args { - cmd.arg(arg); - } - cmd.env("TERM", "dumb"); - cmd.env("NO_COLOR", "1"); - let pty_session = rexpect::session::spawn_command(cmd, timeout)?; - let mut repl = PtyReplSession { - prompt: "nix-repl> ".to_string(), - pty_session, - quit_command: Some(":q".to_string()), - echo_on: true, - }; - repl.wait_for_prompt()?; - Ok(Self { - session: repl, - next_id: 0, - free_list: vec![], - finished: false, - }) - } - fn expression_result(&mut self, cmd: &str) -> Result { - dbg!(cmd); - self.session.send_line(cmd)?; - dbg!("waiting"); - let result = self.session.wait_for_prompt()?; - let result = strip_ansi_escapes::strip_str(&result); - let result = result.trim(); - dbg!(result); - Ok(result.to_owned()) - } - fn json_result(&mut self, cmd: &str) -> Result { - let v = match self.expression_result(&format!("builtins.toJSON ({cmd})")) { - Ok(v) => { - if let Some(e) = parse_error(&v) { - bail!("{e}") - } - v - } - Err(e) => { - self.finished = true; - bail!("{e}") - } - }; - // Remove outer quoting - let v: String = serde_json::from_str(&v)?; - Ok(serde_json::from_str(&v)?) - } - /// Id should be immediately used - fn allocate_id(&mut self) -> u32 { - if let Some(free) = self.free_list.pop() { - free - } else { - let v = self.next_id; - self.next_id += 1; - v - } - } - fn allocate_result(&mut self, cmd: &str) -> Result { - let id = self.allocate_id(); - match self.expression_result(&format!("sess_field_{id} = ({cmd})")) { - Ok(v) => { - if let Some(e) = parse_error(&v) { - self.free_list.push(id); - bail!("{e}") - } - } - Err(e) => { - self.finished = true; - } - } - - Ok(id) - } - /// Nix has no way to deallocate variable, yet GC will correct everything not reachable. - fn free_id(&mut self, id: u32) { - if let Err(e) = self.expression_result(&format!("sess_field_{id} = null")) { - self.finished = true; - } else { - self.free_list.push(id) - } - } -} - -#[derive(Clone, Debug)] -enum Index { - String(String), - Idx(u32), -} - -pub struct Field { - full_path: Vec, - session: Arc>>, - value: Option, -} -impl Field { - pub fn root(conn: PooledConnection) -> Self { - Self { - full_path: vec![], - session: Arc::new(Mutex::new(conn)), - value: None, - } - } - pub fn get_field_deep<'a>(&self, name: impl IntoIterator) -> Result { - let mut iter = name.into_iter(); - - let mut full_path = self.full_path.clone(); - let mut query = if let Some(id) = self.value { - format!("sess_field_{id}") - } else { - let first = iter.next().expect("name not empty"); - ensure!( - !(first.contains('.') | first.contains(' ')), - "bad name for root query: {first}" - ); - full_path.push(Index::String(first.to_string())); - first.to_string() - }; - for v in iter { - full_path.push(Index::String(v.to_string())); - // Escape - let escaped = nixlike::serialize(v)?; - let escaped = escaped.trim(); - query.push('.'); - query.push_str(escaped); - } - - let vid = self - .session - .lock() - .unwrap() - .allocate_result(&query) - .with_context(|| format!("full path: {:?}", full_path))?; - Ok(Self { - full_path, - session: self.session.clone(), - value: Some(vid), - }) - } - pub fn get_field<'a>(&self, name: &str) -> Result { - self.get_field_deep([name]) - } - pub fn as_json(&self) -> Result { - let id = self.value.expect("can't serialize root field"); - self.session - .lock() - .unwrap() - .json_result(&format!("sess_field_{id}")) - .with_context(|| format!("full path: {:?}", self.full_path)) - } - pub fn list_fields(&self) -> Result> { - let id = self.value.expect("can't list root fields"); - self.session - .lock() - .unwrap() - .json_result(&format!("builtins.attrNames sess_field_{id}")) - .with_context(|| format!("full path: {:?}", self.full_path)) - } -} -impl Drop for Field { - fn drop(&mut self) { - if let Some(id) = self.value { - self.session.lock().unwrap().free_id(id) - } - } -} --- a/cmds/install-secrets/Cargo.toml +++ b/cmds/install-secrets/Cargo.toml @@ -4,18 +4,18 @@ edition = "2021" [dependencies] -age = { version = "0.9.0", features = ["ssh"] } -anyhow = "1.0.44" +age = { version = "0.9.2", features = ["ssh"] } +anyhow = "1.0.75" env_logger = "0.10.0" -log = "0.4.14" -nix = "0.26.1" -serde = { version = "1.0.130", features = ["derive"] } -serde_json = "1.0.89" -clap = { version = "4.0.29", features = [ +log = "0.4.20" +nix = {version = "0.27.1", features = ["user", "fs"]} +serde = { version = "1.0.190", features = ["derive"] } +serde_json = "1.0.107" +clap = { version = "4.4.7", features = [ "derive", "env", "wrap_help", "unicode", ] } -tempfile = "3.2.0" -z85 = "3.0.3" +tempfile = "3.8.1" +z85 = "3.0.5" --- a/cmds/install-secrets/src/main.rs +++ b/cmds/install-secrets/src/main.rs @@ -4,7 +4,7 @@ use clap::Parser; use log::{error, info, warn}; use nix::sys::stat::Mode; -use nix::unistd::{chown, Group, User}; +use nix::unistd::{User, Group, chown}; use serde::{Deserialize, Deserializer}; use std::fmt::{self, Display}; use std::fs::{self, File}; @@ -161,7 +161,7 @@ let mut hashed = File::create(&value.secret_path)?; // File is owned by root, and only root can modify it - let decrypted = decrypt(&secret, identity)?; + let decrypted = decrypt(secret, identity)?; if decrypted.is_empty() { warn!("secret is decoded as empty, something is broken?"); } --- a/crates/nixlike/Cargo.toml +++ b/crates/nixlike/Cargo.toml @@ -5,11 +5,10 @@ [dependencies] alejandra = {git = "https://github.com/kamadorueda/alejandra"} -rnix = "0.10.2" -linked-hash-map = "0.5.4" -peg = "0.8.0" -serde = "1.0.130" -thiserror = "1.0.29" -serde_json = "1.0.91" -ron = "0.8.0" +linked-hash-map = "0.5.6" +peg = "0.8.2" +serde = "1.0.190" +thiserror = "1.0.50" +serde_json = "1.0.107" +ron = "0.8.1" serde-transcode = "1.1.1" --- a/crates/nixlike/src/lib.rs +++ b/crates/nixlike/src/lib.rs @@ -119,6 +119,12 @@ Ok(serialize_value_pretty(value)) } +pub fn format_identifier(i: &str) -> String { + let mut out = String::new(); + to_string::write_identifier(i, &mut out); + out +} + #[test] fn test() { assert_eq!(serialize("Hello\nworld").unwrap(), "\"Hello\\nworld\"\n"); --- a/crates/nixlike/src/se_impl.rs +++ b/crates/nixlike/src/se_impl.rs @@ -212,7 +212,7 @@ } fn serialize_i64(self, v: i64) -> Result { - Ok(Value::Number(v as i64)) + Ok(Value::Number(v)) } fn serialize_u8(self, v: u8) -> Result { --- a/crates/nixlike/src/to_string.rs +++ b/crates/nixlike/src/to_string.rs @@ -1,24 +1,26 @@ use crate::Value; -fn write_nix_obj_key_buf(k: &str, v: &Value, out: &mut String) { - if k.contains('.') { - out.push_str("\""); - out.push_str(k); - out.push_str("\""); +pub fn write_identifier(k: &str, out: &mut String) { + if k.contains(['.', '\'', '\"', '\\', '\n', '\t', '\r', '$']) { + write_nix_str(k, out); } else { out.push_str(k); } +} + +fn write_nix_obj_key_buf(k: &str, v: &Value, out: &mut String) { + write_identifier(k, out); match v { Value::Object(o) if o.len() == 1 => { let (k, v) = o.iter().next().unwrap(); - out.push_str("."); + out.push('.'); write_nix_obj_key_buf(k, v, out); } v => { out.push_str(" = "); write_nix_buf(v, out); - out.push_str(";"); + out.push(';'); } } } --- a/flake.lock +++ b/flake.lock @@ -38,11 +38,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1696884899, - "narHash": "sha256-SZILkoh8KZxjvFHO3yzOUw7n1Mf9WqMdUqoxf8eKPM4=", + "lastModified": 1698350982, + "narHash": "sha256-zoEV8Ad3bOAejp0ys/mOpaHSWrzK+GupZwGGYfuWuEY=", "owner": "nixos", "repo": "nixpkgs", - "rev": "ba10489eae3b2b2f665947b516e7043594a235c8", + "rev": "dd83f9de26ff7c0326468b659ea4729fa5cf6262", "type": "github" }, "original": { @@ -67,11 +67,11 @@ ] }, "locked": { - "lastModified": 1696817516, - "narHash": "sha256-Xt9OY4Wnk9/vuUfA0OHFtmSlaen5GyiS9msgwOz3okI=", + "lastModified": 1698199907, + "narHash": "sha256-n8RtHBIb0rLuYs4RDehW6mj6r6Yam/ODY1af/VCcurw=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "c0df7f2a856b5ff27a3ce314f6d7aacf5fda546f", + "rev": "22b8d29fd22cfaa2c311e0d6fd8a0ed9c2a1152b", "type": "github" }, "original": { --- a/flake.nix +++ b/flake.nix @@ -15,7 +15,7 @@ inherit system; overlays = [ (import rust-overlay) ]; }; llvmPkgs = pkgs.buildPackages.llvmPackages_11; - rust = (pkgs.rustChannelOf { date = "2023-10-05"; channel = "nightly"; }).default.override { extensions = [ "rust-src" "rust-analyzer" ]; }; + rust = (pkgs.rustChannelOf { date = "2023-10-20"; channel = "nightly"; }).default.override { extensions = [ "rust-src" "rust-analyzer" ]; }; rustPlatform = pkgs.makeRustPlatform { cargo = rust; rustc = rust; }; in { @@ -29,6 +29,7 @@ pkg-config openssl + bacon ]; }; }); --- a/modules/fleet/secrets.nix +++ b/modules/fleet/secrets.nix @@ -55,6 +55,14 @@ description = "Time in hours, in which this secret should be regenerated"; default = null; }; + createdAt = mkOption { + type = nullOr str; + default = null; + }; + expiresAt = mkOption { + type = nullOr str; + default = null; + }; owners = mkOption { type = listOf str; @@ -82,23 +90,24 @@ }; hostSecret = with types; { options = { - generator = mkOption { - type = package; - description = "Derivation to execute for secret generation"; + createdAt = mkOption { + type = nullOr str; + default = null; }; - expireIn = mkOption { - type = nullOr int; - description = "Time in hours, in which this secret should be regenerated"; + expiresAt = mkOption { + type = nullOr str; default = null; }; public = mkOption { type = nullOr str; - description = "Secret public data"; + description = "Secret public data. Imported from fleet.nix"; default = null; }; secret = mkOption { - type = str; - description = "Encrypted secret data"; + type = nullOr str; + description = "Encrypted secret data. Imported from fleet.nix"; + default = null; + internal = true; }; }; }; @@ -113,7 +122,8 @@ hostSecrets = mkOption { type = attrsOf (attrsOf (submodule hostSecret)); default = { }; - description = "Host secrets"; + description = "Host secrets. Imported from fleet.nix"; + internal = true; }; }; config = {