mirror of
https://github.com/Gu1llaum-3/sshm.git
synced 2026-01-27 03:04:21 +01:00
feat: support custom SSH config files via -c flag
This commit is contained in:
@@ -181,15 +181,18 @@ func ParseSSHConfigFile(configPath string) ([]SSHHost, error) {
|
||||
|
||||
// AddSSHHost adds a new SSH host to the config file
|
||||
func AddSSHHost(host SSHHost) error {
|
||||
configMutex.Lock()
|
||||
defer configMutex.Unlock()
|
||||
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configPath := filepath.Join(homeDir, ".ssh", "config")
|
||||
return AddSSHHostToFile(host, configPath)
|
||||
}
|
||||
|
||||
// AddSSHHostToFile adds a new SSH host to a specific config file
|
||||
func AddSSHHostToFile(host SSHHost, configPath string) error {
|
||||
configMutex.Lock()
|
||||
defer configMutex.Unlock()
|
||||
|
||||
// Create backup before modification if file exists
|
||||
if _, err := os.Stat(configPath); err == nil {
|
||||
@@ -198,8 +201,8 @@ func AddSSHHost(host SSHHost) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if host already exists
|
||||
exists, err := HostExists(host.Name)
|
||||
// Check if host already exists in the specified config file
|
||||
exists, err := HostExistsInFile(host.Name, configPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -354,6 +357,21 @@ func HostExists(hostName string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// HostExistsInFile checks if a host exists in a specific config file
|
||||
func HostExistsInFile(hostName string, configPath string) (bool, error) {
|
||||
hosts, err := ParseSSHConfigFile(configPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, host := range hosts {
|
||||
if host.Name == hostName {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// GetSSHHost retrieves a specific host configuration by name
|
||||
func GetSSHHost(hostName string) (*SSHHost, error) {
|
||||
hosts, err := ParseSSHConfig()
|
||||
@@ -369,17 +387,35 @@ func GetSSHHost(hostName string) (*SSHHost, error) {
|
||||
return nil, fmt.Errorf("host '%s' not found", hostName)
|
||||
}
|
||||
|
||||
// GetSSHHostFromFile retrieves a specific host configuration by name from a specific config file
|
||||
func GetSSHHostFromFile(hostName string, configPath string) (*SSHHost, error) {
|
||||
hosts, err := ParseSSHConfigFile(configPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, host := range hosts {
|
||||
if host.Name == hostName {
|
||||
return &host, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("host '%s' not found", hostName)
|
||||
}
|
||||
|
||||
// UpdateSSHHost updates an existing SSH host configuration
|
||||
func UpdateSSHHost(oldName string, newHost SSHHost) error {
|
||||
configMutex.Lock()
|
||||
defer configMutex.Unlock()
|
||||
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configPath := filepath.Join(homeDir, ".ssh", "config")
|
||||
return UpdateSSHHostInFile(oldName, newHost, configPath)
|
||||
}
|
||||
|
||||
// UpdateSSHHostInFile updates an existing SSH host configuration in a specific file
|
||||
func UpdateSSHHostInFile(oldName string, newHost SSHHost, configPath string) error {
|
||||
configMutex.Lock()
|
||||
defer configMutex.Unlock()
|
||||
|
||||
// Create backup before modification
|
||||
if err := backupConfig(configPath); err != nil {
|
||||
@@ -528,15 +564,18 @@ func UpdateSSHHost(oldName string, newHost SSHHost) error {
|
||||
|
||||
// DeleteSSHHost removes an SSH host configuration from the config file
|
||||
func DeleteSSHHost(hostName string) error {
|
||||
configMutex.Lock()
|
||||
defer configMutex.Unlock()
|
||||
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configPath := filepath.Join(homeDir, ".ssh", "config")
|
||||
return DeleteSSHHostFromFile(hostName, configPath)
|
||||
}
|
||||
|
||||
// DeleteSSHHostFromFile deletes an SSH host from a specific config file
|
||||
func DeleteSSHHostFromFile(hostName, configPath string) error {
|
||||
configMutex.Lock()
|
||||
defer configMutex.Unlock()
|
||||
|
||||
// Create backup before modification
|
||||
if err := backupConfig(configPath); err != nil {
|
||||
|
||||
@@ -13,17 +13,18 @@ import (
|
||||
)
|
||||
|
||||
type addFormModel struct {
|
||||
inputs []textinput.Model
|
||||
focused int
|
||||
err string
|
||||
styles Styles
|
||||
success bool
|
||||
width int
|
||||
height int
|
||||
inputs []textinput.Model
|
||||
focused int
|
||||
err string
|
||||
styles Styles
|
||||
success bool
|
||||
width int
|
||||
height int
|
||||
configFile string
|
||||
}
|
||||
|
||||
// NewAddForm creates a new add form model
|
||||
func NewAddForm(hostname string, styles Styles, width, height int) *addFormModel {
|
||||
func NewAddForm(hostname string, styles Styles, width, height int, configFile string) *addFormModel {
|
||||
// Get current user for default
|
||||
currentUser, _ := user.Current()
|
||||
defaultUser := "root"
|
||||
@@ -100,11 +101,12 @@ func NewAddForm(hostname string, styles Styles, width, height int) *addFormModel
|
||||
inputs[tagsInput].Width = 50
|
||||
|
||||
return &addFormModel{
|
||||
inputs: inputs,
|
||||
focused: nameInput,
|
||||
styles: styles,
|
||||
width: width,
|
||||
height: height,
|
||||
inputs: inputs,
|
||||
focused: nameInput,
|
||||
styles: styles,
|
||||
width: width,
|
||||
height: height,
|
||||
configFile: configFile,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +272,7 @@ func (m standaloneAddForm) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// RunAddForm provides backward compatibility for standalone add form
|
||||
func RunAddForm(hostname string) error {
|
||||
styles := NewStyles(80)
|
||||
addForm := NewAddForm(hostname, styles, 80, 24)
|
||||
addForm := NewAddForm(hostname, styles, 80, 24, "")
|
||||
m := standaloneAddForm{addForm}
|
||||
|
||||
p := tea.NewProgram(m, tea.WithAltScreen())
|
||||
@@ -327,7 +329,12 @@ func (m *addFormModel) submitForm() tea.Cmd {
|
||||
}
|
||||
|
||||
// Add to config
|
||||
err := config.AddSSHHost(host)
|
||||
var err error
|
||||
if m.configFile != "" {
|
||||
err = config.AddSSHHostToFile(host, m.configFile)
|
||||
} else {
|
||||
err = config.AddSSHHost(host)
|
||||
}
|
||||
return addFormSubmitMsg{hostname: name, err: err}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,12 +18,21 @@ type editFormModel struct {
|
||||
originalName string
|
||||
width int
|
||||
height int
|
||||
configFile string
|
||||
}
|
||||
|
||||
// NewEditForm creates a new edit form model
|
||||
func NewEditForm(hostName string, styles Styles, width, height int) (*editFormModel, error) {
|
||||
func NewEditForm(hostName string, styles Styles, width, height int, configFile string) (*editFormModel, error) {
|
||||
// Get the existing host configuration
|
||||
host, err := config.GetSSHHost(hostName)
|
||||
var host *config.SSHHost
|
||||
var err error
|
||||
|
||||
if configFile != "" {
|
||||
host, err = config.GetSSHHostFromFile(hostName, configFile)
|
||||
} else {
|
||||
host, err = config.GetSSHHost(hostName)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -93,6 +102,7 @@ func NewEditForm(hostName string, styles Styles, width, height int) (*editFormMo
|
||||
inputs: inputs,
|
||||
focused: nameInput,
|
||||
originalName: hostName,
|
||||
configFile: configFile,
|
||||
styles: styles,
|
||||
width: width,
|
||||
height: height,
|
||||
@@ -250,7 +260,7 @@ func (m standaloneEditForm) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// RunEditForm provides backward compatibility for standalone edit form
|
||||
func RunEditForm(hostName string) error {
|
||||
styles := NewStyles(80)
|
||||
editForm, err := NewEditForm(hostName, styles, 80, 24)
|
||||
editForm, err := NewEditForm(hostName, styles, 80, 24, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -308,7 +318,12 @@ func (m *editFormModel) submitEditForm() tea.Cmd {
|
||||
}
|
||||
|
||||
// Update the configuration
|
||||
err := config.UpdateSSHHost(m.originalName, host)
|
||||
var err error
|
||||
if m.configFile != "" {
|
||||
err = config.UpdateSSHHostInFile(m.originalName, host, m.configFile)
|
||||
} else {
|
||||
err = config.UpdateSSHHost(m.originalName, host)
|
||||
}
|
||||
return editFormSubmitMsg{hostname: name, err: err}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ type Model struct {
|
||||
deleteHost string
|
||||
historyManager *history.HistoryManager
|
||||
sortMode SortMode
|
||||
configFile string // Path to the SSH config file
|
||||
|
||||
// View management
|
||||
viewMode ViewMode
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
// NewModel creates a new TUI model with the given SSH hosts
|
||||
func NewModel(hosts []config.SSHHost) Model {
|
||||
func NewModel(hosts []config.SSHHost, configFile string) Model {
|
||||
// Initialize the history manager
|
||||
historyManager, err := history.NewHistoryManager()
|
||||
if err != nil {
|
||||
@@ -31,6 +31,7 @@ func NewModel(hosts []config.SSHHost) Model {
|
||||
hosts: hosts,
|
||||
historyManager: historyManager,
|
||||
sortMode: SortByName,
|
||||
configFile: configFile,
|
||||
styles: styles,
|
||||
width: 80,
|
||||
height: 24,
|
||||
@@ -138,8 +139,8 @@ func NewModel(hosts []config.SSHHost) Model {
|
||||
}
|
||||
|
||||
// RunInteractiveMode starts the interactive TUI interface
|
||||
func RunInteractiveMode(hosts []config.SSHHost) error {
|
||||
m := NewModel(hosts)
|
||||
func RunInteractiveMode(hosts []config.SSHHost, configFile string) error {
|
||||
m := NewModel(hosts, configFile)
|
||||
|
||||
// Start the application in alt screen mode for clean output
|
||||
p := tea.NewProgram(m, tea.WithAltScreen())
|
||||
|
||||
@@ -53,7 +53,15 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
} else {
|
||||
// Success: refresh hosts and return to list view
|
||||
hosts, err := config.ParseSSHConfig()
|
||||
var hosts []config.SSHHost
|
||||
var err error
|
||||
|
||||
if m.configFile != "" {
|
||||
hosts, err = config.ParseSSHConfigFile(m.configFile)
|
||||
} else {
|
||||
hosts, err = config.ParseSSHConfig()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return m, tea.Quit
|
||||
}
|
||||
@@ -82,7 +90,15 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
} else {
|
||||
// Success: refresh hosts and return to list view
|
||||
hosts, err := config.ParseSSHConfig()
|
||||
var hosts []config.SSHHost
|
||||
var err error
|
||||
|
||||
if m.configFile != "" {
|
||||
hosts, err = config.ParseSSHConfigFile(m.configFile)
|
||||
} else {
|
||||
hosts, err = config.ParseSSHConfig()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return m, tea.Quit
|
||||
}
|
||||
@@ -183,7 +199,12 @@ func (m Model) handleListViewKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
} else if m.deleteMode {
|
||||
// Confirm deletion
|
||||
err := config.DeleteSSHHost(m.deleteHost)
|
||||
var err error
|
||||
if m.configFile != "" {
|
||||
err = config.DeleteSSHHostFromFile(m.deleteHost, m.configFile)
|
||||
} else {
|
||||
err = config.DeleteSSHHost(m.deleteHost)
|
||||
}
|
||||
if err != nil {
|
||||
// Could display an error message here
|
||||
m.deleteMode = false
|
||||
@@ -192,8 +213,16 @@ func (m Model) handleListViewKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
}
|
||||
// Refresh the hosts list
|
||||
hosts, err := config.ParseSSHConfig()
|
||||
if err != nil {
|
||||
var hosts []config.SSHHost
|
||||
var parseErr error
|
||||
|
||||
if m.configFile != "" {
|
||||
hosts, parseErr = config.ParseSSHConfigFile(m.configFile)
|
||||
} else {
|
||||
hosts, parseErr = config.ParseSSHConfig()
|
||||
}
|
||||
|
||||
if parseErr != nil {
|
||||
// Could display an error message here
|
||||
m.deleteMode = false
|
||||
m.deleteHost = ""
|
||||
@@ -222,7 +251,15 @@ func (m Model) handleListViewKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
}
|
||||
|
||||
return m, tea.ExecProcess(exec.Command("ssh", hostName), func(err error) tea.Msg {
|
||||
// Build the SSH command with the appropriate config file
|
||||
var sshCmd *exec.Cmd
|
||||
if m.configFile != "" {
|
||||
sshCmd = exec.Command("ssh", "-F", m.configFile, hostName)
|
||||
} else {
|
||||
sshCmd = exec.Command("ssh", hostName)
|
||||
}
|
||||
|
||||
return m, tea.ExecProcess(sshCmd, func(err error) tea.Msg {
|
||||
return tea.Quit()
|
||||
})
|
||||
}
|
||||
@@ -233,7 +270,7 @@ func (m Model) handleListViewKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
selected := m.table.SelectedRow()
|
||||
if len(selected) > 0 {
|
||||
hostName := selected[0] // The hostname is in the first column
|
||||
editForm, err := NewEditForm(hostName, m.styles, m.width, m.height)
|
||||
editForm, err := NewEditForm(hostName, m.styles, m.width, m.height, m.configFile)
|
||||
if err != nil {
|
||||
// Handle error - could show in UI
|
||||
return m, nil
|
||||
@@ -246,7 +283,7 @@ func (m Model) handleListViewKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
case "a":
|
||||
if !m.searchMode && !m.deleteMode {
|
||||
// Add a new host
|
||||
m.addForm = NewAddForm("", m.styles, m.width, m.height)
|
||||
m.addForm = NewAddForm("", m.styles, m.width, m.height, m.configFile)
|
||||
m.viewMode = ViewAdd
|
||||
return m, textinput.Blink
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user