nixos/preload: init
[NixPkgs.git] / nixos / modules / services / home-automation / homeassistant-satellite.nix
blobe3f0617cf01cb4cd31ebadd2da8d404fbab5324d
1 { config
2 , lib
3 , pkgs
4 , ...
5 }:
7 let
8   cfg = config.services.homeassistant-satellite;
10   inherit (lib)
11     escapeShellArg
12     escapeShellArgs
13     mkOption
14     mdDoc
15     mkEnableOption
16     mkIf
17     mkPackageOptionMD
18     types
19     ;
21   inherit (builtins)
22     toString
23     ;
25   # override the package with the relevant vad dependencies
26   package = cfg.package.overridePythonAttrs (oldAttrs: {
27     propagatedBuildInputs = oldAttrs.propagatedBuildInputs
28       ++ lib.optional (cfg.vad == "webrtcvad") cfg.package.optional-dependencies.webrtc
29       ++ lib.optional (cfg.vad == "silero") cfg.package.optional-dependencies.silerovad
30       ++ lib.optional (cfg.pulseaudio.enable) cfg.package.optional-dependencies.pulseaudio;
31   });
36   meta.buildDocsInSandbox = false;
38   options.services.homeassistant-satellite = with types; {
39     enable = mkEnableOption (mdDoc "Home Assistant Satellite");
41     package = mkPackageOptionMD pkgs "homeassistant-satellite" { };
43     user = mkOption {
44       type = str;
45       example = "alice";
46       description = mdDoc ''
47         User to run homeassistant-satellite under.
48       '';
49     };
51     group = mkOption {
52       type = str;
53       default = "users";
54       description = mdDoc ''
55         Group to run homeassistant-satellite under.
56       '';
57     };
59     host = mkOption {
60       type = str;
61       example = "home-assistant.local";
62       description = mdDoc ''
63         Hostname on which your Home Assistant instance can be reached.
64       '';
65     };
67     port = mkOption {
68       type = port;
69       example = 8123;
70       description = mdDoc ''
71         Port on which your Home Assistance can be reached.
72       '';
73       apply = toString;
74     };
76     protocol = mkOption {
77       type = enum [ "http" "https" ];
78       default = "http";
79       example = "https";
80       description = mdDoc ''
81         The transport protocol used to connect to Home Assistant.
82       '';
83     };
85     tokenFile = mkOption {
86       type = path;
87       example = "/run/keys/hass-token";
88       description = mdDoc ''
89         Path to a file containing a long-lived access token for your Home Assistant instance.
90       '';
91       apply = escapeShellArg;
92     };
94     sounds = {
95       awake = mkOption {
96         type = nullOr str;
97         default = null;
98         description = mdDoc ''
99           Audio file to play when the wake word is detected.
100         '';
101       };
103       done = mkOption {
104         type = nullOr str;
105         default = null;
106         description = mdDoc ''
107           Audio file to play when the voice command is done.
108         '';
109       };
110     };
112     vad = mkOption {
113       type = enum [ "disabled" "webrtcvad" "silero" ];
114       default = "disabled";
115       example = "silero";
116       description = mdDoc ''
117         Voice activity detection model. With `disabled` sound will be transmitted continously.
118       '';
119     };
121     pulseaudio = {
122       enable = mkEnableOption "recording/playback via PulseAudio or PipeWire";
124       socket = mkOption {
125         type = nullOr str;
126         default = null;
127         example = "/run/user/1000/pulse/native";
128         description = mdDoc ''
129           Path or hostname to connect with the PulseAudio server.
130         '';
131       };
133       duckingVolume = mkOption {
134         type = nullOr float;
135         default = null;
136         example = 0.4;
137         description = mdDoc ''
138           Reduce output volume (between 0 and 1) to this percentage value while recording.
139         '';
140       };
142       echoCancellation = mkEnableOption "acoustic echo cancellation";
143     };
145     extraArgs = mkOption {
146       type = listOf str;
147       default = [ ];
148       description = mdDoc ''
149         Extra arguments to pass to the commandline.
150       '';
151       apply = escapeShellArgs;
152     };
153   };
155   config = mkIf cfg.enable {
156     systemd.services."homeassistant-satellite" = {
157       description = "Home Assistant Satellite";
158       after = [
159         "network-online.target"
160       ];
161       wants = [
162         "network-online.target"
163       ];
164       wantedBy = [
165         "multi-user.target"
166       ];
167       path = with pkgs; [
168         ffmpeg-headless
169       ] ++ lib.optionals (!cfg.pulseaudio.enable) [
170         alsa-utils
171       ];
172       serviceConfig = {
173         User = cfg.user;
174         Group = cfg.group;
175         # https://github.com/rhasspy/hassio-addons/blob/master/assist_microphone/rootfs/etc/s6-overlay/s6-rc.d/assist_microphone/run
176         ExecStart = ''
177           ${package}/bin/homeassistant-satellite \
178             --host ${cfg.host} \
179             --port ${cfg.port} \
180             --protocol ${cfg.protocol} \
181             --token-file ${cfg.tokenFile} \
182             --vad ${cfg.vad} \
183             ${lib.optionalString cfg.pulseaudio.enable "--pulseaudio"}${lib.optionalString (cfg.pulseaudio.socket != null) "=${cfg.pulseaudio.socket}"} \
184             ${lib.optionalString (cfg.pulseaudio.enable && cfg.pulseaudio.duckingVolume != null) "--ducking-volume=${toString cfg.pulseaudio.duckingVolume}"} \
185             ${lib.optionalString (cfg.pulseaudio.enable && cfg.pulseaudio.echoCancellation) "--echo-cancel"} \
186             ${lib.optionalString (cfg.sounds.awake != null) "--awake-sound=${toString cfg.sounds.awake}"} \
187             ${lib.optionalString (cfg.sounds.done != null) "--done-sound=${toString cfg.sounds.done}"} \
188             ${cfg.extraArgs}
189         '';
190         CapabilityBoundingSet = "";
191         DeviceAllow = "";
192         DevicePolicy = "closed";
193         LockPersonality = true;
194         MemoryDenyWriteExecute = false; # onnxruntime/capi/onnxruntime_pybind11_state.so: cannot enable executable stack as shared object requires: Operation not permitted
195         PrivateDevices = true;
196         PrivateUsers = true;
197         ProtectHome = false; # Would deny access to local pulse/pipewire server
198         ProtectHostname = true;
199         ProtectKernelLogs = true;
200         ProtectKernelModules = true;
201         ProtectKernelTunables = true;
202         ProtectControlGroups = true;
203         ProtectProc = "invisible";
204         ProcSubset = "all"; # Error in cpuinfo: failed to parse processor information from /proc/cpuinfo
205         Restart = "always";
206         RestrictAddressFamilies = [
207           "AF_INET"
208           "AF_INET6"
209           "AF_UNIX"
210         ];
211         RestrictNamespaces = true;
212         RestrictRealtime = true;
213         SupplementaryGroups = [
214           "audio"
215         ];
216         SystemCallArchitectures = "native";
217         SystemCallFilter = [
218           "@system-service"
219           "~@privileged"
220         ];
221         UMask = "0077";
222       };
223     };
224   };