package cli import ( "fmt" "strings" "banger/internal/config" "banger/internal/daemon" "banger/internal/paths" "github.com/spf13/cobra" ) // newSSHConfigCommand exposes the opt-in ergonomics for `ssh .vm`. // Default mode prints current status + the exact Include line the user // can paste into ~/.ssh/config themselves. --install does the include // for them inside a marker-fenced block; --uninstall reverses it. func newSSHConfigCommand() *cobra.Command { var ( install bool uninstall bool ) cmd := &cobra.Command{ Use: "ssh-config", Short: "Enable plain 'ssh .vm' from any terminal", Long: `Banger keeps a self-contained SSH client config under its own config directory (never touching ~/.ssh/config on its own). Opt in to the convenience shortcut that lets you run 'ssh .vm' from any terminal, bypassing 'banger vm ssh': banger ssh-config # print status + copy-paste snippet banger ssh-config --install # add an Include line to ~/.ssh/config banger ssh-config --uninstall # remove banger's Include from ~/.ssh/config After --install, 'ssh agent.vm' works the same as 'banger vm ssh agent', including for tools like rsync, scp, and editor remotes. `, Example: strings.TrimSpace(` banger ssh-config --install ssh agent.vm rsync -avz ./code agent.vm:/root/repo/ `), Args: noArgsUsage("usage: banger ssh-config [--install|--uninstall]"), RunE: func(cmd *cobra.Command, args []string) error { if install && uninstall { return fmt.Errorf("use only one of --install or --uninstall") } layout, err := paths.Resolve() if err != nil { return err } cfg, err := config.Load(layout) if err != nil { return err } if err := daemon.SyncVMSSHClientConfig(layout, cfg.SSHKeyPath); err != nil { return err } bangerConfig := daemon.BangerSSHConfigPath(layout) switch { case install: if err := daemon.InstallUserSSHInclude(layout); err != nil { return err } _, err = fmt.Fprintf(cmd.OutOrStdout(), "added Include %s to ~/.ssh/config — `ssh .vm` will now route through banger\n", bangerConfig, ) return err case uninstall: if err := daemon.UninstallUserSSHInclude(); err != nil { return err } _, err = fmt.Fprintln(cmd.OutOrStdout(), "removed banger's entries from ~/.ssh/config") return err default: installed, err := daemon.UserSSHIncludeInstalled() if err != nil { return err } out := cmd.OutOrStdout() fmt.Fprintf(out, "banger ssh_config: %s\n", bangerConfig) if installed { fmt.Fprintln(out, "status: included from ~/.ssh/config") fmt.Fprintln(out, "") fmt.Fprintln(out, "`ssh .vm` is enabled. Run `banger ssh-config --uninstall` to revert.") } else { fmt.Fprintln(out, "status: not included (opt-in)") fmt.Fprintln(out, "") fmt.Fprintln(out, "Enable `ssh .vm` in two ways:") fmt.Fprintln(out, " banger ssh-config --install") fmt.Fprintln(out, "or add this line to ~/.ssh/config yourself:") fmt.Fprintf(out, " Include %s\n", bangerConfig) } return nil } }, } cmd.Flags().BoolVar(&install, "install", false, "add an Include line to ~/.ssh/config") cmd.Flags().BoolVar(&uninstall, "uninstall", false, "remove banger's Include from ~/.ssh/config") return cmd }