Created
December 19, 2025 03:27
-
-
Save melmatsuoka/a52c226ad043d9ded403e01bb1aa1097 to your computer and use it in GitHub Desktop.
Adds a "Sharing only" macOS user account, and properly enables the account for SMB mounts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| set -euo pipefail | |
| if [ "$(id -u)" -ne 0 ]; then | |
| echo "This script must be run as root" | |
| exit 1 | |
| fi | |
| gid="20" # Default "Staff" group | |
| # Prompt for username | |
| while true; do | |
| read -r -p "Enter short username (no spaces, lowercase only): " username | |
| # Validate username | |
| if [[ ! "${username}" =~ ^[a-z_][a-z0-9_]{0,30}$ ]]; then | |
| echo "Error: Invalid username." | |
| echo "Allowed: lowercase letters, numbers, underscore. Must not start with a number." | |
| continue | |
| fi | |
| # Check if user already exists | |
| if dscl . -read /Users/"${username}" &>/dev/null; then | |
| echo "Error: User \"${username}\" already exists." | |
| continue | |
| fi | |
| break | |
| done | |
| # Prompt for real (display) name | |
| read -r -p "Enter full display name: " realname | |
| # Prompt for password (hidden input) | |
| while true; do | |
| read -r -s -p "Enter password: " password | |
| echo | |
| read -r -s -p "Confirm password: " password_confirm | |
| echo | |
| if [ "${password}" != "${password_confirm}" ]; then | |
| echo "Error: Passwords do not match. Try again." | |
| continue | |
| fi | |
| if [ -z "${password}" ]; then | |
| echo "Error: Password cannot be empty." | |
| continue | |
| fi | |
| break | |
| done | |
| createUser() { | |
| # Create a new user with the username Sharing user | |
| dscl . create /Users/${username} | |
| # Give the display name of the user | |
| dscl . create /Users/${username} RealName "${realname}" | |
| # (Optional) Add a password hint | |
| # dscl . create /Users/${username} hint "Password hint goes here" | |
| # (Optional) Set a profile picture. | |
| # dscl . create /Users/${username} picture "/Provide path to the image.png" | |
| # password for the user account. | |
| dscl . passwd /Users/${username} "${password}" | |
| # Set the unique ID for the user. Provide an ID which is not already used. | |
| dscl . create /Users/${username} UniqueID ${next_uid} | |
| # Set the Group ID for the user | |
| dscl . create /Users/${username} PrimaryGroupID ${gid} | |
| # Deny shell access for the user | |
| # NOTE: This has to be set to /sbin/nologin (not /usr/bin/false) if the account needs SMB access | |
| dscl . create /Users/${username} UserShell /sbin/nologin | |
| # No home directory for the user | |
| dscl . create /Users/${username} NFSHomeDirectory /dev/null | |
| } | |
| enableSMB() { | |
| echo "Enabling SMB access for user \"${username}\"…" | |
| # For more info see: https://stackoverflow.com/questions/7824946/how-to-enable-user-account-smb-sharing-from-terminal-on-mac-os-x | |
| pwpolicy -u "${username}" sethashtypes SMB-NT on | |
| # note that you must use "sudo" to read the exisiting hashtypes for a user using gethashtypes. | |
| # Otherwise, the command silently returns nothing | |
| # | |
| } | |
| # Check if username already exists | |
| if dscl . -read /Users/"${username}" &>/dev/null; then | |
| echo "Error: User \"${username}\" already exists" | |
| exit 1 | |
| fi | |
| # Find the next available UID after the current highest UID | |
| # Uses dscacheutil to find highest UID, then verifies with dscl | |
| # Get the highest UID currently in use | |
| highest_uid=$(dscl . -list /Users UniqueID | awk '{print $2}' | sort -n | tail -1) | |
| # Check if we got a valid result | |
| if [ -z "${highest_uid}" ]; then | |
| echo "Error: Could not determine highest UID" | |
| exit 1 | |
| fi | |
| echo "Highest UID found: $highest_uid" | |
| # Start checking from the next UID | |
| next_uid=$((highest_uid + 1)) | |
| # Keep checking until we find an available UID | |
| while true; do | |
| # Check if this UID exists using dscl | |
| result=$(dscl . -search /Users UniqueID "${next_uid}" 2>/dev/null) | |
| if [ -z "${result}" ]; then | |
| # UID is available | |
| echo -e "Next available UID: ${next_uid}\n" | |
| echo -e "Checking if UID is already in use...\n" | |
| if [ "${next_uid}" -lt 500 ]; then | |
| echo "Error: Refusing to create user with UID < 500" | |
| exit 1 | |
| fi | |
| echo -e "Creating sharing-only user \"${username}\" with UID ${next_uid}…\n" | |
| createUser | |
| enableSMB | |
| echo -e "Done." | |
| exit 0 | |
| else | |
| # UID exists, try the next one | |
| echo "UID ${next_uid} is already in use, checking next..." | |
| next_uid=$((next_uid + 1)) | |
| fi | |
| done |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment