diff options
author | Rok Garbas <rok@garbas.si> | 2022-02-28 00:57:11 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-02-28 00:57:11 +0100 |
commit | 993c35991bf2203c34a9c90add6aeda232ccfad0 (patch) | |
tree | 2b4630ccf7263f16da39a1706d75fb88506d12d3 /nixos | |
parent | cfe7ba29c548eb8ca4ade36ac6fe807184023d08 (diff) | |
parent | 13e35662ccfc4b516dca5e2802cb57ec6a08c57a (diff) | |
download | nixlib-993c35991bf2203c34a9c90add6aeda232ccfad0.tar nixlib-993c35991bf2203c34a9c90add6aeda232ccfad0.tar.gz nixlib-993c35991bf2203c34a9c90add6aeda232ccfad0.tar.bz2 nixlib-993c35991bf2203c34a9c90add6aeda232ccfad0.tar.lz nixlib-993c35991bf2203c34a9c90add6aeda232ccfad0.tar.xz nixlib-993c35991bf2203c34a9c90add6aeda232ccfad0.tar.zst nixlib-993c35991bf2203c34a9c90add6aeda232ccfad0.zip |
Merge pull request #157693 from Radvendii/zammad
zammad: init at 5.0.2
Diffstat (limited to 'nixos')
-rw-r--r-- | nixos/modules/module-list.nix | 1 | ||||
-rw-r--r-- | nixos/modules/services/development/zammad.nix | 323 | ||||
-rw-r--r-- | nixos/tests/all-tests.nix | 1 | ||||
-rw-r--r-- | nixos/tests/zammad.nix | 60 |
4 files changed, 385 insertions, 0 deletions
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 29fcc920f421..c6f4ec5f08c3 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -396,6 +396,7 @@ ./services/development/jupyterhub/default.nix ./services/development/rstudio-server/default.nix ./services/development/lorri.nix + ./services/development/zammad.nix ./services/display-managers/greetd.nix ./services/editors/emacs.nix ./services/editors/infinoted.nix diff --git a/nixos/modules/services/development/zammad.nix b/nixos/modules/services/development/zammad.nix new file mode 100644 index 000000000000..d457a6071873 --- /dev/null +++ b/nixos/modules/services/development/zammad.nix @@ -0,0 +1,323 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.zammad; + settingsFormat = pkgs.formats.yaml { }; + filterNull = filterAttrs (_: v: v != null); + serviceConfig = { + Type = "simple"; + Restart = "always"; + + User = "zammad"; + Group = "zammad"; + PrivateTmp = true; + StateDirectory = "zammad"; + WorkingDirectory = cfg.dataDir; + }; + environment = { + RAILS_ENV = "production"; + NODE_ENV = "production"; + RAILS_SERVE_STATIC_FILES = "true"; + RAILS_LOG_TO_STDOUT = "true"; + }; + databaseConfig = settingsFormat.generate "database.yml" cfg.database.settings; +in +{ + + options = { + services.zammad = { + enable = mkEnableOption "Zammad, a web-based, open source user support/ticketing solution."; + + package = mkOption { + type = types.package; + default = pkgs.zammad; + defaultText = literalExpression "pkgs.zammad"; + description = "Zammad package to use."; + }; + + dataDir = mkOption { + type = types.path; + default = "/var/lib/zammad"; + description = '' + Path to a folder that will contain Zammad working directory. + ''; + }; + + host = mkOption { + type = types.str; + default = "127.0.0.1"; + example = "192.168.23.42"; + description = "Host address."; + }; + + openPorts = mkOption { + type = types.bool; + default = false; + description = "Whether to open firewall ports for Zammad"; + }; + + port = mkOption { + type = types.port; + default = 3000; + description = "Web service port."; + }; + + websocketPort = mkOption { + type = types.port; + default = 6042; + description = "Websocket service port."; + }; + + database = { + type = mkOption { + type = types.enum [ "PostgreSQL" "MySQL" ]; + default = "PostgreSQL"; + example = "MySQL"; + description = "Database engine to use."; + }; + + host = mkOption { + type = types.nullOr types.str; + default = { + PostgreSQL = "/run/postgresql"; + MySQL = "localhost"; + }.${cfg.database.type}; + defaultText = literalExpression '' + { + PostgreSQL = "/run/postgresql"; + MySQL = "localhost"; + }.''${config.services.zammad.database.type}; + ''; + description = '' + Database host address. + ''; + }; + + port = mkOption { + type = types.nullOr types.port; + default = null; + description = "Database port. Use <literal>null</literal> for default port."; + }; + + name = mkOption { + type = types.str; + default = "zammad"; + description = '' + Database name. + ''; + }; + + user = mkOption { + type = types.nullOr types.str; + default = "zammad"; + description = "Database user."; + }; + + passwordFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/run/keys/zammad-dbpassword"; + description = '' + A file containing the password for <option>services.zammad.database.user</option>. + ''; + }; + + createLocally = mkOption { + type = types.bool; + default = true; + description = "Whether to create a local database automatically."; + }; + + settings = mkOption { + type = settingsFormat.type; + default = { }; + example = literalExpression '' + { + } + ''; + description = '' + The <filename>database.yml</filename> configuration file as key value set. + See <link xlink:href='TODO' /> + for list of configuration parameters. + ''; + }; + }; + + secretKeyBaseFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/run/keys/secret_key_base"; + description = '' + The path to a file containing the + <literal>secret_key_base</literal> secret. + + Zammad uses <literal>secret_key_base</literal> to encrypt + the cookie store, which contains session data, and to digest + user auth tokens. + + Needs to be a 64 byte long string of hexadecimal + characters. You can generate one by running + + <screen> + <prompt>$ </prompt>openssl rand -hex 64 >/path/to/secret_key_base_file + </screen> + + This should be a string, not a nix path, since nix paths are + copied into the world-readable nix store. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + + services.zammad.database.settings = { + production = mapAttrs (_: v: mkDefault v) (filterNull { + adapter = { + PostgreSQL = "postgresql"; + MySQL = "mysql2"; + }.${cfg.database.type}; + database = cfg.database.name; + pool = 50; + timeout = 5000; + encoding = "utf8"; + username = cfg.database.user; + host = cfg.database.host; + port = cfg.database.port; + }); + }; + + networking.firewall.allowedTCPPorts = mkIf cfg.openPorts [ + config.services.zammad.port + config.services.zammad.websocketPort + ]; + + users.users.zammad = { + isSystemUser = true; + home = cfg.dataDir; + group = "zammad"; + }; + + users.groups.zammad = { }; + + assertions = [ + { + assertion = cfg.database.createLocally -> cfg.database.user == "zammad"; + message = "services.zammad.database.user must be set to \"zammad\" if services.zammad.database.createLocally is set to true"; + } + { + assertion = cfg.database.createLocally -> cfg.database.passwordFile == null; + message = "a password cannot be specified if services.zammad.database.createLocally is set to true"; + } + ]; + + services.mysql = optionalAttrs (cfg.database.createLocally && cfg.database.type == "MySQL") { + enable = true; + package = mkDefault pkgs.mariadb; + ensureDatabases = [ cfg.database.name ]; + ensureUsers = [ + { + name = cfg.database.user; + ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; }; + } + ]; + }; + + services.postgresql = optionalAttrs (cfg.database.createLocally && cfg.database.type == "PostgreSQL") { + enable = true; + ensureDatabases = [ cfg.database.name ]; + ensureUsers = [ + { + name = cfg.database.user; + ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; }; + } + ]; + }; + + systemd.services.zammad-web = { + inherit environment; + serviceConfig = serviceConfig // { + # loading all the gems takes time + TimeoutStartSec = 1200; + }; + after = [ + "network.target" + "postgresql.service" + ]; + requires = [ + "postgresql.service" + ]; + description = "Zammad web"; + wantedBy = [ "multi-user.target" ]; + preStart = '' + # Blindly copy the whole project here. + chmod -R +w . + rm -rf ./public/assets/* + rm -rf ./tmp/* + rm -rf ./log/* + cp -r --no-preserve=owner ${cfg.package}/* . + chmod -R +w . + # config file + cp ${databaseConfig} ./config/database.yml + chmod -R +w . + ${optionalString (cfg.database.passwordFile != null) '' + { + echo -n " password: " + cat ${cfg.database.passwordFile} + } >> ./config/database.yml + ''} + ${optionalString (cfg.secretKeyBaseFile != null) '' + { + echo "production: " + echo -n " secret_key_base: " + cat ${cfg.secretKeyBaseFile} + } > ./config/secrets.yml + ''} + + if [ `${config.services.postgresql.package}/bin/psql \ + --host ${cfg.database.host} \ + ${optionalString + (cfg.database.port != null) + "--port ${toString cfg.database.port}"} \ + --username ${cfg.database.user} \ + --dbname ${cfg.database.name} \ + --command "SELECT COUNT(*) FROM pg_class c \ + JOIN pg_namespace s ON s.oid = c.relnamespace \ + WHERE s.nspname NOT IN ('pg_catalog', 'pg_toast', 'information_schema') \ + AND s.nspname NOT LIKE 'pg_temp%';" | sed -n 3p` -eq 0 ]; then + echo "Initialize database" + ./bin/rake --no-system db:migrate + ./bin/rake --no-system db:seed + else + echo "Migrate database" + ./bin/rake --no-system db:migrate + fi + echo "Done" + ''; + script = "./script/rails server -b ${cfg.host} -p ${toString cfg.port}"; + }; + + systemd.services.zammad-websocket = { + inherit serviceConfig environment; + after = [ "zammad-web.service" ]; + requires = [ "zammad-web.service" ]; + description = "Zammad websocket"; + wantedBy = [ "multi-user.target" ]; + script = "./script/websocket-server.rb -b ${cfg.host} -p ${toString cfg.websocketPort} start"; + }; + + systemd.services.zammad-scheduler = { + inherit environment; + serviceConfig = serviceConfig // { Type = "forking"; }; + after = [ "zammad-web.service" ]; + requires = [ "zammad-web.service" ]; + description = "Zammad scheduler"; + wantedBy = [ "multi-user.target" ]; + script = "./script/scheduler.rb start"; + }; + }; + + meta.maintainers = with lib.maintainers; [ garbas taeer ]; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index da94fc6d042d..5d03893a56bd 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -572,6 +572,7 @@ in xxh = handleTest ./xxh.nix {}; yabar = handleTest ./yabar.nix {}; yggdrasil = handleTest ./yggdrasil.nix {}; + zammad = handleTest ./zammad.nix {}; zfs = handleTest ./zfs.nix {}; zigbee2mqtt = handleTest ./zigbee2mqtt.nix {}; zoneminder = handleTest ./zoneminder.nix {}; diff --git a/nixos/tests/zammad.nix b/nixos/tests/zammad.nix new file mode 100644 index 000000000000..4e466f6e3b9b --- /dev/null +++ b/nixos/tests/zammad.nix @@ -0,0 +1,60 @@ +import ./make-test-python.nix ( + { lib, pkgs, ... }: + + { + name = "zammad"; + + meta.maintainers = with lib.maintainers; [ garbas taeer ]; + + nodes.machine = { config, ... }: { + services.zammad.enable = true; + services.zammad.secretKeyBaseFile = pkgs.writeText "secret" '' + 52882ef142066e09ab99ce816ba72522e789505caba224a52d750ec7dc872c2c371b2fd19f16b25dfbdd435a4dd46cb3df9f82eb63fafad715056bdfe25740d6 + ''; + + systemd.services.zammad-locale-cheat = + let cfg = config.services.zammad; in + { + serviceConfig = { + Type = "simple"; + Restart = "always"; + + User = "zammad"; + Group = "zammad"; + PrivateTmp = true; + StateDirectory = "zammad"; + WorkingDirectory = cfg.dataDir; + }; + wantedBy = [ "zammad-web.service" ]; + description = "Hack in the locale files so zammad doesn't try to access the internet"; + script = '' + mkdir -p ./config/translations + VERSION=$(cat ${cfg.package}/VERSION) + + # If these files are not in place, zammad will try to access the internet. + # For the test, we only need to supply en-us. + echo '[{"locale":"en-us","alias":"en","name":"English (United States)","active":true,"dir":"ltr"}]' \ + > ./config/locales-$VERSION.yml + echo '[{"locale":"en-us","format":"time","source":"date","target":"mm/dd/yyyy","target_initial":"mm/dd/yyyy"},{"locale":"en-us","format":"time","source":"timestamp","target":"mm/dd/yyyy HH:MM","target_initial":"mm/dd/yyyy HH:MM"}]' \ + > ./config/translations/en-us-$VERSION.yml + ''; + }; + }; + + testScript = '' + start_all() + machine.wait_for_unit("postgresql.service") + machine.wait_for_unit("zammad-web.service") + machine.wait_for_unit("zammad-websocket.service") + machine.wait_for_unit("zammad-scheduler.service") + # wait for zammad to fully come up + machine.sleep(120) + + # without the grep the command does not produce valid utf-8 for some reason + with subtest("welcome screen loads"): + machine.succeed( + "curl -sSfL http://localhost:3000/ | grep '<title>Zammad Helpdesk</title>'" + ) + ''; + } +) |