{ config, lib, pkgs, ... }: let instanceName = "hr"; serviceName = "zammad-${instanceName}"; portUtils = import ../../../../lib/port-utils.nix {inherit lib;}; servicePort = portUtils.getPort serviceName "AZ-CLD-1"; elasticsearchPort = portUtils.getPort "${serviceName}-elasticsearch" "AZ-CLD-1"; envFileProd = config.age.secrets."${serviceName}-env-prod".path; envFileCommon = config.age.secrets."${serviceName}-env".path; zammadVersion = "6.5.2-22"; zammadImage = "ghcr.io/zammad/zammad:${zammadVersion}"; ipBase = "10.89.0"; ipOffset = 40; # Domain-Konfiguration zammadDomain = "hr-ticket.az-gruppe.com"; sharedEnvironment = { MEMCACHE_SERVERS = "zammad-memcached:11211"; POSTGRESQL_DB = "zammad_${instanceName}"; POSTGRESQL_HOST = "10.89.0.1"; POSTGRESQL_USER = "zammad_${instanceName}"; POSTGRESQL_PORT = "5432"; POSTGRESQL_OPTIONS = "?pool=50"; REDIS_URL = "redis://zammad-redis:6379"; TZ = "Europe/Berlin"; BACKUP_DIR = "/var/tmp/zammad"; BACKUP_TIME = "03:00"; HOLD_DAYS = "10"; ELASTICSEARCH_ENABLED = "true"; ELASTICSEARCH_HOST = "zammad-elasticsearch"; ELASTICSEARCH_PORT = "9200"; ELASTICSEARCH_NAMESPACE = "zammad_${instanceName}"; NGINX_PORT = "8080"; # CSRF & Reverse Proxy Settings NGINX_SERVER_SCHEME = "https"; NGINX_SERVER_NAME = zammadDomain; ZAMMAD_HTTP_TYPE = "https"; ZAMMAD_FQDN = zammadDomain; RAILS_TRUSTED_PROXIES = "['127.0.0.1', '::1', '10.89.0.0/24']"; }; in { virtualisation.oci-containers = { containers."${serviceName}-elasticsearch" = { image = "elasticsearch:8.19.6"; autoStart = true; volumes = ["${serviceName}_elasticsearch:/usr/share/elasticsearch/data"]; environment = { "discovery.type" = "single-node"; "xpack.security.enabled" = "false"; ES_JAVA_OPTS = "-Xms1g -Xmx1g"; }; extraOptions = [ "--ip=${ipBase}.${toString ipOffset}" "--network=web" "--network-alias=zammad-elasticsearch" ]; ports = ["127.0.0.1:${toString elasticsearchPort}:9200"]; }; containers."${serviceName}-memcached" = { image = "memcached:1.6.39-alpine"; autoStart = true; cmd = ["memcached" "-m" "256M"]; extraOptions = [ "--ip=${ipBase}.${toString (ipOffset + 1)}" "--network=web" "--network-alias=zammad-memcached" ]; }; containers."${serviceName}-redis" = { image = "redis:7.4.6-alpine"; autoStart = true; volumes = ["${serviceName}_redis:/data"]; extraOptions = [ "--ip=${ipBase}.${toString (ipOffset + 2)}" "--network=web" "--network-alias=zammad-redis" ]; }; containers."${serviceName}-railsserver" = { image = zammadImage; autoStart = true; cmd = ["zammad-railsserver"]; environment = sharedEnvironment; environmentFiles = [envFileCommon envFileProd]; volumes = ["${serviceName}_storage:/opt/zammad/storage"]; dependsOn = ["${serviceName}-memcached" "${serviceName}-redis" "${serviceName}-elasticsearch"]; extraOptions = [ "--ip=${ipBase}.${toString (ipOffset + 4)}" "--network=web" "--add-host=postgres:10.89.0.1" "--network-alias=zammad-railsserver" ]; }; containers."${serviceName}-scheduler" = { image = zammadImage; autoStart = true; cmd = ["zammad-scheduler"]; environment = sharedEnvironment; environmentFiles = [envFileCommon envFileProd]; volumes = ["${serviceName}_storage:/opt/zammad/storage"]; dependsOn = ["${serviceName}-memcached" "${serviceName}-redis"]; extraOptions = [ "--ip=${ipBase}.${toString (ipOffset + 5)}" "--network=web" "--add-host=postgres:10.89.0.1" ]; }; containers."${serviceName}-websocket" = { image = zammadImage; autoStart = true; cmd = ["zammad-websocket"]; environment = sharedEnvironment; environmentFiles = [envFileCommon envFileProd]; volumes = ["${serviceName}_storage:/opt/zammad/storage"]; dependsOn = ["${serviceName}-memcached" "${serviceName}-redis"]; extraOptions = [ "--ip=${ipBase}.${toString (ipOffset + 6)}" "--network=web" "--add-host=postgres:10.89.0.1" "--network-alias=zammad-websocket" ]; }; containers."${serviceName}-nginx" = { image = zammadImage; autoStart = true; cmd = ["zammad-nginx"]; environment = sharedEnvironment; environmentFiles = [envFileCommon envFileProd]; volumes = ["${serviceName}_storage:/opt/zammad/storage"]; dependsOn = ["${serviceName}-railsserver"]; ports = ["127.0.0.1:${toString servicePort}:8080"]; extraOptions = [ "--ip=${ipBase}.${toString (ipOffset + 7)}" "--network=web" "--add-host=postgres:10.89.0.1" ]; }; containers."${serviceName}-backup" = { image = zammadImage; autoStart = true; cmd = ["zammad-backup"]; environment = sharedEnvironment; environmentFiles = [envFileCommon envFileProd]; volumes = [ "${serviceName}_storage:/opt/zammad/storage:ro" "/var/backup/${serviceName}:/var/tmp/zammad:rw" ]; dependsOn = ["${serviceName}-memcached" "${serviceName}-redis"]; extraOptions = [ "--ip=${ipBase}.${toString (ipOffset + 8)}" "--network=web" "--add-host=postgres:10.89.0.1" "--user=0:0" ]; }; }; # Init als oneshot systemd-Service systemd.services."${serviceName}-init" = { description = "Zammad ${instanceName} Database Initialization"; after = [ "podman-${serviceName}-memcached.service" "podman-${serviceName}-redis.service" "podman-${serviceName}-elasticsearch.service" ]; requires = [ "podman-${serviceName}-memcached.service" "podman-${serviceName}-redis.service" ]; wantedBy = []; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; User = "root"; Group = "root"; }; script = '' set -euo pipefail echo "Starting Zammad ${instanceName} database initialization..." ${pkgs.podman}/bin/podman run --rm \ --name ${serviceName}-init-oneshot \ --network web \ --ip ${ipBase}.${toString (ipOffset + 3)} \ --add-host=postgres:10.89.0.1 \ --user 0:0 \ --env-file ${envFileCommon} \ --env-file ${envFileProd} \ --env MEMCACHE_SERVERS=zammad-memcached:11211 \ --env POSTGRESQL_DB=zammad_${instanceName} \ --env POSTGRESQL_HOST=10.89.0.1 \ --env POSTGRESQL_USER=zammad_${instanceName} \ --env POSTGRESQL_PORT=5432 \ --env POSTGRESQL_OPTIONS='?pool=50' \ --env REDIS_URL=redis://zammad-redis:6379 \ --env TZ=Europe/Berlin \ --env ELASTICSEARCH_ENABLED=true \ --env ELASTICSEARCH_HOST=zammad-elasticsearch \ --env ELASTICSEARCH_PORT=9200 \ --env ELASTICSEARCH_NAMESPACE=zammad_${instanceName} \ --env NGINX_SERVER_SCHEME=https \ --env NGINX_SERVER_NAME=${zammadDomain} \ --env ZAMMAD_HTTP_TYPE=https \ --env ZAMMAD_FQDN=${zammadDomain} \ -v ${serviceName}_storage:/opt/zammad/storage \ ${zammadImage} \ zammad-init echo "Zammad ${instanceName} initialization completed successfully" ''; }; # Backup retention service systemd.services."${serviceName}-backup-cleanup" = { serviceConfig = { Type = "oneshot"; User = "root"; Group = "root"; }; script = '' set -euo pipefail BACKUP_DIR="/var/backup/${serviceName}" HOLD_DAYS=10 echo "Starting ${serviceName} backup cleanup at $(date)" mkdir -p "$BACKUP_DIR" chown root:root "$BACKUP_DIR" chmod 750 "$BACKUP_DIR" ${pkgs.findutils}/bin/find "$BACKUP_DIR" -type f -name "*.gz" -mtime +$HOLD_DAYS -delete echo "Current backups:" ls -lah "$BACKUP_DIR" || echo "No backups found" echo "${serviceName} backup cleanup completed at $(date)" ''; }; systemd.timers."${serviceName}-backup-cleanup" = { wantedBy = ["timers.target"]; timerConfig = { OnCalendar = "*-*-* 04:00:00"; RandomizedDelaySec = "30m"; Persistent = true; }; }; # Traefik configuration with proper headers services.traefik.dynamicConfigOptions.http = { services.${serviceName}.loadBalancer.servers = [ { url = "http://localhost:${toString servicePort}/"; } ]; middlewares."${serviceName}-headers".headers = { customRequestHeaders = { X-Forwarded-Proto = "https"; X-Forwarded-Port = "443"; X-Forwarded-Host = zammadDomain; X-Real-IP = ""; }; }; routers.${serviceName} = { rule = "Host(`${zammadDomain}`)"; tls = { certResolver = "ionos"; }; service = serviceName; entrypoints = "websecure"; middlewares = ["${serviceName}-headers"]; }; }; }