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 # NB: migration must be performed prior to pre-start, else we get the failure message!
40 preStart = ''
41 ipfs repo fsck # workaround for BUG #4212 (https://github.com/ipfs/go-ipfs/issues/4214)
42 '' + optionalString 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 ({ Addresses.API = cfg.apiAddress;
58 Addresses.Gateway = cfg.gatewayAddress;
59 Addresses.Swarm = cfg.swarmAddress;
60 } //
61 cfg.extraConfig))
62 );
63 serviceConfig = {
64 ExecStart = "${wrapped}/bin/ipfs daemon ${ipfsFlags}";
65 Restart = "on-failure";
66 RestartSec = 1;
67 } // optionalAttrs (cfg.serviceFdlimit != null) { LimitNOFILE = cfg.serviceFdlimit; };
68 };
69in {
70
71 ###### interface
72
73 options = {
74
75 services.ipfs = {
76
77 enable = mkEnableOption "Interplanetary File System";
78
79 user = mkOption {
80 type = types.str;
81 default = "ipfs";
82 description = "User under which the IPFS daemon runs";
83 };
84
85 group = mkOption {
86 type = types.str;
87 default = "ipfs";
88 description = "Group under which the IPFS daemon runs";
89 };
90
91 dataDir = mkOption {
92 type = types.str;
93 default = defaultDataDir;
94 description = "The data dir for IPFS";
95 };
96
97 defaultMode = mkOption {
98 type = types.enum [ "online" "offline" "norouting" ];
99 default = "online";
100 description = "systemd service that is enabled by default";
101 };
102
103 /*
104 autoMigrate = mkOption {
105 type = types.bool;
106 default = false;
107 description = ''
108 Whether IPFS should try to migrate the file system automatically.
109
110 The daemon will need to be able to download a binary from https://ipfs.io to perform the migration.
111 '';
112 };
113 */
114
115 autoMount = mkOption {
116 type = types.bool;
117 default = false;
118 description = "Whether IPFS should try to mount /ipfs and /ipns at startup.";
119 };
120
121 ipfsMountDir = mkOption {
122 type = types.str;
123 default = "/ipfs";
124 description = "Where to mount the IPFS namespace to";
125 };
126
127 ipnsMountDir = mkOption {
128 type = types.str;
129 default = "/ipns";
130 description = "Where to mount the IPNS namespace to";
131 };
132
133 gatewayAddress = mkOption {
134 type = types.str;
135 default = "/ip4/127.0.0.1/tcp/8080";
136 description = "Where the IPFS Gateway can be reached";
137 };
138
139 apiAddress = mkOption {
140 type = types.str;
141 default = "/ip4/127.0.0.1/tcp/5001";
142 description = "Where IPFS exposes its API to";
143 };
144
145 swarmAddress = mkOption {
146 type = types.listOf types.str;
147 default = [ "/ip4/0.0.0.0/tcp/4001" "/ip6/::/tcp/4001" ];
148 description = "Where IPFS listens for incoming p2p connections";
149 };
150
151 enableGC = mkOption {
152 type = types.bool;
153 default = false;
154 description = "Whether to enable automatic garbage collection";
155 };
156
157 emptyRepo = mkOption {
158 type = types.bool;
159 default = false;
160 description = "If set to true, the repo won't be initialized with help files";
161 };
162
163 extraConfig = mkOption {
164 type = types.attrs;
165 description = ''
166 Attrset of daemon configuration to set using <command>ipfs config</command>, every time the daemon starts.
167 These are applied last, so may override configuration set by other options in this module.
168 Keep in mind that this configuration is stateful; i.e., unsetting anything in here does not reset the value to the default!
169 '';
170 default = {};
171 example = {
172 Datastore.StorageMax = "100GB";
173 Discovery.MDNS.Enabled = false;
174 Bootstrap = [
175 "/ip4/128.199.219.111/tcp/4001/ipfs/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu"
176 "/ip4/162.243.248.213/tcp/4001/ipfs/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm"
177 ];
178 Swarm.AddrFilters = null;
179 };
180
181 };
182
183 extraFlags = mkOption {
184 type = types.listOf types.str;
185 description = "Extra flags passed to the IPFS daemon";
186 default = [];
187 };
188
189 localDiscovery = mkOption {
190 type = types.bool;
191 description = ''Whether to enable local discovery for the ipfs daemon.
192 This will allow ipfs to scan ports on your local network. Some hosting services will ban you if you do this.
193 '';
194 default = true;
195 };
196
197 serviceFdlimit = mkOption {
198 type = types.nullOr types.int;
199 default = null;
200 description = "The fdlimit for the IPFS systemd unit or <literal>null</literal> to have the daemon attempt to manage it";
201 example = 64*1024;
202 };
203
204 };
205 };
206
207 ###### implementation
208
209 config = mkIf cfg.enable {
210 environment.systemPackages = [ wrapped ];
211 environment.etc."fuse.conf" = mkIf cfg.autoMount { text = ''
212 user_allow_other
213 ''; };
214
215 users.users = mkIf (cfg.user == "ipfs") {
216 ipfs = {
217 group = cfg.group;
218 home = cfg.dataDir;
219 createHome = false;
220 uid = config.ids.uids.ipfs;
221 description = "IPFS daemon user";
222 };
223 };
224
225 users.groups = mkIf (cfg.group == "ipfs") {
226 ipfs.gid = config.ids.gids.ipfs;
227 };
228
229 systemd.services.ipfs-init = recursiveUpdate commonEnv {
230 description = "IPFS Initializer";
231
232 after = [ "local-fs.target" ];
233 before = [ "ipfs.service" "ipfs-offline.service" "ipfs-norouting.service" ];
234
235 preStart = ''
236 install -m 0755 -o ${cfg.user} -g ${cfg.group} -d ${cfg.dataDir}
237 '' + optionalString cfg.autoMount ''
238 install -m 0755 -o ${cfg.user} -g ${cfg.group} -d ${cfg.ipfsMountDir}
239 install -m 0755 -o ${cfg.user} -g ${cfg.group} -d ${cfg.ipnsMountDir}
240 '';
241 script = ''
242 if [[ ! -f ${cfg.dataDir}/config ]]; then
243 ipfs init ${optionalString cfg.emptyRepo "-e"} \
244 ${optionalString (! cfg.localDiscovery) "--profile=server"}
245 else
246 ${if cfg.localDiscovery
247 then "ipfs config profile apply local-discovery"
248 else "ipfs config profile apply server"
249 }
250 fi
251 '';
252
253 serviceConfig = {
254 Type = "oneshot";
255 RemainAfterExit = true;
256 PermissionsStartOnly = true;
257 };
258 };
259
260 # TODO These 3 definitions possibly be further abstracted through use of a function
261 # like: mutexServices "ipfs" [ "", "offline", "norouting" ] { ... shared conf here ... }
262
263 systemd.services.ipfs = recursiveUpdate baseService {
264 description = "IPFS Daemon";
265 wantedBy = mkIf (cfg.defaultMode == "online") [ "multi-user.target" ];
266 after = [ "network.target" "local-fs.target" "ipfs-init.service" ];
267 conflicts = [ "ipfs-offline.service" "ipfs-norouting.service"];
268 };
269
270 systemd.services.ipfs-offline = recursiveUpdate baseService {
271 description = "IPFS Daemon (offline mode)";
272 wantedBy = mkIf (cfg.defaultMode == "offline") [ "multi-user.target" ];
273 after = [ "local-fs.target" "ipfs-init.service" ];
274 conflicts = [ "ipfs.service" "ipfs-norouting.service"];
275 };
276
277 systemd.services.ipfs-norouting = recursiveUpdate baseService {
278 description = "IPFS Daemon (no routing mode)";
279 wantedBy = mkIf (cfg.defaultMode == "norouting") [ "multi-user.target" ];
280 after = [ "local-fs.target" "ipfs-init.service" ];
281 conflicts = [ "ipfs.service" "ipfs-offline.service"];
282 };
283
284 };
285}