ssh-config: make the ssh <name>.vm shortcut opt-in
Before this change, every daemon.Open() wrote a Host *.vm stanza into
~/.ssh/config in a marker-fenced block. That's a real footgun for users
who manage their SSH config declaratively (chezmoi, dotfiles, NixOS):
banger was mutating host state outside its own directory on every
daemon start, easy to miss and hard to audit.
New contract: the daemon only ever writes its own ssh_config file at
~/.config/banger/ssh_config. ~/.ssh/config is untouched unless the user
opts in. `banger vm ssh <name>` still works out of the box — the
shortcut only matters for plain `ssh sandbox.vm` from any terminal.
The opt-in surface is `banger ssh-config`:
banger ssh-config # prints path + include-line +
# install/uninstall hints
banger ssh-config --install # adds `Include <bangerConfig>` to
# ~/.ssh/config inside a marker-fenced
# block; idempotent; migrates any
# legacy inline Host *.vm block from
# pre-opt-in builds
banger ssh-config --uninstall # removes the new Include block AND
# any legacy inline block
Doctor gains a gentle warn-level note when banger's ssh_config exists
but the user hasn't wired it in — not a fail, since the shortcut is
convenience and `banger vm ssh` covers the essential case.
Tests cover: daemon writes banger file and does NOT touch ~/.ssh/config,
Install adds the block, Install is idempotent, Install migrates the
legacy inline block cleanly (removing it, preserving unrelated
entries, adding the new Include block), Uninstall removes both marker
variants, Uninstall is a no-op when ~/.ssh/config is absent, and
UserSSHIncludeInstalled detects both marker shapes.
README reframes the feature as optional convenience.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
99d0811097
commit
108f7a0600
7 changed files with 509 additions and 82 deletions
|
|
@ -33,6 +33,7 @@ func (d *deps) newRootCommand() *cobra.Command {
|
|||
d.newImageCommand(),
|
||||
d.newInternalCommand(),
|
||||
d.newKernelCommand(),
|
||||
newSSHConfigCommand(),
|
||||
newVersionCommand(),
|
||||
d.newPSCommand(),
|
||||
d.newVMCommand(),
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ func TestNewBangerCommandHasExpectedSubcommands(t *testing.T) {
|
|||
for _, sub := range cmd.Commands() {
|
||||
names = append(names, sub.Name())
|
||||
}
|
||||
want := []string{"daemon", "doctor", "image", "internal", "kernel", "ps", "version", "vm"}
|
||||
want := []string{"daemon", "doctor", "image", "internal", "kernel", "ps", "ssh-config", "version", "vm"}
|
||||
if !reflect.DeepEqual(names, want) {
|
||||
t.Fatalf("subcommands = %v, want %v", names, want)
|
||||
}
|
||||
|
|
|
|||
86
internal/cli/commands_ssh_config.go
Normal file
86
internal/cli/commands_ssh_config.go
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"banger/internal/daemon"
|
||||
"banger/internal/paths"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// newSSHConfigCommand exposes the opt-in ergonomics for `ssh <name>.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 (also
|
||||
// cleans up any legacy inline block from pre-opt-in builds).
|
||||
func newSSHConfigCommand() *cobra.Command {
|
||||
var (
|
||||
install bool
|
||||
uninstall bool
|
||||
)
|
||||
cmd := &cobra.Command{
|
||||
Use: "ssh-config",
|
||||
Short: "Manage the optional `ssh <name>.vm` shortcut",
|
||||
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 <name>.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
|
||||
`,
|
||||
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
|
||||
}
|
||||
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 <name>.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 <name>.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 <name>.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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue