{ config, lib, pkgs, ... }: let cfg = config.services.dufs; # Helper to create auth string from user list mkAuthString = users: lib.concatMapStringsSep "|" (user: "${user.username}:${user.passwordHash}@/:rw") users; in { options.services.dufs = { enable = lib.mkEnableOption "dufs file server"; package = lib.mkOption { type = lib.types.package; default = pkgs.dufs; description = "The dufs package to use"; }; servePathPublic = lib.mkOption { type = lib.types.str; default = "/nfs_export/kage/dufs/public"; description = "Directory path to serve files from"; }; servePathPrivate = lib.mkOption { type = lib.types.str; default = "/nfs_export/kage/dufs/private"; description = "Directory path to serve files from"; }; user = lib.mkOption { type = lib.types.str; default = "dufs"; description = "User to run dufs service as"; }; group = lib.mkOption { type = lib.types.str; default = "dufs"; description = "Group to run dufs service as"; }; publicInstance = { enable = lib.mkEnableOption "public read-only instance"; port = lib.mkOption { type = lib.types.port; default = 5000; description = "Port for public read-only instance"; }; bind = lib.mkOption { type = lib.types.str; default = "0.0.0.0"; description = "Bind address for public instance"; }; allowSearch = lib.mkOption { type = lib.types.bool; default = true; description = "Allow searching in public instance"; }; }; privateInstance = { enable = lib.mkEnableOption "private read-write instance"; port = lib.mkOption { type = lib.types.port; default = 5001; description = "Port for private authenticated instance"; }; bind = lib.mkOption { type = lib.types.str; default = "0.0.0.0"; description = "Bind address for private instance"; }; users = lib.mkOption { type = lib.types.listOf (lib.types.submodule { options = { username = lib.mkOption { type = lib.types.str; description = "Username for authentication"; }; passwordHash = lib.mkOption { type = lib.types.str; description = "SHA-512 password hash (generate with: openssl passwd -6)"; }; }; }); default = []; description = "List of users with read-write access"; example = [ { username = "admin"; passwordHash = "$6$rounds=656000$..."; } ]; }; allowUpload = lib.mkOption { type = lib.types.bool; default = true; description = "Allow file uploads"; }; allowDelete = lib.mkOption { type = lib.types.bool; default = true; description = "Allow file deletion"; }; allowSearch = lib.mkOption { type = lib.types.bool; default = true; description = "Allow searching"; }; }; openFirewall = lib.mkOption { type = lib.types.bool; default = false; description = "Open firewall ports for enabled instances"; }; }; config = lib.mkIf cfg.enable { # Create dufs user and group users.users.${cfg.user} = { isSystemUser = true; group = cfg.group; extraGroups = [ "users" ]; # Add to users group for access to shared files description = "dufs file server user"; }; users.groups.${cfg.group} = {}; # Public read-only instance systemd.services.dufs-public = lib.mkIf cfg.publicInstance.enable { description = "dufs public read-only file server"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; serviceConfig = { Type = "simple"; User = cfg.user; Group = cfg.group; ExecStart = '' ${cfg.package}/bin/dufs ${cfg.servePathPublic} \ --bind ${cfg.publicInstance.bind} \ --port ${toString cfg.publicInstance.port} \ ${lib.optionalString cfg.publicInstance.allowSearch "--allow-search"} \ --render-index ''; Restart = "on-failure"; RestartSec = "10s"; # Hardening NoNewPrivileges = true; PrivateTmp = true; ProtectSystem = "strict"; ProtectHome = true; ReadOnlyPaths = [ cfg.servePathPublic ]; }; }; # Private read-write instance systemd.services.dufs-private = lib.mkIf cfg.privateInstance.enable { description = "dufs private read-write file server"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; serviceConfig = { Type = "simple"; User = cfg.user; Group = cfg.group; ExecStart = '' ${cfg.package}/bin/dufs ${cfg.servePathPrivate} \ --bind ${cfg.privateInstance.bind} \ --port ${toString cfg.privateInstance.port} \ --auth '${mkAuthString cfg.privateInstance.users}' \ ${lib.optionalString cfg.privateInstance.allowUpload "--allow-upload"} \ ${lib.optionalString cfg.privateInstance.allowDelete "--allow-delete"} \ ${lib.optionalString cfg.privateInstance.allowSearch "--allow-search"} \ --render-index ''; Restart = "on-failure"; RestartSec = "10s"; # Hardening NoNewPrivileges = true; PrivateTmp = true; ProtectSystem = "strict"; ProtectHome = true; ReadWritePaths = [ cfg.servePathPrivate ]; }; }; # Firewall configuration networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall ( (lib.optional cfg.publicInstance.enable cfg.publicInstance.port) ++ (lib.optional cfg.privateInstance.enable cfg.privateInstance.port) ); }; }