├── .gitignore ├── .vscode └── settings.json ├── src ├── utils │ ├── mod.rs │ ├── find_binary_path.rs │ ├── service_names.rs │ ├── process_status.rs │ ├── service_actions.rs │ └── systemd.rs ├── handlers │ ├── mod.rs │ ├── handle_stop_service.rs │ ├── handle_enable_service.rs │ ├── handle_disable_service.rs │ ├── handle_delete_service.rs │ ├── handle_reload_service.rs │ ├── handle_print_service_file.rs │ ├── handle_start_service.rs │ ├── handle_print_paths.rs │ ├── handle_show_logs.rs │ ├── handle_rename_service.rs │ ├── handle_edit_service_file.rs │ ├── handle_show_status.rs │ └── handle_create_service.rs └── main.rs ├── CONTRIBUTING.md ├── CHANGELOG.md ├── snapcraft.yaml ├── Cargo.toml ├── LICENSE ├── .github └── workflows │ └── release.yml ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.snap 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.linkedProjects": [ 3 | "./Cargo.toml", 4 | "./Cargo.toml" 5 | ] 6 | } -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod find_binary_path; 2 | pub mod process_status; 3 | pub mod service_actions; 4 | pub mod service_names; 5 | pub mod systemd; 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | - Ensure you have linux with `systemd` installed 4 | - To test locally 5 | 6 | ```sh 7 | cargo build 8 | 9 | # Run commands 10 | ./target/debug/servicer --help 11 | ``` 12 | -------------------------------------------------------------------------------- /src/handlers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod handle_create_service; 2 | pub mod handle_delete_service; 3 | pub mod handle_disable_service; 4 | pub mod handle_edit_service_file; 5 | pub mod handle_enable_service; 6 | pub mod handle_print_paths; 7 | pub mod handle_print_service_file; 8 | pub mod handle_reload_service; 9 | pub mod handle_rename_service; 10 | pub mod handle_show_logs; 11 | pub mod handle_show_status; 12 | pub mod handle_start_service; 13 | pub mod handle_stop_service; 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog], and this project adheres to [Semantic Versioning]. 6 | 7 | ## [0.1.4] - 2023-09-01 8 | 9 | ### Added 10 | - Support for `reload_unit` 11 | 12 | ### Fixed 13 | - Use `?` instead of `unwrap` for cleaner error messages 14 | 15 | [Keep a Changelog]: https://keepachangelog.com/en/1.0.0/ 16 | [Semantic Versioning]: https://github.com/AldaronLau/semver/ 17 | -------------------------------------------------------------------------------- /snapcraft.yaml: -------------------------------------------------------------------------------- 1 | name: servicer # you probably want to 'snapcraft register ' 2 | base: core22 # the base snap is the execution environment for this snap 3 | version: 0.1.2 # just for humans, typically '1.2+git' or '1.3.2' 4 | summary: Simplify Service Management on systemd # 79 char long summary 5 | description: A CLI tool for service management on systemd. 6 | 7 | apps: 8 | servicer: 9 | command: bin/servicer 10 | plugs: [home] 11 | 12 | grade: stable # must be 'stable' to release into candidate/stable channels 13 | confinement: classic # use 'strict' once you have the right plugs and slots 14 | 15 | parts: 16 | servicer: 17 | # See 'snapcraft plugins' 18 | plugin: rust 19 | source: . 20 | build-packages: [cargo, rustc] 21 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "servicer" 3 | authors = ["Shardul Aeer "] 4 | description = "Simplify Service Management on systemd" 5 | version = "0.1.14" 6 | edition = "2021" 7 | license = "MIT" 8 | homepage = "https://servicer.dev" 9 | repository = "https://github.com/servicer-labs/servicer" 10 | keywords = ["systemd", "dbus", "service", "process"] 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | tokio = { version = "1.33.0", features = ["rt-multi-thread", "macros", "fs"] } 16 | clap = { version = "4.4.6", features = ["derive"] } 17 | cli-table = "0.4.7" 18 | indoc = "2.0.4" 19 | zbus = { version = "3.14.1", default-features = false, features = ["tokio"] } 20 | bytesize = "1.3.0" 21 | libc = "0.2.149" 22 | futures = "0.3.28" 23 | tempfile = "3.8.0" 24 | regex = "1.10.2" 25 | -------------------------------------------------------------------------------- /src/handlers/handle_stop_service.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | handlers::handle_show_status::handle_show_status, 3 | utils::{ 4 | service_actions::stop_service, service_names::get_full_service_name, systemd::ManagerProxy, 5 | }, 6 | }; 7 | 8 | /// Stops a service 9 | /// 10 | /// TODO support stopping all services with `all` 11 | /// 12 | /// # Arguments 13 | /// 14 | /// * `name`- Name of the service to stop 15 | /// 16 | pub async fn handle_stop_service( 17 | name: &String, 18 | show_status: bool, 19 | ) -> Result<(), Box> { 20 | let full_service_name = get_full_service_name(&name); 21 | 22 | let connection = zbus::Connection::system().await?; 23 | let manager_proxy = ManagerProxy::new(&connection).await?; 24 | stop_service(&manager_proxy, &full_service_name).await; 25 | 26 | println!("Stopped {name}"); 27 | 28 | if show_status { 29 | handle_show_status().await?; 30 | } 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /src/handlers/handle_enable_service.rs: -------------------------------------------------------------------------------- 1 | use crate::handlers::handle_show_status::handle_show_status; 2 | use crate::utils::service_actions::enable_service; 3 | use crate::utils::{service_names::get_full_service_name, systemd::ManagerProxy}; 4 | 5 | /// Enables a service to start on boot 6 | /// 7 | /// # Arguments 8 | /// 9 | /// * `name`- Name of the service to stop 10 | /// 11 | pub async fn handle_enable_service( 12 | name: &String, 13 | show_status: bool, 14 | ) -> Result<(), Box> { 15 | let full_service_name = get_full_service_name(&name); 16 | 17 | let connection = zbus::Connection::system().await?; 18 | let manager_proxy = ManagerProxy::new(&connection).await?; 19 | 20 | enable_service(&manager_proxy, &full_service_name).await; 21 | 22 | // Reload necessary for UnitFileState to update 23 | manager_proxy.reload().await?; 24 | 25 | println!("Enabled {name}"); 26 | 27 | if show_status { 28 | handle_show_status().await?; 29 | } 30 | 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /src/handlers/handle_disable_service.rs: -------------------------------------------------------------------------------- 1 | use crate::handlers::handle_show_status::handle_show_status; 2 | use crate::utils::service_actions::disable_service; 3 | use crate::utils::{service_names::get_full_service_name, systemd::ManagerProxy}; 4 | 5 | /// Disables a service from starting on boot 6 | /// 7 | /// # Arguments 8 | /// 9 | /// * `name`- Name of the service to disable 10 | /// 11 | pub async fn handle_disable_service( 12 | name: &String, 13 | show_status: bool, 14 | ) -> Result<(), Box> { 15 | let full_service_name = get_full_service_name(&name); 16 | 17 | let connection = zbus::Connection::system().await?; 18 | let manager_proxy = ManagerProxy::new(&connection).await?; 19 | 20 | disable_service(&manager_proxy, &full_service_name).await; 21 | 22 | // Reload necessary for UnitFileState to update 23 | manager_proxy.reload().await?; 24 | 25 | println!("Disabled {name}"); 26 | 27 | if show_status { 28 | handle_show_status().await?; 29 | } 30 | 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023- Shardul Aeer 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/utils/find_binary_path.rs: -------------------------------------------------------------------------------- 1 | use tokio::process::Command; 2 | 3 | /** 4 | * Runs `which` as SUDO_USER to find the path of the given binary. 5 | * 6 | * `ser create` must be called in sudo mode. The variable $PATH in sudo mode doesn't 7 | * hold most of the paths available to the regular user. Therefore we must call `which` 8 | * as SUDO_USER. 9 | * 10 | * # Arguments 11 | * 12 | * * `binary_name`- Find path for this interpreter 13 | * * `user` - Lookup as this user 14 | */ 15 | pub async fn find_binary_path( 16 | binary_name: &str, 17 | user: &str, 18 | ) -> Result> { 19 | // Runs sudo -u hp bash -i -c "which deno" 20 | let output = Command::new("sudo") 21 | .arg("-u") 22 | .arg(user) 23 | .arg("bash") 24 | .arg("-i") 25 | .arg("-c") 26 | .arg(format!("which {binary_name}")) 27 | .output() 28 | .await?; 29 | 30 | let stdout = String::from_utf8_lossy(&output.stdout).to_string(); 31 | 32 | if !output.status.success() { 33 | panic!("Failed to find {binary_name} in PATH") 34 | } 35 | 36 | Ok(stdout) 37 | } 38 | -------------------------------------------------------------------------------- /src/handlers/handle_delete_service.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::service_names::{get_full_service_name, get_service_file_path}; 2 | 3 | use super::{ 4 | handle_disable_service::handle_disable_service, handle_show_status::handle_show_status, 5 | handle_stop_service::handle_stop_service, 6 | }; 7 | 8 | /// Deletes a service, stopping and disabling it if necessary and removing the .service file 9 | /// 10 | /// # Arguments 11 | /// 12 | /// * `name`- Name of the service to stop 13 | /// 14 | pub async fn handle_delete_service( 15 | name: &String, 16 | show_status: bool, 17 | ) -> Result<(), Box> { 18 | handle_stop_service(&name, false).await?; 19 | handle_disable_service(&name, false).await?; 20 | 21 | let full_service_name = get_full_service_name(&name); 22 | let service_file_path = get_service_file_path(&full_service_name); 23 | let service_file_path_str = service_file_path.to_str().unwrap().to_string(); 24 | 25 | // Delete .service file 26 | tokio::fs::remove_file(&service_file_path).await?; 27 | 28 | println!("Deleted {service_file_path_str}"); 29 | 30 | if show_status { 31 | handle_show_status().await?; 32 | } 33 | 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /src/handlers/handle_reload_service.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | utils::service_names::get_full_service_name, 3 | utils::{ 4 | service_actions::reload_service, 5 | systemd::{get_active_state, ManagerProxy}, 6 | }, 7 | }; 8 | 9 | use super::handle_show_status::handle_show_status; 10 | 11 | /// Reloads the unit of a failed service. The service state must be 'failed', otherwise the 12 | /// systemd dbus API throws an error. 13 | /// 14 | /// # Arguments 15 | /// 16 | /// * `name` - The service name 17 | /// 18 | pub async fn handle_reload_service( 19 | name: &String, 20 | show_status: bool, 21 | ) -> Result<(), Box> { 22 | let connection = zbus::Connection::system().await?; 23 | let manager_proxy = ManagerProxy::new(&connection).await?; 24 | 25 | let full_service_name = get_full_service_name(&name); 26 | 27 | let active_state = get_active_state(&connection, &full_service_name).await; 28 | 29 | if active_state == "reloading" { 30 | eprintln!("No-op. Service {full_service_name} is already {active_state}"); 31 | } else { 32 | reload_service(&manager_proxy, &full_service_name).await; 33 | println!("service reloaded: {name}"); 34 | }; 35 | 36 | if show_status { 37 | handle_show_status().await?; 38 | } 39 | 40 | Ok(()) 41 | } 42 | -------------------------------------------------------------------------------- /src/handlers/handle_print_service_file.rs: -------------------------------------------------------------------------------- 1 | use tokio::{fs, io::AsyncReadExt}; 2 | 3 | use crate::utils::service_names::{get_full_service_name, get_service_file_path}; 4 | 5 | /// Print contents of a .service file 6 | /// 7 | /// # Arguments 8 | /// 9 | /// * `name` - The service name 10 | /// 11 | pub async fn handle_print_service_file(name: &String) -> Result<(), Box> { 12 | let full_service_name = get_full_service_name(&name); 13 | let service_file_path = get_service_file_path(&full_service_name); 14 | 15 | if service_file_path.exists() { 16 | // Open the file using Tokio's File API 17 | let mut file = fs::File::open(&service_file_path).await?; 18 | 19 | // Create a buffer to hold the file contents 20 | let mut buffer = Vec::new(); 21 | 22 | // Read the entire contents of the file into the buffer asynchronously 23 | file.read_to_end(&mut buffer).await?; 24 | 25 | // Convert the buffer to a UTF-8 string and print it 26 | let contents = String::from_utf8(buffer)?; 27 | println!( 28 | "Reading {}:\n{}", 29 | service_file_path.to_str().unwrap(), 30 | contents 31 | ); 32 | } else { 33 | eprintln!("{}: No such file", service_file_path.to_str().unwrap()); 34 | } 35 | 36 | Ok(()) 37 | } 38 | -------------------------------------------------------------------------------- /src/handlers/handle_start_service.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | utils::service_names::get_full_service_name, 3 | utils::{ 4 | service_actions::start_service, 5 | systemd::{get_active_state, ManagerProxy}, 6 | }, 7 | }; 8 | 9 | use super::handle_show_status::handle_show_status; 10 | 11 | /// Starts a systemd service. This is a no-op if the service is already running. 12 | /// 13 | /// # Arguments 14 | /// 15 | /// * `name` - The service name 16 | /// 17 | pub async fn handle_start_service( 18 | name: &String, 19 | show_status: bool, 20 | ) -> Result<(), Box> { 21 | let connection = zbus::Connection::system().await?; 22 | let manager_proxy = ManagerProxy::new(&connection).await?; 23 | 24 | let full_service_name = get_full_service_name(&name); 25 | 26 | let active_state = get_active_state(&connection, &full_service_name).await; 27 | 28 | if active_state == "active" || active_state == "reloading" { 29 | eprintln!("No-op. Service {full_service_name} is already {active_state}"); 30 | } else { 31 | let start_service_result = start_service(&manager_proxy, &full_service_name).await; 32 | 33 | println!("service started: {start_service_result}"); 34 | }; 35 | 36 | if show_status { 37 | handle_show_status().await?; 38 | } 39 | 40 | Ok(()) 41 | } 42 | -------------------------------------------------------------------------------- /src/utils/service_names.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | 3 | /// Shortens the service name from `example.ser.service` to `example`. 4 | /// 5 | /// Must externally check whether `.ser.service` exists at the end otherwise this function 6 | /// will throw an error 7 | /// 8 | /// # Arguments 9 | /// 10 | /// * `full_service_name` 11 | /// 12 | pub fn get_short_service_name(full_service_name: &str) -> String { 13 | let file_extension = format!(".ser.service"); 14 | 15 | full_service_name 16 | .trim_end_matches(file_extension.as_str()) 17 | .to_string() 18 | } 19 | 20 | /// Returns the full service name, ending with `.ser.service` 21 | /// 22 | /// Must externally ensure that `.ser.service` is already not present. 23 | /// 24 | /// # Arguments 25 | /// 26 | /// * `short_name` 27 | /// 28 | pub fn get_full_service_name(short_name: &str) -> String { 29 | format!("{}.ser.service", short_name) 30 | } 31 | 32 | /// Whether it is a full service name, i.e. ending with `ser.service` 33 | /// 34 | /// # Arguments 35 | /// 36 | /// * `name` - The service name 37 | /// 38 | pub fn is_full_name(name: &str) -> bool { 39 | let service_extension = format!(".ser.service"); 40 | 41 | name.ends_with(&service_extension) 42 | } 43 | 44 | /// Get the path to a service file 45 | /// 46 | /// # Arguments 47 | /// 48 | /// * `full_service_name` 49 | /// 50 | pub fn get_service_file_path(full_service_name: &str) -> PathBuf { 51 | Path::new("/etc/systemd/system/").join(full_service_name) 52 | } 53 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | build_and_release: 13 | name: Release ${{ matrix.platform.release_for }} binaries 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | platform: 18 | - release_for: amd64 19 | target: x86_64-unknown-linux-gnu 20 | name: servicer-amd64-linux 21 | - release_for: arm64 22 | target: aarch64-unknown-linux-gnu 23 | name: servicer-aarch64-linux 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v3 28 | - name: Build binary 29 | uses: houseabsolute/actions-rust-cross@v0 30 | with: 31 | target: ${{ matrix.platform.target }} 32 | args: "--locked --release" 33 | - name: Publish release artifacts 34 | uses: actions/upload-artifact@v3 35 | with: 36 | name: servicer-${{ matrix.platform.target }} 37 | path: "target/${{ matrix.platform.target }}/release/servicer" 38 | - name: Rename build artifact 39 | run: | 40 | cp target/${{ matrix.platform.target }}/release/servicer target/servicer-${{ matrix.platform.target }} 41 | - name: Publish GitHub release 42 | uses: softprops/action-gh-release@v1 43 | with: 44 | name: ${{ github.ref_name }} 45 | files: "target/servicer-${{ matrix.platform.target }}" 46 | -------------------------------------------------------------------------------- /src/handlers/handle_print_paths.rs: -------------------------------------------------------------------------------- 1 | use cli_table::{Table, WithTitle}; 2 | 3 | use crate::utils::{ 4 | service_names::{get_full_service_name, get_service_file_path}, 5 | systemd::get_unit_path, 6 | }; 7 | 8 | #[derive(Table, Clone)] 9 | pub struct PathStatus { 10 | // The file name 11 | name: String, 12 | 13 | // The file path 14 | path: String, 15 | } 16 | 17 | /// Locate files used by a service and print their paths. Displays the .service path and unit path 18 | /// if the service is enabled 19 | /// 20 | /// # Arguments 21 | /// 22 | /// * `name` - The service name 23 | /// 24 | pub async fn handle_print_paths(name: &String) -> Result<(), Box> { 25 | let mut path_details = Vec::::new(); 26 | 27 | let full_service_name = get_full_service_name(name); 28 | let service_file_path = get_service_file_path(&full_service_name); 29 | 30 | if service_file_path.exists() { 31 | println!("Paths for {}:", full_service_name); 32 | 33 | // 1. Service file path 34 | path_details.push(PathStatus { 35 | name: "Service file".to_string(), 36 | path: service_file_path.to_str().unwrap().to_string(), 37 | }); 38 | 39 | // 2. Unit file 40 | path_details.push(PathStatus { 41 | name: "Unit file".to_string(), 42 | path: get_unit_path(&full_service_name), 43 | }); 44 | 45 | cli_table::print_stdout(path_details.with_title())?; 46 | } else { 47 | eprintln!("No such service {}", full_service_name); 48 | } 49 | 50 | Ok(()) 51 | } 52 | -------------------------------------------------------------------------------- /src/handlers/handle_show_logs.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::service_names::get_full_service_name; 2 | use std::process::Stdio; 3 | use tokio::io::{self, AsyncBufReadExt}; 4 | use tokio::process::Command; 5 | 6 | /// Show logs for a service 7 | /// 8 | /// Proxies to `journalctl`. Consider decoding the journal directly in future. 9 | /// 10 | /// # Arguments 11 | /// 12 | /// * `name`- Name of the service in short form (hello-world) 13 | /// * `follow` - Print logs 14 | /// 15 | pub async fn handle_show_logs( 16 | name: &String, 17 | lines: u32, 18 | follow: bool, 19 | ) -> Result<(), Box> { 20 | let full_name = get_full_service_name(&name); 21 | 22 | let mut command = Command::new("journalctl"); 23 | 24 | // Set the journal unit name with -u option 25 | command.arg("-u").arg(full_name); 26 | 27 | // Set the number of lines to show with -n option 28 | command.arg("-n").arg(lines.to_string()); 29 | 30 | if follow { 31 | // Enable continuous following with --follow option 32 | command.arg("--follow"); 33 | } 34 | 35 | // Set stdout to be captured (piped) so we can read the output 36 | command.stdout(Stdio::piped()); 37 | 38 | // Run the command asynchronously 39 | let mut child = command.spawn()?; 40 | 41 | // Get a handle to the child process's stdout 42 | let stdout = child.stdout.take().unwrap(); 43 | 44 | // Create a stream to read lines from the stdout 45 | let reader = io::BufReader::new(stdout).lines(); 46 | 47 | // Process the lines and proxy the output to the user 48 | tokio::pin!(reader); 49 | while let Some(line) = reader.next_line().await? { 50 | println!("{}", line); // You can send the line to the user in your actual code 51 | } 52 | 53 | // Wait for the child process to complete and get its exit status 54 | child.wait().await?; 55 | 56 | Ok(()) 57 | } 58 | -------------------------------------------------------------------------------- /src/handlers/handle_rename_service.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | handlers::handle_show_status::handle_show_status, 3 | utils::{ 4 | service_names::{get_full_service_name, get_service_file_path}, 5 | systemd::{get_active_state, get_unit_file_state}, 6 | }, 7 | }; 8 | 9 | use zbus::Connection; 10 | 11 | use super::{ 12 | handle_delete_service::handle_delete_service, handle_enable_service::handle_enable_service, 13 | handle_start_service::handle_start_service, 14 | }; 15 | 16 | /// Renames a service. A running service will be restarted 17 | /// 18 | /// Under the hood the exiting service is stopped and deleted. A new service file 19 | /// with same contents is created. 20 | /// 21 | /// # Arguments 22 | /// 23 | /// * `name`- Name of the service to restart 24 | /// * `new_name` - New name 25 | /// 26 | pub async fn handle_rename_service( 27 | name: &String, 28 | new_name: &String, 29 | ) -> Result<(), Box> { 30 | // Create new service file 31 | let full_service_name = get_full_service_name(&name); 32 | let service_file_path = get_service_file_path(&full_service_name); 33 | let service_file_path_str = service_file_path.to_str().unwrap().to_string(); 34 | 35 | let new_full_service_name = get_full_service_name(&new_name); 36 | let new_service_file_path = get_service_file_path(&new_full_service_name); 37 | let new_service_file_path_str = new_service_file_path.to_str().unwrap().to_string(); 38 | 39 | // Copy .service file 40 | tokio::fs::copy(service_file_path_str, new_service_file_path_str).await?; 41 | 42 | // Read active and unit state of current service 43 | let connection = Connection::system().await?; 44 | let active_state: String = get_active_state(&connection, &full_service_name).await; 45 | let unit_state = get_unit_file_state(&connection, &full_service_name).await; 46 | 47 | // Delete existing service 48 | handle_delete_service(name, false).await?; 49 | 50 | if active_state == "active" { 51 | handle_start_service(new_name, false).await?; 52 | } 53 | 54 | if unit_state == "enabled" { 55 | handle_enable_service(new_name, false).await?; 56 | } 57 | 58 | handle_show_status().await?; 59 | 60 | Ok(()) 61 | } 62 | -------------------------------------------------------------------------------- /src/utils/process_status.rs: -------------------------------------------------------------------------------- 1 | use tokio::{ 2 | fs::File, 3 | io::{AsyncBufReadExt, AsyncReadExt, BufReader}, 4 | }; 5 | 6 | /// Gets the kernel page size of the system in KB 7 | pub async fn get_page_size() -> Result> { 8 | let path = "/proc/self/smaps"; 9 | let file = File::open(path).await?; 10 | let reader = BufReader::new(file); 11 | 12 | let mut kernel_page_size: Option = None; 13 | 14 | let mut lines = reader.lines(); 15 | while let Some(line) = lines.next_line().await? { 16 | if line.starts_with("KernelPageSize:") { 17 | if let Some(size_str) = line.split_whitespace().nth(1) { 18 | if let Ok(size) = size_str.parse::() { 19 | kernel_page_size = Some(size); 20 | break; 21 | } 22 | } 23 | } 24 | } 25 | 26 | kernel_page_size.ok_or_else(|| format!("can't find KernelPageSize from {}", path).into()) 27 | } 28 | 29 | /// Gets the memory used by a process in KB 30 | /// 31 | /// Formula from QPS: (rss pages - shared pages) * page size 32 | /// 33 | /// # Arguments 34 | /// 35 | /// * `pid` - Process ID 36 | /// * `page_size` - The page size in KB 37 | /// 38 | pub async fn get_memory_usage(pid: u32, page_size_kb: u64) -> Result { 39 | let path = format!("/proc/{}/statm", pid); 40 | let mut file = File::open(&path).await?; 41 | let mut contents = String::new(); 42 | file.read_to_string(&mut contents).await?; 43 | 44 | let values: Vec<&str> = contents.trim().split_whitespace().collect(); 45 | if values.len() < 2 { 46 | panic!("Invalid format of /proc/PID/statm file"); 47 | } 48 | 49 | let rss_pages: u64 = values[1].parse().unwrap_or(0); 50 | let shared_pages: u64 = values[2].parse().unwrap_or(0); 51 | 52 | Ok((rss_pages - shared_pages) * page_size_kb) 53 | } 54 | 55 | /// Gets the CPU time of a process 56 | /// 57 | /// # Arguments 58 | /// 59 | /// * `pid` 60 | /// 61 | pub async fn get_cpu_time(pid: u32) -> Result> { 62 | let stat_path = format!("/proc/{}/stat", pid); 63 | let stat_content = tokio::fs::read_to_string(stat_path).await?; 64 | let stat_fields: Vec<&str> = stat_content.split_whitespace().collect(); 65 | 66 | // The 14th field in /proc//stat represents utime (user mode CPU time) in clock ticks 67 | // The 15th field represents stime (kernel mode CPU time) in clock ticks 68 | let utime: u64 = stat_fields[13].parse()?; 69 | let stime: u64 = stat_fields[14].parse()?; 70 | 71 | Ok(utime + stime) 72 | } 73 | -------------------------------------------------------------------------------- /src/utils/service_actions.rs: -------------------------------------------------------------------------------- 1 | use super::systemd::ManagerProxy; 2 | 3 | /// Starts a service 4 | /// 5 | /// # Arguments 6 | /// 7 | /// * `manager_proxy`: Manager proxy object 8 | /// * `full_service_name`: Full name of the service, having '.ser.service' at the end 9 | /// 10 | pub async fn start_service(manager_proxy: &ManagerProxy<'_>, full_service_name: &String) -> String { 11 | manager_proxy 12 | .start_unit(full_service_name.clone(), "replace".into()) 13 | .await 14 | .expect(&format!("Failed to start service {full_service_name}")) 15 | .to_string() 16 | } 17 | 18 | /// Enables a service on boot 19 | /// 20 | /// # Arguments 21 | /// 22 | /// * `manager_proxy`: Manager proxy object 23 | /// * `full_service_name`: Full name of the service, having '.ser.service' at the end 24 | /// 25 | pub async fn enable_service( 26 | manager_proxy: &ManagerProxy<'_>, 27 | full_service_name: &String, 28 | ) -> (bool, Vec<(String, String, String)>) { 29 | manager_proxy 30 | .enable_unit_files(vec![full_service_name.clone()], false, true) 31 | .await 32 | .expect(&format!( 33 | "Failed to enable service {full_service_name}. Retry in sudo mode." 34 | )) 35 | } 36 | 37 | pub async fn stop_service(manager_proxy: &ManagerProxy<'_>, full_service_name: &String) { 38 | manager_proxy 39 | .stop_unit(full_service_name.to_string(), "replace".into()) 40 | .await 41 | .expect(&format!("Failed to stop service {full_service_name}")); 42 | } 43 | 44 | /// Reloads the unit of a failed service 45 | /// 46 | /// # Arguments 47 | /// 48 | /// * `manager_proxy`: Manager proxy object 49 | /// * `full_service_name`: Full name of the service, having '.ser.service' at the end 50 | /// 51 | pub async fn reload_service(manager_proxy: &ManagerProxy<'_>, full_service_name: &String) -> () { 52 | manager_proxy 53 | .reload_unit(full_service_name.clone(), "replace".into()) 54 | .await 55 | .expect(&format!( 56 | "Failed to reload service {full_service_name}. Ensure it has an ExecReload statement" 57 | )); 58 | } 59 | 60 | /// Disables a service on boot 61 | /// 62 | /// # Arguments 63 | /// 64 | /// * `manager_proxy`: Manager proxy object 65 | /// * `full_service_name`: Full name of the service, having '.ser.service' at the end 66 | /// 67 | pub async fn disable_service(manager_proxy: &ManagerProxy<'_>, full_service_name: &String) { 68 | manager_proxy 69 | .disable_unit_files(vec![full_service_name.clone()], false) 70 | .await 71 | .expect(&format!( 72 | "Failed to disable service {full_service_name}. Retry in sudo mode." 73 | )); 74 | } 75 | -------------------------------------------------------------------------------- /src/handlers/handle_edit_service_file.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use crate::utils::service_names::{get_full_service_name, get_service_file_path}; 4 | use tempfile::Builder; 5 | use tokio::fs; 6 | use tokio::io::AsyncWriteExt; 7 | 8 | const SERVICE_TEMPLATE: &str = r#" 9 | # Generated with servicer 10 | [Unit] 11 | Description=My Sample Service 12 | After=network.target 13 | 14 | [Service] 15 | Type=simple 16 | ExecStart=/path/to/your/command 17 | Restart=always 18 | 19 | # Add a reload script to enable the `reload` command 20 | # ExecReload= 21 | 22 | [Install] 23 | WantedBy=multi-user.target 24 | "#; 25 | 26 | /// Opens an text editor to create or update a service file 27 | /// 28 | /// # Arguments 29 | /// 30 | /// * `name`- Name of the service to edit 31 | /// * `editor` - Name of editor. The editor must be visible in path 32 | /// 33 | pub async fn handle_edit_service_file( 34 | name: &String, 35 | editor: &String, 36 | ) -> Result<(), Box> { 37 | let full_service_name = get_full_service_name(name); 38 | let service_file_path = get_service_file_path(&full_service_name); 39 | 40 | if service_file_path.exists() { 41 | let edit_success = edit_file(editor, &service_file_path).await?; 42 | 43 | if edit_success { 44 | println!( 45 | "Service file {} edited successfully.", 46 | service_file_path.to_str().unwrap() 47 | ); 48 | } else { 49 | eprintln!("Edit operation canceled. No changes were saved."); 50 | } 51 | } else { 52 | // Write the template content to a temporary file 53 | let temp_file = Builder::new().prefix(&full_service_name).tempfile()?; 54 | let temp_file_path = temp_file.path().to_owned(); 55 | 56 | let mut file = fs::File::create(&temp_file_path).await?; 57 | file.write_all(SERVICE_TEMPLATE.as_bytes()).await?; 58 | 59 | // Prompt user to edit 60 | let edit_success = edit_file(&editor, &temp_file_path).await?; 61 | 62 | if edit_success { 63 | // Copy the content of the temporary file to the target location 64 | fs::copy(&temp_file_path, &service_file_path).await?; 65 | 66 | println!( 67 | "Service file {} created.", 68 | service_file_path.to_str().unwrap() 69 | ); 70 | } else { 71 | eprintln!("Create operation canceled. No changes were saved."); 72 | } 73 | 74 | // Remove the temporary file 75 | fs::remove_file(&temp_file_path).await?; 76 | } 77 | 78 | Ok(()) 79 | } 80 | 81 | /// Prompt the user to edit the file. Returns true if the file editor command exits successfully 82 | /// and the file's `modified` time updates. 83 | /// 84 | /// # Args 85 | /// 86 | /// * `editor` 87 | /// * `path` 88 | /// 89 | async fn edit_file(editor: &str, path: &PathBuf) -> Result { 90 | let orig_mod_time = fs::metadata(path).await?.modified()?; 91 | let edit_status = tokio::process::Command::new(&editor) 92 | .arg(path) 93 | .status() 94 | .await?; 95 | 96 | let edited_mod_time = fs::metadata(path).await?.modified()?; 97 | 98 | Ok(edit_status.success() && orig_mod_time != edited_mod_time) 99 | } 100 | -------------------------------------------------------------------------------- /src/handlers/handle_show_status.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | utils::service_names::{get_short_service_name, is_full_name}, 3 | utils::{ 4 | process_status::{get_cpu_time, get_memory_usage, get_page_size}, 5 | systemd::{get_active_state, get_main_pid, get_unit_file_state}, 6 | }, 7 | }; 8 | use bytesize::ByteSize; 9 | use cli_table::{Table, WithTitle}; 10 | use futures; 11 | use std::path::Path; 12 | use tokio::fs; 13 | use zbus::Connection; 14 | 15 | #[derive(Table, Clone)] 16 | pub struct ServiceStatus { 17 | /// Process ID 18 | pub pid: u32, 19 | 20 | /// The short service name, excluding '.ser.service' 21 | pub name: String, 22 | 23 | /// Active state 24 | pub active: String, 25 | 26 | /// Load the service on boot 27 | #[table(title = "enable on boot")] 28 | pub enabled_on_boot: bool, 29 | 30 | /// CPU usage. Formatted string in % 31 | #[table(title = "cpu %")] 32 | pub cpu: f32, 33 | 34 | /// RAM usage. Formatted string with MB, KB and other units 35 | pub memory: String, 36 | } 37 | 38 | /// Display the status of your services 39 | pub async fn handle_show_status() -> Result<(), Box> { 40 | let page_size = get_page_size().await?; 41 | let services = get_servicer_services().await?; 42 | 43 | let connection = Connection::system().await?; 44 | 45 | let mut active_process_exists = true; 46 | let mut service_statuses: Vec = vec![]; 47 | 48 | for full_service_name in services { 49 | let active_state: String = get_active_state(&connection, &full_service_name).await; 50 | let unit_state = get_unit_file_state(&connection, &full_service_name).await; 51 | 52 | let enabled_on_boot = unit_state == "enabled" || unit_state == "enabled-runtime"; 53 | 54 | // PID, CPU and memory is 0 for inactive and errored processes 55 | let (pid, cpu, memory) = if active_state == "active" { 56 | active_process_exists = true; 57 | 58 | let pid = get_main_pid(&connection, &full_service_name).await?; 59 | let memory = get_memory_usage(pid, page_size as u64).await?; 60 | 61 | (pid, 0f32, ByteSize(memory).to_string()) 62 | } else { 63 | (0, 0f32, "0".to_string()) 64 | }; 65 | 66 | service_statuses.push(ServiceStatus { 67 | pid, 68 | name: get_short_service_name(&full_service_name), 69 | active: active_state, 70 | enabled_on_boot, 71 | cpu, 72 | memory, 73 | }); 74 | } 75 | 76 | // CPU time algorithm- Find the change in CPU time over an interval, then divide by the interval 77 | // Source- https://github.com/dalance/procs/blob/ba703e98cd44be46ba32e084f1474d81b9a7f660/src/columns/usage_cpu.rs#L36C57-L36C83 78 | 79 | // Sleep duration in ms 80 | const SLEEP_DURATION: u32 = 100; 81 | 82 | if active_process_exists { 83 | // We only need to sleep once with this method 84 | let initial_cpu_times = get_cpu_times(service_statuses.clone()).await?; 85 | tokio::time::sleep(tokio::time::Duration::from_millis(SLEEP_DURATION as u64)).await; 86 | let final_cpu_times = get_cpu_times(service_statuses.clone()).await?; 87 | 88 | let tps = clock_ticks_per_second(); 89 | 90 | for i in 0..service_statuses.len() { 91 | let initial_time = initial_cpu_times.get(i).unwrap().clone(); 92 | let final_time = final_cpu_times.get(i).unwrap().clone(); 93 | let usage_ms = (final_time - initial_time) * 1000 / tps; 94 | let cpu_usage = usage_ms as f32 * 100.0 / SLEEP_DURATION as f32; 95 | 96 | let status = service_statuses.get_mut(i).unwrap(); 97 | status.cpu = cpu_usage; 98 | } 99 | } 100 | 101 | cli_table::print_stdout(service_statuses.with_title())?; 102 | 103 | Ok(()) 104 | } 105 | 106 | /// Get systemd services having an extension `.ser.service`. We only monitor services created by this tool 107 | async fn get_servicer_services() -> Result, std::io::Error> { 108 | let folder_path = "/etc/systemd/system/"; 109 | 110 | let folder_path = Path::new(folder_path); 111 | 112 | let mut files = Vec::::new(); 113 | let mut dir = fs::read_dir(folder_path).await?; 114 | 115 | while let Some(entry) = dir.next_entry().await? { 116 | let path = entry.path(); 117 | 118 | if path.is_file() { 119 | let name = path.file_name().unwrap().to_str().unwrap(); 120 | if is_full_name(name) { 121 | files.push(name.to_string()); 122 | } 123 | } 124 | } 125 | 126 | Ok(files) 127 | } 128 | 129 | /// Get CPU clock ticks per second. This value is usually 100 on x86_64 130 | pub fn clock_ticks_per_second() -> u64 { 131 | unsafe { libc::sysconf(libc::_SC_CLK_TCK) as u64 } 132 | } 133 | 134 | /// Get CPU time for a vector of services 135 | /// 136 | /// # Arguments 137 | /// 138 | /// * `service_statuses` 139 | /// 140 | pub async fn get_cpu_times( 141 | service_statuses: Vec, 142 | ) -> Result, tokio::task::JoinError> { 143 | futures::future::try_join_all(service_statuses.into_iter().map(|status| { 144 | tokio::spawn(async move { 145 | if status.active == "active" { 146 | get_cpu_time(status.pid).await.unwrap_or(0) 147 | } else { 148 | 0 149 | } 150 | }) 151 | })) 152 | .await 153 | } 154 | -------------------------------------------------------------------------------- /src/utils/systemd.rs: -------------------------------------------------------------------------------- 1 | use zbus::Connection; 2 | use zbus::{dbus_proxy, zvariant}; 3 | 4 | /// Proxy object for `org.freedesktop.systemd1.Manager`. 5 | /// Taken from https://github.com/lucab/zbus_systemd/blob/main/src/systemd1/generated.rs 6 | #[dbus_proxy( 7 | interface = "org.freedesktop.systemd1.Manager", 8 | default_service = "org.freedesktop.systemd1", 9 | default_path = "/org/freedesktop/systemd1", 10 | gen_blocking = false 11 | )] 12 | pub trait Manager { 13 | /// [📖](https://www.freedesktop.org/software/systemd/man/systemd.directives.html#StartUnit()) Call interface method `StartUnit`. 14 | #[dbus_proxy(name = "StartUnit")] 15 | fn start_unit(&self, name: String, mode: String) -> zbus::Result; 16 | 17 | /// [📖](https://www.freedesktop.org/software/systemd/man/systemd.directives.html#StopUnit()) Call interface method `StopUnit`. 18 | #[dbus_proxy(name = "StopUnit")] 19 | fn stop_unit(&self, name: String, mode: String) -> zbus::Result; 20 | 21 | /// [📖](https://www.freedesktop.org/software/systemd/man/systemd.directives.html#ReloadUnit()) Call interface method `ReloadUnit`. 22 | #[dbus_proxy(name = "ReloadUnit")] 23 | fn reload_unit(&self, name: String, mode: String) -> zbus::Result; 24 | 25 | /// [📖](https://www.freedesktop.org/software/systemd/man/systemd.directives.html#EnableUnitFiles()) Call interface method `EnableUnitFiles`. 26 | #[dbus_proxy(name = "EnableUnitFiles")] 27 | fn enable_unit_files( 28 | &self, 29 | files: Vec, 30 | runtime: bool, 31 | force: bool, 32 | ) -> zbus::Result<(bool, Vec<(String, String, String)>)>; 33 | 34 | /// [📖](https://www.freedesktop.org/software/systemd/man/systemd.directives.html#DisableUnitFiles()) Call interface method `DisableUnitFiles`. 35 | #[dbus_proxy(name = "DisableUnitFiles")] 36 | fn disable_unit_files( 37 | &self, 38 | files: Vec, 39 | runtime: bool, 40 | ) -> zbus::Result>; 41 | 42 | /// [📖](https://www.freedesktop.org/software/systemd/man/systemd.directives.html#Reload()) Call interface method `Reload`. 43 | #[dbus_proxy(name = "Reload")] 44 | fn reload(&self) -> zbus::Result<()>; 45 | } 46 | 47 | /// Proxy object for `org.freedesktop.systemd1.Unit`. 48 | /// Taken from https://github.com/lucab/zbus_systemd/blob/main/src/systemd1/generated.rs 49 | #[dbus_proxy( 50 | interface = "org.freedesktop.systemd1.Unit", 51 | default_service = "org.freedesktop.systemd1", 52 | assume_defaults = false, 53 | gen_blocking = false 54 | )] 55 | pub trait Unit { 56 | /// Get property `ActiveState`. 57 | #[dbus_proxy(property)] 58 | fn active_state(&self) -> zbus::Result; 59 | 60 | /// Get property `LoadState`. 61 | #[dbus_proxy(property)] 62 | fn load_state(&self) -> zbus::Result; 63 | 64 | /// Get property `UnitFileState`. 65 | #[dbus_proxy(property)] 66 | fn unit_file_state(&self) -> zbus::Result; 67 | } 68 | 69 | /// Proxy object for `org.freedesktop.systemd1.Service`. 70 | /// Taken from https://github.com/lucab/zbus_systemd/blob/main/src/systemd1/generated.rs 71 | #[dbus_proxy( 72 | interface = "org.freedesktop.systemd1.Service", 73 | default_service = "org.freedesktop.systemd1", 74 | assume_defaults = false, 75 | gen_blocking = false 76 | )] 77 | trait Service { 78 | /// Get property `MainPID`. 79 | #[dbus_proxy(property, name = "MainPID")] 80 | fn main_pid(&self) -> zbus::Result; 81 | } 82 | 83 | /// Returns the load state of a systemd unit 84 | /// 85 | /// Returns `invalid-unit-path` if the path is invalid 86 | /// 87 | /// # Arguments 88 | /// 89 | /// * `connection`: zbus connection 90 | /// * `full_service_name`: Full name of the service name with '.service' in the end 91 | /// 92 | pub async fn get_active_state(connection: &Connection, full_service_name: &String) -> String { 93 | let object_path = get_unit_path(full_service_name); 94 | 95 | match zvariant::ObjectPath::try_from(object_path) { 96 | Ok(path) => { 97 | let unit_proxy = UnitProxy::new(connection, path).await.unwrap(); 98 | unit_proxy 99 | .active_state() 100 | .await 101 | .unwrap_or("invalid-unit-path".into()) 102 | } 103 | Err(_) => "invalid-unit-path".to_string(), 104 | } 105 | } 106 | 107 | /// Returns the unit file state of a systemd unit. If the state is `enabled`, the unit loads on every boot 108 | /// 109 | /// Returns `invalid-unit-path` if the path is invalid 110 | /// 111 | /// # Arguments 112 | /// 113 | /// * `connection`: zbus connection 114 | /// * `full_service_name`: Full name of the service name with '.service' in the end 115 | /// 116 | pub async fn get_unit_file_state(connection: &Connection, full_service_name: &String) -> String { 117 | let object_path = get_unit_path(full_service_name); 118 | 119 | match zvariant::ObjectPath::try_from(object_path) { 120 | Ok(path) => { 121 | let unit_proxy = UnitProxy::new(connection, path).await.unwrap(); 122 | unit_proxy 123 | .unit_file_state() 124 | .await 125 | .unwrap_or("invalid-unit-path".into()) 126 | } 127 | Err(_) => "invalid-unit-path".to_string(), 128 | } 129 | } 130 | 131 | /// Returns the PID of a systemd service 132 | /// 133 | /// # Arguments 134 | /// 135 | /// * `connection`: zbus connection 136 | /// * `full_service_name`: Full name of the service name with '.service' in the end 137 | /// 138 | pub async fn get_main_pid( 139 | connection: &Connection, 140 | full_service_name: &String, 141 | ) -> Result { 142 | let object_path = get_unit_path(full_service_name); 143 | 144 | let validated_object_path = zvariant::ObjectPath::try_from(object_path)?; 145 | 146 | let service_proxy = ServiceProxy::new(connection, validated_object_path).await?; 147 | 148 | service_proxy.main_pid().await 149 | } 150 | 151 | /// Encode into a valid dbus string 152 | /// 153 | /// # Arguments 154 | /// 155 | /// * `input_string` 156 | /// 157 | fn encode_as_dbus_object_path(input_string: &str) -> String { 158 | input_string 159 | .chars() 160 | .map(|c| { 161 | if c.is_ascii_alphanumeric() || c == '/' || c == '_' { 162 | c.to_string() 163 | } else { 164 | format!("_{:x}", c as u32) 165 | } 166 | }) 167 | .collect() 168 | } 169 | 170 | /// Unit file path for a service 171 | /// 172 | /// # Arguments 173 | /// 174 | /// * `full_service_name` 175 | /// 176 | pub fn get_unit_path(full_service_name: &str) -> String { 177 | format!( 178 | "/org/freedesktop/systemd1/unit/{}", 179 | encode_as_dbus_object_path(full_service_name) 180 | ) 181 | } 182 | -------------------------------------------------------------------------------- /src/handlers/handle_create_service.rs: -------------------------------------------------------------------------------- 1 | use indoc::formatdoc; 2 | use std::{env, path::PathBuf}; 3 | use tokio::fs; 4 | 5 | use crate::{ 6 | handlers::{ 7 | handle_enable_service::handle_enable_service, handle_show_status::handle_show_status, 8 | handle_start_service::handle_start_service, 9 | }, 10 | utils::{ 11 | find_binary_path::find_binary_path, 12 | service_names::{get_full_service_name, get_service_file_path}, 13 | }, 14 | }; 15 | 16 | /// Creates a new systemd service file. 17 | /// 18 | /// # Arguments 19 | /// 20 | /// * `path` - Create service for a file at this path 21 | /// * `custom_name` 22 | /// * `custom_interpreter` 23 | /// * `env_vars` 24 | /// * `internal_args` 25 | /// 26 | pub async fn handle_create_service( 27 | path: PathBuf, 28 | custom_name: Option, 29 | start: bool, 30 | enable: bool, 31 | auto_restart: bool, 32 | custom_interpreter: Option, 33 | env_vars: Option, 34 | internal_args: Vec, 35 | ) -> Result<(), Box> { 36 | if !path.is_file() { 37 | return Err(format!("{} is not a file", path.to_str().unwrap()).into()); 38 | } 39 | 40 | // The file name including extension, eg. index.js 41 | let file_name = path 42 | .file_name() 43 | .expect("Failed to get file name") 44 | .to_str() 45 | .expect("Failed to stringify file name") 46 | .to_string(); 47 | 48 | let service_name = custom_name.unwrap_or_else(|| file_name.to_string()); 49 | let full_service_name = get_full_service_name(&service_name); 50 | 51 | // Create file if it doesn't exist 52 | let service_file_path = get_service_file_path(&full_service_name); 53 | let service_file_path_str = service_file_path.to_str().unwrap().to_string(); 54 | 55 | if service_file_path.exists() { 56 | panic!( 57 | "Service {} already exists at {}. Provide a custom name with --name or delete the existing service with `ser delete {}", 58 | service_name, 59 | service_file_path_str, 60 | service_name 61 | ); 62 | } else { 63 | let interpreter = match custom_interpreter { 64 | Some(_) => custom_interpreter, 65 | None => get_interpreter(path.extension()), 66 | }; 67 | 68 | // Handle case `ser create index.js` where relative path lacks ./ 69 | let mut parent_path = path.parent().unwrap(); 70 | let current_dir = env::current_dir().unwrap(); 71 | if parent_path.to_str() == Some("") { 72 | parent_path = ¤t_dir; 73 | } 74 | let working_directory = fs::canonicalize(parent_path) 75 | .await 76 | .unwrap() 77 | .to_str() 78 | .unwrap() 79 | .to_string(); 80 | 81 | create_service_file( 82 | &service_file_path_str, 83 | &working_directory, 84 | auto_restart, 85 | interpreter, 86 | env_vars, 87 | internal_args, 88 | &file_name, 89 | ) 90 | .await 91 | .unwrap(); 92 | 93 | println!("Service {service_name} created at {service_file_path_str}. To start run `ser start {service_name}`"); 94 | 95 | if start { 96 | handle_start_service(&service_name, false).await.unwrap(); 97 | } 98 | if enable { 99 | handle_enable_service(&service_name, false).await.unwrap(); 100 | } 101 | 102 | handle_show_status().await?; 103 | } 104 | 105 | Ok(()) 106 | } 107 | 108 | /// Find the interpreter needed to execute a file with the given extension 109 | /// 110 | /// # Arguments 111 | /// 112 | /// * `extension`: The file extension 113 | /// 114 | fn get_interpreter(extension: Option<&std::ffi::OsStr>) -> Option { 115 | match extension { 116 | Some(extension_os_str) => { 117 | let extension_str = extension_os_str 118 | .to_str() 119 | .expect("failed to stringify extension"); 120 | 121 | let interpreter = match extension_str { 122 | "js" => "node", 123 | "py" => "python3", 124 | _ => panic!("No interpeter found for extension {}. Please provide a custom interpeter and try again.", extension_str) 125 | }; 126 | 127 | Some(interpreter.to_string()) 128 | } 129 | None => None, 130 | } 131 | } 132 | 133 | /// Creates a systemd service file at `/etc/systemd/system/{}.ser.service` and returns the unit name 134 | /// 135 | /// # Arguments 136 | /// 137 | /// * `service_name`- Name of the service without '.ser.service' in the end 138 | /// * `service_file_path` - Path where the service file will be written 139 | /// * `working_directory` - Working directory of the file to execute 140 | /// * `auto_restart` - Auto restart the service on error 141 | /// * `interpreter` - The executable used to run the app, eg. `node` or `python3`. The executable 142 | /// must be visible from path for a sudo user. Note that the app itself does not run in sudo. 143 | /// * `env_vars` - Environment variables 144 | /// * `internal_args` - Args passed to the file 145 | /// * `file_name` - Name of the file to run 146 | /// 147 | async fn create_service_file( 148 | service_file_path: &str, 149 | working_directory: &str, 150 | auto_restart: bool, 151 | interpreter: Option, 152 | env_vars: Option, 153 | internal_args: Vec, 154 | file_name: &str, 155 | ) -> std::io::Result<()> { 156 | // This gets `root` instead of `hp` if sudo is used 157 | let user = 158 | env::var("SUDO_USER").expect("Must be in sudo mode. ENV variable $SUDO_USER not found"); 159 | let mut exec_start = match interpreter { 160 | Some(interpreter) => { 161 | let interpreter_path = find_binary_path(&interpreter, &user) 162 | .await 163 | .unwrap() 164 | .trim_end_matches("\n") 165 | .to_string(); 166 | 167 | println!("got path {}", interpreter_path); 168 | 169 | format!("{} {}", interpreter_path, file_name) 170 | } 171 | None => file_name.to_string(), 172 | }; 173 | 174 | for arg in internal_args { 175 | exec_start = format!("{} {}", exec_start, arg); 176 | } 177 | 178 | let env_vars_formatted = match env_vars { 179 | Some(vars) => { 180 | // Split the input string by whitespace 181 | let pairs: Vec<&str> = vars.split_whitespace().collect(); 182 | 183 | // Format each pair as "Environment=key=value" 184 | let formatted_pairs: Vec = pairs 185 | .iter() 186 | .map(|pair| format!("Environment={}", pair)) 187 | .collect(); 188 | 189 | // Join the formatted pairs with newlines 190 | let result = formatted_pairs.join("\n"); 191 | 192 | result 193 | } 194 | None => "".to_string(), 195 | }; 196 | 197 | let restart_policy = if auto_restart { "Restart=always" } else { "" }; 198 | 199 | // Replacement for format!(). This proc macro removes spaces produced by indentation. 200 | let service_body = formatdoc! { 201 | r#" 202 | # Generated with Servicer 203 | [Unit] 204 | After=network.target 205 | 206 | [Service] 207 | Type=simple 208 | User={user} 209 | 210 | WorkingDirectory={working_directory} 211 | ExecStart={exec_start} 212 | {restart_policy} 213 | {env_vars_formatted} 214 | 215 | [Install] 216 | WantedBy=multi-user.target 217 | "# 218 | }; 219 | 220 | // Create the service file and write the content 221 | fs::write(service_file_path, service_body.as_bytes()).await 222 | } 223 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use clap::{Parser, Subcommand}; 4 | 5 | mod handlers; 6 | mod utils; 7 | 8 | use handlers::handle_create_service::handle_create_service; 9 | use handlers::handle_delete_service::handle_delete_service; 10 | use handlers::handle_disable_service::handle_disable_service; 11 | use handlers::handle_edit_service_file::handle_edit_service_file; 12 | use handlers::handle_enable_service::handle_enable_service; 13 | use handlers::handle_print_paths::handle_print_paths; 14 | use handlers::handle_print_service_file::handle_print_service_file; 15 | use handlers::handle_reload_service::handle_reload_service; 16 | use handlers::handle_rename_service::handle_rename_service; 17 | use handlers::handle_show_logs::handle_show_logs; 18 | use handlers::handle_show_status::handle_show_status; 19 | use handlers::handle_start_service::handle_start_service; 20 | use handlers::handle_stop_service::handle_stop_service; 21 | 22 | /// servicer process manager 23 | #[derive(Parser, Debug)] 24 | #[command(author, about, version)] 25 | struct Args { 26 | #[command(subcommand)] 27 | command: Commands, 28 | } 29 | 30 | #[derive(Debug, Subcommand)] 31 | pub enum Commands { 32 | /// Create a systemd service for a file at the given path 33 | #[command(arg_required_else_help = true)] 34 | Create { 35 | /// The file path 36 | path: PathBuf, 37 | 38 | /// Optional custom name for the service 39 | #[arg(short, long)] 40 | name: Option, 41 | 42 | /// Start the service 43 | #[arg(short, long)] 44 | start: bool, 45 | 46 | /// Enable the service to start every time on boot. This doesn't immediately start the service, to do that run 47 | /// together with `start 48 | #[arg(short, long)] 49 | enable: bool, 50 | 51 | /// Auto-restart on failure. Default false. You should edit the .service file for more advanced features. 52 | /// The service must be enabled for auto-restart to work. 53 | #[arg(short = 'r', long)] 54 | auto_restart: bool, 55 | 56 | /// Optional custom interpreter. Input can be the executable's name, eg `python3` or the full path 57 | /// `usr/bin/python3`. If no input is provided servicer will use the file extension to detect the interpreter. 58 | #[arg(short, long)] 59 | interpreter: Option, 60 | 61 | /// Optional environment variables. To run `FOO=BAR node index.js` call `ser create index.js --env_vars "FOO=BAR"` 62 | #[arg(short = 'v', long)] 63 | env_vars: Option, 64 | 65 | /// Optional args passed to the file. Eg. to run `node index.js --foo bar` call `ser create index.js -- --foo bar` 66 | #[arg(last = true)] 67 | internal_args: Vec, 68 | }, 69 | 70 | /// Open a text editor to create or edit the .service file for a service 71 | #[command(arg_required_else_help = true)] 72 | Edit { 73 | /// The service name, eg. hello-world 74 | name: String, 75 | 76 | /// Custom editor to use. Default nano 77 | #[arg(short, long, default_value = "nano")] 78 | editor: String, 79 | }, 80 | 81 | /// Start a service 82 | #[command(arg_required_else_help = true)] 83 | Start { 84 | /// The service name, eg. hello-world 85 | name: String, 86 | }, 87 | /// Stop a service 88 | #[command(arg_required_else_help = true)] 89 | Stop { 90 | /// The service name, eg. hello-world 91 | name: String, 92 | }, 93 | 94 | /// Enable a service to start on boot. Doesn't immediately start the service. To do so use the `start` command. 95 | #[command(arg_required_else_help = true)] 96 | Enable { 97 | /// The service name, eg. hello-world 98 | name: String, 99 | }, 100 | 101 | /// Disable a service from starting on boot 102 | #[command(arg_required_else_help = true)] 103 | Disable { 104 | /// The service name, eg. hello-world 105 | name: String, 106 | }, 107 | 108 | /// Delete a service, stopping and disabling it if necessary and removing the .service file (alias: delete, rm, remove) 109 | #[command(arg_required_else_help = true, alias = "rm", alias = "remove")] 110 | Delete { 111 | /// The service name, eg. hello-world 112 | name: String, 113 | }, 114 | 115 | /// View the status of your services (alias: ls) 116 | #[command(alias = "ls")] 117 | Status {}, 118 | 119 | /// View logs for a service 120 | #[command(arg_required_else_help = true)] 121 | Logs { 122 | /// The service name 123 | name: String, 124 | 125 | /// Output the last N lines, instead of the default 15 126 | #[arg(short = 'n', long, default_value_t = 15)] 127 | lines: u32, 128 | 129 | /// Follow the logs as they change 130 | #[arg(short, long, default_value_t = false)] 131 | follow: bool, 132 | }, 133 | 134 | /// Reloads a service having an `ExecScript` 135 | #[command(arg_required_else_help = true)] 136 | Reload { 137 | /// The service name 138 | name: String, 139 | }, 140 | 141 | /// Display contents of the .service file of a service 142 | #[command(arg_required_else_help = true)] 143 | Cat { 144 | /// The service name, eg hello-world 145 | name: String, 146 | }, 147 | 148 | /// Print the of the .service file and unit file 149 | #[command(arg_required_else_help = true)] 150 | Which { 151 | /// The service name, eg hello-world 152 | name: String, 153 | }, 154 | 155 | /// Renames a service. A running service will be restarted 156 | #[command(arg_required_else_help = true, alias = "mv")] 157 | Rename { 158 | /// The service to rename 159 | name: String, 160 | 161 | /// The new name 162 | new_name: String, 163 | }, 164 | } 165 | 166 | #[tokio::main] 167 | async fn main() -> Result<(), Box> { 168 | let args = Args::parse(); 169 | 170 | match args.command { 171 | Commands::Create { 172 | path, 173 | name, 174 | start, 175 | enable, 176 | auto_restart, 177 | interpreter, 178 | env_vars, 179 | internal_args, 180 | } => { 181 | handle_create_service( 182 | path, 183 | name, 184 | start, 185 | enable, 186 | auto_restart, 187 | interpreter, 188 | env_vars, 189 | internal_args, 190 | ) 191 | .await? 192 | } 193 | 194 | Commands::Start { name } => handle_start_service(&name, true).await?, 195 | 196 | Commands::Stop { name } => handle_stop_service(&name, true).await?, 197 | 198 | Commands::Enable { name } => handle_enable_service(&name, true).await?, 199 | 200 | Commands::Disable { name } => handle_disable_service(&name, true).await?, 201 | 202 | Commands::Status {} => handle_show_status().await?, 203 | 204 | Commands::Logs { 205 | name, 206 | lines, 207 | follow, 208 | } => handle_show_logs(&name, lines, follow).await?, 209 | 210 | Commands::Edit { name, editor } => handle_edit_service_file(&name, &editor).await?, 211 | 212 | Commands::Reload { name } => handle_reload_service(&name, true).await?, 213 | 214 | Commands::Cat { name } => handle_print_service_file(&name).await?, 215 | 216 | Commands::Which { name } => handle_print_paths(&name).await?, 217 | 218 | Commands::Delete { name } => handle_delete_service(&name, true).await?, 219 | 220 | Commands::Rename { name, new_name } => handle_rename_service(&name, &new_name).await?, 221 | } 222 | 223 | Ok(()) 224 | } 225 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # servicer: Simplify Service Management on systemd 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/servicer?style=flat-square)](https://crates.io/crates/servicer) 4 | [![Crates.io](https://img.shields.io/crates/d/servicer?style=flat-square)](https://crates.io/crates/servicer) 5 | [![License](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](LICENSE-MIT) 6 | 7 | `servicer` is a user-friendly Command Line Interface (CLI) tool designed to simplify service management on `systemd`, abstracting away the complexities of the systemd ecosystem. With an easy-to-use API comparable to popular tools like pm2, servicer empowers users to create, control, and manage services effortlessly. 8 | 9 | ## Key Features: 10 | 11 | 1. **Intuitive CLI**: servicer provides a simple and intuitive command-line interface, making it accessible to both beginners and experienced users. 12 | 13 | 2. **Service Creation**: Easily create and define new services by specifying essential details like service name, command, working directory, and environment variables. 14 | 15 | 3. **Service Control**: Start, stop, restart, enable, or disable services seamlessly with straightforward commands. 16 | 17 | 4. **Process Monitoring**: Monitor the status and health of services, ensuring reliable operation and automatic restarts in case of failures. 18 | 19 | 5. **Service Logs**: View real-time service logs directly from the terminal to facilitate troubleshooting and debugging. 20 | 21 | 6. **Cross-platform Compatibility**: servicer is designed to work on various Linux distributions with systemd support. MacOS and Windows support using `launchd` and `scm` is planned. 22 | 23 | ## Goals 24 | 25 | 1. **Use OS native primitives**: Unlike `pm2`, `servicer` does not fork processes nor run a custom logger. It hooks up your app into systemd and gets out of the way. Logs are handled by journald. You need not worry about your services going down if something wrong happens to `servicer`. 26 | 27 | 2. **Lightweight**: Servicer is daemonless, i.e. does not run in the background consuming resources. 28 | 29 | 3. **Language agnostic**: Servicer comes as a binary executable which does not require rust to be installed. There is not bloat from language exclusive features, such as `pm2` cluster mode for node. 30 | 31 | ## Platform support 32 | 33 | Currently servicer supports Linux. Systemd must be installed on the system. MacOS (launchd) and Windows (SCM) support is planned. 34 | 35 | ## How do I install it? 36 | 37 | ### Download binary 38 | 39 | Download the binary from the [release page](https://github.com/servicer-labs/servicer/releases/download/v0.1.2/servicer) for run `wget https://github.com/servicer-labs/servicer/releases/download/v0.1.2/servicer`. Then setup as 40 | 41 | ```sh 42 | # grant permissions 43 | chmod +rwx ./servicer 44 | 45 | # Rename to ser and make it accessable from path 46 | sudo mv ./servicer /usr/bin/ser 47 | 48 | # Important- symlink to node. Node must be visible to sudo user 49 | sudo ln -s $(which node) "/usr/local/bin/node" 50 | sudo ln -s $(which npm) "/usr/local/bin/npm" 51 | 52 | # This should work now 53 | ser --help 54 | ``` 55 | 56 | ### Cargo 57 | 58 | ```sh 59 | cargo install servicer 60 | 61 | # Create a symlink to use the short name `ser`. We can now access servicer in sudo mode 62 | sudo ln -s ~/.cargo/bin/servicer /usr/bin/ser 63 | 64 | # Important- symlink to node. Node must be visible to sudo user 65 | sudo ln -s $(which node) "/usr/local/bin/node" 66 | sudo ln -s $(which npm) "/usr/local/bin/npm" 67 | ``` 68 | 69 | ## Usage 70 | 71 | Run `--help` to display tooltip. Note that `sudo` mode is needed for most commands. 72 | 73 | ### 1. Create service 74 | 75 | ```sh 76 | # Create a service for index.js 77 | sudo ser create index.js 78 | 79 | # Create service, start and enable on boot 80 | sudo ser create index.js --start --enable 81 | 82 | # Create a service for a binary 83 | sudo ser create awesome-binary 84 | 85 | # Custom interpreter 86 | sudo ser create hello-typescript.ts --interpreter /home/hp/.config/nvm/versions/node/v20.1.0/bin/ts-node 87 | 88 | # Custom name 89 | sudo ser create index.js --name hello-world 90 | 91 | # Pass params to index.js by adding them after a `--` followed by space 92 | sudo ser create index.js -- --foo bar 93 | 94 | # Pass env variables 95 | sudo ser create index.js --env-vars "FOO=BAR GG=WP" 96 | 97 | # Enable auto-restart on exit 98 | sudo ser create index.js --auto-restart 99 | ``` 100 | 101 | - This creates a service file in `etc/systemd/system/hello-world.ser.service`. You must follow up with `start` and `enable` commands to start the service. 102 | 103 | - Servicer auto-detects the interpreter for `node` and `python` from $PATH available to the sudo user. You must manually provide the interpeter for other platforms using the interpreter flag, eg. `--interpreter conda`. If the interpreter is not found in sudo $PATH, run `which conda` and paste the absolute path. 104 | 105 | - You can write your own service files and manage them with `servicer`. Simply rename file to end with `.ser.service` instead of `.service`. 106 | 107 | ### 2. Edit .service file 108 | 109 | ```sh 110 | sudo ser edit hello-world 111 | 112 | # Custom editor 113 | sudo ser edit hello-world --editor vi 114 | ``` 115 | 116 | Opens a `.service` file in a text editor. Provides a template if the service doesn't exist. 117 | 118 | ### 3. Start service 119 | 120 | ```sh 121 | sudo ser start hello-world 122 | ``` 123 | 124 | ### 4. Stop service 125 | 126 | ```sh 127 | sudo ser stop hello-world 128 | ``` 129 | 130 | ### 5. Enable service 131 | 132 | ```sh 133 | sudo ser enable hello-world 134 | ``` 135 | 136 | ### 6. Disable service 137 | 138 | ```sh 139 | sudo ser disable hello-world 140 | ``` 141 | 142 | ### 7. Delete service 143 | 144 | ```sh 145 | sudo ser delete hello-world 146 | 147 | sudo ser rm hello-world 148 | ``` 149 | 150 | ### 8. View status of services 151 | 152 | Prints PID, name, active state, enabled state, CPU and memory utilization for every service. 153 | 154 | ```sh 155 | sudo ser status 156 | ``` 157 | 158 | ``` 159 | +-------+-------------+--------+----------------+-------+--------+ 160 | | pid | name | active | enable on boot | cpu % | memory | 161 | +-------+-------------+--------+----------------+-------+--------+ 162 | | 24294 | index.js | active | false | 0 | 9.5 KB | 163 | +-------+-------------+--------+----------------+-------+--------+ 164 | ``` 165 | 166 | ### 9. View file paths for a service 167 | 168 | Finds the `.service` and unit file path for a service. 169 | 170 | ```sh 171 | sudo ser which hello-world 172 | ``` 173 | 174 | ```sh 175 | +--------------+--------------------------------------------------------------+ 176 | | name | path | 177 | +--------------+--------------------------------------------------------------+ 178 | | Service file | /etc/systemd/system/hello-world.ser.service | 179 | +--------------+--------------------------------------------------------------+ 180 | | Unit file | /org/freedesktop/systemd1/unit/hello_2dworld_2eser_2eservice | 181 | +--------------+--------------------------------------------------------------+ 182 | ``` 183 | 184 | ### 10. View logs 185 | 186 | ```sh 187 | ser logs hello-world 188 | 189 | # Follow live logs 190 | ser logs hello-world --follow 191 | ``` 192 | 193 | ### 11. Print contents of .service file 194 | 195 | ```sh 196 | ser cat hello-world 197 | ``` 198 | 199 | ### 13. Rename service 200 | 201 | ```sh 202 | ser rename index.js hello-world 203 | 204 | # Or 205 | ser mv index.js hello-world 206 | ``` 207 | 208 | ## Quirks 209 | 210 | 1. nvm: `node` is unavailable in sudo mode. You must symlink `node` to the path available to sudo. Source- https://stackoverflow.com/a/40078875/7721443 211 | 212 | ```sh 213 | sudo ln -s $(which node) "/usr/local/bin/node" 214 | sudo ln -s $(which npm) "/usr/local/bin/npm" 215 | ``` 216 | 217 | ## License 218 | 219 | `servicer` is licensed under the MIT license. 220 | 221 | ## Disclaimer 222 | 223 | `servicer` is distributed "as-is" and without any warranty, expressed or implied. The authors and contributors of `servicer` shall not be held liable for any damages or losses resulting from the use or inability to use the software. 224 | 225 | Before using `servicer`, please review the MIT License and the lack of warranty carefully. By using the software, you are agreeing to the terms of the license and acknowledging the lack of warranty. 226 | 227 | ## Acknowledgements 228 | 229 | We acknowledge all the packages and libraries used in the development of `servicer`. Their contributions have been invaluable in making this project possible. Parts of the README and codebase are generated with ChatGPT. 230 | 231 | ## Contribution and support 232 | 233 | We welcome contributions and feedback from the community. Feel free to open issues, submit pull requests, or share your thoughts on how we can improve servicer further. 234 | 235 | Get started with servicer and simplify your service management on systemd. Happy service creation! 236 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.20.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.0.2" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "anstream" 31 | version = "0.6.4" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" 34 | dependencies = [ 35 | "anstyle", 36 | "anstyle-parse", 37 | "anstyle-query", 38 | "anstyle-wincon", 39 | "colorchoice", 40 | "utf8parse", 41 | ] 42 | 43 | [[package]] 44 | name = "anstyle" 45 | version = "1.0.1" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" 48 | 49 | [[package]] 50 | name = "anstyle-parse" 51 | version = "0.2.1" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" 54 | dependencies = [ 55 | "utf8parse", 56 | ] 57 | 58 | [[package]] 59 | name = "anstyle-query" 60 | version = "1.0.0" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" 63 | dependencies = [ 64 | "windows-sys", 65 | ] 66 | 67 | [[package]] 68 | name = "anstyle-wincon" 69 | version = "3.0.1" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" 72 | dependencies = [ 73 | "anstyle", 74 | "windows-sys", 75 | ] 76 | 77 | [[package]] 78 | name = "async-broadcast" 79 | version = "0.5.1" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" 82 | dependencies = [ 83 | "event-listener", 84 | "futures-core", 85 | ] 86 | 87 | [[package]] 88 | name = "async-channel" 89 | version = "1.9.0" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" 92 | dependencies = [ 93 | "concurrent-queue", 94 | "event-listener", 95 | "futures-core", 96 | ] 97 | 98 | [[package]] 99 | name = "async-io" 100 | version = "1.13.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" 103 | dependencies = [ 104 | "async-lock", 105 | "autocfg", 106 | "cfg-if", 107 | "concurrent-queue", 108 | "futures-lite", 109 | "log", 110 | "parking", 111 | "polling", 112 | "rustix 0.37.23", 113 | "slab", 114 | "socket2 0.4.9", 115 | "waker-fn", 116 | ] 117 | 118 | [[package]] 119 | name = "async-lock" 120 | version = "2.7.0" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" 123 | dependencies = [ 124 | "event-listener", 125 | ] 126 | 127 | [[package]] 128 | name = "async-process" 129 | version = "1.7.0" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" 132 | dependencies = [ 133 | "async-io", 134 | "async-lock", 135 | "autocfg", 136 | "blocking", 137 | "cfg-if", 138 | "event-listener", 139 | "futures-lite", 140 | "rustix 0.37.23", 141 | "signal-hook", 142 | "windows-sys", 143 | ] 144 | 145 | [[package]] 146 | name = "async-recursion" 147 | version = "1.0.4" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" 150 | dependencies = [ 151 | "proc-macro2", 152 | "quote", 153 | "syn 2.0.27", 154 | ] 155 | 156 | [[package]] 157 | name = "async-task" 158 | version = "4.4.0" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" 161 | 162 | [[package]] 163 | name = "async-trait" 164 | version = "0.1.72" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" 167 | dependencies = [ 168 | "proc-macro2", 169 | "quote", 170 | "syn 2.0.27", 171 | ] 172 | 173 | [[package]] 174 | name = "atomic-waker" 175 | version = "1.1.1" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" 178 | 179 | [[package]] 180 | name = "autocfg" 181 | version = "1.1.0" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 184 | 185 | [[package]] 186 | name = "backtrace" 187 | version = "0.3.68" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" 190 | dependencies = [ 191 | "addr2line", 192 | "cc", 193 | "cfg-if", 194 | "libc", 195 | "miniz_oxide", 196 | "object", 197 | "rustc-demangle", 198 | ] 199 | 200 | [[package]] 201 | name = "bitflags" 202 | version = "1.3.2" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 205 | 206 | [[package]] 207 | name = "bitflags" 208 | version = "2.3.3" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" 211 | 212 | [[package]] 213 | name = "block-buffer" 214 | version = "0.10.4" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 217 | dependencies = [ 218 | "generic-array", 219 | ] 220 | 221 | [[package]] 222 | name = "blocking" 223 | version = "1.3.1" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" 226 | dependencies = [ 227 | "async-channel", 228 | "async-lock", 229 | "async-task", 230 | "atomic-waker", 231 | "fastrand 1.9.0", 232 | "futures-lite", 233 | "log", 234 | ] 235 | 236 | [[package]] 237 | name = "byteorder" 238 | version = "1.4.3" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 241 | 242 | [[package]] 243 | name = "bytes" 244 | version = "1.4.0" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" 247 | 248 | [[package]] 249 | name = "bytesize" 250 | version = "1.3.0" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" 253 | 254 | [[package]] 255 | name = "cc" 256 | version = "1.0.79" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 259 | 260 | [[package]] 261 | name = "cfg-if" 262 | version = "1.0.0" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 265 | 266 | [[package]] 267 | name = "clap" 268 | version = "4.4.6" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" 271 | dependencies = [ 272 | "clap_builder", 273 | "clap_derive", 274 | ] 275 | 276 | [[package]] 277 | name = "clap_builder" 278 | version = "4.4.6" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" 281 | dependencies = [ 282 | "anstream", 283 | "anstyle", 284 | "clap_lex", 285 | "strsim", 286 | ] 287 | 288 | [[package]] 289 | name = "clap_derive" 290 | version = "4.4.2" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" 293 | dependencies = [ 294 | "heck", 295 | "proc-macro2", 296 | "quote", 297 | "syn 2.0.27", 298 | ] 299 | 300 | [[package]] 301 | name = "clap_lex" 302 | version = "0.5.0" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" 305 | 306 | [[package]] 307 | name = "cli-table" 308 | version = "0.4.7" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "adfbb116d9e2c4be7011360d0c0bee565712c11e969c9609b25b619366dc379d" 311 | dependencies = [ 312 | "cli-table-derive", 313 | "csv", 314 | "termcolor", 315 | "unicode-width", 316 | ] 317 | 318 | [[package]] 319 | name = "cli-table-derive" 320 | version = "0.4.5" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "2af3bfb9da627b0a6c467624fb7963921433774ed435493b5c08a3053e829ad4" 323 | dependencies = [ 324 | "proc-macro2", 325 | "quote", 326 | "syn 1.0.109", 327 | ] 328 | 329 | [[package]] 330 | name = "colorchoice" 331 | version = "1.0.0" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 334 | 335 | [[package]] 336 | name = "concurrent-queue" 337 | version = "2.2.0" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" 340 | dependencies = [ 341 | "crossbeam-utils", 342 | ] 343 | 344 | [[package]] 345 | name = "cpufeatures" 346 | version = "0.2.9" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" 349 | dependencies = [ 350 | "libc", 351 | ] 352 | 353 | [[package]] 354 | name = "crossbeam-utils" 355 | version = "0.8.16" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" 358 | dependencies = [ 359 | "cfg-if", 360 | ] 361 | 362 | [[package]] 363 | name = "crypto-common" 364 | version = "0.1.6" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 367 | dependencies = [ 368 | "generic-array", 369 | "typenum", 370 | ] 371 | 372 | [[package]] 373 | name = "csv" 374 | version = "1.2.2" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "626ae34994d3d8d668f4269922248239db4ae42d538b14c398b74a52208e8086" 377 | dependencies = [ 378 | "csv-core", 379 | "itoa", 380 | "ryu", 381 | "serde", 382 | ] 383 | 384 | [[package]] 385 | name = "csv-core" 386 | version = "0.1.10" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" 389 | dependencies = [ 390 | "memchr", 391 | ] 392 | 393 | [[package]] 394 | name = "derivative" 395 | version = "2.2.0" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" 398 | dependencies = [ 399 | "proc-macro2", 400 | "quote", 401 | "syn 1.0.109", 402 | ] 403 | 404 | [[package]] 405 | name = "digest" 406 | version = "0.10.7" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 409 | dependencies = [ 410 | "block-buffer", 411 | "crypto-common", 412 | ] 413 | 414 | [[package]] 415 | name = "enumflags2" 416 | version = "0.7.7" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "c041f5090df68b32bcd905365fd51769c8b9d553fe87fde0b683534f10c01bd2" 419 | dependencies = [ 420 | "enumflags2_derive", 421 | "serde", 422 | ] 423 | 424 | [[package]] 425 | name = "enumflags2_derive" 426 | version = "0.7.7" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" 429 | dependencies = [ 430 | "proc-macro2", 431 | "quote", 432 | "syn 2.0.27", 433 | ] 434 | 435 | [[package]] 436 | name = "equivalent" 437 | version = "1.0.1" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 440 | 441 | [[package]] 442 | name = "errno" 443 | version = "0.3.1" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" 446 | dependencies = [ 447 | "errno-dragonfly", 448 | "libc", 449 | "windows-sys", 450 | ] 451 | 452 | [[package]] 453 | name = "errno-dragonfly" 454 | version = "0.1.2" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 457 | dependencies = [ 458 | "cc", 459 | "libc", 460 | ] 461 | 462 | [[package]] 463 | name = "event-listener" 464 | version = "2.5.3" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" 467 | 468 | [[package]] 469 | name = "fastrand" 470 | version = "1.9.0" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" 473 | dependencies = [ 474 | "instant", 475 | ] 476 | 477 | [[package]] 478 | name = "fastrand" 479 | version = "2.0.0" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" 482 | 483 | [[package]] 484 | name = "futures" 485 | version = "0.3.28" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" 488 | dependencies = [ 489 | "futures-channel", 490 | "futures-core", 491 | "futures-executor", 492 | "futures-io", 493 | "futures-sink", 494 | "futures-task", 495 | "futures-util", 496 | ] 497 | 498 | [[package]] 499 | name = "futures-channel" 500 | version = "0.3.28" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" 503 | dependencies = [ 504 | "futures-core", 505 | "futures-sink", 506 | ] 507 | 508 | [[package]] 509 | name = "futures-core" 510 | version = "0.3.28" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" 513 | 514 | [[package]] 515 | name = "futures-executor" 516 | version = "0.3.28" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" 519 | dependencies = [ 520 | "futures-core", 521 | "futures-task", 522 | "futures-util", 523 | ] 524 | 525 | [[package]] 526 | name = "futures-io" 527 | version = "0.3.28" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" 530 | 531 | [[package]] 532 | name = "futures-lite" 533 | version = "1.13.0" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" 536 | dependencies = [ 537 | "fastrand 1.9.0", 538 | "futures-core", 539 | "futures-io", 540 | "memchr", 541 | "parking", 542 | "pin-project-lite", 543 | "waker-fn", 544 | ] 545 | 546 | [[package]] 547 | name = "futures-macro" 548 | version = "0.3.28" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" 551 | dependencies = [ 552 | "proc-macro2", 553 | "quote", 554 | "syn 2.0.27", 555 | ] 556 | 557 | [[package]] 558 | name = "futures-sink" 559 | version = "0.3.28" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" 562 | 563 | [[package]] 564 | name = "futures-task" 565 | version = "0.3.28" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" 568 | 569 | [[package]] 570 | name = "futures-util" 571 | version = "0.3.28" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" 574 | dependencies = [ 575 | "futures-channel", 576 | "futures-core", 577 | "futures-io", 578 | "futures-macro", 579 | "futures-sink", 580 | "futures-task", 581 | "memchr", 582 | "pin-project-lite", 583 | "pin-utils", 584 | "slab", 585 | ] 586 | 587 | [[package]] 588 | name = "generic-array" 589 | version = "0.14.7" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 592 | dependencies = [ 593 | "typenum", 594 | "version_check", 595 | ] 596 | 597 | [[package]] 598 | name = "getrandom" 599 | version = "0.2.10" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" 602 | dependencies = [ 603 | "cfg-if", 604 | "libc", 605 | "wasi", 606 | ] 607 | 608 | [[package]] 609 | name = "gimli" 610 | version = "0.27.3" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" 613 | 614 | [[package]] 615 | name = "hashbrown" 616 | version = "0.14.0" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" 619 | 620 | [[package]] 621 | name = "heck" 622 | version = "0.4.1" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 625 | 626 | [[package]] 627 | name = "hermit-abi" 628 | version = "0.3.2" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" 631 | 632 | [[package]] 633 | name = "hex" 634 | version = "0.4.3" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 637 | 638 | [[package]] 639 | name = "indexmap" 640 | version = "2.0.0" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" 643 | dependencies = [ 644 | "equivalent", 645 | "hashbrown", 646 | ] 647 | 648 | [[package]] 649 | name = "indoc" 650 | version = "2.0.4" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" 653 | 654 | [[package]] 655 | name = "instant" 656 | version = "0.1.12" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 659 | dependencies = [ 660 | "cfg-if", 661 | ] 662 | 663 | [[package]] 664 | name = "io-lifetimes" 665 | version = "1.0.11" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" 668 | dependencies = [ 669 | "hermit-abi", 670 | "libc", 671 | "windows-sys", 672 | ] 673 | 674 | [[package]] 675 | name = "itoa" 676 | version = "1.0.9" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 679 | 680 | [[package]] 681 | name = "libc" 682 | version = "0.2.149" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" 685 | 686 | [[package]] 687 | name = "linux-raw-sys" 688 | version = "0.3.8" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" 691 | 692 | [[package]] 693 | name = "linux-raw-sys" 694 | version = "0.4.3" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" 697 | 698 | [[package]] 699 | name = "log" 700 | version = "0.4.19" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" 703 | 704 | [[package]] 705 | name = "memchr" 706 | version = "2.6.3" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" 709 | 710 | [[package]] 711 | name = "memoffset" 712 | version = "0.7.1" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" 715 | dependencies = [ 716 | "autocfg", 717 | ] 718 | 719 | [[package]] 720 | name = "miniz_oxide" 721 | version = "0.7.1" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 724 | dependencies = [ 725 | "adler", 726 | ] 727 | 728 | [[package]] 729 | name = "mio" 730 | version = "0.8.8" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" 733 | dependencies = [ 734 | "libc", 735 | "wasi", 736 | "windows-sys", 737 | ] 738 | 739 | [[package]] 740 | name = "nix" 741 | version = "0.26.2" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" 744 | dependencies = [ 745 | "bitflags 1.3.2", 746 | "cfg-if", 747 | "libc", 748 | "memoffset", 749 | "static_assertions", 750 | ] 751 | 752 | [[package]] 753 | name = "num_cpus" 754 | version = "1.16.0" 755 | source = "registry+https://github.com/rust-lang/crates.io-index" 756 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 757 | dependencies = [ 758 | "hermit-abi", 759 | "libc", 760 | ] 761 | 762 | [[package]] 763 | name = "object" 764 | version = "0.31.1" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" 767 | dependencies = [ 768 | "memchr", 769 | ] 770 | 771 | [[package]] 772 | name = "once_cell" 773 | version = "1.18.0" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 776 | 777 | [[package]] 778 | name = "ordered-stream" 779 | version = "0.2.0" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" 782 | dependencies = [ 783 | "futures-core", 784 | "pin-project-lite", 785 | ] 786 | 787 | [[package]] 788 | name = "parking" 789 | version = "2.1.0" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" 792 | 793 | [[package]] 794 | name = "pin-project-lite" 795 | version = "0.2.13" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 798 | 799 | [[package]] 800 | name = "pin-utils" 801 | version = "0.1.0" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 804 | 805 | [[package]] 806 | name = "polling" 807 | version = "2.8.0" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" 810 | dependencies = [ 811 | "autocfg", 812 | "bitflags 1.3.2", 813 | "cfg-if", 814 | "concurrent-queue", 815 | "libc", 816 | "log", 817 | "pin-project-lite", 818 | "windows-sys", 819 | ] 820 | 821 | [[package]] 822 | name = "ppv-lite86" 823 | version = "0.2.17" 824 | source = "registry+https://github.com/rust-lang/crates.io-index" 825 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 826 | 827 | [[package]] 828 | name = "proc-macro-crate" 829 | version = "1.3.1" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" 832 | dependencies = [ 833 | "once_cell", 834 | "toml_edit", 835 | ] 836 | 837 | [[package]] 838 | name = "proc-macro2" 839 | version = "1.0.66" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" 842 | dependencies = [ 843 | "unicode-ident", 844 | ] 845 | 846 | [[package]] 847 | name = "quote" 848 | version = "1.0.32" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" 851 | dependencies = [ 852 | "proc-macro2", 853 | ] 854 | 855 | [[package]] 856 | name = "rand" 857 | version = "0.8.5" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 860 | dependencies = [ 861 | "libc", 862 | "rand_chacha", 863 | "rand_core", 864 | ] 865 | 866 | [[package]] 867 | name = "rand_chacha" 868 | version = "0.3.1" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 871 | dependencies = [ 872 | "ppv-lite86", 873 | "rand_core", 874 | ] 875 | 876 | [[package]] 877 | name = "rand_core" 878 | version = "0.6.4" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 881 | dependencies = [ 882 | "getrandom", 883 | ] 884 | 885 | [[package]] 886 | name = "redox_syscall" 887 | version = "0.3.5" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" 890 | dependencies = [ 891 | "bitflags 1.3.2", 892 | ] 893 | 894 | [[package]] 895 | name = "regex" 896 | version = "1.10.2" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" 899 | dependencies = [ 900 | "aho-corasick", 901 | "memchr", 902 | "regex-automata", 903 | "regex-syntax", 904 | ] 905 | 906 | [[package]] 907 | name = "regex-automata" 908 | version = "0.4.3" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" 911 | dependencies = [ 912 | "aho-corasick", 913 | "memchr", 914 | "regex-syntax", 915 | ] 916 | 917 | [[package]] 918 | name = "regex-syntax" 919 | version = "0.8.2" 920 | source = "registry+https://github.com/rust-lang/crates.io-index" 921 | checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" 922 | 923 | [[package]] 924 | name = "rustc-demangle" 925 | version = "0.1.23" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 928 | 929 | [[package]] 930 | name = "rustix" 931 | version = "0.37.23" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" 934 | dependencies = [ 935 | "bitflags 1.3.2", 936 | "errno", 937 | "io-lifetimes", 938 | "libc", 939 | "linux-raw-sys 0.3.8", 940 | "windows-sys", 941 | ] 942 | 943 | [[package]] 944 | name = "rustix" 945 | version = "0.38.4" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" 948 | dependencies = [ 949 | "bitflags 2.3.3", 950 | "errno", 951 | "libc", 952 | "linux-raw-sys 0.4.3", 953 | "windows-sys", 954 | ] 955 | 956 | [[package]] 957 | name = "ryu" 958 | version = "1.0.15" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 961 | 962 | [[package]] 963 | name = "serde" 964 | version = "1.0.176" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "76dc28c9523c5d70816e393136b86d48909cfb27cecaa902d338c19ed47164dc" 967 | dependencies = [ 968 | "serde_derive", 969 | ] 970 | 971 | [[package]] 972 | name = "serde_derive" 973 | version = "1.0.176" 974 | source = "registry+https://github.com/rust-lang/crates.io-index" 975 | checksum = "a4e7b8c5dc823e3b90651ff1d3808419cd14e5ad76de04feaf37da114e7a306f" 976 | dependencies = [ 977 | "proc-macro2", 978 | "quote", 979 | "syn 2.0.27", 980 | ] 981 | 982 | [[package]] 983 | name = "serde_repr" 984 | version = "0.1.16" 985 | source = "registry+https://github.com/rust-lang/crates.io-index" 986 | checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" 987 | dependencies = [ 988 | "proc-macro2", 989 | "quote", 990 | "syn 2.0.27", 991 | ] 992 | 993 | [[package]] 994 | name = "servicer" 995 | version = "0.1.14" 996 | dependencies = [ 997 | "bytesize", 998 | "clap", 999 | "cli-table", 1000 | "futures", 1001 | "indoc", 1002 | "libc", 1003 | "regex", 1004 | "tempfile", 1005 | "tokio", 1006 | "zbus", 1007 | ] 1008 | 1009 | [[package]] 1010 | name = "sha1" 1011 | version = "0.10.5" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" 1014 | dependencies = [ 1015 | "cfg-if", 1016 | "cpufeatures", 1017 | "digest", 1018 | ] 1019 | 1020 | [[package]] 1021 | name = "signal-hook" 1022 | version = "0.3.17" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 1025 | dependencies = [ 1026 | "libc", 1027 | "signal-hook-registry", 1028 | ] 1029 | 1030 | [[package]] 1031 | name = "signal-hook-registry" 1032 | version = "1.4.1" 1033 | source = "registry+https://github.com/rust-lang/crates.io-index" 1034 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 1035 | dependencies = [ 1036 | "libc", 1037 | ] 1038 | 1039 | [[package]] 1040 | name = "slab" 1041 | version = "0.4.8" 1042 | source = "registry+https://github.com/rust-lang/crates.io-index" 1043 | checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" 1044 | dependencies = [ 1045 | "autocfg", 1046 | ] 1047 | 1048 | [[package]] 1049 | name = "socket2" 1050 | version = "0.4.9" 1051 | source = "registry+https://github.com/rust-lang/crates.io-index" 1052 | checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" 1053 | dependencies = [ 1054 | "libc", 1055 | "winapi", 1056 | ] 1057 | 1058 | [[package]] 1059 | name = "socket2" 1060 | version = "0.5.4" 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" 1062 | checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" 1063 | dependencies = [ 1064 | "libc", 1065 | "windows-sys", 1066 | ] 1067 | 1068 | [[package]] 1069 | name = "static_assertions" 1070 | version = "1.1.0" 1071 | source = "registry+https://github.com/rust-lang/crates.io-index" 1072 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1073 | 1074 | [[package]] 1075 | name = "strsim" 1076 | version = "0.10.0" 1077 | source = "registry+https://github.com/rust-lang/crates.io-index" 1078 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 1079 | 1080 | [[package]] 1081 | name = "syn" 1082 | version = "1.0.109" 1083 | source = "registry+https://github.com/rust-lang/crates.io-index" 1084 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1085 | dependencies = [ 1086 | "proc-macro2", 1087 | "quote", 1088 | "unicode-ident", 1089 | ] 1090 | 1091 | [[package]] 1092 | name = "syn" 1093 | version = "2.0.27" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" 1096 | dependencies = [ 1097 | "proc-macro2", 1098 | "quote", 1099 | "unicode-ident", 1100 | ] 1101 | 1102 | [[package]] 1103 | name = "tempfile" 1104 | version = "3.8.0" 1105 | source = "registry+https://github.com/rust-lang/crates.io-index" 1106 | checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" 1107 | dependencies = [ 1108 | "cfg-if", 1109 | "fastrand 2.0.0", 1110 | "redox_syscall", 1111 | "rustix 0.38.4", 1112 | "windows-sys", 1113 | ] 1114 | 1115 | [[package]] 1116 | name = "termcolor" 1117 | version = "1.2.0" 1118 | source = "registry+https://github.com/rust-lang/crates.io-index" 1119 | checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" 1120 | dependencies = [ 1121 | "winapi-util", 1122 | ] 1123 | 1124 | [[package]] 1125 | name = "tokio" 1126 | version = "1.33.0" 1127 | source = "registry+https://github.com/rust-lang/crates.io-index" 1128 | checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" 1129 | dependencies = [ 1130 | "backtrace", 1131 | "bytes", 1132 | "libc", 1133 | "mio", 1134 | "num_cpus", 1135 | "pin-project-lite", 1136 | "signal-hook-registry", 1137 | "socket2 0.5.4", 1138 | "tokio-macros", 1139 | "tracing", 1140 | "windows-sys", 1141 | ] 1142 | 1143 | [[package]] 1144 | name = "tokio-macros" 1145 | version = "2.1.0" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" 1148 | dependencies = [ 1149 | "proc-macro2", 1150 | "quote", 1151 | "syn 2.0.27", 1152 | ] 1153 | 1154 | [[package]] 1155 | name = "toml_datetime" 1156 | version = "0.6.3" 1157 | source = "registry+https://github.com/rust-lang/crates.io-index" 1158 | checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" 1159 | 1160 | [[package]] 1161 | name = "toml_edit" 1162 | version = "0.19.14" 1163 | source = "registry+https://github.com/rust-lang/crates.io-index" 1164 | checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" 1165 | dependencies = [ 1166 | "indexmap", 1167 | "toml_datetime", 1168 | "winnow", 1169 | ] 1170 | 1171 | [[package]] 1172 | name = "tracing" 1173 | version = "0.1.37" 1174 | source = "registry+https://github.com/rust-lang/crates.io-index" 1175 | checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 1176 | dependencies = [ 1177 | "cfg-if", 1178 | "pin-project-lite", 1179 | "tracing-attributes", 1180 | "tracing-core", 1181 | ] 1182 | 1183 | [[package]] 1184 | name = "tracing-attributes" 1185 | version = "0.1.26" 1186 | source = "registry+https://github.com/rust-lang/crates.io-index" 1187 | checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" 1188 | dependencies = [ 1189 | "proc-macro2", 1190 | "quote", 1191 | "syn 2.0.27", 1192 | ] 1193 | 1194 | [[package]] 1195 | name = "tracing-core" 1196 | version = "0.1.31" 1197 | source = "registry+https://github.com/rust-lang/crates.io-index" 1198 | checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" 1199 | dependencies = [ 1200 | "once_cell", 1201 | ] 1202 | 1203 | [[package]] 1204 | name = "typenum" 1205 | version = "1.16.0" 1206 | source = "registry+https://github.com/rust-lang/crates.io-index" 1207 | checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" 1208 | 1209 | [[package]] 1210 | name = "uds_windows" 1211 | version = "1.0.2" 1212 | source = "registry+https://github.com/rust-lang/crates.io-index" 1213 | checksum = "ce65604324d3cce9b966701489fbd0cf318cb1f7bd9dd07ac9a4ee6fb791930d" 1214 | dependencies = [ 1215 | "tempfile", 1216 | "winapi", 1217 | ] 1218 | 1219 | [[package]] 1220 | name = "unicode-ident" 1221 | version = "1.0.11" 1222 | source = "registry+https://github.com/rust-lang/crates.io-index" 1223 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 1224 | 1225 | [[package]] 1226 | name = "unicode-width" 1227 | version = "0.1.10" 1228 | source = "registry+https://github.com/rust-lang/crates.io-index" 1229 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 1230 | 1231 | [[package]] 1232 | name = "utf8parse" 1233 | version = "0.2.1" 1234 | source = "registry+https://github.com/rust-lang/crates.io-index" 1235 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 1236 | 1237 | [[package]] 1238 | name = "version_check" 1239 | version = "0.9.4" 1240 | source = "registry+https://github.com/rust-lang/crates.io-index" 1241 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1242 | 1243 | [[package]] 1244 | name = "waker-fn" 1245 | version = "1.1.0" 1246 | source = "registry+https://github.com/rust-lang/crates.io-index" 1247 | checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" 1248 | 1249 | [[package]] 1250 | name = "wasi" 1251 | version = "0.11.0+wasi-snapshot-preview1" 1252 | source = "registry+https://github.com/rust-lang/crates.io-index" 1253 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1254 | 1255 | [[package]] 1256 | name = "winapi" 1257 | version = "0.3.9" 1258 | source = "registry+https://github.com/rust-lang/crates.io-index" 1259 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1260 | dependencies = [ 1261 | "winapi-i686-pc-windows-gnu", 1262 | "winapi-x86_64-pc-windows-gnu", 1263 | ] 1264 | 1265 | [[package]] 1266 | name = "winapi-i686-pc-windows-gnu" 1267 | version = "0.4.0" 1268 | source = "registry+https://github.com/rust-lang/crates.io-index" 1269 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1270 | 1271 | [[package]] 1272 | name = "winapi-util" 1273 | version = "0.1.5" 1274 | source = "registry+https://github.com/rust-lang/crates.io-index" 1275 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1276 | dependencies = [ 1277 | "winapi", 1278 | ] 1279 | 1280 | [[package]] 1281 | name = "winapi-x86_64-pc-windows-gnu" 1282 | version = "0.4.0" 1283 | source = "registry+https://github.com/rust-lang/crates.io-index" 1284 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1285 | 1286 | [[package]] 1287 | name = "windows-sys" 1288 | version = "0.48.0" 1289 | source = "registry+https://github.com/rust-lang/crates.io-index" 1290 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1291 | dependencies = [ 1292 | "windows-targets", 1293 | ] 1294 | 1295 | [[package]] 1296 | name = "windows-targets" 1297 | version = "0.48.1" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" 1300 | dependencies = [ 1301 | "windows_aarch64_gnullvm", 1302 | "windows_aarch64_msvc", 1303 | "windows_i686_gnu", 1304 | "windows_i686_msvc", 1305 | "windows_x86_64_gnu", 1306 | "windows_x86_64_gnullvm", 1307 | "windows_x86_64_msvc", 1308 | ] 1309 | 1310 | [[package]] 1311 | name = "windows_aarch64_gnullvm" 1312 | version = "0.48.0" 1313 | source = "registry+https://github.com/rust-lang/crates.io-index" 1314 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 1315 | 1316 | [[package]] 1317 | name = "windows_aarch64_msvc" 1318 | version = "0.48.0" 1319 | source = "registry+https://github.com/rust-lang/crates.io-index" 1320 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 1321 | 1322 | [[package]] 1323 | name = "windows_i686_gnu" 1324 | version = "0.48.0" 1325 | source = "registry+https://github.com/rust-lang/crates.io-index" 1326 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 1327 | 1328 | [[package]] 1329 | name = "windows_i686_msvc" 1330 | version = "0.48.0" 1331 | source = "registry+https://github.com/rust-lang/crates.io-index" 1332 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 1333 | 1334 | [[package]] 1335 | name = "windows_x86_64_gnu" 1336 | version = "0.48.0" 1337 | source = "registry+https://github.com/rust-lang/crates.io-index" 1338 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 1339 | 1340 | [[package]] 1341 | name = "windows_x86_64_gnullvm" 1342 | version = "0.48.0" 1343 | source = "registry+https://github.com/rust-lang/crates.io-index" 1344 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 1345 | 1346 | [[package]] 1347 | name = "windows_x86_64_msvc" 1348 | version = "0.48.0" 1349 | source = "registry+https://github.com/rust-lang/crates.io-index" 1350 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 1351 | 1352 | [[package]] 1353 | name = "winnow" 1354 | version = "0.5.1" 1355 | source = "registry+https://github.com/rust-lang/crates.io-index" 1356 | checksum = "25b5872fa2e10bd067ae946f927e726d7d603eaeb6e02fa6a350e0722d2b8c11" 1357 | dependencies = [ 1358 | "memchr", 1359 | ] 1360 | 1361 | [[package]] 1362 | name = "xdg-home" 1363 | version = "1.0.0" 1364 | source = "registry+https://github.com/rust-lang/crates.io-index" 1365 | checksum = "2769203cd13a0c6015d515be729c526d041e9cf2c0cc478d57faee85f40c6dcd" 1366 | dependencies = [ 1367 | "nix", 1368 | "winapi", 1369 | ] 1370 | 1371 | [[package]] 1372 | name = "zbus" 1373 | version = "3.14.1" 1374 | source = "registry+https://github.com/rust-lang/crates.io-index" 1375 | checksum = "31de390a2d872e4cd04edd71b425e29853f786dc99317ed72d73d6fcf5ebb948" 1376 | dependencies = [ 1377 | "async-broadcast", 1378 | "async-process", 1379 | "async-recursion", 1380 | "async-trait", 1381 | "byteorder", 1382 | "derivative", 1383 | "enumflags2", 1384 | "event-listener", 1385 | "futures-core", 1386 | "futures-sink", 1387 | "futures-util", 1388 | "hex", 1389 | "nix", 1390 | "once_cell", 1391 | "ordered-stream", 1392 | "rand", 1393 | "serde", 1394 | "serde_repr", 1395 | "sha1", 1396 | "static_assertions", 1397 | "tokio", 1398 | "tracing", 1399 | "uds_windows", 1400 | "winapi", 1401 | "xdg-home", 1402 | "zbus_macros", 1403 | "zbus_names", 1404 | "zvariant", 1405 | ] 1406 | 1407 | [[package]] 1408 | name = "zbus_macros" 1409 | version = "3.14.1" 1410 | source = "registry+https://github.com/rust-lang/crates.io-index" 1411 | checksum = "41d1794a946878c0e807f55a397187c11fc7a038ba5d868e7db4f3bd7760bc9d" 1412 | dependencies = [ 1413 | "proc-macro-crate", 1414 | "proc-macro2", 1415 | "quote", 1416 | "regex", 1417 | "syn 1.0.109", 1418 | "zvariant_utils", 1419 | ] 1420 | 1421 | [[package]] 1422 | name = "zbus_names" 1423 | version = "2.6.0" 1424 | source = "registry+https://github.com/rust-lang/crates.io-index" 1425 | checksum = "fb80bb776dbda6e23d705cf0123c3b95df99c4ebeaec6c2599d4a5419902b4a9" 1426 | dependencies = [ 1427 | "serde", 1428 | "static_assertions", 1429 | "zvariant", 1430 | ] 1431 | 1432 | [[package]] 1433 | name = "zvariant" 1434 | version = "3.15.0" 1435 | source = "registry+https://github.com/rust-lang/crates.io-index" 1436 | checksum = "44b291bee0d960c53170780af148dca5fa260a63cdd24f1962fa82e03e53338c" 1437 | dependencies = [ 1438 | "byteorder", 1439 | "enumflags2", 1440 | "libc", 1441 | "serde", 1442 | "static_assertions", 1443 | "zvariant_derive", 1444 | ] 1445 | 1446 | [[package]] 1447 | name = "zvariant_derive" 1448 | version = "3.15.0" 1449 | source = "registry+https://github.com/rust-lang/crates.io-index" 1450 | checksum = "934d7a7dfc310d6ee06c87ffe88ef4eca7d3e37bb251dece2ef93da8f17d8ecd" 1451 | dependencies = [ 1452 | "proc-macro-crate", 1453 | "proc-macro2", 1454 | "quote", 1455 | "syn 1.0.109", 1456 | "zvariant_utils", 1457 | ] 1458 | 1459 | [[package]] 1460 | name = "zvariant_utils" 1461 | version = "1.0.1" 1462 | source = "registry+https://github.com/rust-lang/crates.io-index" 1463 | checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" 1464 | dependencies = [ 1465 | "proc-macro2", 1466 | "quote", 1467 | "syn 1.0.109", 1468 | ] 1469 | --------------------------------------------------------------------------------