grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / home-automation / wyoming / satellite.nix
blob531d375e703a3eba86fadc4984738c445857dda8
1 { config
2 , lib
3 , pkgs
4 , ...
5 }:
7 let
8   cfg = config.services.wyoming.satellite;
10   inherit (lib)
11     elem
12     escapeShellArgs
13     getExe
14     literalExpression
15     mkOption
16     mkEnableOption
17     mkIf
18     mkPackageOption
19     optional
20     optionals
21     types
22   ;
24   finalPackage = cfg.package.overridePythonAttrs (oldAttrs: {
25     propagatedBuildInputs = oldAttrs.propagatedBuildInputs
26       # for audio enhancements like auto-gain, noise suppression
27       ++ cfg.package.optional-dependencies.webrtc
28       # vad is currently optional, because it is broken on aarch64-linux
29       ++ optionals cfg.vad.enable cfg.package.optional-dependencies.silerovad;
30     });
34   meta.buildDocsInSandbox = false;
36   options.services.wyoming.satellite = with types; {
37     enable = mkEnableOption "Wyoming Satellite";
39     package = mkPackageOption pkgs "wyoming-satellite" { };
41     user = mkOption {
42       type = str;
43       example = "alice";
44       description = ''
45         User to run wyoming-satellite under.
46       '';
47     };
49     group = mkOption {
50       type = str;
51       default = "users";
52       description = ''
53         Group to run wyoming-satellite under.
54       '';
55     };
57     uri = mkOption {
58       type = str;
59       default = "tcp://0.0.0.0:10700";
60       description = ''
61         URI where wyoming-satellite will bind its socket.
62       '';
63     };
65     name = mkOption {
66       type = str;
67       default = config.networking.hostName;
68       defaultText = literalExpression ''
69         config.networking.hostName
70       '';
71       description = ''
72         Name of the satellite.
73       '';
74     };
76     area = mkOption {
77       type = nullOr str;
78       default = null;
79       example = "Kitchen";
80       description = ''
81         Area to the satellite.
82       '';
83     };
85     microphone = {
86       command = mkOption {
87         type = str;
88         default = "arecord -r 16000 -c 1 -f S16_LE -t raw";
89         description = ''
90           Program to run for audio input.
91         '';
92       };
94       autoGain = mkOption {
95         type = ints.between 0 31;
96         default = 5;
97         example = 15;
98         description = ''
99           Automatic gain control in dbFS, with 31 being the loudest value. Set to 0 to disable.
100         '';
101       };
103       noiseSuppression = mkOption {
104         type = ints.between 0 4;
105         default = 2;
106         example = 3;
107         description = ''
108           Noise suppression level with 4 being the maximum suppression,
109           which may cause audio distortion. Set to 0 to disable.
110         '';
111       };
112     };
114     sound = {
115       command = mkOption {
116         type = nullOr str;
117         default = "aplay -r 22050 -c 1 -f S16_LE -t raw";
118         description = ''
119           Program to run for sound output.
120         '';
121       };
122     };
124     sounds = {
125       awake = mkOption {
126         type = nullOr path;
127         default = null;
128         description = ''
129           Path to audio file in WAV format to play when wake word is detected.
130         '';
131       };
133       done = mkOption {
134         type = nullOr path;
135         default = null;
136         description = ''
137           Path to audio file in WAV format to play when voice command recording has ended.
138         '';
139       };
140     };
142     vad = {
143       enable = mkOption {
144         type = bool;
145         default = true;
146         description = ''
147           Whether to enable voice activity detection.
149           Enabling will result in only streaming audio, when speech gets
150           detected.
151         '';
152       };
153     };
155     extraArgs = mkOption {
156       type = listOf str;
157       default = [ ];
158       description = ''
159         Extra arguments to pass to the executable.
161         Check `wyoming-satellite --help` for possible options.
162       '';
163     };
164   };
166   config = mkIf cfg.enable {
167     systemd.services."wyoming-satellite" = {
168       description = "Wyoming Satellite";
169       after = [
170         "network-online.target"
171         "sound.target"
172       ];
173       wants = [
174         "network-online.target"
175         "sound.target"
176       ];
177       wantedBy = [
178         "multi-user.target"
179       ];
180       path = with pkgs; [
181         alsa-utils
182       ];
183       script = let
184         optionalParam = param: argument: optionals (!elem argument [ null 0 false ]) [
185           param argument
186         ];
187       in ''
188         export XDG_RUNTIME_DIR=/run/user/$UID
189         ${escapeShellArgs ([
190           (getExe finalPackage)
191           "--uri" cfg.uri
192           "--name" cfg.name
193           "--mic-command" cfg.microphone.command
194         ]
195         ++ optionalParam "--mic-auto-gain" cfg.microphone.autoGain
196         ++ optionalParam "--mic-noise-suppression" cfg.microphone.noiseSuppression
197         ++ optionalParam "--area" cfg.area
198         ++ optionalParam "--snd-command" cfg.sound.command
199         ++ optionalParam "--awake-wav" cfg.sounds.awake
200         ++ optionalParam "--done-wav" cfg.sounds.done
201         ++ optional cfg.vad.enable "--vad"
202         ++ cfg.extraArgs)}
203       '';
204       serviceConfig = {
205         User = cfg.user;
206         Group = cfg.group;
207         # https://github.com/rhasspy/hassio-addons/blob/master/assist_microphone/rootfs/etc/s6-overlay/s6-rc.d/assist_microphone/run
208         CapabilityBoundingSet = "";
209         DeviceAllow = "";
210         DevicePolicy = "closed";
211         LockPersonality = true;
212         MemoryDenyWriteExecute = false; # onnxruntime/capi/onnxruntime_pybind11_state.so: cannot enable executable stack as shared object requires: Operation not permitted
213         PrivateDevices = true;
214         PrivateUsers = true;
215         ProtectHome = false; # Would deny access to local pulse/pipewire server
216         ProtectHostname = true;
217         ProtectKernelLogs = true;
218         ProtectKernelModules = true;
219         ProtectKernelTunables = true;
220         ProtectControlGroups = true;
221         ProtectProc = "invisible";
222         ProcSubset = "all"; # Error in cpuinfo: failed to parse processor information from /proc/cpuinfo
223         Restart = "always";
224         RestrictAddressFamilies = [
225           "AF_INET"
226           "AF_INET6"
227           "AF_UNIX"
228           "AF_NETLINK"
229         ];
230         RestrictNamespaces = true;
231         RestrictRealtime = true;
232         SupplementaryGroups = [
233           "audio"
234         ];
235         SystemCallArchitectures = "native";
236         SystemCallFilter = [
237           "@system-service"
238           "~@privileged"
239         ];
240         UMask = "0077";
241       };
242     };
243   };