1 { config, lib, pkgs, utils, ... }:
3 cfg = config.services.kubo;
5 settingsFormat = pkgs.formats.json {};
7 rawDefaultConfig = lib.importJSON (pkgs.runCommand "kubo-default-config" {
8 nativeBuildInputs = [ cfg.package ];
10 export IPFS_PATH="$TMPDIR"
11 ipfs init --empty-repo --profile=${profile}
12 ipfs --offline config show > "$out"
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" ''
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" ++
47 then "local-discovery"
50 splitMulitaddr = addrRaw: lib.tail (lib.splitString "/" addrRaw);
52 multiaddrsToListenStreams = addrIn:
54 addrs = if builtins.isList addrIn
55 then addrIn else [ addrIn ];
56 unfilteredResult = map multiaddrToListenStream addrs;
58 builtins.filter (addr: addr != null) unfilteredResult;
60 multiaddrsToListenDatagrams = addrIn:
62 addrs = if builtins.isList addrIn
63 then addrIn else [ addrIn ];
64 unfilteredResult = map multiaddrToListenDatagram addrs;
66 builtins.filter (addr: addr != null) unfilteredResult;
68 multiaddrToListenStream = addrRaw:
70 addr = splitMulitaddr addrRaw;
71 s = builtins.elemAt addr;
73 if s 0 == "ip4" && s 2 == "tcp"
75 else if s 0 == "ip6" && s 2 == "tcp"
76 then "[${s 1}]:${s 3}"
78 then "/${lib.concatStringsSep "/" (lib.tail addr)}"
79 else null; # not valid for listen stream, skip
81 multiaddrToListenDatagram = addrRaw:
83 addr = splitMulitaddr addrRaw;
84 s = builtins.elemAt addr;
86 if s 0 == "ip4" && s 2 == "udp"
88 else if s 0 == "ip6" && s 2 == "udp"
89 then "[${s 1}]:${s 3}"
90 else null; # not valid for listen datagram, skip
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
108 package = lib.mkPackageOption pkgs "kubo" { };
110 user = lib.mkOption {
111 type = lib.types.str;
113 description = "User under which the Kubo daemon runs";
116 group = lib.mkOption {
117 type = lib.types.str;
119 description = "Group under which the Kubo daemon runs";
122 dataDir = lib.mkOption {
123 type = lib.types.str;
125 if lib.versionAtLeast config.system.stateVersion "17.09"
127 else "/var/lib/ipfs/.ipfs";
128 defaultText = lib.literalExpression ''
129 if lib.versionAtLeast config.system.stateVersion "17.09"
131 else "/var/lib/ipfs/.ipfs"
133 description = "The data dir for Kubo";
136 defaultMode = lib.mkOption {
137 type = lib.types.enum [ "online" "offline" "norouting" ];
139 description = "systemd service that is enabled by default";
142 autoMount = lib.mkOption {
143 type = lib.types.bool;
145 description = "Whether Kubo should try to mount /ipfs and /ipns at startup.";
148 autoMigrate = lib.mkOption {
149 type = lib.types.bool;
151 description = "Whether Kubo should try to run the fs-repo-migration at startup.";
154 enableGC = lib.mkOption {
155 type = lib.types.bool;
157 description = "Whether to enable automatic garbage collection";
160 emptyRepo = lib.mkOption {
161 type = lib.types.bool;
163 description = "If set to false, the repo will be initialized with help files";
166 settings = lib.mkOption {
167 type = lib.types.submodule {
168 freeformType = settingsFormat.type;
171 Addresses.API = lib.mkOption {
172 type = lib.types.oneOf [ lib.types.str (lib.types.listOf lib.types.str) ];
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 ];`
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";
188 Addresses.Swarm = lib.mkOption {
189 type = lib.types.listOf lib.types.str;
191 "/ip4/0.0.0.0/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"
198 description = "Where Kubo listens for incoming p2p connections";
201 Mounts.IPFS = lib.mkOption {
202 type = lib.types.str;
204 description = "Where to mount the IPFS namespace to";
207 Mounts.IPNS = lib.mkOption {
208 type = lib.types.str;
210 description = "Where to mount the IPNS namespace to";
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`.
221 Datastore.StorageMax = "100GB";
222 Discovery.MDNS.Enabled = false;
224 "/ip4/128.199.219.111/tcp/4001/ipfs/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu"
225 "/ip4/162.243.248.213/tcp/4001/ipfs/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm"
227 Swarm.AddrFilters = null;
232 extraFlags = lib.mkOption {
233 type = lib.types.listOf lib.types.str;
234 description = "Extra flags passed to the Kubo daemon";
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.
246 serviceFdlimit = lib.mkOption {
247 type = lib.types.nullOr lib.types.int;
249 description = "The fdlimit for the Kubo systemd unit or `null` to have the daemon attempt to manage it";
253 startWhenNeeded = lib.mkOption {
254 type = lib.types.bool;
256 description = "Whether to use socket activation to start Kubo when needed.";
262 ###### implementation
264 config = lib.mkIf cfg.enable {
267 assertion = !builtins.hasAttr "Identity" cfg.settings;
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.
273 assertion = !((builtins.hasAttr "Pinning" cfg.settings) && (builtins.hasAttr "RemoteServices" cfg.settings.Pinning));
275 You can't set services.kubo.settings.Pinning.RemoteServices because the ``config replace`` subcommand used at startup does not work with it.
279 assertion = !((lib.versionAtLeast cfg.package.version "0.21") && (builtins.hasAttr "Experimental" cfg.settings) && (builtins.hasAttr "AcceleratedDHTClient" cfg.settings.Experimental));
281 The `services.kubo.settings.Experimental.AcceleratedDHTClient` option was renamed to `services.kubo.settings.Routing.AcceleratedDHTClient` in Kubo 0.21.
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;
297 users.users = lib.mkIf (cfg.user == "ipfs") {
302 uid = config.ids.uids.ipfs;
303 description = "IPFS daemon user";
310 users.groups = lib.mkIf (cfg.group == "ipfs") {
311 ipfs.gid = config.ids.gids.ipfs;
314 systemd.tmpfiles.settings."10-kubo" = let
315 defaultConfig = { inherit (cfg) user group; };
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;
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;
331 systemd.services.ipfs = {
332 path = [ "/run/wrappers" cfg.package ];
333 environment.IPFS_PATH = cfg.dataDir;
336 if [[ ! -f "$IPFS_PATH/config" ]]; then
337 ipfs init --empty-repo=${lib.boolToString cfg.emptyRepo}
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
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 -
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
363 ExecStart = [ "" "${cfg.package}/bin/ipfs daemon ${kuboFlags}" ];
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" ];
375 systemd.sockets.ipfs-gateway = {
376 wantedBy = [ "sockets.target" ];
379 [ "" ] ++ (multiaddrsToListenStreams cfg.settings.Addresses.Gateway);
381 [ "" ] ++ (multiaddrsToListenDatagrams cfg.settings.Addresses.Gateway);
385 systemd.sockets.ipfs-api = {
386 wantedBy = [ "sockets.target" ];
388 # We also include "%t/ipfs.sock" because there is no way to put the "%t"
391 [ "" "%t/ipfs.sock" ] ++ (multiaddrsToListenStreams cfg.settings.Addresses.API);
393 SocketUser = cfg.user;
394 SocketGroup = cfg.group;
400 maintainers = with lib.maintainers; [ Luflosi ];
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" ])