fix: harden config loading and ssh addresses, stop resolver on API errors

- read netssh.yaml from the user config directory only, not from the
  working directory
- refuse addresses from NetBox or the cache that are neither an IP
  address nor a hostname (ssh read "-oProxyCommand=…" as an option)
- resolver chain stops at an API failure; only "no address" falls
  through to the next strategy
- netssh --help and -h show netssh's help
- start at most one background cache refresh per 5 minutes
- validate the legacy netbox.url
- page through all IP addresses of a host
- CI: run format check, vet, and tests on pull requests and main
- changelog for v0.0.25

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
Sebastian UnterschützandClaude Fable 5.2 committed 2026-11-15 23:25:28 +01:00
1 parent 6e80b733dc
commit 4287b7b650
19 files changed
+317 -30

No files matched your search

+20
View File
@@ -83,6 +83,26 @@ func (c *Cache) NeedsRefresh(d time.Duration) bool {
return time.Since(t) >= d
}
// ClaimRefresh reports whether a background refresh should start now: the last
// full refresh is older than d and no attempt was claimed within retry. It
// records the attempt, so parallel shells and repeated commands start one
// refresh per retry interval instead of one each.
func (c *Cache) ClaimRefresh(d, retry time.Duration) bool {
if !c.NeedsRefresh(d) {
return false
}
attempt := filepath.Join(filepath.Dir(c.refreshStamp), "refresh_attempt")
if info, err := os.Stat(attempt); err == nil && time.Since(info.ModTime()) < retry {
return false
}
if err := os.MkdirAll(filepath.Dir(attempt), 0o755); err != nil {
return true // cannot record the attempt; refreshing is still right
}
// Best effort: two processes racing here may both refresh once.
_ = os.WriteFile(attempt, nil, 0o600)
return true
}
// SetRefreshed records the current time as the last successful full refresh.
func (c *Cache) SetRefreshed() error {
if c.refreshStamp == "" {