1 { config, lib, pkgs, ... }:
6 cfg = config.services.zammad;
7 settingsFormat = pkgs.formats.yaml { };
8 filterNull = filterAttrs (_: v: v != null);
16 StateDirectory = "zammad";
17 WorkingDirectory = cfg.dataDir;
20 RAILS_ENV = "production";
21 NODE_ENV = "production";
22 RAILS_SERVE_STATIC_FILES = "true";
23 RAILS_LOG_TO_STDOUT = "true";
25 databaseConfig = settingsFormat.generate "database.yml" cfg.database.settings;
31 enable = mkEnableOption (lib.mdDoc "Zammad, a web-based, open source user support/ticketing solution.");
35 default = pkgs.zammad;
36 defaultText = literalExpression "pkgs.zammad";
37 description = lib.mdDoc "Zammad package to use.";
42 default = "/var/lib/zammad";
43 description = lib.mdDoc ''
44 Path to a folder that will contain Zammad working directory.
50 default = "127.0.0.1";
51 example = "192.168.23.42";
52 description = lib.mdDoc "Host address.";
55 openPorts = mkOption {
58 description = lib.mdDoc "Whether to open firewall ports for Zammad";
64 description = lib.mdDoc "Web service port.";
67 websocketPort = mkOption {
70 description = lib.mdDoc "Websocket service port.";
75 type = types.enum [ "PostgreSQL" "MySQL" ];
76 default = "PostgreSQL";
78 description = lib.mdDoc "Database engine to use.";
82 type = types.nullOr types.str;
84 PostgreSQL = "/run/postgresql";
86 }.${cfg.database.type};
87 defaultText = literalExpression ''
89 PostgreSQL = "/run/postgresql";
91 }.''${config.services.zammad.database.type};
93 description = lib.mdDoc ''
94 Database host address.
99 type = types.nullOr types.port;
101 description = lib.mdDoc "Database port. Use `null` for default port.";
107 description = lib.mdDoc ''
113 type = types.nullOr types.str;
115 description = lib.mdDoc "Database user.";
118 passwordFile = mkOption {
119 type = types.nullOr types.path;
121 example = "/run/keys/zammad-dbpassword";
122 description = lib.mdDoc ''
123 A file containing the password for {option}`services.zammad.database.user`.
127 createLocally = mkOption {
130 description = lib.mdDoc "Whether to create a local database automatically.";
133 settings = mkOption {
134 type = settingsFormat.type;
136 example = literalExpression ''
140 description = lib.mdDoc ''
141 The {file}`database.yml` configuration file as key value set.
143 for list of configuration parameters.
148 secretKeyBaseFile = mkOption {
149 type = types.nullOr types.path;
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
160 Needs to be a 64 byte long string of hexadecimal
161 characters. You can generate one by running
164 openssl rand -hex 64 >/path/to/secret_key_base_file
167 This should be a string, not a nix path, since nix paths are
168 copied into the world-readable nix store.
174 config = mkIf cfg.enable {
176 services.zammad.database.settings = {
177 production = mapAttrs (_: v: mkDefault v) (filterNull {
179 PostgreSQL = "postgresql";
181 }.${cfg.database.type};
182 database = cfg.database.name;
186 username = cfg.database.user;
187 host = cfg.database.host;
188 port = cfg.database.port;
192 networking.firewall.allowedTCPPorts = mkIf cfg.openPorts [
193 config.services.zammad.port
194 config.services.zammad.websocketPort
197 users.users.zammad = {
203 users.groups.zammad = { };
207 assertion = cfg.database.createLocally -> cfg.database.user == "zammad";
208 message = "services.zammad.database.user must be set to \"zammad\" if services.zammad.database.createLocally is set to true";
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";
216 services.mysql = optionalAttrs (cfg.database.createLocally && cfg.database.type == "MySQL") {
218 package = mkDefault pkgs.mariadb;
219 ensureDatabases = [ cfg.database.name ];
222 name = cfg.database.user;
223 ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
228 services.postgresql = optionalAttrs (cfg.database.createLocally && cfg.database.type == "PostgreSQL") {
230 ensureDatabases = [ cfg.database.name ];
233 name = cfg.database.user;
234 ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
239 systemd.services.zammad-web = {
241 serviceConfig = serviceConfig // {
242 # loading all the gems takes time
243 TimeoutStartSec = 1200;
252 description = "Zammad web";
253 wantedBy = [ "multi-user.target" ];
255 # Blindly copy the whole project here.
257 rm -rf ./public/assets/*
260 cp -r --no-preserve=owner ${cfg.package}/* .
263 cp ${databaseConfig} ./config/database.yml
265 ${optionalString (cfg.database.passwordFile != null) ''
267 echo -n " password: "
268 cat ${cfg.database.passwordFile}
269 } >> ./config/database.yml
271 ${optionalString (cfg.secretKeyBaseFile != null) ''
274 echo -n " secret_key_base: "
275 cat ${cfg.secretKeyBaseFile}
276 } > ./config/secrets.yml
279 if [ `${config.services.postgresql.package}/bin/psql \
280 --host ${cfg.database.host} \
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
294 echo "Migrate database"
295 ./bin/rake --no-system db:migrate
299 script = "./script/rails server -b ${cfg.host} -p ${toString cfg.port}";
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";
311 systemd.services.zammad-scheduler = {
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";
322 meta.maintainers = with lib.maintainers; [ garbas taeer ];