git.delta.rocks / jrsonnet / refs/commits / 989a90dd7b97

difftreelog

feat move secret generation helpers to core

Yaroslav Bolyukin2024-03-02parent: #a31940c.patch.diff
in: trunk

13 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -38,9 +38,9 @@
 
 [[package]]
 name = "aes"
-version = "0.8.3"
+version = "0.8.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2"
+checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
 dependencies = [
  "cfg-if",
  "cipher",
@@ -63,14 +63,14 @@
 
 [[package]]
 name = "age"
-version = "0.9.2"
+version = "0.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d55a4d912c80a92762ffd1c884065f3f9646467d22c95390e824a0ff7def472"
+checksum = "edeef7d7b199195a2d7d7a8155d2d04aee736e60c5c7bdd7097d115369a8817d"
 dependencies = [
  "aes",
  "aes-gcm",
  "age-core",
- "base64 0.13.1",
+ "base64",
  "bcrypt-pbkdf",
  "bech32",
  "cbc",
@@ -79,7 +79,6 @@
  "cookie-factory",
  "ctr",
  "curve25519-dalek",
- "hkdf",
  "hmac",
  "i18n-embed",
  "i18n-embed-fl",
@@ -87,8 +86,7 @@
  "nom",
  "num-traits",
  "pin-project",
- "rand 0.7.3",
- "rand 0.8.5",
+ "rand",
  "rsa",
  "rust-embed",
  "scrypt",
@@ -100,17 +98,17 @@
 
 [[package]]
 name = "age-core"
-version = "0.9.0"
+version = "0.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3d2e815ac879dc23c1139e720d21c6cd4d1276345c772587285d965a69b8f32"
+checksum = "a5f11899bc2bbddd135edbc30c36b1924fa59d0746bb45beb5933fafe3fe509b"
 dependencies = [
- "base64 0.13.1",
+ "base64",
  "chacha20poly1305",
  "cookie-factory",
  "hkdf",
  "io_tee",
  "nom",
- "rand 0.8.5",
+ "rand",
  "secrecy",
  "sha2",
 ]
@@ -150,29 +148,10 @@
 ]
 
 [[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.6.5"
+version = "0.6.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6"
+checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5"
 dependencies = [
  "anstyle",
  "anstyle-parse",
@@ -184,9 +163,9 @@
 
 [[package]]
 name = "anstyle"
-version = "1.0.4"
+version = "1.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
+checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
 
 [[package]]
 name = "anstyle-parse"
@@ -218,9 +197,9 @@
 
 [[package]]
 name = "anyhow"
-version = "1.0.77"
+version = "1.0.79"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9d19de80eff169429ac1e9f48fffb163916b448a44e8e046186232046d9e1f9"
+checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
 
 [[package]]
 name = "arc-swap"
@@ -230,12 +209,6 @@
 
 [[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"
@@ -248,18 +221,7 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.48",
-]
-
-[[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",
+ "syn 2.0.49",
 ]
 
 [[package]]
@@ -285,15 +247,9 @@
 
 [[package]]
 name = "base64"
-version = "0.13.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
-
-[[package]]
-name = "base64"
-version = "0.21.5"
+version = "0.21.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
 
 [[package]]
 name = "base64ct"
@@ -303,9 +259,9 @@
 
 [[package]]
 name = "bcrypt-pbkdf"
-version = "0.9.0"
+version = "0.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3806a8db60cf56efee531616a34a6aaa9a114d6da2add861b0fa4a188881b2c7"
+checksum = "6aeac2e1fe888769f34f05ac343bbef98b14d1ffb292ab69d4608b3abc86f2a2"
 dependencies = [
  "blowfish",
  "pbkdf2",
@@ -338,9 +294,9 @@
 
 [[package]]
 name = "bitflags"
-version = "2.4.1"
+version = "2.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
 dependencies = [
  "serde",
 ]
@@ -375,9 +331,9 @@
 
 [[package]]
 name = "bumpalo"
-version = "3.14.0"
+version = "3.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
+checksum = "d32a994c2b3ca201d9b263612a374263f05e7adde37c4707f693dcd375076d1f"
 
 [[package]]
 name = "bytecount"
@@ -456,9 +412,9 @@
 
 [[package]]
 name = "chrono"
-version = "0.4.31"
+version = "0.4.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
+checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b"
 dependencies = [
  "android-tzdata",
  "iana-time-zone",
@@ -466,7 +422,7 @@
  "num-traits",
  "serde",
  "wasm-bindgen",
- "windows-targets 0.48.5",
+ "windows-targets 0.52.0",
 ]
 
 [[package]]
@@ -482,9 +438,9 @@
 
 [[package]]
 name = "clap"
-version = "4.4.12"
+version = "4.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d"
+checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da"
 dependencies = [
  "clap_builder",
  "clap_derive",
@@ -492,14 +448,14 @@
 
 [[package]]
 name = "clap_builder"
-version = "4.4.12"
+version = "4.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9"
+checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb"
 dependencies = [
  "anstream",
  "anstyle",
  "clap_lex",
- "strsim",
+ "strsim 0.11.0",
  "terminal_size",
  "unicase",
  "unicode-width",
@@ -507,21 +463,21 @@
 
 [[package]]
 name = "clap_derive"
-version = "4.4.7"
+version = "4.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
+checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47"
 dependencies = [
  "heck",
  "proc-macro2",
  "quote",
- "syn 2.0.48",
+ "syn 2.0.49",
 ]
 
 [[package]]
 name = "clap_lex"
-version = "0.6.0"
+version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
+checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
 
 [[package]]
 name = "colorchoice"
@@ -531,15 +487,15 @@
 
 [[package]]
 name = "console"
-version = "0.15.7"
+version = "0.15.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8"
+checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
 dependencies = [
  "encode_unicode",
  "lazy_static",
  "libc",
  "unicode-width",
- "windows-sys 0.45.0",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
@@ -568,9 +524,9 @@
 
 [[package]]
 name = "cpufeatures"
-version = "0.2.11"
+version = "0.2.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
+checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
 dependencies = [
  "libc",
 ]
@@ -582,7 +538,7 @@
 checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
 dependencies = [
  "generic-array",
- "rand_core 0.6.4",
+ "rand_core",
  "typenum",
 ]
 
@@ -597,18 +553,32 @@
 
 [[package]]
 name = "curve25519-dalek"
-version = "3.2.0"
+version = "4.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
+checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348"
 dependencies = [
- "byteorder",
- "digest 0.9.0",
- "rand_core 0.5.1",
+ "cfg-if",
+ "cpufeatures",
+ "curve25519-dalek-derive",
+ "fiat-crypto",
+ "platforms",
+ "rustc_version",
  "subtle",
  "zeroize",
 ]
 
 [[package]]
+name = "curve25519-dalek-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.49",
+]
+
+[[package]]
 name = "dashmap"
 version = "5.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -623,9 +593,9 @@
 
 [[package]]
 name = "der"
-version = "0.6.1"
+version = "0.7.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de"
+checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c"
 dependencies = [
  "const-oid",
  "zeroize",
@@ -639,15 +609,6 @@
 dependencies = [
  "powerfmt",
  "serde",
-]
-
-[[package]]
-name = "digest"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
-dependencies = [
- "generic-array",
 ]
 
 [[package]]
@@ -660,27 +621,6 @@
  "const-oid",
  "crypto-common",
  "subtle",
-]
-
-[[package]]
-name = "dirs"
-version = "5.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
-dependencies = [
- "dirs-sys",
-]
-
-[[package]]
-name = "dirs-sys"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
-dependencies = [
- "libc",
- "option-ext",
- "redox_users",
- "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -691,14 +631,14 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.48",
+ "syn 2.0.49",
 ]
 
 [[package]]
 name = "either"
-version = "1.9.0"
+version = "1.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
+checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
 
 [[package]]
 name = "encode_unicode"
@@ -707,19 +647,6 @@
 checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
 
 [[package]]
-name = "env_logger"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece"
-dependencies = [
- "humantime",
- "is-terminal",
- "log",
- "regex",
- "termcolor",
-]
-
-[[package]]
 name = "equivalent"
 version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -742,6 +669,12 @@
 checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
 
 [[package]]
+name = "fiat-crypto"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382"
+
+[[package]]
 name = "find-crate"
 version = "0.6.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -759,7 +692,7 @@
  "age-core",
  "anyhow",
  "async-trait",
- "base64 0.21.5",
+ "base64",
  "better-command",
  "chrono",
  "clap",
@@ -797,12 +730,12 @@
  "age",
  "anyhow",
  "clap",
- "env_logger",
- "log",
  "nix",
  "serde",
  "serde_json",
  "tempfile",
+ "tracing",
+ "tracing-subscriber",
  "z85",
 ]
 
@@ -912,7 +845,7 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.48",
+ "syn 2.0.49",
 ]
 
 [[package]]
@@ -957,24 +890,13 @@
 
 [[package]]
 name = "getrandom"
-version = "0.1.16"
+version = "0.2.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
+checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
 dependencies = [
  "cfg-if",
  "libc",
- "wasi 0.9.0+wasi-snapshot-preview1",
-]
-
-[[package]]
-name = "getrandom"
-version = "0.2.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
-dependencies = [
- "cfg-if",
- "libc",
- "wasi 0.11.0+wasi-snapshot-preview1",
+ "wasi",
 ]
 
 [[package]]
@@ -1013,20 +935,11 @@
 
 [[package]]
 name = "hermit-abi"
-version = "0.1.19"
+version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
-dependencies = [
- "libc",
-]
+checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd"
 
 [[package]]
-name = "hermit-abi"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
-
-[[package]]
 name = "hkdf"
 version = "0.12.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1041,7 +954,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
 dependencies = [
- "digest 0.10.7",
+ "digest",
 ]
 
 [[package]]
@@ -1062,12 +975,6 @@
 checksum = "f58b778a5761513caf593693f8951c97a5b610841e754788400f32102eefdff1"
 
 [[package]]
-name = "humantime"
-version = "2.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
-
-[[package]]
 name = "i18n-config"
 version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1077,15 +984,15 @@
  "serde",
  "serde_derive",
  "thiserror",
- "toml 0.8.8",
+ "toml 0.8.10",
  "unic-langid",
 ]
 
 [[package]]
 name = "i18n-embed"
-version = "0.13.9"
+version = "0.14.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92a86226a7a16632de6723449ee5fe70bac5af718bc642ee9ca2f0f6e14fa1fa"
+checksum = "94205d95764f5bb9db9ea98fa77f89653365ca748e27161f5bbea2ffd50e459c"
 dependencies = [
  "arc-swap",
  "fluent",
@@ -1104,9 +1011,9 @@
 
 [[package]]
 name = "i18n-embed-fl"
-version = "0.6.7"
+version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d26a3d3569737dfaac7fc1c4078e6af07471c3060b8e570bcd83cdd5f4685395"
+checksum = "9fc1f8715195dffc4caddcf1cf3128da15fe5d8a137606ea8856c9300047d5a2"
 dependencies = [
  "dashmap",
  "find-crate",
@@ -1118,8 +1025,8 @@
  "proc-macro-error",
  "proc-macro2",
  "quote",
- "strsim",
- "syn 2.0.48",
+ "strsim 0.10.0",
+ "syn 2.0.49",
  "unic-langid",
 ]
 
@@ -1133,14 +1040,14 @@
  "i18n-config",
  "proc-macro2",
  "quote",
- "syn 2.0.48",
+ "syn 2.0.49",
 ]
 
 [[package]]
 name = "iana-time-zone"
-version = "0.1.59"
+version = "0.1.60"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539"
+checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
 dependencies = [
  "android_system_properties",
  "core-foundation-sys",
@@ -1161,9 +1068,9 @@
 
 [[package]]
 name = "indexmap"
-version = "2.1.0"
+version = "2.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
+checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177"
 dependencies = [
  "equivalent",
  "hashbrown 0.14.3",
@@ -1171,9 +1078,9 @@
 
 [[package]]
 name = "indicatif"
-version = "0.17.7"
+version = "0.17.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25"
+checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3"
 dependencies = [
  "console",
  "instant",
@@ -1229,26 +1136,26 @@
 
 [[package]]
 name = "is-terminal"
-version = "0.4.10"
+version = "0.4.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
+checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b"
 dependencies = [
- "hermit-abi 0.3.3",
- "rustix",
+ "hermit-abi",
+ "libc",
  "windows-sys 0.52.0",
 ]
 
 [[package]]
 name = "is_ci"
-version = "1.1.1"
+version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb"
+checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
 
 [[package]]
 name = "itertools"
-version = "0.11.0"
+version = "0.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
 dependencies = [
  "either",
 ]
@@ -1261,9 +1168,9 @@
 
 [[package]]
 name = "js-sys"
-version = "0.3.66"
+version = "0.3.68"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca"
+checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -1279,9 +1186,9 @@
 
 [[package]]
 name = "libc"
-version = "0.2.151"
+version = "0.2.153"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
+checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
 
 [[package]]
 name = "libm"
@@ -1300,17 +1207,6 @@
 ]
 
 [[package]]
-name = "libredox"
-version = "0.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
-dependencies = [
- "bitflags 2.4.1",
- "libc",
- "redox_syscall",
-]
-
-[[package]]
 name = "linked-hash-map"
 version = "0.5.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1318,9 +1214,9 @@
 
 [[package]]
 name = "linux-raw-sys"
-version = "0.4.12"
+version = "0.4.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
+checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
 
 [[package]]
 name = "lock_api"
@@ -1385,9 +1281,9 @@
 
 [[package]]
 name = "miniz_oxide"
-version = "0.7.1"
+version = "0.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
 dependencies = [
  "adler",
 ]
@@ -1399,7 +1295,7 @@
 checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
 dependencies = [
  "libc",
- "wasi 0.11.0+wasi-snapshot-preview1",
+ "wasi",
  "windows-sys 0.48.0",
 ]
 
@@ -1409,7 +1305,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
 dependencies = [
- "bitflags 2.4.1",
+ "bitflags 2.4.2",
  "cfg-if",
  "libc",
 ]
@@ -1460,26 +1356,31 @@
  "num-integer",
  "num-iter",
  "num-traits",
- "rand 0.8.5",
+ "rand",
  "smallvec",
  "zeroize",
 ]
 
 [[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
+[[package]]
 name = "num-integer"
-version = "0.1.45"
+version = "0.1.46"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
 dependencies = [
- "autocfg",
  "num-traits",
 ]
 
 [[package]]
 name = "num-iter"
-version = "0.1.43"
+version = "0.1.44"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
+checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9"
 dependencies = [
  "autocfg",
  "num-integer",
@@ -1488,9 +1389,9 @@
 
 [[package]]
 name = "num-traits"
-version = "0.2.17"
+version = "0.2.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
+checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
 dependencies = [
  "autocfg",
  "libm",
@@ -1502,7 +1403,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
 dependencies = [
- "hermit-abi 0.3.3",
+ "hermit-abi",
  "libc",
 ]
 
@@ -1535,11 +1436,10 @@
 
 [[package]]
 name = "openssh"
-version = "0.10.2"
+version = "0.10.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8274f2bf1fc3785406a3ff07c92c15590c00e84efb883da77b671562ca9a6115"
+checksum = "cab71dc3fc68747816c7eecdffcede064d6bac9621fd658bf1ab5414e91558a3"
 dependencies = [
- "dirs",
  "libc",
  "once_cell",
  "shell-escape",
@@ -1548,12 +1448,6 @@
  "tokio",
  "tokio-pipe",
 ]
-
-[[package]]
-name = "option-ext"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
 
 [[package]]
 name = "overload"
@@ -1563,21 +1457,19 @@
 
 [[package]]
 name = "owo-colors"
-version = "3.5.0"
+version = "4.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
+checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f"
 dependencies = [
  "supports-color",
 ]
 
 [[package]]
 name = "papergrid"
-version = "0.10.0"
+version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2ccbe15f2b6db62f9a9871642746427e297b0ceb85f9a7f1ee5ff47d184d0c8"
+checksum = "9ad43c07024ef767f9160710b3a6773976194758c7919b17e63b863db0bdf7fb"
 dependencies = [
- "ansi-str",
- "ansitok",
  "bytecount",
  "fnv",
  "unicode-width",
@@ -1608,11 +1500,12 @@
 
 [[package]]
 name = "pbkdf2"
-version = "0.11.0"
+version = "0.12.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
+checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
 dependencies = [
- "digest 0.10.7",
+ "digest",
+ "hmac",
 ]
 
 [[package]]
@@ -1644,22 +1537,22 @@
 
 [[package]]
 name = "pin-project"
-version = "1.1.3"
+version = "1.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
+checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0"
 dependencies = [
  "pin-project-internal",
 ]
 
 [[package]]
 name = "pin-project-internal"
-version = "1.1.3"
+version = "1.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
+checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.48",
+ "syn 2.0.49",
 ]
 
 [[package]]
@@ -1676,27 +1569,32 @@
 
 [[package]]
 name = "pkcs1"
-version = "0.4.1"
+version = "0.7.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eff33bdbdfc54cc98a2eca766ebdec3e1b8fb7387523d5c9c9a2891da856f719"
+checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
 dependencies = [
  "der",
  "pkcs8",
  "spki",
- "zeroize",
 ]
 
 [[package]]
 name = "pkcs8"
-version = "0.9.0"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba"
+checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
 dependencies = [
  "der",
  "spki",
 ]
 
 [[package]]
+name = "platforms"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c"
+
+[[package]]
 name = "poly1305"
 version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1763,9 +1661,9 @@
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.75"
+version = "1.0.78"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708"
+checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
 dependencies = [
  "unicode-ident",
 ]
@@ -1788,19 +1686,6 @@
  "log",
  "parking_lot",
  "scheduled-thread-pool",
-]
-
-[[package]]
-name = "rand"
-version = "0.7.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
-dependencies = [
- "getrandom 0.1.16",
- "libc",
- "rand_chacha 0.2.2",
- "rand_core 0.5.1",
- "rand_hc",
 ]
 
 [[package]]
@@ -1810,18 +1695,8 @@
 checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
 dependencies = [
  "libc",
- "rand_chacha 0.3.1",
- "rand_core 0.6.4",
-]
-
-[[package]]
-name = "rand_chacha"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
-dependencies = [
- "ppv-lite86",
- "rand_core 0.5.1",
+ "rand_chacha",
+ "rand_core",
 ]
 
 [[package]]
@@ -1831,16 +1706,7 @@
 checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
 dependencies = [
  "ppv-lite86",
- "rand_core 0.6.4",
-]
-
-[[package]]
-name = "rand_core"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
-dependencies = [
- "getrandom 0.1.16",
+ "rand_core",
 ]
 
 [[package]]
@@ -1848,17 +1714,8 @@
 version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
-dependencies = [
- "getrandom 0.2.11",
-]
-
-[[package]]
-name = "rand_hc"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
 dependencies = [
- "rand_core 0.5.1",
+ "getrandom",
 ]
 
 [[package]]
@@ -1868,28 +1725,17 @@
 checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
 dependencies = [
  "bitflags 1.3.2",
-]
-
-[[package]]
-name = "redox_users"
-version = "0.4.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
-dependencies = [
- "getrandom 0.2.11",
- "libredox",
- "thiserror",
 ]
 
 [[package]]
 name = "regex"
-version = "1.10.2"
+version = "1.10.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
+checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
 dependencies = [
  "aho-corasick",
  "memchr",
- "regex-automata 0.4.3",
+ "regex-automata 0.4.5",
  "regex-syntax 0.8.2",
 ]
 
@@ -1904,9 +1750,9 @@
 
 [[package]]
 name = "regex-automata"
-version = "0.4.3"
+version = "0.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
+checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -1950,8 +1796,8 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94"
 dependencies = [
- "base64 0.21.5",
- "bitflags 2.4.1",
+ "base64",
+ "bitflags 2.4.2",
  "serde",
  "serde_derive",
 ]
@@ -1971,30 +1817,29 @@
 
 [[package]]
 name = "rsa"
-version = "0.7.2"
+version = "0.9.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "094052d5470cbcef561cb848a7209968c9f12dfa6d668f4bca048ac5de51099c"
+checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc"
 dependencies = [
- "byteorder",
- "digest 0.10.7",
+ "const-oid",
+ "digest",
  "num-bigint-dig",
  "num-integer",
- "num-iter",
  "num-traits",
  "pkcs1",
  "pkcs8",
- "rand_core 0.6.4",
+ "rand_core",
  "signature",
- "smallvec",
+ "spki",
  "subtle",
  "zeroize",
 ]
 
 [[package]]
 name = "rust-embed"
-version = "6.8.1"
+version = "8.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a36224c3276f8c4ebc8c20f158eca7ca4359c8db89991c4925132aaaf6702661"
+checksum = "a82c0bbc10308ed323529fd3c1dce8badda635aa319a5ff0e6466f33b8101e3f"
 dependencies = [
  "rust-embed-impl",
  "rust-embed-utils",
@@ -2003,22 +1848,22 @@
 
 [[package]]
 name = "rust-embed-impl"
-version = "6.8.1"
+version = "8.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49b94b81e5b2c284684141a2fb9e2a31be90638caf040bf9afbc5a0416afe1ac"
+checksum = "6227c01b1783cdfee1bcf844eb44594cd16ec71c35305bf1c9fb5aade2735e16"
 dependencies = [
  "proc-macro2",
  "quote",
  "rust-embed-utils",
- "syn 2.0.48",
+ "syn 2.0.49",
  "walkdir",
 ]
 
 [[package]]
 name = "rust-embed-utils"
-version = "7.8.1"
+version = "8.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d38ff6bf570dc3bb7100fce9f7b60c33fa71d80e88da3f2580df4ff2bdded74"
+checksum = "8cb0a25bfbb2d4b4402179c2cf030387d9990857ce08a32592c6238db9fa8665"
 dependencies = [
  "sha2",
  "walkdir",
@@ -2037,12 +1882,21 @@
 checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
 
 [[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
+[[package]]
 name = "rustix"
-version = "0.38.28"
+version = "0.38.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
+checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
 dependencies = [
- "bitflags 2.4.1",
+ "bitflags 2.4.2",
  "errno",
  "libc",
  "linux-raw-sys",
@@ -2090,11 +1944,10 @@
 
 [[package]]
 name = "scrypt"
-version = "0.10.0"
+version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d"
+checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f"
 dependencies = [
- "hmac",
  "pbkdf2",
  "salsa20",
  "sha2",
@@ -2125,10 +1978,16 @@
 checksum = "58bf37232d3bb9a2c4e641ca2a11d83b5062066f88df7fed36c28772046d65ba"
 
 [[package]]
+name = "semver"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
+
+[[package]]
 name = "serde"
-version = "1.0.193"
+version = "1.0.196"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
+checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
 dependencies = [
  "serde_derive",
 ]
@@ -2144,20 +2003,20 @@
 
 [[package]]
 name = "serde_derive"
-version = "1.0.193"
+version = "1.0.196"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
+checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.48",
+ "syn 2.0.49",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.108"
+version = "1.0.113"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
+checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
 dependencies = [
  "itoa",
  "ryu",
@@ -2181,7 +2040,7 @@
 dependencies = [
  "cfg-if",
  "cpufeatures",
- "digest 0.10.7",
+ "digest",
 ]
 
 [[package]]
@@ -2201,9 +2060,9 @@
 
 [[package]]
 name = "shlex"
-version = "1.2.0"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
 
 [[package]]
 name = "signal-hook-registry"
@@ -2216,12 +2075,12 @@
 
 [[package]]
 name = "signature"
-version = "1.6.4"
+version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c"
+checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
 dependencies = [
- "digest 0.10.7",
- "rand_core 0.6.4",
+ "digest",
+ "rand_core",
 ]
 
 [[package]]
@@ -2235,9 +2094,9 @@
 
 [[package]]
 name = "smallvec"
-version = "1.11.2"
+version = "1.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
+checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
 
 [[package]]
 name = "smol_str"
@@ -2266,9 +2125,9 @@
 
 [[package]]
 name = "spki"
-version = "0.6.0"
+version = "0.7.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b"
+checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
 dependencies = [
  "base64ct",
  "der",
@@ -2281,6 +2140,12 @@
 checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
 
 [[package]]
+name = "strsim"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
+
+[[package]]
 name = "subtle"
 version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2288,11 +2153,11 @@
 
 [[package]]
 name = "supports-color"
-version = "1.3.1"
+version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ba6faf2ca7ee42fdd458f4347ae0a9bd6bcc445ad7cb57ad82b383f18870d6f"
+checksum = "d6398cde53adc3c4557306a96ce67b302968513830a77a95b2b17305d9719a89"
 dependencies = [
- "atty",
+ "is-terminal",
  "is_ci",
 ]
 
@@ -2309,9 +2174,9 @@
 
 [[package]]
 name = "syn"
-version = "2.0.48"
+version = "2.0.49"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
+checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2320,12 +2185,10 @@
 
 [[package]]
 name = "tabled"
-version = "0.14.0"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dfe9c3632da101aba5131ed63f9eed38665f8b3c68703a6bb18124835c1a5d22"
+checksum = "4c998b0c8b921495196a48aabaf1901ff28be0760136e31604f7967b0792050e"
 dependencies = [
- "ansi-str",
- "ansitok",
  "papergrid",
  "tabled_derive",
  "unicode-width",
@@ -2333,9 +2196,9 @@
 
 [[package]]
 name = "tabled_derive"
-version = "0.6.0"
+version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99f688a08b54f4f02f0a3c382aefdb7884d3d69609f785bd253dc033243e3fe4"
+checksum = "4c138f99377e5d653a371cdad263615634cfc8467685dfe8e73e2b8e98f44b17"
 dependencies = [
  "heck",
  "proc-macro-error",
@@ -2346,27 +2209,17 @@
 
 [[package]]
 name = "tempfile"
-version = "3.9.0"
+version = "3.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa"
+checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67"
 dependencies = [
  "cfg-if",
  "fastrand",
- "redox_syscall",
  "rustix",
  "windows-sys 0.52.0",
 ]
 
 [[package]]
-name = "termcolor"
-version = "1.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449"
-dependencies = [
- "winapi-util",
-]
-
-[[package]]
 name = "terminal_size"
 version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2384,22 +2237,22 @@
 
 [[package]]
 name = "thiserror"
-version = "1.0.53"
+version = "1.0.57"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2cd5904763bad08ad5513ddbb12cf2ae273ca53fa9f68e843e236ec6dfccc09"
+checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.53"
+version = "1.0.57"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3dcf4a824cce0aeacd6f38ae6f24234c8e80d68632338ebaa1443b5df9e29e19"
+checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.48",
+ "syn 2.0.49",
 ]
 
 [[package]]
@@ -2414,11 +2267,12 @@
 
 [[package]]
 name = "time"
-version = "0.3.31"
+version = "0.3.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e"
+checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749"
 dependencies = [
  "deranged",
+ "num-conv",
  "powerfmt",
  "serde",
  "time-core",
@@ -2433,10 +2287,11 @@
 
 [[package]]
 name = "time-macros"
-version = "0.2.16"
+version = "0.2.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f"
+checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774"
 dependencies = [
+ "num-conv",
  "time-core",
 ]
 
@@ -2451,9 +2306,9 @@
 
 [[package]]
 name = "tokio"
-version = "1.35.1"
+version = "1.36.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
+checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
 dependencies = [
  "backtrace",
  "bytes",
@@ -2475,7 +2330,7 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.48",
+ "syn 2.0.49",
 ]
 
 [[package]]
@@ -2513,9 +2368,9 @@
 
 [[package]]
 name = "toml"
-version = "0.8.8"
+version = "0.8.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35"
+checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290"
 dependencies = [
  "serde",
  "serde_spanned",
@@ -2534,9 +2389,9 @@
 
 [[package]]
 name = "toml_edit"
-version = "0.21.0"
+version = "0.22.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03"
+checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6"
 dependencies = [
  "indexmap",
  "serde",
@@ -2564,7 +2419,7 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.48",
+ "syn 2.0.49",
 ]
 
 [[package]]
@@ -2716,18 +2571,7 @@
  "itoa",
  "log",
  "unicode-width",
- "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",
+ "vte",
 ]
 
 [[package]]
@@ -2736,7 +2580,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197"
 dependencies = [
- "arrayvec 0.7.4",
+ "arrayvec",
  "utf8parse",
  "vte_generate_state_changes",
 ]
@@ -2763,21 +2607,15 @@
 
 [[package]]
 name = "wasi"
-version = "0.9.0+wasi-snapshot-preview1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
-
-[[package]]
-name = "wasi"
 version = "0.11.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.89"
+version = "0.2.91"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e"
+checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f"
 dependencies = [
  "cfg-if",
  "wasm-bindgen-macro",
@@ -2785,24 +2623,24 @@
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.89"
+version = "0.2.91"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826"
+checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b"
 dependencies = [
  "bumpalo",
  "log",
  "once_cell",
  "proc-macro2",
  "quote",
- "syn 2.0.48",
+ "syn 2.0.49",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.89"
+version = "0.2.91"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2"
+checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -2810,22 +2648,22 @@
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.89"
+version = "0.2.91"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
+checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.48",
+ "syn 2.0.49",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.89"
+version = "0.2.91"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
+checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838"
 
 [[package]]
 name = "winapi"
@@ -2869,15 +2707,6 @@
 
 [[package]]
 name = "windows-sys"
-version = "0.45.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
-dependencies = [
- "windows-targets 0.42.2",
-]
-
-[[package]]
-name = "windows-sys"
 version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
@@ -2892,21 +2721,6 @@
 checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
 dependencies = [
  "windows-targets 0.52.0",
-]
-
-[[package]]
-name = "windows-targets"
-version = "0.42.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
-dependencies = [
- "windows_aarch64_gnullvm 0.42.2",
- "windows_aarch64_msvc 0.42.2",
- "windows_i686_gnu 0.42.2",
- "windows_i686_msvc 0.42.2",
- "windows_x86_64_gnu 0.42.2",
- "windows_x86_64_gnullvm 0.42.2",
- "windows_x86_64_msvc 0.42.2",
 ]
 
 [[package]]
@@ -2938,12 +2752,6 @@
  "windows_x86_64_gnullvm 0.52.0",
  "windows_x86_64_msvc 0.52.0",
 ]
-
-[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.42.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
 
 [[package]]
 name = "windows_aarch64_gnullvm"
@@ -2959,12 +2767,6 @@
 
 [[package]]
 name = "windows_aarch64_msvc"
-version = "0.42.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
-
-[[package]]
-name = "windows_aarch64_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
@@ -2974,12 +2776,6 @@
 version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
-
-[[package]]
-name = "windows_i686_gnu"
-version = "0.42.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
 
 [[package]]
 name = "windows_i686_gnu"
@@ -2992,12 +2788,6 @@
 version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
-
-[[package]]
-name = "windows_i686_msvc"
-version = "0.42.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
 
 [[package]]
 name = "windows_i686_msvc"
@@ -3010,12 +2800,6 @@
 version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
-
-[[package]]
-name = "windows_x86_64_gnu"
-version = "0.42.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
 
 [[package]]
 name = "windows_x86_64_gnu"
@@ -3028,12 +2812,6 @@
 version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
-
-[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.42.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
@@ -3046,12 +2824,6 @@
 version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
-
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.42.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
 
 [[package]]
 name = "windows_x86_64_msvc"
@@ -3067,21 +2839,22 @@
 
 [[package]]
 name = "winnow"
-version = "0.5.31"
+version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97a4882e6b134d6c28953a387571f1acdd3496830d5e36c5e3a1075580ea641c"
+checksum = "d90f4e0f530c4c69f62b80d839e9ef3855edc9cba471a160c4d692deed62b401"
 dependencies = [
  "memchr",
 ]
 
 [[package]]
 name = "x25519-dalek"
-version = "1.1.1"
+version = "2.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f"
+checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
 dependencies = [
  "curve25519-dalek",
- "rand_core 0.5.1",
+ "rand_core",
+ "serde",
  "zeroize",
 ]
 
@@ -3108,5 +2881,5 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.48",
+ "syn 2.0.49",
 ]
modifiedCargo.tomldiffbeforeafterboth
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,5 +6,5 @@
 nixlike = { path = "./crates/nixlike" }
 better-command = { path = "./crates/better-command" }
 bifrostlink = "0.1.0"
-uuid = { version = "1.3.3", features = ["v4"] }
-tokio = { version = "1.33.0", features = ["fs", "rt", "macros", "sync", "time", "rt-multi-thread"] }
+uuid = { version = "1.7.0", features = ["v4"] }
+tokio = { version = "1.36.0", features = ["fs", "rt", "macros", "sync", "time", "rt-multi-thread"] }
modifiedREADME.adocdiffbeforeafterboth
--- a/README.adoc
+++ b/README.adoc
@@ -11,3 +11,164 @@
 - 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)
+
+== Secret generator example
+
+TODO:: This section should into some kind of fleet documentation... But as there is none, it is just left here as-is.
+
+=== Quickly run securely setup gitlab
+
+[source,nix]
+----
+{config, ...}: {
+  secrets = let ownership = { owner = "gitlab"; group = "gitlab"; }; in {
+    gitlab-initial-root = {
+      generator = {mkPassword}: mkPassword {};
+    } // ownership;
+    gitlab-secret = {
+      generator = {mkPassword}: mkPassword {};
+    } // ownership;
+    gitlab-otp = {
+      generator = {mkPassword}: mkPassword {};
+    } // ownership;
+    gitlab-db = {
+      generator = {mkPassword}: mkPassword {};
+    } // ownership;
+    gitlab-jws = {
+      generator = {mkRsa}: mkRsa {};
+    } // ownership;
+  };
+  services.gitlab = let secrets = config.secrets; in {
+    enable = true;
+    initialRootPasswordFile = secrets.gitlab-initial-root.secretPath;
+    secrets = {
+      secretFile = secrets.gitlab-secret.secretPath;
+      otpFile = secrets.gitlab-otp.secretPath;
+      dbFile = secrets.gitlab-db.secretPath;
+      jwsFile = secrets.gitlab-jws.secretPath;
+    };
+  };
+}
+----
+
+=== Securely initialize kubernetes secrets
+
+In my homelab and clusters, I almost always have some sort of HSM, and to issue new kubernetes certs I directly connect to it.
+This setup should probably split into multiple steps, where I allow target machine to generate CSR, then copy it to the HSM machine, and then sign it there... But this is just the plan.
+I want to build ansible-like script execution in fleet for this kind of tasks.
+
+[source,nix]
+----
+{...}: {
+  # First I define required secret generators:
+  nixpkgs.overlays = [
+    (final: prev: let
+      lib = final.lib;
+    in {
+      readKubernetesCa = {impureOn}:
+        final.mkImpureSecretGenerator ''
+          cd ~/ca
+
+          cert=kubernetes-intermediateCA.crt
+
+          expires_at=$(openssl x509 -in $cert -noout -enddate | cut -d= -f2 | xargs -I{} date -u -d {} +"%Y-%m-%dT%H:%M:%S.%NZ")
+          echo -n $expires_at > $out/expires_at
+
+          cat $cert > $out/public
+        ''
+        impureOn;
+      mkKubernetesCert = {
+        subj,
+        sans ? [],
+        impureOn,
+      }:
+        final.mkImpureSecretGenerator ''
+          cd ~/ca
+
+          params=$(sudo mktemp)
+          csr=$(sudo mktemp)
+          cert=$(sudo mktemp)
+          sudo openssl ecparam -genkey -name secp384r1 -out $params
+          sudo openssl req -new -key $params \
+            -subj "${lib.strings.concatStringsSep "" (lib.attrsets.mapAttrsToList (k: v: "/${k}=${v}") subj)}" \
+            ${lib.optionalString (sans != []) "-addext \"subjectAltName = ${lib.strings.concatStringsSep "," sans}\""} \
+            -out $csr
+          sudo hsms x509 -req -days 365 -in $csr -CA kubernetes-intermediateCA.crt -CAkey "pkcs11:object=[CENSORED] Kubernetes Intermediate CA;type=private" -CAcreateserial -copy_extensions copy -out $cert
+
+          expires_at=$(sudo openssl x509 -in $cert -noout -enddate | cut -d= -f2 | xargs -I{} date -u -d {} +"%Y-%m-%dT%H:%M:%S.%NZ")
+          echo -n $expires_at > $out/expires_at
+
+          sudo cat $params | encrypt > $out/secret
+          sudo cat $cert > $out/public
+        ''
+        impureOn;
+    })
+  ];
+  # Those secret generators are impure, thus they are run in system environment.
+  # Probably there needs to be a dedicated user for that kind of tasks, but this is my current setup, don't judge.
+  # I write a couple of scripts for executing openssl with HSM.
+  environment.systemPackages = [
+    pkgs.openssl.bin
+    (pkgs.writeShellApplication {
+      name = "hsms";
+      text = ''
+        set -eu
+        export OPENSSL_CONF=${openssl-conf}
+        # Yay, using secrets to generate secrets!
+        HSM_PIN=$(cat ${config.secrets.hsm-pin.secretPath})
+        exec ${pkgs.openssl}/bin/openssl "$@" -keyform=engine -CAkeyform=engine -engine=pkcs11 -passin=pass:"$HSM_PIN"
+      '';
+    })
+    (pkgs.writeShellApplication {
+      name = "hsmt";
+      text = ''
+        set -eu
+        HSM_PIN=$(cat ${config.secrets.hsm-pin.secretPath})
+        exec ${pkgs.opensc}/bin/pkcs11-tool -l --pin="$HSM_PIN" "$@"
+      '';
+    })
+  ];
+  # And finally, I have secrets, which are shared between machines.
+  # Note that this example is somewhat wrong, as this goes not into the machine configuration, but to fleet configuration.
+  sharedSecrets = {
+    "ca.pem" = {
+      # This is just the public key, no need to regenerate it to change owner list
+      regenerateOnOwnerAdded = false;
+      # For secret regeneration/reencryption, we need to specify which machines SHOULD have it.
+      expectedOwners = ["controlplane-1" "controlplane-2" "worker-1" "worker-2"];
+      generator = {readKubernetesCa}:
+        readKubernetesCa {
+          impureOn = "[CENSORED]";
+        };
+    };
+    "kube-admin.pem" = {
+      regenerateOnOwnerAdded = false;
+      expectedOwners = ["cluster-admin"];
+      generator = {mkKubernetesCert}:
+        mkKubernetesCert {
+          subj = {
+            CN = "admin";
+            O = "system:masters";
+          };
+          impureOn = "[CENSORED]";
+        };
+    };
+    "kube-apiserver.pem" = {
+      # This secret depends on machine SANS, so if owner list has been changed, then we need to regenerate it.
+      # However, SANS dependency is in fact handled by secret seed, and secret is regenerated if the seed is changed...
+      #
+      # In this case regeneration is added as a half-assed security measure, as if apiserver is removed, we don't
+      # want for it to be able to pretend like it is a valid server.
+      #
+      # However, certificate revokation is complicated in my setup, and I can't show it here.
+      regenerateOnOwnerAdded = true;
+      expectedOwners = ["controlplane-1" "controlplane-2"];
+      generator = {mkKubernetesCert}:
+        mkKubernetesCert {
+          inherit sans;
+          subj.CN = "kubernetes";
+          impureOn = "[CENSORED]";
+        };
+    };
+}
+----
modifiedcmds/fleet/Cargo.tomldiffbeforeafterboth
--- a/cmds/fleet/Cargo.toml
+++ b/cmds/fleet/Cargo.toml
@@ -12,17 +12,17 @@
 anyhow = "1.0"
 serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
-time = { version = "0.3.30", features = ["serde"] }
-tempfile = "3.8"
-once_cell = "1.18.0"
-hostname = "0.3.1"
-age-core = "0.9.0"
-peg = "0.8.2"
-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 = [
+time = { version = "0.3", features = ["serde"] }
+tempfile = "3.10"
+once_cell = "1.19"
+hostname = "0.3"
+age-core = "0.10"
+peg = "0.8"
+age = { version = "0.10", features = ["ssh", "armor"] }
+base64 = "0.21"
+chrono = { version = "0.4", features = ["serde"] }
+z85 = "3.0"
+clap = { version = "4.5", features = [
 	"derive",
 	"env",
 	"wrap_help",
@@ -30,18 +30,18 @@
 ] }
 tracing = "0.1"
 tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] }
-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"] }
+tokio-util = { version = "0.7", features = ["codec"] }
+async-trait = "0.1"
+futures = "0.3"
+tracing-indicatif = "0.3"
+indicatif = "0.17"
+itertools = "0.12"
+shlex = "1.3"
+tabled = { version = "0.15" }
+owo-colors = { version = "4.0", features = ["supports-color", "supports-colors"] }
 r2d2 = "0.8.10"
-abort-on-drop = "0.2.2"
-unindent = "0.2.3"
-regex = "1.10.2"
-openssh = "0.10.1"
-human-repr = "1.1.0"
+abort-on-drop = "0.2"
+unindent = "0.2"
+regex = "1.10"
+openssh = "0.10"
+human-repr = "1.1"
modifiedcmds/fleet/src/cmds/secrets/mod.rsdiffbeforeafterboth
after · cmds/fleet/src/cmds/secrets/mod.rs
1use crate::{2	better_nix_eval::Field,3	command::MyCommand,4	fleetdata::{FleetSecret, FleetSharedSecret, SecretData},5	host::Config,6	nix_go, nix_go_json,7};8use anyhow::{anyhow, bail, ensure, Context, Result};9use chrono::{DateTime, Utc};10use clap::Parser;11use owo_colors::OwoColorize;12use serde::{de::DeserializeOwned, Deserialize};13use std::{14	collections::{BTreeSet, HashSet},15	io::{self, Cursor, Read},16	path::{Path, PathBuf},17	str::FromStr,18};19use tabled::{Table, Tabled};20use tempfile::tempdir;21use tokio::fs::{self, read_to_string};22use tracing::{error, info, info_span, warn, Instrument};2324#[derive(Parser)]25pub enum Secret {26	/// Force load host keys for all defined hosts27	ForceKeys,28	/// Add secret, data should be provided in stdin29	AddShared {30		/// Secret name31		name: String,32		/// Secret owners33		machines: Vec<String>,34		/// Override secret if already present35		#[clap(long)]36		force: bool,37		/// Secret public part38		#[clap(long)]39		public: Option<String>,40		/// Load public part from specified file41		#[clap(long)]42		public_file: Option<PathBuf>,4344		/// Create a notification on secret expiration45		#[clap(long)]46		expires_at: Option<DateTime<Utc>>,4748		/// Secret with this name already exists, override its value while keeping the same owners.49		#[clap(long)]50		re_add: bool,51	},52	/// Add secret, data should be provided in stdin53	Add {54		/// Secret name55		name: String,56		/// Secret owners57		machine: String,58		/// Override secret if already present59		#[clap(long)]60		force: bool,61		#[clap(long)]62		public: Option<String>,63		#[clap(long)]64		public_file: Option<PathBuf>,65	},66	/// Read secret from remote host, requires sudo on said host67	Read {68		name: String,69		machine: String,70		#[clap(long)]71		plaintext: bool,72	},73	ReadPublic {74		name: String,75		machine: String,76	},77	UpdateShared {78		name: String,7980		#[clap(long)]81		machines: Option<Vec<String>>,8283		#[clap(long)]84		add_machines: Vec<String>,85		#[clap(long)]86		remove_machines: Vec<String>,8788		/// Which host should we use to decrypt89		#[clap(long)]90		prefer_identities: Vec<String>,91	},92	Regenerate {93		/// Which host should we use to decrypt, in case if reencryption is required, without94		/// regeneration95		#[clap(long)]96		prefer_identities: Vec<String>,97	},98	List {},99}100101#[tracing::instrument(skip(config, secret, field, prefer_identities))]102async fn update_owner_set(103	secret_name: &str,104	config: &Config,105	mut secret: FleetSharedSecret,106	field: Field,107	updated_set: &[String],108	prefer_identities: &[String],109) -> Result<FleetSharedSecret> {110	let original_set = secret.owners.clone();111112	let set = original_set.iter().collect::<BTreeSet<_>>();113	let expected_set = updated_set.iter().collect::<BTreeSet<_>>();114115	if set == expected_set {116		info!("no need to update owner list, it is already correct");117		return Ok(secret);118	}119120	let should_regenerate = if set.difference(&expected_set).next().is_some() {121		// TODO: Remove this warning for revokable secrets.122		warn!("host was removed from secret owners, but until this host rebuild, the secret will still be stored on it.");123		nix_go_json!(field.regenerateOnOwnerRemoved)124	} else if expected_set.difference(&set).next().is_some() {125		nix_go_json!(field.regenerateOnOwnerAdded)126	} else {127		false128	};129130	if should_regenerate {131		info!("secret is owner-dependent, will regenerate");132		let generated = generate_shared(config, secret_name, field, updated_set.to_vec()).await?;133		Ok(generated)134	} else {135		let identity_holder = if !prefer_identities.is_empty() {136			prefer_identities137				.iter()138				.find(|i| original_set.iter().any(|s| s == *i))139		} else {140			secret.owners.first()141		};142		let Some(identity_holder) = identity_holder else {143			bail!("no available holder found");144		};145146		if let Some(data) = secret.secret.secret {147			let host = config.host(identity_holder).await?;148			let encrypted = host.reencrypt(data, updated_set.to_vec()).await?;149			secret.secret.secret = Some(encrypted);150		}151152		secret.owners = updated_set.to_vec();153		Ok(secret)154	}155}156157#[derive(Deserialize)]158#[serde(rename_all = "camelCase")]159enum GeneratorKind {160	Impure,161	Pure,162}163164async fn generate_pure(165	config: &Config,166	_display_name: &str,167	secret: Field,168	default_generator: Field,169	owners: &[String],170) -> Result<FleetSecret> {171	// TODO: pure secrets are supposed to be generated by nix daemon itself,172	// inside of a sandbox... But we aren't here yet.173	let config_field = &config.config_unchecked_field;174	let generator = nix_go!(secret.generator);175	let default_pkgs = &config.default_pkgs;176177	let call_package = nix_go!(default_pkgs.callPackage);178179	let generator = nix_go!(call_package(generator)(Obj {}));180	let generator = generator.build().await?;181	let generator = generator182		.get("out")183		.ok_or_else(|| anyhow!("missing generate out"))?;184185	let mut recipients = String::new();186	for owner in owners {187		let key = config.key(owner).await?;188		recipients.push_str(&format!("-r \"{key}\" "));189	}190	recipients.push_str("-e");191192	let out = tempdir()?;193194	let mut gen = MyCommand::new(generator);195	gen.env("rageArgs", recipients);196	gen.env(197		"out",198		out.path().to_str().expect("sane tempdir should be utf-8"),199	);200	gen.run().await.context("impure generator")?;201202	{203		let mut marker_path = out.path().to_owned();204		marker_path.push("marker");205		let marker = fs::read_to_string(&marker_path).await?;206		ensure!(marker == "SUCCESS", "generation not succeeded");207	}208209	let mut public_path = out.path().to_owned();210	public_path.push("public");211	let mut secret_path = out.path().to_owned();212	secret_path.push("secret");213	let public = fs::read_to_string(&public_path).await.ok();214	let secret = fs::read(&secret_path).await.ok();215	if let Some(secret) = &secret {216		ensure!(217			age::Decryptor::new(Cursor::new(&secret)).is_ok(),218			"builder produced non-encrypted value as secret, this is highly insecure, and not allowed."219		);220	}221222	let mut created_at_path = out.path().to_owned();223	created_at_path.push("created_at");224	let mut expires_at_path = out.path().to_owned();225	expires_at_path.push("expires_at");226227	async fn read_value<T: FromStr>(path: &Path) -> Result<T> {228		dbg!(path);229		let raw = fs::read(path).await?;230		let raw = String::from_utf8(raw)?;231		raw.parse().map_err(|_| anyhow!("fromStr failed"))232	}233234	let created_at = read_value(&created_at_path).await?;235	let expires_at = read_value(&expires_at_path).await.ok();236237	Ok(FleetSecret {238		created_at,239		expires_at,240		public,241		secret: secret.map(SecretData),242	})243}244async fn generate_impure(245	config: &Config,246	_display_name: &str,247	secret: Field,248	default_generator: Field,249	owners: &[String],250) -> Result<FleetSecret> {251	let config_field = &config.config_unchecked_field;252	let generator = nix_go!(secret.generator);253254	let on: String = nix_go_json!(default_generator.impureOn);255	let call_package = nix_go!(256		config_field.hosts[{ on }]257			.nixosSystem258			.config259			.nixpkgs260			.resolvedPkgs261			.callPackage262	);263264	let host = config.host(&on).await?;265266	let generator = nix_go!(call_package(generator)(Obj {}));267	let generator = generator.build().await?;268	let generator = generator269		.get("out")270		.ok_or_else(|| anyhow!("missing generateImpure out"))?;271	let generator = host.remote_derivation(generator).await?;272273	let mut recipients = String::new();274	for owner in owners {275		let key = config.key(owner).await?;276		recipients.push_str(&format!("-r \"{key}\" "));277	}278	recipients.push_str("-e");279280	let out = host.mktemp_dir().await?;281282	let mut gen = host.cmd(generator).await?;283	gen.env("rageArgs", recipients).env("out", &out);284	gen.run().await.context("impure generator")?;285286	{287		let marker = host.read_file_text(format!("{out}/marker")).await?;288		ensure!(marker == "SUCCESS", "generation not succeeded");289	}290291	let public = host.read_file_text(format!("{out}/public")).await.ok();292	let secret = host.read_file_bin(format!("{out}/secret")).await.ok();293	if let Some(secret) = &secret {294		ensure!(295			age::Decryptor::new(Cursor::new(&secret)).is_ok(),296			"builder produced non-encrypted value as secret, this is highly insecure, and not allowed."297		);298	}299300	let created_at = host.read_file_value(format!("{out}/created_at")).await?;301	let expires_at = host.read_file_value(format!("{out}/expires_at")).await.ok();302303	Ok(FleetSecret {304		created_at,305		expires_at,306		public,307		secret: secret.map(SecretData),308	})309}310async fn generate(311	config: &Config,312	display_name: &str,313	secret: Field,314	owners: &[String],315) -> Result<FleetSecret> {316	let generator = nix_go!(secret.generator);317	// Can't properly check on nix module system level318	{319		let gen_ty = generator.type_of().await?;320		if gen_ty == "null" {321			bail!("secret has no generator defined, can't automatically generate it.");322		}323		if gen_ty != "lambda" {324			bail!("generator should be lambda, got {gen_ty}");325		}326	}327	let default_pkgs = &config.default_pkgs;328	let default_call_package = nix_go!(default_pkgs.callPackage);329	// Generators provide additional information in passthru, to access330	// passthru we should call generator, but information about where this generator is supposed to build331	// is located in passthru... Thus evaluating generator on host.332	//333	// Maybe it is also possible to do some magic with __functor?334	//335	// I don't want to make modules always responsible for additional secret data anyway,336	// so it should be in derivation, and not in the secret data itself.337	let default_generator = nix_go!(default_call_package(generator)(Obj {}));338339	let kind: GeneratorKind = nix_go_json!(default_generator.generatorKind);340341	match kind {342		GeneratorKind::Impure => {343			generate_impure(config, display_name, secret, default_generator, owners).await344		}345		GeneratorKind::Pure => {346			generate_pure(config, display_name, secret, default_generator, owners).await347		}348	}349}350async fn generate_shared(351	config: &Config,352	display_name: &str,353	secret: Field,354	expected_owners: Vec<String>,355) -> Result<FleetSharedSecret> {356	// let owners: Vec<String> = nix_go_json!(secret.expectedOwners);357	Ok(FleetSharedSecret {358		secret: generate(config, display_name, secret, &expected_owners).await?,359		owners: expected_owners,360	})361}362363async fn parse_public(364	public: Option<String>,365	public_file: Option<PathBuf>,366) -> Result<Option<String>> {367	Ok(match (public, public_file) {368		(Some(v), None) => Some(v),369		(None, Some(v)) => Some(read_to_string(v).await?),370		(Some(_), Some(_)) => {371			bail!("only public or public_file should be set")372		}373		(None, None) => None,374	})375}376377fn parse_machines(378	initial: Vec<String>,379	machines: Option<Vec<String>>,380	mut add_machines: Vec<String>,381	mut remove_machines: Vec<String>,382) -> Result<Vec<String>> {383	if machines.is_none() && add_machines.is_empty() && remove_machines.is_empty() {384		bail!("no operation");385	}386387	let initial_machines = initial.clone();388	let mut target_machines = initial;389	info!("Currently encrypted for {initial_machines:?}");390391	// ensure!(machines.is_some() || !add_machines.is_empty() || )392	if let Some(machines) = machines {393		ensure!(394			add_machines.is_empty() && remove_machines.is_empty(),395			"can't combine --machines and --add-machines/--remove-machines"396		);397		let target = initial_machines.iter().collect::<HashSet<_>>();398		let source = machines.iter().collect::<HashSet<_>>();399		for removed in target.difference(&source) {400			remove_machines.push((*removed).clone());401		}402		for added in source.difference(&target) {403			add_machines.push((*added).clone());404		}405	}406407	for machine in &remove_machines {408		let mut removed = false;409		while let Some(pos) = target_machines.iter().position(|m| m == machine) {410			target_machines.swap_remove(pos);411			removed = true;412		}413		if !removed {414			warn!("secret is not enabled for {machine}");415		}416	}417	for machine in &add_machines {418		if target_machines.iter().any(|m| m == machine) {419			warn!("secret is already added to {machine}");420		} else {421			target_machines.push(machine.to_owned());422		}423	}424	if !remove_machines.is_empty() {425		// TODO: maybe force secret regeneration?426		// Not that useful without revokation.427		warn!("secret will not be regenerated for removed machines, and until host rebuild, they will still possess the ability to decode secret");428	}429	Ok(target_machines)430}431impl Secret {432	pub async fn run(self, config: &Config) -> Result<()> {433		match self {434			Secret::ForceKeys => {435				for host in config.list_hosts().await? {436					if config.should_skip(&host.name) {437						continue;438					}439					config.key(&host.name).await?;440				}441			}442			Secret::AddShared {443				mut machines,444				name,445				force,446				public,447				public_file,448				expires_at,449				re_add,450			} => {451				// TODO: Forbid updating secrets with set expectedOwners (= not user-managed).452453				let exists = config.has_shared(&name);454				if exists && !force && !re_add {455					bail!("secret already defined");456				}457				if re_add {458					// Fixme: use clap to limit this usage459					ensure!(!force, "--force and --readd are not compatible");460					ensure!(exists, "secret doesn't exists");461					ensure!(462						machines.is_empty(),463						"you can't use machines argument for --readd"464					);465					let shared = config.shared_secret(&name)?;466					machines = shared.owners;467				}468469				let recipients = config.recipients(machines.clone()).await?;470471				let secret = {472					let mut input = vec![];473					io::stdin().read_to_end(&mut input)?;474475					if input.is_empty() {476						None477					} else {478						Some(479							SecretData::encrypt(recipients, input)480								.ok_or_else(|| anyhow!("no recipients provided"))?,481						)482					}483				};484				let public = parse_public(public, public_file).await?;485				config.replace_shared(486					name,487					FleetSharedSecret {488						owners: machines,489						secret: FleetSecret {490							created_at: Utc::now(),491							expires_at,492							secret,493							public,494						},495					},496				);497			}498			Secret::Add {499				machine,500				name,501				force,502				public,503				public_file,504			} => {505				let recipient = config.recipient(&machine).await?;506507				let secret = {508					let mut input = vec![];509					io::stdin().read_to_end(&mut input)?;510					if input.is_empty() {511						bail!("no data provided")512					}513514					Some(SecretData::encrypt(vec![recipient], input).expect("recipient provided"))515				};516517				if config.has_secret(&machine, &name) && !force {518					bail!("secret already defined");519				}520				let public = parse_public(public, public_file).await?;521522				config.insert_secret(523					&machine,524					name,525					FleetSecret {526						created_at: Utc::now(),527						expires_at: None,528						secret,529						public,530					},531				);532			}533			#[allow(clippy::await_holding_refcell_ref)]534			Secret::Read {535				name,536				machine,537				plaintext,538			} => {539				let secret = config.host_secret(&machine, &name)?;540				let Some(secret) = secret.secret else {541					bail!("no secret {name}");542				};543				let host = config.host(&machine).await?;544				let data = host.decrypt(secret).await?;545				if plaintext {546					let s = String::from_utf8(data).context("output is not utf8")?;547					print!("{s}");548				} else {549					println!("{}", z85::encode(&data));550				}551			}552			Secret::ReadPublic {553				name,554				machine,555			} => {556				let secret = config.host_secret(&machine, &name)?;557				let Some(public) = secret.public else {558					bail!("no secret {name}");559				};560				print!("{public}");561			}562			Secret::UpdateShared {563				name,564				machines,565				add_machines,566				remove_machines,567				prefer_identities,568			} => {569				// TODO: Forbid updating secrets with set expectedOwners (= not user-managed).570571				let secret = config.shared_secret(&name)?;572				if secret.secret.secret.is_none() {573					bail!("no secret");574				}575576				let initial_machines = secret.owners.clone();577				let target_machines = parse_machines(578					initial_machines.clone(),579					machines,580					add_machines,581					remove_machines,582				)?;583584				if target_machines.is_empty() {585					info!("no machines left for secret, removing it");586					config.remove_shared(&name);587					return Ok(());588				}589590				let config_field = &config.config_unchecked_field;591				let field = nix_go!(config_field.sharedSecrets[{ name }]);592593				let updated = update_owner_set(594					&name,595					config,596					secret,597					field,598					&target_machines,599					&prefer_identities,600				)601				.await?;602				config.replace_shared(name, updated);603			}604			Secret::Regenerate { prefer_identities } => {605				info!("checking for secrets to regenerate");606				{607					let _span = info_span!("shared").entered();608					let expected_shared_set = config609						.list_configured_shared()610						.await?611						.into_iter()612						.collect::<HashSet<_>>();613					let shared_set = config.list_shared().into_iter().collect::<HashSet<_>>();614					for missing in expected_shared_set.difference(&shared_set) {615						let config_field = &config.config_unchecked_field;616						let secret = nix_go!(config_field.sharedSecrets[{ missing }]);617						let expected_owners: Option<Vec<String>> =618							nix_go_json!(secret.expectedOwners);619						let Some(expected_owners) = expected_owners else {620							// TODO: Might still need to regenerate621							continue;622						};623						info!("generating secret: {missing}");624						let shared = generate_shared(config, missing, secret, expected_owners)625							.in_current_span()626							.await?;627						config.replace_shared(missing.to_string(), shared)628					}629				}630				for host in config.list_hosts().await? {631					let _span = info_span!("host", host = host.name).entered();632					let expected_set = host633						.list_configured_secrets()634						.in_current_span()635						.await?636						.into_iter()637						.collect::<HashSet<_>>();638					let stored_set = config639						.list_secrets(&host.name)640						.into_iter()641						.collect::<HashSet<_>>();642					for missing in expected_set.difference(&stored_set) {643						info!("generating secret: {missing}");644						let secret = host.secret_field(missing).in_current_span().await?;645						let generated =646							match generate(config, missing, secret, &[host.name.clone()])647								.in_current_span()648								.await649							{650								Ok(v) => v,651								Err(e) => {652									error!("{e}");653									continue;654								}655							};656						config.insert_secret(&host.name, missing.to_string(), generated)657					}658				}659				let mut to_remove = Vec::new();660				for name in &config.list_shared() {661					info!("updating secret: {name}");662					let data = config.shared_secret(name)?;663					let config_field = &config.config_unchecked_field;664					let expected_owners: Vec<String> =665						nix_go_json!(config_field.sharedSecrets[{ name }].expectedOwners);666					if expected_owners.is_empty() {667						warn!("secret was removed from fleet config: {name}, removing from data");668						to_remove.push(name.to_string());669						continue;670					}671672					let secret = nix_go!(config_field.sharedSecrets[{ name }]);673					config.replace_shared(674						name.to_owned(),675						update_owner_set(676							name,677							config,678							data,679							secret,680							&expected_owners,681							&prefer_identities,682						)683						.await?,684					);685				}686				for k in to_remove {687					config.remove_shared(&k);688				}689			}690			Secret::List {} => {691				let _span = info_span!("loading secrets").entered();692				let configured = config.list_configured_shared().await?;693				#[derive(Tabled)]694				struct SecretDisplay {695					#[tabled(rename = "Name")]696					name: String,697					#[tabled(rename = "Owners")]698					owners: String,699				}700				let mut table = vec![];701				for name in configured.iter().cloned() {702					let config = config.clone();703					let expected_owners = config.shared_secret_expected_owners(&name).await?;704					let data = config.shared_secret(&name)?;705					let owners = data706						.owners707						.iter()708						.map(|o| {709							if expected_owners.contains(o) {710								o.green().to_string()711							} else {712								o.red().to_string()713							}714						})715						.collect::<Vec<_>>();716					table.push(SecretDisplay {717						owners: owners.join(", "),718						name,719					})720				}721				info!("loaded\n{}", Table::new(table).to_string())722			}723		}724		Ok(())725	}726}
modifiedcmds/fleet/src/main.rsdiffbeforeafterboth
--- a/cmds/fleet/src/main.rs
+++ b/cmds/fleet/src/main.rs
@@ -11,9 +11,8 @@
 
 mod fleetdata;
 
-use std::ffi::OsString;
-use std::process::exit;
 use std::time::Duration;
+use std::{ffi::OsString, process::ExitCode};
 
 use anyhow::{bail, Result};
 use clap::Parser;
@@ -62,6 +61,7 @@
 				path.push(entry.path());
 
 				let mut status = MyCommand::new("nix");
+				status.args(&config.nix_args);
 				status.arg("store").arg("prefetch-file").arg(path);
 				status.run_nix_string().instrument(span).await?;
 				Ok(())
@@ -118,7 +118,11 @@
 				return;
 			};
 			let pos = state.pos();
-			let _ = write!(writer, "{} / {}", pos.human_count_bare(), len.human_count_bare());
+			if pos > len {
+				let _ = write!(writer, "{}", pos.human_count_bare());
+			} else {
+				let _ = write!(writer, "{} / {}", pos.human_count_bare(), len.human_count_bare());
+			}
 		})
 		.with_key(
 			"color_start",
@@ -151,7 +155,7 @@
 			tracing_subscriber::fmt::layer()
 				.without_time()
 				.with_target(true)
-				.with_writer(indicatif_layer.get_stderr_writer())
+				.with_writer(indicatif_layer.get_stdout_writer())
 				.with_filter(filter), // .withou,
 		)
 		.with(indicatif_layer)
@@ -159,12 +163,15 @@
 }
 
 #[tokio::main]
-async fn main() {
+async fn main() -> ExitCode {
 	setup_logging();
 	if let Err(e) = main_real().await {
+		// If I remove this line, the next error!() line gets eaten.
+		info!("fixme: this line gets eaten by tracing-indicatif on levels info+");
 		error!("{e:#}");
-		exit(1);
+		return ExitCode::FAILURE;
 	}
+	ExitCode::SUCCESS
 }
 
 async fn main_real() -> Result<()> {
modifiedcmds/install-secrets/Cargo.tomldiffbeforeafterboth
--- a/cmds/install-secrets/Cargo.toml
+++ b/cmds/install-secrets/Cargo.toml
@@ -4,18 +4,18 @@
 edition = "2021"
 
 [dependencies]
-age = { version = "0.9.2", features = ["ssh"] }
-anyhow = "1.0.75"
-env_logger = "0.10.0"
-log = "0.4.20"
+age = { version = "0.10.0", features = ["ssh"] }
+anyhow = "1.0.79"
+tracing-subscriber = "0.3"
+tracing = "0.1"
 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 = [
+serde = { version = "1.0.196", features = ["derive"] }
+serde_json = "1.0.113"
+clap = { version = "4.5.1", features = [
 	"derive",
 	"env",
 	"wrap_help",
 	"unicode",
 ] }
-tempfile = "3.8.1"
+tempfile = "3.10.0"
 z85 = "3.0.5"
modifiedcmds/install-secrets/src/main.rsdiffbeforeafterboth
--- a/cmds/install-secrets/src/main.rs
+++ b/cmds/install-secrets/src/main.rs
@@ -2,9 +2,8 @@
 use age::{Encryptor, Identity, Recipient};
 use anyhow::{anyhow, bail, Context, Result};
 use clap::Parser;
-use log::{error, info, warn};
 use nix::sys::stat::Mode;
-use nix::unistd::{User, Group, chown};
+use nix::unistd::{chown, Group, User};
 use serde::{Deserialize, Deserializer};
 use std::fmt::{self, Display};
 use std::fs::{self, File};
@@ -14,6 +13,9 @@
 use std::path::Path;
 use std::str::{from_utf8, FromStr};
 use std::{collections::HashMap, path::PathBuf};
+use tracing::{error, info, warn};
+use tracing_subscriber::filter::LevelFilter;
+use tracing_subscriber::EnvFilter;
 
 #[derive(Clone, Debug)]
 struct SecretWrapper(Vec<u8>);
@@ -228,8 +230,13 @@
 }
 
 fn main() -> anyhow::Result<()> {
-	env_logger::Builder::new()
-		.filter_level(log::LevelFilter::Info)
+	tracing_subscriber::fmt()
+		.with_env_filter(
+			EnvFilter::builder()
+				.with_default_directive(LevelFilter::INFO.into())
+				.from_env_lossy(),
+		)
+		.without_time()
 		.init();
 
 	let opts = Opts::parse();
modifiedcrates/better-command/Cargo.tomldiffbeforeafterboth
--- a/crates/better-command/Cargo.toml
+++ b/crates/better-command/Cargo.toml
@@ -3,12 +3,10 @@
 version = "0.1.0"
 edition = "2021"
 
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
 [dependencies]
-once_cell = "1.19.0"
-regex = "1.10.2"
-serde = { version = "1.0.193", features = ["derive"] }
-serde_json = "1.0.108"
-tracing = "0.1.40"
-tracing-indicatif = "0.3.6"
+once_cell = "1.19"
+regex = "1.10"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+tracing = "0.1"
+tracing-indicatif = "0.3"
modifiedcrates/nixlike/Cargo.tomldiffbeforeafterboth
--- a/crates/nixlike/Cargo.toml
+++ b/crates/nixlike/Cargo.toml
@@ -7,8 +7,8 @@
 alejandra = {git = "https://github.com/kamadorueda/alejandra"}
 linked-hash-map = "0.5.6"
 peg = "0.8.2"
-serde = "1.0.190"
-thiserror = "1.0.50"
-serde_json = "1.0.107"
+serde = "1.0.196"
+thiserror = "1.0.57"
+serde_json = "1.0.113"
 ron = "0.8.1"
 serde-transcode = "1.1.1"
modifiedflake.lockdiffbeforeafterboth
--- a/flake.lock
+++ b/flake.lock
@@ -5,11 +5,11 @@
         "systems": "systems"
       },
       "locked": {
-        "lastModified": 1701680307,
-        "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
+        "lastModified": 1705309234,
+        "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
         "owner": "numtide",
         "repo": "flake-utils",
-        "rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
+        "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
         "type": "github"
       },
       "original": {
@@ -23,11 +23,11 @@
         "systems": "systems_2"
       },
       "locked": {
-        "lastModified": 1681202837,
-        "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
+        "lastModified": 1705309234,
+        "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
         "owner": "numtide",
         "repo": "flake-utils",
-        "rev": "cfacdce06f30d2b68473a46042957675eebb3401",
+        "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
         "type": "github"
       },
       "original": {
@@ -38,11 +38,11 @@
     },
     "nixpkgs": {
       "locked": {
-        "lastModified": 1704409229,
-        "narHash": "sha256-Vc41cRJ3trOnocovLe0zZE35pK5Lfuo/zHk0xx3CNDY=",
+        "lastModified": 1708177587,
+        "narHash": "sha256-Tj/YV9kdC+I7V/kjrq3Bdl8z2VIHT5hiAG74s52vLgw=",
         "owner": "nixos",
         "repo": "nixpkgs",
-        "rev": "786f788914f2a6e94cedf361541894e972b8fd23",
+        "rev": "3c43b81701e73452df1c080b05770407da9e16d6",
         "type": "github"
       },
       "original": {
@@ -67,11 +67,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1704075545,
-        "narHash": "sha256-L3zgOuVKhPjKsVLc3yTm2YJ6+BATyZBury7wnhyc8QU=",
+        "lastModified": 1708135817,
+        "narHash": "sha256-EUMO/K3+Wgh0THOLoRXhxrh6G/pQ7BlJ8No+ciy1nKA=",
         "owner": "oxalica",
         "repo": "rust-overlay",
-        "rev": "a0df72e106322b67e9c6e591fe870380bd0da0d5",
+        "rev": "c77e68d33a84ce3f9e86905c0f2ef78d5defad28",
         "type": "github"
       },
       "original": {
modifiedflake.nixdiffbeforeafterboth
--- a/flake.nix
+++ b/flake.nix
@@ -29,7 +29,7 @@
         llvmPkgs = pkgs.buildPackages.llvmPackages_11;
         rust =
           (pkgs.rustChannelOf {
-            date = "2024-01-01";
+            date = "2024-02-10";
             channel = "nightly";
           })
           .default
@@ -38,12 +38,14 @@
         packages = (import ./pkgs) pkgs pkgs;
         devShell = (pkgs.mkShell.override {stdenv = llvmPkgs.stdenv;}) {
           nativeBuildInputs = with pkgs; [
+            alejandra
             rust
             lld
             cargo-edit
             cargo-udeps
             cargo-fuzz
             cargo-watch
+            cargo-outdated
 
             pkg-config
             openssl
modifiedmodules/fleet/secrets.nixdiffbeforeafterboth
--- a/modules/fleet/secrets.nix
+++ b/modules/fleet/secrets.nix
@@ -1,5 +1,11 @@
-{ lib, fleetLib, config, ... }: with lib; with fleetLib;
-let
+{
+  lib,
+  fleetLib,
+  config,
+  ...
+}:
+with lib;
+with fleetLib; let
   sharedSecret = with types; ({config, ...}: {
     options = {
       expectedOwners = mkOption {
@@ -9,13 +15,14 @@
 
           Secrets would be decrypted and stored to /run/secrets/$\{name} on owners
         '';
+        default = null;
       };
       # TODO: Aren't those options may be just desugared to data/expectedData?
       regenerateOnOwnerAdded = mkOption {
         type = bool;
         description = ''
           Is this secret owner-dependent, and needs to be regenerated on ownership set change, or it may be just reencrypted.
-          
+
           You want to have this option set to true, when this secret contains some reference to its owners, i.e x509 SANs.
         '';
       };
@@ -24,7 +31,7 @@
         type = bool;
         description = ''
           Should this secret be removed on owner removal, or it may be just reencrypted
-          
+
           Most probably its value should be equal to regenerateOnOwnerAdded, override only if you know what are you doing.
           Contrary to regenerateOnOwnerAdded, you may want to set this option to false, when host permissions are revoked
           in some other way than by this secret ownership, I.e by firewall/etc.
@@ -55,7 +62,7 @@
 
           Imported from fleet.nix
         '';
-        default = [ ];
+        default = [];
       };
       # TODO: Make secret generator generate arbitrary number of secret/public parts?
       # Make it generate a folder, where all files except suffixed by .enc are public, and the rest are secret?
@@ -96,43 +103,121 @@
       };
     };
   };
-in
-{
+in {
   options = with types; {
     sharedSecrets = mkOption {
       type = attrsOf (submodule sharedSecret);
-      default = { };
+      default = {};
       description = "Shared secrets";
     };
     hostSecrets = mkOption {
       type = attrsOf (attrsOf (submodule hostSecret));
-      default = { };
+      default = {};
       description = "Host secrets. Imported from fleet.nix";
       internal = true;
     };
   };
   config = {
-    assertions = mapAttrsToList
+    assertions =
+      mapAttrsToList
       (name: secret: {
         assertion = secret.expectedOwners == null || builtins.sort (a: b: a < b) secret.owners == builtins.sort (a: b: a < b) secret.expectedOwners;
         message = "Shared secret ${name} is expected to be encrypted for ${builtins.toJSON secret.expectedOwners}, but it is encrypted for ${builtins.toJSON secret.owners}. Run fleet secrets regenerate to fix";
       })
       config.sharedSecrets;
     hosts = hostsToAttrs (host: {
-      modules =
-        let
-          cleanupSecret = (secretName: v: {
-            inherit (v) public secret;
-            shared = true;
-          });
-        in
-        [
-          {
-            secrets = (mapAttrs cleanupSecret
+      modules = let
+        cleanupSecret = secretName: v: {
+          inherit (v) public secret;
+          shared = true;
+        };
+      in [
+        {
+          secrets =
+            (
+              mapAttrs cleanupSecret
               (filterAttrs (_: v: builtins.elem host v.owners) config.sharedSecrets)
-            ) // (mapAttrs cleanupSecret (config.hostSecrets.${host} or { }));
-          }
-        ];
+            )
+            // (mapAttrs cleanupSecret (config.hostSecrets.${host} or {}));
+        }
+      ];
     });
+    # TODO: Should this attribute be moved to `nixpkgs.overlays`?
+    overlays = [
+      (final: prev: let
+        lib = final.lib;
+      in {
+        mkPassword = {size ? 32}:
+          final.mkSecretGenerator ''
+            ${final.coreutils}/bin/tr -dc 'A-Za-z0-9!?%=' < /dev/random \
+              | ${final.coreutils}/bin/head -c ${toString size} \
+              | encrypt > $out/secret
+          '';
+        mkRsa = {size ? 4096}:
+          final.mkSecretGenerator ''
+            ${final.openssl}/bin/openssl genrsa -out rsa_private.key ${toString size}
+            ${final.openssl}/bin/openssl rsa -in rsa_private.key -pubout -out rsa_public.key
+
+            sudo cat rsa_private.key | encrypt > $out/secret
+            sudo cat rsa_public.key > $out/public
+          '';
+        # TODO: Move to fleet
+        # TODO: Merge both generators to one with consistent options syntax?
+        # Impure generator is built on local machine, then built closure is copied to remote machine,
+        # and then it is ran in inpure context, so that this generator may access HSMs and other things.
+        mkImpureSecretGenerator = generatorText: machine:
+          (prev.writeShellScript "impureGenerator.sh" ''
+            #!/bin/sh
+            set -eu
+
+            # TODO: Provide encryption function as script passed to `callPackage generator {encrypt = ...;}`
+            function encrypt() {
+              eval ${final.rage}/bin/rage $rageArgs
+            }
+
+            created_at=$(date -u +"%Y-%m-%dT%H:%M:%S.%NZ")
+            echo -n $created_at > $out/created_at
+
+            ${generatorText}
+
+            echo -n SUCCESS > $out/marker
+          '')
+          .overrideAttrs (old: {
+            passthru = {
+              generatorKind = "impure";
+              impureOn = machine;
+            };
+          });
+        # TODO: Implement consistent naming
+        # Pure secret generator is supposed to be run entirely by nix, using `__impure` derivation type...
+        # But for now, it is ran the same way as `impureSecretGenerator`, but on the local machine.
+        mkSecretGenerator = generatorText:
+          (prev.writeShellScript "generator.sh" ''
+            #!/bin/sh
+            set -eu
+            # TODO: User should create output directory by themselves.
+            cd $out
+
+            # TODO: Provide encryption function as script passed to `callPackage generator {encrypt = ...;}`
+            function encrypt() {
+              eval ${final.rage}/bin/rage $rageArgs
+            }
+
+            created_at=$(date -u +"%Y-%m-%dT%H:%M:%S.%NZ")
+            echo -n $created_at > $out/created_at
+
+            ${generatorText}
+
+            echo -n SUCCESS > $out/marker
+          '')
+          .overrideAttrs (old: {
+            passthru = {
+              generatorKind = "pure";
+            };
+            # TODO: make nix daemon build secret, not just the script.
+            # __impure = true;
+          });
+      })
+    ];
   };
 }