We just added endpoint discovery to Wishlist, our SSH host directory.
Your SSH directory.
Wishlist can act as a bastion, presenting the user with a list of hosts they can SSH into from that host.
You can also run it locally, in which case it becomes a TUI (text-based
user interface) for your
~/.ssh/config (or to a predefined list of hosts in
a YAML configuration file).
But there’s more to it: did you know you can discover hosts from several sources?
Currently, we support the following sources:
- DNS SRV records
- Zeroconf with mDNS
Let’s see how it works, shall we?
Tailscale is a VPN service that makes devices and applications you own securely accessible anywhere in the world. It uses the WireGuard protocol, and has apps for pretty much all platforms.
To discover your Tailscale-connected machines, you’ll need an API Access Token, and the name of your tailnet.
With that information in hand, run
--tailscale.net, for example:
wishlist --tailscale.net=charm --tailscale.key=ts-key-aaabbb...
And that’s it! Wishlist will discover all your tailnet’s machines on startup.
Note: We can’t get the open ports through the API, so all endpoints discovered will use the default SSH port (
22). You can change that with hints.
It’s also worth mentioning that Tailscale’s API keys expire after 90 days (max). To avoid the trouble of having to change the key every couple of months, you can also use their beta OAuth Clients.
Create an app with
here, and run with:
wishlist --tailscale.net=charm \ --tailscale.client.id=aaabbb... \ --tailscale.client.secret=tskey-client-aaaabbb...
This also gives Wishlist a more restricted access: only reading the device list, nothing else.
Zeroconf with mDNS
If you run anything Apple in your network, chances are they are all already
This happens because Apple devices employ
Bonjour (a Zeroconf implementation)
which is installed and configured by default.
For Avahi, installing and enabling the daemon (the package is usually named
avahi-daemon) will make your host available in the network as
To make the host available in Wishlist, though, you’ll need to expose a
You can do that by creating the file
/etc/avahi/services/ssh.service with the
<?xml version="1.0" standalone="no"?> <!DOCTYPE service-group SYSTEM "avahi-service.dtd"> <service-group> <name replace-wildcards="yes">%h</name> <service> <type>_ssh._tcp</type> <port>22</port> </service> </service-group>
You can check if you have any available endpoints by running:
# on Linux: avahi-browse --domain local _ssh._tcp # on macOS: dns-sd -B _ssh._tcp
All that being said, you can run
--zeroconf.enabled, and it’ll operate using sensible defaults:
wishlist --help | grep zeroconf to see all options.
Service Records (SRV) are specified in DNS for defining the host name and port number of servers for specific services.
It is defined as:
_service._proto.name. ttl IN SRV priority weight port target
Wishlist will look for records with
You can mimic what it’ll do with
dig, e.g. for the
dig SRV _ssh._tcp.caarlos0.dev +short
So, for instance, if I want to expose a
SRV record on port
I would add an entry like this to my DNS:
_ssh._tcp.caarlos0.dev. 300 IN SRV 10 2 2244 192.168.1.123.
Once you’ve done that, you can run
make it query the your name server and list the SRV records as endpoints.
You might be asking yourself: “What if I want to set some more advanced options in these endpoints?” That’s what hints are for.
Hints have a similar structure to the
endpoints setting, but they work
differently: if a hint doesn’t match a discovered endpoint, it won’t get
added to the final endpoint list, whereas regular
It works by using a
match field (which can be a glob), and then tries
to match all discovered endpoints hostnames with it.
If it matches, the options in that hint will be set onto the final endpoint.
You can set
port (especially useful with Tailscale),
description, and more.
Here’s an example showing all available fields:
hints: - match: "*.local" port: 23234 description: "A description of this endpoint.\nCan have multiple lines." user: notme remote_command: uptime -a forward_agent: true request_tty: true connect_timeout: 10s proxy_jump: user@host:22 link: name: Optional link name url: https://github.com/charmbracelet/wishlist identity_files: - ~/.ssh/id_ed25519 - ~/.ssh/charm_id_ed25519 set_env: - FOO=bar - BAR=baz send_env: - LC_* - LANG - SOME_ENV
You can see the full, commented out, configuration file here.
Once you have your configuration file, run
wishlist passing its path
Wishlist will then discover all the endpoints (through all options available),
and then, if there are any
hints, iterate through them and apply
them to the discovered nodes.
Finally, on server mode, you can specify a
so Wishlist will re-discover the nodes and also reload the configuration file,
re-applying the hints too.
We believe that those features will make your Wishlist-powered SSH directories easier to maintain and to use, and can’t wait to see what you’ll do with it.