11#!/usr/bin/env python3
22
3+ """
4+ ssh-switch: A CLI tool to manage SSH keys and switch between them easily.
5+
6+ This script provides functionalities for listing SSH keys, switching between them,
7+ and managing the ssh-agent environment.
8+ """
9+
310import os
11+ import sys
412import re
513import stat
614import subprocess
715import platform
816from pathlib import Path
9- import questionary
1017from collections import OrderedDict
18+ import questionary
1119
1220SSH_DIR = Path .home () / ".ssh"
1321RECENT_KEYS_FILE = Path .home () / ".ssh_recent_keys"
1624BASHRC_FILE = Path .home () / ".bashrc"
1725ZSHRC_FILE = Path .home () / ".zshrc"
1826
19- max_keys_per_page = 5
27+ MAX_KEYS_PER_PAGE = 5
28+ ACTIVATION_MESSAGE = False
2029
2130def get_os_shell_profile ():
2231 """Return the correct shell profile file based on OS."""
2332 return ZSHRC_FILE if platform .system () == "Darwin" else BASHRC_FILE
2433
34+ def print_ACTIVATION_MESSAGE ():
35+ """Prints a message to activate the SSH agent environment."""
36+ print (f"\n 👉 SSH agent is not correctly configured in the terminal session. To fix this, run:\n " )
37+ print (f"\033 [1;32m source { get_os_shell_profile ()} \033 [0m\n " )
2538
2639def ensure_ssh_agent_auto_start ():
2740 """Ensure SSH agent environment is auto-loaded in shell profile."""
@@ -39,33 +52,45 @@ def ensure_ssh_agent_auto_start():
3952 print (f"✅ Added SSH agent auto-start snippet to { shell_profile } " )
4053
4154def is_ssh_agent_running ():
42- """Check if ssh-agent is running and export environment variables."""
55+
56+ global ACTIVATION_MESSAGE
57+
58+ """Check if ssh-agent is running and export environment variables if needed."""
4359 if not SSH_ENV_FILE .exists ():
4460 return False
4561
4662 with SSH_ENV_FILE .open ("r" ) as f :
4763 env_lines = f .readlines ()
4864
49- ssh_auth_sock , ssh_agent_pid = None , None
65+ file_ssh_auth_sock , file_ssh_agent_pid = None , None
5066
5167 for line in env_lines :
5268 if "SSH_AUTH_SOCK" in line :
53- ssh_auth_sock = line .strip ().split ("=" )[1 ].replace ('"' , "" )
69+ file_ssh_auth_sock = line .strip ().split ("=" )[1 ].replace ('"' , "" )
5470 if "SSH_AGENT_PID" in line :
55- ssh_agent_pid = line .strip ().split ("=" )[1 ].replace ('"' , "" )
71+ file_ssh_agent_pid = line .strip ().split ("=" )[1 ].replace ('"' , "" )
5672
57- if not ssh_auth_sock or not ssh_agent_pid :
73+ if not file_ssh_auth_sock or not file_ssh_agent_pid :
5874 return False
5975
60- # Verify if the SSH_AGENT_PID is still running
61- if subprocess .run (["ps" , "-p" , ssh_agent_pid ], capture_output = True ).returncode == 0 :
62- os .environ ["SSH_AUTH_SOCK" ] = ssh_auth_sock
63- os .environ ["SSH_AGENT_PID" ] = ssh_agent_pid
64- return True
76+ # Get current terminal environment variables
77+ env_ssh_auth_sock = os .environ .get ("SSH_AUTH_SOCK" )
78+ env_ssh_agent_pid = os .environ .get ("SSH_AGENT_PID" )
79+
80+ # Check if the agent process is running
81+ if subprocess .run (["ps" , "-p" , file_ssh_agent_pid ], capture_output = True ).returncode == 0 :
82+ # If the values in the environment and file differ, update them
83+ if file_ssh_auth_sock != env_ssh_auth_sock or file_ssh_agent_pid != env_ssh_agent_pid :
84+ ACTIVATION_MESSAGE = True
85+
86+ return True # Agent is running and environment is up to date
6587
6688 return False # Agent is not running, so we need to start a new one
6789
6890def start_ssh_agent ():
91+
92+ global ACTIVATION_MESSAGE
93+
6994 """Start ssh-agent only if it's not already running."""
7095 if is_ssh_agent_running ():
7196 print ("✅ ssh-agent is already running." )
@@ -88,10 +113,9 @@ def start_ssh_agent():
88113 print ("✅ ssh-agent started successfully." )
89114 else :
90115 print ("❌ Failed to start ssh-agent." )
91- exit (1 )
116+ sys . exit (1 )
92117
93- print ("\n 👉 To activate the SSH agent environment, run:\n " )
94- print (f"\033 [1;32m source { get_os_shell_profile ()} \033 [0m\n " )
118+ ACTIVATION_MESSAGE = True
95119
96120def list_ssh_keys ():
97121 """List all valid SSH private keys in ~/.ssh."""
@@ -100,10 +124,11 @@ def list_ssh_keys():
100124 if file .is_file ()
101125 and not file .suffix
102126 and file .stat ().st_mode & (stat .S_IRWXG | stat .S_IRWXO ) == 0
103- and subprocess .run (["ssh-keygen" , "-y" , "-f" , str (file )], check = False , capture_output = True ).returncode == 0
127+ and subprocess .run (
128+ ["ssh-keygen" , "-y" , "-f" , str (file )], check = False , capture_output = True
129+ ).returncode == 0
104130 )
105131
106-
107132def load_recent_keys ():
108133 """Load recently used SSH keys from a file as strings."""
109134 if RECENT_KEYS_FILE .exists ():
@@ -120,30 +145,33 @@ def save_recent_key(key_path):
120145 recent_keys .remove (key_path ) # Move it to the top
121146
122147 recent_keys .insert (0 , key_path ) # Add as most recent
123- recent_keys = recent_keys [:max_keys_per_page ] # Keep only the last 5 keys
148+ recent_keys = recent_keys [:MAX_KEYS_PER_PAGE ] # Keep only the last 5 keys
124149
125150 with open (RECENT_KEYS_FILE , "w" ) as f :
126151 f .writelines (f"{ key } \n " for key in recent_keys )
127152
128153
129154def switch_ssh_key (key_path ):
155+
156+ global ACTIVATION_MESSAGE
157+
130158 """Switch SSH key by removing old keys and adding the new key."""
131159 if not is_ssh_agent_running ():
132160 print ("❌ ssh-agent is not running." )
133- print ("\n 👉 To activate the SSH agent environment, run:\n " )
134- print (f"\033 [1;32m source { get_os_shell_profile ()} \033 [0m\n " )
161+ ACTIVATION_MESSAGE = True
135162 return
136163
137164 subprocess .run (["ssh-add" , "-D" ], check = False , capture_output = True )
138- result = subprocess .run (["ssh-add" , str (Path (key_path ).resolve ())], check = False , capture_output = True )
165+ result = subprocess .run (
166+ ["ssh-add" , str (Path (key_path ).resolve ())], check = False , capture_output = True
167+ )
139168
140169 if result .returncode == 0 :
141170 print (f"✅ Switched to SSH key: { key_path } " )
142171 save_recent_key (key_path )
143172 else :
144173 print (f"❌ Failed to add SSH key: { result .stderr .strip ()} " )
145174
146-
147175def verify_ssh_agent ():
148176 """Check if ssh-agent is working correctly."""
149177 result = subprocess .run (["ssh-add" , "-l" ], check = False , text = True , capture_output = True )
@@ -182,11 +210,10 @@ def interactive_key_selection():
182210
183211 # Pagination setup
184212 page = 0
185- total_pages = (len (key_list ) + max_keys_per_page - 1 ) // max_keys_per_page # Total pages
186213
187214 while True :
188- start = page * max_keys_per_page
189- end = start + max_keys_per_page
215+ start = page * MAX_KEYS_PER_PAGE
216+ end = start + MAX_KEYS_PER_PAGE
190217 display_keys = key_list [start :end ]
191218
192219 choices = [
@@ -222,6 +249,8 @@ def main():
222249 else :
223250 print ("❌ No key selected. Exiting." )
224251
252+ if ACTIVATION_MESSAGE :
253+ print_ACTIVATION_MESSAGE ()
225254
226255if __name__ == "__main__" :
227- main ()
256+ main ()
0 commit comments