diff --git a/.github/workflows/cachix.yaml b/.github/workflows/cachix.yaml new file mode 100644 index 0000000..3edb139 --- /dev/null +++ b/.github/workflows/cachix.yaml @@ -0,0 +1,19 @@ +name: "Cachix build" +on: [push] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2.3.4 + - uses: cachix/install-nix-action@v13 + with: + install_url: https://nixos-nix-install-tests.cachix.org/serve/lb41az54kzk6j12p81br4bczary7m145/install + install_options: '--tarball-url-prefix https://nixos-nix-install-tests.cachix.org/serve' + extra_nix_config: | + experimental-features = nix-command flakes + - uses: cachix/cachix-action@v8 + with: + name: alarsyo + authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' + - run: | + nix build --verbose diff --git a/Cargo.lock b/Cargo.lock index 2d4247d..365f914 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -241,9 +241,9 @@ dependencies = [ [[package]] name = "figment" -version = "0.10.4" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708a94ecb9ca72e347442fef4563e459035b950c78091a355ed8a40180b33367" +checksum = "0ca029e813a72b7526d28273d25f3e4a2f365d1b7a1018a6f93ec9053a119763" dependencies = [ "atomic", "pear", @@ -443,9 +443,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7245cd7449cc792608c3c8a9eaf69bd4eabbabf802713748fd739c98b82f0747" +checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11" dependencies = [ "bytes", "fnv", @@ -465,9 +465,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.3.5" +version = "1.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "615caabe2c3160b313d52ccc905335f4ed5f10881dd63dc5699d47e90be85691" +checksum = "bc35c995b9d93ec174cf9a27d425c7892722101e14993cd227fdb51d70cf9589" [[package]] name = "httpdate" @@ -539,9 +539,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714" +checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" [[package]] name = "linked-hash-map" @@ -551,9 +551,9 @@ checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" [[package]] name = "lock_api" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" +checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176" dependencies = [ "scopeguard", ] @@ -569,7 +569,7 @@ dependencies = [ [[package]] name = "lohr" -version = "0.3.1" +version = "0.4.0" dependencies = [ "anyhow", "clap", @@ -788,9 +788,9 @@ checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" [[package]] name = "proc-macro2" -version = "1.0.24" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" dependencies = [ "unicode-xid", ] @@ -915,7 +915,7 @@ dependencies = [ [[package]] name = "rocket" version = "0.5.0-dev" -source = "git+https://github.com/SergioBenitez/Rocket?rev=2893ce754d6535e0a752586e60d7e292343016c0#2893ce754d6535e0a752586e60d7e292343016c0" +source = "git+https://github.com/SergioBenitez/Rocket?rev=8d4d01106e2e10b08100805d40bfa19a7357e900#8d4d01106e2e10b08100805d40bfa19a7357e900" dependencies = [ "async-trait", "atomic", @@ -948,7 +948,7 @@ dependencies = [ [[package]] name = "rocket_codegen" version = "0.5.0-dev" -source = "git+https://github.com/SergioBenitez/Rocket?rev=2893ce754d6535e0a752586e60d7e292343016c0#2893ce754d6535e0a752586e60d7e292343016c0" +source = "git+https://github.com/SergioBenitez/Rocket?rev=8d4d01106e2e10b08100805d40bfa19a7357e900#8d4d01106e2e10b08100805d40bfa19a7357e900" dependencies = [ "devise", "glob", @@ -961,7 +961,7 @@ dependencies = [ [[package]] name = "rocket_http" version = "0.5.0-dev" -source = "git+https://github.com/SergioBenitez/Rocket?rev=2893ce754d6535e0a752586e60d7e292343016c0#2893ce754d6535e0a752586e60d7e292343016c0" +source = "git+https://github.com/SergioBenitez/Rocket?rev=8d4d01106e2e10b08100805d40bfa19a7357e900#8d4d01106e2e10b08100805d40bfa19a7357e900" dependencies = [ "cookie", "either", @@ -1219,9 +1219,9 @@ checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" [[package]] name = "syn" -version = "1.0.67" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6498a9efc342871f91cc2d0d694c674368b4ceb40f62b65a7a08c3792935e702" +checksum = "3ce15dd3ed8aa2f8eeac4716d6ef5ab58b6b9256db41d7e1a0224c2788e8fd87" dependencies = [ "proc-macro2", "quote", @@ -1401,9 +1401,9 @@ dependencies = [ [[package]] name = "uncased" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300932469d646d39929ffe84ad5c1837beecf602519ef5695e485b472de4082b" +checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0" dependencies = [ "serde", "version_check", diff --git a/Cargo.toml b/Cargo.toml index 9ee6f4d..83eefef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lohr" -version = "0.3.1" +version = "0.4.0" authors = ["Antoine Martin "] edition = "2018" license = "Apache-2.0 OR MIT" @@ -33,4 +33,4 @@ version = "2.33.3" default-features = false [patch.crates-io] -rocket = { git = "https://github.com/SergioBenitez/Rocket", rev = "2893ce754d6535e0a752586e60d7e292343016c0" } +rocket = { git = "https://github.com/SergioBenitez/Rocket", rev = "8d4d01106e2e10b08100805d40bfa19a7357e900" } diff --git a/README.md b/README.md new file mode 100644 index 0000000..9abfe4a --- /dev/null +++ b/README.md @@ -0,0 +1,138 @@ +# lohr + +`lohr` is a Git mirroring tool. + +I created it to solve a simple problem I had: I host my own git server at +, but want to mirror my public projects to GitHub / +GitLab, for backup and visibility purposes. + +GitLab has a mirroring setting, but it doesn't allow for multiple mirrors, as +far as I know. I also wanted my instance to be the single source of truth. + +## How it works + +Gitea is setup to send webhooks to my `lohr` server on every push update. When +`lohr` receives a push, it clones the concerned repository, or updates it if +already cloned. Then it pushes the update to **all remotes listed** in the +[.lohr](.lohr) file at the repo root. + +### Destructive + +This is a very destructive process: anything removed from the single source of +truth is effectively removed from any mirror as well. + +## Installing + +`lohr` is [published on crates.io](https://crates.io/crates/lohr), so you can +install it with `cargo install`: + + $ cargo install lohr + +Note: currently this method won't get you the latest version of `lohr`, as it +depends on Rocket v0.5.0, which isn't released yet. Updated versions of `lohr` +will be published on crates.io as soon as Rocket v0.5.0 releases. + +## Setup + +### Quickstart + +Setting up `lohr` should be quite simple: + +1. Create a `Rocket.toml` file and [add your + configuration](https://rocket.rs/v0.4/guide/configuration/). + +2. Export a secret variable: + + $ export LOHR_SECRET=42 # please don't use this secret + +3. Run `lohr`: + + $ cargo run # or `cargo run --release` for production usage + +4. Configure your favorite git server to send a webhook to `lohr`'s address on + every push event. + + I used [Gitea's webhooks format](https://docs.gitea.io/en-us/webhooks/), but + I **think** they're similar to GitHub and GitLab's webhooks, so these should + work too! (If they don't, **please** file an issue!) + + Don't forget to set the webhook secret to the one you chose above. + +5. Add a `.lohr` file containing the remotes you want to mirror this repo to: + + git@github.com:you/your_repo + + and push it. That's it! `lohr` is mirroring your repo now. + + +### Configuration + +#### Home directory + +`lohr` needs a place to clone repos and store its data. By default, it's the +current directory, but you can set the `LOHR_HOME` environment variable to +customize it. + +#### Shared secret + +As shown in the quickstart guide, you **must** set the `LOHR_SECRET` environment +variable. + +#### Extra remote configuration + +You can provide `lohr` with a YAML file containing additional configuration. You +can pass its path to the `--config` flag when launching `lohr`. If no +configuration is provided via a CLI flag, `lohr` will check the `LOHR_CONFIG` +environment variable. If the environment variable isn't set either, it will +check in `LOHR_HOME` is a `lohr-config.yaml` file exists, and try to load it. + +This file takes the following format: + +``` yaml +default_remotes: + - "git@github:user" + - "git@gitlab:user" + +additional_remotes: + - "git@git.sr.ht:~user" + +filters: + - FIXME: +``` + +- `default_remotes` is a list of remotes to use if no `.lohr` file is found in a + repository. +- `additional_remotes` is a list of remotes to add in any case, whether the + original set of remotes is set via `default_remotes` or via a `.lohr` file. +- `blacklist` is a list of regular expressions to match against the full + repository names. Any that matches will not be mirrored, even if it contains a + `.lohr` file. + +Both settings take as input a list of "stems", i.e. incomplete remote addresses, +to which the repo's name will be appended (so for example, if my +`default_remotes` contains `git@github.com:alarsyo`, and a push event webhook is +received for repository `git@gitlab.com:some/long/path/repo_name`, then the +mirror destination will be `git@github.com:alarsyo/repo_name`. + +## Contributing + +I accept patches anywhere! Feel free to [open a GitHub Pull +Request](https://github.com/alarsyo/lohr/pulls), [a GitLab Merge +Request](https://gitlab.com/alarsyo/lohr/-/merge_requests), or [send me a patch +by email](https://lists.sr.ht/~alarsyo/lohr-dev)! + +## Why lohr? + +I was looking for a cool name, and thought about the Magic Mirror in Snow White. +Some **[furious wikipedia +searching](https://en.wikipedia.org/wiki/Magic_Mirror_(Snow_White))** later, I +found that the Magic Mirror was probably inspired by [the Talking Mirror in Lohr +am Main](http://spessartmuseum.de/seiten/schneewittchen_engl.html). That's it, +that's the story. + +## License + +`lohr` is distributed under the terms of both the MIT license and the Apache +License (Version 2.0). + +See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) for details. diff --git a/README.org b/README.org deleted file mode 100644 index 07c5c01..0000000 --- a/README.org +++ /dev/null @@ -1,135 +0,0 @@ -#+title: lohr - -=lohr= is a Git mirroring tool. - -I created it to solve a simple problem I had: I host my own git server at -[[https://git.alarsyo.net]], but want to mirror my public projects to GitHub / -GitLab, for backup and visibility purposes. - -GitLab has a mirroring setting, but it doesn't allow for multiple mirrors, as -far as I know. I also wanted my instance to be the single source of truth. - -** How it works - -Gitea is setup to send webhooks to my =lohr= server on every push update. When -=lohr= receives a push, it clones the concerned repository, or updates it if -already cloned. Then it pushes the update to *all remotes listed* in the [[file:.lohr][.lohr]] -file at the repo root. - -*** Destructive - -This is a very destructive process: anything removed from the single source of -truth is effectively removed from any mirror as well. - -** Installing - -=lohr= is [[https://crates.io/crates/lohr][published on crates.io]], so you can install it with ~cargo install~: - -#+begin_src sh -$ cargo install lohr -#+end_src - -** Setup - -*** Quickstart - -Setting up =lohr= should be quite simple: - -1. Create a =Rocket.toml= file and [[https://rocket.rs/v0.4/guide/configuration/][add your configuration]]. - -2. Export a secret variable: - - #+begin_src sh - $ export LOHR_SECRET=42 # please don't use this secret - #+end_src - -3. Run =lohr=: - - #+begin_src sh - $ cargo run # or `cargo run --release` for production usage - #+end_src - -4. Configure your favorite git server to send a webhook to =lohr='s address on - every push event. - - I used [[https://docs.gitea.io/en-us/webhooks/][Gitea's webhooks format]], but I *think* they're similar to GitHub and - GitLab's webhooks, so these should work too! (If they don't, *please* file an - issue!) - - Don't forget to set the webhook secret to the one you chose above. - -5. Add a =.lohr= file containing the remotes you want to mirror this repo to: - - #+begin_example - git@github.com:you/your_repo - #+end_example - - and push it. That's it! =lohr= is mirroring your repo now. - -*** Configuration - -**** Home directory - -=lohr= needs a place to clone repos and store its data. By default, it's the -current directory, but you can set the =LOHR_HOME= environment variable to -customize it. - -**** Shared secret - -As shown in the quickstart guide, you *must* set the =LOHR_SECRET= environment -variable. - -**** Extra remote configuration - -You can provide =lohr= with a YAML file containing additional configuration. You -can pass its path to the =--config= flag when launching =lohr=. If no -configuration is provided via a CLI flag, =lohr= will check the =LOHR_CONFIG= -environment variable. If the environment variable isn't set either, it will -check in =LOHR_HOME= is a =lohr-config.yaml= file exists, and try to load it. - -This file takes the following format: - -#+begin_src yaml -default_remotes: - - "git@github:user" - - "git@gitlab:user" - -additional_remotes: - - "git@git.sr.ht:~user" - -blacklist: - - "private-.*" -#+end_src - -- ~default_remotes~ is a list of remotes to use if no ~.lohr~ file is found in a - repository. -- ~additional_remotes~ is a list of remotes to add in any case, whether the - original set of remotes is set via ~default_remotes~ or via a =.lohr= file. -- ~blacklist~ is a list of regular expressions to match against the full - repository names. Any that matches will not be mirrored, even if it contains a - `.lohr` file. - -Both settings take as input a list of "stems", i.e. incomplete remote addresses, -to which the repo's name will be appended (so for example, if my -~default_remotes~ contains ~git@github.com:alarsyo~, and a push event webhook -is received for repository =git@gitlab.com:some/long/path/repo_name=, then the -mirror destination will be =git@github.com:alarsyo/repo_name=. - -** Contributing - -I accept patches anywhere! Feel free to [[https://github.com/alarsyo/lohr/pulls][open a GitHub Pull Request]], [[https://gitlab.com/alarsyo/lohr/-/merge_requests][a GitLab -Merge Request]], or [[https://lists.sr.ht/~alarsyo/lohr-dev][send me a patch by email]]! - -** Why lohr? - -I was looking for a cool name, and thought about the Magic Mirror in Snow White. -Some *[[https://en.wikipedia.org/wiki/Magic_Mirror_(Snow_White)][furious wikipedia searching]]* later, I found that the Magic Mirror was -probably inspired by [[http://spessartmuseum.de/seiten/schneewittchen_engl.html][the Talking Mirror in Lohr am Main]]. That's it, that's the -story. - -** License - -=lohr= is distributed under the terms of both the MIT license and the Apache -License (Version 2.0). - -See [[file:LICENSE-APACHE][LICENSE-APACHE]] and [[file:LICENSE-MIT][LICENSE-MIT]] for details. diff --git a/flake.nix b/flake.nix index b87c588..cc866ad 100644 --- a/flake.nix +++ b/flake.nix @@ -16,11 +16,11 @@ { defaultPackage = pkgs.rustPlatform.buildRustPackage { pname = "lohr"; - version = "0.3.1"; + version = "0.4.0"; src = ./.; - cargoSha256 = "sha256-XnBvb13Pv7bNTLCL3WV+bxRK0/uMEKA1/Bk0Tfua3Rs="; + cargoSha256 = "sha256-5a2mK+E6LlR5RHDAhHDvnfPNG+0JdvpnL4kuTiz7vVg="; meta = with pkgs.lib; { description = "A Git mirroring tool"; diff --git a/src/gitea.rs b/src/gitea.rs index e4826e5..eae1528 100644 --- a/src/gitea.rs +++ b/src/gitea.rs @@ -4,7 +4,7 @@ use serde::Deserialize; pub(crate) struct Repository { pub(crate) name: String, pub(crate) full_name: String, - pub(crate) clone_url: String, + pub(crate) ssh_url: String, } #[derive(Deserialize)] diff --git a/src/job.rs b/src/job.rs index 2e91cf1..0e386c4 100644 --- a/src/job.rs +++ b/src/job.rs @@ -38,7 +38,7 @@ impl Job { let output = Command::new("git") .arg("clone") .arg("--mirror") - .arg(&self.repo.clone_url) + .arg(&self.repo.ssh_url) .arg(format!("{}", self.local_path.as_ref().unwrap().display())) .output()?; diff --git a/src/main.rs b/src/main.rs index 2eccf61..6bf0d1d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use std::sync::{ use std::thread; use anyhow::Context; -use clap::{App, Arg}; +use clap::{crate_version, App, Arg}; use log::{error, info}; use rocket::{http::Status, post, routes, State}; @@ -34,12 +34,15 @@ fn gitea_webhook( config: State, ) -> Status { if config - .blacklist + .filters .iter() - .any(|re| re.is_match(&payload.repository.full_name)) + // Find first filter that matches the given destination + .find(|filter| filter.destination.as_ref().map_or(false, |re| re.is_match(&payload.repository.full_name))) + // Default to mirroring, unless told not to + .map_or(true, |filter| filter.mirror) { info!( - "Ignoring webhook for repo {} which is blacklisted", + "Ignoring webhook for repo {} which is marked as not mirrored", payload.repository.full_name ); return Status::Ok; @@ -93,7 +96,7 @@ fn parse_config(home: &Path, flags: &clap::ArgMatches) -> anyhow::Result anyhow::Result<()> { let matches = App::new("lohr") - .version("0.3.1") + .version(crate_version!()) .about("Git mirroring daemon") .arg( Arg::with_name("config") diff --git a/src/settings.rs b/src/settings.rs index 8dc71f9..8b91376 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -10,8 +10,29 @@ pub(crate) struct GlobalSettings { /// List of remote stems to use for every repository #[serde(default)] pub additional_remotes: Vec, - /// List of regexes, if a repository's name matches any of the, it is not mirrored by `lohr` - /// even if it contains a `.lorh` file. - #[serde(with = "serde_regex")] - pub blacklist: Vec, + /// List of filters to blacklist repositories, or modify push options on specific remotes. + /// Only the first matching filter is applied, so order is important. + #[serde(default)] + pub filters: Vec, +} + +#[derive(Clone, Default, Deserialize)] +pub(crate) struct FilterSettings { + /// Match on the source remote + #[serde(with = "serde_regex", default)] + pub source: Option, + /// Match on the destination remote + #[serde(with = "serde_regex", default)] + pub destination: Option, + /// Whether to mirror the repository or not + #[serde(default = "default_true")] + pub mirror: bool, + /// Push options to be used for the matched remote + #[serde(default)] + pub push_options: Vec, +} + +// Workaround for https://github.com/serde-rs/serde/issues/368 +fn default_true() -> bool { + true }