diff --git a/.gitignore b/.gitignore index fedaa2b1..45e115b0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target +*/target .env diff --git a/Cargo.lock b/Cargo.lock index 2f7d6d73..16e8e82d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55f82cfe485775d02112886f4169bde0c5894d75e79ead7eafe7e40a25e45f7" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "ahash" version = "0.4.7" @@ -100,17 +85,46 @@ name = "atuin" version = "0.5.0" dependencies = [ "async-trait", + "atuin-client", + "atuin-common", + "atuin-server", "base64", "chrono", "chrono-english", "cli-table", - "config", "directories", - "dotenv", "eyre", "fern", "fork", - "human-panic", + "humantime", + "indicatif", + "itertools", + "log", + "reqwest", + "rusqlite", + "serde 1.0.125", + "serde_derive", + "serde_json", + "structopt", + "termion", + "tokio", + "tui", + "unicode-width", +] + +[[package]] +name = "atuin-client" +version = "0.1.0" +dependencies = [ + "async-trait", + "atuin-common", + "base64", + "chrono", + "chrono-english", + "config", + "directories", + "eyre", + "fern", "humantime", "indicatif", "itertools", @@ -126,11 +140,55 @@ dependencies = [ "serde_json", "shellexpand", "sodiumoxide", - "sqlx", - "structopt", - "termion", "tokio", - "tui", + "urlencoding", + "uuid", + "whoami", +] + +[[package]] +name = "atuin-common" +version = "0.1.0" +dependencies = [ + "chrono", + "eyre", + "rmp-serde", + "rust-crypto", + "serde 1.0.125", + "serde_derive", + "serde_json", + "sodiumoxide", + "uuid", + "warp", +] + +[[package]] +name = "atuin-server" +version = "0.1.0" +dependencies = [ + "async-trait", + "atuin-common", + "base64", + "chrono", + "chrono-english", + "config", + "directories", + "eyre", + "fern", + "fork", + "indicatif", + "log", + "parse_duration", + "rand 0.8.3", + "reqwest", + "rmp-serde", + "rust-crypto", + "serde 1.0.125", + "serde_derive", + "serde_json", + "sodiumoxide", + "sqlx", + "tokio", "unicode-width", "urlencoding", "uuid", @@ -144,20 +202,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" -[[package]] -name = "backtrace" -version = "0.3.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d117600f438b1707d4e4ae15d3595657288f8235a0eb593e80ecc98ab34e1bc" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - [[package]] name = "base64" version = "0.13.0" @@ -771,12 +815,6 @@ dependencies = [ "wasi 0.10.2+wasi-snapshot-preview1", ] -[[package]] -name = "gimli" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce" - [[package]] name = "h2" version = "0.3.2" @@ -907,21 +945,6 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" -[[package]] -name = "human-panic" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39f357a500abcbd7c5f967c1d45c8838585b36743823b9d43488f24850534e36" -dependencies = [ - "backtrace", - "os_type", - "serde 1.0.125", - "serde_derive", - "termcolor", - "toml", - "uuid", -] - [[package]] name = "humantime" version = "2.1.0" @@ -1168,16 +1191,6 @@ dependencies = [ "unicase", ] -[[package]] -name = "miniz_oxide" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg", -] - [[package]] name = "mio" version = "0.7.11" @@ -1377,12 +1390,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" -[[package]] -name = "object" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4" - [[package]] name = "once_cell" version = "1.5.2" @@ -1428,15 +1435,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "os_type" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edc011af0ae98b7f88cf7e4a83b70a54a75d2b8cb013d6efd02e5956207e9eb" -dependencies = [ - "regex", -] - [[package]] name = "parking_lot" version = "0.11.1" @@ -1900,12 +1898,6 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" -[[package]] -name = "rustc-demangle" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" - [[package]] name = "rustc-serialize" version = "0.3.24" diff --git a/Cargo.toml b/Cargo.toml index 9a57f947..57cd00e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,47 +1,40 @@ [package] name = "atuin" version = "0.5.0" -authors = ["Ellie Huxtable "] +authors = ["Ellie Huxtable "] edition = "2018" license = "MIT" description = "atuin - magical shell history" +[workspace] +members = ["./atuin-client", "./atuin-server", "./atuin-common"] + [dependencies] +atuin-server = { path = "atuin-server", version = "0.1.0" } +atuin-client = { path = "atuin-client", version = "0.1.0" } +atuin-common = { path = "atuin-common", version = "0.1.0" } + log = "0.4" fern = {version = "0.6.0", features = ["colored"] } chrono = { version = "0.4", features = ["serde"] } eyre = "0.6" -shellexpand = "2" structopt = "0.3" directories = "3" -uuid = { version = "0.8", features = ["v4"] } indicatif = "0.15.0" -whoami = "1.1.2" -chrono-english = "0.1.4" -cli-table = "0.4" -config = "0.11" serde_derive = "1.0.125" serde = "1.0.125" serde_json = "1.0.64" -rmp-serde = "0.15.4" tui = "0.14" termion = "1.5" unicode-width = "0.1" itertools = "0.10.0" -dotenv = "0.15.0" -sodiumoxide = "0.2.6" +fork = "0.1.18" +tokio = { version = "1", features = ["full"] } +async-trait = "0.1.49" +chrono-english = "0.1.4" +cli-table = "0.4" reqwest = { version = "0.11", features = ["blocking", "json"] } base64 = "0.13.0" -fork = "0.1.18" -parse_duration = "2.1.1" -rand = "0.8.3" -rust-crypto = "^0.2" -human-panic = "1.0.3" -tokio = { version = "1", features = ["full"] } -warp = "0.3" -sqlx = { version = "0.5", features = [ "runtime-tokio-native-tls", "uuid", "chrono", "postgres" ] } -async-trait = "0.1.49" -urlencoding = "1.1.1" humantime = "2.1.0" [dependencies.rusqlite] diff --git a/Dockerfile b/Dockerfile index 0c19ef6d..e7125414 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,31 +1,22 @@ -FROM rust:1.51-buster as builder +FROM lukemathwalker/cargo-chef as planner +WORKDIR app +COPY . . +RUN cargo chef prepare --recipe-path recipe.json -RUN cargo new --bin atuin -WORKDIR /atuin -COPY ./Cargo.toml ./Cargo.toml -COPY ./Cargo.lock ./Cargo.lock +FROM lukemathwalker/cargo-chef as cacher +WORKDIR app +COPY --from=planner /app/recipe.json recipe.json +RUN cargo chef cook --release --recipe-path recipe.json -RUN cargo build --release +FROM rust as builder +WORKDIR app +COPY . . +# Copy over the cached dependencies +COPY --from=cacher /app/target target +COPY --from=cacher $CARGO_HOME $CARGO_HOME +RUN cargo build --release --bin atuin -RUN rm src/*.rs - -ADD . ./ - -RUN rm ./target/release/deps/atuin* -RUN cargo build --release - -FROM debian:buster-slim - -RUN apt-get update \ - && apt-get install -y ca-certificates tzdata libpq-dev \ - && rm -rf /var/lib/apt/lists/* - -EXPOSE 8888 - -ENV TZ=Etc/UTC -ENV RUST_LOG=info -ENV ATUIN_CONFIG=/config/config.toml - -COPY --from=builder /atuin/target/release/atuin ./atuin - -ENTRYPOINT ["./atuin"] +FROM debian:buster-slim as runtime +WORKDIR app +COPY --from=builder /app/target/release/atuin /usr/local/bin +ENTRYPOINT ["/usr/local/bin/atuin"] diff --git a/atuin-client/Cargo.toml b/atuin-client/Cargo.toml new file mode 100644 index 00000000..06c96a9a --- /dev/null +++ b/atuin-client/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "atuin-client" +version = "0.1.0" +authors = ["Ellie Huxtable "] +edition = "2018" +license = "MIT" +description = "client library for atuin" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +atuin-common = { path = "../atuin-common", version = "0.1.0" } + +log = "0.4" +fern = {version = "0.6.0", features = ["colored"] } +chrono = { version = "0.4", features = ["serde"] } +eyre = "0.6" +directories = "3" +uuid = { version = "0.8", features = ["v4"] } +indicatif = "0.15.0" +whoami = "1.1.2" +chrono-english = "0.1.4" +config = "0.11" +serde_derive = "1.0.125" +serde = "1.0.125" +serde_json = "1.0.64" +rmp-serde = "0.15.4" +sodiumoxide = "0.2.6" +reqwest = { version = "0.11", features = ["blocking", "json"] } +base64 = "0.13.0" +parse_duration = "2.1.1" +rand = "0.8.3" +rust-crypto = "^0.2" +tokio = { version = "1", features = ["full"] } +async-trait = "0.1.49" +urlencoding = "1.1.1" +humantime = "2.1.0" +rusqlite= { version = "0.25", features = ["bundled"] } +itertools = "0.10.0" +shellexpand = "2" diff --git a/config.toml b/atuin-client/config.toml similarity index 59% rename from config.toml rename to atuin-client/config.toml index fe776d6e..33e5b545 100644 --- a/config.toml +++ b/atuin-client/config.toml @@ -1,8 +1,3 @@ -# A'tuin example config - -# This section specifies the config for a local client, -# ie where your shell history is on your local machine -[local] ## where to store your database, default is your system data directory ## mac: ~/Library/Application Support/com.elliehuxtable.atuin/history.db ## linux: ~/.local/share/atuin/history.db @@ -27,17 +22,3 @@ ## address of the sync server # sync_address = "https://api.atuin.sh" - -# This section configures the sync server, if you decide to host your own -[server] -## host to bind, can also be passed via CLI args -# host = "127.0.0.1" - -## port to bind, can also be passed via CLI args -# port = 8888 - -## whether to allow anyone to register an account -# open_registration = false - -## URI for postgres (using development creds here) -# db_uri="postgres://username:password@localhost/atuin" diff --git a/src/local/api_client.rs b/atuin-client/src/api_client.rs similarity index 93% rename from src/local/api_client.rs rename to atuin-client/src/api_client.rs index 1b64a295..db2802c3 100644 --- a/src/local/api_client.rs +++ b/atuin-client/src/api_client.rs @@ -4,10 +4,11 @@ use reqwest::header::{HeaderMap, AUTHORIZATION}; use reqwest::Url; use sodiumoxide::crypto::secretbox; -use crate::api::{AddHistoryRequest, CountResponse, SyncHistoryResponse}; -use crate::local::encryption::decrypt; -use crate::local::history::History; -use crate::utils::hash_str; +use atuin_common::api::{AddHistoryRequest, CountResponse, SyncHistoryResponse}; +use atuin_common::utils::hash_str; + +use crate::encryption::decrypt; +use crate::history::History; pub struct Client<'a> { sync_addr: &'a str, diff --git a/src/local/database.rs b/atuin-client/src/database.rs similarity index 100% rename from src/local/database.rs rename to atuin-client/src/database.rs diff --git a/src/local/encryption.rs b/atuin-client/src/encryption.rs similarity index 97% rename from src/local/encryption.rs rename to atuin-client/src/encryption.rs index 3c1699e3..37153f94 100644 --- a/src/local/encryption.rs +++ b/atuin-client/src/encryption.rs @@ -15,7 +15,7 @@ use std::path::PathBuf; use eyre::{eyre, Result}; use sodiumoxide::crypto::secretbox; -use crate::local::history::History; +use crate::history::History; use crate::settings::Settings; #[derive(Debug, Serialize, Deserialize)] @@ -26,7 +26,7 @@ pub struct EncryptedHistory { // Loads the secret key, will create + save if it doesn't exist pub fn load_key(settings: &Settings) -> Result { - let path = settings.local.key_path.as_str(); + let path = settings.key_path.as_str(); if PathBuf::from(path).exists() { let bytes = std::fs::read(path)?; diff --git a/src/local/history.rs b/atuin-client/src/history.rs similarity index 97% rename from src/local/history.rs rename to atuin-client/src/history.rs index 1712f8b9..7f607784 100644 --- a/src/local/history.rs +++ b/atuin-client/src/history.rs @@ -3,7 +3,7 @@ use std::hash::{Hash, Hasher}; use chrono::Utc; -use crate::command::uuid_v4; +use atuin_common::utils::uuid_v4; // Any new fields MUST be Optional<>! #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src/local/import.rs b/atuin-client/src/import.rs similarity index 100% rename from src/local/import.rs rename to atuin-client/src/import.rs diff --git a/src/local/mod.rs b/atuin-client/src/lib.rs similarity index 53% rename from src/local/mod.rs rename to atuin-client/src/lib.rs index 9fe31292..1207bfdb 100644 --- a/src/local/mod.rs +++ b/atuin-client/src/lib.rs @@ -1,6 +1,13 @@ +#[macro_use] +extern crate log; + +#[macro_use] +extern crate serde_derive; + pub mod api_client; pub mod database; pub mod encryption; pub mod history; pub mod import; +pub mod settings; pub mod sync; diff --git a/src/settings.rs b/atuin-client/src/settings.rs similarity index 74% rename from src/settings.rs rename to atuin-client/src/settings.rs index 5325610e..e28963c0 100644 --- a/src/settings.rs +++ b/atuin-client/src/settings.rs @@ -12,7 +12,7 @@ use parse_duration::parse; pub const HISTORY_PAGE_SIZE: i64 = 100; #[derive(Clone, Debug, Deserialize)] -pub struct Local { +pub struct Settings { pub dialect: String, pub auto_sync: bool, pub sync_address: String, @@ -26,7 +26,7 @@ pub struct Local { pub session_token: String, } -impl Local { +impl Settings { pub fn save_sync_time() -> Result<()> { let sync_time_path = ProjectDirs::from("com", "elliehuxtable", "atuin") .ok_or_else(|| eyre!("could not determine key file location"))?; @@ -66,28 +66,12 @@ impl Local { match parse(self.sync_frequency.as_str()) { Ok(d) => { let d = chrono::Duration::from_std(d).unwrap(); - Ok(Utc::now() - Local::last_sync()? >= d) + Ok(Utc::now() - Settings::last_sync()? >= d) } Err(e) => Err(eyre!("failed to check sync: {}", e)), } } -} -#[derive(Clone, Debug, Deserialize)] -pub struct Server { - pub host: String, - pub port: u16, - pub db_uri: String, - pub open_registration: bool, -} - -#[derive(Clone, Debug, Deserialize)] -pub struct Settings { - pub local: Local, - pub server: Server, -} - -impl Settings { pub fn new() -> Result { let config_dir = ProjectDirs::from("com", "elliehuxtable", "atuin").unwrap(); let config_dir = config_dir.config_dir(); @@ -103,8 +87,6 @@ impl Settings { config_file }; - // create the config file if it does not exist - let mut s = Config::new(); let db_path = ProjectDirs::from("com", "elliehuxtable", "atuin") @@ -122,18 +104,13 @@ impl Settings { .data_dir() .join("session"); - s.set_default("local.db_path", db_path.to_str())?; - s.set_default("local.key_path", key_path.to_str())?; - s.set_default("local.session_path", session_path.to_str())?; - s.set_default("local.dialect", "us")?; - s.set_default("local.auto_sync", true)?; - s.set_default("local.sync_frequency", "5m")?; - s.set_default("local.sync_address", "https://api.atuin.sh")?; - - s.set_default("server.host", "127.0.0.1")?; - s.set_default("server.port", 8888)?; - s.set_default("server.open_registration", false)?; - s.set_default("server.db_uri", "default_uri")?; + s.set_default("db_path", db_path.to_str())?; + s.set_default("key_path", key_path.to_str())?; + s.set_default("session_path", session_path.to_str())?; + s.set_default("dialect", "us")?; + s.set_default("auto_sync", true)?; + s.set_default("sync_frequency", "5m")?; + s.set_default("sync_address", "https://api.atuin.sh")?; if config_file.exists() { s.merge(ConfigFile::with_name(config_file.to_str().unwrap()))?; @@ -146,24 +123,24 @@ impl Settings { s.merge(Environment::with_prefix("atuin").separator("_"))?; // all paths should be expanded - let db_path = s.get_str("local.db_path")?; + let db_path = s.get_str("db_path")?; let db_path = shellexpand::full(db_path.as_str())?; - s.set("local.db_path", db_path.to_string())?; + s.set("db_path", db_path.to_string())?; - let key_path = s.get_str("local.key_path")?; + let key_path = s.get_str("key_path")?; let key_path = shellexpand::full(key_path.as_str())?; - s.set("local.key_path", key_path.to_string())?; + s.set("key_path", key_path.to_string())?; - let session_path = s.get_str("local.session_path")?; + let session_path = s.get_str("session_path")?; let session_path = shellexpand::full(session_path.as_str())?; - s.set("local.session_path", session_path.to_string())?; + s.set("session_path", session_path.to_string())?; // Finally, set the auth token if Path::new(session_path.to_string().as_str()).exists() { let token = std::fs::read_to_string(session_path.to_string())?; - s.set("local.session_token", token.trim())?; + s.set("session_token", token.trim())?; } else { - s.set("local.session_token", "not logged in")?; + s.set("session_token", "not logged in")?; } s.try_into() diff --git a/src/local/sync.rs b/atuin-client/src/sync.rs similarity index 90% rename from src/local/sync.rs rename to atuin-client/src/sync.rs index e0feb759..0ca8d3a6 100644 --- a/src/local/sync.rs +++ b/atuin-client/src/sync.rs @@ -3,11 +3,12 @@ use std::convert::TryInto; use chrono::prelude::*; use eyre::Result; -use crate::local::api_client; -use crate::local::database::Database; -use crate::local::encryption::{encrypt, load_key}; -use crate::settings::{Local, Settings, HISTORY_PAGE_SIZE}; -use crate::{api::AddHistoryRequest, utils::hash_str}; +use atuin_common::{api::AddHistoryRequest, utils::hash_str}; + +use crate::api_client; +use crate::database::Database; +use crate::encryption::{encrypt, load_key}; +use crate::settings::{Settings, HISTORY_PAGE_SIZE}; // Currently sync is kinda naive, and basically just pages backwards through // history. This means newly added stuff shows up properly! We also just use @@ -33,7 +34,7 @@ async fn sync_download( let mut last_sync = if force { Utc.timestamp_millis(0) } else { - Local::last_sync()? + Settings::last_sync()? }; let mut last_timestamp = Utc.timestamp_millis(0); @@ -124,8 +125,8 @@ async fn sync_upload( pub async fn sync(settings: &Settings, force: bool, db: &mut (impl Database + Send)) -> Result<()> { let client = api_client::Client::new( - settings.local.sync_address.as_str(), - settings.local.session_token.as_str(), + settings.sync_address.as_str(), + settings.session_token.as_str(), load_key(settings)?, ); @@ -135,7 +136,7 @@ pub async fn sync(settings: &Settings, force: bool, db: &mut (impl Database + Se debug!("sync downloaded {}", download.0); - Local::save_sync_time()?; + Settings::save_sync_time()?; Ok(()) } diff --git a/atuin-common/Cargo.toml b/atuin-common/Cargo.toml new file mode 100644 index 00000000..efe7da63 --- /dev/null +++ b/atuin-common/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "atuin-common" +version = "0.1.0" +authors = ["Ellie Huxtable "] +edition = "2018" +license = "MIT" +description = "common library for atuin" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rust-crypto = "^0.2" +sodiumoxide = "0.2.6" +chrono = { version = "0.4", features = ["serde"] } +eyre = "0.6" +serde_derive = "1.0.125" +serde = "1.0.125" +serde_json = "1.0.64" +rmp-serde = "0.15.4" +warp = "0.3" +uuid = { version = "0.8", features = ["v4"] } diff --git a/src/api.rs b/atuin-common/src/api.rs similarity index 100% rename from src/api.rs rename to atuin-common/src/api.rs diff --git a/atuin-common/src/lib.rs b/atuin-common/src/lib.rs new file mode 100644 index 00000000..0a01e10d --- /dev/null +++ b/atuin-common/src/lib.rs @@ -0,0 +1,5 @@ +#[macro_use] +extern crate serde_derive; + +pub mod api; +pub mod utils; diff --git a/src/utils.rs b/atuin-common/src/utils.rs similarity index 87% rename from src/utils.rs rename to atuin-common/src/utils.rs index b395b148..ac5738b3 100644 --- a/src/utils.rs +++ b/atuin-common/src/utils.rs @@ -1,6 +1,7 @@ use crypto::digest::Digest; use crypto::sha2::Sha256; use sodiumoxide::crypto::pwhash::argon2id13; +use uuid::Uuid; pub fn hash_secret(secret: &str) -> String { sodiumoxide::init().unwrap(); @@ -22,3 +23,7 @@ pub fn hash_str(string: &str) -> String { hasher.result_str() } + +pub fn uuid_v4() -> String { + Uuid::new_v4().to_simple().to_string() +} diff --git a/atuin-server/Cargo.toml b/atuin-server/Cargo.toml new file mode 100644 index 00000000..2cf85d26 --- /dev/null +++ b/atuin-server/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "atuin-server" +version = "0.1.0" +authors = ["Ellie Huxtable "] +edition = "2018" +license = "MIT" +description = "server library for atuin" + +[dependencies] +atuin-common = { path = "../atuin-common", version = "0.1.0" } + +log = "0.4" +fern = {version = "0.6.0", features = ["colored"] } +chrono = { version = "0.4", features = ["serde"] } +eyre = "0.6" +directories = "3" +uuid = { version = "0.8", features = ["v4"] } +indicatif = "0.15.0" +whoami = "1.1.2" +chrono-english = "0.1.4" +config = "0.11" +serde_derive = "1.0.125" +serde = "1.0.125" +serde_json = "1.0.64" +rmp-serde = "0.15.4" +unicode-width = "0.1" +sodiumoxide = "0.2.6" +reqwest = { version = "0.11", features = ["blocking", "json"] } +base64 = "0.13.0" +fork = "0.1.18" +parse_duration = "2.1.1" +rand = "0.8.3" +rust-crypto = "^0.2" +tokio = { version = "1", features = ["full"] } +warp = "0.3" +sqlx = { version = "0.5", features = [ "runtime-tokio-native-tls", "uuid", "chrono", "postgres" ] } +async-trait = "0.1.49" +urlencoding = "1.1.1" diff --git a/atuin-server/server.toml b/atuin-server/server.toml new file mode 100644 index 00000000..808f15f1 --- /dev/null +++ b/atuin-server/server.toml @@ -0,0 +1,11 @@ +## host to bind, can also be passed via CLI args +# host = "127.0.0.1" + +## port to bind, can also be passed via CLI args +# port = 8888 + +## whether to allow anyone to register an account +# open_registration = false + +## URI for postgres (using development creds here) +# db_uri="postgres://username:password@localhost/atuin" diff --git a/src/server/auth.rs b/atuin-server/src/auth.rs similarity index 100% rename from src/server/auth.rs rename to atuin-server/src/auth.rs diff --git a/src/server/database.rs b/atuin-server/src/database.rs similarity index 100% rename from src/server/database.rs rename to atuin-server/src/database.rs diff --git a/src/server/handlers/history.rs b/atuin-server/src/handlers/history.rs similarity index 95% rename from src/server/handlers/history.rs rename to atuin-server/src/handlers/history.rs index 4fd6f03f..1aebdde1 100644 --- a/src/server/handlers/history.rs +++ b/atuin-server/src/handlers/history.rs @@ -2,11 +2,11 @@ use std::convert::Infallible; use warp::{http::StatusCode, reply::json}; -use crate::api::{ +use crate::database::Database; +use crate::models::{NewHistory, User}; +use atuin_common::api::{ AddHistoryRequest, CountResponse, ErrorResponse, SyncHistoryRequest, SyncHistoryResponse, }; -use crate::server::database::Database; -use crate::server::models::{NewHistory, User}; pub async fn count( user: User, diff --git a/src/server/handlers/mod.rs b/atuin-server/src/handlers/mod.rs similarity index 100% rename from src/server/handlers/mod.rs rename to atuin-server/src/handlers/mod.rs diff --git a/src/server/handlers/user.rs b/atuin-server/src/handlers/user.rs similarity index 95% rename from src/server/handlers/user.rs rename to atuin-server/src/handlers/user.rs index 782d7dbd..6b142cdc 100644 --- a/src/server/handlers/user.rs +++ b/atuin-server/src/handlers/user.rs @@ -5,13 +5,14 @@ use uuid::Uuid; use warp::http::StatusCode; use warp::reply::json; -use crate::api::{ +use atuin_common::api::{ ErrorResponse, LoginRequest, LoginResponse, RegisterRequest, RegisterResponse, UserResponse, }; -use crate::server::database::Database; -use crate::server::models::{NewSession, NewUser}; +use atuin_common::utils::hash_secret; + +use crate::database::Database; +use crate::models::{NewSession, NewUser}; use crate::settings::Settings; -use crate::utils::hash_secret; pub fn verify_str(secret: &str, verify: &str) -> bool { sodiumoxide::init().unwrap(); @@ -52,7 +53,7 @@ pub async fn register( settings: Settings, db: impl Database + Clone + Send + Sync, ) -> Result, Infallible> { - if !settings.server.open_registration { + if !settings.open_registration { return Ok(Box::new(ErrorResponse::reply( "this server is not open for registrations", StatusCode::BAD_REQUEST, diff --git a/src/server/mod.rs b/atuin-server/src/lib.rs similarity index 84% rename from src/server/mod.rs rename to atuin-server/src/lib.rs index d5e083df..36b6ffa7 100644 --- a/src/server/mod.rs +++ b/atuin-server/src/lib.rs @@ -4,11 +4,18 @@ use eyre::Result; use crate::settings::Settings; +#[macro_use] +extern crate log; + +#[macro_use] +extern crate serde_derive; + pub mod auth; pub mod database; pub mod handlers; pub mod models; pub mod router; +pub mod settings; pub async fn launch(settings: &Settings, host: String, port: u16) -> Result<()> { // routes to run: diff --git a/src/server/models.rs b/atuin-server/src/models.rs similarity index 100% rename from src/server/models.rs rename to atuin-server/src/models.rs diff --git a/src/server/router.rs b/atuin-server/src/router.rs similarity index 95% rename from src/server/router.rs rename to atuin-server/src/router.rs index ed317ab2..d106068d 100644 --- a/src/server/router.rs +++ b/atuin-server/src/router.rs @@ -3,10 +3,12 @@ use std::convert::Infallible; use eyre::Result; use warp::Filter; +use atuin_common::api::SyncHistoryRequest; + use super::handlers; use super::{database::Database, database::Postgres}; -use crate::server::models::User; -use crate::{api::SyncHistoryRequest, settings::Settings}; +use crate::models::User; +use crate::settings::Settings; fn with_settings( settings: Settings, @@ -55,7 +57,7 @@ fn with_user( pub async fn router( settings: &Settings, ) -> Result + Clone> { - let postgres = Postgres::new(settings.server.db_uri.as_str()).await?; + let postgres = Postgres::new(settings.db_uri.as_str()).await?; let index = warp::get().and(warp::path::end()).map(handlers::index); let count = warp::get() diff --git a/atuin-server/src/settings.rs b/atuin-server/src/settings.rs new file mode 100644 index 00000000..596b9018 --- /dev/null +++ b/atuin-server/src/settings.rs @@ -0,0 +1,57 @@ +use std::fs::{create_dir_all, File}; +use std::io::prelude::*; +use std::path::PathBuf; + +use config::{Config, Environment, File as ConfigFile}; +use directories::ProjectDirs; +use eyre::{eyre, Result}; + +pub const HISTORY_PAGE_SIZE: i64 = 100; + +#[derive(Clone, Debug, Deserialize)] +pub struct Settings { + pub host: String, + pub port: u16, + pub db_uri: String, + pub open_registration: bool, +} + +impl Settings { + pub fn new() -> Result { + let config_dir = ProjectDirs::from("com", "elliehuxtable", "atuin").unwrap(); + let config_dir = config_dir.config_dir(); + + create_dir_all(config_dir)?; + + let config_file = if let Ok(p) = std::env::var("ATUIN_CONFIG") { + PathBuf::from(p) + } else { + let mut config_file = PathBuf::new(); + config_file.push(config_dir); + config_file.push("server.toml"); + config_file + }; + + // create the config file if it does not exist + + let mut s = Config::new(); + + if config_file.exists() { + s.merge(ConfigFile::with_name(config_file.to_str().unwrap()))?; + } else { + let example_config = include_bytes!("../server.toml"); + let mut file = File::create(config_file)?; + file.write_all(example_config)?; + } + + s.set_default("host", "127.0.0.1")?; + s.set_default("port", 8888)?; + s.set_default("open_registration", false)?; + s.set_default("db_uri", "default_uri")?; + + s.merge(Environment::with_prefix("atuin").separator("_"))?; + + s.try_into() + .map_err(|e| eyre!("failed to deserialize: {}", e)) + } +} diff --git a/src/command/history.rs b/src/command/history.rs index 627efae4..2b691bac 100644 --- a/src/command/history.rs +++ b/src/command/history.rs @@ -4,10 +4,10 @@ use eyre::Result; use fork::{fork, Fork}; use structopt::StructOpt; -use crate::local::database::Database; -use crate::local::history::History; -use crate::local::sync; -use crate::settings::Settings; +use atuin_client::database::Database; +use atuin_client::history::History; +use atuin_client::settings::Settings; +use atuin_client::sync; #[derive(StructOpt)] pub enum Cmd { @@ -79,7 +79,7 @@ impl Cmd { db.update(&h)?; - if settings.local.should_sync()? { + if settings.should_sync()? { match fork() { Ok(Fork::Parent(child)) => { debug!("launched sync background process with PID {}", child); diff --git a/src/command/import.rs b/src/command/import.rs index ae927aaf..56fb30a7 100644 --- a/src/command/import.rs +++ b/src/command/import.rs @@ -5,9 +5,9 @@ use directories::UserDirs; use eyre::{eyre, Result}; use structopt::StructOpt; -use crate::local::database::Database; -use crate::local::history::History; -use crate::local::import::Zsh; +use atuin_client::database::Database; +use atuin_client::history::History; +use atuin_client::import::Zsh; use indicatif::ProgressBar; #[derive(StructOpt)] diff --git a/src/command/login.rs b/src/command/login.rs index 636ac0d3..eaeb297f 100644 --- a/src/command/login.rs +++ b/src/command/login.rs @@ -5,7 +5,7 @@ use std::io::prelude::*; use eyre::{eyre, Result}; use structopt::StructOpt; -use crate::settings::Settings; +use atuin_client::settings::Settings; #[derive(StructOpt)] #[structopt(setting(structopt::clap::AppSettings::DeriveDisplayOrder))] @@ -26,7 +26,7 @@ impl Cmd { map.insert("username", self.username.clone()); map.insert("password", self.password.clone()); - let url = format!("{}/login", settings.local.sync_address); + let url = format!("{}/login", settings.sync_address); let client = reqwest::blocking::Client::new(); let resp = client.post(url).json(&map).send()?; @@ -38,11 +38,11 @@ impl Cmd { let session = resp.json::>()?; let session = session["session"].clone(); - let session_path = settings.local.session_path.as_str(); + let session_path = settings.session_path.as_str(); let mut file = File::create(session_path)?; file.write_all(session.as_bytes())?; - let key_path = settings.local.key_path.as_str(); + let key_path = settings.key_path.as_str(); let mut file = File::create(key_path)?; file.write_all(&base64::decode(self.key.clone())?)?; diff --git a/src/command/mod.rs b/src/command/mod.rs index cd857e9f..6fd52613 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -1,9 +1,12 @@ +use std::path::PathBuf; + use eyre::Result; use structopt::StructOpt; -use uuid::Uuid; -use crate::local::database::Database; -use crate::settings::Settings; +use atuin_client::database::Sqlite; +use atuin_client::settings::Settings as ClientSettings; +use atuin_common::utils::uuid_v4; +use atuin_server::settings::Settings as ServerSettings; mod event; mod history; @@ -58,30 +61,33 @@ pub enum AtuinCmd { Key, } -pub fn uuid_v4() -> String { - Uuid::new_v4().to_simple().to_string() -} - impl AtuinCmd { - pub async fn run(self, db: &mut T, settings: &Settings) -> Result<()> { - match self { - Self::History(history) => history.run(settings, db).await, - Self::Import(import) => import.run(db), - Self::Server(server) => server.run(settings).await, - Self::Stats(stats) => stats.run(db, settings), - Self::Init => init::init(), - Self::Search { query } => search::run(&query, db), + pub async fn run(self) -> Result<()> { + let client_settings = ClientSettings::new()?; + let server_settings = ServerSettings::new()?; - Self::Sync { force } => sync::run(settings, force, db).await, - Self::Login(l) => l.run(settings), + let db_path = PathBuf::from(client_settings.db_path.as_str()); + + let mut db = Sqlite::new(db_path)?; + + match self { + Self::History(history) => history.run(&client_settings, &mut db).await, + Self::Import(import) => import.run(&mut db), + Self::Server(server) => server.run(&server_settings).await, + Self::Stats(stats) => stats.run(&mut db, &client_settings), + Self::Init => init::init(), + Self::Search { query } => search::run(&query, &mut db), + + Self::Sync { force } => sync::run(&client_settings, force, &mut db).await, + Self::Login(l) => l.run(&client_settings), Self::Register(r) => register::run( - settings, + &client_settings, r.username.as_str(), r.email.as_str(), r.password.as_str(), ), Self::Key => { - let key = std::fs::read(settings.local.key_path.as_str())?; + let key = std::fs::read(client_settings.key_path.as_str())?; println!("{}", base64::encode(key)); Ok(()) } diff --git a/src/command/register.rs b/src/command/register.rs index 62bbeaeb..1126645a 100644 --- a/src/command/register.rs +++ b/src/command/register.rs @@ -5,7 +5,7 @@ use std::io::prelude::*; use eyre::{eyre, Result}; use structopt::StructOpt; -use crate::settings::Settings; +use atuin_client::settings::Settings; #[derive(StructOpt)] #[structopt(setting(structopt::clap::AppSettings::DeriveDisplayOrder))] @@ -26,7 +26,7 @@ pub fn run(settings: &Settings, username: &str, email: &str, password: &str) -> map.insert("email", email); map.insert("password", password); - let url = format!("{}/user/{}", settings.local.sync_address, username); + let url = format!("{}/user/{}", settings.sync_address, username); let resp = reqwest::blocking::get(url)?; if resp.status().is_success() { @@ -34,7 +34,7 @@ pub fn run(settings: &Settings, username: &str, email: &str, password: &str) -> return Ok(()); } - let url = format!("{}/register", settings.local.sync_address); + let url = format!("{}/register", settings.sync_address); let client = reqwest::blocking::Client::new(); let resp = client.post(url).json(&map).send()?; @@ -46,7 +46,7 @@ pub fn run(settings: &Settings, username: &str, email: &str, password: &str) -> let session = resp.json::>()?; let session = session["session"].clone(); - let path = settings.local.session_path.as_str(); + let path = settings.session_path.as_str(); let mut file = File::create(path)?; file.write_all(session.as_bytes())?; diff --git a/src/command/search.rs b/src/command/search.rs index d7b477da..773c112f 100644 --- a/src/command/search.rs +++ b/src/command/search.rs @@ -14,9 +14,10 @@ use tui::{ }; use unicode_width::UnicodeWidthStr; +use atuin_client::database::Database; +use atuin_client::history::History; + use crate::command::event::{Event, Events}; -use crate::local::database::Database; -use crate::local::history::History; const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/src/command/server.rs b/src/command/server.rs index a7835092..2fcf23d6 100644 --- a/src/command/server.rs +++ b/src/command/server.rs @@ -1,8 +1,8 @@ use eyre::Result; use structopt::StructOpt; -use crate::server; -use crate::settings::Settings; +use atuin_server::launch; +use atuin_server::settings::Settings; #[derive(StructOpt)] pub enum Cmd { @@ -23,13 +23,12 @@ impl Cmd { pub async fn run(&self, settings: &Settings) -> Result<()> { match self { Self::Start { host, port } => { - let host = host.as_ref().map_or( - settings.server.host.clone(), - std::string::ToString::to_string, - ); - let port = port.map_or(settings.server.port, |p| p); + let host = host + .as_ref() + .map_or(settings.host.clone(), std::string::ToString::to_string); + let port = port.map_or(settings.port, |p| p); - server::launch(settings, host, port).await + launch(settings, host, port).await } } } diff --git a/src/command/stats.rs b/src/command/stats.rs index 694484bc..0da303d7 100644 --- a/src/command/stats.rs +++ b/src/command/stats.rs @@ -8,9 +8,9 @@ use cli_table::{format::Justify, print_stdout, Cell, Style, Table}; use eyre::{eyre, Result}; use structopt::StructOpt; -use crate::local::database::Database; -use crate::local::history::History; -use crate::settings::Settings; +use atuin_client::database::Database; +use atuin_client::history::History; +use atuin_client::settings::Settings; #[derive(StructOpt)] pub enum Cmd { @@ -80,7 +80,7 @@ impl Cmd { words.join(" ") }; - let start = match settings.local.dialect.to_lowercase().as_str() { + let start = match settings.dialect.to_lowercase().as_str() { "uk" => parse_date_string(&words, Local::now(), Dialect::Uk)?, _ => parse_date_string(&words, Local::now(), Dialect::Us)?, }; diff --git a/src/command/sync.rs b/src/command/sync.rs index 88217b3c..d70b554f 100644 --- a/src/command/sync.rs +++ b/src/command/sync.rs @@ -1,8 +1,8 @@ use eyre::Result; -use crate::local::database::Database; -use crate::local::sync; -use crate::settings::Settings; +use atuin_client::database::Database; +use atuin_client::settings::Settings; +use atuin_client::sync; pub async fn run(settings: &Settings, force: bool, db: &mut (impl Database + Send)) -> Result<()> { sync::sync(settings, force, db).await?; diff --git a/src/main.rs b/src/main.rs index 0045a943..c116d1f3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,29 +1,16 @@ #![warn(clippy::pedantic, clippy::nursery)] #![allow(clippy::use_self)] // not 100% reliable -use std::path::PathBuf; - -use eyre::{eyre, Result}; +use eyre::Result; use fern::colors::{Color, ColoredLevelConfig}; -use human_panic::setup_panic; use structopt::{clap::AppSettings, StructOpt}; #[macro_use] extern crate log; -#[macro_use] -extern crate serde_derive; - use command::AtuinCmd; -use local::database::Sqlite; -use settings::Settings; -mod api; mod command; -mod local; -mod server; -mod settings; -mod utils; #[derive(StructOpt)] #[structopt( @@ -33,28 +20,13 @@ mod utils; global_settings(&[AppSettings::ColoredHelp, AppSettings::DeriveDisplayOrder]) )] struct Atuin { - #[structopt(long, parse(from_os_str), help = "db file path")] - db: Option, - #[structopt(subcommand)] atuin: AtuinCmd, } impl Atuin { - async fn run(self, settings: &Settings) -> Result<()> { - let db_path = if let Some(db_path) = self.db { - let path = db_path - .to_str() - .ok_or_else(|| eyre!("path {:?} was not valid UTF-8", db_path))?; - let path = shellexpand::full(path)?; - PathBuf::from(path.as_ref()) - } else { - PathBuf::from(settings.local.db_path.as_str()) - }; - - let mut db = Sqlite::new(db_path)?; - - self.atuin.run(&mut db, settings).await + async fn run(self) -> Result<()> { + self.atuin.run().await } } @@ -78,8 +50,5 @@ async fn main() -> Result<()> { .chain(std::io::stdout()) .apply()?; - let settings = Settings::new()?; - setup_panic!(); - - Atuin::from_args().run(&settings).await + Atuin::from_args().run().await }