aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--crates/atuin-client/meta-migrations/20260203030924_create_meta.sql5
-rw-r--r--crates/atuin-client/src/database.rs17
-rw-r--r--crates/atuin-client/src/history/store.rs2
-rw-r--r--crates/atuin-client/src/lib.rs1
-rw-r--r--crates/atuin-client/src/login.rs11
-rw-r--r--crates/atuin-client/src/logout.rs11
-rw-r--r--crates/atuin-client/src/meta.rs365
-rw-r--r--crates/atuin-client/src/record/sync.rs2
-rw-r--r--crates/atuin-client/src/register.rs7
-rw-r--r--crates/atuin-client/src/settings.rs176
-rw-r--r--crates/atuin-client/src/settings/meta.rs17
-rw-r--r--crates/atuin-client/src/sync.rs6
-rw-r--r--crates/atuin-daemon/src/server.rs2
-rw-r--r--crates/atuin-daemon/src/server/sync.rs14
-rw-r--r--crates/atuin/src/command/client/account.rs2
-rw-r--r--crates/atuin/src/command/client/account/change_password.rs2
-rw-r--r--crates/atuin/src/command/client/account/delete.rs14
-rw-r--r--crates/atuin/src/command/client/account/login.rs17
-rw-r--r--crates/atuin/src/command/client/account/logout.rs5
-rw-r--r--crates/atuin/src/command/client/account/register.rs6
-rw-r--r--crates/atuin/src/command/client/doctor.rs13
-rw-r--r--crates/atuin/src/command/client/dotfiles/alias.rs2
-rw-r--r--crates/atuin/src/command/client/dotfiles/var.rs2
-rw-r--r--crates/atuin/src/command/client/history.rs12
-rw-r--r--crates/atuin/src/command/client/info.rs4
-rw-r--r--crates/atuin/src/command/client/init.rs2
-rw-r--r--crates/atuin/src/command/client/kv.rs2
-rw-r--r--crates/atuin/src/command/client/scripts.rs4
-rw-r--r--crates/atuin/src/command/client/search.rs4
-rw-r--r--crates/atuin/src/command/client/search/interactive.rs2
-rw-r--r--crates/atuin/src/command/client/stats.rs2
-rw-r--r--crates/atuin/src/command/client/store.rs2
-rw-r--r--crates/atuin/src/command/client/store/push.rs4
-rw-r--r--crates/atuin/src/command/client/store/rebuild.rs6
-rw-r--r--crates/atuin/src/command/client/sync.rs4
-rw-r--r--crates/atuin/src/command/client/sync/status.rs10
-rw-r--r--crates/atuin/src/command/client/wrapped.rs2
-rw-r--r--crates/atuin/src/sync.rs2
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(&current_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(&current_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(&[]);