diff --git a/luci/modules/home-manager/home.nix b/luci/modules/home-manager/home.nix index 88ede30..13dc107 100644 --- a/luci/modules/home-manager/home.nix +++ b/luci/modules/home-manager/home.nix @@ -15,6 +15,7 @@ in fonts.fontconfig.enable = true; home.packages = with pkgs; [ helix + ghostty jq # diff --git a/luci/nixos/hardware-configuration.nix b/luci/nixos/hardware-configuration.nix index dd1ee24..117077a 100644 --- a/luci/nixos/hardware-configuration.nix +++ b/luci/nixos/hardware-configuration.nix @@ -14,7 +14,11 @@ boot.extraModulePackages = [ ]; boot.supportedFilesystems = [ "zfs" ]; - boot.kernelPackages = pkgs.linuxPackages_6_10; + boot.kernelPackages = pkgs.linuxPackages; # latest LTS - should be safe with zfs + # List all available kernel packages + # nix-shell -p nixpkgs-unstable --run "nix repl" + # nix-repl> :l + # nix-repl> pkgs.linuxPackages boot.zfs.forceImportRoot = false; diff --git a/shared/modules/services/dufs.nix b/shared/modules/services/dufs.nix new file mode 100644 index 0000000..73e9f5f --- /dev/null +++ b/shared/modules/services/dufs.nix @@ -0,0 +1,208 @@ +{ 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; + 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) + ); + }; +} diff --git a/shared/server-configuration.nix b/shared/server-configuration.nix index 2c4b005..9e405ec 100644 --- a/shared/server-configuration.nix +++ b/shared/server-configuration.nix @@ -55,6 +55,7 @@ in imports = [ ./modules/user/main_user.nix + ./modules/services/dufs.nix ]; config = { @@ -165,9 +166,50 @@ in }; auth = { type = "htpasswd"; - htpasswd_filename = "/home/luci/.config/radicale/rad_pass"; + htpasswd_filename = "/var/lib/radicale/users"; htpasswd_encryption = "bcrypt"; }; + storage = { + filesystem_folder = "/var/lib/radicale/collections"; + }; + }; + }; + + # DUFS file server configuration + services.dufs = { + enable = true; + openFirewall = true; + + # Public read-only instance + publicInstance = { + enable = true; + port = 5000; + allowSearch = true; + }; + + # Private read-write instance with authentication + privateInstance = { + enable = true; + port = 5001; + allowUpload = true; + allowDelete = true; + allowSearch = true; + users = [ + # Generate password hash with: openssl passwd -6 + # Replace with actual username and hash + # { + # username = "admin"; + # passwordHash = "$6$rounds=656000$..."; + # } + { + username = "nate"; + passwordHash = "$6$rounds=656000$6$3.mottgY50yEZJlr$8a8ztrB/G2kZ39C0cAMDEfQGd93sqL4tS.gQKjnDrRQVvE.VTIlp5JF/GRW95YsKhaOF3r9ui9RTj88Z8LBV80"; + } + { + username = "guest"; + passwordHash = "$6$rounds=656000$6$tJlnxZhnNFPaDEXf$0Q.wZwDczaLfk5rIvX6FfCYvS75IY16WpuXKJyRMbdq4Ie8mZC3fSp5oOB95bMDRHRcabexi5Fp8j39c0pYc8."; + } + ]; }; };