1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.tailscale;
7 firewallOn = config.networking.firewall.enable;
8 rpfMode = config.networking.firewall.checkReversePath;
9 isNetworkd = config.networking.useNetworkd;
10 rpfIsStrict = rpfMode == true || rpfMode == "strict";
11in {
12 meta.maintainers = with maintainers; [ danderson mbaillie twitchyliquid64 ];
13
14 options.services.tailscale = {
15 enable = mkEnableOption (lib.mdDoc "Tailscale client daemon");
16
17 port = mkOption {
18 type = types.port;
19 default = 41641;
20 description = lib.mdDoc "The port to listen on for tunnel traffic (0=autoselect).";
21 };
22
23 interfaceName = mkOption {
24 type = types.str;
25 default = "tailscale0";
26 description = lib.mdDoc ''The interface name for tunnel traffic. Use "userspace-networking" (beta) to not use TUN.'';
27 };
28
29 permitCertUid = mkOption {
30 type = types.nullOr types.nonEmptyStr;
31 default = null;
32 description = lib.mdDoc "Username or user ID of the user allowed to to fetch Tailscale TLS certificates for the node.";
33 };
34
35 package = mkOption {
36 type = types.package;
37 default = pkgs.tailscale;
38 defaultText = literalExpression "pkgs.tailscale";
39 description = lib.mdDoc "The package to use for tailscale";
40 };
41 };
42
43 config = mkIf cfg.enable {
44 warnings = optional (firewallOn && rpfIsStrict) ''
45 Strict reverse path filtering breaks Tailscale exit node use and some subnet routing setups. Consider setting:
46
47 networking.firewall.checkReversePath = "loose";
48 '';
49 environment.systemPackages = [ cfg.package ]; # for the CLI
50 systemd.packages = [ cfg.package ];
51 systemd.services.tailscaled = {
52 wantedBy = [ "multi-user.target" ];
53 path = [
54 config.networking.resolvconf.package # for configuring DNS in some configs
55 pkgs.procps # for collecting running services (opt-in feature)
56 pkgs.glibc # for `getent` to look up user shells
57 ];
58 serviceConfig.Environment = [
59 "PORT=${toString cfg.port}"
60 ''"FLAGS=--tun ${lib.escapeShellArg cfg.interfaceName}"''
61 ] ++ (lib.optionals (cfg.permitCertUid != null) [
62 "TS_PERMIT_CERT_UID=${cfg.permitCertUid}"
63 ]);
64 # Restart tailscaled with a single `systemctl restart` at the
65 # end of activation, rather than a `stop` followed by a later
66 # `start`. Activation over Tailscale can hang for tens of
67 # seconds in the stop+start setup, if the activation script has
68 # a significant delay between the stop and start phases
69 # (e.g. script blocked on another unit with a slow shutdown).
70 #
71 # Tailscale is aware of the correctness tradeoff involved, and
72 # already makes its upstream systemd unit robust against unit
73 # version mismatches on restart for compatibility with other
74 # linux distros.
75 stopIfChanged = false;
76 };
77
78 networking.dhcpcd.denyInterfaces = [ cfg.interfaceName ];
79
80 systemd.network.networks."50-tailscale" = mkIf isNetworkd {
81 matchConfig = {
82 Name = cfg.interfaceName;
83 };
84 linkConfig = {
85 Unmanaged = true;
86 ActivationPolicy = "manual";
87 };
88 };
89 };
90}