1{
2 config,
3 lib,
4 pkgs,
5 utils,
6 ...
7}:
8
9let
10 cfg = config.services.wyoming.piper;
11
12 inherit (lib)
13 literalExpression
14 mkOption
15 mkEnableOption
16 mkPackageOption
17 types
18 ;
19
20 inherit (builtins)
21 toString
22 ;
23
24 inherit (utils)
25 escapeSystemdExecArgs
26 ;
27in
28
29{
30 options.services.wyoming.piper = with types; {
31 package = mkPackageOption pkgs "wyoming-piper" { };
32
33 servers = mkOption {
34 default = { };
35 description = ''
36 Attribute set of wyoming-piper instances to spawn.
37 '';
38 type = types.attrsOf (
39 types.submodule (
40 { ... }:
41 {
42 options = {
43 enable = mkEnableOption "Wyoming Piper server";
44
45 voice = mkOption {
46 type = str;
47 example = "en-us-ryan-medium";
48 description = ''
49 Name of the voice model to use. See the following website for samples:
50 https://rhasspy.github.io/piper-samples/
51 '';
52 };
53
54 uri = mkOption {
55 type = strMatching "^(tcp|unix)://.*$";
56 example = "tcp://0.0.0.0:10200";
57 description = ''
58 URI to bind the wyoming server to.
59 '';
60 };
61
62 speaker = mkOption {
63 type = ints.unsigned;
64 default = 0;
65 description = ''
66 ID of a specific speaker in a multi-speaker model.
67 '';
68 apply = toString;
69 };
70
71 noiseScale = mkOption {
72 type = float;
73 default = 0.667;
74 description = ''
75 Generator noise value.
76 '';
77 apply = toString;
78 };
79
80 noiseWidth = mkOption {
81 type = float;
82 default = 0.333;
83 description = ''
84 Phoneme width noise value.
85 '';
86 apply = toString;
87 };
88
89 lengthScale = mkOption {
90 type = float;
91 default = 1.0;
92 description = ''
93 Phoneme length value.
94 '';
95 apply = toString;
96 };
97
98 streaming = mkEnableOption "audio streaming on sentence boundaries" // {
99 default = true;
100 };
101
102 useCUDA = mkOption {
103 type = bool;
104 default = pkgs.config.cudaSupport;
105 defaultText = literalExpression "pkgs.config.cudaSupport";
106 description = ''
107 Whether to accelerate the underlying onnxruntime library with CUDA.
108 '';
109 };
110
111 extraArgs = mkOption {
112 type = listOf str;
113 default = [ ];
114 description = ''
115 Extra arguments to pass to the server commandline.
116 '';
117 };
118 };
119 }
120 )
121 );
122 };
123 };
124
125 config =
126 let
127 inherit (lib)
128 mapAttrs'
129 mkIf
130 nameValuePair
131 ;
132 in
133 mkIf (cfg.servers != { }) {
134 systemd.services = mapAttrs' (
135 server: options:
136 nameValuePair "wyoming-piper-${server}" {
137 inherit (options) enable;
138 description = "Wyoming Piper server instance ${server}";
139 wants = [
140 "network-online.target"
141 ];
142 after = [
143 "network-online.target"
144 ];
145 wantedBy = [
146 "multi-user.target"
147 ];
148 serviceConfig = {
149 DynamicUser = true;
150 User = "wyoming-piper";
151 StateDirectory = [ "wyoming/piper" ];
152 # https://github.com/home-assistant/addons/blob/master/piper/rootfs/etc/s6-overlay/s6-rc.d/piper/run
153 ExecStart = escapeSystemdExecArgs (
154 [
155 (lib.getExe cfg.package)
156 "--data-dir"
157 "/var/lib/wyoming/piper"
158 "--uri"
159 options.uri
160 "--voice"
161 options.voice
162 "--speaker"
163 options.speaker
164 "--length-scale"
165 options.lengthScale
166 "--noise-scale"
167 options.noiseScale
168 "--noise-w-scale"
169 options.noiseWidth
170 ]
171 ++ lib.optionals options.streaming [
172 "--streaming"
173 ]
174 ++ lib.optionals options.useCUDA [
175 "--use-cuda"
176 ]
177 ++ options.extraArgs
178 );
179 CapabilityBoundingSet = "";
180 DeviceAllow = "";
181 DevicePolicy = "closed";
182 LockPersonality = true;
183 MemoryDenyWriteExecute = false; # required for onnxruntime
184 PrivateDevices = true;
185 PrivateUsers = true;
186 ProtectHome = true;
187 ProtectHostname = true;
188 ProtectKernelLogs = true;
189 ProtectKernelModules = true;
190 ProtectKernelTunables = true;
191 ProtectControlGroups = true;
192 ProtectProc = "invisible";
193 ProcSubset = "all"; # for onnxruntime, which queries cpuinfo
194 RestrictAddressFamilies = [
195 "AF_INET"
196 "AF_INET6"
197 "AF_UNIX"
198 ];
199 RestrictNamespaces = true;
200 RestrictRealtime = true;
201 SystemCallArchitectures = "native";
202 SystemCallFilter = [
203 "@system-service"
204 "~@privileged"
205 ];
206 UMask = "0077";
207 };
208 }
209 ) cfg.servers;
210 };
211}