@@ -14,12 +14,8 @@ with lib; let
1414 mkArrHostConfigService = import ./hostConfigService.nix { inherit lib pkgs ; } ;
1515 mkArrRootFoldersService = import ./rootFoldersService.nix { inherit lib pkgs ; } ;
1616 capitalizedName = toUpper ( substring 0 1 serviceName ) + substring 1 ( - 1 ) serviceName ;
17- usesDynamicUser = elem serviceName [ "prowlarr" ] ;
1817 usesMediaDirs = ! ( elem serviceName [ "prowlarr" ] ) ;
19- effectiveUser =
20- if usesDynamicUser
21- then serviceName
22- else cfg . user ;
18+ serviceSupportsUserGroup = ! ( elem serviceName [ "prowlarr" ] ) ;
2319in {
2420 options . nixflix . ${ serviceName } =
2521 {
3733 } ;
3834 } ;
3935
36+ user = mkOption {
37+ type = types . str ;
38+ default = serviceName ;
39+ description = "User under which the service runs" ;
40+ } ;
41+
42+ group = mkOption {
43+ type = types . str ;
44+ default = serviceName ;
45+ description = "Group under which the service runs" ;
46+ } ;
47+
4048 config = mkOption {
4149 type =
4250 arrConfigModule
5866 description = "${ capitalizedName } configuration options that will be set via the API." ;
5967 } ;
6068 }
61- // optionalAttrs ( ! usesDynamicUser ) {
62- group = mkOption {
63- type = types . str ;
64- default = serviceName ;
65- description = "Group under which the service runs" ;
66- } ;
67-
68- user = mkOption {
69- type = types . str ;
70- default = serviceName ;
71- description = "User under which the service runs" ;
72- } ;
73- }
7469 // optionalAttrs usesMediaDirs {
7570 mediaDirs = mkOption {
7671 type = types . listOf ( types . submodule {
9287 } ;
9388
9489 config = mkIf ( nixflix . enable && cfg . enable ) {
95- # Assertion: VPN routing requires Mullvad to be enabled
9690 assertions = [
9791 {
9892 assertion = cfg . vpn . enable -> config . nixflix . mullvad . enable ;
9993 message = "Cannot enable VPN routing for ${ capitalizedName } (nixflix.${ serviceName } .vpn.enable = true) when Mullvad VPN is disabled. Please set nixflix.mullvad.enable = true." ;
10094 }
10195 ] ;
10296
103- # Set pattern-based defaults
10497 nixflix . ${ serviceName } . config = {
10598 apiKeyPath = mkDefault null ;
10699 hostConfig = {
@@ -115,23 +108,13 @@ in {
115108 } ;
116109 } ;
117110
118- # Register directories to be created
119111 nixflix . dirRegistrations =
120112 [
121- (
122- if usesDynamicUser
123- then {
124- dir = stateDir ;
125- owner = "root" ;
126- group = "root" ;
127- mode = "0700" ;
128- }
129- else {
130- inherit ( cfg ) group ;
131- dir = stateDir ;
132- owner = cfg . user ;
133- }
134- )
113+ {
114+ inherit ( cfg ) group ;
115+ dir = stateDir ;
116+ owner = cfg . user ;
117+ }
135118 ]
136119 ++ optionals usesMediaDirs ( map ( mediaDir : {
137120 inherit ( cfg ) group ;
@@ -144,6 +127,11 @@ in {
144127 {
145128 inherit ( cfg ) enable ;
146129 dataDir = stateDir ;
130+ }
131+ // optionalAttrs serviceSupportsUserGroup {
132+ inherit ( cfg ) user group ;
133+ }
134+ // {
147135 settings =
148136 {
149137 auth = {
@@ -155,23 +143,20 @@ in {
155143 // optionalAttrs config . services . postgresql . enable {
156144 log . dbEnabled = true ;
157145 postgres = {
158- user = effectiveUser ;
146+ user = cfg . user ;
159147 host = "/run/postgresql" ;
160148 port = 5432 ;
161- mainDb = effectiveUser ;
162- logDb = effectiveUser ;
149+ mainDb = cfg . user ;
150+ logDb = cfg . user ;
163151 } ;
164152 } ;
165- }
166- // optionalAttrs ( ! usesDynamicUser ) {
167- inherit ( cfg ) user group ;
168153 } ;
169154
170155 postgresql = mkIf config . services . postgresql . enable {
171- ensureDatabases = [ effectiveUser ] ;
156+ ensureDatabases = [ cfg . user ] ;
172157 ensureUsers = [
173158 {
174- name = effectiveUser ;
159+ name = cfg . user ;
175160 ensureDBOwnership = true ;
176161 }
177162 ] ;
195180 } ;
196181 } ;
197182
198- users = mkIf ( ! usesDynamicUser ) {
183+ users = {
199184 groups . ${ cfg . group } = optionalAttrs ( globals . gids ? ${ cfg . group } ) {
200185 gid = globals . gids . ${ cfg . group } ;
201186 } ;
@@ -218,30 +203,17 @@ in {
218203 before = [ "postgresql-ready.target" ] ;
219204 requiredBy = [ "postgresql-ready.target" ] ;
220205
221- serviceConfig =
222- {
223- Type = "oneshot" ;
224- RemainAfterExit = true ;
225- TimeoutStartSec = "5min" ;
226- }
227- // optionalAttrs ( ! usesDynamicUser ) {
228- User = cfg . user ;
229- Group = cfg . group ;
230- } ;
206+ serviceConfig = {
207+ Type = "oneshot" ;
208+ RemainAfterExit = true ;
209+ TimeoutStartSec = "5min" ;
210+ User = cfg . user ;
211+ Group = cfg . group ;
212+ } ;
231213
232- script = let
233- dbUser = effectiveUser ;
234- psqlCmd =
235- if usesDynamicUser
236- then "${ pkgs . sudo } /bin/sudo -u postgres ${ pkgs . postgresql } /bin/psql"
237- else "${ pkgs . postgresql } /bin/psql -h /run/postgresql" ;
238- checkCmd =
239- if usesDynamicUser
240- then "SELECT 1 FROM pg_database WHERE datname='${ dbUser } '"
241- else "SELECT 1" ;
242- in ''
214+ script = ''
243215 while true; do
244- if ${ psqlCmd } - d ${ dbUser } -c "${ checkCmd } " > /dev/null 2>&1; then
216+ if ${ pkgs . postgresql } /bin/psql -h /run/postgresql - d ${ cfg . user } -c "SELECT 1 " > /dev/null 2>&1; then
245217 echo "${ capitalizedName } PostgreSQL database is ready"
246218 exit 0
247219 fi
@@ -265,16 +237,29 @@ in {
265237 ++ ( optional ( cfg . config . apiKeyPath != null && cfg . config . hostConfig . passwordPath != null ) "${ serviceName } -env.service" )
266238 ++ ( optional config . services . postgresql . enable "postgresql-ready.target" ) ;
267239 wants = optional config . nixflix . mullvad . enable "mullvad-config.service" ;
268- }
269- // optionalAttrs ( cfg . config . apiKeyPath != null && cfg . config . hostConfig . passwordPath != null ) {
270- serviceConfig . EnvironmentFile = "/run/${ serviceName } /env" ;
271- }
272- // optionalAttrs ( config . nixflix . mullvad . enable && ! cfg . vpn . enable ) {
273- # Bypass VPN by wrapping with mullvad-exclude
274- serviceConfig . ExecStart = mkForce ( pkgs . writeShellScript "${ serviceName } -vpn-bypass" ''
275- exec /run/wrappers/bin/mullvad-exclude ${ getExe config . services . ${ serviceName } . package } \
276- -nobrowser -data='${ stateDir } '
277- '' ) ;
240+
241+ # Always use static users and configure VPN bypass
242+ serviceConfig =
243+ {
244+ # DynamicUser causes issues with VPN bypass and permissions
245+ DynamicUser = mkForce false ;
246+ User = cfg . user ;
247+ Group = cfg . group ;
248+ }
249+ // optionalAttrs ( cfg . config . apiKeyPath != null && cfg . config . hostConfig . passwordPath != null ) {
250+ EnvironmentFile = "/run/${ serviceName } /env" ;
251+ }
252+ // optionalAttrs ( config . nixflix . mullvad . enable && ! cfg . vpn . enable ) {
253+ # Bypass VPN by wrapping with mullvad-exclude
254+ ExecStart = mkForce ( pkgs . writeShellScript "${ serviceName } -vpn-bypass" ''
255+ exec /run/wrappers/bin/mullvad-exclude ${ getExe config . services . ${ serviceName } . package } \
256+ -nobrowser -data='${ stateDir } '
257+ '' ) ;
258+ # mullvad-exclude needs CAP_SYS_ADMIN to manipulate cgroups
259+ AmbientCapabilities = "CAP_SYS_ADMIN" ;
260+ # Delegate allows the service to manage its cgroup subtree
261+ Delegate = mkForce true ;
262+ } ;
278263 } ;
279264 }
280265 # Only create config and rootfolders services if apiKeyPath is configured
@@ -295,12 +280,8 @@ in {
295280 in ''
296281 mkdir -p /run/${ serviceName }
297282 echo "${ envVar } =$(cat ${ cfg . config . apiKeyPath } )" > /run/${ serviceName } /env
298- ${ optionalString ( ! usesDynamicUser ) "chown ${ cfg . user } :${ cfg . group } /run/${ serviceName } /env" }
299- chmod 0${
300- if usesDynamicUser
301- then "444"
302- else "400"
303- } /run/${ serviceName } /env
283+ chown ${ cfg . user } :${ cfg . group } /run/${ serviceName } /env
284+ chmod 0400 /run/${ serviceName } /env
304285 '' ;
305286 } ;
306287
0 commit comments