first commit
This commit is contained in:
70
hosts/AZ-CLD-1/services/containers/baserow.nix
Normal file
70
hosts/AZ-CLD-1/services/containers/baserow.nix
Normal file
@@ -0,0 +1,70 @@
|
||||
{config, ...}: let
|
||||
serviceName = "baserow";
|
||||
servicePort = config.m3ta.ports.get serviceName;
|
||||
in {
|
||||
virtualisation.oci-containers.containers.${serviceName} = {
|
||||
image = "docker.io/baserow/baserow:2.1.6";
|
||||
environment = {
|
||||
BASEROW_AMOUNT_OF_GUNICORN_WORKERS = "4";
|
||||
BASEROW_AMOUNT_OF_WORKERS = "2";
|
||||
DATABASE_CONN_MAX_AGE = "60";
|
||||
# Proxy: tell Django the connection is HTTPS so cookies get Secure flag
|
||||
BASEROW_ENABLE_SECURE_PROXY_SSL_HEADER = "yes";
|
||||
# Published apps run on different origins — allow cross-origin cookie delivery
|
||||
BASEROW_FRONTEND_SAME_SITE_COOKIE = "none";
|
||||
# Valid base domain for published app subdomains
|
||||
BASEROW_BUILDER_DOMAINS = "az-gruppe.com";
|
||||
# Disable Caddy's on_demand TLS — Traefik handles TLS termination
|
||||
BASEROW_CADDY_GLOBAL_CONF = "auto_https off";
|
||||
};
|
||||
environmentFiles = [config.age.secrets.baserow-env.path];
|
||||
ports = ["127.0.0.1:${toString servicePort}:80"];
|
||||
volumes = ["baserow_data:/baserow/data"];
|
||||
extraOptions = ["--add-host=postgres:10.89.0.1" "--ip=10.89.0.10" "--network=web"];
|
||||
};
|
||||
|
||||
# Traefik configuration
|
||||
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";
|
||||
};
|
||||
};
|
||||
|
||||
routers.${serviceName} = {
|
||||
rule = "Host(`br.az-gruppe.com`)";
|
||||
tls = {
|
||||
certResolver = "ionos";
|
||||
};
|
||||
service = serviceName;
|
||||
entrypoints = "websecure";
|
||||
middlewares = ["${serviceName}-headers"];
|
||||
};
|
||||
|
||||
routers.azubi = {
|
||||
rule = "Host(`azubi.az-gruppe.com`)";
|
||||
tls = {
|
||||
certResolver = "ionos";
|
||||
};
|
||||
service = serviceName;
|
||||
entrypoints = "websecure";
|
||||
middlewares = ["${serviceName}-headers"];
|
||||
};
|
||||
routers.ausbilder = {
|
||||
rule = "Host(`ausbilder.az-gruppe.com`)";
|
||||
tls = {
|
||||
certResolver = "ionos";
|
||||
};
|
||||
service = serviceName;
|
||||
entrypoints = "websecure";
|
||||
middlewares = ["${serviceName}-headers"];
|
||||
};
|
||||
};
|
||||
}
|
||||
20
hosts/AZ-CLD-1/services/containers/default.nix
Normal file
20
hosts/AZ-CLD-1/services/containers/default.nix
Normal file
@@ -0,0 +1,20 @@
|
||||
{lib, ...}: {
|
||||
imports = [
|
||||
./baserow.nix
|
||||
./it-tools.nix
|
||||
./librechat.nix
|
||||
./litellm.nix
|
||||
./librechat-dev.nix
|
||||
./netbird.nix
|
||||
./portainer.nix
|
||||
./zammad-hr.nix
|
||||
];
|
||||
system.activationScripts.createPodmanNetworkWeb = lib.mkAfter ''
|
||||
if ! /run/current-system/sw/bin/podman network exists web; then
|
||||
/run/current-system/sw/bin/podman network create web --subnet=10.89.0.0/24 --internal
|
||||
fi
|
||||
if ! /run/current-system/sw/bin/podman network exists web-dev; then
|
||||
/run/current-system/sw/bin/podman network create web-dev --subnet=10.89.1.0/24 --internal
|
||||
fi
|
||||
'';
|
||||
}
|
||||
27
hosts/AZ-CLD-1/services/containers/it-tools.nix
Normal file
27
hosts/AZ-CLD-1/services/containers/it-tools.nix
Normal file
@@ -0,0 +1,27 @@
|
||||
{config, ...}: let
|
||||
serviceName = "it-tools";
|
||||
servicePort = config.m3ta.ports.get serviceName;
|
||||
in {
|
||||
virtualisation.oci-containers.containers.${serviceName} = {
|
||||
image = "docker.io/sharevb/it-tools:latest";
|
||||
ports = ["127.0.0.1:${toString servicePort}:8080"];
|
||||
};
|
||||
|
||||
# Traefik configuration
|
||||
services.traefik.dynamicConfigOptions.http = {
|
||||
services.${serviceName}.loadBalancer.servers = [
|
||||
{
|
||||
url = "http://localhost:${toString servicePort}/";
|
||||
}
|
||||
];
|
||||
|
||||
routers.${serviceName} = {
|
||||
rule = "Host(`tools.az-gruppe.com`)";
|
||||
tls = {
|
||||
certResolver = "ionos";
|
||||
};
|
||||
service = serviceName;
|
||||
entrypoints = "websecure";
|
||||
};
|
||||
};
|
||||
}
|
||||
133
hosts/AZ-CLD-1/services/containers/librechat-dev.nix
Normal file
133
hosts/AZ-CLD-1/services/containers/librechat-dev.nix
Normal file
@@ -0,0 +1,133 @@
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
serviceName = "librechat-dev";
|
||||
servicePort = config.m3ta.ports.get serviceName;
|
||||
ragApiDevServiceName = "rag-api-dev";
|
||||
ragApiDevPort = config.m3ta.ports.get ragApiDevServiceName;
|
||||
envFileDev = config.age.secrets.librechat-env-dev.path;
|
||||
envFileCommon = config.age.secrets.librechat.path;
|
||||
in {
|
||||
virtualisation.oci-containers = {
|
||||
containers.meilisearch-dev = {
|
||||
image = "getmeili/meilisearch:v1.12.3";
|
||||
autoStart = false;
|
||||
volumes = ["librechat_dev_meili:/meili_data"];
|
||||
environment = {
|
||||
MEILI_HTTP_ADDR = "0.0.0.0:7700";
|
||||
MEILI_NO_ANALYTICS = "true";
|
||||
};
|
||||
environmentFiles = [envFileDev envFileCommon];
|
||||
extraOptions = ["--ip=10.89.1.20" "--network=web-dev"];
|
||||
};
|
||||
|
||||
containers.rag_api-dev = {
|
||||
image = "ghcr.io/danny-avila/librechat-rag-api-dev-lite:latest";
|
||||
autoStart = false;
|
||||
environment = {
|
||||
RAG_PORT = "8000";
|
||||
DB_HOST = "10.89.1.1";
|
||||
DB_PORT = "5432";
|
||||
};
|
||||
environmentFiles = [envFileDev envFileCommon];
|
||||
dependsOn = ["meilisearch-dev"];
|
||||
extraOptions = ["--add-host=postgres:10.89.1.1" "--ip=10.89.1.21" "--network=web-dev"];
|
||||
ports = ["127.0.0.1:${toString ragApiDevPort}:8000"];
|
||||
};
|
||||
|
||||
containers.mongodb-dev = {
|
||||
image = "mongo:7";
|
||||
autoStart = false;
|
||||
volumes = [
|
||||
"librechat_dev_mongo:/data/db"
|
||||
"/var/backup/mongodb-dev:/data/backups"
|
||||
];
|
||||
extraOptions = ["--ip=10.89.1.22" "--network=web-dev"];
|
||||
};
|
||||
|
||||
containers.${serviceName} = {
|
||||
image = "ghcr.io/danny-avila/librechat-dev-api:latest";
|
||||
autoStart = false;
|
||||
ports = ["127.0.0.1:${toString servicePort}:3080"];
|
||||
dependsOn = ["mongodb-dev" "rag_api-dev" "meilisearch-dev"];
|
||||
environment = {
|
||||
HOST = "0.0.0.0";
|
||||
NODE_ENV = "development";
|
||||
MONGO_URI = "mongodb://mongodb-dev:27017/LibreChatDev";
|
||||
MEILI_HOST = "http://meilisearch-dev:7700";
|
||||
RAG_PORT = "8000";
|
||||
RAG_API_URL = "http://rag_api-dev:8000";
|
||||
};
|
||||
environmentFiles = [envFileDev envFileCommon];
|
||||
volumes = [
|
||||
"/var/lib/librechat-dev/librechat.yaml:/app/librechat.yaml:ro"
|
||||
"librechat_dev_images:/app/client/public/images"
|
||||
"librechat_dev_uploads:/app/uploads"
|
||||
"librechat_dev_logs:/app/api/logs"
|
||||
];
|
||||
extraOptions = ["--ip=10.89.1.23" "--network=web-dev"];
|
||||
};
|
||||
};
|
||||
|
||||
# Traefik configuration
|
||||
services.traefik.dynamicConfigOptions.http = {
|
||||
services.${serviceName}.loadBalancer.servers = [
|
||||
{
|
||||
url = "http://localhost:${toString servicePort}/";
|
||||
}
|
||||
];
|
||||
|
||||
routers.${serviceName} = {
|
||||
rule = "Host(`chat-dev.az-gruppe.com`)";
|
||||
tls = {
|
||||
certResolver = "ionos";
|
||||
};
|
||||
service = serviceName;
|
||||
entrypoints = "websecure";
|
||||
};
|
||||
};
|
||||
|
||||
environment.systemPackages = [
|
||||
(pkgs.writeShellScriptBin "librechat-dev" ''
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
SERVICES=(
|
||||
podman-meilisearch-dev
|
||||
podman-mongodb-dev
|
||||
podman-rag_api-dev
|
||||
podman-librechat-dev
|
||||
)
|
||||
|
||||
case "$1" in
|
||||
up)
|
||||
echo "🚀 Starte LibreChat-Dev-Umgebung..."
|
||||
for svc in "''${SERVICES[@]}"; do
|
||||
sudo systemctl start "$svc"
|
||||
done
|
||||
;;
|
||||
down)
|
||||
echo "🛑 Stoppe LibreChat-Dev-Umgebung..."
|
||||
for svc in "''${SERVICES[@]}"; do
|
||||
sudo systemctl stop "$svc"
|
||||
done
|
||||
;;
|
||||
restart)
|
||||
echo "🔄 Neustart der LibreChat-Dev-Umgebung..."
|
||||
for svc in "''${SERVICES[@]}"; do
|
||||
sudo systemctl restart "$svc"
|
||||
done
|
||||
;;
|
||||
status)
|
||||
systemctl status "''${SERVICES[@]}"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: librechat-dev {up|down|restart|status}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
'')
|
||||
];
|
||||
}
|
||||
169
hosts/AZ-CLD-1/services/containers/librechat.nix
Normal file
169
hosts/AZ-CLD-1/services/containers/librechat.nix
Normal file
@@ -0,0 +1,169 @@
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
serviceName = "librechat";
|
||||
servicePort = config.m3ta.ports.get serviceName;
|
||||
ragApiServiceName = "rag-api";
|
||||
ragApiPort = config.m3ta.ports.get ragApiServiceName;
|
||||
envFileProd = config.age.secrets.librechat-env-prod.path;
|
||||
envFileCommon = config.age.secrets.librechat.path;
|
||||
in {
|
||||
virtualisation.oci-containers = {
|
||||
containers.meilisearch = {
|
||||
image = "getmeili/meilisearch:v1.35.1";
|
||||
autoStart = true;
|
||||
volumes = ["librechat_meili:/meili_data"];
|
||||
environment = {
|
||||
MEILI_HTTP_ADDR = "0.0.0.0:7700";
|
||||
MEILI_NO_ANALYTICS = "true";
|
||||
};
|
||||
environmentFiles = [envFileCommon envFileProd];
|
||||
extraOptions = ["--ip=10.89.0.20" "--network=web"];
|
||||
};
|
||||
|
||||
containers.rag_api = {
|
||||
image = "registry.librechat.ai/danny-avila/librechat-rag-api-dev-lite:latest";
|
||||
autoStart = true;
|
||||
environment = {
|
||||
RAG_PORT = "8000";
|
||||
DB_HOST = "10.89.0.1";
|
||||
DB_PORT = "5432";
|
||||
};
|
||||
environmentFiles = [envFileCommon envFileProd];
|
||||
dependsOn = ["meilisearch"];
|
||||
extraOptions = ["--add-host=postgres:10.89.0.1" "--ip=10.89.0.21" "--network=web"];
|
||||
ports = ["127.0.0.1:${toString ragApiPort}:8000"];
|
||||
};
|
||||
|
||||
containers.mongodb = {
|
||||
image = "mongo:8.0.17";
|
||||
autoStart = true;
|
||||
volumes = [
|
||||
"librechat_mongo:/data/db"
|
||||
"/var/backup/mongodb:/data/backups"
|
||||
];
|
||||
# Enable auth once users exist; see Mongo auth doc.
|
||||
# command = [ "mongod", "--auth" ];
|
||||
extraOptions = ["--ip=10.89.0.22" "--network=web"];
|
||||
};
|
||||
|
||||
containers.${serviceName} = {
|
||||
image = "registry.librechat.ai/danny-avila/librechat-dev:latest";
|
||||
autoStart = true;
|
||||
user = "1000:1000";
|
||||
ports = ["127.0.0.1:${toString servicePort}:3080"];
|
||||
dependsOn = ["mongodb" "rag_api" "meilisearch"];
|
||||
environment = {
|
||||
HOST = "0.0.0.0";
|
||||
NODE_ENV = "production";
|
||||
# Mongo URI (start without auth; switch to mongodb://user:pass@mongodb:27017/LibreChat after Step 4)
|
||||
MONGO_URI = "mongodb://mongodb:27017/LibreChat";
|
||||
MEILI_HOST = "http://meilisearch:7700";
|
||||
RAG_PORT = "8000";
|
||||
RAG_API_URL = "http://rag_api:8000";
|
||||
};
|
||||
environmentFiles = [envFileCommon envFileProd];
|
||||
volumes = [
|
||||
# Config file still needs to be a bind mount for host management
|
||||
"/var/lib/librechat/librechat.yaml:/app/librechat.yaml:ro"
|
||||
# Use named volumes for application data
|
||||
"librechat_images:/app/client/public/images"
|
||||
"librechat_uploads:/app/uploads"
|
||||
"librechat_logs:/app/api/logs"
|
||||
];
|
||||
extraOptions = ["--ip=10.89.0.23" "--network=web" "--dns=8.8.8.8" "--dns=8.8.4.4"];
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services."mongo-backup" = {
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "root";
|
||||
Group = "root";
|
||||
};
|
||||
script = ''
|
||||
set -euo pipefail
|
||||
|
||||
BACKUP_DIR="/var/backup/mongodb"
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
TEMP_BACKUP="mongodb_backup_$TIMESTAMP"
|
||||
ARCHIVE_NAME="mongodb_backup_$TIMESTAMP.tar.gz"
|
||||
|
||||
# Ensure backup directory exists with proper permissions
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
chown root:root "$BACKUP_DIR"
|
||||
chmod 750 "$BACKUP_DIR"
|
||||
|
||||
echo "Starting MongoDB backup at $(date)"
|
||||
|
||||
# Create the backup dump in container
|
||||
if ${pkgs.podman}/bin/podman exec mongodb mongodump --out "/data/backups/$TEMP_BACKUP"; then
|
||||
echo "MongoDB dump completed successfully"
|
||||
|
||||
# Create compressed archive from the backup
|
||||
cd "$BACKUP_DIR"
|
||||
if [ -d "$TEMP_BACKUP" ]; then
|
||||
echo "Creating compressed archive: $ARCHIVE_NAME"
|
||||
${pkgs.gnutar}/bin/tar --use-compress-program=${pkgs.gzip}/bin/gzip -cf "$ARCHIVE_NAME" -C . "$TEMP_BACKUP"
|
||||
|
||||
# Remove the uncompressed backup directory
|
||||
rm -rf "$TEMP_BACKUP"
|
||||
|
||||
# Verify archive was created
|
||||
if [ -f "$ARCHIVE_NAME" ]; then
|
||||
ARCHIVE_SIZE=$(${pkgs.coreutils}/bin/du -sh "$ARCHIVE_NAME" | cut -f1)
|
||||
echo "Compressed backup created: $ARCHIVE_NAME (Size: $ARCHIVE_SIZE)"
|
||||
|
||||
# Keep only the 2 most recent backup archives
|
||||
ls -1t mongodb_backup_*.tar.gz | tail -n +3 | xargs -r rm -f
|
||||
echo "Old backup archives cleaned up, keeping 2 most recent"
|
||||
|
||||
# List current backups
|
||||
echo "Current backups:"
|
||||
ls -lah mongodb_backup_*.tar.gz 2>/dev/null || echo "No previous backups found"
|
||||
else
|
||||
echo "ERROR: Failed to create compressed archive" >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "ERROR: Backup directory not found at $BACKUP_DIR/$TEMP_BACKUP" >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "ERROR: MongoDB backup failed" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "MongoDB backup completed successfully at $(date)"
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.timers."mongo-backup" = {
|
||||
wantedBy = ["timers.target"];
|
||||
timerConfig = {
|
||||
OnCalendar = "*-*-* 02: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(`chat.az-gruppe.com`)";
|
||||
tls = {
|
||||
certResolver = "ionos";
|
||||
};
|
||||
service = serviceName;
|
||||
entrypoints = "websecure";
|
||||
};
|
||||
};
|
||||
}
|
||||
37
hosts/AZ-CLD-1/services/containers/litellm.nix
Normal file
37
hosts/AZ-CLD-1/services/containers/litellm.nix
Normal file
@@ -0,0 +1,37 @@
|
||||
{config, ...}: let
|
||||
serviceName = "litellm";
|
||||
servicePort = config.m3ta.ports.get serviceName;
|
||||
in {
|
||||
virtualisation.oci-containers.containers.${serviceName} = {
|
||||
#image = "ghcr.io/berriai/litellm:v1.78.5-stable";
|
||||
image = "docker.litellm.ai/berriai/litellm:v1.82.3-stable";
|
||||
ports = ["127.0.0.1:${toString servicePort}:4000"];
|
||||
environmentFiles = [config.age.secrets.litellm-env.path];
|
||||
environment = {
|
||||
ANONYMIZED_TELEMETRY = "False";
|
||||
DO_NOT_TRACK = "True";
|
||||
SCARF_NO_ANALYTICS = "True";
|
||||
STORE_MODEL_IN_DB = "True";
|
||||
};
|
||||
volumes = ["/var/lib/litellm/config.yaml:/app/config.yaml"];
|
||||
extraOptions = ["--add-host=postgres:10.89.0.1" "--ip=10.89.0.30" "--network=web"];
|
||||
};
|
||||
|
||||
# Traefik configuration
|
||||
services.traefik.dynamicConfigOptions.http = {
|
||||
services.${serviceName}.loadBalancer.servers = [
|
||||
{
|
||||
url = "http://localhost:${toString servicePort}/";
|
||||
}
|
||||
];
|
||||
|
||||
routers.${serviceName} = {
|
||||
rule = "Host(`llm.az-gruppe.com`)";
|
||||
tls = {
|
||||
certResolver = "ionos";
|
||||
};
|
||||
service = serviceName;
|
||||
entrypoints = "websecure";
|
||||
};
|
||||
};
|
||||
}
|
||||
243
hosts/AZ-CLD-1/services/containers/netbird.nix
Normal file
243
hosts/AZ-CLD-1/services/containers/netbird.nix
Normal file
@@ -0,0 +1,243 @@
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
serviceName = "netbird";
|
||||
|
||||
servicePort = config.m3ta.ports.get serviceName;
|
||||
|
||||
domain = "v.az-gruppe.com";
|
||||
proxyDomain = "p.az-gruppe.com";
|
||||
|
||||
ipBase = "10.89.0";
|
||||
ipOffset = 50;
|
||||
|
||||
# Derived IPs
|
||||
gatewayIp = "${ipBase}.1";
|
||||
dashboardIp = "${ipBase}.${toString ipOffset}";
|
||||
serverIp = "${ipBase}.${toString (ipOffset + 1)}";
|
||||
proxyIp = "${ipBase}.${toString (ipOffset + 2)}";
|
||||
|
||||
# Database configuration
|
||||
dbName = "netbird";
|
||||
dbUser = "netbird";
|
||||
dbHost = gatewayIp;
|
||||
|
||||
# NetBird config as Nix attribute set
|
||||
netbirdConfig = {
|
||||
server = {
|
||||
listenAddress = ":80";
|
||||
exposedAddress = "https://${domain}:443";
|
||||
stunPorts = [3478];
|
||||
metricsPort = 9090;
|
||||
healthcheckAddress = ":9000";
|
||||
logLevel = "info";
|
||||
logFile = "console";
|
||||
dataDir = "/var/lib/netbird";
|
||||
|
||||
auth = {
|
||||
issuer = "https://${domain}/oauth2";
|
||||
localAuthDisabled = true;
|
||||
signKeyRefreshEnabled = true;
|
||||
dashboardRedirectURIs = [
|
||||
"https://${domain}/nb-auth"
|
||||
"https://${domain}/nb-silent-auth"
|
||||
];
|
||||
cliRedirectURIs = ["http://localhost:53000/"];
|
||||
};
|
||||
|
||||
reverseProxy = {
|
||||
trustedHTTPProxies = ["${gatewayIp}/32"];
|
||||
};
|
||||
|
||||
# Proxy Feature
|
||||
proxy = {
|
||||
enabled = true;
|
||||
domain = proxyDomain;
|
||||
};
|
||||
|
||||
store = {
|
||||
engine = "postgres";
|
||||
postgres = {
|
||||
host = dbHost;
|
||||
port = 5432;
|
||||
database = dbName;
|
||||
username = dbUser;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Generate YAML config
|
||||
yamlFormat = pkgs.formats.yaml {};
|
||||
configYamlBase = yamlFormat.generate "netbird-config-base.yaml" netbirdConfig;
|
||||
|
||||
# Script to inject secrets at runtime
|
||||
configGenScript = pkgs.writeShellScript "netbird-gen-config" ''
|
||||
set -euo pipefail
|
||||
|
||||
AUTH_SECRET=$(cat "$1")
|
||||
DB_PASSWORD=$(cat "$2")
|
||||
ENCRYPTION_KEY=$(cat "$3")
|
||||
|
||||
${pkgs.yq-go}/bin/yq eval "
|
||||
.server.authSecret = \"$AUTH_SECRET\" |
|
||||
.server.store.encryptionKey = \"$ENCRYPTION_KEY\" |
|
||||
.server.store.postgres.password = \"$DB_PASSWORD\"
|
||||
" ${configYamlBase}
|
||||
'';
|
||||
in {
|
||||
age.secrets."${serviceName}-auth-secret".file = ../../../../secrets/${serviceName}-auth-secret.age;
|
||||
age.secrets."${serviceName}-db-password".file = ../../../../secrets/${serviceName}-db-password.age;
|
||||
age.secrets."${serviceName}-encryption-key".file = ../../../../secrets/${serviceName}-encryption-key.age;
|
||||
age.secrets."${serviceName}-dashboard-env".file = ../../../../secrets/${serviceName}-dashboard-env.age;
|
||||
age.secrets."${serviceName}-server-env".file = ../../../../secrets/${serviceName}-server-env.age;
|
||||
age.secrets."${serviceName}-proxy-env".file = ../../../../secrets/${serviceName}-proxy-env.age;
|
||||
|
||||
# Systemd oneshot service to generate config with secrets
|
||||
systemd.services."${serviceName}-config" = {
|
||||
description = "Generate NetBird config with secrets";
|
||||
wantedBy = ["multi-user.target"];
|
||||
before = ["podman-${serviceName}-server.service"];
|
||||
requiredBy = ["podman-${serviceName}-server.service"];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = pkgs.writeShellScript "netbird-write-config" ''
|
||||
mkdir -p /var/lib/${serviceName}
|
||||
${configGenScript} \
|
||||
${config.age.secrets."${serviceName}-auth-secret".path} \
|
||||
${config.age.secrets."${serviceName}-db-password".path} \
|
||||
${config.age.secrets."${serviceName}-encryption-key".path} \
|
||||
> /var/lib/${serviceName}/config.yaml
|
||||
chmod 600 /var/lib/${serviceName}/config.yaml
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
virtualisation.oci-containers.containers = {
|
||||
"${serviceName}-dashboard" = {
|
||||
image = "netbirdio/dashboard:latest";
|
||||
autoStart = true;
|
||||
ports = ["127.0.0.1:${toString servicePort}:80"];
|
||||
environmentFiles = [config.age.secrets."${serviceName}-dashboard-env".path];
|
||||
extraOptions = [
|
||||
"--ip=${dashboardIp}"
|
||||
"--network=web"
|
||||
];
|
||||
};
|
||||
|
||||
"${serviceName}-server" = {
|
||||
image = "netbirdio/netbird-server:latest";
|
||||
autoStart = true;
|
||||
ports = ["3478:3478/udp"];
|
||||
environmentFiles = [config.age.secrets."${serviceName}-server-env".path];
|
||||
volumes = [
|
||||
"${serviceName}_data:/var/lib/netbird"
|
||||
"/var/lib/${serviceName}/config.yaml:/etc/netbird/config.yaml:ro"
|
||||
];
|
||||
cmd = ["--config" "/etc/netbird/config.yaml"];
|
||||
extraOptions = [
|
||||
"--ip=${serverIp}"
|
||||
"--network=web"
|
||||
];
|
||||
};
|
||||
|
||||
"${serviceName}-proxy" = {
|
||||
image = "netbirdio/reverse-proxy:latest";
|
||||
autoStart = true;
|
||||
ports = ["51820:51820/udp"];
|
||||
volumes = [
|
||||
"${serviceName}_proxy_certs:/certs"
|
||||
];
|
||||
environmentFiles = [config.age.secrets."${serviceName}-proxy-env".path];
|
||||
cmd = [
|
||||
"--domain=${proxyDomain}"
|
||||
"--mgmt=https://${domain}:443"
|
||||
"--addr=:8443"
|
||||
"--cert-dir=/certs"
|
||||
"--acme-certs"
|
||||
"--trusted-proxies=${gatewayIp}/32"
|
||||
];
|
||||
dependsOn = ["${serviceName}-server"];
|
||||
extraOptions = [
|
||||
"--ip=${proxyIp}"
|
||||
"--network=web"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
services.traefik.dynamicConfigOptions = {
|
||||
# HTTP services and routers
|
||||
http = {
|
||||
services = {
|
||||
"${serviceName}-dashboard".loadBalancer.servers = [
|
||||
{url = "http://localhost:${toString servicePort}/";}
|
||||
];
|
||||
|
||||
"${serviceName}-server".loadBalancer.servers = [
|
||||
{url = "http://${serverIp}:80/";}
|
||||
];
|
||||
|
||||
"${serviceName}-server-h2c".loadBalancer.servers = [
|
||||
{url = "h2c://${serverIp}:80";}
|
||||
];
|
||||
};
|
||||
|
||||
routers = {
|
||||
# gRPC (Signal + Management)
|
||||
"${serviceName}-grpc" = {
|
||||
rule = "Host(`${domain}`) && (PathPrefix(`/signalexchange.SignalExchange/`) || PathPrefix(`/management.ManagementService/`) || PathPrefix(`/management.ProxyService/`))";
|
||||
entrypoints = "websecure";
|
||||
tls.certResolver = "ionos";
|
||||
service = "${serviceName}-server-h2c";
|
||||
priority = 100;
|
||||
};
|
||||
# Backend (relay, WebSocket, API, OAuth2)
|
||||
"${serviceName}-backend" = {
|
||||
rule = "Host(`${domain}`) && (PathPrefix(`/relay`) || PathPrefix(`/ws-proxy/`) || PathPrefix(`/api`) || PathPrefix(`/oauth2`))";
|
||||
entrypoints = "websecure";
|
||||
tls.certResolver = "ionos";
|
||||
service = "${serviceName}-server";
|
||||
priority = 100;
|
||||
};
|
||||
|
||||
# Dashboard (catch-all, lowest priority)
|
||||
"${serviceName}-dashboard" = {
|
||||
rule = "Host(`${domain}`)";
|
||||
entrypoints = "websecure";
|
||||
tls.certResolver = "ionos";
|
||||
service = "${serviceName}-dashboard";
|
||||
priority = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# TCP for proxy TLS passthrough
|
||||
tcp = {
|
||||
services."${serviceName}-proxy-tls".loadBalancer.servers = [
|
||||
{address = "${proxyIp}:8443";}
|
||||
];
|
||||
|
||||
routers."${serviceName}-proxy-passthrough" = {
|
||||
entryPoints = ["websecure"];
|
||||
rule = "HostSNI(`*`)";
|
||||
service = "${serviceName}-proxy-tls";
|
||||
priority = 1;
|
||||
tls.passthrough = true;
|
||||
};
|
||||
};
|
||||
|
||||
# ServersTransport for proxy protocol v2 (optional)
|
||||
serversTransports."pp-v2" = {
|
||||
proxyProtocol.version = 2;
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall.allowedUDPPorts = [
|
||||
3478 # STUN
|
||||
51820 # WireGuard for proxy
|
||||
];
|
||||
}
|
||||
32
hosts/AZ-CLD-1/services/containers/portainer.nix
Normal file
32
hosts/AZ-CLD-1/services/containers/portainer.nix
Normal file
@@ -0,0 +1,32 @@
|
||||
{config, ...}: let
|
||||
serviceName = "portainer";
|
||||
servicePort = config.m3ta.ports.get serviceName;
|
||||
in {
|
||||
virtualisation.oci-containers.containers.${serviceName} = {
|
||||
image = "docker.io/portainer/portainer-ce:latest";
|
||||
ports = ["127.0.0.1:${toString servicePort}:9000"];
|
||||
volumes = [
|
||||
"/etc/localtime:/etc/localtime:ro"
|
||||
"/run/podman/podman.sock:/var/run/docker.sock:ro"
|
||||
"portainer_data:/data"
|
||||
];
|
||||
};
|
||||
|
||||
# Traefik configuration
|
||||
services.traefik.dynamicConfigOptions.http = {
|
||||
services.${serviceName}.loadBalancer.servers = [
|
||||
{
|
||||
url = "http://localhost:${toString servicePort}/";
|
||||
}
|
||||
];
|
||||
|
||||
routers.${serviceName} = {
|
||||
rule = "Host(`pt.az-gruppe.com`)";
|
||||
tls = {
|
||||
certResolver = "ionos";
|
||||
};
|
||||
service = serviceName;
|
||||
entrypoints = "websecure";
|
||||
};
|
||||
};
|
||||
}
|
||||
296
hosts/AZ-CLD-1/services/containers/zammad-hr.nix
Normal file
296
hosts/AZ-CLD-1/services/containers/zammad-hr.nix
Normal file
@@ -0,0 +1,296 @@
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
instanceName = "hr";
|
||||
serviceName = "zammad-${instanceName}";
|
||||
|
||||
servicePort = config.m3ta.ports.get serviceName;
|
||||
elasticsearchServiceName = "${serviceName}-elasticsearch";
|
||||
elasticsearchPort = config.m3ta.ports.get elasticsearchServiceName;
|
||||
|
||||
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"];
|
||||
};
|
||||
};
|
||||
}
|
||||
18
hosts/AZ-CLD-1/services/default.nix
Normal file
18
hosts/AZ-CLD-1/services/default.nix
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
imports = [
|
||||
./containers
|
||||
|
||||
./gitea.nix
|
||||
./gotenberg.nix
|
||||
./metabase.nix
|
||||
./n8n.nix
|
||||
./netbird.nix
|
||||
./ntfy.nix
|
||||
./outline.nix
|
||||
./postgres.nix
|
||||
./traefik.nix
|
||||
./vaultwarden.nix
|
||||
./zugferd.nix
|
||||
# ./zammad.nix
|
||||
];
|
||||
}
|
||||
41
hosts/AZ-CLD-1/services/gitea.nix
Normal file
41
hosts/AZ-CLD-1/services/gitea.nix
Normal file
@@ -0,0 +1,41 @@
|
||||
{config, ...}: let
|
||||
serviceName = "gitea";
|
||||
servicePort = config.m3ta.ports.get serviceName;
|
||||
in {
|
||||
services.${serviceName} = {
|
||||
enable = true;
|
||||
settings = {
|
||||
server = {
|
||||
ROOT_URL = "https://git.az-gruppe.com";
|
||||
HTTP_PORT = servicePort;
|
||||
};
|
||||
mailer.SENDMAIL_PATH = "/run/wrappers/bin/sendmail";
|
||||
service.DISABLE_REGISTRATION = true;
|
||||
};
|
||||
lfs.enable = true;
|
||||
dump = {
|
||||
enable = true;
|
||||
type = "tar.gz";
|
||||
interval = "03:30:00";
|
||||
backupDir = "/var/backup/gitea";
|
||||
};
|
||||
};
|
||||
|
||||
# Traefik configuration
|
||||
services.traefik.dynamicConfigOptions.http = {
|
||||
services.${serviceName}.loadBalancer.servers = [
|
||||
{
|
||||
url = "http://localhost:${toString servicePort}/";
|
||||
}
|
||||
];
|
||||
|
||||
routers.${serviceName} = {
|
||||
rule = "Host(`git.az-gruppe.com`)";
|
||||
tls = {
|
||||
certResolver = "ionos";
|
||||
};
|
||||
service = serviceName;
|
||||
entrypoints = "websecure";
|
||||
};
|
||||
};
|
||||
}
|
||||
10
hosts/AZ-CLD-1/services/gotenberg.nix
Normal file
10
hosts/AZ-CLD-1/services/gotenberg.nix
Normal file
@@ -0,0 +1,10 @@
|
||||
{config, ...}: let
|
||||
serviceName = "gotenberg";
|
||||
servicePort = config.m3ta.ports.get serviceName;
|
||||
in {
|
||||
services.gotenberg = {
|
||||
enable = true;
|
||||
port = servicePort;
|
||||
bindIP = "127.0.0.1";
|
||||
};
|
||||
}
|
||||
31
hosts/AZ-CLD-1/services/metabase.nix
Normal file
31
hosts/AZ-CLD-1/services/metabase.nix
Normal file
@@ -0,0 +1,31 @@
|
||||
{config, ...}: let
|
||||
serviceName = "metabase";
|
||||
servicePort = config.m3ta.ports.get serviceName;
|
||||
in {
|
||||
services.${serviceName} = {
|
||||
enable = true;
|
||||
listen.port = servicePort;
|
||||
};
|
||||
|
||||
systemd.services.${serviceName}.serviceConfig = {
|
||||
EnvironmentFile = config.age.secrets.metabase-env.path;
|
||||
};
|
||||
|
||||
# Traefik configuration
|
||||
services.traefik.dynamicConfigOptions.http = {
|
||||
services.${serviceName}.loadBalancer.servers = [
|
||||
{
|
||||
url = "http://localhost:${toString servicePort}/";
|
||||
}
|
||||
];
|
||||
|
||||
routers.${serviceName} = {
|
||||
rule = "Host(`kpi.az-gruppe.com`)";
|
||||
tls = {
|
||||
certResolver = "ionos";
|
||||
};
|
||||
service = serviceName;
|
||||
entrypoints = "websecure";
|
||||
};
|
||||
};
|
||||
}
|
||||
31
hosts/AZ-CLD-1/services/n8n.nix
Normal file
31
hosts/AZ-CLD-1/services/n8n.nix
Normal file
@@ -0,0 +1,31 @@
|
||||
{config, ...}: let
|
||||
serviceName = "n8n";
|
||||
servicePort = config.m3ta.ports.get serviceName;
|
||||
in {
|
||||
services.${serviceName} = {
|
||||
enable = true;
|
||||
environment.WEBHOOK_URL = "https://wf.az-gruppe.com";
|
||||
};
|
||||
|
||||
systemd.services.${serviceName}.serviceConfig = {
|
||||
EnvironmentFile = config.age.secrets.n8n-env.path;
|
||||
};
|
||||
|
||||
# Traefik configuration
|
||||
services.traefik.dynamicConfigOptions.http = {
|
||||
services.${serviceName}.loadBalancer.servers = [
|
||||
{
|
||||
url = "http://localhost:${toString servicePort}/";
|
||||
}
|
||||
];
|
||||
|
||||
routers.${serviceName} = {
|
||||
rule = "Host(`wf.az-gruppe.com`)";
|
||||
tls = {
|
||||
certResolver = "ionos";
|
||||
};
|
||||
service = serviceName;
|
||||
entrypoints = "websecure";
|
||||
};
|
||||
};
|
||||
}
|
||||
31
hosts/AZ-CLD-1/services/netbird.nix
Normal file
31
hosts/AZ-CLD-1/services/netbird.nix
Normal file
@@ -0,0 +1,31 @@
|
||||
{pkgs, ...}: {
|
||||
services.netbird = {
|
||||
enable = true;
|
||||
package = pkgs.unstable.netbird;
|
||||
};
|
||||
|
||||
systemd.services.netbird = {
|
||||
environment = {
|
||||
NB_DISABLE_SSH_CONFIG = "true";
|
||||
};
|
||||
path = [
|
||||
pkgs.shadow
|
||||
pkgs.util-linux
|
||||
];
|
||||
};
|
||||
|
||||
programs.ssh.extraConfig = ''
|
||||
Match exec "${pkgs.netbird}/bin/netbird ssh detect %h %p"
|
||||
PreferredAuthentications password,publickey,keyboard-interactive
|
||||
PasswordAuthentication yes
|
||||
PubkeyAuthentication yes
|
||||
BatchMode no
|
||||
ProxyCommand ${pkgs.netbird}/bin/netbird ssh proxy %h %p
|
||||
StrictHostKeyChecking no
|
||||
UserKnownHostsFile /dev/null
|
||||
CheckHostIP no
|
||||
LogLevel ERROR
|
||||
'';
|
||||
|
||||
networking.firewall.checkReversePath = "loose";
|
||||
}
|
||||
32
hosts/AZ-CLD-1/services/ntfy.nix
Normal file
32
hosts/AZ-CLD-1/services/ntfy.nix
Normal file
@@ -0,0 +1,32 @@
|
||||
{config, ...}: let
|
||||
serviceName = "ntfy-sh";
|
||||
servicePort = config.m3ta.ports.get serviceName;
|
||||
in {
|
||||
services.${serviceName} = {
|
||||
enable = true;
|
||||
settings = {
|
||||
base-url = "https://ping.az-gruppe.com";
|
||||
listen-http = ":${toString servicePort}";
|
||||
auth-file = "/var/lib/ntfy-sh/user.db";
|
||||
auth-default-access = "deny-all";
|
||||
};
|
||||
};
|
||||
|
||||
# Traefik configuration
|
||||
services.traefik.dynamicConfigOptions.http = {
|
||||
services.${serviceName}.loadBalancer.servers = [
|
||||
{
|
||||
url = "http://localhost:${toString servicePort}/";
|
||||
}
|
||||
];
|
||||
|
||||
routers.${serviceName} = {
|
||||
rule = "Host(`ping.az-gruppe.com`)";
|
||||
tls = {
|
||||
certResolver = "ionos";
|
||||
};
|
||||
service = serviceName;
|
||||
entrypoints = "websecure";
|
||||
};
|
||||
};
|
||||
}
|
||||
41
hosts/AZ-CLD-1/services/outline.nix
Normal file
41
hosts/AZ-CLD-1/services/outline.nix
Normal file
@@ -0,0 +1,41 @@
|
||||
{config, ...}: let
|
||||
serviceName = "outline";
|
||||
servicePort = config.m3ta.ports.get serviceName;
|
||||
in {
|
||||
services.${serviceName} = {
|
||||
enable = true;
|
||||
port = servicePort;
|
||||
publicUrl = "https://wiki.az-gruppe.com";
|
||||
databaseUrl = "postgresql://outline:outline@127.0.0.1:5432/outline";
|
||||
storage = {
|
||||
storageType = "s3";
|
||||
region = "eu-central";
|
||||
uploadBucketUrl = "https://nbg1.your-objectstorage.com";
|
||||
uploadBucketName = "az-wiki";
|
||||
secretKeyFile = config.age.secrets.hetzner-s3-az-intern-secret-key.path;
|
||||
accessKey = "CRT7V4HR5CG9NHICD2WW";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.${serviceName}.serviceConfig = {
|
||||
EnvironmentFile = config.age.secrets.outline-env.path;
|
||||
};
|
||||
|
||||
# Traefik configuration
|
||||
services.traefik.dynamicConfigOptions.http = {
|
||||
services.${serviceName}.loadBalancer.servers = [
|
||||
{
|
||||
url = "http://localhost:${toString servicePort}/";
|
||||
}
|
||||
];
|
||||
|
||||
routers.${serviceName} = {
|
||||
rule = "Host(`wiki.az-gruppe.com`)";
|
||||
tls = {
|
||||
certResolver = "ionos";
|
||||
};
|
||||
service = serviceName;
|
||||
entrypoints = "websecure";
|
||||
};
|
||||
};
|
||||
}
|
||||
182
hosts/AZ-CLD-1/services/postgres.nix
Normal file
182
hosts/AZ-CLD-1/services/postgres.nix
Normal file
@@ -0,0 +1,182 @@
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
serviceName = "pgadmin";
|
||||
pgadminPort = config.m3ta.ports.get serviceName;
|
||||
in {
|
||||
services.postgresql = {
|
||||
enable = true;
|
||||
enableTCPIP = true;
|
||||
package = pkgs.postgresql_17;
|
||||
settings = {
|
||||
ssl = true;
|
||||
max_connections = 200;
|
||||
shared_buffers = "4GB";
|
||||
superuser_reserved_connections = 5;
|
||||
|
||||
idle_in_transaction_session_timeout = "10min";
|
||||
idle_session_timeout = "2h";
|
||||
|
||||
tcp_keepalives_idle = 60;
|
||||
tcp_keepalives_interval = 10;
|
||||
tcp_keepalives_count = 6;
|
||||
|
||||
deadlock_timeout = "1s";
|
||||
|
||||
authentication_timeout = "30s";
|
||||
|
||||
log_connections = true;
|
||||
log_disconnections = true;
|
||||
log_lock_waits = true;
|
||||
};
|
||||
extensions = with pkgs.postgresql17Packages; [
|
||||
pgvector
|
||||
];
|
||||
initialScript = pkgs.writeText "backend-initScript" ''
|
||||
CREATE USER baserow WITH ENCRYPTED PASSWORD 'baserow';
|
||||
CREATE DATABASE baserow;
|
||||
ALTER DATABASE baserow OWNER to baserow;
|
||||
ALTER DATABASE baserow CONNECTION LIMIT 60;
|
||||
|
||||
CREATE USER kestra WITH ENCRYPTED PASSWORD 'kestra';
|
||||
CREATE DATABASE kestra;
|
||||
ALTER DATABASE kestra OWNER to kestra;
|
||||
ALTER DATABASE kestra CONNECTION LIMIT 10;
|
||||
|
||||
CREATE USER librechat_rag WITH ENCRYPTED PASSWORD 'librechat_rag';
|
||||
CREATE DATABASE librechat_rag;
|
||||
ALTER DATABASE librechat_rag OWNER to librechat_rag;
|
||||
ALTER DATABASE librechat_rag CONNECTION LIMIT 20;
|
||||
|
||||
CREATE USER librechat_rag_dev WITH ENCRYPTED PASSWORD 'librechat_rag_dev';
|
||||
CREATE DATABASE librechat_rag_dev;
|
||||
ALTER DATABASE librechat_rag_dev OWNER to librechat_rag_dev;
|
||||
ALTER DATABASE librechat_rag_dev CONNECTION LIMIT 10;
|
||||
|
||||
CREATE USER metabase WITH ENCRYPTED PASSWORD 'metabase';
|
||||
CREATE DATABASE metabase;
|
||||
ALTER DATABASE metabase OWNER to metabase;
|
||||
ALTER DATABASE metabase CONNECTION LIMIT 15;
|
||||
|
||||
CREATE USER n8n WITH ENCRYPTED PASSWORD 'n8n';
|
||||
CREATE DATABASE n8n;
|
||||
ALTER DATABASE n8n OWNER to n8n;
|
||||
ALTER DATABASE n8n CONNECTION LIMIT 5;
|
||||
|
||||
CREATE USER outline WITH ENCRYPTED PASSWORD 'outline';
|
||||
CREATE DATABASE outline;
|
||||
ALTER DATABASE outline OWNER to outline;
|
||||
ALTER DATABASE outline CONNECTION LIMIT 5;
|
||||
|
||||
CREATE USER vaultwarden WITH ENCRYPTED PASSWORD 'vaultwarden';
|
||||
CREATE DATABASE vaultwarden;
|
||||
ALTER DATABASE vaultwarden OWNER to vaultwarden;
|
||||
ALTER DATABASE vaultwarden CONNECTION LIMIT 20;
|
||||
|
||||
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;
|
||||
|
||||
-- Group roles (NOLOGIN, for permission management)
|
||||
CREATE ROLE admin NOLOGIN;
|
||||
CREATE ROLE dba NOLOGIN;
|
||||
|
||||
-- Personal login roles
|
||||
CREATE USER sascha_koenig WITH ENCRYPTED PASSWORD 'sascha_koenig';
|
||||
GRANT admin TO sascha_koenig;
|
||||
|
||||
CREATE USER jannik_mueller WITH ENCRYPTED PASSWORD 'jannik_mueller';
|
||||
GRANT admin TO jannik_mueller;
|
||||
'';
|
||||
authentication = pkgs.lib.mkOverride 10 ''
|
||||
# Local connections (Unix socket)
|
||||
local all postgres peer
|
||||
local all sascha_koenig scram-sha-256
|
||||
local all jannik_mueller scram-sha-256
|
||||
local az_test az_test scram-sha-256
|
||||
local metabase,az_kpi_raw metabase scram-sha-256
|
||||
local n8n n8n scram-sha-256
|
||||
local outline outline scram-sha-256
|
||||
local vaultwarden vaultwarden scram-sha-256
|
||||
local zammad zammad scram-sha-256
|
||||
|
||||
# Localhost connections (IPv4 and IPv6)
|
||||
host all postgres 127.0.0.1/32 scram-sha-256
|
||||
host all postgres ::1/128 scram-sha-256
|
||||
|
||||
host all sascha_koenig 127.0.0.1/32 scram-sha-256
|
||||
host all sascha_koenig ::1/128 scram-sha-256
|
||||
|
||||
host all jannik_mueller 127.0.0.1/32 scram-sha-256
|
||||
host all jannik_mueller ::1/128 scram-sha-256
|
||||
|
||||
host az_test az_test 127.0.0.1/32 scram-sha-256
|
||||
host az_test az_test ::1/128 scram-sha-256
|
||||
|
||||
host outline outline 127.0.0.1/32 scram-sha-256
|
||||
host outline outline ::1/128 scram-sha-256
|
||||
|
||||
host metabase,az_kpi_raw metabase 127.0.0.1/32 scram-sha-256
|
||||
host metabase,az_kpi_raw metabase ::1/128 scram-sha-256
|
||||
|
||||
host n8n n8n 127.0.0.1/32 scram-sha-256
|
||||
host n8n n8n ::1/128 scram-sha-256
|
||||
|
||||
host vaultwarden vaultwarden 127.0.0.1/32 scram-sha-256
|
||||
host vaultwarden vaultwarden ::1/128 scram-sha-256
|
||||
|
||||
host zammad zammad 127.0.0.1/32 scram-sha-256
|
||||
host zammad zammad ::1/128 scram-sha-256
|
||||
|
||||
# Podman network connections for Baserow
|
||||
host baserow baserow 10.89.0.0/24 scram-sha-256
|
||||
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 postgres zammad_hr 10.89.0.0/24 scram-sha-256
|
||||
host litellm litellm 10.89.0.0/24 scram-sha-256
|
||||
host netbird netbird 10.89.0.0/24 scram-sha-256
|
||||
|
||||
# Netbird network connections
|
||||
host az_kpi_raw kestra_prm 100.91.49.26/32 scram-sha-256
|
||||
|
||||
# Deny all other connections
|
||||
local all all reject
|
||||
host all all 0.0.0.0/0 reject
|
||||
host all all ::/0 reject
|
||||
'';
|
||||
};
|
||||
services.postgresqlBackup = {
|
||||
enable = true;
|
||||
startAt = "03:10:00";
|
||||
databases = ["az_kpi_raw" "baserow" "kestra" "librechat_rag" "litellm" "metabase" "n8n" "outline" "vaultwarden" "zammad" "zammad_hr"];
|
||||
};
|
||||
services.pgadmin = {
|
||||
enable = true;
|
||||
initialPasswordFile = "${config.age.secrets.pgadmin-pw.path}";
|
||||
initialEmail = "sascha.koenig@azintec.com";
|
||||
};
|
||||
|
||||
# Traefik configuration specific to pgadmin
|
||||
services.traefik.dynamicConfigOptions.http = {
|
||||
services.pgadmin.loadBalancer.servers = [{url = "http://localhost:${toString pgadminPort}/";}];
|
||||
routers.pgadmin = {
|
||||
rule = "Host(`pg.az-gruppe.com`)";
|
||||
tls.certResolver = "ionos";
|
||||
service = "pgadmin";
|
||||
entrypoints = "websecure";
|
||||
};
|
||||
};
|
||||
networking.firewall = {
|
||||
extraCommands = ''
|
||||
iptables -A INPUT -p tcp -s 127.0.0.1 --dport 5432 -j ACCEPT
|
||||
iptables -A INPUT -p tcp -s 10.89.0.0/24 --dport 5432 -j ACCEPT
|
||||
iptables -A INPUT -p tcp -s 10.89.1.0/24 --dport 5432 -j ACCEPT
|
||||
iptables -A INPUT -p tcp -s 100.91.49.26/32 --dport 5432 -j ACCEPT
|
||||
'';
|
||||
};
|
||||
}
|
||||
77
hosts/AZ-CLD-1/services/traefik.nix
Normal file
77
hosts/AZ-CLD-1/services/traefik.nix
Normal file
@@ -0,0 +1,77 @@
|
||||
{config, ...}: let
|
||||
httpPort = config.m3ta.ports.get "traefik";
|
||||
httpsPort = config.m3ta.ports.get "traefik-ssl";
|
||||
in {
|
||||
services.traefik = {
|
||||
enable = true;
|
||||
staticConfigOptions = {
|
||||
log = {level = "WARN";};
|
||||
certificatesResolvers = {
|
||||
ionos = {
|
||||
acme = {
|
||||
email = "sascha.koenig@azintec.com";
|
||||
storage = "/var/lib/traefik/acme.json";
|
||||
caserver = "https://acme-v02.api.letsencrypt.org/directory";
|
||||
dnsChallenge = {
|
||||
provider = "ionos";
|
||||
resolvers = ["1.1.1.1:53" "8.8.8.8:53"];
|
||||
propagation = {
|
||||
delayBeforeChecks = 60;
|
||||
disableChecks = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
api = {};
|
||||
entryPoints = {
|
||||
web = {
|
||||
address = ":${toString httpPort}";
|
||||
http.redirections.entryPoint = {
|
||||
to = "websecure";
|
||||
scheme = "https";
|
||||
};
|
||||
};
|
||||
websecure = {
|
||||
address = ":${toString httpsPort}";
|
||||
};
|
||||
};
|
||||
};
|
||||
dynamicConfigOptions = {
|
||||
http = {
|
||||
services = {
|
||||
dummy = {
|
||||
loadBalancer.servers = [
|
||||
{url = "http://192.168.0.1";} # Diese URL wird nie verwendet
|
||||
];
|
||||
};
|
||||
};
|
||||
middlewares = {
|
||||
auth = {
|
||||
basicAuth = {
|
||||
users = ["sascha.koenig:$apr1$1xqdta2b$DIVNvvp5iTUGNccJjguKh."];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
routers = {
|
||||
api = {
|
||||
rule = "Host(`r.az-gruppe.com`)";
|
||||
service = "api@internal";
|
||||
middlewares = ["auth"];
|
||||
entrypoints = ["websecure"];
|
||||
tls = {
|
||||
certResolver = "ionos";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.traefik.serviceConfig = {
|
||||
EnvironmentFile = ["${config.age.secrets.traefik-env.path}"];
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [httpPort httpsPort];
|
||||
}
|
||||
32
hosts/AZ-CLD-1/services/vaultwarden.nix
Normal file
32
hosts/AZ-CLD-1/services/vaultwarden.nix
Normal file
@@ -0,0 +1,32 @@
|
||||
{config, ...}: let
|
||||
serviceName = "vaultwarden";
|
||||
servicePort = config.m3ta.ports.get serviceName;
|
||||
in {
|
||||
services.${serviceName} = {
|
||||
enable = true;
|
||||
dbBackend = "postgresql";
|
||||
config = {
|
||||
ROCKET_ADDRESS = "127.0.0.1";
|
||||
ROCKET_PORT = servicePort;
|
||||
};
|
||||
environmentFile = config.age.secrets.vaultwarden-env.path;
|
||||
};
|
||||
|
||||
# Traefik configuration
|
||||
services.traefik.dynamicConfigOptions.http = {
|
||||
services.${serviceName}.loadBalancer.servers = [
|
||||
{
|
||||
url = "http://localhost:${toString servicePort}/";
|
||||
}
|
||||
];
|
||||
|
||||
routers.${serviceName} = {
|
||||
rule = "Host(`pw.az-gruppe.com`)";
|
||||
tls = {
|
||||
certResolver = "ionos";
|
||||
};
|
||||
service = serviceName;
|
||||
entrypoints = "websecure";
|
||||
};
|
||||
};
|
||||
}
|
||||
35
hosts/AZ-CLD-1/services/zammad.nix
Normal file
35
hosts/AZ-CLD-1/services/zammad.nix
Normal file
@@ -0,0 +1,35 @@
|
||||
{config, ...}: let
|
||||
serviceName = "zammad";
|
||||
servicePort = config.m3ta.ports.get serviceName;
|
||||
in {
|
||||
services.${serviceName} = {
|
||||
enable = true;
|
||||
openPorts = false;
|
||||
port = servicePort;
|
||||
secretKeyBaseFile = config.age.secrets.zammad-secret.path;
|
||||
database = {
|
||||
createLocally = false;
|
||||
port = 5432;
|
||||
host = "127.0.0.1";
|
||||
passwordFile = config.age.secrets.zammad-pw.path;
|
||||
};
|
||||
};
|
||||
|
||||
# Traefik configuration
|
||||
services.traefik.dynamicConfigOptions.http = {
|
||||
services.${serviceName}.loadBalancer.servers = [
|
||||
{
|
||||
url = "http://localhost:${toString servicePort}/";
|
||||
}
|
||||
];
|
||||
|
||||
routers.${serviceName} = {
|
||||
rule = "Host(`help.az-gruppe.com`)";
|
||||
tls = {
|
||||
certResolver = "ionos";
|
||||
};
|
||||
service = serviceName;
|
||||
entrypoints = "websecure";
|
||||
};
|
||||
};
|
||||
}
|
||||
10
hosts/AZ-CLD-1/services/zugferd.nix
Normal file
10
hosts/AZ-CLD-1/services/zugferd.nix
Normal file
@@ -0,0 +1,10 @@
|
||||
{config, ...}: let
|
||||
serviceName = "zugferd-service";
|
||||
zugferdPort = config.m3ta.ports.get serviceName;
|
||||
in {
|
||||
services.${serviceName} = {
|
||||
enable = true;
|
||||
port = zugferdPort;
|
||||
host = "127.0.0.1";
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user