504525a148
GitOrigin-RevId: bd645e8668ec6612439a9ee7e71f7eac4099d4f6
368 lines
12 KiB
Nix
368 lines
12 KiB
Nix
{ 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 = mkPackageOption pkgs "sftpgo" { };
|
|
|
|
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;
|
|
})
|
|
];
|
|
};
|
|
};
|
|
}
|