mirror of
https://github.com/atuinsh/atuin.git
synced 2025-06-26 12:51:35 +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",
|
"semver",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"serde_yaml",
|
||||||
|
"sysinfo",
|
||||||
"time",
|
"time",
|
||||||
"tiny-bip39",
|
"tiny-bip39",
|
||||||
"tokio",
|
"tokio",
|
||||||
@ -804,6 +806,16 @@ version = "2.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
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]]
|
[[package]]
|
||||||
name = "crossbeam-epoch"
|
name = "crossbeam-epoch"
|
||||||
version = "0.9.18"
|
version = "0.9.18"
|
||||||
@ -2108,6 +2120,15 @@ dependencies = [
|
|||||||
"minimal-lexical",
|
"minimal-lexical",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ntapi"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.46.0"
|
version = "0.46.0"
|
||||||
@ -2617,6 +2638,26 @@ dependencies = [
|
|||||||
"bitflags 1.3.2",
|
"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]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
@ -3100,6 +3141,19 @@ dependencies = [
|
|||||||
"syn 2.0.51",
|
"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]]
|
[[package]]
|
||||||
name = "sha1"
|
name = "sha1"
|
||||||
version = "0.10.6"
|
version = "0.10.6"
|
||||||
@ -3560,6 +3614,21 @@ version = "0.1.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
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]]
|
[[package]]
|
||||||
name = "system-configuration"
|
name = "system-configuration"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@ -3976,6 +4045,12 @@ dependencies = [
|
|||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unsafe-libyaml"
|
||||||
|
version = "0.2.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@ -4231,6 +4306,16 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
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]]
|
[[package]]
|
||||||
name = "windows-core"
|
name = "windows-core"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
|
@ -80,6 +80,8 @@ tracing = "0.1"
|
|||||||
cli-clipboard = { version = "0.4.0", optional = true }
|
cli-clipboard = { version = "0.4.0", optional = true }
|
||||||
uuid = { workspace = true }
|
uuid = { workspace = true }
|
||||||
unicode-segmentation = "1.11.0"
|
unicode-segmentation = "1.11.0"
|
||||||
|
serde_yaml = "0.9.32"
|
||||||
|
sysinfo = "0.30.5"
|
||||||
|
|
||||||
|
|
||||||
[dependencies.tracing-subscriber]
|
[dependencies.tracing-subscriber]
|
||||||
|
@ -14,6 +14,7 @@ mod account;
|
|||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
mod default_config;
|
mod default_config;
|
||||||
|
mod doctor;
|
||||||
mod history;
|
mod history;
|
||||||
mod import;
|
mod import;
|
||||||
mod init;
|
mod init;
|
||||||
@ -58,6 +59,9 @@ pub enum Cmd {
|
|||||||
#[command()]
|
#[command()]
|
||||||
Init(init::Cmd),
|
Init(init::Cmd),
|
||||||
|
|
||||||
|
#[command()]
|
||||||
|
Doctor,
|
||||||
|
|
||||||
/// Print example configuration
|
/// Print example configuration
|
||||||
#[command()]
|
#[command()]
|
||||||
DefaultConfig,
|
DefaultConfig,
|
||||||
@ -113,6 +117,8 @@ impl Cmd {
|
|||||||
|
|
||||||
Self::Init(init) => init.run(&settings).await,
|
Self::Init(init) => init.run(&settings).await,
|
||||||
|
|
||||||
|
Self::Doctor => doctor::run(&settings),
|
||||||
|
|
||||||
Self::DefaultConfig => {
|
Self::DefaultConfig => {
|
||||||
default_config::run();
|
default_config::run();
|
||||||
Ok(())
|
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