···
1
+
{ config, pkgs, lib, ... }:
4
+
cfg = config.services.guix;
6
+
package = cfg.package.override { inherit (cfg) stateDir storeDir; };
8
+
guixBuildUser = id: {
9
+
name = "guixbuilder${toString id}";
11
+
extraGroups = [ cfg.group ];
13
+
description = "Guix build user ${toString id}";
14
+
isSystemUser = true;
17
+
guixBuildUsers = numberOfUsers:
18
+
builtins.listToAttrs (map
23
+
(builtins.genList guixBuildUser numberOfUsers));
25
+
# A set of Guix user profiles to be linked at activation.
26
+
guixUserProfiles = {
27
+
# The current Guix profile that is created through `guix pull`.
28
+
"current-guix" = "\${XDG_CONFIG_HOME}/guix/current";
30
+
# The default Guix profile similar to $HOME/.nix-profile from Nix.
31
+
"guix-profile" = "$HOME/.guix-profile";
34
+
# All of the Guix profiles to be used.
35
+
guixProfiles = lib.attrValues guixUserProfiles;
38
+
GUIX_LOCPATH = "${cfg.stateDir}/guix/profiles/per-user/root/guix-profile/lib/locale";
43
+
meta.maintainers = with lib.maintainers; [ foo-dogsquared ];
45
+
options.services.guix = with lib; {
46
+
enable = mkEnableOption "Guix build daemon service";
50
+
default = "guixbuild";
51
+
example = "guixbuild";
53
+
The group of the Guix build user pool.
57
+
nrBuildUsers = mkOption {
58
+
type = types.ints.unsigned;
60
+
Number of Guix build users to be used in the build pool.
66
+
extraArgs = mkOption {
67
+
type = with types; listOf str;
69
+
example = [ "--max-jobs=4" "--debug" ];
71
+
Extra flags to pass to the Guix daemon service.
75
+
package = mkPackageOption pkgs "guix" {
76
+
extraDescription = ''
77
+
It should contain {command}`guix-daemon` and {command}`guix`
82
+
storeDir = mkOption {
84
+
default = "/gnu/store";
86
+
The store directory where the Guix service will serve to/from. Take
87
+
note Guix cannot take advantage of substitutes if you set it something
88
+
other than {file}`/gnu/store` since most of the cached builds are
89
+
assumed to be in there.
92
+
This will also recompile all packages because the normal cache no
98
+
stateDir = mkOption {
102
+
The state directory where Guix service will store its data such as its
103
+
user-specific profiles, cache, and state files.
106
+
Changing it to something other than the default will rebuild the
110
+
example = "/gnu/var";
114
+
enable = mkEnableOption "substitute server for your Guix store directory";
116
+
generateKeyPair = mkOption {
119
+
Whether to generate signing keys in {file}`/etc/guix` which are
120
+
required to initialize a substitute server. Otherwise,
121
+
`--public-key=$FILE` and `--private-key=$FILE` can be passed in
122
+
{option}`services.guix.publish.extraArgs`.
133
+
Port of the substitute server to listen on.
139
+
default = "guix-publish";
141
+
Name of the user to change once the server is up.
145
+
extraArgs = mkOption {
146
+
type = with types; listOf str;
148
+
Extra flags to pass to the substitute server.
152
+
"--compression=zstd:6"
159
+
enable = mkEnableOption "automatic garbage collection service for Guix";
161
+
extraArgs = mkOption {
162
+
type = with types; listOf str;
165
+
List of arguments to be passed to {command}`guix gc`.
167
+
When given no option, it will try to collect all garbage which is
168
+
often inconvenient so it is recommended to set [some
169
+
options](https://guix.gnu.org/en/manual/en/html_node/Invoking-guix-gc.html).
172
+
"--delete-generations=1m"
178
+
dates = lib.mkOption {
181
+
example = "weekly";
183
+
How often the garbage collection occurs. This takes the time format
184
+
from {manpage}`systemd.time(7)`.
190
+
config = lib.mkIf cfg.enable (lib.mkMerge [
192
+
environment.systemPackages = [ package ];
194
+
users.users = guixBuildUsers cfg.nrBuildUsers;
195
+
users.groups.${cfg.group} = { };
197
+
# Guix uses Avahi (through guile-avahi) both for the auto-discovering and
198
+
# advertising substitute servers in the local network.
199
+
services.avahi.enable = lib.mkDefault true;
200
+
services.avahi.publish.enable = lib.mkDefault true;
201
+
services.avahi.publish.userServices = lib.mkDefault true;
203
+
# It's similar to Nix daemon so there's no question whether or not this
204
+
# should be sandboxed.
205
+
systemd.services.guix-daemon = {
206
+
environment = serviceEnv;
208
+
${lib.getExe' package "guix-daemon"} \
209
+
--build-users-group=${cfg.group} \
210
+
${lib.escapeShellArgs cfg.extraArgs}
213
+
OOMPolicy = "continue";
214
+
RemainAfterExit = "yes";
215
+
Restart = "always";
218
+
unitConfig.RequiresMountsFor = [
222
+
wantedBy = [ "multi-user.target" ];
225
+
# This is based from Nix daemon socket unit from upstream Nix package.
226
+
# Guix build daemon has support for systemd-style socket activation.
227
+
systemd.sockets.guix-daemon = {
228
+
description = "Guix daemon socket";
229
+
before = [ "multi-user.target" ];
230
+
listenStreams = [ "${cfg.stateDir}/guix/daemon-socket/socket" ];
232
+
RequiresMountsFor = [
236
+
ConditionPathIsReadWrite = "${cfg.stateDir}/guix/daemon-socket";
238
+
wantedBy = [ "socket.target" ];
241
+
systemd.mounts = [{
242
+
description = "Guix read-only store directory";
243
+
before = [ "guix-daemon.service" ];
244
+
what = cfg.storeDir;
245
+
where = cfg.storeDir;
247
+
options = "bind,ro";
249
+
unitConfig.DefaultDependencies = false;
250
+
wantedBy = [ "guix-daemon.service" ];
253
+
# Make transferring files from one store to another easier with the usual
254
+
# case being of most substitutes from the official Guix CI instance.
255
+
system.activationScripts.guix-authorize-keys = ''
256
+
for official_server_keys in ${package}/share/guix/*.pub; do
257
+
${lib.getExe' package "guix"} archive --authorize < $official_server_keys
261
+
# Link the usual Guix profiles to the home directory. This is useful in
262
+
# ephemeral setups where only certain part of the filesystem is
263
+
# persistent (e.g., "Erase my darlings"-type of setup).
264
+
system.userActivationScripts.guix-activate-user-profiles.text = let
265
+
linkProfileToPath = acc: profile: location: let
266
+
guixProfile = "${cfg.stateDir}/guix/profiles/per-user/\${USER}/${profile}";
268
+
[ -d "${guixProfile}" ] && ln -sf "${guixProfile}" "${location}"
271
+
activationScript = lib.foldlAttrs linkProfileToPath "" guixUserProfiles;
273
+
# Don't export this please! It is only expected to be used for this
274
+
# activation script and nothing else.
275
+
XDG_CONFIG_HOME=''${XDG_CONFIG_HOME:-$HOME/.config}
277
+
# Linking the usual Guix profiles into the home directory.
278
+
${activationScript}
281
+
# GUIX_LOCPATH is basically LOCPATH but for Guix libc which in turn used by
282
+
# virtually every Guix-built packages. This is so that Guix-installed
283
+
# applications wouldn't use incompatible locale data and not touch its host
285
+
environment.sessionVariables.GUIX_LOCPATH = lib.makeSearchPath "lib/locale" guixProfiles;
287
+
# What Guix profiles export is very similar to Nix profiles so it is
288
+
# acceptable to list it here. Also, it is more likely that the user would
289
+
# want to use packages explicitly installed from Guix so we're putting it
291
+
environment.profiles = lib.mkBefore guixProfiles;
294
+
(lib.mkIf cfg.publish.enable {
295
+
systemd.services.guix-publish = {
296
+
description = "Guix remote store";
297
+
environment = serviceEnv;
299
+
# Mounts will be required by the daemon service anyways so there's no
300
+
# need add RequiresMountsFor= or something similar.
301
+
requires = [ "guix-daemon.service" ];
302
+
after = [ "guix-daemon.service" ];
303
+
partOf = [ "guix-daemon.service" ];
305
+
preStart = lib.mkIf cfg.publish.generateKeyPair ''
306
+
# Generate the keypair if it's missing.
307
+
[ -f "/etc/guix/signing-key.sec" ] && [ -f "/etc/guix/signing-key.pub" ] || \
308
+
${lib.getExe' package "guix"} archive --generate-key || {
309
+
rm /etc/guix/signing-key.*;
310
+
${lib.getExe' package "guix"} archive --generate-key;
314
+
${lib.getExe' package "guix"} publish \
315
+
--user=${cfg.publish.user} --port=${builtins.toString cfg.publish.port} \
316
+
${lib.escapeShellArgs cfg.publish.extraArgs}
320
+
Restart = "always";
323
+
ProtectClock = true;
324
+
ProtectHostname = true;
325
+
ProtectKernelTunables = true;
326
+
ProtectKernelModules = true;
327
+
ProtectControlGroups = true;
328
+
SystemCallFilter = [
334
+
RestrictNamespaces = true;
335
+
RestrictAddressFamilies = [
341
+
# While the permissions can be set, it is assumed to be taken by Guix
342
+
# daemon service which it has already done the setup.
343
+
ConfigurationDirectory = "guix";
345
+
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
346
+
CapabilityBoundingSet = [
347
+
"CAP_NET_BIND_SERVICE"
352
+
wantedBy = [ "multi-user.target" ];
355
+
users.users.guix-publish = lib.mkIf (cfg.publish.user == "guix-publish") {
356
+
description = "Guix publish user";
357
+
group = config.users.groups.guix-publish.name;
358
+
isSystemUser = true;
360
+
users.groups.guix-publish = {};
363
+
(lib.mkIf cfg.gc.enable {
364
+
# This service should be handled by root to collect all garbage by all
366
+
systemd.services.guix-gc = {
367
+
description = "Guix garbage collection";
368
+
startAt = cfg.gc.dates;
370
+
${lib.getExe' package "guix"} gc ${lib.escapeShellArgs cfg.gc.extraArgs}
376
+
MemoryDenyWriteExecute = true;
377
+
PrivateDevices = true;
378
+
PrivateNetworks = true;
379
+
ProtectControlGroups = true;
380
+
ProtectHostname = true;
381
+
ProtectKernelTunables = true;
382
+
SystemCallFilter = [
391
+
systemd.timers.guix-gc.timerConfig.Persistent = true;