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
before · cmds/fleet/src/cmds/secrets/mod.rs
1use crate::{2	better_nix_eval::Field,3	fleetdata::{FleetSecret, FleetSharedSecret, SecretData},4	host::Config,5	nix_go, nix_go_json,6};7use anyhow::{anyhow, bail, ensure, Context, Result};8use chrono::{DateTime, Utc};9use clap::Parser;10use owo_colors::OwoColorize;11use serde::Deserialize;12use std::{13	collections::{BTreeSet, HashSet},14	io::{self, Cursor, Read},15	path::PathBuf,16};17use tabled::{Table, Tabled};18use tokio::fs::read_to_string;19use tracing::{error, info, info_span, warn, Instrument};2021#[derive(Parser)]22pub enum Secret {23	/// Force load host keys for all defined hosts24	ForceKeys,25	/// Add secret, data should be provided in stdin26	AddShared {27		/// Secret name28		name: String,29		/// Secret owners30		machines: Vec<String>,31		/// Override secret if already present32		#[clap(long)]33		force: bool,34		/// Secret public part35		#[clap(long)]36		public: Option<String>,37		/// Load public part from specified file38		#[clap(long)]39		public_file: Option<PathBuf>,4041		/// Create a notification on secret expiration42		#[clap(long)]43		expires_at: Option<DateTime<Utc>>,4445		/// Secret with this name already exists, override its value while keeping the same owners.46		#[clap(long)]47		re_add: bool,48	},49	/// Add secret, data should be provided in stdin50	Add {51		/// Secret name52		name: String,53		/// Secret owners54		machine: String,55		/// Override secret if already present56		#[clap(long)]57		force: bool,58		#[clap(long)]59		public: Option<String>,60		#[clap(long)]61		public_file: Option<PathBuf>,62	},63	/// Read secret from remote host, requires sudo on said host64	Read {65		name: String,66		machine: String,67		#[clap(long)]68		plaintext: bool,69	},70	UpdateShared {71		name: String,7273		#[clap(long)]74		machines: Option<Vec<String>>,7576		#[clap(long)]77		add_machines: Vec<String>,78		#[clap(long)]79		remove_machines: Vec<String>,8081		/// Which host should we use to decrypt82		#[clap(long)]83		prefer_identities: Vec<String>,84	},85	Regenerate {86		/// Which host should we use to decrypt, in case if reencryption is required, without87		/// regeneration88		#[clap(long)]89		prefer_identities: Vec<String>,90	},91	List {},92}9394#[tracing::instrument(skip(config, secret, field, prefer_identities))]95async fn update_owner_set(96	secret_name: &str,97	config: &Config,98	mut secret: FleetSharedSecret,99	field: Field,100	updated_set: &[String],101	prefer_identities: &[String],102) -> Result<FleetSharedSecret> {103	let original_set = secret.owners.clone();104105	let set = original_set.iter().collect::<BTreeSet<_>>();106	let expected_set = updated_set.iter().collect::<BTreeSet<_>>();107108	if set == expected_set {109		info!("no need to update owner list, it is already correct");110		return Ok(secret);111	}112113	let should_regenerate = if set.difference(&expected_set).next().is_some() {114		// TODO: Remove this warning for revokable secrets.115		warn!("host was removed from secret owners, but until this host rebuild, the secret will still be stored on it.");116		nix_go_json!(field.regenerateOnOwnerRemoved)117	} else if expected_set.difference(&set).next().is_some() {118		nix_go_json!(field.regenerateOnOwnerAdded)119	} else {120		false121	};122123	if should_regenerate {124		info!("secret is owner-dependent, will regenerate");125		let generated = generate_shared(config, secret_name, field, updated_set.to_vec()).await?;126		Ok(generated)127	} else {128		let identity_holder = if !prefer_identities.is_empty() {129			prefer_identities130				.iter()131				.find(|i| original_set.iter().any(|s| s == *i))132		} else {133			secret.owners.first()134		};135		let Some(identity_holder) = identity_holder else {136			bail!("no available holder found");137		};138139		if let Some(data) = secret.secret.secret {140			let host = config.host(identity_holder).await?;141			let encrypted = host.reencrypt(data, updated_set.to_vec()).await?;142			secret.secret.secret = Some(encrypted);143		}144145		secret.owners = updated_set.to_vec();146		Ok(secret)147	}148}149150#[derive(Deserialize)]151#[serde(rename_all = "camelCase")]152enum GeneratorKind {153	Impure,154}155156async fn generate_impure(157	config: &Config,158	_display_name: &str,159	secret: Field,160	default_generator: Field,161	owners: &[String],162) -> Result<FleetSecret> {163	let config_field = &config.config_unchecked_field;164	let generator = nix_go!(secret.generator);165166	let on: String = nix_go_json!(default_generator.impureOn);167	let call_package = nix_go!(168		config_field.hosts[{ on }]169			.nixosSystem170			.config171			.nixpkgs172			.resolvedPkgs173			.callPackage174	);175176	let host = config.host(&on).await?;177178	let generator = nix_go!(call_package(generator)(Obj {}));179	let generator = generator.build().await?;180	let generator = generator181		.get("out")182		.ok_or_else(|| anyhow!("missing generateImpure out"))?;183	let generator = host.remote_derivation(generator).await?;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 = host.mktemp_dir().await?;193194	let mut gen = host.cmd(generator).await?;195	gen.env("rageArgs", recipients).env("out", &out);196	gen.run().await.context("impure generator")?;197198	{199		let marker = host.read_file_text(format!("{out}/marker")).await?;200		ensure!(marker == "SUCCESS", "generation not succeeded");201	}202203	let public = host.read_file_text(format!("{out}/public")).await.ok();204	let secret = host.read_file_bin(format!("{out}/secret")).await.ok();205	if let Some(secret) = &secret {206		ensure!(207			age::Decryptor::new(Cursor::new(&secret)).is_ok(),208			"builder produced non-encrypted value as secret, this is highly insecure, and not allowed."209		);210	}211212	let created_at = host.read_file_value(format!("{out}/created_at")).await?;213	let expires_at = host.read_file_value(format!("{out}/expires_at")).await.ok();214215	Ok(FleetSecret {216		created_at,217		expires_at,218		public,219		secret: secret.map(SecretData),220	})221}222async fn generate(223	config: &Config,224	display_name: &str,225	secret: Field,226	owners: &[String],227) -> Result<FleetSecret> {228	let generator = nix_go!(secret.generator);229	// Can't properly check on nix module system level230	{231		let gen_ty = generator.type_of().await?;232		if gen_ty == "null" {233			bail!("secret has no generator defined, can't automatically generate it.");234		}235		if gen_ty != "lambda" {236			bail!("generator should be lambda, got {gen_ty}");237		}238	}239	let default_pkgs = &config.default_pkgs;240	let default_call_package = nix_go!(default_pkgs.callPackage);241	// Generators provide additional information in passthru, to access242	// passthru we should call generator, but information about where this generator is supposed to build243	// is located in passthru... Thus evaluating generator on host.244	//245	// Maybe it is also possible to do some magic with __functor?246	//247	// I don't want to make modules always responsible for additional secret data anyway,248	// so it should be in derivation, and not in the secret data itself.249	let default_generator = nix_go!(default_call_package(generator)(Obj {}));250251	let kind: GeneratorKind = nix_go_json!(default_generator.generatorKind);252253	match kind {254		GeneratorKind::Impure => {255			generate_impure(config, display_name, secret, default_generator, owners).await256		}257	}258}259async fn generate_shared(260	config: &Config,261	display_name: &str,262	secret: Field,263	expected_owners: Vec<String>,264) -> Result<FleetSharedSecret> {265	// let owners: Vec<String> = nix_go_json!(secret.expectedOwners);266	Ok(FleetSharedSecret {267		secret: generate(config, display_name, secret, &expected_owners).await?,268		owners: expected_owners,269	})270}271272async fn parse_public(273	public: Option<String>,274	public_file: Option<PathBuf>,275) -> Result<Option<String>> {276	Ok(match (public, public_file) {277		(Some(v), None) => Some(v),278		(None, Some(v)) => Some(read_to_string(v).await?),279		(Some(_), Some(_)) => {280			bail!("only public or public_file should be set")281		}282		(None, None) => None,283	})284}285286fn parse_machines(287	initial: Vec<String>,288	machines: Option<Vec<String>>,289	mut add_machines: Vec<String>,290	mut remove_machines: Vec<String>,291) -> Result<Vec<String>> {292	if machines.is_none() && add_machines.is_empty() && remove_machines.is_empty() {293		bail!("no operation");294	}295296	let initial_machines = initial.clone();297	let mut target_machines = initial;298	info!("Currently encrypted for {initial_machines:?}");299300	// ensure!(machines.is_some() || !add_machines.is_empty() || )301	if let Some(machines) = machines {302		ensure!(303			add_machines.is_empty() && remove_machines.is_empty(),304			"can't combine --machines and --add-machines/--remove-machines"305		);306		let target = initial_machines.iter().collect::<HashSet<_>>();307		let source = machines.iter().collect::<HashSet<_>>();308		for removed in target.difference(&source) {309			remove_machines.push((*removed).clone());310		}311		for added in source.difference(&target) {312			add_machines.push((*added).clone());313		}314	}315316	for machine in &remove_machines {317		let mut removed = false;318		while let Some(pos) = target_machines.iter().position(|m| m == machine) {319			target_machines.swap_remove(pos);320			removed = true;321		}322		if !removed {323			warn!("secret is not enabled for {machine}");324		}325	}326	for machine in &add_machines {327		if target_machines.iter().any(|m| m == machine) {328			warn!("secret is already added to {machine}");329		} else {330			target_machines.push(machine.to_owned());331		}332	}333	if !remove_machines.is_empty() {334		// TODO: maybe force secret regeneration?335		// Not that useful without revokation.336		warn!("secret will not be regenerated for removed machines, and until host rebuild, they will still possess the ability to decode secret");337	}338	Ok(target_machines)339}340impl Secret {341	pub async fn run(self, config: &Config) -> Result<()> {342		match self {343			Secret::ForceKeys => {344				for host in config.list_hosts().await? {345					if config.should_skip(&host.name) {346						continue;347					}348					config.key(&host.name).await?;349				}350			}351			Secret::AddShared {352				mut machines,353				name,354				force,355				public,356				public_file,357				expires_at,358				re_add,359			} => {360				let exists = config.has_shared(&name);361				if exists && !force && !re_add {362					bail!("secret already defined");363				}364				if re_add {365					// Fixme: use clap to limit this usage366					ensure!(!force, "--force and --readd are not compatible");367					ensure!(exists, "secret doesn't exists");368					ensure!(369						machines.is_empty(),370						"you can't use machines argument for --readd"371					);372					let shared = config.shared_secret(&name)?;373					machines = shared.owners;374				}375376				let recipients = config.recipients(machines.clone()).await?;377378				let secret = {379					let mut input = vec![];380					io::stdin().read_to_end(&mut input)?;381382					if input.is_empty() {383						None384					} else {385						Some(386							SecretData::encrypt(recipients, input)387								.ok_or_else(|| anyhow!("no recipients provided"))?,388						)389					}390				};391				let public = parse_public(public, public_file).await?;392				config.replace_shared(393					name,394					FleetSharedSecret {395						owners: machines,396						secret: FleetSecret {397							created_at: Utc::now(),398							expires_at,399							secret,400							public,401						},402					},403				);404			}405			Secret::Add {406				machine,407				name,408				force,409				public,410				public_file,411			} => {412				let recipient = config.recipient(&machine).await?;413414				let secret = {415					let mut input = vec![];416					io::stdin().read_to_end(&mut input)?;417					if input.is_empty() {418						bail!("no data provided")419					}420421					Some(SecretData::encrypt(vec![recipient], input).expect("recipient provided"))422				};423424				if config.has_secret(&machine, &name) && !force {425					bail!("secret already defined");426				}427				let public = parse_public(public, public_file).await?;428429				config.insert_secret(430					&machine,431					name,432					FleetSecret {433						created_at: Utc::now(),434						expires_at: None,435						secret,436						public,437					},438				);439			}440			#[allow(clippy::await_holding_refcell_ref)]441			Secret::Read {442				name,443				machine,444				plaintext,445			} => {446				let secret = config.host_secret(&machine, &name)?;447				let Some(secret) = secret.secret else {448					bail!("no secret {name}");449				};450				let host = config.host(&machine).await?;451				let data = host.decrypt(secret).await?;452				if plaintext {453					let s = String::from_utf8(data).context("output is not utf8")?;454					print!("{s}");455				} else {456					println!("{}", z85::encode(&data));457				}458			}459			Secret::UpdateShared {460				name,461				machines,462				add_machines,463				remove_machines,464				prefer_identities,465			} => {466				let secret = config.shared_secret(&name)?;467				if secret.secret.secret.is_none() {468					bail!("no secret");469				}470471				let initial_machines = secret.owners.clone();472				let target_machines = parse_machines(473					initial_machines.clone(),474					machines,475					add_machines,476					remove_machines,477				)?;478479				if target_machines.is_empty() {480					info!("no machines left for secret, removing it");481					config.remove_shared(&name);482					return Ok(());483				}484485				let config_field = &config.config_unchecked_field;486				let field = nix_go!(config_field.sharedSecrets[{ name }]);487488				let updated = update_owner_set(489					&name,490					config,491					secret,492					field,493					&target_machines,494					&prefer_identities,495				)496				.await?;497				config.replace_shared(name, updated);498			}499			Secret::Regenerate { prefer_identities } => {500				info!("checking for secrets to regenerate");501				{502					let _span = info_span!("shared").entered();503					let expected_shared_set = config504						.list_configured_shared()505						.await?506						.into_iter()507						.collect::<HashSet<_>>();508					let shared_set = config.list_shared().into_iter().collect::<HashSet<_>>();509					for missing in expected_shared_set.difference(&shared_set) {510						let config_field = &config.config_unchecked_field;511						let secret = nix_go!(config_field.sharedSecrets[{ missing }]);512						let expected_owners: Option<Vec<String>> =513							nix_go_json!(secret.expectedOwners);514						let Some(expected_owners) = expected_owners else {515							// TODO: Might still need to regenerate516							continue;517						};518						info!("generating secret: {missing}");519						let shared = generate_shared(config, missing, secret, expected_owners)520							.in_current_span()521							.await?;522						config.replace_shared(missing.to_string(), shared)523					}524				}525				for host in config.list_hosts().await? {526					let _span = info_span!("host", host = host.name).entered();527					let expected_set = host528						.list_configured_secrets()529						.in_current_span()530						.await?531						.into_iter()532						.collect::<HashSet<_>>();533					let stored_set = config534						.list_secrets(&host.name)535						.into_iter()536						.collect::<HashSet<_>>();537					for missing in expected_set.difference(&stored_set) {538						info!("generating secret: {missing}");539						let secret = host.secret_field(missing).in_current_span().await?;540						let generated =541							match generate(config, missing, secret, &[host.name.clone()])542								.in_current_span()543								.await544							{545								Ok(v) => v,546								Err(e) => {547									error!("{e}");548									continue;549								}550							};551						config.insert_secret(&host.name, missing.to_string(), generated)552					}553				}554				let mut to_remove = Vec::new();555				for name in &config.list_shared() {556					info!("updating secret: {name}");557					let data = config.shared_secret(name)?;558					let config_field = &config.config_unchecked_field;559					let expected_owners: Vec<String> =560						nix_go_json!(config_field.sharedSecrets[{ name }].expectedOwners);561					if expected_owners.is_empty() {562						warn!("secret was removed from fleet config: {name}, removing from data");563						to_remove.push(name.to_string());564						continue;565					}566567					let secret = nix_go!(config_field.sharedSecrets[{ name }]);568					config.replace_shared(569						name.to_owned(),570						update_owner_set(571							name,572							config,573							data,574							secret,575							&expected_owners,576							&prefer_identities,577						)578						.await?,579					);580				}581				for k in to_remove {582					config.remove_shared(&k);583				}584			}585			Secret::List {} => {586				let _span = info_span!("loading secrets").entered();587				let configured = config.list_configured_shared().await?;588				#[derive(Tabled)]589				struct SecretDisplay {590					#[tabled(rename = "Name")]591					name: String,592					#[tabled(rename = "Owners")]593					owners: String,594				}595				let mut table = vec![];596				for name in configured.iter().cloned() {597					let config = config.clone();598					let expected_owners = config.shared_secret_expected_owners(&name).await?;599					let data = config.shared_secret(&name)?;600					let owners = data601						.owners602						.iter()603						.map(|o| {604							if expected_owners.contains(o) {605								o.green().to_string()606							} else {607								o.red().to_string()608							}609						})610						.collect::<Vec<_>>();611					table.push(SecretDisplay {612						owners: owners.join(", "),613						name,614					})615				}616				info!("loaded\n{}", Table::new(table).to_string())617			}618		}619		Ok(())620	}621}
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;
+          });
+      })
+    ];
   };
 }