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}