chore: migrate to rust 2024 (#2635)

* chore: upgrade to 2024 edition

* ugh unsafe

* format

* nixxxxxxxxxxx why
This commit is contained in:
Ellie Huxtable
2025-03-19 12:44:20 +00:00
committed by GitHub
parent 28d5ff83c2
commit 14ec768b45
93 changed files with 1035 additions and 777 deletions

1119
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package] [package]
name = "atuin-client" name = "atuin-client"
edition = "2021" edition = "2024"
description = "client library for atuin" description = "client library for atuin"
rust-version = { workspace = true } rust-version = { workspace = true }

View File

@ -2,12 +2,16 @@ use std::collections::HashMap;
use std::env; use std::env;
use std::time::Duration; use std::time::Duration;
use eyre::{bail, Result}; use eyre::{Result, bail};
use reqwest::{ use reqwest::{
header::{HeaderMap, AUTHORIZATION, USER_AGENT},
Response, StatusCode, Url, Response, StatusCode, Url,
header::{AUTHORIZATION, HeaderMap, USER_AGENT},
}; };
use atuin_common::{
api::{ATUIN_CARGO_VERSION, ATUIN_HEADER_VERSION, ATUIN_VERSION},
record::{EncryptedData, HostId, Record, RecordIdx},
};
use atuin_common::{ use atuin_common::{
api::{ api::{
AddHistoryRequest, ChangePasswordRequest, CountResponse, DeleteHistoryRequest, AddHistoryRequest, ChangePasswordRequest, CountResponse, DeleteHistoryRequest,
@ -17,14 +21,10 @@ use atuin_common::{
}, },
record::RecordStatus, record::RecordStatus,
}; };
use atuin_common::{
api::{ATUIN_CARGO_VERSION, ATUIN_HEADER_VERSION, ATUIN_VERSION},
record::{EncryptedData, HostId, Record, RecordIdx},
};
use semver::Version; use semver::Version;
use time::format_description::well_known::Rfc3339;
use time::OffsetDateTime; use time::OffsetDateTime;
use time::format_description::well_known::Rfc3339;
use crate::{history::History, sync::hash_str, utils::get_host_user}; use crate::{history::History, sync::hash_str, utils::get_host_user};
@ -126,7 +126,9 @@ pub fn ensure_version(response: &Response) -> Result<bool> {
// If the client is newer than the server // If the client is newer than the server
if version.major < ATUIN_VERSION.major { if version.major < ATUIN_VERSION.major {
println!("Atuin version mismatch! In order to successfully sync, the server needs to run a newer version of Atuin"); println!(
"Atuin version mismatch! In order to successfully sync, the server needs to run a newer version of Atuin"
);
println!("Client: {}", ATUIN_CARGO_VERSION); println!("Client: {}", ATUIN_CARGO_VERSION);
println!("Server: {}", version); println!("Server: {}", version);
@ -157,10 +159,14 @@ async fn handle_resp_error(resp: Response) -> Result<Response> {
bail!("Invalid request to the service: {status} - {reason}.") bail!("Invalid request to the service: {status} - {reason}.")
} }
bail!("There was an error with the atuin sync service, server error {status}: {reason}.\nIf the problem persists, contact the host") bail!(
"There was an error with the atuin sync service, server error {status}: {reason}.\nIf the problem persists, contact the host"
)
} }
bail!("There was an error with the atuin sync service: Status {status:?}.\nIf the problem persists, contact the host") bail!(
"There was an error with the atuin sync service: Status {status:?}.\nIf the problem persists, contact the host"
)
} }
Ok(resp) Ok(resp)

View File

