vuls: init at 0.27.0
[NixPkgs.git] / nixos / modules / services / development / zammad.nix
blob43ae9a34b4043f3373bfa89ff0d34ff0123e6c42
1 { config, lib, pkgs, ... }:
2 let
3   cfg = config.services.zammad;
4   settingsFormat = pkgs.formats.yaml { };
5   filterNull = lib.filterAttrs (_: v: v != null);
6   serviceConfig = {
7     Type = "simple";
8     Restart = "always";
10     User = "zammad";
11     Group = "zammad";
12     PrivateTmp = true;
13     StateDirectory = "zammad";
14     WorkingDirectory = cfg.dataDir;
15   };
16   environment = {
17     RAILS_ENV = "production";
18     NODE_ENV = "production";
19     RAILS_SERVE_STATIC_FILES = "true";
20     RAILS_LOG_TO_STDOUT = "true";
21     REDIS_URL = "redis://${cfg.redis.host}:${toString cfg.redis.port}";
22   };
23   databaseConfig = settingsFormat.generate "database.yml" cfg.database.settings;
27   options = {
28     services.zammad = {
29       enable = lib.mkEnableOption "Zammad, a web-based, open source user support/ticketing solution";
31       package = lib.mkPackageOption pkgs "zammad" { };
33       dataDir = lib.mkOption {
34         type = lib.types.path;
35         default = "/var/lib/zammad";
36         description = ''
37           Path to a folder that will contain Zammad working directory.
38         '';
39       };
41       host = lib.mkOption {
42         type = lib.types.str;
43         default = "127.0.0.1";
44         example = "192.168.23.42";
45         description = "Host address.";
46       };
48       openPorts = lib.mkOption {
49         type = lib.types.bool;
50         default = false;
51         description = "Whether to open firewall ports for Zammad";
52       };
54       port = lib.mkOption {
55         type = lib.types.port;
56         default = 3000;
57         description = "Web service port.";
58       };
60       websocketPort = lib.mkOption {
61         type = lib.types.port;
62         default = 6042;
63         description = "Websocket service port.";
64       };
66       redis = {
67         createLocally = lib.mkOption {
68           type = lib.types.bool;
69           default = true;
70           description = "Whether to create a local redis automatically.";
71         };
73         name = lib.mkOption {
74           type = lib.types.str;
75           default = "zammad";
76           description = ''
77             Name of the redis server. Only used if `createLocally` is set to true.
78           '';
79         };
81         host = lib.mkOption {
82           type = lib.types.str;
83           default = "localhost";
84           description = ''
85             Redis server address.
86           '';
87         };
89         port = lib.mkOption {
90           type = lib.types.port;
91           default = 6379;
92           description = "Port of the redis server.";
93         };
94       };
96       database = {
97         type = lib.mkOption {
98           type = lib.types.enum [ "PostgreSQL" "MySQL" ];
99           default = "PostgreSQL";
100           example = "MySQL";
101           description = "Database engine to use.";
102         };
104         host = lib.mkOption {
105           type = lib.types.nullOr lib.types.str;
106           default = {
107             PostgreSQL = "/run/postgresql";
108             MySQL = "localhost";
109           }.${cfg.database.type};
110           defaultText = lib.literalExpression ''
111             {
112               PostgreSQL = "/run/postgresql";
113               MySQL = "localhost";
114             }.''${config.services.zammad.database.type};
115           '';
116           description = ''
117             Database host address.
118           '';
119         };
121         port = lib.mkOption {
122           type = lib.types.nullOr lib.types.port;
123           default = null;
124           description = "Database port. Use `null` for default port.";
125         };
127         name = lib.mkOption {
128           type = lib.types.str;
129           default = "zammad";
130           description = ''
131             Database name.
132           '';
133         };
135         user = lib.mkOption {
136           type = lib.types.nullOr lib.types.str;
137           default = "zammad";
138           description = "Database user.";
139         };
141         passwordFile = lib.mkOption {
142           type = lib.types.nullOr lib.types.path;
143           default = null;
144           example = "/run/keys/zammad-dbpassword";
145           description = ''
146             A file containing the password for {option}`services.zammad.database.user`.
147           '';
148         };
150         createLocally = lib.mkOption {
151           type = lib.types.bool;
152           default = true;
153           description = "Whether to create a local database automatically.";
154         };
156         settings = lib.mkOption {
157           type = settingsFormat.type;
158           default = { };
159           example = lib.literalExpression ''
160             {
161             }
162           '';
163           description = ''
164             The {file}`database.yml` configuration file as key value set.
165             See \<TODO\>
166             for list of configuration parameters.
167           '';
168         };
169       };
171       secretKeyBaseFile = lib.mkOption {
172         type = lib.types.nullOr lib.types.path;
173         default = null;
174         example = "/run/keys/secret_key_base";
175         description = ''
176           The path to a file containing the
177           `secret_key_base` secret.
179           Zammad uses `secret_key_base` to encrypt
180           the cookie store, which contains session data, and to digest
181           user auth tokens.
183           Needs to be a 64 byte long string of hexadecimal
184           characters. You can generate one by running
186           ```
187           openssl rand -hex 64 >/path/to/secret_key_base_file
188           ```
190           This should be a string, not a nix path, since nix paths are
191           copied into the world-readable nix store.
192         '';
193       };
194     };
195   };
197   config = lib.mkIf cfg.enable {
199     services.zammad.database.settings = {
200       production = lib.mapAttrs (_: v: lib.mkDefault v) (filterNull {
201         adapter = {
202           PostgreSQL = "postgresql";
203           MySQL = "mysql2";
204         }.${cfg.database.type};
205         database = cfg.database.name;
206         pool = 50;
207         timeout = 5000;
208         encoding = "utf8";
209         username = cfg.database.user;
210         host = cfg.database.host;
211         port = cfg.database.port;
212       });
213     };
215     networking.firewall.allowedTCPPorts = lib.mkIf cfg.openPorts [
216       config.services.zammad.port
217       config.services.zammad.websocketPort
218     ];
220     users.users.zammad = {
221       isSystemUser = true;
222       home = cfg.dataDir;
223       group = "zammad";
224     };
226     users.groups.zammad = { };
228     assertions = [
229       {
230         assertion = cfg.database.createLocally -> cfg.database.user == "zammad" && cfg.database.name == "zammad";
231         message = "services.zammad.database.user must be set to \"zammad\" if services.zammad.database.createLocally is set to true";
232       }
233       {
234         assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
235         message = "a password cannot be specified if services.zammad.database.createLocally is set to true";
236       }
237       {
238         assertion = cfg.redis.createLocally -> cfg.redis.host == "localhost";
239         message = "the redis host must be localhost if services.zammad.redis.createLocally is set to true";
240       }
241     ];
243     services.mysql = lib.optionalAttrs (cfg.database.createLocally && cfg.database.type == "MySQL") {
244       enable = true;
245       package = lib.mkDefault pkgs.mariadb;
246       ensureDatabases = [ cfg.database.name ];
247       ensureUsers = [
248         {
249           name = cfg.database.user;
250           ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
251         }
252       ];
253     };
255     services.postgresql = lib.optionalAttrs (cfg.database.createLocally && cfg.database.type == "PostgreSQL") {
256       enable = true;
257       ensureDatabases = [ cfg.database.name ];
258       ensureUsers = [
259         {
260           name = cfg.database.user;
261           ensureDBOwnership = true;
262         }
263       ];
264     };
266     services.redis = lib.optionalAttrs cfg.redis.createLocally {
267       servers."${cfg.redis.name}" = {
268         enable = true;
269         port = cfg.redis.port;
270       };
271     };
273     systemd.services.zammad-web = {
274       inherit environment;
275       serviceConfig = serviceConfig // {
276         # loading all the gems takes time
277         TimeoutStartSec = 1200;
278       };
279       after = [
280         "network.target"
281         "postgresql.service"
282       ] ++ lib.optionals cfg.redis.createLocally [
283         "redis-${cfg.redis.name}.service"
284       ];
285       requires = [
286         "postgresql.service"
287       ];
288       description = "Zammad web";
289       wantedBy = [ "multi-user.target" ];
290       preStart = ''
291         # Blindly copy the whole project here.
292         chmod -R +w .
293         rm -rf ./public/assets/
294         rm -rf ./tmp/*
295         rm -rf ./log/*
296         cp -r --no-preserve=owner ${cfg.package}/* .
297         chmod -R +w .
298         # config file
299         cp ${databaseConfig} ./config/database.yml
300         chmod -R +w .
301         ${lib.optionalString (cfg.database.passwordFile != null) ''
302         {
303           echo -n "  password: "
304           cat ${cfg.database.passwordFile}
305         } >> ./config/database.yml
306         ''}
307         ${lib.optionalString (cfg.secretKeyBaseFile != null) ''
308         {
309           echo "production: "
310           echo -n "  secret_key_base: "
311           cat ${cfg.secretKeyBaseFile}
312         } > ./config/secrets.yml
313         ''}
315         if [ `${config.services.postgresql.package}/bin/psql \
316                   --host ${cfg.database.host} \
317                   ${lib.optionalString
318                     (cfg.database.port != null)
319                     "--port ${toString cfg.database.port}"} \
320                   --username ${cfg.database.user} \
321                   --dbname ${cfg.database.name} \
322                   --command "SELECT COUNT(*) FROM pg_class c \
323                             JOIN pg_namespace s ON s.oid = c.relnamespace \
324                             WHERE s.nspname NOT IN ('pg_catalog', 'pg_toast', 'information_schema') \
325                               AND s.nspname NOT LIKE 'pg_temp%';" | sed -n 3p` -eq 0 ]; then
326           echo "Initialize database"
327           ./bin/rake --no-system db:migrate
328           ./bin/rake --no-system db:seed
329         else
330           echo "Migrate database"
331           ./bin/rake --no-system db:migrate
332         fi
333         echo "Done"
334       '';
335       script = "./script/rails server -b ${cfg.host} -p ${toString cfg.port}";
336     };
338     systemd.services.zammad-websocket = {
339       inherit serviceConfig environment;
340       after = [ "zammad-web.service" ];
341       requires = [ "zammad-web.service" ];
342       description = "Zammad websocket";
343       wantedBy = [ "multi-user.target" ];
344       script = "./script/websocket-server.rb -b ${cfg.host} -p ${toString cfg.websocketPort} start";
345     };
347     systemd.services.zammad-worker = {
348       inherit serviceConfig environment;
349       after = [ "zammad-web.service" ];
350       requires = [ "zammad-web.service" ];
351       description = "Zammad background worker";
352       wantedBy = [ "multi-user.target" ];
353       script = "./script/background-worker.rb start";
354     };
355   };
357   meta.maintainers = with lib.maintainers; [ taeer netali ];