├── rust-toolchain.toml ├── .gitignore ├── renovate.json ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ ├── build.yaml │ └── release.yml ├── package.nix ├── Cargo.toml ├── src ├── webhook │ ├── plain.rs │ ├── gitlab.rs │ ├── github.rs │ └── mod.rs ├── config.rs ├── main.rs └── repo.rs ├── LICENSE ├── module.nix ├── flake.nix ├── flake.lock ├── README.md └── Cargo.lock /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | components = [ "rustfmt", "cargo", "clippy", "rust-src", "rustc" ] 4 | profile = "complete" 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /target 3 | **/*.rs.bk 4 | 5 | # IDE 6 | /.idea 7 | pullomatic.iml 8 | 9 | # NIX 10 | /result 11 | /.direnv/ 12 | /.envrc 13 | 14 | .pre-commit-config.yaml 15 | 16 | config.example/ 17 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ], 6 | "nix": { 7 | "enabled": true 8 | }, 9 | "packageRules": [ 10 | { 11 | "matchUpdateTypes": [ 12 | "minor", 13 | "patch", 14 | "pin", 15 | "digest" 16 | ], 17 | "matchCurrentVersion": "!/^0/", 18 | "automerge": true 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Cargo build and test 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | 10 | jobs: 11 | check: 12 | name: Check 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v5 16 | - name: Check Formatting 17 | run: cargo fmt --all -- --check 18 | - name: Check Clippy 19 | run: cargo clippy --all 20 | 21 | build_and_test: 22 | name: Build and Test 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v5 26 | - name: Build 27 | run: cargo build --verbose --all-features 28 | - name: Test 29 | run: cargo test --verbose 30 | 31 | -------------------------------------------------------------------------------- /package.nix: -------------------------------------------------------------------------------- 1 | { lib 2 | , rustPlatform 3 | , pkg-config 4 | , openssl 5 | , libgit2 6 | , 7 | }: 8 | 9 | rustPlatform.buildRustPackage { 10 | pname = "pullomatic"; 11 | version = "0.2.0"; 12 | 13 | src = lib.cleanSource ./.; 14 | 15 | uesFetchCargoVendor = true; 16 | cargoHash = "sha256-+B/DzDaF3qQlPzjh97CBMAseyeUClgsgzE0EJ8kTlqg="; 17 | 18 | nativeBuildInputs = [ 19 | pkg-config 20 | ]; 21 | 22 | buildInputs = [ 23 | openssl 24 | libgit2 25 | ]; 26 | 27 | LIBGIT2_NO_VENDOR = 1; 28 | 29 | meta = with lib; { 30 | description = "Automated git pulls"; 31 | mainProgram = "pullomatic"; 32 | homepage = "https://github.com/fooker/pullomatic"; 33 | license = licenses.mit; 34 | maintainers = with maintainers; [ fooker ]; 35 | }; 36 | } 37 | 38 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pullomatic" 3 | description = "Automates git repository syncing through pure configuration" 4 | version = "0.2.2" 5 | authors = ["Dustin Frisch "] 6 | license = "MIT" 7 | 8 | edition = "2021" 9 | 10 | [dependencies] 11 | anyhow = "1.0.98" 12 | 13 | tokio = { version = "1.45.0", features = ["full"] } 14 | tokio-util = { version = "0.7.15", features = ["full"] } 15 | 16 | futures = "0.3.31" 17 | 18 | tracing = "0.1.41" 19 | tracing-subscriber = "0.3.19" 20 | 21 | git2 = "0.20.0" 22 | 23 | serde = { version = "1.0.219", features = ["derive"] } 24 | serde_yaml = "0.9.33" 25 | serde-humantime = "0.1.1" 26 | 27 | axum = { version = "0.8.4", features = ["macros"] } 28 | hmac = "0.12.1" 29 | sha1 = "0.10.6" 30 | hex = "0.4.3" 31 | json = "0.12.4" 32 | 33 | clap = { version = "4.5.37", features = ["derive", "color"] } 34 | -------------------------------------------------------------------------------- /src/webhook/plain.rs: -------------------------------------------------------------------------------- 1 | use crate::config::PlainWebhook; 2 | use crate::repo::Repo; 3 | use anyhow::Result; 4 | use axum::extract::State; 5 | use axum::http::StatusCode; 6 | use axum::routing::post; 7 | use axum::Router; 8 | use std::sync::Arc; 9 | use tracing::debug; 10 | 11 | pub(super) fn router( 12 | config: PlainWebhook, 13 | producer: tokio::sync::mpsc::Sender>, 14 | repo: Arc, 15 | ) -> Router { 16 | Router::new() 17 | .route("/", post(handle)) 18 | .with_state((config, producer, repo)) 19 | } 20 | 21 | async fn handle( 22 | State((_config, producer, repo)): State<( 23 | PlainWebhook, 24 | tokio::sync::mpsc::Sender>, 25 | Arc, 26 | )>, 27 | ) -> Result<(), (StatusCode, &'static str)> { 28 | debug!("Trigger update from hook"); 29 | producer.send(repo.clone()).await.expect("Receiver dropped"); 30 | 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Desktop (please complete the following information):** 24 | - OS: [e.g. iOS] 25 | - Browser [e.g. chrome, safari] 26 | - Version [e.g. 22] 27 | 28 | **Smartphone (please complete the following information):** 29 | - Device: [e.g. iPhone6] 30 | - OS: [e.g. iOS8.1] 31 | - Browser [e.g. stock browser, safari] 32 | - Version [e.g. 22] 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Dustin Frisch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /module.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, config, ... }: 2 | 3 | with lib; 4 | 5 | let 6 | cfg = config.services.pullomatic; 7 | 8 | repoFormat = pkgs.formats.yaml { }; 9 | 10 | repos = pkgs.linkFarm "pullomatic.config" (mapAttrsToList 11 | (name: val: { 12 | inherit name; 13 | path = repoFormat.generate "${name}.yaml" val; 14 | }) 15 | cfg.repos); 16 | 17 | pullomatic = pkgs.callPackage ./package.nix { }; 18 | 19 | in 20 | { 21 | options.services.pullomatic = { 22 | enable = mkEnableOption "pullomatic"; 23 | 24 | repos = mkOption { 25 | type = types.attrsOf (repoFormat.type); 26 | default = { }; 27 | }; 28 | }; 29 | 30 | config = mkIf cfg.enable { 31 | systemd.services.pullomatic = { 32 | description = "Pullomatic"; 33 | requires = [ "network-online.target" ]; 34 | after = [ "network-online.target" ]; 35 | wantedBy = [ "multi-user.target" ]; 36 | 37 | restartTriggers = [ repos ]; 38 | 39 | serviceConfig = { 40 | Type = "simple"; 41 | Restart = "always"; 42 | ExecStart = "${pullomatic}/bin/pullomatic --config '${repos}'"; 43 | }; 44 | }; 45 | }; 46 | } 47 | 48 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | permissions: 4 | pull-requests: write 5 | contents: write 6 | 7 | on: 8 | push: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | release-plz_release: 14 | name: Release-plz release 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v5 19 | with: 20 | fetch-depth: 0 21 | - name: Install Rust toolchain 22 | uses: dtolnay/rust-toolchain@stable 23 | - name: Run release-plz 24 | uses: MarcoIeni/release-plz-action@v0.5 25 | with: 26 | command: release 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 30 | 31 | release-plz_pr: 32 | name: Release-plz PR 33 | runs-on: ubuntu-latest 34 | concurrency: 35 | group: release-plz-${{ github.ref }} 36 | cancel-in-progress: false 37 | steps: 38 | - name: Checkout repository 39 | uses: actions/checkout@v5 40 | with: 41 | fetch-depth: 0 42 | - name: Install Rust toolchain 43 | uses: dtolnay/rust-toolchain@stable 44 | - name: Run release-plz 45 | uses: MarcoIeni/release-plz-action@v0.5 46 | with: 47 | command: release-pr 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 51 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "pullomatic - automated git pulls"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 | 7 | flake-utils.url = "github:numtide/flake-utils"; 8 | 9 | pre-commit-hooks = { 10 | url = "github:cachix/git-hooks.nix"; 11 | inputs = { 12 | nixpkgs.follows = "nixpkgs"; 13 | }; 14 | }; 15 | }; 16 | 17 | outputs = { self, nixpkgs, flake-utils, pre-commit-hooks, ... }: 18 | (flake-utils.lib.eachDefaultSystem (system: 19 | let 20 | pkgs = nixpkgs.legacyPackages.${system}; 21 | 22 | in 23 | { 24 | checks = { 25 | inherit (self.packages.${system}) pullomatic; 26 | 27 | pre-commit-check = pre-commit-hooks.lib.${system}.run { 28 | src = ./.; 29 | 30 | hooks = { 31 | nixpkgs-fmt.enable = true; 32 | 33 | clippy = { 34 | enable = true; 35 | settings.allFeatures = true; 36 | }; 37 | 38 | cargo-check = { 39 | enable = true; 40 | }; 41 | 42 | rustfmt = { 43 | enable = true; 44 | }; 45 | }; 46 | }; 47 | }; 48 | 49 | packages = { 50 | pullomatic = pkgs.callPackage ./package.nix { }; 51 | default = self.packages.${system}.pullomatic; 52 | }; 53 | 54 | devShells.default = pkgs.mkShell { 55 | inputsFrom = [ self.packages.${system}.pullomatic ] 56 | ++ self.checks.${system}.pre-commit-check.enabledPackages; 57 | 58 | packages = with pkgs; [ 59 | cargo-deny 60 | cargo-outdated 61 | cargo-machete 62 | 63 | rustfmt 64 | clippy 65 | 66 | codespell 67 | 68 | nodejs 69 | ]; 70 | 71 | inherit (self.checks.${system}.pre-commit-check) shellHook; 72 | 73 | RUST_BACKTRACE = 1; 74 | #RUST_SRC_PATH = "${pkgs.rust}/lib.rs/rustlib/src/rust/library"; 75 | }; 76 | }) 77 | ) // { 78 | nixosModules = { 79 | pullomatic = ./module.nix; 80 | default = self.nixosModules.pullomatic; 81 | }; 82 | }; 83 | } 84 | -------------------------------------------------------------------------------- /src/webhook/gitlab.rs: -------------------------------------------------------------------------------- 1 | use crate::config::GitLabWebhook; 2 | use crate::repo::Repo; 3 | use anyhow::Result; 4 | use axum::extract::State; 5 | use axum::http::{HeaderMap, StatusCode}; 6 | use axum::routing::post; 7 | use axum::Router; 8 | use std::sync::Arc; 9 | use tracing::{debug, trace}; 10 | 11 | pub(super) fn router( 12 | config: GitLabWebhook, 13 | producer: tokio::sync::mpsc::Sender>, 14 | repo: Arc, 15 | ) -> Router { 16 | Router::new() 17 | .route("/", post(handle)) 18 | .with_state((config, producer, repo)) 19 | } 20 | 21 | async fn handle( 22 | State((config, producer, repo)): State<( 23 | GitLabWebhook, 24 | tokio::sync::mpsc::Sender>, 25 | Arc, 26 | )>, 27 | headers: HeaderMap, 28 | body: String, 29 | ) -> Result<(), (StatusCode, &'static str)> { 30 | // Check if the token matches 31 | if let Some(ref token) = config.token { 32 | let token = token 33 | .load() 34 | .await 35 | .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Failed to load secret"))?; 36 | 37 | if token.as_bytes() 38 | != headers 39 | .get("X-Gitlab-Token") 40 | .ok_or((StatusCode::UNAUTHORIZED, "Token missing"))? 41 | { 42 | return Err((StatusCode::UNAUTHORIZED, "Token mismatch")); 43 | } 44 | } 45 | 46 | // Only allow 'push' or 'ping' events 47 | let event = headers 48 | .get("X-Gitlab-Event") 49 | .ok_or((StatusCode::BAD_REQUEST, "Not a GitLab webhook request"))?; 50 | trace!("Got GitLab event: {:?}", event); 51 | if event != "Push Hook" && event != "Push Event" { 52 | return Err((StatusCode::BAD_REQUEST, "Event not supported")); 53 | } 54 | 55 | // Parse the payload 56 | let payload = json::parse(&body).map_err(|_| (StatusCode::BAD_REQUEST, "Invalid payload"))?; 57 | 58 | // Check if push is for our remote branch 59 | trace!("Got push event for '{}'", payload["ref"]); 60 | if config.check_branch.unwrap_or(true) 61 | && payload["ref"].as_str() != Some(&repo.config.remote_ref()) 62 | { 63 | return Ok(()); 64 | } 65 | 66 | debug!("Trigger update from hook"); 67 | producer.send(repo.clone()).await.expect("Receiver dropped"); 68 | 69 | Ok(()) 70 | } 71 | -------------------------------------------------------------------------------- /src/webhook/github.rs: -------------------------------------------------------------------------------- 1 | use crate::config::GitHubWebhook; 2 | use crate::repo::Repo; 3 | use anyhow::Result; 4 | use axum::extract::State; 5 | use axum::http::{HeaderMap, StatusCode}; 6 | use axum::routing::post; 7 | use axum::Router; 8 | use hmac::{Hmac, Mac}; 9 | use sha1::Sha1; 10 | use std::sync::Arc; 11 | use tracing::{debug, trace}; 12 | 13 | pub(super) fn router( 14 | config: GitHubWebhook, 15 | producer: tokio::sync::mpsc::Sender>, 16 | repo: Arc, 17 | ) -> Router { 18 | Router::<_>::new() 19 | .route("/", post(handle)) 20 | .with_state((config, producer, repo)) 21 | } 22 | 23 | async fn handle( 24 | State((config, producer, repo)): State<( 25 | GitHubWebhook, 26 | tokio::sync::mpsc::Sender>, 27 | Arc, 28 | )>, 29 | headers: HeaderMap, 30 | body: String, 31 | ) -> Result<(), (StatusCode, &'static str)> { 32 | // Check if the signature matches the secret 33 | if let Some(ref secret) = config.secret { 34 | let signature = headers 35 | .get("X-Hub-Signature") 36 | .ok_or((StatusCode::UNAUTHORIZED, "Signature missing"))? 37 | .as_bytes(); 38 | let signature = signature 39 | .strip_prefix(b"sha1=") 40 | .ok_or((StatusCode::UNAUTHORIZED, "Signature prefix missing"))?; 41 | let signature = 42 | hex::decode(signature).map_err(|_| (StatusCode::UNAUTHORIZED, "Invalid signature"))?; 43 | 44 | let mut hmac = Hmac::::new_from_slice( 45 | secret 46 | .load() 47 | .await 48 | .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Failed to load secret"))? 49 | .as_bytes(), 50 | ) 51 | .expect("HMAC can take key of any size"); 52 | hmac.update(body.as_bytes()); 53 | 54 | if let Err(_) = hmac.verify_slice(&signature) { 55 | return Err((StatusCode::UNAUTHORIZED, "Signature mismatch")); 56 | } 57 | } 58 | 59 | // Only allow 'push' or 'ping' events 60 | let event = headers 61 | .get("X-GitHub-Event") 62 | .ok_or((StatusCode::BAD_REQUEST, "Not a GitHub webhook request"))?; 63 | trace!("Got GitHub event: {:?}", event); 64 | 65 | if event == "ping" { 66 | return Ok(()); 67 | } else if event != "push" { 68 | return Err((StatusCode::BAD_REQUEST, "Event not supported")); 69 | } 70 | 71 | // Parse the payload 72 | let payload = json::parse(&body).map_err(|_| (StatusCode::BAD_REQUEST, "Invalid payload"))?; 73 | 74 | // Check if push is for our remote branch 75 | trace!("Got push event for '{}'", payload["ref"]); 76 | if config.check_branch.unwrap_or(true) 77 | && payload["ref"].as_str() != Some(&repo.config.remote_ref()) 78 | { 79 | return Ok(()); 80 | } 81 | 82 | debug!("Trigger update from hook"); 83 | producer.send(repo.clone()).await.expect("Receiver dropped"); 84 | 85 | Ok(()) 86 | } 87 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-compat": { 4 | "flake": false, 5 | "locked": { 6 | "lastModified": 1696426674, 7 | "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", 8 | "owner": "edolstra", 9 | "repo": "flake-compat", 10 | "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", 11 | "type": "github" 12 | }, 13 | "original": { 14 | "owner": "edolstra", 15 | "repo": "flake-compat", 16 | "type": "github" 17 | } 18 | }, 19 | "flake-utils": { 20 | "inputs": { 21 | "systems": "systems" 22 | }, 23 | "locked": { 24 | "lastModified": 1731533236, 25 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 26 | "owner": "numtide", 27 | "repo": "flake-utils", 28 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 29 | "type": "github" 30 | }, 31 | "original": { 32 | "owner": "numtide", 33 | "repo": "flake-utils", 34 | "type": "github" 35 | } 36 | }, 37 | "gitignore": { 38 | "inputs": { 39 | "nixpkgs": [ 40 | "pre-commit-hooks", 41 | "nixpkgs" 42 | ] 43 | }, 44 | "locked": { 45 | "lastModified": 1709087332, 46 | "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", 47 | "owner": "hercules-ci", 48 | "repo": "gitignore.nix", 49 | "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "hercules-ci", 54 | "repo": "gitignore.nix", 55 | "type": "github" 56 | } 57 | }, 58 | "nixpkgs": { 59 | "locked": { 60 | "lastModified": 1746663147, 61 | "narHash": "sha256-Ua0drDHawlzNqJnclTJGf87dBmaO/tn7iZ+TCkTRpRc=", 62 | "owner": "NixOS", 63 | "repo": "nixpkgs", 64 | "rev": "dda3dcd3fe03e991015e9a74b22d35950f264a54", 65 | "type": "github" 66 | }, 67 | "original": { 68 | "owner": "NixOS", 69 | "ref": "nixos-unstable", 70 | "repo": "nixpkgs", 71 | "type": "github" 72 | } 73 | }, 74 | "pre-commit-hooks": { 75 | "inputs": { 76 | "flake-compat": "flake-compat", 77 | "gitignore": "gitignore", 78 | "nixpkgs": [ 79 | "nixpkgs" 80 | ] 81 | }, 82 | "locked": { 83 | "lastModified": 1746537231, 84 | "narHash": "sha256-Wb2xeSyOsCoTCTj7LOoD6cdKLEROyFAArnYoS+noCWo=", 85 | "owner": "cachix", 86 | "repo": "git-hooks.nix", 87 | "rev": "fa466640195d38ec97cf0493d6d6882bc4d14969", 88 | "type": "github" 89 | }, 90 | "original": { 91 | "owner": "cachix", 92 | "repo": "git-hooks.nix", 93 | "type": "github" 94 | } 95 | }, 96 | "root": { 97 | "inputs": { 98 | "flake-utils": "flake-utils", 99 | "nixpkgs": "nixpkgs", 100 | "pre-commit-hooks": "pre-commit-hooks" 101 | } 102 | }, 103 | "systems": { 104 | "locked": { 105 | "lastModified": 1681028828, 106 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 107 | "owner": "nix-systems", 108 | "repo": "default", 109 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 110 | "type": "github" 111 | }, 112 | "original": { 113 | "owner": "nix-systems", 114 | "repo": "default", 115 | "type": "github" 116 | } 117 | } 118 | }, 119 | "root": "root", 120 | "version": 7 121 | } 122 | -------------------------------------------------------------------------------- /src/webhook/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::config::Webhook; 2 | use crate::repo::Repo; 3 | use anyhow::Result; 4 | use axum::routing::get; 5 | use axum::Router; 6 | use std::future::Future; 7 | use std::sync::Arc; 8 | use tokio_util::sync::CancellationToken; 9 | 10 | mod github; 11 | mod gitlab; 12 | mod plain; 13 | 14 | async fn root() -> &'static str { 15 | "pullomatic webhook server" 16 | } 17 | 18 | pub fn serve( 19 | addr: String, 20 | running: CancellationToken, 21 | producer: tokio::sync::mpsc::Sender>, 22 | repos: &[Arc], 23 | ) -> impl Future> + use<> { 24 | let mut app = Router::new().route("/", get(root)); 25 | 26 | for repo in repos { 27 | let Some(ref config) = repo.config.webhook else { 28 | continue; 29 | }; 30 | 31 | app = app.nest( 32 | &format!("/{}", repo.name), 33 | match config { 34 | Webhook::Plain(config) => { 35 | plain::router(config.clone(), producer.clone(), repo.clone()) 36 | } 37 | Webhook::GitHub(config) => { 38 | github::router(config.clone(), producer.clone(), repo.clone()) 39 | } 40 | Webhook::GitLab(config) => { 41 | gitlab::router(config.clone(), producer.clone(), repo.clone()) 42 | } 43 | }, 44 | ); 45 | } 46 | 47 | async move { 48 | let listener = tokio::net::TcpListener::bind(addr).await?; 49 | axum::serve(listener, app) 50 | .with_graceful_shutdown(running.cancelled_owned()) 51 | .await?; 52 | 53 | Ok(()) 54 | } 55 | } 56 | 57 | // fn handle(repo: &Repo, request: &Request) -> Result { 58 | // if let Some(ref config) = repo.config().webhook { 59 | // return match config { 60 | // &Webhook::Plain(ref config) => plain::handle(&repo, config, request), 61 | // &Webhook::GitHub(ref config) => github::handle(&repo, config, request), 62 | // &Webhook::GitLab(ref config) => gitlab::handle(&repo, config, request), 63 | // }; 64 | // } else { 65 | // return Err("Repository not configured for webhooks".to_owned()); 66 | // } 67 | // } 68 | // 69 | // pub fn serve( 70 | // addr: String, 71 | // repos: Arc>>, 72 | // producer: SyncSender>, 73 | // ) -> JoinHandle<()> { 74 | // return thread::spawn(move || { 75 | // let server = Server::new(addr, move |request: &Request| { 76 | // let _request = info_span!("Handle webhook request").entered(); 77 | // 78 | // // Get the path without the leading slash 79 | // let path = &request.url()[1..]; 80 | // 81 | // // Try find the repo this the path interpreted as name 82 | // let repo = repos.iter().find(move |repo| repo.name() == path).cloned(); 83 | // let Some(repo) = repo else { 84 | // return Response::empty_404(); 85 | // }; 86 | // 87 | // let _repo = info_span!("Handle webhook request", repo = repo.name()).entered(); 88 | // 89 | // match handle(&repo, request) { 90 | // Ok(trigger) => { 91 | // if trigger { 92 | // producer.send(repo).unwrap(); 93 | // } 94 | // 95 | // return Response::empty_204(); 96 | // } 97 | // 98 | // Err(error) => { 99 | // return Response::text(error).with_status_code(400); 100 | // } 101 | // } 102 | // }) 103 | // .expect("Failed to start server"); 104 | // 105 | // use super::RUNNING; 106 | // while RUNNING.load(Ordering::SeqCst) { 107 | // server.poll(); 108 | // } 109 | // }); 110 | // } 111 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use serde::Deserialize; 3 | use std::collections::HashMap; 4 | use std::path::{Path, PathBuf}; 5 | use std::time::Duration; 6 | 7 | #[derive(Clone, Debug, Deserialize)] 8 | #[serde(untagged)] 9 | pub enum Secret { 10 | Literal(String), 11 | File { file: PathBuf }, 12 | } 13 | 14 | impl Secret { 15 | pub async fn load(&self) -> Result { 16 | Ok(match self { 17 | Secret::Literal(s) => s.clone(), 18 | Secret::File { file } => tokio::fs::read_to_string(file) 19 | .await 20 | .with_context(|| format!("Failed to read secret file: {}", file.display()))?, 21 | }) 22 | } 23 | } 24 | 25 | #[derive(Clone, Debug, Deserialize)] 26 | pub struct SshCredentials { 27 | pub username: String, 28 | 29 | pub public_key: Option, 30 | pub private_key: Secret, 31 | 32 | pub passphrase: Option, 33 | } 34 | 35 | #[derive(Clone, Debug, Deserialize)] 36 | pub struct PasswordCredentials { 37 | pub username: String, 38 | pub password: Secret, 39 | } 40 | 41 | #[derive(Clone, Debug, Deserialize)] 42 | #[serde(untagged)] 43 | pub enum Credentials { 44 | Ssh(SshCredentials), 45 | Password(PasswordCredentials), 46 | } 47 | 48 | #[derive(Clone, Debug, Deserialize)] 49 | pub struct PlainWebhook {} 50 | 51 | #[derive(Clone, Debug, Deserialize)] 52 | pub struct GitHubWebhook { 53 | pub secret: Option, 54 | pub check_branch: Option, 55 | } 56 | 57 | #[derive(Clone, Debug, Deserialize)] 58 | pub struct GitLabWebhook { 59 | pub token: Option, 60 | pub check_branch: Option, 61 | } 62 | 63 | #[derive(Clone, Debug, Deserialize)] 64 | #[serde(tag = "provider", rename_all = "lowercase")] 65 | pub enum Webhook { 66 | Plain(PlainWebhook), 67 | GitHub(GitHubWebhook), 68 | GitLab(GitLabWebhook), 69 | } 70 | 71 | #[derive(Clone, Debug, Deserialize)] 72 | pub struct Interval { 73 | #[serde(with = "serde_humantime")] 74 | pub interval: Duration, 75 | } 76 | 77 | #[derive(Clone, Debug, Deserialize)] 78 | pub struct Config { 79 | pub path: PathBuf, 80 | 81 | pub remote_url: String, 82 | pub remote_branch: String, 83 | 84 | pub on_change: Option, 85 | 86 | pub credentials: Option, 87 | 88 | pub interval: Option, 89 | pub webhook: Option, 90 | } 91 | 92 | impl Config { 93 | pub async fn load(path: &Path) -> Result> { 94 | let mut configs = HashMap::new(); 95 | 96 | if !path.exists() { 97 | anyhow::bail!("Config directory does not exist: {}", path.display()); 98 | } 99 | 100 | let mut dir = tokio::fs::read_dir(path) 101 | .await 102 | .with_context(|| format!("Failed to read config directory: {}", path.display()))?; 103 | 104 | while let Some(entry) = dir.next_entry().await? { 105 | let path = entry.path(); 106 | 107 | // TODO: Can we do this better? 108 | let name = path.file_name().unwrap().to_str().unwrap().to_owned(); 109 | 110 | let config = Self::load_config(&path) 111 | .await 112 | .with_context(|| format!("Failed to load config file: {}", path.display()))?; 113 | 114 | configs.insert(name, config); 115 | } 116 | 117 | Ok(configs) 118 | } 119 | 120 | async fn load_config(path: &Path) -> Result { 121 | // FIXME: Specify interval as string (i.e. "5m") 122 | 123 | let input = tokio::fs::read_to_string(&path) 124 | .await 125 | .with_context(|| format!("Failed to read config file: {}", path.display()))?; 126 | 127 | let config = serde_yaml::from_str(&input) 128 | .with_context(|| format!("Failed to parse config file: {}", path.display()))?; 129 | 130 | Ok(config) 131 | } 132 | 133 | pub fn remote_ref(&self) -> String { 134 | format!("refs/heads/{}", self.remote_branch) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use clap::Parser; 3 | use config::Config; 4 | use futures::future::FutureExt; 5 | use repo::Repo; 6 | use std::path::PathBuf; 7 | use std::process::Stdio; 8 | use std::sync::Arc; 9 | use tokio::io::{AsyncBufReadExt, BufReader}; 10 | use tokio_util::sync::CancellationToken; 11 | use tokio_util::task::TaskTracker; 12 | use tracing::{debug, error, info_span, trace, Instrument, Level}; 13 | 14 | mod config; 15 | mod repo; 16 | mod webhook; 17 | 18 | #[derive(Parser, Debug)] 19 | #[command(version, about, author, name = "pullomatic")] 20 | struct Args { 21 | #[arg(short = 'c', long = "config", default_value = "/etc/pullomatic")] 22 | config: PathBuf, 23 | 24 | #[arg(short = 'w', long = "webhook-listen", default_value = "localhost:8000")] 25 | webhook_listen: String, 26 | 27 | #[arg(short = 'v', long = "verbose", action = clap::ArgAction::Count, default_value = "0")] 28 | verbose: u8, 29 | } 30 | 31 | #[tokio::main] 32 | async fn main() -> Result<()> { 33 | let args = Args::parse(); 34 | 35 | tracing_subscriber::FmtSubscriber::builder() 36 | .with_max_level(match args.verbose { 37 | 0 => Level::WARN, 38 | 1 => Level::INFO, 39 | 2 => Level::DEBUG, 40 | _ => Level::TRACE, 41 | }) 42 | .init(); 43 | 44 | let config = Config::load(&args.config) 45 | .await 46 | .with_context(|| format!("Failed to load config from {}", args.config.display()))?; 47 | 48 | let repos: Vec> = config 49 | .into_iter() 50 | .map(|(name, config)| Arc::new(Repo::new(name, config))) 51 | .collect(); 52 | 53 | // A single global worker queue to serialize all update checks 54 | let (producer, mut consumer) = tokio::sync::mpsc::channel(repos.len() + 1); 55 | 56 | let running = CancellationToken::new(); 57 | let tasks = TaskTracker::new(); 58 | 59 | // Create periodic update tasks for all repos 60 | for repo in repos.iter().cloned() { 61 | let Some(interval) = &repo.config.interval else { 62 | continue; 63 | }; 64 | 65 | let interval = interval.interval; 66 | 67 | let producer = producer.clone(); 68 | let running = running.clone(); 69 | 70 | tasks.spawn(async move { 71 | let mut interval = tokio::time::interval(interval); 72 | loop { 73 | tokio::select! { 74 | _ = interval.tick() => { 75 | producer.send(repo.clone()).await.expect("Receiver closed"); 76 | } 77 | 78 | _ = running.cancelled() => { 79 | break; 80 | } 81 | } 82 | } 83 | }); 84 | } 85 | 86 | // Start web server 87 | tasks.spawn(webhook::serve( 88 | args.webhook_listen, 89 | running.clone(), 90 | producer.clone(), 91 | &repos, 92 | )); 93 | 94 | // Listen for shutdown signal 95 | tasks.spawn({ 96 | let running = running.clone(); 97 | 98 | async move { 99 | tokio::signal::ctrl_c() 100 | .await 101 | .expect("Failed to listen for Ctrl+C"); 102 | debug!("Received Ctrl+C. Shutting down."); 103 | running.cancel(); 104 | } 105 | }); 106 | 107 | // Handle refresh tasks from queue 108 | loop { 109 | tokio::select! { 110 | _ = running.cancelled() => { 111 | debug!("Shutting down"); 112 | break; 113 | } 114 | 115 | repo = consumer.recv() => { 116 | let Some(repo) = repo else { 117 | break; 118 | }; 119 | 120 | let task = precess(repo.clone()); 121 | let task = task.map(|result| match result { 122 | Ok(_) => { trace!("Update successful"); } 123 | Err(err) => { error!("Error while updating: {:#}", err); } 124 | }); 125 | let task = task.instrument(info_span!("Update repo", repo = repo.name)); 126 | 127 | task.await; 128 | } 129 | } 130 | } 131 | 132 | tasks.close(); 133 | tasks.wait().await; 134 | 135 | return Ok(()); 136 | } 137 | 138 | async fn precess(repo: Arc) -> Result<()> { 139 | let changed = repo 140 | .update() 141 | .await 142 | .with_context(|| format!("Error while update {}", repo.name))?; 143 | 144 | if !changed { 145 | trace!("No changes"); 146 | return Ok(()); 147 | } 148 | 149 | let Some(ref script) = repo.config.on_change else { 150 | trace!("No script to execute"); 151 | return Ok(()); 152 | }; 153 | 154 | let mut child = tokio::process::Command::new("sh") 155 | .arg("-c") 156 | .arg(script) 157 | .current_dir(&repo.config.path) 158 | .stdin(Stdio::null()) 159 | .stdout(Stdio::piped()) 160 | .stderr(Stdio::piped()) 161 | .spawn() 162 | .context("Failed to spawn script")?; 163 | 164 | let mut stdout = BufReader::new(child.stdout.take().expect("Failed to take stdout")).lines(); 165 | let mut stderr = BufReader::new(child.stderr.take().expect("Failed to take stderr")).lines(); 166 | 167 | loop { 168 | tokio::select! { 169 | Ok(Some(line)) = stdout.next_line() => { 170 | trace!("> {}", line); 171 | } 172 | 173 | Ok(Some(line)) = stderr.next_line() => { 174 | trace!("! {}", line); 175 | } 176 | 177 | else => break, 178 | } 179 | } 180 | 181 | child.wait().await.context("Failed to wait for script")?; 182 | 183 | Ok(()) 184 | } 185 | -------------------------------------------------------------------------------- /src/repo.rs: -------------------------------------------------------------------------------- 1 | use crate::config::{Config, Credentials}; 2 | use anyhow::{Context, Result}; 3 | use std::time::Instant; 4 | use tokio::sync::Mutex; 5 | use tracing::{debug, info, trace}; 6 | 7 | #[derive(Debug)] 8 | struct RepoState { 9 | last_checked: Option, 10 | last_changed: Option, 11 | } 12 | 13 | #[derive(Debug)] 14 | pub struct Repo { 15 | pub name: String, 16 | pub config: Config, 17 | 18 | state: Mutex, 19 | } 20 | 21 | const TARGET_REF: &str = "refs/pullomatic"; 22 | 23 | impl Repo { 24 | pub fn new(name: String, config: Config) -> Self { 25 | Self { 26 | name, 27 | config, 28 | 29 | state: Mutex::new(RepoState { 30 | last_checked: None, 31 | last_changed: None, 32 | }), 33 | } 34 | } 35 | 36 | pub async fn update(&self) -> Result { 37 | let mut state = self.state.lock().await; 38 | 39 | let now = Some(Instant::now()); 40 | state.last_checked = now; 41 | 42 | let path = self.config.path.as_path(); 43 | 44 | let repository: git2::Repository; 45 | if path.exists() { 46 | debug!("Using existing repository"); 47 | repository = tokio::task::block_in_place(|| git2::Repository::open(path))?; 48 | } else { 49 | debug!("Initialized new repository"); 50 | tokio::fs::create_dir_all(path).await?; 51 | repository = tokio::task::block_in_place(|| git2::Repository::init(path))?; 52 | } 53 | 54 | let mut remote = repository.remote_anonymous(&self.config.remote_url)?; 55 | 56 | let mut remote_cb = git2::RemoteCallbacks::new(); 57 | match &self.config.credentials { 58 | None => {} 59 | 60 | Some(Credentials::Password(password)) => { 61 | let plain_username = password.username.clone(); 62 | let plain_password = password.password.load().await?; 63 | 64 | remote_cb.credentials(move |url, username, allowed| { 65 | trace!("cred: url = {:?}", url); 66 | trace!("cred: username = {:?}", username); 67 | trace!("cred: allowed = {:?}", allowed); 68 | 69 | if allowed.contains(git2::CredentialType::USERNAME) { 70 | return git2::Cred::username(&plain_username); 71 | } 72 | 73 | if allowed.contains(git2::CredentialType::USER_PASS_PLAINTEXT) { 74 | return git2::Cred::userpass_plaintext(&plain_username, &plain_password); 75 | } 76 | 77 | Err(git2::Error::from_str("Unsupported authentication")) 78 | }); 79 | } 80 | 81 | Some(Credentials::Ssh(ssh)) => { 82 | let ssh_username = ssh.username.as_str(); 83 | 84 | let ssh_private_key = ssh.private_key.load().await?; 85 | let ssh_public_key = ssh.public_key.as_ref().map(String::as_ref); 86 | 87 | let ssh_passphrase = match ssh.passphrase { 88 | Some(ref passphrase) => Some(passphrase.load().await?), 89 | None => None, 90 | }; 91 | 92 | remote_cb.credentials(move |url, username, allowed| { 93 | trace!("cred: url = {:?}", url); 94 | trace!("cred: username = {:?}", username); 95 | trace!("cred: allowed = {:?}", allowed); 96 | 97 | if allowed.contains(git2::CredentialType::USERNAME) { 98 | return git2::Cred::username(ssh_username); 99 | } 100 | 101 | if allowed.contains(git2::CredentialType::SSH_KEY) { 102 | return git2::Cred::ssh_key_from_memory( 103 | ssh_username, 104 | ssh_public_key, 105 | ssh_private_key.as_ref(), 106 | ssh_passphrase.as_ref().map(String::as_ref), 107 | ); 108 | } 109 | 110 | Err(git2::Error::from_str("Unsupported authentication")) 111 | }); 112 | } 113 | } 114 | 115 | debug!("Fetching data from remote"); 116 | tokio::task::block_in_place(|| { 117 | // Fetch the remote branch head ref into our target ref 118 | remote 119 | .fetch( 120 | &[&format!("+{}:{}", self.config.remote_ref(), TARGET_REF)], 121 | Some( 122 | git2::FetchOptions::new() 123 | .prune(git2::FetchPrune::On) 124 | .download_tags(git2::AutotagOption::None) 125 | .remote_callbacks(remote_cb), 126 | ), 127 | None, 128 | ) 129 | .with_context(|| { 130 | format!( 131 | "Failed to fetch data from remote: {}", 132 | self.config.remote_ref() 133 | ) 134 | }) 135 | })?; 136 | debug!("Fetched data from remote"); 137 | 138 | let latest_obj = repository.revparse_single("HEAD").ok(); 139 | let target_obj = repository 140 | .revparse_single(TARGET_REF) 141 | .expect("target ref fetched"); 142 | 143 | // If the remote ref is the same as the local HEAD ref, we're up to date 144 | if let Some(ref latest_obj) = latest_obj { 145 | if latest_obj.id() == target_obj.id() { 146 | debug!("Already up to date"); 147 | return Ok(false); 148 | } 149 | } 150 | 151 | tokio::task::block_in_place(|| { 152 | // Reset the local HEAD ref to the remote ref, and force a checkout 153 | repository 154 | .reset( 155 | &target_obj, 156 | git2::ResetType::Hard, 157 | Some( 158 | git2::build::CheckoutBuilder::new() 159 | .force() 160 | .remove_untracked(true), 161 | ), 162 | ) 163 | .with_context(|| format!("Failed to reset repo to target ref: {}", target_obj.id())) 164 | })?; 165 | 166 | info!("Updated to {}", target_obj.id()); 167 | state.last_changed = now; 168 | 169 | Ok(true) 170 | } 171 | 172 | #[allow(unused)] 173 | pub async fn last_checked(&self) -> Option { 174 | let state = self.state.lock().await; 175 | state.last_checked 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | `pullomatic` automates GIT repository synchronisation. 2 | 3 | [![Build Status](https://travis-ci.org/fooker/pullomatic.svg?branch=master)](https://travis-ci.org/fooker/pullomatic) 4 | 5 | Storing configuration or other application data in a GIT repository is a common practice. 6 | Usually custom scripts are used to pull updates from remote from time to time. 7 | `pullomatic` replaces these scripts with pure configuration. 8 | 9 | Beside the polling interval, `pullomatic` provides a HTTP endpoint which can be used to trigger updates by web-hooks. 10 | 11 | Whenever a change is detected in the remote repository branch, the new branch head will be checked out to the path. 12 | 13 | 14 | ## Features 15 | 16 | * One configuration file per repository allowing for easy deployment 17 | * Automatically creates target directory with initial clone 18 | * Runs as daemon and checks repositories by interval without cron / timers 19 | * Can be target for Webhooks supporting [github](https://developer.github.com/webhooks/), [gitlab](https://docs.gitlab.com/ee/user/project/integrations/webhooks.html) and others 20 | * Inline configuration of SSH deploy-keys and credentials 21 | * Executes scripts and commands after updates 22 | 23 | 24 | ## Build 25 | 26 | To build, the following requirements must be installed: 27 | * [rustc](https://www.rust-lang.org) >= 1.25 28 | * [cargo](https://github.com/rust-lang/cargo) >= 0.25 29 | 30 | After downloading the sources. the following command is used to build: 31 | ```bash 32 | cargo build --release 33 | ``` 34 | 35 | The resulting binary will be placed in `target/release/pullomatic`. 36 | 37 | 38 | ## Configuration 39 | 40 | Each repository is configured in a single file which must be placed inside `/etc/pullomatic/`. 41 | The filename is used as repository name and must be formatted as YAML file. 42 | 43 | The configuration must contain a `path` which specifies the path where the repository lives locally. 44 | On startup, the existence of the repository will checked. 45 | If the repository does not exists, the remote repository will be cloned to that path. 46 | Second, the config must contain a `remote_url` and a `remote_branch` which specifies the remote URL of the GIT repository and the branch to check out. 47 | 48 | ### Credentials 49 | The configuration can contain a `credentials` section depending on the transport type used to connect to the remote GIT server. 50 | 51 | Usually, the `remote_url` contains a username if one is required to authenticate. 52 | As a fallback, a `username` can be specified in this section. 53 | 54 | If the SSH or HTTP(S) transport is used, the section can contain a `password` which is used to authenticate agains the remote server. 55 | 56 | For SSH, a key pair can be used as an alternative. 57 | Therefore, a `private_key` must be specified containing the SSH private key (as generated by `ssh-keygen`). 58 | Additionally, a `passphrase` can be specified which is used to unlock the key. 59 | If a `public_key` is given, it will not be derived from the private key, but the given one will be used. 60 | 61 | ### Interval 62 | The check the repository regularly for changes, the configuration can contain a `interval` section. 63 | If this section is present, it must contain a `interval` parameter, which specifies the interval to poll for changes. 64 | The format of this option allows to specify the interval in multiple ways like `30sec` or `5m` (See [here](https://docs.rs/humantime/1.1.1/humantime/fn.parse_duration.html) for more details). 65 | 66 | ### Webhook 67 | To make updates as instant as possible, webhooks can be used to trigger update checks. 68 | The `webhook` section can be used to enable webhook support. 69 | If webhook is enabled in at least one repository, `pullomatic` will listen for incoming HTTP `POST` requests. 70 | 71 | The `provider` parameter must be set to enable support for one of the following supported services: 72 | 73 | | Provider | Config Value | Remarks | 74 | | -------- | ------------ | ------- | 75 | | GitHub | `github` | Only `push` events are supported | 76 | | GitLab | `gitlab` | Only `push` events are supported | 77 | | Plain | `plain` || 78 | 79 | #### GitHub 80 | If the GitHub provider is selected, a `secret` parameter can be given. 81 | The same value must be configured in the GitHub webhook configuration. 82 | 83 | The `check_branch` parameter controls if the branch in the event must match the `remote_branch` of the repository configuration (enabled by default). 84 | 85 | #### GitLab 86 | If the GitLab provider is selected, a `token` parameter can be given. 87 | The same value must be configured in the GitLab webhook configuration. 88 | 89 | The `check_branch` parameter controls if the branch in the event must match the `remote_branch` of the repository configuration (enabled by default). 90 | 91 | #### Plain 92 | If The Plain provider is selected, every `POST` request will trigger an update check. 93 | 94 | 95 | ### Script 96 | Each configuration can include a `on_change` hook option which allows to specify a script which is executed every time the repository has changed. 97 | The script is executed right after the updates has been checked out. 98 | It will be started with the working directory set to the local repository path. 99 | 100 | The script is executed using `sh -c` and therefor it can contain arbitrary shell commands over multiple lines. 101 | Buf for complex scripts, it is recommended to store the script externally (maybe in the repository itself) and just call the script inside the hook. 102 | 103 | ### Overview 104 | The following options are allowed in the configuration: 105 | 106 | | Option | Type | Required | Description | 107 | | ------ | ---- | -------- |----------- | 108 | | `path` | `str` | ✓ | Path to the GIT repository on disk | 109 | | `remote_url` | `str` | ✓ | Remote URL of the GIT repository to pull changes from | 110 | | `remote_branch` | `str` | ✓ | The branch to check out and pull changes from | 111 | | `credentials.username` | `str` | | The username to use if none is given by `remote_url` | 112 | | `credentials.password` | `str` | (✓) | The password used to authenticate (required for password authentication) | 113 | | `credentials.private_key` | `str` | (✓) | The private SSH key used to authenticate (required for SSH authentication) | 114 | | `credentials.public_key` | `str` | | The public SSH key matching the private SSH key | 115 | | `credentials.passphrase` | `str` | | The passphrase used to unlock the private SSH KEY| 116 | | `interval.interval` | `str` | | The interval used to check the remote GIT repository for updates | 117 | | `webhook.provider` | `str` | | Can be one of `github`, `gitlab` or `plain` | 118 | | `webhook.secret` | `str` | | Secret used to authenitcate GitLab webhook events (only valid for provider `github`) | 119 | | `webhook.token` | `str` | | Secret used to authenitcate GitLab webhook events (only valid for provider `gitlab`) | 120 | | `webhook.check_branch` | `bool` | | Checks if the event branch matches `remote_branch` (only valid for provider `github` or `gitlab`) | 121 | | `on_change` | `str` | | A script executed every time the repository has changed | 122 | 123 | 124 | ## Running 125 | 126 | Just execute the `pullomatic` binary. 127 | 128 | The configuration path can be changed by using `-c PATH` or `--config PATH`. 129 | If webhooks are used, the listening address can be changed using `-w ADDR:PORT` or `--webhook-listen ADDR:PORT` (defaults to `locahost:8000`). 130 | 131 | 132 | ## Versioning 133 | 134 | We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/fooker/pullomatic/tags). 135 | 136 | 137 | ## Authors 138 | 139 | * **Dustin Frisch** - *Initial work* - [Dustin Frisch](https://github.com/fooker) 140 | 141 | See also the list of [contributors](https://github.com/fooker/pullomatic/contributors) who participated in this project. 142 | 143 | 144 | ## License 145 | 146 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 147 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "anstream" 7 | version = "0.6.19" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-query", 14 | "anstyle-wincon", 15 | "colorchoice", 16 | "is_terminal_polyfill", 17 | "utf8parse", 18 | ] 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "1.0.11" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" 25 | 26 | [[package]] 27 | name = "anstyle-parse" 28 | version = "0.2.7" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" 31 | dependencies = [ 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle-query" 37 | version = "1.1.3" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" 40 | dependencies = [ 41 | "windows-sys 0.59.0", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-wincon" 46 | version = "3.0.9" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" 49 | dependencies = [ 50 | "anstyle", 51 | "once_cell_polyfill", 52 | "windows-sys 0.59.0", 53 | ] 54 | 55 | [[package]] 56 | name = "anyhow" 57 | version = "1.0.100" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" 60 | 61 | [[package]] 62 | name = "autocfg" 63 | version = "1.4.0" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 66 | 67 | [[package]] 68 | name = "axum" 69 | version = "0.8.4" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" 72 | dependencies = [ 73 | "axum-core", 74 | "axum-macros", 75 | "bytes", 76 | "form_urlencoded", 77 | "futures-util", 78 | "http", 79 | "http-body", 80 | "http-body-util", 81 | "hyper", 82 | "hyper-util", 83 | "itoa", 84 | "matchit", 85 | "memchr", 86 | "mime", 87 | "percent-encoding", 88 | "pin-project-lite", 89 | "rustversion", 90 | "serde", 91 | "serde_json", 92 | "serde_path_to_error", 93 | "serde_urlencoded", 94 | "sync_wrapper", 95 | "tokio", 96 | "tower", 97 | "tower-layer", 98 | "tower-service", 99 | "tracing", 100 | ] 101 | 102 | [[package]] 103 | name = "axum-core" 104 | version = "0.5.2" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" 107 | dependencies = [ 108 | "bytes", 109 | "futures-core", 110 | "http", 111 | "http-body", 112 | "http-body-util", 113 | "mime", 114 | "pin-project-lite", 115 | "rustversion", 116 | "sync_wrapper", 117 | "tower-layer", 118 | "tower-service", 119 | "tracing", 120 | ] 121 | 122 | [[package]] 123 | name = "axum-macros" 124 | version = "0.5.0" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" 127 | dependencies = [ 128 | "proc-macro2", 129 | "quote", 130 | "syn", 131 | ] 132 | 133 | [[package]] 134 | name = "bitflags" 135 | version = "2.9.1" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" 138 | 139 | [[package]] 140 | name = "block-buffer" 141 | version = "0.10.4" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 144 | dependencies = [ 145 | "generic-array", 146 | ] 147 | 148 | [[package]] 149 | name = "bytes" 150 | version = "1.10.1" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 153 | 154 | [[package]] 155 | name = "cc" 156 | version = "1.2.26" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac" 159 | dependencies = [ 160 | "jobserver", 161 | "libc", 162 | "shlex", 163 | ] 164 | 165 | [[package]] 166 | name = "cfg-if" 167 | version = "1.0.0" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 170 | 171 | [[package]] 172 | name = "clap" 173 | version = "4.5.53" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" 176 | dependencies = [ 177 | "clap_builder", 178 | "clap_derive", 179 | ] 180 | 181 | [[package]] 182 | name = "clap_builder" 183 | version = "4.5.53" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" 186 | dependencies = [ 187 | "anstream", 188 | "anstyle", 189 | "clap_lex", 190 | "strsim", 191 | ] 192 | 193 | [[package]] 194 | name = "clap_derive" 195 | version = "4.5.49" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" 198 | dependencies = [ 199 | "heck", 200 | "proc-macro2", 201 | "quote", 202 | "syn", 203 | ] 204 | 205 | [[package]] 206 | name = "clap_lex" 207 | version = "0.7.4" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 210 | 211 | [[package]] 212 | name = "colorchoice" 213 | version = "1.0.4" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 216 | 217 | [[package]] 218 | name = "cpufeatures" 219 | version = "0.2.17" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 222 | dependencies = [ 223 | "libc", 224 | ] 225 | 226 | [[package]] 227 | name = "crypto-common" 228 | version = "0.1.6" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 231 | dependencies = [ 232 | "generic-array", 233 | "typenum", 234 | ] 235 | 236 | [[package]] 237 | name = "digest" 238 | version = "0.10.7" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 241 | dependencies = [ 242 | "block-buffer", 243 | "crypto-common", 244 | "subtle", 245 | ] 246 | 247 | [[package]] 248 | name = "displaydoc" 249 | version = "0.2.5" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 252 | dependencies = [ 253 | "proc-macro2", 254 | "quote", 255 | "syn", 256 | ] 257 | 258 | [[package]] 259 | name = "equivalent" 260 | version = "1.0.2" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 263 | 264 | [[package]] 265 | name = "fnv" 266 | version = "1.0.7" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 269 | 270 | [[package]] 271 | name = "form_urlencoded" 272 | version = "1.2.1" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 275 | dependencies = [ 276 | "percent-encoding", 277 | ] 278 | 279 | [[package]] 280 | name = "futures" 281 | version = "0.3.31" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 284 | dependencies = [ 285 | "futures-channel", 286 | "futures-core", 287 | "futures-executor", 288 | "futures-io", 289 | "futures-sink", 290 | "futures-task", 291 | "futures-util", 292 | ] 293 | 294 | [[package]] 295 | name = "futures-channel" 296 | version = "0.3.31" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 299 | dependencies = [ 300 | "futures-core", 301 | "futures-sink", 302 | ] 303 | 304 | [[package]] 305 | name = "futures-core" 306 | version = "0.3.31" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 309 | 310 | [[package]] 311 | name = "futures-executor" 312 | version = "0.3.31" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 315 | dependencies = [ 316 | "futures-core", 317 | "futures-task", 318 | "futures-util", 319 | ] 320 | 321 | [[package]] 322 | name = "futures-io" 323 | version = "0.3.31" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 326 | 327 | [[package]] 328 | name = "futures-macro" 329 | version = "0.3.31" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 332 | dependencies = [ 333 | "proc-macro2", 334 | "quote", 335 | "syn", 336 | ] 337 | 338 | [[package]] 339 | name = "futures-sink" 340 | version = "0.3.31" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 343 | 344 | [[package]] 345 | name = "futures-task" 346 | version = "0.3.31" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 349 | 350 | [[package]] 351 | name = "futures-util" 352 | version = "0.3.31" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 355 | dependencies = [ 356 | "futures-channel", 357 | "futures-core", 358 | "futures-io", 359 | "futures-macro", 360 | "futures-sink", 361 | "futures-task", 362 | "memchr", 363 | "pin-project-lite", 364 | "pin-utils", 365 | "slab", 366 | ] 367 | 368 | [[package]] 369 | name = "generic-array" 370 | version = "0.14.7" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 373 | dependencies = [ 374 | "typenum", 375 | "version_check", 376 | ] 377 | 378 | [[package]] 379 | name = "getrandom" 380 | version = "0.3.3" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" 383 | dependencies = [ 384 | "cfg-if", 385 | "libc", 386 | "r-efi", 387 | "wasi 0.14.2+wasi-0.2.4", 388 | ] 389 | 390 | [[package]] 391 | name = "git2" 392 | version = "0.20.2" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "2deb07a133b1520dc1a5690e9bd08950108873d7ed5de38dcc74d3b5ebffa110" 395 | dependencies = [ 396 | "bitflags", 397 | "libc", 398 | "libgit2-sys", 399 | "log", 400 | "openssl-probe", 401 | "openssl-sys", 402 | "url", 403 | ] 404 | 405 | [[package]] 406 | name = "hashbrown" 407 | version = "0.15.4" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" 410 | 411 | [[package]] 412 | name = "heck" 413 | version = "0.5.0" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 416 | 417 | [[package]] 418 | name = "hex" 419 | version = "0.4.3" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 422 | 423 | [[package]] 424 | name = "hmac" 425 | version = "0.12.1" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 428 | dependencies = [ 429 | "digest", 430 | ] 431 | 432 | [[package]] 433 | name = "http" 434 | version = "1.3.1" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" 437 | dependencies = [ 438 | "bytes", 439 | "fnv", 440 | "itoa", 441 | ] 442 | 443 | [[package]] 444 | name = "http-body" 445 | version = "1.0.1" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 448 | dependencies = [ 449 | "bytes", 450 | "http", 451 | ] 452 | 453 | [[package]] 454 | name = "http-body-util" 455 | version = "0.1.3" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" 458 | dependencies = [ 459 | "bytes", 460 | "futures-core", 461 | "http", 462 | "http-body", 463 | "pin-project-lite", 464 | ] 465 | 466 | [[package]] 467 | name = "httparse" 468 | version = "1.10.1" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 471 | 472 | [[package]] 473 | name = "httpdate" 474 | version = "1.0.3" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 477 | 478 | [[package]] 479 | name = "humantime" 480 | version = "1.3.0" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 483 | dependencies = [ 484 | "quick-error", 485 | ] 486 | 487 | [[package]] 488 | name = "hyper" 489 | version = "1.6.0" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" 492 | dependencies = [ 493 | "bytes", 494 | "futures-channel", 495 | "futures-util", 496 | "http", 497 | "http-body", 498 | "httparse", 499 | "httpdate", 500 | "itoa", 501 | "pin-project-lite", 502 | "smallvec", 503 | "tokio", 504 | ] 505 | 506 | [[package]] 507 | name = "hyper-util" 508 | version = "0.1.14" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" 511 | dependencies = [ 512 | "bytes", 513 | "futures-core", 514 | "http", 515 | "http-body", 516 | "hyper", 517 | "pin-project-lite", 518 | "tokio", 519 | "tower-service", 520 | ] 521 | 522 | [[package]] 523 | name = "icu_collections" 524 | version = "2.0.0" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" 527 | dependencies = [ 528 | "displaydoc", 529 | "potential_utf", 530 | "yoke", 531 | "zerofrom", 532 | "zerovec", 533 | ] 534 | 535 | [[package]] 536 | name = "icu_locale_core" 537 | version = "2.0.0" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" 540 | dependencies = [ 541 | "displaydoc", 542 | "litemap", 543 | "tinystr", 544 | "writeable", 545 | "zerovec", 546 | ] 547 | 548 | [[package]] 549 | name = "icu_normalizer" 550 | version = "2.0.0" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" 553 | dependencies = [ 554 | "displaydoc", 555 | "icu_collections", 556 | "icu_normalizer_data", 557 | "icu_properties", 558 | "icu_provider", 559 | "smallvec", 560 | "zerovec", 561 | ] 562 | 563 | [[package]] 564 | name = "icu_normalizer_data" 565 | version = "2.0.0" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" 568 | 569 | [[package]] 570 | name = "icu_properties" 571 | version = "2.0.1" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" 574 | dependencies = [ 575 | "displaydoc", 576 | "icu_collections", 577 | "icu_locale_core", 578 | "icu_properties_data", 579 | "icu_provider", 580 | "potential_utf", 581 | "zerotrie", 582 | "zerovec", 583 | ] 584 | 585 | [[package]] 586 | name = "icu_properties_data" 587 | version = "2.0.1" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" 590 | 591 | [[package]] 592 | name = "icu_provider" 593 | version = "2.0.0" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" 596 | dependencies = [ 597 | "displaydoc", 598 | "icu_locale_core", 599 | "stable_deref_trait", 600 | "tinystr", 601 | "writeable", 602 | "yoke", 603 | "zerofrom", 604 | "zerotrie", 605 | "zerovec", 606 | ] 607 | 608 | [[package]] 609 | name = "idna" 610 | version = "1.0.3" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 613 | dependencies = [ 614 | "idna_adapter", 615 | "smallvec", 616 | "utf8_iter", 617 | ] 618 | 619 | [[package]] 620 | name = "idna_adapter" 621 | version = "1.2.1" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 624 | dependencies = [ 625 | "icu_normalizer", 626 | "icu_properties", 627 | ] 628 | 629 | [[package]] 630 | name = "indexmap" 631 | version = "2.9.0" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" 634 | dependencies = [ 635 | "equivalent", 636 | "hashbrown", 637 | ] 638 | 639 | [[package]] 640 | name = "is_terminal_polyfill" 641 | version = "1.70.1" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 644 | 645 | [[package]] 646 | name = "itoa" 647 | version = "1.0.15" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 650 | 651 | [[package]] 652 | name = "jobserver" 653 | version = "0.1.33" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" 656 | dependencies = [ 657 | "getrandom", 658 | "libc", 659 | ] 660 | 661 | [[package]] 662 | name = "json" 663 | version = "0.12.4" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" 666 | 667 | [[package]] 668 | name = "lazy_static" 669 | version = "1.5.0" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 672 | 673 | [[package]] 674 | name = "libc" 675 | version = "0.2.172" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 678 | 679 | [[package]] 680 | name = "libgit2-sys" 681 | version = "0.18.1+1.9.0" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "e1dcb20f84ffcdd825c7a311ae347cce604a6f084a767dec4a4929829645290e" 684 | dependencies = [ 685 | "cc", 686 | "libc", 687 | "libssh2-sys", 688 | "libz-sys", 689 | "openssl-sys", 690 | "pkg-config", 691 | ] 692 | 693 | [[package]] 694 | name = "libssh2-sys" 695 | version = "0.3.1" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "220e4f05ad4a218192533b300327f5150e809b54c4ec83b5a1d91833601811b9" 698 | dependencies = [ 699 | "cc", 700 | "libc", 701 | "libz-sys", 702 | "openssl-sys", 703 | "pkg-config", 704 | "vcpkg", 705 | ] 706 | 707 | [[package]] 708 | name = "libz-sys" 709 | version = "1.1.22" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" 712 | dependencies = [ 713 | "cc", 714 | "libc", 715 | "pkg-config", 716 | "vcpkg", 717 | ] 718 | 719 | [[package]] 720 | name = "litemap" 721 | version = "0.8.0" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" 724 | 725 | [[package]] 726 | name = "lock_api" 727 | version = "0.4.13" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" 730 | dependencies = [ 731 | "autocfg", 732 | "scopeguard", 733 | ] 734 | 735 | [[package]] 736 | name = "log" 737 | version = "0.4.27" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 740 | 741 | [[package]] 742 | name = "matchit" 743 | version = "0.8.4" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" 746 | 747 | [[package]] 748 | name = "memchr" 749 | version = "2.7.4" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 752 | 753 | [[package]] 754 | name = "mime" 755 | version = "0.3.17" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 758 | 759 | [[package]] 760 | name = "mio" 761 | version = "1.0.4" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" 764 | dependencies = [ 765 | "libc", 766 | "wasi 0.11.0+wasi-snapshot-preview1", 767 | "windows-sys 0.59.0", 768 | ] 769 | 770 | [[package]] 771 | name = "nu-ansi-term" 772 | version = "0.46.0" 773 | source = "registry+https://github.com/rust-lang/crates.io-index" 774 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 775 | dependencies = [ 776 | "overload", 777 | "winapi", 778 | ] 779 | 780 | [[package]] 781 | name = "once_cell" 782 | version = "1.21.3" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 785 | 786 | [[package]] 787 | name = "once_cell_polyfill" 788 | version = "1.70.1" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" 791 | 792 | [[package]] 793 | name = "openssl-probe" 794 | version = "0.1.6" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 797 | 798 | [[package]] 799 | name = "openssl-sys" 800 | version = "0.9.109" 801 | source = "registry+https://github.com/rust-lang/crates.io-index" 802 | checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" 803 | dependencies = [ 804 | "cc", 805 | "libc", 806 | "pkg-config", 807 | "vcpkg", 808 | ] 809 | 810 | [[package]] 811 | name = "overload" 812 | version = "0.1.1" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 815 | 816 | [[package]] 817 | name = "parking_lot" 818 | version = "0.12.4" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" 821 | dependencies = [ 822 | "lock_api", 823 | "parking_lot_core", 824 | ] 825 | 826 | [[package]] 827 | name = "parking_lot_core" 828 | version = "0.9.11" 829 | source = "registry+https://github.com/rust-lang/crates.io-index" 830 | checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" 831 | dependencies = [ 832 | "cfg-if", 833 | "libc", 834 | "redox_syscall", 835 | "smallvec", 836 | "windows-targets", 837 | ] 838 | 839 | [[package]] 840 | name = "percent-encoding" 841 | version = "2.3.1" 842 | source = "registry+https://github.com/rust-lang/crates.io-index" 843 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 844 | 845 | [[package]] 846 | name = "pin-project-lite" 847 | version = "0.2.16" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 850 | 851 | [[package]] 852 | name = "pin-utils" 853 | version = "0.1.0" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 856 | 857 | [[package]] 858 | name = "pkg-config" 859 | version = "0.3.32" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 862 | 863 | [[package]] 864 | name = "potential_utf" 865 | version = "0.1.2" 866 | source = "registry+https://github.com/rust-lang/crates.io-index" 867 | checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" 868 | dependencies = [ 869 | "zerovec", 870 | ] 871 | 872 | [[package]] 873 | name = "proc-macro2" 874 | version = "1.0.95" 875 | source = "registry+https://github.com/rust-lang/crates.io-index" 876 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 877 | dependencies = [ 878 | "unicode-ident", 879 | ] 880 | 881 | [[package]] 882 | name = "pullomatic" 883 | version = "0.2.2" 884 | dependencies = [ 885 | "anyhow", 886 | "axum", 887 | "clap", 888 | "futures", 889 | "git2", 890 | "hex", 891 | "hmac", 892 | "json", 893 | "serde", 894 | "serde-humantime", 895 | "serde_yaml", 896 | "sha1", 897 | "tokio", 898 | "tokio-util", 899 | "tracing", 900 | "tracing-subscriber", 901 | ] 902 | 903 | [[package]] 904 | name = "quick-error" 905 | version = "1.2.3" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 908 | 909 | [[package]] 910 | name = "quote" 911 | version = "1.0.40" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 914 | dependencies = [ 915 | "proc-macro2", 916 | ] 917 | 918 | [[package]] 919 | name = "r-efi" 920 | version = "5.2.0" 921 | source = "registry+https://github.com/rust-lang/crates.io-index" 922 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 923 | 924 | [[package]] 925 | name = "redox_syscall" 926 | version = "0.5.12" 927 | source = "registry+https://github.com/rust-lang/crates.io-index" 928 | checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" 929 | dependencies = [ 930 | "bitflags", 931 | ] 932 | 933 | [[package]] 934 | name = "rustversion" 935 | version = "1.0.21" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" 938 | 939 | [[package]] 940 | name = "ryu" 941 | version = "1.0.20" 942 | source = "registry+https://github.com/rust-lang/crates.io-index" 943 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 944 | 945 | [[package]] 946 | name = "scopeguard" 947 | version = "1.2.0" 948 | source = "registry+https://github.com/rust-lang/crates.io-index" 949 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 950 | 951 | [[package]] 952 | name = "serde" 953 | version = "1.0.228" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 956 | dependencies = [ 957 | "serde_core", 958 | "serde_derive", 959 | ] 960 | 961 | [[package]] 962 | name = "serde-humantime" 963 | version = "0.1.1" 964 | source = "registry+https://github.com/rust-lang/crates.io-index" 965 | checksum = "c367b5dafa12cef19c554638db10acde90d5e9acea2b80e1ad98b00f88068f7d" 966 | dependencies = [ 967 | "humantime", 968 | "serde", 969 | ] 970 | 971 | [[package]] 972 | name = "serde_core" 973 | version = "1.0.228" 974 | source = "registry+https://github.com/rust-lang/crates.io-index" 975 | checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 976 | dependencies = [ 977 | "serde_derive", 978 | ] 979 | 980 | [[package]] 981 | name = "serde_derive" 982 | version = "1.0.228" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 985 | dependencies = [ 986 | "proc-macro2", 987 | "quote", 988 | "syn", 989 | ] 990 | 991 | [[package]] 992 | name = "serde_json" 993 | version = "1.0.140" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 996 | dependencies = [ 997 | "itoa", 998 | "memchr", 999 | "ryu", 1000 | "serde", 1001 | ] 1002 | 1003 | [[package]] 1004 | name = "serde_path_to_error" 1005 | version = "0.1.17" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" 1008 | dependencies = [ 1009 | "itoa", 1010 | "serde", 1011 | ] 1012 | 1013 | [[package]] 1014 | name = "serde_urlencoded" 1015 | version = "0.7.1" 1016 | source = "registry+https://github.com/rust-lang/crates.io-index" 1017 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1018 | dependencies = [ 1019 | "form_urlencoded", 1020 | "itoa", 1021 | "ryu", 1022 | "serde", 1023 | ] 1024 | 1025 | [[package]] 1026 | name = "serde_yaml" 1027 | version = "0.9.34+deprecated" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" 1030 | dependencies = [ 1031 | "indexmap", 1032 | "itoa", 1033 | "ryu", 1034 | "serde", 1035 | "unsafe-libyaml", 1036 | ] 1037 | 1038 | [[package]] 1039 | name = "sha1" 1040 | version = "0.10.6" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 1043 | dependencies = [ 1044 | "cfg-if", 1045 | "cpufeatures", 1046 | "digest", 1047 | ] 1048 | 1049 | [[package]] 1050 | name = "sharded-slab" 1051 | version = "0.1.7" 1052 | source = "registry+https://github.com/rust-lang/crates.io-index" 1053 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 1054 | dependencies = [ 1055 | "lazy_static", 1056 | ] 1057 | 1058 | [[package]] 1059 | name = "shlex" 1060 | version = "1.3.0" 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" 1062 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1063 | 1064 | [[package]] 1065 | name = "signal-hook-registry" 1066 | version = "1.4.5" 1067 | source = "registry+https://github.com/rust-lang/crates.io-index" 1068 | checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" 1069 | dependencies = [ 1070 | "libc", 1071 | ] 1072 | 1073 | [[package]] 1074 | name = "slab" 1075 | version = "0.4.9" 1076 | source = "registry+https://github.com/rust-lang/crates.io-index" 1077 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1078 | dependencies = [ 1079 | "autocfg", 1080 | ] 1081 | 1082 | [[package]] 1083 | name = "smallvec" 1084 | version = "1.15.1" 1085 | source = "registry+https://github.com/rust-lang/crates.io-index" 1086 | checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 1087 | 1088 | [[package]] 1089 | name = "socket2" 1090 | version = "0.6.0" 1091 | source = "registry+https://github.com/rust-lang/crates.io-index" 1092 | checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" 1093 | dependencies = [ 1094 | "libc", 1095 | "windows-sys 0.59.0", 1096 | ] 1097 | 1098 | [[package]] 1099 | name = "stable_deref_trait" 1100 | version = "1.2.0" 1101 | source = "registry+https://github.com/rust-lang/crates.io-index" 1102 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1103 | 1104 | [[package]] 1105 | name = "strsim" 1106 | version = "0.11.1" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1109 | 1110 | [[package]] 1111 | name = "subtle" 1112 | version = "2.6.1" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1115 | 1116 | [[package]] 1117 | name = "syn" 1118 | version = "2.0.101" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" 1121 | dependencies = [ 1122 | "proc-macro2", 1123 | "quote", 1124 | "unicode-ident", 1125 | ] 1126 | 1127 | [[package]] 1128 | name = "sync_wrapper" 1129 | version = "1.0.2" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 1132 | 1133 | [[package]] 1134 | name = "synstructure" 1135 | version = "0.13.2" 1136 | source = "registry+https://github.com/rust-lang/crates.io-index" 1137 | checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 1138 | dependencies = [ 1139 | "proc-macro2", 1140 | "quote", 1141 | "syn", 1142 | ] 1143 | 1144 | [[package]] 1145 | name = "thread_local" 1146 | version = "1.1.8" 1147 | source = "registry+https://github.com/rust-lang/crates.io-index" 1148 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 1149 | dependencies = [ 1150 | "cfg-if", 1151 | "once_cell", 1152 | ] 1153 | 1154 | [[package]] 1155 | name = "tinystr" 1156 | version = "0.8.1" 1157 | source = "registry+https://github.com/rust-lang/crates.io-index" 1158 | checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" 1159 | dependencies = [ 1160 | "displaydoc", 1161 | "zerovec", 1162 | ] 1163 | 1164 | [[package]] 1165 | name = "tokio" 1166 | version = "1.48.0" 1167 | source = "registry+https://github.com/rust-lang/crates.io-index" 1168 | checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" 1169 | dependencies = [ 1170 | "bytes", 1171 | "libc", 1172 | "mio", 1173 | "parking_lot", 1174 | "pin-project-lite", 1175 | "signal-hook-registry", 1176 | "socket2", 1177 | "tokio-macros", 1178 | "windows-sys 0.61.2", 1179 | ] 1180 | 1181 | [[package]] 1182 | name = "tokio-macros" 1183 | version = "2.6.0" 1184 | source = "registry+https://github.com/rust-lang/crates.io-index" 1185 | checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" 1186 | dependencies = [ 1187 | "proc-macro2", 1188 | "quote", 1189 | "syn", 1190 | ] 1191 | 1192 | [[package]] 1193 | name = "tokio-util" 1194 | version = "0.7.17" 1195 | source = "registry+https://github.com/rust-lang/crates.io-index" 1196 | checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" 1197 | dependencies = [ 1198 | "bytes", 1199 | "futures-core", 1200 | "futures-io", 1201 | "futures-sink", 1202 | "futures-util", 1203 | "hashbrown", 1204 | "pin-project-lite", 1205 | "slab", 1206 | "tokio", 1207 | ] 1208 | 1209 | [[package]] 1210 | name = "tower" 1211 | version = "0.5.2" 1212 | source = "registry+https://github.com/rust-lang/crates.io-index" 1213 | checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" 1214 | dependencies = [ 1215 | "futures-core", 1216 | "futures-util", 1217 | "pin-project-lite", 1218 | "sync_wrapper", 1219 | "tokio", 1220 | "tower-layer", 1221 | "tower-service", 1222 | "tracing", 1223 | ] 1224 | 1225 | [[package]] 1226 | name = "tower-layer" 1227 | version = "0.3.3" 1228 | source = "registry+https://github.com/rust-lang/crates.io-index" 1229 | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 1230 | 1231 | [[package]] 1232 | name = "tower-service" 1233 | version = "0.3.3" 1234 | source = "registry+https://github.com/rust-lang/crates.io-index" 1235 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 1236 | 1237 | [[package]] 1238 | name = "tracing" 1239 | version = "0.1.41" 1240 | source = "registry+https://github.com/rust-lang/crates.io-index" 1241 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 1242 | dependencies = [ 1243 | "log", 1244 | "pin-project-lite", 1245 | "tracing-attributes", 1246 | "tracing-core", 1247 | ] 1248 | 1249 | [[package]] 1250 | name = "tracing-attributes" 1251 | version = "0.1.29" 1252 | source = "registry+https://github.com/rust-lang/crates.io-index" 1253 | checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662" 1254 | dependencies = [ 1255 | "proc-macro2", 1256 | "quote", 1257 | "syn", 1258 | ] 1259 | 1260 | [[package]] 1261 | name = "tracing-core" 1262 | version = "0.1.34" 1263 | source = "registry+https://github.com/rust-lang/crates.io-index" 1264 | checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" 1265 | dependencies = [ 1266 | "once_cell", 1267 | "valuable", 1268 | ] 1269 | 1270 | [[package]] 1271 | name = "tracing-log" 1272 | version = "0.2.0" 1273 | source = "registry+https://github.com/rust-lang/crates.io-index" 1274 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 1275 | dependencies = [ 1276 | "log", 1277 | "once_cell", 1278 | "tracing-core", 1279 | ] 1280 | 1281 | [[package]] 1282 | name = "tracing-subscriber" 1283 | version = "0.3.19" 1284 | source = "registry+https://github.com/rust-lang/crates.io-index" 1285 | checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" 1286 | dependencies = [ 1287 | "nu-ansi-term", 1288 | "sharded-slab", 1289 | "smallvec", 1290 | "thread_local", 1291 | "tracing-core", 1292 | "tracing-log", 1293 | ] 1294 | 1295 | [[package]] 1296 | name = "typenum" 1297 | version = "1.18.0" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 1300 | 1301 | [[package]] 1302 | name = "unicode-ident" 1303 | version = "1.0.18" 1304 | source = "registry+https://github.com/rust-lang/crates.io-index" 1305 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 1306 | 1307 | [[package]] 1308 | name = "unsafe-libyaml" 1309 | version = "0.2.11" 1310 | source = "registry+https://github.com/rust-lang/crates.io-index" 1311 | checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" 1312 | 1313 | [[package]] 1314 | name = "url" 1315 | version = "2.5.4" 1316 | source = "registry+https://github.com/rust-lang/crates.io-index" 1317 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 1318 | dependencies = [ 1319 | "form_urlencoded", 1320 | "idna", 1321 | "percent-encoding", 1322 | ] 1323 | 1324 | [[package]] 1325 | name = "utf8_iter" 1326 | version = "1.0.4" 1327 | source = "registry+https://github.com/rust-lang/crates.io-index" 1328 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 1329 | 1330 | [[package]] 1331 | name = "utf8parse" 1332 | version = "0.2.2" 1333 | source = "registry+https://github.com/rust-lang/crates.io-index" 1334 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1335 | 1336 | [[package]] 1337 | name = "valuable" 1338 | version = "0.1.1" 1339 | source = "registry+https://github.com/rust-lang/crates.io-index" 1340 | checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 1341 | 1342 | [[package]] 1343 | name = "vcpkg" 1344 | version = "0.2.15" 1345 | source = "registry+https://github.com/rust-lang/crates.io-index" 1346 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1347 | 1348 | [[package]] 1349 | name = "version_check" 1350 | version = "0.9.5" 1351 | source = "registry+https://github.com/rust-lang/crates.io-index" 1352 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1353 | 1354 | [[package]] 1355 | name = "wasi" 1356 | version = "0.11.0+wasi-snapshot-preview1" 1357 | source = "registry+https://github.com/rust-lang/crates.io-index" 1358 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1359 | 1360 | [[package]] 1361 | name = "wasi" 1362 | version = "0.14.2+wasi-0.2.4" 1363 | source = "registry+https://github.com/rust-lang/crates.io-index" 1364 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 1365 | dependencies = [ 1366 | "wit-bindgen-rt", 1367 | ] 1368 | 1369 | [[package]] 1370 | name = "winapi" 1371 | version = "0.3.9" 1372 | source = "registry+https://github.com/rust-lang/crates.io-index" 1373 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1374 | dependencies = [ 1375 | "winapi-i686-pc-windows-gnu", 1376 | "winapi-x86_64-pc-windows-gnu", 1377 | ] 1378 | 1379 | [[package]] 1380 | name = "winapi-i686-pc-windows-gnu" 1381 | version = "0.4.0" 1382 | source = "registry+https://github.com/rust-lang/crates.io-index" 1383 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1384 | 1385 | [[package]] 1386 | name = "winapi-x86_64-pc-windows-gnu" 1387 | version = "0.4.0" 1388 | source = "registry+https://github.com/rust-lang/crates.io-index" 1389 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1390 | 1391 | [[package]] 1392 | name = "windows-link" 1393 | version = "0.2.1" 1394 | source = "registry+https://github.com/rust-lang/crates.io-index" 1395 | checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 1396 | 1397 | [[package]] 1398 | name = "windows-sys" 1399 | version = "0.59.0" 1400 | source = "registry+https://github.com/rust-lang/crates.io-index" 1401 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1402 | dependencies = [ 1403 | "windows-targets", 1404 | ] 1405 | 1406 | [[package]] 1407 | name = "windows-sys" 1408 | version = "0.61.2" 1409 | source = "registry+https://github.com/rust-lang/crates.io-index" 1410 | checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 1411 | dependencies = [ 1412 | "windows-link", 1413 | ] 1414 | 1415 | [[package]] 1416 | name = "windows-targets" 1417 | version = "0.52.6" 1418 | source = "registry+https://github.com/rust-lang/crates.io-index" 1419 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1420 | dependencies = [ 1421 | "windows_aarch64_gnullvm", 1422 | "windows_aarch64_msvc", 1423 | "windows_i686_gnu", 1424 | "windows_i686_gnullvm", 1425 | "windows_i686_msvc", 1426 | "windows_x86_64_gnu", 1427 | "windows_x86_64_gnullvm", 1428 | "windows_x86_64_msvc", 1429 | ] 1430 | 1431 | [[package]] 1432 | name = "windows_aarch64_gnullvm" 1433 | version = "0.52.6" 1434 | source = "registry+https://github.com/rust-lang/crates.io-index" 1435 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1436 | 1437 | [[package]] 1438 | name = "windows_aarch64_msvc" 1439 | version = "0.52.6" 1440 | source = "registry+https://github.com/rust-lang/crates.io-index" 1441 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1442 | 1443 | [[package]] 1444 | name = "windows_i686_gnu" 1445 | version = "0.52.6" 1446 | source = "registry+https://github.com/rust-lang/crates.io-index" 1447 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1448 | 1449 | [[package]] 1450 | name = "windows_i686_gnullvm" 1451 | version = "0.52.6" 1452 | source = "registry+https://github.com/rust-lang/crates.io-index" 1453 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1454 | 1455 | [[package]] 1456 | name = "windows_i686_msvc" 1457 | version = "0.52.6" 1458 | source = "registry+https://github.com/rust-lang/crates.io-index" 1459 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1460 | 1461 | [[package]] 1462 | name = "windows_x86_64_gnu" 1463 | version = "0.52.6" 1464 | source = "registry+https://github.com/rust-lang/crates.io-index" 1465 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1466 | 1467 | [[package]] 1468 | name = "windows_x86_64_gnullvm" 1469 | version = "0.52.6" 1470 | source = "registry+https://github.com/rust-lang/crates.io-index" 1471 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1472 | 1473 | [[package]] 1474 | name = "windows_x86_64_msvc" 1475 | version = "0.52.6" 1476 | source = "registry+https://github.com/rust-lang/crates.io-index" 1477 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1478 | 1479 | [[package]] 1480 | name = "wit-bindgen-rt" 1481 | version = "0.39.0" 1482 | source = "registry+https://github.com/rust-lang/crates.io-index" 1483 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 1484 | dependencies = [ 1485 | "bitflags", 1486 | ] 1487 | 1488 | [[package]] 1489 | name = "writeable" 1490 | version = "0.6.1" 1491 | source = "registry+https://github.com/rust-lang/crates.io-index" 1492 | checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" 1493 | 1494 | [[package]] 1495 | name = "yoke" 1496 | version = "0.8.0" 1497 | source = "registry+https://github.com/rust-lang/crates.io-index" 1498 | checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" 1499 | dependencies = [ 1500 | "serde", 1501 | "stable_deref_trait", 1502 | "yoke-derive", 1503 | "zerofrom", 1504 | ] 1505 | 1506 | [[package]] 1507 | name = "yoke-derive" 1508 | version = "0.8.0" 1509 | source = "registry+https://github.com/rust-lang/crates.io-index" 1510 | checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" 1511 | dependencies = [ 1512 | "proc-macro2", 1513 | "quote", 1514 | "syn", 1515 | "synstructure", 1516 | ] 1517 | 1518 | [[package]] 1519 | name = "zerofrom" 1520 | version = "0.1.6" 1521 | source = "registry+https://github.com/rust-lang/crates.io-index" 1522 | checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 1523 | dependencies = [ 1524 | "zerofrom-derive", 1525 | ] 1526 | 1527 | [[package]] 1528 | name = "zerofrom-derive" 1529 | version = "0.1.6" 1530 | source = "registry+https://github.com/rust-lang/crates.io-index" 1531 | checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 1532 | dependencies = [ 1533 | "proc-macro2", 1534 | "quote", 1535 | "syn", 1536 | "synstructure", 1537 | ] 1538 | 1539 | [[package]] 1540 | name = "zerotrie" 1541 | version = "0.2.2" 1542 | source = "registry+https://github.com/rust-lang/crates.io-index" 1543 | checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" 1544 | dependencies = [ 1545 | "displaydoc", 1546 | "yoke", 1547 | "zerofrom", 1548 | ] 1549 | 1550 | [[package]] 1551 | name = "zerovec" 1552 | version = "0.11.2" 1553 | source = "registry+https://github.com/rust-lang/crates.io-index" 1554 | checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" 1555 | dependencies = [ 1556 | "yoke", 1557 | "zerofrom", 1558 | "zerovec-derive", 1559 | ] 1560 | 1561 | [[package]] 1562 | name = "zerovec-derive" 1563 | version = "0.11.1" 1564 | source = "registry+https://github.com/rust-lang/crates.io-index" 1565 | checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" 1566 | dependencies = [ 1567 | "proc-macro2", 1568 | "quote", 1569 | "syn", 1570 | ] 1571 | --------------------------------------------------------------------------------