commit fd6bf0adbd1fc768dd65b90ebe891c177b5460e8 Author: Ash Svitan Date: Thu Apr 30 10:53:53 2026 +0200 :tada: Inits project Signed-off-by: Ash Svitan diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1968c76 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,68 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitwarden-proton-sync" +version = "0.1.0" +dependencies = [ + "fancy-regex", +] + +[[package]] +name = "fancy-regex" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277" +dependencies = [ + "bit-set", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0a3197e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "bitwarden-proton-sync" +version = "0.1.0" +edition = "2024" + +[dependencies] +fancy-regex = "0.18.0" diff --git a/README.md b/README.md new file mode 100644 index 0000000..e4605cf --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# bitwarden-proton-sync + +This is a cron-based CLI tool to sync Bitwarden to Proton Pass. + +### Runtime requirements: + +- `pass-cli` - Proton Pass CLI (must be logged in) + +### Building + +```sh +cargo build +``` diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..b96b81b --- /dev/null +++ b/src/main.rs @@ -0,0 +1,36 @@ +mod pass; +mod sh; + +use std::env; +use std::io::{Error, ErrorKind}; + +const ENV_VAR_DEFAULT_VAULT: &str = "PASS_VAULT"; + +fn main() -> Result<(), Error> { + pass::check_pass()?; + let vaults = pass::get_vaults()?; + if vaults.len() <= 0 { + return Err(Error::new(ErrorKind::Other, "No vaults found")); + } + + let mut vault = String::new(); + match env::var(ENV_VAR_DEFAULT_VAULT) { + Ok(default_vault) => { + if vaults.contains(&default_vault) { + vault = default_vault; + } else { + return Err(Error::new( + ErrorKind::Other, + format!("Vault {} does not exist", default_vault), + )); + } + } + Err(_) => { + vault = vaults.get(0).unwrap().clone(); + println!("No default vault found (you can set it with {}), using {}...", ENV_VAR_DEFAULT_VAULT, vault); + } + }; + println!("selected: {:?}", vault); + + return Ok(()); +} diff --git a/src/pass.rs b/src/pass.rs new file mode 100644 index 0000000..0b5b386 --- /dev/null +++ b/src/pass.rs @@ -0,0 +1,55 @@ +use crate::sh; +use fancy_regex::Regex; +use std::io::{Error, ErrorKind}; + +const EXECUTABLE: &str = "pass-cli"; +const VAULT_NAME_REGEX: &str = r"(?m)(?<=- \[.{88}\]: ).*$"; + +pub fn check_pass() -> Result<(), Error> { + let which = sh::sh(format!("which {}", EXECUTABLE)); + if which.is_empty() { + return Err(Error::new( + ErrorKind::Other, + format!("{} is not installed", EXECUTABLE), + )); + } + + let test = sh::sh(format!("{} test", EXECUTABLE)); + if test != "Connection successful\n" { + return Err(Error::new( + ErrorKind::Other, + format!("{} test failed", EXECUTABLE), + )); + } + + return Ok(()); +} + +pub fn get_vaults() -> Result, Error> { + let re = Regex::new(VAULT_NAME_REGEX).unwrap(); + let vaults_raw = sh::sh(format!("{} vault list", EXECUTABLE)); + + let captures = re + .captures(vaults_raw.as_str()) + .expect("Error running regex"); + if let None = captures { + return Err(Error::new(ErrorKind::Other, "No vault found")); + } + + let mut vaults = Vec::::new(); + + let captures = captures.unwrap(); + for each in captures.iter() { + if let None = each { + continue; + } + + let each = each.unwrap(); + let vault = each.as_str(); + if !vault.is_empty() { + vaults.push(vault.to_string()); + } + } + + return Ok(vaults); +} diff --git a/src/sh.rs b/src/sh.rs new file mode 100644 index 0000000..4f99563 --- /dev/null +++ b/src/sh.rs @@ -0,0 +1,11 @@ +use std::process::Command; + +pub fn sh(command: impl Into) -> String { + let output = Command::new("sh") + .arg("-c") + .arg(command.into()) + .output() + .expect("Failed to execute command"); + + return String::from_utf8(output.stdout).expect("Invalid UTF-8 sequence"); +}