1 { config, pkgs, lib, ... }:
4 cfg = config.services.guix;
6 package = cfg.package.override { inherit (cfg) stateDir storeDir; };
9 name = "guixbuilder${toString id}";
11 extraGroups = [ cfg.group ];
13 description = "Guix build user ${toString id}";
17 guixBuildUsers = numberOfUsers:
18 builtins.listToAttrs (map
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`.
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";
42 # All of the Guix profiles to be used.
43 guixProfiles = lib.attrValues guixUserProfiles;
46 GUIX_LOCPATH = "${cfg.stateDir}/guix/profiles/per-user/root/guix-profile/lib/locale";
51 meta.maintainers = with lib.maintainers; [ foo-dogsquared ];
53 options.services.guix = with lib; {
54 enable = mkEnableOption "Guix build daemon service";
58 default = "guixbuild";
59 example = "guixbuild";
61 The group of the Guix build user pool.
65 nrBuildUsers = mkOption {
66 type = types.ints.unsigned;
68 Number of Guix build users to be used in the build pool.
74 extraArgs = mkOption {
75 type = with types; listOf str;
77 example = [ "--max-jobs=4" "--debug" ];
79 Extra flags to pass to the Guix daemon service.
83 package = mkPackageOption pkgs "guix" {
85 It should contain {command}`guix-daemon` and {command}`guix`
92 default = "/gnu/store";
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.
100 This will also recompile all packages because the normal cache no
106 stateDir = mkOption {
110 The state directory where Guix service will store its data such as its
111 user-specific profiles, cache, and state files.
114 Changing it to something other than the default will rebuild the
118 example = "/gnu/var";
122 enable = mkEnableOption "substitute server for your Guix store directory";
124 generateKeyPair = mkOption {
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`.
141 Port of the substitute server to listen on.
147 default = "guix-publish";
149 Name of the user to change once the server is up.
153 extraArgs = mkOption {
154 type = with types; listOf str;
156 Extra flags to pass to the substitute server.
160 "--compression=zstd:6"
167 enable = mkEnableOption "automatic garbage collection service for Guix";
169 extraArgs = mkOption {
170 type = with types; listOf str;
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).
180 "--delete-generations=1m"
186 dates = lib.mkOption {
191 How often the garbage collection occurs. This takes the time format
192 from {manpage}`systemd.time(7)`.
198 config = lib.mkIf cfg.enable (lib.mkMerge [
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;
216 ${lib.getExe' package "guix-daemon"} \
217 --build-users-group=${cfg.group} \
218 ${lib.escapeShellArgs cfg.extraArgs}
221 OOMPolicy = "continue";
222 RemainAfterExit = "yes";
226 unitConfig.RequiresMountsFor = [
230 wantedBy = [ "multi-user.target" ];
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" ];
244 description = "Guix read-only store directory";
245 before = [ "guix-daemon.service" ];
247 where = cfg.storeDir;
251 unitConfig.DefaultDependencies = false;
252 wantedBy = [ "guix-daemon.service" ];
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
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;
271 [ -d "${userProfile}" ] && ln -sfn "${userProfile}" "${location}"
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';
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.
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"
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
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
304 environment.profiles = lib.mkBefore guixProfiles;
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;
327 ${lib.getExe' package "guix"} publish \
328 --user=${cfg.publish.user} --port=${builtins.toString cfg.publish.port} \
329 ${lib.escapeShellArgs cfg.publish.extraArgs}
337 ProtectHostname = true;
338 ProtectKernelTunables = true;
339 ProtectKernelModules = true;
340 ProtectControlGroups = true;
347 RestrictNamespaces = true;
348 RestrictAddressFamilies = [
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"
365 wantedBy = [ "multi-user.target" ];
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;
373 users.groups.guix-publish = {};
376 (lib.mkIf cfg.gc.enable {
377 # This service should be handled by root to collect all garbage by all
379 systemd.services.guix-gc = {
380 description = "Guix garbage collection";
381 startAt = cfg.gc.dates;
383 ${lib.getExe' package "guix"} gc ${lib.escapeShellArgs cfg.gc.extraArgs}
389 PrivateDevices = true;
390 PrivateNetworks = true;
391 ProtectControlGroups = true;
392 ProtectHostname = true;
393 ProtectKernelTunables = true;
403 systemd.timers.guix-gc.timerConfig.Persistent = true;