1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 cfg = config.services.libretranslate;
10 ltmanageKeysCli = pkgs.writeShellScriptBin "ltmanage-keys" ''
11 set -a
12 export HOME="/var/lib/libretranslate"
13 sudo=exec
14 if [[ "$USER" != ${cfg.user} ]]; then
15 sudo='exec /run/wrappers/bin/sudo -u ${cfg.user} --preserve-env'
16 fi
17 $sudo ${cfg.package}/bin/ltmanage keys --api-keys-db-path ${cfg.dataDir}/db/api_keys.db "$@"
18 '';
19
20in
21{
22 options = {
23 services.libretranslate = {
24 enable = lib.mkEnableOption "LibreTranslate service";
25
26 package = lib.mkPackageOption pkgs "libretranslate" { };
27
28 user = lib.mkOption {
29 type = lib.types.str;
30 default = "libretranslate";
31 description = "User account under which libretranslate runs.";
32 };
33
34 group = lib.mkOption {
35 type = lib.types.str;
36 default = "libretranslate";
37 description = "Group account under which libretranslate runs.";
38 };
39
40 host = lib.mkOption {
41 description = "The address the application should listen on.";
42 type = lib.types.str;
43 default = "127.0.0.1";
44 };
45
46 port = lib.mkOption {
47 type = lib.types.port;
48 default = 5000;
49 description = "The the application should listen on.";
50 };
51
52 dataDir = lib.mkOption {
53 type = lib.types.path;
54 default = "/var/lib/libretranslate";
55 example = "/srv/data/libretranslate";
56 description = "The data directory.";
57 };
58
59 threads = lib.mkOption {
60 type = lib.types.nullOr lib.types.ints.positive;
61 default = null;
62 example = 8;
63 description = "Set number of threads.";
64 };
65
66 enableApiKeys = lib.mkOption {
67 type = lib.types.bool;
68 default = false;
69 example = true;
70 description = "Whether to enable the API keys database.";
71 };
72
73 disableWebUI = lib.mkOption {
74 type = lib.types.bool;
75 default = false;
76 example = true;
77 description = "Whether to disable the Web UI.";
78 };
79
80 updateModels = lib.mkOption {
81 type = lib.types.bool;
82 default = false;
83 example = true;
84 description = "Update language models at startup";
85 };
86
87 domain = lib.mkOption {
88 type = lib.types.str;
89 default = "";
90 example = "libretranslate.example.com";
91 description = ''
92 The domain serving your LibreTranslate instance.
93 Required for configure nginx as a reverse proxy.
94 '';
95 };
96
97 configureNginx = lib.mkOption {
98 type = lib.types.bool;
99 default = false;
100 description = "Configure nginx as a reverse proxy for LibreTranslate.";
101 };
102
103 extraArgs = lib.mkOption {
104 type =
105 with lib.types;
106 attrsOf (
107 nullOr (oneOf [
108 bool
109 str
110 int
111 (listOf (oneOf [
112 bool
113 str
114 int
115 ]))
116 ])
117 );
118 default = { };
119 example = {
120 debug = true;
121 disable-files-translation = true;
122 url-prefix = "translate";
123 };
124 description = "Extra arguments passed to the LibreTranslate.";
125 };
126 };
127 };
128
129 config = lib.mkIf cfg.enable {
130 environment.systemPackages = lib.mkIf cfg.enableApiKeys [ ltmanageKeysCli ];
131
132 systemd.tmpfiles.rules = lib.mkIf (cfg.dataDir != "/var/lib/libretranslate") [
133 "d '${cfg.dataDir}' 0750 ${cfg.user} ${cfg.group} - -"
134 "z '${cfg.dataDir}' 0750 ${cfg.user} ${cfg.group} - -"
135 ];
136
137 systemd.services.libretranslate = {
138 description = "LibreTranslate service";
139 after = [ "network.target" ];
140 wantedBy = [ "multi-user.target" ];
141 environment = {
142 HOME = cfg.dataDir;
143 };
144 serviceConfig = lib.mkMerge [
145 {
146 Type = "simple";
147 ExecStart = ''
148 ${cfg.package}/bin/libretranslate ${
149 lib.cli.toGNUCommandLineShell { } (
150 cfg.extraArgs
151 // {
152 inherit (cfg) host port threads;
153 api-keys = cfg.enableApiKeys;
154 disable-web-ui = cfg.disableWebUI;
155 update-models = cfg.updateModels;
156 }
157 )
158 }
159 '';
160 WorkingDirectory = cfg.dataDir;
161 User = cfg.user;
162 Group = cfg.group;
163 ProcSubset = "all";
164 ProtectProc = "invisible";
165 UMask = "0027";
166 CapabilityBoundingSet = "";
167 NoNewPrivileges = true;
168 ProtectSystem = "strict";
169 ProtectHome = true;
170 PrivateTmp = true;
171 PrivateDevices = true;
172 PrivateUsers = true;
173 ProtectHostname = true;
174 ProtectClock = true;
175 ProtectKernelTunables = true;
176 ProtectKernelModules = true;
177 ProtectKernelLogs = true;
178 ProtectControlGroups = true;
179 RestrictAddressFamilies = [
180 "AF_INET"
181 "AF_INET6"
182 ];
183 RestrictNamespaces = true;
184 LockPersonality = true;
185 MemoryDenyWriteExecute = false;
186 RestrictRealtime = true;
187 RestrictSUIDSGID = true;
188 RemoveIPC = true;
189 PrivateMounts = true;
190 SystemCallArchitectures = "native";
191 SystemCallFilter = [ "~@cpu-emulation @debug @keyring @mount @obsolete @privileged @setuid" ];
192 }
193 (lib.mkIf (cfg.dataDir == "/var/lib/libretranslate") {
194 StateDirectory = "libretranslate";
195 StateDirectoryMode = "0750";
196 })
197 (lib.mkIf (cfg.dataDir != "/var/lib/libretranslate") {
198 ReadWritePaths = cfg.dataDir;
199 })
200 ];
201 };
202
203 services.nginx = lib.mkIf cfg.configureNginx {
204 enable = true;
205 virtualHosts."${cfg.domain}" = {
206 root = "/var/empty";
207
208 locations."/" = {
209 proxyPass = "http://127.0.0.1:${toString cfg.port}";
210 };
211
212 locations."= /favicon.ico" = {
213 alias = "${cfg.package.static-compressed}/share/libretranslate/static/favicon.ico";
214 };
215
216 locations."^~ /static/" = {
217 alias = "${cfg.package.static-compressed}/share/libretranslate/static/";
218 };
219 };
220 };
221
222 users.users = lib.optionalAttrs (cfg.user == "libretranslate") {
223 libretranslate = {
224 group = cfg.group;
225 isSystemUser = true;
226 };
227 };
228
229 users.groups = lib.optionalAttrs (cfg.group == "libretranslate") {
230 libretranslate = { };
231 };
232 };
233}