From 7bbcd5ae4e2021930f144841f2ac50aff3e5b0a3 Mon Sep 17 00:00:00 2001 From: "sascha.koenig" Date: Tue, 18 Nov 2025 11:15:08 +0100 Subject: [PATCH] +zammad-hr --- home/features/desktop/coding.nix | 12 +- hosts/AZ-CLD-1/secrets.nix | 6 + .../AZ-CLD-1/services/containers/default.nix | 1 + .../services/containers/zammad-hr.nix | 238 ++++++++++++++++++ hosts/AZ-CLD-1/services/postgres.nix | 11 +- lib/ports.nix | 2 + secrets.nix | 2 + secrets/zammad-hr-env-prod.age | 12 + secrets/zammad-hr-env.age | 12 + 9 files changed, 288 insertions(+), 8 deletions(-) create mode 100644 hosts/AZ-CLD-1/services/containers/zammad-hr.nix create mode 100644 secrets/zammad-hr-env-prod.age create mode 100644 secrets/zammad-hr-env.age diff --git a/home/features/desktop/coding.nix b/home/features/desktop/coding.nix index 075eec7..8358eed 100644 --- a/home/features/desktop/coding.nix +++ b/home/features/desktop/coding.nix @@ -123,9 +123,15 @@ in { show_edit_predictions = true; ssh_connections = [ { - args = ["-i" "~/.ssh/m3tam3re"]; - host = "152.53.85.162"; - nickname = "m3-atlas"; + args = ["-i" "~/.ssh/sascha.koenig"]; + host = "152.53.186.119"; + port = 2022; + nickname = "AZ-CLD-1"; + "projects" = [ + { + paths = ["/home/sascha.koenig/AZ-NIX"]; + } + ]; } ]; telemetry = { diff --git a/hosts/AZ-CLD-1/secrets.nix b/hosts/AZ-CLD-1/secrets.nix index 94f20fc..749d2f0 100644 --- a/hosts/AZ-CLD-1/secrets.nix +++ b/hosts/AZ-CLD-1/secrets.nix @@ -54,6 +54,12 @@ file = ../../secrets/zammad-secret.age; owner = "zammad"; }; + zammad-hr-env-prod = { + file = ../../secrets/zammad-hr-env-prod.age; + }; + zammad-hr-env = { + file = ../../secrets/zammad-hr-env.age; + }; }; }; } diff --git a/hosts/AZ-CLD-1/services/containers/default.nix b/hosts/AZ-CLD-1/services/containers/default.nix index db191f8..c2122ae 100644 --- a/hosts/AZ-CLD-1/services/containers/default.nix +++ b/hosts/AZ-CLD-1/services/containers/default.nix @@ -6,6 +6,7 @@ ./litellm.nix ./librechat-dev.nix ./portainer.nix + ./zammad-hr.nix ]; system.activationScripts.createPodmanNetworkWeb = lib.mkAfter '' if ! /run/current-system/sw/bin/podman network exists web; then diff --git a/hosts/AZ-CLD-1/services/containers/zammad-hr.nix b/hosts/AZ-CLD-1/services/containers/zammad-hr.nix new file mode 100644 index 0000000..a4f1bed --- /dev/null +++ b/hosts/AZ-CLD-1/services/containers/zammad-hr.nix @@ -0,0 +1,238 @@ +{ + 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"; + }; + }; +} diff --git a/hosts/AZ-CLD-1/services/postgres.nix b/hosts/AZ-CLD-1/services/postgres.nix index 06d5649..b521124 100644 --- a/hosts/AZ-CLD-1/services/postgres.nix +++ b/hosts/AZ-CLD-1/services/postgres.nix @@ -72,10 +72,10 @@ ALTER DATABASE vaultwarden OWNER to vaultwarden; ALTER DATABASE vaultwarden CONNECTION LIMIT 20; - CREATE USER zammad WITH ENCRYPTED PASSWORD 'zammad'; - CREATE DATABASE zammad; - ALTER DATABASE zammad OWNER to zammad; - ALTER DATABASE zammad CONNECTION LIMIT 50; + CREATE USER zammad-hr WITH ENCRYPTED PASSWORD 'zammad-hr'; + CREATE DATABASE zammad-hr; + ALTER DATABASE zammad-hr OWNER to zammad-hr; + ALTER DATABASE zammad-hr CONNECTION LIMIT 50; ''; authentication = pkgs.lib.mkOverride 10 '' # Local connections (Unix socket) @@ -114,6 +114,7 @@ host kestra kestra 10.89.0.0/24 scram-sha-256 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 litellm litellm 10.89.0.0/24 scram-sha-256 # Deny all other connections @@ -125,7 +126,7 @@ services.postgresqlBackup = { enable = true; startAt = "03:10:00"; - databases = ["baserow" "kestra" "librechat_rag" "metabase" "n8n" "outline" "vaultwarden" "zammad"]; + databases = ["baserow" "kestra" "librechat_rag" "litellm" "metabase" "n8n" "outline" "vaultwarden" "zammad" "zammad_hr"]; }; services.pgadmin = { enable = true; diff --git a/lib/ports.nix b/lib/ports.nix index 489efec..4c38035 100644 --- a/lib/ports.nix +++ b/lib/ports.nix @@ -15,6 +15,8 @@ metabase = 3013; ntfy-sh = 3033; it-tools = 3035; + zammad-hr = 3036; + zammad-hr-elasticsearch = 3037; # Docker services (3100-3199 range) librechat = 3040; diff --git a/secrets.nix b/secrets.nix index e74e201..7192e37 100644 --- a/secrets.nix +++ b/secrets.nix @@ -29,4 +29,6 @@ in { "secrets/vaultwarden-db.age".publicKeys = systems ++ users; "secrets/zammad-pw.age".publicKeys = systems ++ users; "secrets/zammad-secret.age".publicKeys = systems ++ users; + "secrets/zammad-hr-env.age".publicKeys = systems ++ users; + "secrets/zammad-hr-env-prod.age".publicKeys = systems ++ users; } diff --git a/secrets/zammad-hr-env-prod.age b/secrets/zammad-hr-env-prod.age new file mode 100644 index 0000000..04d8390 --- /dev/null +++ b/secrets/zammad-hr-env-prod.age @@ -0,0 +1,12 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFpoVnNlZyBQMzFq +VXRGU2hHVjFuazdvdE5GM3puenBlV0pNekh1WFNLaHUzOHNNZWswCll1VEtsbVlS +Uys3SVNmUS9pS0lGWE5GdUhyNDhsZldkYUlxd2w1ODN2RFEKLT4gc3NoLWVkMjU1 +MTkgQ1NNeWhnIHRsSTR6alh3ZFZNYjFCdXBIenVuaXdPd1FDKzlXa3NnWktvYmxv +eG1Tak0Kd0lESnJKZVNYUGp0SEJDNzZYQXUyVlNGaDh5LzA1amNrZFdtQmdQalg3 +awotPiA7R0ciZG8tZ3JlYXNlCjlEVlFicnByZHRPNGs1SG92ZjJKV295Y3VyMjM0 +cDlrcDQrMnVrMk54bjlrTG1PSUZrL2NrSlZvYWtVVHlwbHIKTWZYdjFRCi0tLSBk +WnMvaFZsTmlSOWRRa0prZXJQVkxta240Sm1TWkIrQ21LZytEUHJOckRRCk7tRMUc +s3agudDWyiHVrLZKwDFR7peGyHCfXNV19kpDThVKxKe3DzWGWWDYwggblHJbUnAj +OpIA/tq0s2nNc2F5Db2Hm7qen7YnOhF7WDoRPTXdG6VRERBqrZ/3 +-----END AGE ENCRYPTED FILE----- diff --git a/secrets/zammad-hr-env.age b/secrets/zammad-hr-env.age new file mode 100644 index 0000000..037df96 --- /dev/null +++ b/secrets/zammad-hr-env.age @@ -0,0 +1,12 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFpoVnNlZyBsc0xG +VzRzRGMxZ3h4MU1jZkZ4MEQzbUhaS0JYSzZ5eTZ5YXhWZEtTbWxFCnBrT2RKaEdN +U1BRRnZBWVYyWVpxTVhVM0d4VzhtbEEzNnoza0xpSEtQbXcKLT4gc3NoLWVkMjU1 +MTkgQ1NNeWhnIGNySDFJcjN4SEFReG5kUVBTdXVjWEpLRG8rSWVlSkI3WXovWE5q +N3dZQVEKaWdJVVJuWUM1MEZGaTQrV3c2YVRSTmRIYk4wdkVnRStuWG5UVGxjQ0FP +TQotPiAtV3l1P0sjLWdyZWFzZQpGb1gzY0pYNHBGRjUvK2JhWlBDdHZtSnROaU5X +aVc2L3hCSDJrUnJEdHNaUkFZT1cxK25IKzJza0VTb3hsTEFBCnkxSUJ2TFA3Sllz +UGVQY285djc2eXhtU2wrbldtTnkyRGZONgotLS0gV0Y5Qk53TXNhYUozTVZlQjRh +RHRNOWs2RS9jNzA0cW53b09ucGZQSURyYwomvbCCgvqCeAaza/IO2/ih4OOOWJHO +m579mV6FSO0Ak2+AdYVWC6ddOqQEuqPwTmAkebfrSJ3IajZtczXtj8k= +-----END AGE ENCRYPTED FILE-----