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}