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 serviceFdlimit = mkOption {
190 type = types.nullOr types.int;
191 default = null;
192 description = "The fdlimit for the IPFS systemd unit or <literal>null</literal> to have the daemon attempt to manage it";
193 example = 64*1024;
194 };
195
196 };
197 };
198
199 ###### implementation
200
201 config = mkIf cfg.enable {
202 environment.systemPackages = [ wrapped ];
203 environment.etc."fuse.conf" = mkIf cfg.autoMount { text = ''
204 user_allow_other
205 ''; };
206
207 users.extraUsers = mkIf (cfg.user == "ipfs") {
208 ipfs = {
209 group = cfg.group;
210 home = cfg.dataDir;
211 createHome = false;
212 uid = config.ids.uids.ipfs;
213 description = "IPFS daemon user";
214 };
215 };
216
217 users.extraGroups = mkIf (cfg.group == "ipfs") {
218 ipfs.gid = config.ids.gids.ipfs;
219 };
220
221 systemd.services.ipfs-init = recursiveUpdate commonEnv {
222 description = "IPFS Initializer";
223
224 after = [ "local-fs.target" ];
225 before = [ "ipfs.service" "ipfs-offline.service" "ipfs-norouting.service" ];
226
227 preStart = ''
228 install -m 0755 -o ${cfg.user} -g ${cfg.group} -d ${cfg.dataDir}
229 '' + optionalString cfg.autoMount ''
230 install -m 0755 -o ${cfg.user} -g ${cfg.group} -d ${cfg.ipfsMountDir}
231 install -m 0755 -o ${cfg.user} -g ${cfg.group} -d ${cfg.ipnsMountDir}
232 '';
233 script = ''
234 if [[ ! -f ${cfg.dataDir}/config ]]; then
235 ipfs init ${optionalString cfg.emptyRepo "-e"}
236 fi
237 '';
238
239 serviceConfig = {
240 Type = "oneshot";
241 RemainAfterExit = true;
242 PermissionsStartOnly = true;
243 };
244 };
245
246 # TODO These 3 definitions possibly be further abstracted through use of a function
247 # like: mutexServices "ipfs" [ "", "offline", "norouting" ] { ... shared conf here ... }
248
249 systemd.services.ipfs = recursiveUpdate baseService {
250 description = "IPFS Daemon";
251 wantedBy = mkIf (cfg.defaultMode == "online") [ "multi-user.target" ];
252 after = [ "network.target" "local-fs.target" "ipfs-init.service" ];
253 conflicts = [ "ipfs-offline.service" "ipfs-norouting.service"];
254 };
255
256 systemd.services.ipfs-offline = recursiveUpdate baseService {
257 description = "IPFS Daemon (offline mode)";
258 wantedBy = mkIf (cfg.defaultMode == "offline") [ "multi-user.target" ];
259 after = [ "local-fs.target" "ipfs-init.service" ];
260 conflicts = [ "ipfs.service" "ipfs-norouting.service"];
261 };
262
263 systemd.services.ipfs-norouting = recursiveUpdate baseService {
264 description = "IPFS Daemon (no routing mode)";
265 wantedBy = mkIf (cfg.defaultMode == "norouting") [ "multi-user.target" ];
266 after = [ "local-fs.target" "ipfs-init.service" ];
267 conflicts = [ "ipfs.service" "ipfs-offline.service"];
268 };
269
270 };
271}