python312Packages.fnllm: 0.0.11 -> 0.0.12 (#364582)
[NixPkgs.git] / nixos / modules / services / development / zammad.nix
blobac659e9fa52714426828ea644a0a09627530a14a
2   config,
3   lib,
4   pkgs,
5   ...
6 }:
8 let
9   cfg = config.services.zammad;
10   settingsFormat = pkgs.formats.yaml { };
11   filterNull = lib.filterAttrs (_: v: v != null);
12   serviceConfig = {
13     Type = "simple";
14     Restart = "always";
16     User = cfg.user;
17     Group = cfg.group;
18     PrivateTmp = true;
19     StateDirectory = "zammad";
20     WorkingDirectory = package;
21   };
22   environment = {
23     RAILS_ENV = "production";
24     NODE_ENV = "production";
25     RAILS_SERVE_STATIC_FILES = "true";
26     RAILS_LOG_TO_STDOUT = "true";
27     REDIS_URL = "redis://${cfg.redis.host}:${toString cfg.redis.port}";
28   };
29   databaseConfig = settingsFormat.generate "database.yml" cfg.database.settings;
30   package = cfg.package.override {
31     dataDir = cfg.dataDir;
32   };
36   options = {
37     services.zammad = {
38       enable = lib.mkEnableOption "Zammad, a web-based, open source user support/ticketing solution";
40       package = lib.mkPackageOption pkgs "zammad" { };
42       user = lib.mkOption {
43         type = lib.types.str;
44         default = "zammad";
45         description = ''
46           Name of the Zammad user.
47         '';
48       };
50       group = lib.mkOption {
51         type = lib.types.str;
52         default = "zammad";
53         description = ''
54           Name of the Zammad group.
55         '';
56       };
58       dataDir = lib.mkOption {
59         type = lib.types.path;
60         default = "/var/lib/zammad";
61         description = ''
62           Path to a folder that will contain Zammad working directory.
63         '';
64       };
66       host = lib.mkOption {
67         type = lib.types.str;
68         default = "127.0.0.1";
69         example = "192.168.23.42";
70         description = "Host address.";
71       };
73       openPorts = lib.mkOption {
74         type = lib.types.bool;
75         default = false;
76         description = "Whether to open firewall ports for Zammad";
77       };
79       port = lib.mkOption {
80         type = lib.types.port;
81         default = 3000;
82         description = "Web service port.";
83       };
85       websocketPort = lib.mkOption {
86         type = lib.types.port;
87         default = 6042;
88         description = "Websocket service port.";
89       };
91       redis = {
92         createLocally = lib.mkOption {
93           type = lib.types.bool;
94           default = true;
95           description = "Whether to create a local redis automatically.";
96         };
98         name = lib.mkOption {
99           type = lib.types.str;
100           default = "zammad";
101           description = ''
102             Name of the redis server. Only used if `createLocally` is set to true.
103           '';
104         };
106         host = lib.mkOption {
107           type = lib.types.str;
108           default = "localhost";
109           description = ''
110             Redis server address.
111           '';
112         };
114         port = lib.mkOption {
115           type = lib.types.port;
116           default = 6379;
117           description = "Port of the redis server.";
118         };
119       };
121       database = {
122         host = lib.mkOption {
123           type = lib.types.str;
124           default = "/run/postgresql";
125           description = ''
126             Database host address.
127           '';
128         };
130         port = lib.mkOption {
131           type = lib.types.nullOr lib.types.port;
132           default = null;
133           description = "Database port. Use `null` for default port.";
134         };
136         name = lib.mkOption {
137           type = lib.types.str;
138           default = "zammad";
139           description = ''
140             Database name.
141           '';
142         };
144         user = lib.mkOption {
145           type = lib.types.nullOr lib.types.str;
146           default = "zammad";
147           description = "Database user.";
148         };
150         passwordFile = lib.mkOption {
151           type = lib.types.nullOr lib.types.path;
152           default = null;
153           example = "/run/keys/zammad-dbpassword";
154           description = ''
155             A file containing the password for {option}`services.zammad.database.user`.
156           '';
157         };
159         createLocally = lib.mkOption {
160           type = lib.types.bool;
161           default = true;
162           description = "Whether to create a local database automatically.";
163         };
165         settings = lib.mkOption {
166           type = settingsFormat.type;
167           default = { };
168           example = lib.literalExpression ''
169             {
170             }
171           '';
172           description = ''
173             The {file}`database.yml` configuration file as key value set.
174             See \<TODO\>
175             for list of configuration parameters.
176           '';
177         };
178       };
180       secretKeyBaseFile = lib.mkOption {
181         type = lib.types.nullOr lib.types.path;
182         default = null;
183         example = "/run/keys/secret_key_base";
184         description = ''
185           The path to a file containing the
186           `secret_key_base` secret.
188           Zammad uses `secret_key_base` to encrypt
189           the cookie store, which contains session data, and to digest
190           user auth tokens.
192           Needs to be a 64 byte long string of hexadecimal
193           characters. You can generate one by running
195           ```
196           openssl rand -hex 64 >/path/to/secret_key_base_file
197           ```
199           This should be a string, not a nix path, since nix paths are
200           copied into the world-readable nix store.
201         '';
202       };
203     };
204   };
206   config = lib.mkIf cfg.enable {
207     services.zammad.database.settings = {
208       production = lib.mapAttrs (_: v: lib.mkDefault v) (filterNull {
209         adapter = "postgresql";
210         database = cfg.database.name;
211         pool = 50;
212         timeout = 5000;
213         encoding = "utf8";
214         username = cfg.database.user;
215         host = cfg.database.host;
216         port = cfg.database.port;
217       });
218     };
220     networking.firewall.allowedTCPPorts = lib.mkIf cfg.openPorts [
221       config.services.zammad.port
222       config.services.zammad.websocketPort
223     ];
225     users.users.${cfg.user} = {
226       group = "${cfg.group}";
227       isSystemUser = true;
228     };
230     users.groups.${cfg.group} = { };
232     assertions = [
233       {
234         assertion =
235           cfg.database.createLocally -> cfg.database.user == "zammad" && cfg.database.name == "zammad";
236         message = "services.zammad.database.user must be set to \"zammad\" if services.zammad.database.createLocally is set to true";
237       }
238       {
239         assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
240         message = "a password cannot be specified if services.zammad.database.createLocally is set to true";
241       }
242       {
243         assertion = cfg.redis.createLocally -> cfg.redis.host == "localhost";
244         message = "the redis host must be localhost if services.zammad.redis.createLocally is set to true";
245       }
246     ];
248     services.postgresql = lib.optionalAttrs (cfg.database.createLocally) {
249       enable = true;
250       ensureDatabases = [ cfg.database.name ];
251       ensureUsers = [
252         {
253           name = cfg.database.user;
254           ensureDBOwnership = true;
255         }
256       ];
257     };
259     services.redis = lib.optionalAttrs cfg.redis.createLocally {
260       servers."${cfg.redis.name}" = {
261         enable = true;
262         port = cfg.redis.port;
263       };
264     };
266     systemd.services.zammad-web = {
267       inherit environment;
268       serviceConfig = serviceConfig // {
269         # loading all the gems takes time
270         TimeoutStartSec = 1200;
271       };
272       after =
273         [
274           "network.target"
275           "systemd-tmpfiles-setup.service"
276         ]
277         ++ lib.optionals (cfg.database.createLocally) [
278           "postgresql.service"
279         ]
280         ++ lib.optionals cfg.redis.createLocally [
281           "redis-${cfg.redis.name}.service"
282         ];
283       requires = lib.optionals (cfg.database.createLocally) [
284         "postgresql.service"
285       ];
286       description = "Zammad web";
287       wantedBy = [ "multi-user.target" ];
288       preStart = ''
289         # config file
290         cat ${databaseConfig} > ${cfg.dataDir}/config/database.yml
291         ${lib.optionalString (cfg.database.passwordFile != null) ''
292           {
293             echo -n "  password: "
294             cat ${cfg.database.passwordFile}
295           } >> ${cfg.dataDir}/config/database.yml
296         ''}
297         ${lib.optionalString (cfg.secretKeyBaseFile != null) ''
298           {
299             echo "production: "
300             echo -n "  secret_key_base: "
301             cat ${cfg.secretKeyBaseFile}
302           } > ${cfg.dataDir}/config/secrets.yml
303         ''}
305         # needed for cleanup
306         shopt -s extglob
308         # cleanup state directory from module before refactoring in
309         # https://github.com/NixOS/nixpkgs/pull/277456
310         if [[ -e ${cfg.dataDir}/node_modules ]]; then
311           rm -rf ${cfg.dataDir}/!("tmp"|"config"|"log"|"state_dir_migrated"|"db_seeded")
312           rm -rf ${cfg.dataDir}/config/!("database.yml"|"secrets.yml")
313           # state directory cleanup required --> zammad was already installed --> do not seed db
314           echo true > ${cfg.dataDir}/db_seeded
315         fi
317         SEEDED=$(cat ${cfg.dataDir}/db_seeded)
318         if [[ $SEEDED != "true" ]]; then
319           echo "Initialize database"
320           ./bin/rake --no-system db:migrate
321           ./bin/rake --no-system db:seed
322           echo true > ${cfg.dataDir}/db_seeded
323         else
324           echo "Migrate database"
325           ./bin/rake --no-system db:migrate
326         fi
327         echo "Done"
328       '';
329       script = "./script/rails server -b ${cfg.host} -p ${toString cfg.port}";
330     };
332     systemd.tmpfiles.rules = [
333       "d ${cfg.dataDir}                               0750 ${cfg.user} ${cfg.group} - -"
334       "d ${cfg.dataDir}/config                        0750 ${cfg.user} ${cfg.group} - -"
335       "d ${cfg.dataDir}/tmp                           0750 ${cfg.user} ${cfg.group} - -"
336       "d ${cfg.dataDir}/log                           0750 ${cfg.user} ${cfg.group} - -"
337       "f ${cfg.dataDir}/config/secrets.yml            0640 ${cfg.user} ${cfg.group} - -"
338       "f ${cfg.dataDir}/config/database.yml           0640 ${cfg.user} ${cfg.group} - -"
339       "f ${cfg.dataDir}/db_seeded                     0640 ${cfg.user} ${cfg.group} - -"
340     ];
342     systemd.services.zammad-websocket = {
343       inherit serviceConfig environment;
344       after = [ "zammad-web.service" ];
345       requires = [ "zammad-web.service" ];
346       description = "Zammad websocket";
347       wantedBy = [ "multi-user.target" ];
348       script = "./script/websocket-server.rb -b ${cfg.host} -p ${toString cfg.websocketPort} start";
349     };
351     systemd.services.zammad-worker = {
352       inherit serviceConfig environment;
353       after = [ "zammad-web.service" ];
354       requires = [ "zammad-web.service" ];
355       description = "Zammad background worker";
356       wantedBy = [ "multi-user.target" ];
357       script = "./script/background-worker.rb start";
358     };
359   };
361   meta.maintainers = with lib.maintainers; [
362     taeer
363     netali
364   ];