286 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			286 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
{ config, lib, pkgs, ... }:
 | 
						|
with lib;
 | 
						|
let
 | 
						|
  inherit (pkgs) ipfs runCommand makeWrapper;
 | 
						|
 | 
						|
  cfg = config.services.ipfs;
 | 
						|
 | 
						|
  ipfsFlags = toString ([
 | 
						|
    (optionalString  cfg.autoMount                   "--mount")
 | 
						|
    #(optionalString  cfg.autoMigrate                 "--migrate")
 | 
						|
    (optionalString  cfg.enableGC                    "--enable-gc")
 | 
						|
    (optionalString (cfg.serviceFdlimit != null)     "--manage-fdlimit=false")
 | 
						|
    (optionalString (cfg.defaultMode == "offline")   "--offline")
 | 
						|
    (optionalString (cfg.defaultMode == "norouting") "--routing=none")
 | 
						|
  ] ++ cfg.extraFlags);
 | 
						|
 | 
						|
  defaultDataDir = if versionAtLeast config.system.stateVersion "17.09" then
 | 
						|
    "/var/lib/ipfs" else
 | 
						|
    "/var/lib/ipfs/.ipfs";
 | 
						|
 | 
						|
  # Wrapping the ipfs binary with the environment variable IPFS_PATH set to dataDir because we can't set it in the user environment
 | 
						|
  wrapped = runCommand "ipfs" { buildInputs = [ makeWrapper ]; preferLocalBuild = true; } ''
 | 
						|
    mkdir -p "$out/bin"
 | 
						|
    makeWrapper "${ipfs}/bin/ipfs" "$out/bin/ipfs" \
 | 
						|
      --set IPFS_PATH ${cfg.dataDir} \
 | 
						|
      --prefix PATH : /run/wrappers/bin
 | 
						|
  '';
 | 
						|
 | 
						|
 | 
						|
  commonEnv = {
 | 
						|
    environment.IPFS_PATH = cfg.dataDir;
 | 
						|
    path = [ wrapped ];
 | 
						|
    serviceConfig.User = cfg.user;
 | 
						|
    serviceConfig.Group = cfg.group;
 | 
						|
  };
 | 
						|
 | 
						|
  baseService = recursiveUpdate commonEnv {
 | 
						|
    wants = [ "ipfs-init.service" ];
 | 
						|
    # NB: migration must be performed prior to pre-start, else we get the failure message!
 | 
						|
    preStart = ''
 | 
						|
      ipfs repo fsck # workaround for BUG #4212 (https://github.com/ipfs/go-ipfs/issues/4214)
 | 
						|
    '' + optionalString cfg.autoMount ''
 | 
						|
      ipfs --local config Mounts.FuseAllowOther --json true
 | 
						|
      ipfs --local config Mounts.IPFS ${cfg.ipfsMountDir}
 | 
						|
      ipfs --local config Mounts.IPNS ${cfg.ipnsMountDir}
 | 
						|
    '' + concatStringsSep "\n" (collect
 | 
						|
          isString
 | 
						|
          (mapAttrsRecursive
 | 
						|
            (path: value:
 | 
						|
            # Using heredoc below so that the value is never improperly quoted
 | 
						|
            ''
 | 
						|
              read value <<EOF
 | 
						|
              ${builtins.toJSON value}
 | 
						|
              EOF
 | 
						|
              ipfs --local config --json "${concatStringsSep "." path}" "$value"
 | 
						|
            '')
 | 
						|
            ({ Addresses.API = cfg.apiAddress;
 | 
						|
               Addresses.Gateway = cfg.gatewayAddress;
 | 
						|
               Addresses.Swarm = cfg.swarmAddress;
 | 
						|
            } //
 | 
						|
            cfg.extraConfig))
 | 
						|
        );
 | 
						|
    serviceConfig = {
 | 
						|
      ExecStart = "${wrapped}/bin/ipfs daemon ${ipfsFlags}";
 | 
						|
      Restart = "on-failure";
 | 
						|
      RestartSec = 1;
 | 
						|
    } // optionalAttrs (cfg.serviceFdlimit != null) { LimitNOFILE = cfg.serviceFdlimit; };
 | 
						|
  };
 | 
						|
