package main import ( "context" "fmt" "os" "strings" "time" "github.com/spf13/cobra" "git.tukangketik.id/swanadiva/hostkeeper/internal/models" "git.tukangketik.id/swanadiva/hostkeeper/pkg/config" "git.tukangketik.id/swanadiva/hostkeeper/pkg/storage" ) var ( editHostname string editPort int editUser string editPassword string editKeyPath string editAuthType string editGroup string editTags []string editNotes string editName string ) // editCmd represents the edit command var editCmd = &cobra.Command{ Use: "edit ", Short: "Edit a saved SSH host", Long: `Edit an existing SSH host configuration in HostKeeper. You can update fields using flags or interactively. Examples: # Edit hostname and port hostkeeper edit myserver --host 10.0.0.1 --port 2222 # Change username and auth hostkeeper edit myserver --user root --auth-type key # Edit interactively hostkeeper edit myserver`, Args: cobra.ExactArgs(1), RunE: runEditHost, } func init() { editCmd.Flags().StringVar(&editHostname, "host", "", "new hostname or IP address") editCmd.Flags().IntVar(&editPort, "port", 0, "new SSH port") editCmd.Flags().StringVar(&editUser, "user", "", "new SSH username") editCmd.Flags().StringVar(&editPassword, "password", "", "new SSH password") editCmd.Flags().StringVar(&editKeyPath, "key", "", "new path to SSH private key") editCmd.Flags().StringVar(&editAuthType, "auth-type", "", "new authentication type: password, key, or both") editCmd.Flags().StringVar(&editGroup, "group", "", "new host group") editCmd.Flags().StringSliceVar(&editTags, "tags", nil, "new tags (comma-separated)") editCmd.Flags().StringVar(&editNotes, "notes", "", "new notes") editCmd.Flags().StringVar(&editName, "name", "", "new host name") rootCmd.AddCommand(editCmd) } func runEditHost(cmd *cobra.Command, args []string) error { hostIdentifier := args[0] cfg := appCfg if cfg == nil { var err error cfg, err = config.New() if err != nil { return fmt.Errorf("failed to initialize config: %w", err) } } store, err := newStorage(cfg) if err != nil { return fmt.Errorf("failed to initialize storage: %w", err) } ctx := context.Background() host, err := findHost(ctx, store, hostIdentifier) if err != nil { return err } // Check if any flags were provided flagProvided := cmd.Flags().Changed("host") || cmd.Flags().Changed("port") || cmd.Flags().Changed("user") || cmd.Flags().Changed("password") || cmd.Flags().Changed("key") || cmd.Flags().Changed("auth-type") || cmd.Flags().Changed("group") || cmd.Flags().Changed("tags") || cmd.Flags().Changed("notes") || cmd.Flags().Changed("name") if !flagProvided { return editHostInteractive(cfg, store, host) } // Apply flag-based updates if cmd.Flags().Changed("host") { host.Hostname = editHostname } if cmd.Flags().Changed("port") { if editPort > 0 { host.Port = editPort } else { host.Port = cfg.GetAppConfig().DefaultPort } } if cmd.Flags().Changed("user") { host.Username = editUser } if cmd.Flags().Changed("password") { host.Auth.Password = editPassword } if cmd.Flags().Changed("auth-type") { host.Auth.Type = editAuthType } if cmd.Flags().Changed("key") { data, err := os.ReadFile(editKeyPath) if err != nil { return fmt.Errorf("failed to read key file: %w", err) } host.Auth.Password = string(data) } if cmd.Flags().Changed("group") { host.Group = editGroup } if cmd.Flags().Changed("tags") { host.Tags = editTags } if cmd.Flags().Changed("notes") { host.Notes = editNotes } if cmd.Flags().Changed("name") { host.Name = editName } host.UpdatedAt = time.Now() if err := store.SaveHost(ctx, host); err != nil { return fmt.Errorf("failed to update host: %w", err) } fmt.Printf("✓ Host '%s' updated successfully\n", host.Name) return nil } func editHostInteractive(cfg *config.Config, store *storage.JSONStorage, host *models.Host) error { fmt.Println("╔══════════════════════════════════════╗") fmt.Println("║ Edit SSH Host ║") fmt.Println("╚══════════════════════════════════════╝") fmt.Println() fmt.Println("Press Enter to keep the current value.") fmt.Println() // Name fmt.Printf("Host Name [%s]: ", host.Name) var name string fmt.Scanln(&name) if name != "" { host.Name = name } // Hostname fmt.Printf("Hostname or IP [%s]: ", host.Hostname) var hostname string fmt.Scanln(&hostname) if hostname != "" { host.Hostname = hostname } // Port defaultPort := cfg.GetAppConfig().DefaultPort fmt.Printf("Port [%d]: ", host.Port) var portInput string fmt.Scanln(&portInput) if portInput != "" { fmt.Sscanf(portInput, "%d", &host.Port) } else if host.Port == 0 { host.Port = defaultPort } // Username fmt.Printf("Username [%s]: ", host.Username) var username string fmt.Scanln(&username) if username != "" { host.Username = username } // Auth type currentAuth := host.Auth.Type if currentAuth == "" { currentAuth = "password" } fmt.Printf("Auth Type (password/key/both) [%s]: ", currentAuth) var authType string fmt.Scanln(&authType) if authType != "" { host.Auth.Type = authType } else { host.Auth.Type = currentAuth } // Password if host.Auth.Type == "password" || host.Auth.Type == "both" { prompt := "Password" if host.Auth.Password != "" { prompt += " [********]" } fmt.Printf("%s: ", prompt) var password string fmt.Scanln(&password) if password != "" { host.Auth.Password = password } } // Key path if host.Auth.Type == "key" || host.Auth.Type == "both" { fmt.Printf("Path to private key: ") var keyPath string fmt.Scanln(&keyPath) if keyPath != "" { data, err := os.ReadFile(keyPath) if err != nil { return fmt.Errorf("failed to read key file: %w", err) } host.Auth.Password = string(data) } } // Group fmt.Printf("Group [%s]: ", host.Group) var group string fmt.Scanln(&group) if group != "" { host.Group = group } // Tags currentTags := strings.Join(host.Tags, ",") fmt.Printf("Tags (comma-separated) [%s]: ", currentTags) var tagsInput string fmt.Scanln(&tagsInput) if tagsInput != "" { tags := strings.Split(tagsInput, ",") for i, t := range tags { tags[i] = strings.TrimSpace(t) } host.Tags = tags } // Notes fmt.Printf("Notes [%s]: ", host.Notes) var notes string fmt.Scanln(¬es) if notes != "" { host.Notes = notes } host.UpdatedAt = time.Now() ctx := context.Background() if err := store.SaveHost(ctx, host); err != nil { return fmt.Errorf("failed to update host: %w", err) } fmt.Println() fmt.Printf("✓ Host '%s' updated successfully!\n", host.Name) return nil }