mirror of
https://github.com/atuinsh/atuin.git
synced 2025-06-26 04:41:27 +02:00
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
This commit is contained in:
parent
5f0e6dd307
commit
6d62749e19
85
Cargo.lock
generated
85
Cargo.lock
generated
@ -212,6 +212,8 @@ dependencies = [
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"sysinfo",
|
||||
"time",
|
||||
"tiny-bip39",
|
||||
"tokio",
|
||||
@ -804,6 +806,16 @@ version = "2.4.0"
|
||||
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"
|
||||
@ -2108,6 +2120,15 @@ dependencies = [
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[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"
|
||||
@ -2617,6 +2638,26 @@ dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[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"
|
||||
@ -3100,6 +3141,19 @@ dependencies = [
|
||||
"syn 2.0.51",
|
||||
]
|
||||
|
||||
[[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"
|
||||
@ -3560,6 +3614,21 @@ version = "0.1.2"
|
||||
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"
|
||||
@ -3976,6 +4045,12 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[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"
|
||||
@ -4231,6 +4306,16 @@ version = "0.4.0"
|
||||
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"
|
||||
|
@ -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]
|
||||
|
@ -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(())
|
||||
|
197
atuin/src/command/client/doctor.rs
Normal file
197
atuin/src/command/client/doctor.rs
Normal file
@ -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(())
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user