@@ -14,12 +14,86 @@ with lib; let
1414 rootFolders = import ./rootFolders.nix { inherit lib pkgs serviceName ; } ;
1515 downloadClients = import ./downloadClients.nix { inherit lib pkgs serviceName ; } ;
1616 capitalizedName = toUpper ( substring 0 1 serviceName ) + substring 1 ( - 1 ) serviceName ;
17+ screamingName = toUpper serviceName ;
1718 usesMediaDirs = ! ( elem serviceName [ "prowlarr" ] ) ;
18- serviceSupportsUserGroup = ! ( elem serviceName [ "prowlarr" ] ) ;
19+ mkServarrSettingsOptions = name :
20+ lib . mkOption {
21+ type = lib . types . submodule {
22+ freeformType = ( pkgs . formats . ini { } ) . type ;
23+ options = {
24+ update = {
25+ mechanism = lib . mkOption {
26+ type = with lib . types ;
27+ nullOr ( enum [
28+ "external"
29+ "builtIn"
30+ "script"
31+ ] ) ;
32+ description = "which update mechanism to use" ;
33+ default = "external" ;
34+ } ;
35+ automatically = lib . mkOption {
36+ type = lib . types . bool ;
37+ description = "Automatically download and install updates." ;
38+ default = false ;
39+ } ;
40+ } ;
41+ server = {
42+ port = lib . mkOption {
43+ type = lib . types . port ;
44+ description = "Port Number" ;
45+ } ;
46+ } ;
47+ log = {
48+ analyticsEnabled = lib . mkOption {
49+ type = lib . types . bool ;
50+ description = "Send Anonymous Usage Data" ;
51+ default = false ;
52+ } ;
53+ } ;
54+ } ;
55+ } ;
56+ example = lib . options . literalExpression ''
57+ {
58+ update.mechanism = "internal";
59+ server = {
60+ urlbase = "localhost";
61+ port = ${ toString port } ;
62+ bindaddress = "*";
63+ };
64+ }
65+ '' ;
66+ default = { } ;
67+ description = ''
68+ Attribute set of arbitrary config options.
69+ Please consult the documentation at the [wiki](https://wiki.servarr.com/useful-tools#using-environment-variables-for-config).
70+
71+ WARNING: this configuration is stored in the world-readable Nix store!
72+ Don't put secrets here!
73+ '' ;
74+ } ;
75+
76+ mkServarrSettingsEnvVars = name : settings :
77+ lib . pipe settings [
78+ ( lib . mapAttrsRecursive (
79+ path : value :
80+ lib . optionalAttrs ( value != null ) {
81+ name = lib . toUpper "${ name } __${ lib . concatStringsSep "__" path } " ;
82+ value = toString (
83+ if lib . isBool value
84+ then lib . boolToString value
85+ else value
86+ ) ;
87+ }
88+ ) )
89+ ( lib . collect ( x : lib . isString x . name or false && lib . isString x . value or false ) )
90+ lib . listToAttrs
91+ ] ;
1992in {
2093 options . nixflix . ${ serviceName } =
2194 {
2295 enable = mkEnableOption "${ capitalizedName } " ;
96+ package = lib . mkPackageOption pkgs serviceName { } ;
2397
2498 vpn = {
2599 enable = mkOption {
45119 description = "Group under which the service runs" ;
46120 } ;
47121
122+ openFirewall = lib . mkOption {
123+ type = lib . types . bool ;
124+ default = false ;
125+ description = "Open ports in the firewall for the Radarr web interface." ;
126+ } ;
127+
128+ settings = mkServarrSettingsOptions serviceName ;
129+
48130 config = mkOption {
49131 type = types . submodule {
50132 options =
76158 }
77159 // optionalAttrs usesMediaDirs {
78160 mediaDirs = mkOption {
79- type = types . listOf ( types . submodule {
80- options = {
81- dir = mkOption {
82- type = types . str ;
83- description = "Directory path" ;
84- } ;
85- owner = mkOption {
86- type = types . str ;
87- default = "root" ;
88- description = "Directory owner" ;
89- } ;
90- } ;
91- } ) ;
161+ type = types . listOf types . path ;
92162 default = [ ] ;
93163 description = "List of media directories to create and manage" ;
94164 } ;
@@ -102,76 +172,52 @@ in {
102172 }
103173 ] ;
104174
105- nixflix . ${ serviceName } . config = {
106- apiKeyPath = mkDefault null ;
107- hostConfig = {
108- username = mkDefault serviceName ;
109- passwordPath = mkDefault null ;
110- instanceName = mkDefault capitalizedName ;
111- urlBase = mkDefault (
112- if nixflix . serviceNameIsUrlBase
113- then "/${ serviceName } "
114- else ""
175+ nixflix . ${ serviceName } = {
176+ settings = mkDefault ( {
177+ auth = {
178+ required = "Enabled" ;
179+ method = "Forms" ;
180+ } ;
181+ server = { inherit ( cfg . config . hostConfig ) port urlBase ; } ;
182+ }
183+ // optionalAttrs config . services . postgresql . enable {
184+ log . dbEnabled = true ;
185+ postgres = {
186+ inherit ( cfg ) user ;
187+ host = "/run/postgresql" ;
188+ port = 5432 ;
189+ mainDb = cfg . user ;
190+ logDb = cfg . user ;
191+ } ;
192+ } ) ;
193+ config = {
194+ apiKeyPath = mkDefault null ;
195+ hostConfig = {
196+ username = mkDefault serviceName ;
197+ passwordPath = mkDefault null ;
198+ instanceName = mkDefault capitalizedName ;
199+ urlBase = mkDefault (
200+ if nixflix . serviceNameIsUrlBase
201+ then "/${ serviceName } "
202+ else ""
203+ ) ;
204+ } ;
205+ downloadClients = mkDefault (
206+ optionals ( config . nixflix . sabnzbd . enable or false ) [
207+ {
208+ name = "SABnzbd" ;
209+ implementationName = "SABnzbd" ;
210+ inherit ( config . nixflix . sabnzbd ) apiKeyPath ;
211+ inherit ( config . nixflix . sabnzbd . settings ) host ;
212+ inherit ( config . nixflix . sabnzbd . settings ) port ;
213+ urlBase = config . nixflix . sabnzbd . settings . url_base ;
214+ }
215+ ]
115216 ) ;
116217 } ;
117- downloadClients = mkDefault (
118- optionals ( config . nixflix . sabnzbd . enable or false ) [
119- {
120- name = "SABnzbd" ;
121- implementationName = "SABnzbd" ;
122- inherit ( config . nixflix . sabnzbd ) apiKeyPath ;
123- inherit ( config . nixflix . sabnzbd . settings ) host ;
124- inherit ( config . nixflix . sabnzbd . settings ) port ;
125- urlBase = config . nixflix . sabnzbd . settings . url_base ;
126- }
127- ]
128- ) ;
129218 } ;
130219
131- nixflix . dirRegistrations =
132- [
133- {
134- inherit ( cfg ) group ;
135- dir = stateDir ;
136- owner = cfg . user ;
137- }
138- ]
139- ++ optionals usesMediaDirs ( map ( mediaDir : {
140- inherit ( cfg ) group ;
141- inherit ( mediaDir ) dir owner ;
142- } )
143- cfg . mediaDirs ) ;
144-
145220 services = {
146- ${ serviceName } =
147- {
148- inherit ( cfg ) enable ;
149- dataDir = stateDir ;
150- }
151- // optionalAttrs serviceSupportsUserGroup {
152- inherit ( cfg ) user group ;
153- }
154- // {
155- settings =
156- {
157- auth = {
158- required = "Enabled" ;
159- method = "Forms" ;
160- } ;
161- server = { inherit ( cfg . config . hostConfig ) port urlBase ; } ;
162- }
163- // optionalAttrs config . services . postgresql . enable {
164- log . dbEnabled = true ;
165- postgres = {
166- inherit ( cfg ) user ;
167- host = "/run/postgresql" ;
168- port = 5432 ;
169- mainDb = cfg . user ;
170- logDb = cfg . user ;
171- } ;
172- } ;
173- } ;
174-
175221 postgresql = mkIf config . services . postgresql . enable {
176222 ensureDatabases = [ cfg . user ] ;
177223 ensureUsers = [
@@ -207,13 +253,29 @@ in {
207253 users . ${ cfg . user } =
208254 {
209255 inherit ( cfg ) group ;
256+ home = stateDir ;
210257 isSystemUser = true ;
211258 }
212259 // optionalAttrs ( globals . uids ? ${ cfg . user } ) {
213260 uid = globals . uids . ${ cfg . user } ;
214261 } ;
215262 } ;
216263
264+ networking . firewall = lib . mkIf cfg . openFirewall {
265+ allowedTCPPorts = [ cfg . config . hostConfig . port ] ;
266+ } ;
267+
268+ systemd . tmpfiles = {
269+ settings . "10-${ serviceName } " . ${ stateDir } . d = {
270+ inherit ( cfg ) user group ;
271+ mode = "0700" ;
272+ } ;
273+
274+ rules =
275+ optionals usesMediaDirs ( map ( mediaDir : "d '${ mediaDir } ' 0770 ${ globals . libraryOwner . user } ${ globals . libraryOwner . group } - -" )
276+ cfg . mediaDirs ) ;
277+ } ;
278+
217279 systemd . services =
218280 {
219281 "${ serviceName } -wait-for-db" = mkIf config . services . postgresql . enable {
@@ -243,8 +305,11 @@ in {
243305 } ;
244306
245307 ${ serviceName } = {
308+ description = capitalizedName ;
309+ environment = mkServarrSettingsEnvVars screamingName cfg . settings ;
310+
246311 after =
247- [ "nixflix-setup-dirs.service" ]
312+ [ "network.target" " nixflix-setup-dirs.service"]
248313 ++ ( optional ( cfg . config . apiKeyPath != null && cfg . config . hostConfig . passwordPath != null ) "${ serviceName } -env.service" )
249314 ++ ( optional config . services . postgresql . enable "postgresql-ready.target" )
250315 ++ ( optional config . nixflix . mullvad . enable "mullvad-config.service" ) ;
@@ -253,19 +318,22 @@ in {
253318 ++ ( optional ( cfg . config . apiKeyPath != null && cfg . config . hostConfig . passwordPath != null ) "${ serviceName } -env.service" )
254319 ++ ( optional config . services . postgresql . enable "postgresql-ready.target" ) ;
255320 wants = optional config . nixflix . mullvad . enable "mullvad-config.service" ;
321+ wantedBy = [ "multi-user.target" ] ;
256322
257323 serviceConfig =
258324 {
259- DynamicUser = mkForce false ;
325+ Type = "simple" ;
260326 User = cfg . user ;
261327 Group = cfg . group ;
328+ ExecStart = "${ getExe cfg . package } -nobrowser -data='${ stateDir } '" ;
329+ Restart = "on-failure" ;
262330 }
263331 // optionalAttrs ( cfg . config . apiKeyPath != null && cfg . config . hostConfig . passwordPath != null ) {
264332 EnvironmentFile = "/run/${ serviceName } /env" ;
265333 }
266334 // optionalAttrs ( config . nixflix . mullvad . enable && ! cfg . vpn . enable ) {
267335 ExecStart = mkForce ( pkgs . writeShellScript "${ serviceName } -vpn-bypass" ''
268- exec /run/wrappers/bin/mullvad-exclude ${ getExe config . services . ${ serviceName } . package } \
336+ exec /run/wrappers/bin/mullvad-exclude ${ getExe cfg . package } \
269337 -nobrowser -data='${ stateDir } '
270338 '' ) ;
271339 AmbientCapabilities = "CAP_SYS_ADMIN" ;
0 commit comments