diff options
38 files changed, 537 insertions, 224 deletions
diff --git a/crates/atuin-client/meta-migrations/20260203030924_create_meta.sql b/crates/atuin-client/meta-migrations/20260203030924_create_meta.sql new file mode 100644 index 00000000..26c3c142 --- /dev/null +++ b/crates/atuin-client/meta-migrations/20260203030924_create_meta.sql @@ -0,0 +1,5 @@ +create table if not exists meta ( + key text not null primary key, + value text not null, + updated_at integer not null default (strftime('%s', 'now')) +); diff --git a/crates/atuin-client/src/database.rs b/crates/atuin-client/src/database.rs index 408e8e52..28d6c0f0 100644 --- a/crates/atuin-client/src/database.rs +++ b/crates/atuin-client/src/database.rs @@ -54,25 +54,22 @@ pub struct OptFilters { pub include_duplicates: bool, } -pub fn current_context() -> Context { - let Ok(session) = env::var("ATUIN_SESSION") else { - eprintln!( - "ERROR: Failed to find $ATUIN_SESSION in the environment. Check that you have correctly set up your shell." - ); - std::process::exit(1); - }; +pub async fn current_context() -> eyre::Result<Context> { + let session = env::var("ATUIN_SESSION").map_err(|_| { + eyre::eyre!("Failed to find $ATUIN_SESSION in the environment. Check that you have correctly set up your shell.") + })?; let hostname = get_host_user(); let cwd = utils::get_current_dir(); - let host_id = Settings::host_id().expect("failed to load host ID"); + let host_id = Settings::host_id().await?; let git_root = utils::in_git_repo(cwd.as_str()); - Context { + Ok(Context { session, hostname, cwd, git_root, host_id: host_id.0.as_simple().to_string(), - } + }) } fn get_session_start_time(session_id: &str) -> Option<i64> { diff --git a/crates/atuin-client/src/history/store.rs b/crates/atuin-client/src/history/store.rs index b8ac6ff4..041d90ce 100644 --- a/crates/atuin-client/src/history/store.rs +++ b/crates/atuin-client/src/history/store.rs @@ -302,7 +302,7 @@ impl HistoryStore { pb.set_message("Fetching history from old database"); - let context = current_context(); + let context = current_context().await?; let history = db.list(&[], &context, None, false, true).await?; pb.set_message("Fetching history already in store"); diff --git a/crates/atuin-client/src/lib.rs b/crates/atuin-client/src/lib.rs index 78819548..160d4529 100644 --- a/crates/atuin-client/src/lib.rs +++ b/crates/atuin-client/src/lib.rs @@ -14,6 +14,7 @@ pub mod history; pub mod import; pub mod login; pub mod logout; +pub mod meta; pub mod ordering; pub mod plugin; pub mod record; diff --git a/crates/atuin-client/src/login.rs b/crates/atuin-client/src/login.rs index 78168c7e..ab265928 100644 --- a/crates/atuin-client/src/login.rs +++ b/crates/atuin-client/src/login.rs @@ -54,7 +54,7 @@ pub async fn login( bail!("the specified key was invalid"); } - let mut file = File::create(key_path).await?; + let mut file = File::create(&key_path).await?; file.write_all(key.as_bytes()).await?; } else { // we now know that the user has logged in specifying a key, AND that the key path @@ -75,7 +75,7 @@ pub async fn login( store.re_encrypt(¤t_key, &new_key).await?; println!("Writing new key"); - let mut file = File::create(key_path).await?; + let mut file = File::create(&key_path).await?; file.write_all(encoded.as_bytes()).await?; } } @@ -86,9 +86,10 @@ pub async fn login( ) .await?; - let session_path = settings.session_path.as_str(); - let mut file = File::create(session_path).await?; - file.write_all(session.session.as_bytes()).await?; + Settings::meta_store() + .await? + .save_session(&session.session) + .await?; Ok(session.session) } diff --git a/crates/atuin-client/src/logout.rs b/crates/atuin-client/src/logout.rs index fe1a4d23..f720b302 100644 --- a/crates/atuin-client/src/logout.rs +++ b/crates/atuin-client/src/logout.rs @@ -1,13 +1,12 @@ -use eyre::{Context, Result}; -use fs_err::remove_file; +use eyre::Result; use crate::settings::Settings; -pub fn logout(settings: &Settings) -> Result<()> { - let session_path = settings.session_path.as_str(); +pub async fn logout() -> Result<()> { + let meta = Settings::meta_store().await?; - if settings.logged_in() { - remove_file(session_path).context("Failed to remove session file")?; + if meta.logged_in().await? { + meta.delete_session().await?; println!("You have logged out!"); } else { println!("You are not logged in"); diff --git a/crates/atuin-client/src/meta.rs b/crates/atuin-client/src/meta.rs new file mode 100644 index 00000000..870f36d0 --- /dev/null +++ b/crates/atuin-client/src/meta.rs @@ -0,0 +1,365 @@ +use std::path::Path; +use std::str::FromStr; +use std::time::Duration; + +use atuin_common::record::HostId; +use eyre::{Result, eyre}; +use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePool, SqlitePoolOptions}; +use time::{OffsetDateTime, format_description::well_known::Rfc3339}; +use tokio::sync::OnceCell; +use uuid::Uuid; + +// Filenames for the legacy plain-text files that we migrate from. +const LEGACY_HOST_ID_FILENAME: &str = "host_id"; +const LEGACY_LAST_SYNC_FILENAME: &str = "last_sync_time"; +const LEGACY_LAST_VERSION_CHECK_FILENAME: &str = "last_version_check_time"; +const LEGACY_LATEST_VERSION_FILENAME: &str = "latest_version"; +const LEGACY_SESSION_FILENAME: &str = "session"; + +const KEY_HOST_ID: &str = "host_id"; +const KEY_LAST_SYNC: &str = "last_sync_time"; +const KEY_LAST_VERSION_CHECK: &str = "last_version_check_time"; +const KEY_LATEST_VERSION: &str = "latest_version"; +const KEY_SESSION: &str = "session"; +const KEY_FILES_MIGRATED: &str = "files_migrated"; + +pub struct MetaStore { + pool: SqlitePool, + cached_host_id: OnceCell<HostId>, +} + +impl MetaStore { + pub async fn new(path: impl AsRef<Path>, timeout: f64) -> Result<Self> { + let path = path.as_ref(); + let path_str = path + .as_os_str() + .to_str() + .ok_or_else(|| eyre!("meta database path is not valid UTF-8: {path:?}"))?; + debug!("opening meta sqlite database at {path:?}"); + + let is_memory = path_str.contains(":memory:"); + + if !is_memory + && !path.exists() + && let Some(dir) = path.parent() + { + fs_err::create_dir_all(dir)?; + } + + // Use DELETE journal mode instead of WAL. This is a small, infrequently- + // written KV store — WAL's concurrency benefits aren't needed, and DELETE + // mode avoids creating auxiliary -wal/-shm files that complicate + // permission handling. + let opts = SqliteConnectOptions::from_str(path_str)? + .journal_mode(SqliteJournalMode::Delete) + .optimize_on_close(true, None) + .create_if_missing(true); + + let pool = SqlitePoolOptions::new() + .acquire_timeout(Duration::from_secs_f64(timeout)) + .connect_with(opts) + .await?; + + sqlx::migrate!("./meta-migrations").run(&pool).await?; + + // Session tokens are stored in this database, so restrict permissions. + #[cfg(unix)] + if !is_memory { + use std::os::unix::fs::PermissionsExt; + std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))?; + } + + let store = Self { + pool, + cached_host_id: OnceCell::const_new(), + }; + + if !is_memory { + store.migrate_files().await?; + } + + Ok(store) + } + + // Generic key-value operations + + pub async fn get(&self, key: &str) -> Result<Option<String>> { + let row: Option<(String,)> = sqlx::query_as("SELECT value FROM meta WHERE key = ?1") + .bind(key) + .fetch_optional(&self.pool) + .await?; + + Ok(row.map(|r| r.0)) + } + + pub async fn set(&self, key: &str, value: &str) -> Result<()> { + sqlx::query( + "INSERT INTO meta (key, value, updated_at) VALUES (?1, ?2, strftime('%s', 'now')) + ON CONFLICT(key) DO UPDATE SET value = ?2, updated_at = strftime('%s', 'now')", + ) + .bind(key) + .bind(value) + .execute(&self.pool) + .await?; + + Ok(()) + } + + pub async fn delete(&self, key: &str) -> Result<()> { + sqlx::query("DELETE FROM meta WHERE key = ?1") + .bind(key) + .execute(&self.pool) + .await?; + + Ok(()) + } + + // Typed accessors + + pub async fn host_id(&self) -> Result<HostId> { + self.cached_host_id + .get_or_try_init(|| async { + if let Some(id) = self.get(KEY_HOST_ID).await? { + let parsed = Uuid::from_str(id.as_str()) + .map_err(|e| eyre!("failed to parse host ID: {e}"))?; + return Ok(HostId(parsed)); + } + + let uuid = atuin_common::utils::uuid_v7(); + self.set(KEY_HOST_ID, uuid.as_simple().to_string().as_ref()) + .await?; + + Ok(HostId(uuid)) + }) + .await + .copied() + } + + pub async fn last_sync(&self) -> Result<OffsetDateTime> { + match self.get(KEY_LAST_SYNC).await? { + Some(v) => Ok(OffsetDateTime::parse(v.as_str(), &Rfc3339)?), + None => Ok(OffsetDateTime::UNIX_EPOCH), + } + } + + pub async fn save_sync_time(&self) -> Result<()> { + self.set( + KEY_LAST_SYNC, + OffsetDateTime::now_utc().format(&Rfc3339)?.as_str(), + ) + .await + } + + pub async fn last_version_check(&self) -> Result<OffsetDateTime> { + match self.get(KEY_LAST_VERSION_CHECK).await? { + Some(v) => Ok(OffsetDateTime::parse(v.as_str(), &Rfc3339)?), + None => Ok(OffsetDateTime::UNIX_EPOCH), + } + } + + pub async fn save_version_check_time(&self) -> Result<()> { + self.set( + KEY_LAST_VERSION_CHECK, + OffsetDateTime::now_utc().format(&Rfc3339)?.as_str(), + ) + .await + } + + pub async fn latest_version(&self) -> Result<Option<String>> { + self.get(KEY_LATEST_VERSION).await + } + + pub async fn save_latest_version(&self, version: &str) -> Result<()> { + self.set(KEY_LATEST_VERSION, version).await + } + + pub async fn session_token(&self) -> Result<Option<String>> { + self.get(KEY_SESSION).await + } + + pub async fn save_session(&self, token: &str) -> Result<()> { + self.set(KEY_SESSION, token).await + } + + pub async fn delete_session(&self) -> Result<()> { + self.delete(KEY_SESSION).await + } + + pub async fn logged_in(&self) -> Result<bool> { + Ok(self.session_token().await?.is_some()) + } + + // File migration: on first open, migrate old plain-text files into the database. + // Old files are left in place for safe downgrades. + + async fn migrate_files(&self) -> Result<()> { + if self.get(KEY_FILES_MIGRATED).await?.is_some() { + return Ok(()); + } + + let data_dir = crate::settings::Settings::effective_data_dir(); + + // host_id — validate as UUID + let host_id_path = data_dir.join(LEGACY_HOST_ID_FILENAME); + if host_id_path.exists() + && let Ok(value) = fs_err::read_to_string(&host_id_path) + { + let value = value.trim(); + if !value.is_empty() { + if Uuid::from_str(value).is_ok() { + self.set(KEY_HOST_ID, value).await?; + } else { + warn!("skipping migration of host_id: invalid UUID {value:?}"); + } + } + } + + // last_sync_time — validate as RFC3339 + let sync_path = data_dir.join(LEGACY_LAST_SYNC_FILENAME); + if sync_path.exists() + && let Ok(value) = fs_err::read_to_string(&sync_path) + { + let value = value.trim(); + if !value.is_empty() { + if OffsetDateTime::parse(value, &Rfc3339).is_ok() { + self.set(KEY_LAST_SYNC, value).await?; + } else { + warn!("skipping migration of last_sync_time: invalid RFC3339 {value:?}"); + } + } + } + + // last_version_check_time — validate as RFC3339 + let version_check_path = data_dir.join(LEGACY_LAST_VERSION_CHECK_FILENAME); + if version_check_path.exists() + && let Ok(value) = fs_err::read_to_string(&version_check_path) + { + let value = value.trim(); + if !value.is_empty() { + if OffsetDateTime::parse(value, &Rfc3339).is_ok() { + self.set(KEY_LAST_VERSION_CHECK, value).await?; + } else { + warn!( + "skipping migration of last_version_check_time: invalid RFC3339 {value:?}" + ); + } + } + } + + // latest_version — no strict validation, just non-empty + let latest_version_path = data_dir.join(LEGACY_LATEST_VERSION_FILENAME); + if latest_version_path.exists() + && let Ok(value) = fs_err::read_to_string(&latest_version_path) + { + let value = value.trim(); + if !value.is_empty() { + self.set(KEY_LATEST_VERSION, value).await?; + } + } + + // session token — no strict validation, just non-empty + let session_path = data_dir.join(LEGACY_SESSION_FILENAME); + if session_path.exists() + && let Ok(value) = fs_err::read_to_string(&session_path) + { + let value = value.trim(); + if !value.is_empty() { + self.set(KEY_SESSION, value).await?; + } + } + + self.set(KEY_FILES_MIGRATED, "true").await?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + async fn new_test_store() -> MetaStore { + MetaStore::new("sqlite::memory:", 2.0).await.unwrap() + } + + #[tokio::test] + async fn test_get_set_delete() { + let store = new_test_store().await; + + assert_eq!(store.get("foo").await.unwrap(), None); + + store.set("foo", "bar").await.unwrap(); + assert_eq!(store.get("foo").await.unwrap(), Some("bar".to_string())); + + store.set("foo", "baz").await.unwrap(); + assert_eq!(store.get("foo").await.unwrap(), Some("baz".to_string())); + + store.delete("foo").await.unwrap(); + assert_eq!(store.get("foo").await.unwrap(), None); + } + + #[tokio::test] + async fn test_host_id_generation_and_stability() { + let store = new_test_store().await; + + let id1 = store.host_id().await.unwrap(); + let id2 = store.host_id().await.unwrap(); + + assert_eq!(id1, id2, "host_id should be stable across calls"); + } + + #[tokio::test] + async fn test_sync_time() { + let store = new_test_store().await; + + let t = store.last_sync().await.unwrap(); + assert_eq!(t, OffsetDateTime::UNIX_EPOCH); + + store.save_sync_time().await.unwrap(); + let t = store.last_sync().await.unwrap(); + assert!(t > OffsetDateTime::UNIX_EPOCH); + } + + #[tokio::test] + async fn test_version_check_time() { + let store = new_test_store().await; + + let t = store.last_version_check().await.unwrap(); + assert_eq!(t, OffsetDateTime::UNIX_EPOCH); + + store.save_version_check_time().await.unwrap(); + let t = store.last_version_check().await.unwrap(); + assert!(t > OffsetDateTime::UNIX_EPOCH); + } + + #[tokio::test] + async fn test_session_crud() { + let store = new_test_store().await; + + assert!(!store.logged_in().await.unwrap()); + assert_eq!(store.session_token().await.unwrap(), None); + + store.save_session("tok123").await.unwrap(); + assert!(store.logged_in().await.unwrap()); + assert_eq!( + store.session_token().await.unwrap(), + Some("tok123".to_string()) + ); + + store.delete_session().await.unwrap(); + assert!(!store.logged_in().await.unwrap()); + } + + #[tokio::test] + async fn test_latest_version() { + let store = new_test_store().await; + + assert_eq!(store.latest_version().await.unwrap(), None); + + store.save_latest_version("1.2.3").await.unwrap(); + assert_eq!( + store.latest_version().await.unwrap(), + Some("1.2.3".to_string()) + ); + } +} diff --git a/crates/atuin-client/src/record/sync.rs b/crates/atuin-client/src/record/sync.rs index bd357b79..52c34a50 100644 --- a/crates/atuin-client/src/record/sync.rs +++ b/crates/atuin-client/src/record/sync.rs @@ -57,6 +57,7 @@ pub async fn diff( &settings.sync_address, settings .session_token() + .await .map_err(|e| SyncError::RemoteRequestError { msg: e.to_string() })? .as_str(), settings.network_connect_timeout, @@ -282,6 +283,7 @@ pub async fn sync_remote( &settings.sync_address, settings .session_token() + .await .map_err(|e| SyncError::RemoteRequestError { msg: e.to_string() })? .as_str(), settings.network_connect_timeout, diff --git a/crates/atuin-client/src/register.rs b/crates/atuin-client/src/register.rs index dae01efd..b0c80dc4 100644 --- a/crates/atuin-client/src/register.rs +++ b/crates/atuin-client/src/register.rs @@ -1,6 +1,4 @@ use eyre::Result; -use tokio::fs::File; -use tokio::io::AsyncWriteExt; use crate::{api_client, settings::Settings}; @@ -13,9 +11,8 @@ pub async fn register( let session = api_client::register(settings.sync_address.as_str(), &username, &email, &password).await?; - let path = settings.session_path.as_str(); - let mut file = File::create(path).await?; - file.write_all(session.session.as_bytes()).await?; + let meta = Settings::meta_store().await?; + meta.save_session(&session.session).await?; let _key = crate::encryption::load_key(settings)?; diff --git a/crates/atuin-client/src/settings.rs b/crates/atuin-client/src/settings.rs index a988e145..df629664 100644 --- a/crates/atuin-client/src/settings.rs +++ b/crates/atuin-client/src/settings.rs @@ -1,7 +1,5 @@ -use std::{ - collections::HashMap, convert::TryFrom, fmt, io::prelude::*, path::PathBuf, str::FromStr, - sync::OnceLock, -}; +use std::{collections::HashMap, fmt, io::prelude::*, path::PathBuf, str::FromStr, sync::OnceLock}; +use tokio::sync::OnceCell; use atuin_common::record::HostId; use atuin_common::utils; @@ -16,24 +14,18 @@ use regex::RegexSet; use semver::Version; use serde::{Deserialize, Serialize}; use serde_with::DeserializeFromStr; -use time::{ - OffsetDateTime, UtcOffset, - format_description::{FormatItem, well_known::Rfc3339}, - macros::format_description, -}; -use uuid::Uuid; +use time::{OffsetDateTime, UtcOffset, format_description::FormatItem, macros::format_description}; pub const HISTORY_PAGE_SIZE: i64 = 100; -pub const LAST_SYNC_FILENAME: &str = "last_sync_time"; -pub const LAST_VERSION_CHECK_FILENAME: &str = "last_version_check_time"; -pub const LATEST_VERSION_FILENAME: &str = "latest_version"; -pub const HOST_ID_FILENAME: &str = "host_id"; static EXAMPLE_CONFIG: &str = include_str!("../config.toml"); static DATA_DIR: OnceLock<PathBuf> = OnceLock::new(); +static META_CONFIG: OnceLock<(String, f64)> = OnceLock::new(); +static META_STORE: OnceCell<crate::meta::MetaStore> = OnceCell::const_new(); mod dotfiles; mod kv; +pub(crate) mod meta; mod scripts; #[derive(Clone, Debug, Deserialize, Copy, ValueEnum, PartialEq, Serialize)] @@ -673,7 +665,6 @@ pub struct Settings { pub db_path: String, pub record_store_path: String, pub key_path: String, - pub session_path: String, pub search_mode: SearchMode, pub filter_mode: Option<FilterMode>, pub filter_mode_shell_up_key_binding: Option<FilterMode>, @@ -751,6 +742,9 @@ pub struct Settings { #[serde(default)] pub tmux: Tmux, + + #[serde(default)] + pub meta: meta::Settings, } impl Settings { @@ -765,92 +759,48 @@ impl Settings { .expect("Could not deserialize config") } - fn effective_data_dir() -> PathBuf { + pub(crate) fn effective_data_dir() -> PathBuf { DATA_DIR .get() .cloned() .unwrap_or_else(atuin_common::utils::data_dir) } - fn save_to_data_dir(filename: &str, value: &str) -> Result<()> { - let data_dir = Self::effective_data_dir(); - let data_dir = data_dir.as_path(); - - let path = data_dir.join(filename); - - fs_err::write(path, value)?; - - Ok(()) - } - - fn read_from_data_dir(filename: &str) -> Option<String> { - let data_dir = Self::effective_data_dir(); - let data_dir = data_dir.as_path(); - - let path = data_dir.join(filename); - - if !path.exists() { - return None; - } - - let value = fs_err::read_to_string(path); - - value.ok() - } - - fn save_current_time(filename: &str) -> Result<()> { - Settings::save_to_data_dir( - filename, - OffsetDateTime::now_utc().format(&Rfc3339)?.as_str(), - )?; + // -- Meta store: lazily initialized on first access -- - Ok(()) - } - - fn load_time_from_file(filename: &str) -> Result<OffsetDateTime> { - let value = Settings::read_from_data_dir(filename); - - match value { - Some(v) => Ok(OffsetDateTime::parse(v.as_str(), &Rfc3339)?), - None => Ok(OffsetDateTime::UNIX_EPOCH), - } + pub async fn meta_store() -> Result<&'static crate::meta::MetaStore> { + META_STORE + .get_or_try_init(|| async { + let (db_path, timeout) = META_CONFIG.get().ok_or_else(|| { + eyre!("meta store config not set — Settings::new() has not been called") + })?; + crate::meta::MetaStore::new(db_path, *timeout).await + }) + .await } - pub fn save_sync_time() -> Result<()> { - Settings::save_current_time(LAST_SYNC_FILENAME) + pub async fn host_id() -> Result<HostId> { + Self::meta_store().await?.host_id().await } - pub fn save_version_check_time() -> Result<()> { - Settings::save_current_time(LAST_VERSION_CHECK_FILENAME) + pub async fn last_sync() -> Result<OffsetDateTime> { + Self::meta_store().await?.last_sync().await } - pub fn last_sync() -> Result<OffsetDateTime> { - Settings::load_time_from_file(LAST_SYNC_FILENAME) + pub async fn save_sync_time() -> Result<()> { + Self::meta_store().await?.save_sync_time().await } - pub fn last_version_check() -> Result<OffsetDateTime> { - Settings::load_time_from_file(LAST_VERSION_CHECK_FILENAME) + pub async fn last_version_check() -> Result<OffsetDateTime> { + Self::meta_store().await?.last_version_check().await } - pub fn host_id() -> Option<HostId> { - let id = Settings::read_from_data_dir(HOST_ID_FILENAME); - - if let Some(id) = id { - let parsed = - Uuid::from_str(id.as_str()).expect("failed to parse host ID from local directory"); - return Some(HostId(parsed)); - } - - let uuid = atuin_common::utils::uuid_v7(); - - Settings::save_to_data_dir(HOST_ID_FILENAME, uuid.as_simple().to_string().as_ref()) - .expect("Could not write host ID to data dir"); - - Some(HostId(uuid)) + pub async fn save_version_check_time() -> Result<()> { + Self::meta_store().await?.save_version_check_time().await } - pub fn should_sync(&self) -> Result<bool> { - if !self.auto_sync || !PathBuf::from(self.session_path.as_str()).exists() { + pub async fn should_sync(&self) -> Result<bool> { + if !self.auto_sync || !Self::meta_store().await?.logged_in().await? { return Ok(false); } @@ -861,30 +811,26 @@ impl Settings { match parse_duration(self.sync_frequency.as_str()) { Ok(d) => { let d = time::Duration::try_from(d)?; - Ok(OffsetDateTime::now_utc() - Settings::last_sync()? >= d) + Ok(OffsetDateTime::now_utc() - Settings::last_sync().await? >= d) } Err(e) => Err(eyre!("failed to check sync: {}", e)), } } - pub fn logged_in(&self) -> bool { - let session_path = self.session_path.as_str(); - - PathBuf::from(session_path).exists() + pub async fn logged_in(&self) -> Result<bool> { + Self::meta_store().await?.logged_in().await } - pub fn session_token(&self) -> Result<String> { - if !self.logged_in() { - return Err(eyre!("Tried to load session; not logged in")); + pub async fn session_token(&self) -> Result<String> { + match Self::meta_store().await?.session_token().await? { + Some(token) => Ok(token), + None => Err(eyre!("Tried to load session; not logged in")), } - - let session_path = self.session_path.as_str(); - Ok(fs_err::read_to_string(session_path)?) } #[cfg(feature = "check-update")] - fn needs_update_check(&self) -> Result<bool> { - let last_check = Settings::last_version_check()?; + async fn needs_update_check(&self) -> Result<bool> { + let last_check = Settings::last_version_check().await?; let diff = OffsetDateTime::now_utc() - last_check; // Check a max of once per hour @@ -898,16 +844,9 @@ impl Settings { let current = Version::parse(env!("CARGO_PKG_VERSION")).unwrap_or(Version::new(100000, 0, 0)); - if !self.needs_update_check()? { - // Worst case, we don't want Atuin to fail to start because something funky is going on with - // version checking. - let version = tokio::task::spawn_blocking(|| { - Settings::read_from_data_dir(LATEST_VERSION_FILENAME) - }) - .await - .expect("file task panicked"); - - let version = match version { + if !self.needs_update_check().await? { + let meta = Self::meta_store().await?; + let version = match meta.latest_version().await? { Some(v) => Version::parse(&v).unwrap_or(current), None => current, }; @@ -921,14 +860,9 @@ impl Settings { #[cfg(not(feature = "sync"))] let latest = current; - let latest_encoded = latest.to_string(); - tokio::task::spawn_blocking(move || { - Settings::save_version_check_time()?; - Settings::save_to_data_dir(LATEST_VERSION_FILENAME, &latest_encoded)?; - Ok::<(), eyre::Report>(()) - }) - .await - .expect("file task panicked")?; + let meta = Self::meta_store().await?; + Settings::save_version_check_time().await?; + meta.save_latest_version(&latest.to_string()).await?; Ok(latest) } @@ -992,14 +926,13 @@ impl Settings { let socket_path = atuin_common::utils::runtime_dir().join("atuin.sock"); let key_path = data_dir.join("key"); - let session_path = data_dir.join("session"); + let meta_path = data_dir.join("meta.db"); Ok(Config::builder() .set_default("history_format", "{time}\t{command}\t{duration}")? .set_default("db_path", db_path.to_str())? .set_default("record_store_path", record_store_path.to_str())? .set_default("key_path", key_path.to_str())? - .set_default("session_path", session_path.to_str())? .set_default("dialect", "us")? .set_default("timezone", "local")? .set_default("auto_sync", true)? @@ -1058,6 +991,7 @@ impl Settings { .set_default("daemon.tcp_port", 8889)? .set_default("kv.db_path", kv_path.to_str())? .set_default("scripts.db_path", scripts_path.to_str())? + .set_default("meta.db_path", meta_path.to_str())? .set_default( "search.filters", vec![ @@ -1170,12 +1104,16 @@ impl Settings { settings.db_path = Self::expand_path(settings.db_path)?; settings.record_store_path = Self::expand_path(settings.record_store_path)?; settings.key_path = Self::expand_path(settings.key_path)?; - settings.session_path = Self::expand_path(settings.session_path)?; settings.daemon.socket_path = Self::expand_path(settings.daemon.socket_path)?; // Validate UI settings settings.ui.validate()?; + // Register meta store config for lazy initialization on first access + META_CONFIG + .set((settings.meta.db_path.clone(), settings.local_timeout)) + .ok(); + Ok(settings) } @@ -1194,7 +1132,7 @@ impl Settings { &self.db_path, &self.record_store_path, &self.key_path, - &self.session_path, + &self.meta.db_path, ]; paths.iter().all(|p| !utils::broken_symlink(p)) } @@ -1325,14 +1263,13 @@ mod tests { let db_path: String = config.get("db_path")?; let key_path: String = config.get("key_path")?; - let session_path: String = config.get("session_path")?; let record_store_path: String = config.get("record_store_path")?; let kv_db_path: String = config.get("kv.db_path")?; let scripts_db_path: String = config.get("scripts.db_path")?; + let meta_db_path: String = config.get("meta.db_path")?; assert_eq!(db_path, custom_dir.join("history.db").to_str().unwrap()); assert_eq!(key_path, custom_dir.join("key").to_str().unwrap()); - assert_eq!(session_path, custom_dir.join("session").to_str().unwrap()); assert_eq!( record_store_path, custom_dir.join("records.db").to_str().unwrap() @@ -1342,6 +1279,7 @@ mod tests { scripts_db_path, custom_dir.join("scripts.db").to_str().unwrap() ); + assert_eq!(meta_db_path, custom_dir.join("meta.db").to_str().unwrap()); Ok(()) } diff --git a/crates/atuin-client/src/settings/meta.rs b/crates/atuin-client/src/settings/meta.rs new file mode 100644 index 00000000..108d74ec --- /dev/null +++ b/crates/atuin-client/src/settings/meta.rs @@ -0,0 +1,17 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Settings { + pub db_path: String, +} + +impl Default for Settings { + fn default() -> Self { + let dir = atuin_common::utils::data_dir(); + let path = dir.join("meta.db"); + + Self { + db_path: path.to_string_lossy().to_string(), + } + } +} diff --git a/crates/atuin-client/src/sync.rs b/crates/atuin-client/src/sync.rs index 57ac18e4..4c236de4 100644 --- a/crates/atuin-client/src/sync.rs +++ b/crates/atuin-client/src/sync.rs @@ -53,7 +53,7 @@ async fn sync_download( let mut last_sync = if force { OffsetDateTime::UNIX_EPOCH } else { - Settings::last_sync()? + Settings::last_sync().await? }; let mut last_timestamp = OffsetDateTime::UNIX_EPOCH; @@ -194,12 +194,12 @@ async fn sync_upload( pub async fn sync(settings: &Settings, force: bool, db: &impl Database) -> Result<()> { let client = api_client::Client::new( &settings.sync_address, - settings.session_token()?.as_str(), + settings.session_token().await?.as_str(), settings.network_connect_timeout, settings.network_timeout, )?; - Settings::save_sync_time()?; + Settings::save_sync_time().await?; let key = load_key(settings)?; // encryption key diff --git a/crates/atuin-daemon/src/server.rs b/crates/atuin-daemon/src/server.rs index ce864343..2cba1753 100644 --- a/crates/atuin-daemon/src/server.rs +++ b/crates/atuin-daemon/src/server.rs @@ -254,7 +254,7 @@ pub async fn listen( .context("could not load encryption key")? .into(); - let host_id = Settings::host_id().expect("failed to get host_id"); + let host_id = Settings::host_id().await?; let history_store = HistoryStore::new(store.clone(), host_id, encryption_key); let history = HistoryService::new(history_store.clone(), history_db.clone()); diff --git a/crates/atuin-daemon/src/server/sync.rs b/crates/atuin-daemon/src/server/sync.rs index 3aa5dec3..e1e49597 100644 --- a/crates/atuin-daemon/src/server/sync.rs +++ b/crates/atuin-daemon/src/server/sync.rs @@ -21,7 +21,7 @@ pub async fn worker( tracing::info!("booting sync worker"); let encryption_key: [u8; 32] = encryption::load_key(&settings)?.into(); - let host_id = Settings::host_id().expect("failed to get host_id"); + let host_id = Settings::host_id().await?; let alias_store = AliasStore::new(store.clone(), host_id, encryption_key); let var_store = VarStore::new(store.clone(), host_id, encryption_key); @@ -38,7 +38,15 @@ pub async fn worker( ticker.tick().await; tracing::info!("sync worker tick"); - if !settings.logged_in() { + let logged_in = match settings.logged_in().await { + Ok(v) => v, + Err(e) => { + tracing::warn!("failed to check login status, skipping sync tick: {e}"); + continue; + } + }; + + if !logged_in { tracing::debug!("not logged in, skipping sync tick"); continue; } @@ -82,7 +90,7 @@ pub async fn worker( } // store sync time - tokio::task::spawn_blocking(Settings::save_sync_time).await??; + Settings::save_sync_time().await?; } } } diff --git a/crates/atuin/src/command/client/account.rs b/crates/atuin/src/command/client/account.rs index e99c9593..eae8afdb 100644 --- a/crates/atuin/src/command/client/account.rs +++ b/crates/atuin/src/command/client/account.rs @@ -39,7 +39,7 @@ impl Cmd { match self.command { Commands::Login(l) => l.run(&settings, &store).await, Commands::Register(r) => r.run(&settings).await, - Commands::Logout => logout::run(&settings), + Commands::Logout => logout::run().await, Commands::Delete => delete::run(&settings).await, Commands::ChangePassword(c) => c.run(&settings).await, } diff --git a/crates/atuin/src/command/client/account/change_password.rs b/crates/atuin/src/command/client/account/change_password.rs index a91495db..acd4b262 100644 --- a/crates/atuin/src/command/client/account/change_password.rs +++ b/crates/atuin/src/command/client/account/change_password.rs @@ -26,7 +26,7 @@ pub async fn run( ) -> Result<()> { let client = api_client::Client::new( &settings.sync_address, - settings.session_token()?.as_str(), + settings.session_token().await?.as_str(), settings.network_connect_timeout, settings.network_timeout, )?; diff --git a/crates/atuin/src/command/client/account/delete.rs b/crates/atuin/src/command/client/account/delete.rs index 3e2db33c..73dcb5dd 100644 --- a/crates/atuin/src/command/client/account/delete.rs +++ b/crates/atuin/src/command/client/account/delete.rs @@ -1,28 +1,22 @@ use atuin_client::{api_client, settings::Settings}; use eyre::{Result, bail}; -use std::fs::remove_file; -use std::path::PathBuf; pub async fn run(settings: &Settings) -> Result<()> { - let session_path = settings.session_path.as_str(); - - if !PathBuf::from(session_path).exists() { + if !settings.logged_in().await? { bail!("You are not logged in"); } let client = api_client::Client::new( &settings.sync_address, - settings.session_token()?.as_str(), + settings.session_token().await?.as_str(), settings.network_connect_timeout, settings.network_timeout, )?; client.delete().await?; - // Fixes stale session+key when account is deleted via CLI. - if PathBuf::from(session_path).exists() { - remove_file(PathBuf::from(session_path))?; - } + // Clean up session from meta store + Settings::meta_store().await?.delete_session().await?; println!("Your account is deleted"); diff --git a/crates/atuin/src/command/client/account/login.rs b/crates/atuin/src/command/client/account/login.rs index 2d82abca..504b8223 100644 --- a/crates/atuin/src/command/client/account/login.rs +++ b/crates/atuin/src/command/client/account/login.rs @@ -41,7 +41,7 @@ impl Cmd { // // I'd quite like to ditch that behaviour, so have not brought it into the library // function. - if settings.logged_in() { + if settings.logged_in().await? { bail!( "You are already logged in! Please run 'atuin logout' if you wish to login again" ); @@ -99,11 +99,9 @@ impl Cmd { } }; - // I've simplified this a little, but it could really do with a refactor - // Annoyingly, it's also very important to get it correct if key.is_empty() { if key_path.exists() { - let bytes = fs_err::read_to_string(key_path) + let bytes = fs_err::read_to_string(&key_path) .context("existing key file couldn't be read")?; if decode_key(bytes).is_err() { bail!("the key in existing key file was invalid"); @@ -118,7 +116,7 @@ impl Cmd { bail!("the specified key was invalid"); } - let mut file = File::create(key_path).await?; + let mut file = File::create(&key_path).await?; file.write_all(key.as_bytes()).await?; } else { // we now know that the user has logged in specifying a key, AND that the key path @@ -139,7 +137,7 @@ impl Cmd { store.re_encrypt(¤t_key, &new_key).await?; println!("Writing new key"); - let mut file = File::create(key_path).await?; + let mut file = File::create(&key_path).await?; file.write_all(encoded.as_bytes()).await?; } } @@ -150,9 +148,10 @@ impl Cmd { ) .await?; - let session_path = settings.session_path.as_str(); - let mut file = File::create(session_path).await?; - file.write_all(session.session.as_bytes()).await?; + Settings::meta_store() + .await? + .save_session(&session.session) + .await?; println!("Logged in!"); diff --git a/crates/atuin/src/command/client/account/logout.rs b/crates/atuin/src/command/client/account/logout.rs index 836360a1..b958e65a 100644 --- a/crates/atuin/src/command/client/account/logout.rs +++ b/crates/atuin/src/command/client/account/logout.rs @@ -1,6 +1,5 @@ -use atuin_client::settings::Settings; use eyre::Result; -pub fn run(settings: &Settings) -> Result<()> { - atuin_client::logout::logout(settings) +pub async fn run() -> Result<()> { + atuin_client::logout::logout().await } diff --git a/crates/atuin/src/command/client/account/register.rs b/crates/atuin/src/command/client/account/register.rs index 80c4c29c..918c89e8 100644 --- a/crates/atuin/src/command/client/account/register.rs +++ b/crates/atuin/src/command/client/account/register.rs @@ -1,6 +1,5 @@ use clap::Parser; use eyre::{Result, bail}; -use tokio::{fs::File, io::AsyncWriteExt}; use atuin_client::{api_client, settings::Settings}; @@ -45,9 +44,8 @@ pub async fn run( let session = api_client::register(settings.sync_address.as_str(), &username, &email, &password).await?; - let path = settings.session_path.as_str(); - let mut file = File::create(path).await?; - file.write_all(session.session.as_bytes()).await?; + let meta = Settings::meta_store().await?; + meta.save_session(&session.session).await?; let _key = atuin_client::encryption::load_key(settings)?; diff --git a/crates/atuin/src/command/client/doctor.rs b/crates/atuin/src/command/client/doctor.rs index 975b4cf3..6f9cd875 100644 --- a/crates/atuin/src/command/client/doctor.rs +++ b/crates/atuin/src/command/client/doctor.rs @@ -1,5 +1,5 @@ use std::process::Command; -use std::{env, path::PathBuf, str::FromStr}; +use std::{env, str::FromStr}; use atuin_client::database::Sqlite; use atuin_client::settings::Settings; @@ -246,12 +246,13 @@ struct SyncInfo { } impl SyncInfo { - pub fn new(settings: &Settings) -> Self { + pub async fn new(settings: &Settings) -> Self { Self { cloud: settings.sync_address == "https://api.atuin.sh", auto_sync: settings.auto_sync, records: settings.sync.records, last_sync: Settings::last_sync() + .await .map_or_else(|_| "no last sync".to_string(), |v| v.to_string()), } } @@ -262,7 +263,6 @@ struct SettingPaths { db: String, record_store: String, key: String, - session: String, } impl SettingPaths { @@ -271,7 +271,6 @@ impl SettingPaths { db: settings.db_path.clone(), record_store: settings.record_store_path.clone(), key: settings.key_path.clone(), - session: settings.session_path.clone(), } } @@ -280,7 +279,6 @@ impl SettingPaths { ("ATUIN_DB_PATH", &self.db), ("ATUIN_RECORD_STORE", &self.record_store), ("ATUIN_KEY", &self.key), - ("ATUIN_SESSION", &self.session), ]; for (path_env_var, path) in paths { @@ -310,11 +308,10 @@ struct AtuinInfo { impl AtuinInfo { pub async fn new(settings: &Settings) -> Self { - let session_path = settings.session_path.as_str(); - let logged_in = PathBuf::from(session_path).exists(); + let logged_in = settings.logged_in().await.unwrap_or(false); let sync = if logged_in { - Some(SyncInfo::new(settings)) + Some(SyncInfo::new(settings).await) } else { None }; diff --git a/crates/atuin/src/command/client/dotfiles/alias.rs b/crates/atuin/src/command/client/dotfiles/alias.rs index 8b640ab0..2a916b06 100644 --- a/crates/atuin/src/command/client/dotfiles/alias.rs +++ b/crates/atuin/src/command/client/dotfiles/alias.rs @@ -102,7 +102,7 @@ impl Cmd { let encryption_key: [u8; 32] = encryption::load_key(settings) .context("could not load encryption key")? .into(); - let host_id = Settings::host_id().expect("failed to get host_id"); + let host_id = Settings::host_id().await?; let alias_store = AliasStore::new(store, host_id, encryption_key); diff --git a/crates/atuin/src/command/client/dotfiles/var.rs b/crates/atuin/src/command/client/dotfiles/var.rs index f4e0361d..b48ceaa4 100644 --- a/crates/atuin/src/command/client/dotfiles/var.rs +++ b/crates/atuin/src/command/client/dotfiles/var.rs @@ -83,7 +83,7 @@ impl Cmd { let encryption_key: [u8; 32] = encryption::load_key(settings) .context("could not load encryption key")? .into(); - let host_id = Settings::host_id().expect("failed to get host_id"); + let host_id = Settings::host_id().await?; let var_store = VarStore::new(store, host_id, encryption_key); diff --git a/crates/atuin/src/command/client/history.rs b/crates/atuin/src/command/client/history.rs index c85f6c49..e8e544b5 100644 --- a/crates/atuin/src/command/client/history.rs +++ b/crates/atuin/src/command/client/history.rs @@ -454,12 +454,12 @@ impl Cmd { db.update(&h).await?; history_store.push(h).await?; - if settings.should_sync()? { + if settings.should_sync().await? { #[cfg(feature = "sync")] { if settings.sync.records { let (_, downloaded) = record::sync::sync(settings, &store).await?; - Settings::save_sync_time()?; + Settings::save_sync_time().await?; crate::sync::build(settings, &store, db, Some(&downloaded)).await?; } else { @@ -579,7 +579,7 @@ impl Cmd { let encryption_key: [u8; 32] = encryption::load_key(settings) .context("could not load encryption key")? .into(); - let host_id = Settings::host_id().expect("failed to get host_id"); + let host_id = Settings::host_id().await?; let history_store = HistoryStore::new(store.clone(), host_id, encryption_key); for entry in matches { @@ -634,7 +634,7 @@ impl Cmd { let encryption_key: [u8; 32] = encryption::load_key(settings) .context("could not load encryption key")? .into(); - let host_id = Settings::host_id().expect("failed to get host_id"); + let host_id = Settings::host_id().await?; let history_store = HistoryStore::new(store.clone(), host_id, encryption_key); for entry in matches { @@ -651,7 +651,7 @@ impl Cmd { } pub async fn run(self, settings: &Settings) -> Result<()> { - let context = current_context(); + let context = current_context().await?; #[cfg(feature = "daemon")] // Skip initializing any databases for start/end, if the daemon is enabled @@ -680,7 +680,7 @@ impl Cmd { .context("could not load encryption key")? .into(); - let host_id = Settings::host_id().expect("failed to get host_id"); + let host_id = Settings::host_id().await?; let history_store = HistoryStore::new(store.clone(), host_id, encryption_key); match self { diff --git a/crates/atuin/src/command/client/info.rs b/crates/atuin/src/command/client/info.rs index efc107e3..a69f9b2f 100644 --- a/crates/atuin/src/command/client/info.rs +++ b/crates/atuin/src/command/client/info.rs @@ -10,12 +10,12 @@ pub fn run(settings: &Settings) { sever_config.push("server.toml");
let config_paths = format!(
- "Config files:\nclient config: {:?}\nserver config: {:?}\nclient db path: {:?}\nkey path: {:?}\nsession path: {:?}",
+ "Config files:\nclient config: {:?}\nserver config: {:?}\nclient db path: {:?}\nkey path: {:?}\nmeta db path: {:?}",
config_file.to_string_lossy(),
sever_config.to_string_lossy(),
settings.db_path,
settings.key_path,
- settings.session_path
+ settings.meta.db_path
);
let env_vars = format!(
diff --git a/crates/atuin/src/command/client/init.rs b/crates/atuin/src/command/client/init.rs index 3b55f160..99fce33d 100644 --- a/crates/atuin/src/command/client/init.rs +++ b/crates/atuin/src/command/client/init.rs @@ -124,7 +124,7 @@ $env.config = ( let encryption_key: [u8; 32] = encryption::load_key(settings) .context("could not load encryption key")? .into(); - let host_id = Settings::host_id().expect("failed to get host_id"); + let host_id = Settings::host_id().await?; let alias_store = AliasStore::new(sqlite_store.clone(), host_id, encryption_key); let var_store = VarStore::new(sqlite_store.clone(), host_id, encryption_key); diff --git a/crates/atuin/src/command/client/kv.rs b/crates/atuin/src/command/client/kv.rs index b4db9c17..de487b89 100644 --- a/crates/atuin/src/command/client/kv.rs +++ b/crates/atuin/src/command/client/kv.rs @@ -65,7 +65,7 @@ impl Cmd { .context("could not load encryption key")? .into(); - let host_id = Settings::host_id().expect("failed to get host_id"); + let host_id = Settings::host_id().await?; let kv_db = atuin_kv::database::Database::new(settings.kv.db_path.clone(), 1.0).await?; let kv_store = KvStore::new(store.clone(), kv_db, host_id, encryption_key); diff --git a/crates/atuin/src/command/client/scripts.rs b/crates/atuin/src/command/client/scripts.rs index 851755af..e5adacc4 100644 --- a/crates/atuin/src/command/client/scripts.rs +++ b/crates/atuin/src/command/client/scripts.rs @@ -227,7 +227,7 @@ impl Cmd { let script_content = if let Some(count_opt) = new_script.last { // Get the last N commands from history, plus 1 to exclude the command that runs this script let count = count_opt.unwrap_or(1) + 1; // Add 1 to the count to exclude the current command - let context = atuin_client::database::current_context(); + let context = atuin_client::database::current_context().await?; // Get the last N+1 commands, filtering by the default mode let filters = [settings.default_filter_mode(context.git_root.is_some())]; @@ -566,7 +566,7 @@ impl Cmd { store: SqliteStore, history_db: &impl Database, ) -> Result<()> { - let host_id = Settings::host_id().expect("failed to get host_id"); + let host_id = Settings::host_id().await?; let encryption_key: [u8; 32] = atuin_client::encryption::load_key(settings)?.into(); let script_store = ScriptStore::new(store, host_id, encryption_key); diff --git a/crates/atuin/src/command/client/search.rs b/crates/atuin/src/command/client/search.rs index cb03420a..fd090fbc 100644 --- a/crates/atuin/src/command/client/search.rs +++ b/crates/atuin/src/command/client/search.rs @@ -211,7 +211,7 @@ impl Cmd { let encryption_key: [u8; 32] = encryption::load_key(settings)?.into(); - let host_id = Settings::host_id().expect("failed to get host_id"); + let host_id = Settings::host_id().await?; let history_store = HistoryStore::new(store.clone(), host_id, encryption_key); if self.interactive { @@ -305,7 +305,7 @@ async fn run_non_interactive( filter_options.cwd }; - let context = current_context(); + let context = current_context().await?; let opt_filter = OptFilters { cwd: dir.clone(), diff --git a/crates/atuin/src/command/client/search/interactive.rs b/crates/atuin/src/command/client/search/interactive.rs index ccfa60bb..5879bb69 100644 --- a/crates/atuin/src/command/client/search/interactive.rs +++ b/crates/atuin/src/command/client/search/interactive.rs @@ -1372,7 +1372,7 @@ pub async fn history( let update_needed = tokio::spawn(async move { settings2.needs_update().await }).fuse(); tokio::pin!(update_needed); - let context = current_context(); + let context = current_context().await?; let history_count = db.history_count(false).await?; let search_mode = if settings.shell_up_key_binding { diff --git a/crates/atuin/src/command/client/stats.rs b/crates/atuin/src/command/client/stats.rs index 3ee60633..a7fc00ac 100644 --- a/crates/atuin/src/command/client/stats.rs +++ b/crates/atuin/src/command/client/stats.rs @@ -40,7 +40,7 @@ pub struct Cmd { impl Cmd { pub async fn run(&self, db: &impl Database, settings: &Settings, theme: &Theme) -> Result<()> { - let context = current_context(); + let context = current_context().await?; let words = if self.period.is_empty() { String::from("all") } else { diff --git a/crates/atuin/src/command/client/store.rs b/crates/atuin/src/command/client/store.rs index 63029ee1..513c404a 100644 --- a/crates/atuin/src/command/client/store.rs +++ b/crates/atuin/src/command/client/store.rs @@ -70,7 +70,7 @@ impl Cmd { } pub async fn status(&self, store: SqliteStore) -> Result<()> { - let host_id = Settings::host_id().expect("failed to get host_id"); + let host_id = Settings::host_id().await?; let offset = UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC); let status = store.status().await?; diff --git a/crates/atuin/src/command/client/store/push.rs b/crates/atuin/src/command/client/store/push.rs index b5e13092..243dc7ec 100644 --- a/crates/atuin/src/command/client/store/push.rs +++ b/crates/atuin/src/command/client/store/push.rs @@ -34,7 +34,7 @@ pub struct Push { impl Push { pub async fn run(&self, settings: &Settings, store: SqliteStore) -> Result<()> { - let host_id = Settings::host_id().expect("failed to get host_id"); + let host_id = Settings::host_id().await?; if self.force { println!("Forcing remote store overwrite!"); @@ -42,7 +42,7 @@ impl Push { let client = Client::new( &settings.sync_address, - settings.session_token()?.as_str(), + settings.session_token().await?.as_str(), settings.network_connect_timeout, settings.network_timeout * 10, // we may be deleting a lot of data... so up the // timeout diff --git a/crates/atuin/src/command/client/store/rebuild.rs b/crates/atuin/src/command/client/store/rebuild.rs index f8784a6f..8acec531 100644 --- a/crates/atuin/src/command/client/store/rebuild.rs +++ b/crates/atuin/src/command/client/store/rebuild.rs @@ -52,7 +52,7 @@ impl Rebuild { ) -> Result<()> { let encryption_key: [u8; 32] = encryption::load_key(settings)?.into(); - let host_id = Settings::host_id().expect("failed to get host_id"); + let host_id = Settings::host_id().await?; let history_store = HistoryStore::new(store, host_id, encryption_key); history_store.build(database).await?; @@ -63,7 +63,7 @@ impl Rebuild { async fn rebuild_dotfiles(&self, settings: &Settings, store: SqliteStore) -> Result<()> { let encryption_key: [u8; 32] = encryption::load_key(settings)?.into(); - let host_id = Settings::host_id().expect("failed to get host_id"); + let host_id = Settings::host_id().await?; let alias_store = AliasStore::new(store.clone(), host_id, encryption_key); let var_store = VarStore::new(store.clone(), host_id, encryption_key); @@ -76,7 +76,7 @@ impl Rebuild { async fn rebuild_scripts(&self, settings: &Settings, store: SqliteStore) -> Result<()> { let encryption_key: [u8; 32] = encryption::load_key(settings)?.into(); - let host_id = Settings::host_id().expect("failed to get host_id"); + let host_id = Settings::host_id().await?; let script_store = ScriptStore::new(store, host_id, encryption_key); let database = atuin_scripts::database::Database::new(settings.scripts.db_path.clone(), 1.0).await?; diff --git a/crates/atuin/src/command/client/sync.rs b/crates/atuin/src/command/client/sync.rs index be1bf6d2..6063e489 100644 --- a/crates/atuin/src/command/client/sync.rs +++ b/crates/atuin/src/command/client/sync.rs @@ -53,7 +53,7 @@ impl Cmd { match self { Self::Sync { force } => run(&settings, force, db, store).await, Self::Login(l) => l.run(&settings, &store).await, - Self::Logout => account::logout::run(&settings), + Self::Logout => account::logout::run().await, Self::Register(r) => r.run(&settings).await, Self::Status => status::run(&settings, db).await, Self::Key { base64 } => { @@ -85,7 +85,7 @@ async fn run( .context("could not load encryption key")? .into(); - let host_id = Settings::host_id().expect("failed to get host_id"); + let host_id = Settings::host_id().await?; let history_store = HistoryStore::new(store.clone(), host_id, encryption_key); let (uploaded, downloaded) = sync::sync(settings, &store).await?; diff --git a/crates/atuin/src/command/client/sync/status.rs b/crates/atuin/src/command/client/sync/status.rs index 79ac4705..2162fa00 100644 --- a/crates/atuin/src/command/client/sync/status.rs +++ b/crates/atuin/src/command/client/sync/status.rs @@ -1,26 +1,22 @@ -use std::path::PathBuf; - use crate::{SHA, VERSION}; use atuin_client::{api_client, database::Database, settings::Settings}; use colored::Colorize; use eyre::{Result, bail}; pub async fn run(settings: &Settings, db: &impl Database) -> Result<()> { - let session_path = settings.session_path.as_str(); - - if !PathBuf::from(session_path).exists() { + if !settings.logged_in().await? { bail!("You are not logged in to a sync server - cannot show sync status"); } let client = api_client::Client::new( &settings.sync_address, - settings.session_token()?.as_str(), + settings.session_token().await?.as_str(), settings.network_connect_timeout, settings.network_timeout, )?; let me = client.me().await?; - let last_sync = Settings::last_sync()?; + let last_sync = Settings::last_sync().await?; println!("Atuin v{VERSION} - Build rev {SHA}\n"); diff --git a/crates/atuin/src/command/client/wrapped.rs b/crates/atuin/src/command/client/wrapped.rs index ad578f7b..20b79a2e 100644 --- a/crates/atuin/src/command/client/wrapped.rs +++ b/crates/atuin/src/command/client/wrapped.rs @@ -328,7 +328,7 @@ pub async fn run( let alias_map: HashMap<String, String> = if settings.dotfiles.enabled { if let Ok(encryption_key) = encryption::load_key(settings) { let encryption_key: [u8; 32] = encryption_key.into(); - let host_id = Settings::host_id().expect("failed to get host_id"); + let host_id = Settings::host_id().await?; let alias_store = AliasStore::new(store, host_id, encryption_key); alias_store diff --git a/crates/atuin/src/sync.rs b/crates/atuin/src/sync.rs index b95b78b9..26004130 100644 --- a/crates/atuin/src/sync.rs +++ b/crates/atuin/src/sync.rs @@ -25,7 +25,7 @@ pub async fn build( .context("could not load encryption key")? .into(); - let host_id = Settings::host_id().expect("failed to get host_id"); + let host_id = Settings::host_id().await?; let downloaded = downloaded.unwrap_or(&[]); |
