at 25.11-pre 13 kB view raw
1{ 2 options, 3 config, 4 lib, 5 pkgs, 6 utils, 7 ... 8}: 9 10with lib; 11 12let 13 cfg = config.services.sftpgo; 14 defaultUser = "sftpgo"; 15 settingsFormat = pkgs.formats.json { }; 16 configFile = settingsFormat.generate "sftpgo.json" cfg.settings; 17 hasPrivilegedPorts = any (port: port > 0 && port < 1024) ( 18 catAttrs "port" ( 19 cfg.settings.httpd.bindings 20 ++ cfg.settings.ftpd.bindings 21 ++ cfg.settings.sftpd.bindings 22 ++ cfg.settings.webdavd.bindings 23 ) 24 ); 25in 26{ 27 options.services.sftpgo = { 28 enable = mkOption { 29 type = types.bool; 30 default = false; 31 description = "sftpgo"; 32 }; 33 34 package = mkPackageOption pkgs "sftpgo" { }; 35 36 extraArgs = mkOption { 37 type = with types; listOf str; 38 default = [ ]; 39 description = '' 40 Additional command line arguments to pass to the sftpgo daemon. 41 ''; 42 example = [ 43 "--log-level" 44 "info" 45 ]; 46 }; 47 48 dataDir = mkOption { 49 type = types.path; 50 default = "/var/lib/sftpgo"; 51 description = '' 52 The directory where SFTPGo stores its data files. 53 ''; 54 }; 55 56 extraReadWriteDirs = mkOption { 57 type = types.listOf types.path; 58 default = [ ]; 59 description = '' 60 Extra directories where SFTPGo is allowed to write to. 61 ''; 62 }; 63 64 user = mkOption { 65 type = types.str; 66 default = defaultUser; 67 description = '' 68 User account name under which SFTPGo runs. 69 ''; 70 }; 71 72 group = mkOption { 73 type = types.str; 74 default = defaultUser; 75 description = '' 76 Group name under which SFTPGo runs. 77 ''; 78 }; 79 80 loadDataFile = mkOption { 81 default = null; 82 type = with types; nullOr path; 83 description = '' 84 Path to a json file containing users and folders to load (or update) on startup. 85 Check the [documentation](https://sftpgo.github.io/latest/config-file/) 86 for the `--loaddata-from` command line argument for more info. 87 ''; 88 }; 89 90 settings = mkOption { 91 default = { }; 92 description = '' 93 The primary sftpgo configuration. See the 94 [configuration reference](https://sftpgo.github.io/latest/config-file/) 95 for possible values. 96 ''; 97 type = 98 with types; 99 submodule { 100 freeformType = settingsFormat.type; 101 options = { 102 httpd.bindings = mkOption { 103 default = [ ]; 104 description = '' 105 Configure listen addresses and ports for httpd. 106 ''; 107 type = types.listOf ( 108 types.submodule { 109 freeformType = settingsFormat.type; 110 options = { 111 address = mkOption { 112 type = types.str; 113 default = "127.0.0.1"; 114 description = '' 115 Network listen address. Leave blank to listen on all available network interfaces. 116 On *NIX you can specify an absolute path to listen on a Unix-domain socket. 117 ''; 118 }; 119 120 port = mkOption { 121 type = types.port; 122 default = 8080; 123 description = '' 124 The port for serving HTTP(S) requests. 125 126 Setting the port to `0` disables listening on this interface binding. 127 ''; 128 }; 129 130 enable_web_admin = mkOption { 131 type = types.bool; 132 default = true; 133 description = '' 134 Enable the built-in web admin for this interface binding. 135 ''; 136 }; 137 138 enable_web_client = mkOption { 139 type = types.bool; 140 default = true; 141 description = '' 142 Enable the built-in web client for this interface binding. 143 ''; 144 }; 145 }; 146 } 147 ); 148 }; 149 150 ftpd.bindings = mkOption { 151 default = [ ]; 152 description = '' 153 Configure listen addresses and ports for ftpd. 154 ''; 155 type = types.listOf ( 156 types.submodule { 157 freeformType = settingsFormat.type; 158 options = { 159 address = mkOption { 160 type = types.str; 161 default = "127.0.0.1"; 162 description = '' 163 Network listen address. Leave blank to listen on all available network interfaces. 164 On *NIX you can specify an absolute path to listen on a Unix-domain socket. 165 ''; 166 }; 167 168 port = mkOption { 169 type = types.port; 170 default = 0; 171 description = '' 172 The port for serving FTP requests. 173 174 Setting the port to `0` disables listening on this interface binding. 175 ''; 176 }; 177 }; 178 } 179 ); 180 }; 181 182 sftpd.bindings = mkOption { 183 default = [ ]; 184 description = '' 185 Configure listen addresses and ports for sftpd. 186 ''; 187 type = types.listOf ( 188 types.submodule { 189 freeformType = settingsFormat.type; 190 options = { 191 address = mkOption { 192 type = types.str; 193 default = "127.0.0.1"; 194 description = '' 195 Network listen address. Leave blank to listen on all available network interfaces. 196 On *NIX you can specify an absolute path to listen on a Unix-domain socket. 197 ''; 198 }; 199 200 port = mkOption { 201 type = types.port; 202 default = 0; 203 description = '' 204 The port for serving SFTP requests. 205 206 Setting the port to `0` disables listening on this interface binding. 207 ''; 208 }; 209 }; 210 } 211 ); 212 }; 213 214 webdavd.bindings = mkOption { 215 default = [ ]; 216 description = '' 217 Configure listen addresses and ports for webdavd. 218 ''; 219 type = types.listOf ( 220 types.submodule { 221 freeformType = settingsFormat.type; 222 options = { 223 address = mkOption { 224 type = types.str; 225 default = "127.0.0.1"; 226 description = '' 227 Network listen address. Leave blank to listen on all available network interfaces. 228 On *NIX you can specify an absolute path to listen on a Unix-domain socket. 229 ''; 230 }; 231 232 port = mkOption { 233 type = types.port; 234 default = 0; 235 description = '' 236 The port for serving WebDAV requests. 237 238 Setting the port to `0` disables listening on this interface binding. 239 ''; 240 }; 241 }; 242 } 243 ); 244 }; 245 246 smtp = mkOption { 247 default = { }; 248 description = '' 249 SMTP configuration section. 250 ''; 251 type = types.submodule { 252 freeformType = settingsFormat.type; 253 options = { 254 host = mkOption { 255 type = types.str; 256 default = ""; 257 description = '' 258 Location of SMTP email server. Leave empty to disable email sending capabilities. 259 ''; 260 }; 261 262 port = mkOption { 263 type = types.port; 264 default = 465; 265 description = "Port of the SMTP Server."; 266 }; 267 268 encryption = mkOption { 269 type = types.enum [ 270 0 271 1 272 2 273 ]; 274 default = 1; 275 description = '' 276 Encryption scheme: 277 - `0`: No encryption 278 - `1`: TLS 279 - `2`: STARTTLS 280 ''; 281 }; 282 283 auth_type = mkOption { 284 type = types.enum [ 285 0 286 1 287 2 288 ]; 289 default = 0; 290 description = '' 291 - `0`: Plain 292 - `1`: Login 293 - `2`: CRAM-MD5 294 ''; 295 }; 296 297 user = mkOption { 298 type = types.str; 299 default = "sftpgo"; 300 description = "SMTP username."; 301 }; 302 303 from = mkOption { 304 type = types.str; 305 default = "SFTPGo <sftpgo@example.com>"; 306 description = '' 307 From address. 308 ''; 309 }; 310 }; 311 }; 312 }; 313 }; 314 }; 315 }; 316 }; 317 318 config = mkIf cfg.enable { 319 services.sftpgo.settings = ( 320 mapAttrs (name: mkDefault) { 321 ftpd.bindings = [ { port = 0; } ]; 322 httpd.bindings = [ { port = 0; } ]; 323 sftpd.bindings = [ { port = 0; } ]; 324 webdavd.bindings = [ { port = 0; } ]; 325 httpd.openapi_path = "${cfg.package}/share/sftpgo/openapi"; 326 httpd.templates_path = "${cfg.package}/share/sftpgo/templates"; 327 httpd.static_files_path = "${cfg.package}/share/sftpgo/static"; 328 smtp.templates_path = "${cfg.package}/share/sftpgo/templates"; 329 } 330 ); 331 332 users = optionalAttrs (cfg.user == defaultUser) { 333 users = { 334 ${defaultUser} = { 335 description = "SFTPGo system user"; 336 isSystemUser = true; 337 group = defaultUser; 338 home = cfg.dataDir; 339 }; 340 }; 341 342 groups = { 343 ${defaultUser} = { 344 members = [ defaultUser ]; 345 }; 346 }; 347 }; 348 349 systemd.services.sftpgo = { 350 description = "SFTPGo daemon"; 351 after = [ "network.target" ]; 352 wantedBy = [ "multi-user.target" ]; 353 354 environment = { 355 SFTPGO_CONFIG_FILE = mkDefault configFile; 356 SFTPGO_LOG_FILE_PATH = mkDefault ""; # log to journal 357 SFTPGO_LOADDATA_FROM = mkIf (cfg.loadDataFile != null) cfg.loadDataFile; 358 }; 359 360 serviceConfig = mkMerge [ 361 ({ 362 Type = "simple"; 363 User = cfg.user; 364 Group = cfg.group; 365 WorkingDirectory = cfg.dataDir; 366 ReadWritePaths = [ cfg.dataDir ] ++ cfg.extraReadWriteDirs; 367 LimitNOFILE = 8192; # taken from upstream 368 KillMode = "mixed"; 369 ExecStart = "${cfg.package}/bin/sftpgo serve ${utils.escapeSystemdExecArgs cfg.extraArgs}"; 370 ExecReload = "${pkgs.util-linux}/bin/kill -s HUP $MAINPID"; 371 372 # Service hardening 373 CapabilityBoundingSet = [ (optionalString hasPrivilegedPorts "CAP_NET_BIND_SERVICE") ]; 374 DevicePolicy = "closed"; 375 LockPersonality = true; 376 NoNewPrivileges = true; 377 PrivateDevices = true; 378 PrivateTmp = true; 379 ProcSubset = "pid"; 380 ProtectClock = true; 381 ProtectControlGroups = true; 382 ProtectHome = true; 383 ProtectHostname = true; 384 ProtectKernelLogs = true; 385 ProtectKernelModules = true; 386 ProtectKernelTunables = true; 387 ProtectProc = "invisible"; 388 ProtectSystem = "strict"; 389 RemoveIPC = true; 390 RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX"; 391 RestrictNamespaces = true; 392 RestrictRealtime = true; 393 RestrictSUIDSGID = true; 394 SystemCallArchitectures = "native"; 395 SystemCallFilter = [ 396 "@system-service" 397 "~@privileged" 398 ]; 399 UMask = "0077"; 400 }) 401 (mkIf hasPrivilegedPorts { 402 AmbientCapabilities = "CAP_NET_BIND_SERVICE"; 403 }) 404 (mkIf (cfg.dataDir == options.services.sftpgo.dataDir.default) { 405 StateDirectory = baseNameOf cfg.dataDir; 406 }) 407 ]; 408 }; 409 }; 410}