Skip to content

Repository files navigation

traytail

CI License: MIT

A minimal Tailscale tray icon for Wayland bars that host the StatusNotifierItem protocol — built and tested against ashell (see ashell for configuration notes).

No GUI toolkit. Pure Go, one dependency (github.com/godbus/dbus/v5), ~900 lines.

Features

  • Status icon: connected (filled) / offline (hollow) / exit node in use (green ring) / login required (amber)
  • Multiple profiles — switch accounts (tailscale switch) from the Profile submenu
  • Exit nodes, cleanly split:
    • Own exit nodes — regular tailnet peers with ExitNodeOption
    • Mullvad VPN — grouped by country (tag:mullvad-exit-node-*)
  • Connect / Disconnect, copy Tailscale IP, open admin console
  • Left-click on the bar icon opens the menu; updates are pushed via NewIcon / LayoutUpdated signals (5s poll, deduplicated)

Requirements

  • tailscale CLI in $PATH, tailscaled running
  • You must be the Tailscale "operator" once:
    sudo tailscale set --operator=$USER
    

Build & run

go build .
./traytail

Menu

Haruhi — zaolin.github          (click = admin console)
Copy IP: 100.121.168.35
────────────
Profile: zaolin@github ▸        (if >1 account; parent shows active)
  ● zaolin@github               (active: click does nothing)
  ○ philipp@binarly.io          (click = switch, refreshes menu)
Exit node: Berlin ▸             (parent shows full city of the active node)
  ● Auto (smart: away→own, home→Mullvad)
  ○ Off
  ──
  ○ zds-nabara                  (own nodes, first DNS label)
  ──
  Mullvad ▸
     ● Germany ▸                (active country hoisted to top)
     Albania ▸  Argentina ▸ …   (full country names, sorted)
        ● Berlin                (city labels; ● marks the active one)
Disconnect
────────────
Admin console
Quit

Exit node selection: entries are labelled with full city/country names parsed from tailscale exit-node list (hostname fallback when that fails). The active country is hoisted to the top of the Mullvad list, and every item carries a ●/○ radio marker — including an explicit Off entry, so there is no click-active-to-disable trick. State is shown as a text glyph instead of checkmark properties because SNI hosts like ashell render toggle-type: checkmark items as switches.

Smart auto replaces tailscale's native auto:any with a policy traytail manages itself: when your machine is away (no local interface address inside the LAN routes advertised by your own exit node) it routes through the own exit node; when at home it switches to the last-used Mullvad node (persisted across restarts). The policy re-applies only when the home/away state flips — manual selections and Off stay untouched while the state is stable, and any manual pick or Off disables smart auto.

Deselecting the own exit node (clicking its ● item) applies the last-used Mullvad node instead — regardless of whether smart auto is on. Active nodes are matched by Tailscale IP, so a user-renamed device (Nabara vs zds-nabara.tailb4e47d.ts.net) still shows as selected.

Notes

  • Exit node selection uses tailscale set --exit-node=<hostname>; labels and grouping come from tailscale exit-node list.
  • Works with any SNI host (ashell, waybar, KDE Plasma), not just ashell.

ashell

traytail is developed against ashell's tray module and works out of the box with its defaults — the module only appears once a tray icon exists, so no configuration is needed. Notes for ashell setups:

  • Menu opens on left click by default (ashell's right_click is unset → left click opens the context menu). If you set right_click = "Menu", left click switches to activating the app instead — traytail has no window, so keep the default or use right_click = "Open".
  • ashell renders checkmark items as switches, which is why traytail carries all selection state as ●/○ text glyphs on plain items — every menu entry renders as a normal button and closes the menu on click.
  • Submenus render inline with ▸ toggles; the exit-node menu nests Mullvad ▸ country ▸ city, which ashell displays fine (indentation per level).
  • traytail registers under the name traytail; add it to ashell's [tray] blocklist only if you don't want it shown.
  • Other SNI hosts (waybar with SNI support, KDE Plasma) should work too, but are untested — ashell is the reference host.

Development

make build    # go build
make test     # unit tests with -race
make cover    # unit + integration coverage (~93%)
make integration  # DBus integration tests under a session bus

Install

sudo make install        # build + install to /usr/local/bin
sudo make uninstall      # remove it again

Custom location (no root needed):

make install PREFIX=$HOME/.local/bin

License

MIT

About

Minimal Tailscale tray icon for Wayland bars (SNI)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages