diff --git a/modules/default.nix b/modules/default.nix index 58f65b9..7556bcd 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -9,6 +9,7 @@ let hercules-ci-agent = import ./hercules-ci-agent; hydra = import ./hydra; hyprspace = import ./hyprspace; + ipfs-cluster = import ./ipfs-cluster; maintenance = import ./maintenance; minimal = import ./minimal; motd = import ./motd; diff --git a/modules/ipfs-cluster/default.nix b/modules/ipfs-cluster/default.nix new file mode 100644 index 0000000..a6c12a2 --- /dev/null +++ b/modules/ipfs-cluster/default.nix @@ -0,0 +1,121 @@ +{ config, lib, pkgs, options, ... }: +with lib; +let + cfg = config.services.ipfs-cluster; + opt = options.services.ipfs-cluster; + + # secret is by envvar, not flag + initFlags = toString [ + (optionalString (cfg.initPeers != [ ]) "--peers") + (lib.strings.concatStringsSep "," cfg.initPeers) + ]; +in { + + ###### interface + + options = { + + services.ipfs-cluster = { + + enable = mkEnableOption + "Pinset orchestration for IPFS - requires ipfs daemon to be useful"; + + user = mkOption { + type = types.str; + default = "ipfs"; + description = "User under which the ipfs-cluster daemon runs."; + }; + + group = mkOption { + type = types.str; + default = "ipfs"; + description = "Group under which the ipfs-cluster daemon runs."; + }; + + consensus = mkOption { + type = types.enum [ "raft" "crdt" ]; + description = "Consensus protocol - 'raft' or 'crdt'. https://cluster.ipfs.io/documentation/guides/consensus/"; + }; + + dataDir = mkOption { + type = types.str; + default = "/var/lib/ipfs-cluster"; + description = "The data dir for ipfs-cluster."; + }; + + initPeers = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "Peer addresses to initialize with on first run."; + }; + + openSwarmPort = mkOption { + type = types.bool; + description = "Open swarm port, secured by the cluster secret. This does not expose the API or proxy. https://cluster.ipfs.io/documentation/guides/security/"; + }; + + secretFile = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + File containing the cluster secret in the format of EnvironmentFile as described by + systemd.exec + 5. For example: + + CLUSTER_SECRET=... + + + if null, a new secret will be generated on first run. + A secret in the correct format can also be generated by: openssl rand -hex 32 + ''; + }; + }; + }; + + ###### implementation + + config = mkIf cfg.enable { + environment.systemPackages = [ pkgs.ipfs-cluster ]; + + + systemd.tmpfiles.rules = + [ "d '${cfg.dataDir}' - ${cfg.user} ${cfg.group} - -" ]; + + systemd.services.ipfs-cluster-init = { + path = [ "/run/wrappers" pkgs.ipfs-cluster ]; + environment.IPFS_CLUSTER_PATH = cfg.dataDir; + wantedBy = [ "default.target" ]; + + serviceConfig = { + # "" clears exec list (man systemd.service -> execStart) + ExecStart = [ + "" + "${pkgs.ipfs-cluster}/bin/ipfs-cluster-service init --consensus ${cfg.consensus} ${initFlags}" + ]; + Type = "oneshot"; + RemainAfterExit = true; + User = cfg.user; + Group = cfg.group; + } // optionalAttrs (cfg.secretFile != null) { + EnvironmentFile = cfg.secretFile; + }; + unitConfig.ConditionDirectoryNotEmpty = "!${cfg.dataDir}"; + }; + + systemd.services.ipfs-cluster = { + environment.IPFS_CLUSTER_PATH = cfg.dataDir; + wantedBy = [ "multi-user.target" ]; + + wants = [ "ipfs-cluster-init.service" ]; + after = [ "ipfs-cluster-init.service" ]; + + serviceConfig = { + ExecStart = + [ "" "${pkgs.ipfs-cluster}/bin/ipfs-cluster-service daemon" ]; + User = cfg.user; + Group = cfg.group; + }; + }; + networking.firewall.allowedTCPPorts = mkIf cfg.openSwarmPort [ 9096 ]; + }; +}