in {
 | 
						|
 | 
						|
  ###### interface
 | 
						|
 | 
						|
  options = {
 | 
						|
 | 
						|
    services.ipfs = {
 | 
						|
 | 
						|
      enable = mkEnableOption "Interplanetary File System (WARNING: may cause severe network degredation)";
 | 
						|
 | 
						|
      user = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        default = "ipfs";
 | 
						|
        description = "User under which the IPFS daemon runs";
 | 
						|
      };
 | 
						|
 | 
						|
      group = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        default = "ipfs";
 | 
						|
        description = "Group under which the IPFS daemon runs";
 | 
						|
      };
 | 
						|
 | 
						|
      dataDir = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        default = defaultDataDir;
 | 
						|
        description = "The data dir for IPFS";
 | 
						|
      };
 | 
						|
 | 
						|
      defaultMode = mkOption {
 | 
						|
        type = types.enum [ "online" "offline" "norouting" ];
 | 
						|
        default = "online";
 | 
						|
        description = "systemd service that is enabled by default";
 | 
						|
      };
 | 
						|
 | 
						|
      /*
 | 
						|
      autoMigrate = mkOption {
 | 
						|
        type = types.bool;
 | 
						|
        default = false;
 | 
						|
        description = ''
 | 
						|
          Whether IPFS should try to migrate the file system automatically.
 | 
						|
 | 
						|
          The daemon will need to be able to download a binary from https://ipfs.io to perform the migration.
 | 
						|
        '';
 | 
						|
      };
 | 
						|
      */
 | 
						|
 | 
						|
      autoMount = mkOption {
 | 
						|
        type = types.bool;
 | 
						|
        default = false;
 | 
						|
        description = "Whether IPFS should try to mount /ipfs and /ipns at startup.";
 | 
						|
      };
 | 
						|
 | 
						|
      ipfsMountDir = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        default = "/ipfs";
 | 
						|
        description = "Where to mount the IPFS namespace to";
 | 
						|
      };
 | 
						|
 | 
						|
      ipnsMountDir = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        default = "/ipns";
 | 
						|
        description = "Where to mount the IPNS namespace to";
 | 
						|
      };
 | 
						|
 | 
						|
      gatewayAddress = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        default = "/ip4/127.0.0.1/tcp/8080";
 | 
						|
        description = "Where the IPFS Gateway can be reached";
 | 
						|
      };
 | 
						|
 | 
						|
      apiAddress = mkOption {
 | 
						|
        type = types.str;
 | 
						|
        default = "/ip4/127.0.0.1/tcp/5001";
 | 
						|
        description = "Where IPFS exposes its API to";
 | 
						|
      };
 | 
						|
 | 
						|
      swarmAddress = mkOption {
 | 
						|
        type = types.listOf types.str;
 | 
						|
        default = [ "/ip4/0.0.0.0/tcp/4001" "/ip6/::/tcp/4001" ];
 | 
						|
        description = "Where IPFS listens for incoming p2p connections";
 | 
						|
      };
 | 
						|
 | 
						|
      enableGC = mkOption {
 | 
						|
        type = types.bool;
 | 
						|
        default = false;
 | 
						|
        description = "Whether to enable automatic garbage collection";
 | 
						|
      };
 | 
						|
 | 
						|
      emptyRepo = mkOption {
 | 
						|
        type = types.bool;
 | 
						|
        default = false;
 | 
						|
        description = "If set to true, the repo won't be initialized with help files";
 | 
						|
      };
 | 
						|
 | 
						|
      extraConfig = mkOption {
 | 
						|
        type = types.attrs;
 | 
						|
        description = ''
 | 
						|
          Attrset of daemon configuration to set using <command>ipfs config</command>, every time the daemon starts.
 | 
						|
          These are applied last, so may override configuration set by other options in this module.
 | 
						|
          Keep in mind that this configuration is stateful; i.e., unsetting anything in here does not reset the value to the default!
 | 
						|
        '';
 | 
						|
        default = {};
 | 
						|
        example = {
 | 
						|
          Datastore.StorageMax = "100GB";
 | 
						|
          Discovery.MDNS.Enabled = false;
 | 
						|
          Bootstrap = [
 | 
						|
            "/ip4/128.199.219.111/tcp/4001/ipfs/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu"
 | 
						|
            "/ip4/162.243.248.213/tcp/4001/ipfs/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm"
 | 
						|
          ];
 | 
						|
          Swarm.AddrFilters = null;
 | 
						|
        };
 | 
						|
 | 
						|
      };
 | 
						|
 | 
						|
      extraFlags = mkOption {
 | 
						|
        type = types.listOf types.str;
 | 
						|
        description = "Extra flags passed to the IPFS daemon";
 | 
						|
        default = [];
 | 
						|
      };
 | 
						|
 | 
						|
      localDiscovery = mkOption {
 | 
						|
        type = types.bool;
 | 
						|
        description = ''Whether to enable local discovery for the ipfs daemon.
 | 
						|
          This will allow ipfs to scan ports on your local network. Some hosting services will ban you if you do this.
 | 
						|
        '';
 | 
						|
        default = true;
 | 
						|
      };
 | 
						|
 | 
						|
      serviceFdlimit = mkOption {
 | 
						|
        type = types.nullOr types.int;
 | 
						|
        default = null;
 | 
						|
        description = "The fdlimit for the IPFS systemd unit or <literal>null</literal> to have the daemon attempt to manage it";
 | 
						|
        example = 64*1024;
 | 
						|
      };
 | 
						|
 | 
						|
    };
 | 
						|
  };
 | 
						|
 | 
						|
  ###### implementation
 | 
						|
 | 
						|
  config = mkIf cfg.enable {
 | 
						|
    environment.systemPackages = [ wrapped ];
 | 
						|
    environment.etc."fuse.conf" = mkIf cfg.autoMount { text = ''
 | 
						|
      user_allow_other
 | 
						|
    ''; };
 | 
						|
 | 
						|
    users.users = mkIf (cfg.user == "ipfs") {
 | 
						|
      ipfs = {
 | 
						|
        group = cfg.group;
 | 
						|
        home = cfg.dataDir;
 | 
						|
        createHome = false;
 | 
						|
        uid = config.ids.uids.ipfs;
 | 
						|
        description = "IPFS daemon user";
 | 
						|
      };
 | 
						|
    };
 | 
						|
 | 
						|
    users.groups = mkIf (cfg.group == "ipfs") {
 | 
						|
      ipfs.gid = config.ids.gids.ipfs;
 | 
						|
    };
 | 
						|
 | 
						|
    systemd.tmpfiles.rules = [
 | 
						|
      "d '${cfg.dataDir}' - ${cfg.user} ${cfg.group} - -"
 | 
						|
    ] ++ optionals cfg.autoMount [
 | 
						|
      "d '${cfg.ipfsMountDir}' - ${cfg.user} ${cfg.group} - -"
 | 
						|
      "d '${cfg.ipnsMountDir}' - ${cfg.user} ${cfg.group} - -"
 | 
						|
    ];
 | 
						|
 | 
						|
    systemd.services.ipfs-init = recursiveUpdate commonEnv {
 | 
						|
      description = "IPFS Initializer";
 | 
						|
 | 
						|
      after = [ "local-fs.target" ];
 | 
						|
      before = [ "ipfs.service" "ipfs-offline.service" "ipfs-norouting.service" ];
 | 
						|
 | 
						|
      script = ''
 | 
						|
        if [[ ! -f ${cfg.dataDir}/config ]]; then
 | 
						|
          ipfs init ${optionalString cfg.emptyRepo "-e"} \
 | 
						|
            ${optionalString (! cfg.localDiscovery) "--profile=server"}
 | 
						|
        else
 | 
						|
          ${if cfg.localDiscovery
 | 
						|
            then "ipfs config profile apply local-discovery"
 | 
						|
            else "ipfs config profile apply server"
 | 
						|
          }
 | 
						|
        fi
 | 
						|
      '';
 | 
						|
 | 
						|
      serviceConfig = {
 | 
						|
        Type = "oneshot";
 | 
						|
        RemainAfterExit = true;
 | 
						|
      };
 | 
						|
    };
 | 
						|
 | 
						|
    # TODO These 3 definitions possibly be further abstracted through use of a function
 | 
						|
    # like: mutexServices "ipfs" [ "", "offline", "norouting" ] { ... shared conf here ... }
 | 
						|
 | 
						|
    systemd.services.ipfs = recursiveUpdate baseService {
 | 
						|
      description = "IPFS Daemon";
 | 
						|
      wantedBy = mkIf (cfg.defaultMode == "online") [ "multi-user.target" ];
 | 
						|
      after = [ "network.target" "local-fs.target" "ipfs-init.service" ];
 | 
						|
      conflicts = [ "ipfs-offline.service" "ipfs-norouting.service"];
 | 
						|
    };
 | 
						|
 | 
						|
    systemd.services.ipfs-offline = recursiveUpdate baseService {
 | 
						|
      description = "IPFS Daemon (offline mode)";
 | 
						|
      wantedBy = mkIf (cfg.defaultMode == "offline") [ "multi-user.target" ];
 | 
						|
      after = [ "local-fs.target" "ipfs-init.service" ];
 | 
						|
      conflicts = [ "ipfs.service" "ipfs-norouting.service"];
 | 
						|
    };
 | 
						|
 | 
						|
    systemd.services.ipfs-norouting = recursiveUpdate baseService {
 | 
						|
      description = "IPFS Daemon (no routing mode)";
 | 
						|
      wantedBy = mkIf (cfg.defaultMode == "norouting") [ "multi-user.target" ];
 | 
						|
      after = [ "local-fs.target" "ipfs-init.service" ];
 | 
						|
      conflicts = [ "ipfs.service" "ipfs-offline.service"];
 | 
						|
    };
 | 
						|
 | 
						|
  };
 | 
						|
}
 |