this repo has no description
at master 271 lines 8.8 kB view raw
1#!/bin/sh 2# sysadminctl — macOS user management stub for Darling 3# 4# Translates macOS sysadminctl commands to direct /etc/passwd file operations. 5# Only implements the subset needed by the Nix installer: 6# 7# sysadminctl -addUser <name> [-UID <uid>] [-GID <gid>] 8# [-home <dir>] [-shell <shell>] [-fullName <name>] 9# sysadminctl -deleteUser <name> 10# 11# This stub operates on the Darling prefix's /etc/passwd file, NOT the host's. 12# 13# See: plan/07-phase5-daemon.md (Task 5.1) 14 15set -eu 16 17PASSWD_FILE="/etc/passwd" 18 19# ── Helpers ───────────────────────────────────────────────────────────────── 20 21errcho() { 22 echo "$@" >&2 23} 24 25usage() { 26 errcho "Usage: sysadminctl -addUser <username> [-UID <uid>] [-GID <gid>]" 27 errcho " [-home <homedir>] [-shell <shell>] [-fullName <gecos>]" 28 errcho " sysadminctl -deleteUser <username>" 29 errcho "" 30 errcho "Options:" 31 errcho " -addUser <name> Create a new user" 32 errcho " -deleteUser <name> Delete an existing user" 33 errcho " -UID <uid> User ID (numeric)" 34 errcho " -GID <gid> Primary group ID (numeric)" 35 errcho " -home <dir> Home directory (default: /var/empty)" 36 errcho " -shell <shell> Login shell (default: /usr/bin/false)" 37 errcho " -fullName <gecos> Full name / GECOS field" 38 errcho " -password <pass> Password (ignored — always set to locked)" 39 errcho " -adminUser <user> Admin user for authentication (ignored)" 40 errcho " -adminPassword <pw> Admin password (ignored)" 41 exit 64 42} 43 44# user_exists <name> — exit 0 if the user exists in /etc/passwd 45user_exists() { 46 grep -q "^${1}:" "$PASSWD_FILE" 2>/dev/null 47} 48 49# uid_exists <uid> — exit 0 if the UID is already taken 50uid_exists() { 51 awk -F: -v uid="$1" '$3 == uid { found=1; exit } END { exit !found }' "$PASSWD_FILE" 2>/dev/null 52} 53 54# get_uid_owner <uid> — print the username that owns this UID 55get_uid_owner() { 56 awk -F: -v uid="$1" '$3 == uid { print $1; exit }' "$PASSWD_FILE" 57} 58 59# next_uid — find the next available UID >= 300 (Nix build user range) 60next_uid() { 61 awk -F: '{ if ($3 >= 300 && $3 > max) max = $3 } END { print (max ? max + 1 : 300) }' "$PASSWD_FILE" 62} 63 64# ── Argument parsing ──────────────────────────────────────────────────────── 65 66if [ "$#" -lt 2 ]; then 67 usage 68fi 69 70ACTION="" 71USERNAME="" 72 73case "$1" in 74 -addUser) 75 ACTION="add" 76 USERNAME="$2" 77 shift 2 78 ;; 79 -deleteUser) 80 ACTION="delete" 81 USERNAME="$2" 82 shift 2 83 ;; 84 -h|--help|-help) 85 usage 86 ;; 87 *) 88 errcho "sysadminctl: unknown option '$1'" 89 usage 90 ;; 91esac 92 93# Validate username (alphanumeric, underscore, dash, dot; max 256 chars) 94case "$USERNAME" in 95 "") 96 errcho "sysadminctl: username must not be empty" 97 exit 1 98 ;; 99 *[!a-zA-Z0-9_.-]*) 100 errcho "sysadminctl: invalid username '$USERNAME' (allowed: a-z A-Z 0-9 _ . -)" 101 exit 1 102 ;; 103esac 104 105if [ "${#USERNAME}" -gt 256 ]; then 106 errcho "sysadminctl: username too long (max 256 characters)" 107 exit 1 108fi 109 110# ── addUser ───────────────────────────────────────────────────────────────── 111 112if [ "$ACTION" = "add" ]; then 113 UID_VAL="" 114 GID_VAL="" 115 HOME_DIR="/var/empty" 116 SHELL="/usr/bin/false" 117 GECOS="" 118 119 while [ "$#" -gt 0 ]; do 120 case "$1" in 121 -UID) 122 [ "$#" -ge 2 ] || { errcho "sysadminctl: -UID requires an argument"; exit 1; } 123 UID_VAL="$2" 124 shift 2 125 ;; 126 -GID) 127 [ "$#" -ge 2 ] || { errcho "sysadminctl: -GID requires an argument"; exit 1; } 128 GID_VAL="$2" 129 shift 2 130 ;; 131 -home) 132 [ "$#" -ge 2 ] || { errcho "sysadminctl: -home requires an argument"; exit 1; } 133 HOME_DIR="$2" 134 shift 2 135 ;; 136 -shell) 137 [ "$#" -ge 2 ] || { errcho "sysadminctl: -shell requires an argument"; exit 1; } 138 SHELL="$2" 139 shift 2 140 ;; 141 -fullName) 142 [ "$#" -ge 2 ] || { errcho "sysadminctl: -fullName requires an argument"; exit 1; } 143 GECOS="$2" 144 shift 2 145 ;; 146 -password) 147 # Ignored — we always set the password field to 'x' (locked) 148 [ "$#" -ge 2 ] || { errcho "sysadminctl: -password requires an argument"; exit 1; } 149 shift 2 150 ;; 151 -adminUser|-admin) 152 # Ignored — no authentication needed in a Darling prefix 153 [ "$#" -ge 2 ] || { errcho "sysadminctl: $1 requires an argument"; exit 1; } 154 shift 2 155 ;; 156 -adminPassword) 157 # Ignored — no authentication needed in a Darling prefix 158 [ "$#" -ge 2 ] || { errcho "sysadminctl: -adminPassword requires an argument"; exit 1; } 159 shift 2 160 ;; 161 -roleAccount) 162 # Boolean flag used by newer Nix installers — no argument 163 shift 164 ;; 165 *) 166 errcho "sysadminctl: unknown option '$1'" 167 usage 168 ;; 169 esac 170 done 171 172 # Idempotent: if user already exists, succeed silently 173 if user_exists "$USERNAME"; then 174 echo "User '$USERNAME' already exists" 175 exit 0 176 fi 177 178 # Assign UID if not specified 179 if [ -z "$UID_VAL" ]; then 180 UID_VAL=$(next_uid) 181 fi 182 183 # Validate UID is numeric 184 case "$UID_VAL" in 185 *[!0-9]*) 186 errcho "sysadminctl: UID must be numeric, got '$UID_VAL'" 187 exit 1 188 ;; 189 esac 190 191 # Check for UID conflicts 192 if uid_exists "$UID_VAL"; then 193 existing=$(get_uid_owner "$UID_VAL") 194 errcho "sysadminctl: UID $UID_VAL already in use by user '$existing'" 195 exit 1 196 fi 197 198 # Default GID to 0 (wheel/root) if not specified 199 if [ -z "$GID_VAL" ]; then 200 GID_VAL=0 201 fi 202 203 # Validate GID is numeric 204 case "$GID_VAL" in 205 *[!0-9]*) 206 errcho "sysadminctl: GID must be numeric, got '$GID_VAL'" 207 exit 1 208 ;; 209 esac 210 211 # Ensure /etc/passwd exists 212 if [ ! -f "$PASSWD_FILE" ]; then 213 errcho "sysadminctl: $PASSWD_FILE does not exist" 214 exit 1 215 fi 216 217 # Ensure the home directory's parent exists (but don't create the home 218 # directory itself — Nix build users use /var/empty which should already 219 # exist). 220 HOME_PARENT=$(dirname "$HOME_DIR") 221 if [ ! -d "$HOME_PARENT" ] && [ "$HOME_PARENT" != "/" ]; then 222 mkdir -p "$HOME_PARENT" 2>/dev/null || true 223 fi 224 225 # Create the passwd entry 226 # Format: name:password:UID:GID:GECOS:home:shell 227 echo "${USERNAME}:x:${UID_VAL}:${GID_VAL}:${GECOS}:${HOME_DIR}:${SHELL}" >> "$PASSWD_FILE" 228 229 echo "Created user '$USERNAME' (UID=$UID_VAL, GID=$GID_VAL, home=$HOME_DIR, shell=$SHELL)" 230 exit 0 231fi 232 233# ── deleteUser ────────────────────────────────────────────────────────────── 234 235if [ "$ACTION" = "delete" ]; then 236 # Consume any remaining flags (some callers pass -secure, etc.) 237 while [ "$#" -gt 0 ]; do 238 case "$1" in 239 -secure|-keepHome|-adminUser|-adminPassword) 240 # Ignored — these are macOS-specific options 241 if [ "$1" = "-adminUser" ] || [ "$1" = "-adminPassword" ]; then 242 shift 2 2>/dev/null || shift 243 else 244 shift 245 fi 246 ;; 247 *) 248 errcho "sysadminctl: unknown option '$1' (ignoring)" 249 shift 250 ;; 251 esac 252 done 253 254 # Idempotent: if user doesn't exist, succeed silently 255 if ! user_exists "$USERNAME"; then 256 echo "User '$USERNAME' does not exist" 257 exit 0 258 fi 259 260 # Remove the user from /etc/passwd 261 TMPFILE=$(mktemp "${PASSWD_FILE}.XXXXXX") 262 grep -v "^${USERNAME}:" "$PASSWD_FILE" > "$TMPFILE" || true 263 mv "$TMPFILE" "$PASSWD_FILE" 264 265 echo "Deleted user '$USERNAME'" 266 exit 0 267fi 268 269# Should not reach here 270errcho "sysadminctl: internal error — unknown action '$ACTION'" 271exit 1