{ config, lib, tailscalesdPackage ? null, ... }: let inherit (lib) mkEnableOption mkIf mkOption types ; cfg = config.services.tailscalesd; execStart = lib.concatStringsSep " " ( [ (lib.getExe' cfg.package "tailscalesd") ] ++ map lib.escapeShellArg cfg.extraArgs ); in { options.services.tailscalesd = { enable = mkEnableOption "tailscalesd Prometheus HTTP service discovery daemon"; package = mkOption { type = types.nullOr types.package; default = tailscalesdPackage; defaultText = "self.packages..tailscalesd"; description = "Package that provides the tailscalesd executable."; }; environment = mkOption { type = types.attrsOf types.str; default = { }; description = "Extra environment variables for tailscalesd."; }; credentials = { bearerTokenFile = mkOption { type = types.nullOr types.str; default = null; example = "/run/secrets/tailscalesd/bearer-token"; description = "Path to bearer token secret loaded with systemd LoadCredential."; }; clientIdFile = mkOption { type = types.nullOr types.str; default = null; example = "/run/secrets/tailscalesd/client-id"; description = "Path to Tailscale OAuth client id secret loaded with systemd LoadCredential."; }; clientSecretFile = mkOption { type = types.nullOr types.str; default = null; example = "/run/secrets/tailscalesd/client-secret"; description = "Path to Tailscale OAuth client secret loaded with systemd LoadCredential."; }; }; host = mkOption { type = types.str; default = "127.0.0.1"; description = "Bind address for tailscalesd."; }; port = mkOption { type = types.port; default = 9242; description = "Bind port for tailscalesd."; }; interval = mkOption { type = types.ints.positive; default = 60; description = "Polling interval in seconds."; }; extraArgs = mkOption { type = types.listOf types.str; default = [ ]; description = "Extra CLI arguments appended to the tailscalesd command."; }; openFirewall = mkOption { type = types.bool; default = false; description = "Open the service port in the host firewall."; }; }; config = mkIf cfg.enable { assertions = [ { assertion = cfg.package != null; message = "services.tailscalesd.package must be set when services.tailscalesd.enable = true."; } { assertion = cfg.credentials.bearerTokenFile != null; message = "services.tailscalesd.credentials.bearerTokenFile must be set when services.tailscalesd.enable = true."; } { assertion = cfg.credentials.clientIdFile != null; message = "services.tailscalesd.credentials.clientIdFile must be set when services.tailscalesd.enable = true."; } { assertion = cfg.credentials.clientSecretFile != null; message = "services.tailscalesd.credentials.clientSecretFile must be set when services.tailscalesd.enable = true."; } ]; networking.firewall.allowedTCPPorts = lib.optionals cfg.openFirewall [ cfg.port ]; systemd.services.tailscalesd = { description = "tailscalesd"; wantedBy = [ "multi-user.target" ]; wants = [ "network-online.target" ]; after = [ "network-online.target" ]; serviceConfig = { Type = "simple"; DynamicUser = true; ExecStart = execStart; Restart = "on-failure"; RestartSec = "5s"; NoNewPrivileges = true; PrivateTmp = true; ProtectHome = true; ProtectSystem = "strict"; WorkingDirectory = "/var/lib/tailscalesd"; StateDirectory = "tailscalesd"; LoadCredential = [ "bearer_token:${cfg.credentials.bearerTokenFile}" "client_id:${cfg.credentials.clientIdFile}" "client_secret:${cfg.credentials.clientSecretFile}" ]; }; environment = cfg.environment // { TAILSCALESD_HOST = cfg.host; TAILSCALESD_PORT = toString cfg.port; TAILSCALESD_INTERVAL = toString cfg.interval; TAILSCALESD_BEARER_TOKEN_FILE = "%d/bearer_token"; TAILSCALESD_CLIENT_ID_FILE = "%d/client_id"; TAILSCALESD_CLIENT_SECRET_FILE = "%d/client_secret"; }; }; }; }