Persist your Tailscale Darlings

Channelling Graham Christensen’s Erase your darlings I’m trying to configure tailscale to persist its configuration away from /var/lib/tailscale, which disappears at each reboot.

In line with the blog posts philosophy I don’t want to have to create and mount non ephemeral global file system at /var/lib/tailscale.

The blog post suggests using systemd.tmpfiles.rules to get links provisioned - which is what the nix configuration below attempts to do. However it isn’t successful - it seems that tailscale doesn’t follow symlinks???

Aug 12 12:10:57 nixvm systemd[1]: tailscaled.service: Control process exited, code=exited, status=238/STATE_DIRECTORY
Aug 12 12:10:57 nixvm systemd[1236]: tailscaled.service: Failed to set up special execution directory in /var/lib: Not a directory
Aug 12 12:10:57 nixvm systemd[1236]: tailscaled.service: Failed at step STATE_DIRECTORY spawning /nix/store/4vhrm5ibvkh50655lzyyx75iic9x8sm7-tailscale-1.8.6/bin/tailscaled: Not a directory

Anyone have any suggestions, other than patching the source code as part of the package build?

# Tailscale VPN

{ config, pkgs, lib, ... }:

with lib;

let
  enable = true;
  openssh = config.services.openssh.enable;
  ports = config.services.openssh.ports;
in
{
  # Tailscale VPN
  services.tailscale.enable = enable;

  # persist state
  systemd.tmpfiles.rules = [
    "L /var/lib/tailscale - - - - /persist/var/lib/tailscale"
  ];
  systemd.services.tailscaled.after = [ "systemd-tmpfiles-setup.service" ];


  # allow the Tailscale UDP port through the firewall; is this a nessasary setting? If so when?
  # networking.firewall.allowedUDPPorts = [ config.services.tailscale.port ];
  # Do we rely upon tailscale controls??? Then:
  # networking.firewall.trustedInterfaces = [ "tailscale0" ];
  # Or be explicit
  networking.firewall.interfaces."tailscale0".allowedTCPPorts = if enable && openssh then ports else []; # allow SSH from VPN
}

I’m not totally familiar with how all that works, but those error messages look like they’re coming from systemd, not tailscale itself. So you might need to twiddle with the systemd files.

I personally ran into this as well, and worked around systemd’s distaste for symlinks with a bind mount in my configuration.nix:

  fileSystems."/var/lib/tailscale" = {
    device = "/persist/var/lib/tailscale";
    options = [ "bind" ];
    noCheck = true;
  };
2 Likes

Hello,

Rather than adding an extra fileSystems entry, one can add a systemd override, directly, as described by Michael, in Weeknotes: 186.

systemd.services.tailscaled.serviceConfig.BindPaths = "/persist/var/lib/tailscale:/var/lib/tailscale";

Don’t be like me… remember to create the /persist/var/lib/tailscale directory, first.

Thanks for documenting that option.

However isn’t that service local? i.e. the tailscale cli wouldn’t work (unless there was some way to wrap it and give it to have a similar binding in place while it executes?

The tailscale cli appears to work, for me? I’m guessing that the cli uses /run/tailscale/tailscaled.sock, rather than using the /var/lib/tailscale directory itself. I have not yet read through the tailscale source, to confirm this.

(Please excuse the delay — my internet access has been spotty.)

Maybe things have changed… I’ll have to have another look at that!