nixpkgs/nixos/modules/services/web-apps/sftpgo.nix
Silvan Mosberger 4f0dadbf38 treewide: format all inactive Nix files
After final improvements to the official formatter implementation,
this commit now performs the first treewide reformat of Nix files using it.
This is part of the implementation of RFC 166.

Only "inactive" files are reformatted, meaning only files that
aren't being touched by any PR with activity in the past 2 months.
This is to avoid conflicts for PRs that might soon be merged.
Later we can do a full treewide reformat to get the rest,
which should not cause as many conflicts.

A CI check has already been running for some time to ensure that new and
already-formatted files are formatted, so the files being reformatted here
should also stay formatted.

This commit was automatically created and can be verified using

    nix-build a08b3a4d19.tar.gz \
      --argstr baseRev b32a094368
    result/bin/apply-formatting $NIXPKGS_PATH
2024-12-10 20:26:33 +01:00

411 lines
13 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 = "sftpgo";
};
package = mkPackageOption pkgs "sftpgo" { };
extraArgs = mkOption {
type = with types; listOf str;
default = [ ];
description = ''
Additional command line arguments to pass to the sftpgo daemon.
'';
example = [
"--log-level"
"info"
];
};
dataDir = mkOption {
type = types.path;
default = "/var/lib/sftpgo";
description = ''
The directory where SFTPGo stores its data files.
'';
};
extraReadWriteDirs = mkOption {
type = types.listOf types.path;
default = [ ];
description = ''
Extra directories where SFTPGo is allowed to write to.
'';
};
user = mkOption {
type = types.str;
default = defaultUser;
description = ''
User account name under which SFTPGo runs.
'';
};
group = mkOption {
type = types.str;
default = defaultUser;
description = ''
Group name under which SFTPGo runs.
'';
};
loadDataFile = mkOption {
default = null;
type = with types; nullOr path;
description = ''
Path to a json file containing users and folders to load (or update) on startup.
Check the [documentation](https://sftpgo.github.io/latest/config-file/)
for the `--loaddata-from` command line argument for more info.
'';
};
settings = mkOption {
default = { };
description = ''
The primary sftpgo configuration. See the
[configuration reference](https://sftpgo.github.io/latest/config-file/)
for possible values.
'';
type =
with types;
submodule {
freeformType = settingsFormat.type;
options = {
httpd.bindings = mkOption {
default = [ ];
description = ''
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 = ''
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 = ''
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 = ''
Enable the built-in web admin for this interface binding.
'';
};
enable_web_client = mkOption {
type = types.bool;
default = true;
description = ''
Enable the built-in web client for this interface binding.
'';
};
};
}
);
};
ftpd.bindings = mkOption {
default = [ ];
description = ''
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 = ''
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 = ''
The port for serving FTP requests.
Setting the port to `0` disables listening on this interface binding.
'';
};
};
}
);
};
sftpd.bindings = mkOption {
default = [ ];
description = ''
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 = ''
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 = ''
The port for serving SFTP requests.
Setting the port to `0` disables listening on this interface binding.
'';
};
};
}
);
};
webdavd.bindings = mkOption {
default = [ ];
description = ''
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 = ''
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 = ''
The port for serving WebDAV requests.
Setting the port to `0` disables listening on this interface binding.
'';
};
};
}
);
};
smtp = mkOption {
default = { };
description = ''
SMTP configuration section.
'';
type = types.submodule {
freeformType = settingsFormat.type;
options = {
host = mkOption {
type = types.str;
default = "";
description = ''
Location of SMTP email server. Leave empty to disable email sending capabilities.
'';
};
port = mkOption {
type = types.port;
default = 465;
description = "Port of the SMTP Server.";
};
encryption = mkOption {
type = types.enum [
0
1
2
];
default = 1;
description = ''
Encryption scheme:
- `0`: No encryption
- `1`: TLS
- `2`: STARTTLS
'';
};
auth_type = mkOption {
type = types.enum [
0
1
2
];
default = 0;
description = ''
- `0`: Plain
- `1`: Login
- `2`: CRAM-MD5
'';
};
user = mkOption {
type = types.str;
default = "sftpgo";
description = "SMTP username.";
};
from = mkOption {
type = types.str;
default = "SFTPGo <sftpgo@example.com>";
description = ''
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 ] ++ cfg.extraReadWriteDirs;
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;
})
];
};
};
}