From 07a3d1e6b85cd05ea8dde02d5e44ca6810b82e72 Mon Sep 17 00:00:00 2001 From: "sascha.koenig" Date: Tue, 18 Nov 2025 15:26:59 +0100 Subject: [PATCH] zammad-hr setup finished --- .../services/containers/zammad-hr.nix | 131 +++++++++++++----- hosts/AZ-CLD-1/services/postgres.nix | 1 + secrets/zammad-hr-env.age | 19 ++- 3 files changed, 105 insertions(+), 46 deletions(-) diff --git a/hosts/AZ-CLD-1/services/containers/zammad-hr.nix b/hosts/AZ-CLD-1/services/containers/zammad-hr.nix index a4f1bed..a642a46 100644 --- a/hosts/AZ-CLD-1/services/containers/zammad-hr.nix +++ b/hosts/AZ-CLD-1/services/containers/zammad-hr.nix @@ -4,43 +4,49 @@ 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 + ipOffset = 40; + + # Domain-Konfiguration + zammadDomain = "hr-ticket.az-gruppe.com"; + sharedEnvironment = { - MEMCACHE_SERVERS = "${serviceName}-memcached:11211"; + MEMCACHE_SERVERS = "zammad-memcached:11211"; POSTGRESQL_DB = "zammad_${instanceName}"; - POSTGRESQL_HOST = "10.89.0.1"; # Host PostgreSQL + POSTGRESQL_HOST = "10.89.0.1"; POSTGRESQL_USER = "zammad_${instanceName}"; POSTGRESQL_PORT = "5432"; POSTGRESQL_OPTIONS = "?pool=50"; - REDIS_URL = "redis://${serviceName}-redis:6379"; + 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 = "${serviceName}-elasticsearch"; + 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 = { @@ -56,6 +62,7 @@ in { extraOptions = [ "--ip=${ipBase}.${toString ipOffset}" "--network=web" + "--network-alias=zammad-elasticsearch" ]; ports = ["127.0.0.1:${toString elasticsearchPort}:9200"]; }; @@ -67,6 +74,7 @@ in { extraOptions = [ "--ip=${ipBase}.${toString (ipOffset + 1)}" "--network=web" + "--network-alias=zammad-memcached" ]; }; @@ -77,23 +85,7 @@ in { 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" + "--network-alias=zammad-redis" ]; }; @@ -109,6 +101,7 @@ in { "--ip=${ipBase}.${toString (ipOffset + 4)}" "--network=web" "--add-host=postgres:10.89.0.1" + "--network-alias=zammad-railsserver" ]; }; @@ -139,6 +132,7 @@ in { "--ip=${ipBase}.${toString (ipOffset + 6)}" "--network=web" "--add-host=postgres:10.89.0.1" + "--network-alias=zammad-websocket" ]; }; @@ -165,7 +159,6 @@ in { environment = sharedEnvironment; environmentFiles = [envFileCommon envFileProd]; volumes = [ - "${serviceName}_backup:/var/tmp/zammad" "${serviceName}_storage:/opt/zammad/storage:ro" "/var/backup/${serviceName}:/var/tmp/zammad:rw" ]; @@ -179,6 +172,64 @@ in { }; }; + # 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 = { @@ -194,12 +245,10 @@ in { 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:" @@ -218,7 +267,7 @@ in { }; }; - # Traefik configuration + # Traefik configuration with proper headers services.traefik.dynamicConfigOptions.http = { services.${serviceName}.loadBalancer.servers = [ { @@ -226,13 +275,23 @@ in { } ]; + middlewares."${serviceName}-headers".headers = { + customRequestHeaders = { + X-Forwarded-Proto = "https"; + X-Forwarded-Port = "443"; + X-Forwarded-Host = zammadDomain; + X-Real-IP = ""; + }; + }; + routers.${serviceName} = { - rule = "Host(`hr-ticket.az-gruppe.com`)"; # HR-spezifische Domain + rule = "Host(`${zammadDomain}`)"; tls = { certResolver = "ionos"; }; service = serviceName; entrypoints = "websecure"; + middlewares = ["${serviceName}-headers"]; }; }; } diff --git a/hosts/AZ-CLD-1/services/postgres.nix b/hosts/AZ-CLD-1/services/postgres.nix index b521124..add0488 100644 --- a/hosts/AZ-CLD-1/services/postgres.nix +++ b/hosts/AZ-CLD-1/services/postgres.nix @@ -115,6 +115,7 @@ host librechat_rag librechat_rag 10.89.0.0/24 scram-sha-256 host librechat_rag_dev librechat_rag_dev 10.89.1.0/24 scram-sha-256 host zammad_hr zammad_hr 10.89.0.0/24 scram-sha-256 + host postgres zammad_hr 10.89.0.0/24 scram-sha-256 host litellm litellm 10.89.0.0/24 scram-sha-256 # Deny all other connections diff --git a/secrets/zammad-hr-env.age b/secrets/zammad-hr-env.age index 037df96..0c3b4b8 100644 --- a/secrets/zammad-hr-env.age +++ b/secrets/zammad-hr-env.age @@ -1,12 +1,11 @@ -----BEGIN AGE ENCRYPTED FILE----- -YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFpoVnNlZyBsc0xG -VzRzRGMxZ3h4MU1jZkZ4MEQzbUhaS0JYSzZ5eTZ5YXhWZEtTbWxFCnBrT2RKaEdN -U1BRRnZBWVYyWVpxTVhVM0d4VzhtbEEzNnoza0xpSEtQbXcKLT4gc3NoLWVkMjU1 -MTkgQ1NNeWhnIGNySDFJcjN4SEFReG5kUVBTdXVjWEpLRG8rSWVlSkI3WXovWE5q -N3dZQVEKaWdJVVJuWUM1MEZGaTQrV3c2YVRSTmRIYk4wdkVnRStuWG5UVGxjQ0FP -TQotPiAtV3l1P0sjLWdyZWFzZQpGb1gzY0pYNHBGRjUvK2JhWlBDdHZtSnROaU5X -aVc2L3hCSDJrUnJEdHNaUkFZT1cxK25IKzJza0VTb3hsTEFBCnkxSUJ2TFA3Sllz -UGVQY285djc2eXhtU2wrbldtTnkyRGZONgotLS0gV0Y5Qk53TXNhYUozTVZlQjRh -RHRNOWs2RS9jNzA0cW53b09ucGZQSURyYwomvbCCgvqCeAaza/IO2/ih4OOOWJHO -m579mV6FSO0Ak2+AdYVWC6ddOqQEuqPwTmAkebfrSJ3IajZtczXtj8k= +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFpoVnNlZyA2RTVP +SU1CQkxJSDlMNkxnTDlYODFHOGRoMjBoY2ZxU2tYaDIvM0haOEQwCnlkdWtlckkz +aEh4N0Nucm80MWZKUjZzOHFiUUV2WjUyQ2g3UndxQ1NaRFEKLT4gc3NoLWVkMjU1 +MTkgQ1NNeWhnIE1VZVFURmgreFJSTHJ0N1ZLYU5DSGdUdDh5S1hlN2p2b2dxbm9k +VjJWWDgKQkQzbjZIdG4zNC9lOU5BYy9mdVBZUzdRQXdlQzJRbGQzWG0zeXhmdG9w +SQotPiBSLWdyZWFzZSBwMCBUcTdoIEtSZntRCnQweWJyVUJvL1EKLS0tIEFKQmpt +NkdNVHZHa0lEeTRWWTVndWFBWEhRT1JWYkFjL01GbWExMWQzM0kKRR8z5f9okejC +26wOgfMoOYGDe1WKV+pN61IIMvodI6G/JBG2PGnzqDCwib7gzYetS6k/h4FJYMUu +VvTX93e8kw== -----END AGE ENCRYPTED FILE-----