1{ config, lib, pkgs, utils, ... }:
2with utils;
3with systemdUtils.unitOptions;
4with lib;
5
6let
7 cfg = config.systemd.user;
8
9 systemd = config.systemd.package;
10
11 inherit
12 (systemdUtils.lib)
13 makeUnit
14 generateUnits
15 targetToUnit
16 serviceToUnit
17 sliceToUnit
18 socketToUnit
19 timerToUnit
20 pathToUnit;
21
22 upstreamUserUnits = [
23 "app.slice"
24 "background.slice"
25 "basic.target"
26 "bluetooth.target"
27 "default.target"
28 "exit.target"
29 "graphical-session-pre.target"
30 "graphical-session.target"
31 "paths.target"
32 "printer.target"
33 "session.slice"
34 "shutdown.target"
35 "smartcard.target"
36 "sockets.target"
37 "sound.target"
38 "systemd-exit.service"
39 "timers.target"
40 "xdg-desktop-autostart.target"
41 ] ++ config.systemd.additionalUpstreamUserUnits;
42
43 writeTmpfiles = { rules, user ? null }:
44 let
45 suffix = optionalString (user != null) "-${user}";
46 in
47 pkgs.writeTextFile {
48 name = "nixos-user-tmpfiles.d${suffix}";
49 destination = "/etc/xdg/user-tmpfiles.d/00-nixos${suffix}.conf";
50 text = ''
51 # This file is created automatically and should not be modified.
52 # Please change the options ‘systemd.user.tmpfiles’ instead.
53 ${concatStringsSep "\n" rules}
54 '';
55 };
56in {
57 options = {
58 systemd.user.extraConfig = mkOption {
59 default = "";
60 type = types.lines;
61 example = "DefaultCPUAccounting=yes";
62 description = ''
63 Extra config options for systemd user instances. See {manpage}`systemd-user.conf(5)` for
64 available options.
65 '';
66 };
67
68 systemd.user.units = mkOption {
69 description = "Definition of systemd per-user units.";
70 default = {};
71 type = systemdUtils.types.units;
72 };
73
74 systemd.user.paths = mkOption {
75 default = {};
76 type = systemdUtils.types.paths;
77 description = "Definition of systemd per-user path units.";
78 };
79
80 systemd.user.services = mkOption {
81 default = {};
82 type = systemdUtils.types.services;
83 description = "Definition of systemd per-user service units.";
84 };
85
86 systemd.user.slices = mkOption {
87 default = {};
88 type = systemdUtils.types.slices;
89 description = "Definition of systemd per-user slice units.";
90 };
91
92 systemd.user.sockets = mkOption {
93 default = {};
94 type = systemdUtils.types.sockets;
95 description = "Definition of systemd per-user socket units.";
96 };
97
98 systemd.user.targets = mkOption {
99 default = {};
100 type = systemdUtils.types.targets;
101 description = "Definition of systemd per-user target units.";
102 };
103
104 systemd.user.timers = mkOption {
105 default = {};
106 type = systemdUtils.types.timers;
107 description = "Definition of systemd per-user timer units.";
108 };
109
110 systemd.user.tmpfiles = {
111 rules = mkOption {
112 type = types.listOf types.str;
113 default = [];
114 example = [ "D %C - - - 7d" ];
115 description = ''
116 Global user rules for creation, deletion and cleaning of volatile and
117 temporary files automatically. See
118 {manpage}`tmpfiles.d(5)`
119 for the exact format.
120 '';
121 };
122
123 users = mkOption {
124 description = ''
125 Per-user rules for creation, deletion and cleaning of volatile and
126 temporary files automatically.
127 '';
128 default = {};
129 type = types.attrsOf (types.submodule {
130 options = {
131 rules = mkOption {
132 type = types.listOf types.str;
133 default = [];
134 example = [ "D %C - - - 7d" ];
135 description = ''
136 Per-user rules for creation, deletion and cleaning of volatile and
137 temporary files automatically. See
138 {manpage}`tmpfiles.d(5)`
139 for the exact format.
140 '';
141 };
142 };
143 });
144 };
145 };
146
147 systemd.additionalUpstreamUserUnits = mkOption {
148 default = [];
149 type = types.listOf types.str;
150 example = [];
151 description = ''
152 Additional units shipped with systemd that should be enabled for per-user systemd instances.
153 '';
154 internal = true;
155 };
156 };
157
158 config = {
159 systemd.additionalUpstreamSystemUnits = [
160 "user.slice"
161 ];
162
163 environment.etc = {
164 "systemd/user".source = generateUnits {
165 type = "user";
166 inherit (cfg) units;
167 upstreamUnits = upstreamUserUnits;
168 upstreamWants = [];
169 };
170
171 "systemd/user.conf".text = ''
172 [Manager]
173 ${cfg.extraConfig}
174 '';
175 };
176
177 systemd.user.units =
178 mapAttrs' (n: v: nameValuePair "${n}.path" (pathToUnit v)) cfg.paths
179 // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit v)) cfg.services
180 // mapAttrs' (n: v: nameValuePair "${n}.slice" (sliceToUnit v)) cfg.slices
181 // mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit v)) cfg.sockets
182 // mapAttrs' (n: v: nameValuePair "${n}.target" (targetToUnit v)) cfg.targets
183 // mapAttrs' (n: v: nameValuePair "${n}.timer" (timerToUnit v)) cfg.timers;
184
185 # Generate timer units for all services that have a ‘startAt’ value.
186 systemd.user.timers =
187 mapAttrs (name: service: {
188 wantedBy = ["timers.target"];
189 timerConfig.OnCalendar = service.startAt;
190 })
191 (filterAttrs (name: service: service.startAt != []) cfg.services);
192
193 # Provide the systemd-user PAM service, required to run systemd
194 # user instances.
195 security.pam.services.systemd-user =
196 { # Ensure that pam_systemd gets included. This is special-cased
197 # in systemd to provide XDG_RUNTIME_DIR.
198 startSession = true;
199 # Disable pam_mount in systemd-user to prevent it from being called
200 # multiple times during login, because it will prevent pam_mount from
201 # unmounting the previously mounted volumes.
202 pamMount = false;
203 };
204
205 # Some overrides to upstream units.
206 systemd.services."user@".restartIfChanged = false;
207 systemd.services.systemd-user-sessions.restartIfChanged = false; # Restart kills all active sessions.
208
209 # enable systemd user tmpfiles
210 systemd.user.services.systemd-tmpfiles-setup.wantedBy =
211 optional
212 (cfg.tmpfiles.rules != [] || any (cfg': cfg'.rules != []) (attrValues cfg.tmpfiles.users))
213 "basic.target";
214
215 # /run/current-system/sw/etc/xdg is in systemd's $XDG_CONFIG_DIRS so we can
216 # write the tmpfiles.d rules for everyone there
217 environment.systemPackages =
218 optional
219 (cfg.tmpfiles.rules != [])
220 (writeTmpfiles { inherit (cfg.tmpfiles) rules; });
221
222 # /etc/profiles/per-user/$USER/etc/xdg is in systemd's $XDG_CONFIG_DIRS so
223 # we can write a single user's tmpfiles.d rules there
224 users.users =
225 mapAttrs
226 (user: cfg': {
227 packages = optional (cfg'.rules != []) (writeTmpfiles {
228 inherit (cfg') rules;
229 inherit user;
230 });
231 })
232 cfg.tmpfiles.users;
233
234 system.userActivationScripts.tmpfiles = ''
235 ${config.systemd.package}/bin/systemd-tmpfiles --user --create --remove
236 '';
237 };
238}