Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Wayle Tailscale Module

A custom Wayle bar module for managing your Tailscale VPN directly from the status bar. It shows connection state at a glance and lets you connect, disconnect, switch exit nodes, and copy your host IP without leaving the bar.

Features

  • Always-visible status indicator — a shield icon that reflects the current BackendState (connected, routing through an exit node, starting, or disconnected). When Tailscale is down it also shows an off label, so a dropped connection is impossible to miss.
  • Rich tooltip — hover to see your Tailscale IP, the active exit node, the number of online peers, and any health warnings reported by the daemon.
  • Left-click — toggle the connection (tailscale up / tailscale down).
  • Right-click — open a rofi menu to connect/disconnect or pick an exit node (including Mullvad nodes if enabled).
  • Middle-click — copy your host's Tailscale IP to the clipboard, with a desktop notification for confirmation.

Requirements

The module is a set of shell scripts driven by Wayle's custom-module system. It relies on the following command-line tools being available in the session PATH:

Tool Provides Used for
tailscale Tailscale CLI + daemon Querying and controlling the VPN
jq jq Parsing tailscale status --json
wl-copy wl-clipboard Copying the IP (Wayland clipboard)
notify-send libnotify The copy confirmation popup
rofi rofi (Wayland build) The exit-node picker menu

Wayle itself must be installed and running. See the Wayle getting-started guide if you don't have it set up yet.

Files

File Role
scripts/tailscale-status.sh Poll emitter. Prints one JSON line describing the current state (icon key, label, CSS class, tooltip) for the bar to render.
scripts/tailscale-toggle.sh Left-click handler. Connects or disconnects depending on the current state.
scripts/tailscale-menu.sh Right-click handler. Builds and shows the rofi menu, then applies the chosen action.
scripts/tailscale-copyip.sh Middle-click handler. Copies the host IP and fires a notification.
config.toml The module declaration to paste into ~/.config/wayle/config.toml (manual, non-Nix setups).
default.nix The same module declaration expressed as a NixOS module for services.wayle, for declarative setups.

The four scripts must be placed in ~/.config/wayle/scripts/ regardless of which installation method you use below — default.nix only declares the module configuration, it does not deploy the scripts.

Installation

1. Install the scripts

mkdir -p ~/.config/wayle/scripts
# copy the four .sh files into ~/.config/wayle/scripts/
chmod +x ~/.config/wayle/scripts/tailscale-*.sh

2. Allow your user to control Tailscale without sudo

By default tailscale up, down, and set require root. Register your user as the Tailscale operator once, so the click actions run without privilege escalation:

sudo tailscale set --operator=$USER

3. Declare the module

Pick whichever matches your setup — both produce the same configuration.

Option A — manual config.toml edit

Copy the contents of config.toml into ~/.config/wayle/config.toml:

[[modules.custom]]
id = "tailscale"
interval-ms  = 3000
command      = "~/.config/wayle/scripts/tailscale-status.sh"
left-click   = "~/.config/wayle/scripts/tailscale-toggle.sh"
right-click  = "~/.config/wayle/scripts/tailscale-menu.sh"
middle-click = "~/.config/wayle/scripts/tailscale-copyip.sh"
on-action    = "~/.config/wayle/scripts/tailscale-status.sh"
format = "{{ label }}"
icon-map = { up = "ld-shield-check-symbolic", exit = "ld-globe-symbolic", starting = "ld-shield-symbolic", down = "ld-shield-off-symbolic" }

If the tilde paths don't resolve on your setup, replace ~ with the absolute path (for example $HOME/.config/wayle/scripts/...).

Option B — NixOS module (default.nix)

If your NixOS configuration already exposes the services.wayle option (i.e. Wayle is managed as a NixOS module), import default.nix instead of hand-editing config.toml — see NixOS notes below for details and caveats.

4. Add it to a bar

Reference the module in a bar layout as custom-tailscale:

[[bar.layout]]
monitor = "*"
right = ["custom-tailscale", "volume", "network", "battery"]

