|
|
|
@@ -13,6 +13,21 @@ 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.
|
|
|
|
|
|
|
|
|
|
The same lookup drives `netssh sftp`, `netssh scp`, and `netssh tunnel`, the interactive search, shell completion, and generated shell aliases.
|
|
|
|
|
|
|
|
|
|
## Contents
|
|
|
|
|
|
|
|
|
|
- [Features](#features)
|
|
|
|
|
- [Installation](#installation)
|
|
|
|
|
- [Quick start](#quick-start)
|
|
|
|
|
- [Configuration](#configuration) — [wizard](#interactive-wizard), [manual config](#manual-config), [reference](#configuration-reference), [environment variables](#environment-variables), [files](#files-and-directories), [multiple instances](#multiple-netbox-instances)
|
|
|
|
|
- [Usage](#usage) — [command overview](#command-overview), [SSH options](#ssh-options), [SFTP & SCP](#sftp-and-scp), [port forwarding](#port-forwarding), [shortcuts](#host-shortcuts), [TUI](#interactive-tui), [notes](#server-notes), [cache](#cache-management)
|
|
|
|
|
- [Recipes](#recipes)
|
|
|
|
|
- [IP Resolution Strategies](#ip-resolution-strategies)
|
|
|
|
|
- [Shell Completion](#shell-completion) and [Shell Hook](#shell-hook)
|
|
|
|
|
- [Troubleshooting](#troubleshooting)
|
|
|
|
|
- [Uninstall](#uninstall), [Development](#development), [How it works](#how-it-works)
|
|
|
|
|
|
|
|
|
|
## Features
|
|
|
|
|
|
|
|
|
|
- **Transparent proxy** — replaces itself with `ssh` via `syscall.Exec`, preserving all SSH flags and options
|
|
|
|
@@ -97,6 +112,22 @@ cd ssh-netbox-wrapper
|
|
|
|
|
go build -o netssh ./cmd/netssh
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Quick start
|
|
|
|
|
|
|
|
|
|
1. **Install** with the one-liner above and open a new terminal.
|
|
|
|
|
2. **Configure** — run `netssh` (or `netssh configure`). The wizard asks for your NetBox URL and an API token; a read-only token is enough unless you want to edit NetBox comments. Everything else has working defaults and can be changed later.
|
|
|
|
|
3. **Fill the cache** — `netssh cache refresh` fetches all devices and virtual machines once. Connecting works without it, but completion, shortcuts, aliases, and the recently-used list need cached hosts.
|
|
|
|
|
4. **Connect** — `netssh web01`, or just `netssh` for the interactive search.
|
|
|
|
|
5. **Optional** — enable completion and aliases with `netssh hook install` (the installer usually did this already), and try `netssh --dry-run web01` to see the exact ssh command.
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
netssh configure # NetBox URL + token
|
|
|
|
|
netssh cache refresh # fetch all hosts once
|
|
|
|
|
netssh web01 # ssh to web01 via its NetBox IP
|
|
|
|
|
netssh sftp web01 # sftp to the same host
|
|
|
|
|
netssh tunnel web01 8080:localhost:80 # local port 8080 → web01:80
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Configuration
|
|
|
|
|
|
|
|
|
|
### Interactive wizard
|
|
|
|
@@ -163,7 +194,72 @@ shortcuts:
|
|
|
|
|
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.).
|
|
|
|
|
Single-instance and shared settings can be overridden with environment variables (`NETSSH_NETBOX_URL`, `NETSSH_NETBOX_TOKEN`, etc.; see [Environment variables](#environment-variables)).
|
|
|
|
|
|
|
|
|
|
netssh reads `netssh.yaml` from the user config directory and, if none exists there, from the current working directory. Unknown keys are ignored; invalid values (an unknown strategy, a bad regex, an invalid SSH option) stop netssh with an error that names the key.
|
|
|
|
|
|
|
|
|
|
### Configuration reference
|
|
|
|
|
|
|
|
|
|
Every key, its default, and the section that explains it. Keys marked *layered* can also be set per NetBox instance (`netboxes[].ssh.…`) and per rule (`ssh.rules[].…`).
|
|
|
|
|
|
|
|
|
|
| Key | Type | Default | Description |
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
| `netbox.url` | string | — | NetBox base URL (single instance). Use either `netbox` or `netboxes`. |
|
|
|
|
|
| `netbox.token` | string | — | API token; `nbt_…` is v2, anything else v1. |
|
|
|
|
|
| `netbox.token_version` | 1 \| 2 | auto | Detected from the token prefix. |
|
|
|
|
|
| `netboxes[]` | list | — | Named instances with `name`, `url`, `token`, `token_version`, `ssh` — see [Multiple NetBox instances](#multiple-netbox-instances). |
|
|
|
|
|
| `resolver.strategies` | list | `[management_subnet, primary_ip]` | Tried in order, first IP wins — see [IP Resolution Strategies](#ip-resolution-strategies). The wizard suggests `primary_ip`. |
|
|
|
|
|
| `resolver.management_subnets` | list of CIDR | `[]` | Used by `management_subnet`. |
|
|
|
|
|
| `resolver.interface_name` | string | — | Used by `interface_name`, e.g. `mgmt0`. |
|
|
|
|
|
| `cache.ttl` | seconds | `3600` | How long a cached IP is used without asking NetBox; `0` always asks NetBox when connecting. |
|
|
|
|
|
| `cache.path` | path | user cache dir `/netssh/hosts.json` | Cache file; named instances use a subdirectory next to it. |
|
|
|
|
|
| `ssh.default_user` | string | — | Login user when none is given (`-l` for ssh, `-o User=` for sftp/scp). |
|
|
|
|
|
| `ssh.default_port` | int | — | Port when none is given (`-p` for ssh, `-P` for sftp/scp). |
|
|
|
|
|
| `ssh.proxy_jump` | string | — | *Layered.* Jump host (`-J`); `none` disables an inherited one. |
|
|
|
|
|
| `ssh.host_key_checking` | `strict` \| `accept-new` \| `off` | ssh's own | *Layered.* See [SSH options](#ssh-options). |
|
|
|
|
|
| `ssh.target` | `ip` \| `name` \| `hostname` | `ip` | *Layered.* What is passed to ssh as destination. |
|
|
|
|
|
| `ssh.address_family` | `any` \| `inet` \| `inet6` | — | *Layered.* `-o AddressFamily=`. |
|
|
|
|
|
| `ssh.extra_args` | list | `[]` | *Layered.* Extra ssh arguments; accumulate across layers; ssh only. |
|
|
|
|
|
| `ssh.options` | map | `{}` | *Layered.* `-o Key=Value`; per key, the more specific layer wins. |
|
|
|
|
|
| `ssh.forwards` | map | `{}` | *Layered.* Named port forwards (`local`, `remote`, `dynamic`) — see [Port forwarding](#port-forwarding). |
|
|
|
|
|
| `ssh.rules[]` | list | `[]` | `match` (`tag`, `instance`, `kind`, `name`) plus any layered key. |
|
|
|
|
|
| `shortcuts.domains` | list | `[]` | Suffixes stripped for shortcuts and tried when expanding a short name. |
|
|
|
|
|
| `shortcuts.strip_hyphens` | bool | `false` | Ignore hyphens when matching shortcuts. |
|
|
|
|
|
| `shortcuts.rewrites[]` | list | `[]` | `pattern` (Go regex) and `replace` — see [Host shortcuts](#host-shortcuts). |
|
|
|
|
|
| `completion.max_results` | int | `0` | Hostnames offered per `<Tab>`; `0` = unlimited. |
|
|
|
|
|
| `shell.alias` | string | — | Short command name for netssh, e.g. `j` — see [Short command alias](#short-command-alias). |
|
|
|
|
|
|
|
|
|
|
### Environment variables
|
|
|
|
|
|
|
|
|
|
These variables override the file. Lists are comma-separated (`NETSSH_SHORTCUTS_DOMAINS=.example.com,.example.de`). Settings without a variable (`extra_args`, `options`, `forwards`, `rules`, `rewrites`, `netboxes`) can only be set in the file.
|
|
|
|
|
|
|
|
|
|
| Variable | Key |
|
|
|
|
|
|---|---|
|
|
|
|
|
| `NETSSH_NETBOX_URL`, `NETSSH_NETBOX_TOKEN`, `NETSSH_NETBOX_TOKEN_VERSION` | `netbox.*` (single instance only; ignored when the file has only `netboxes:`) |
|
|
|
|
|
| `NETSSH_RESOLVER_STRATEGIES`, `NETSSH_RESOLVER_MANAGEMENT_SUBNETS`, `NETSSH_RESOLVER_INTERFACE_NAME` | `resolver.*` |
|
|
|
|
|
| `NETSSH_CACHE_TTL`, `NETSSH_CACHE_PATH` | `cache.*` |
|
|
|
|
|
| `NETSSH_SSH_DEFAULT_USER`, `NETSSH_SSH_DEFAULT_PORT` | `ssh.default_*` |
|
|
|
|
|
| `NETSSH_SSH_PROXY_JUMP`, `NETSSH_SSH_HOST_KEY_CHECKING`, `NETSSH_SSH_TARGET`, `NETSSH_SSH_ADDRESS_FAMILY` | global `ssh.*` options |
|
|
|
|
|
| `NETSSH_SHORTCUTS_DOMAINS`, `NETSSH_SHORTCUTS_STRIP_HYPHENS` | `shortcuts.*` |
|
|
|
|
|
| `NETSSH_COMPLETION_MAX_RESULTS` | `completion.max_results` |
|
|
|
|
|
| `NETSSH_SHELL_ALIAS` | `shell.alias` |
|
|
|
|
|
|
|
|
|
|
netssh also respects `XDG_CONFIG_HOME`, `XDG_CACHE_HOME`, `ZDOTDIR`, and `SHELL` (for `hook install`, `completion install`, and `alias` without `--shell`).
|
|
|
|
|
|
|
|
|
|
### Files and directories
|
|
|
|
|
|
|
|
|
|
Paths are shown for Linux, where `XDG_CONFIG_HOME` and `XDG_CACHE_HOME` replace `~/.config` and `~/.cache`. On macOS, config and notes live under `~/Library/Application Support`, the host cache and `aliases.sh` under `~/Library/Caches/netssh/` (unless `XDG_CACHE_HOME` is set for `aliases.sh`); fish and completion files use the paths in the table.
|
|
|
|
|
|
|
|
|
|
| Path | Content |
|
|
|
|
|
|---|---|
|
|
|
|
|
| `~/.config/netssh.yaml` | Configuration, including API tokens; written with mode `0600`. |
|
|
|
|
|
| `~/.config/netssh/notes/` | Local server notes (`0600`), keyed by NetBox URL, kind, and hostname. |
|
|
|
|
|
| `~/.cache/netssh/hosts.json` | Host cache (single instance). |
|
|
|
|
|
| `~/.cache/netssh/instances-<hash>/hosts.json` | Host cache for a set of named instances; the hash changes when names or URLs change. |
|
|
|
|
|
| `~/.cache/netssh/last_refresh` | Time of the last full refresh, for the 24-hour background refresh. |
|
|
|
|
|
| `~/.cache/netssh/aliases.sh` | Generated aliases for bash and zsh. |
|
|
|
|
|
| `~/.config/fish/conf.d/netssh.fish` | Generated aliases for fish. |
|
|
|
|
|
| `~/.local/share/bash-completion/completions/netssh`, `~/.zfunc/_netssh`, `~/.config/fish/completions/netssh.fish` | Installed completions. |
|
|
|
|
|
|
|
|
|
|
### Multiple NetBox instances
|
|
|
|
|
|
|
|
|
@@ -249,6 +345,27 @@ A hint is shown during `netssh configure` if a legacy v1 token is entered.
|
|
|
|
|
|
|
|
|
|
## Usage
|
|
|
|
|
|
|
|
|
|
### Command overview
|
|
|
|
|
|
|
|
|
|
| Command | Purpose |
|
|
|
|
|
|---|---|
|
|
|
|
|
| `netssh` | Interactive search (TUI). |
|
|
|
|
|
| `netssh [--dry-run] [--jump HOST\|--no-jump] [--forward NAME]... [ssh options] HOST [command]` | Connect with ssh. |
|
|
|
|
|
| `netssh sftp [netssh flags] [sftp options] HOST[:PATH]` | SFTP session — see [SFTP and SCP](#sftp-and-scp). |
|
|
|
|
|
| `netssh scp [netssh flags] [scp options] SOURCE... TARGET` | Copy files — see [SFTP and SCP](#sftp-and-scp). |
|
|
|
|
|
| `netssh tunnel [netssh flags] [ssh options] HOST FORWARD...` | Port forwards without a shell — see [Port forwarding](#port-forwarding). |
|
|
|
|
|
| `netssh configure` | Setup wizard. |
|
|
|
|
|
| `netssh cache list \| refresh [--tag T] [--kind device\|vm] [--instance I] \| clear` | Inspect and manage the host cache. |
|
|
|
|
|
| `netssh search QUERY` | Print matching cached hostnames, one per line. |
|
|
|
|
|
| `netssh alias [--shell bash\|zsh\|fish] [--test HOSTNAME]` | Print shell aliases, or test how a name is shortened. |
|
|
|
|
|
| `netssh notes show\|set\|append HOST [TEXT] [--netbox] [--kind device\|vm]` | Local notes or NetBox comments. |
|
|
|
|
|
| `netssh hook install\|uninstall [--shell S]` | Shell integration (aliases, completion, background refresh). |
|
|
|
|
|
| `netssh completion install [--shell S]` / `netssh completion bash\|zsh\|fish` | Install or print completion scripts. |
|
|
|
|
|
| `netssh uninstall [--dry-run] [--keep-config] [--yes]` | Remove netssh. |
|
|
|
|
|
| `netssh version` / `netssh --version` | Print the version. |
|
|
|
|
|
|
|
|
|
|
The netssh flags `--dry-run`, `--jump`, `--no-jump`, and `--forward` must come before the host. Everything after the host is passed to ssh as the remote command (for `tunnel`: forward names).
|
|
|
|
|
|
|
|
|
|
### SSH wrapper mode
|
|
|
|
|
|
|
|
|
|
Pass any SSH flags and a NetBox hostname:
|
|
|
|
@@ -329,6 +446,30 @@ With `target: hostname`, ssh resolves the NetBox name itself (DNS or
|
|
|
|
|
Switching to `name` stores new known_hosts entries under the device name, so
|
|
|
|
|
ssh asks once to confirm each host key.
|
|
|
|
|
|
|
|
|
|
**Rules.** A rule applies when every field set in its `match` fits the host:
|
|
|
|
|
|
|
|
|
|
| Match field | Compared with | Notes |
|
|
|
|
|
|---|---|---|
|
|
|
|
|
| `tag` | NetBox tags of the host | Tag name or slug, case-insensitive (`DMZ Hosts` matches `dmz-hosts`). |
|
|
|
|
|
| `instance` | Name of the NetBox instance | Only useful with `netboxes:`. |
|
|
|
|
|
| `kind` | `device` or `vm` | Devices come from DCIM, VMs from Virtualization. |
|
|
|
|
|
| `name` | Hostname without instance prefix | Shell glob, case-insensitive (`*.dmz.example.com`, `fw-??`). |
|
|
|
|
|
|
|
|
|
|
Tags and kind are taken from the cache or the NetBox answer, so rules work offline for cached hosts. A rule needs at least one match field. All matching rules apply in file order; a later rule overrides an earlier one.
|
|
|
|
|
|
|
|
|
|
**How layers combine.** netssh merges global → instance → matching rules:
|
|
|
|
|
|
|
|
|
|
- single values (`proxy_jump`, `host_key_checking`, `target`, `address_family`) — the most specific non-empty value wins;
|
|
|
|
|
- `options` and `forwards` — merged per key or name, the more specific layer wins;
|
|
|
|
|
- `extra_args` — collected from all layers.
|
|
|
|
|
|
|
|
|
|
Options you type yourself always win: netssh adds its `-o` options after yours, and ssh uses the first value it sees; `-J`, `-4`/`-6`, or your own `StrictHostKeyChecking` suppress the matching setting. Check the result with `--dry-run`:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
$ netssh --dry-run fw01.dmz.example.com
|
|
|
|
|
ssh -A -l admin -J dmz-jump.example.com -o StrictHostKeyChecking=accept-new -o serveraliveinterval=60 192.0.2.10
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 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`.
|
|
|
|
@@ -581,6 +722,88 @@ The hook is a no-op when the cache is fresh — it adds no measurable delay to y
|
|
|
|
|
netssh search app- # prints matching hostnames, one per line
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Recipes
|
|
|
|
|
|
|
|
|
|
**Everything through a bastion, except the bastion itself**
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
ssh:
|
|
|
|
|
proxy_jump: admin@bastion.example.com
|
|
|
|
|
rules:
|
|
|
|
|
- match: {name: "bastion*"}
|
|
|
|
|
proxy_jump: none
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**A different jump host per site (NetBox instance)**
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
netboxes:
|
|
|
|
|
- name: fsn
|
|
|
|
|
url: https://netbox.fsn.example.com
|
|
|
|
|
token: nbt_…
|
|
|
|
|
ssh: {proxy_jump: jump.fsn.example.com}
|
|
|
|
|
- name: hel
|
|
|
|
|
url: https://netbox.hel.example.com
|
|
|
|
|
token: nbt_…
|
|
|
|
|
ssh: {proxy_jump: jump.hel.example.com}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Lab machines that are reinstalled every day**
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
ssh:
|
|
|
|
|
rules:
|
|
|
|
|
- match: {instance: lab}
|
|
|
|
|
host_key_checking: off # prints a notice on every connection
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Database and web tunnels by tag**
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
ssh:
|
|
|
|
|
forwards:
|
|
|
|
|
socks: {dynamic: "1080"} # browser via SOCKS on localhost:1080
|
|
|
|
|
rules:
|
|
|
|
|
- match: {tag: postgres}
|
|
|
|
|
forwards: {db: {local: "5432:localhost:5432"}}
|
|
|
|
|
- match: {tag: grafana}
|
|
|
|
|
forwards: {web: {local: "3000:localhost:3000"}}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
netssh tunnel db01 db # psql -h localhost then reaches db01
|
|
|
|
|
netssh tunnel grafana01 web socks
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Short names for a hosting naming scheme** (`fsn1-bx1.hoster.de` → `fsnbx1`)
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
shortcuts:
|
|
|
|
|
domains: [.hoster.de]
|
|
|
|
|
rewrites:
|
|
|
|
|
- pattern: '^([a-z]+)[0-9]+-'
|
|
|
|
|
replace: '$1'
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
netssh cache refresh && netssh alias --test fsn1-bx1.hoster.de
|
|
|
|
|
netssh fsnbx1 # also works as shell alias: fsnbx1
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Deploy a file and restart a service**
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
netssh scp ./app.tar prod/app01:/tmp/ && netssh prod/app01 'sudo systemctl restart app'
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Use netssh in scripts**
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
for host in $(netssh search web-); do
|
|
|
|
|
netssh "$host" uptime
|
|
|
|
|
done
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## IP Resolution Strategies
|
|
|
|
|
|
|
|
|
|
Strategies are tried in the configured order; the first to succeed wins.
|
|
|
|
@@ -728,6 +951,26 @@ The shell hook and the connect-time trigger (which fires on each `netssh` invoca
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
## Troubleshooting
|
|
|
|
|
|
|
|
|
|
**See what netssh would run.** `--dry-run` works for ssh, `sftp`, `scp`, and `tunnel` and prints the final command with the resolved IP and all added options.
|
|
|
|
|
|
|
|
|
|
| Message / symptom | Cause and fix |
|
|
|
|
|
|---|---|
|
|
|
|
|
| `host not found` | The name is not in NetBox (devices and VMs are searched). Check the spelling, or configure `shortcuts.domains` if you type short names. |
|
|
|
|
|
| `ambiguous shortcut "x"; use a full hostname` | Several cached hosts shorten to the same name. Use the full name, or check with `netssh alias --test`. |
|
|
|
|
|
| `found 2 exact matches` | The name exists in several NetBox instances, or as both device and VM. Prefix the instance (`prod/web01`); for notes and the TUI also `--kind`. |
|
|
|
|
|
| Shortcut works for one host but not another | Shortcuts (hyphen folding, rewrites) only match cached hosts. Run `netssh cache refresh`. |
|
|
|
|
|
| Connection fails although a NetBox instance is down | Without an instance prefix, every instance must answer to rule out duplicates. Use `instance/host`; a fresh cache entry then needs no NetBox at all. |
|
|
|
|
|
| Wrong or outdated IP | Lower `cache.ttl`, run `netssh cache refresh`, and check the [strategy order](#ip-resolution-strategies). |
|
|
|
|
|
| `REMOTE HOST IDENTIFICATION HAS CHANGED` | The host was reinstalled or the IP reused. Remove the old key (`ssh-keygen -R <ip>`), consider `target: name`, or for lab hosts `host_key_checking: off`. |
|
|
|
|
|
| `unknown forward "x"` | The name is not configured for this host (forwards can be per instance or rule). The message lists the available ones. |
|
|
|
|
|
| `bind [127.0.0.1]:8080: Address already in use` | The local port of a forward is taken; `netssh tunnel` then exits instead of hanging. Choose another port. |
|
|
|
|
|
| Saving notes with `--netbox` fails with a permission error | The API token is read-only. Local notes work without write permission. |
|
|
|
|
|
| `<Tab>` completes files instead of hosts | Run `netssh completion install` and open a new terminal; for zsh also `rm -f ~/.zcompdump*`. |
|
|
|
|
|
| Aliases missing in a new shell | `netssh hook install`, then a new terminal. The cache must not be empty. |
|
|
|
|
|
| A host is named `sftp`, `scp`, `tunnel`, or like another subcommand | Use `netssh -- sftp`. |
|
|
|
|
|
|
|
|
|
|
## Uninstall
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
@@ -757,7 +1000,23 @@ 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.
|
|
|
|
|
The test suite covers the cache, NetBox client (via `httptest`), IP resolver chain, the argument parsers for ssh, sftp, and scp, SSH options and rules, port forwards, config loading, the setup wizard, shortcut normalization and rewrites, completion, the TUI keys, and shell hook install/uninstall. Wrapper tests run the real command (`sftp`, `scp`, `tunnel` with `--dry-run`) in a subprocess against a pre-filled cache, so no NetBox or ssh server is needed.
|
|
|
|
|
|
|
|
|
|
Project layout:
|
|
|
|
|
|
|
|
|
|
| Directory | Content |
|
|
|
|
|
|---|---|
|
|
|
|
|
| `cmd/netssh` | Commands, host resolution, completion. |
|
|
|
|
|
| `internal/config` | Config loading, validation, SSH option layering. |
|
|
|
|
|
| `internal/netbox` | NetBox API client, multiple instances. |
|
|
|
|
|
| `internal/resolver` | IP resolution strategies. |
|
|
|
|
|
| `internal/cache` | Host cache. |
|
|
|
|
|
| `internal/ssh` | Argument parsing and option injection for ssh, sftp, scp. |
|
|
|
|
|
| `internal/shortcuts` | Shortcut normalization. |
|
|
|
|
|
| `internal/tui` | Interactive search. |
|
|
|
|
|
| `internal/setup` | Setup wizard. |
|
|
|
|
|
| `internal/hook` | Shell hooks, aliases, completion files. |
|
|
|
|
|
| `internal/notes` | Local notes and NetBox comments. |
|
|
|
|
|
|
|
|
|
|
## Disclaimer
|
|
|
|
|
|
|
|
|
@@ -765,9 +1024,10 @@ This is a **vibe-coded** project: the entire codebase — architecture, implemen
|
|
|
|
|
|
|
|
|
|
## 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`).
|
|
|
|
|
1. `netssh` checks whether the first argument is a known subcommand (`configure`, `cache`, `sftp`, `scp`, `tunnel`, `notes`, …). If not, it enters SSH wrapper mode; without arguments it starts the TUI.
|
|
|
|
|
2. On first run or when no NetBox is configured, the interactive setup wizard starts automatically.
|
|
|
|
|
3. It removes its own long options (`--dry-run`, `--jump`, `--no-jump`, `--forward`) and parses the remaining arguments with the flag table of the program it will run (ssh, sftp, or scp differ, e.g. in `-l` and `-P`) to find the destination: `[user@]host` for ssh, `[user@]host[:path]` for sftp, and every `host:path` operand for scp.
|
|
|
|
|
4. It adds the default user and port in the program's syntax unless you gave your own.
|
|
|
|
|
5. It resolves the host: an exact cache lookup, then a shortcut match against the cache (domains, rewrites, hyphen folding), and finally NetBox (`/api/dcim/devices/` and `/api/virtualization/virtual-machines/`, all instances in parallel) with the resolver chain. A fresh cache entry needs no network request; new results are cached.
|
|
|
|
|
6. It merges the SSH options for the host (global, instance, matching rules), resolves named forwards, and inserts the options before the first operand: jump host, host key checking, `-o` options, forwards, and for `tunnel` `-N` with `ExitOnForwardFailure`.
|
|
|
|
|
7. It replaces the hostname with the resolved IP (or the name, with `ssh.target: name`; IPv6 in brackets for sftp/scp) and calls `syscall.Exec` to become `ssh`, `sftp`, or `scp` — or prints the command with `--dry-run`.
|