Prevents curl exit 22 (HTTP 409) when a tag's release already exists. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
netssh
A transparent SSH wrapper that resolves hostnames via NetBox before connecting.
Instead of looking up an IP manually, you just type the hostname as it appears in NetBox:
netssh my-router-01
netssh -p 2222 admin@app-server-03 uptime
netssh looks up the host in NetBox, resolves the right IP using a configurable strategy chain, and replaces the process with the native ssh binary — so all your existing SSH configs, keys, and agent forwarding work without any changes.
Features
- Transparent proxy — replaces itself with
sshviasyscall.Exec, preserving all SSH flags and options - Flexible IP resolution — configurable chain of strategies: management subnet, primary IP, or named interface
- Interactive TUI — fuzzy search with live NetBox queries and 300 ms debouncing (start with
netssh, no arguments) - Persistent cache — successful lookups are cached to
~/.cache/netssh/hosts.jsonfor instant shell completion - Shell completion — tab-complete hostnames from the cache in zsh, bash, and fish
- Default SSH user — set a fallback username once in config instead of typing it every time
Installation
One-liner (Linux & macOS)
curl -fsSL https://git.zb-server.de/Sebi/ssh-netbox-wrapper/raw/branch/main/install.sh | bash
The script detects your OS and architecture, downloads the matching binary from the latest release, verifies the SHA-256 checksum, and installs to /usr/local/bin/netssh (using sudo only if necessary).
To install to a custom directory:
INSTALL_DIR=~/.local/bin curl -fsSL https://git.zb-server.de/Sebi/ssh-netbox-wrapper/raw/branch/main/install.sh | bash
Build from source
git clone ssh://git@git.zb-server.de:30022/Sebi/ssh-netbox-wrapper.git
cd ssh-netbox-wrapper
go build -o netssh ./cmd/netssh
Configuration
Create ~/.config/netssh.yaml:
netbox:
url: https://netbox.example.com
token: your-api-token-here
resolver:
# Strategies are tried in order; the first to return an IP wins.
strategies:
- management_subnet
- primary_ip
# Used by the management_subnet strategy.
management_subnets:
- 10.0.0.0/8
- 172.16.0.0/12
# Used by the interface_name strategy.
interface_name: mgmt0
cache:
ttl: 3600 # seconds; 0 = always query NetBox on connect (cache still used for completion)
# path: ~/.cache/netssh/hosts.json # default
ssh:
default_user: admin # used when no user is specified on the command line
Any value can be overridden with environment variables (NETSSH_NETBOX_URL, NETSSH_NETBOX_TOKEN, etc.) or will be read from the config file.
Usage
SSH wrapper mode
Pass any SSH flags and a NetBox hostname:
netssh my-router-01
netssh -p 2222 admin@app-server-03 uptime
netssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no db-primary
The process is replaced by ssh with the resolved IP — your ~/.ssh/config, agent, and keys all work as normal.
Default username
Set ssh.default_user in the config to avoid typing a username every time:
netssh my-router # → ssh -l admin 10.0.0.1
The default is only applied when no user is specified on the command line. An explicit user always takes precedence:
netssh root@my-router # user@ prefix wins → ssh root@10.0.0.1
netssh -l ops my-router # -l flag wins → ssh -l ops 10.0.0.1
Interactive TUI
Run without arguments to open the interactive search:
netssh
| Key | Action |
|---|---|
| type | filter hosts (300 ms debounce → NetBox query) |
Tab |
autocomplete top result into the search field |
↑ / ↓ |
navigate results |
Enter |
connect to selected host |
Esc / Ctrl+C |
quit |
Cache management
netssh cache list # show all cached entries
netssh cache refresh # re-fetch all hosts from NetBox
netssh cache clear # wipe the cache
Search (for scripting)
netssh search app- # prints matching hostnames, one per line
IP Resolution Strategies
Strategies are tried in the configured order; the first to succeed wins.
| Name | Description |
|---|---|
primary_ip |
Returns the primary_ip4 (or primary_ip6) set in NetBox. No extra API call. |
management_subnet |
Fetches all IPs for the host and returns the first one matching a configured CIDR. |
interface_name |
Fetches IPs attached to a specific named interface (e.g. mgmt0). |
Shell Completion
zsh
netssh completion zsh > "${fpath[1]}/_netssh"
Or add to .zshrc:
source <(netssh completion zsh)
bash
netssh completion bash > /etc/bash_completion.d/netssh
fish
netssh completion fish > ~/.config/fish/completions/netssh.fish
Completions are served from the local cache — no network request on every <Tab>.
Development
go test ./... # run all tests
go build ./... # build all packages
The test suite covers the cache, NetBox client (via httptest), IP resolver chain, and SSH argument parser.
How it works
netsshchecks whether the first argument is a known subcommand (search,cache,completion). If not, it enters SSH wrapper mode.- It parses the SSH arguments to extract the destination hostname, handling all flags that consume an extra argument (
-p,-i,-J, …). - It checks the local cache. If the entry exists and is within the TTL, it connects immediately.
- Otherwise it queries NetBox (
/api/dcim/devices/and/api/virtualization/virtual-machines/in parallel), runs the result through the resolver chain, and caches the IP. - It calls
syscall.Execto replace itself withssh, substituting the hostname with the resolved IP.