1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.services.centrifugo;
9
10 settingsFormat = pkgs.formats.json { };
11
12 configFile = settingsFormat.generate "centrifugo.json" cfg.settings;
13in
14{
15 options.services.centrifugo = {
16 enable = lib.mkEnableOption "Centrifugo messaging server";
17
18 package = lib.mkPackageOption pkgs "centrifugo" { };
19
20 settings = lib.mkOption {
21 type = settingsFormat.type;
22 default = { };
23 description = ''
24 Declarative Centrifugo configuration. See the [Centrifugo
25 documentation] for a list of options.
26
27 [Centrifugo documentation]: https://centrifugal.dev/docs/server/configuration
28 '';
29 };
30
31 credentials = lib.mkOption {
32 type = lib.types.attrsOf lib.types.path;
33 default = { };
34 example = {
35 CENTRIFUGO_UNI_GRPC_TLS_KEY = "/run/keys/centrifugo-uni-grpc-tls.key";
36 };
37 description = ''
38 Environment variables with absolute paths to credentials files to load
39 on service startup.
40 '';
41 };
42
43 environmentFiles = lib.mkOption {
44 type = lib.types.listOf lib.types.path;
45 default = [ ];
46 description = ''
47 Files to load environment variables from. Options set via environment
48 variables take precedence over {option}`settings`.
49
50 See the [Centrifugo documentation] for the environment variable name
51 format.
52
53 [Centrifugo documentation]: https://centrifugal.dev/docs/server/configuration#os-environment-variables
54 '';
55 };
56
57 extraGroups = lib.mkOption {
58 type = lib.types.listOf lib.types.str;
59 default = [ ];
60 example = [ "redis-centrifugo" ];
61 description = ''
62 Additional groups for the systemd service.
63 '';
64 };
65 };
66
67 config = lib.mkIf cfg.enable {
68 assertions = [
69 {
70 assertion =
71 (lib.versionAtLeast cfg.package.version "6") -> (!(cfg.settings ? name) && !(cfg.settings ? port));
72 message = "`services.centrifugo.settings` is v5 config, must be compatible with centrifugo v6 config format";
73 }
74 ];
75
76 systemd.services.centrifugo = {
77 description = "Centrifugo messaging server";
78 wantedBy = [ "multi-user.target" ];
79 after = [ "network.target" ];
80
81 serviceConfig = {
82 Type = "exec";
83
84 ExecStartPre = "${lib.getExe cfg.package} checkconfig --config ${configFile}";
85 ExecStart = "${lib.getExe cfg.package} --config ${configFile}";
86 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
87
88 Restart = "always";
89 RestartSec = "1s";
90
91 # Copy files to the credentials directory with file name being the
92 # environment variable name. Note that "%d" specifier expands to the
93 # path of the credentials directory.
94 LoadCredential = lib.mapAttrsToList (name: value: "${name}:${value}") cfg.credentials;
95 Environment = lib.mapAttrsToList (name: _: "${name}=%d/${name}") cfg.credentials;
96
97 EnvironmentFile = cfg.environmentFiles;
98
99 SupplementaryGroups = cfg.extraGroups;
100
101 DynamicUser = true;
102 UMask = "0077";
103
104 ProtectHome = true;
105 ProtectProc = "invisible";
106 ProcSubset = "pid";
107 ProtectClock = true;
108 ProtectHostname = true;
109 ProtectControlGroups = true;
110 ProtectKernelLogs = true;
111 ProtectKernelModules = true;
112 ProtectKernelTunables = true;
113 PrivateUsers = true;
114 PrivateDevices = true;
115 RestrictRealtime = true;
116 RestrictNamespaces = true;
117 RestrictAddressFamilies = [
118 "AF_INET"
119 "AF_INET6"
120 "AF_UNIX"
121 ];
122 DeviceAllow = [ "" ];
123 DevicePolicy = "closed";
124 CapabilityBoundingSet = [ "" ];
125 MemoryDenyWriteExecute = true;
126 LockPersonality = true;
127 SystemCallArchitectures = "native";
128 SystemCallErrorNumber = "EPERM";
129 SystemCallFilter = [
130 "@system-service"
131 "~@privileged"
132 ];
133 };
134 };
135 };
136}