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