forked from CCCHH/nix-infra
Move audio configuration into a service module
- Move audio configuration into a service module to make it easily usable by multiple hosts. - Allow configuration of AirPlay 2 name. - Don't set MQTT topic of Shairport-Sync (AirPlay 2) explictly. Defaults to hostname now. - Don't set sound.enable anymore, since its not needed.
This commit is contained in:
parent
8355a6f635
commit
c9310374af
13
config/hosts/audio/audio.nix
Normal file
13
config/hosts/audio/audio.nix
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
../../../modules/services/audio
|
||||||
|
];
|
||||||
|
|
||||||
|
ccchh.services.audio = {
|
||||||
|
enable = true;
|
||||||
|
name = "Audio";
|
||||||
|
};
|
||||||
|
|
||||||
|
users.users.chaos.extraGroups = [ "pipewire" ];
|
||||||
|
}
|
|
@ -4,7 +4,5 @@
|
||||||
hostName = "audio";
|
hostName = "audio";
|
||||||
};
|
};
|
||||||
|
|
||||||
sound.enable = true;
|
|
||||||
|
|
||||||
system.stateVersion = "23.05";
|
system.stateVersion = "23.05";
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
{ ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
|
./audio.nix
|
||||||
./configuration.nix
|
./configuration.nix
|
||||||
./librespot.nix
|
|
||||||
./networking.nix
|
./networking.nix
|
||||||
./pipewire.nix
|
|
||||||
./shairport-sync.nix
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
# Sources for this configuration:
|
|
||||||
# - https://github.com/librespot-org/librespot
|
|
||||||
# - https://github.com/librespot-org/librespot/wiki/Options
|
|
||||||
# - https://github.com/librespot-org/librespot/wiki/Audio-Backends
|
|
||||||
|
|
||||||
{ config, pkgs, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
systemd.services.spotify-connect-receiver = {
|
|
||||||
enable = true;
|
|
||||||
description = "Spotify Connect Receiver Using librespot";
|
|
||||||
unitConfig = {
|
|
||||||
Requires = [ "network-online.target" "pipewire.service" ];
|
|
||||||
After = [ "network-online.target" "pipewire.service" ];
|
|
||||||
};
|
|
||||||
serviceConfig = {
|
|
||||||
ExecStart = "${pkgs.librespot}/bin/librespot --name 'Z9 Hauptraum' --device-type speaker --bitrate 320 --enable-volume-normalisation --disable-audio-cache --disable-credential-cache --quiet";
|
|
||||||
User = "librespot";
|
|
||||||
Group = "librespot";
|
|
||||||
};
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
users = {
|
|
||||||
users.librespot = {
|
|
||||||
isSystemUser = true;
|
|
||||||
group = "librespot";
|
|
||||||
extraGroups = [ "pipewire" ];
|
|
||||||
};
|
|
||||||
groups.librespot = { };
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -14,8 +14,6 @@
|
||||||
nameservers = [
|
nameservers = [
|
||||||
"10.31.210.1"
|
"10.31.210.1"
|
||||||
];
|
];
|
||||||
# Disable IPv6, since Shairport-Sync doesn't work with IPv6. Unclear why.
|
|
||||||
enableIPv6 = false;
|
|
||||||
};
|
};
|
||||||
systemd.network.links."10-net0" = {
|
systemd.network.links."10-net0" = {
|
||||||
matchConfig.MACAddress = "1E:EF:2D:92:81:DA";
|
matchConfig.MACAddress = "1E:EF:2D:92:81:DA";
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
# Sources for this configuration:
|
|
||||||
# - https://nixos.wiki/wiki/PipeWire
|
|
||||||
|
|
||||||
{ config, pkgs, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
security.rtkit.enable = true;
|
|
||||||
services.pipewire = {
|
|
||||||
enable = true;
|
|
||||||
systemWide = true;
|
|
||||||
alsa.enable = true;
|
|
||||||
alsa.support32Bit = true;
|
|
||||||
pulse.enable = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
users.users.chaos.extraGroups = [ "pipewire" ];
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
// Config for MQTT and metadata.
|
|
||||||
// For reference see:
|
|
||||||
// https://github.com/mikebrady/shairport-sync/blob/8c607e2d61ea5fde8ed0b48b2471a543840c31e6/scripts/shairport-sync.conf
|
|
||||||
// https://github.com/mikebrady/shairport-sync/blob/0f0f65b67649d4a25d06f13b56a6077a90b0f6cc/MQTT.md
|
|
||||||
metadata =
|
|
||||||
{
|
|
||||||
enabled = "yes"; // set this to yes to get Shairport Sync to solicit metadata from the source and to pass it on via a pipe
|
|
||||||
include_cover_art = "yes"; // set to "yes" to get Shairport Sync to solicit cover art from the source and pass it via the pipe. You must also set "enabled" to "yes".
|
|
||||||
cover_art_cache_directory = "/tmp/shairport-sync/.cache/coverart"; // artwork will be stored in this directory if the dbus or MPRIS interfaces are enabled or if the MQTT client is in use. Set it to "" to prevent caching, which may be useful on some systems
|
|
||||||
pipe_name = "/tmp/shairport-sync-metadata";
|
|
||||||
pipe_timeout = 5000; // wait for this number of milliseconds for a blocked pipe to unblock before giving up
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
mqtt =
|
|
||||||
{
|
|
||||||
enabled = "yes"; // set this to yes to enable the mqtt-metadata-service
|
|
||||||
hostname = "mqtt.ccchh.net"; // Hostname of the MQTT Broker
|
|
||||||
port = 1883; // Port on the MQTT Broker to connect to
|
|
||||||
topic = "audio"; //MQTT topic where this instance of shairport-sync should publish. If not set, the general.name value is used.
|
|
||||||
// publish_raw = "no"; //whether to publish all available metadata under the codes given in the 'metadata' docs.
|
|
||||||
publish_parsed = "yes"; //whether to publish a small (but useful) subset of metadata under human-understandable topics
|
|
||||||
publish_cover = "yes"; //whether to publish the cover over mqtt in binary form. This may lead to a bit of load on the broker
|
|
||||||
// enable_remote = "yes"; //whether to remote control via MQTT. RC is available under `topic`/remote.
|
|
||||||
};
|
|
|
@ -1,74 +0,0 @@
|
||||||
# Sources for this configuration:
|
|
||||||
# - https://github.com/mikebrady/shairport-sync/blob/f5c4b51da827a7f8d9a72a1b6f986807aba47bfc/AIRPLAY2.md
|
|
||||||
# - https://github.com/mikebrady/nqptp
|
|
||||||
# - https://github.com/mikebrady/nqptp/blob/050a8c2de9f3e1f4859abf9b36d2f18afd4c34d7/nqptp.service.in
|
|
||||||
|
|
||||||
{ pkgs, lib, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
services.shairport-sync = {
|
|
||||||
enable = true;
|
|
||||||
arguments = "-o pw";
|
|
||||||
};
|
|
||||||
|
|
||||||
users.users.shairport.extraGroups = [ "pipewire" ];
|
|
||||||
|
|
||||||
environment.etc.shairport-sync-config = {
|
|
||||||
enable = true;
|
|
||||||
source = ./shairport-sync.conf;
|
|
||||||
target = "shairport-sync.conf";
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.services.shairport-sync = {
|
|
||||||
serviceConfig = {
|
|
||||||
Restart = "on-failure";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
users.users.nqptp = {
|
|
||||||
isSystemUser = true;
|
|
||||||
group = "nqptp";
|
|
||||||
};
|
|
||||||
users.groups.nqptp = { };
|
|
||||||
|
|
||||||
systemd.services.nqptp = {
|
|
||||||
enable = true;
|
|
||||||
description = "NQPTP -- Not Quite PTP";
|
|
||||||
unitConfig = {
|
|
||||||
Wants = [ "network-online.target" ];
|
|
||||||
After = [ "network.target" "network-online.target" ];
|
|
||||||
Before = [ "shairport-sync.service" ];
|
|
||||||
};
|
|
||||||
serviceConfig = {
|
|
||||||
ExecStart = "${pkgs.nqptp}/bin/nqptp";
|
|
||||||
User = "nqptp";
|
|
||||||
Group = "nqptp";
|
|
||||||
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
|
|
||||||
Restart = "on-failure";
|
|
||||||
};
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
# See here for docs:
|
|
||||||
# https://github.com/mikebrady/shairport-sync/blob/4ca5a15de2d53c69e6c3c23b0440c27978bb91df/TROUBLESHOOTING.md#ufw-firewall-blocking-ports-commonly-includes-raspberry-pi
|
|
||||||
# These docs seem like they also include the ports for AirPlay 1. Since we're
|
|
||||||
# doing just AirPlay 2, we can have a more restrictive firewall than
|
|
||||||
# documented there.
|
|
||||||
# This more restritive firewall also matches with a packet capture I did.
|
|
||||||
networking.firewall = {
|
|
||||||
allowedTCPPorts = [ 7000 ];
|
|
||||||
allowedUDPPorts = [ 319 320 5353 ];
|
|
||||||
allowedTCPPortRanges = [
|
|
||||||
{
|
|
||||||
from = 32768;
|
|
||||||
to = 60999;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
allowedUDPPortRanges = [
|
|
||||||
{
|
|
||||||
from = 32768;
|
|
||||||
to = 60999;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}
|
|
35
modules/services/audio/default.nix
Normal file
35
modules/services/audio/default.nix
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
|
||||||
|
cfg = config.ccchh.services.audio;
|
||||||
|
|
||||||
|
in
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./librespot.nix
|
||||||
|
./networking.nix
|
||||||
|
./pipewire.nix
|
||||||
|
./shairport-sync.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
options = {
|
||||||
|
ccchh.services.audio = {
|
||||||
|
enable = mkEnableOption (lib.mdDoc "audio");
|
||||||
|
|
||||||
|
name = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
example = "Audio Living Room";
|
||||||
|
default = config.networking.hostName;
|
||||||
|
defaultText = literalExpression "config.networking.HostName";
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
The name to use for display in the various audio services. For example
|
||||||
|
the name of the displayed Spotify speaker or AirPlay audio device.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
42
modules/services/audio/librespot.nix
Normal file
42
modules/services/audio/librespot.nix
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
# Sources for this configuration:
|
||||||
|
# - https://github.com/librespot-org/librespot
|
||||||
|
# - https://github.com/librespot-org/librespot/wiki/Options
|
||||||
|
# - https://github.com/librespot-org/librespot/wiki/Audio-Backends
|
||||||
|
|
||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
|
||||||
|
cfg = config.ccchh.services.audio;
|
||||||
|
|
||||||
|
in
|
||||||
|
|
||||||
|
{
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
systemd.services.spotify-connect-receiver = {
|
||||||
|
enable = true;
|
||||||
|
description = "Spotify Connect Receiver Using librespot";
|
||||||
|
unitConfig = {
|
||||||
|
Requires = [ "network-online.target" "pipewire.service" ];
|
||||||
|
After = [ "network-online.target" "pipewire.service" ];
|
||||||
|
};
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = "${pkgs.librespot}/bin/librespot --name '${config.ccchh.services.audio.name}' --device-type speaker --bitrate 320 --enable-volume-normalisation --disable-audio-cache --disable-credential-cache --quiet";
|
||||||
|
User = "librespot";
|
||||||
|
Group = "librespot";
|
||||||
|
};
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
users = {
|
||||||
|
users.librespot = {
|
||||||
|
isSystemUser = true;
|
||||||
|
group = "librespot";
|
||||||
|
extraGroups = [ "pipewire" ];
|
||||||
|
};
|
||||||
|
groups.librespot = { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
16
modules/services/audio/networking.nix
Normal file
16
modules/services/audio/networking.nix
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
|
||||||
|
cfg = config.ccchh.services.audio;
|
||||||
|
|
||||||
|
in
|
||||||
|
|
||||||
|
{
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
# Disable IPv6, since Shairport-Sync doesn't work with IPv6. Unclear why.
|
||||||
|
networking.enableIPv6 = false;
|
||||||
|
};
|
||||||
|
}
|
25
modules/services/audio/pipewire.nix
Normal file
25
modules/services/audio/pipewire.nix
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# Sources for this configuration:
|
||||||
|
# - https://nixos.wiki/wiki/PipeWire
|
||||||
|
|
||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
|
||||||
|
cfg = config.ccchh.services.audio;
|
||||||
|
|
||||||
|
in
|
||||||
|
|
||||||
|
{
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
security.rtkit.enable = true;
|
||||||
|
services.pipewire = {
|
||||||
|
enable = true;
|
||||||
|
systemWide = true;
|
||||||
|
alsa.enable = true;
|
||||||
|
alsa.support32Bit = true;
|
||||||
|
pulse.enable = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
114
modules/services/audio/shairport-sync.nix
Normal file
114
modules/services/audio/shairport-sync.nix
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
# Sources for this configuration:
|
||||||
|
# - https://github.com/mikebrady/shairport-sync/blob/f5c4b51da827a7f8d9a72a1b6f986807aba47bfc/AIRPLAY2.md
|
||||||
|
# - https://github.com/mikebrady/nqptp
|
||||||
|
# - https://github.com/mikebrady/nqptp/blob/050a8c2de9f3e1f4859abf9b36d2f18afd4c34d7/nqptp.service.in
|
||||||
|
|
||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
|
||||||
|
cfg = config.ccchh.services.audio;
|
||||||
|
|
||||||
|
in
|
||||||
|
|
||||||
|
{
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
services.shairport-sync = {
|
||||||
|
enable = true;
|
||||||
|
arguments = "-o pw";
|
||||||
|
};
|
||||||
|
|
||||||
|
users.users.shairport.extraGroups = [ "pipewire" ];
|
||||||
|
|
||||||
|
environment.etc.shairport-sync-config = {
|
||||||
|
enable = true;
|
||||||
|
text = ''
|
||||||
|
// Config for MQTT and metadata.
|
||||||
|
// For reference see:
|
||||||
|
// https://github.com/mikebrady/shairport-sync/blob/8c607e2d61ea5fde8ed0b48b2471a543840c31e6/scripts/shairport-sync.conf
|
||||||
|
// https://github.com/mikebrady/shairport-sync/blob/0f0f65b67649d4a25d06f13b56a6077a90b0f6cc/MQTT.md
|
||||||
|
general =
|
||||||
|
{
|
||||||
|
name = "${cfg.name}";
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata =
|
||||||
|
{
|
||||||
|
enabled = "yes"; // set this to yes to get Shairport Sync to solicit metadata from the source and to pass it on via a pipe
|
||||||
|
include_cover_art = "yes"; // set to "yes" to get Shairport Sync to solicit cover art from the source and pass it via the pipe. You must also set "enabled" to "yes".
|
||||||
|
cover_art_cache_directory = "/tmp/shairport-sync/.cache/coverart"; // artwork will be stored in this directory if the dbus or MPRIS interfaces are enabled or if the MQTT client is in use. Set it to "" to prevent caching, which may be useful on some systems
|
||||||
|
pipe_name = "/tmp/shairport-sync-metadata";
|
||||||
|
pipe_timeout = 5000; // wait for this number of milliseconds for a blocked pipe to unblock before giving up
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
mqtt =
|
||||||
|
{
|
||||||
|
enabled = "yes"; // set this to yes to enable the mqtt-metadata-service
|
||||||
|
hostname = "mqtt.ccchh.net"; // Hostname of the MQTT Broker
|
||||||
|
port = 1883; // Port on the MQTT Broker to connect to
|
||||||
|
// publish_raw = "no"; //whether to publish all available metadata under the codes given in the 'metadata' docs.
|
||||||
|
publish_parsed = "yes"; //whether to publish a small (but useful) subset of metadata under human-understandable topics
|
||||||
|
publish_cover = "yes"; //whether to publish the cover over mqtt in binary form. This may lead to a bit of load on the broker
|
||||||
|
// enable_remote = "yes"; //whether to remote control via MQTT. RC is available under `topic`/remote.
|
||||||
|
};
|
||||||
|
'';
|
||||||
|
target = "shairport-sync.conf";
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.shairport-sync = {
|
||||||
|
serviceConfig = {
|
||||||
|
Restart = "on-failure";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
users.users.nqptp = {
|
||||||
|
isSystemUser = true;
|
||||||
|
group = "nqptp";
|
||||||
|
};
|
||||||
|
users.groups.nqptp = { };
|
||||||
|
|
||||||
|
systemd.services.nqptp = {
|
||||||
|
enable = true;
|
||||||
|
description = "NQPTP -- Not Quite PTP";
|
||||||
|
unitConfig = {
|
||||||
|
Wants = [ "network-online.target" ];
|
||||||
|
After = [ "network.target" "network-online.target" ];
|
||||||
|
Before = [ "shairport-sync.service" ];
|
||||||
|
};
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = "${pkgs.nqptp}/bin/nqptp";
|
||||||
|
User = "nqptp";
|
||||||
|
Group = "nqptp";
|
||||||
|
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
|
||||||
|
Restart = "on-failure";
|
||||||
|
};
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# See here for docs:
|
||||||
|
# https://github.com/mikebrady/shairport-sync/blob/4ca5a15de2d53c69e6c3c23b0440c27978bb91df/TROUBLESHOOTING.md#ufw-firewall-blocking-ports-commonly-includes-raspberry-pi
|
||||||
|
# These docs seem like they also include the ports for AirPlay 1. Since we're
|
||||||
|
# doing just AirPlay 2, we can have a more restrictive firewall than
|
||||||
|
# documented there.
|
||||||
|
# This more restritive firewall also matches with a packet capture I did.
|
||||||
|
networking.firewall = {
|
||||||
|
allowedTCPPorts = [ 7000 ];
|
||||||
|
allowedUDPPorts = [ 319 320 5353 ];
|
||||||
|
allowedTCPPortRanges = [
|
||||||
|
{
|
||||||
|
from = 32768;
|
||||||
|
to = 60999;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
allowedUDPPortRanges = [
|
||||||
|
{
|
||||||
|
from = 32768;
|
||||||
|
to = 60999;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in a new issue