From c113b5d3efdd4cfc971c789bed4d5910c30092b3 Mon Sep 17 00:00:00 2001 From: Mohammad Rafiq Date: Sat, 31 May 2025 13:52:33 +0800 Subject: [PATCH] refactor(librechat): declare options nicely --- modules/nixos/server/librechat/default.nix | 225 ++++++++++++++++++--- systems/x86_64-linux/apollo/default.nix | 13 +- 2 files changed, 208 insertions(+), 30 deletions(-) diff --git a/modules/nixos/server/librechat/default.nix b/modules/nixos/server/librechat/default.nix index a09f798..f306b05 100644 --- a/modules/nixos/server/librechat/default.nix +++ b/modules/nixos/server/librechat/default.nix @@ -10,37 +10,210 @@ let in { options.server.librechat = { - enable = lib.mkEnableOption ""; - openFirewall = lib.mkEnableOption ""; - host = lib.mkOption { - type = lib.types.str; - default = "0.0.0.0"; - }; - port = lib.mkOption { - type = lib.types.int; - default = 3080; - }; - mongodbURI = lib.mkOption { type = lib.types.str; }; - creds_key_file = lib.mkOption { type = lib.types.str; }; - creds_iv_file = lib.mkOption { type = lib.types.str; }; - jwt_secret_file = lib.mkOption { type = lib.types.str; }; - jwt_refresh_secret_file = lib.mkOption { type = lib.types.str; }; - meili_master_key_file = lib.mkOption { type = lib.types.str; }; + enable = lib.mkEnableOption "Whether to enable the LibreChat server."; + openFirewall = lib.mkEnableOption "Whether to open the port in the firewall."; + path = lib.mkOption { type = lib.types.str; default = "/var/lib/librechat"; + description = "Absolute path for where the LibreChat server will use as its working directory."; }; + user = lib.mkOption { type = lib.types.str; default = "librechat"; + description = "The user to run the service as."; }; + group = lib.mkOption { type = lib.types.str; default = "librechat"; + description = "The group to run the service as."; }; + + prevent-indexing = lib.mkOption { + type = lib.types.bool; + default = true; + example = false; + description = "Prevents public search engines from indexing your website."; + }; + + host = lib.mkOption { + type = lib.types.str; + default = "localhost"; + example = "0.0.0.0"; + description = "Specifies the host."; + }; + + port = lib.mkOption { + type = lib.types.int; + default = 3080; + example = 2309; + description = "Specifies the port."; + }; + + #TODO: Add option to use documentDb. + mongodbURI = lib.mkOption { + type = lib.types.str; + default = ""; + example = "mongodb://127.0.0.1:27017/LibreChat"; + description = "Specifies the MongoDB URI. Must be set or the app will crash on startup."; + }; + + trust_proxy = lib.mkOption { + type = lib.types.int; + default = 1; + example = 0; + description = "Use the address that is at most n number of hops away from the Express application. See https://expressjs.com/en/guide/behind-proxies.html for more information about this."; + }; + + credentials = { + creds_key = lib.mkOption { + type = lib.types.str; + default = ""; + description = "32-byte key (64 characters in hex) for securely storing credentials. Required for app startup. WARNING: If you don't set this or the _file option, the app will crash on startup. You can use this https://www.librechat.ai/toolkit/creds_generator to generate them quickly."; + }; + creds_key_file = lib.mkOption { + type = lib.types.str; + default = ""; + description = "Path to file containing 32-byte key (64 characters in hex) for securely storing credentials. Required for app startup. WARNING: If you don't set this or the _file option, the app will crash on startup. You can use this https://www.librechat.ai/toolkit/creds_generator to generate them quickly."; + }; + creds_iv = lib.mkOption { + type = lib.types.str; + default = ""; + description = "16-byte IV (32 characters in hex) for securely storing credentials. Required for app startup. WARNING: If you don't set this or the _file option, the app will crash on startup. You can use this https://www.librechat.ai/toolkit/creds_generator to generate them quickly."; + }; + creds_iv_file = lib.mkOption { + type = lib.types.str; + default = ""; + description = "Path to file containing 16-byte IV (32 characters in hex) for securely storing credentials. Required for app startup. WARNING: If you don't set this or the _file option, the app will crash on startup. You can use this https://www.librechat.ai/toolkit/creds_generator to generate them quickly."; + }; + jwt_secret = lib.mkOption { + type = lib.types.str; + default = ""; + description = "JWT secret key. Generate with https://www.librechat.ai/toolkit/creds_generator."; + }; + jwt_secret_file = lib.mkOption { + type = lib.types.str; + default = ""; + description = "Absolute path to file containing JWT secret key. Generate with https://www.librechat.ai/toolkit/creds_generator."; + }; + jwt_refresh_secret = lib.mkOption { + type = lib.types.str; + default = ""; + description = "JWT refresh secret key. Generate with https://www.librechat.ai/toolkit/creds_generator."; + }; + jwt_refresh_secret_file = lib.mkOption { + type = lib.types.str; + default = ""; + description = "Absolute path to file containing JWT refresh secret key. Generate with https://www.librechat.ai/toolkit/creds_generator."; + }; + }; + + app_domains = { + client = lib.mkOption { + type = lib.types.str; + default = "http://localhost:${cfg.port}"; + example = "https://librechat.example.com"; + description = "Specifies the client-side domain."; + }; + server = lib.mkOption { + type = lib.types.str; + default = "http://localhost:${cfg.port}"; + example = "https://librechat.example.com"; + description = "Specifies the server-side domain."; + }; + }; + + logging = { + enableDebugLogging = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Keep debug logs active."; + }; + enableConsoleLogging = lib.mkEnableOption "Enable verbose console/stdout logs in the same format as file debug logs."; + enableConsoleJSONLogging = lib.mkEnableOption "Enable verbose JSON console/stdout logs suitable for cloud deployments like GCP/AWS."; + consoleJSONLoggingLength = lib.mkOption { + type = lib.types.int; + default = 255; + description = "Configure the truncation size for console/stdout logs."; + }; + }; + + static_cache = { + max_age = lib.mkOption { + type = lib.types.str; + default = "172800"; + description = "Cache-Control max-age in seconds."; + }; + s_max_age = lib.mkOption { + type = lib.types.str; + default = "86400"; + description = "Cache-Control s-maxage in seconds for shared caches (CDNs and proxies)."; + }; + disable_compression = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Disables compression for static files."; + }; + }; + + index_page_caching = { + cache_control = lib.mkOption { + type = lib.types.str; + default = "no-cache, no-store, must-revalidate"; + description = "Cache-Control header for index.html."; + }; + pragma = lib.mkOption { + type = lib.types.str; + default = "no-cache"; + description = "Pragma header for index.html."; + }; + expires = lib.mkOption { + type = lib.types.str; + default = "0"; + description = "Expires header for index.html."; + }; + }; + }; config = lib.mkIf cfg.enable { + + assertions = [ + { + assertion = (cfg.credentials.creds_key != "") || (cfg.credentials.creds_key_file != ""); + message = "You must set either credentials.creds_key or credentials.creds_key_file."; + } + { + assertion = (cfg.credentials.creds_iv != "") || (cfg.credentials.creds_iv_file != ""); + message = "You must set either credentials.creds_iv or credentials.creds_iv_file."; + } + { + assertion = cfg.mongodbURI != ""; + message = "You must set the mongodbURI option."; + } + { + assertion = + cfg.logging.enableDebugLogging + && ( + (cfg.logging.enableConsoleLogging && !cfg.logging.enableConsoleJSONLogging) + || (!cfg.logging.enableConsoleLogging && cfg.logging.enableConsoleJSONLogging) + || (!cfg.logging.enableConsoleLogging && !cfg.logging.enableConsoleJSONLogging) + ); + message = "DEBUG_LOGGING can be used with either DEBUG_CONSOLE or CONSOLE_JSON but not both."; + } + { + assertion = (cfg.credentials.jwt_secret != "") || (cfg.credentials.jwt_secret_file != ""); + message = "You must set either credentials.jwt_secret or credentials.jwt_secret_file."; + } + { + assertion = + (cfg.credentials.jwt_refresh_secret != "") || (cfg.credentials.jwt_refresh_secret_file != ""); + message = "You must set either credentials.jwt_refresh_secret or credentials.jwt_refresh_secret_file."; + } + ]; + networking.firewall.allowedTCPPorts = if cfg.openFirewall then [ cfg.port ] else [ ]; systemd.services.librechat = { wantedBy = [ "multi-user.target" ]; @@ -56,23 +229,25 @@ in "${pkgs.coreutils}/bin/chown -R ${cfg.user}:${cfg.group} ${cfg.path}" ]; LoadCredential = [ - "CREDS_KEY_FILE:${cfg.creds_key_file}" - "CREDS_IV_FILE:${cfg.creds_iv_file}" - "JWT_SECRET_FILE:${cfg.jwt_secret_file}" - "JWT_REFRESH_SECRET_FILE:${cfg.jwt_refresh_secret_file}" - "MEILI_MASTER_KEY_FILE:${cfg.meili_master_key_file}" + #TODO: Use the creds_* options + "CREDS_KEY_FILE:${cfg.credentials.creds_key_file}" + "CREDS_IV_FILE:${cfg.credentials.creds_iv_file}" + "JWT_SECRET_FILE:${cfg.credentials.jwt_secret_file}" + "JWT_REFRESH_SECRET_FILE:${cfg.credentials.jwt_refresh_secret_file}" ]; }; script = # sh '' - export HOST=${cfg.host} - export PORT=${builtins.toString cfg.port} - export MONGO_URI="${cfg.mongodbURI}" + # Load the systemd credentials export CREDS_KEY=$(${pkgs.systemd}/bin/systemd-creds cat CREDS_KEY_FILE) export CREDS_IV=$(${pkgs.systemd}/bin/systemd-creds cat CREDS_IV_FILE) export JWT_SECRET=$(${pkgs.systemd}/bin/systemd-creds cat JWT_SECRET_FILE) export JWT_REFRESH_SECRET=$(${pkgs.systemd}/bin/systemd-creds cat JWT_REFRESH_SECRET_FILE) - export MEILI_MASTER_KEY=$(${pkgs.systemd}/bin/systemd-creds cat MEILI_MASTER_KEY_FILE) + + export HOST=${cfg.host} + export PORT=${builtins.toString cfg.port} + export MONGO_URI="${cfg.mongodbURI}" + cd ${cfg.path} ${pkgs.librechat}/bin/librechat-server ''; diff --git a/systems/x86_64-linux/apollo/default.nix b/systems/x86_64-linux/apollo/default.nix index 0f594ec..8d28c32 100644 --- a/systems/x86_64-linux/apollo/default.nix +++ b/systems/x86_64-linux/apollo/default.nix @@ -27,12 +27,15 @@ databases.mongodb.enable = true; librechat = { enable = true; + openFirewall = true; + host = "0.0.0.0"; mongodbURI = "mongodb://apollo:27017"; - creds_key_file = config.sops.secrets."librechat/creds_key".path; - creds_iv_file = config.sops.secrets."librechat/creds_iv".path; - jwt_secret_file = config.sops.secrets."librechat/jwt_secret".path; - jwt_refresh_secret_file = config.sops.secrets."librechat/jwt_refresh_secret".path; - meili_master_key_file = config.sops.secrets."librechat/meili_master_key".path; + credentials = { + creds_key_file = config.sops.secrets."librechat/creds_key".path; + creds_iv_file = config.sops.secrets."librechat/creds_iv".path; + jwt_secret_file = config.sops.secrets."librechat/jwt_secret".path; + jwt_refresh_secret_file = config.sops.secrets."librechat/jwt_refresh_secret".path; + }; }; };