package main import ( "fmt" "os" "strings" "text/tabwriter" "github.com/spf13/cobra" "git.zb-server.de/Sebi/ssh-netbox-wrapper/internal/config" "git.zb-server.de/Sebi/ssh-netbox-wrapper/internal/hook" "git.zb-server.de/Sebi/ssh-netbox-wrapper/internal/setup" ) func hostsCmd() *cobra.Command { cmd := &cobra.Command{ Use: "hosts", Short: "Manage static hosts that are not in NetBox", Long: `Static hosts are defined under hosts: in the config. They are found before NetBox, work offline, and support shortcuts, rules (kind: static), aliases, completion, sftp, scp, and tunnels like NetBox hosts.`, RunE: func(cmd *cobra.Command, args []string) error { return listHosts() }, } cmd.AddCommand(hostsListCmd(), hostsAddCmd(), hostsRemoveCmd()) return cmd } func hostsListCmd() *cobra.Command { return &cobra.Command{ Use: "list", Short: "List static hosts", RunE: func(cmd *cobra.Command, args []string) error { return listHosts() }, } } func listHosts() error { cfg, err := config.Load() if err != nil { return err } if len(cfg.Hosts) == 0 { fmt.Println("No static hosts. Add one with: netssh hosts add NAME ADDRESS") return nil } w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) fmt.Fprintln(w, "NAME\tADDRESS\tUSER\tPORT\tTAGS") for _, host := range cfg.Hosts { port := "" if host.Port > 0 { port = fmt.Sprint(host.Port) } fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", host.Name, host.Address, host.User, port, strings.Join(host.Tags, ",")) } return w.Flush() } func hostsAddCmd() *cobra.Command { var host config.StaticHost var replace bool cmd := &cobra.Command{ Use: "add NAME ADDRESS", Short: "Add a static host (or replace one with --replace)", Example: ` netssh hosts add router 192.168.1.1 --user admin netssh hosts add backup backup.example.org --port 2222 --tag offsite`, Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { cfg, err := config.Load() if err != nil { return err } host.Name, host.Address = args[0], args[1] if existing := cfg.StaticHost(host.Name); existing != nil { if !replace { return fmt.Errorf("static host %q already exists; use --replace to overwrite it", existing.Name) } host.SSH = existing.SSH // keep SSH options set in the config or wizard *existing = host } else { cfg.Hosts = append(cfg.Hosts, host) } return saveHosts(cfg) }, } cmd.Flags().StringVar(&host.User, "user", "", "Login user (default: ssh.default_user)") cmd.Flags().IntVar(&host.Port, "port", 0, "SSH port (default: ssh.default_port or 22)") cmd.Flags().StringSliceVar(&host.Tags, "tag", nil, "Tag for ssh.rules (repeatable)") cmd.Flags().BoolVar(&replace, "replace", false, "Overwrite an existing static host") return cmd } func hostsRemoveCmd() *cobra.Command { return &cobra.Command{ Use: "remove NAME", Aliases: []string{"rm"}, Short: "Remove a static host", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { cfg, err := config.Load() if err != nil { return err } for i, host := range cfg.Hosts { if strings.EqualFold(host.Name, args[0]) { cfg.Hosts = append(cfg.Hosts[:i], cfg.Hosts[i+1:]...) return saveHosts(cfg) } } return fmt.Errorf("no static host %q", args[0]) }, ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) { cfg, err := config.Load() if err != nil || len(args) > 0 { return nil, cobra.ShellCompDirectiveNoFileComp } var names []string for _, host := range cfg.Hosts { if strings.HasPrefix(strings.ToLower(host.Name), strings.ToLower(toComplete)) { names = append(names, host.Name) } } return names, cobra.ShellCompDirectiveNoFileComp }, } } // saveHosts writes the config and regenerates the shell aliases. func saveHosts(cfg *config.Config) error { if err := setup.Save(*cfg); err != nil { return err } loaded, err := config.Load() if err != nil { return err } c := newCache(loaded) _ = c.Load() if err := hook.WriteAliasFiles(hook.BuildAliasEntries(c.All(), *loaded)); err != nil { fmt.Fprintf(os.Stderr, "warning: could not write alias files: %v\n", err) } return nil }