From c4fa01cb0e6dd5f86e2b3ea914ca5f44948ccc5a Mon Sep 17 00:00:00 2001 From: Ludwig Lehnert Date: Wed, 18 Feb 2026 19:40:13 +0100 Subject: [PATCH] attempted fix on group shares not appearing (GID not found) (3) --- README.md | 3 ++- app/reconcile_shares.py | 35 +++++++++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f7d3477..6cc2a75 100644 --- a/README.md +++ b/README.md @@ -150,10 +150,11 @@ Kerberos requires close time alignment. ### Dynamic Group Shares - AD groups: `FileShare_*` and `FS_*` -- Share name: prefix removed (`FileShare_Finance` -> `\\server\Finance`, `FS_Finance` -> `\\server\Finance`) +- Share name: group title from AD (`displayName` -> `name`/`cn` fallback). Prefix stripping from `sAMAccountName` is only a fallback when no title exists. - Backing path: `/data/groups/` - Share exposure generated in `/etc/samba/generated/shares.conf` - Dynamic share names are validated for SMB compatibility and deduplicated case-insensitively. +- Group membership changes are refreshed during each reconciliation cycle (winbind cache flush + Samba config reload). ## Useful Commands diff --git a/app/reconcile_shares.py b/app/reconcile_shares.py index 043ed72..f804b7d 100755 --- a/app/reconcile_shares.py +++ b/app/reconcile_shares.py @@ -27,6 +27,7 @@ LDAP_FILTER = ( ) GROUP_PREFIXES = ("FileShare_", "FS_") USER_STATUS_FILTER = "(&(objectClass=user)(!(objectClass=computer))(sAMAccountName=*))" +GROUP_TITLE_ATTRS = ("displayname", "name", "cn") REQUIRED_ENV = ["REALM", "WORKGROUP", "DOMAIN"] ATTR_RE = re.compile(r"^([^:]+)(::?)\s*(.*)$") @@ -123,6 +124,15 @@ def derive_share_name(sam_account_name: str) -> Optional[str]: return None +def derive_group_title(entry: Dict[str, Tuple[str, bool]]) -> Optional[str]: + for attr in GROUP_TITLE_ATTRS: + if attr in entry: + value = entry[attr][0].strip() + if value: + return value + return None + + def parse_groups_from_ldap_output(output: str) -> List[Dict[str, str]]: entries = parse_ldap_entries(output) @@ -133,7 +143,7 @@ def parse_groups_from_ldap_output(output: str) -> List[Dict[str, str]]: sam_value, _ = entry["samaccountname"] sam = sam_value.strip() - share_name = derive_share_name(sam) + share_name = derive_group_title(entry) or derive_share_name(sam) if not share_name: continue @@ -157,7 +167,18 @@ def parse_groups_from_ldap_output(output: str) -> List[Dict[str, str]]: def fetch_groups_via_net_ads() -> List[Dict[str, str]]: result = run_command( - ["net", "ads", "search", "-P", LDAP_FILTER, "objectGUID", "sAMAccountName"], + [ + "net", + "ads", + "search", + "-P", + LDAP_FILTER, + "objectGUID", + "sAMAccountName", + "displayName", + "name", + "cn", + ], check=False, ) if result.returncode != 0: @@ -204,6 +225,9 @@ def fetch_groups_via_ldap_bind() -> List[Dict[str, str]]: LDAP_FILTER, "objectGUID", "sAMAccountName", + "displayName", + "name", + "cn", ] ) return parse_groups_from_ldap_output(result.stdout) @@ -447,6 +471,12 @@ def reload_samba() -> None: log("smbcontrol reload-config failed; will retry on next run") +def refresh_winbind_cache() -> None: + result = run_command(["net", "cache", "flush"], check=False) + if result.returncode != 0: + log("net cache flush failed; group membership updates may be delayed") + + def resolve_user_uid(qualified_user: str) -> Optional[int]: try: return pwd.getpwnam(qualified_user).pw_uid @@ -701,6 +731,7 @@ def with_lock() -> bool: sync_public_directory() sync_private_directories() + refresh_winbind_cache() reload_samba() log("Reconciliation completed") return True