at 23.11-pre 9.7 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.services.zammad; 7 settingsFormat = pkgs.formats.yaml { }; 8 filterNull = filterAttrs (_: v: v != null); 9 serviceConfig = { 10 Type = "simple"; 11 Restart = "always"; 12 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; 26in 27{ 28 29 options = { 30 services.zammad = { 31 enable = mkEnableOption (lib.mdDoc "Zammad, a web-based, open source user support/ticketing solution"); 32 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 }; 39 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 }; 47 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 }; 54 55 openPorts = mkOption { 56 type = types.bool; 57 default = false; 58 description = lib.mdDoc "Whether to open firewall ports for Zammad"; 59 }; 60 61 port = mkOption { 62 type = types.port; 63 default = 3000; 64 description = lib.mdDoc "Web service port."; 65 }; 66 67 websocketPort = mkOption { 68 type = types.port; 69 default = 6042; 70 description = lib.mdDoc "Websocket service port."; 71 }; 72 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 }; 80 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 }; 97 98 port = mkOption { 99 type = types.nullOr types.port; 100 default = null; 101 description = lib.mdDoc "Database port. Use `null` for default port."; 102 }; 103 104 name = mkOption { 105 type = types.str; 106 default = "zammad"; 107 description = lib.mdDoc '' 108 Database name. 109 ''; 110 }; 111 112 user = mkOption { 113 type = types.nullOr types.str; 114 default = "zammad"; 115 description = lib.mdDoc "Database user."; 116 }; 117 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 }; 126 127 createLocally = mkOption { 128 type = types.bool; 129 default = true; 130 description = lib.mdDoc "Whether to create a local database automatically."; 131 }; 132 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 }; 147 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. 155 156 Zammad uses `secret_key_base` to encrypt 157 the cookie store, which contains session data, and to digest 158 user auth tokens. 159 160 Needs to be a 64 byte long string of hexadecimal 161 characters. You can generate one by running 162 163 ``` 164 openssl rand -hex 64 >/path/to/secret_key_base_file 165 ``` 166 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 }; 173 174 config = mkIf cfg.enable { 175 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 }; 191 192 networking.firewall.allowedTCPPorts = mkIf cfg.openPorts [ 193 config.services.zammad.port 194 config.services.zammad.websocketPort 195 ]; 196 197 users.users.zammad = { 198 isSystemUser = true; 199 home = cfg.dataDir; 200 group = "zammad"; 201 }; 202 203 users.groups.zammad = { }; 204 205 assertions = [ 206 { 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"; 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 ]; 215 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 }; 227 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 ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; }; 235 } 236 ]; 237 }; 238 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 ''} 278 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 }; 301 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 }; 310 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 }; 321 322 meta.maintainers = with lib.maintainers; [ garbas taeer ]; 323}