{ config, lib, pkgs, ... }: let serviceName = "librechat"; portUtils = import ../../../../lib/port-utils.nix {inherit lib;}; servicePort = portUtils.getPort serviceName "AZ-CLD-1"; ragApiPort = portUtils.getPort "rag-api" "AZ-CLD-1"; envFileProd = config.age.secrets.librechat-env-prod.path; envFileCommon = config.age.secrets.librechat.path; in { virtualisation.oci-containers = { containers.meilisearch = { image = "getmeili/meilisearch:v1.12.3"; 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 = "ghcr.io/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:7"; 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 = "ghcr.io/danny-avila/librechat-dev-api:latest"; autoStart = true; 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"]; }; }; 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"; }; }; }