grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / network-filesystems / kubo.nix
blob35efd2ba0a576a07912b6f84c46aaa6373bbaba2
1 { config, lib, pkgs, utils, ... }:
2 let
3   cfg = config.services.kubo;
5   settingsFormat = pkgs.formats.json {};
7   rawDefaultConfig = lib.importJSON (pkgs.runCommand "kubo-default-config" {
8     nativeBuildInputs = [ cfg.package ];
9   } ''
10     export IPFS_PATH="$TMPDIR"
11     ipfs init --empty-repo --profile=${profile}
12     ipfs --offline config show > "$out"
13   '');
15   # Remove the PeerID (an attribute of "Identity") of the temporary Kubo repo.
16   # The "Pinning" section contains the "RemoteServices" section, which would prevent
17   # the daemon from starting as that setting can't be changed via ipfs config replace.
18   defaultConfig = builtins.removeAttrs rawDefaultConfig [ "Identity" "Pinning" ];
20   customizedConfig = lib.recursiveUpdate defaultConfig cfg.settings;
22   configFile = settingsFormat.generate "kubo-config.json" customizedConfig;
24   # Create a fake repo containing only the file "api".
25   # $IPFS_PATH will point to this directory instead of the real one.
26   # For some reason the Kubo CLI tools insist on reading the
27   # config file when it exists. But the Kubo daemon sets the file
28   # permissions such that only the ipfs user is allowed to read
29   # this file. This prevents normal users from talking to the daemon.
30   # To work around this terrible design, create a fake repo with no
31   # config file, only an api file and everything should work as expected.
32   fakeKuboRepo = pkgs.writeTextDir "api" ''
33     /unix/run/ipfs.sock
34   '';
36   kuboFlags = utils.escapeSystemdExecArgs (
37     lib.optional cfg.autoMount "--mount" ++
38     lib.optional cfg.enableGC "--enable-gc" ++
39     lib.optional (cfg.serviceFdlimit != null) "--manage-fdlimit=false" ++
40     lib.optional (cfg.defaultMode == "offline") "--offline" ++
41     lib.optional (cfg.defaultMode == "norouting") "--routing=none" ++
42     cfg.extraFlags
43   );
45   profile =
46     if cfg.localDiscovery
47     then "local-discovery"
48     else "server";
50   splitMulitaddr = addrRaw: lib.tail (lib.splitString "/" addrRaw);
52   multiaddrsToListenStreams = addrIn:
53     let
54       addrs = if builtins.isList addrIn
55       then addrIn else [ addrIn ];
56       unfilteredResult = map multiaddrToListenStream addrs;
57     in
58       builtins.filter (addr: addr != null) unfilteredResult;
60   multiaddrsToListenDatagrams = addrIn:
61     let
62       addrs = if builtins.isList addrIn
63       then addrIn else [ addrIn ];
64       unfilteredResult = map multiaddrToListenDatagram addrs;
65     in
66       builtins.filter (addr: addr != null) unfilteredResult;
68   multiaddrToListenStream = addrRaw:
69     let
70       addr = splitMulitaddr addrRaw;
71       s = builtins.elemAt addr;
72     in
73     if s 0 == "ip4" && s 2 == "tcp"
74     then "${s 1}:${s 3}"
75     else if s 0 == "ip6" && s 2 == "tcp"
76     then "[${s 1}]:${s 3}"
77     else if s 0 == "unix"
78     then "/${lib.concatStringsSep "/" (lib.tail addr)}"
79     else null; # not valid for listen stream, skip
81   multiaddrToListenDatagram = addrRaw:
82     let
83       addr = splitMulitaddr addrRaw;
84       s = builtins.elemAt addr;
85     in
86     if s 0 == "ip4" && s 2 == "udp"
87     then "${s 1}:${s 3}"
88     else if s 0 == "ip6" && s 2 == "udp"
89     then "[${s 1}]:${s 3}"
90     else null; # not valid for listen datagram, skip
95   ###### interface
97   options = {
99     services.kubo = {
101       enable = lib.mkEnableOption ''
102         the Interplanetary File System (WARNING: may cause severe network degradation).
103         NOTE: after enabling this option and rebuilding your system, you need to log out
104         and back in for the `IPFS_PATH` environment variable to be present in your shell.
105         Until you do that, the CLI tools won't be able to talk to the daemon by default
106       '';
108       package = lib.mkPackageOption pkgs "kubo" { };
110       user = lib.mkOption {
111         type = lib.types.str;
112         default = "ipfs";
113         description = "User under which the Kubo daemon runs";
114       };
116       group = lib.mkOption {
117         type = lib.types.str;
118         default = "ipfs";
119         description = "Group under which the Kubo daemon runs";
120       };
122       dataDir = lib.mkOption {
123         type = lib.types.str;
124         default =
125           if lib.versionAtLeast config.system.stateVersion "17.09"
126           then "/var/lib/ipfs"
127           else "/var/lib/ipfs/.ipfs";
128         defaultText = lib.literalExpression ''
129           if lib.versionAtLeast config.system.stateVersion "17.09"
130           then "/var/lib/ipfs"
131           else "/var/lib/ipfs/.ipfs"
132         '';
133         description = "The data dir for Kubo";
134       };
136       defaultMode = lib.mkOption {
137         type = lib.types.enum [ "online" "offline" "norouting" ];
138         default = "online";
139         description = "systemd service that is enabled by default";
140       };
142       autoMount = lib.mkOption {
143         type = lib.types.bool;
144         default = false;
145         description = "Whether Kubo should try to mount /ipfs and /ipns at startup.";
146       };
148       autoMigrate = lib.mkOption {
149         type = lib.types.bool;
150         default = true;
151         description = "Whether Kubo should try to run the fs-repo-migration at startup.";
152       };
154       enableGC = lib.mkOption {
155         type = lib.types.bool;
156         default = false;
157         description = "Whether to enable automatic garbage collection";
158       };
160       emptyRepo = lib.mkOption {
161         type = lib.types.bool;
162         default = true;
163         description = "If set to false, the repo will be initialized with help files";
164       };
166       settings = lib.mkOption {
167         type = lib.types.submodule {
168           freeformType = settingsFormat.type;
170           options = {
171             Addresses.API = lib.mkOption {
172               type = lib.types.oneOf [ lib.types.str (lib.types.listOf lib.types.str) ];
173               default = [ ];
174               description = ''
175                 Multiaddr or array of multiaddrs describing the address to serve the local HTTP API on.
176                 In addition to the multiaddrs listed here, the daemon will also listen on a Unix domain socket.
177                 To allow the ipfs CLI tools to communicate with the daemon over that socket,
178                 add your user to the correct group, e.g. `users.users.alice.extraGroups = [ config.services.kubo.group ];`
179               '';
180             };
182             Addresses.Gateway = lib.mkOption {
183               type = lib.types.oneOf [ lib.types.str (lib.types.listOf lib.types.str) ];
184               default = "/ip4/127.0.0.1/tcp/8080";
185               description = "Where the IPFS Gateway can be reached";
186             };
188             Addresses.Swarm = lib.mkOption {
189               type = lib.types.listOf lib.types.str;
190               default = [
191                 "/ip4/0.0.0.0/tcp/4001"
192                 "/ip6/::/tcp/4001"
193                 "/ip4/0.0.0.0/udp/4001/quic-v1"
194                 "/ip4/0.0.0.0/udp/4001/quic-v1/webtransport"
195                 "/ip6/::/udp/4001/quic-v1"
196                 "/ip6/::/udp/4001/quic-v1/webtransport"
197               ];
198               description = "Where Kubo listens for incoming p2p connections";
199             };
201             Mounts.IPFS = lib.mkOption {
202               type = lib.types.str;
203               default = "/ipfs";
204               description = "Where to mount the IPFS namespace to";
205             };
207             Mounts.IPNS = lib.mkOption {
208               type = lib.types.str;
209               default = "/ipns";
210               description = "Where to mount the IPNS namespace to";
211             };
212           };
213         };
214         description = ''
215           Attrset of daemon configuration.
216           See [https://github.com/ipfs/kubo/blob/master/docs/config.md](https://github.com/ipfs/kubo/blob/master/docs/config.md) for reference.
217           You can't set `Identity` or `Pinning`.
218         '';
219         default = { };
220         example = {
221           Datastore.StorageMax = "100GB";
222           Discovery.MDNS.Enabled = false;
223           Bootstrap = [
224             "/ip4/128.199.219.111/tcp/4001/ipfs/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu"
225             "/ip4/162.243.248.213/tcp/4001/ipfs/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm"
226           ];
227           Swarm.AddrFilters = null;
228         };
230       };
232       extraFlags = lib.mkOption {
233         type = lib.types.listOf lib.types.str;
234         description = "Extra flags passed to the Kubo daemon";
235         default = [ ];
236       };
238       localDiscovery = lib.mkOption {
239         type = lib.types.bool;
240         description = ''Whether to enable local discovery for the Kubo daemon.
241           This will allow Kubo to scan ports on your local network. Some hosting services will ban you if you do this.
242         '';
243         default = false;
244       };
246       serviceFdlimit = lib.mkOption {
247         type = lib.types.nullOr lib.types.int;
248         default = null;
249         description = "The fdlimit for the Kubo systemd unit or `null` to have the daemon attempt to manage it";
250         example = 64 * 1024;
251       };
253       startWhenNeeded = lib.mkOption {
254         type = lib.types.bool;
255         default = false;
256         description = "Whether to use socket activation to start Kubo when needed.";
257       };
259     };
260   };
262   ###### implementation
264   config = lib.mkIf cfg.enable {
265     assertions = [
266       {
267         assertion = !builtins.hasAttr "Identity" cfg.settings;
268         message = ''
269           You can't set services.kubo.settings.Identity because the ``config replace`` subcommand used at startup does not support modifying any of the Identity settings.
270         '';
271       }
272       {
273         assertion = !((builtins.hasAttr "Pinning" cfg.settings) && (builtins.hasAttr "RemoteServices" cfg.settings.Pinning));
274         message = ''
275           You can't set services.kubo.settings.Pinning.RemoteServices because the ``config replace`` subcommand used at startup does not work with it.
276         '';
277       }
278       {
279         assertion = !((lib.versionAtLeast cfg.package.version "0.21") && (builtins.hasAttr "Experimental" cfg.settings) && (builtins.hasAttr "AcceleratedDHTClient" cfg.settings.Experimental));
280         message = ''
281           The `services.kubo.settings.Experimental.AcceleratedDHTClient` option was renamed to `services.kubo.settings.Routing.AcceleratedDHTClient` in Kubo 0.21.
282         '';
283       }
284     ];
286     environment.systemPackages = [ cfg.package ];
287     environment.variables.IPFS_PATH = fakeKuboRepo;
289     # https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes
290     boot.kernel.sysctl."net.core.rmem_max" = lib.mkDefault 2500000;
291     boot.kernel.sysctl."net.core.wmem_max" = lib.mkDefault 2500000;
293     programs.fuse = lib.mkIf cfg.autoMount {
294       userAllowOther = true;
295     };
297     users.users = lib.mkIf (cfg.user == "ipfs") {
298       ipfs = {
299         group = cfg.group;
300         home = cfg.dataDir;
301         createHome = false;
302         uid = config.ids.uids.ipfs;
303         description = "IPFS daemon user";
304         packages = [
305           pkgs.kubo-migrator
306         ];
307       };
308     };
310     users.groups = lib.mkIf (cfg.group == "ipfs") {
311       ipfs.gid = config.ids.gids.ipfs;
312     };
314     systemd.tmpfiles.settings."10-kubo" = let
315       defaultConfig = { inherit (cfg) user group; };
316     in {
317       ${cfg.dataDir}.d = defaultConfig;
318       ${cfg.settings.Mounts.IPFS}.d = lib.mkIf (cfg.autoMount) defaultConfig;
319       ${cfg.settings.Mounts.IPNS}.d = lib.mkIf (cfg.autoMount) defaultConfig;
320     };
322     # The hardened systemd unit breaks the fuse-mount function according to documentation in the unit file itself
323     systemd.packages = if cfg.autoMount
324       then [ cfg.package.systemd_unit ]
325       else [ cfg.package.systemd_unit_hardened ];
327     services.kubo.settings = lib.mkIf cfg.autoMount {
328       Mounts.FuseAllowOther = lib.mkDefault true;
329     };
331     systemd.services.ipfs = {
332       path = [ "/run/wrappers" cfg.package ];
333       environment.IPFS_PATH = cfg.dataDir;
335       preStart = ''
336         if [[ ! -f "$IPFS_PATH/config" ]]; then
337           ipfs init --empty-repo=${lib.boolToString cfg.emptyRepo}
338         else
339           # After an unclean shutdown this file may exist which will cause the config command to attempt to talk to the daemon. This will hang forever if systemd is holding our sockets open.
340           rm -vf "$IPFS_PATH/api"
341       '' + lib.optionalString cfg.autoMigrate ''
342         ${pkgs.kubo-migrator}/bin/fs-repo-migrations -to '${cfg.package.repoVersion}' -y
343       '' + ''
344         fi
345         ipfs --offline config show |
346           ${pkgs.jq}/bin/jq -s '.[0].Pinning as $Pinning | .[0].Identity as $Identity | .[1] + {$Identity,$Pinning}' - '${configFile}' |
348           # This command automatically injects the private key and other secrets from
349           # the old config file back into the new config file.
350           # Unfortunately, it doesn't keep the original `Identity.PeerID`,
351           # so we need `ipfs config show` and jq above.
352           # See https://github.com/ipfs/kubo/issues/8993 for progress on fixing this problem.
353           # Kubo also wants a specific version of the original "Pinning.RemoteServices"
354           # section (redacted by `ipfs config show`), such that that section doesn't
355           # change when the changes are applied. Whyyyyyy.....
356           ipfs --offline config replace -
357       '';
358       postStop = lib.mkIf cfg.autoMount ''
359         # After an unclean shutdown the fuse mounts at cfg.settings.Mounts.IPFS and cfg.settings.Mounts.IPNS are locked
360         umount --quiet '${cfg.settings.Mounts.IPFS}' '${cfg.settings.Mounts.IPNS}' || true
361       '';
362       serviceConfig = {
363         ExecStart = [ "" "${cfg.package}/bin/ipfs daemon ${kuboFlags}" ];
364         User = cfg.user;
365         Group = cfg.group;
366         StateDirectory = "";
367         ReadWritePaths = lib.optionals (!cfg.autoMount) [ "" cfg.dataDir ];
368         # Make sure the socket units are started before ipfs.service
369         Sockets = [ "ipfs-gateway.socket" "ipfs-api.socket" ];
370       } // lib.optionalAttrs (cfg.serviceFdlimit != null) { LimitNOFILE = cfg.serviceFdlimit; };
371     } // lib.optionalAttrs (!cfg.startWhenNeeded) {
372       wantedBy = [ "default.target" ];
373     };
375     systemd.sockets.ipfs-gateway = {
376       wantedBy = [ "sockets.target" ];
377       socketConfig = {
378         ListenStream =
379           [ "" ] ++ (multiaddrsToListenStreams cfg.settings.Addresses.Gateway);
380         ListenDatagram =
381           [ "" ] ++ (multiaddrsToListenDatagrams cfg.settings.Addresses.Gateway);
382       };
383     };
385     systemd.sockets.ipfs-api = {
386       wantedBy = [ "sockets.target" ];
387       socketConfig = {
388         # We also include "%t/ipfs.sock" because there is no way to put the "%t"
389         # in the multiaddr.
390         ListenStream =
391           [ "" "%t/ipfs.sock" ] ++ (multiaddrsToListenStreams cfg.settings.Addresses.API);
392         SocketMode = "0660";
393         SocketUser = cfg.user;
394         SocketGroup = cfg.group;
395       };
396     };
397   };
399   meta = {
400     maintainers = with lib.maintainers; [ Luflosi ];
401   };
403   imports = [
404     (lib.mkRenamedOptionModule [ "services" "ipfs" "enable" ] [ "services" "kubo" "enable" ])
405     (lib.mkRenamedOptionModule [ "services" "ipfs" "package" ] [ "services" "kubo" "package" ])
406     (lib.mkRenamedOptionModule [ "services" "ipfs" "user" ] [ "services" "kubo" "user" ])
407     (lib.mkRenamedOptionModule [ "services" "ipfs" "group" ] [ "services" "kubo" "group" ])
408     (lib.mkRenamedOptionModule [ "services" "ipfs" "dataDir" ] [ "services" "kubo" "dataDir" ])
409     (lib.mkRenamedOptionModule [ "services" "ipfs" "defaultMode" ] [ "services" "kubo" "defaultMode" ])
410     (lib.mkRenamedOptionModule [ "services" "ipfs" "autoMount" ] [ "services" "kubo" "autoMount" ])
411     (lib.mkRenamedOptionModule [ "services" "ipfs" "autoMigrate" ] [ "services" "kubo" "autoMigrate" ])
412     (lib.mkRenamedOptionModule [ "services" "ipfs" "ipfsMountDir" ] [ "services" "kubo" "settings" "Mounts" "IPFS" ])
413     (lib.mkRenamedOptionModule [ "services" "ipfs" "ipnsMountDir" ] [ "services" "kubo" "settings" "Mounts" "IPNS" ])
414     (lib.mkRenamedOptionModule [ "services" "ipfs" "gatewayAddress" ] [ "services" "kubo" "settings" "Addresses" "Gateway" ])
415     (lib.mkRenamedOptionModule [ "services" "ipfs" "apiAddress" ] [ "services" "kubo" "settings" "Addresses" "API" ])
416     (lib.mkRenamedOptionModule [ "services" "ipfs" "swarmAddress" ] [ "services" "kubo" "settings" "Addresses" "Swarm" ])
417     (lib.mkRenamedOptionModule [ "services" "ipfs" "enableGC" ] [ "services" "kubo" "enableGC" ])
418     (lib.mkRenamedOptionModule [ "services" "ipfs" "emptyRepo" ] [ "services" "kubo" "emptyRepo" ])
419     (lib.mkRenamedOptionModule [ "services" "ipfs" "extraConfig" ] [ "services" "kubo" "settings" ])
420     (lib.mkRenamedOptionModule [ "services" "ipfs" "extraFlags" ] [ "services" "kubo" "extraFlags" ])
421     (lib.mkRenamedOptionModule [ "services" "ipfs" "localDiscovery" ] [ "services" "kubo" "localDiscovery" ])
422     (lib.mkRenamedOptionModule [ "services" "ipfs" "serviceFdlimit" ] [ "services" "kubo" "serviceFdlimit" ])
423     (lib.mkRenamedOptionModule [ "services" "ipfs" "startWhenNeeded" ] [ "services" "kubo" "startWhenNeeded" ])
424     (lib.mkRenamedOptionModule [ "services" "kubo" "extraConfig" ] [ "services" "kubo" "settings" ])
425     (lib.mkRenamedOptionModule [ "services" "kubo" "gatewayAddress" ] [ "services" "kubo" "settings" "Addresses" "Gateway" ])
426     (lib.mkRenamedOptionModule [ "services" "kubo" "apiAddress" ] [ "services" "kubo" "settings" "Addresses" "API" ])
427     (lib.mkRenamedOptionModule [ "services" "kubo" "swarmAddress" ] [ "services" "kubo" "settings" "Addresses" "Swarm" ])
428     (lib.mkRenamedOptionModule [ "services" "kubo" "ipfsMountDir" ] [ "services" "kubo" "settings" "Mounts" "IPFS" ])
429     (lib.mkRenamedOptionModule [ "services" "kubo" "ipnsMountDir" ] [ "services" "kubo" "settings" "Mounts" "IPNS" ])
430   ];