1 { options, config, lib, pkgs, utils, ... }:
6 cfg = config.services.sftpgo;
7 defaultUser = "sftpgo";
8 settingsFormat = pkgs.formats.json {};
9 configFile = settingsFormat.generate "sftpgo.json" cfg.settings;
10 hasPrivilegedPorts = any (port: port > 0 && port < 1024) (
11 catAttrs "port" (cfg.settings.httpd.bindings
12 ++ cfg.settings.ftpd.bindings
13 ++ cfg.settings.sftpd.bindings
14 ++ cfg.settings.webdavd.bindings
19 options.services.sftpgo = {
23 description = "sftpgo";
26 package = mkPackageOption pkgs "sftpgo" { };
28 extraArgs = mkOption {
29 type = with types; listOf str;
32 Additional command line arguments to pass to the sftpgo daemon.
34 example = [ "--log-level" "info" ];
39 default = "/var/lib/sftpgo";
41 The directory where SFTPGo stores its data files.
47 default = defaultUser;
49 User account name under which SFTPGo runs.
55 default = defaultUser;
57 Group name under which SFTPGo runs.
61 loadDataFile = mkOption {
63 type = with types; nullOr path;
65 Path to a json file containing users and folders to load (or update) on startup.
66 Check the [documentation](https://github.com/drakkan/sftpgo/blob/main/docs/full-configuration.md)
67 for the `--loaddata-from` command line argument for more info.
74 The primary sftpgo configuration. See the
75 [configuration reference](https://github.com/drakkan/sftpgo/blob/main/docs/full-configuration.md)
78 type = with types; submodule {
79 freeformType = settingsFormat.type;
81 httpd.bindings = mkOption {
84 Configure listen addresses and ports for httpd.
86 type = types.listOf (types.submodule {
87 freeformType = settingsFormat.type;
91 default = "127.0.0.1";
93 Network listen address. Leave blank to listen on all available network interfaces.
94 On *NIX you can specify an absolute path to listen on a Unix-domain socket.
102 The port for serving HTTP(S) requests.
104 Setting the port to `0` disables listening on this interface binding.
108 enable_web_admin = mkOption {
112 Enable the built-in web admin for this interface binding.
116 enable_web_client = mkOption {
120 Enable the built-in web client for this interface binding.
127 ftpd.bindings = mkOption {
130 Configure listen addresses and ports for ftpd.
132 type = types.listOf (types.submodule {
133 freeformType = settingsFormat.type;
137 default = "127.0.0.1";
139 Network listen address. Leave blank to listen on all available network interfaces.
140 On *NIX you can specify an absolute path to listen on a Unix-domain socket.
148 The port for serving FTP requests.
150 Setting the port to `0` disables listening on this interface binding.
157 sftpd.bindings = mkOption {
160 Configure listen addresses and ports for sftpd.
162 type = types.listOf (types.submodule {
163 freeformType = settingsFormat.type;
167 default = "127.0.0.1";
169 Network listen address. Leave blank to listen on all available network interfaces.
170 On *NIX you can specify an absolute path to listen on a Unix-domain socket.
178 The port for serving SFTP requests.
180 Setting the port to `0` disables listening on this interface binding.
187 webdavd.bindings = mkOption {
190 Configure listen addresses and ports for webdavd.
192 type = types.listOf (types.submodule {
193 freeformType = settingsFormat.type;
197 default = "127.0.0.1";
199 Network listen address. Leave blank to listen on all available network interfaces.
200 On *NIX you can specify an absolute path to listen on a Unix-domain socket.
208 The port for serving WebDAV requests.
210 Setting the port to `0` disables listening on this interface binding.
220 SMTP configuration section.
222 type = types.submodule {
223 freeformType = settingsFormat.type;
229 Location of SMTP email server. Leave empty to disable email sending capabilities.
236 description = "Port of the SMTP Server.";
239 encryption = mkOption {
240 type = types.enum [ 0 1 2 ];
250 auth_type = mkOption {
251 type = types.enum [ 0 1 2 ];
263 description = "SMTP username.";
268 default = "SFTPGo <sftpgo@example.com>";
281 config = mkIf cfg.enable {
282 services.sftpgo.settings = (mapAttrs (name: mkDefault) {
283 ftpd.bindings = [{ port = 0; }];
284 httpd.bindings = [{ port = 0; }];
285 sftpd.bindings = [{ port = 0; }];
286 webdavd.bindings = [{ port = 0; }];
287 httpd.openapi_path = "${cfg.package}/share/sftpgo/openapi";
288 httpd.templates_path = "${cfg.package}/share/sftpgo/templates";
289 httpd.static_files_path = "${cfg.package}/share/sftpgo/static";
290 smtp.templates_path = "${cfg.package}/share/sftpgo/templates";
293 users = optionalAttrs (cfg.user == defaultUser) {
296 description = "SFTPGo system user";
305 members = [ defaultUser ];
310 systemd.services.sftpgo = {
311 description = "SFTPGo daemon";
312 after = [ "network.target" ];
313 wantedBy = [ "multi-user.target" ];
316 SFTPGO_CONFIG_FILE = mkDefault configFile;
317 SFTPGO_LOG_FILE_PATH = mkDefault ""; # log to journal
318 SFTPGO_LOADDATA_FROM = mkIf (cfg.loadDataFile != null) cfg.loadDataFile;
321 serviceConfig = mkMerge [
326 WorkingDirectory = cfg.dataDir;
327 ReadWritePaths = [ cfg.dataDir ];
328 LimitNOFILE = 8192; # taken from upstream
330 ExecStart = "${cfg.package}/bin/sftpgo serve ${utils.escapeSystemdExecArgs cfg.extraArgs}";
331 ExecReload = "${pkgs.util-linux}/bin/kill -s HUP $MAINPID";
334 CapabilityBoundingSet = [ (optionalString hasPrivilegedPorts "CAP_NET_BIND_SERVICE") ];
335 DevicePolicy = "closed";
336 LockPersonality = true;
337 NoNewPrivileges = true;
338 PrivateDevices = true;
342 ProtectControlGroups = true;
344 ProtectHostname = true;
345 ProtectKernelLogs = true;
346 ProtectKernelModules = true;
347 ProtectKernelTunables = true;
348 ProtectProc = "invisible";
349 ProtectSystem = "strict";
351 RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX";
352 RestrictNamespaces = true;
353 RestrictRealtime = true;
354 RestrictSUIDSGID = true;
355 SystemCallArchitectures = "native";
356 SystemCallFilter = [ "@system-service" "~@privileged" ];
359 (mkIf hasPrivilegedPorts {
360 AmbientCapabilities = "CAP_NET_BIND_SERVICE";
362 (mkIf (cfg.dataDir == options.services.sftpgo.dataDir.default) {
363 StateDirectory = baseNameOf cfg.dataDir;