Files
Gutyina GergőandNiklas Korz d0200948ee nixos/librechat: set config version by default
Looks like after upgrading from 0.8.0 to 0.8.2 this is mandatory.
2026-03-23 10:13:06 +01:00

304 lines
10 KiB
Nix

{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.librechat;
meiliCfg = config.services.meilisearch;
format = pkgs.formats.yaml { };
configFile = format.generate "librechat.yaml" cfg.settings;
exportCredentials = n: _: ''export ${n}="$(${pkgs.systemd}/bin/systemd-creds cat ${n}_FILE)"'';
exportAllCredentials = vars: lib.concatStringsSep "\n" (lib.mapAttrsToList exportCredentials vars);
getLoadCredentialList = lib.mapAttrsToList (n: v: "${n}_FILE:${v}") cfg.credentials;
in
{
options.services.librechat = {
enable = lib.mkEnableOption "the LibreChat server";
package = lib.mkPackageOption pkgs "librechat" { };
openFirewall = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to open the port in the firewall.
'';
};
dataDir = lib.mkOption {
type = lib.types.path;
default = "/var/lib/librechat";
example = "/persist/librechat";
description = "Absolute path for where the LibreChat server will use as its data directory to store logs, user uploads, and generated images.";
};
user = lib.mkOption {
type = lib.types.str;
default = "librechat";
example = "alice";
description = "The user to run the service as.";
};
group = lib.mkOption {
type = lib.types.str;
default = "librechat";
example = "users";
description = "The group to run the service as.";
};
credentials = lib.mkOption {
type = lib.types.attrsOf lib.types.path;
default = { };
example = {
CREDS_KEY = "/run/secrets/creds_key";
};
description = ''
Environment variables which are loaded from the contents of files at a file paths, mainly used for secrets.
See [LibreChat environment variables](https://www.librechat.ai/docs/configuration/dotenv).
Alternatively you can use `services.librechat.credentialsFile` to define all the variables in a single file.
'';
};
env = lib.mkOption {
type = lib.types.submodule {
freeformType =
with lib.types;
attrsOf (oneOf [
str
path
(coercedTo int toString str)
(coercedTo float toString str)
(coercedTo port toString str)
(coercedTo bool (x: if x then "true" else "false") str)
]);
options = {
CONFIG_PATH = lib.mkOption {
default = configFile;
internal = true;
readOnly = true;
};
PORT = lib.mkOption {
type = with lib.types; coercedTo port toString str;
default = 3080;
example = 2309;
description = "The value that will be passed to the PORT environment variable, telling LibreChat what to listen on.";
};
LIBRECHAT_LOG_DIR = lib.mkOption {
type = lib.types.str;
default = "${cfg.dataDir}/logs";
defaultText = lib.literalExpression "/var/lib/librechat/logs";
description = ''
Logs will be saved into this directory.
By default it is relative to `services.librechat.dataDir`.
'';
};
};
};
example = {
ALLOW_REGISTRATION = true;
HOST = "0.0.0.0";
PORT = 2309;
CONSOLE_JSON_STRING_LENGTH = 255;
};
description = ''
Environment variables that will be set for the service.
See [LibreChat environment variables](https://www.librechat.ai/docs/configuration/dotenv).
'';
};
credentialsFile = lib.mkOption {
type = lib.types.nullOr lib.types.path;
description = ''
Path to a file that contains environment variables.
See [LibreChat environment variables](https://www.librechat.ai/docs/configuration/dotenv).
Example content of the file:
```
CREDS_KEY=6d6deb03cdfb27ea454f6b9ddd42494bdce4af25d50d8aee454ddce583690cc5
```
Alternatively you can use `services.librechat.credentials` to define the value of each variable in a separate file.
'';
default = "/dev/null";
example = "/run/secrets/librechat";
};
settings = lib.mkOption {
type = lib.types.submodule {
freeformType = format.type;
};
default = {
version = "1.2.1";
};
example = {
version = "1.2.1";
cache = true;
interface = {
privacyPolicy = {
externalUrl = "https://librechat.ai/privacy-policy";
openNewTab = true;
};
};
endpoints = {
custom = [
{
name = "OpenRouter";
apiKey = "\${OPENROUTER_KEY}";
baseURL = "https://openrouter.ai/api/v1";
models = {
default = [ "meta-llama/llama-3-70b-instruct" ];
fetch = true;
};
titleConvo = true;
titleModule = "meta-llama/llama-3-70b-instruct";
dropParams = [ "stop" ];
modelDisplayLabel = "OpenRouter";
}
];
};
};
description = ''
A free-form attribute set that will be written to librechat.yaml.
See the [LibreChat configuration options](https://www.librechat.ai/docs/configuration/librechat_yaml).
You can use environment variables by wrapping them in $\{}. Take care to escape the \$ character.
'';
};
enableLocalDB = lib.mkEnableOption "a local mongodb instance";
meilisearch = lib.mkOption {
type = lib.types.submodule {
options = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
example = true;
description = ''
Whether to enable and configure Meilisearch locally for Librechat.
You will manually need to set `services.meilisearch.masterKeyFile`.
'';
};
};
};
default = { };
description = ''
See [LibreChat search feature](https://www.librechat.ai/docs/features/search).
'';
};
};
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = cfg.env ? MONGO_URI || cfg.credentials ? MONGO_URI;
message = "MongoDB is not configured, either set `services.librechat.enableLocalDB = true` or provide your own MongoDB instance by setting `services.librechat.env.MONGO_URI` or `services.credentials.MONGO_URI`.";
}
{
assertion =
cfg.credentialsFile != "/dev/null"
|| (
(cfg.env ? CREDS_KEY || cfg.credentials ? CREDS_KEY)
&& (cfg.env ? CREDS_IV || cfg.credentials ? CREDS_IV)
&& (cfg.env ? JWT_SECRET || cfg.credentials ? JWT_SECRET)
&& (cfg.env ? JWT_REFRESH_SECRET || cfg.credentials ? JWT_REFRESH_SECRET)
);
message = ''
CREDS_KEY, CREDS_IV, JWT_SECRET, and JWT_REFRESH_SECRET must be defined in `services.librechat.credentials` and point to locations of files on the host or in a file that `services.credentialsFile` is pointing to.
Alternatively it can be defined in `services.librechat.env` with literal values but they will be saved within the world-readable nix store.;
You can use https://www.librechat.ai/toolkit/creds_generator to generate these.
'';
}
{
assertion = cfg.meilisearch.enable -> meiliCfg.masterKeyFile != null;
message = ''
LibreChat's Meilisearch integration requires `services.meilisearch.masterKeyFile` to be set.
'';
}
];
networking.firewall.allowedTCPPorts = lib.optional cfg.openFirewall cfg.port;
systemd.tmpfiles.settings."10-librechat"."${cfg.dataDir}".d = {
mode = "0755";
inherit (cfg) user group;
};
systemd.services.librechat = {
wantedBy = [ "multi-user.target" ];
after = [
"tmpfiles.target"
]
++ lib.optional cfg.meilisearch.enable "meilisearch.service";
wants = lib.optional cfg.meilisearch.enable "meilisearch.service";
description = "Open-source app for all your AI conversations, fully customizable and compatible with any AI provider";
environment = cfg.env;
script = # sh
''
${exportAllCredentials cfg.credentials}
cd ${cfg.dataDir}
${lib.getExe cfg.package}
'';
serviceConfig = {
Type = "simple";
User = cfg.user;
Group = cfg.group;
StateDirectory = baseNameOf cfg.dataDir;
WorkingDirectory = cfg.dataDir;
LoadCredential = getLoadCredentialList;
EnvironmentFile = cfg.credentialsFile;
Restart = "on-failure";
RestartSec = 10;
# Hardening
CapabilityBoundingSet = "";
NoNewPrivileges = true;
PrivateUsers = true;
PrivateTmp = true;
PrivateDevices = true;
PrivateMounts = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
UMask = "0077";
};
};
users.users.librechat = lib.mkIf (cfg.user == "librechat") {
name = "librechat";
isSystemUser = true;
group = "librechat";
description = "LibreChat server user";
};
users.groups.librechat = lib.mkIf (cfg.user == "librechat") { };
services.librechat.env.MONGO_URI = lib.mkIf cfg.enableLocalDB "mongodb://localhost:27017";
services.mongodb.enable = lib.mkIf cfg.enableLocalDB true;
services.meilisearch.enable = lib.mkIf cfg.meilisearch.enable true;
services.librechat.env.SEARCH = lib.mkIf cfg.meilisearch.enable true;
services.librechat.env.MEILI_HOST = lib.mkIf cfg.meilisearch.enable "http://${meiliCfg.settings.http_addr}";
services.librechat.credentials.MEILI_MASTER_KEY = lib.mkIf cfg.meilisearch.enable meiliCfg.masterKeyFile;
};
meta.maintainers = with lib.maintainers; [
gepbird
niklaskorz
rrvsh
];
}