Sebastian UnterschützandClaude Opus 5.5 b7fa71fe45 feat: sftp, scp, and named port forwards
Add netssh sftp and netssh scp. Both resolve the host like a connection
does (cache, shortcuts and rewrites, instance/host, default user and
port, SSH options and rules, --jump/--no-jump, --dry-run) and get shell
completion. The per-client flag tables handle the differences from ssh:
the port is -P, -l is a bandwidth limit (the default user goes in as
-o User=), and IPv6 addresses are bracketed in host:path. All remote
scp operands must name the same host.

Add named port forwards under ssh.forwards (global, per instance, per
rule; a more specific layer replaces a forward of the same name),
netssh tunnel HOST NAME|SPEC... (ssh -N with ExitOnForwardFailure) and
netssh --forward NAME. Specs such as 8080:localhost:80 work without
configuration. Forwards are editable in netssh configure and completed
in the shell; the TUI opens SFTP with ctrl+o and a tunnel with ctrl+t.

Host resolution moves out of runSSHWrapper into resolveTarget, shared
by ssh, sftp, scp, tunnel, and the TUI.

Options added by netssh are now inserted before the first operand or
"--": after "--", ssh read them as the destination, and scp stops
reading options at the first operand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 23:05:07 +01:00
2026-10-28 11:23:28 +01:00

netssh

Vibe-coded project — this codebase was written entirely by an AI assistant (Claude) without human code review. Use in production at your own risk.

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 ssh via syscall.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)
  • Recently-used list — TUI opens with your 10 most-recently-connected hosts, no typing needed
  • Tag/kind filter — press Ctrl+F in the TUI to filter by tag:prod or kind:vm
  • User/port override — press Ctrl+E in the TUI to override the SSH user or port before connecting
  • Server notes — multiline local notes or optional NetBox comments, available in the TUI and CLI
  • Persistent cache — successful lookups are cached to ~/.cache/netssh/hosts.json for instant shell completion
  • Full pagination — cache refresh fetches all hosts from NetBox (not just the first 50)
  • Selective refresh — cache refresh --tag prod --kind vm limits what gets synced
  • Setup wizard — interactive first-run onboarding; re-run anytime with netssh configure
  • Shell completion — install without sudo via netssh completion install
  • Default SSH user — set a fallback username once in config instead of typing it every time
  • Default SSH port — set a fallback port in config; injected as -p when not specified on the command line
  • Host shortcuts — type web01 instead of web01.example.com; configurable domain strip, hyphen folding, and regex rewrite rules (fsn1-bx1.hoster.de → fsnbx1)
  • Shell aliases — netssh alias generates shell aliases for all cached hosts
  • Automatic 24h cache refresh — the cache is refreshed in the background every 24 hours
  • Jump hosts and SSH options — proxy_jump, host key checking, connection target, and free -o options per netssh, per NetBox instance, or per tag/name rule — without touching ~/.ssh/config
  • SFTP & SCP — netssh sftp host and netssh scp file host:/tmp/ with the same host lookup, shortcuts, and SSH options as ssh
  • Port forwarding — named forwards in the config (netssh tunnel host web, netssh --forward web host), ad hoc specs, and Ctrl+T in the TUI
  • Dry run — netssh --dry-run host prints the final ssh command instead of connecting
  • Clean uninstall — netssh uninstall removes the binary, profile entries of all versions, completions, cache, and config

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:

curl -fsSL https://git.zb-server.de/Sebi/ssh-netbox-wrapper/raw/branch/main/install.sh | INSTALL_DIR="$HOME/.local/bin" bash

Updating an existing installation

Run the same one-liner again to update to the latest release. The installer uses an existing netssh found in PATH, or /usr/local/bin/netssh for a new installation. INSTALL_DIR explicitly overrides the destination. The replacement is atomic, so an installed binary can be updated while it is running.

The installer checks all required programs before downloading or modifying files: curl, uname, sed, awk, mktemp, rm, dirname, readlink, mkdir, cat, install, mv, cp, chmod, cmp, and either sha256sum (Linux) or shasum (macOS). Bash runs the installer; sudo is checked beforehand only if the target directory requires elevated permissions. Missing programs are listed together; install them with your package manager and retry. Python is not required. SHA-256 verification is mandatory: missing, duplicate or mismatched checksums abort the update before replacing anything.

