nixos/librechat: init (#462287)
This commit is contained in:
@@ -22688,6 +22688,12 @@
|
||||
keys = [ { fingerprint = "7DCA 5615 8AB2 621F 2F32 9FF4 1C7C E491 479F A273"; } ];
|
||||
name = "Rahul Butani";
|
||||
};
|
||||
rrvsh = {
|
||||
email = "rafiq@rrv.sh";
|
||||
github = "rrvsh";
|
||||
githubId = 20300874;
|
||||
name = "Mohammad Rafiq";
|
||||
};
|
||||
rseichter = {
|
||||
email = "nixos.org@seichter.de";
|
||||
github = "rseichter";
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
|
||||
- [ImmichFrame](https://immichframe.dev/), display your photos from Immich as a digital photo frame. Available as `services.immichframe`.
|
||||
|
||||
- [LibreChat](https://www.librechat.ai/), open-source self-hostable ChatGPT clone with Agents and RAG APIs. Available as [services.librechat](#opt-services.librechat.enable).
|
||||
|
||||
- [udp-over-tcp](https://github.com/mullvad/udp-over-tcp), a tunnel for proxying UDP traffic over a TCP stream. Available as `services.udp-over-tcp`.
|
||||
|
||||
## Backward Incompatibilities {#sec-release-26.05-incompatibilities}
|
||||
|
||||
@@ -1655,6 +1655,7 @@
|
||||
./services/web-apps/lasuite-docs.nix
|
||||
./services/web-apps/lasuite-meet.nix
|
||||
./services/web-apps/lemmy.nix
|
||||
./services/web-apps/librechat.nix
|
||||
./services/web-apps/librespeed.nix
|
||||
./services/web-apps/libretranslate.nix
|
||||
./services/web-apps/limesurvey.nix
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.services.librechat;
|
||||
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.";
|
||||
};
|
||||
};
|
||||
};
|
||||
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 = { };
|
||||
example = {
|
||||
version = "1.0.8";
|
||||
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";
|
||||
};
|
||||
|
||||
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.
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
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.enableLocalDB "mongodb.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 = builtins.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;
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [
|
||||
gepbird
|
||||
niklaskorz
|
||||
rrvsh
|
||||
];
|
||||
}
|
||||
@@ -857,6 +857,7 @@ in
|
||||
lemurs-xorg = runTest ./lemurs/lemurs-xorg.nix;
|
||||
lemurs-xorg-script = runTest ./lemurs/lemurs-xorg-script.nix;
|
||||
libinput = runTest ./libinput.nix;
|
||||
librechat = runTest ./librechat.nix;
|
||||
librenms = runTest ./librenms.nix;
|
||||
libresprite = runTest ./libresprite.nix;
|
||||
libreswan = runTest ./libreswan.nix;
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# Run this test with NIXPKGS_ALLOW_UNFREE=1
|
||||
{ lib, ... }:
|
||||
{
|
||||
name = "librechat";
|
||||
|
||||
meta.maintainers = with lib.maintainers; [
|
||||
gepbird
|
||||
niklaskorz
|
||||
rrvsh
|
||||
];
|
||||
|
||||
nodes.machine =
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
# !!! Don't do this with real keys. The /nix store is world-readable!
|
||||
credsKeyFile = pkgs.writeText "librechat-creds-key" "6d6deb03cdfb27ea454f6b9ddd42494bdce4af25d50d8aee454ddce583690cc5";
|
||||
credsIvFile = pkgs.writeText "librechat-creds-iv" "7c09a571f65ac793611685cc9ab1dbe7";
|
||||
jwtSecret = pkgs.writeText "librechat-jwt-secret" "29c4dc7f7de15306accf5eddb4cb8a70eb233d9fba4301f8f47f14c8c047ac81";
|
||||
jwtRefreshSecret = pkgs.writeText "librechat-jwt-refresh-secret" "f2c1685561f2f570b3e7955df267b5c602ee099f14dc5caa0dacc320580ea180";
|
||||
in
|
||||
{
|
||||
services.librechat = {
|
||||
enable = true;
|
||||
env = {
|
||||
ALLOW_REGISTRATION = true;
|
||||
};
|
||||
credentials = {
|
||||
# The following were randomly generated with https://www.librechat.ai/toolkit/creds_generator
|
||||
CREDS_KEY = credsKeyFile;
|
||||
CREDS_IV = credsIvFile;
|
||||
JWT_SECRET = jwtSecret;
|
||||
JWT_REFRESH_SECRET = jwtRefreshSecret;
|
||||
};
|
||||
enableLocalDB = true;
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.start()
|
||||
|
||||
machine.succeed("grep -qF 'ALLOW_REGISTRATION=true' /etc/systemd/system/librechat.service")
|
||||
|
||||
machine.wait_for_unit("librechat.service")
|
||||
machine.wait_for_open_port(3080)
|
||||
|
||||
machine.succeed("curl --fail http://localhost:3080/")
|
||||
|
||||
machine.shutdown()
|
||||
'';
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
node-gyp,
|
||||
vips,
|
||||
nix-update-script,
|
||||
nixosTests,
|
||||
}:
|
||||
|
||||
buildNpmPackage rec {
|
||||
@@ -84,6 +85,9 @@ buildNpmPackage rec {
|
||||
"^v(\\d+\\.\\d+\\.\\d+)$"
|
||||
];
|
||||
};
|
||||
tests = {
|
||||
inherit (nixosTests) librechat;
|
||||
};
|
||||
};
|
||||
|
||||
meta = {
|
||||
|
||||
Reference in New Issue
Block a user