at 24.11-pre 5.1 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.services.tailscale; 7 isNetworkd = config.networking.useNetworkd; 8in { 9 meta.maintainers = with maintainers; [ mbaillie mfrw ]; 10 11 options.services.tailscale = { 12 enable = mkEnableOption "Tailscale client daemon"; 13 14 port = mkOption { 15 type = types.port; 16 default = 41641; 17 description = "The port to listen on for tunnel traffic (0=autoselect)."; 18 }; 19 20 interfaceName = mkOption { 21 type = types.str; 22 default = "tailscale0"; 23 description = ''The interface name for tunnel traffic. Use "userspace-networking" (beta) to not use TUN.''; 24 }; 25 26 permitCertUid = mkOption { 27 type = types.nullOr types.nonEmptyStr; 28 default = null; 29 description = "Username or user ID of the user allowed to to fetch Tailscale TLS certificates for the node."; 30 }; 31 32 package = lib.mkPackageOption pkgs "tailscale" {}; 33 34 openFirewall = mkOption { 35 default = false; 36 type = types.bool; 37 description = "Whether to open the firewall for the specified port."; 38 }; 39 40 useRoutingFeatures = mkOption { 41 type = types.enum [ "none" "client" "server" "both" ]; 42 default = "none"; 43 example = "server"; 44 description = '' 45 Enables settings required for Tailscale's routing features like subnet routers and exit nodes. 46 47 To use these these features, you will still need to call `sudo tailscale up` with the relevant flags like `--advertise-exit-node` and `--exit-node`. 48 49 When set to `client` or `both`, reverse path filtering will be set to loose instead of strict. 50 When set to `server` or `both`, IP forwarding will be enabled. 51 ''; 52 }; 53 54 authKeyFile = mkOption { 55 type = types.nullOr types.path; 56 default = null; 57 example = "/run/secrets/tailscale_key"; 58 description = '' 59 A file containing the auth key. 60 ''; 61 }; 62 63 extraUpFlags = mkOption { 64 description = "Extra flags to pass to {command}`tailscale up`."; 65 type = types.listOf types.str; 66 default = []; 67 example = ["--ssh"]; 68 }; 69 70 extraDaemonFlags = mkOption { 71 description = "Extra flags to pass to {command}`tailscaled`."; 72 type = types.listOf types.str; 73 default = []; 74 example = ["--no-logs-no-support"]; 75 }; 76 }; 77 78 config = mkIf cfg.enable { 79 environment.systemPackages = [ cfg.package ]; # for the CLI 80 systemd.packages = [ cfg.package ]; 81 systemd.services.tailscaled = { 82 wantedBy = [ "multi-user.target" ]; 83 path = [ 84 pkgs.procps # for collecting running services (opt-in feature) 85 pkgs.getent # for `getent` to look up user shells 86 pkgs.kmod # required to pass tailscale's v6nat check 87 ] ++ lib.optional config.networking.resolvconf.enable config.networking.resolvconf.package; 88 serviceConfig.Environment = [ 89 "PORT=${toString cfg.port}" 90 ''"FLAGS=--tun ${lib.escapeShellArg cfg.interfaceName} ${lib.concatStringsSep " " cfg.extraDaemonFlags}"'' 91 ] ++ (lib.optionals (cfg.permitCertUid != null) [ 92 "TS_PERMIT_CERT_UID=${cfg.permitCertUid}" 93 ]); 94 # Restart tailscaled with a single `systemctl restart` at the 95 # end of activation, rather than a `stop` followed by a later 96 # `start`. Activation over Tailscale can hang for tens of 97 # seconds in the stop+start setup, if the activation script has 98 # a significant delay between the stop and start phases 99 # (e.g. script blocked on another unit with a slow shutdown). 100 # 101 # Tailscale is aware of the correctness tradeoff involved, and 102 # already makes its upstream systemd unit robust against unit 103 # version mismatches on restart for compatibility with other 104 # linux distros. 105 stopIfChanged = false; 106 }; 107 108 systemd.services.tailscaled-autoconnect = mkIf (cfg.authKeyFile != null) { 109 after = ["tailscaled.service"]; 110 wants = ["tailscaled.service"]; 111 wantedBy = [ "multi-user.target" ]; 112 serviceConfig = { 113 Type = "oneshot"; 114 }; 115 script = '' 116 status=$(${config.systemd.package}/bin/systemctl show -P StatusText tailscaled.service) 117 if [[ $status != Connected* ]]; then 118 ${cfg.package}/bin/tailscale up --auth-key 'file:${cfg.authKeyFile}' ${escapeShellArgs cfg.extraUpFlags} 119 fi 120 ''; 121 }; 122 123 boot.kernel.sysctl = mkIf (cfg.useRoutingFeatures == "server" || cfg.useRoutingFeatures == "both") { 124 "net.ipv4.conf.all.forwarding" = mkOverride 97 true; 125 "net.ipv6.conf.all.forwarding" = mkOverride 97 true; 126 }; 127 128 networking.firewall.allowedUDPPorts = mkIf cfg.openFirewall [ cfg.port ]; 129 130 networking.firewall.checkReversePath = mkIf (cfg.useRoutingFeatures == "client" || cfg.useRoutingFeatures == "both") "loose"; 131 132 networking.dhcpcd.denyInterfaces = [ cfg.interfaceName ]; 133 134 systemd.network.networks."50-tailscale" = mkIf isNetworkd { 135 matchConfig = { 136 Name = cfg.interfaceName; 137 }; 138 linkConfig = { 139 Unmanaged = true; 140 ActivationPolicy = "manual"; 141 }; 142 }; 143 }; 144}