Files
ssh-netbox-wrapper/README.md
T
Sebastian Unterschütz 7c902cab3a
Release / release (push) Successful in 50s
feat: introduce shortcuts and shell hook support
- **Shortcuts**: Add hostname normalization with domain stripping and hyphen folding. Include alias generation for cached hosts.
- **Shell Hook**: Automate 24h cache refresh trigger with shell startup hook. Add install/uninstall commands for bash, zsh, and fish.
- **Wizard**: Extend setup wizard to configure shortcuts (domains, hyphen stripping) and default SSH port.
- **Cache**: Add `GetByShortcut` for resolving hosts via normalized shortcuts. Implement `NeedsRefresh` / `SetRefreshed` logic for refresh timestamps.
- **Tests**: Comprehensive unit tests for shortcuts, hook installation, cache refresh, and alias generation.
- **Docs**: Update README with shortcuts, shell hook, and default SSH port configuration.
2026-05-27 22:53:24 +02:00

15 KiB

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 e in the TUI to override the SSH user or port before connecting
  • Persistent cache — successful lookups are cached to ~/.cache/netssh/hosts.json for instant shell completion
  • Full paginationcache refresh fetches all hosts from NetBox (not just the first 50)
  • Selective refreshcache 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 and hyphen folding
  • Shell aliasesnetssh alias generates shell aliases for all cached hosts
  • Automatic 24h cache refresh — the cache is refreshed in the background every 24 hours

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

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 walks through NetBox connection, SSH defaults, resolver strategies, and cache TTL, then saves to ~/.config/netssh.yaml.

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

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

Any value can be overridden with environment variables (NETSSH_NETBOX_URL, NETSSH_NETBOX_TOKEN, etc.).

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.

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

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 (web01web01.example.com). If no cache hit, NetBox is queried with the expanded name.
  3. Hyphen folding (strip_hyphens: true) — hyphens are inserted back before looking up in the cache (fsn1web01fsn1-web01.example.com). This only works when the host is already cached; run netssh cache refresh first.
Input Config Resolved as
web01 domains: [.example.com] web01.example.com
fsn1web01 domains: [.example.com], strip_hyphens: true fsn1-web01.example.com (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
e open inline editor to override user/port before connecting
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 e on any highlighted host:

Connect as: admin@my-router:22

Edit the pre-filled value and press Enter to connect. Esc cancels. Port 22 is treated as default and omitted from the ssh command.

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 appends a single line (netssh shell-init) 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.

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, make sure ~/.zfunc is in your fpath (add to ~/.zshrc):

fpath=(~/.zfunc $fpath)
autoload -Uz compinit && compinit

Completions are served from the local cache — no network request on every <Tab>.

Shell Hook

The shell hook runs netssh shell-init at the start of every new shell session. It checks whether the cache is older than 24 hours and, if so, starts a background refresh. The check reads a single small file and adds no measurable delay to your shell startup.

Install

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

This appends exactly one line to your shell profile:

netssh shell-init  # netssh cache auto-refresh
Shell Profile file
bash ~/.bashrc
zsh ~/.zshrc
fish ~/.config/fish/config.fish

The install is idempotent — running it again does nothing if the hook is already present.

Reload your profile after installation:

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

Uninstall

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

Removes the netssh shell-init line from the profile and collapses any blank lines left behind.

How it differs from the connect-time trigger

Trigger When it fires
Connect / TUI start On the next SSH command or netssh TUI after 24 h
Shell hook On the first new shell session after 24 h

Both triggers are non-blocking: the refresh runs in the background and your SSH connection (or prompt) is not delayed. You can install both — they share the same ~/.cache/netssh/last_refresh timestamp, so the background process runs at most once per 24 hours regardless of how many shells or connections you open.

Development

go test ./...        # run all 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). 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 calls syscall.Exec to replace itself with ssh, substituting the hostname with the resolved IP.