Cider Isn't Darwin Emulation, Really
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272#!/bin/sh# sysadminctl — macOS user management stub for Darling## Translates macOS sysadminctl commands to direct /etc/passwd file operations.# Only implements the subset needed by the Nix installer:## sysadminctl -addUser <name> [-UID <uid>] [-GID <gid>]# [-home <dir>] [-shell <shell>] [-fullName <name>]# sysadminctl -deleteUser <name>## This stub operates on the Darling prefix's /etc/passwd file, NOT the host's.## See: plan/07-phase5-daemon.md (Task 5.1)set -euPASSWD_FILE="/etc/passwd"# ── Helpers ─────────────────────────────────────────────────────────────────errcho() { echo "$@" >&2}usage() { errcho "Usage: sysadminctl -addUser <username> [-UID <uid>] [-GID <gid>]" errcho " [-home <homedir>] [-shell <shell>] [-fullName <gecos>]" errcho " sysadminctl -deleteUser <username>" errcho "" errcho "Options:" errcho " -addUser <name> Create a new user" errcho " -deleteUser <name> Delete an existing user" errcho " -UID <uid> User ID (numeric)" errcho " -GID <gid> Primary group ID (numeric)" errcho " -home <dir> Home directory (default: /var/empty)" errcho " -shell <shell> Login shell (default: /usr/bin/false)" errcho " -fullName <gecos> Full name / GECOS field" errcho " -password <pass> Password (ignored — always set to locked)" errcho " -adminUser <user> Admin user for authentication (ignored)" errcho " -adminPassword <pw> Admin password (ignored)" exit 64}# user_exists <name> — exit 0 if the user exists in /etc/passwduser_exists() { grep -q "^${1}:" "$PASSWD_FILE" 2>/dev/null}# uid_exists <uid> — exit 0 if the UID is already takenuid_exists() { awk -F: -v uid="$1" '$3 == uid { found=1; exit } END { exit !found }' "$PASSWD_FILE" 2>/dev/null}# get_uid_owner <uid> — print the username that owns this UIDget_uid_owner() { awk -F: -v uid="$1" '$3 == uid { print $1; exit }' "$PASSWD_FILE"}# next_uid — find the next available UID >= 300 (Nix build user range)next_uid() { awk -F: '{ if ($3 >= 300 && $3 > max) max = $3 } END { print (max ? max + 1 : 300) }' "$PASSWD_FILE"}# ── Argument parsing ────────────────────────────────────────────────────────if [ "$#" -lt 2 ]; then usagefiACTION=""USERNAME=""case "$1" in -addUser) ACTION="add" USERNAME="$2" shift 2 ;; -deleteUser) ACTION="delete" USERNAME="$2" shift 2 ;; -h|--help|-help) usage ;; *) errcho "sysadminctl: unknown option '$1'" usage ;;esac# Validate username (alphanumeric, underscore, dash, dot; max 256 chars)case "$USERNAME" in "") errcho "sysadminctl: username must not be empty" exit 1 ;; *[!a-zA-Z0-9_.-]*) errcho "sysadminctl: invalid username '$USERNAME' (allowed: a-z A-Z 0-9 _ . -)" exit 1 ;;esacif [ "${#USERNAME}" -gt 256 ]; then errcho "sysadminctl: username too long (max 256 characters)" exit 1fi# ── addUser ─────────────────────────────────────────────────────────────────if [ "$ACTION" = "add" ]; then UID_VAL="" GID_VAL="" HOME_DIR="/var/empty" SHELL="/usr/bin/false" GECOS="" while [ "$#" -gt 0 ]; do case "$1" in -UID) [ "$#" -ge 2 ] || { errcho "sysadminctl: -UID requires an argument"; exit 1; } UID_VAL="$2" shift 2 ;; -GID) [ "$#" -ge 2 ] || { errcho "sysadminctl: -GID requires an argument"; exit 1; } GID_VAL="$2" shift 2 ;; -home) [ "$#" -ge 2 ] || { errcho "sysadminctl: -home requires an argument"; exit 1; } HOME_DIR="$2" shift 2 ;; -shell) [ "$#" -ge 2 ] || { errcho "sysadminctl: -shell requires an argument"; exit 1; } SHELL="$2" shift 2 ;; -fullName) [ "$#" -ge 2 ] || { errcho "sysadminctl: -fullName requires an argument"; exit 1; } GECOS="$2" shift 2 ;; -password) # Ignored — we always set the password field to 'x' (locked) [ "$#" -ge 2 ] || { errcho "sysadminctl: -password requires an argument"; exit 1; } shift 2 ;; -adminUser|-admin) # Ignored — no authentication needed in a Darling prefix [ "$#" -ge 2 ] || { errcho "sysadminctl: $1 requires an argument"; exit 1; } shift 2 ;; -adminPassword) # Ignored — no authentication needed in a Darling prefix [ "$#" -ge 2 ] || { errcho "sysadminctl: -adminPassword requires an argument"; exit 1; } shift 2 ;; -roleAccount) # Boolean flag used by newer Nix installers — no argument shift ;; *) errcho "sysadminctl: unknown option '$1'" usage ;; esac done # Idempotent: if user already exists, succeed silently if user_exists "$USERNAME"; then echo "User '$USERNAME' already exists" exit 0 fi # Assign UID if not specified if [ -z "$UID_VAL" ]; then UID_VAL=$(next_uid) fi # Validate UID is numeric case "$UID_VAL" in *[!0-9]*) errcho "sysadminctl: UID must be numeric, got '$UID_VAL'" exit 1 ;; esac # Check for UID conflicts if uid_exists "$UID_VAL"; then existing=$(get_uid_owner "$UID_VAL") errcho "sysadminctl: UID $UID_VAL already in use by user '$existing'" exit 1 fi # Default GID to 0 (wheel/root) if not specified if [ -z "$GID_VAL" ]; then GID_VAL=0 fi # Validate GID is numeric case "$GID_VAL" in *[!0-9]*) errcho "sysadminctl: GID must be numeric, got '$GID_VAL'" exit 1 ;; esac # Ensure /etc/passwd exists if [ ! -f "$PASSWD_FILE" ]; then errcho "sysadminctl: $PASSWD_FILE does not exist" exit 1 fi # Ensure the home directory's parent exists (but don't create the home # directory itself — Nix build users use /var/empty which should already # exist). HOME_PARENT=$(dirname "$HOME_DIR") if [ ! -d "$HOME_PARENT" ] && [ "$HOME_PARENT" != "/" ]; then mkdir -p "$HOME_PARENT" 2>/dev/null || true fi # Create the passwd entry # Format: name:password:UID:GID:GECOS:home:shell echo "${USERNAME}:x:${UID_VAL}:${GID_VAL}:${GECOS}:${HOME_DIR}:${SHELL}" >> "$PASSWD_FILE" echo "Created user '$USERNAME' (UID=$UID_VAL, GID=$GID_VAL, home=$HOME_DIR, shell=$SHELL)" exit 0fi# ── deleteUser ──────────────────────────────────────────────────────────────if [ "$ACTION" = "delete" ]; then # Consume any remaining flags (some callers pass -secure, etc.) while [ "$#" -gt 0 ]; do case "$1" in -secure|-keepHome|-adminUser|-adminPassword) # Ignored — these are macOS-specific options if [ "$1" = "-adminUser" ] || [ "$1" = "-adminPassword" ]; then shift 2 2>/dev/null || shift else shift fi ;; *) errcho "sysadminctl: unknown option '$1' (ignoring)" shift ;; esac done # Idempotent: if user doesn't exist, succeed silently if ! user_exists "$USERNAME"; then echo "User '$USERNAME' does not exist" exit 0 fi # Remove the user from /etc/passwd TMPFILE=$(mktemp "${PASSWD_FILE}.XXXXXX") grep -v "^${USERNAME}:" "$PASSWD_FILE" > "$TMPFILE" || true mv "$TMPFILE" "$PASSWD_FILE" echo "Deleted user '$USERNAME'" exit 0fi# Should not reach hereerrcho "sysadminctl: internal error — unknown action '$ACTION'"exit 1