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