about summary refs log tree commit diff
path: root/nixos/modules/services/web-apps/sftpgo.nix
diff options
context:
space:
mode:
authorYaya <mak@nyantec.com>2023-04-19 13:13:47 +0000
committerYureka <yuka@yuka.dev>2023-05-25 22:46:15 +0200
commitf63f781063b28ebf1d667f9ba47f67424a044110 (patch)
treecb4259b6d4e703133140504df9afc984998eda2f /nixos/modules/services/web-apps/sftpgo.nix
parent643d213ea63492cd493458eed98c1e7cc03fc9a1 (diff)
nixos/sftpgo: init
A fully featured and highly configurable SFTP server with optional
HTTP/S, FTP/S and WebDAV support.

https://github.com/drakkan/sftpgo
Diffstat (limited to 'nixos/modules/services/web-apps/sftpgo.nix')
-rw-r--r--nixos/modules/services/web-apps/sftpgo.nix375
1 files changed, 375 insertions, 0 deletions
diff --git a/nixos/modules/services/web-apps/sftpgo.nix b/nixos/modules/services/web-apps/sftpgo.nix
new file mode 100644
index 0000000000000..846478ecbd6d7
--- /dev/null
+++ b/nixos/modules/services/web-apps/sftpgo.nix
@@ -0,0 +1,375 @@
+{ options, config, lib, pkgs, utils, ... }:
+
+with lib;
+
+let
+  cfg = config.services.sftpgo;
+  defaultUser = "sftpgo";
+  settingsFormat = pkgs.formats.json {};
+  configFile = settingsFormat.generate "sftpgo.json" cfg.settings;
+  hasPrivilegedPorts = any (port: port > 0 && port < 1024) (
+    catAttrs "port" (cfg.settings.httpd.bindings
+      ++ cfg.settings.ftpd.bindings
+      ++ cfg.settings.sftpd.bindings
+      ++ cfg.settings.webdavd.bindings
+    )
+  );
+in
+{
+  options.services.sftpgo = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = mdDoc "sftpgo";
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.sftpgo;
+      defaultText = literalExpression "pkgs.sftpgo";
+      description = mdDoc ''
+        Which SFTPGo package to use.
+      '';
+    };
+
+    extraArgs = mkOption {
+      type = with types; listOf str;
+      default = [];
+      description = mdDoc ''
+        Additional command line arguments to pass to the sftpgo daemon.
+      '';
+      example = [ "--log-level" "info" ];
+    };
+
+    dataDir = mkOption {
+      type = types.str;
+      default = "/var/lib/sftpgo";
+      description = mdDoc ''
+        The directory where SFTPGo stores its data files.
+      '';
+    };
+
+    user = mkOption {
+      type = types.str;
+      default = defaultUser;
+      description = mdDoc ''
+        User account name under which SFTPGo runs.
+      '';
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = defaultUser;
+      description = mdDoc ''
+        Group name under which SFTPGo runs.
+      '';
+    };
+
+    loadDataFile = mkOption {
+      default = null;
+      type = with types; nullOr path;
+      description = mdDoc ''
+        Path to a json file containing users and folders to load (or update) on startup.
+        Check the [documentation](https://github.com/drakkan/sftpgo/blob/main/docs/full-configuration.md)
+        for the `--loaddata-from` command line argument for more info.
+      '';
+    };
+
+    settings = mkOption {
+      default = {};
+      description = mdDoc ''
+        The primary sftpgo configuration. See the
+        [configuration reference](https://github.com/drakkan/sftpgo/blob/main/docs/full-configuration.md)
+        for possible values.
+      '';
+      type = with types; submodule {
+        freeformType = settingsFormat.type;
+        options = {
+          httpd.bindings = mkOption {
+            default = [];
+            description = mdDoc ''
+              Configure listen addresses and ports for httpd.
+            '';
+            type = types.listOf (types.submodule {
+              freeformType = settingsFormat.type;
+              options = {
+                address = mkOption {
+                  type = types.str;
+                  default = "127.0.0.1";
+                  description = mdDoc ''
+                    Network listen address. Leave blank to listen on all available network interfaces.
+                    On *NIX you can specify an absolute path to listen on a Unix-domain socket.
+                  '';
+                };
+
+                port = mkOption {
+                  type = types.port;
+                  default = 8080;
+                  description = mdDoc ''
+                    The port for serving HTTP(S) requests.
+
+                    Setting the port to `0` disables listening on this interface binding.
+                  '';
+                };
+
+                enable_web_admin = mkOption {
+                  type = types.bool;
+                  default = true;
+                  description = mdDoc ''
+                    Enable the built-in web admin for this interface binding.
+                  '';
+                };
+
+                enable_web_client = mkOption {
+                  type = types.bool;
+                  default = true;
+                  description = mdDoc ''
+                    Enable the built-in web client for this interface binding.
+                  '';
+                };
+              };
+            });
+          };
+
+          ftpd.bindings = mkOption {
+            default = [];
+            description = mdDoc ''
+              Configure listen addresses and ports for ftpd.
+            '';
+            type = types.listOf (types.submodule {
+              freeformType = settingsFormat.type;
+              options = {
+                address = mkOption {
+                  type = types.str;
+                  default = "127.0.0.1";
+                  description = mdDoc ''
+                    Network listen address. Leave blank to listen on all available network interfaces.
+                    On *NIX you can specify an absolute path to listen on a Unix-domain socket.
+                  '';
+                };
+
+                port = mkOption {
+                  type = types.port;
+                  default = 0;
+                  description = mdDoc ''
+                    The port for serving FTP requests.
+
+                    Setting the port to `0` disables listening on this interface binding.
+                  '';
+                };
+              };
+            });
+          };
+
+          sftpd.bindings = mkOption {
+            default = [];
+            description = mdDoc ''
+              Configure listen addresses and ports for sftpd.
+            '';
+            type = types.listOf (types.submodule {
+              freeformType = settingsFormat.type;
+              options = {
+                address = mkOption {
+                  type = types.str;
+                  default = "127.0.0.1";
+                  description = mdDoc ''
+                    Network listen address. Leave blank to listen on all available network interfaces.
+                    On *NIX you can specify an absolute path to listen on a Unix-domain socket.
+                  '';
+                };
+
+                port = mkOption {
+                  type = types.port;
+                  default = 0;
+                  description = mdDoc ''
+                    The port for serving SFTP requests.
+
+                    Setting the port to `0` disables listening on this interface binding.
+                  '';
+                };
+              };
+            });
+          };
+
+          webdavd.bindings = mkOption {
+            default = [];
+            description = mdDoc ''
+              Configure listen addresses and ports for webdavd.
+            '';
+            type = types.listOf (types.submodule {
+              freeformType = settingsFormat.type;
+              options = {
+                address = mkOption {
+                  type = types.str;
+                  default = "127.0.0.1";
+                  description = mdDoc ''
+                    Network listen address. Leave blank to listen on all available network interfaces.
+                    On *NIX you can specify an absolute path to listen on a Unix-domain socket.
+                  '';
+                };
+
+                port = mkOption {
+                  type = types.port;
+                  default = 0;
+                  description = mdDoc ''
+                    The port for serving WebDAV requests.
+
+                    Setting the port to `0` disables listening on this interface binding.
+                  '';
+                };
+              };
+            });
+          };
+
+          smtp = mkOption {
+            default = {};
+            description = mdDoc ''
+              SMTP configuration section.
+            '';
+            type = types.submodule {
+              freeformType = settingsFormat.type;
+              options = {
+                host = mkOption {
+                  type = types.str;
+                  default = "";
+                  description = mdDoc ''
+                    Location of SMTP email server. Leave empty to disable email sending capabilities.
+                  '';
+                };
+
+                port = mkOption {
+                  type = types.port;
+                  default = 465;
+                  description = mdDoc "Port of the SMTP Server.";
+                };
+
+                encryption = mkOption {
+                  type = types.enum [ 0 1 2 ];
+                  default = 1;
+                  description = mdDoc ''
+                    Encryption scheme:
+                    - `0`: No encryption
+                    - `1`: TLS
+                    - `2`: STARTTLS
+                  '';
+                };
+
+                auth_type = mkOption {
+                  type = types.enum [ 0 1 2 ];
+                  default = 0;
+                  description = mdDoc ''
+                    - `0`: Plain
+                    - `1`: Login
+                    - `2`: CRAM-MD5
+                  '';
+                };
+
+                user = mkOption {
+                  type = types.str;
+                  default = "sftpgo";
+                  description = mdDoc "SMTP username.";
+                };
+
+                from = mkOption {
+                  type = types.str;
+                  default = "SFTPGo <sftpgo@example.com>";
+                  description = mdDoc ''
+                    From address.
+                  '';
+                };
+              };
+            };
+          };
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.sftpgo.settings = (mapAttrs (name: mkDefault) {
+      ftpd.bindings = [{ port = 0; }];
+      httpd.bindings = [{ port = 0; }];
+      sftpd.bindings = [{ port = 0; }];
+      webdavd.bindings = [{ port = 0; }];
+      httpd.openapi_path = "${cfg.package}/share/sftpgo/openapi";
+      httpd.templates_path = "${cfg.package}/share/sftpgo/templates";
+      httpd.static_files_path = "${cfg.package}/share/sftpgo/static";
+      smtp.templates_path = "${cfg.package}/share/sftpgo/templates";
+    });
+
+    users = optionalAttrs (cfg.user == defaultUser) {
+      users = {
+        ${defaultUser} = {
+          description = "SFTPGo system user";
+          isSystemUser = true;
+          group = defaultUser;
+          home = cfg.dataDir;
+        };
+      };
+
+      groups = {
+        ${defaultUser} = {
+          members = [ defaultUser ];
+        };
+      };
+    };
+
+    systemd.services.sftpgo = {
+      description = "SFTPGo daemon";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      environment = {
+        SFTPGO_CONFIG_FILE = mkDefault configFile;
+        SFTPGO_LOG_FILE_PATH = mkDefault ""; # log to journal
+        SFTPGO_LOADDATA_FROM = mkIf (cfg.loadDataFile != null) cfg.loadDataFile;
+      };
+
+      serviceConfig = mkMerge [
+        ({
+          Type = "simple";
+          User = cfg.user;
+          Group = cfg.group;
+          WorkingDirectory = cfg.dataDir;
+          ReadWritePaths = [ cfg.dataDir ];
+          LimitNOFILE = 8192; # taken from upstream
+          KillMode = "mixed";
+          ExecStart = "${cfg.package}/bin/sftpgo serve ${utils.escapeSystemdExecArgs cfg.extraArgs}";
+          ExecReload = "${pkgs.util-linux}/bin/kill -s HUP $MAINPID";
+
+          # Service hardening
+          CapabilityBoundingSet = [ (optionalString hasPrivilegedPorts "CAP_NET_BIND_SERVICE") ];
+          DevicePolicy = "closed";
+          LockPersonality = true;
+          NoNewPrivileges = true;
+          PrivateDevices = true;
+          PrivateTmp = true;
+          ProcSubset = "pid";
+          ProtectClock = true;
+          ProtectControlGroups = true;
+          ProtectHome = true;
+          ProtectHostname = true;
+          ProtectKernelLogs = true;
+          ProtectKernelModules = true;
+          ProtectKernelTunables = true;
+          ProtectProc = "invisible";
+          ProtectSystem = "strict";
+          RemoveIPC = true;
+          RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX";
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          SystemCallArchitectures = "native";
+          SystemCallFilter = [ "@system-service" "~@privileged" ];
+          UMask = "0077";
+        })
+        (mkIf hasPrivilegedPorts {
+          AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+        })
+        (mkIf (cfg.dataDir == options.services.sftpgo.dataDir.default) {
+          StateDirectory = baseNameOf cfg.dataDir;
+        })
+      ];
+    };
+  };
+}