at 25.11-pre 12 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7let 8 cfg = config.services.gotenberg; 9 10 args = 11 [ 12 "--api-port=${toString cfg.port}" 13 "--api-timeout=${cfg.timeout}" 14 "--api-root-path=${cfg.rootPath}" 15 "--log-level=${cfg.logLevel}" 16 "--chromium-max-queue-size=${toString cfg.chromium.maxQueueSize}" 17 "--libreoffice-restart-after=${toString cfg.libreoffice.restartAfter}" 18 "--libreoffice-max-queue-size=${toString cfg.libreoffice.maxQueueSize}" 19 "--pdfengines-merge-engines=${lib.concatStringsSep "," cfg.pdfEngines.merge}" 20 "--pdfengines-convert-engines=${lib.concatStringsSep "," cfg.pdfEngines.convert}" 21 "--pdfengines-read-metadata-engines=${lib.concatStringsSep "," cfg.pdfEngines.readMetadata}" 22 "--pdfengines-write-metadata-engines=${lib.concatStringsSep "," cfg.pdfEngines.writeMetadata}" 23 "--api-download-from-allow-list=${cfg.downloadFrom.allowList}" 24 "--api-download-from-max-retry=${toString cfg.downloadFrom.maxRetries}" 25 ] 26 ++ optional cfg.enableBasicAuth "--api-enable-basic-auth" 27 ++ optional cfg.chromium.autoStart "--chromium-auto-start" 28 ++ optional cfg.chromium.disableJavascript "--chromium-disable-javascript" 29 ++ optional cfg.chromium.disableRoutes "--chromium-disable-routes" 30 ++ optional cfg.libreoffice.autoStart "--libreoffice-auto-start" 31 ++ optional cfg.libreoffice.disableRoutes "--libreoffice-disable-routes" 32 ++ optional cfg.pdfEngines.disableRoutes "--pdfengines-disable-routes" 33 ++ optional ( 34 cfg.downloadFrom.denyList != null 35 ) "--api-download-from-deny-list=${cfg.downloadFrom.denyList}" 36 ++ optional cfg.downloadFrom.disable "--api-disable-download-from" 37 ++ optional (cfg.bodyLimit != null) "--api-body-limit=${cfg.bodyLimit}" 38 ++ lib.optionals (cfg.extraArgs != [ ]) cfg.extraArgs; 39 40 inherit (lib) 41 mkEnableOption 42 mkPackageOption 43 mkOption 44 types 45 mkIf 46 optional 47 optionalAttrs 48 ; 49in 50{ 51 options = { 52 services.gotenberg = { 53 enable = mkEnableOption "Gotenberg, a stateless API for PDF files"; 54 55 # Users can override only gotenberg, libreoffice and chromium if they want to (eg. ungoogled-chromium, different LO version, etc) 56 # Don't allow setting the qpdf, pdftk, or unoconv paths, as those are very stable 57 # and there's only one version of each. 58 package = mkPackageOption pkgs "gotenberg" { }; 59 60 port = mkOption { 61 type = types.port; 62 default = 3000; 63 description = "Port on which the API should listen."; 64 }; 65 66 bindIP = mkOption { 67 type = types.nullOr types.str; 68 default = "127.0.0.1"; 69 description = "Port the API listener should bind to. Set to 0.0.0.0 to listen on all available IPs."; 70 }; 71 72 timeout = mkOption { 73 type = types.nullOr types.str; 74 default = "30s"; 75 description = "Timeout for API requests."; 76 }; 77 78 rootPath = mkOption { 79 type = types.str; 80 default = "/"; 81 description = "Root path for the Gotenberg API."; 82 }; 83 84 enableBasicAuth = mkOption { 85 type = types.bool; 86 default = false; 87 description = '' 88 HTTP Basic Authentication. 89 90 If you set this, be sure to set `GOTENBERG_API_BASIC_AUTH_USERNAME`and `GOTENBERG_API_BASIC_AUTH_PASSWORD` 91 in your `services.gotenberg.environmentFile` file. 92 ''; 93 }; 94 95 bodyLimit = mkOption { 96 type = types.nullOr types.str; 97 default = null; 98 description = "Sets the max limit for `multipart/form-data` requests. Accepts values like '5M', '20G', etc."; 99 }; 100 101 extraFontPackages = mkOption { 102 type = types.listOf types.package; 103 default = [ ]; 104 description = "Extra fonts to make available."; 105 }; 106 107 chromium = { 108 package = mkPackageOption pkgs "chromium" { }; 109 110 maxQueueSize = mkOption { 111 type = types.int; 112 default = 0; 113 description = "Maximum queue size for chromium-based conversions. Setting to 0 disables the limit."; 114 }; 115 116 autoStart = mkOption { 117 type = types.bool; 118 default = false; 119 description = "Automatically start chromium when Gotenberg starts. If false, Chromium will start on the first conversion request that uses it."; 120 }; 121 122 disableJavascript = mkOption { 123 type = types.bool; 124 default = false; 125 description = "Disable Javascript execution."; 126 }; 127 128 disableRoutes = mkOption { 129 type = types.bool; 130 default = false; 131 description = "Disable all routes allowing Chromium-based conversion."; 132 }; 133 }; 134 135 downloadFrom = { 136 allowList = mkOption { 137 type = types.nullOr types.str; 138 default = ".*"; 139 description = "Allow these URLs to be used in the `downloadFrom` API field. Accepts a regular expression."; 140 }; 141 denyList = mkOption { 142 type = types.nullOr types.str; 143 default = null; 144 description = "Deny accepting URLs from these domains in the `downloadFrom` API field. Accepts a regular expression."; 145 }; 146 maxRetries = mkOption { 147 type = types.int; 148 default = 4; 149 description = "The maximum amount of times to retry downloading a file specified with `downloadFrom`."; 150 }; 151 disable = mkOption { 152 type = types.bool; 153 default = false; 154 description = "Whether to disable the ability to download files for conversion from outside sources."; 155 }; 156 }; 157 158 libreoffice = { 159 package = mkPackageOption pkgs "libreoffice" { }; 160 161 restartAfter = mkOption { 162 type = types.int; 163 default = 10; 164 description = "Restart LibreOffice after this many conversions. Setting to 0 disables this feature."; 165 }; 166 167 maxQueueSize = mkOption { 168 type = types.int; 169 default = 0; 170 description = "Maximum queue size for LibreOffice-based conversions. Setting to 0 disables the limit."; 171 }; 172 173 autoStart = mkOption { 174 type = types.bool; 175 default = false; 176 description = "Automatically start LibreOffice when Gotenberg starts. If false, Chromium will start on the first conversion request that uses it."; 177 }; 178 179 disableRoutes = mkOption { 180 type = types.bool; 181 default = false; 182 description = "Disable all routes allowing LibreOffice-based conversion."; 183 }; 184 }; 185 186 pdfEngines = { 187 merge = mkOption { 188 type = types.listOf ( 189 types.enum [ 190 "qpdf" 191 "pdfcpu" 192 "pdftk" 193 ] 194 ); 195 default = [ 196 "qpdf" 197 "pdfcpu" 198 "pdftk" 199 ]; 200 description = "PDF Engines to use for merging files."; 201 }; 202 convert = mkOption { 203 type = types.listOf ( 204 types.enum [ 205 "libreoffice-pdfengine" 206 ] 207 ); 208 default = [ 209 "libreoffice-pdfengine" 210 ]; 211 description = "PDF Engines to use for converting files."; 212 }; 213 readMetadata = mkOption { 214 type = types.listOf ( 215 types.enum [ 216 "exiftool" 217 ] 218 ); 219 default = [ 220 "exiftool" 221 ]; 222 description = "PDF Engines to use for reading metadata from files."; 223 }; 224 writeMetadata = mkOption { 225 type = types.listOf ( 226 types.enum [ 227 "exiftool" 228 ] 229 ); 230 default = [ 231 "exiftool" 232 ]; 233 description = "PDF Engines to use for writing metadata to files."; 234 }; 235 236 disableRoutes = mkOption { 237 type = types.bool; 238 default = false; 239 description = "Disable routes related to PDF engines."; 240 }; 241 }; 242 243 logLevel = mkOption { 244 type = types.enum [ 245 "error" 246 "warn" 247 "info" 248 "debug" 249 ]; 250 default = "info"; 251 description = "The logging level for Gotenberg."; 252 }; 253 254 environmentFile = mkOption { 255 type = types.nullOr types.path; 256 default = null; 257 description = "Environment file to load extra environment variables from."; 258 }; 259 260 extraArgs = mkOption { 261 type = types.listOf types.str; 262 default = [ ]; 263 description = "Any extra command-line flags to pass to the Gotenberg service."; 264 }; 265 }; 266 }; 267 268 config = mkIf cfg.enable { 269 assertions = [ 270 { 271 assertion = cfg.enableBasicAuth -> cfg.environmentFile != null; 272 message = '' 273 When enabling HTTP Basic Authentication with `services.gotenberg.enableBasicAuth`, 274 you must provide an environment file via `services.gotenberg.environmentFile` with the appropriate environment variables set in it. 275 276 See `services.gotenberg.enableBasicAuth` for the names of those variables. 277 ''; 278 } 279 { 280 assertion = !(lib.isList cfg.pdfEngines); 281 message = '' 282 Setting `services.gotenberg.pdfEngines` to a list is now deprecated. 283 Use the new `pdfEngines.mergeEngines`, `pdfEngines.convertEngines`, `pdfEngines.readMetadataEngines`, and `pdfEngines.writeMetadataEngines` settings instead. 284 285 The previous option was using a method that is now deprecated by upstream. 286 ''; 287 } 288 ]; 289 290 systemd.services.gotenberg = { 291 description = "Gotenberg API server"; 292 after = [ "network.target" ]; 293 wantedBy = [ "multi-user.target" ]; 294 path = [ cfg.package ]; 295 environment = { 296 LIBREOFFICE_BIN_PATH = "${cfg.libreoffice.package}/lib/libreoffice/program/soffice.bin"; 297 CHROMIUM_BIN_PATH = lib.getExe cfg.chromium.package; 298 FONTCONFIG_FILE = pkgs.makeFontsConf { 299 fontDirectories = [ pkgs.liberation_ttf_v2 ] ++ cfg.extraFontPackages; 300 }; 301 # Needed for LibreOffice to work correctly. 302 # https://github.com/NixOS/nixpkgs/issues/349123#issuecomment-2418330936 303 HOME = "/run/gotenberg"; 304 }; 305 serviceConfig = { 306 Type = "simple"; 307 DynamicUser = true; 308 ExecStart = "${lib.getExe cfg.package} ${lib.escapeShellArgs args}"; 309 310 # Needed for LibreOffice to work correctly. 311 # See above issue comment. 312 WorkingDirectory = "/run/gotenberg"; 313 RuntimeDirectory = "gotenberg"; 314 315 # Hardening options 316 PrivateDevices = true; 317 PrivateIPC = true; 318 PrivateUsers = true; 319 320 ProtectClock = true; 321 ProtectControlGroups = true; 322 ProtectHome = true; 323 ProtectHostname = true; 324 ProtectKernelLogs = true; 325 ProtectKernelModules = true; 326 ProtectKernelTunables = true; 327 ProtectProc = "invisible"; 328 329 RestrictAddressFamilies = [ 330 "AF_UNIX" 331 "AF_INET" 332 "AF_INET6" 333 "AF_NETLINK" 334 ]; 335 RestrictNamespaces = true; 336 RestrictRealtime = true; 337 338 LockPersonality = true; 339 340 SystemCallFilter = [ 341 "@sandbox" 342 "@system-service" 343 "@chown" 344 ]; 345 SystemCallArchitectures = "native"; 346 347 UMask = 77; 348 } // optionalAttrs (cfg.environmentFile != null) { EnvironmentFile = cfg.environmentFile; }; 349 }; 350 }; 351 352 meta.maintainers = with lib.maintainers; [ pyrox0 ]; 353}