diff --git a/modules/nixos/librechat-oci/default.nix b/modules/nixos/librechat-oci/default.nix index 625df85..df87a02 100644 --- a/modules/nixos/librechat-oci/default.nix +++ b/modules/nixos/librechat-oci/default.nix @@ -8,223 +8,67 @@ let cfg = config.services.librechat-oci; - defaultImages = { - librechat = pkgs.dockerTools.pullImage { - imageName = "ghcr.io/danny-avila/librechat"; - imageDigest = "sha256:a46254938507971e0d4f7ed3f9d116bd9b118f4810b5b75eb716baf575645068"; - hash = "sha256-zevUN6vrs3hymwCGFmk/YXlUzYjN37H+EO5aLxYchyc="; - finalImageName = "ghcr.io/danny-avila/librechat"; - finalImageTag = "v0.8.5"; - }; - - meilisearch = pkgs.dockerTools.pullImage { - imageName = "getmeili/meilisearch"; - imageDigest = "sha256:b839a48d008d4e67e57f78dcff5b21d5e8b8fa066bacd11f97824d6307abb0dd"; - hash = "sha256-+hypvjFDSdnnWAO4tARTnjNd/6HlF6pMg1L6UUffdYM="; - finalImageName = "getmeili/meilisearch"; - finalImageTag = "v1.44.0"; - }; - - mongodb = pkgs.dockerTools.pullImage { - imageName = "mongo"; - imageDigest = "sha256:098862b1339f031900ca66cf8fef799e616d6324fa41b9a263f2ec899552c1ef"; - hash = "sha256-XuWnvcqAAkAGshdQtnngKOOJP2Bd33FXTbGHTRX3nUc="; - finalImageName = "mongo"; - finalImageTag = "8.0.20"; - }; - - vectordb = pkgs.dockerTools.pullImage { - imageName = "pgvector/pgvector"; - imageDigest = "sha256:8809cfffff0082cf260c9ac752f1dd1afc77f6f0a55c4e6411321e78efc3d9a5"; - hash = "sha256-rc6gQLMzv8UOZVLmWKGUESNIo+iPf5DR7T79AmbzWc4="; - finalImageName = "pgvector/pgvector"; - finalImageTag = "0.8.0-pg15-trixie"; - }; - - ragApi = pkgs.dockerTools.pullImage { - imageName = "registry.librechat.ai/danny-avila/librechat-rag-api-dev-lite"; - imageDigest = "sha256:6dfb6832661ff9c26fa329c823ce266059e33567670a763e9ecb9b566b8daa68"; - hash = "sha256-k8pkEgbqT4NU2+2ZjdRFlfFvMUk/1p+pkysbELh95pM="; - finalImageName = "registry.librechat.ai/danny-avila/librechat-rag-api-dev-lite"; - finalImageTag = "latest"; - }; + image = pkgs.dockerTools.pullImage { + imageName = "ghcr.io/danny-avila/librechat"; + imageDigest = "sha256:a46254938507971e0d4f7ed3f9d116bd9b118f4810b5b75eb716baf575645068"; + hash = "sha256-zevUN6vrs3hymwCGFmk/YXlUzYjN37H+EO5aLxYchyc="; + finalImageName = "ghcr.io/danny-avila/librechat"; + finalImageTag = "v0.8.5"; }; defaultEnv = { - # Server HOST = "0.0.0.0"; - PORT = toString cfg.port; + PORT = "3080"; NO_INDEX = "true"; - TRUST_PROXY = "1"; - - # Logging + DEBUG_LOGGING = "false"; CONSOLE_JSON = "false"; - DEBUG_LOGGING = "true"; - DEBUG_CONSOLE = "false"; - AGENT_DEBUG_LOGGING = "false"; - DEBUG_OPENAI = "false"; - - # Node - NODE_MAX_OLD_SPACE_SIZE = "6144"; - - # Search + ALLOW_REGISTRATION = "true"; + ALLOW_EMAIL_LOGIN = "true"; SEARCH = "true"; MEILI_NO_ANALYTICS = "true"; - - # Moderation - OPENAI_MODERATION = "false"; - BAN_VIOLATIONS = "true"; - BAN_DURATION = "1000 * 60 * 60 * 2"; - BAN_INTERVAL = "20"; - LOGIN_VIOLATION_SCORE = "1"; - REGISTRATION_VIOLATION_SCORE = "1"; - CONCURRENT_VIOLATION_SCORE = "1"; - MESSAGE_VIOLATION_SCORE = "1"; - NON_BROWSER_VIOLATION_SCORE = "20"; - TTS_VIOLATION_SCORE = "0"; - STT_VIOLATION_SCORE = "0"; - FORK_VIOLATION_SCORE = "0"; - IMPORT_VIOLATION_SCORE = "0"; - FILE_UPLOAD_VIOLATION_SCORE = "0"; - LOGIN_MAX = "7"; - LOGIN_WINDOW = "5"; - REGISTER_MAX = "5"; - REGISTER_WINDOW = "60"; - LIMIT_CONCURRENT_MESSAGES = "true"; - CONCURRENT_MESSAGE_MAX = "2"; - LIMIT_MESSAGE_IP = "true"; - MESSAGE_IP_MAX = "40"; - MESSAGE_IP_WINDOW = "1"; - LIMIT_MESSAGE_USER = "false"; - MESSAGE_USER_MAX = "40"; - MESSAGE_USER_WINDOW = "1"; - ILLEGAL_MODEL_REQ_SCORE = "5"; - - # Registration and login - ALLOW_EMAIL_LOGIN = "true"; - ALLOW_REGISTRATION = "false"; - ALLOW_SOCIAL_LOGIN = "false"; - ALLOW_SOCIAL_REGISTRATION = "false"; - ALLOW_PASSWORD_RESET = "false"; - ALLOW_UNVERIFIED_EMAIL_LOGIN = "true"; - SESSION_EXPIRY = "1000 * 60 * 15"; - REFRESH_TOKEN_EXPIRY = "(1000 * 60 * 60 * 24) * 7"; - - # OpenID - OPENID_SCOPE = "openid profile email"; - OPENID_CALLBACK_URL = "/oauth/openid/callback"; - OPENID_AUTO_REDIRECT = "false"; - OPENID_USE_PKCE = "false"; - OPENID_ON_BEHALF_FLOW_USERINFO_SCOPE = "user.read"; - - # OAuth callback URLs - DISCORD_CALLBACK_URL = "/oauth/discord/callback"; - FACEBOOK_CALLBACK_URL = "/oauth/facebook/callback"; - GITHUB_CALLBACK_URL = "/oauth/github/callback"; - GOOGLE_CALLBACK_URL = "/oauth/google/callback"; - APPLE_CALLBACK_URL = "/oauth/apple/callback"; - SAML_CALLBACK_URL = "/oauth/saml/callback"; - - # Entra ID - USE_ENTRA_ID_FOR_PEOPLE_SEARCH = "false"; - ENTRA_ID_INCLUDE_OWNERS_AS_MEMBERS = "false"; - OPENID_GRAPH_SCOPES = "User.Read,People.Read,GroupMember.Read.All"; - - # Shared links - ALLOW_SHARED_LINKS = "true"; - ALLOW_SHARED_LINKS_PUBLIC = "false"; - - # UI - APP_TITLE = "LibreChat"; - HELP_AND_FAQ_URL = "https://librechat.ai"; - - # Flux - FLUX_API_BASE_URL = "https://api.us1.bfl.ai"; - - # Email - EMAIL_PORT = "25"; - EMAIL_FROM = "noreply@librechat.ai"; - - # Azure Blob Storage - AZURE_STORAGE_PUBLIC_ACCESS = "false"; - AZURE_CONTAINER_NAME = "files"; }; - mkImageOption = - name: description: - mkOption { - type = types.package; - default = defaultImages.${name}; - description = description; - }; - inherit (lib) literalExpression mkEnableOption mkIf mkOption mkOverride - optional types ; in { options.services.librechat-oci = { enable = mkEnableOption "LibreChat container with Podman."; - - images = { - librechat = mkImageOption "librechat" "The LibreChat Docker image (`pkgs.dockerTools.pullImage`)."; - meilisearch = mkImageOption "meilisearch" "The Meilisearch Docker image (`pkgs.dockerTools.pullImage`)."; - mongodb = mkImageOption "mongodb" "The MongoDB Docker image (`pkgs.dockerTools.pullImage`)."; - vectordb = mkImageOption "vectordb" "The pgvector Docker image (`pkgs.dockerTools.pullImage`)."; - ragApi = mkImageOption "ragApi" "The LibreChat RAG API Docker image (`pkgs.dockerTools.pullImage`)."; + image = mkOption { + type = types.package; + default = image; + description = "The Docker image to use (`pkgs.dockerTools.pullImage`)."; }; - externalUrl = mkOption { type = types.nullOr types.str; default = null; - example = literalExpression ''"https://chat.example.com"''; - description = "Public URL to configure for LibreChat (sets DOMAIN_CLIENT and DOMAIN_SERVER)."; + example = literalExpression "http://${config.networking.domain}"; + description = "Public URL to configure for LibreChat."; }; - port = mkOption { type = types.port; default = 3080; - description = "Which port the LibreChat server listens on."; + description = "Which port the LibreChat server listens to."; }; - - meiliPort = mkOption { - type = types.port; - default = 7700; - description = "Which port Meilisearch listens on."; - }; - - ragPort = mkOption { - type = types.port; - default = 8000; - description = "Which port the RAG API listens on."; - }; - environment = mkOption { default = { }; type = types.attrsOf types.str; description = '' Extra environment variables for LibreChat. - These are merged on top of the defaults and can override them. - For secrets use instead. - See . + For more details see ''; }; - environmentFile = mkOption { + description = "Environment file to be passed to the LibreChat container."; type = types.nullOr types.path; default = null; - example = literalExpression "config.sops.templates.librechat-env.path"; - description = '' - Environment file passed to the LibreChat, Meilisearch, and RAG API - containers. Use this for secrets such as JWT_SECRET, CREDS_KEY, - MEILI_MASTER_KEY, and API keys. - ''; + example = "config.sops.templates.librechat-env.path"; }; }; @@ -245,273 +89,159 @@ in virtualisation.oci-containers.backend = "podman"; - virtualisation.oci-containers.containers.librechat-mongodb = { - image = with cfg.images.mongodb; "${imageName}:${imageTag}"; - imageFile = cfg.images.mongodb; - cmd = [ - "mongod" - "--noauth" - ]; + virtualisation.oci-containers.containers."librechat-mongodb" = { + image = "mongo:7.0"; + environment = { + MONGO_INITDB_ROOT_USERNAME = "root"; + MONGO_INITDB_ROOT_PASSWORD = "librechat"; + MONGO_INITDB_DATABASE = "LibreChat"; + }; volumes = [ "librechat_mongodb_data:/data/db:rw" ]; log-driver = "journald"; extraOptions = [ - "--network-alias=mongodb" - "--network=librechat_default" - ]; - }; - - virtualisation.oci-containers.containers.librechat-meilisearch = { - image = with cfg.images.meilisearch; "${imageName}:${imageTag}"; - imageFile = cfg.images.meilisearch; - environment = { - MEILI_NO_ANALYTICS = "true"; - MEILI_HOST = "http://meilisearch:${toString cfg.meiliPort}"; - }; - environmentFiles = optional (cfg.environmentFile != null) cfg.environmentFile; - volumes = [ - "librechat_meili_data:/meili_data:rw" - ]; - log-driver = "journald"; - extraOptions = [ - "--network-alias=meilisearch" - "--network=librechat_default" - ]; - }; - - virtualisation.oci-containers.containers.librechat-vectordb = { - image = with cfg.images.vectordb; "${imageName}:${imageTag}"; - imageFile = cfg.images.vectordb; - environment = { - POSTGRES_DB = "mydatabase"; - POSTGRES_USER = "myuser"; - POSTGRES_PASSWORD = "mypassword"; - }; - volumes = [ - "librechat_pgdata:/var/lib/postgresql/data:rw" - ]; - log-driver = "journald"; - extraOptions = [ - "--network-alias=vectordb" - "--network=librechat_default" - ]; - }; - - virtualisation.oci-containers.containers.librechat-rag-api = { - image = with cfg.images.ragApi; "${imageName}:${imageTag}"; - imageFile = cfg.images.ragApi; - environment = { - DB_HOST = "vectordb"; - RAG_PORT = toString cfg.ragPort; - }; - environmentFiles = optional (cfg.environmentFile != null) cfg.environmentFile; - dependsOn = [ "librechat-vectordb" ]; - log-driver = "journald"; - extraOptions = [ - "--network-alias=rag_api" - "--network=librechat_default" + "--network=host" ]; }; virtualisation.oci-containers.containers.librechat = { - image = with cfg.images.librechat; "${imageName}:${imageTag}"; - imageFile = cfg.images.librechat; + image = with cfg.image; imageName + ":" + imageTag; + imageFile = cfg.image; environment = defaultEnv // { - MONGO_URI = "mongodb://mongodb:27017/LibreChat"; - MEILI_HOST = "http://meilisearch:${toString cfg.meiliPort}"; - RAG_PORT = toString cfg.ragPort; - RAG_API_URL = "http://rag_api:${toString cfg.ragPort}"; - DOMAIN_CLIENT = - if cfg.externalUrl != null then cfg.externalUrl else "http://localhost:${toString cfg.port}"; - DOMAIN_SERVER = - if cfg.externalUrl != null then cfg.externalUrl else "http://localhost:${toString cfg.port}"; + MONGO_URI = "mongodb://root:librechat@localhost:27017/LibreChat?authSource=admin"; } // cfg.environment; - environmentFiles = optional (cfg.environmentFile != null) cfg.environmentFile; volumes = [ + "librechat_data:/app/client/data:rw" "librechat_images:/app/client/public/images:rw" - "librechat_uploads:/app/uploads:rw" + "librechat_uploads:/app/api/server/files/uploads:rw" "librechat_logs:/app/logs:rw" ]; ports = [ - "127.0.0.1:${toString cfg.port}:${toString cfg.port}/tcp" - ]; - dependsOn = [ - "librechat-mongodb" - "librechat-rag-api" + "${toString cfg.port}:${toString cfg.port}" ]; log-driver = "journald"; extraOptions = [ - "--network-alias=api" - "--network=librechat_default" + "--network=host" ]; }; - systemd.services.podman-librechat-mongodb = { - serviceConfig.Restart = mkOverride 90 "always"; + systemd.services."podman-librechat-mongodb" = { + serviceConfig = { + Restart = mkOverride 90 "always"; + }; after = [ - "podman-network-librechat_default.service" "podman-volume-librechat_mongodb_data.service" ]; requires = [ - "podman-network-librechat_default.service" "podman-volume-librechat_mongodb_data.service" ]; - partOf = [ "podman-compose-librechat-root.target" ]; - wantedBy = [ "podman-compose-librechat-root.target" ]; + partOf = [ + "podman-compose-librechat-root.target" + ]; + wantedBy = [ + "podman-compose-librechat-root.target" + ]; }; - systemd.services.podman-librechat-meilisearch = { - serviceConfig.Restart = mkOverride 90 "always"; + systemd.services."podman-librechat" = { + serviceConfig = { + Restart = mkOverride 90 "always"; + }; after = [ - "podman-network-librechat_default.service" - "podman-volume-librechat_meili_data.service" - ]; - requires = [ - "podman-network-librechat_default.service" - "podman-volume-librechat_meili_data.service" - ]; - partOf = [ "podman-compose-librechat-root.target" ]; - wantedBy = [ "podman-compose-librechat-root.target" ]; - }; - - systemd.services.podman-librechat-vectordb = { - serviceConfig.Restart = mkOverride 90 "always"; - after = [ - "podman-network-librechat_default.service" - "podman-volume-librechat_pgdata.service" - ]; - requires = [ - "podman-network-librechat_default.service" - "podman-volume-librechat_pgdata.service" - ]; - partOf = [ "podman-compose-librechat-root.target" ]; - wantedBy = [ "podman-compose-librechat-root.target" ]; - }; - - systemd.services.podman-librechat-rag-api = { - serviceConfig.Restart = mkOverride 90 "always"; - after = [ - "podman-network-librechat_default.service" - "podman-librechat-vectordb.service" - ]; - requires = [ - "podman-network-librechat_default.service" - "podman-librechat-vectordb.service" - ]; - partOf = [ "podman-compose-librechat-root.target" ]; - wantedBy = [ "podman-compose-librechat-root.target" ]; - }; - - systemd.services.podman-librechat = { - serviceConfig.Restart = mkOverride 90 "always"; - after = [ - "podman-network-librechat_default.service" + "podman-volume-librechat_data.service" "podman-volume-librechat_images.service" "podman-volume-librechat_uploads.service" "podman-volume-librechat_logs.service" "podman-librechat-mongodb.service" - "podman-librechat-meilisearch.service" - "podman-librechat-rag-api.service" ]; requires = [ - "podman-network-librechat_default.service" + "podman-volume-librechat_data.service" "podman-volume-librechat_images.service" "podman-volume-librechat_uploads.service" "podman-volume-librechat_logs.service" "podman-librechat-mongodb.service" - "podman-librechat-meilisearch.service" - "podman-librechat-rag-api.service" ]; - partOf = [ "podman-compose-librechat-root.target" ]; - wantedBy = [ "podman-compose-librechat-root.target" ]; + partOf = [ + "podman-compose-librechat-root.target" + ]; + wantedBy = [ + "podman-compose-librechat-root.target" + ]; }; - systemd.services.podman-network-librechat_default = { + systemd.services."podman-volume-librechat_data" = { path = [ pkgs.podman ]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; - ExecStop = "podman network rm -f librechat_default"; }; script = '' - podman network inspect librechat_default || podman network create librechat_default + podman volume inspect librechat_data || podman volume create librechat_data ''; partOf = [ "podman-compose-librechat-root.target" ]; wantedBy = [ "podman-compose-librechat-root.target" ]; }; - systemd.services.podman-volume-librechat_mongodb_data = { + systemd.services."podman-volume-librechat_images" = { path = [ pkgs.podman ]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; }; - script = "podman volume inspect librechat_mongodb_data || podman volume create librechat_mongodb_data"; + script = '' + podman volume inspect librechat_images || podman volume create librechat_images + ''; partOf = [ "podman-compose-librechat-root.target" ]; wantedBy = [ "podman-compose-librechat-root.target" ]; }; - systemd.services.podman-volume-librechat_meili_data = { + systemd.services."podman-volume-librechat_uploads" = { path = [ pkgs.podman ]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; }; - script = "podman volume inspect librechat_meili_data || podman volume create librechat_meili_data"; + script = '' + podman volume inspect librechat_uploads || podman volume create librechat_uploads + ''; partOf = [ "podman-compose-librechat-root.target" ]; wantedBy = [ "podman-compose-librechat-root.target" ]; }; - systemd.services.podman-volume-librechat_pgdata = { + systemd.services."podman-volume-librechat_logs" = { path = [ pkgs.podman ]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; }; - script = "podman volume inspect librechat_pgdata || podman volume create librechat_pgdata"; + script = '' + podman volume inspect librechat_logs || podman volume create librechat_logs + ''; partOf = [ "podman-compose-librechat-root.target" ]; wantedBy = [ "podman-compose-librechat-root.target" ]; }; - systemd.services.podman-volume-librechat_images = { + systemd.services."podman-volume-librechat_mongodb_data" = { path = [ pkgs.podman ]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; }; - script = "podman volume inspect librechat_images || podman volume create librechat_images"; + script = '' + podman volume inspect librechat_mongodb_data || podman volume create librechat_mongodb_data + ''; partOf = [ "podman-compose-librechat-root.target" ]; wantedBy = [ "podman-compose-librechat-root.target" ]; }; - systemd.services.podman-volume-librechat_uploads = { - path = [ pkgs.podman ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; + systemd.targets."podman-compose-librechat-root" = { + unitConfig = { + Description = "Root target generated by compose2nix."; }; - script = "podman volume inspect librechat_uploads || podman volume create librechat_uploads"; - partOf = [ "podman-compose-librechat-root.target" ]; - wantedBy = [ "podman-compose-librechat-root.target" ]; - }; - - systemd.services.podman-volume-librechat_logs = { - path = [ pkgs.podman ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - }; - script = "podman volume inspect librechat_logs || podman volume create librechat_logs"; - partOf = [ "podman-compose-librechat-root.target" ]; - wantedBy = [ "podman-compose-librechat-root.target" ]; - }; - - systemd.targets.podman-compose-librechat-root = { - unitConfig.Description = "Root target generated by compose2nix."; wantedBy = [ "multi-user.target" ]; }; };