depot/third_party/nixpkgs/nixos/modules/services/home-automation/wyoming/satellite.nix

244 lines
6.2 KiB
Nix

{ config
, lib
, pkgs
, ...
}:
let
cfg = config.services.wyoming.satellite;
inherit (lib)
elem
escapeShellArgs
getExe
literalExpression
mkOption
mkEnableOption
mkIf
mkPackageOption
optional
optionals
types
;
finalPackage = cfg.package.overridePythonAttrs (oldAttrs: {
propagatedBuildInputs = oldAttrs.propagatedBuildInputs
# for audio enhancements like auto-gain, noise suppression
++ cfg.package.optional-dependencies.webrtc
# vad is currently optional, because it is broken on aarch64-linux
++ optionals cfg.vad.enable cfg.package.optional-dependencies.silerovad;
});
in
{
meta.buildDocsInSandbox = false;
options.services.wyoming.satellite = with types; {
enable = mkEnableOption "Wyoming Satellite";
package = mkPackageOption pkgs "wyoming-satellite" { };
user = mkOption {
type = str;
example = "alice";
description = ''
User to run wyoming-satellite under.
'';
};
group = mkOption {
type = str;
default = "users";
description = ''
Group to run wyoming-satellite under.
'';
};
uri = mkOption {
type = str;
default = "tcp://0.0.0.0:10700";
description = ''
URI where wyoming-satellite will bind its socket.
'';
};
name = mkOption {
type = str;
default = config.networking.hostName;
defaultText = literalExpression ''
config.networking.hostName
'';
description = ''
Name of the satellite.
'';
};
area = mkOption {
type = nullOr str;
default = null;
example = "Kitchen";
description = ''
Area to the satellite.
'';
};
microphone = {
command = mkOption {
type = str;
default = "arecord -r 16000 -c 1 -f S16_LE -t raw";
description = ''
Program to run for audio input.
'';
};
autoGain = mkOption {
type = ints.between 0 31;
default = 5;
example = 15;
description = ''
Automatic gain control in dbFS, with 31 being the loudest value. Set to 0 to disable.
'';
};
noiseSuppression = mkOption {
type = ints.between 0 4;
default = 2;
example = 3;
description = ''
Noise suppression level with 4 being the maximum suppression,
which may cause audio distortion. Set to 0 to disable.
'';
};
};
sound = {
command = mkOption {
type = nullOr str;
default = "aplay -r 22050 -c 1 -f S16_LE -t raw";
description = ''
Program to run for sound output.
'';
};
};
sounds = {
awake = mkOption {
type = nullOr path;
default = null;
description = ''
Path to audio file in WAV format to play when wake word is detected.
'';
};
done = mkOption {
type = nullOr path;
default = null;
description = ''
Path to audio file in WAV format to play when voice command recording has ended.
'';
};
};
vad = {
enable = mkOption {
type = bool;
default = true;
description = ''
Whether to enable voice activity detection.
Enabling will result in only streaming audio, when speech gets
detected.
'';
};
};
extraArgs = mkOption {
type = listOf str;
default = [ ];
description = ''
Extra arguments to pass to the executable.
Check `wyoming-satellite --help` for possible options.
'';
};
};
config = mkIf cfg.enable {
systemd.services."wyoming-satellite" = {
description = "Wyoming Satellite";
after = [
"network-online.target"
"sound.target"
];
wants = [
"network-online.target"
"sound.target"
];
wantedBy = [
"multi-user.target"
];
path = with pkgs; [
alsa-utils
];
script = let
optionalParam = param: argument: optionals (!elem argument [ null 0 false ]) [
param argument
];
in ''
export XDG_RUNTIME_DIR=/run/user/$UID
${escapeShellArgs ([
(getExe finalPackage)
"--uri" cfg.uri
"--name" cfg.name
"--mic-command" cfg.microphone.command
]
++ optionalParam "--mic-auto-gain" cfg.microphone.autoGain
++ optionalParam "--mic-noise-suppression" cfg.microphone.noiseSuppression
++ optionalParam "--area" cfg.area
++ optionalParam "--snd-command" cfg.sound.command
++ optionalParam "--awake-wav" cfg.sounds.awake
++ optionalParam "--done-wav" cfg.sounds.done
++ optional cfg.vad.enable "--vad"
++ cfg.extraArgs)}
'';
serviceConfig = {
User = cfg.user;
Group = cfg.group;
# https://github.com/rhasspy/hassio-addons/blob/master/assist_microphone/rootfs/etc/s6-overlay/s6-rc.d/assist_microphone/run
CapabilityBoundingSet = "";
DeviceAllow = "";
DevicePolicy = "closed";
LockPersonality = true;
MemoryDenyWriteExecute = false; # onnxruntime/capi/onnxruntime_pybind11_state.so: cannot enable executable stack as shared object requires: Operation not permitted
PrivateDevices = true;
PrivateUsers = true;
ProtectHome = false; # Would deny access to local pulse/pipewire server
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
ProtectProc = "invisible";
ProcSubset = "all"; # Error in cpuinfo: failed to parse processor information from /proc/cpuinfo
Restart = "always";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
"AF_NETLINK"
];
RestrictNamespaces = true;
RestrictRealtime = true;
SupplementaryGroups = [
"audio"
];
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~@privileged"
];
UMask = "0077";
};
};
};
}