9 cfg = config.services.zammad;
10 settingsFormat = pkgs.formats.yaml { };
11 filterNull = lib.filterAttrs (_: v: v != null);
19 StateDirectory = "zammad";
20 WorkingDirectory = package;
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}";
29 databaseConfig = settingsFormat.generate "database.yml" cfg.database.settings;
30 package = cfg.package.override {
31 dataDir = cfg.dataDir;
38 enable = lib.mkEnableOption "Zammad, a web-based, open source user support/ticketing solution";
40 package = lib.mkPackageOption pkgs "zammad" { };
46 Name of the Zammad user.
50 group = lib.mkOption {
54 Name of the Zammad group.
58 dataDir = lib.mkOption {
59 type = lib.types.path;
60 default = "/var/lib/zammad";
62 Path to a folder that will contain Zammad working directory.
68 default = "127.0.0.1";
69 example = "192.168.23.42";
70 description = "Host address.";
73 openPorts = lib.mkOption {
74 type = lib.types.bool;
76 description = "Whether to open firewall ports for Zammad";
80 type = lib.types.port;
82 description = "Web service port.";
85 websocketPort = lib.mkOption {
86 type = lib.types.port;
88 description = "Websocket service port.";
92 createLocally = lib.mkOption {
93 type = lib.types.bool;
95 description = "Whether to create a local redis automatically.";
102 Name of the redis server. Only used if `createLocally` is set to true.
106 host = lib.mkOption {
107 type = lib.types.str;
108 default = "localhost";
110 Redis server address.
114 port = lib.mkOption {
115 type = lib.types.port;
117 description = "Port of the redis server.";
122 host = lib.mkOption {
123 type = lib.types.str;
124 default = "/run/postgresql";
126 Database host address.
130 port = lib.mkOption {
131 type = lib.types.nullOr lib.types.port;
133 description = "Database port. Use `null` for default port.";
136 name = lib.mkOption {
137 type = lib.types.str;
144 user = lib.mkOption {
145 type = lib.types.nullOr lib.types.str;
147 description = "Database user.";
150 passwordFile = lib.mkOption {
151 type = lib.types.nullOr lib.types.path;
153 example = "/run/keys/zammad-dbpassword";
155 A file containing the password for {option}`services.zammad.database.user`.
159 createLocally = lib.mkOption {
160 type = lib.types.bool;
162 description = "Whether to create a local database automatically.";
165 settings = lib.mkOption {
166 type = settingsFormat.type;
168 example = lib.literalExpression ''
173 The {file}`database.yml` configuration file as key value set.
175 for list of configuration parameters.
180 secretKeyBaseFile = lib.mkOption {
181 type = lib.types.nullOr lib.types.path;
183 example = "/run/keys/secret_key_base";
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
192 Needs to be a 64 byte long string of hexadecimal
193 characters. You can generate one by running
196 openssl rand -hex 64 >/path/to/secret_key_base_file
199 This should be a string, not a nix path, since nix paths are
200 copied into the world-readable nix store.
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;
214 username = cfg.database.user;
215 host = cfg.database.host;
216 port = cfg.database.port;
220 networking.firewall.allowedTCPPorts = lib.mkIf cfg.openPorts [
221 config.services.zammad.port
222 config.services.zammad.websocketPort
225 users.users.${cfg.user} = {
226 group = "${cfg.group}";
230 users.groups.${cfg.group} = { };
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";
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";
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";
248 services.postgresql = lib.optionalAttrs (cfg.database.createLocally) {
250 ensureDatabases = [ cfg.database.name ];
253 name = cfg.database.user;
254 ensureDBOwnership = true;
259 services.redis = lib.optionalAttrs cfg.redis.createLocally {
260 servers."${cfg.redis.name}" = {
262 port = cfg.redis.port;
266 systemd.services.zammad-web = {
268 serviceConfig = serviceConfig // {
269 # loading all the gems takes time
270 TimeoutStartSec = 1200;
275 "systemd-tmpfiles-setup.service"
277 ++ lib.optionals (cfg.database.createLocally) [
280 ++ lib.optionals cfg.redis.createLocally [
281 "redis-${cfg.redis.name}.service"
283 requires = lib.optionals (cfg.database.createLocally) [
286 description = "Zammad web";
287 wantedBy = [ "multi-user.target" ];
290 cat ${databaseConfig} > ${cfg.dataDir}/config/database.yml
291 ${lib.optionalString (cfg.database.passwordFile != null) ''
293 echo -n " password: "
294 cat ${cfg.database.passwordFile}
295 } >> ${cfg.dataDir}/config/database.yml
297 ${lib.optionalString (cfg.secretKeyBaseFile != null) ''
300 echo -n " secret_key_base: "
301 cat ${cfg.secretKeyBaseFile}
302 } > ${cfg.dataDir}/config/secrets.yml
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
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
324 echo "Migrate database"
325 ./bin/rake --no-system db:migrate
329 script = "./script/rails server -b ${cfg.host} -p ${toString cfg.port}";
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} - -"
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";
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";
361 meta.maintainers = with lib.maintainers; [