vuls: init at 0.27.0
[NixPkgs.git] / nixos / modules / services / misc / guix / default.nix
blob7174ff36b7090827e34aadf1d86d6005217c089d
1 { config, pkgs, lib, ... }:
3 let
4   cfg = config.services.guix;
6   package = cfg.package.override { inherit (cfg) stateDir storeDir; };
8   guixBuildUser = id: {
9     name = "guixbuilder${toString id}";
10     group = cfg.group;
11     extraGroups = [ cfg.group ];
12     createHome = false;
13     description = "Guix build user ${toString id}";
14     isSystemUser = true;
15   };
17   guixBuildUsers = numberOfUsers:
18     builtins.listToAttrs (map
19       (user: {
20         name = user.name;
21         value = user;
22       })
23       (builtins.genList guixBuildUser numberOfUsers));
25   # A set of Guix user profiles to be linked at activation. All of these should
26   # be default profiles managed by Guix CLI and the profiles are located in
27   # `${cfg.stateDir}/profiles/per-user/$USER/$PROFILE`.
28   guixUserProfiles = {
29     # The default Guix profile managed by `guix pull`. Take note this should be
30     # the profile with the most precedence in `PATH` env to let users use their
31     # updated versions of `guix` CLI.
32     "current-guix" = "\${XDG_CONFIG_HOME}/guix/current";
34     # The default Guix home profile. This profile contains more than exports
35     # such as an activation script at `$GUIX_HOME_PROFILE/activate`.
36     "guix-home" = "$HOME/.guix-home/profile";
38     # The default Guix profile similar to $HOME/.nix-profile from Nix.
39     "guix-profile" = "$HOME/.guix-profile";
40   };
42   # All of the Guix profiles to be used.
43   guixProfiles = lib.attrValues guixUserProfiles;
45   serviceEnv = {
46     GUIX_LOCPATH = "${cfg.stateDir}/guix/profiles/per-user/root/guix-profile/lib/locale";
47     LC_ALL = "C.UTF-8";
48   };
51   meta.maintainers = with lib.maintainers; [ foo-dogsquared ];
53   options.services.guix = with lib; {
54     enable = mkEnableOption "Guix build daemon service";
56     group = mkOption {
57       type = types.str;
58       default = "guixbuild";
59       example = "guixbuild";
60       description = ''
61         The group of the Guix build user pool.
62       '';
63     };
65     nrBuildUsers = mkOption {
66       type = types.ints.unsigned;
67       description = ''
68         Number of Guix build users to be used in the build pool.
69       '';
70       default = 10;
71       example = 20;
72     };
74     extraArgs = mkOption {
75       type = with types; listOf str;
76       default = [ ];
77       example = [ "--max-jobs=4" "--debug" ];
78       description = ''
79         Extra flags to pass to the Guix daemon service.
80       '';
81     };
83     package = mkPackageOption pkgs "guix" {
84       extraDescription = ''
85         It should contain {command}`guix-daemon` and {command}`guix`
86         executable.
87       '';
88     };
90     storeDir = mkOption {
91       type = types.path;
92       default = "/gnu/store";
93       description = ''
94         The store directory where the Guix service will serve to/from. Take
95         note Guix cannot take advantage of substitutes if you set it something
96         other than {file}`/gnu/store` since most of the cached builds are
97         assumed to be in there.
99         ::: {.warning}
100         This will also recompile all packages because the normal cache no
101         longer applies.
102         :::
103       '';
104     };
106     stateDir = mkOption {
107       type = types.path;
108       default = "/var";
109       description = ''
110         The state directory where Guix service will store its data such as its
111         user-specific profiles, cache, and state files.
113         ::: {.warning}
114         Changing it to something other than the default will rebuild the
115         package.
116         :::
117       '';
118       example = "/gnu/var";
119     };
121     publish = {
122       enable = mkEnableOption "substitute server for your Guix store directory";
124       generateKeyPair = mkOption {
125         type = types.bool;
126         description = ''
127           Whether to generate signing keys in {file}`/etc/guix` which are
128           required to initialize a substitute server. Otherwise,
129           `--public-key=$FILE` and `--private-key=$FILE` can be passed in
130           {option}`services.guix.publish.extraArgs`.
131         '';
132         default = true;
133         example = false;
134       };
136       port = mkOption {
137         type = types.port;
138         default = 8181;
139         example = 8200;
140         description = ''
141           Port of the substitute server to listen on.
142         '';
143       };
145       user = mkOption {
146         type = types.str;
147         default = "guix-publish";
148         description = ''
149           Name of the user to change once the server is up.
150         '';
151       };
153       extraArgs = mkOption {
154         type = with types; listOf str;
155         description = ''
156           Extra flags to pass to the substitute server.
157         '';
158         default = [];
159         example = [
160           "--compression=zstd:6"
161           "--discover=no"
162         ];
163       };
164     };
166     gc = {
167       enable = mkEnableOption "automatic garbage collection service for Guix";
169       extraArgs = mkOption {
170         type = with types; listOf str;
171         default = [ ];
172         description = ''
173           List of arguments to be passed to {command}`guix gc`.
175           When given no option, it will try to collect all garbage which is
176           often inconvenient so it is recommended to set [some
177           options](https://guix.gnu.org/en/manual/en/html_node/Invoking-guix-gc.html).
178         '';
179         example = [
180           "--delete-generations=1m"
181           "--free-space=10G"
182           "--optimize"
183         ];
184       };
186       dates = lib.mkOption {
187         type = types.str;
188         default = "03:15";
189         example = "weekly";
190         description = ''
191           How often the garbage collection occurs. This takes the time format
192           from {manpage}`systemd.time(7)`.
193         '';
194       };
195     };
196   };
198   config = lib.mkIf cfg.enable (lib.mkMerge [
199     {
200       environment.systemPackages = [ package ];
202       users.users = guixBuildUsers cfg.nrBuildUsers;
203       users.groups.${cfg.group} = { };
205       # Guix uses Avahi (through guile-avahi) both for the auto-discovering and
206       # advertising substitute servers in the local network.
207       services.avahi.enable = lib.mkDefault true;
208       services.avahi.publish.enable = lib.mkDefault true;
209       services.avahi.publish.userServices = lib.mkDefault true;
211       # It's similar to Nix daemon so there's no question whether or not this
212       # should be sandboxed.
213       systemd.services.guix-daemon = {
214         environment = serviceEnv;
215         script = ''
216           ${lib.getExe' package "guix-daemon"} \
217             --build-users-group=${cfg.group} \
218             ${lib.escapeShellArgs cfg.extraArgs}
219         '';
220         serviceConfig = {
221           OOMPolicy = "continue";
222           RemainAfterExit = "yes";
223           Restart = "always";
224           TasksMax = 8192;
225         };
226         unitConfig.RequiresMountsFor = [
227           cfg.storeDir
228           cfg.stateDir
229         ];
230         wantedBy = [ "multi-user.target" ];
231       };
233       # This is based from Nix daemon socket unit from upstream Nix package.
234       # Guix build daemon has support for systemd-style socket activation.
235       systemd.sockets.guix-daemon = {
236         description = "Guix daemon socket";
237         before = [ "multi-user.target" ];
238         listenStreams = [ "${cfg.stateDir}/guix/daemon-socket/socket" ];
239         unitConfig.RequiresMountsFor = [ cfg.storeDir cfg.stateDir ];
240         wantedBy = [ "sockets.target" ];
241       };
243       systemd.mounts = [{
244         description = "Guix read-only store directory";
245         before = [ "guix-daemon.service" ];
246         what = cfg.storeDir;
247         where = cfg.storeDir;
248         type = "none";
249         options = "bind,ro";
251         unitConfig.DefaultDependencies = false;
252         wantedBy = [ "guix-daemon.service" ];
253       }];
255       # Make transferring files from one store to another easier with the usual
256       # case being of most substitutes from the official Guix CI instance.
257       system.activationScripts.guix-authorize-keys = ''
258         for official_server_keys in ${package}/share/guix/*.pub; do
259           ${lib.getExe' package "guix"} archive --authorize < $official_server_keys
260         done
261       '';
263       # Link the usual Guix profiles to the home directory. This is useful in
264       # ephemeral setups where only certain part of the filesystem is
265       # persistent (e.g., "Erase my darlings"-type of setup).
266       system.userActivationScripts.guix-activate-user-profiles.text = let
267         guixProfile = profile: "${cfg.stateDir}/guix/profiles/per-user/\${USER}/${profile}";
268         linkProfile = profile: location: let
269           userProfile = guixProfile profile;
270         in ''
271           [ -d "${userProfile}" ] && ln -sfn "${userProfile}" "${location}"
272         '';
273         linkProfileToPath = acc: profile: location: let
274           in acc + (linkProfile profile location);
276         # This should contain export-only Guix user profiles. The rest of it is
277         # handled manually in the activation script.
278         guixUserProfiles' = lib.attrsets.removeAttrs guixUserProfiles [ "guix-home" ];
280         linkExportsScript = lib.foldlAttrs linkProfileToPath "" guixUserProfiles';
281       in ''
282         # Don't export this please! It is only expected to be used for this
283         # activation script and nothing else.
284         XDG_CONFIG_HOME=''${XDG_CONFIG_HOME:-$HOME/.config}
286         # Linking the usual Guix profiles into the home directory.
287         ${linkExportsScript}
289         # Activate all of the default Guix non-exports profiles manually.
290         ${linkProfile "guix-home" "$HOME/.guix-home"}
291         [ -L "$HOME/.guix-home" ] && "$HOME/.guix-home/activate"
292       '';
294       # GUIX_LOCPATH is basically LOCPATH but for Guix libc which in turn used by
295       # virtually every Guix-built packages. This is so that Guix-installed
296       # applications wouldn't use incompatible locale data and not touch its host
297       # system.
298       environment.sessionVariables.GUIX_LOCPATH = lib.makeSearchPath "lib/locale" guixProfiles;
300       # What Guix profiles export is very similar to Nix profiles so it is
301       # acceptable to list it here. Also, it is more likely that the user would
302       # want to use packages explicitly installed from Guix so we're putting it
303       # first.
304       environment.profiles = lib.mkBefore guixProfiles;
305     }
307     (lib.mkIf cfg.publish.enable {
308       systemd.services.guix-publish = {
309         description = "Guix remote store";
310         environment = serviceEnv;
312         # Mounts will be required by the daemon service anyways so there's no
313         # need add RequiresMountsFor= or something similar.
314         requires = [ "guix-daemon.service" ];
315         after = [ "guix-daemon.service" ];
316         partOf = [ "guix-daemon.service" ];
318         preStart = lib.mkIf cfg.publish.generateKeyPair ''
319           # Generate the keypair if it's missing.
320           [ -f "/etc/guix/signing-key.sec" ] && [ -f "/etc/guix/signing-key.pub" ] || \
321             ${lib.getExe' package "guix"} archive --generate-key || {
322               rm /etc/guix/signing-key.*;
323               ${lib.getExe' package "guix"} archive --generate-key;
324             }
325         '';
326         script = ''
327           ${lib.getExe' package "guix"} publish \
328             --user=${cfg.publish.user} --port=${builtins.toString cfg.publish.port} \
329             ${lib.escapeShellArgs cfg.publish.extraArgs}
330         '';
332         serviceConfig = {
333           Restart = "always";
334           RestartSec = 10;
336           ProtectClock = true;
337           ProtectHostname = true;
338           ProtectKernelTunables = true;
339           ProtectKernelModules = true;
340           ProtectControlGroups = true;
341           SystemCallFilter = [
342             "@system-service"
343             "@debug"
344             "@setuid"
345           ];
347           RestrictNamespaces = true;
348           RestrictAddressFamilies = [
349             "AF_UNIX"
350             "AF_INET"
351             "AF_INET6"
352           ];
354           # While the permissions can be set, it is assumed to be taken by Guix
355           # daemon service which it has already done the setup.
356           ConfigurationDirectory = "guix";
358           AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
359           CapabilityBoundingSet = [
360             "CAP_NET_BIND_SERVICE"
361             "CAP_SETUID"
362             "CAP_SETGID"
363           ];
364         };
365         wantedBy = [ "multi-user.target" ];
366       };
368       users.users.guix-publish = lib.mkIf (cfg.publish.user == "guix-publish") {
369         description = "Guix publish user";
370         group = config.users.groups.guix-publish.name;
371         isSystemUser = true;
372       };
373       users.groups.guix-publish = {};
374     })
376     (lib.mkIf cfg.gc.enable {
377       # This service should be handled by root to collect all garbage by all
378       # users.
379       systemd.services.guix-gc = {
380         description = "Guix garbage collection";
381         startAt = cfg.gc.dates;
382         script = ''
383           ${lib.getExe' package "guix"} gc ${lib.escapeShellArgs cfg.gc.extraArgs}
384         '';
386         serviceConfig = {
387           Type = "oneshot";
389           PrivateDevices = true;
390           PrivateNetworks = true;
391           ProtectControlGroups = true;
392           ProtectHostname = true;
393           ProtectKernelTunables = true;
394           SystemCallFilter = [
395             "@default"
396             "@file-system"
397             "@basic-io"
398             "@system-service"
399           ];
400         };
401       };
403       systemd.timers.guix-gc.timerConfig.Persistent = true;
404     })
405   ]);