Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Importing hosts

sshelf can populate its database from two sources you already have: your SSH config and your Tailscale tailnet. Both are read-only towards their source and add-only towards sshelf: a host whose name already exists is left alone, so re-running is always safe.

From ~/.ssh/config

sshelf import (or Ctrl-o in the TUI) copies hosts from ~/.ssh/config into sshelf’s own database. It is strictly read-only: sshelf parses the file and never writes back. Your SSH config is not touched, ever.

sshelf import --dry-run    # preview what would be imported
sshelf import              # import

What it does:

  • adds every host whose name isn’t already present in sshelf, so re-running is safe and existing names are left alone;
  • carries over the fields sshelf models (hostname, user, port, identity files);
  • warns about the directives it doesn’t import (Match, Include, ProxyJump) instead of silently mis-importing them.

From Tailscale

sshelf import --tailscale imports your tailnet: every machine in it becomes a searchable sshelf host, in one command.

sshelf import --tailscale --dry-run   # preview
sshelf import --tailscale             # import

It runs your own tailscale CLI (tailscale status --json) and reads the output. sshelf still makes no network calls of its own: nothing happens unless you run this command, never at startup, never on a save, never in the background, and there’s no Tailscale entry point in the TUI. Your API keys and tailnet credentials stay with the Tailscale client; sshelf never sees them, and nothing tailscale-specific is written to hosts.toml.

Which machines are imported. Every peer whose MagicDNS name is under your tailnet’s own domain, one rule that leaves out Mullvad exit nodes and machines shared in from other tailnets. Peers with an expired node key are skipped. Offline peers are imported: being asleep is temporary, and the host is still real. This machine (Self) is not imported.

What each machine becomes:

sshelf fieldFrom the peer
nameFirst label of the MagicDNS name, lowercased (e.g. nas.tail4f9a2.ts.net.nas).
hostnameThe MagicDNS FQDN (nas.tail4f9a2.ts.net), stable across IP churn, and what Tailscale SSH expects. If MagicDNS is off for your tailnet, its Tailscale IP (IPv4 first).
siteYour tailnet’s name, matched to an existing site case-insensitively, or created as a bare one (name only, no defaults).
tagsThe machine’s ACL tags, minus the tag: prefix (tag:serverserver). Untagged machines get no tags.
authagent, your key/agent auth, unchanged. Add a user, port or password afterwards if a box needs one.

Two machines whose names collide (possible only across sub-domains) keep the first; the second is reported. Everything skipped is counted in a warning line, so the numbers always add up.

If sshelf can’t find the CLI. It looks at $SSHELF_TAILSCALE_BIN, then tailscale on your PATH, then /Applications/Tailscale.app/Contents/MacOS/Tailscale, since the macOS app doesn’t put its CLI on PATH. For any other location:

SSHELF_TAILSCALE_BIN=/path/to/tailscale sshelf import --tailscale

The import needs the Tailscale backend to be running (tailscale up); if it isn’t, sshelf says so and changes nothing.

After importing

Import brings everything in at once (there’s no per-host picker); curate afterwards with Ctrl-e / Ctrl-d, and organize with tags or sites. Your ~/.ssh/config keeps working exactly as before, and sshelf’s database is independent of it by design (the FAQ explains why).

The reverse direction exists too: export projects your sshelf hosts back out as an Include fragment, so plain ssh/scp can use them by name, including everything a tailnet import just added.