summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEllie Huxtable <ellie@elliehuxtable.com>2024-01-18 19:17:48 +0000
committerEllie Huxtable <ellie@elliehuxtable.com>2024-01-18 19:17:48 +0000
commit60dfc5fc05a17abd7d69b0709d501b259325556b (patch)
treedb17ec8fb6217485fdbc792920610badd5b3db82
parent1ada8660581d93fecee5423949dc137fd82894a3 (diff)
-rw-r--r--Cargo.lock12
-rw-r--r--Cargo.toml2
-rw-r--r--atuin-client/Cargo.toml2
-rw-r--r--atuin-config/Cargo.toml22
-rw-r--r--atuin-config/src/alias/mod.rs2
-rw-r--r--atuin-config/src/alias/record.rs244
-rw-r--r--atuin-config/src/alias/store.rs48
-rw-r--r--atuin-config/src/lib.rs2
-rw-r--r--atuin-server-postgres/Cargo.toml2
-rw-r--r--atuin-server/Cargo.toml2
-rw-r--r--atuin/src/command/client.rs6
-rw-r--r--atuin/src/command/client/config.rs5
-rw-r--r--atuin/src/command/client/config/alias.rs16
-rw-r--r--atuin/src/command/client/config/mod.rs19
14 files changed, 375 insertions, 9 deletions
diff --git a/Cargo.lock b/Cargo.lock
index b445a28e..e0e17528 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -269,6 +269,18 @@ dependencies = [
]
[[package]]
+name = "atuin-config"
+version = "0.0.1"
+dependencies = [
+ "atuin-client",
+ "atuin-common",
+ "eyre",
+ "rmp",
+ "serde",
+ "uuid",
+]
+
+[[package]]
name = "atuin-server"
version = "17.2.1"
dependencies = [
diff --git a/Cargo.toml b/Cargo.toml
index eb627028..8fc3bb7d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,6 +6,7 @@ members = [
"atuin-server-postgres",
"atuin-server-database",
"atuin-common",
+ "atuin-config",
]
[workspace.package]
@@ -45,6 +46,7 @@ typed-builder = "0.18.0"
pretty_assertions = "1.3.0"
thiserror = "1.0"
rustix = {version = "0.38.30", features=["process", "fs"]}
+rmp = { version = "0.8.11" }
[workspace.dependencies.reqwest]
version = "0.11"
diff --git a/atuin-client/Cargo.toml b/atuin-client/Cargo.toml
index cbb8d016..3ab991d0 100644
--- a/atuin-client/Cargo.toml
+++ b/atuin-client/Cargo.toml
@@ -44,7 +44,7 @@ fs-err = { workspace = true }
sql-builder = "3"
lazy_static = "1"
memchr = "2.5"
-rmp = { version = "0.8.11" }
+rmp = { workspace= true }
typed-builder = { workspace = true }
tokio = { workspace = true }
semver = { workspace = true }
diff --git a/atuin-config/Cargo.toml b/atuin-config/Cargo.toml
new file mode 100644
index 00000000..497a25ee
--- /dev/null
+++ b/atuin-config/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "atuin-config"
+edition = "2021"
+version = "0.0.1"
+authors = {workspace = true}
+
+rust-version = { workspace = true }
+license = { workspace = true }
+homepage = { workspace = true }
+repository = { workspace = true }
+readme.workspace = true
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+atuin-common = { path = "../atuin-common", version = "17.2.1" }
+atuin-client = { path = "../atuin-client", version = "17.2.1" }
+
+uuid = { workspace = true }
+serde = { workspace = true }
+rmp = { workspace= true }
+eyre = {workspace = true}
diff --git a/atuin-config/src/alias/mod.rs b/atuin-config/src/alias/mod.rs
new file mode 100644
index 00000000..b5f67df6
--- /dev/null
+++ b/atuin-config/src/alias/mod.rs
@@ -0,0 +1,2 @@
+pub mod record;
+pub mod store;
diff --git a/atuin-config/src/alias/record.rs b/atuin-config/src/alias/record.rs
new file mode 100644
index 00000000..c74f462e
--- /dev/null
+++ b/atuin-config/src/alias/record.rs
@@ -0,0 +1,244 @@
+use eyre::{bail, eyre, Result};
+use rmp::decode::Bytes;
+use rmp::encode::RmpWrite;
+use uuid::Uuid;
+
+pub const CONFIG_ALIAS_VERSION: &str = "v0";
+pub const CONFIG_ALIAS_TAG: &str = "config-alias";
+
+use atuin_common::record::{DecryptedData, Host, HostId};
+
+#[derive(Debug, Eq, PartialEq, Clone)]
+pub struct AliasId(Uuid);
+
+#[derive(Debug, Eq, PartialEq, Clone)]
+pub struct Alias {
+ pub id: AliasId,
+ pub name: String,
+ pub definition: String,
+}
+
+#[derive(Debug, Eq, PartialEq, Clone)]
+pub enum AliasRecord {
+ Create(Alias), // Create a history record
+ Delete(AliasId), // Delete a history record, identified by ID
+}
+
+impl AliasRecord {
+ pub fn serialize(&self) -> Result<DecryptedData> {
+ use rmp::encode;
+
+ let mut output = vec![];
+
+ // write a 0u8 for create, and a 1u8 for delete
+ // followed by the data
+ // we don't need to write an explicit version in here, because
+ // 1. it's stored in the overall record
+ // 2. we write the field count anyways
+ match self {
+ AliasRecord::Create(alias) => {
+ // write the type
+ encode::write_u8(&mut output, 0)?;
+
+ // write how many fields we are writing
+ encode::write_array_len(&mut output, 3)?;
+
+ // write the fields
+ let (most, least) = alias.id.0.as_u64_pair();
+ encode::write_u64(&mut output, most)?;
+ encode::write_u64(&mut output, least)?;
+
+ encode::write_str(&mut output, &alias.name)?;
+ encode::write_str(&mut output, &alias.definition)?;
+ }
+ AliasRecord::Delete(id) => {
+ // write the type
+ encode::write_u8(&mut output, 1)?;
+
+ // write how many fields we are writing
+ encode::write_array_len(&mut output, 1)?;
+
+ let (most, least) = id.0.as_u64_pair();
+ encode::write_u64(&mut output, most)?;
+ encode::write_u64(&mut output, least)?;
+ }
+ }
+
+ Ok(DecryptedData(output))
+ }
+
+ pub fn deserialize(data: &DecryptedData, version: &str) -> Result<AliasRecord> {
+ use rmp::decode;
+
+ if version != CONFIG_ALIAS_VERSION {
+ bail!("Invalid version for AliasRecord::deserialize");
+ }
+
+ fn error_report<E: std::fmt::Debug>(err: E) -> eyre::Report {
+ eyre!("{err:?}")
+ }
+
+ let mut bytes = Bytes::new(&data.0);
+
+ // read the type
+ let record_type = decode::read_u8(&mut bytes).map_err(error_report)?;
+
+ // read the number of fields
+ let field_count = decode::read_array_len(&mut bytes).map_err(error_report)?;
+
+ match record_type {
+ 0 => {
+ // create
+ if field_count != 3 {
+ return Err(eyre::eyre!("Invalid field count for AliasRecord::Create"));
+ }
+
+ let most = decode::read_u64(&mut bytes).map_err(error_report)?;
+ let least = decode::read_u64(&mut bytes).map_err(error_report)?;
+ let id = Uuid::from_u64_pair(most, least);
+
+ let bytes = bytes.remaining_slice();
+ let (name, bytes) = decode::read_str_from_slice(bytes).map_err(error_report)?;
+ let (definition, bytes) =
+ decode::read_str_from_slice(bytes).map_err(error_report)?;
+
+ if !bytes.is_empty() {
+ bail!("trailing bytes in encoded history. malformed")
+ }
+
+ let alias = Alias {
+ id: AliasId(id),
+ name: name.to_string(),
+ definition: definition.to_string(),
+ };
+
+ Ok(AliasRecord::Create(alias))
+ }
+ 1 => {
+ // delete
+ if field_count != 1 {
+ return Err(eyre::eyre!("Invalid field count for AliasRecord::Delete"));
+ }
+
+ let most = decode::read_u64(&mut bytes).map_err(error_report)?;
+ let least = decode::read_u64(&mut bytes).map_err(error_report)?;
+ let id = Uuid::from_u64_pair(most, least);
+
+ let alias = AliasId(id);
+
+ Ok(AliasRecord::Delete(alias))
+ }
+ _ => Err(eyre::eyre!("Invalid record type")),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use atuin_common::record::DecryptedData;
+
+ use super::{Alias, AliasRecord};
+
+ #[test]
+ fn test_encode_alias_create() {
+ let snapshot = &[
+ 204, 0, 147, 207, 1, 141, 29, 204, 218, 86, 127, 99, 207, 185, 95, 141, 153, 16, 161,
+ 141, 155, 164, 116, 101, 115, 116, 164, 116, 101, 115, 116,
+ ];
+
+ // write a test for encoding an alias
+ let alias = Alias {
+ id: super::AliasId(uuid::uuid!("018d1dccda567f63b95f8d9910a18d9b")),
+ name: "test".to_string(),
+ definition: "test".to_string(),
+ };
+ let alias_record = super::AliasRecord::Create(alias);
+
+ let encoded = alias_record.serialize().expect("failed to encode alias");
+
+ assert_eq!(encoded.0.len(), 31);
+ assert_eq!(encoded.0, snapshot);
+ }
+
+ #[test]
+ fn test_decode_alias_create() {
+ let snapshot = vec![
+ 204, 0, 147, 207, 1, 141, 29, 204, 218, 86, 127, 99, 207, 185, 95, 141, 153, 16, 161,
+ 141, 155, 164, 116, 101, 115, 116, 164, 116, 101, 115, 116,
+ ];
+
+ let alias = Alias {
+ id: super::AliasId(uuid::uuid!("018d1dccda567f63b95f8d9910a18d9b")),
+ name: "test".to_string(),
+ definition: "test".to_string(),
+ };
+
+ let decoded = AliasRecord::deserialize(&DecryptedData(snapshot), "v0")
+ .expect("failed to decode alias");
+
+ assert_eq!(decoded, AliasRecord::Create(alias));
+ }
+
+ #[test]
+ fn test_alias_encode_decode_create() {
+ let alias = Alias {
+ id: super::AliasId(uuid::uuid!("018d1dccda567f63b95f8d9910a18d9b")),
+ name: "test".to_string(),
+ definition: "test".to_string(),
+ };
+
+ let create = AliasRecord::Create(alias.clone());
+
+ let encoded = create.serialize().expect("failed to serialize");
+
+ let decoded = AliasRecord::deserialize(&encoded, "v0").expect("failed to deserialize");
+
+ assert_eq!(create, decoded);
+ }
+
+ #[test]
+ fn test_alias_encode_delete() {
+ let snapshot = &[
+ 204, 1, 145, 207, 1, 141, 29, 227, 138, 189, 120, 83, 207, 140, 223, 145, 3, 114, 55,
+ 127, 126,
+ ];
+
+ let record = AliasRecord::Delete(super::AliasId(uuid::uuid!(
+ "018d1de38abd78538cdf910372377f7e"
+ )));
+
+ let encoded = record.serialize().expect("failed to serialize");
+
+ assert_eq!(encoded.0.len(), 21);
+ assert_eq!(encoded.0, snapshot);
+ }
+
+ #[test]
+ fn test_alias_decode_delete() {
+ let snapshot = vec![
+ 204, 1, 145, 207, 1, 141, 29, 227, 138, 189, 120, 83, 207, 140, 223, 145, 3, 114, 55,
+ 127, 126,
+ ];
+ let record = AliasRecord::Delete(super::AliasId(uuid::uuid!(
+ "018d1de38abd78538cdf910372377f7e"
+ )));
+
+ let decoded = AliasRecord::deserialize(&DecryptedData(snapshot), "v0")
+ .expect("failed to decode alias");
+
+ assert_eq!(decoded, record);
+ }
+
+ #[test]
+ fn test_alias_encode_decode_delete() {
+ let delete = AliasRecord::Delete(super::AliasId(uuid::uuid!(
+ "018d1dccda567f63b95f8d9910a18d9b"
+ )));
+
+ let encoded = delete.serialize().expect("failed to serialize");
+
+ let decoded = AliasRecord::deserialize(&encoded, "v0").expect("failed to deserialize");
+
+ assert_eq!(delete, decoded);
+ }
+}
diff --git a/atuin-config/src/alias/store.rs b/atuin-config/src/alias/store.rs
new file mode 100644
index 00000000..f7709a95
--- /dev/null
+++ b/atuin-config/src/alias/store.rs
@@ -0,0 +1,48 @@
+use eyre::{bail, eyre, Result};
+
+use atuin_client::record::{encryption::PASETO_V4, sqlite_store::SqliteStore, store::Store};
+use atuin_common::record::{Host, HostId, Record, RecordId, RecordIdx};
+
+use super::record::{AliasId, AliasRecord, CONFIG_ALIAS_TAG, CONFIG_ALIAS_VERSION};
+
+#[derive(Debug)]
+pub struct AliasStore {
+ pub store: SqliteStore,
+ pub host_id: HostId,
+ pub encryption_key: [u8; 32],
+}
+
+impl AliasStore {
+ pub fn new(store: SqliteStore, host_id: HostId, encryption_key: [u8; 32]) -> Self {
+ AliasStore {
+ store,
+ host_id,
+ encryption_key,
+ }
+ }
+
+ async fn push_record(&self, record: AliasRecord) -> Result<(RecordId, RecordIdx)> {
+ let bytes = record.serialize()?;
+ let idx = self
+ .store
+ .last(self.host_id, CONFIG_ALIAS_TAG)
+ .await?
+ .map_or(0, |p| p.idx + 1);
+
+ let record = Record::builder()
+ .host(Host::new(self.host_id))
+ .version(CONFIG_ALIAS_VERSION.to_string())
+ .tag(CONFIG_ALIAS_TAG.to_string())
+ .idx(idx)
+ .data(bytes)
+ .build();
+
+ let id = record.id;
+
+ self.store
+ .push(&record.encrypt::<PASETO_V4>(&self.encryption_key))
+ .await?;
+
+ Ok((id, idx))
+ }
+}
diff --git a/atuin-config/src/lib.rs b/atuin-config/src/lib.rs
new file mode 100644
index 00000000..31e998cd
--- /dev/null
+++ b/atuin-config/src/lib.rs
@@ -0,0 +1,2 @@
+pub mod alias;
+
diff --git a/atuin-server-postgres/Cargo.toml b/atuin-server-postgres/Cargo.toml
index b1adcdd3..c00cf9f8 100644
--- a/atuin-server-postgres/Cargo.toml
+++ b/atuin-server-postgres/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "atuin-server-postgres"
-edition = "2018"
+edition = "2021"
description = "server postgres database library for atuin"
version = { workspace = true }
diff --git a/atuin-server/Cargo.toml b/atuin-server/Cargo.toml
index 4464ee0e..1af263e9 100644
--- a/atuin-server/Cargo.toml
+++ b/atuin-server/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "atuin-server"
-edition = "2018"
+edition = "2021"
description = "server library for atuin"
rust-version = { workspace = true }
diff --git a/atuin/src/command/client.rs b/atuin/src/command/client.rs
index 38048504..3313e878 100644
--- a/atuin/src/command/client.rs
+++ b/atuin/src/command/client.rs
@@ -50,6 +50,9 @@ pub enum Cmd {
#[command(subcommand)]
Store(store::Cmd),
+ #[command(subcommand)]
+ Config(config::Cmd),
+
/// Print example configuration
#[command()]
DefaultConfig,
@@ -100,9 +103,10 @@ impl Cmd {
Self::Kv(kv) => kv.run(&settings, &sqlite_store).await,
Self::Store(store) => store.run(&settings, &db, sqlite_store).await,
+ Self::Config(config) => config.run(&settings).await,
Self::DefaultConfig => {
- config::run();
+ println!("{}", Settings::example_config());
Ok(())
}
}
diff --git a/atuin/src/command/client/config.rs b/atuin/src/command/client/config.rs
deleted file mode 100644
index f51e45c2..00000000
--- a/atuin/src/command/client/config.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-use atuin_client::settings::Settings;
-
-pub fn run() {
- println!("{}", Settings::example_config());
-}
diff --git a/atuin/src/command/client/config/alias.rs b/atuin/src/command/client/config/alias.rs
new file mode 100644
index 00000000..c700b625
--- /dev/null
+++ b/atuin/src/command/client/config/alias.rs
@@ -0,0 +1,16 @@
+use clap::Parser;
+use eyre::Result;
+
+use atuin_client::settings::Settings;
+
+#[derive(Subcommand, Debug)]
+pub enum Cmd {
+ Create,
+}
+
+impl Cmd {
+ pub async fn run(&self, settings: &Settings) -> Result<()> {
+ println!("omg an alias");
+ Ok(())
+ }
+}
diff --git a/atuin/src/command/client/config/mod.rs b/atuin/src/command/client/config/mod.rs
new file mode 100644
index 00000000..f12aba88
--- /dev/null
+++ b/atuin/src/command/client/config/mod.rs
@@ -0,0 +1,19 @@
+use clap::{Args, Subcommand};
+use eyre::Result;
+
+use atuin_client::settings::Settings;
+
+pub mod alias;
+
+#[derive(Subcommand, Debug)]
+pub enum Cmd {
+ Alias(alias::Cmd),
+}
+
+impl Cmd {
+ pub async fn run(self, settings: &Settings) -> Result<()> {
+ match self {
+ Cmd::Alias(cmd) => cmd.run(&settings).await,
+ }
+ }
+}