uboot: (firmwareOdroidC2/C4) don't invoke patch tool, use patches = [] instead
https://github.com/NixOS/nixpkgs/blob/master/pkgs/stdenv/generic/setup.sh#L948 this can do it nicely. Signed-off-by: Anton Arapov <anton@deadbeef.mx>
This commit is contained in:
commit
56de2bcd43
30691 changed files with 3076956 additions and 0 deletions
133
nixos/modules/services/audio/alsa.nix
Normal file
133
nixos/modules/services/audio/alsa.nix
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
# ALSA sound support.
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
inherit (pkgs) alsa-utils;
|
||||
|
||||
pulseaudioEnabled = config.hardware.pulseaudio.enable;
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
imports = [
|
||||
(mkRenamedOptionModule [ "sound" "enableMediaKeys" ] [ "sound" "mediaKeys" "enable" ])
|
||||
];
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
sound = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable ALSA sound.
|
||||
'';
|
||||
};
|
||||
|
||||
enableOSSEmulation = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable ALSA OSS emulation (with certain cards sound mixing may not work!).
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
example = ''
|
||||
defaults.pcm.!card 3
|
||||
'';
|
||||
description = ''
|
||||
Set addition configuration for system-wide alsa.
|
||||
'';
|
||||
};
|
||||
|
||||
mediaKeys = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable volume and capture control with keyboard media keys.
|
||||
|
||||
You want to leave this disabled if you run a desktop environment
|
||||
like KDE, Gnome, Xfce, etc, as those handle such things themselves.
|
||||
You might want to enable this if you run a minimalistic desktop
|
||||
environment or work from bare linux ttys/framebuffers.
|
||||
|
||||
Enabling this will turn on <option>services.actkbd</option>.
|
||||
'';
|
||||
};
|
||||
|
||||
volumeStep = mkOption {
|
||||
type = types.str;
|
||||
default = "1";
|
||||
example = "1%";
|
||||
description = ''
|
||||
The value by which to increment/decrement volume on media keys.
|
||||
|
||||
See amixer(1) for allowed values.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf config.sound.enable {
|
||||
|
||||
environment.systemPackages = [ alsa-utils ];
|
||||
|
||||
environment.etc = mkIf (!pulseaudioEnabled && config.sound.extraConfig != "")
|
||||
{ "asound.conf".text = config.sound.extraConfig; };
|
||||
|
||||
# ALSA provides a udev rule for restoring volume settings.
|
||||
services.udev.packages = [ alsa-utils ];
|
||||
|
||||
boot.kernelModules = optional config.sound.enableOSSEmulation "snd_pcm_oss";
|
||||
|
||||
systemd.services.alsa-store =
|
||||
{ description = "Store Sound Card State";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
unitConfig.RequiresMountsFor = "/var/lib/alsa";
|
||||
unitConfig.ConditionVirtualization = "!systemd-nspawn";
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = "${pkgs.coreutils}/bin/mkdir -p /var/lib/alsa";
|
||||
ExecStop = "${alsa-utils}/sbin/alsactl store --ignore";
|
||||
};
|
||||
};
|
||||
|
||||
services.actkbd = mkIf config.sound.mediaKeys.enable {
|
||||
enable = true;
|
||||
bindings = [
|
||||
# "Mute" media key
|
||||
{ keys = [ 113 ]; events = [ "key" ]; command = "${alsa-utils}/bin/amixer -q set Master toggle"; }
|
||||
|
||||
# "Lower Volume" media key
|
||||
{ keys = [ 114 ]; events = [ "key" "rep" ]; command = "${alsa-utils}/bin/amixer -q set Master ${config.sound.mediaKeys.volumeStep}- unmute"; }
|
||||
|
||||
# "Raise Volume" media key
|
||||
{ keys = [ 115 ]; events = [ "key" "rep" ]; command = "${alsa-utils}/bin/amixer -q set Master ${config.sound.mediaKeys.volumeStep}+ unmute"; }
|
||||
|
||||
# "Mic Mute" media key
|
||||
{ keys = [ 190 ]; events = [ "key" ]; command = "${alsa-utils}/bin/amixer -q set Capture toggle"; }
|
||||
];
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
115
nixos/modules/services/audio/botamusique.nix
Normal file
115
nixos/modules/services/audio/botamusique.nix
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.botamusique;
|
||||
|
||||
format = pkgs.formats.ini {};
|
||||
configFile = format.generate "botamusique.ini" cfg.settings;
|
||||
in
|
||||
{
|
||||
meta.maintainers = with lib.maintainers; [ hexa ];
|
||||
|
||||
options.services.botamusique = {
|
||||
enable = mkEnableOption "botamusique, a bot to play audio streams on mumble";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.botamusique;
|
||||
defaultText = literalExpression "pkgs.botamusique";
|
||||
description = "The botamusique package to use.";
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = with types; submodule {
|
||||
freeformType = format.type;
|
||||
options = {
|
||||
server.host = mkOption {
|
||||
type = types.str;
|
||||
default = "localhost";
|
||||
example = "mumble.example.com";
|
||||
description = "Hostname of the mumble server to connect to.";
|
||||
};
|
||||
|
||||
server.port = mkOption {
|
||||
type = types.port;
|
||||
default = 64738;
|
||||
description = "Port of the mumble server to connect to.";
|
||||
};
|
||||
|
||||
bot.username = mkOption {
|
||||
type = types.str;
|
||||
default = "botamusique";
|
||||
description = "Name the bot should appear with.";
|
||||
};
|
||||
|
||||
bot.comment = mkOption {
|
||||
type = types.str;
|
||||
default = "Hi, I'm here to play radio, local music or youtube/soundcloud music. Have fun!";
|
||||
description = "Comment displayed for the bot.";
|
||||
};
|
||||
};
|
||||
};
|
||||
default = {};
|
||||
description = ''
|
||||
Your <filename>configuration.ini</filename> as a Nix attribute set. Look up
|
||||
possible options in the <link xlink:href="https://github.com/azlux/botamusique/blob/master/configuration.example.ini">configuration.example.ini</link>.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.botamusique = {
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
unitConfig.Documentation = "https://github.com/azlux/botamusique/wiki";
|
||||
|
||||
environment.HOME = "/var/lib/botamusique";
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/botamusique --config ${configFile}";
|
||||
Restart = "always"; # the bot exits when the server connection is lost
|
||||
|
||||
# Hardening
|
||||
CapabilityBoundingSet = [ "" ];
|
||||
DynamicUser = true;
|
||||
IPAddressDeny = [
|
||||
"link-local"
|
||||
"multicast"
|
||||
];
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
ProcSubset = "pid";
|
||||
PrivateDevices = true;
|
||||
PrivateUsers = true;
|
||||
PrivateTmp = true;
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectProc = "invisible";
|
||||
ProtectSystem = "strict";
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictAddressFamilies = [
|
||||
"AF_INET"
|
||||
"AF_INET6"
|
||||
];
|
||||
StateDirectory = "botamusique";
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
"~@privileged"
|
||||
"~@resources"
|
||||
];
|
||||
UMask = "0077";
|
||||
WorkingDirectory = "/var/lib/botamusique";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
142
nixos/modules/services/audio/hqplayerd.nix
Normal file
142
nixos/modules/services/audio/hqplayerd.nix
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.hqplayerd;
|
||||
pkg = pkgs.hqplayerd;
|
||||
# XXX: This is hard-coded in the distributed binary, don't try to change it.
|
||||
stateDir = "/var/lib/hqplayer";
|
||||
configDir = "/etc/hqplayer";
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.hqplayerd = {
|
||||
enable = mkEnableOption "HQPlayer Embedded";
|
||||
|
||||
auth = {
|
||||
username = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Username used for HQPlayer's WebUI.
|
||||
|
||||
Without this you will need to manually create the credentials after
|
||||
first start by going to http://your.ip/8088/auth
|
||||
'';
|
||||
};
|
||||
|
||||
password = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Password used for HQPlayer's WebUI.
|
||||
|
||||
Without this you will need to manually create the credentials after
|
||||
first start by going to http://your.ip/8088/auth
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
licenseFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
Path to the HQPlayer license key file.
|
||||
|
||||
Without this, the service will run in trial mode and restart every 30
|
||||
minutes.
|
||||
'';
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Opens ports needed for the WebUI and controller API.
|
||||
'';
|
||||
};
|
||||
|
||||
config = mkOption {
|
||||
type = types.nullOr types.lines;
|
||||
default = null;
|
||||
description = ''
|
||||
HQplayer daemon configuration, written to /etc/hqplayer/hqplayerd.xml.
|
||||
|
||||
Refer to share/doc/hqplayerd/readme.txt in the hqplayerd derivation for possible values.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = (cfg.auth.username != null -> cfg.auth.password != null)
|
||||
&& (cfg.auth.password != null -> cfg.auth.username != null);
|
||||
message = "You must set either both services.hqplayer.auth.username and password, or neither.";
|
||||
}
|
||||
];
|
||||
|
||||
environment = {
|
||||
etc = {
|
||||
"hqplayer/hqplayerd.xml" = mkIf (cfg.config != null) { source = pkgs.writeText "hqplayerd.xml" cfg.config; };
|
||||
"hqplayer/hqplayerd4-key.xml" = mkIf (cfg.licenseFile != null) { source = cfg.licenseFile; };
|
||||
"modules-load.d/taudio2.conf".source = "${pkg}/etc/modules-load.d/taudio2.conf";
|
||||
};
|
||||
systemPackages = [ pkg ];
|
||||
};
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ 8088 4321 ];
|
||||
};
|
||||
|
||||
services.udev.packages = [ pkg ];
|
||||
|
||||
systemd = {
|
||||
tmpfiles.rules = [
|
||||
"d ${configDir} 0755 hqplayer hqplayer - -"
|
||||
"d ${stateDir} 0755 hqplayer hqplayer - -"
|
||||
"d ${stateDir}/home 0755 hqplayer hqplayer - -"
|
||||
];
|
||||
|
||||
packages = [ pkg ];
|
||||
|
||||
services.hqplayerd = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "systemd-tmpfiles-setup.service" ];
|
||||
|
||||
environment.HOME = "${stateDir}/home";
|
||||
|
||||
unitConfig.ConditionPathExists = [ configDir stateDir ];
|
||||
|
||||
restartTriggers = optionals (cfg.config != null) [ config.environment.etc."hqplayer/hqplayerd.xml".source ];
|
||||
|
||||
preStart = ''
|
||||
cp -r "${pkg}/var/lib/hqplayer/web" "${stateDir}"
|
||||
chmod -R u+wX "${stateDir}/web"
|
||||
|
||||
if [ ! -f "${configDir}/hqplayerd.xml" ]; then
|
||||
echo "creating initial config file"
|
||||
install -m 0644 "${pkg}/etc/hqplayer/hqplayerd.xml" "${configDir}/hqplayerd.xml"
|
||||
fi
|
||||
'' + optionalString (cfg.auth.username != null && cfg.auth.password != null) ''
|
||||
${pkg}/bin/hqplayerd -s ${cfg.auth.username} ${cfg.auth.password}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = {
|
||||
hqplayer.gid = config.ids.gids.hqplayer;
|
||||
};
|
||||
|
||||
users.users = {
|
||||
hqplayer = {
|
||||
description = "hqplayer daemon user";
|
||||
extraGroups = [ "audio" ];
|
||||
group = "hqplayer";
|
||||
uid = config.ids.uids.hqplayer;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
131
nixos/modules/services/audio/icecast.nix
Normal file
131
nixos/modules/services/audio/icecast.nix
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.icecast;
|
||||
configFile = pkgs.writeText "icecast.xml" ''
|
||||
<icecast>
|
||||
<hostname>${cfg.hostname}</hostname>
|
||||
|
||||
<authentication>
|
||||
<admin-user>${cfg.admin.user}</admin-user>
|
||||
<admin-password>${cfg.admin.password}</admin-password>
|
||||
</authentication>
|
||||
|
||||
<paths>
|
||||
<logdir>${cfg.logDir}</logdir>
|
||||
<adminroot>${pkgs.icecast}/share/icecast/admin</adminroot>
|
||||
<webroot>${pkgs.icecast}/share/icecast/web</webroot>
|
||||
<alias source="/" dest="/status.xsl"/>
|
||||
</paths>
|
||||
|
||||
<listen-socket>
|
||||
<port>${toString cfg.listen.port}</port>
|
||||
<bind-address>${cfg.listen.address}</bind-address>
|
||||
</listen-socket>
|
||||
|
||||
<security>
|
||||
<chroot>0</chroot>
|
||||
<changeowner>
|
||||
<user>${cfg.user}</user>
|
||||
<group>${cfg.group}</group>
|
||||
</changeowner>
|
||||
</security>
|
||||
|
||||
${cfg.extraConf}
|
||||
</icecast>
|
||||
'';
|
||||
in {
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.icecast = {
|
||||
|
||||
enable = mkEnableOption "Icecast server";
|
||||
|
||||
hostname = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
description = "DNS name or IP address that will be used for the stream directory lookups or possibily the playlist generation if a Host header is not provided.";
|
||||
default = config.networking.domain;
|
||||
defaultText = literalExpression "config.networking.domain";
|
||||
};
|
||||
|
||||
admin = {
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
description = "Username used for all administration functions.";
|
||||
default = "admin";
|
||||
};
|
||||
|
||||
password = mkOption {
|
||||
type = types.str;
|
||||
description = "Password used for all administration functions.";
|
||||
};
|
||||
};
|
||||
|
||||
logDir = mkOption {
|
||||
type = types.path;
|
||||
description = "Base directory used for logging.";
|
||||
default = "/var/log/icecast";
|
||||
};
|
||||
|
||||
listen = {
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
description = "TCP port that will be used to accept client connections.";
|
||||
default = 8000;
|
||||
};
|
||||
|
||||
address = mkOption {
|
||||
type = types.str;
|
||||
description = "Address Icecast will listen on.";
|
||||
default = "::";
|
||||
};
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
description = "User privileges for the server.";
|
||||
default = "nobody";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
description = "Group privileges for the server.";
|
||||
default = "nogroup";
|
||||
};
|
||||
|
||||
extraConf = mkOption {
|
||||
type = types.lines;
|
||||
description = "icecast.xml content.";
|
||||
default = "";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
systemd.services.icecast = {
|
||||
after = [ "network.target" ];
|
||||
description = "Icecast Network Audio Streaming Server";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
preStart = "mkdir -p ${cfg.logDir} && chown ${cfg.user}:${cfg.group} ${cfg.logDir}";
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = "${pkgs.icecast}/bin/icecast -c ${configFile}";
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
294
nixos/modules/services/audio/jack.nix
Normal file
294
nixos/modules/services/audio/jack.nix
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.jack;
|
||||
|
||||
pcmPlugin = cfg.jackd.enable && cfg.alsa.enable;
|
||||
loopback = cfg.jackd.enable && cfg.loopback.enable;
|
||||
|
||||
enable32BitAlsaPlugins = cfg.alsa.support32Bit && pkgs.stdenv.isx86_64 && pkgs.pkgsi686Linux.alsa-lib != null;
|
||||
|
||||
umaskNeeded = versionOlder cfg.jackd.package.version "1.9.12";
|
||||
bridgeNeeded = versionAtLeast cfg.jackd.package.version "1.9.12";
|
||||
in {
|
||||
options = {
|
||||
services.jack = {
|
||||
jackd = {
|
||||
enable = mkEnableOption ''
|
||||
JACK Audio Connection Kit. You need to add yourself to the "jackaudio" group
|
||||
'';
|
||||
|
||||
package = mkOption {
|
||||
# until jack1 promiscuous mode is fixed
|
||||
internal = true;
|
||||
type = types.package;
|
||||
default = pkgs.jack2;
|
||||
defaultText = literalExpression "pkgs.jack2";
|
||||
example = literalExpression "pkgs.jack1";
|
||||
description = ''
|
||||
The JACK package to use.
|
||||
'';
|
||||
};
|
||||
|
||||
extraOptions = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [
|
||||
"-dalsa"
|
||||
];
|
||||
example = literalExpression ''
|
||||
[ "-dalsa" "--device" "hw:1" ];
|
||||
'';
|
||||
description = ''
|
||||
Specifies startup command line arguments to pass to JACK server.
|
||||
'';
|
||||
};
|
||||
|
||||
session = mkOption {
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Commands to run after JACK is started.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
alsa = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Route audio to/from generic ALSA-using applications using ALSA JACK PCM plugin.
|
||||
'';
|
||||
};
|
||||
|
||||
support32Bit = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to support sound for 32-bit ALSA applications on 64-bit system.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
loopback = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Create ALSA loopback device, instead of using PCM plugin. Has broader
|
||||
application support (things like Steam will work), but may need fine-tuning
|
||||
for concrete hardware.
|
||||
'';
|
||||
};
|
||||
|
||||
index = mkOption {
|
||||
type = types.int;
|
||||
default = 10;
|
||||
description = ''
|
||||
Index of an ALSA loopback device.
|
||||
'';
|
||||
};
|
||||
|
||||
config = mkOption {
|
||||
type = types.lines;
|
||||
description = ''
|
||||
ALSA config for loopback device.
|
||||
'';
|
||||
};
|
||||
|
||||
dmixConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
example = ''
|
||||
period_size 2048
|
||||
periods 2
|
||||
'';
|
||||
description = ''
|
||||
For music production software that still doesn't support JACK natively you
|
||||
would like to put buffer/period adjustments here
|
||||
to decrease dmix device latency.
|
||||
'';
|
||||
};
|
||||
|
||||
session = mkOption {
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Additional commands to run to setup loopback device.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkMerge [
|
||||
|
||||
(mkIf pcmPlugin {
|
||||
sound.extraConfig = ''
|
||||
pcm_type.jack {
|
||||
libs.native = ${pkgs.alsa-plugins}/lib/alsa-lib/libasound_module_pcm_jack.so ;
|
||||
${lib.optionalString enable32BitAlsaPlugins
|
||||
"libs.32Bit = ${pkgs.pkgsi686Linux.alsa-plugins}/lib/alsa-lib/libasound_module_pcm_jack.so ;"}
|
||||
}
|
||||
pcm.!default {
|
||||
@func getenv
|
||||
vars [ PCM ]
|
||||
default "plug:jack"
|
||||
}
|
||||
'';
|
||||
})
|
||||
|
||||
(mkIf loopback {
|
||||
boot.kernelModules = [ "snd-aloop" ];
|
||||
boot.kernelParams = [ "snd-aloop.index=${toString cfg.loopback.index}" ];
|
||||
sound.extraConfig = cfg.loopback.config;
|
||||
})
|
||||
|
||||
(mkIf cfg.jackd.enable {
|
||||
services.jack.jackd.session = ''
|
||||
${lib.optionalString bridgeNeeded "${pkgs.a2jmidid}/bin/a2jmidid -e &"}
|
||||
'';
|
||||
# https://alsa.opensrc.org/Jack_and_Loopback_device_as_Alsa-to-Jack_bridge#id06
|
||||
services.jack.loopback.config = ''
|
||||
pcm.loophw00 {
|
||||
type hw
|
||||
card ${toString cfg.loopback.index}
|
||||
device 0
|
||||
subdevice 0
|
||||
}
|
||||
pcm.amix {
|
||||
type dmix
|
||||
ipc_key 219345
|
||||
slave {
|
||||
pcm loophw00
|
||||
${cfg.loopback.dmixConfig}
|
||||
}
|
||||
}
|
||||
pcm.asoftvol {
|
||||
type softvol
|
||||
slave.pcm "amix"
|
||||
control { name Master }
|
||||
}
|
||||
pcm.cloop {
|
||||
type hw
|
||||
card ${toString cfg.loopback.index}
|
||||
device 1
|
||||
subdevice 0
|
||||
format S32_LE
|
||||
}
|
||||
pcm.loophw01 {
|
||||
type hw
|
||||
card ${toString cfg.loopback.index}
|
||||
device 0
|
||||
subdevice 1
|
||||
}
|
||||
pcm.ploop {
|
||||
type hw
|
||||
card ${toString cfg.loopback.index}
|
||||
device 1
|
||||
subdevice 1
|
||||
format S32_LE
|
||||
}
|
||||
pcm.aduplex {
|
||||
type asym
|
||||
playback.pcm "asoftvol"
|
||||
capture.pcm "loophw01"
|
||||
}
|
||||
pcm.!default {
|
||||
type plug
|
||||
slave.pcm aduplex
|
||||
}
|
||||
'';
|
||||
services.jack.loopback.session = ''
|
||||
alsa_in -j cloop -dcloop &
|
||||
alsa_out -j ploop -dploop &
|
||||
while [ "$(jack_lsp cloop)" == "" ] || [ "$(jack_lsp ploop)" == "" ]; do sleep 1; done
|
||||
jack_connect cloop:capture_1 system:playback_1
|
||||
jack_connect cloop:capture_2 system:playback_2
|
||||
jack_connect system:capture_1 ploop:playback_1
|
||||
jack_connect system:capture_2 ploop:playback_2
|
||||
'';
|
||||
|
||||
assertions = [
|
||||
{
|
||||
assertion = !(cfg.alsa.enable && cfg.loopback.enable);
|
||||
message = "For JACK both alsa and loopback options shouldn't be used at the same time.";
|
||||
}
|
||||
];
|
||||
|
||||
users.users.jackaudio = {
|
||||
group = "jackaudio";
|
||||
extraGroups = [ "audio" ];
|
||||
description = "JACK Audio system service user";
|
||||
isSystemUser = true;
|
||||
};
|
||||
# http://jackaudio.org/faq/linux_rt_config.html
|
||||
security.pam.loginLimits = [
|
||||
{ domain = "@jackaudio"; type = "-"; item = "rtprio"; value = "99"; }
|
||||
{ domain = "@jackaudio"; type = "-"; item = "memlock"; value = "unlimited"; }
|
||||
];
|
||||
users.groups.jackaudio = {};
|
||||
|
||||
environment = {
|
||||
systemPackages = [ cfg.jackd.package ];
|
||||
etc."alsa/conf.d/50-jack.conf".source = "${pkgs.alsa-plugins}/etc/alsa/conf.d/50-jack.conf";
|
||||
variables.JACK_PROMISCUOUS_SERVER = "jackaudio";
|
||||
};
|
||||
|
||||
services.udev.extraRules = ''
|
||||
ACTION=="add", SUBSYSTEM=="sound", ATTRS{id}!="Loopback", TAG+="systemd", ENV{SYSTEMD_WANTS}="jack.service"
|
||||
'';
|
||||
|
||||
systemd.services.jack = {
|
||||
description = "JACK Audio Connection Kit";
|
||||
serviceConfig = {
|
||||
User = "jackaudio";
|
||||
SupplementaryGroups = lib.optional
|
||||
(config.hardware.pulseaudio.enable
|
||||
&& !config.hardware.pulseaudio.systemWide) "users";
|
||||
ExecStart = "${cfg.jackd.package}/bin/jackd ${lib.escapeShellArgs cfg.jackd.extraOptions}";
|
||||
LimitRTPRIO = 99;
|
||||
LimitMEMLOCK = "infinity";
|
||||
} // optionalAttrs umaskNeeded {
|
||||
UMask = "007";
|
||||
};
|
||||
path = [ cfg.jackd.package ];
|
||||
environment = {
|
||||
JACK_PROMISCUOUS_SERVER = "jackaudio";
|
||||
JACK_NO_AUDIO_RESERVATION = "1";
|
||||
};
|
||||
restartIfChanged = false;
|
||||
};
|
||||
systemd.services.jack-session = {
|
||||
description = "JACK session";
|
||||
script = ''
|
||||
jack_wait -w
|
||||
${cfg.jackd.session}
|
||||
${lib.optionalString cfg.loopback.enable cfg.loopback.session}
|
||||
'';
|
||||
serviceConfig = {
|
||||
RemainAfterExit = true;
|
||||
User = "jackaudio";
|
||||
StateDirectory = "jack";
|
||||
LimitRTPRIO = 99;
|
||||
LimitMEMLOCK = "infinity";
|
||||
};
|
||||
path = [ cfg.jackd.package ];
|
||||
environment = {
|
||||
JACK_PROMISCUOUS_SERVER = "jackaudio";
|
||||
HOME = "/var/lib/jack";
|
||||
};
|
||||
wantedBy = [ "jack.service" ];
|
||||
partOf = [ "jack.service" ];
|
||||
after = [ "jack.service" ];
|
||||
restartIfChanged = false;
|
||||
};
|
||||
})
|
||||
|
||||
];
|
||||
|
||||
meta.maintainers = [ ];
|
||||
}
|
||||
48
nixos/modules/services/audio/jmusicbot.nix
Normal file
48
nixos/modules/services/audio/jmusicbot.nix
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.jmusicbot;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.jmusicbot = {
|
||||
enable = mkEnableOption "jmusicbot, a Discord music bot that's easy to set up and run yourself";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.jmusicbot;
|
||||
defaultText = literalExpression "pkgs.jmusicbot";
|
||||
description = "JMusicBot package to use";
|
||||
};
|
||||
|
||||
stateDir = mkOption {
|
||||
type = types.path;
|
||||
description = ''
|
||||
The directory where config.txt and serversettings.json is saved.
|
||||
If left as the default value this directory will automatically be created before JMusicBot starts, otherwise the sysadmin is responsible for ensuring the directory exists with appropriate ownership and permissions.
|
||||
Untouched by the value of this option config.txt needs to be placed manually into this directory.
|
||||
'';
|
||||
default = "/var/lib/jmusicbot/";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.jmusicbot = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
description = "Discord music bot that's easy to set up and run yourself!";
|
||||
serviceConfig = mkMerge [{
|
||||
ExecStart = "${cfg.package}/bin/JMusicBot";
|
||||
WorkingDirectory = cfg.stateDir;
|
||||
Restart = "always";
|
||||
RestartSec = 20;
|
||||
DynamicUser = true;
|
||||
}
|
||||
(mkIf (cfg.stateDir == "/var/lib/jmusicbot") { StateDirectory = "jmusicbot"; })];
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = with maintainers; [ SuperSandro2000 ];
|
||||
}
|
||||
69
nixos/modules/services/audio/liquidsoap.nix
Normal file
69
nixos/modules/services/audio/liquidsoap.nix
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
streams = builtins.attrNames config.services.liquidsoap.streams;
|
||||
|
||||
streamService =
|
||||
name:
|
||||
let stream = builtins.getAttr name config.services.liquidsoap.streams; in
|
||||
{ inherit name;
|
||||
value = {
|
||||
after = [ "network-online.target" "sound.target" ];
|
||||
description = "${name} liquidsoap stream";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = [ pkgs.wget ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.liquidsoap}/bin/liquidsoap ${stream}";
|
||||
User = "liquidsoap";
|
||||
LogsDirectory = "liquidsoap";
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
|
||||
##### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.liquidsoap.streams = mkOption {
|
||||
|
||||
description =
|
||||
''
|
||||
Set of Liquidsoap streams to start,
|
||||
one systemd service per stream.
|
||||
'';
|
||||
|
||||
default = {};
|
||||
|
||||
example = {
|
||||
myStream1 = "/etc/liquidsoap/myStream1.liq";
|
||||
myStream2 = literalExpression "./myStream2.liq";
|
||||
myStream3 = "out(playlist(\"/srv/music/\"))";
|
||||
};
|
||||
|
||||
type = types.attrsOf (types.either types.path types.str);
|
||||
};
|
||||
|
||||
};
|
||||
##### implementation
|
||||
|
||||
config = mkIf (builtins.length streams != 0) {
|
||||
|
||||
users.users.liquidsoap = {
|
||||
uid = config.ids.uids.liquidsoap;
|
||||
group = "liquidsoap";
|
||||
extraGroups = [ "audio" ];
|
||||
description = "Liquidsoap streaming user";
|
||||
home = "/var/lib/liquidsoap";
|
||||
createHome = true;
|
||||
};
|
||||
|
||||
users.groups.liquidsoap.gid = config.ids.gids.liquidsoap;
|
||||
|
||||
systemd.services = builtins.listToAttrs ( map streamService streams );
|
||||
};
|
||||
|
||||
}
|
||||
108
nixos/modules/services/audio/mopidy.nix
Normal file
108
nixos/modules/services/audio/mopidy.nix
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with pkgs;
|
||||
with lib;
|
||||
|
||||
let
|
||||
uid = config.ids.uids.mopidy;
|
||||
gid = config.ids.gids.mopidy;
|
||||
cfg = config.services.mopidy;
|
||||
|
||||
mopidyConf = writeText "mopidy.conf" cfg.configuration;
|
||||
|
||||
mopidyEnv = buildEnv {
|
||||
name = "mopidy-with-extensions-${mopidy.version}";
|
||||
paths = closePropagation cfg.extensionPackages;
|
||||
pathsToLink = [ "/${mopidyPackages.python.sitePackages}" ];
|
||||
buildInputs = [ makeWrapper ];
|
||||
postBuild = ''
|
||||
makeWrapper ${mopidy}/bin/mopidy $out/bin/mopidy \
|
||||
--prefix PYTHONPATH : $out/${mopidyPackages.python.sitePackages}
|
||||
'';
|
||||
};
|
||||
in {
|
||||
|
||||
options = {
|
||||
|
||||
services.mopidy = {
|
||||
|
||||
enable = mkEnableOption "Mopidy, a music player daemon";
|
||||
|
||||
dataDir = mkOption {
|
||||
default = "/var/lib/mopidy";
|
||||
type = types.str;
|
||||
description = ''
|
||||
The directory where Mopidy stores its state.
|
||||
'';
|
||||
};
|
||||
|
||||
extensionPackages = mkOption {
|
||||
default = [];
|
||||
type = types.listOf types.package;
|
||||
example = literalExpression "[ pkgs.mopidy-spotify ]";
|
||||
description = ''
|
||||
Mopidy extensions that should be loaded by the service.
|
||||
'';
|
||||
};
|
||||
|
||||
configuration = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
description = ''
|
||||
The configuration that Mopidy should use.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfigFiles = mkOption {
|
||||
default = [];
|
||||
type = types.listOf types.str;
|
||||
description = ''
|
||||
Extra config file read by Mopidy when the service starts.
|
||||
Later files in the list overrides earlier configuration.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.dataDir}' - mopidy mopidy - -"
|
||||
];
|
||||
|
||||
systemd.services.mopidy = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" "sound.target" ];
|
||||
description = "mopidy music player daemon";
|
||||
serviceConfig = {
|
||||
ExecStart = "${mopidyEnv}/bin/mopidy --config ${concatStringsSep ":" ([mopidyConf] ++ cfg.extraConfigFiles)}";
|
||||
User = "mopidy";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.mopidy-scan = {
|
||||
description = "mopidy local files scanner";
|
||||
serviceConfig = {
|
||||
ExecStart = "${mopidyEnv}/bin/mopidy --config ${concatStringsSep ":" ([mopidyConf] ++ cfg.extraConfigFiles)} local scan";
|
||||
User = "mopidy";
|
||||
Type = "oneshot";
|
||||
};
|
||||
};
|
||||
|
||||
users.users.mopidy = {
|
||||
inherit uid;
|
||||
group = "mopidy";
|
||||
extraGroups = [ "audio" ];
|
||||
description = "Mopidy daemon user";
|
||||
home = cfg.dataDir;
|
||||
};
|
||||
|
||||
users.groups.mopidy.gid = gid;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
265
nixos/modules/services/audio/mpd.nix
Normal file
265
nixos/modules/services/audio/mpd.nix
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
name = "mpd";
|
||||
|
||||
uid = config.ids.uids.mpd;
|
||||
gid = config.ids.gids.mpd;
|
||||
cfg = config.services.mpd;
|
||||
|
||||
credentialsPlaceholder = (creds:
|
||||
let
|
||||
placeholders = (imap0
|
||||
(i: c: ''password "{{password-${toString i}}}@${concatStringsSep "," c.permissions}"'')
|
||||
creds);
|
||||
in
|
||||
concatStringsSep "\n" placeholders);
|
||||
|
||||
mpdConf = pkgs.writeText "mpd.conf" ''
|
||||
# This file was automatically generated by NixOS. Edit mpd's configuration
|
||||
# via NixOS' configuration.nix, as this file will be rewritten upon mpd's
|
||||
# restart.
|
||||
|
||||
music_directory "${cfg.musicDirectory}"
|
||||
playlist_directory "${cfg.playlistDirectory}"
|
||||
${lib.optionalString (cfg.dbFile != null) ''
|
||||
db_file "${cfg.dbFile}"
|
||||
''}
|
||||
state_file "${cfg.dataDir}/state"
|
||||
sticker_file "${cfg.dataDir}/sticker.sql"
|
||||
|
||||
${optionalString (cfg.network.listenAddress != "any") ''bind_to_address "${cfg.network.listenAddress}"''}
|
||||
${optionalString (cfg.network.port != 6600) ''port "${toString cfg.network.port}"''}
|
||||
${optionalString (cfg.fluidsynth) ''
|
||||
decoder {
|
||||
plugin "fluidsynth"
|
||||
soundfont "${pkgs.soundfont-fluid}/share/soundfonts/FluidR3_GM2-2.sf2"
|
||||
}
|
||||
''}
|
||||
|
||||
${optionalString (cfg.credentials != []) (credentialsPlaceholder cfg.credentials)}
|
||||
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
|
||||
in {
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.mpd = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable MPD, the music player daemon.
|
||||
'';
|
||||
};
|
||||
|
||||
startWhenNeeded = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
If set, <command>mpd</command> is socket-activated; that
|
||||
is, instead of having it permanently running as a daemon,
|
||||
systemd will start it on the first incoming connection.
|
||||
'';
|
||||
};
|
||||
|
||||
musicDirectory = mkOption {
|
||||
type = with types; either path (strMatching "(http|https|nfs|smb)://.+");
|
||||
default = "${cfg.dataDir}/music";
|
||||
defaultText = literalExpression ''"''${dataDir}/music"'';
|
||||
description = ''
|
||||
The directory or NFS/SMB network share where MPD reads music from. If left
|
||||
as the default value this directory will automatically be created before
|
||||
the MPD server starts, otherwise the sysadmin is responsible for ensuring
|
||||
the directory exists with appropriate ownership and permissions.
|
||||
'';
|
||||
};
|
||||
|
||||
playlistDirectory = mkOption {
|
||||
type = types.path;
|
||||
default = "${cfg.dataDir}/playlists";
|
||||
defaultText = literalExpression ''"''${dataDir}/playlists"'';
|
||||
description = ''
|
||||
The directory where MPD stores playlists. If left as the default value
|
||||
this directory will automatically be created before the MPD server starts,
|
||||
otherwise the sysadmin is responsible for ensuring the directory exists
|
||||
with appropriate ownership and permissions.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
Extra directives added to to the end of MPD's configuration file,
|
||||
mpd.conf. Basic configuration like file location and uid/gid
|
||||
is added automatically to the beginning of the file. For available
|
||||
options see <literal>man 5 mpd.conf</literal>'.
|
||||
'';
|
||||
};
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/lib/${name}";
|
||||
description = ''
|
||||
The directory where MPD stores its state, tag cache, playlists etc. If
|
||||
left as the default value this directory will automatically be created
|
||||
before the MPD server starts, otherwise the sysadmin is responsible for
|
||||
ensuring the directory exists with appropriate ownership and permissions.
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = name;
|
||||
description = "User account under which MPD runs.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = name;
|
||||
description = "Group account under which MPD runs.";
|
||||
};
|
||||
|
||||
network = {
|
||||
|
||||
listenAddress = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1";
|
||||
example = "any";
|
||||
description = ''
|
||||
The address for the daemon to listen on.
|
||||
Use <literal>any</literal> to listen on all addresses.
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
default = 6600;
|
||||
description = ''
|
||||
This setting is the TCP port that is desired for the daemon to get assigned
|
||||
to.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
dbFile = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = "${cfg.dataDir}/tag_cache";
|
||||
defaultText = literalExpression ''"''${dataDir}/tag_cache"'';
|
||||
description = ''
|
||||
The path to MPD's database. If set to <literal>null</literal> the
|
||||
parameter is omitted from the configuration.
|
||||
'';
|
||||
};
|
||||
|
||||
credentials = mkOption {
|
||||
type = types.listOf (types.submodule {
|
||||
options = {
|
||||
passwordFile = mkOption {
|
||||
type = types.path;
|
||||
description = ''
|
||||
Path to file containing the password.
|
||||
'';
|
||||
};
|
||||
permissions = let
|
||||
perms = ["read" "add" "control" "admin"];
|
||||
in mkOption {
|
||||
type = types.listOf (types.enum perms);
|
||||
default = [ "read" ];
|
||||
description = ''
|
||||
List of permissions that are granted with this password.
|
||||
Permissions can be "${concatStringsSep "\", \"" perms}".
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
description = ''
|
||||
Credentials and permissions for accessing the mpd server.
|
||||
'';
|
||||
default = [];
|
||||
example = [
|
||||
{passwordFile = "/var/lib/secrets/mpd_readonly_password"; permissions = [ "read" ];}
|
||||
{passwordFile = "/var/lib/secrets/mpd_admin_password"; permissions = ["read" "add" "control" "admin"];}
|
||||
];
|
||||
};
|
||||
|
||||
fluidsynth = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
If set, add fluidsynth soundfont and configure the plugin.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
# install mpd units
|
||||
systemd.packages = [ pkgs.mpd ];
|
||||
|
||||
systemd.sockets.mpd = mkIf cfg.startWhenNeeded {
|
||||
wantedBy = [ "sockets.target" ];
|
||||
listenStreams = [
|
||||
(if pkgs.lib.hasPrefix "/" cfg.network.listenAddress
|
||||
then cfg.network.listenAddress
|
||||
else "${optionalString (cfg.network.listenAddress != "any") "${cfg.network.listenAddress}:"}${toString cfg.network.port}")
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services.mpd = {
|
||||
wantedBy = optional (!cfg.startWhenNeeded) "multi-user.target";
|
||||
|
||||
preStart =
|
||||
''
|
||||
set -euo pipefail
|
||||
install -m 600 ${mpdConf} /run/mpd/mpd.conf
|
||||
'' + optionalString (cfg.credentials != [])
|
||||
(concatStringsSep "\n"
|
||||
(imap0
|
||||
(i: c: ''${pkgs.replace-secret}/bin/replace-secret '{{password-${toString i}}}' '${c.passwordFile}' /run/mpd/mpd.conf'')
|
||||
cfg.credentials));
|
||||
|
||||
serviceConfig =
|
||||
{
|
||||
User = "${cfg.user}";
|
||||
# Note: the first "" overrides the ExecStart from the upstream unit
|
||||
ExecStart = [ "" "${pkgs.mpd}/bin/mpd --systemd /run/mpd/mpd.conf" ];
|
||||
RuntimeDirectory = "mpd";
|
||||
StateDirectory = []
|
||||
++ optionals (cfg.dataDir == "/var/lib/${name}") [ name ]
|
||||
++ optionals (cfg.playlistDirectory == "/var/lib/${name}/playlists") [ name "${name}/playlists" ]
|
||||
++ optionals (cfg.musicDirectory == "/var/lib/${name}/music") [ name "${name}/music" ];
|
||||
};
|
||||
};
|
||||
|
||||
users.users = optionalAttrs (cfg.user == name) {
|
||||
${name} = {
|
||||
inherit uid;
|
||||
group = cfg.group;
|
||||
extraGroups = [ "audio" ];
|
||||
description = "Music Player Daemon user";
|
||||
home = "${cfg.dataDir}";
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = optionalAttrs (cfg.group == name) {
|
||||
${name}.gid = gid;
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
213
nixos/modules/services/audio/mpdscribble.nix
Normal file
213
nixos/modules/services/audio/mpdscribble.nix
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
{ config, lib, options, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.mpdscribble;
|
||||
mpdCfg = config.services.mpd;
|
||||
mpdOpt = options.services.mpd;
|
||||
|
||||
endpointUrls = {
|
||||
"last.fm" = "http://post.audioscrobbler.com";
|
||||
"libre.fm" = "http://turtle.libre.fm";
|
||||
"jamendo" = "http://postaudioscrobbler.jamendo.com";
|
||||
"listenbrainz" = "http://proxy.listenbrainz.org";
|
||||
};
|
||||
|
||||
mkSection = secname: secCfg: ''
|
||||
[${secname}]
|
||||
url = ${secCfg.url}
|
||||
username = ${secCfg.username}
|
||||
password = {{${secname}_PASSWORD}}
|
||||
journal = /var/lib/mpdscribble/${secname}.journal
|
||||
'';
|
||||
|
||||
endpoints = concatStringsSep "\n" (mapAttrsToList mkSection cfg.endpoints);
|
||||
cfgTemplate = pkgs.writeText "mpdscribble.conf" ''
|
||||
## This file was automatically genenrated by NixOS and will be overwritten.
|
||||
## Do not edit. Edit your NixOS configuration instead.
|
||||
|
||||
## mpdscribble - an audioscrobbler for the Music Player Daemon.
|
||||
## http://mpd.wikia.com/wiki/Client:mpdscribble
|
||||
|
||||
# HTTP proxy URL.
|
||||
${optionalString (cfg.proxy != null) "proxy = ${cfg.proxy}"}
|
||||
|
||||
# The location of the mpdscribble log file. The special value
|
||||
# "syslog" makes mpdscribble use the local syslog daemon. On most
|
||||
# systems, log messages will appear in /var/log/daemon.log then.
|
||||
# "-" means log to stderr (the current terminal).
|
||||
log = -
|
||||
|
||||
# How verbose mpdscribble's logging should be. Default is 1.
|
||||
verbose = ${toString cfg.verbose}
|
||||
|
||||
# How often should mpdscribble save the journal file? [seconds]
|
||||
journal_interval = ${toString cfg.journalInterval}
|
||||
|
||||
# The host running MPD, possibly protected by a password
|
||||
# ([PASSWORD@]HOSTNAME).
|
||||
host = ${(optionalString (cfg.passwordFile != null) "{{MPD_PASSWORD}}@") + cfg.host}
|
||||
|
||||
# The port that the MPD listens on and mpdscribble should try to
|
||||
# connect to.
|
||||
port = ${toString cfg.port}
|
||||
|
||||
${endpoints}
|
||||
'';
|
||||
|
||||
cfgFile = "/run/mpdscribble/mpdscribble.conf";
|
||||
|
||||
replaceSecret = secretFile: placeholder: targetFile:
|
||||
optionalString (secretFile != null) ''
|
||||
${pkgs.replace-secret}/bin/replace-secret '${placeholder}' '${secretFile}' '${targetFile}' '';
|
||||
|
||||
preStart = pkgs.writeShellScript "mpdscribble-pre-start" ''
|
||||
cp -f "${cfgTemplate}" "${cfgFile}"
|
||||
${replaceSecret cfg.passwordFile "{{MPD_PASSWORD}}" cfgFile}
|
||||
${concatStringsSep "\n" (mapAttrsToList (secname: cfg:
|
||||
replaceSecret cfg.passwordFile "{{${secname}_PASSWORD}}" cfgFile)
|
||||
cfg.endpoints)}
|
||||
'';
|
||||
|
||||
localMpd = (cfg.host == "localhost" || cfg.host == "127.0.0.1");
|
||||
|
||||
in {
|
||||
###### interface
|
||||
|
||||
options.services.mpdscribble = {
|
||||
|
||||
enable = mkEnableOption "mpdscribble";
|
||||
|
||||
proxy = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.str;
|
||||
description = ''
|
||||
HTTP proxy URL.
|
||||
'';
|
||||
};
|
||||
|
||||
verbose = mkOption {
|
||||
default = 1;
|
||||
type = types.int;
|
||||
description = ''
|
||||
Log level for the mpdscribble daemon.
|
||||
'';
|
||||
};
|
||||
|
||||
journalInterval = mkOption {
|
||||
default = 600;
|
||||
example = 60;
|
||||
type = types.int;
|
||||
description = ''
|
||||
How often should mpdscribble save the journal file? [seconds]
|
||||
'';
|
||||
};
|
||||
|
||||
host = mkOption {
|
||||
default = (if mpdCfg.network.listenAddress != "any" then
|
||||
mpdCfg.network.listenAddress
|
||||
else
|
||||
"localhost");
|
||||
defaultText = literalExpression ''
|
||||
if config.${mpdOpt.network.listenAddress} != "any"
|
||||
then config.${mpdOpt.network.listenAddress}
|
||||
else "localhost"
|
||||
'';
|
||||
type = types.str;
|
||||
description = ''
|
||||
Host for the mpdscribble daemon to search for a mpd daemon on.
|
||||
'';
|
||||
};
|
||||
|
||||
passwordFile = mkOption {
|
||||
default = if localMpd then
|
||||
(findFirst
|
||||
(c: any (x: x == "read") c.permissions)
|
||||
{ passwordFile = null; }
|
||||
mpdCfg.credentials).passwordFile
|
||||
else
|
||||
null;
|
||||
defaultText = literalDocBook ''
|
||||
The first password file with read access configured for MPD when using a local instance,
|
||||
otherwise <literal>null</literal>.
|
||||
'';
|
||||
type = types.nullOr types.str;
|
||||
description = ''
|
||||
File containing the password for the mpd daemon.
|
||||
If there is a local mpd configured using <option>services.mpd.credentials</option>
|
||||
the default is automatically set to a matching passwordFile of the local mpd.
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
default = mpdCfg.network.port;
|
||||
defaultText = literalExpression "config.${mpdOpt.network.port}";
|
||||
type = types.port;
|
||||
description = ''
|
||||
Port for the mpdscribble daemon to search for a mpd daemon on.
|
||||
'';
|
||||
};
|
||||
|
||||
endpoints = mkOption {
|
||||
type = (let
|
||||
endpoint = { name, ... }: {
|
||||
options = {
|
||||
url = mkOption {
|
||||
type = types.str;
|
||||
default = endpointUrls.${name} or "";
|
||||
description =
|
||||
"The url endpoint where the scrobble API is listening.";
|
||||
};
|
||||
username = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
Username for the scrobble service.
|
||||
'';
|
||||
};
|
||||
passwordFile = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
description =
|
||||
"File containing the password, either as MD5SUM or cleartext.";
|
||||
};
|
||||
};
|
||||
};
|
||||
in types.attrsOf (types.submodule endpoint));
|
||||
default = { };
|
||||
example = {
|
||||
"last.fm" = {
|
||||
username = "foo";
|
||||
passwordFile = "/run/secrets/lastfm_password";
|
||||
};
|
||||
};
|
||||
description = ''
|
||||
Endpoints to scrobble to.
|
||||
If the endpoint is one of "${
|
||||
concatStringsSep "\", \"" (attrNames endpointUrls)
|
||||
}" the url is set automatically.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.mpdscribble = {
|
||||
after = [ "network.target" ] ++ (optional localMpd "mpd.service");
|
||||
description = "mpdscribble mpd scrobble client";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
StateDirectory = "mpdscribble";
|
||||
RuntimeDirectory = "mpdscribble";
|
||||
RuntimeDirectoryMode = "700";
|
||||
# TODO use LoadCredential= instead of running preStart with full privileges?
|
||||
ExecStartPre = "+${preStart}";
|
||||
ExecStart =
|
||||
"${pkgs.mpdscribble}/bin/mpdscribble --no-daemon --conf ${cfgFile}";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
71
nixos/modules/services/audio/navidrome.nix
Normal file
71
nixos/modules/services/audio/navidrome.nix
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.navidrome;
|
||||
settingsFormat = pkgs.formats.json {};
|
||||
in {
|
||||
options = {
|
||||
services.navidrome = {
|
||||
|
||||
enable = mkEnableOption "Navidrome music server";
|
||||
|
||||
settings = mkOption rec {
|
||||
type = settingsFormat.type;
|
||||
apply = recursiveUpdate default;
|
||||
default = {
|
||||
Address = "127.0.0.1";
|
||||
Port = 4533;
|
||||
};
|
||||
example = {
|
||||
MusicFolder = "/mnt/music";
|
||||
};
|
||||
description = ''
|
||||
Configuration for Navidrome, see <link xlink:href="https://www.navidrome.org/docs/usage/configuration-options/"/> for supported values.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.navidrome = {
|
||||
description = "Navidrome Media Server";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = ''
|
||||
${pkgs.navidrome}/bin/navidrome --configfile ${settingsFormat.generate "navidrome.json" cfg.settings}
|
||||
'';
|
||||
DynamicUser = true;
|
||||
StateDirectory = "navidrome";
|
||||
WorkingDirectory = "/var/lib/navidrome";
|
||||
RuntimeDirectory = "navidrome";
|
||||
RootDirectory = "/run/navidrome";
|
||||
ReadWritePaths = "";
|
||||
BindReadOnlyPaths = [
|
||||
builtins.storeDir
|
||||
] ++ lib.optional (cfg.settings ? MusicFolder) cfg.settings.MusicFolder;
|
||||
CapabilityBoundingSet = "";
|
||||
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
|
||||
RestrictNamespaces = true;
|
||||
PrivateDevices = true;
|
||||
PrivateUsers = true;
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
|
||||
RestrictRealtime = true;
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
UMask = "0066";
|
||||
ProtectHostname = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
19
nixos/modules/services/audio/networkaudiod.nix
Normal file
19
nixos/modules/services/audio/networkaudiod.nix
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
name = "networkaudiod";
|
||||
cfg = config.services.networkaudiod;
|
||||
in {
|
||||
options = {
|
||||
services.networkaudiod = {
|
||||
enable = mkEnableOption "Networkaudiod (NAA)";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.packages = [ pkgs.networkaudiod ];
|
||||
systemd.services.networkaudiod.wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
}
|
||||
76
nixos/modules/services/audio/roon-bridge.nix
Normal file
76
nixos/modules/services/audio/roon-bridge.nix
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
name = "roon-bridge";
|
||||
cfg = config.services.roon-bridge;
|
||||
in {
|
||||
options = {
|
||||
services.roon-bridge = {
|
||||
enable = mkEnableOption "Roon Bridge";
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Open ports in the firewall for the bridge.
|
||||
'';
|
||||
};
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "roon-bridge";
|
||||
description = ''
|
||||
User to run the Roon bridge as.
|
||||
'';
|
||||
};
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "roon-bridge";
|
||||
description = ''
|
||||
Group to run the Roon Bridge as.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.roon-bridge = {
|
||||
after = [ "network.target" ];
|
||||
description = "Roon Bridge";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
environment.ROON_DATAROOT = "/var/lib/${name}";
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.roon-bridge}/start.sh";
|
||||
LimitNOFILE = 8192;
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
StateDirectory = name;
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
allowedTCPPortRanges = [{ from = 9100; to = 9200; }];
|
||||
allowedUDPPorts = [ 9003 ];
|
||||
extraCommands = ''
|
||||
iptables -A INPUT -s 224.0.0.0/4 -j ACCEPT
|
||||
iptables -A INPUT -d 224.0.0.0/4 -j ACCEPT
|
||||
iptables -A INPUT -s 240.0.0.0/5 -j ACCEPT
|
||||
iptables -A INPUT -m pkttype --pkt-type multicast -j ACCEPT
|
||||
iptables -A INPUT -m pkttype --pkt-type broadcast -j ACCEPT
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
users.groups.${cfg.group} = {};
|
||||
users.users.${cfg.user} =
|
||||
if cfg.user == "roon-bridge" then {
|
||||
isSystemUser = true;
|
||||
description = "Roon Bridge user";
|
||||
group = cfg.group;
|
||||
extraGroups = [ "audio" ];
|
||||
}
|
||||
else {};
|
||||
};
|
||||
}
|
||||
79
nixos/modules/services/audio/roon-server.nix
Normal file
79
nixos/modules/services/audio/roon-server.nix
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
name = "roon-server";
|
||||
cfg = config.services.roon-server;
|
||||
in {
|
||||
options = {
|
||||
services.roon-server = {
|
||||
enable = mkEnableOption "Roon Server";
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Open ports in the firewall for the server.
|
||||
'';
|
||||
};
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "roon-server";
|
||||
description = ''
|
||||
User to run the Roon Server as.
|
||||
'';
|
||||
};
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "roon-server";
|
||||
description = ''
|
||||
Group to run the Roon Server as.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.roon-server = {
|
||||
after = [ "network.target" ];
|
||||
description = "Roon Server";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
environment.ROON_DATAROOT = "/var/lib/${name}";
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.roon-server}/bin/RoonServer";
|
||||
LimitNOFILE = 8192;
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
StateDirectory = name;
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
allowedTCPPortRanges = [
|
||||
{ from = 9100; to = 9200; }
|
||||
{ from = 9330; to = 9332; }
|
||||
];
|
||||
allowedUDPPorts = [ 9003 ];
|
||||
extraCommands = ''
|
||||
iptables -A INPUT -s 224.0.0.0/4 -j ACCEPT
|
||||
iptables -A INPUT -d 224.0.0.0/4 -j ACCEPT
|
||||
iptables -A INPUT -s 240.0.0.0/5 -j ACCEPT
|
||||
iptables -A INPUT -m pkttype --pkt-type multicast -j ACCEPT
|
||||
iptables -A INPUT -m pkttype --pkt-type broadcast -j ACCEPT
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
users.groups.${cfg.group} = {};
|
||||
users.users.${cfg.user} =
|
||||
if cfg.user == "roon-server" then {
|
||||
isSystemUser = true;
|
||||
description = "Roon Server user";
|
||||
group = cfg.group;
|
||||
extraGroups = [ "audio" ];
|
||||
}
|
||||
else {};
|
||||
};
|
||||
}
|
||||
73
nixos/modules/services/audio/slimserver.nix
Normal file
73
nixos/modules/services/audio/slimserver.nix
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.slimserver;
|
||||
|
||||
in {
|
||||
options = {
|
||||
|
||||
services.slimserver = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable slimserver.
|
||||
'';
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.slimserver;
|
||||
defaultText = literalExpression "pkgs.slimserver";
|
||||
description = "Slimserver package to use.";
|
||||
};
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/lib/slimserver";
|
||||
description = ''
|
||||
The directory where slimserver stores its state, tag cache,
|
||||
playlists etc.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.dataDir}' - slimserver slimserver - -"
|
||||
];
|
||||
|
||||
systemd.services.slimserver = {
|
||||
after = [ "network.target" ];
|
||||
description = "Slim Server for Logitech Squeezebox Players";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
User = "slimserver";
|
||||
# Issue 40589: Disable broken image/video support (audio still works!)
|
||||
ExecStart = "${cfg.package}/slimserver.pl --logdir ${cfg.dataDir}/logs --prefsdir ${cfg.dataDir}/prefs --cachedir ${cfg.dataDir}/cache --noimage --novideo";
|
||||
};
|
||||
};
|
||||
|
||||
users = {
|
||||
users.slimserver = {
|
||||
description = "Slimserver daemon user";
|
||||
home = cfg.dataDir;
|
||||
group = "slimserver";
|
||||
isSystemUser = true;
|
||||
};
|
||||
groups.slimserver = {};
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
323
nixos/modules/services/audio/snapserver.nix
Normal file
323
nixos/modules/services/audio/snapserver.nix
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
{ config, options, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
name = "snapserver";
|
||||
|
||||
cfg = config.services.snapserver;
|
||||
|
||||
# Using types.nullOr to inherit upstream defaults.
|
||||
sampleFormat = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
Default sample format.
|
||||
'';
|
||||
example = "48000:16:2";
|
||||
};
|
||||
|
||||
codec = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
Default audio compression method.
|
||||
'';
|
||||
example = "flac";
|
||||
};
|
||||
|
||||
streamToOption = name: opt:
|
||||
let
|
||||
os = val:
|
||||
optionalString (val != null) "${val}";
|
||||
os' = prefix: val:
|
||||
optionalString (val != null) (prefix + "${val}");
|
||||
flatten = key: value:
|
||||
"&${key}=${value}";
|
||||
in
|
||||
"--stream.stream=\"${opt.type}://" + os opt.location + "?" + os' "name=" name
|
||||
+ concatStrings (mapAttrsToList flatten opt.query) + "\"";
|
||||
|
||||
optionalNull = val: ret:
|
||||
optional (val != null) ret;
|
||||
|
||||
optionString = concatStringsSep " " (mapAttrsToList streamToOption cfg.streams
|
||||
# global options
|
||||
++ [ "--stream.bind_to_address=${cfg.listenAddress}" ]
|
||||
++ [ "--stream.port=${toString cfg.port}" ]
|
||||
++ optionalNull cfg.sampleFormat "--stream.sampleformat=${cfg.sampleFormat}"
|
||||
++ optionalNull cfg.codec "--stream.codec=${cfg.codec}"
|
||||
++ optionalNull cfg.streamBuffer "--stream.stream_buffer=${toString cfg.streamBuffer}"
|
||||
++ optionalNull cfg.buffer "--stream.buffer=${toString cfg.buffer}"
|
||||
++ optional cfg.sendToMuted "--stream.send_to_muted"
|
||||
# tcp json rpc
|
||||
++ [ "--tcp.enabled=${toString cfg.tcp.enable}" ]
|
||||
++ optionals cfg.tcp.enable [
|
||||
"--tcp.bind_to_address=${cfg.tcp.listenAddress}"
|
||||
"--tcp.port=${toString cfg.tcp.port}" ]
|
||||
# http json rpc
|
||||
++ [ "--http.enabled=${toString cfg.http.enable}" ]
|
||||
++ optionals cfg.http.enable [
|
||||
"--http.bind_to_address=${cfg.http.listenAddress}"
|
||||
"--http.port=${toString cfg.http.port}"
|
||||
] ++ optional (cfg.http.docRoot != null) "--http.doc_root=\"${toString cfg.http.docRoot}\"");
|
||||
|
||||
in {
|
||||
imports = [
|
||||
(mkRenamedOptionModule [ "services" "snapserver" "controlPort" ] [ "services" "snapserver" "tcp" "port" ])
|
||||
];
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.snapserver = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable snapserver.
|
||||
'';
|
||||
};
|
||||
|
||||
listenAddress = mkOption {
|
||||
type = types.str;
|
||||
default = "::";
|
||||
example = "0.0.0.0";
|
||||
description = ''
|
||||
The address where snapclients can connect.
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 1704;
|
||||
description = ''
|
||||
The port that snapclients can connect to.
|
||||
'';
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
# Make the behavior consistent with other services. Set the default to
|
||||
# false and remove the accompanying warning after NixOS 22.05 is released.
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to automatically open the specified ports in the firewall.
|
||||
'';
|
||||
};
|
||||
|
||||
inherit sampleFormat;
|
||||
inherit codec;
|
||||
|
||||
streamBuffer = mkOption {
|
||||
type = with types; nullOr int;
|
||||
default = null;
|
||||
description = ''
|
||||
Stream read (input) buffer in ms.
|
||||
'';
|
||||
example = 20;
|
||||
};
|
||||
|
||||
buffer = mkOption {
|
||||
type = with types; nullOr int;
|
||||
default = null;
|
||||
description = ''
|
||||
Network buffer in ms.
|
||||
'';
|
||||
example = 1000;
|
||||
};
|
||||
|
||||
sendToMuted = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Send audio to muted clients.
|
||||
'';
|
||||
};
|
||||
|
||||
tcp.enable = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to enable the JSON-RPC via TCP.
|
||||
'';
|
||||
};
|
||||
|
||||
tcp.listenAddress = mkOption {
|
||||
type = types.str;
|
||||
default = "::";
|
||||
example = "0.0.0.0";
|
||||
description = ''
|
||||
The address where the TCP JSON-RPC listens on.
|
||||
'';
|
||||
};
|
||||
|
||||
tcp.port = mkOption {
|
||||
type = types.port;
|
||||
default = 1705;
|
||||
description = ''
|
||||
The port where the TCP JSON-RPC listens on.
|
||||
'';
|
||||
};
|
||||
|
||||
http.enable = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to enable the JSON-RPC via HTTP.
|
||||
'';
|
||||
};
|
||||
|
||||
http.listenAddress = mkOption {
|
||||
type = types.str;
|
||||
default = "::";
|
||||
example = "0.0.0.0";
|
||||
description = ''
|
||||
The address where the HTTP JSON-RPC listens on.
|
||||
'';
|
||||
};
|
||||
|
||||
http.port = mkOption {
|
||||
type = types.port;
|
||||
default = 1780;
|
||||
description = ''
|
||||
The port where the HTTP JSON-RPC listens on.
|
||||
'';
|
||||
};
|
||||
|
||||
http.docRoot = mkOption {
|
||||
type = with types; nullOr path;
|
||||
default = null;
|
||||
description = ''
|
||||
Path to serve from the HTTP servers root.
|
||||
'';
|
||||
};
|
||||
|
||||
streams = mkOption {
|
||||
type = with types; attrsOf (submodule {
|
||||
options = {
|
||||
location = mkOption {
|
||||
type = types.oneOf [ types.path types.str ];
|
||||
description = ''
|
||||
For type <literal>pipe</literal> or <literal>file</literal>, the path to the pipe or file.
|
||||
For type <literal>librespot</literal>, <literal>airplay</literal> or <literal>process</literal>, the path to the corresponding binary.
|
||||
For type <literal>tcp</literal>, the <literal>host:port</literal> address to connect to or listen on.
|
||||
For type <literal>meta</literal>, a list of stream names in the form <literal>/one/two/...</literal>. Don't forget the leading slash.
|
||||
For type <literal>alsa</literal>, use an empty string.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
"/path/to/pipe"
|
||||
"/path/to/librespot"
|
||||
"192.168.1.2:4444"
|
||||
"/MyTCP/Spotify/MyPipe"
|
||||
'';
|
||||
};
|
||||
type = mkOption {
|
||||
type = types.enum [ "pipe" "librespot" "airplay" "file" "process" "tcp" "alsa" "spotify" "meta" ];
|
||||
default = "pipe";
|
||||
description = ''
|
||||
The type of input stream.
|
||||
'';
|
||||
};
|
||||
query = mkOption {
|
||||
type = attrsOf str;
|
||||
default = {};
|
||||
description = ''
|
||||
Key-value pairs that convey additional parameters about a stream.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
# for type == "pipe":
|
||||
{
|
||||
mode = "create";
|
||||
};
|
||||
# for type == "process":
|
||||
{
|
||||
params = "--param1 --param2";
|
||||
logStderr = "true";
|
||||
};
|
||||
# for type == "tcp":
|
||||
{
|
||||
mode = "client";
|
||||
}
|
||||
# for type == "alsa":
|
||||
{
|
||||
device = "hw:0,0";
|
||||
}
|
||||
'';
|
||||
};
|
||||
inherit sampleFormat;
|
||||
inherit codec;
|
||||
};
|
||||
});
|
||||
default = { default = {}; };
|
||||
description = ''
|
||||
The definition for an input source.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
mpd = {
|
||||
type = "pipe";
|
||||
location = "/run/snapserver/mpd";
|
||||
sampleFormat = "48000:16:2";
|
||||
codec = "pcm";
|
||||
};
|
||||
};
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
warnings =
|
||||
# https://github.com/badaix/snapcast/blob/98ac8b2fb7305084376607b59173ce4097c620d8/server/streamreader/stream_manager.cpp#L85
|
||||
filter (w: w != "") (mapAttrsToList (k: v: if v.type == "spotify" then ''
|
||||
services.snapserver.streams.${k}.type = "spotify" is deprecated, use services.snapserver.streams.${k}.type = "librespot" instead.
|
||||
'' else "") cfg.streams)
|
||||
# Remove this warning after NixOS 22.05 is released.
|
||||
++ optional (options.services.snapserver.openFirewall.highestPrio >= (mkOptionDefault null).priority) ''
|
||||
services.snapserver.openFirewall will no longer default to true starting with NixOS 22.11.
|
||||
Enable it explicitly if you need to control Snapserver remotely.
|
||||
'';
|
||||
|
||||
systemd.services.snapserver = {
|
||||
after = [ "network.target" ];
|
||||
description = "Snapserver";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
before = [ "mpd.service" "mopidy.service" ];
|
||||
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
ExecStart = "${pkgs.snapcast}/bin/snapserver --daemon ${optionString}";
|
||||
Type = "forking";
|
||||
LimitRTPRIO = 50;
|
||||
LimitRTTIME = "infinity";
|
||||
NoNewPrivileges = true;
|
||||
PIDFile = "/run/${name}/pid";
|
||||
ProtectKernelTunables = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectKernelModules = true;
|
||||
RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX AF_NETLINK";
|
||||
RestrictNamespaces = true;
|
||||
RuntimeDirectory = name;
|
||||
StateDirectory = name;
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts =
|
||||
optionals cfg.openFirewall [ cfg.port ]
|
||||
++ optional (cfg.openFirewall && cfg.tcp.enable) cfg.tcp.port
|
||||
++ optional (cfg.openFirewall && cfg.http.enable) cfg.http.port;
|
||||
};
|
||||
|
||||
meta = {
|
||||
maintainers = with maintainers; [ tobim ];
|
||||
};
|
||||
|
||||
}
|
||||
68
nixos/modules/services/audio/spotifyd.nix
Normal file
68
nixos/modules/services/audio/spotifyd.nix
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.spotifyd;
|
||||
toml = pkgs.formats.toml {};
|
||||
warnConfig =
|
||||
if cfg.config != ""
|
||||
then lib.trace "Using the stringly typed .config attribute is discouraged. Use the TOML typed .settings attribute instead."
|
||||
else id;
|
||||
spotifydConf =
|
||||
if cfg.settings != {}
|
||||
then toml.generate "spotify.conf" cfg.settings
|
||||
else warnConfig (pkgs.writeText "spotifyd.conf" cfg.config);
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.spotifyd = {
|
||||
enable = mkEnableOption "spotifyd, a Spotify playing daemon";
|
||||
|
||||
config = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
description = ''
|
||||
(Deprecated) Configuration for Spotifyd. For syntax and directives, see
|
||||
<link xlink:href="https://github.com/Spotifyd/spotifyd#Configuration"/>.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
default = {};
|
||||
type = toml.type;
|
||||
example = { global.bitrate = 320; };
|
||||
description = ''
|
||||
Configuration for Spotifyd. For syntax and directives, see
|
||||
<link xlink:href="https://github.com/Spotifyd/spotifyd#Configuration"/>.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.config == "" || cfg.settings == {};
|
||||
message = "At most one of the .config attribute and the .settings attribute may be set";
|
||||
}
|
||||
];
|
||||
|
||||
systemd.services.spotifyd = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network-online.target" "sound.target" ];
|
||||
description = "spotifyd, a Spotify playing daemon";
|
||||
environment.SHELL = "/bin/sh";
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.spotifyd}/bin/spotifyd --no-daemon --cache-path /var/cache/spotifyd --config-path ${spotifydConf}";
|
||||
Restart = "always";
|
||||
RestartSec = 12;
|
||||
DynamicUser = true;
|
||||
CacheDirectory = "spotifyd";
|
||||
SupplementaryGroups = ["audio"];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = [ maintainers.anderslundstedt ];
|
||||
}
|
||||
46
nixos/modules/services/audio/squeezelite.nix
Normal file
46
nixos/modules/services/audio/squeezelite.nix
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
inherit (lib) mkEnableOption mkIf mkOption optionalString types;
|
||||
|
||||
dataDir = "/var/lib/squeezelite";
|
||||
cfg = config.services.squeezelite;
|
||||
pkg = if cfg.pulseAudio then pkgs.squeezelite-pulse else pkgs.squeezelite;
|
||||
bin = "${pkg}/bin/${pkg.pname}";
|
||||
|
||||
in
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options.services.squeezelite = {
|
||||
enable = mkEnableOption "Squeezelite, a software Squeezebox emulator";
|
||||
|
||||
pulseAudio = mkEnableOption "pulseaudio support";
|
||||
|
||||
extraArguments = mkOption {
|
||||
default = "";
|
||||
type = types.str;
|
||||
description = ''
|
||||
Additional command line arguments to pass to Squeezelite.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.squeezelite = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" "sound.target" ];
|
||||
description = "Software Squeezebox emulator";
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
ExecStart = "${bin} -N ${dataDir}/player-name ${cfg.extraArguments}";
|
||||
StateDirectory = builtins.baseNameOf dataDir;
|
||||
SupplementaryGroups = "audio";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
57
nixos/modules/services/audio/ympd.nix
Normal file
57
nixos/modules/services/audio/ympd.nix
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.ympd;
|
||||
in {
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.ympd = {
|
||||
|
||||
enable = mkEnableOption "ympd, the MPD Web GUI";
|
||||
|
||||
webPort = mkOption {
|
||||
type = types.either types.str types.port; # string for backwards compat
|
||||
default = "8080";
|
||||
description = "The port where ympd's web interface will be available.";
|
||||
example = "ssl://8080:/path/to/ssl-private-key.pem";
|
||||
};
|
||||
|
||||
mpd = {
|
||||
host = mkOption {
|
||||
type = types.str;
|
||||
default = "localhost";
|
||||
description = "The host where MPD is listening.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
default = config.services.mpd.network.port;
|
||||
defaultText = literalExpression "config.services.mpd.network.port";
|
||||
description = "The port where MPD is listening.";
|
||||
example = 6600;
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
systemd.services.ympd = {
|
||||
description = "Standalone MPD Web GUI written in C";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig.ExecStart = "${pkgs.ympd}/bin/ympd --host ${cfg.mpd.host} --port ${toString cfg.mpd.port} --webport ${toString cfg.webPort} --user nobody";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue