Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@ All notable changes to Bayesian SSH will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.1.1] - 2025-08-29

### Fixed
- **Configuration defaults**: Changed default user from hardcoded "admin" to current system user
- **Kerberos default**: Disabled Kerberos by default (changed from `true` to `false`)
- **Documentation**: Updated all examples to reflect new sensible defaults

### Changed
- **Default configuration**: Application now uses current Linux username instead of "admin"
- **Kerberos behavior**: Kerberos authentication is now opt-in rather than default
- **Documentation examples**: Updated configuration commands and JSON examples across all docs

### Technical
- **Dependencies**: Added `whoami` crate for system user detection
- **Configuration**: Updated `AppConfig::default()` implementation
- **Documentation**: Updated README.md, docs/README.md, and docs/advanced-usage.md

## [1.1.0] - 2025-08-28

### Added
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bayesian-ssh"
version = "1.1.0"
version = "1.1.1"
edition = "2021"
authors = ["Abdoufermat5"]
description = "A fast and lightweight SSH session manager with Kerberos support"
Expand Down Expand Up @@ -35,6 +35,7 @@ tracing-subscriber = "0.3"
# Configuration
config = "0.13"
dirs = "5.0"
whoami = "1.5"

# Utilities
chrono = { version = "0.4", features = ["serde"] }
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ The app automatically creates configuration in `~/.config/bayesian-ssh/`:
# View current config
bayesian-ssh config