@ -10,14 +10,14 @@ use async_trait::async_trait;
use atuin_common::utils; use atuin_common::utils;
use fs_err as fs; use fs_err as fs;
use itertools::Itertools; use itertools::Itertools;
use rand::{distributions::Alphanumeric, Rng}; use rand::{Rng, distributions::Alphanumeric};
use sql_builder::{bind::Bind, esc, quote, SqlBuilder, SqlName}; use sql_builder::{SqlBuilder, SqlName, bind::Bind, esc, quote};
use sqlx::{ use sqlx::{
Result, Row,
sqlite::{ sqlite::{
SqliteConnectOptions, SqliteJournalMode, SqlitePool, SqlitePoolOptions, SqliteRow, SqliteConnectOptions, SqliteJournalMode, SqlitePool, SqlitePoolOptions, SqliteRow,
SqliteSynchronous, SqliteSynchronous,
}, },
Result, Row,
}; };
use time::OffsetDateTime; use time::OffsetDateTime;
@ -55,7 +55,9 @@ pub struct OptFilters {
pub fn current_context() -> Context { pub fn current_context() -> Context {
let Ok(session) = env::var("ATUIN_SESSION") else { 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."); eprintln!(
"ERROR: Failed to find $ATUIN_SESSION in the environment. Check that you have correctly set up your shell."
);
std::process::exit(1); std::process::exit(1);
}; };
let hostname = get_host_user(); let hostname = get_host_user();
@ -131,7 +133,9 @@ impl Sqlite {
debug!("opening sqlite database at {:?}", path); debug!("opening sqlite database at {:?}", path);
if utils::broken_symlink(path) { if utils::broken_symlink(path) {
eprintln!("Atuin: Sqlite db path ({path:?}) is a broken symlink. Unable to read or create replacement."); eprintln!(
"Atuin: Sqlite db path ({path:?}) is a broken symlink. Unable to read or create replacement."
);
std::process::exit(1); std::process::exit(1);
} }

View File

@ -10,17 +10,17 @@
use std::{io::prelude::*, path::PathBuf}; use std::{io::prelude::*, path::PathBuf};
use base64::prelude::{Engine, BASE64_STANDARD}; use base64::prelude::{BASE64_STANDARD, Engine};
pub use crypto_secretbox::Key; pub use crypto_secretbox::Key;
use crypto_secretbox::{ use crypto_secretbox::{
aead::{Nonce, OsRng},
AeadCore, AeadInPlace, KeyInit, XSalsa20Poly1305, AeadCore, AeadInPlace, KeyInit, XSalsa20Poly1305,
aead::{Nonce, OsRng},
}; };
use eyre::{bail, ensure, eyre, Context, Result}; use eyre::{Context, Result, bail, ensure, eyre};
use fs_err as fs; use fs_err as fs;
use rmp::{decode::Bytes, Marker}; use rmp::{Marker, decode::Bytes};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use time::{format_description::well_known::Rfc3339, macros::format_description, OffsetDateTime}; use time::{OffsetDateTime, format_description::well_known::Rfc3339, macros::format_description};
use crate::{history::History, settings::Settings}; use crate::{history::History, settings::Settings};
@ -265,9 +265,9 @@ fn error_report<E: std::fmt::Debug>(err: E) -> eyre::Report {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crypto_secretbox::{aead::OsRng, KeyInit, XSalsa20Poly1305}; use crypto_secretbox::{KeyInit, XSalsa20Poly1305, aead::OsRng};
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use time::{macros::datetime, OffsetDateTime}; use time::{OffsetDateTime, macros::datetime};
use crate::history::History; use crate::history::History;
@ -392,7 +392,7 @@ mod test {
#[test] #[test]
fn key_encodings() { fn key_encodings() {
use super::{decode_key, encode_key, Key}; use super::{Key, decode_key, encode_key};
// a history of our key encodings. // a history of our key encodings.
// v11.0.0 xCAbWypb0msJ2Kq+8j4GVEWUlDX7deKnrTRSIopuqXxc5Q== // v11.0.0 xCAbWypb0msJ2Kq+8j4GVEWUlDX7deKnrTRSIopuqXxc5Q==

View File

@ -1,13 +1,13 @@
use core::fmt::Formatter; use core::fmt::Formatter;
use rmp::decode::ValueReadError; use rmp::decode::ValueReadError;
use rmp::{decode::Bytes, Marker}; use rmp::{Marker, decode::Bytes};
use std::env; use std::env;
use std::fmt::Display; use std::fmt::Display;
use atuin_common::record::DecryptedData; use atuin_common::record::DecryptedData;
use atuin_common::utils::uuid_v7; use atuin_common::utils::uuid_v7;
use eyre::{bail, eyre, Result}; use eyre::{Result, bail, eyre};
use crate::secrets::SECRET_PATTERNS_RE; use crate::secrets::SECRET_PATTERNS_RE;
use crate::settings::Settings; use crate::settings::Settings;

View File

@ -1,16 +1,16 @@
use std::{collections::HashSet, fmt::Write, time::Duration}; use std::{collections::HashSet, fmt::Write, time::Duration};
use eyre::{bail, eyre, Result}; use eyre::{Result, bail, eyre};
use indicatif::{ProgressBar, ProgressState, ProgressStyle}; use indicatif::{ProgressBar, ProgressState, ProgressStyle};
use rmp::decode::Bytes; use rmp::decode::Bytes;
use crate::{ use crate::{
database::{current_context, Database}, database::{Database, current_context},
record::{encryption::PASETO_V4, sqlite_store::SqliteStore, store::Store}, record::{encryption::PASETO_V4, sqlite_store::SqliteStore, store::Store},
}; };
use atuin_common::record::{DecryptedData, Host, HostId, Record, RecordId, RecordIdx}; use atuin_common::record::{DecryptedData, Host, HostId, Record, RecordId, RecordIdx};
use super::{History, HistoryId, HISTORY_TAG, HISTORY_VERSION}; use super::{HISTORY_TAG, HISTORY_VERSION, History, HistoryId};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct HistoryStore { pub struct HistoryStore {
@ -246,10 +246,11 @@ impl HistoryStore {
for id in ids { for id in ids {
let record = self.store.get(*id).await; let record = self.store.get(*id).await;
let record = if let Ok(record) = record { let record = match record {
record Ok(record) => record,
} else { _ => {
continue; continue;
}
}; };
if record.tag != HISTORY_TAG { if record.tag != HISTORY_TAG {
@ -342,7 +343,7 @@ mod tests {
use atuin_common::record::DecryptedData; use atuin_common::record::DecryptedData;
use time::macros::datetime; use time::macros::datetime;
use crate::history::{store::HistoryRecord, HISTORY_VERSION}; use crate::history::{HISTORY_VERSION, store::HistoryRecord};
use super::History; use super::History;

View File

@ -2,11 +2,11 @@ use std::{path::PathBuf, str};
use async_trait::async_trait; use async_trait::async_trait;
use directories::UserDirs; use directories::UserDirs;
use eyre::{eyre, Result}; use eyre::{Result, eyre};
use itertools::Itertools; use itertools::Itertools;
use time::{Duration, OffsetDateTime}; use time::{Duration, OffsetDateTime};
use super::{get_histpath, unix_byte_lines, Importer, Loader}; use super::{Importer, Loader, get_histpath, unix_byte_lines};
use crate::history::History; use crate::history::History;
use crate::import::read_to_end; use crate::import::read_to_end;
@ -73,7 +73,9 @@ impl Importer for Bash {
LineType::Empty => {} // do nothing LineType::Empty => {} // do nothing
LineType::Timestamp(t) => { LineType::Timestamp(t) => {
if t < next_timestamp { if t < next_timestamp {
warn!("Time reversal detected in Bash history! Commands may be ordered incorrectly."); warn!(
"Time reversal detected in Bash history! Commands may be ordered incorrectly."
);
} }
next_timestamp = t; next_timestamp = t;
} }
@ -126,9 +128,9 @@ fn try_parse_line_as_timestamp(line: &str) -> Option<OffsetDateTime> {
mod test { mod test {
use std::cmp::Ordering; use std::cmp::Ordering;
use itertools::{assert_equal, Itertools}; use itertools::{Itertools, assert_equal};
use crate::import::{tests::TestLoader, Importer}; use crate::import::{Importer, tests::TestLoader};
use super::Bash; use super::Bash;

View File

@ -5,10 +5,10 @@ use std::path::PathBuf;
use async_trait::async_trait; use async_trait::async_trait;
use directories::BaseDirs; use directories::BaseDirs;
use eyre::{eyre, Result}; use eyre::{Result, eyre};
use time::OffsetDateTime; use time::OffsetDateTime;
use super::{unix_byte_lines, Importer, Loader}; use super::{Importer, Loader, unix_byte_lines};
use crate::history::History; use crate::history::History;
use crate::import::read_to_end; use crate::import::read_to_end;
@ -110,7 +110,7 @@ impl Importer for Fish {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::import::{tests::TestLoader, Importer}; use crate::import::{Importer, tests::TestLoader};
use super::Fish; use super::Fish;
@ -160,7 +160,7 @@ ERROR
// simple wrapper for fish history entry // simple wrapper for fish history entry
macro_rules! fishtory { macro_rules! fishtory {
($timestamp:expr, $command:expr) => { ($timestamp:expr_2021, $command:expr_2021) => {
let h = history.next().expect("missing entry in history"); let h = history.next().expect("missing entry in history");
assert_eq!(h.command.as_str(), $command); assert_eq!(h.command.as_str(), $command);
assert_eq!(h.timestamp.unix_timestamp(), $timestamp); assert_eq!(h.timestamp.unix_timestamp(), $timestamp);

View File

@ -3,7 +3,7 @@ use std::io::Read;
use std::path::PathBuf; use std::path::PathBuf;
use async_trait::async_trait; use async_trait::async_trait;
use eyre::{bail, Result}; use eyre::{Result, bail};
use memchr::Memchr; use memchr::Memchr;
use crate::history::History; use crate::history::History;

View File

@ -5,10 +5,10 @@ use std::path::PathBuf;
use async_trait::async_trait; use async_trait::async_trait;
use directories::BaseDirs; use directories::BaseDirs;
use eyre::{eyre, Result}; use eyre::{Result, eyre};
use time::OffsetDateTime; use time::OffsetDateTime;
use super::{unix_byte_lines, Importer, Loader}; use super::{Importer, Loader, unix_byte_lines};
use crate::history::History; use crate::history::History;
use crate::import::read_to_end; use crate::import::read_to_end;

View File

@ -5,8 +5,8 @@ use std::path::PathBuf;
use async_trait::async_trait; use async_trait::async_trait;
use directories::BaseDirs; use directories::BaseDirs;
use eyre::{eyre, Result}; use eyre::{Result, eyre};
use sqlx::{sqlite::SqlitePool, Pool}; use sqlx::{Pool, sqlite::SqlitePool};
use time::{Duration, OffsetDateTime}; use time::{Duration, OffsetDateTime};
use super::Importer; use super::Importer;

View File

@ -2,10 +2,10 @@ use std::{path::PathBuf, str};
use async_trait::async_trait; use async_trait::async_trait;
use directories::UserDirs; use directories::UserDirs;
use eyre::{eyre, Result}; use eyre::{Result, eyre};
use time::{macros::format_description, OffsetDateTime, PrimitiveDateTime}; use time::{OffsetDateTime, PrimitiveDateTime, macros::format_description};
use super::{get_histpath, unix_byte_lines, Importer, Loader}; use super::{Importer, Loader, get_histpath, unix_byte_lines};
use crate::history::History; use crate::history::History;
use crate::import::read_to_end; use crate::import::read_to_end;
@ -72,7 +72,7 @@ fn try_parse_line_as_timestamp(line: &str) -> Option<OffsetDateTime> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::import::{tests::TestLoader, Importer}; use crate::import::{Importer, tests::TestLoader};
use super::Replxx; use super::Replxx;
@ -100,7 +100,7 @@ CREATE TABLE test( stamp DateTime('UTC'))ENGINE = MergeTreePARTITION BY toDat
// simple wrapper for replxx history entry // simple wrapper for replxx history entry
macro_rules! history { macro_rules! history {
($timestamp:expr, $command:expr) => { ($timestamp:expr_2021, $command:expr_2021) => {
let h = history.next().expect("missing entry in history"); let h = history.next().expect("missing entry in history");
assert_eq!(h.command.as_str(), $command); assert_eq!(h.command.as_str(), $command);
assert_eq!(h.timestamp.unix_timestamp(), $timestamp); assert_eq!(h.timestamp.unix_timestamp(), $timestamp);
@ -114,6 +114,9 @@ CREATE TABLE test( stamp DateTime('UTC'))ENGINE = MergeTreePARTITION BY toDat
history!(1707603396, "select * from numbers(10)"); history!(1707603396, "select * from numbers(10)");
history!(1707603401, "select * from system.numbers"); history!(1707603401, "select * from system.numbers");
history!(1707603568, "select 1"); history!(1707603568, "select 1");
history!(1708600533, "CREATE TABLE test\n( stamp DateTime('UTC'))\nENGINE = MergeTree\nPARTITION BY toDate(stamp)\norder by tuple() as select toDateTime('2020-01-01')+number*60 from numbers(80000);"); history!(
1708600533,
"CREATE TABLE test\n( stamp DateTime('UTC'))\nENGINE = MergeTree\nPARTITION BY toDate(stamp)\norder by tuple() as select toDateTime('2020-01-01')+number*60 from numbers(80000);"
);
} }
} }

View File

@ -2,13 +2,13 @@ use std::path::PathBuf;
use async_trait::async_trait; use async_trait::async_trait;
use directories::UserDirs; use directories::UserDirs;
use eyre::{eyre, Result}; use eyre::{Result, eyre};
use serde::Deserialize; use serde::Deserialize;
use atuin_common::utils::uuid_v7; use atuin_common::utils::uuid_v7;
use time::OffsetDateTime; use time::OffsetDateTime;
use super::{get_histpath, unix_byte_lines, Importer, Loader}; use super::{Importer, Loader, get_histpath, unix_byte_lines};
use crate::history::History; use crate::history::History;
use crate::import::read_to_end; use crate::import::read_to_end;

View File

@ -4,13 +4,13 @@ use std::path::{Path, PathBuf};
use async_trait::async_trait; use async_trait::async_trait;
use directories::BaseDirs; use directories::BaseDirs;
use eyre::{eyre, Result}; use eyre::{Result, eyre};
use serde::Deserialize; use serde::Deserialize;
use time::OffsetDateTime; use time::OffsetDateTime;
use uuid::timestamp::{context::NoContext, Timestamp};
use uuid::Uuid; use uuid::Uuid;
use uuid::timestamp::{Timestamp, context::NoContext};
use super::{get_histpath, Importer, Loader}; use super::{Importer, Loader, get_histpath};
use crate::history::History; use crate::history::History;
use crate::utils::get_host_user; use crate::utils::get_host_user;

View File

@ -3,14 +3,14 @@ use std::path::PathBuf;
use async_trait::async_trait; use async_trait::async_trait;
use directories::BaseDirs; use directories::BaseDirs;
use eyre::{eyre, Result}; use eyre::{Result, eyre};
use futures::TryStreamExt; use futures::TryStreamExt;
use sqlx::{sqlite::SqlitePool, FromRow, Row}; use sqlx::{FromRow, Row, sqlite::SqlitePool};
use time::OffsetDateTime; use time::OffsetDateTime;
use uuid::timestamp::{context::NoContext, Timestamp};
use uuid::Uuid; use uuid::Uuid;
use uuid::timestamp::{Timestamp, context::NoContext};
use super::{get_histpath, Importer, Loader}; use super::{Importer, Loader, get_histpath};
use crate::history::History; use crate::history::History;
use crate::utils::get_host_user; use crate::utils::get_host_user;

View File

@ -6,10 +6,10 @@ use std::path::PathBuf;
use async_trait::async_trait; use async_trait::async_trait;
use directories::UserDirs; use directories::UserDirs;
use eyre::{eyre, Result}; use eyre::{Result, eyre};
use time::OffsetDateTime; use time::OffsetDateTime;
use super::{get_histpath, unix_byte_lines, Importer, Loader}; use super::{Importer, Loader, get_histpath, unix_byte_lines};
use crate::history::History; use crate::history::History;
use crate::import::read_to_end; use crate::import::read_to_end;
@ -38,7 +38,7 @@ fn default_histpath() -> Result<PathBuf> {
None => { None => {
break Err(eyre!( break Err(eyre!(
"Could not find history file. Try setting and exporting $HISTFILE" "Could not find history file. Try setting and exporting $HISTFILE"
)) ));
} }
} }
} }

View File

@ -38,8 +38,8 @@ use std::path::{Path, PathBuf};
use async_trait::async_trait; use async_trait::async_trait;
use atuin_common::utils::uuid_v7; use atuin_common::utils::uuid_v7;
use directories::UserDirs; use directories::UserDirs;
use eyre::{eyre, Result}; use eyre::{Result, eyre};
use sqlx::{sqlite::SqlitePool, Pool}; use sqlx::{Pool, sqlite::SqlitePool};
use time::PrimitiveDateTime; use time::PrimitiveDateTime;
use super::Importer; use super::Importer;
@ -178,10 +178,12 @@ mod test {
use sqlx::sqlite::SqlitePoolOptions; use sqlx::sqlite::SqlitePoolOptions;
use std::env; use std::env;
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
#[allow(unsafe_code)]
async fn test_env_vars() { async fn test_env_vars() {
let test_env_db = "nonstd-zsh-history.db"; let test_env_db = "nonstd-zsh-history.db";
let key = "HISTDB_FILE"; let key = "HISTDB_FILE";
env::set_var(key, test_env_db); // TODO: Audit that the environment access only happens in single-threaded code.
unsafe { env::set_var(key, test_env_db) };
// test the env got set // test the env got set
assert_eq!(env::var(key).unwrap(), test_env_db.to_string()); assert_eq!(env::var(key).unwrap(), test_env_db.to_string());

View File

@ -1,7 +1,7 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use atuin_common::record::{DecryptedData, Host, HostId}; use atuin_common::record::{DecryptedData, Host, HostId};
use eyre::{bail, ensure, eyre, Result}; use eyre::{Result, bail, ensure, eyre};
use serde::Deserialize; use serde::Deserialize;
use crate::record::encryption::PASETO_V4; use crate::record::encryption::PASETO_V4;
@ -200,7 +200,7 @@ mod tests {
use crate::record::sqlite_store::SqliteStore; use crate::record::sqlite_store::SqliteStore;
use crate::settings::test_local_timeout; use crate::settings::test_local_timeout;
use super::{KvRecord, KvStore, KV_VERSION}; use super::{KV_VERSION, KvRecord, KvStore};
#[test] #[test]
fn encode_decode() { fn encode_decode() {

View File

@ -1,4 +1,4 @@
#![forbid(unsafe_code)] #![deny(unsafe_code)]
#[macro_use] #[macro_use]
extern crate log; extern crate log;

View File

@ -1,13 +1,13 @@
use std::path::PathBuf; use std::path::PathBuf;
use atuin_common::api::LoginRequest; use atuin_common::api::LoginRequest;
use eyre::{bail, Context, Result}; use eyre::{Context, Result, bail};
use tokio::fs::File; use tokio::fs::File;
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use crate::{ use crate::{
api_client, api_client,
encryption::{decode_key, encode_key, load_key, Key}, encryption::{Key, decode_key, encode_key, load_key},
record::{sqlite_store::SqliteStore, store::Store}, record::{sqlite_store::SqliteStore, store::Store},
settings::Settings, settings::Settings,
}; };
@ -23,22 +23,25 @@ pub async fn login(
let key = match bip39::Mnemonic::from_phrase(&key, bip39::Language::English) { let key = match bip39::Mnemonic::from_phrase(&key, bip39::Language::English) {
Ok(mnemonic) => encode_key(Key::from_slice(mnemonic.entropy()))?, Ok(mnemonic) => encode_key(Key::from_slice(mnemonic.entropy()))?,
Err(err) => { Err(err) => {
if let Some(err) = err.downcast_ref::<bip39::ErrorKind>() { match err.downcast_ref::<bip39::ErrorKind>() {
match err { Some(err) => {
// assume they copied in the base64 key match err {
bip39::ErrorKind::InvalidWord => key, // assume they copied in the base64 key
bip39::ErrorKind::InvalidChecksum => { bip39::ErrorKind::InvalidWord => key,
bail!("key mnemonic was not valid") bip39::ErrorKind::InvalidChecksum => {
} bail!("key mnemonic was not valid")
bip39::ErrorKind::InvalidKeysize(_) }
| bip39::ErrorKind::InvalidWordLength(_) bip39::ErrorKind::InvalidKeysize(_)
| bip39::ErrorKind::InvalidEntropyLength(_, _) => { | bip39::ErrorKind::InvalidWordLength(_)
bail!("key was not the correct length") | bip39::ErrorKind::InvalidEntropyLength(_, _) => {
bail!("key was not the correct length")
}
} }
} }
} else { _ => {
// unknown error. assume they copied the base64 key // unknown error. assume they copied the base64 key
key key
}
} }
} }
}; };

View File

@ -1,8 +1,8 @@
use atuin_common::record::{ use atuin_common::record::{
AdditionalData, DecryptedData, EncryptedData, Encryption, HostId, RecordId, RecordIdx, AdditionalData, DecryptedData, EncryptedData, Encryption, HostId, RecordId, RecordIdx,
}; };
use base64::{engine::general_purpose, Engine}; use base64::{Engine, engine::general_purpose};
use eyre::{ensure, Context, Result}; use eyre::{Context, Result, ensure};
use rusty_paserk::{Key, KeyId, Local, PieWrappedKey}; use rusty_paserk::{Key, KeyId, Local, PieWrappedKey};
use rusty_paseto::core::{ use rusty_paseto::core::{
ImplicitAssertion, Key as DataKey, Local as LocalPurpose, Paseto, PasetoNonce, Payload, V4, ImplicitAssertion, Key as DataKey, Local as LocalPurpose, Paseto, PasetoNonce, Payload, V4,

View File

@ -6,12 +6,12 @@ use std::str::FromStr;
use std::{path::Path, time::Duration}; use std::{path::Path, time::Duration};
use async_trait::async_trait; use async_trait::async_trait;
use eyre::{eyre, Result}; use eyre::{Result, eyre};
use fs_err as fs; use fs_err as fs;
use sqlx::{ use sqlx::{
sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePool, SqlitePoolOptions, SqliteRow},
Row, Row,
sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePool, SqlitePoolOptions, SqliteRow},
}; };
use atuin_common::record::{ use atuin_common::record::{
@ -35,7 +35,9 @@ impl SqliteStore {
debug!("opening sqlite database at {:?}", path); debug!("opening sqlite database at {:?}", path);
if utils::broken_symlink(path) { if utils::broken_symlink(path) {
eprintln!("Atuin: Sqlite db path ({path:?}) is a broken symlink. Unable to read or create replacement."); eprintln!(
"Atuin: Sqlite db path ({path:?}) is a broken symlink. Unable to read or create replacement."
);
std::process::exit(1); std::process::exit(1);
} }

View File

@ -133,7 +133,7 @@ pub async fn operations(
msg: String::from( msg: String::from(
"diff has nothing for local or remote - (host, tag) does not exist", "diff has nothing for local or remote - (host, tag) does not exist",
), ),
}) });
} }
}; };

View File

@ -38,7 +38,9 @@ pub static SECRET_PATTERNS: &[(&str, &str, TestValue)] = &[
( (
"Atuin login", "Atuin login",
r"atuin\s+login", r"atuin\s+login",
TestValue::Single("atuin login -u mycoolusername -p mycoolpassword -k \"lots of random words\""), TestValue::Single(
"atuin login -u mycoolusername -p mycoolpassword -k \"lots of random words\"",
),
), ),
( (
"GitHub PAT (old)", "GitHub PAT (old)",
@ -51,32 +53,34 @@ pub static SECRET_PATTERNS: &[(&str, &str, TestValue)] = &[
TestValue::Multiple(&[ TestValue::Multiple(&[
"gh1_1234567890abcdefghijk_1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklm", "gh1_1234567890abcdefghijk_1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklm",
"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", // also legit, also expired "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", // also legit, also expired
]) ]),
), ),
( (
"GitHub OAuth Access Token", "GitHub OAuth Access Token",
"gho_[A-Za-z0-9]{36}", "gho_[A-Za-z0-9]{36}",
TestValue::Single("gho_1234567890abcdefghijklmnopqrstuvwx000"), // not a real token TestValue::Single("gho_1234567890abcdefghijklmnopqrstuvwx000"), // not a real token
), ),
( (
"GitHub OAuth Access Token (user)", "GitHub OAuth Access Token (user)",
"ghu_[A-Za-z0-9]{36}", "ghu_[A-Za-z0-9]{36}",
TestValue::Single("ghu_1234567890abcdefghijklmnopqrstuvwx000"), // not a real token TestValue::Single("ghu_1234567890abcdefghijklmnopqrstuvwx000"), // not a real token
), ),
( (
"GitHub App Installation Access Token", "GitHub App Installation Access Token",
"ghs_[A-Za-z0-9]{36}", "ghs_[A-Za-z0-9]{36}",
TestValue::Single("ghs_1234567890abcdefghijklmnopqrstuvwx000"), // not a real token TestValue::Single("ghs_1234567890abcdefghijklmnopqrstuvwx000"), // not a real token
), ),
( (
"GitHub Refresh Token", "GitHub Refresh Token",
"ghr_[A-Za-z0-9]{76}", "ghr_[A-Za-z0-9]{76}",
TestValue::Single("ghr_1234567890abcdefghijklmnopqrstuvwx1234567890abcdefghijklmnopqrstuvwx1234567890abcdefghijklmnopqrstuvwx"), // not a real token TestValue::Single(
"ghr_1234567890abcdefghijklmnopqrstuvwx1234567890abcdefghijklmnopqrstuvwx1234567890abcdefghijklmnopqrstuvwx",
), // not a real token
), ),
( (
"GitHub App Installation Access Token v1", "GitHub App Installation Access Token v1",
"v1\\.[0-9A-Fa-f]{40}", "v1\\.[0-9A-Fa-f]{40}",
TestValue::Single("v1.1234567890abcdef1234567890abcdef12345678"), // not a real token TestValue::Single("v1.1234567890abcdef1234567890abcdef12345678"), // not a real token
), ),
( (
"GitLab PAT", "GitLab PAT",
@ -96,10 +100,20 @@ pub static SECRET_PATTERNS: &[(&str, &str, TestValue)] = &[
( (
"Slack webhook", "Slack webhook",
"T[a-zA-Z0-9_]{8}/B[a-zA-Z0-9_]{8}/[a-zA-Z0-9_]{24}", "T[a-zA-Z0-9_]{8}/B[a-zA-Z0-9_]{8}/[a-zA-Z0-9_]{24}",
TestValue::Single("https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"), TestValue::Single(
"https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX",
),
),
(
"Stripe test key",
"sk_test_[0-9a-zA-Z]{24}",
TestValue::Single("sk_test_1234567890abcdefghijklmnop"),
),
(
"Stripe live key",
"sk_live_[0-9a-zA-Z]{24}",
TestValue::Single("sk_live_1234567890abcdefghijklmnop"),
), ),
("Stripe test key", "sk_test_[0-9a-zA-Z]{24}", TestValue::Single("sk_test_1234567890abcdefghijklmnop")),
("Stripe live key", "sk_live_[0-9a-zA-Z]{24}", TestValue::Single("sk_live_1234567890abcdefghijklmnop")),
( (
"Netlify authentication token", "Netlify authentication token",
"nf[pcoub]_[0-9a-zA-Z]{36}", "nf[pcoub]_[0-9a-zA-Z]{36}",
@ -127,7 +141,7 @@ pub static SECRET_PATTERNS_RE: LazyLock<RegexSet> = LazyLock::new(|| {
mod tests { mod tests {
use regex::Regex; use regex::Regex;
use crate::secrets::{TestValue, SECRET_PATTERNS}; use crate::secrets::{SECRET_PATTERNS, TestValue};
#[test] #[test]
fn test_secrets() { fn test_secrets() {

View File

@ -6,19 +6,19 @@ use atuin_common::record::HostId;
use atuin_common::utils; use atuin_common::utils;
use clap::ValueEnum; use clap::ValueEnum;
use config::{ use config::{
builder::DefaultState, Config, ConfigBuilder, Environment, File as ConfigFile, FileFormat, Config, ConfigBuilder, Environment, File as ConfigFile, FileFormat, builder::DefaultState,
}; };
use eyre::{bail, eyre, Context, Error, Result}; use eyre::{Context, Error, Result, bail, eyre};
use fs_err::{create_dir_all, File}; use fs_err::{File, create_dir_all};
use humantime::parse_duration; use humantime::parse_duration;
use regex::RegexSet; use regex::RegexSet;
use semver::Version; use semver::Version;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_with::DeserializeFromStr; use serde_with::DeserializeFromStr;
use time::{ use time::{
format_description::{well_known::Rfc3339, FormatItem},
macros::format_description,
OffsetDateTime, UtcOffset, OffsetDateTime, UtcOffset,
format_description::{FormatItem, well_known::Rfc3339},
macros::format_description,
}; };
use uuid::Uuid; use uuid::Uuid;
@ -141,8 +141,9 @@ impl fmt::Display for Timezone {
} }
} }
/// format: <+|-><hour>[:<minute>[:<second>]] /// format: <+|-><hour>[:<minute>[:<second>]]
static OFFSET_FMT: &[FormatItem<'_>] = static OFFSET_FMT: &[FormatItem<'_>] = format_description!(
format_description!("[offset_hour sign:mandatory padding:none][optional [:[offset_minute padding:none][optional [:[offset_second padding:none]]]]]"); "[offset_hour sign:mandatory padding:none][optional [:[offset_minute padding:none][optional [:[offset_second padding:none]]]]]"
);
impl FromStr for Timezone { impl FromStr for Timezone {
type Err = Error; type Err = Error;

View File

@ -109,13 +109,16 @@ async fn sync_download(
for i in remote_status.deleted { for i in remote_status.deleted {
// we will update the stored history to have this data // we will update the stored history to have this data
// pretty much everything can be nullified // pretty much everything can be nullified
if let Some(h) = db.load(i.as_str()).await? { match db.load(i.as_str()).await? {
db.delete(h).await?; Some(h) => {
} else { db.delete(h).await?;
info!( }
"could not delete history with id {}, not found locally", _ => {
i.as_str() info!(
); "could not delete history with id {}, not found locally",
i.as_str()
);
}
} }
} }

View File

@ -416,7 +416,7 @@ impl ThemeManager {
"set theme debug on for more info".to_string() "set theme debug on for more info".to_string()
} }
), ),
))) )));
} }
}; };
let colors: HashMap<Meaning, String> = theme_config.colors; let colors: HashMap<Meaning, String> = theme_config.colors;
@ -697,7 +697,8 @@ mod theme_tests {
testing_logger::validate(|captured_logs| { testing_logger::validate(|captured_logs| {
assert_eq!(captured_logs.len(), 1); assert_eq!(captured_logs.len(), 1);
assert_eq!(captured_logs[0].body, assert_eq!(
captured_logs[0].body,
"Could not load theme nonsolarized: Empty theme directory override and could not find theme elsewhere" "Could not load theme nonsolarized: Empty theme directory override and could not find theme elsewhere"
); );
assert_eq!(captured_logs[0].level, log::Level::Warn) assert_eq!(captured_logs[0].level, log::Level::Warn)

View File

@ -1,6 +1,6 @@
[package] [package]
name = "atuin-common" name = "atuin-common"
edition = "2021" edition = "2024"
description = "common library for atuin" description = "common library for atuin"
rust-version = { workspace = true } rust-version = { workspace = true }

View File

@ -1,4 +1,4 @@
#![forbid(unsafe_code)] #![deny(unsafe_code)]
/// Defines a new UUID type wrapper /// Defines a new UUID type wrapper
macro_rules! new_uuid { macro_rules! new_uuid {

View File

@ -1,7 +1,7 @@
use std::{ffi::OsStr, path::Path, process::Command}; use std::{ffi::OsStr, path::Path, process::Command};
use serde::Serialize; use serde::Serialize;
use sysinfo::{get_current_pid, Process, System}; use sysinfo::{Process, System, get_current_pid};
use thiserror::Error; use thiserror::Error;
#[derive(PartialEq)] #[derive(PartialEq)]

View File

@ -2,9 +2,9 @@ use std::borrow::Cow;
use std::env; use std::env;
use std::path::PathBuf; use std::path::PathBuf;
use eyre::{eyre, Result}; use eyre::{Result, eyre};
use base64::prelude::{Engine, BASE64_URL_SAFE_NO_PAD}; use base64::prelude::{BASE64_URL_SAFE_NO_PAD, Engine};
use getrandom::getrandom; use getrandom::getrandom;
use uuid::Uuid; use uuid::Uuid;
@ -194,6 +194,7 @@ pub fn unquote(s: &str) -> Result<String> {
impl<T: AsRef<str>> Escapable for T {} impl<T: AsRef<str>> Escapable for T {}
#[allow(unsafe_code)]
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_ne; use pretty_assertions::assert_ne;
@ -214,36 +215,48 @@ mod tests {
} }
fn test_config_dir_xdg() { fn test_config_dir_xdg() {
env::remove_var("HOME"); // TODO: Audit that the environment access only happens in single-threaded code.
env::set_var("XDG_CONFIG_HOME", "/home/user/custom_config"); unsafe { env::remove_var("HOME") };
// TODO: Audit that the environment access only happens in single-threaded code.
unsafe { env::set_var("XDG_CONFIG_HOME", "/home/user/custom_config") };
assert_eq!( assert_eq!(
config_dir(), config_dir(),
PathBuf::from("/home/user/custom_config/atuin") PathBuf::from("/home/user/custom_config/atuin")
); );
env::remove_var("XDG_CONFIG_HOME"); // TODO: Audit that the environment access only happens in single-threaded code.
unsafe { env::remove_var("XDG_CONFIG_HOME") };
} }
fn test_config_dir() { fn test_config_dir() {
env::set_var("HOME", "/home/user"); // TODO: Audit that the environment access only happens in single-threaded code.
env::remove_var("XDG_CONFIG_HOME"); unsafe { env::set_var("HOME", "/home/user") };
// TODO: Audit that the environment access only happens in single-threaded code.
unsafe { env::remove_var("XDG_CONFIG_HOME") };
assert_eq!(config_dir(), PathBuf::from("/home/user/.config/atuin")); assert_eq!(config_dir(), PathBuf::from("/home/user/.config/atuin"));
env::remove_var("HOME"); // TODO: Audit that the environment access only happens in single-threaded code.
unsafe { env::remove_var("HOME") };
} }
fn test_data_dir_xdg() { fn test_data_dir_xdg() {
env::remove_var("HOME"); // TODO: Audit that the environment access only happens in single-threaded code.
env::set_var("XDG_DATA_HOME", "/home/user/custom_data"); unsafe { env::remove_var("HOME") };
// TODO: Audit that the environment access only happens in single-threaded code.
unsafe { env::set_var("XDG_DATA_HOME", "/home/user/custom_data") };
assert_eq!(data_dir(), PathBuf::from("/home/user/custom_data/atuin")); assert_eq!(data_dir(), PathBuf::from("/home/user/custom_data/atuin"));
env::remove_var("XDG_DATA_HOME"); // TODO: Audit that the environment access only happens in single-threaded code.
unsafe { env::remove_var("XDG_DATA_HOME") };
} }
fn test_data_dir() { fn test_data_dir() {
env::set_var("HOME", "/home/user"); // TODO: Audit that the environment access only happens in single-threaded code.
env::remove_var("XDG_DATA_HOME"); unsafe { env::set_var("HOME", "/home/user") };
// TODO: Audit that the environment access only happens in single-threaded code.
unsafe { env::remove_var("XDG_DATA_HOME") };
assert_eq!(data_dir(), PathBuf::from("/home/user/.local/share/atuin")); assert_eq!(data_dir(), PathBuf::from("/home/user/.local/share/atuin"));
env::remove_var("HOME"); // TODO: Audit that the environment access only happens in single-threaded code.
unsafe { env::remove_var("HOME") };
} }
#[test] #[test]

View File

@ -1,6 +1,6 @@
[package] [package]
name = "atuin-daemon" name = "atuin-daemon"
edition = "2021" edition = "2024"
version = { workspace = true } version = { workspace = true }
description = "The daemon crate for Atuin" description = "The daemon crate for Atuin"

View File

@ -12,7 +12,7 @@ use tokio::net::UnixStream;
use atuin_client::history::History; use atuin_client::history::History;
use crate::history::{ use crate::history::{
history_client::HistoryClient as HistoryServiceClient, EndHistoryRequest, StartHistoryRequest, EndHistoryRequest, StartHistoryRequest, history_client::HistoryClient as HistoryServiceClient,
}; };
pub struct HistoryClient { pub struct HistoryClient {

View File

@ -7,13 +7,13 @@ use atuin_client::settings::Settings;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use time::OffsetDateTime; use time::OffsetDateTime;
use tracing::{instrument, Level}; use tracing::{Level, instrument};
use atuin_client::database::{Database, Sqlite as HistoryDatabase}; use atuin_client::database::{Database, Sqlite as HistoryDatabase};
use atuin_client::history::{History, HistoryId}; use atuin_client::history::{History, HistoryId};
use dashmap::DashMap; use dashmap::DashMap;
use eyre::Result; use eyre::Result;
use tonic::{transport::Server, Request, Response, Status}; use tonic::{Request, Response, Status, transport::Server};
use crate::history::history_server::{History as HistorySvc, HistoryServer}; use crate::history::history_server::{History as HistorySvc, HistoryServer};
@ -194,7 +194,9 @@ async fn start_server(settings: Settings, history: HistoryService) -> Result<()>
} }
} }
Err(err) => { Err(err) => {
tracing::warn!("could not detect systemd socket path, ensure that it's at the configured path: {socket_path:?}, error: {err:?}"); tracing::warn!(
"could not detect systemd socket path, ensure that it's at the configured path: {socket_path:?}, error: {err:?}"
);
} }
} }
(UnixListener::from_std(listener)?, false) (UnixListener::from_std(listener)?, false)

View File

@ -10,7 +10,7 @@ use atuin_client::{
settings::Settings, settings::Settings,
}; };
use atuin_dotfiles::store::{var::VarStore, AliasStore}; use atuin_dotfiles::store::{AliasStore, var::VarStore};
pub async fn worker( pub async fn worker(
settings: Settings, settings: Settings,

View File

@ -1,7 +1,7 @@
[package] [package]
name = "atuin-dotfiles" name = "atuin-dotfiles"
description = "The dotfiles crate for Atuin" description = "The dotfiles crate for Atuin"
edition = "2021" edition = "2024"
version = { workspace = true } version = { workspace = true }
authors.workspace = true authors.workspace = true

View File

@ -1,4 +1,4 @@
use eyre::{ensure, eyre, Result}; use eyre::{Result, ensure, eyre};
use rmp::{decode, encode}; use rmp::{decode, encode};
use serde::Serialize; use serde::Serialize;
@ -158,7 +158,7 @@ pub async fn import_aliases(store: &AliasStore) -> Result<Vec<Alias>> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::shell::{parse_alias, Alias}; use crate::shell::{Alias, parse_alias};
#[test] #[test]
fn test_parse_simple_alias() { fn test_parse_simple_alias() {
@ -177,7 +177,10 @@ mod tests {
let git_alias = super::parse_alias("gwip='git add -A; git rm $(git ls-files --deleted) 2> /dev/null; git commit --no-verify --no-gpg-sign --message \"--wip-- [skip ci]\"'").expect("failed to parse alias"); let git_alias = super::parse_alias("gwip='git add -A; git rm $(git ls-files --deleted) 2> /dev/null; git commit --no-verify --no-gpg-sign --message \"--wip-- [skip ci]\"'").expect("failed to parse alias");
assert_eq!(git_alias.name, "gwip"); assert_eq!(git_alias.name, "gwip");
assert_eq!(git_alias.value, "'git add -A; git rm $(git ls-files --deleted) 2> /dev/null; git commit --no-verify --no-gpg-sign --message \"--wip-- [skip ci]\"'"); assert_eq!(
git_alias.value,
"'git add -A; git rm $(git ls-files --deleted) 2> /dev/null; git commit --no-verify --no-gpg-sign --message \"--wip-- [skip ci]\"'"
);
} }
#[test] #[test]

View File

@ -1,6 +1,6 @@
use std::path::PathBuf; use std::path::PathBuf;
use crate::store::{var::VarStore, AliasStore}; use crate::store::{AliasStore, var::VarStore};
async fn cached_aliases(path: PathBuf, store: &AliasStore) -> String { async fn cached_aliases(path: PathBuf, store: &AliasStore) -> String {
match tokio::fs::read_to_string(path).await { match tokio::fs::read_to_string(path).await {

View File

@ -1,7 +1,7 @@
// Configuration for fish // Configuration for fish
use std::path::PathBuf; use std::path::PathBuf;
use crate::store::{var::VarStore, AliasStore}; use crate::store::{AliasStore, var::VarStore};
async fn cached_aliases(path: PathBuf, store: &AliasStore) -> String { async fn cached_aliases(path: PathBuf, store: &AliasStore) -> String {
match tokio::fs::read_to_string(path).await { match tokio::fs::read_to_string(path).await {

View File

@ -1,6 +1,6 @@
use std::path::PathBuf; use std::path::PathBuf;
use crate::store::{var::VarStore, AliasStore}; use crate::store::{AliasStore, var::VarStore};
async fn cached_aliases(path: PathBuf, store: &AliasStore) -> String { async fn cached_aliases(path: PathBuf, store: &AliasStore) -> String {
match tokio::fs::read_to_string(path).await { match tokio::fs::read_to_string(path).await {

View File

@ -1,6 +1,6 @@
use std::path::PathBuf; use std::path::PathBuf;
use crate::store::{var::VarStore, AliasStore}; use crate::store::{AliasStore, var::VarStore};
async fn cached_aliases(path: PathBuf, store: &AliasStore) -> String { async fn cached_aliases(path: PathBuf, store: &AliasStore) -> String {
match tokio::fs::read_to_string(path).await { match tokio::fs::read_to_string(path).await {

View File

@ -7,7 +7,7 @@ use atuin_client::record::sqlite_store::SqliteStore;
// + stores, rather than one mega config store. // + stores, rather than one mega config store.
use atuin_common::record::{DecryptedData, Host, HostId}; use atuin_common::record::{DecryptedData, Host, HostId};
use atuin_common::utils::unquote; use atuin_common::utils::unquote;
use eyre::{bail, ensure, eyre, Result}; use eyre::{Result, bail, ensure, eyre};
use atuin_client::record::encryption::PASETO_V4; use atuin_client::record::encryption::PASETO_V4;
use atuin_client::record::store::Store; use atuin_client::record::store::Store;
@ -315,7 +315,7 @@ mod tests {
use crate::shell::Alias; use crate::shell::Alias;
use super::{test_local_timeout, AliasRecord, AliasStore, CONFIG_SHELL_ALIAS_VERSION}; use super::{AliasRecord, AliasStore, CONFIG_SHELL_ALIAS_VERSION, test_local_timeout};
use crypto_secretbox::{KeyInit, XSalsa20Poly1305}; use crypto_secretbox::{KeyInit, XSalsa20Poly1305};
#[test] #[test]

View File

@ -6,7 +6,7 @@ use std::collections::BTreeMap;
use atuin_client::record::sqlite_store::SqliteStore; use atuin_client::record::sqlite_store::SqliteStore;
use atuin_common::record::{DecryptedData, Host, HostId}; use atuin_common::record::{DecryptedData, Host, HostId};
use eyre::{bail, ensure, eyre, Result}; use eyre::{Result, bail, ensure, eyre};
use atuin_client::record::encryption::PASETO_V4; use atuin_client::record::encryption::PASETO_V4;
use atuin_client::record::store::Store; use atuin_client::record::store::Store;
@ -294,7 +294,7 @@ mod tests {
use crate::{shell::Var, store::test_local_timeout}; use crate::{shell::Var, store::test_local_timeout};
use super::{VarRecord, VarStore, DOTFILES_VAR_VERSION}; use super::{DOTFILES_VAR_VERSION, VarRecord, VarStore};
use crypto_secretbox::{KeyInit, XSalsa20Poly1305}; use crypto_secretbox::{KeyInit, XSalsa20Poly1305};
#[test] #[test]

View File

@ -1,7 +1,7 @@
[package] [package]
name = "atuin-history" name = "atuin-history"
description = "The history crate for Atuin" description = "The history crate for Atuin"
edition = "2021" edition = "2024"
version = { workspace = true } version = { workspace = true }
authors.workspace = true authors.workspace = true

View File

@ -178,7 +178,9 @@ pub fn pretty_print(stats: Stats, ngram_size: usize, theme: &Theme) {
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(" | "); .join(" | ");
println!("{ResetColor}] {gray}{count:num_pad$}{ResetColor} {bold}{formatted_command}{ResetColor}"); println!(
"{ResetColor}] {gray}{count:num_pad$}{ResetColor} {bold}{formatted_command}{ResetColor}"
);
} }
println!("Total commands: {}", stats.total_commands); println!("Total commands: {}", stats.total_commands);
println!("Unique commands: {}", stats.unique_commands); println!("Unique commands: {}", stats.unique_commands);

View File

@ -1,6 +1,6 @@
[package] [package]
name = "atuin-server-database" name = "atuin-server-database"
edition = "2021" edition = "2024"
description = "server database library for atuin" description = "server database library for atuin"
version = { workspace = true } version = { workspace = true }

View File

@ -15,7 +15,7 @@ use self::{
}; };
use async_trait::async_trait; use async_trait::async_trait;
use atuin_common::record::{EncryptedData, HostId, Record, RecordIdx, RecordStatus}; use atuin_common::record::{EncryptedData, HostId, Record, RecordIdx, RecordStatus};
use serde::{de::DeserializeOwned, Serialize}; use serde::{Serialize, de::DeserializeOwned};
use time::{Date, Duration, Month, OffsetDateTime, Time, UtcOffset}; use time::{Date, Duration, Month, OffsetDateTime, Time, UtcOffset};
use tracing::instrument; use tracing::instrument;
@ -83,7 +83,7 @@ pub trait Database: Sized + Clone + Send + Sync + 'static {
async fn status(&self, user: &User) -> DbResult<RecordStatus>; async fn status(&self, user: &User) -> DbResult<RecordStatus>;
async fn count_history_range(&self, user: &User, range: Range<OffsetDateTime>) async fn count_history_range(&self, user: &User, range: Range<OffsetDateTime>)
-> DbResult<i64>; -> DbResult<i64>;
async fn list_history( async fn list_history(
&self, &self,

View File

@ -1,6 +1,6 @@
[package] [package]
name = "atuin-server-postgres" name = "atuin-server-postgres"
edition = "2021" edition = "2024"
description = "server postgres database library for atuin" description = "server postgres database library for atuin"
version = { workspace = true } version = { workspace = true }

View File

@ -10,8 +10,8 @@ use atuin_server_database::{Database, DbError, DbResult};
use futures_util::TryStreamExt; use futures_util::TryStreamExt;
use metrics::counter; use metrics::counter;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::postgres::PgPoolOptions;
use sqlx::Row; use sqlx::Row;
use sqlx::postgres::PgPoolOptions;
use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset}; use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset};
use tracing::{instrument, trace}; use tracing::{instrument, trace};

View File

@ -1,7 +1,7 @@
use ::sqlx::{FromRow, Result}; use ::sqlx::{FromRow, Result};
use atuin_common::record::{EncryptedData, Host, Record}; use atuin_common::record::{EncryptedData, Host, Record};
use atuin_server_database::models::{History, Session, User}; use atuin_server_database::models::{History, Session, User};
use sqlx::{postgres::PgRow, Row}; use sqlx::{Row, postgres::PgRow};
use time::PrimitiveDateTime; use time::PrimitiveDateTime;
pub struct DbUser(pub User); pub struct DbUser(pub User);

View File

@ -1,6 +1,6 @@
[package] [package]
name = "atuin-server" name = "atuin-server"
edition = "2021" edition = "2024"
description = "server library for atuin" description = "server library for atuin"
rust-version = { workspace = true } rust-version = { workspace = true }

View File

@ -1,4 +1,4 @@
use axum::{http, response::IntoResponse, Json}; use axum::{Json, http, response::IntoResponse};
use serde::Serialize; use serde::Serialize;

View File

@ -1,9 +1,9 @@
use std::{collections::HashMap, convert::TryFrom}; use std::{collections::HashMap, convert::TryFrom};
use axum::{ use axum::{
Json,
extract::{Path, Query, State}, extract::{Path, Query, State},
http::{HeaderMap, StatusCode}, http::{HeaderMap, StatusCode},
Json,
}; };
use metrics::counter; use metrics::counter;
use time::{Month, UtcOffset}; use time::{Month, UtcOffset};
@ -15,9 +15,9 @@ use crate::{
utils::client_version_min, utils::client_version_min,
}; };
use atuin_server_database::{ use atuin_server_database::{
Database,
calendar::{TimePeriod, TimePeriodInfo}, calendar::{TimePeriod, TimePeriodInfo},
models::NewHistory, models::NewHistory,
Database,
}; };
use atuin_common::api::*; use atuin_common::api::*;
@ -223,7 +223,7 @@ pub async fn calendar<DB: Database>(
"day" => TimePeriod::Day { year, month }, "day" => TimePeriod::Day { year, month },
_ => { _ => {
return Err(ErrorResponse::reply("invalid focus: use year/month/day") return Err(ErrorResponse::reply("invalid focus: use year/month/day")
.with_status(StatusCode::BAD_REQUEST)) .with_status(StatusCode::BAD_REQUEST));
} }
}; };

View File

@ -1,6 +1,6 @@
use atuin_common::api::{ErrorResponse, IndexResponse}; use atuin_common::api::{ErrorResponse, IndexResponse};
use atuin_server_database::Database; use atuin_server_database::Database;
use axum::{extract::State, http, response::IntoResponse, Json}; use axum::{Json, extract::State, http, response::IntoResponse};
use crate::router::AppState; use crate::router::AppState;

View File

@ -1,4 +1,4 @@
use axum::{http::StatusCode, response::IntoResponse, Json}; use axum::{Json, http::StatusCode, response::IntoResponse};
use serde_json::json; use serde_json::json;
use tracing::instrument; use tracing::instrument;

View File

@ -1,4 +1,4 @@
use axum::{extract::State, http::StatusCode, Json}; use axum::{Json, extract::State, http::StatusCode};
use tracing::instrument; use tracing::instrument;
use super::{ErrorResponse, ErrorResponseStatus, RespExt}; use super::{ErrorResponse, ErrorResponseStatus, RespExt};
@ -28,7 +28,7 @@ pub async fn status<DB: Database>(
Ok(count) => count, Ok(count) => count,
Err(_) => { Err(_) => {
return Err(ErrorResponse::reply("failed to query history count") return Err(ErrorResponse::reply("failed to query history count")
.with_status(StatusCode::INTERNAL_SERVER_ERROR)) .with_status(StatusCode::INTERNAL_SERVER_ERROR));
} }
}, },
}; };

View File

@ -3,17 +3,17 @@ use std::collections::HashMap;
use std::time::Duration; use std::time::Duration;
use argon2::{ use argon2::{
password_hash::SaltString, Algorithm, Argon2, Params, PasswordHash, PasswordHasher, Algorithm, Argon2, Params, PasswordHash, PasswordHasher, PasswordVerifier, Version,
PasswordVerifier, Version, password_hash::SaltString,
}; };
use axum::{ use axum::{
Json,
extract::{Path, State}, extract::{Path, State},
http::StatusCode, http::StatusCode,
Json,
}; };
use metrics::counter; use metrics::counter;
use postmark::{reqwest::PostmarkClient, Query}; use postmark::{Query, reqwest::PostmarkClient};
use rand::rngs::OsRng; use rand::rngs::OsRng;
use tracing::{debug, error, info, instrument}; use tracing::{debug, error, info, instrument};
@ -21,8 +21,8 @@ use tracing::{debug, error, info, instrument};
use super::{ErrorResponse, ErrorResponseStatus, RespExt}; use super::{ErrorResponse, ErrorResponseStatus, RespExt};
use crate::router::{AppState, UserAuth}; use crate::router::{AppState, UserAuth};
use atuin_server_database::{ use atuin_server_database::{
models::{NewSession, NewUser},
Database, DbError, Database, DbError,
models::{NewSession, NewUser},
}; };
use reqwest::header::CONTENT_TYPE; use reqwest::header::CONTENT_TYPE;
@ -104,7 +104,7 @@ pub async fn register<DB: Database>(
return Err(ErrorResponse::reply( return Err(ErrorResponse::reply(
"Only alphanumeric and hyphens (-) are allowed in usernames", "Only alphanumeric and hyphens (-) are allowed in usernames",
) )
.with_status(StatusCode::BAD_REQUEST)) .with_status(StatusCode::BAD_REQUEST));
} }
} }
} }
@ -201,12 +201,13 @@ pub async fn send_verification<DB: Database>(
} }
// TODO: if we ever add another mail provider, can match on them all here. // TODO: if we ever add another mail provider, can match on them all here.
let postmark_token = if let Some(token) = settings.mail.postmark.token { let postmark_token = match settings.mail.postmark.token {
token Some(token) => token,
} else { _ => {
error!("Failed to verify email: got None for postmark token"); error!("Failed to verify email: got None for postmark token");
return Err(ErrorResponse::reply("mail not configured") return Err(ErrorResponse::reply("mail not configured")
.with_status(StatusCode::INTERNAL_SERVER_ERROR)); .with_status(StatusCode::INTERNAL_SERVER_ERROR));
}
}; };
let db = &state.0.database; let db = &state.0.database;

View File

@ -1,4 +1,4 @@
use axum::{extract::Query, extract::State, http::StatusCode, Json}; use axum::{Json, extract::Query, extract::State, http::StatusCode};
use metrics::counter; use metrics::counter;
use serde::Deserialize; use serde::Deserialize;
use tracing::{error, instrument}; use tracing::{error, instrument};

View File

@ -4,18 +4,18 @@ use std::future::Future;
use std::net::SocketAddr; use std::net::SocketAddr;
use atuin_server_database::Database; use atuin_server_database::Database;
use axum::{serve, Router}; use axum::{Router, serve};
use axum_server::tls_rustls::RustlsConfig;
use axum_server::Handle; use axum_server::Handle;
use eyre::{eyre, Context, Result}; use axum_server::tls_rustls::RustlsConfig;
use eyre::{Context, Result, eyre};
mod handlers; mod handlers;
mod metrics; mod metrics;
mod router; mod router;
mod utils; mod utils;
pub use settings::example_config;
pub use settings::Settings; pub use settings::Settings;
pub use settings::example_config;
pub mod settings; pub mod settings;

View File

@ -28,10 +28,9 @@ pub fn setup_metrics_recorder() -> PrometheusHandle {
pub async fn track_metrics(req: Request, next: Next) -> impl IntoResponse { pub async fn track_metrics(req: Request, next: Next) -> impl IntoResponse {
let start = Instant::now(); let start = Instant::now();
let path = if let Some(matched_path) = req.extensions().get::<MatchedPath>() { let path = match req.extensions().get::<MatchedPath>() {
matched_path.as_str().to_owned() Some(matched_path) => matched_path.as_str().to_owned(),
} else { _ => req.uri().path().to_owned(),
req.uri().path().to_owned()
}; };
let method = req.method().clone(); let method = req.method().clone();

View File

@ -1,12 +1,12 @@
use async_trait::async_trait; use async_trait::async_trait;
use atuin_common::api::{ErrorResponse, ATUIN_CARGO_VERSION, ATUIN_HEADER_VERSION}; use atuin_common::api::{ATUIN_CARGO_VERSION, ATUIN_HEADER_VERSION, ErrorResponse};
use axum::{ use axum::{
Router,
extract::{FromRequestParts, Request}, extract::{FromRequestParts, Request},
http::{self, request::Parts}, http::{self, request::Parts},
middleware::Next, middleware::Next,
response::{IntoResponse, Response}, response::{IntoResponse, Response},
routing::{delete, get, patch, post}, routing::{delete, get, patch, post},
Router,
}; };
use eyre::Result; use eyre::Result;
use tower::ServiceBuilder; use tower::ServiceBuilder;
@ -18,7 +18,7 @@ use crate::{
metrics, metrics,
settings::Settings, settings::Settings,
}; };
use atuin_server_database::{models::User, Database, DbError}; use atuin_server_database::{Database, DbError, models::User};
pub struct UserAuth(pub User); pub struct UserAuth(pub User);

View File

@ -1,9 +1,9 @@
use std::{io::prelude::*, path::PathBuf}; use std::{io::prelude::*, path::PathBuf};
use config::{Config, Environment, File as ConfigFile, FileFormat}; use config::{Config, Environment, File as ConfigFile, FileFormat};
use eyre::{eyre, Result}; use eyre::{Result, eyre};
use fs_err::{create_dir_all, File}; use fs_err::{File, create_dir_all};
use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde::{Deserialize, Serialize, de::DeserializeOwned};
static EXAMPLE_CONFIG: &str = include_str!("../server.toml"); static EXAMPLE_CONFIG: &str = include_str!("../server.toml");

View File

@ -1,6 +1,6 @@
[package] [package]
name = "atuin" name = "atuin"
edition = "2021" edition = "2024"
description = "atuin - magical shell history" description = "atuin - magical shell history"
readme = "./README.md" readme = "./README.md"

View File

@ -1,5 +1,5 @@
use clap::Parser; use clap::Parser;
use eyre::{bail, Result}; use eyre::{Result, bail};
use atuin_client::{api_client, settings::Settings}; use atuin_client::{api_client, settings::Settings};
use rpassword::prompt_password; use rpassword::prompt_password;

View File

@ -1,5 +1,5 @@
use atuin_client::{api_client, settings::Settings}; use atuin_client::{api_client, settings::Settings};
use eyre::{bail, Result}; use eyre::{Result, bail};
use std::fs::remove_file; use std::fs::remove_file;
use std::path::PathBuf; use std::path::PathBuf;

View File

@ -1,12 +1,12 @@
use std::{io, path::PathBuf}; use std::{io, path::PathBuf};
use clap::Parser; use clap::Parser;
use eyre::{bail, Context, Result}; use eyre::{Context, Result, bail};
use tokio::{fs::File, io::AsyncWriteExt}; use tokio::{fs::File, io::AsyncWriteExt};
use atuin_client::{ use atuin_client::{
api_client, api_client,
encryption::{decode_key, encode_key, load_key, Key}, encryption::{Key, decode_key, encode_key, load_key},
record::sqlite_store::SqliteStore, record::sqlite_store::SqliteStore,
record::store::Store, record::store::Store,
settings::Settings, settings::Settings,
@ -56,7 +56,9 @@ impl Cmd {
let key_path = PathBuf::from(key_path); let key_path = PathBuf::from(key_path);
println!("IMPORTANT"); println!("IMPORTANT");
println!("If you are already logged in on another machine, you must ensure that the key you use here is the same as the key you used there."); println!(
"If you are already logged in on another machine, you must ensure that the key you use here is the same as the key you used there."
);
println!("You can find your key by running 'atuin key' on the other machine"); println!("You can find your key by running 'atuin key' on the other machine");
println!("Do not share this key with anyone"); println!("Do not share this key with anyone");
println!("\nRead more here: https://docs.atuin.sh/guide/sync/#login \n"); println!("\nRead more here: https://docs.atuin.sh/guide/sync/#login \n");
@ -75,22 +77,25 @@ impl Cmd {
match bip39::Mnemonic::from_phrase(&key, bip39::Language::English) { match bip39::Mnemonic::from_phrase(&key, bip39::Language::English) {
Ok(mnemonic) => encode_key(Key::from_slice(mnemonic.entropy()))?, Ok(mnemonic) => encode_key(Key::from_slice(mnemonic.entropy()))?,
Err(err) => { Err(err) => {
if let Some(err) = err.downcast_ref::<bip39::ErrorKind>() { match err.downcast_ref::<bip39::ErrorKind>() {
match err { Some(err) => {
// assume they copied in the base64 key match err {
bip39::ErrorKind::InvalidWord => key, // assume they copied in the base64 key
bip39::ErrorKind::InvalidChecksum => { bip39::ErrorKind::InvalidWord => key,
bail!("key mnemonic was not valid") bip39::ErrorKind::InvalidChecksum => {
} bail!("key mnemonic was not valid")
bip39::ErrorKind::InvalidKeysize(_) }
| bip39::ErrorKind::InvalidWordLength(_) bip39::ErrorKind::InvalidKeysize(_)
| bip39::ErrorKind::InvalidEntropyLength(_, _) => { | bip39::ErrorKind::InvalidWordLength(_)
bail!("key was not the correct length") | bip39::ErrorKind::InvalidEntropyLength(_, _) => {
bail!("key was not the correct length")
}
} }
} }
} else { _ => {
// unknown error. assume they copied the base64 key // unknown error. assume they copied the base64 key
key key
}
} }
} }
} }
@ -106,7 +111,9 @@ impl Cmd {
bail!("the key in existing key file was invalid"); bail!("the key in existing key file was invalid");
} }
} else { } else {
panic!("No key provided. Please use 'atuin key' on your other machine, or recover your key from a backup.") panic!(
"No key provided. Please use 'atuin key' on your other machine, or recover your key from a backup."
)
} }
} else if !key_path.exists() { } else if !key_path.exists() {
if decode_key(key.clone()).is_err() { if decode_key(key.clone()).is_err() {
@ -184,6 +191,9 @@ mod tests {
.into_phrase(); .into_phrase();
let mnemonic = bip39::Mnemonic::from_phrase(&phrase, bip39::Language::English).unwrap(); let mnemonic = bip39::Mnemonic::from_phrase(&phrase, bip39::Language::English).unwrap();
assert_eq!(mnemonic.entropy(), key.as_slice()); assert_eq!(mnemonic.entropy(), key.as_slice());
assert_eq!(phrase, "adapt amused able anxiety mother adapt beef gaze amount else seat alcohol cage lottery avoid scare alcohol cactus school avoid coral adjust catch pink"); assert_eq!(
phrase,
"adapt amused able anxiety mother adapt beef gaze amount else seat alcohol cage lottery avoid scare alcohol cactus school avoid coral adjust catch pink"
);
} }
} }

View File

@ -1,5 +1,5 @@
use clap::Parser; use clap::Parser;
use eyre::{bail, Result}; use eyre::{Result, bail};
use tokio::{fs::File, io::AsyncWriteExt}; use tokio::{fs::File, io::AsyncWriteExt};
use atuin_client::{api_client, settings::Settings}; use atuin_client::{api_client, settings::Settings};
@ -51,8 +51,12 @@ pub async fn run(
let _key = atuin_client::encryption::load_key(settings)?; let _key = atuin_client::encryption::load_key(settings)?;
println!("Registration successful! Please make a note of your key (run 'atuin key') and keep it safe."); println!(
println!("You will need it to log in on other devices, and we cannot help recover it if you lose it."); "Registration successful! Please make a note of your key (run 'atuin key') and keep it safe."
);
println!(
"You will need it to log in on other devices, and we cannot help recover it if you lose it."
);
Ok(()) Ok(())
} }

View File

@ -35,11 +35,15 @@ pub async fn run(settings: &Settings, token: Option<String>) -> Result<()> {
} }
(false, false) => { (false, false) => {
println!("Your Atuin server does not have mail setup. This is not required, though your account cannot be verified. Speak to your admin."); println!(
"Your Atuin server does not have mail setup. This is not required, though your account cannot be verified. Speak to your admin."
);
} }
_ => { _ => {
println!("Invalid email and verification status. This is a bug. Please open an issue: https://github.com/atuinsh/atuin"); println!(
"Invalid email and verification status. This is a bug. Please open an issue: https://github.com/atuinsh/atuin"
);
} }
} }

View File

@ -3,13 +3,13 @@ use std::{env, path::PathBuf, str::FromStr};
use atuin_client::database::Sqlite; use atuin_client::database::Sqlite;
use atuin_client::settings::Settings; use atuin_client::settings::Settings;
use atuin_common::shell::{shell_name, Shell}; use atuin_common::shell::{Shell, shell_name};
use atuin_common::utils; use atuin_common::utils;
use colored::Colorize; use colored::Colorize;
use eyre::Result; use eyre::Result;
use serde::Serialize; use serde::Serialize;
use sysinfo::{get_current_pid, Disks, System}; use sysinfo::{Disks, System, get_current_pid};
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
struct ShellInfo { struct ShellInfo {
@ -353,7 +353,7 @@ impl DoctorDump {
fn checks(info: &DoctorDump) { fn checks(info: &DoctorDump) {
println!(); // spacing println!(); // spacing
// //
let zfs_error = "[Filesystem] ZFS is known to have some issues with SQLite. Atuin uses SQLite heavily. If you are having poor performance, there are some workarounds here: https://github.com/atuinsh/atuin/issues/952".bold().red(); let zfs_error = "[Filesystem] ZFS is known to have some issues with SQLite. Atuin uses SQLite heavily. If you are having poor performance, there are some workarounds here: https://github.com/atuinsh/atuin/issues/952".bold().red();
let bash_plugin_error = "[Shell] If you are using Bash, Atuin requires that either bash-preexec or ble.sh be installed. An older ble.sh may not be detected. so ignore this if you have it set up! Read more here: https://docs.atuin.sh/guide/installation/#bash".bold().red(); let bash_plugin_error = "[Shell] If you are using Bash, Atuin requires that either bash-preexec or ble.sh be installed. An older ble.sh may not be detected. so ignore this if you have it set up! Read more here: https://docs.atuin.sh/guide/installation/#bash".bold().red();
let blesh_integration_error = "[Shell] Atuin and ble.sh seem to be loaded in the session, but the integration does not seem to be working. Please check the setup in .bashrc.".bold().red(); let blesh_integration_error = "[Shell] Atuin and ble.sh seem to be loaded in the session, but the integration does not seem to be working. Please check the setup in .bashrc.".bold().red();

View File

@ -1,5 +1,5 @@
use clap::Subcommand; use clap::Subcommand;
use eyre::{eyre, Context, Result}; use eyre::{Context, Result, eyre};
use atuin_client::{encryption, record::sqlite_store::SqliteStore, settings::Settings}; use atuin_client::{encryption, record::sqlite_store::SqliteStore, settings::Settings};
@ -92,7 +92,9 @@ impl Cmd {
pub async fn run(&self, settings: &Settings, store: SqliteStore) -> Result<()> { pub async fn run(&self, settings: &Settings, store: SqliteStore) -> Result<()> {
if !settings.dotfiles.enabled { if !settings.dotfiles.enabled {
eprintln!("Dotfiles are not enabled. Add\n\n[dotfiles]\nenabled = true\n\nto your configuration file to enable them.\n"); eprintln!(
"Dotfiles are not enabled. Add\n\n[dotfiles]\nenabled = true\n\nto your configuration file to enable them.\n"
);
eprintln!("The default configuration file is located at ~/.config/atuin/config.toml."); eprintln!("The default configuration file is located at ~/.config/atuin/config.toml.");
return Ok(()); return Ok(());
} }

View File

@ -73,7 +73,9 @@ impl Cmd {
pub async fn run(&self, settings: &Settings, store: SqliteStore) -> Result<()> { pub async fn run(&self, settings: &Settings, store: SqliteStore) -> Result<()> {
if !settings.dotfiles.enabled { if !settings.dotfiles.enabled {
eprintln!("Dotfiles are not enabled. Add\n\n[dotfiles]\nenabled = true\n\nto your configuration file to enable them.\n"); eprintln!(
"Dotfiles are not enabled. Add\n\n[dotfiles]\nenabled = true\n\nto your configuration file to enable them.\n"
);
eprintln!("The default configuration file is located at ~/.config/atuin/config.toml."); eprintln!("The default configuration file is located at ~/.config/atuin/config.toml.");
return Ok(()); return Ok(());
} }

View File

@ -11,9 +11,9 @@ use eyre::{Context, Result};
use runtime_format::{FormatKey, FormatKeyError, ParseSegment, ParsedFmt}; use runtime_format::{FormatKey, FormatKeyError, ParseSegment, ParsedFmt};
use atuin_client::{ use atuin_client::{
database::{current_context, Database, Sqlite}, database::{Database, Sqlite, current_context},
encryption, encryption,
history::{store::HistoryStore, History}, history::{History, store::HistoryStore},
record::sqlite_store::SqliteStore, record::sqlite_store::SqliteStore,
settings::{ settings::{
FilterMode::{Directory, Global, Session}, FilterMode::{Directory, Global, Session},
@ -25,7 +25,7 @@ use atuin_client::{
use atuin_client::{record, sync}; use atuin_client::{record, sync};
use log::{debug, warn}; use log::{debug, warn};
use time::{macros::format_description, OffsetDateTime}; use time::{OffsetDateTime, macros::format_description};
use super::search::format_duration_into; use super::search::format_duration_into;
@ -285,7 +285,9 @@ fn parse_fmt(format: &str) -> ParsedFmt {
Ok(fmt) => fmt, Ok(fmt) => fmt,
Err(err) => { Err(err) => {
eprintln!("ERROR: History formatting failed with the following error: {err}"); eprintln!("ERROR: History formatting failed with the following error: {err}");
println!("If your formatting string contains curly braces (eg: {{var}}) you need to escape them this way: {{{{var}}."); println!(
"If your formatting string contains curly braces (eg: {{var}}) you need to escape them this way: {{{{var}}."
);
std::process::exit(1) std::process::exit(1)
} }
} }
@ -550,11 +552,11 @@ impl Cmd {
if settings.daemon.enabled { if settings.daemon.enabled {
match self { match self {
Self::Start { command } => { Self::Start { command } => {
return Self::handle_daemon_start(settings, &command).await return Self::handle_daemon_start(settings, &command).await;
} }
Self::End { id, exit, duration } => { Self::End { id, exit, duration } => {
return Self::handle_daemon_end(settings, &id, exit, duration).await return Self::handle_daemon_end(settings, &id, exit, duration).await;
} }
_ => {} _ => {}

View File

@ -9,8 +9,8 @@ use atuin_client::{
database::Database, database::Database,
history::History, history::History,
import::{ import::{
bash::Bash, fish::Fish, nu::Nu, nu_histdb::NuHistDb, replxx::Replxx, resh::Resh, Importer, Loader, bash::Bash, fish::Fish, nu::Nu, nu_histdb::NuHistDb, replxx::Replxx,
xonsh::Xonsh, xonsh_sqlite::XonshSqlite, zsh::Zsh, zsh_histdb::ZshHistDb, Importer, Loader, resh::Resh, xonsh::Xonsh, xonsh_sqlite::XonshSqlite, zsh::Zsh, zsh_histdb::ZshHistDb,
}, },
}; };
@ -57,7 +57,9 @@ impl Cmd {
match self { match self {
Self::Auto => { Self::Auto => {
if cfg!(windows) { if cfg!(windows) {
println!("This feature does not work on windows. Please run atuin import <SHELL>. To view a list of shells, run atuin import."); println!(
"This feature does not work on windows. Please run atuin import <SHELL>. To view a list of shells, run atuin import."
);
return Ok(()); return Ok(());
} }

View File

@ -1,7 +1,7 @@
use std::path::PathBuf; use std::path::PathBuf;
use atuin_client::{encryption, record::sqlite_store::SqliteStore, settings::Settings}; use atuin_client::{encryption, record::sqlite_store::SqliteStore, settings::Settings};
use atuin_dotfiles::store::{var::VarStore, AliasStore}; use atuin_dotfiles::store::{AliasStore, var::VarStore};
use clap::{Parser, ValueEnum}; use clap::{Parser, ValueEnum};
use eyre::{Result, WrapErr}; use eyre::{Result, WrapErr};
@ -160,7 +160,9 @@ $env.config = (
pub async fn run(self, settings: &Settings) -> Result<()> { pub async fn run(self, settings: &Settings) -> Result<()> {
if !settings.paths_ok() { if !settings.paths_ok() {
eprintln!("Atuin settings paths are broken. Disabling atuin shell hooks. Run `atuin doctor` to diagnose."); eprintln!(
"Atuin settings paths are broken. Disabling atuin shell hooks. Run `atuin doctor` to diagnose."
);
return Ok(()); return Ok(());
} }

View File

@ -1,4 +1,4 @@
use atuin_dotfiles::store::{var::VarStore, AliasStore}; use atuin_dotfiles::store::{AliasStore, var::VarStore};
use eyre::Result; use eyre::Result;
pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool) { pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool) {

View File

@ -1,4 +1,4 @@
use atuin_dotfiles::store::{var::VarStore, AliasStore}; use atuin_dotfiles::store::{AliasStore, var::VarStore};
use eyre::Result; use eyre::Result;
pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool) { pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool) {

View File

@ -1,4 +1,4 @@
use atuin_dotfiles::store::{var::VarStore, AliasStore}; use atuin_dotfiles::store::{AliasStore, var::VarStore};
use eyre::Result; use eyre::Result;
pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool) { pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool) {

View File

@ -1,4 +1,4 @@
use atuin_dotfiles::store::{var::VarStore, AliasStore}; use atuin_dotfiles::store::{AliasStore, var::VarStore};
use eyre::Result; use eyre::Result;
pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool) { pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool) {

View File

@ -1,4 +1,4 @@
use std::io::{stderr, IsTerminal as _}; use std::io::{IsTerminal as _, stderr};
use atuin_common::utils::{self, Escapable as _}; use atuin_common::utils::{self, Escapable as _};
use clap::Parser; use clap::Parser;
@ -6,9 +6,9 @@ use eyre::Result;
use atuin_client::{ use atuin_client::{
database::Database, database::Database,
database::{current_context, OptFilters}, database::{OptFilters, current_context},
encryption, encryption,
history::{store::HistoryStore, History}, history::{History, store::HistoryStore},
record::sqlite_store::SqliteStore, record::sqlite_store::SqliteStore,
settings::{FilterMode, KeymapMode, SearchMode, Settings, Timezone}, settings::{FilterMode, KeymapMode, SearchMode, Settings, Timezone},
theme::Theme, theme::Theme,
@ -165,7 +165,9 @@ impl Cmd {
} }
if self.delete && query.is_empty() { if self.delete && query.is_empty() {
eprintln!("Please specify a query to match the items you wish to delete. If you wish to delete all history, pass --delete-it-all"); eprintln!(
"Please specify a query to match the items you wish to delete. If you wish to delete all history, pass --delete-it-all"
);
return Ok(()); return Ok(());
} }

View File

@ -184,11 +184,7 @@ impl Cursor {
} }
pub fn back(&mut self) -> Option<char> { pub fn back(&mut self) -> Option<char> {
if self.left() { if self.left() { self.remove() } else { None }
self.remove()
} else {
None
}
} }
pub fn clear(&mut self) { pub fn clear(&mut self) {

View File

@ -3,7 +3,7 @@ use std::path::Path;
use async_trait::async_trait; use async_trait::async_trait;
use atuin_client::{database::Database, history::History, settings::FilterMode}; use atuin_client::{database::Database, history::History, settings::FilterMode};
use eyre::Result; use eyre::Result;
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher}; use fuzzy_matcher::{FuzzyMatcher, skim::SkimMatcherV2};
use itertools::Itertools; use itertools::Itertools;
use time::OffsetDateTime; use time::OffsetDateTime;
use tokio::task::yield_now; use tokio::task::yield_now;

View File

@ -6,12 +6,12 @@ use atuin_client::{
settings::Settings, settings::Settings,
}; };
use ratatui::{ use ratatui::{
Frame,
crossterm::event::{KeyCode, KeyEvent, KeyModifiers}, crossterm::event::{KeyCode, KeyEvent, KeyModifiers},
layout::Rect, layout::Rect,
prelude::{Constraint, Direction, Layout}, prelude::{Constraint, Direction, Layout},
style::Style, style::Style,
widgets::{Bar, BarChart, BarGroup, Block, Borders, Padding, Paragraph, Row, Table}, widgets::{Bar, BarChart, BarGroup, Block, Borders, Padding, Paragraph, Row, Table},
Frame,
}; };
use super::duration::format_duration; use super::duration::format_duration;
@ -21,11 +21,7 @@ use super::interactive::{InputAction, State};
#[allow(clippy::cast_sign_loss)] #[allow(clippy::cast_sign_loss)]
fn u64_or_zero(num: i64) -> u64 { fn u64_or_zero(num: i64) -> u64 {
if num < 0 { if num < 0 { 0 } else { num as u64 }
0
} else {
num as u64
}
} }
pub fn draw_commands( pub fn draw_commands(

View File

@ -1,5 +1,5 @@
use std::{ use std::{
io::{stdout, Write}, io::{Write, stdout},
time::Duration, time::Duration,
}; };
@ -11,8 +11,8 @@ use time::OffsetDateTime;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use atuin_client::{ use atuin_client::{
database::{current_context, Database}, database::{Database, current_context},
history::{store::HistoryStore, History, HistoryStats}, history::{History, HistoryStats, store::HistoryStore},
settings::{ settings::{
CursorStyle, ExitMode, FilterMode, KeymapMode, PreviewStrategy, SearchMode, Settings, CursorStyle, ExitMode, FilterMode, KeymapMode, PreviewStrategy, SearchMode, Settings,
}, },
@ -25,9 +25,10 @@ use super::{
}; };
use crate::command::client::theme::{Meaning, Theme}; use crate::command::client::theme::{Meaning, Theme};
use crate::{command::client::search::engines, VERSION}; use crate::{VERSION, command::client::search::engines};
use ratatui::{ use ratatui::{
Frame, Terminal, TerminalOptions, Viewport,
backend::CrosstermBackend, backend::CrosstermBackend,
crossterm::{ crossterm::{
cursor::SetCursorStyle, cursor::SetCursorStyle,
@ -42,8 +43,7 @@ use ratatui::{
prelude::*, prelude::*,
style::{Modifier, Style}, style::{Modifier, Style},
text::{Line, Span, Text}, text::{Line, Span, Text},
widgets::{block::Title, Block, BorderType, Borders, Padding, Paragraph, Tabs}, widgets::{Block, BorderType, Borders, Padding, Paragraph, Tabs, block::Title},
Frame, Terminal, TerminalOptions, Viewport,
}; };
const TAB_TITLES: [&str; 2] = ["Search", "Inspect"]; const TAB_TITLES: [&str; 2] = ["Search", "Inspect"];
@ -390,7 +390,7 @@ impl State {
KeyCode::Char(c @ '1'..='9') if modfr => { KeyCode::Char(c @ '1'..='9') if modfr => {
return c.to_digit(10).map_or(InputAction::Continue, |c| { return c.to_digit(10).map_or(InputAction::Continue, |c| {
InputAction::Accept(self.results_state.selected() + c as usize) InputAction::Accept(self.results_state.selected() + c as usize)
}) });
} }
KeyCode::Left if ctrl => self KeyCode::Left if ctrl => self
.search .search
@ -763,7 +763,9 @@ impl State {
// HACK: I'm following up with abstracting this into the UI container, with a // HACK: I'm following up with abstracting this into the UI container, with a
// sub-widget for search + for inspector // sub-widget for search + for inspector
let feedback = Paragraph::new("The inspector is new - please give feedback (good, or bad) at https://forum.atuin.sh"); let feedback = Paragraph::new(
"The inspector is new - please give feedback (good, or bad) at https://forum.atuin.sh",
);
f.render_widget(feedback, input_chunk); f.render_widget(feedback, input_chunk);
return; return;

View File

@ -4,7 +4,7 @@ use interim::parse_date_string;
use time::{Duration, OffsetDateTime, Time}; use time::{Duration, OffsetDateTime, Time};
use atuin_client::{ use atuin_client::{
database::{current_context, Database}, database::{Database, current_context},
settings::Settings, settings::Settings,
theme::Theme, theme::Theme,
}; };

View File

@ -1,6 +1,6 @@
use atuin_dotfiles::store::{var::VarStore, AliasStore}; use atuin_dotfiles::store::{AliasStore, var::VarStore};
use clap::Args; use clap::Args;
use eyre::{bail, Result}; use eyre::{Result, bail};
use atuin_client::{ use atuin_client::{
database::Database, encryption, history::store::HistoryStore, database::Database, encryption, history::store::HistoryStore,

View File

@ -1,9 +1,9 @@
use clap::Args; use clap::Args;
use eyre::{bail, Result}; use eyre::{Result, bail};
use tokio::{fs::File, io::AsyncWriteExt}; use tokio::{fs::File, io::AsyncWriteExt};
use atuin_client::{ use atuin_client::{
encryption::{decode_key, encode_key, generate_encoded_key, load_key, Key}, encryption::{Key, decode_key, encode_key, generate_encoded_key, load_key},
record::sqlite_store::SqliteStore, record::sqlite_store::SqliteStore,
record::store::Store, record::store::Store,
settings::Settings, settings::Settings,
@ -23,22 +23,25 @@ impl Rekey {
let key = match bip39::Mnemonic::from_phrase(&key, bip39::Language::English) { let key = match bip39::Mnemonic::from_phrase(&key, bip39::Language::English) {
Ok(mnemonic) => encode_key(Key::from_slice(mnemonic.entropy()))?, Ok(mnemonic) => encode_key(Key::from_slice(mnemonic.entropy()))?,
Err(err) => { Err(err) => {
if let Some(err) = err.downcast_ref::<bip39::ErrorKind>() { match err.downcast_ref::<bip39::ErrorKind>() {
match err { Some(err) => {
// assume they copied in the base64 key match err {
bip39::ErrorKind::InvalidWord => key, // assume they copied in the base64 key
bip39::ErrorKind::InvalidChecksum => { bip39::ErrorKind::InvalidWord => key,
bail!("key mnemonic was not valid") bip39::ErrorKind::InvalidChecksum => {
} bail!("key mnemonic was not valid")
bip39::ErrorKind::InvalidKeysize(_) }
| bip39::ErrorKind::InvalidWordLength(_) bip39::ErrorKind::InvalidKeysize(_)
| bip39::ErrorKind::InvalidEntropyLength(_, _) => { | bip39::ErrorKind::InvalidWordLength(_)
bail!("key was not the correct length") | bip39::ErrorKind::InvalidEntropyLength(_, _) => {
bail!("key was not the correct length")
}
} }
} }
} else { _ => {
// unknown error. assume they copied the base64 key // unknown error. assume they copied the base64 key
key key
}
} }
} }
}; };

View File

@ -5,7 +5,7 @@ use time::{Date, Duration, Month, OffsetDateTime, Time};
use atuin_client::{database::Database, settings::Settings, theme::Theme}; use atuin_client::{database::Database, settings::Settings, theme::Theme};
use atuin_history::stats::{compute, Stats}; use atuin_history::stats::{Stats, compute};
#[derive(Debug)] #[derive(Debug)]
struct WrappedStats { struct WrappedStats {
@ -293,7 +293,9 @@ pub async fn run(
let history = db.range(start, end).await?; let history = db.range(start, end).await?;
if history.is_empty() { if history.is_empty() {
println!("Your history for {year} is empty!\nMaybe 'atuin import' could help you import your previous history 🪄"); println!(
"Your history for {year} is empty!\nMaybe 'atuin import' could help you import your previous history 🪄"
);
return Ok(()); return Ok(());
} }

View File

@ -1,5 +1,5 @@
use clap::{CommandFactory, Parser, ValueEnum}; use clap::{CommandFactory, Parser, ValueEnum};
use clap_complete::{generate, generate_to, Generator, Shell}; use clap_complete::{Generator, Shell, generate, generate_to};
use clap_complete_nushell::Nushell; use clap_complete_nushell::Nushell;
use eyre::Result; use eyre::Result;

View File

@ -1,12 +1,12 @@
use std::net::SocketAddr; use std::net::SocketAddr;
use atuin_server_postgres::Postgres; use atuin_server_postgres::Postgres;
use tracing_subscriber::{fmt, prelude::*, EnvFilter}; use tracing_subscriber::{EnvFilter, fmt, prelude::*};
use clap::Parser; use clap::Parser;
use eyre::{Context, Result}; use eyre::{Context, Result};
use atuin_server::{example_config, launch, launch_metrics_server, Settings}; use atuin_server::{Settings, example_config, launch, launch_metrics_server};
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[clap(infer_subcommands = true)] #[clap(infer_subcommands = true)]

View File

@ -1,4 +1,4 @@
use atuin_dotfiles::store::{var::VarStore, AliasStore}; use atuin_dotfiles::store::{AliasStore, var::VarStore};
use eyre::{Context, Result}; use eyre::{Context, Result};
use atuin_client::{ use atuin_client::{

View File

@ -2,12 +2,12 @@ use std::{env, time::Duration};
use atuin_client::api_client; use atuin_client::api_client;
use atuin_common::utils::uuid_v7; use atuin_common::utils::uuid_v7;
use atuin_server::{launch_with_tcp_listener, Settings as ServerSettings}; use atuin_server::{Settings as ServerSettings, launch_with_tcp_listener};
use atuin_server_postgres::{Postgres, PostgresSettings}; use atuin_server_postgres::{Postgres, PostgresSettings};
use futures_util::TryFutureExt; use futures_util::TryFutureExt;
use tokio::{net::TcpListener, sync::oneshot, task::JoinHandle}; use tokio::{net::TcpListener, sync::oneshot, task::JoinHandle};
use tracing::{dispatcher, Dispatch}; use tracing::{Dispatch, dispatcher};
use tracing_subscriber::{layer::SubscriberExt, EnvFilter}; use tracing_subscriber::{EnvFilter, layer::SubscriberExt};
pub async fn start_server(path: &str) -> (String, oneshot::Sender<()>, JoinHandle<()>) { pub async fn start_server(path: &str) -> (String, oneshot::Sender<()>, JoinHandle<()>) {
let formatting_layer = tracing_tree::HierarchicalLayer::default() let formatting_layer = tracing_tree::HierarchicalLayer::default()

View File

@ -32,7 +32,7 @@
fenix.packages.${system}.fromToolchainFile fenix.packages.${system}.fromToolchainFile
{ {
file = ./rust-toolchain.toml; file = ./rust-toolchain.toml;
sha256 = "sha256-AJ6LX/Q/Er9kS15bn9iflkUwcgYqRQxiOIL2ToVAXaU="; sha256 = "sha256-Hn2uaQzRLidAWpfmRwSRdImifGUCAb9HeAqTYFXWeQk=";
}; };
in in
pkgs.makeRustPlatform { pkgs.makeRustPlatform {