1{ config, lib, pkgs, ... }:
2with lib;
3let
4 inherit (pkgs) ipfs runCommand makeWrapper;
5
6 cfg = config.services.ipfs;
7
8 ipfsFlags = toString ([
9 #(optionalString cfg.autoMount "--mount")
10 (optionalString cfg.autoMigrate "--migrate")
11 (optionalString cfg.enableGC "--enable-gc")
12 (optionalString (cfg.serviceFdlimit != null) "--manage-fdlimit=false")
13 (optionalString (cfg.defaultMode == "offline") "--offline")
14 (optionalString (cfg.defaultMode == "norouting") "--routing=none")
15 ] ++ cfg.extraFlags);
16
17 defaultDataDir = if versionAtLeast config.system.stateVersion "17.09" then
18 "/var/lib/ipfs" else
19 "/var/lib/ipfs/.ipfs";
20
21 # Wrapping the ipfs binary with the environment variable IPFS_PATH set to dataDir because we can't set it in the user environment
22 wrapped = runCommand "ipfs" { buildInputs = [ makeWrapper ]; } ''
23 mkdir -p "$out/bin"
24 makeWrapper "${ipfs}/bin/ipfs" "$out/bin/ipfs" \
25 --set IPFS_PATH ${cfg.dataDir} \
26 --prefix PATH : /run/wrappers/bin
27 '';
28
29
30 commonEnv = {
31 environment.IPFS_PATH = cfg.dataDir;
32 path = [ wrapped ];
33 serviceConfig.User = cfg.user;
34 serviceConfig.Group = cfg.group;
35 };
36
37 baseService = recursiveUpdate commonEnv {
38 wants = [ "ipfs-init.service" ];
39 preStart = ''
40 ipfs --local config Addresses.API ${cfg.apiAddress}
41 ipfs --local config Addresses.Gateway ${cfg.gatewayAddress}
42 '' + optionalString false/*cfg.autoMount*/ ''
43 ipfs --local config Mounts.FuseAllowOther --json true
44 ipfs --local config Mounts.IPFS ${cfg.ipfsMountDir}
45 ipfs --local config Mounts.IPNS ${cfg.ipnsMountDir}
46 '' + concatStringsSep "\n" (collect
47 isString
48 (mapAttrsRecursive
49 (path: value:
50 # Using heredoc below so that the value is never improperly quoted
51 ''
52 read value <<EOF
53 ${builtins.toJSON value}
54 EOF
55 ipfs --local config --json "${concatStringsSep "." path}" "$value"
56 '')
57 cfg.extraConfig)
58 );
59 serviceConfig = {
60 ExecStart = "${wrapped}/bin/ipfs daemon ${ipfsFlags}";
61 Restart = "on-failure";
62 RestartSec = 1;
63 } // optionalAttrs (cfg.serviceFdlimit != null) { LimitNOFILE = cfg.serviceFdlimit; };
64 };
65in {
66
67 ###### interface
68
69 options = {
70
71 services.ipfs = {
72
73 enable = mkEnableOption "Interplanetary File System";
74
75 user = mkOption {
76 type = types.str;
77 default = "ipfs";
78 description = "User under which the IPFS daemon runs";
79 };
80
81 group = mkOption {
82 type = types.str;
83 default = "ipfs";
84 description = "Group under which the IPFS daemon runs";
85 };
86
87 dataDir = mkOption {
88 type = types.str;
89 default = defaultDataDir;
90 description = "The data dir for IPFS";
91 };
92
93 defaultMode = mkOption {
94 description = "systemd service that is enabled by default";
95 type = types.enum [ "online" "offline" "norouting" ];
96 default = "online";
97 };
98
99 autoMigrate = mkOption {
100 type = types.bool;
101 default = false;
102 description = ''
103 Whether IPFS should try to migrate the file system automatically.
104 '';
105 };
106
107 #autoMount = mkOption {
108 # type = types.bool;
109 # default = false;
110 # description = "Whether IPFS should try to mount /ipfs and /ipns at startup.";
111 #};
112
113 ipfsMountDir = mkOption {
114 type = types.str;
115 default = "/ipfs";
116 description = "Where to mount the IPFS namespace to";
117 };
118
119 ipnsMountDir = mkOption {
120 type = types.str;
121 default = "/ipns";
122 description = "Where to mount the IPNS namespace to";
123 };
124
125 gatewayAddress = mkOption {
126 type = types.str;
127 default = "/ip4/127.0.0.1/tcp/8080";
128 description = "Where the IPFS Gateway can be reached";
129 };
130
131 apiAddress = mkOption {
132 type = types.str;
133 default = "/ip4/127.0.0.1/tcp/5001";
134 description = "Where IPFS exposes its API to";
135 };
136
137 enableGC = mkOption {
138 type = types.bool;
139 default = false;
140 description = ''
141 Whether to enable automatic garbage collection.
142 '';
143 };
144
145 emptyRepo = mkOption {
146 type = types.bool;
147 default = false;
148 description = ''
149 If set to true, the repo won't be initialized with help files
150 '';
151 };
152
153 extraConfig = mkOption {
154 type = types.attrs;
155 description = toString [
156 "Attrset of daemon configuration to set using `ipfs config`, every time the daemon starts."
157 "These are applied last, so may override configuration set by other options in this module."
158 "Keep in mind that this configuration is stateful; i.e., unsetting anything in here does not reset the value to the default!"
159 ];
160 default = {};
161 example = {
162 Datastore.StorageMax = "100GB";
163 Discovery.MDNS.Enabled = false;
164 Bootstrap = [
165 "/ip4/128.199.219.111/tcp/4001/ipfs/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu"
166 "/ip4/162.243.248.213/tcp/4001/ipfs/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm"
167 ];
168 Swarm.AddrFilters = null;
169 };
170
171 };
172
173 extraFlags = mkOption {
174 type = types.listOf types.str;
175 description = "Extra flags passed to the IPFS daemon";
176 default = [];
177 };
178
179 serviceFdlimit = mkOption {
180 type = types.nullOr types.int;
181 default = null;
182 description = ''
183 The fdlimit for the IPFS systemd unit or `null` to have the daemon attempt to manage it.
184 '';
185 example = 256*1024;
186 };
187
188 };
189 };
190
191 ###### implementation
192
193 config = mkIf cfg.enable {
194 environment.systemPackages = [ wrapped ];
195
196 users.extraUsers = mkIf (cfg.user == "ipfs") {
197 ipfs = {
198 group = cfg.group;
199 home = cfg.dataDir;
200 createHome = false;
201 uid = config.ids.uids.ipfs;
202 description = "IPFS daemon user";
203 };
204 };
205
206 users.extraGroups = mkIf (cfg.group == "ipfs") {
207 ipfs.gid = config.ids.gids.ipfs;
208 };
209
210 systemd.services.ipfs-init = recursiveUpdate commonEnv {
211 description = "IPFS Initializer";
212
213 after = [ "local-fs.target" ];
214 before = [ "ipfs.service" "ipfs-offline.service" ];
215
216 preStart = ''
217 install -m 0755 -o ${cfg.user} -g ${cfg.group} -d ${cfg.dataDir}
218 '' + optionalString false/*cfg.autoMount*/ ''
219 install -m 0755 -o ${cfg.user} -g ${cfg.group} -d ${cfg.ipfsMountDir}
220 install -m 0755 -o ${cfg.user} -g ${cfg.group} -d ${cfg.ipnsMountDir}
221 '';
222 script = ''
223 if [[ ! -f ${cfg.dataDir}/config ]]; then
224 ipfs init ${optionalString cfg.emptyRepo "-e"}
225 fi
226 '';
227
228 serviceConfig = {
229 Type = "oneshot";
230 RemainAfterExit = true;
231 PermissionsStartOnly = true;
232 };
233 };
234
235 # TODO These 3 definitions possibly be further abstracted through use of a function
236 # like: mutexServices "ipfs" [ "", "offline", "norouting" ] { ... shared conf here ... }
237
238 systemd.services.ipfs = recursiveUpdate baseService {
239 description = "IPFS Daemon";
240 wantedBy = mkIf (cfg.defaultMode == "online") [ "multi-user.target" ];
241 after = [ "network.target" "local-fs.target" "ipfs-init.service" ];
242 conflicts = [ "ipfs-offline.service" "ipfs-norouting.service"];
243 };
244
245 systemd.services.ipfs-offline = recursiveUpdate baseService {
246 description = "IPFS Daemon (offline mode)";
247 wantedBy = mkIf (cfg.defaultMode == "offline") [ "multi-user.target" ];
248 after = [ "local-fs.target" "ipfs-init.service" ];
249 conflicts = [ "ipfs.service" "ipfs-norouting.service"];
250 };
251
252 systemd.services.ipfs-norouting = recursiveUpdate baseService {
253 description = "IPFS Daemon (no routing mode)";
254 wantedBy = mkIf (cfg.defaultMode == "norouting") [ "multi-user.target" ];
255 after = [ "local-fs.target" "ipfs-init.service" ];
256 conflicts = [ "ipfs.service" "ipfs-offline.service"];
257 };
258
259 };
260}