It also consolidates netssh integration in existing Bash, Zsh and Fish profiles and creates the current shell's rc file when needed. Old standalone netssh hooks, inline host aliases and eval "$(netssh alias)" entries are replaced by a single managed block per rc file. Legacy entries in login profiles are removed as well. Other shell settings, NetBox configuration, cache and local notes are preserved. Every changed existing profile gets a .netssh-backup.XXXXXX backup; symlinks and permissions are preserved. XDG_CONFIG_HOME, XDG_CACHE_HOME and ZDOTDIR are respected. Alias files and installed shell completions are regenerated. Open a new terminal after updating to load the new aliases and hooks.

Release downloads and change history

Each new release includes install.sh, binaries for Linux and macOS (amd64 and arm64), and checksums.txt covering all of these files. The release notes include an installation command pointing directly to that release's install.sh asset and a list of changes since the previous version tag.

See CHANGELOG.md for the change history. The installer attached to a release installs or updates to the latest published binary release.

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

Interactive wizard

On first run (when no config exists), netssh automatically starts an interactive setup wizard. Re-run it at any time to change settings without editing the file manually:

netssh configure

The wizard first lets you add, edit, and remove named NetBox instances and set SSH options per instance. A menu then gives access to every other setting; open only the sections you want to change and choose Save and exit:

Section Settings
SSH defaults ssh.default_user, ssh.default_port
SSH options proxy_jump, host_key_checking, target, address_family, extra_args (one per line), options (Key=Value per line), forwards (NAME L|R|D SPEC per line)
SSH rules add, edit, remove ssh.rules — match by tag, instance, kind, name pattern, each with its own SSH options
Resolver & cache strategies, management subnets, interface name, cache TTL, cache file
Shortcuts & command alias domain suffixes, hyphen stripping, rewrite rules (REGEX => REPLACEMENT per line), shell.alias
Completion completion.max_results

The result is validated and saved to ~/.config/netssh.yaml; Ctrl+C cancels without writing.

Manual config

~/.config/netssh.yaml:

netbox:
  url: https://netbox.example.com
  token: nbt_your-api-token-here   # v2 token (nbt_ prefix) recommended
  token_version: 2                 # auto-detected from token; 1 = legacy, 2 = nbt_

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
  default_port: 2222   # optional; injected as -p if not specified on the command line
  # See "SSH options" below for proxy_jump, host_key_checking, target,
  # address_family, extra_args, options, and rules.

completion:
  max_results: 0       # hostnames offered per <Tab>; 0 = unlimited

shortcuts:
  domains:             # strip these suffixes to create short aliases
    - .example.com
  strip_hyphens: false # if true, fsn1-web01 → fsn1web01 (cache-only resolution)

Single-instance and shared settings can be overridden with environment variables (NETSSH_NETBOX_URL, NETSSH_NETBOX_TOKEN, etc.).

Multiple NetBox instances

Use netssh configure to manage instances, or replace the netbox: section with a netboxes: list:

netboxes:
  - name: office
    url: https://netbox.office.example
    token: nbt_office-token
  - name: lab
    url: https://netbox.lab.example
    token: nbt_lab-token

Names must be unique and contain only letters, digits, underscores, or hyphens. Names are normalized to lowercase. Each instance has its own token and optional token_version; SSH, resolver, and shortcut settings apply to all instances. Configure either netbox: or netboxes:. Existing single-instance files continue to work; the wizard migrates them to a named default instance when saved.

The legacy NETSSH_NETBOX_URL and NETSSH_NETBOX_TOKEN variables apply to single-instance configuration. They are ignored when the file contains only a named netboxes: list.

Each NetBox keeps its own inventory of devices and virtual machines; either inventory may be empty. The interactive search queries all instances in parallel, including when one already has cached matches. Results show the instance as part of the hostname, for example office/web01. If an instance is unavailable, available results and cached hosts remain visible with a warning.

netssh                              # search all instances interactively
netssh office/web01                 # choose an instance explicitly
netssh admin@lab/web01 uptime
netssh web01                        # accepted only if the live lookup is unambiguous
netssh cache refresh                 # refresh all instances
netssh cache refresh --instance lab  # refresh just one instance
netssh notes set office/web01 "Office maintenance"
netssh notes show lab/web01 --netbox --kind device