# Set defaults
bayesian-ssh config --default-user admin --use-kerberos
# Set defaults (Kerberos is disabled by default, current user is used)
bayesian-ssh config --use-kerberos --default-user customuser
```

## 📚 Documentation
Expand Down
12 changes: 6 additions & 6 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ bayesian-ssh list
bayesian-ssh connect "Server Name"

# Connect with custom parameters
bayesian-ssh connect "Server Name" --no-bastion --user admin
bayesian-ssh connect "Server Name" --no-bastion --user customuser

# Show connection details
bayesian-ssh show "Server Name"
Expand All @@ -88,8 +88,8 @@ bayesian-ssh remove "Server Name"
# View current configuration
bayesian-ssh config

# Update configuration
bayesian-ssh config --default-user admin --use-kerberos
# Update configuration (Kerberos disabled by default)
bayesian-ssh config --use-kerberos --default-user customuser

# Set default bastion
bayesian-ssh config --default-bastion bastion.company.com
Expand Down Expand Up @@ -119,10 +119,10 @@ bayesian-ssh import --file /path/to/ssh/config
### Key Configuration Options
```json
{
"default_user": "admin",
"default_user": "current-system-user",
"default_bastion": "bastion.company.com",
"default_bastion_user": "admin",
"use_kerberos_by_default": true,
"default_bastion_user": "current-system-user",
"use_kerberos_by_default": false,
"log_level": "info",
"auto_save_history": true,
"max_history_size": 1000
Expand Down
12 changes: 6 additions & 6 deletions docs/advanced-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
```bash
# Set up default enterprise configuration
bayesian-ssh config \
--default-user admin \
--default-user currentuser \
--default-bastion bastion-server.company.priv \
--use-kerberos

Expand Down Expand Up @@ -106,7 +106,7 @@ bayesian-ssh add "DB EC2" ec2-db.company.com \

# Application instance
bayesian-ssh add "App EC2" ec2-app.company.com \
--user admin \
--user currentuser \
--kerberos false \
--key ~/.ssh/ec2-app-key.pem \
--tags ec2,production,application
Expand Down Expand Up @@ -143,7 +143,7 @@ bayesian-ssh add "K8s Web Pod" web-pod.namespace.svc.cluster.local \

# Service access
bayesian-ssh add "K8s Service" web-service.namespace.svc.cluster.local \
--user admin \
--user currentuser \
--kerberos false \
--tags kubernetes,service,web
```
Expand Down Expand Up @@ -200,12 +200,12 @@ bayesian-ssh add "GCP App" gcp-app.company.com \
```bash
# Primary load balancer
bayesian-ssh add "LB Primary" lb-primary.company.com \
--user admin \
--user currentuser \
--tags loadbalancer,primary,production

# Secondary load balancer
bayesian-ssh add "LB Secondary" lb-secondary.company.com \
--user admin \
--user currentuser \
--tags loadbalancer,secondary,production

# Backend servers
Expand Down Expand Up @@ -431,7 +431,7 @@ kinit -f
ssh -t -A -K user@bastion.company.com

# Test with specific bastion user
bayesian-ssh connect "Target Server" --bastion-user admin
bayesian-ssh connect "Target Server" --bastion-user currentuser

# Test bastion port
bayesian-ssh connect "Target Server" --bastion-port 2222
Expand Down
89 changes: 75 additions & 14 deletions src/cli/commands/connect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ pub async fn execute(
Ok(_) => return Ok(()), // Exact match found and connected
Err(_) => {
// No exact match, try fuzzy search
info!("No exact match found for '{}', attempting fuzzy search", target);
info!(
"No exact match found for '{}', attempting fuzzy search",
target
);
}
}

Expand All @@ -53,14 +56,27 @@ pub async fn execute(
if !recent.is_empty() {
println!("\n📅 Recent connections:");
for (i, conn) in recent.iter().enumerate() {
let last_used = conn.last_used
let last_used = conn
.last_used
.map(|dt| format!(" (last used: {})", format_duration(dt)))
.unwrap_or_else(|| "".to_string());
.unwrap_or_default();
println!(" {}. {}{}", i + 1, conn.name, last_used);
}

if let Some(selection) = interactive_selection(&recent, "Select recent connection")? {
return ssh_service.connect_to_connection(&selection, user, port, kerberos, bastion, no_bastion, bastion_user, key).await;
if let Some(selection) = interactive_selection(&recent, "Select recent connection")?
{
return ssh_service
.connect_to_connection(
&selection,
user,
port,
kerberos,
bastion,
no_bastion,
bastion_user,
key,
)
.await;
}
} else {
println!("No recent connections found.");
Expand All @@ -81,23 +97,49 @@ pub async fn execute(
let input = input.trim().to_lowercase();

if matches!(input.as_str(), "y" | "yes") {
return ssh_service.connect_to_connection(conn, user, port, kerberos, bastion, no_bastion, bastion_user, key).await;
return ssh_service
.connect_to_connection(
conn,
user,
port,
kerberos,
bastion,
no_bastion,
bastion_user,
key,
)
.await;
} else {
println!("Connection cancelled.");
return Ok(());
}
}
_ => {
// Multiple matches - interactive selection
println!("🔍 Found {} similar connections for '{}':", matches.len(), target);
println!(
"🔍 Found {} similar connections for '{}':",
matches.len(),
target
);
println!();

for (i, conn) in matches.iter().enumerate() {
print_connection_info(conn, i + 1);
}

if let Some(selection) = interactive_selection(&matches, "Select connection")? {
return ssh_service.connect_to_connection(&selection, user, port, kerberos, bastion, no_bastion, bastion_user, key).await;
return ssh_service
.connect_to_connection(
&selection,
user,
port,
kerberos,
bastion,
no_bastion,
bastion_user,
key,
)
.await;
}
}
}
Expand All @@ -112,12 +154,21 @@ fn print_connection_info(connection: &crate::models::Connection, index: usize) {
format!(" [{}]", connection.tags.join(", "))
};

let last_used = connection.last_used
let last_used = connection
.last_used
.map(|dt| format!(" (last used: {})", format_duration(dt)))
.unwrap_or_else(|| "".to_string());
.unwrap_or_default();

println!(" {}. {} ({})", index, connection.name, connection.host);
println!(" Tags: {}{}", if tags_str.is_empty() { "none" } else { &tags_str[1..tags_str.len()-1] }, last_used);
println!(
" Tags: {}{}",
if tags_str.is_empty() {
"none"
} else {
&tags_str[1..tags_str.len() - 1]
},
last_used
);
println!();
}

Expand Down Expand Up @@ -148,9 +199,16 @@ fn format_duration(dt: chrono::DateTime<chrono::Utc>) -> String {
}
}

fn interactive_selection(connections: &[crate::models::Connection], prompt: &str) -> Result<Option<crate::models::Connection>> {
fn interactive_selection(
connections: &[crate::models::Connection],
prompt: &str,
) -> Result<Option<crate::models::Connection>> {
loop {
print!("{} [1-{}, 's' to search again, 'q' to quit]: ", prompt, connections.len());
print!(
"{} [1-{}, 's' to search again, 'q' to quit]: ",
prompt,
connections.len()
);
io::stdout().flush()?;

let mut input = String::new();
Expand Down Expand Up @@ -184,7 +242,10 @@ fn interactive_selection(connections: &[crate::models::Connection], prompt: &str
if index >= 1 && index <= connections.len() {
return Ok(Some(connections[index - 1].clone()));
} else {
println!("Invalid selection. Please enter a number between 1 and {}.", connections.len());
println!(
"Invalid selection. Please enter a number between 1 and {}.",
connections.len()
);
}
} else {
println!("Invalid input. Please enter a number, 's' to search again, or 'q' to quit.");
Expand Down
52 changes: 41 additions & 11 deletions src/cli/commands/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ pub async fn execute(
}

// No exact match, try fuzzy search
info!("No exact match found for '{}', attempting fuzzy search", target);
info!(
"No exact match found for '{}', attempting fuzzy search",
target
);

let matches = ssh_service.fuzzy_search(&target, 10).await?;

Expand All @@ -58,13 +61,16 @@ pub async fn execute(
if !recent.is_empty() {
println!("\nRecent connections:");
for (i, conn) in recent.iter().enumerate() {
let last_used = conn.last_used
let last_used = conn
.last_used
.map(|dt| format!(" (last: {})", format_duration(dt)))
.unwrap_or_else(|| "".to_string());
.unwrap_or_default();
println!(" {}. {}{}", i + 1, conn.name, last_used);
}

if let Some(selection) = interactive_selection(&recent, "Select connection to edit")? {
if let Some(selection) =
interactive_selection(&recent, "Select connection to edit")?
{
return update_connection(
ssh_service,
selection,
Expand Down Expand Up @@ -123,7 +129,11 @@ pub async fn execute(
}
_ => {
// Multiple matches - interactive selection
println!("Found {} similar connections for '{}':", matches.len(), target);
println!(
"Found {} similar connections for '{}':",
matches.len(),
target
);
println!();

for (i, conn) in matches.iter().enumerate() {
Expand Down Expand Up @@ -154,6 +164,7 @@ pub async fn execute(
Ok(())
}

#[allow(clippy::too_many_arguments)]
async fn update_connection(
ssh_service: SshService,
mut connection: crate::models::Connection,
Expand Down Expand Up @@ -247,12 +258,21 @@ fn print_connection_info(connection: &crate::models::Connection, index: usize) {
format!(" [{}]", connection.tags.join(", "))
};

let last_used = connection.last_used
let last_used = connection
.last_used
.map(|dt| format!(" (last used: {})", format_duration(dt)))
.unwrap_or_else(|| "".to_string());
.unwrap_or_default();

println!(" {}. {} ({})", index, connection.name, connection.host);
println!(" Tags: {}{}", if tags_str.is_empty() { "none" } else { &tags_str[1..tags_str.len()-1] }, last_used);
println!(
" Tags: {}{}",
if tags_str.is_empty() {
"none"
} else {
&tags_str[1..tags_str.len() - 1]
},
last_used
);
println!();
}

Expand Down Expand Up @@ -283,9 +303,16 @@ fn format_duration(dt: chrono::DateTime<chrono::Utc>) -> String {
}
}

fn interactive_selection(connections: &[crate::models::Connection], prompt: &str) -> Result<Option<crate::models::Connection>> {
fn interactive_selection(
connections: &[crate::models::Connection],
prompt: &str,
) -> Result<Option<crate::models::Connection>> {
loop {
print!("{} [1-{}, 's' to search again, 'q' to quit]: ", prompt, connections.len());
print!(
"{} [1-{}, 's' to search again, 'q' to quit]: ",
prompt,
connections.len()
);
io::stdout().flush()?;

let mut input = String::new();
Expand Down Expand Up @@ -318,7 +345,10 @@ fn interactive_selection(connections: &[crate::models::Connection], prompt: &str
if index >= 1 && index <= connections.len() {
return Ok(Some(connections[index - 1].clone()));
} else {
println!("Invalid selection. Please enter a number between 1 and {}.", connections.len());
println!(
"Invalid selection. Please enter a number between 1 and {}.",
connections.len()
);
}
} else {
println!("Invalid input. Please enter a number, 's' to search again, or 'q' to quit.");
Expand Down
Loading