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}