From d866c975e1c7964a5e70e71229629cf245b078d1 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Sat, 14 Nov 2020 22:09:44 +0000 Subject: [PATCH] feat: system build --- --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +target +result +example --- /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" --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "fleet" +description = "NixOS configuration management" +version = "0.1.0" +authors = ["Yaroslav Bolyukin "] +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" --- /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) --- /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 +} --- /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; + }; +} --- /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 {} + }; +} --- /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; + }; +} --- /dev/null +++ b/modules/modules.nix @@ -0,0 +1,8 @@ +{ pkgs +, lib +, check ? true +}: +with lib; [ + ./networking/wireguard + ./root.nix +] --- /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; + }; + } + ]; + } + ); + }; +} --- /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 --- /dev/null +++ b/modules/root.nix @@ -0,0 +1,68 @@ +{ lib, ... }: with lib; +let + secret = with types; { + options = { + owners = mkOption { + type = listOf str; + description = '' + List of hosts to encrypt secret for + + Secrets would be decrypted and stored to /run/secrets/$\{name} on owners + ''; + }; + generator = mkOption { + type = types.package; + description = "Derivation to execute for secret generation"; + }; + expireIn = mkOption { + type = nullOr int; + description = "Time in hours, in which this secret should be regenerated"; + default = null; + }; + data = mkOption { + type = attrsOf anything; + description = "Generated secret data, do not set it yourself"; + default = {}; + }; + }; + }; + host = with types; { + options = { + modules = mkOption { + type = listOf anything; + description = "List of nixos modules"; + default = []; + }; + network = mkOption { + type = submodule { + options = { + fleetIp = { + type = str; + description = "Ip which is available to all hosts in fleet"; + }; + }; + }; + description = "Network definition of host"; + }; + system = mkOption { + type = str; + description = "Type of system"; + }; + }; + }; +in +{ + options = with types; { + hosts = mkOption { + type = attrsOf (submodule host); + default = {}; + description = "Configurations of individual hosts"; + }; + secrets = mkOption { + type = attrsOf (submodule secret); + default = {}; + description = "Secrets"; + }; + }; + config = {}; +} --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +hard_tabs = true --- /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(()) + } +} --- /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, + /// 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(()) + } +} --- /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::>(); + 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(()) + } +} --- /dev/null +++ b/src/cmds/mod.rs @@ -0,0 +1,3 @@ +pub mod build_systems; +pub mod fetch_keys; +pub mod generate_secrets; --- /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); +impl CommandOutput { + pub fn into_json<'d, T: Deserialize<'d>>(&'d self) -> Result { + 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(host: impl AsRef, command: I) -> Result +where + I: IntoIterator, + S: AsRef, +{ + 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)) +} --- /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, + _lockfile: lockfile::Lockfile, +} + +pub trait DbData: DeserializeOwned + Serialize + Default { + const DB_NAME: &'static str; + + fn open(db: &Db) -> Result> { + db.db::() + } +} + +#[derive(Clone)] +pub struct Db(Arc>); +impl Db { + pub fn new(root: impl AsRef) -> Result { + 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(&self) -> Result> { + 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 { + db: Db, + root: PathBuf, + path: PathBuf, + data: T, + dirty: Cell, +} + +impl Deref for DbFile { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl DerefMut for DbFile { + fn deref_mut(&mut self) -> &mut Self::Target { + self.dirty.set(true); + &mut self.data + } +} + +impl DbFile { + 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 Drop for DbFile { + fn drop(&mut self) { + let mut db = self.db.0.lock().unwrap(); + self.write().unwrap(); + db.locked_paths.remove(&self.path); + } +} --- /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> { + Ok(NixEval::new(HOSTS_ATTRIBUTE.into()) + .apply("builtins.attrNames".into()) + .run_json()?) +} + +#[derive(Serialize, Deserialize, Default)] +pub struct KeyDb { + host_keys: BTreeMap, +} +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 { + 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); + } +} --- /dev/null +++ b/src/db/mod.rs @@ -0,0 +1,5 @@ +mod db; +pub mod keys; +pub mod secret; + +pub use db::*; --- /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, + #[serde(rename = "expireIn")] + renew_in: Option, +} +pub fn list_secrets() -> Result> { + 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(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.0.to_string()) + } +} +impl<'de> Deserialize<'de> for ReadableDate { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(Self( + PrimitiveDateTime::parse(String::deserialize(deserializer)?, "%F %T").unwrap(), + )) + } +} +impl From for ReadableDate { + fn from(d: PrimitiveDateTime) -> Self { + Self(d) + } +} +impl From for PrimitiveDateTime { + fn from(d: ReadableDate) -> Self { + d.0 + } +} + +#[derive(serde::Serialize, serde::Deserialize)] +struct SecretData { + created_at: ReadableDate, + renew_at: Option, + owners: BTreeSet, + + public_data: BTreeMap, + private_files: BTreeMap, +} +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, +} + +#[derive(serde::Serialize, serde::Deserialize)] +struct NixData { + secrets: BTreeMap, +} + +#[derive(serde::Serialize, serde::Deserialize, Default)] +pub struct SecretDb { + secrets: BTreeMap, +} +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 { + 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 { + 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); + } +} --- /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 ")] +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(()) +} --- /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 { + 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) -> Result<()> { + let from = from.as_ref(); + self.run_internal(|cmd| { + cmd.arg("--from").arg(from); + })?; + Ok(()) + } + pub fn to(&self, to: impl AsRef) -> 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, +} + +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 { + 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, + env: HashMap, +} + +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 { + 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 { + Ok(self.run_internal(|_cmd| {})?.as_str()?.to_owned()) + } + pub fn run_json(&self) -> Result { + Ok(serde_json::from_slice( + &self + .run_internal(|cmd| { + cmd.arg("--json"); + })? + .0, + )?) + } + pub fn run_raw(&self) -> Result { + Ok(self + .run_internal(|cmd| { + cmd.arg("--raw"); + })? + .as_str()? + .to_owned()) + } +} -- gitstuff