diff options
| author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-07-09 21:43:23 +0200 |
|---|---|---|
| committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-07-09 21:43:23 +0200 |
| commit | 3223d93cb3c77ab02aa0a35f2a8314e447cee9a4 (patch) | |
| tree | 3971a1f37f5fe3cadb747c5229a0d54e868e45e3 /crates/turtle/src/command/client/store | |
| parent | fix(client/sync): Pass through precise error on `SyncError::WrongKey` (diff) | |
| download | atuin-3223d93cb3c77ab02aa0a35f2a8314e447cee9a4.zip | |
chore: Separate daemon, client, server, and lib into crates
Diffstat (limited to 'crates/turtle/src/command/client/store')
| -rw-r--r-- | crates/turtle/src/command/client/store/pull.rs | 95 | ||||
| -rw-r--r-- | crates/turtle/src/command/client/store/purge.rs | 24 | ||||
| -rw-r--r-- | crates/turtle/src/command/client/store/push.rs | 113 | ||||
| -rw-r--r-- | crates/turtle/src/command/client/store/rebuild.rs | 56 | ||||
| -rw-r--r-- | crates/turtle/src/command/client/store/rekey.rs | 46 | ||||
| -rw-r--r-- | crates/turtle/src/command/client/store/verify.rs | 24 |
6 files changed, 0 insertions, 358 deletions
diff --git a/crates/turtle/src/command/client/store/pull.rs b/crates/turtle/src/command/client/store/pull.rs deleted file mode 100644 index 3a0865be..00000000 --- a/crates/turtle/src/command/client/store/pull.rs +++ /dev/null @@ -1,95 +0,0 @@ -use clap::Args; -use eyre::Result; - -use crate::atuin_client::{ - database::ClientSqlite, - encryption::load_key, - record::{ - sqlite_store::SqliteStore, - sync::{self, Operation}, - }, - settings::Settings, -}; - -#[derive(Args, Debug)] -pub(crate) struct Pull { - /// The tag to push (eg, 'history'). Defaults to all tags - #[arg(long, short)] - pub(crate) tag: Option<String>, - - /// Force push records - /// This will first wipe the local store, and then download all records from the remote - #[arg(long, default_value = "false")] - pub(crate) force: bool, - - /// Page Size - /// How many records to download at once. Defaults to 100 - #[arg(long, default_value = "100")] - pub(crate) page: u64, -} - -impl Pull { - pub(crate) async fn run( - &self, - settings: &Settings, - store: SqliteStore, - db: &ClientSqlite, - ) -> Result<()> { - if self.force { - println!("Forcing local overwrite!"); - println!("Clearing local store"); - - store.delete_all().await?; - } - - // We can actually just use the existing diff/etc to push - // 1. Diff - // 2. Get operations - // 3. Filter operations by - // a) are they a download op? - // b) are they for the host/tag we are pushing here? - let client = sync::build_client(settings)?; - let (diff, remote_index) = sync::diff(&client, &store).await?; - - // Skip on --force: local was already wiped above, mismatch is the user's call. - if !self.force { - let key: [u8; 32] = load_key(settings)?.into(); - sync::check_encryption_key(&client, &remote_index, &key) - .await - .map_err(crate::print_error::format_sync_error)?; - } - - let operations = sync::operations(diff, &store)?; - - let operations = operations - .into_iter() - .filter(|op| match op { - // No noops or downloads thx - Operation::Noop { .. } | Operation::Upload { .. } => false, - - // pull, so yes plz to downloads! - Operation::Download { tag, .. } => { - if self.force { - return true; - } - - if let Some(t) = self.tag.clone() - && t != *tag - { - return false; - } - - true - } - }) - .collect(); - - let (_, downloaded) = sync::sync_remote(&client, operations, &store, self.page).await?; - - println!("Downloaded {} records", downloaded.len()); - - crate::sync::build(settings, &store, db, Some(&downloaded)).await?; - - Ok(()) - } -} diff --git a/crates/turtle/src/command/client/store/purge.rs b/crates/turtle/src/command/client/store/purge.rs deleted file mode 100644 index a23f1886..00000000 --- a/crates/turtle/src/command/client/store/purge.rs +++ /dev/null @@ -1,24 +0,0 @@ -use clap::Args; -use eyre::Result; - -use crate::atuin_client::{ - encryption::load_key, record::sqlite_store::SqliteStore, settings::Settings, -}; - -#[derive(Args, Debug)] -pub(crate) struct Purge {} - -impl Purge { - pub(crate) async fn run(&self, settings: &Settings, store: SqliteStore) -> Result<()> { - println!("Purging local records that cannot be decrypted"); - - let key = load_key(settings)?; - - match store.purge(&key.into()).await { - Ok(()) => println!("Local store purge completed OK"), - Err(e) => println!("Failed to purge local store: {e:?}"), - } - - Ok(()) - } -} diff --git a/crates/turtle/src/command/client/store/push.rs b/crates/turtle/src/command/client/store/push.rs deleted file mode 100644 index 9d66b5b2..00000000 --- a/crates/turtle/src/command/client/store/push.rs +++ /dev/null @@ -1,113 +0,0 @@ -use crate::atuin_common::record::HostId; -use clap::Args; -use eyre::{OptionExt, Result}; -use uuid::Uuid; - -use crate::atuin_client::{ - api_client::Client, - encryption::load_key, - record::sync::Operation, - record::{sqlite_store::SqliteStore, sync}, - settings::Settings, -}; - -#[derive(Args, Debug)] -pub(crate) struct Push { - /// The tag to push (eg, 'history'). Defaults to all tags - #[arg(long, short)] - pub(crate) tag: Option<String>, - - /// The host to push, in the form of a UUID host ID. Defaults to the current host. - #[arg(long)] - pub(crate) host: Option<Uuid>, - - /// Force push records - /// This will override both host and tag, to be all hosts and all tags. First clear the remote store, then upload all of the - /// local store - #[arg(long, default_value = "false")] - pub(crate) force: bool, - - /// Page Size - /// How many records to upload at once. Defaults to 100 - #[arg(long, default_value = "100")] - pub(crate) page: u64, -} - -impl Push { - pub(crate) async fn run(&self, settings: &Settings, store: SqliteStore) -> Result<()> { - let host_id = Settings::host_id().await?; - - if self.force { - println!("Forcing remote store overwrite!"); - println!("Clearing remote store"); - - let client = Client::new( - &settings.sync.address, - settings.network_connect_timeout, - // we may be deleting a lot of data... so increase the - // timeout - settings.network_timeout * 10, - settings.sync.user_id()?.ok_or_eyre("no sync user-id")?, - ) - .expect("failed to create client"); - - client.delete_store().await?; - } - - // We can actually just use the existing diff/etc to push - // 1. Diff - // 2. Get operations - // 3. Filter operations by - // a) are they an upload op? - // b) are they for the host/tag we are pushing here? - let client = sync::build_client(settings)?; - let (diff, remote_index) = sync::diff(&client, &store).await?; - - // Skip on --force: that path intentionally replaces remote with local. - if !self.force { - let key: [u8; 32] = load_key(settings)?.into(); - sync::check_encryption_key(&client, &remote_index, &key) - .await - .map_err(crate::print_error::format_sync_error)?; - } - - let operations = sync::operations(diff, &store)?; - - let operations = operations - .into_iter() - .filter(|op| match op { - // No noops or downloads thx - Operation::Noop { .. } | Operation::Download { .. } => false, - - // push, so yes plz to uploads! - Operation::Upload { host, tag, .. } => { - if self.force { - return true; - } - - if let Some(h) = self.host { - if HostId(h) != *host { - return false; - } - } else if *host != host_id { - return false; - } - - if let Some(t) = self.tag.clone() - && t != *tag - { - return false; - } - - true - } - }) - .collect(); - - let (uploaded, _) = sync::sync_remote(&client, operations, &store, self.page).await?; - - println!("Uploaded {uploaded} records"); - - Ok(()) - } -} diff --git a/crates/turtle/src/command/client/store/rebuild.rs b/crates/turtle/src/command/client/store/rebuild.rs deleted file mode 100644 index 6be67cd0..00000000 --- a/crates/turtle/src/command/client/store/rebuild.rs +++ /dev/null @@ -1,56 +0,0 @@ -use clap::Args; -use eyre::{Result, bail}; - -use crate::command::client::daemon as daemon_cmd; - -use crate::atuin_client::{ - database::ClientSqlite, encryption, history::store::HistoryStore, - record::sqlite_store::SqliteStore, settings::Settings, -}; - -#[derive(Args, Debug)] -pub(crate) struct Rebuild { - pub(crate) tag: String, -} - -impl Rebuild { - pub(crate) async fn run( - &self, - settings: &Settings, - store: SqliteStore, - database: &ClientSqlite, - ) -> Result<()> { - // keep it as a string and not an enum atm - // would be super cool to build this dynamically in the future - // eg register handles for rebuilding various tags without having to make this part of the - // binary big - match self.tag.as_str() { - "history" => { - self.rebuild_history(settings, store.clone(), database) - .await?; - } - - tag => bail!("unknown tag: {tag}"), - } - - Ok(()) - } - - async fn rebuild_history( - &self, - settings: &Settings, - store: SqliteStore, - database: &ClientSqlite, - ) -> Result<()> { - let encryption_key: [u8; 32] = encryption::load_key(settings)?.into(); - - let host_id = Settings::host_id().await?; - let history_store = HistoryStore::new(store, host_id, encryption_key); - - history_store.build(database).await?; - - daemon_cmd::emit_event(settings, crate::atuin_daemon::DaemonEvent::HistoryRebuilt).await; - - Ok(()) - } -} diff --git a/crates/turtle/src/command/client/store/rekey.rs b/crates/turtle/src/command/client/store/rekey.rs deleted file mode 100644 index 2b379327..00000000 --- a/crates/turtle/src/command/client/store/rekey.rs +++ /dev/null @@ -1,46 +0,0 @@ -use clap::Args; -use eyre::Result; -use tokio::{fs::File, io::AsyncWriteExt}; - -use crate::atuin_client::{ - encryption::{decode_key, generate_encoded_key, load_key}, - record::sqlite_store::SqliteStore, - settings::Settings, -}; - -#[derive(Args, Debug)] -pub(crate) struct Rekey { - /// The new key to use for encryption. Omit for a randomly-generated key - key: Option<String>, -} - -impl Rekey { - pub(crate) async fn run(&self, settings: &Settings, store: SqliteStore) -> Result<()> { - let key = if let Some(key) = self.key.clone() { - println!("Re-encrypting store with specified key"); - - key - } else { - println!("Re-encrypting store with freshly-generated key"); - let (_, encoded) = generate_encoded_key()?; - encoded - }; - - let current_key: [u8; 32] = load_key(settings)?.into(); - let new_key: [u8; 32] = decode_key(&key)?.into(); - - store.re_encrypt(¤t_key, &new_key).await?; - - if let Some(key_path) = settings.sync.encryption_key_path.as_ref() { - println!("Store rewritten. Saving new key"); - let mut file = File::create(key_path).await?; - file.write_all(key.as_bytes()).await?; - } else { - println!( - "No key-path (settings.sync.encryption_key_path) set in config, will not save new key." - ); - } - - Ok(()) - } -} diff --git a/crates/turtle/src/command/client/store/verify.rs b/crates/turtle/src/command/client/store/verify.rs deleted file mode 100644 index a39227f9..00000000 --- a/crates/turtle/src/command/client/store/verify.rs +++ /dev/null @@ -1,24 +0,0 @@ -use clap::Args; -use eyre::Result; - -use crate::atuin_client::{ - encryption::load_key, record::sqlite_store::SqliteStore, settings::Settings, -}; - -#[derive(Args, Debug)] -pub(crate) struct Verify {} - -impl Verify { - pub(crate) async fn run(&self, settings: &Settings, store: SqliteStore) -> Result<()> { - println!("Verifying local store can be decrypted with the current key"); - - let key = load_key(settings)?; - - match store.verify(&key.into()).await { - Ok(()) => println!("Local store encryption verified OK"), - Err(e) => println!("Failed to verify local store encryption: {e:?}"), - } - - Ok(()) - } -} |
