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
2 changes: 1 addition & 1 deletion .github/workflows/pre-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Build
run: cargo build --verbose
- name: Run tests
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
create-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: taiki-e/create-gh-release-action@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -33,7 +33,7 @@ jobs:
# os: windows-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: taiki-e/upload-rust-binary-action@v1
with:
include: LICENSE,README.md
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ jobs:
runs-on: ubuntu-latest
name: Testing
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: Kristories/[email protected]
33 changes: 32 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,38 @@
# .nfs files are created when an open file is removed but is still being accessed
.nfs*

#!! ERROR: mac is undefined. Use list command to see defined gitignore types !!#
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon


# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

### macOS Patch ###
# iCloud generated files
*.icloud

### Rust ###
# Generated by Cargo
Expand Down
7 changes: 3 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
[package]
name = "myssh"
description = "Checking for existing SSH keys"
version = "0.0.1"
authors = ["Wahyu Kristianto <[email protected]>"]
version = "0.0.4"
authors = ["W Kristianto <[email protected]>"]
license = "MIT OR Apache-2.0"
edition = "2021"

[dependencies]
cursive = "0.21"
regex = "1"
cursive = "0.21.1"

[profile.release]
debug = false
Expand Down
99 changes: 85 additions & 14 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use cursive::traits::*;
use cursive::views::{Dialog, SelectView, TextView};
use cursive::Cursive;
use std::fs;
use std::path::Path;
use std::path::{Path, PathBuf};

fn main() {
let mut siv = cursive::default();
Expand All @@ -12,19 +12,17 @@ fn main() {
}

fn show_file_selection(siv: &mut Cursive) {
let home = std::env::var("HOME").expect("HOME environment variable not set");
let ssh_path = Path::new(&home).join(".ssh");
let mut keys = Vec::new();

for entry in fs::read_dir(&ssh_path).expect("Unable to list directory") {
let entry = entry.expect("Unable to read entry");
let path = entry.path();
let file_path = path.display().to_string();

if !file_path.contains("known_hosts") {
keys.push(file_path);
let keys = match list_ssh_files() {
Ok(keys) => keys,
Err(err) => {
siv.add_layer(
Dialog::text(err)
.title("MySSH")
.button("Quit", |s| s.quit()),
);
return;
}
}
};

let mut select = SelectView::new().h_align(HAlign::Center).autojump();
select.add_all_str(keys);
Expand All @@ -40,7 +38,10 @@ fn show_file_selection(siv: &mut Cursive) {
fn show_next_window(siv: &mut Cursive, file_path: &str) {
siv.pop_layer();

let contents = fs::read_to_string(file_path).expect("Unable to read the file");
let contents = match fs::read_to_string(file_path) {
Ok(contents) => contents,
Err(err) => format!("Unable to read file:\n{err}"),
};
let text_view = TextView::new(contents);

siv.add_layer(
Expand All @@ -52,3 +53,73 @@ fn show_next_window(siv: &mut Cursive, file_path: &str) {
.button("Quit", |s| s.quit()),
);
}

fn list_ssh_files() -> Result<Vec<String>, String> {
let home =
std::env::var("HOME").map_err(|_| "HOME environment variable not set".to_string())?;
let ssh_path = Path::new(&home).join(".ssh");
let entries = fs::read_dir(&ssh_path)
.map_err(|err| format!("Unable to list {}: {err}", ssh_path.display()))?;

let mut keys: Vec<String> = entries
.filter_map(Result::ok)
.map(|entry| entry.path())
.filter(|path| should_show_entry(path))
.map(|path| path.display().to_string())
.collect();

keys.sort();

if keys.is_empty() {
return Err(format!("No SSH key files found in {}", ssh_path.display()));
}

Ok(keys)
}

fn should_show_entry(path: &PathBuf) -> bool {
if !path.is_file() {
return false;
}

let Some(file_name) = path.file_name().and_then(|n| n.to_str()) else {
return false;
};

should_show_file_name(file_name)
}

fn should_show_file_name(file_name: &str) -> bool {
let excluded = [
"known_hosts",
"known_hosts.old",
"authorized_keys",
"config",
];

if excluded.contains(&file_name) {
return false;
}

!file_name.ends_with(".pub")
}

#[cfg(test)]
mod tests {
use super::should_show_file_name;

#[test]
fn excludes_known_hosts() {
assert!(!should_show_file_name("known_hosts"));
}

#[test]
fn excludes_public_keys() {
assert!(!should_show_file_name("id_rsa.pub"));
}

#[test]
fn includes_private_key_like_name() {
assert!(should_show_file_name("id_ed25519"));
}
}
Loading