#!/usr/bin/env python3 import json import os import pwd # Shells that indicate a user cannot log in INVALID_SHELLS = { "/sbin/nologin", "/usr/sbin/nologin", "/usr/bin/nologin", "/bin/false", "/usr/bin/false", "/bin/true", "/usr/bin/true", "", } # Minimum UID for regular users (typically 1000 on most Linux distributions) MIN_UID = 1000 def get_face_path(home: str, username: str) -> str: """Get the path to the user's face/avatar image if it exists.""" # Check common locations for user avatars candidates = [ os.path.join(home, ".face"), os.path.join(home, ".face.icon"), f"/var/lib/AccountsService/icons/{username}", ] for path in candidates: if os.path.isfile(path): return path return "" def is_graphical_user(pw: pwd.struct_passwd) -> bool: """Check if a user is allowed to log in graphically.""" # Must be a regular user (UID >= 1000) if pw.pw_uid < MIN_UID: return False # Must have a valid login shell if pw.pw_shell in INVALID_SHELLS: return False # Home directory should exist if not os.path.isdir(pw.pw_dir): return False return True def main(): users = [] for pw in pwd.getpwall(): if not is_graphical_user(pw): continue users.append({ "username": pw.pw_name, "uid": pw.pw_uid, "home": pw.pw_dir, "shell": pw.pw_shell, "gecos": pw.pw_gecos.split(",")[0] if pw.pw_gecos else "", "face": get_face_path(pw.pw_dir, pw.pw_name), }) # Sort by username for consistent ordering users.sort(key=lambda u: u["username"]) print(json.dumps(users)) if __name__ == "__main__": main()