Wayle reloads on save, so the module should appear as soon as you write the file.

5. Sync the icons

The four icons in icon-map (ld-shield-check-symbolic, ld-globe-symbolic, ld-shield-symbolic, ld-shield-off-symbolic) need to exist in your local icon theme before Wayle can render them:

wayle icons sync

This scans your config for every referenced icon name and downloads whatever is missing. Without it, the module still works but shows an empty box instead of an icon — see Customization for details and Troubleshooting if sync reports a failure.

How it works

The command script runs every interval-ms (3 s) and emits a JSON object. Wayle maps its fields onto the widget:

  • alt selects the icon from icon-map (up, exit, starting, down).
  • label is the visible text, rendered through format = "{{ label }}".
  • class adds CSS classes to the widget (tailscale-connected, tailscale-exit, tailscale-disconnected, plus warning when the daemon reports a health issue).
  • tooltip is shown on hover.

Each click handler runs its own script, and on-action re-runs the status emitter immediately afterwards so the bar updates without waiting for the next poll tick.

Usage

Action Result
Hover Show IP, exit node, peer count, and health details
Left-click Connect if down, disconnect if up
Right-click Open the rofi menu (connect/disconnect + exit-node selection)
Middle-click Copy the host's Tailscale IP and show a notification

NixOS notes

On NixOS, install the dependencies declaratively rather than imperatively. Because Wayle runs inside your session, it inherits this PATH:

# Home Manager
home.packages = with pkgs; [
  jq            # parses the Tailscale status
  wl-clipboard  # provides wl-copy
  libnotify     # provides notify-send
  rofi-wayland  # rofi, Wayland build (see below)
];

# Tailscale itself is best enabled as a service (installs the CLI and daemon):
services.tailscale.enable = true;

