diff --git a/src/app/settings.rs b/src/app/settings.rs index 9e40e77..73a3a21 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -13,7 +13,6 @@ pub enum SettingsField { S3Bucket, S3AccessKey, S3SecretKey, - SyncUsage, } impl SettingsField { @@ -37,7 +36,6 @@ impl SettingsField { ]); } } - fields.push(SettingsField::SyncUsage); fields } @@ -53,12 +51,11 @@ impl SettingsField { Self::S3Bucket => "Bucket", Self::S3AccessKey => "Access Key", Self::S3SecretKey => "Secret Key", - Self::SyncUsage => "Sync Usage", - } + } } pub fn is_toggle(self) -> bool { - matches!(self, Self::Backend | Self::SyncUsage) + matches!(self, Self::Backend) } pub fn is_text(self) -> bool { @@ -78,7 +75,6 @@ pub struct SettingsState { pub s3_bucket: String, pub s3_access_key: String, pub s3_secret_key: String, - pub sync_usage: bool, pub active: SettingsField, pub cursor: usize, } @@ -148,7 +144,6 @@ impl Default for SettingsState { s3_bucket: String::new(), s3_access_key: String::new(), s3_secret_key: String::new(), - sync_usage: false, active: SettingsField::SyncPassword, cursor: 0, } @@ -225,7 +220,6 @@ impl App { self.config.settings.s3_access_key.clone().unwrap_or_default(); self.session.settings.s3_secret_key = self.config.settings.s3_secret_key.clone().unwrap_or_default(); - self.session.settings.sync_usage = self.config.settings.sync_usage_count; self.session.settings.active = SettingsField::SyncPassword; self.session.settings.cursor = char_len(self.session.settings.active_text()); self.session.mode = Mode::Settings; @@ -259,7 +253,6 @@ impl App { } else { Some(s3_sk) }; - self.config.settings.sync_usage_count = self.session.settings.sync_usage; self.config.save()?; self.session.mode = Mode::Home; Ok(()) diff --git a/src/config.rs b/src/config.rs index 3356acd..140651a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -64,8 +64,6 @@ pub struct Settings { pub s3_bucket: Option, pub s3_access_key: Option, pub s3_secret_key: Option, - #[serde(default)] - pub sync_usage_count: bool, pub sync_password: Option, } diff --git a/src/connection.rs b/src/connection.rs index 526f57d..c02807f 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -1,9 +1,38 @@ -use crate::config::{ConnectionType, CredentialEntry, SshellConfig}; +use crate::config::{ConnectionType, CredentialEntry, SshellConfig, find_binary}; use anyhow::{Context, Result, bail}; use std::io::Write; use std::process::Command; use tempfile::NamedTempFile; +fn require_binary(name: &str) -> Result<()> { + if find_binary(name).is_some() { + return Ok(()); + } + let hint = match name { + "ssh" => "\n\ + sshell requires `ssh` to connect via SSH.\n\ + \n\ + Install it with:\n\ + macOS: pre-installed (or: xcode-select --install)\n\ + Debian: sudo apt install openssh-client\n\ + Arch: sudo pacman -S openssh\n\ + Fedora: sudo dnf install openssh-clients\n\ + Windows: Settings → Apps → Optional Features → OpenSSH Client" + , + "sshpass" => "\n\ + Password-based SSH login requires `sshpass`.\n\ + Consider switching to private-key auth instead, or install it:\n\ + macOS: brew install hudochenkov/sshpass/sshpass\n\ + Debian: sudo apt install sshpass\n\ + Arch: sudo pacman -S sshpass\n\ + Fedora: sudo dnf install sshpass\n\ + Windows: not available — use private-key auth" + , + _ => "", + }; + bail!("command not found: `{name}`{hint}"); +} + pub fn connect(name: &str, cfg: &SshellConfig) -> Result<()> { let profile = cfg .connections @@ -41,8 +70,10 @@ fn connect_ssh( if auth_ref.is_empty() { bail!("this connection has no credential; edit it and set password or private key"); } + require_binary("ssh")?; match cfg.credential(auth_ref) { Some(CredentialEntry::Password { value, .. }) => { + require_binary("sshpass")?; let args = vec![ "ssh".to_string(), "-o".to_string(), @@ -87,7 +118,7 @@ fn run_sshpass(args: &[String], password: &str) -> Result<()> { .args(args) .env("SSHPASS", password) .status() - .context("failed to run sshpass — is it installed?")?; + .context("failed to run sshpass")?; std::process::exit(status.code().unwrap_or(1)); } diff --git a/src/sync.rs b/src/sync.rs index c822d4e..4d21f77 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -60,9 +60,6 @@ pub(crate) fn build_sync_payload( sync_password: Option<&str>, ) -> Result { let mut payload = cfg.clone(); - payload.settings.sync_password = None; - payload.settings.webdav_password = None; - payload.settings.s3_secret_key = None; let synced_refs: Vec = payload .connections @@ -90,14 +87,10 @@ pub(crate) fn build_sync_payload( "version".to_string(), toml::Value::Integer(payload.version as i64), ); - table.insert("settings".to_string(), to_toml_value(&payload.settings)?); - let mut conns = toml::map::Map::new(); for (name, profile) in &mut payload.connections { profile.local_tags.clear(); - if !payload.settings.sync_usage_count { - profile.usage_count = 0; - } + profile.usage_count = 0; match &mut profile.kind { ConnectionType::Shell { auth_ref, diff --git a/src/ui/view/settings.rs b/src/ui/view/settings.rs index 00be4e2..d172f3d 100644 --- a/src/ui/view/settings.rs +++ b/src/ui/view/settings.rs @@ -1,7 +1,7 @@ use crate::app::{App, FormAction, Mode, SettingsField, SettingsState, char_len}; use crate::config::SyncBackend; use crate::ui::component::{FormRow, badge_span}; -use crate::ui::{ACCENT, GREEN, ORANGE, PURPLE, RED}; +use crate::ui::{ACCENT, ORANGE, PURPLE}; use super::{View, handle_form_nav}; @@ -31,7 +31,7 @@ impl View for SettingsView { let active = settings.active == field; let label = field.label().to_string(); - if i == 2 || matches!(field, SettingsField::SyncUsage) { + if i == 2 { rows.push(FormRow::Separator); } @@ -42,13 +42,6 @@ impl View for SettingsView { SyncBackend::Webdav => badge_span("WebDAV", ORANGE), SyncBackend::S3 => badge_span("S3", PURPLE), }, - SettingsField::SyncUsage => { - if settings.sync_usage { - badge_span("Yes", GREEN) - } else { - badge_span("No", RED) - } - } _ => unreachable!(), }; rows.push(FormRow::Toggle { @@ -131,9 +124,6 @@ fn settings_toggle(settings: &mut SettingsState) { }; settings.ensure_active_visible(); } - SettingsField::SyncUsage => { - settings.sync_usage = !settings.sync_usage; - } _ => {} } }