1{
2 config,
3 lib,
4 pkgs,
5 options,
6 ...
7}:
8let
9 cfg = config.services.ipfs-cluster;
10
11 # secret is by envvar, not flag
12 initFlags = toString [
13 (lib.optionalString (cfg.initPeers != [ ]) "--peers")
14 (lib.strings.concatStringsSep "," cfg.initPeers)
15 ];
16in
17{
18 options = {
19
20 services.ipfs-cluster = {
21
22 enable = lib.mkEnableOption "Pinset orchestration for IPFS - requires ipfs daemon to be useful";
23
24 consensus = lib.mkOption {
25 type = lib.types.enum [
26 "raft"
27 "crdt"
28 ];
29 description = "Consensus protocol - 'raft' or 'crdt'. <https://cluster.ipfs.io/documentation/guides/consensus/>";
30 };
31
32 dataDir = lib.mkOption {
33 type = lib.types.str;
34 default = "/var/lib/ipfs-cluster";
35 description = "The data dir for ipfs-cluster.";
36 };
37
38 initPeers = lib.mkOption {
39 type = lib.types.listOf lib.types.str;
40 default = [ ];
41 description = "Peer addresses to initialize with on first run.";
42 };
43
44 openSwarmPort = lib.mkOption {
45 type = lib.types.bool;
46 default = false;
47 description = "Open swarm port, secured by the cluster secret. This does not expose the API or proxy. <https://cluster.ipfs.io/documentation/guides/security/>";
48 };
49
50 secretFile = lib.mkOption {
51 type = lib.types.nullOr lib.types.path;
52 default = null;
53 description = ''
54 File containing the cluster secret in the format of EnvironmentFile as described by
55 {manpage}`systemd.exec(5)`. For example:
56 <programlisting>
57 CLUSTER_SECRET=<replaceable>...</replaceable>
58 </programlisting>
59
60 If null, a new secret will be generated on first run and stored in the data directory.
61 A secret in the correct format can also be generated by: `openssl rand -hex 32`
62 '';
63 };
64 };
65 };
66
67 config = lib.mkIf cfg.enable {
68 assertions = [
69 {
70 assertion = cfg.enable -> config.services.kubo.enable;
71 message = "ipfs-cluster requires ipfs - configure and enable services.kubo";
72 }
73 ];
74
75 environment.systemPackages = [ pkgs.ipfs-cluster ];
76
77 systemd.tmpfiles.rules = [
78 "d '${cfg.dataDir}' - ${config.services.kubo.user} ${config.services.kubo.group} - -"
79 ];
80
81 systemd.services.ipfs-cluster-init = {
82 path = [
83 "/run/wrappers"
84 pkgs.ipfs-cluster
85 ];
86 environment.IPFS_CLUSTER_PATH = cfg.dataDir;
87 wantedBy = [ "default.target" ];
88
89 serviceConfig = {
90 ExecStart = [
91 "${lib.getExe' pkgs.ipfs-cluster "ipfs-cluster-service"} init --consensus ${cfg.consensus} ${initFlags}"
92 ];
93 Type = "oneshot";
94 RemainAfterExit = true;
95 User = config.services.kubo.user;
96 Group = config.services.kubo.group;
97 EnvironmentFile = lib.mkIf (cfg.secretFile != null) cfg.secretFile;
98 };
99 # only run once (= when the data directory is empty)
100 unitConfig.ConditionDirectoryNotEmpty = "!${cfg.dataDir}";
101 };
102
103 systemd.services.ipfs-cluster = {
104 environment.IPFS_CLUSTER_PATH = cfg.dataDir;
105 wantedBy = [ "multi-user.target" ];
106
107 wants = [ "ipfs-cluster-init.service" ];
108 after = [ "ipfs-cluster-init.service" ];
109
110 serviceConfig = {
111 Type = "notify";
112 ExecStart = [ "${lib.getExe' pkgs.ipfs-cluster "ipfs-cluster-service"} daemon" ];
113 User = config.services.kubo.user;
114 Group = config.services.kubo.group;
115 };
116 };
117
118 networking.firewall.allowedTCPPorts = lib.mkIf cfg.openSwarmPort [ 9096 ];
119 };
120
121 meta = {
122 maintainers = with lib.maintainers; [
123 sorki
124 ];
125 };
126}