For direct connections without an instance prefix, every instance must be reachable to verify uniqueness. A qualified target queries only its own instance and can use a fresh cached IP while other instances are offline. Qualified shortcuts such as office/web01 also support configured domain suffixes and hyphen folding.

Cache entries and generated aliases retain the instance name (office_web01 and lab_web01). Named-instance caches live in a configuration-specific subdirectory beneath the configured cache path, so changing an endpoint cannot reuse another server's cached IPs. Run netssh cache refresh after adding or changing instances. A failed full refresh leaves the cache and its refresh timestamp unchanged; a per-instance refresh preserves the other instances and does not mark the whole cache as refreshed.

Local notes remain keyed by the NetBox URL, host kind, and canonical hostname. Migrating a single instance or renaming it preserves its notes. Use instance/hostname and --kind for uncached local notes; these operations work offline. NetBox comments and interface/IP lookups always use the selected instance's credentials.

Short command alias

In netssh configure, set Command alias to j to use j anywhere you would use netssh:

j                    # interactive server search
j server01           # connect to a server
j notes show server01

The setting is saved as:

shell:
  alias: j

The wizard regenerates shell aliases and installs the current shell's hook when an alias is set. Open a new terminal (or follow the displayed reload command). Changing the setting replaces the old generated alias; leaving it empty disables it for new shells. The command alias takes priority over a host alias with the same name and works even when no servers are cached. Environment override: NETSSH_SHELL_ALIAS=j.

The command alias also supports shell completion: j ser<Tab> suggests cached server names, just like netssh ser<Tab>. The wizard, shell hook, installer, and netssh completion install maintain the alias completion files automatically. Changing or disabling the alias removes its old generated completion files. Bash requires bash-completion to load user completion files; for Zsh, include ~/.zfunc in fpath before running compinit as described below. Fish loads completion files automatically. Open a new terminal after changing the alias.

API tokens

NetBox supports two token formats:

Format Example Notes
v2 (recommended) nbt_abc123… Create in NetBox → Admin → API Tokens
v1 (legacy) abc123def456… Older format; still works, but v2 is preferred

netssh auto-detects the version from the token prefix and stores it as token_version in the config. A hint is shown during netssh configure if a legacy v1 token is entered.

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.

Dry run and version

netssh --dry-run web01      # print the final ssh command instead of connecting
netssh --version            # or: netssh version

Long options (--dry-run, --jump, --no-jump) are netssh's own; OpenSSH has no long options. They must come before the hostname — everything after the hostname is the remote command.

SSH options

All settings below go under ssh:. The same keys (except rules) can be set per NetBox instance under netboxes[].ssh (or netbox.ssh), and per rule. More specific settings win: global → instance → matching rules (in order) → command-line flags. Options you pass on the command line always take precedence over the config.

ssh:
  proxy_jump: "jumphost.example.com"  # passed as -J; "none" disables an inherited jump host
  host_key_checking: accept-new       # strict | accept-new | off; unset = ssh's own setting
  target: ip                          # ip (default) | name | hostname
  address_family: inet                # any | inet (-4) | inet6 (-6)
  extra_args: ["-A"]                  # inserted before your own arguments
  options:                            # passed as -o Key=Value
    ServerAliveInterval: 60
    ServerAliveCountMax: 5
  rules:                              # every set match field must match
    - match: {tag: dmz}               # tag name or slug
      proxy_jump: "dmz-jump.example.com"
    - match: {instance: lab, name: "*.lab.example.com", kind: vm}
      host_key_checking: off

netboxes:
  - name: prod
    url: "https://netbox.example.com"
    token: "nbt_…"
    ssh:
      proxy_jump: "jumphost.example.com"
  - name: staging
    url: "https://netbox-staging.example.com"
    token: "nbt_…"
    ssh:
      host_key_checking: off

