{ config, lib, pkgs, ... }: with lib; let cfg = config.services.tailscale; firewallOn = config.networking.firewall.enable; rpfMode = config.networking.firewall.checkReversePath; isNetworkd = config.networking.useNetworkd; rpfIsStrict = rpfMode == true || rpfMode == "strict"; in { meta.maintainers = with maintainers; [ danderson mbaillie twitchyliquid64 ]; options.services.tailscale = { enable = mkEnableOption (lib.mdDoc "Tailscale client daemon"); port = mkOption { type = types.port; default = 41641; description = lib.mdDoc "The port to listen on for tunnel traffic (0=autoselect)."; }; interfaceName = mkOption { type = types.str; default = "tailscale0"; description = lib.mdDoc ''The interface name for tunnel traffic. Use "userspace-networking" (beta) to not use TUN.''; }; permitCertUid = mkOption { type = types.nullOr types.nonEmptyStr; default = null; description = lib.mdDoc "Username or user ID of the user allowed to to fetch Tailscale TLS certificates for the node."; }; package = mkOption { type = types.package; default = pkgs.tailscale; defaultText = literalExpression "pkgs.tailscale"; description = lib.mdDoc "The package to use for tailscale"; }; }; config = mkIf cfg.enable { warnings = optional (firewallOn && rpfIsStrict) "Strict reverse path filtering breaks Tailscale exit node use and some subnet routing setups. Consider setting `networking.firewall.checkReversePath` = 'loose'"; environment.systemPackages = [ cfg.package ]; # for the CLI systemd.packages = [ cfg.package ]; systemd.services.tailscaled = { wantedBy = [ "multi-user.target" ]; path = [ config.networking.resolvconf.package # for configuring DNS in some configs pkgs.procps # for collecting running services (opt-in feature) pkgs.glibc # for `getent` to look up user shells ]; serviceConfig.Environment = [ "PORT=${toString cfg.port}" ''"FLAGS=--tun ${lib.escapeShellArg cfg.interfaceName}"'' ] ++ (lib.optionals (cfg.permitCertUid != null) [ "TS_PERMIT_CERT_UID=${cfg.permitCertUid}" ]); # Restart tailscaled with a single `systemctl restart` at the # end of activation, rather than a `stop` followed by a later # `start`. Activation over Tailscale can hang for tens of # seconds in the stop+start setup, if the activation script has # a significant delay between the stop and start phases # (e.g. script blocked on another unit with a slow shutdown). # # Tailscale is aware of the correctness tradeoff involved, and # already makes its upstream systemd unit robust against unit # version mismatches on restart for compatibility with other # linux distros. stopIfChanged = false; }; networking.dhcpcd.denyInterfaces = [ cfg.interfaceName ]; systemd.network.networks."50-tailscale" = mkIf isNetworkd { matchConfig = { Name = cfg.interfaceName; }; linkConfig = { Unmanaged = true; ActivationPolicy = "manual"; }; }; }; }