summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEllie Huxtable <ellie@elliehuxtable.com>2024-02-29 15:32:48 +0000
committerGitHub <noreply@github.com>2024-02-29 15:32:48 +0000
commit6d62749e199fe1ee164e6d8e66c2e53c08f8db42 (patch)
tree7094db7af5977a95bcd49f281160885452266216
parent5f0e6dd3076fe61c3e70898d789f4a7087f53d49 (diff)
feat: add atuin doctor (#1796)
* feat add atuin doctor * registered -> logged_in * not logged in, no sync info * add plugin detection * add a hack * clippy * add filesystem detection * add title * hmm * need interactive shell
-rw-r--r--Cargo.lock85
-rw-r--r--atuin/Cargo.toml2
-rw-r--r--atuin/src/command/client.rs6
-rw-r--r--atuin/src/command/client/doctor.rs197
4 files changed, 290 insertions, 0 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 338dab76..ab0eaa31 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -212,6 +212,8 @@ dependencies = [
"semver",
"serde",
"serde_json",
+ "serde_yaml",
+ "sysinfo",
"time",
"tiny-bip39",
"tokio",
@@ -805,6 +807,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
[[package]]
+name = "crossbeam-deque"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2109,6 +2121,15 @@ dependencies = [
]
[[package]]
+name = "ntapi"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2618,6 +2639,26 @@ dependencies = [
]
[[package]]
+name = "rayon"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
name = "redox_syscall"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3101,6 +3142,19 @@ dependencies = [
]
[[package]]
+name = "serde_yaml"
+version = "0.9.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f"
+dependencies = [
+ "indexmap 2.2.3",
+ "itoa",
+ "ryu",
+ "serde",
+ "unsafe-libyaml",
+]
+
+[[package]]
name = "sha1"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3561,6 +3615,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]]
+name = "sysinfo"
+version = "0.30.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fb4f3438c8f6389c864e61221cbc97e9bca98b4daf39a5beb7bea660f528bb2"
+dependencies = [
+ "cfg-if",
+ "core-foundation-sys",
+ "libc",
+ "ntapi",
+ "once_cell",
+ "rayon",
+ "windows",
+]
+
+[[package]]
name = "system-configuration"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3977,6 +4046,12 @@ dependencies = [
]
[[package]]
+name = "unsafe-libyaml"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b"
+
+[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4232,6 +4307,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
+name = "windows"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
+dependencies = [
+ "windows-core",
+ "windows-targets 0.52.3",
+]
+
+[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/atuin/Cargo.toml b/atuin/Cargo.toml
index 21318431..b53a8b5b 100644
--- a/atuin/Cargo.toml
+++ b/atuin/Cargo.toml
@@ -80,6 +80,8 @@ tracing = "0.1"
cli-clipboard = { version = "0.4.0", optional = true }
uuid = { workspace = true }
unicode-segmentation = "1.11.0"
+serde_yaml = "0.9.32"
+sysinfo = "0.30.5"
[dependencies.tracing-subscriber]
diff --git a/atuin/src/command/client.rs b/atuin/src/command/client.rs
index b74c9bc7..5b66aa39 100644
--- a/atuin/src/command/client.rs
+++ b/atuin/src/command/client.rs
@@ -14,6 +14,7 @@ mod account;
mod config;
mod default_config;
+mod doctor;
mod history;
mod import;
mod init;
@@ -58,6 +59,9 @@ pub enum Cmd {
#[command()]
Init(init::Cmd),
+ #[command()]
+ Doctor,
+
/// Print example configuration
#[command()]
DefaultConfig,
@@ -113,6 +117,8 @@ impl Cmd {
Self::Init(init) => init.run(&settings).await,
+ Self::Doctor => doctor::run(&settings),
+
Self::DefaultConfig => {
default_config::run();
Ok(())
diff --git a/atuin/src/command/client/doctor.rs b/atuin/src/command/client/doctor.rs
new file mode 100644
index 00000000..6602c04f
--- /dev/null
+++ b/atuin/src/command/client/doctor.rs
@@ -0,0 +1,197 @@
+use std::process::Command;
+use std::{collections::HashMap, path::PathBuf};
+
+use atuin_client::settings::Settings;
+use colored::Colorize;
+use eyre::Result;
+use serde::{Deserialize, Serialize};
+
+use sysinfo::{get_current_pid, Disks, System};
+
+#[derive(Debug, Serialize, Deserialize)]
+struct ShellInfo {
+ pub name: String,
+
+ // Detect some shell plugins that the user has installed.
+ // I'm just going to start with preexec/blesh
+ pub plugins: Vec<String>,
+}
+
+impl ShellInfo {
+ // HACK ALERT!
+ // Many of the env vars we need to detect are not exported :(
+ // So, we're going to run `env` in a subshell and parse the output
+ // There's a chance this won't work, so it should not be fatal.
+ //
+ // Every shell we support handles `shell -c 'command'`
+ fn env_exists(shell: &str, var: &str) -> bool {
+ let mut cmd = Command::new(shell)
+ .args(["-ic", format!("echo ${var}").as_str()])
+ .output()
+ .map_or(String::new(), |v| {
+ let out = v.stdout;
+ String::from_utf8(out).unwrap_or_default()
+ });
+
+ cmd.retain(|c| !c.is_whitespace());
+
+ !cmd.is_empty()
+ }
+
+ pub fn plugins(shell: &str) -> Vec<String> {
+ // consider a different detection approach if there are plugins
+ // that don't set env vars
+
+ let map = HashMap::from([
+ ("ATUIN_SESSION", "atuin"),
+ ("BLE_ATTACHED", "blesh"),
+ ("bash_preexec_imported", "bash-preexec"),
+ ]);
+
+ map.into_iter()
+ .filter_map(|(env, plugin)| {
+ if ShellInfo::env_exists(shell, env) {
+ return Some(plugin.to_string());
+ }
+
+ None
+ })
+ .collect()
+ }
+
+ pub fn new() -> Self {
+ let sys = System::new_all();
+
+ let process = sys
+ .process(get_current_pid().expect("Failed to get current PID"))
+ .expect("Process with current pid does not exist");
+
+ let parent = sys
+ .process(process.parent().expect("Atuin running with no parent!"))
+ .expect("Process with parent pid does not exist");
+
+ let shell = parent.name().trim().to_lowercase();
+ let shell = shell.strip_prefix('-').unwrap_or(&shell);
+ let name = shell.to_string();
+
+ let plugins = ShellInfo::plugins(name.as_str());
+
+ Self { name, plugins }
+ }
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+struct DiskInfo {
+ pub name: String,
+ pub filesystem: String,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+struct SystemInfo {
+ pub os: String,
+
+ pub arch: String,
+
+ pub version: String,
+ pub disks: Vec<DiskInfo>,
+}
+
+impl SystemInfo {
+ pub fn new() -> Self {
+ let disks = Disks::new_with_refreshed_list();
+ let disks = disks
+ .list()
+ .iter()
+ .map(|d| DiskInfo {
+ name: d.name().to_os_string().into_string().unwrap(),
+ filesystem: d.file_system().to_os_string().into_string().unwrap(),
+ })
+ .collect();
+
+ Self {
+ os: System::name().unwrap_or_else(|| "unknown".to_string()),
+ arch: System::cpu_arch().unwrap_or_else(|| "unknown".to_string()),
+ version: System::os_version().unwrap_or_else(|| "unknown".to_string()),
+ disks,
+ }
+ }
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+struct SyncInfo {
+ /// Whether the main Atuin sync server is in use
+ /// I'm just calling it Atuin Cloud for lack of a better name atm
+ pub cloud: bool,
+ pub records: bool,
+ pub auto_sync: bool,
+
+ pub last_sync: String,
+}
+
+impl SyncInfo {
+ pub fn new(settings: &Settings) -> Self {
+ Self {
+ cloud: settings.sync_address == "https://api.atuin.sh",
+ auto_sync: settings.auto_sync,
+ records: settings.sync.records,
+ last_sync: Settings::last_sync().map_or("no last sync".to_string(), |v| v.to_string()),
+ }
+ }
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+struct AtuinInfo {
+ pub version: String,
+
+ /// Whether the main Atuin sync server is in use
+ /// I'm just calling it Atuin Cloud for lack of a better name atm
+ pub sync: Option<SyncInfo>,
+}
+
+impl AtuinInfo {
+ pub fn new(settings: &Settings) -> Self {
+ let session_path = settings.session_path.as_str();
+ let logged_in = PathBuf::from(session_path).exists();
+
+ let sync = if logged_in {
+ Some(SyncInfo::new(settings))
+ } else {
+ None
+ };
+
+ Self {
+ version: crate::VERSION.to_string(),
+ sync,
+ }
+ }
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+struct DoctorDump {
+ pub atuin: AtuinInfo,
+ pub shell: ShellInfo,
+ pub system: SystemInfo,
+}
+
+impl DoctorDump {
+ pub fn new(settings: &Settings) -> Self {
+ Self {
+ atuin: AtuinInfo::new(settings),
+ shell: ShellInfo::new(),
+ system: SystemInfo::new(),
+ }
+ }
+}
+
+pub fn run(settings: &Settings) -> Result<()> {
+ println!("{}", "Atuin Doctor".bold());
+ println!("Checking for diagnostics");
+ println!("Please include the output below with any bug reports or issues\n");
+
+ let dump = DoctorDump::new(settings);
+
+ let dump = serde_yaml::to_string(&dump)?;
+ println!("{dump}");
+
+ Ok(())
+}