1 { config, lib, pkgs, utils, ... }:
4 cfg = config.services.kubo;
6 settingsFormat = pkgs.formats.json {};
8 rawDefaultConfig = lib.importJSON (pkgs.runCommand "kubo-default-config" {
9 nativeBuildInputs = [ cfg.package ];
11 export IPFS_PATH="$TMPDIR"
12 ipfs init --empty-repo --profile=${profile}
13 ipfs --offline config show > "$out"
16 # Remove the PeerID (an attribute of "Identity") of the temporary Kubo repo.
17 # The "Pinning" section contains the "RemoteServices" section, which would prevent
18 # the daemon from starting as that setting can't be changed via ipfs config replace.
19 defaultConfig = builtins.removeAttrs rawDefaultConfig [ "Identity" "Pinning" ];
21 customizedConfig = lib.recursiveUpdate defaultConfig cfg.settings;
23 configFile = settingsFormat.generate "kubo-config.json" customizedConfig;
25 # Create a fake repo containing only the file "api".
26 # $IPFS_PATH will point to this directory instead of the real one.
27 # For some reason the Kubo CLI tools insist on reading the
28 # config file when it exists. But the Kubo daemon sets the file
29 # permissions such that only the ipfs user is allowed to read
30 # this file. This prevents normal users from talking to the daemon.
31 # To work around this terrible design, create a fake repo with no
32 # config file, only an api file and everything should work as expected.
33 fakeKuboRepo = pkgs.writeTextDir "api" ''
37 kuboFlags = utils.escapeSystemdExecArgs (
38 optional cfg.autoMount "--mount" ++
39 optional cfg.enableGC "--enable-gc" ++
40 optional (cfg.serviceFdlimit != null) "--manage-fdlimit=false" ++
41 optional (cfg.defaultMode == "offline") "--offline" ++
42 optional (cfg.defaultMode == "norouting") "--routing=none" ++
48 then "local-discovery"
51 splitMulitaddr = addrRaw: lib.tail (lib.splitString "/" addrRaw);
53 multiaddrsToListenStreams = addrIn:
55 addrs = if builtins.typeOf addrIn == "list"
56 then addrIn else [ addrIn ];
57 unfilteredResult = map multiaddrToListenStream addrs;
59 builtins.filter (addr: addr != null) unfilteredResult;
61 multiaddrsToListenDatagrams = addrIn:
63 addrs = if builtins.typeOf addrIn == "list"
64 then addrIn else [ addrIn ];
65 unfilteredResult = map multiaddrToListenDatagram addrs;
67 builtins.filter (addr: addr != null) unfilteredResult;
69 multiaddrToListenStream = addrRaw:
71 addr = splitMulitaddr addrRaw;
72 s = builtins.elemAt addr;
74 if s 0 == "ip4" && s 2 == "tcp"
76 else if s 0 == "ip6" && s 2 == "tcp"
77 then "[${s 1}]:${s 3}"
79 then "/${lib.concatStringsSep "/" (lib.tail addr)}"
80 else null; # not valid for listen stream, skip
82 multiaddrToListenDatagram = addrRaw:
84 addr = splitMulitaddr addrRaw;
85 s = builtins.elemAt addr;
87 if s 0 == "ip4" && s 2 == "udp"
89 else if s 0 == "ip6" && s 2 == "udp"
90 then "[${s 1}]:${s 3}"
91 else null; # not valid for listen datagram, skip
102 enable = mkEnableOption (lib.mdDoc "Interplanetary File System (WARNING: may cause severe network degradation)");
105 type = types.package;
107 defaultText = literalExpression "pkgs.kubo";
108 description = lib.mdDoc "Which Kubo package to use.";
114 description = lib.mdDoc "User under which the Kubo daemon runs";
120 description = lib.mdDoc "Group under which the Kubo daemon runs";
126 if versionAtLeast config.system.stateVersion "17.09"
128 else "/var/lib/ipfs/.ipfs";
129 defaultText = literalExpression ''
130 if versionAtLeast config.system.stateVersion "17.09"
132 else "/var/lib/ipfs/.ipfs"
134 description = lib.mdDoc "The data dir for Kubo";
137 defaultMode = mkOption {
138 type = types.enum [ "online" "offline" "norouting" ];
140 description = lib.mdDoc "systemd service that is enabled by default";
143 autoMount = mkOption {
146 description = lib.mdDoc "Whether Kubo should try to mount /ipfs and /ipns at startup.";
149 autoMigrate = mkOption {
152 description = lib.mdDoc "Whether Kubo should try to run the fs-repo-migration at startup.";
155 ipfsMountDir = mkOption {
158 description = lib.mdDoc "Where to mount the IPFS namespace to";
161 ipnsMountDir = mkOption {
164 description = lib.mdDoc "Where to mount the IPNS namespace to";
167 enableGC = mkOption {
170 description = lib.mdDoc "Whether to enable automatic garbage collection";
173 emptyRepo = mkOption {
176 description = lib.mdDoc "If set to false, the repo will be initialized with help files";
179 settings = mkOption {
180 type = lib.types.submodule {
181 freeformType = settingsFormat.type;
184 Addresses.API = mkOption {
185 type = types.oneOf [ types.str (types.listOf types.str) ];
187 description = lib.mdDoc ''
188 Multiaddr or array of multiaddrs describing the address to serve the local HTTP API on.
189 In addition to the multiaddrs listed here, the daemon will also listen on a Unix domain socket.
190 To allow the ipfs CLI tools to communicate with the daemon over that socket,
191 add your user to the correct group, e.g. `users.users.alice.extraGroups = [ config.services.kubo.group ];`
195 Addresses.Gateway = mkOption {
196 type = types.oneOf [ types.str (types.listOf types.str) ];
197 default = "/ip4/127.0.0.1/tcp/8080";
198 description = lib.mdDoc "Where the IPFS Gateway can be reached";
201 Addresses.Swarm = mkOption {
202 type = types.listOf types.str;
204 "/ip4/0.0.0.0/tcp/4001"
206 "/ip4/0.0.0.0/udp/4001/quic-v1"
207 "/ip4/0.0.0.0/udp/4001/quic-v1/webtransport"
208 "/ip6/::/udp/4001/quic-v1"
209 "/ip6/::/udp/4001/quic-v1/webtransport"
211 description = lib.mdDoc "Where Kubo listens for incoming p2p connections";
215 description = lib.mdDoc ''
216 Attrset of daemon configuration.
217 See [https://github.com/ipfs/kubo/blob/master/docs/config.md](https://github.com/ipfs/kubo/blob/master/docs/config.md) for reference.
218 You can't set `Identity` or `Pinning`.
222 Datastore.StorageMax = "100GB";
223 Discovery.MDNS.Enabled = false;
225 "/ip4/128.199.219.111/tcp/4001/ipfs/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu"
226 "/ip4/162.243.248.213/tcp/4001/ipfs/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm"
228 Swarm.AddrFilters = null;
233 extraFlags = mkOption {
234 type = types.listOf types.str;
235 description = lib.mdDoc "Extra flags passed to the Kubo daemon";
239 localDiscovery = mkOption {
241 description = lib.mdDoc ''Whether to enable local discovery for the Kubo daemon.
242 This will allow Kubo to scan ports on your local network. Some hosting services will ban you if you do this.
247 serviceFdlimit = mkOption {
248 type = types.nullOr types.int;
250 description = lib.mdDoc "The fdlimit for the Kubo systemd unit or `null` to have the daemon attempt to manage it";
254 startWhenNeeded = mkOption {
257 description = lib.mdDoc "Whether to use socket activation to start Kubo when needed.";
263 ###### implementation
265 config = mkIf cfg.enable {
268 assertion = !builtins.hasAttr "Identity" cfg.settings;
270 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.
274 assertion = !((builtins.hasAttr "Pinning" cfg.settings) && (builtins.hasAttr "RemoteServices" cfg.settings.Pinning));
276 You can't set services.kubo.settings.Pinning.RemoteServices because the ``config replace`` subcommand used at startup does not work with it.
280 assertion = !((lib.versionAtLeast cfg.package.version "0.21") && (builtins.hasAttr "Experimental" cfg.settings) && (builtins.hasAttr "AcceleratedDHTClient" cfg.settings.Experimental));
282 The `services.kubo.settings.Experimental.AcceleratedDHTClient` option was renamed to `services.kubo.settings.Routing.AcceleratedDHTClient` in Kubo 0.21.
287 environment.systemPackages = [ cfg.package ];
288 environment.variables.IPFS_PATH = fakeKuboRepo;
290 # https://github.com/lucas-clemente/quic-go/wiki/UDP-Receive-Buffer-Size
291 boot.kernel.sysctl."net.core.rmem_max" = mkDefault 2500000;
293 programs.fuse = mkIf cfg.autoMount {
294 userAllowOther = true;
297 users.users = mkIf (cfg.user == "ipfs") {
302 uid = config.ids.uids.ipfs;
303 description = "IPFS daemon user";
310 users.groups = mkIf (cfg.group == "ipfs") {
311 ipfs.gid = config.ids.gids.ipfs;
314 systemd.tmpfiles.rules = [
315 "d '${cfg.dataDir}' - ${cfg.user} ${cfg.group} - -"
316 ] ++ optionals cfg.autoMount [
317 "d '${cfg.ipfsMountDir}' - ${cfg.user} ${cfg.group} - -"
318 "d '${cfg.ipnsMountDir}' - ${cfg.user} ${cfg.group} - -"
321 # The hardened systemd unit breaks the fuse-mount function according to documentation in the unit file itself
322 systemd.packages = if cfg.autoMount
323 then [ cfg.package.systemd_unit ]
324 else [ cfg.package.systemd_unit_hardened ];
326 services.kubo.settings = mkIf cfg.autoMount {
327 Mounts.FuseAllowOther = lib.mkDefault true;
328 Mounts.IPFS = lib.mkDefault cfg.ipfsMountDir;
329 Mounts.IPNS = lib.mkDefault cfg.ipnsMountDir;
332 systemd.services.ipfs = {
333 path = [ "/run/wrappers" cfg.package ];
334 environment.IPFS_PATH = cfg.dataDir;
337 if [[ ! -f "$IPFS_PATH/config" ]]; then
338 ipfs init --empty-repo=${lib.boolToString cfg.emptyRepo}
340 # 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.
341 rm -vf "$IPFS_PATH/api"
342 '' + optionalString cfg.autoMigrate ''
343 ${pkgs.kubo-migrator}/bin/fs-repo-migrations -to '${cfg.package.repoVersion}' -y
346 ipfs --offline config show |
347 ${pkgs.jq}/bin/jq -s '.[0].Pinning as $Pinning | .[0].Identity as $Identity | .[1] + {$Identity,$Pinning}' - '${configFile}' |
349 # This command automatically injects the private key and other secrets from
350 # the old config file back into the new config file.
351 # Unfortunately, it doesn't keep the original `Identity.PeerID`,
352 # so we need `ipfs config show` and jq above.
353 # See https://github.com/ipfs/kubo/issues/8993 for progress on fixing this problem.
354 # Kubo also wants a specific version of the original "Pinning.RemoteServices"
355 # section (redacted by `ipfs config show`), such that that section doesn't
356 # change when the changes are applied. Whyyyyyy.....
357 ipfs --offline config replace -
359 postStop = mkIf cfg.autoMount ''
360 # After an unclean shutdown the fuse mounts at cfg.ipnsMountDir and cfg.ipfsMountDir are locked
361 umount --quiet '${cfg.ipnsMountDir}' '${cfg.ipfsMountDir}' || true
364 ExecStart = [ "" "${cfg.package}/bin/ipfs daemon ${kuboFlags}" ];
368 ReadWritePaths = optionals (!cfg.autoMount) [ "" cfg.dataDir ];
369 } // optionalAttrs (cfg.serviceFdlimit != null) { LimitNOFILE = cfg.serviceFdlimit; };
370 } // optionalAttrs (!cfg.startWhenNeeded) {
371 wantedBy = [ "default.target" ];
374 systemd.sockets.ipfs-gateway = {
375 wantedBy = [ "sockets.target" ];
378 [ "" ] ++ (multiaddrsToListenStreams cfg.settings.Addresses.Gateway);
380 [ "" ] ++ (multiaddrsToListenDatagrams cfg.settings.Addresses.Gateway);
384 systemd.sockets.ipfs-api = {
385 wantedBy = [ "sockets.target" ];
387 # We also include "%t/ipfs.sock" because there is no way to put the "%t"
390 [ "" "%t/ipfs.sock" ] ++ (multiaddrsToListenStreams cfg.settings.Addresses.API);
392 SocketUser = cfg.user;
393 SocketGroup = cfg.group;
399 maintainers = with lib.maintainers; [ Luflosi ];
403 (mkRenamedOptionModule [ "services" "ipfs" "enable" ] [ "services" "kubo" "enable" ])
404 (mkRenamedOptionModule [ "services" "ipfs" "package" ] [ "services" "kubo" "package" ])
405 (mkRenamedOptionModule [ "services" "ipfs" "user" ] [ "services" "kubo" "user" ])
406 (mkRenamedOptionModule [ "services" "ipfs" "group" ] [ "services" "kubo" "group" ])
407 (mkRenamedOptionModule [ "services" "ipfs" "dataDir" ] [ "services" "kubo" "dataDir" ])
408 (mkRenamedOptionModule [ "services" "ipfs" "defaultMode" ] [ "services" "kubo" "defaultMode" ])
409 (mkRenamedOptionModule [ "services" "ipfs" "autoMount" ] [ "services" "kubo" "autoMount" ])
410 (mkRenamedOptionModule [ "services" "ipfs" "autoMigrate" ] [ "services" "kubo" "autoMigrate" ])
411 (mkRenamedOptionModule [ "services" "ipfs" "ipfsMountDir" ] [ "services" "kubo" "ipfsMountDir" ])
412 (mkRenamedOptionModule [ "services" "ipfs" "ipnsMountDir" ] [ "services" "kubo" "ipnsMountDir" ])
413 (mkRenamedOptionModule [ "services" "ipfs" "gatewayAddress" ] [ "services" "kubo" "settings" "Addresses" "Gateway" ])
414 (mkRenamedOptionModule [ "services" "ipfs" "apiAddress" ] [ "services" "kubo" "settings" "Addresses" "API" ])
415 (mkRenamedOptionModule [ "services" "ipfs" "swarmAddress" ] [ "services" "kubo" "settings" "Addresses" "Swarm" ])
416 (mkRenamedOptionModule [ "services" "ipfs" "enableGC" ] [ "services" "kubo" "enableGC" ])
417 (mkRenamedOptionModule [ "services" "ipfs" "emptyRepo" ] [ "services" "kubo" "emptyRepo" ])
418 (mkRenamedOptionModule [ "services" "ipfs" "extraConfig" ] [ "services" "kubo" "settings" ])
419 (mkRenamedOptionModule [ "services" "ipfs" "extraFlags" ] [ "services" "kubo" "extraFlags" ])
420 (mkRenamedOptionModule [ "services" "ipfs" "localDiscovery" ] [ "services" "kubo" "localDiscovery" ])
421 (mkRenamedOptionModule [ "services" "ipfs" "serviceFdlimit" ] [ "services" "kubo" "serviceFdlimit" ])
422 (mkRenamedOptionModule [ "services" "ipfs" "startWhenNeeded" ] [ "services" "kubo" "startWhenNeeded" ])
423 (mkRenamedOptionModule [ "services" "kubo" "extraConfig" ] [ "services" "kubo" "settings" ])
424 (mkRenamedOptionModule [ "services" "kubo" "gatewayAddress" ] [ "services" "kubo" "settings" "Addresses" "Gateway" ])
425 (mkRenamedOptionModule [ "services" "kubo" "apiAddress" ] [ "services" "kubo" "settings" "Addresses" "API" ])
426 (mkRenamedOptionModule [ "services" "kubo" "swarmAddress" ] [ "services" "kubo" "settings" "Addresses" "Swarm" ])