summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--nixos/services/fileshares.nix222
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 ];
- })
];
})
]);