Two NixOS-specific details:

  • Use rofi-wayland, not rofi. Under Hyprland (Wayland), the plain rofi package is the X11 build and only works through XWayland. The rofi-wayland package still provides a binary named rofi, so no script change is needed — it just has to be the version in your PATH. (Custom rofi plugins don't work with the Wayland port without an overlay, but the simple -dmenu menu used here is unaffected.)
  • The tailscale set --operator=$USER step still applies once the service is running.

The default.nix module

default.nix declares the same [[modules.custom]] block from step 3 through the services.wayle NixOS option, for setups where Wayle itself is configured as a NixOS module (rather than through ~/.config/wayle/config.toml directly):

{ pkgs, lib, ... }:
{
  services.wayle = {
    enable = true;

    settings = {
      bar.layout = [
        {
          monitor = "DP-1";
          show = true;
          right = [ "battery" "network" "custom-tailscale" "bluetooth" "volume" "systray" ];
        }
      ];

      modules.custom = [
        {
          id = "tailscale";
          interval-ms  = 3000;
          command      = "~/.config/wayle/scripts/tailscale-status.sh";
          left-click   = "~/.config/wayle/scripts/tailscale-toggle.sh";
          right-click  = "~/.config/wayle/scripts/tailscale-menu.sh";
          middle-click = "~/.config/wayle/scripts/tailscale-copyip.sh";
          on-action    = "~/.config/wayle/scripts/tailscale-status.sh";
          format = "{{ label }}";
          icon-map = {
            up = "ld-shield-check-symbolic";
            exit = "ld-globe-symbolic";
            starting = "ld-shield-symbolic";
            down = "ld-shield-off-symbolic";
          };
        }
      ];
    };
  };
}

To use it:

  1. Copy default.nix into your NixOS configuration (for example modules/wayle-tailscale.nix), then import it:

    # configuration.nix
    { imports = [ ./modules/wayle-tailscale.nix ]; }
  2. Merge the bar.layout block into your existing layout instead of copy-pasting it verbatim — the example above defines a full layout (monitor = "DP-1", a specific set of modules) that will overwrite any layout you already declare elsewhere in services.wayle.settings. Only the "custom-tailscale" entry and the modules.custom block are specific to this module.

  3. default.nix only sets configuration — it does not install jq, wl-clipboard, libnotify, rofi-wayland, or the four scripts. Keep those declared separately (home.packages above, and the scripts copied per step 1 or managed via home.file."/.config/wayle/scripts/...".source).

  4. services.wayle must already exist as an option in your configuration (i.e. you have the Wayle NixOS/Home Manager module imported). If you configure Wayle purely through ~/.config/wayle/config.toml, use Option A instead.

  5. rebuild switch (or your usual home-manager switch / nixos-rebuild switch) to apply, then reload Wayle.

Notifications

The middle-click popup is emitted with notify-send and rendered by whichever notification daemon owns the org.freedesktop.Notifications D-Bus name. Wayle ships its own notification daemon, so if Wayle is the active one the popup is drawn natively by Wayle.

Only one notification daemon can own that D-Bus name at a time. If another daemon (mako, dunst, swaync, …) is running, it will handle the popup instead. Check with:

pgrep -l 'mako|dunst|swaync'

If repeated copies stack multiple bubbles instead of replacing one, swap the hint in tailscale-copyip.sh from x-canonical-private-synchronous to x-dunst-stack-tag (the exact hint honored depends on the active daemon).

Customization

  • Icons. The ld-*-symbolic names in icon-map are Lucide icons (the ld- prefix), one of Wayle's five built-in icon sources (tb- Tabler, tbf- Tabler Filled, si- Simple Icons, md- Material, ld- Lucide). Icon names resolve against your local icon theme at ~/.local/share/wayle/icons/ — they aren't fetched on the fly, so a name has to be installed there before it will render. Run:

    wayle icons sync

    This reads your effective config, collects every icon name it references (including the four names in this module's icon-map), and downloads whatever is missing from the matching CDN source. It reports each icon as installed, failed, or skipped — imported icons (cm- prefix) are skipped because their source SVG only exists on the machine that imported them via wayle icons import, not on the CDN. Re-run it after changing icon-map or moving your config to a new machine.

    If you'd rather use a different icon, swap the name in icon-map and re-run wayle icons sync (or wayle icons install <source> <slug> for a one-off fetch) — see Wayle's Custom icons guide.

  • Colors. Because the scripts emit CSS classes, you can style each state from a Wayle custom stylesheet, for example:

    .tailscale-disconnected { color: #f7768e; }
    .tailscale-exit         { color: #7aa2f7; }
    .warning                { color: #e0af68; }
  • Label. By default the bar shows nothing when connected without an exit node, the exit node's short name when routing, and off when disconnected. Adjust the label branches in tailscale-status.sh to taste.

Troubleshooting

  • Nothing happens on click / permission errors — make sure you ran sudo tailscale set --operator=$USER and re-checked with tailscale status.
  • The rofi menu doesn't appear — confirm you're using the Wayland build (rofi-wayland on NixOS) and that rofi is in Wayle's PATH.
  • No copy notification — verify notify-send exists (libnotify) and that a notification daemon is running (see Notifications).
  • Icon shows an empty square — the icon named in icon-map hasn't been downloaded to ~/.local/share/wayle/icons/ yet. Run wayle icons sync to fetch every icon referenced in your config (see Customization). If it reports the icon as failed, double-check the name is spelled correctly and exists in its source set, then replace it in icon-map.
  • Module doesn't render at all — run Wayle in the foreground (wayle panel restart, then watch the logs) and confirm the status script prints valid JSON: ~/.config/wayle/scripts/tailscale-status.sh.

A note on the rofi menu

The exit-node picker uses rofi because it works on any Wayle version and gives a flexible, action-per-item menu. Recent Wayle versions also added native dropdown support for custom modules, which renders inside Wayle's own themed popup. If you'd rather use that, inspect the dropdown fields available in your installed version with:

wayle config default --stdout | grep -iA5 dropdown
grep -i dropdown ~/.config/wayle/schema.json

Both are generated from your installed binary, so they match your exact version.

About

Tailscale module for Wayle

Topics

Resources

Stars

Watchers

Forks

Contributors

Languages