diff --git a/nixos/doc/manual/release-notes/rl-2511.section.md b/nixos/doc/manual/release-notes/rl-2511.section.md index d3cff7b638e7..b03412275d90 100644 --- a/nixos/doc/manual/release-notes/rl-2511.section.md +++ b/nixos/doc/manual/release-notes/rl-2511.section.md @@ -34,7 +34,9 @@ - Auto-scrub support for Bcachefs filesystems can now be enabled through [services.bcachefs.autoScrub.enable](#opt-services.bcachefs.autoScrub.enable) to periodically check for data corruption. If there's a correct copy available, it will automatically repair corrupted blocks. -- [tlsrpt-reporter], an application suite to generate and deliver TLSRPT reports. Available as [services.tlsrpt](#opt-services.tlsrpt.enable). +- [LibreTranslate](https://libretranslate.com), a free and open source machine translation API. Available as [services.libretranslate](#opt-services.libretranslate.enable). + +- [tlsrpt-reporter](https://github.com/sys4/tlsrpt-reporter), an application suite to generate and deliver TLSRPT reports. Available as [services.tlsrpt](#opt-services.tlsrpt.enable). - [Chhoto URL](https://github.com/SinTan1729/chhoto-url), a simple, blazingly fast, selfhosted URL shortener with no unnecessary features, written in Rust. Available as [services.chhoto-url](#opt-services.chhoto-url.enable). diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index fc6c4678dbea..916b87d3ffab 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1611,6 +1611,7 @@ ./services/web-apps/lasuite-docs.nix ./services/web-apps/lasuite-meet.nix ./services/web-apps/lemmy.nix + ./services/web-apps/libretranslate.nix ./services/web-apps/limesurvey.nix ./services/web-apps/mainsail.nix ./services/web-apps/mastodon.nix diff --git a/nixos/modules/services/web-apps/libretranslate.nix b/nixos/modules/services/web-apps/libretranslate.nix new file mode 100644 index 000000000000..1664b9ef4581 --- /dev/null +++ b/nixos/modules/services/web-apps/libretranslate.nix @@ -0,0 +1,233 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.services.libretranslate; + ltmanageKeysCli = pkgs.writeShellScriptBin "ltmanage-keys" '' + set -a + export HOME="/var/lib/libretranslate" + sudo=exec + if [[ "$USER" != ${cfg.user} ]]; then + sudo='exec /run/wrappers/bin/sudo -u ${cfg.user} --preserve-env' + fi + $sudo ${cfg.package}/bin/ltmanage keys --api-keys-db-path ${cfg.dataDir}/db/api_keys.db "$@" + ''; + +in +{ + options = { + services.libretranslate = { + enable = lib.mkEnableOption "LibreTranslate service"; + + package = lib.mkPackageOption pkgs "libretranslate" { }; + + user = lib.mkOption { + type = lib.types.str; + default = "libretranslate"; + description = "User account under which libretranslate runs."; + }; + + group = lib.mkOption { + type = lib.types.str; + default = "libretranslate"; + description = "Group account under which libretranslate runs."; + }; + + host = lib.mkOption { + description = "The address the application should listen on."; + type = lib.types.str; + default = "127.0.0.1"; + }; + + port = lib.mkOption { + type = lib.types.port; + default = 5000; + description = "The the application should listen on."; + }; + + dataDir = lib.mkOption { + type = lib.types.path; + default = "/var/lib/libretranslate"; + example = "/srv/data/libretranslate"; + description = "The data directory."; + }; + + threads = lib.mkOption { + type = lib.types.nullOr lib.types.ints.positive; + default = null; + example = 8; + description = "Set number of threads."; + }; + + enableApiKeys = lib.mkOption { + type = lib.types.bool; + default = false; + example = true; + description = "Whether to enable the API keys database."; + }; + + disableWebUI = lib.mkOption { + type = lib.types.bool; + default = false; + example = true; + description = "Whether to disable the Web UI."; + }; + + updateModels = lib.mkOption { + type = lib.types.bool; + default = false; + example = true; + description = "Update language models at startup"; + }; + + domain = lib.mkOption { + type = lib.types.str; + default = ""; + example = "libretranslate.example.com"; + description = '' + The domain serving your LibreTranslate instance. + Required for configure nginx as a reverse proxy. + ''; + }; + + configureNginx = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Configure nginx as a reverse proxy for LibreTranslate."; + }; + + extraArgs = lib.mkOption { + type = + with lib.types; + attrsOf ( + nullOr (oneOf [ + bool + str + int + (listOf (oneOf [ + bool + str + int + ])) + ]) + ); + default = { }; + example = { + debug = true; + disable-files-translation = true; + url-prefix = "translate"; + }; + description = "Extra arguments passed to the LibreTranslate."; + }; + }; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = lib.mkIf cfg.enableApiKeys [ ltmanageKeysCli ]; + + systemd.tmpfiles.rules = lib.mkIf (cfg.dataDir != "/var/lib/libretranslate") [ + "d '${cfg.dataDir}' 0750 ${cfg.user} ${cfg.group} - -" + "z '${cfg.dataDir}' 0750 ${cfg.user} ${cfg.group} - -" + ]; + + systemd.services.libretranslate = { + description = "LibreTranslate service"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + environment = { + HOME = cfg.dataDir; + }; + serviceConfig = lib.mkMerge [ + { + Type = "simple"; + ExecStart = '' + ${cfg.package}/bin/libretranslate ${ + lib.cli.toGNUCommandLineShell { } ( + cfg.extraArgs + // { + inherit (cfg) host port threads; + api-keys = cfg.enableApiKeys; + disable-web-ui = cfg.disableWebUI; + update-models = cfg.updateModels; + } + ) + } + ''; + WorkingDirectory = cfg.dataDir; + User = cfg.user; + Group = cfg.group; + ProcSubset = "all"; + ProtectProc = "invisible"; + UMask = "0027"; + CapabilityBoundingSet = ""; + NoNewPrivileges = true; + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + PrivateDevices = true; + PrivateUsers = true; + ProtectHostname = true; + ProtectClock = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectKernelLogs = true; + ProtectControlGroups = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + ]; + RestrictNamespaces = true; + LockPersonality = true; + MemoryDenyWriteExecute = false; + RestrictRealtime = true; + RestrictSUIDSGID = true; + RemoveIPC = true; + PrivateMounts = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ "~@cpu-emulation @debug @keyring @mount @obsolete @privileged @setuid" ]; + } + (lib.mkIf (cfg.dataDir == "/var/lib/libretranslate") { + StateDirectory = "libretranslate"; + StateDirectoryMode = "0750"; + }) + (lib.mkIf (cfg.dataDir != "/var/lib/libretranslate") { + ReadWritePaths = cfg.dataDir; + }) + ]; + }; + + services.nginx = lib.mkIf cfg.configureNginx { + enable = true; + virtualHosts."${cfg.domain}" = { + root = "/var/empty"; + + locations."/" = { + proxyPass = "http://127.0.0.1:${toString cfg.port}"; + }; + + locations."= /favicon.ico" = { + alias = "${cfg.package.static-compressed}/share/libretranslate/static/favicon.ico"; + }; + + locations."^~ /static/" = { + alias = "${cfg.package.static-compressed}/share/libretranslate/static/"; + }; + }; + }; + + users.users = lib.optionalAttrs (cfg.user == "libretranslate") { + libretranslate = { + group = cfg.group; + isSystemUser = true; + }; + }; + + users.groups = lib.optionalAttrs (cfg.group == "libretranslate") { + libretranslate = { }; + }; + }; +} diff --git a/pkgs/development/python-modules/libretranslate/default.nix b/pkgs/development/python-modules/libretranslate/default.nix index adf8f2040fb9..76a45841718b 100644 --- a/pkgs/development/python-modules/libretranslate/default.nix +++ b/pkgs/development/python-modules/libretranslate/default.nix @@ -1,8 +1,10 @@ { lib, + pkgs, buildPythonPackage, fetchFromGitHub, pytestCheckHook, + runCommand, hatchling, argostranslate, flask, @@ -15,6 +17,7 @@ expiringdict, langdetect, lexilang, + libretranslate, ltpycld2, morfessor, appdirs, @@ -26,6 +29,7 @@ prometheus-client, polib, python, + xorg, }: buildPythonPackage rec { @@ -85,6 +89,26 @@ buildPythonPackage rec { pythonImportsCheck = [ "libretranslate" ]; + passthru = { + static-compressed = + runCommand "libretranslate-data-compressed" + { + nativeBuildInputs = [ + pkgs.brotli + xorg.lndir + ]; + } + '' + mkdir -p $out/share/libretranslate/static + lndir ${libretranslate}/share/libretranslate/static $out/share/libretranslate/static + + # Create static gzip and brotli files + find -L $out -type f -regextype posix-extended -iregex '.*\.(css|ico|js|svg|ttf)' \ + -exec gzip --best --keep --force {} ';' \ + -exec brotli --best --keep --no-copy-stat {} ';' + ''; + }; + meta = with lib; { description = "Free and Open Source Machine Translation API. Self-hosted, no limits, no ties to proprietary services"; homepage = "https://libretranslate.com";