1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8with lib;
9
10let
11
12 cfg = config.services.shairport-sync;
13 configFormat = pkgs.formats.libconfig { };
14 configFile = configFormat.generate "shairport-sync.conf" cfg.settings;
15in
16
17{
18
19 ###### interface
20
21 options = {
22
23 services.shairport-sync = {
24
25 enable = mkOption {
26 type = types.bool;
27 default = false;
28 description = ''
29 Enable the shairport-sync daemon.
30
31 Running with a local system-wide or remote pulseaudio server
32 is recommended.
33 '';
34 };
35
36 package = lib.options.mkPackageOption pkgs "shairport-sync" { };
37
38 settings = mkOption {
39 type = configFormat.type;
40 default = {
41 general.output_backend = "pa";
42 diagnostics.log_verbosity = 1;
43 };
44 example = {
45 general = {
46 name = "NixOS Shairport";
47 output_backend = "pw";
48 };
49 metadata = {
50 enabled = "yes";
51 include_cover_art = "yes";
52 cover_art_cache_directory = "/tmp/shairport-sync/.cache/coverart";
53 pipe_name = "/tmp/shairport-sync-metadata";
54 pipe_timeout = 5000;
55 };
56 mqtt = {
57 enabled = "yes";
58 hostname = "mqtt.server.domain.example";
59 port = 1883;
60 publish_parsed = "yes";
61 publish_cover = "yes";
62 };
63 };
64 description = ''
65 Configuration options for Shairport-Sync.
66
67 See the example [shairport-sync.conf][example-file] for possible options.
68
69 [example-file]: https://github.com/mikebrady/shairport-sync/blob/master/scripts/shairport-sync.conf
70 '';
71 };
72
73 arguments = mkOption {
74 type = types.str;
75 default = "";
76 description = ''
77 Arguments to pass to the daemon. Defaults to a local pulseaudio
78 server.
79 '';
80 };
81
82 openFirewall = mkOption {
83 type = types.bool;
84 default = false;
85 description = ''
86 Whether to automatically open ports in the firewall.
87 '';
88 };
89
90 user = mkOption {
91 type = types.str;
92 default = "shairport";
93 description = ''
94 User account name under which to run shairport-sync. The account
95 will be created.
96 '';
97 };
98
99 group = mkOption {
100 type = types.str;
101 default = "shairport";
102 description = ''
103 Group account name under which to run shairport-sync. The account
104 will be created.
105 '';
106 };
107
108 };
109
110 };
111
112 ###### implementation
113
114 config = mkIf config.services.shairport-sync.enable {
115
116 services.avahi.enable = true;
117 services.avahi.publish.enable = true;
118 services.avahi.publish.userServices = true;
119
120 services.shairport-sync.settings = {
121 general.output_backend = lib.mkDefault "pa";
122 diagnostics.log_verbosity = lib.mkDefault 1;
123 };
124
125 users = {
126 users.${cfg.user} = {
127 description = "Shairport user";
128 isSystemUser = true;
129 createHome = true;
130 home = "/var/lib/shairport-sync";
131 group = cfg.group;
132 extraGroups = [
133 "audio"
134 ] ++ optional (config.services.pulseaudio.enable || config.services.pipewire.pulse.enable) "pulse";
135 };
136 groups.${cfg.group} = { };
137 };
138
139 networking.firewall = mkIf cfg.openFirewall {
140 allowedTCPPorts = [ 5000 ];
141 allowedUDPPortRanges = [
142 {
143 from = 6001;
144 to = 6011;
145 }
146 ];
147 };
148
149 systemd.services.shairport-sync = {
150 description = "shairport-sync";
151 after = [
152 "network.target"
153 "avahi-daemon.service"
154 ];
155 wantedBy = [ "multi-user.target" ];
156 serviceConfig = {
157 User = cfg.user;
158 Group = cfg.group;
159 ExecStart = "${lib.getExe cfg.package} ${cfg.arguments}";
160 Restart = "on-failure";
161 RuntimeDirectory = "shairport-sync";
162 };
163 };
164
165 environment = {
166 systemPackages = [ cfg.package ];
167 etc."shairport-sync.conf".source = configFile;
168 };
169 };
170
171}