git.delta.rocks / jrsonnet / refs/commits / d866c975e1c7

difftreelog

feat system build

Yaroslav Bolyukin2020-11-14.patch.diff
in: trunk

24 files changed

added.gitignorediffbeforeafterboth
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+target
+result
+example
addedCargo.lockdiffbeforeafterboth
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,709 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "aho-corasick"
+version = "0.7.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b476ce7103678b0c6d3d395dbbae31d48ff910bd28be979ba5d48c6351131d0d"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7"
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "base-x"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b"
+
+[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+
+[[package]]
+name = "bumpalo"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
+[[package]]
+name = "clap"
+version = "3.0.0-beta.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142"
+dependencies = [
+ "atty",
+ "bitflags",
+ "clap_derive",
+ "indexmap",
+ "lazy_static",
+ "os_str_bytes",
+ "strsim",
+ "termcolor",
+ "textwrap",
+ "unicode-width",
+ "vec_map",
+]
+
+[[package]]
+name = "clap_derive"
+version = "3.0.0-beta.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "370f715b81112975b1b69db93e0b56ea4cd4e5002ac43b2da8474106a54096a1"
+dependencies = [
+ "heck",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "const_fn"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab"
+
+[[package]]
+name = "discard"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
+
+[[package]]
+name = "env_logger"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54532e3223c5af90a6a757c90b5c5521564b07e5e7a958681bcd2afad421cdcd"
+dependencies = [
+ "atty",
+ "humantime",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "fleet"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "clap",
+ "env_logger",
+ "lockfile",
+ "log",
+ "serde",
+ "serde_json",
+ "tempfile",
+ "time",
+ "toml",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
+
+[[package]]
+name = "heck"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "humantime"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c1ad908cc71012b7bea4d0c53ba96a8cba9962f048fa68d143376143d863b7a"
+
+[[package]]
+name = "indexmap"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"
+
+[[package]]
+name = "lockfile"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e9b01c4735c76fec1c390661ac8794722f0af0b5eb742500308f94b2caae40f"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "log"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "memchr"
+version = "2.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
+
+[[package]]
+name = "os_str_bytes"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ac6fe3538f701e339953a3ebbe4f39941aababa8a3f6964635b24ab526daeac"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20"
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-hack"
+version = "0.5.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom",
+ "libc",
+ "rand_chacha",
+ "rand_core",
+ "rand_hc",
+]
+
+[[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",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.1.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
+
+[[package]]
+name = "regex"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8963b85b8ce3074fecffde43b4b0dded83ce2f367dc8d363afc56679f3ee820b"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+ "thread_local",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8cab7a364d15cde1e505267766a2d3c4e22a843e1a601f0fa7564c0f82ced11c"
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
+
+[[package]]
+name = "semver"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
+dependencies = [
+ "semver-parser",
+]
+
+[[package]]
+name = "semver-parser"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
+
+[[package]]
+name = "serde"
+version = "1.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.59"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha1"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
+
+[[package]]
+name = "standback"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4e0831040d2cf2bdfd51b844be71885783d489898a192f254ae25d57cce725c"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "stdweb"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5"
+dependencies = [
+ "discard",
+ "rustc_version",
+ "stdweb-derive",
+ "stdweb-internal-macros",
+ "stdweb-internal-runtime",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "stdweb-derive"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_derive",
+ "syn",
+]
+
+[[package]]
+name = "stdweb-internal-macros"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11"
+dependencies = [
+ "base-x",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "sha1",
+ "syn",
+]
+
+[[package]]
+name = "stdweb-internal-runtime"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "syn"
+version = "1.0.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e03e57e4fcbfe7749842d53e24ccb9aa12b7252dbe5e91d2acad31834c8b8fdd"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "rand",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "time"
+version = "0.2.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55b7151c9065e80917fbf285d9a5d1432f60db41d170ccafc749a136b41a93af"
+dependencies = [
+ "const_fn",
+ "libc",
+ "serde",
+ "standback",
+ "stdweb",
+ "time-macros",
+ "version_check",
+ "winapi",
+]
+
+[[package]]
+name = "time-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1"
+dependencies = [
+ "proc-macro-hack",
+ "time-macros-impl",
+]
+
+[[package]]
+name = "time-macros-impl"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa"
+dependencies = [
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "standback",
+ "syn",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
+
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
+[[package]]
+name = "version_check"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f22b422e2a757c35a73774860af8e112bff612ce6cb604224e8e47641a9e4f68"
+dependencies = [
+ "bumpalo",
+ "lazy_static",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b13312a745c08c469f0b292dd2fcd6411dba5f7160f593da6ef69b64e407038"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
addedCargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "fleet"
+description = "NixOS configuration management"
+version = "0.1.0"
+authors = ["Yaroslav Bolyukin <iam@lach.pw>"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1.0.34"
+clap = { version = "3.0.0-beta.2", features = ["derive", "suggestions", "color"] }
+log = "0.4.11"
+env_logger = "0.8.1"
+
+serde = { version = "1.0.117", features = ["derive"] }
+serde_json = "1.0.59"
+
+time = { version = "0.2.22", features = ["serde"] }
+
+lockfile = "0.2.2"
+toml = "0.5"
+tempfile = "3.1.0"
addedREADME.mddiffbeforeafterboth
--- /dev/null
+++ b/README.md
@@ -0,0 +1,8 @@
+# fleet
+
+Early prototype stage
+
+## Advantages over existing configuration systems (NixOps/Morph)
+
+- 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)
addedflake.lockdiffbeforeafterboth
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,26 @@
+{
+  "nodes": {
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1605344435,
+        "narHash": "sha256-Xx66M/eTwLc97sge6y210qMBZe2qwrpSqWagfEAOF0M=",
+        "owner": "nixos",
+        "repo": "nixpkgs",
+        "rev": "d67b00e8f0b378b1700e12f5b8e68c0706839c9a",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nixos",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "root": {
+      "inputs": {
+        "nixpkgs": "nixpkgs"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}
addedflake.nixdiffbeforeafterboth
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,10 @@
+{
+  description = "NixOS configuration management";
+
+  inputs = {
+    nixpkgs.url = "github:nixos/nixpkgs";
+  };
+  outputs = { self, nixpkgs }: with nixpkgs.lib; rec {
+    lib = import ./lib;
+  };
+}
addedlib/default.nixdiffbeforeafterboth
--- /dev/null
+++ b/lib/default.nix
@@ -0,0 +1,45 @@
+{
+  fleetConfiguration = { common ? { modules = []; }, hosts, nixpkgs }@args:
+    rec {
+      root = nixpkgs.lib.evalModules {
+        modules = [
+          (
+            { ... }: {
+              config = {
+                inherit hosts;
+                # Secret data is available only via fleet build-systems
+                secrets = if builtins?getEnv then
+                  let
+                    stringData = builtins.getEnv "SECRET_DATA";
+                  in
+                    if stringData != "" then (builtins.fromJSON stringData) else {}
+                else {};
+              };
+
+            }
+          )
+        ] ++ common.modules ++ import ./modules/modules.nix {
+          pkgs = nixpkgs;
+          lib = nixpkgs.lib;
+        };
+
+        specialArgs = {
+          fleet = import ./lib/fleetLib.nix {
+            inherit nixpkgs hosts;
+          };
+        };
+      };
+      configuredHosts = root.config.hosts;
+      configuredSecrets = root.config.secrets;
+      configuredSystems = listToAttrs (
+        map (
+          name: {
+            inherit name; value = nixpkgs.lib.nixosSystem {
+            system = configuredHosts.${name}.system;
+            modules = configuredHosts.${name}.modules;
+          };
+          }
+        ) (builtins.attrNames hosts)
+      ); #nixpkgs.lib.nixosSystem {}
+    };
+}
addedlib/fleetLib.nixdiffbeforeafterboth
--- /dev/null
+++ b/lib/fleetLib.nix
@@ -0,0 +1,52 @@
+# Shared functions for fleet configuration, available as `fleet` module argument
+{ nixpkgs, hosts }: with nixpkgs.lib; rec {
+  mkSecret = let
+    system = builtins.currentSystem;
+    pkgs = import nixpkgs { inherit system; };
+    keys = builtins.getEnv "RAGE_KEYS";
+    encryptCmd = "rage ${keys} -a";
+    impuritySource = builtins.getEnv "IMPURITY_SOURCE";
+  in
+    f: let
+      data = f { inherit pkgs encryptCmd; };
+    in
+      builtins.derivation {
+        inherit system;
+        name = "secret";
+
+        builder = "${pkgs.bash}/bin/bash";
+        args = [
+          (
+            pkgs.writeTextFile {
+              name = "./build-${impuritySource}.sh";
+              text = data.script;
+              executable = true;
+            }
+          )
+        ];
+
+        PATH = "${pkgs.coreutils}/bin:${pkgs.rage}/bin${builtins.concatStringsSep "" (builtins.map (n: ":${n}/bin") data.utils)}";
+      };
+  # Modules can't register hosts because of infinite recursion
+  hostNames = attrNames hosts;
+  hostsToAttrs = f: listToAttrs (
+    map (name: { inherit name; value = f name; }) hostNames
+  );
+  hostsCartesian = remove null (
+    unique (
+      crossLists (
+        a: b: if a == b then
+          null
+        else
+          hostsPair a b
+      ) [ hostNames hostNames ]
+    )
+  );
+  hostsPair = this: other: let
+    sorted = sort (a: b: a < b) [ this other ];
+  in
+    {
+      a = elemAt sorted 0;
+      b = elemAt sorted 1;
+    };
+}
addedmodules/modules.nixdiffbeforeafterboth
--- /dev/null
+++ b/modules/modules.nix
@@ -0,0 +1,8 @@
+{ pkgs
+, lib
+, check ? true
+}:
+with lib; [
+  ./networking/wireguard
+  ./root.nix
+]
addedmodules/networking/wireguard/default.nixdiffbeforeafterboth
--- /dev/null
+++ b/modules/networking/wireguard/default.nix
@@ -0,0 +1,101 @@
+{ config, lib, nixpkgs, fleet, ... }: with lib; with fleet; let
+  cfg = config.networking.wireguard;
+  genWgKey = { owners }: {
+    inherit owners;
+    generator = mkSecret (
+      { pkgs, encryptCmd }: {
+        utils = [ pkgs.wireguard-tools ];
+        script = ''
+          key=$(wg genkey)
+          pub=$(echo $key | wg pubkey)
+
+          mkdir -p $out
+          echo $key | ${encryptCmd} >$out/key
+          echo $pub >$out/pub_key
+        '';
+      }
+    );
+  };
+  genWgPsk = { owners }: {
+    inherit owners;
+    generator = mkSecret (
+      { pkgs, encryptCmd }: {
+        utils = [ pkgs.wireguard-tools ];
+        script = ''
+          key=$(wg genpsk)
+
+          mkdir -p $out
+          echo $key | ${encryptCmd} >$out/key
+        '';
+      }
+    );
+  };
+
+  hostKeys = listToAttrs (
+    map (
+      hostName: {
+        name = "wg-key-${hostName}";
+        value = genWgKey {
+          owners = [ hostName ];
+        };
+      }
+    )
+      hostNames
+  );
+  psks = listToAttrs (
+    map (
+      { a, b }: {
+        name = "wg-psk-${a}-${b}";
+        value = genWgPsk {
+          owners = [ a b ];
+        };
+      }
+    )
+      hostsCartesian
+  );
+in
+{
+  options.networking.wireguard = with types; {
+    enable = mkEnableOption "wireguard";
+    interface = mkOption {
+      type = str;
+      description = "Interface name for wireguard network";
+      default = "fleet";
+    };
+    port = mkOption {
+      type = int;
+      description = "Port, on which wireguard interface should listen";
+      default = 51871;
+    };
+    allowedIPs = mkOption {
+      type = attrsOf (listOf str);
+      description = "Per host allowed ips";
+    };
+  };
+  config = mkIf cfg.enable {
+    secrets =
+      (hostKeys // psks);
+    hosts = hostsToAttrs (
+      hostName: {
+        modules = [
+          {
+            networking.wireguard.enable = true;
+            networking.wireguard.interfaces.fleetwg = {
+              privateKeyFile = "/run/secrets/wg-key-${hostName}";
+              peers = map (
+                peer: let
+                  pair = hostsPair hostName peer;
+                in
+                  {
+                    publicKey = config.secrets."wg-key-${peer}".data.key;
+                    presharedKey = "/run/secrets/wg-psk-${pair.a}-${pair.b}";
+                    allowedIPs = cfg.allowedIPs.${peer};
+                  }
+              ) hostNames;
+            };
+          }
+        ];
+      }
+    );
+  };
+}
addedmodules/networking/wireguard/wgbuilder.shdiffbeforeafterboth
--- /dev/null
+++ b/modules/networking/wireguard/wgbuilder.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+key=$($WG genkey)
+pub=$(echo $key | $WG pubkey)
+
+$COREUTILS/bin/mkdir -p $out
+echo $key | $RAGE $recipients >$out/key
+echo $pub >$out/pub_key
addedmodules/root.nixdiffbeforeafterboth

no changes

addedrustfmt.tomldiffbeforeafterboth
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1 @@
+hard_tabs = true
addedsrc/cmds/build_systems.rsdiffbeforeafterboth
--- /dev/null
+++ b/src/cmds/build_systems.rs
@@ -0,0 +1,32 @@
+use crate::{
+	db::{keys::list_hosts, secret::SecretDb, Db, DbData},
+	nix::{NixBuild, NixCopy, HOSTS_ATTRIBUTE, SYSTEMS_ATTRIBUTE},
+};
+use anyhow::Result;
+use clap::Clap;
+use log::info;
+
+#[derive(Clap)]
+pub struct BuildSystems {}
+
+impl BuildSystems {
+	pub fn run(self) -> Result<()> {
+		let db = Db::new(".fleet")?;
+		let hosts = list_hosts()?;
+		let data = SecretDb::open(&db)?.generate_nix_data()?;
+
+		for host in hosts.iter() {
+			info!("Building host {}", host);
+			let path = NixBuild::new(format!(
+				"{}.{}.config.system.build.toplevel",
+				SYSTEMS_ATTRIBUTE, host,
+			))
+			.env("SECRET_DATA".into(), data.clone())
+			.run()?;
+			info!("{:?}", path.path());
+			NixCopy::new(path.path().to_owned()).to(format!("ssh://root@{}", host))?;
+			std::thread::sleep_ms(9999999)
+		}
+		Ok(())
+	}
+}
addedsrc/cmds/fetch_keys.rsdiffbeforeafterboth
--- /dev/null
+++ b/src/cmds/fetch_keys.rs
@@ -0,0 +1,44 @@
+use crate::db::{
+	keys::{list_hosts, KeyDb},
+	Db, DbData,
+};
+use anyhow::Result;
+use clap::Clap;
+use log::info;
+
+#[derive(Clap)]
+pub struct FetchKeys {
+	/// Fetch if already exists the following hosts
+	#[clap(short = 'f', long)]
+	force_hosts: Vec<String>,
+	/// If true - remove orphaned keys
+	#[clap(long)]
+	cleanup: bool,
+}
+
+impl FetchKeys {
+	pub fn run(self) -> Result<()> {
+		let db = Db::new(".fleet")?;
+		let hosts = list_hosts()?;
+		let mut keys = KeyDb::open(&db)?;
+		for host in hosts.iter() {
+			let force = self.force_hosts.contains(&host);
+			keys.ensure_key_loaded(host, force)?;
+		}
+		let orphans: Vec<_> = hosts.iter().filter(|h| !keys.has_key(h)).cloned().collect();
+		if !orphans.is_empty() {
+			if self.cleanup {
+				info!("Removed orphan host keys:");
+			} else {
+				info!("Orphan host keys found, run with --cleanup to remove them from db:");
+			}
+			for key in orphans {
+				info!("- {}", key);
+				if self.cleanup {
+					keys.remove_key(&key)
+				}
+			}
+		}
+		Ok(())
+	}
+}
addedsrc/cmds/generate_secrets.rsdiffbeforeafterboth
--- /dev/null
+++ b/src/cmds/generate_secrets.rs
@@ -0,0 +1,51 @@
+use std::collections::HashSet;
+
+use anyhow::Result;
+use clap::Clap;
+use log::info;
+
+use crate::db::{
+	keys::KeyDb,
+	secret::{list_secrets, SecretDb},
+	Db, DbData,
+};
+
+#[derive(Clap)]
+pub struct GenerateSecrets {
+	/// If set - remove orphaned secrets
+	#[clap(long)]
+	cleanup: bool,
+}
+
+impl GenerateSecrets {
+	pub fn run(self) -> Result<()> {
+		let db = Db::new(".fleet")?;
+		let mut secrets = SecretDb::open(&db)?;
+
+		let defined_secrets = list_secrets()?;
+		for (secret, data) in defined_secrets.iter() {
+			let keys = KeyDb::open(&db)?;
+			secrets.ensure_generated(&keys, &secret, &data)?;
+		}
+		let key_names = defined_secrets
+			.keys()
+			.filter(|s| !secrets.has_secret(s))
+			.cloned()
+			.collect::<HashSet<_>>();
+		if !key_names.is_empty() {
+			if self.cleanup {
+				info!("Removed orphan secrets:");
+			} else {
+				info!("Orphan secrets found, run with --cleanup to remove them from db:");
+			}
+			for key in key_names {
+				info!("- {}", key);
+				if self.cleanup {
+					secrets.remove_secret(&key)
+				}
+			}
+		}
+
+		Ok(())
+	}
+}
addedsrc/cmds/mod.rsdiffbeforeafterboth
--- /dev/null
+++ b/src/cmds/mod.rs
@@ -0,0 +1,3 @@
+pub mod build_systems;
+pub mod fetch_keys;
+pub mod generate_secrets;
addedsrc/command.rsdiffbeforeafterboth
--- /dev/null
+++ b/src/command.rs
@@ -0,0 +1,34 @@
+use std::{
+	ffi::OsStr,
+	process::{Command, Stdio},
+};
+
+use anyhow::{Context, Result};
+use serde::Deserialize;
+
+pub struct CommandOutput(pub Vec<u8>);
+impl CommandOutput {
+	pub fn into_json<'d, T: Deserialize<'d>>(&'d self) -> Result<T> {
+		let str = self.as_str().ok();
+		Ok(serde_json::from_slice(&self.0).with_context(|| format!("{:?}", str))?)
+	}
+	pub fn as_str(&self) -> Result<&str> {
+		Ok(std::str::from_utf8(&self.0)?)
+	}
+}
+
+pub fn ssh_command<I, S>(host: impl AsRef<OsStr>, command: I) -> Result<CommandOutput>
+where
+	I: IntoIterator<Item = S>,
+	S: AsRef<OsStr>,
+{
+	let out = Command::new("ssh")
+		.stderr(Stdio::inherit())
+		.arg(host)
+		.args(command)
+		.output()?;
+	if !out.status.success() {
+		anyhow::bail!("command failed");
+	}
+	Ok(CommandOutput(out.stdout))
+}
addedsrc/db/db.rsdiffbeforeafterboth
--- /dev/null
+++ b/src/db/db.rs
@@ -0,0 +1,122 @@
+//! Small .toml based readable data store
+
+use anyhow::{Context, Result};
+use serde::{de::DeserializeOwned, Serialize};
+use std::{
+	cell::Cell,
+	collections::HashSet,
+	io::Write,
+	ops::{Deref, DerefMut},
+	path::Path,
+	path::PathBuf,
+	sync::{Arc, Mutex},
+};
+
+struct DbInternal {
+	root: PathBuf,
+	locked_paths: HashSet<PathBuf>,
+	_lockfile: lockfile::Lockfile,
+}
+
+pub trait DbData: DeserializeOwned + Serialize + Default {
+	const DB_NAME: &'static str;
+
+	fn open(db: &Db) -> Result<DbFile<Self>> {
+		db.db::<Self>()
+	}
+}
+
+#[derive(Clone)]
+pub struct Db(Arc<Mutex<DbInternal>>);
+impl Db {
+	pub fn new(root: impl AsRef<Path>) -> Result<Self> {
+		let root: &Path = root.as_ref();
+		std::fs::create_dir_all(&root).context("db root")?;
+		let mut lockfile = root.to_owned();
+		lockfile.push(".lock");
+		let lockfile = lockfile::Lockfile::create(lockfile).context("db lock")?;
+		Ok(Db(Arc::new(Mutex::new(DbInternal {
+			root: root.to_owned(),
+			locked_paths: HashSet::new(),
+			_lockfile: lockfile,
+		}))))
+	}
+
+	pub fn db<T: DbData>(&self) -> Result<DbFile<T>> {
+		let name = T::DB_NAME;
+		assert!(!name.contains("/") && !name.contains("\\"));
+		let mut db = self.0.lock().unwrap();
+		let mut data_path = db.root.clone();
+		data_path.push(format!("{}.toml", name));
+
+		if !db.locked_paths.insert(data_path.clone()) {
+			anyhow::bail!("file is already open");
+		}
+
+		let data = if data_path.exists() {
+			let raw_data = std::fs::read(&data_path).context("reading file")?;
+			toml::from_slice(&raw_data).context("parsing file")?
+		} else {
+			T::default()
+		};
+
+		Ok(DbFile {
+			db: self.clone(),
+			root: db.root.clone(),
+			path: data_path,
+			data,
+			dirty: Cell::new(false),
+		})
+	}
+}
+
+pub struct DbFile<T: DbData> {
+	db: Db,
+	root: PathBuf,
+	path: PathBuf,
+	data: T,
+	dirty: Cell<bool>,
+}
+
+impl<T: DbData> Deref for DbFile<T> {
+	type Target = T;
+
+	fn deref(&self) -> &Self::Target {
+		&self.data
+	}
+}
+
+impl<T: DbData> DerefMut for DbFile<T> {
+	fn deref_mut(&mut self) -> &mut Self::Target {
+		self.dirty.set(true);
+		&mut self.data
+	}
+}
+
+impl<T: DbData> DbFile<T> {
+	pub fn write(&self) -> Result<()> {
+		if !self.dirty.get() {
+			return Ok(());
+		}
+		let mut temp = tempfile::Builder::new()
+			.prefix("~")
+			.suffix(".toml")
+			.tempfile_in(&self.root)?;
+		let mut out = String::new();
+		let mut serializer = toml::Serializer::new(&mut out);
+		serializer.pretty_array(true).pretty_string(true);
+		self.data.serialize(&mut serializer)?;
+		temp.write_all(&out.as_bytes())?;
+		temp.persist(&self.path)?;
+		self.dirty.set(false);
+		Ok(())
+	}
+}
+
+impl<T: DbData> Drop for DbFile<T> {
+	fn drop(&mut self) {
+		let mut db = self.db.0.lock().unwrap();
+		self.write().unwrap();
+		db.locked_paths.remove(&self.path);
+	}
+}
addedsrc/db/keys.rsdiffbeforeafterboth
--- /dev/null
+++ b/src/db/keys.rs
@@ -0,0 +1,62 @@
+use std::collections::BTreeMap;
+
+use anyhow::Result;
+use log::*;
+
+use crate::{
+	command::ssh_command,
+	nix::{NixEval, HOSTS_ATTRIBUTE},
+};
+
+use serde::{Deserialize, Serialize};
+
+use super::db::DbData;
+
+pub fn list_hosts() -> Result<Vec<String>> {
+	Ok(NixEval::new(HOSTS_ATTRIBUTE.into())
+		.apply("builtins.attrNames".into())
+		.run_json()?)
+}
+
+#[derive(Serialize, Deserialize, Default)]
+pub struct KeyDb {
+	host_keys: BTreeMap<String, String>,
+}
+impl DbData for KeyDb {
+	const DB_NAME: &'static str = "keys";
+}
+
+impl KeyDb {
+	pub fn fetch_key(&mut self, host: &str) -> Result<()> {
+		info!("Fetching key for {}", host);
+		let key = ssh_command(host, &["cat", "/etc/ssh/ssh_host_ed25519_key.pub"])?
+			.as_str()?
+			.trim()
+			.to_owned();
+		self.host_keys.insert(host.to_owned(), key);
+		Ok(())
+	}
+
+	pub fn ensure_key_loaded(&mut self, host: &str, force: bool) -> Result<()> {
+		if !self.host_keys.contains_key(host) || force {
+			self.fetch_key(host)?;
+		}
+		Ok(())
+	}
+
+	pub fn get_host_key(&self, host: &str) -> Result<String> {
+		Ok(self
+			.host_keys
+			.get(host)
+			.ok_or_else(|| anyhow::anyhow!("no host key for {}", host))?
+			.to_owned())
+	}
+
+	pub fn has_key(&self, key: &str) -> bool {
+		self.host_keys.contains_key(key)
+	}
+
+	pub fn remove_key(&mut self, host: &str) {
+		self.host_keys.remove(host);
+	}
+}
addedsrc/db/mod.rsdiffbeforeafterboth
--- /dev/null
+++ b/src/db/mod.rs
@@ -0,0 +1,5 @@
+mod db;
+pub mod keys;
+pub mod secret;
+
+pub use db::*;
addedsrc/db/secret.rsdiffbeforeafterboth
--- /dev/null
+++ b/src/db/secret.rs
@@ -0,0 +1,211 @@
+use crate::nix::{NixBuild, NixEval, SECRETS_ATTRIBUTE};
+use anyhow::{bail, Result};
+use log::info;
+use serde::{Deserialize, Deserializer, Serialize, Serializer};
+use std::{
+	collections::{BTreeMap, BTreeSet, HashMap},
+	time::Instant,
+	time::SystemTime,
+};
+use time::{Duration, PrimitiveDateTime};
+
+use super::{db::DbData, keys::KeyDb};
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct SecretListData {
+	pub owners: BTreeSet<String>,
+	#[serde(rename = "expireIn")]
+	renew_in: Option<u64>,
+}
+pub fn list_secrets() -> Result<HashMap<String, SecretListData>> {
+	NixEval::new(format!("{}", SECRETS_ATTRIBUTE))
+		.apply(
+			r#"
+				s: (builtins.mapAttrs (n: {owners, expireIn, ...}: {
+					inherit owners expireIn;
+				}) s)
+			"#
+			.into(),
+		)
+		.run_json()
+}
+
+struct ReadableDate(PrimitiveDateTime);
+impl Serialize for ReadableDate {
+	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+	where
+		S: Serializer,
+	{
+		serializer.serialize_str(&self.0.to_string())
+	}
+}
+impl<'de> Deserialize<'de> for ReadableDate {
+	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+	where
+		D: Deserializer<'de>,
+	{
+		Ok(Self(
+			PrimitiveDateTime::parse(String::deserialize(deserializer)?, "%F %T").unwrap(),
+		))
+	}
+}
+impl From<PrimitiveDateTime> for ReadableDate {
+	fn from(d: PrimitiveDateTime) -> Self {
+		Self(d)
+	}
+}
+impl From<ReadableDate> for PrimitiveDateTime {
+	fn from(d: ReadableDate) -> Self {
+		d.0
+	}
+}
+
+#[derive(serde::Serialize, serde::Deserialize)]
+struct SecretData {
+	created_at: ReadableDate,
+	renew_at: Option<ReadableDate>,
+	owners: BTreeSet<String>,
+
+	public_data: BTreeMap<String, String>,
+	private_files: BTreeMap<String, String>,
+}
+impl SecretData {
+	fn should_renew(&self) -> bool {
+		if let Some(renew_at) = &self.renew_at {
+			let now: PrimitiveDateTime = SystemTime::now().into();
+			renew_at.0 <= now
+		} else {
+			false
+		}
+	}
+	fn is_valid(&self, data: &SecretListData) -> bool {
+		self.owners == data.owners
+	}
+}
+
+#[derive(serde::Serialize, serde::Deserialize)]
+struct NixDataValue {
+	data: BTreeMap<String, String>,
+}
+
+#[derive(serde::Serialize, serde::Deserialize)]
+struct NixData {
+	secrets: BTreeMap<String, NixDataValue>,
+}
+
+#[derive(serde::Serialize, serde::Deserialize, Default)]
+pub struct SecretDb {
+	secrets: BTreeMap<String, SecretData>,
+}
+impl DbData for SecretDb {
+	const DB_NAME: &'static str = "secrets";
+}
+
+impl SecretDb {
+	// Secrets are generated on machine running fleet command
+	pub fn generate_secret(
+		&mut self,
+		keys: &KeyDb,
+		secret: &str,
+		data: &SecretListData,
+	) -> Result<()> {
+		let mut rage_keys = String::new();
+		for (i, owner) in data.owners.iter().enumerate() {
+			if i != 0 {
+				rage_keys.push(' ');
+			}
+			rage_keys.push_str("--recipient \"");
+			rage_keys.push_str(&keys.get_host_key(&owner)?);
+			rage_keys.push('"')
+		}
+		let created_at: PrimitiveDateTime = SystemTime::now().into();
+		let renew_at = data
+			.renew_in
+			.map(|hours| created_at + Duration::hours(hours as i64));
+		let built = NixBuild::new(format!("{}.{}.generator", SECRETS_ATTRIBUTE, secret))
+			.env("RAGE_KEYS".into(), rage_keys)
+			.env("IMPURITY_SOURCE".into(), format!("{:?}", Instant::now()))
+			.run()?;
+		let path = built.path().to_owned();
+		let mut secret_data = SecretData {
+			created_at: created_at.into(),
+			renew_at: renew_at.map(|v| v.into()),
+			owners: data.owners.clone(),
+			public_data: BTreeMap::new(),
+			private_files: BTreeMap::new(),
+		};
+		for file in std::fs::read_dir(path)? {
+			let entry = file?;
+			if !entry.file_type()?.is_file() {
+				bail!("Secret generator should produce files, not directories");
+			}
+			let name = entry.file_name();
+			let name = name
+				.to_str()
+				.ok_or(anyhow::anyhow!("file name should be utf-8"))?;
+			let value = String::from_utf8(std::fs::read(entry.path())?)?;
+			if let Some(name) = name.strip_prefix("pub_") {
+				secret_data.public_data.insert(name.into(), value);
+			} else {
+				secret_data.private_files.insert(name.into(), value);
+			}
+		}
+		self.secrets.insert(secret.into(), secret_data);
+		Ok(())
+	}
+	pub fn need_to_generate(&self, secret: &str, data: &SecretListData) -> Result<bool> {
+		let secret = self.secrets.get(secret);
+		if secret.is_none() {
+			return Ok(true);
+		}
+		let secret = secret.unwrap();
+
+		if secret.should_renew() {
+			return Ok(true);
+		}
+
+		if !secret.is_valid(&data) {
+			return Ok(true);
+		}
+
+		Ok(false)
+	}
+	pub fn ensure_generated(
+		&mut self,
+		keys: &KeyDb,
+		secret: &str,
+		data: &SecretListData,
+	) -> Result<()> {
+		if self.need_to_generate(secret, data)? {
+			info!("Generating secret {}", secret);
+			self.generate_secret(keys, secret, data)?;
+		}
+
+		Ok(())
+	}
+	pub fn generate_nix_data(&self) -> Result<String> {
+		let mut out = BTreeMap::new();
+		for (host, secrets) in &self.secrets {
+			out.insert(
+				host.to_owned(),
+				NixDataValue {
+					data: secrets
+						.public_data
+						.clone()
+						.iter()
+						.map(|(k, v)| (k.to_owned(), v.trim().to_owned()))
+						.collect(),
+				},
+			);
+		}
+		Ok(serde_json::to_string(&out)?)
+	}
+
+	pub fn has_secret(&self, secret: &str) -> bool {
+		self.secrets.contains_key(secret)
+	}
+
+	pub fn remove_secret(&mut self, secret: &str) {
+		self.secrets.remove(secret);
+	}
+}
addedsrc/main.rsdiffbeforeafterboth
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,34 @@
+pub mod command;
+
+pub mod cmds;
+pub mod db;
+pub mod nix;
+
+use anyhow::Result;
+use clap::Clap;
+use cmds::{build_systems::BuildSystems, fetch_keys::FetchKeys, generate_secrets::GenerateSecrets};
+
+#[derive(Clap)]
+#[clap(version = "1.0", author = "CertainLach <iam@lach.pw>")]
+enum Opts {
+	/// Fetch encryption (ssh) public keys from remote hosts
+	FetchKeys(FetchKeys),
+	/// Force generation of missing secrets
+	GenerateSecrets(GenerateSecrets),
+	/// Prepare systems for deployments
+	BuildSystems(BuildSystems),
+}
+
+fn main() -> Result<()> {
+	env_logger::Builder::new()
+		.filter_level(log::LevelFilter::Info)
+		.init();
+	let opts = Opts::parse();
+
+	match opts {
+		Opts::FetchKeys(c) => c.run()?,
+		Opts::BuildSystems(c) => c.run()?,
+		Opts::GenerateSecrets(c) => c.run()?,
+	};
+	Ok(())
+}
addedsrc/nix.rsdiffbeforeafterboth
--- /dev/null
+++ b/src/nix.rs
@@ -0,0 +1,172 @@
+use std::{
+	collections::HashMap,
+	ffi::OsStr,
+	path::PathBuf,
+	process::{Command, Stdio},
+};
+
+use anyhow::Result;
+use serde::de::DeserializeOwned;
+
+use crate::command::CommandOutput;
+
+pub const HOSTS_ATTRIBUTE: &str = ".#fleetConfigurations.default.configuredHosts";
+pub const SECRETS_ATTRIBUTE: &str = ".#fleetConfigurations.default.configuredSecrets";
+pub const SYSTEMS_ATTRIBUTE: &str = ".#fleetConfigurations.default.configuredSystems";
+
+pub struct NixCopy {
+	closure: PathBuf,
+}
+impl NixCopy {
+	pub fn new(closure: PathBuf) -> Self {
+		Self { closure }
+	}
+	fn run_internal(&self, f: impl Fn(&mut Command)) -> Result<CommandOutput> {
+		let mut cmd = Command::new("nix");
+		cmd.stderr(Stdio::inherit())
+			.arg("copy")
+			.arg("--substitute-on-destination")
+			.arg(&self.closure);
+		f(&mut cmd);
+
+		let out = cmd.output()?;
+		if !out.status.success() {
+			anyhow::bail!("nix copy failed");
+		}
+		Ok(CommandOutput(out.stdout))
+	}
+	pub fn from(&self, from: impl AsRef<OsStr>) -> Result<()> {
+		let from = from.as_ref();
+		self.run_internal(|cmd| {
+			cmd.arg("--from").arg(from);
+		})?;
+		Ok(())
+	}
+	pub fn to(&self, to: impl AsRef<OsStr>) -> Result<()> {
+		let to = to.as_ref();
+		self.run_internal(|cmd| {
+			cmd.arg("--to").arg(to);
+		})?;
+		Ok(())
+	}
+}
+
+pub struct NixBuild {
+	attribute: String,
+	impure: bool,
+	env: HashMap<String, String>,
+}
+
+impl NixBuild {
+	pub fn new(attribute: String) -> Self {
+		Self {
+			attribute,
+			impure: false,
+			env: HashMap::new(),
+		}
+	}
+	pub fn env(&mut self, name: String, value: String) -> &mut Self {
+		self.impure = true;
+		self.env.insert(name, value);
+		self
+	}
+	pub fn run(&self) -> Result<tempfile::TempDir> {
+		let dir = tempfile::tempdir()?;
+		std::fs::remove_dir(dir.path())?;
+		let mut cmd = Command::new("nix");
+		cmd.stderr(Stdio::inherit())
+			.arg("build")
+			.arg(&self.attribute)
+			.arg("--no-link")
+			.arg("--out-link")
+			.arg(dir.path());
+		if self.impure {
+			cmd.arg("--impure");
+		}
+		if !self.env.is_empty() {
+			cmd.envs(&self.env);
+		}
+
+		let out = cmd.output()?;
+		if !out.status.success() {
+			anyhow::bail!("nix eval failed");
+		}
+		Ok(dir)
+	}
+}
+
+#[derive(Default)]
+pub struct NixEval {
+	attribute: String,
+	impure: bool,
+	apply: Option<String>,
+	env: HashMap<String, String>,
+}
+
+impl NixEval {
+	pub fn new(attribute: String) -> Self {
+		Self {
+			attribute,
+			..Default::default()
+		}
+	}
+	pub fn impure(&mut self) -> &mut Self {
+		self.impure = true;
+		self
+	}
+	/// This is the only and impure way to pass something to flake
+	/// - https://github.com/NixOS/nix/issues/3949
+	/// - https://github.com/NixOS/nixpkgs/issues/101101
+	pub fn env(&mut self, name: String, value: String) -> &mut Self {
+		self.impure = true;
+		self.env.insert(name, value);
+		self
+	}
+	pub fn apply(&mut self, apply: String) -> &mut Self {
+		self.apply = Some(apply);
+		self
+	}
+	fn run_internal(&self, f: impl Fn(&mut Command)) -> Result<CommandOutput> {
+		let mut cmd = Command::new("nix");
+		cmd.stderr(Stdio::inherit())
+			.arg("eval")
+			.arg("--show-trace")
+			.arg(&self.attribute);
+		if let Some(apply) = &self.apply {
+			cmd.arg("--apply").arg(apply);
+		};
+		if self.impure {
+			cmd.arg("--impure");
+		}
+		if !self.env.is_empty() {
+			cmd.envs(&self.env);
+		}
+		f(&mut cmd);
+
+		let out = cmd.output()?;
+		if !out.status.success() {
+			anyhow::bail!("nix eval failed");
+		}
+		Ok(CommandOutput(out.stdout))
+	}
+	pub fn run(&self) -> Result<String> {
+		Ok(self.run_internal(|_cmd| {})?.as_str()?.to_owned())
+	}
+	pub fn run_json<T: DeserializeOwned>(&self) -> Result<T> {
+		Ok(serde_json::from_slice(
+			&self
+				.run_internal(|cmd| {
+					cmd.arg("--json");
+				})?
+				.0,
+		)?)
+	}
+	pub fn run_raw(&self) -> Result<String> {
+		Ok(self
+			.run_internal(|cmd| {
+				cmd.arg("--raw");
+			})?
+			.as_str()?
+			.to_owned())
+	}
+}