298 lines
9.0 KiB
Nix

{
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"];
};
};
}