diff options
| author | Tim Keller <tjk@tjkeller.xyz> | 2026-08-24 17:34:12 -0500 |
|---|---|---|
| committer | Tim Keller <tjk@tjkeller.xyz> | 2026-08-24 17:34:12 -0500 |
| commit | 8cd20df3b01b0d75be85809add88bbfa4a0a3b37 (patch) | |
| tree | caa5a5b45a0b2bc87ad67fe53cbae01a7bdffbc0 | |
| parent | 672b2803f1821fe8e9372c76206be55863e86c18 (diff) | |
| download | nixos-8cd20df3b01b0d75be85809add88bbfa4a0a3b37.tar.xz nixos-8cd20df3b01b0d75be85809add88bbfa4a0a3b37.zip | |
add unixpasswordsync + enroll users options to samba options, + fix firewall settings in fileshares module
| -rw-r--r-- | nixos/services/fileshares.nix | 222 |
1 files changed, 170 insertions, 52 deletions
diff --git a/nixos/services/fileshares.nix b/nixos/services/fileshares.nix index 90327b8..d90018a 100644 --- a/nixos/services/fileshares.nix +++ b/nixos/services/fileshares.nix @@ -1,53 +1,123 @@ -{ lib, config, ... }: let +{ pkgs, lib, config, ... }: let cfg = config.services._fileShares; - fileShareType = lib.types.submodule ( - { name, ... }: { - options = { - enable = lib.mkEnableOption "enable this file share"; # FIXME mk default - name = lib.mkOption { - type = lib.types.str; - default = name; - description = "name of share"; + fileShareType = lib.types.submodule ({ name, ... }: { + options = { + enable = lib.mkEnableOption "enable this file share"; # FIXME mk default + name = lib.mkOption { + type = lib.types.str; + default = name; + description = "name of share"; + }; + path = lib.mkOption { + type = lib.types.str; + default = name; + description = "path to share"; + }; + readOnly = lib.mkEnableOption "make share read only"; + allowHosts = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = []; + example = [ "192.168.1.100" "192.168.0.0/24" ]; + description = "ip hosts to allow"; + }; + # TODO denyHosts etc + nfs = { + enable = lib.mkEnableOption "share file with nfs protocol"; + }; + smb = { + enable = lib.mkEnableOption "share file with smb protocol"; + allowGuests = lib.mkEnableOption "allow unauthenticated users to mount"; + allowUser = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "allow user"; }; - path = lib.mkOption { - type = lib.types.str; - default = name; - description = "path to share"; + allowGroup = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "allow group"; }; - readOnly = lib.mkEnableOption "make share read only"; - allowHosts = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = []; - example = [ "192.168.1.100" "192.168.0.0/24" ]; - description = "ip hosts to allow"; + defaultFileMask = lib.mkOption { + type = lib.types.strMatching "[0-7]{3,4}"; + default = "0644"; + description = "default permissions for created files"; }; - # TODO denyHosts etc - nfs = { - enable = lib.mkEnableOption "share file with nfs protocol"; + defaultDirectoryMask = lib.mkOption { + type = lib.types.strMatching "[0-7]{3,4}"; + default = "0755"; + description = "default permissions for created directories"; }; - smb = { - enable = lib.mkEnableOption "share file with smb protocol"; - allowGuests = lib.mkEnableOption "allow unauthenticated users to mount"; - allowUser = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - description = "allow user"; - }; - allowGroup = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - description = "allow group"; - }; - extraOptions = lib.mkOption { - type = lib.types.attrsOf lib.types.str; - default = {}; - description = "extra smb options for this share"; - }; + extraOptions = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = {}; + description = "extra smb options for this share"; }; }; - } - ); + }; + }); + + sambaEnrollUserType = lib.types.submodule ({ name, config, ... }: { + options = { + enable = lib.mkEnableOption "enroll this user"; + name = lib.mkOption { + type = lib.types.str; + default = name; + description = "unix username of user to enroll"; + }; + password = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "initial plaintext password to set, visible in nix store"; + }; + passwordFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = "file containing the initial plaintest password"; + }; + hashedPasswordFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = "path to a file containing the NT hash of the initial password"; + }; + mustChangePassword = lib.mkEnableOption "force user to set new password on first logon"; + disabled = lib.mkEnableOption "disable user"; + script = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + readOnly = true; + internal = true; + visible = false; + }; + }; + config = { + script = let + name = lib.escapeShellArg config.name; + pw = + if config.hashedPasswordFile != null then "$(cat ${toString config.hashedPasswordFile})" + else if config.passwordFile != null then "$(cat ${toString config.passwordFile})" + else if config.password != null then "${config.password}" + else "" + ; + in '' + if ! ${pkgs.samba}/bin/pbdedit -L 2>/dev/null | cut -d: -f1 | grep -qx ${name} > /dev/null; then + echo "samba: enrolling ${name}" + pw="${lib.escapeShellArg pw}" + # set password to contents of pw + printf '%s\n%s\n' "$pw" "$pw" | ${pkgs.samba}/bin/smbpasswd -s -a ${name} >/dev/null + # set password hash directly if hashedPasswordFile is used + ${lib.optionalString (config.hashedPasswordFile != null) '' + ${pkgs.samba}/bin/pbdedit -u ${name} --set-nt-hash="$pw" > /dev/null + ''} + # set must change password + ${lib.optionalString config.mustChangePassword '' + ${pkgs.samba}/bin/pbdedit -u ${name} --pwd-must-change-time=0 > /dev/null + ''} + fi + # enable / disable user + ${pkgs.samba}/bin/smbpasswd ${if config.disabled then "-d" else "-e"} ${name} >/dev/null + ''; + }; + }); # Shares sambaShares = lib.filterAttrs (_: s: s.smb.enable) cfg.shares; @@ -76,13 +146,19 @@ "lanman auth" = lib.boolToYesNo true; "client lanman auth" = lib.boolToYesNo true; }; + sambaUnixPasswordSyncSettings.global = { + "unix password sync" = lib.boolToYesNo true; + "pam password change" = lib.boolToYesNo true; + "passwd program" = "/run/wrappers/bin/passwd %u"; + "passwd chat" = "*New*password* %n\\n *Retype*new*password* %n\\n *updated*successfully*"; + }; mkSambaShare = s: { "path" = s.path; "browsable" = lib.boolToYesNo true; "read only" = lib.boolToYesNo s.readOnly; "guest ok" = lib.boolToYesNo s.smb.allowGuests; - "create mask" = "0644"; # TODO configure - "directory mask" = "0755"; # TODO configure + "create mask" = s.smb.defaultFileMask; + "directory mask" = s.smb.defaultDirectoryMask; # allow user/group "force user" = lib.mkIf (s.smb.allowUser != null) s.smb.allowUser; "force group" = lib.mkIf (s.smb.allowGroup != null) s.smb.allowGroup; @@ -92,7 +168,9 @@ } // s.smb.extraOptions; sambaSettings = sambaGlobalSettings // lib.optionalAttrs cfg.smb.enableLegacyNT1 sambaLegacyNT1Settings - // lib.mapAttrs (_: v: mkSambaShare v) sambaShares; + // lib.optionalAttrs cfg.smb.enableUnixPasswordSync sambaUnixPasswordSyncSettings + // lib.mapAttrs (_: v: mkSambaShare v) sambaShares + ; # NFS mkNFSShareHost = s: host: ''${host}(${if s.readOnly then "ro" else "rw"},sync,no_subtree_check)''; # TODO configure @@ -119,6 +197,16 @@ in { required for very old clients (windows xp, legacy nas devices, etc). ''; enableNetBiosDiscovery = lib.mkEnableOption "enable netbios name resolution for clients"; + enableUnixPasswordSync = lib.mkEnableOption '' + enable syncing unix passwords to smb database whenever password is set + with `passwd`. may still need to run `sudo smbpasswd -a $USER` (or set + new password via `passwd`) if enabled after password was already set. + ''; + enrollUsers = lib.mkOption { + type = lib.types.attrsOf sambaEnrollUserType; + default = {}; + description = "users to enroll declaratively for their initial setup"; + }; }; nfs = { enableNFSv3 = lib.mkEnableOption "enable support for nfsv3"; @@ -137,6 +225,32 @@ in { }; }) + # SMB enroll users config + (let + enrollUsers = lib.filterAttrs (_: u: u.enable) cfg.smb.enrollUsers; + pwSources = u: lib.filter (x: x != null) [ u.password u.passwordFile u.hashedPasswordFile ]; + in lib.mkIf (sambaShares != [] && enrollUsers != []) { + assertions = lib.mapAttrsToList (n: u: { + assertion = builtins.length (pwSources u) == 1; + message = '' + services._fileShares.smb.enrollUsers.${n}: set exactly one of + password, passwordFile, or hashedPasswordFile + (found ${toString (builtins.length (pwSources u))}) + ''; + }) enrollUsers; + + warnings = lib.mapAttrsToList (n: u: '' + services._fileShares.smb.enrollUsers.${n}.password is stored in + plaintext in the world-readable Nix store; use passwordFile instead + '') (lib.filterAttrs (_: u: u.password != null) enrollUsers); + + # Enroll users activation script + system.activationScripts.sambaEnrollUsers = lib.stringAfter [ "users" "setupSecrets" ] '' + set -eu + ${lib.concatMapAttrsStringSep "\n" (_: u: u.script) enrollUsers} + ''; + }) + # NFS(v4/v3) config (lib.mkIf (nfsShares != []) { services.nfs.server = lib.mkMerge [ @@ -155,9 +269,19 @@ in { extraNfsdConfig = ''''; }) ]; + }) - # Firewall + # Firewall + ({ networking.firewall = lib.mkMerge [ + # SMB + (lib.mkIf cfg.smb.openFirewall { + allowedTCPPorts = [ cfg.smb.port ] + ++ lib.optional cfg.smb.enableNetBiosDiscovery 139; + allowedUDPPorts = lib.optionals cfg.smb.enableNetBiosDiscovery [ 137 138 ]; + }) + + # NFS (lib.mkIf cfg.nfs.openFirewall ( if cfg.nfs.enableNFSv3 then { allowedTCPPorts = [ 111 2049 4000 4001 4002 20048 ]; @@ -166,12 +290,6 @@ in { allowedTCPPorts = [ 2049 ]; } )) - - (lib.mkIf cfg.smb.openFirewall { - allowedTCPPorts = [ cfg.smb.port ] - ++ lib.optional cfg.smb.enableNetBiosDiscovery 139; - allowedUDPPorts = lib.optionals cfg.smb.enableNetBiosDiscovery [ 137 138 ]; - }) ]; }) ]); |