Jump hosts. Override the configured jump host for a single call with --jump other.example.com (or ssh's own -J), or disable all jump hosts — including ProxyJump from ~/.ssh/config — with --no-jump.

Host key checking. strict sets StrictHostKeyChecking=yes, accept-new accepts keys of new hosts but rejects changed ones. off sets StrictHostKeyChecking=no, UserKnownHostsFile=/dev/null, and LogLevel=ERROR for netssh connections only and prints a short notice. Use it only for hosts that are reinstalled often, such as staging; it disables protection against man-in-the-middle attacks.

Connection target. By default netssh calls ssh <ip>. With target: name, it calls ssh -o HostName=<ip> -o HostKeyAlias=<name> <name>: the resolved IP is still used, but Host *.example.com rules in ~/.ssh/config apply, and known_hosts entries belong to the device instead of an IP that may be reused. With target: hostname, ssh resolves the NetBox name itself (DNS or ~/.ssh/config). Names that are not valid hostnames fall back to the IP. Switching to name stores new known_hosts entries under the device name, so ssh asks once to confirm each host key.

SFTP and SCP

netssh sftp and netssh scp look up the host exactly like a connection does: cache, shortcuts and rewrite rules, instance/host, default user and port, SSH options and rules, --jump/--no-jump, and --dry-run.

netssh sftp fsnbx1                        # interactive sftp
netssh sftp prod/web01:/var/log           # start in a directory
netssh scp ./app.tar fsnbx1:/tmp/         # upload
netssh scp -r web01:/etc/nginx ./nginx    # download
netssh scp --dry-run app.tar web01:/tmp/  # print the scp command

sftp and scp options are passed through. Mind their differences from ssh: the port is -P, and -l is a bandwidth limit, so the default user is passed as -o User=. extra_args and forwards contain ssh flags and apply to ssh only. All remote operands of one scp call must name the same host; copy between two NetBox hosts in two steps. Tab completion offers host: for scp and local files otherwise.

Port forwarding

Plain -L, -R, and -D flags are passed to ssh as usual (netssh -L 8080:localhost:80 web01). For tunnels you use often, name them under ssh.forwards, globally, per NetBox instance (netboxes[].ssh.forwards), or per rule. A more specific layer replaces a forward of the same name.

ssh:
  forwards:
    web:   {local: "8080:localhost:80"}       # -L
    socks: {dynamic: "1080"}                  # -D, SOCKS proxy
    back:  {remote: "9000:localhost:9000"}    # -R
  rules:
    - match: {tag: postgres}
      forwards:
        db: {local: "5432:localhost:5432"}
netssh tunnel fsnbx1 web socks            # only forwards: ssh -N, stops with Ctrl+C
netssh tunnel prod/db01 db 6379:localhost:6379   # names and ad hoc specs mixed
netssh --forward web fsnbx1               # shell plus forward (repeatable)

A value that is no configured name but a port spec ([bind:]port:host:hostport) becomes a local forward. netssh tunnel adds -o ExitOnForwardFailure=yes, so a port already in use fails instead of leaving a silent session. Without forward names it lists the forwards configured for the host. Forward names are completed after the host (netssh tunnel fsnbx1 <Tab>) and after --forward. In the TUI, Ctrl+T on a host opens an input with its configured forwards.

sftp, scp, and tunnel are subcommands; to connect to a host with one of these names, use netssh -- sftp.

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

Default SSH port

Set ssh.default_port in the config to use a non-standard port without specifying it every time:

netssh my-router        # → ssh -p 2222 -l admin 10.0.0.1

The default is only applied when -p is not already present in the SSH arguments. An explicit -p always takes precedence:

netssh -p 22 my-router  # -p flag wins → ssh -p 22 10.0.0.1

Host shortcuts

Configure short aliases so you can type web01 instead of web01.example.com:

shortcuts:
  domains:
    - .example.com
    - .example.de
  strip_hyphens: false  # optional
  rewrites:             # optional, applied in order
    - pattern: '^([a-z]+)[0-9]+-'   # fsn1-bx1 → fsnbx1
      replace: '$1'

Resolution order:

  1. Exact match — the input is looked up as-is in the cache.
  2. Domain expansion — if the input contains no dot, each configured domain is appended and tried (web01 → web01.example.com). If no cache hit, NetBox is queried with the expanded name.
  3. Shortcut match — the input and every cached hostname are shortened the same way and compared: lowercase, strip the domain suffix, apply each rewrite rule in order, then remove hyphens if strip_hyphens is set (fsn1web01 → fsn1-web01.example.com, fsnbx1 → fsn1-bx1.hoster.de). This only works when the host is already cached; run netssh cache refresh first.

Rewrite rules use Go regular expressions (syntax); replace may refer to groups as $1 or ${1}. Every match in the name is replaced. Because the typed shortcut is rewritten too, write rules that leave an already shortened name unchanged: ^([a-z]+)[0-9]+- matches fsn1-bx1 but not fsnbx1. Shortcuts also become shell aliases and are offered by Tab completion when the full name does not match what you typed (fsnb<Tab> → fsnbx1).

Check what a hostname is shortened to, and whether the shortcut is unique in the cache:

$ netssh alias --test fsn1-bx1.hoster.de
fsn1-bx1.hoster.de → fsnbx1
matches fsn1-bx1.hoster.de

Domains may be configured with or without a leading dot. If several cached hosts normalize to the same shortcut, the connection is rejected; use the full hostname. Live host lookups check every result page and reject duplicate exact names.

Input Config Resolved as
web01 domains: [.example.com] web01.example.com
fsn1web01 domains: [.example.com], strip_hyphens: true fsn1-web01.example.com (cache only)
fsnbx1 domains: [.hoster.de], rewrite ^([a-z]+)[0-9]+- → $1 fsn1-bx1.hoster.de (cache only)

Shell aliases

Generate shell aliases for all cached hosts based on the shortcut settings:

netssh alias                  # bash/zsh syntax
netssh alias --shell fish     # fish syntax

Example output:

# netssh aliases (3 hosts) — source with: eval "$(netssh alias)"
alias db01='netssh db01.example.com'
alias fsn1web01='netssh fsn1-web01.example.com'
alias web01='netssh web01.example.com'

Add to your shell startup file:

# bash / zsh — in ~/.bashrc or ~/.zshrc
eval "$(netssh alias)"

# fish — in ~/.config/fish/config.fish
netssh alias --shell fish | source

# scripts
source <(netssh alias)

Without a shortcuts config, the alias name is derived from the full hostname with dots replaced by underscores (e.g. web01_example_com). If two hosts would produce the same alias name, the first entry wins and a warning is printed to stderr.

Interactive TUI

Run without arguments to open the interactive search:

netssh

The TUI opens with your 10 most-recently-connected hosts. Start typing to search all cached hosts or query NetBox live.

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
Ctrl+O open an SFTP session to the selected host
Ctrl+T start a tunnel: enter forward names or specs (web, 8080:localhost:80)
Ctrl+E open inline editor to override user/port before connecting
Ctrl+N edit local notes for the selected server
Ctrl+B edit NetBox comments for the selected server
Ctrl+F open/close tag and kind filter (tag:prod kind:vm)
Esc / Ctrl+C quit (or close filter/edit if open)

Tag and kind filter — press Ctrl+F to open a second input line:

Filter: tag:prod kind:vm

Multiple tag: values are AND-combined. The filter is applied locally against the cache; when doing a live NetBox search the first tag is also forwarded as a query parameter.

User/port override — press Ctrl+E on any highlighted host:

Connect as: admin@my-router:22

Edit the pre-filled value and press Enter to connect. Esc cancels. The configured default port is pre-filled. An explicit port 22 overrides a different configured default.

Server notes

Select a server in the TUI and press Ctrl+N for local notes, or Ctrl+B for NetBox comments. Both open a multiline editor: Ctrl+S saves, Esc cancels. Existing text is loaded before editing. A failed save keeps your draft in the editor. Local notes and NetBox comments are independent; there is no automatic synchronization.

The CLI also supports reading, replacing, and appending notes:

netssh notes set web01 "Maintenance on Friday"
netssh notes append web01 "Reboot completed"
netssh notes show web01
printf 'First line\nSecond line\n' | netssh notes set web01
netssh notes append web01 --netbox --kind vm "Maintenance completed"
netssh notes show web01 --netbox --kind vm

set replaces the selected note; append preserves existing text and adds a new line. Omitting the text reads it from standard input. An empty string clears the note. For an uncached local host, specify --kind device or --kind vm; local operations work offline. Use the full server name as displayed in the TUI.

Local notes live under ~/.config/netssh/notes/ ($XDG_CONFIG_HOME is respected), with file permissions 0600 and atomic file replacement. They are keyed by NetBox URL, host kind, and hostname, so cache refreshes and cache clear preserve them. Changing the hostname or NetBox URL creates a separate note identity.

--netbox explicitly selects the device/VM's shared comments field and requires a token with write permissions. Existing comments are loaded before modification; ambiguous names are rejected (specify --kind where appropriate). The client uses the API's ETag/If-Match conflict protection when the server provides it; older NetBox versions without ETags use the server's last-write-wins behavior. See the NetBox REST API documentation.

Cache management

netssh cache list                         # show all cached entries
netssh cache refresh                      # re-fetch ALL hosts from NetBox (paginated)
netssh cache refresh --tag prod           # only hosts with the "prod" tag
netssh cache refresh --kind vm            # only virtual machines
netssh cache refresh --tag prod --kind vm # combine filters
netssh cache clear                        # wipe the cache

The cache is refreshed automatically in the background every 24 hours. The trigger fires on the next SSH connect or TUI start after the interval has elapsed — there is no delay to the connection itself. The timestamp of the last refresh is stored in ~/.cache/netssh/last_refresh.

To also trigger the check at shell startup (before you run any SSH command), install the shell hook:

netssh hook install             # auto-detects $SHELL
netssh hook install --shell bash
netssh hook install --shell zsh
netssh hook install --shell fish

This adds a marked block (# >>> netssh >>> … # <<< netssh <<<) to your shell profile. To remove it:

netssh hook uninstall

The hook is a no-op when the cache is fresh — it adds no measurable delay to your shell startup.

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.

Cache refresh uses the same strategies as a direct connection. To allow the primary IP as a fallback, include primary_ip in the strategy list. Hosts that cannot be resolved, or have ambiguous names in the refresh results, are removed from the cache and reported as warnings.

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

Install completion for the current user (no sudo required):

netssh completion install             # auto-detects $SHELL
netssh completion install --shell bash
netssh completion install --shell zsh
netssh completion install --shell fish
Shell Install path
bash ~/.local/share/bash-completion/completions/netssh
zsh ~/.zfunc/_netssh
fish ~/.config/fish/completions/netssh.fish

For zsh, the netssh block in ~/.zshrc (written by the installer and by netssh hook install --shell zsh) adds ~/.zfunc to fpath and registers the completion with compdef, also when compinit already ran earlier — for example via oh-my-zsh. Without the block, add fpath=(~/.zfunc $fpath) before compinit. If <Tab> still completes files, clear the completion cache:

rm -f ~/.zcompdump* && exec zsh

Completions are served from the local cache — no network request on every <Tab>. With several NetBox instances, a hostname that exists in only one instance is completed without the instance prefix (web01); names that exist in several instances are offered as prod/web01, staging/web01. In bash, type the instance prefix for those; zsh and fish offer them for the hostname alone. The zsh script that netssh completion zsh prints is built for large caches: 5000 hosts complete in well under a second. To limit the number of hostnames offered, set completion.max_results.

Shell Hook

netssh hook install sets up two things in your shell profile:

  1. netssh shell-init — runs on every new shell session. It regenerates the alias file from the current cache so your aliases are always up to date, and starts a background cache refresh if the last full refresh was more than 24 hours ago.
  2. A source line (bash/zsh only) — loads the generated alias file so shortcuts like web01 work immediately in every new shell. Fish uses conf.d/ auto-sourcing instead, so no explicit source line is needed.

The alias file is also generated immediately during hook install, so aliases are available as soon as you reload your profile — no cache refresh needed first (though the cache must not be empty).

Install

netssh hook install             # auto-detects $SHELL
netssh hook install --shell bash
netssh hook install --shell zsh
netssh hook install --shell fish

Example output:

Hook installed → /home/user/.bashrc
Reload with: source /home/user/.bashrc
42 aliases written to /home/user/.cache/netssh/aliases.sh

What gets written to the profile:

bash (~/.bashrc):

# >>> netssh >>>
netssh shell-init
[ ! -f '/home/user/.cache/netssh/aliases.sh' ] || . '/home/user/.cache/netssh/aliases.sh'
# <<< netssh <<<

zsh (~/.zshrc) additionally registers the completion:

fpath=('/home/user/.zfunc' $fpath)
if (( $+functions[compdef] )); then autoload -Uz _netssh && compdef _netssh netssh; fi

fish (~/.config/fish/config.fish):

# >>> netssh >>>
netssh shell-init
# <<< netssh <<<

Fish alias file (~/.config/fish/conf.d/netssh.fish) is written automatically and sourced by fish on every shell start without any profile changes.

Shell Profile file Alias file
bash ~/.bashrc ~/.cache/netssh/aliases.sh
zsh ~/.zshrc ~/.cache/netssh/aliases.sh
fish ~/.config/fish/config.fish ~/.config/fish/conf.d/netssh.fish

The install is idempotent — running it again changes nothing if the block is already present. Unmarked lines written by older versions are replaced by the block.

Reload your profile after installation to activate immediately:

source ~/.bashrc    # bash
source ~/.zshrc     # zsh
source ~/.config/fish/config.fish  # fish

Alias file updates

The alias file is regenerated automatically — no manual action needed:

Event Aliases updated
New shell session (shell-init) Yes — from the current local cache
netssh cache refresh Yes — after the full refresh completes
Background 24h auto-refresh Yes — after the background refresh completes

Aliases for newly added hosts appear in the next shell session after a cache refresh has run.

Uninstall

netssh hook uninstall             # auto-detects $SHELL
netssh hook uninstall --shell zsh

Removes the netssh block and unmarked entries of older versions from the profile. The alias files (aliases.sh, netssh.fish) are left on disk. To remove netssh completely, use netssh uninstall.

Relation to the connect-time auto-refresh

The shell hook and the connect-time trigger (which fires on each netssh invocation) are independent but complementary:

Trigger When it fires
SSH connect / TUI start On the next netssh call after 24 h have elapsed
Shell hook (shell-init) On the first new shell session after 24 h have elapsed

Both are non-blocking — the refresh runs as a background process and never delays your prompt or SSH connection. They share ~/.cache/netssh/last_refresh, so only one background refresh runs per 24-hour window regardless of how many shells or connections are opened.

Uninstall

netssh uninstall --dry-run       # show what would be removed
netssh uninstall                 # asks for confirmation
netssh uninstall --keep-config   # keep config and notes for a reinstall

netssh uninstall removes:

  • netssh blocks and unmarked entries of older versions from ~/.bashrc, ~/.bash_profile, ~/.bash_login, ~/.profile, ~/.zshrc, ~/.zprofile, ~/.zshenv, and config.fish
  • completion files for bash, zsh (also the oh-my-zsh cache), fish, and the command alias
  • the cache directory ~/.cache/netssh/ including per-instance caches and aliases
  • ~/.config/netssh.yaml (a private backup netssh.yaml.backup-<timestamp> is kept, because it contains API tokens) and local notes
  • the netssh binary; if it is in a protected directory, netssh prints the sudo rm command to run

Afterwards open a new terminal; for zsh also run rm -f ~/.zcompdump*.

Development

Renovate monitors the release workflow's actions, the Go version in go.mod, and all direct and indirect Go module dependencies. Go dependency updates also run go mod tidy to keep go.mod and go.sum consistent.

go test ./...        # includes offline installer/update tests
go build ./...       # build all packages

The test suite covers the cache, NetBox client (via httptest), IP resolver chain, SSH argument parser, config loading, setup wizard, shortcut normalization, and shell hook install/uninstall.

Disclaimer

This is a vibe-coded project: the entire codebase — architecture, implementation, tests, and docs — was generated by an AI assistant (Claude by Anthropic). No human has reviewed or audited the code. It works for the author's personal use case, but correctness and security are not guaranteed. Read the source before running it in sensitive environments.

How it works

  1. netssh checks whether the first argument is a known subcommand (configure, search, cache, completion, version, uninstall, …). If not, it enters SSH wrapper mode.
  2. On first run or when netbox.url is empty, the interactive setup wizard starts automatically.
  3. It parses the SSH arguments to extract the destination hostname, handling all flags that consume an extra argument (-p, -i, -J, …).
  4. It resolves the destination: first an exact cache lookup, then shortcut expansion (domain append, hyphen folding) against the cache, and finally a fresh NetBox query with the expanded name. If the cache entry is within the TTL, no network request is made.
  5. 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.
  6. It adds the configured SSH options for the host (jump host, host key checking, -o options) before the destination and calls syscall.Exec to replace itself with ssh, substituting the hostname with the resolved IP (or the name, with ssh.target: name).
S
Description
No description provided
Readme
1.1 MiB
0 Stars 1 Watchers 0 Forks
v0.0.22
Latest
2026-10-28 10:52:31 +00:00
Languages
Go 96.8%
Shell 3.2%