Compare commits
27 commits
Author | SHA1 | Date | |
---|---|---|---|
8614bfacbd | |||
736b62528b | |||
9d5639462a | |||
fb25f0c0f4 | |||
fb8ed1ae8c | |||
b98a3beec4 | |||
1690131e4f | |||
21080610a7 | |||
6fda40ab6d | |||
6c36757c51 | |||
5fcfbcb11c | |||
e86793bfdd | |||
445ba1fe1f | |||
81f825b9d3 | |||
0ec62a34d4 | |||
d317dae413 | |||
f37549748c | |||
5d06e08f5b | |||
a077d70598 | |||
1d2e915cea | |||
c2573e32d7 | |||
a581235524 | |||
bca1d20e83 | |||
6d3ff502a9 | |||
8d1d3e514b | |||
55f1229989 | |||
06584cbf47 |
17 changed files with 1425 additions and 448 deletions
|
@ -1,4 +1,4 @@
|
|||
edition = "2021"
|
||||
tab_spaces = 2
|
||||
match_block_trailing_comma = true
|
||||
wrap_comments = true
|
||||
#wrap_comments = true
|
302
Cargo.lock
generated
302
Cargo.lock
generated
|
@ -79,9 +79,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.16"
|
||||
version = "1.2.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
|
||||
checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
|
@ -102,9 +102,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.32"
|
||||
version = "4.5.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83"
|
||||
checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
|
@ -112,9 +112,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.32"
|
||||
version = "4.5.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8"
|
||||
checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
|
@ -146,6 +146,15 @@ version = "1.0.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
|
@ -167,9 +176,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
version = "3.4.5"
|
||||
version = "3.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3"
|
||||
checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"windows-sys",
|
||||
|
@ -222,10 +231,21 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "git2"
|
||||
version = "0.20.0"
|
||||
name = "getrandom"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fda788993cc341f69012feba8bf45c0ba4f3291fcc08e214b4d5a7332d88aff"
|
||||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "git2"
|
||||
version = "0.20.1"
|
||||
source = "git+https://github.com/brysonsteck/git2-rs?branch=certificates#1ca81270e632ac30c6e1367d3032ca3043068b2f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
|
@ -238,9 +258,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.2"
|
||||
version = "0.15.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
||||
checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
|
@ -256,21 +276,22 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
|||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "1.5.0"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
|
||||
checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"potential_utf",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locid"
|
||||
version = "1.5.0"
|
||||
name = "icu_locale_core"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
|
||||
checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"litemap",
|
||||
|
@ -279,31 +300,11 @@ dependencies = [
|
|||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locid_transform"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locid",
|
||||
"icu_locid_transform_data",
|
||||
"icu_provider",
|
||||
"tinystr",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locid_transform_data"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer"
|
||||
version = "1.5.0"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
|
||||
checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_collections",
|
||||
|
@ -311,67 +312,54 @@ dependencies = [
|
|||
"icu_properties",
|
||||
"icu_provider",
|
||||
"smallvec",
|
||||
"utf16_iter",
|
||||
"utf8_iter",
|
||||
"write16",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer_data"
|
||||
version = "1.5.0"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
|
||||
checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties"
|
||||
version = "1.5.1"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
|
||||
checksum = "2549ca8c7241c82f59c80ba2a6f415d931c5b58d24fb8412caa1a1f02c49139a"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_collections",
|
||||
"icu_locid_transform",
|
||||
"icu_locale_core",
|
||||
"icu_properties_data",
|
||||
"icu_provider",
|
||||
"tinystr",
|
||||
"potential_utf",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties_data"
|
||||
version = "1.5.0"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
|
||||
checksum = "8197e866e47b68f8f7d95249e172903bec06004b18b2937f1095d40a0c57de04"
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider"
|
||||
version = "1.5.0"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
|
||||
checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locid",
|
||||
"icu_provider_macros",
|
||||
"icu_locale_core",
|
||||
"stable_deref_trait",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider_macros"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "1.0.3"
|
||||
|
@ -385,9 +373,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "idna_adapter"
|
||||
version = "1.2.0"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
|
||||
checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
|
||||
dependencies = [
|
||||
"icu_normalizer",
|
||||
"icu_properties",
|
||||
|
@ -395,9 +383,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.8.0"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
|
||||
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
|
@ -411,24 +399,24 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
|||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.32"
|
||||
version = "0.1.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
|
||||
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.171"
|
||||
version = "0.2.172"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
version = "0.18.0+1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1a117465e7e1597e8febea8bb0c410f1c7fb93b1e1cddf34363f8390367ffec"
|
||||
version = "0.18.1+1.9.0"
|
||||
source = "git+https://github.com/brysonsteck/git2-rs?branch=certificates#1ca81270e632ac30c6e1367d3032ca3043068b2f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
|
@ -466,15 +454,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.7.5"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
|
||||
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.26"
|
||||
version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
|
@ -484,9 +472,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
|||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.29.0"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
|
@ -496,9 +484,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.1"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
|
@ -508,9 +496,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
|||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.106"
|
||||
version = "0.9.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd"
|
||||
checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
|
@ -531,14 +519,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.94"
|
||||
name = "potential_utf"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
|
||||
checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
|
||||
dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quit"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c090c608233d81bd6b90e718cf34506c60a10e633dff2292c3d1029e798d669b"
|
||||
dependencies = [
|
||||
"quit_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quit_macros"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d4b27a0dd5d08ad7af2d17952fb360ec9c30eeade0b32df7a3c9b099ff37564"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
|
@ -548,14 +560,22 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
||||
|
||||
[[package]]
|
||||
name = "refractr"
|
||||
version = "0.3.0"
|
||||
version = "0.6.2"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"colored",
|
||||
"ctrlc",
|
||||
"git2",
|
||||
"hex",
|
||||
"quit",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"sha2",
|
||||
|
@ -595,9 +615,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.8"
|
||||
version = "0.10.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
|
@ -612,9 +632,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
|||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.14.0"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
|
||||
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
|
@ -630,9 +650,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.100"
|
||||
version = "2.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
|
||||
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -641,9 +661,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.13.1"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
||||
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -652,9 +672,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.7.6"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
|
||||
checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"zerovec",
|
||||
|
@ -662,9 +682,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.20"
|
||||
version = "0.8.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
|
||||
checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
|
@ -674,26 +694,33 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.8"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||
checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.24"
|
||||
version = "0.22.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
|
||||
checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_write",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_write"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.18.0"
|
||||
|
@ -737,12 +764,6 @@ dependencies = [
|
|||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf16_iter"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
|
||||
|
||||
[[package]]
|
||||
name = "utf8_iter"
|
||||
version = "1.0.4"
|
||||
|
@ -767,6 +788,15 @@ version = "0.9.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.2+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.2.8"
|
||||
|
@ -854,30 +884,33 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
|||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.4"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36"
|
||||
checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "write16"
|
||||
version = "1.0.0"
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.5.5"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
|
||||
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.7.5"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
|
||||
checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"stable_deref_trait",
|
||||
|
@ -887,9 +920,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "yoke-derive"
|
||||
version = "0.7.5"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
|
||||
checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -919,10 +952,21 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
version = "0.10.4"
|
||||
name = "zerotrie"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
|
||||
checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428"
|
||||
dependencies = [
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
|
@ -931,9 +975,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zerovec-derive"
|
||||
version = "0.10.3"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
|
||||
checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -1,19 +1,23 @@
|
|||
[package]
|
||||
name = "refractr"
|
||||
version = "0.3.0"
|
||||
license = "MPL-2.0"
|
||||
version = "0.6.2"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.29", features = ["derive"] }
|
||||
colored = "3.0.0"
|
||||
ctrlc = "3.4.5"
|
||||
git2 = "0.20.0"
|
||||
git2 = { git = "https://github.com/brysonsteck/git2-rs", branch = "certificates" }
|
||||
hex = "0.4.3"
|
||||
quit = "2.0.0"
|
||||
serde = "1.0.217"
|
||||
serde_derive = "1.0.217"
|
||||
sha2 = "0.10.8"
|
||||
toml = "0.8.20"
|
||||
|
||||
[target.'cfg(target_family = "windows")'.dependencies]
|
||||
username = "0.2.0"
|
||||
|
||||
[target.'cfg(target_family = "unix")'.dependencies]
|
||||
users = "0.11.0"
|
||||
users = "0.11.0"
|
||||
|
|
12
Dockerfile
12
Dockerfile
|
@ -1,12 +0,0 @@
|
|||
FROM git.brysonsteck.xyz/brysonsteck/refractr:latest
|
||||
|
||||
# replace "path/" with the path containing your refractr configs
|
||||
COPY path/ /etc/refractr
|
||||
|
||||
# use --secret with docker build to specify your ssh key
|
||||
# make sure your configs use the location below
|
||||
RUN --mount=type=secret,id=key,target=/id.pub \
|
||||
cp /id.pub /etc/refractr && chmod 400 /etc/refractr/id.pub
|
||||
|
||||
# add arguments to specify verbosity and configs as needed
|
||||
CMD ["refractr", "-c", "/etc/refractr/config.json"]
|
373
LICENSE
Normal file
373
LICENSE
Normal file
|
@ -0,0 +1,373 @@
|
|||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
30
README.md
30
README.md
|
@ -37,7 +37,16 @@ docker build -t refractr --build-arg UID=$(id -u) --build-arg GID=$(id -g) -f pa
|
|||
|
||||
### Windows
|
||||
|
||||
Coming soon...
|
||||
You can download pre-built binaries from the [Releases page](https://git.brysonsteck.xyz/brysonsteck/refractr/releases) or from my package mirror:
|
||||
|
||||
```powershell
|
||||
# Download the exe with PowerShell
|
||||
Invoke-WebRequest -Uri https://pkg.brysonsteck.xyz/dist/refractr/latest/windows/x86_64/refractr.exe -OutFile refractr.exe
|
||||
# Download the SHA256 checksum
|
||||
Invoke-WebRequest -Uri https://pkg.brysonsteck.xyz/dist/refractr/latest/windows/x86_64/refractr.exe.sha256.txt -OutFile refractr.exe.sha256.txt
|
||||
# Verify hashes match (should return "True")
|
||||
(Get-Content .\refractr.exe.sha256.txt) -eq (Get-FileHash .\refractr.exe).hash
|
||||
```
|
||||
|
||||
### macOS
|
||||
|
||||
|
@ -45,15 +54,26 @@ Coming soon...
|
|||
|
||||
### Build from Source
|
||||
|
||||
To build and run refractr from source, run:
|
||||
To build and run refractr from source, you may need some packages installed on your system. On vanilla installations of FreeBSD, OpenBSD, Windows and macOS, you should be able to compile refractr without issue. On Linux, however, you may need to install additional packages that provide the `pkg-config` binary and OpenSSL headers. Refer to your package manager for the correct package names that you will need to verify are installed.
|
||||
|
||||
```sh
|
||||
# the apt command only applies to Ubuntu/Debian
|
||||
# you will need to install the correct packages using your package manager otherwise
|
||||
apt install git pkg-config libssl-dev
|
||||
# clone the repository
|
||||
git clone https://git.brysonsteck.xyz/brysonsteck/refractr && cd refractr
|
||||
# build the release binary
|
||||
cargo build --release
|
||||
# build the debug binary (not recommended for normal use)
|
||||
cargo build
|
||||
# you can run the binary in the target directory
|
||||
# or move it wherever you wish
|
||||
./target/release/refractr
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
### Couldn't I do this with a shell script and a cron job or (insert alternative method here)?
|
||||
|
||||
Yes.
|
||||
|
||||
### Then why reinvent the wheel?
|
||||
|
||||
Stop asking such asinine questions.
|
||||
|
|
32
build
32
build
|
@ -2,18 +2,32 @@
|
|||
# Create all the different builds for refractr
|
||||
|
||||
version=$(cat Cargo.toml | grep -m1 version | awk -F' ' '{print $3}' | sed 's|"||g')
|
||||
major_version=$(echo $version | awk -F'.' '{print $1}')
|
||||
uid=$(id -u)
|
||||
gid=$(id -g)
|
||||
cargo update
|
||||
cargo clean
|
||||
date=$(date -u --rfc-3339=seconds)
|
||||
cargo=$(which cargo 2> /dev/null)
|
||||
|
||||
if [ -n "$cargo" ]; then
|
||||
cargo update
|
||||
cargo clean
|
||||
fi
|
||||
|
||||
# docker builds
|
||||
docker build -t refractr:$version --build-arg UID=$uid --build-arg GID=$gid -f package.Dockerfile .
|
||||
docker tag refractr:$version refractr:latest
|
||||
docker tag refractr:$version git.brysonsteck.xyz/brysonsteck/refractr:latest
|
||||
docker tag refractr:$version git.brysonsteck.xyz/brysonsteck/refractr:$version
|
||||
test "$1" = "push" && docker push -a git.brysonsteck.xyz/brysonsteck/refractr
|
||||
docker build -t refractr:$version -t refractr:$major_version -t refractr:latest \
|
||||
--build-arg VERSION=$version --build-arg DATE="$date" -f docker/Dockerfile .
|
||||
if [ "$1" = "push" ]; then
|
||||
docker tag refractr:$version git.brysonsteck.xyz/brysonsteck/refractr:latest
|
||||
docker tag refractr:$version git.brysonsteck.xyz/brysonsteck/refractr:$version
|
||||
docker tag refractr:$version git.brysonsteck.xyz/brysonsteck/refractr:$major_version
|
||||
docker push -a git.brysonsteck.xyz/brysonsteck/refractr
|
||||
docker image rm git.brysonsteck.xyz/brysonsteck/refractr:latest
|
||||
docker image rm git.brysonsteck.xyz/brysonsteck/refractr:$version
|
||||
docker image rm git.brysonsteck.xyz/brysonsteck/refractr:$major_version
|
||||
fi
|
||||
|
||||
# rust build
|
||||
cargo build
|
||||
cargo build --release
|
||||
if [ -n "$cargo" ]; then
|
||||
cargo build
|
||||
cargo build --release
|
||||
fi
|
||||
|
|
31
docker/Dockerfile
Normal file
31
docker/Dockerfile
Normal file
|
@ -0,0 +1,31 @@
|
|||
FROM rust:1-alpine AS build
|
||||
|
||||
ENV REFRACTR_DOCKER="true"
|
||||
|
||||
WORKDIR /usr/src/refractr
|
||||
COPY . .
|
||||
|
||||
RUN apk upgrade --no-cache && apk add --no-cache pkgconfig libc-dev openssl-dev openssl openssl-libs-static
|
||||
RUN cargo install --path . && cargo clean
|
||||
|
||||
FROM alpine:3 AS package
|
||||
|
||||
ARG VERSION
|
||||
ARG DATE
|
||||
|
||||
LABEL org.opencontainers.image.title="refractr"
|
||||
LABEL org.opencontainers.image.authors="me@brysonsteck.xyz"
|
||||
LABEL org.opencontainers.image.version="${VERSION}"
|
||||
LABEL org.opencontainers.image.url="https://git.brysonsteck.xyz/brysonsteck/-/packages/container/refractr/${VERSION}"
|
||||
LABEL org.opencontainers.image.source="https://git.brysonsteck.xyz/brysonsteck/refractr"
|
||||
LABEL org.opencontainers.image.licenses="MPL-2.0"
|
||||
LABEL org.opencontainers.image.created="${DATE}"
|
||||
|
||||
RUN apk upgrade --no-cache && apk add --no-cache openssl
|
||||
RUN mkdir /etc/refractr
|
||||
COPY ./docker/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
|
||||
COPY --from=build /usr/src/refractr /usr/src
|
||||
COPY --from=build /usr/local/cargo/bin/refractr /usr/local/bin
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
17
docker/docker-compose.example.yaml
Normal file
17
docker/docker-compose.example.yaml
Normal file
|
@ -0,0 +1,17 @@
|
|||
services:
|
||||
refractr:
|
||||
image: git.brysonsteck.xyz/brysonsteck/refractr:latest
|
||||
environment:
|
||||
# change these to your uid/gid
|
||||
# if omitted, the container will guess
|
||||
- UID=1000
|
||||
- GID=1000
|
||||
volumes:
|
||||
- /home/bryson/configs:/etc/refractr:ro
|
||||
secrets:
|
||||
- ssh_key
|
||||
|
||||
secrets:
|
||||
ssh_key:
|
||||
# available in /run/secrets/ssh_key
|
||||
file: /home/bryson/.ssh/id_rsa
|
38
docker/entrypoint.sh
Executable file
38
docker/entrypoint.sh
Executable file
|
@ -0,0 +1,38 @@
|
|||
#!/bin/sh
|
||||
# Entrypoint file for refractr
|
||||
# Runs refractr with some verbosity and all configs in /etc/refractr
|
||||
#
|
||||
# Copyright 2025 Bryson Steck <me@brysonsteck.xyz>
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
#
|
||||
|
||||
if [ -z "$UID" ]; then
|
||||
set=$(ls -lnd /etc/refractr/ | awk '{print $3}')
|
||||
echo "UID not set! Setting to id $set (owner of /etc/refractr)"
|
||||
export UID=$set
|
||||
fi
|
||||
|
||||
if [ -z "$GID" ]; then
|
||||
set=$(ls -lnd /etc/refractr/ | awk '{print $4}')
|
||||
echo "GID not set! Setting to id $set (group of /etc/refractr)"
|
||||
export GID=$set
|
||||
fi
|
||||
|
||||
if ! ls /etc/refractr/*.toml &> /dev/null; then
|
||||
echo "Failed to find any toml config files for refractr!"
|
||||
echo "Make sure you copied configs or set up volumes correctly."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
args=""
|
||||
for config in /etc/refractr/*.toml; do
|
||||
args="${args}-c '${config}' "
|
||||
done
|
||||
|
||||
addgroup -g $GID dockeruser
|
||||
adduser -u $UID -G $(grep :$GID: /etc/group | awk -F: '{print $1}') -D dockeruser
|
||||
|
||||
su -p - dockeruser -c "refractr -vv ${args}"
|
101
man/refractr.1
Normal file
101
man/refractr.1
Normal file
|
@ -0,0 +1,101 @@
|
|||
.TH REFRACTR 1 "Updated 2025-03-25" "Bryson Steck"
|
||||
.SH NAME
|
||||
refractr \- an automated push mirroring utility for Git repositories
|
||||
.SH SYNOPSIS
|
||||
.B refractr
|
||||
[\fBOPTIONS\fR]
|
||||
.SH DESCRIPTION
|
||||
refractr is an automated push mirroring utility for Git repositories.
|
||||
|
||||
With no options, refractr will attempt to load the config located at /etc/refractr/config.toml and run with that configuration.
|
||||
For details for configuration files, see CONFIGURATION OPTIONS.
|
||||
.SH OPTIONS
|
||||
.sp
|
||||
.TP 0.5i
|
||||
\fB\-h\fR, \fB\-\-help\fR
|
||||
Display a brief help message on the command line.
|
||||
.TP 0.5i
|
||||
\fB\-v\fR, \fB\-\-version\fR
|
||||
Display the version of refractr on the command line.
|
||||
.TP 0.5i
|
||||
\fB\-c\fR, \fB\-\-config\fR [FILE]
|
||||
Specify the absolute or relative path to a configuration file. Multiple configuration files can be specified by using multiple flags.
|
||||
.P
|
||||
.TP 0.5i
|
||||
\fB\-v\fR, \fB\-\-verbose...\fR
|
||||
Specify the level of verbose messages for refractr to output.
|
||||
The functionality of this flag is similar to ssh(1); adding multiple flags will increase the amount of output with specific messages to what refractr is doing.
|
||||
The more flags specified, the more low-level messages will appear.
|
||||
.TP 0.5i
|
||||
\fB\-e\fR, \fB\-\-create\fR
|
||||
Print a full, commented config file to the standard output and exit. You can use this to generate config files that you can fill in and pass to refractr with the -c flag.
|
||||
Information on the values for a refractr configuration can be found in the CONFIGURATION section.
|
||||
.TP 0.5i
|
||||
\fB\-s\fR, \fB\-\-strict\fR
|
||||
Enable strict mode.
|
||||
|
||||
By design, refractr will ignore problems that occur when pushing to remotes and verifying host signatures when using SSH remotes.
|
||||
Some of these errors may include networking issues and a host missing from your SSH known_hosts file, but refractr takes your configurations as
|
||||
implicit trust that the remotes you pull/push from are correct and reachable. This also makes it so refractr will continuously try to
|
||||
update the remotes with changes from the upstream repository to avoid synchronization issues.
|
||||
|
||||
If you wish for refractr to exit on these kinds of errors as a way to know of these issues, you can use this flag to enable "strict" mode.
|
||||
Doing so will make refractr exit instead of continue or loop forever when encountering these problems.
|
||||
.TP 0.5i
|
||||
\fB\-p\fR, \fB\-\-persist\fR
|
||||
Do not recursively delete the working directory as defined in your configuration file(s).
|
||||
This can be helpful for troubleshooting issues with your repository, a potential bug with refractr, or general development.
|
||||
.P
|
||||
.SH CONFIGURATION
|
||||
Below are the values for defining a refractr configuration.
|
||||
.TP 0.5i
|
||||
\fBfrom (string, REQUIRED)\fR
|
||||
The original/upstream repository URI. This field MUST START with "https://" or "ssh://".
|
||||
.TP 0.5i
|
||||
\fBto (array, REQUIRED)\fR
|
||||
The list of remotes you wish to push the repo to as cloned from the "from" field.
|
||||
This field must be SSH remotes and the repository must exist on the remotes.
|
||||
.TP 0.5i
|
||||
\fBbranches (array, REQUIRED)\fR
|
||||
The list of branches from the original/upstream repository you wish to push to the remotes.
|
||||
.TP 0.5i
|
||||
\fBwork_dir (string, OPTIONAL)\fR
|
||||
The path on the filesystem refractr will clone this repository to and work from.
|
||||
If omitted, this field defaults to "/tmp/refractr" on UNIX(-like) systems and "$env:TEMP\\refractr" on Windows.
|
||||
.TP 0.5i
|
||||
\fBpush_tags (boolean, REQUIRED)\fR
|
||||
Push tags pulled from the original/upstream repository to the remotes.
|
||||
.TP 0.5i
|
||||
\fBgit.ssh_identity_file (string, REQUIRED)\fR
|
||||
The path to your SSH private key for authenticating to the remotes specified in the "from" (if using SSH) and "to" fields.
|
||||
|
||||
If using Docker, you will need to copy your private key to your container or build it in an image using secrets in your Dockerfile.
|
||||
.TP 0.5i
|
||||
\fBschedule.enabled (bool, REQUIRED)\fR
|
||||
Enable the schedule feature of refractr. This allows you to regularly pull updates and push them out on a regular basis.
|
||||
.TP 0.5i
|
||||
\fBschedule.interval (integer, DEPENDS ON schedule.enabled)\fR
|
||||
The amount of time in seconds to pull updates and push them out to remotes.
|
||||
To avoid creating unneeded stress on servers, this value must be greater than or equal to 60.
|
||||
|
||||
If "schedule.enabled" is false, refractr will not check for this value.
|
||||
If "scheduled.enabled" is true, refractr will check this value. If it does not exist or is less than 60, refractr will return an error.
|
||||
.SH AUTHORS
|
||||
.B Bryson Steck <me@brysonsteck.xyz> (https://brysonsteck.xyz)
|
||||
.P
|
||||
.SH CONTRIBUTE
|
||||
refractr is free and open source! Please report any bugs in, provide feedback for, or contribute to refractr by visiting the Codeberg repository:
|
||||
.IR https://codeberg.org/brysonsteck/refractr
|
||||
|
||||
The Codeberg repository is proof of refractr at work. An upstream, read-only version of the repository is also available at:
|
||||
.IR https://git.brysonsteck.xyz/brysonsteck/refractr
|
||||
.P
|
||||
.SH LICENSE
|
||||
Copyright 2025 Bryson Steck
|
||||
|
||||
The binaries and source code of refractr is distributed under and subject to the terms of the Mozilla Public License v2 (MPL-2.0).
|
||||
A copy of this license should have been distributed with this software. If you did not receive a copy, you can view the license at:
|
||||
.IR https://www.mozilla.org/en-US/MPL/2.0/
|
||||
|
||||
License violations can be reported by creating an issue in the Codeberg repository under the CONTRIBUTE section.
|
||||
.EFRATR
|
|
@ -1,17 +0,0 @@
|
|||
FROM rust:slim
|
||||
ARG UID="1000"
|
||||
ARG GID="1000"
|
||||
ENV REFRACTR_DOCKER="true"
|
||||
|
||||
WORKDIR /usr/src/refractr
|
||||
COPY . .
|
||||
|
||||
RUN apt update && apt install pkg-config libssl-dev -y
|
||||
RUN cargo install --path .
|
||||
|
||||
RUN groupadd -g $GID refractr
|
||||
RUN useradd -u $UID -g $GID -mN refractr
|
||||
RUN mkdir /etc/refractr && chown refractr:refractr /etc/refractr
|
||||
USER refractr
|
||||
|
||||
CMD ["refractr"]
|
|
@ -1,22 +1,51 @@
|
|||
/*
|
||||
* Copyright 2025 Bryson Steck <me@brysonsteck.xyz>
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! freak_out {
|
||||
($msg:expr) => {
|
||||
return Err($msg)
|
||||
};
|
||||
use colored::Colorize;
|
||||
|
||||
pub enum ExitCode {
|
||||
ParseError = 2,
|
||||
FilesystemError = 3,
|
||||
RepositoryError = 4,
|
||||
RemoteError = 5,
|
||||
PushError = 6,
|
||||
FetchError = 7,
|
||||
ConfigError = 8,
|
||||
HaltError = 130,
|
||||
}
|
||||
|
||||
pub struct ReturnData {
|
||||
pub code: ExitCode,
|
||||
pub msg: String,
|
||||
}
|
||||
|
||||
pub fn error_quit(msg: String, code: ExitCode) {
|
||||
eprintln!("{} {}", "error:".red().bold(), msg);
|
||||
quit::with_code(code as u8)
|
||||
}
|
||||
|
||||
pub fn error(msg: String) {
|
||||
eprintln!("{} {}", "error:".red().bold(), msg);
|
||||
}
|
||||
|
||||
pub fn warning(msg: String) {
|
||||
eprintln!("warning: {}", msg)
|
||||
eprintln!("{} {}", "warning:".yellow().bold(), msg)
|
||||
}
|
||||
|
||||
pub fn verbose(level: u8, msg_lvl: u8, msg: String) {
|
||||
if level < msg_lvl { return };
|
||||
if level < msg_lvl {
|
||||
return;
|
||||
};
|
||||
let mut prefix = String::new();
|
||||
for _ in 0..msg_lvl {
|
||||
prefix += "=";
|
||||
}
|
||||
|
||||
prefix += "> ";
|
||||
eprintln!("{}{}", prefix, msg);
|
||||
eprintln!("{}{}", prefix.purple().bold(), msg);
|
||||
}
|
||||
|
|
191
src/config.rs
191
src/config.rs
|
@ -1,3 +1,11 @@
|
|||
/*
|
||||
* Copyright 2025 Bryson Steck <me@brysonsteck.xyz>
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
use crate::common;
|
||||
use crate::refractr::Refractr;
|
||||
|
||||
|
@ -12,7 +20,7 @@ use toml;
|
|||
pub struct ConfigFile {
|
||||
pub path: String,
|
||||
pub file: Metadata,
|
||||
pub config: Config
|
||||
pub config: Config,
|
||||
}
|
||||
|
||||
impl fmt::Display for ConfigFile {
|
||||
|
@ -32,21 +40,24 @@ impl fmt::Display for ConfigFile {
|
|||
if i < self.config.to.len() - 1 {
|
||||
to_list.push_str(", ");
|
||||
}
|
||||
};
|
||||
}
|
||||
to_list.push(']');
|
||||
|
||||
let work_dir_path = match &self.config.work_dir {
|
||||
None => {
|
||||
if cfg!(windows) {
|
||||
format!("Using default \"{}\\refractr\"", match env::var("TEMP") {
|
||||
Ok(val) => val,
|
||||
Err(_) => format!("This shouldn't happen!")
|
||||
})
|
||||
format!(
|
||||
"Using default \"{}\\refractr\"",
|
||||
match env::var("TEMP") {
|
||||
Ok(val) => val,
|
||||
Err(_) => format!("This shouldn't happen!"),
|
||||
}
|
||||
)
|
||||
} else {
|
||||
format!("Using default: /tmp/refractr")
|
||||
}
|
||||
},
|
||||
Some(path) => format!("{}", path)
|
||||
Some(path) => format!("{}", path),
|
||||
};
|
||||
|
||||
let schedule_interval = match self.config.schedule.interval {
|
||||
|
@ -57,10 +68,15 @@ impl fmt::Display for ConfigFile {
|
|||
String::from("This shouldn't happen!\n")
|
||||
}
|
||||
},
|
||||
Some(int) => format!("\n Scheduled interval in seconds: {}", int.to_string())
|
||||
Some(int) => format!(
|
||||
"\n Scheduled interval in seconds: {}",
|
||||
int.to_string()
|
||||
),
|
||||
};
|
||||
|
||||
write!(f, "Config file: \"{}\"\n \
|
||||
write!(
|
||||
f,
|
||||
"Config file: \"{}\"\n \
|
||||
Is a file: {}\n \
|
||||
Read only: {}\n \
|
||||
Configuration:\n \
|
||||
|
@ -70,74 +86,146 @@ impl fmt::Display for ConfigFile {
|
|||
Working directory: {}\n \
|
||||
SSH key for pushing clone: {}\n \
|
||||
Schedule enabled: {}\
|
||||
{}"
|
||||
, self.path, self.file.is_file(), self.file.permissions().readonly(), self.config.from
|
||||
, to_list, branches_list, work_dir_path, self.config.git.ssh_identity_file, self.config.schedule.enabled
|
||||
, schedule_interval)
|
||||
{}",
|
||||
self.path,
|
||||
self.file.is_file(),
|
||||
self.file.permissions().readonly(),
|
||||
self.config.from,
|
||||
to_list,
|
||||
branches_list,
|
||||
work_dir_path,
|
||||
self.config.git.ssh_identity_file,
|
||||
self.config.schedule.enabled,
|
||||
schedule_interval
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(Deserialize)]
|
||||
#[derive(PartialEq, Deserialize)]
|
||||
pub struct Config {
|
||||
pub from: String,
|
||||
pub to: Vec<String>,
|
||||
pub branches: Vec<String>,
|
||||
pub work_dir: Option<String>,
|
||||
pub push_tags: bool,
|
||||
pub git: Git,
|
||||
pub schedule: Schedule
|
||||
pub schedule: Schedule,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(Deserialize)]
|
||||
#[derive(PartialEq, Deserialize)]
|
||||
pub struct Git {
|
||||
pub ssh_identity_file: String,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(Deserialize)]
|
||||
#[derive(PartialEq, Deserialize)]
|
||||
pub struct Schedule {
|
||||
pub enabled: bool,
|
||||
pub interval: Option<i32>,
|
||||
}
|
||||
|
||||
pub fn get_work_dir(work_dir: &Option<String>) -> String {
|
||||
match work_dir {
|
||||
None => {
|
||||
if cfg!(windows) {
|
||||
format!("{}\\refractr", env::var("TEMP").unwrap())
|
||||
} else {
|
||||
format!("/tmp/refractr")
|
||||
}
|
||||
},
|
||||
Some(path) => path.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_config(paths: Vec<PathBuf>, refractr: &Refractr) -> Result<Vec<ConfigFile>, String> {
|
||||
let mut config_files: Vec<ConfigFile> = vec![];
|
||||
for path in paths {
|
||||
common::verbose(refractr.verbose, 1, format!("Reading config file: \"{}\"", String::from(path.to_string_lossy())));
|
||||
common::verbose(
|
||||
refractr.verbose,
|
||||
1,
|
||||
format!(
|
||||
"Reading config file: \"{}\"",
|
||||
String::from(path.to_string_lossy())
|
||||
),
|
||||
);
|
||||
let mut data = String::new();
|
||||
let mut file = match File::open(path.as_path()) {
|
||||
Err(e) => return Err(format!("refractr: unable to open {}: {}", path.as_path().display(), e)),
|
||||
Ok(file) => file
|
||||
Err(e) => {
|
||||
return Err(format!(
|
||||
"unable to open {}: {}",
|
||||
path.as_path().display(),
|
||||
e
|
||||
))
|
||||
},
|
||||
Ok(file) => file,
|
||||
};
|
||||
|
||||
if let Err(e) = file.read_to_string(&mut data) {
|
||||
return Err(format!("refractr: unable to read {}: {}", path.as_path().display(), e))
|
||||
return Err(format!(
|
||||
"unable to read {}: {}",
|
||||
path.as_path().display(),
|
||||
e
|
||||
));
|
||||
}
|
||||
|
||||
let config: Config = match toml::from_str(&data) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
return Err(format!(
|
||||
"issues parsing toml file {}: {}",
|
||||
path.as_path().display(),
|
||||
e
|
||||
))
|
||||
},
|
||||
};
|
||||
|
||||
let config_file = ConfigFile {
|
||||
path: match fs::canonicalize(&path) {
|
||||
Err(_) => return Err(format!("refractr: cannot get absolute path of config file: {}", path.as_path().display())),
|
||||
Ok(abs) => abs.to_string_lossy().to_string()
|
||||
Err(_) => {
|
||||
return Err(format!(
|
||||
"cannot get absolute path of config file: {}",
|
||||
path.as_path().display()
|
||||
))
|
||||
},
|
||||
Ok(abs) => abs.to_string_lossy().to_string(),
|
||||
},
|
||||
file: match fs::metadata(&path) {
|
||||
Err(_) => return Err(format!("refractr: cannot obtain metadata for config file: {}", path.as_path().display())),
|
||||
Ok(metadata) => metadata
|
||||
Err(_) => {
|
||||
return Err(format!(
|
||||
"cannot obtain metadata for config file: {}",
|
||||
path.as_path().display()
|
||||
))
|
||||
},
|
||||
Ok(metadata) => metadata,
|
||||
},
|
||||
config: match verify_config(&config) {
|
||||
Err(e) => {
|
||||
return Err(format!(
|
||||
"invalid config {}: {}",
|
||||
path.as_path().display(),
|
||||
e
|
||||
))
|
||||
},
|
||||
Ok(_) => config,
|
||||
},
|
||||
config: verify_config(toml::from_str(&data).unwrap())
|
||||
};
|
||||
|
||||
let mut dup = false;
|
||||
for i in &config_files {
|
||||
if i.path == config_file.path {
|
||||
common::warning(format!("skipping config file \"{}\" as it was already read", path.as_path().display()));
|
||||
common::warning(format!(
|
||||
"skipping config file \"{}\" as it was already read",
|
||||
path.as_path().display()
|
||||
));
|
||||
dup = true;
|
||||
break;
|
||||
} else if i.config == config_file.config {
|
||||
common::warning(format!("config files \"{}\" and \"{}\" appear to have the same config", i.path, config_file.path));
|
||||
common::warning(format!(
|
||||
"config files \"{}\" and \"{}\" appear to have the same config",
|
||||
i.path, config_file.path
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if !dup {
|
||||
config_files.push(config_file);
|
||||
}
|
||||
|
@ -146,25 +234,32 @@ pub fn read_config(paths: Vec<PathBuf>, refractr: &Refractr) -> Result<Vec<Confi
|
|||
return Ok(config_files);
|
||||
}
|
||||
|
||||
fn verify_config(config: Config) -> Config {
|
||||
fn verify_config(config: &Config) -> Result<(), String> {
|
||||
if config.schedule.enabled {
|
||||
assert_ne!(config.schedule.interval, None);
|
||||
assert!(config.schedule.interval.unwrap() >= 60);
|
||||
match config.schedule.interval {
|
||||
Some(i) => {
|
||||
if i < 60 {
|
||||
return Err(format!("schedule is enabled, but less than 60"));
|
||||
}
|
||||
},
|
||||
None => return Err(format!("schedule is enabled, but no interval was defined")),
|
||||
}
|
||||
}
|
||||
|
||||
assert_ne!("", match &config.work_dir {
|
||||
Some(path) => format!("{}", path),
|
||||
None => {
|
||||
if cfg!(windows) {
|
||||
match env::var("TEMP") {
|
||||
Ok(val) => val,
|
||||
Err(_) => format!("")
|
||||
}
|
||||
} else {
|
||||
format!("/tmp/refractr")
|
||||
if let None = &config.work_dir {
|
||||
if cfg!(windows) {
|
||||
if let Err(e) = env::var("TEMP") {
|
||||
return Err(format!("cannot determine the default temp dir: {}", e));
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return config;
|
||||
if !&config.from.starts_with("ssh://")
|
||||
&& !&config.from.starts_with("https://")
|
||||
&& !&config.from.starts_with("http://")
|
||||
{
|
||||
return Err(format!("'from' value does not use a supported protocol"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -2,40 +2,37 @@
|
|||
# Example configuration for refractr
|
||||
# ***********************************
|
||||
|
||||
[config]
|
||||
# The "from" field is a string of the original/main repository you want to pull
|
||||
# This field is REQUIRED
|
||||
# This field is REQUIRED and MUST start with "https://" or "ssh://"
|
||||
#from = "https://git.brysonsteck.xyz/brysonsteck/refractr"
|
||||
|
||||
# The "to" field is a list of strings of the repositories you want to push the repo
|
||||
# from the "from" field to. These repositories must exist on the remote server
|
||||
# It's recommended that you use SSH to avoid HTTPS authentication
|
||||
# This field is REQUIRED
|
||||
# The "to" field is a list of strings of the remotes you want to push the repo from the "from" field to
|
||||
# These repositories must exist on the remote server
|
||||
# This field is REQUIRED and MUST be SSH remotes
|
||||
#to = ["git@codeberg.org:brysonsteck/refractr.git", "git@github.com:brysonsteck/refractr.git"]
|
||||
|
||||
# The "branches" field is a list of branches you want to mirror from the original
|
||||
# repository.
|
||||
# The "branches" field is a list of branches you want to mirror from the original repository
|
||||
# This field is REQUIRED
|
||||
#branches = ["master"]
|
||||
|
||||
# The "work_dir" field is where refractr will write the clone to
|
||||
# This field is OPTIONAL, will default to /tmp/refractr on *NIX and
|
||||
# $env:TEMP\refractr on Windows
|
||||
# This field is OPTIONAL, will default to /tmp/refractr on *NIX and $env:TEMP\refractr on Windows
|
||||
#work_dir = /tmp/refractr
|
||||
|
||||
[git]
|
||||
# The "ssh_identity_file" is your private SSH key that you will use to push updates
|
||||
# from the original repository.
|
||||
# This field is REQUIRED if you are using SSH to push, otherwise OPTIONAL
|
||||
# The "ssh_identity_file" is your private SSH key that you will use to push updates from the original
|
||||
# repository and, if your "from" field is an SSH remote, for cloning the original repository
|
||||
# If you are running from Docker, you will need to copy your private key to your container or image
|
||||
# This field is REQUIRED
|
||||
#ssh_identity_file = "/path/to/.ssh/id_rsa"
|
||||
|
||||
[schedule]
|
||||
# The "enabled" field turns on the schedule feature of refractr.
|
||||
# The "enabled" field turns on the schedule feature of refractr
|
||||
# This field is REQUIRED.
|
||||
#enabled = false
|
||||
|
||||
# The "interval" field is the amount of seconds refractor will wait before
|
||||
# pulling updates from the original repository if the schedule feature is enabled.
|
||||
# This field is REQUIRED if "enabled" is set to true, otherwise OPTIONAL
|
||||
# To avoid overwhelming servers, this is set to only accept values of >=60
|
||||
# The "interval" field is the amount of seconds refractor will wait before pulling updates from the
|
||||
# original repository if the schedule feature is enabled
|
||||
# To avoid creating a DoS attack, this is set to only accept values of >=60
|
||||
# This field is REQUIRED if "enabled" is set to true, UNUSED if false
|
||||
#interval = 300
|
||||
|
|
107
src/main.rs
107
src/main.rs
|
@ -1,3 +1,11 @@
|
|||
/*
|
||||
* Copyright 2025 Bryson Steck <me@brysonsteck.xyz>
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
mod common;
|
||||
mod config;
|
||||
mod refractr;
|
||||
|
@ -7,10 +15,12 @@ use crate::refractr::Refractr;
|
|||
use clap::Parser;
|
||||
use std::path::PathBuf;
|
||||
use std::process;
|
||||
#[cfg(target_family = "unix")]
|
||||
use users;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
#[cfg(target_family = "windows")]
|
||||
use username;
|
||||
#[cfg(target_family = "unix")]
|
||||
use users;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "refractr")]
|
||||
|
@ -24,11 +34,26 @@ struct Args {
|
|||
#[arg(short, long, help = "Specify the level of verbosity", action = clap::ArgAction::Count)]
|
||||
verbose: u8,
|
||||
|
||||
#[arg(short = 'e', long, help = "Output a full, commented config file and exit")]
|
||||
#[arg(
|
||||
short = 'e',
|
||||
long,
|
||||
help = "Output a full, commented config file and exit"
|
||||
)]
|
||||
create: bool,
|
||||
|
||||
#[arg(short = 's', long, help = "Exit on push errors instead of ignoring")]
|
||||
#[arg(
|
||||
short = 's',
|
||||
long,
|
||||
help = "Exit on push and unknown host errors instead of ignoring them"
|
||||
)]
|
||||
strict: bool,
|
||||
|
||||
#[arg(
|
||||
short = 'p',
|
||||
long,
|
||||
help = "Do not clean the working directories specified in config(s)"
|
||||
)]
|
||||
persist: bool,
|
||||
}
|
||||
|
||||
fn get_config_default() -> &'static str {
|
||||
|
@ -36,10 +61,16 @@ fn get_config_default() -> &'static str {
|
|||
return "C:\\ProgramData\\refractr\\config.toml";
|
||||
#[cfg(target_os = "linux")]
|
||||
return "/etc/refractr/config.toml";
|
||||
#[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))]
|
||||
#[cfg(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "macos"
|
||||
))]
|
||||
return "/usr/local/etc/refractr/config.toml";
|
||||
}
|
||||
|
||||
#[quit::main]
|
||||
fn main() -> Result<(), String> {
|
||||
let args = Args::parse();
|
||||
let refractr = Refractr {
|
||||
|
@ -48,12 +79,14 @@ fn main() -> Result<(), String> {
|
|||
"true" => true,
|
||||
_ => false,
|
||||
},
|
||||
None => false
|
||||
None => false,
|
||||
},
|
||||
persist: args.persist,
|
||||
pid: process::id(),
|
||||
strict: args.strict,
|
||||
unix: cfg!(unix),
|
||||
verbose: args.verbose
|
||||
verbose: args.verbose,
|
||||
run: Arc::new(AtomicBool::new(true))
|
||||
};
|
||||
|
||||
// warn to avoid root/admin
|
||||
|
@ -73,29 +106,55 @@ fn main() -> Result<(), String> {
|
|||
Err(_) => common::warning(format!("failed to get process username")),
|
||||
}
|
||||
|
||||
common::verbose(refractr.verbose, 1, format!("Level {} verbosity enabled", refractr.verbose.to_string()));
|
||||
common::verbose(
|
||||
refractr.verbose,
|
||||
1,
|
||||
format!(
|
||||
"refractr started with level {} verbosity enabled",
|
||||
refractr.verbose.to_string()
|
||||
),
|
||||
);
|
||||
common::verbose(refractr.verbose, 3, format!("Process ID: {}", refractr.pid));
|
||||
common::verbose(refractr.verbose, 3, format!("Running in Docker: {}", refractr.docker));
|
||||
common::verbose(
|
||||
refractr.verbose,
|
||||
3,
|
||||
format!("Running in Docker: {}", refractr.docker),
|
||||
);
|
||||
common::verbose(
|
||||
refractr.verbose,
|
||||
3,
|
||||
format!("System is UNIX(-like): {}", refractr.unix),
|
||||
);
|
||||
common::verbose(refractr.verbose, 2, format!("Checking for create flag"));
|
||||
if args.create {
|
||||
common::verbose(refractr.verbose, 3, format!("Printing sample config"));
|
||||
let example = include_str!("example/config.toml");
|
||||
println!("{}", example);
|
||||
Ok(())
|
||||
} else {
|
||||
let cfgs = match config::read_config(args.config, &refractr) {
|
||||
Ok(cfgs) => cfgs,
|
||||
Err(e) => freak_out!(e)
|
||||
};
|
||||
if refractr.verbose >= 2 {
|
||||
// no need to loop over configs if verbose is not at the correct level
|
||||
for i in &cfgs {
|
||||
common::verbose(refractr.verbose, 2, format!("{}", i));
|
||||
}
|
||||
}
|
||||
|
||||
common::verbose(refractr.verbose, 1, format!("Config file(s) read successfully"));
|
||||
refractr.run(cfgs)
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut cfgs = vec![];
|
||||
match config::read_config(args.config, &refractr) {
|
||||
Ok(c) => cfgs = c,
|
||||
Err(e) => common::error_quit(format!("{}", e), common::ExitCode::ConfigError),
|
||||
};
|
||||
|
||||
if refractr.verbose >= 2 {
|
||||
// no need to loop over configs if verbose is not at the correct level
|
||||
for i in &cfgs {
|
||||
common::verbose(refractr.verbose, 2, format!("{}", i));
|
||||
}
|
||||
}
|
||||
|
||||
common::verbose(
|
||||
refractr.verbose,
|
||||
1,
|
||||
format!("Config file(s) read successfully"),
|
||||
);
|
||||
match refractr.run(cfgs) {
|
||||
Ok(_) => (),
|
||||
Err(e) => common::error_quit(format!("{}", e.msg), e.code),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
532
src/refractr.rs
532
src/refractr.rs
|
@ -1,56 +1,100 @@
|
|||
use crate::freak_out;
|
||||
use crate::common;
|
||||
use crate::config::{Config, ConfigFile};
|
||||
/*
|
||||
* Copyright 2025 Bryson Steck <me@brysonsteck.xyz>
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
use git2::{FetchOptions, CertificateCheckStatus, Cred, PushOptions, RemoteCallbacks, Repository};
|
||||
use crate::common::{self, ExitCode, ReturnData};
|
||||
use crate::config::{self, Config, ConfigFile};
|
||||
|
||||
use git2::build::RepoBuilder;
|
||||
use git2::string_array::StringArray;
|
||||
use git2::{CertificateCheckStatus, Cred, FetchOptions, PushOptions, RemoteCallbacks, Repository};
|
||||
use git2::{Error, ErrorCode};
|
||||
use git2::build::{RepoBuilder, CheckoutBuilder};
|
||||
use hex;
|
||||
use sha2::{Sha256, Digest};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::fs::{self, remove_dir_all};
|
||||
use std::path::{Path, PathBuf, MAIN_SEPARATOR_STR};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time;
|
||||
|
||||
pub struct Refractr {
|
||||
pub docker: bool,
|
||||
pub persist: bool,
|
||||
pub pid: u32,
|
||||
pub strict: bool,
|
||||
pub unix: bool,
|
||||
pub verbose: u8
|
||||
pub verbose: u8,
|
||||
pub run: Arc<AtomicBool>
|
||||
}
|
||||
|
||||
struct OpenedRepository {
|
||||
repo: Repository,
|
||||
path: String,
|
||||
remotes: Vec<String>,
|
||||
cfg: Config,
|
||||
ssh: bool,
|
||||
}
|
||||
|
||||
impl Refractr {
|
||||
fn set_up_work_dir(&self, work_dir: PathBuf) -> Result<String, String> {
|
||||
fn set_up_work_dir(&self, work_dir: PathBuf) -> Result<(), String> {
|
||||
if let Err(e) = fs::create_dir_all(&work_dir) {
|
||||
freak_out!(format!("could not create working directory: {}: {}", work_dir.to_string_lossy().to_string(), e))
|
||||
return Err(format!(
|
||||
"could not create working directory: {}: {}",
|
||||
work_dir.to_string_lossy().to_string(),
|
||||
e
|
||||
));
|
||||
}
|
||||
Ok(work_dir.to_string_lossy().to_string())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_refs(&self, branches: &Vec<String>) -> Vec<String> {
|
||||
fn set_up_ssh(&self, key_path: String, strict: bool) -> Result<RemoteCallbacks, String> {
|
||||
let mut cb = RemoteCallbacks::new();
|
||||
cb.credentials(move |_, _, _| Cred::ssh_key("git", None, Path::new(&key_path), None));
|
||||
cb.certificate_check(move |cert, url| {
|
||||
let sha256 = hex::encode(cert.as_hostkey().unwrap().hash_sha256().unwrap().to_vec());
|
||||
if strict {
|
||||
common::error_quit(
|
||||
format!(
|
||||
"unknown host {} with sha256 host key {}, exiting",
|
||||
url, sha256
|
||||
),
|
||||
ExitCode::RemoteError,
|
||||
);
|
||||
} else {
|
||||
common::warning(format!(
|
||||
"unknown host {} with sha256 host key {}, implicitly trusting",
|
||||
url, sha256
|
||||
));
|
||||
common::warning(format!(
|
||||
"to suppress this warning in the future, add this host to your known_hosts file"
|
||||
));
|
||||
}
|
||||
Ok(CertificateCheckStatus::CertificateOk)
|
||||
});
|
||||
Ok(cb)
|
||||
}
|
||||
|
||||
fn get_refs(&self, branches: &Vec<String>, tags: Option<StringArray>) -> Vec<String> {
|
||||
let mut refs_branches = Vec::new();
|
||||
for branch in branches {
|
||||
refs_branches.push(format!("refs/heads/{}", branch));
|
||||
}
|
||||
if let Some(tags) = tags {
|
||||
for tag in &tags {
|
||||
refs_branches.push(format!("refs/tags/{}", tag.unwrap()))
|
||||
}
|
||||
}
|
||||
refs_branches
|
||||
}
|
||||
|
||||
fn fast_forward(&self, repo_dir: &str, branches: &Vec<String>) -> Result<(), Error> {
|
||||
let repo = Repository::open(repo_dir)?;
|
||||
|
||||
common::verbose(self.verbose, 2, format!("Pulling origin"));
|
||||
repo.find_remote("origin")?.fetch(&branches, None, None)?;
|
||||
fn fast_forward(&self, repo: &Repository, branches: &Vec<String>) -> Result<(), Error> {
|
||||
common::verbose(self.verbose, 2, format!("Fast forwarding repo"));
|
||||
let mut fo = FetchOptions::new();
|
||||
fo.download_tags(git2::AutotagOption::All);
|
||||
|
||||
for branch in branches {
|
||||
let refname = format!("refs/remotes/origin/{}", branch);
|
||||
|
@ -61,49 +105,51 @@ impl Refractr {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn fetch(&self, repo: &Repository, branches: &Vec<String>, ssh: Option<&String>) -> Result<(), Error> {
|
||||
match ssh {
|
||||
Some(key) => {
|
||||
let mut cb = RemoteCallbacks::new();
|
||||
let mut fo = FetchOptions::new();
|
||||
cb.credentials(move |_,_,_| Cred::ssh_key(
|
||||
"git",
|
||||
None,
|
||||
Path::new(&key),
|
||||
None));
|
||||
cb.certificate_check(|cert, url| {
|
||||
let mut sha256 = String::new();
|
||||
for i in cert.as_hostkey().unwrap().hash_sha256().unwrap().to_vec() {
|
||||
sha256.push_str(&hex::encode(i.to_string()));
|
||||
}
|
||||
common::warning(
|
||||
format!("implicitly trusting unknown host {} with sha256 host key {}",
|
||||
url,
|
||||
hex::encode(cert.as_hostkey().unwrap().hash_sha256().unwrap().to_vec())));
|
||||
common::warning(
|
||||
format!("to ignore this error in the future, add this host to your known_hosts file"));
|
||||
Ok(CertificateCheckStatus::CertificateOk)
|
||||
});
|
||||
fo.download_tags(git2::AutotagOption::All);
|
||||
fo.remote_callbacks(cb);
|
||||
repo.find_remote("origin")?.fetch(&branches, Some(&mut fo), None)?;
|
||||
},
|
||||
None => repo.find_remote("origin")?.fetch(&branches, None, None)?
|
||||
};
|
||||
fn fetch(
|
||||
&self,
|
||||
repo: &Repository,
|
||||
branches: &Vec<String>,
|
||||
ssh: bool,
|
||||
ssh_key: &String,
|
||||
strict: bool,
|
||||
) -> Result<(), Error> {
|
||||
common::verbose(self.verbose, 2, format!("Fetching repo"));
|
||||
let mut fo = FetchOptions::new();
|
||||
if ssh {
|
||||
match self.set_up_ssh(ssh_key.clone(), strict.clone()) {
|
||||
Ok(cb) => {
|
||||
fo.remote_callbacks(cb);
|
||||
()
|
||||
},
|
||||
Err(e) => common::error_quit(
|
||||
format!("error setting up ssh: {}", e),
|
||||
ExitCode::ConfigError,
|
||||
),
|
||||
};
|
||||
}
|
||||
fo.download_tags(git2::AutotagOption::All);
|
||||
repo
|
||||
.find_remote("origin")?
|
||||
.fetch(&branches, Some(&mut fo), None)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_up_refs(&self, repo: &Repository, branches: &Vec<String>) -> Result<(), Error> {
|
||||
for branch in branches {
|
||||
let mut fetch_head = repo.find_reference(format!("refs/remotes/origin/{}", branch).as_str())?;
|
||||
let mut fetch_head =
|
||||
repo.find_reference(format!("refs/remotes/origin/{}", branch).as_str())?;
|
||||
fetch_head.rename(format!("refs/heads/{}", branch).as_str(), true, "")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn make_remotes<'a> (&self, repo: &'a Repository, cfg: &ConfigFile) -> Result<Vec<String>, String> {
|
||||
fn make_remotes<'a>(
|
||||
&self,
|
||||
repo: &'a Repository,
|
||||
cfg: &ConfigFile,
|
||||
) -> Result<Vec<String>, String> {
|
||||
// create remotes for each "to" repo
|
||||
let mut remote_list = Vec::new();
|
||||
for to in &cfg.config.to {
|
||||
|
@ -113,7 +159,8 @@ impl Refractr {
|
|||
common::verbose(
|
||||
self.verbose,
|
||||
2,
|
||||
format!("Attempting to create remote {} for url {}", remote_id, to));
|
||||
format!("Attempting to create remote {} for url {}", remote_id, to),
|
||||
);
|
||||
match repo.remote(remote_id.as_str(), to) {
|
||||
Ok(_) => remote_list.push(remote_id),
|
||||
Err(e) => {
|
||||
|
@ -121,115 +168,153 @@ impl Refractr {
|
|||
common::warning(format!("remote {} already exists, skipping", remote_id));
|
||||
remote_list.push(remote_id)
|
||||
} else {
|
||||
freak_out!(format!("failed to create remote: {}", e));
|
||||
return Err(format!("failed to create remote: {}", e));
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(remote_list)
|
||||
}
|
||||
|
||||
fn push_remotes(&self, cfg: &Config, repo: &Repository, remote_list: &Vec<String>) -> Result<(), String> {
|
||||
fn push_remotes(
|
||||
&self,
|
||||
cfg: &Config,
|
||||
repo: &Repository,
|
||||
remote_list: &Vec<String>,
|
||||
) -> Result<(), String> {
|
||||
for id in remote_list {
|
||||
let mut remote = repo.find_remote(&id).unwrap();
|
||||
common::verbose(
|
||||
self.verbose,
|
||||
1,
|
||||
format!("Pushing to remote: {}", remote.url().unwrap()));
|
||||
let mut callbacks = RemoteCallbacks::new();
|
||||
callbacks.credentials(|_,_,_| Cred::ssh_key(
|
||||
"git",
|
||||
None,
|
||||
&Path::new(&cfg.git.ssh_identity_file),
|
||||
None));
|
||||
callbacks.certificate_check(|cert, url| {
|
||||
let mut sha256 = String::new();
|
||||
for i in cert.as_hostkey().unwrap().hash_sha256().unwrap().to_vec() {
|
||||
sha256.push_str(&hex::encode(i.to_string()));
|
||||
}
|
||||
common::warning(
|
||||
format!("implicitly trusting unknown host {} with sha256 host key {}",
|
||||
url,
|
||||
hex::encode(cert.as_hostkey().unwrap().hash_sha256().unwrap().to_vec())));
|
||||
common::warning(
|
||||
format!("to ignore this error in the future, add this host to your known_hosts file"));
|
||||
Ok(CertificateCheckStatus::CertificateOk)
|
||||
});
|
||||
let mut push_options = PushOptions::new();
|
||||
push_options.remote_callbacks(callbacks);
|
||||
self.verbose,
|
||||
1,
|
||||
format!("Pushing to remote: {}", remote.url().unwrap()),
|
||||
);
|
||||
let mut po = PushOptions::new();
|
||||
match self.set_up_ssh(cfg.git.ssh_identity_file.clone(), self.strict.clone()) {
|
||||
Ok(cb) => {
|
||||
po.remote_callbacks(cb);
|
||||
()
|
||||
},
|
||||
Err(e) => common::error_quit(
|
||||
format!("error setting up ssh: {}", e),
|
||||
ExitCode::ConfigError,
|
||||
),
|
||||
};
|
||||
|
||||
let mut refs = Vec::new();
|
||||
let strings = self.get_refs(&cfg.branches);
|
||||
let mut refs_str = String::new();
|
||||
let strings = self.get_refs(
|
||||
&cfg.branches,
|
||||
match cfg.push_tags {
|
||||
true => Some(repo.tag_names(None).unwrap()),
|
||||
false => None,
|
||||
},
|
||||
);
|
||||
for branch in &strings {
|
||||
refs.push(branch.as_str());
|
||||
refs_str.push_str(format!("{} ", branch).as_str());
|
||||
}
|
||||
|
||||
match remote.push::<&str>(&refs, Some(&mut push_options)) {
|
||||
common::verbose(self.verbose, 4, format!("ref list: {}", refs_str));
|
||||
match remote.push::<&str>(&refs, Some(&mut po)) {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
if self.strict {
|
||||
return Err(format!("failed to push to remote: {}: {}", remote.url().unwrap(), e))
|
||||
return Err(format!(
|
||||
"failed to push to remote: {}: {}",
|
||||
remote.url().unwrap(),
|
||||
e
|
||||
));
|
||||
} else {
|
||||
common::warning(format!("failed to push to remote: {}: {}", remote.url().unwrap(), e))
|
||||
common::warning(format!(
|
||||
"failed to push to remote: {}: {}",
|
||||
remote.url().unwrap(),
|
||||
e
|
||||
))
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn looper(&self, repos: Vec<OpenedRepository>) -> Result<(), String> {
|
||||
fn looper(&self, repos: Vec<OpenedRepository>) -> Result<(), ReturnData> {
|
||||
let mut current_ints = Vec::new();
|
||||
let running = Arc::new(AtomicBool::new(true));
|
||||
let r = running.clone();
|
||||
let count = repos.len();
|
||||
for i in 0..repos.len() {
|
||||
current_ints.push(u64::from(repos[i].cfg.schedule.interval.unwrap().unsigned_abs()));
|
||||
};
|
||||
current_ints.push(i64::from(
|
||||
repos[i].cfg.schedule.interval.unwrap().unsigned_abs(),
|
||||
));
|
||||
}
|
||||
let original_ints = current_ints.clone();
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
r.store(false, Ordering::SeqCst);
|
||||
}).expect("Failed to set ^C handler");
|
||||
})
|
||||
.expect("Failed to set ^C handler");
|
||||
|
||||
common::verbose(self.verbose, 1, format!("Starting scheduled loop"));
|
||||
let min = *current_ints.iter().min().unwrap();
|
||||
let mut do_break = false;
|
||||
while !do_break {
|
||||
do_break = true;
|
||||
let sleep_int = time::Duration::from_secs(min);
|
||||
let min = *current_ints.iter().min().unwrap();
|
||||
let sleep_int = time::Duration::from_secs(min as u64);
|
||||
let now = time::Instant::now();
|
||||
|
||||
common::verbose(
|
||||
self.verbose,
|
||||
2,
|
||||
format!("Sleeping for {} seconds", sleep_int.as_secs()));
|
||||
format!("Sleeping for {} seconds", sleep_int.as_secs()),
|
||||
);
|
||||
while running.load(Ordering::SeqCst) {
|
||||
thread::sleep(time::Duration::from_secs(1));
|
||||
thread::sleep(time::Duration::from_millis(200));
|
||||
if now.elapsed().as_secs() >= sleep_int.as_secs() {
|
||||
common::verbose(self.verbose, 3, format!("Thread has awoken!"));
|
||||
for i in 0..count {
|
||||
current_ints[i] -= now.elapsed().as_secs();
|
||||
if i <= 0 {
|
||||
current_ints[i] = original_ints[i].clone();
|
||||
current_ints[i] -= now.elapsed().as_secs() as i64;
|
||||
common::verbose(
|
||||
self.verbose,
|
||||
4,
|
||||
format!("checking repo: {}", repos[i].cfg.from),
|
||||
);
|
||||
if current_ints[i] <= 0 {
|
||||
common::verbose(self.verbose, 4, format!("repo is ready for push"));
|
||||
common::verbose(
|
||||
self.verbose,
|
||||
2,
|
||||
format!("Interval for {} has arrived, pulling", repos[i].cfg.from));
|
||||
|
||||
let _ = self.fast_forward(&repos[i].path, &repos[i].cfg.branches);
|
||||
if let Err(e) = self.push_remotes(
|
||||
&repos[i].cfg,
|
||||
format!("Interval for {} has arrived, pulling", repos[i].cfg.from),
|
||||
);
|
||||
if let Err(e) = self.fetch(
|
||||
&repos[i].repo,
|
||||
&repos[i].remotes) {
|
||||
return Err(e)
|
||||
}
|
||||
&repos[i].cfg.branches,
|
||||
repos[i].ssh,
|
||||
&repos[i].cfg.git.ssh_identity_file,
|
||||
self.strict.clone(),
|
||||
) {
|
||||
common::error_quit(
|
||||
format!("failed to fetch repo {}: {}", repos[i].cfg.from, e),
|
||||
ExitCode::FetchError,
|
||||
);
|
||||
}
|
||||
|
||||
let _ = self.fast_forward(&repos[i].repo, &repos[i].cfg.branches);
|
||||
if let Err(e) = self.push_remotes(&repos[i].cfg, &repos[i].repo, &repos[i].remotes) {
|
||||
common::error_quit(e, ExitCode::PushError)
|
||||
};
|
||||
current_ints[i] = original_ints[i].clone();
|
||||
}
|
||||
common::verbose(
|
||||
self.verbose,
|
||||
4,
|
||||
format!("repo remaining time is now {}", current_ints[i]),
|
||||
);
|
||||
}
|
||||
do_break = false;
|
||||
break
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -238,121 +323,220 @@ impl Refractr {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run(&self, cfgs: Vec<ConfigFile>) -> Result<(), String> {
|
||||
fn clean(&self, dirs: Vec<String>) -> Result<(), ReturnData> {
|
||||
for dir in dirs {
|
||||
common::verbose(self.verbose, 1, format!("removing working dir {}", dir));
|
||||
if let Err(e) = remove_dir_all(&dir) {
|
||||
common::warning(format!("could not clean up working dir: {}: {}", dir, e));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run(&self, cfgs: Vec<ConfigFile>) -> Result<(), ReturnData> {
|
||||
let r = self.run.clone();
|
||||
ctrlc::set_handler(move ||
|
||||
r.store(true, Ordering::SeqCst)
|
||||
).expect("failed to set ^c handler");
|
||||
|
||||
common::verbose(self.verbose, 3, format!("Starting main refractr loop"));
|
||||
let mut loop_repos = Vec::new();
|
||||
let mut work_dirs = Vec::new();
|
||||
|
||||
for cfg in cfgs {
|
||||
// set up the working directory
|
||||
common::verbose(self.verbose, 3, format!("Loading config: {}", cfg.path));
|
||||
let work_dir = self.set_up_work_dir(match &cfg.config.work_dir {
|
||||
None => {
|
||||
if cfg!(windows) {
|
||||
PathBuf::from(format!("\"{}\\refractr\"", env::var("TEMP").unwrap()))
|
||||
} else {
|
||||
PathBuf::from("/tmp/refractr")
|
||||
}
|
||||
},
|
||||
Some(path) => PathBuf::from(path)
|
||||
});
|
||||
let path_str = match work_dir {
|
||||
Ok(p) => p,
|
||||
Err(e) => return Err(e)
|
||||
};
|
||||
let work_dir = config::get_work_dir(&cfg.config.work_dir);
|
||||
if let Err(e) = self.set_up_work_dir(PathBuf::from(&work_dir)) {
|
||||
return Err(ReturnData {
|
||||
msg: e,
|
||||
code: ExitCode::FilesystemError,
|
||||
});
|
||||
}
|
||||
common::verbose(
|
||||
self.verbose,
|
||||
2,
|
||||
format!("Created working directory: {}", &path_str));
|
||||
format!("Created working directory: {}", work_dir),
|
||||
);
|
||||
|
||||
let repo_name = match &cfg.config.from.split("/").last() {
|
||||
Some(split) => split.to_string(),
|
||||
None => freak_out!(format!("failed to parse repository name"))
|
||||
None => {
|
||||
return Err(ReturnData {
|
||||
code: ExitCode::ParseError,
|
||||
msg: format!("failed to parse repository name"),
|
||||
})
|
||||
},
|
||||
};
|
||||
|
||||
let ssh = cfg.config.from.starts_with("ssh://");
|
||||
let mut builder = RepoBuilder::new();
|
||||
let mut fo = FetchOptions::new();
|
||||
|
||||
// make initial clone
|
||||
if ssh {
|
||||
match self.set_up_ssh(
|
||||
cfg.config.git.ssh_identity_file.clone(),
|
||||
self.strict.clone(),
|
||||
) {
|
||||
Ok(cb) => {
|
||||
fo.remote_callbacks(cb);
|
||||
()
|
||||
},
|
||||
Err(e) => common::error_quit(
|
||||
format!("error setting up ssh: {}", e),
|
||||
ExitCode::ConfigError,
|
||||
),
|
||||
};
|
||||
}
|
||||
fo.download_tags(git2::AutotagOption::All);
|
||||
builder.fetch_options(fo);
|
||||
|
||||
let repo_dir = format!("{}{}{}", &work_dir, MAIN_SEPARATOR_STR, repo_name);
|
||||
common::verbose(
|
||||
self.verbose,
|
||||
1,
|
||||
format!("Cloning repository: {}", &cfg.config.from));
|
||||
|
||||
let mut builder = RepoBuilder::new();
|
||||
let mut cb = RemoteCallbacks::new();
|
||||
let mut fo = FetchOptions::new();
|
||||
|
||||
if cfg.config.from.starts_with("ssh://") {
|
||||
let key_string = cfg.config.git.ssh_identity_file.clone();
|
||||
cb.credentials(move |_,_,_| Cred::ssh_key(
|
||||
"git",
|
||||
None,
|
||||
Path::new(&key_string),
|
||||
None));
|
||||
cb.certificate_check(|cert, url| {
|
||||
let mut sha256 = String::new();
|
||||
for i in cert.as_hostkey().unwrap().hash_sha256().unwrap().to_vec() {
|
||||
sha256.push_str(&hex::encode(i.to_string()));
|
||||
}
|
||||
common::warning(
|
||||
format!("implicitly trusting unknown host {} with sha256 host key {}",
|
||||
url,
|
||||
hex::encode(cert.as_hostkey().unwrap().hash_sha256().unwrap().to_vec())));
|
||||
common::warning(
|
||||
format!("to ignore this error in the future, add this host to your known_hosts file"));
|
||||
Ok(CertificateCheckStatus::CertificateOk)
|
||||
});
|
||||
}
|
||||
fo.download_tags(git2::AutotagOption::All);
|
||||
fo.remote_callbacks(cb);
|
||||
builder.fetch_options(fo);
|
||||
|
||||
let repo_dir = format!("{}/{}", &path_str, repo_name);
|
||||
format!("Cloning repository {} to {}", &cfg.config.from, &repo_dir),
|
||||
);
|
||||
let repo = match builder.clone(&cfg.config.from, Path::new(&repo_dir)) {
|
||||
Ok(repo) => repo,
|
||||
Err(_) => {
|
||||
common::warning(format!("found existing repo at {}, attempting to use", repo_dir));
|
||||
match self.fast_forward(&repo_dir, &cfg.config.branches) {
|
||||
Ok(_) => if let Ok(repo) = Repository::open(Path::new(&repo_dir)) {
|
||||
repo
|
||||
} else {
|
||||
freak_out!(format!("failed to obtain existing repo"))
|
||||
},
|
||||
Err(e) => freak_out!(format!("failed to obtain existing repo: {}", e))
|
||||
Err(e) => {
|
||||
if e.code() != ErrorCode::Exists {
|
||||
common::error_quit(
|
||||
format!("failed to clone repo to {}: {}", repo_dir, e),
|
||||
ExitCode::FilesystemError,
|
||||
);
|
||||
}
|
||||
}
|
||||
common::warning(format!(
|
||||
"found existing repo at {}, attempting to use",
|
||||
repo_dir
|
||||
));
|
||||
match Repository::open(Path::new(&repo_dir)) {
|
||||
Ok(r) => {
|
||||
if let Ok(rem) = r.find_remote("origin") {
|
||||
match rem.url() {
|
||||
Some(url) => {
|
||||
if url != &cfg.config.from {
|
||||
return Err(ReturnData {
|
||||
code: ExitCode::RepositoryError,
|
||||
msg: format!(
|
||||
"existing repo's origin does not match 'from' value in config: {}",
|
||||
url
|
||||
),
|
||||
});
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(ReturnData {
|
||||
code: ExitCode::RepositoryError,
|
||||
msg: format!("could not obtain existing repo's origin: {}", repo_dir),
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
// fetch updates for the repo
|
||||
if let Err(e) = self.fetch(
|
||||
&r,
|
||||
&cfg.config.branches,
|
||||
ssh,
|
||||
&cfg.config.git.ssh_identity_file,
|
||||
self.strict.clone(),
|
||||
) {
|
||||
common::error_quit(
|
||||
format!("failed to fetch repo {}: {}", cfg.config.from, e),
|
||||
ExitCode::FetchError,
|
||||
);
|
||||
}
|
||||
// fast forward
|
||||
match self.fast_forward(&r, &cfg.config.branches) {
|
||||
Ok(_) => r,
|
||||
Err(e) => {
|
||||
return Err(ReturnData {
|
||||
code: ExitCode::RepositoryError,
|
||||
msg: format!("failed to fast forward existing repo: {}", e),
|
||||
});
|
||||
},
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
return Err(ReturnData {
|
||||
code: ExitCode::RepositoryError,
|
||||
msg: format!("failed to obtain existing repo: {}", e),
|
||||
})
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
self.set_up_refs(&repo, &cfg.config.branches);
|
||||
self.fetch(&repo, &cfg.config.branches, Some(&cfg.config.git.ssh_identity_file)).unwrap();
|
||||
if let Err(e) = self.set_up_refs(&repo, &cfg.config.branches) {
|
||||
common::error_quit(
|
||||
format!("failed to set up refs: {}", e),
|
||||
ExitCode::RepositoryError,
|
||||
);
|
||||
}
|
||||
|
||||
let remotes = match self.make_remotes(&repo, &cfg) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(e)
|
||||
Err(e) => {
|
||||
return Err(ReturnData {
|
||||
code: ExitCode::RemoteError,
|
||||
msg: e,
|
||||
})
|
||||
},
|
||||
};
|
||||
|
||||
if let Err(e) = self.push_remotes(&cfg.config, &repo, &remotes) {
|
||||
return Err(e)
|
||||
common::error_quit(e, ExitCode::PushError);
|
||||
}
|
||||
|
||||
if cfg.config.schedule.enabled {
|
||||
loop_repos.push(OpenedRepository {
|
||||
repo,
|
||||
path: repo_dir,
|
||||
remotes,
|
||||
cfg: cfg.config
|
||||
cfg: cfg.config,
|
||||
ssh,
|
||||
});
|
||||
if !work_dirs.contains(&work_dir) {
|
||||
work_dirs.push(work_dir);
|
||||
}
|
||||
} else {
|
||||
if let Err(e) = self.clean(vec![work_dir]) {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
if self.run.load(Ordering::SeqCst) {
|
||||
common::error_quit(format!("exiting"), ExitCode::HaltError);
|
||||
}
|
||||
}
|
||||
// end for
|
||||
|
||||
let mut result = Ok(());
|
||||
if loop_repos.len() >= 1 {
|
||||
common::verbose(
|
||||
self.verbose,
|
||||
2,
|
||||
format!("{} configs have schedules enabled, setting up looper", loop_repos.len()));
|
||||
return self.looper(loop_repos);
|
||||
format!(
|
||||
"{} configs have schedules enabled, setting up looper",
|
||||
loop_repos.len()
|
||||
),
|
||||
);
|
||||
result = self.looper(loop_repos);
|
||||
} else {
|
||||
common::verbose(
|
||||
self.verbose,
|
||||
2,
|
||||
format!("No scheduled configs found, exiting refractr"));
|
||||
format!("No scheduled configs found, exiting refractr"),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
if !self.persist {
|
||||
match result {
|
||||
Ok(()) => return self.clean(work_dirs),
|
||||
Err(_) => result = self.clean(work_dirs),
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue