1{ config
2, lib
3, pkgs
4, ...
5}:
6
7let
8 cfg = config.services.tts;
9in
10
11{
12 options.services.tts = let
13 inherit (lib) literalExpression mkOption mdDoc mkEnableOption types;
14 in {
15 servers = mkOption {
16 type = types.attrsOf (types.submodule (
17 { ... }: {
18 options = {
19 enable = mkEnableOption (mdDoc "Coqui TTS server");
20
21 port = mkOption {
22 type = types.port;
23 example = 5000;
24 description = mdDoc ''
25 Port to bind the TTS server to.
26 '';
27 };
28
29 model = mkOption {
30 type = types.nullOr types.str;
31 default = "tts_models/en/ljspeech/tacotron2-DDC";
32 example = null;
33 description = mdDoc ''
34 Name of the model to download and use for speech synthesis.
35
36 Check `tts-server --list_models` for possible values.
37
38 Set to `null` to use a custom model.
39 '';
40 };
41
42 useCuda = mkOption {
43 type = types.bool;
44 default = false;
45 example = true;
46 description = mdDoc ''
47 Whether to offload computation onto a CUDA compatible GPU.
48 '';
49 };
50
51 extraArgs = mkOption {
52 type = types.listOf types.str;
53 default = [];
54 description = mdDoc ''
55 Extra arguments to pass to the server commandline.
56 '';
57 };
58 };
59 }
60 ));
61 default = {};
62 example = literalExpression ''
63 {
64 english = {
65 port = 5300;
66 model = "tts_models/en/ljspeech/tacotron2-DDC";
67 };
68 german = {
69 port = 5301;
70 model = "tts_models/de/thorsten/tacotron2-DDC";
71 };
72 dutch = {
73 port = 5302;
74 model = "tts_models/nl/mai/tacotron2-DDC";
75 };
76 }
77 '';
78 description = mdDoc ''
79 TTS server instances.
80 '';
81 };
82 };
83
84 config = let
85 inherit (lib) mkIf mapAttrs' nameValuePair optionalString concatMapStringsSep escapeShellArgs;
86 in mkIf (cfg.servers != {}) {
87 systemd.services = mapAttrs' (server: options:
88 nameValuePair "tts-${server}" {
89 description = "Coqui TTS server instance ${server}";
90 after = [
91 "network-online.target"
92 ];
93 wantedBy = [
94 "multi-user.target"
95 ];
96 path = with pkgs; [
97 espeak-ng
98 ];
99 environment.HOME = "/var/lib/tts";
100 serviceConfig = {
101 DynamicUser = true;
102 User = "tts";
103 StateDirectory = "tts";
104 ExecStart = "${pkgs.tts}/bin/tts-server --port ${toString options.port}"
105 + optionalString (options.model != null) " --model_name ${options.model}"
106 + optionalString (options.useCuda) " --use_cuda"
107 + (concatMapStringsSep " " escapeShellArgs options.extraArgs);
108 CapabilityBoundingSet = "";
109 DeviceAllow = if options.useCuda then [
110 # https://docs.nvidia.com/dgx/pdf/dgx-os-5-user-guide.pdf
111 "/dev/nvidia1"
112 "/dev/nvidia2"
113 "/dev/nvidia3"
114 "/dev/nvidia4"
115 "/dev/nvidia-caps/nvidia-cap1"
116 "/dev/nvidia-caps/nvidia-cap2"
117 "/dev/nvidiactl"
118 "/dev/nvidia-modeset"
119 "/dev/nvidia-uvm"
120 "/dev/nvidia-uvm-tools"
121 ] else "";
122 DevicePolicy = "closed";
123 LockPersonality = true;
124 # jit via numba->llvmpipe
125 MemoryDenyWriteExecute = false;
126 PrivateDevices = true;
127 PrivateUsers = true;
128 ProtectHome = true;
129 ProtectHostname = true;
130 ProtectKernelLogs = true;
131 ProtectKernelModules = true;
132 ProtectKernelTunables = true;
133 ProtectControlGroups = true;
134 ProtectProc = "invisible";
135 ProcSubset = "pid";
136 RestrictAddressFamilies = [
137 "AF_INET"
138 "AF_INET6"
139 ];
140 RestrictNamespaces = true;
141 RestrictRealtime = true;
142 SystemCallArchitectures = "native";
143 SystemCallFilter = [
144 "@system-service"
145 "~@privileged"
146 ];
147 UMask = "0077";
148 };
149 }) cfg.servers;
150 };
151}