feat(configure): edit every config option in the configurator

After the NetBox instances, netssh configure now opens a menu with all
sections: SSH defaults, global SSH options (jump host, host key
checking, target, address family, extra args, -o options), SSH rules,
resolver & cache (including the cache file), shortcuts & command alias,
and completion.max_results. SSH options can also be set per instance.

The wizard rebuilt the config from its own fields, so re-running it
dropped global SSH options, ssh.rules and completion.max_results from
the file. It now edits a copy of the loaded config, and save validates
SSH options and rules before writing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Sebastian UnterschützandClaude Opus 5.5 committed 2026-11-13 22:42:13 +01:00
1 parent ac713ac97b
commit d51c040d9f
8 files changed
+630 -174

No files matched your search

+25 -11
View File
@@ -50,32 +50,46 @@ var sshOptionKey = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9]*$`)
// ValidateSSH rejects values that would be passed to ssh in an unexpected form.
func (c *Config) ValidateSSH() error {
if err := c.SSH.SSHOptions.validate("ssh"); err != nil {
if err := c.SSH.SSHOptions.Validate("ssh"); err != nil {
return err
}
for i, rule := range c.SSH.Rules {
if err := rule.SSHOptions.validate(fmt.Sprintf("ssh.rules[%d]", i)); err != nil {
if err := rule.Validate(fmt.Sprintf("ssh.rules[%d]", i)); err != nil {
return err
}
if rule.Match == (SSHMatch{}) {
return fmt.Errorf("ssh.rules[%d]: match must set at least one of tag, instance, kind, name", i)
}
if _, err := path.Match(rule.Match.Name, ""); err != nil {
return fmt.Errorf("ssh.rules[%d]: invalid name pattern %q", i, rule.Match.Name)
}
}
if err := c.NetBox.SSH.validate("netbox.ssh"); err != nil {
if err := c.NetBox.SSH.Validate("netbox.ssh"); err != nil {
return err
}
for _, source := range c.NetBoxes {
if err := source.SSH.validate("netboxes." + source.Name + ".ssh"); err != nil {
if err := source.SSH.Validate("netboxes." + source.Name + ".ssh"); err != nil {
return err
}
}
return nil
}
func (o SSHOptions) validate(scope string) error {
// Validate checks the match and options of a single rule.
func (r SSHRule) Validate(scope string) error {
if err := r.SSHOptions.Validate(scope); err != nil {
return err
}
return r.Match.Validate(scope)
}
// Validate requires at least one selector and a well-formed name pattern.
func (m SSHMatch) Validate(scope string) error {
if m == (SSHMatch{}) {
return fmt.Errorf("%s: match must set at least one of tag, instance, kind, name", scope)
}
if _, err := path.Match(m.Name, ""); err != nil {
return fmt.Errorf("%s: invalid name pattern %q", scope, m.Name)
}
return nil
}
// Validate rejects values that ssh would not accept or would misinterpret.
func (o SSHOptions) Validate(scope string) error {
switch o.HostKeyChecking {
case "", "strict", "accept-new", "off":
default: