{ config, lib, pkgs, ... }: let # Instance identifier - macht es einfach, später eine zweite Instanz zu erstellen 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; # Zammad version zammadVersion = "6.5.2-22"; zammadImage = "ghcr.io/zammad/zammad:${zammadVersion}"; # IP-Basis für diese Instanz (HR: .30-.38, später IT: .40-.48) ipBase = "10.89.0"; ipOffset = 30; # HR startet bei .30, IT würde bei .40 starten # Shared environment variables sharedEnvironment = { MEMCACHE_SERVERS = "${serviceName}-memcached:11211"; POSTGRESQL_DB = "zammad_${instanceName}"; POSTGRESQL_HOST = "10.89.0.1"; # Host PostgreSQL POSTGRESQL_USER = "zammad_${instanceName}"; POSTGRESQL_PORT = "5432"; POSTGRESQL_OPTIONS = "?pool=50"; REDIS_URL = "redis://${serviceName}-redis:6379"; TZ = "Europe/Berlin"; BACKUP_DIR = "/var/tmp/zammad"; BACKUP_TIME = "03:00"; HOLD_DAYS = "10"; ELASTICSEARCH_ENABLED = "true"; ELASTICSEARCH_HOST = "${serviceName}-elasticsearch"; ELASTICSEARCH_PORT = "9200"; ELASTICSEARCH_NAMESPACE = "zammad_${instanceName}"; NGINX_PORT = "8080"; }; 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" ]; 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" ]; }; containers."${serviceName}-redis" = { image = "redis:7.4.6-alpine"; autoStart = true; volumes = ["${serviceName}_redis:/data"]; extraOptions = [ "--ip=${ipBase}.${toString (ipOffset + 2)}" "--network=web" ]; }; containers."${serviceName}-init" = { image = zammadImage; autoStart = false; cmd = ["zammad-init"]; environment = sharedEnvironment; environmentFiles = [envFileCommon envFileProd]; volumes = ["${serviceName}_storage:/opt/zammad/storage"]; dependsOn = ["${serviceName}-memcached" "${serviceName}-redis"]; extraOptions = [ "--ip=${ipBase}.${toString (ipOffset + 3)}" "--network=web" "--add-host=postgres:10.89.0.1" "--user=0:0" "--restart=on-failure" ]; }; 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" ]; }; 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" ]; }; 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}_backup:/var/tmp/zammad" "${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" ]; }; }; # 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)" # Ensure backup directory exists mkdir -p "$BACKUP_DIR" chown root:root "$BACKUP_DIR" chmod 750 "$BACKUP_DIR" # Remove backups older than HOLD_DAYS ${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 services.traefik.dynamicConfigOptions.http = { services.${serviceName}.loadBalancer.servers = [ { url = "http://localhost:${toString servicePort}/"; } ]; routers.${serviceName} = { rule = "Host(`hr-ticket.az-gruppe.com`)"; # HR-spezifische Domain tls = { certResolver = "ionos"; }; service = serviceName; entrypoints = "websecure"; }; }; }