File: sequoia-nixbld-user-migration.sh

package info (click to toggle)
nix 2.26.3%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 12,524 kB
  • sloc: cpp: 87,540; sh: 8,864; perl: 649; yacc: 466; xml: 410; javascript: 378; lex: 329; ansic: 215; python: 128; sql: 56; makefile: 33; exp: 5; ruby: 1
file content (172 lines) | stat: -rwxr-xr-x 4,537 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
#!/usr/bin/env bash

set -eo pipefail

# stock path to avoid unexpected command versions
PATH="$(/usr/bin/getconf PATH)"

((NEW_NIX_FIRST_BUILD_UID=351))
((TEMP_NIX_FIRST_BUILD_UID=31000))

nix_user_n() {
	printf "_nixbld%d" "$1"
}

id_unavailable(){
	dscl . list /Users UniqueID | grep -E '\b'"$1"'\b' >/dev/null
}

any_nixbld(){
	dscl . list /Users UniqueID | grep -E '\b_nixbld' >/dev/null
}

dsclattr() {
	dscl . -read "$1" | awk "/$2/ { print \$2 }"
}

re_create_nixbld_user(){
	local name uid

	name="$1"
	uid="$2"
	gid="$3"

	sudo /usr/bin/dscl . -create "/Users/$name" "UniqueID" "$uid"
	sudo /usr/bin/dscl . -create "/Users/$name" "IsHidden" "1"
	sudo /usr/bin/dscl . -create "/Users/$name" "NFSHomeDirectory" "/var/empty"
	sudo /usr/bin/dscl . -create "/Users/$name" "RealName" "Nix build user $name"
	sudo /usr/bin/dscl . -create "/Users/$name" "UserShell" "/sbin/nologin"
	sudo /usr/bin/dscl . -create "/Users/$name" "PrimaryGroupID" "$gid"
}

hit_id_cap(){
	echo "We've hit UID 400 without placing all of your users :("
	echo "You should use the commands in this script as a starting"
	echo "point to review your UID-space and manually move the"
	echo "remaining users (or delete them, if you don't need them)."
}

# evacuate the role-uid space to simplify final placement logic
temporarily_move_existing_nixbld_uids(){
	local name uid next_id user_n

	((next_id=TEMP_NIX_FIRST_BUILD_UID))

	echo ""
	echo "Step 1: move existing _nixbld users out of the destination UID range."

	while read -r name uid; do
		# iterate for a clean ID
		while id_unavailable "$next_id"; do
			((next_id++))
			# We really want to get these all placed, but I guess there's
			# some risk we iterate forever--so we'll give up after 9k uids.
			if ((next_id >= 40000)); then
				echo "We've hit UID 40000 without temporarily placing all of your users :("
				echo "You should use the commands in this script as a starting"
				echo "point to review your UID-space and manually move the"
				echo "remaining users to any open UID over 1000."
				exit 1
			fi
		done
		sudo dscl . -create "/Users/$name" UniqueID "$next_id"
		echo "   Temporarily moved $name from uid $uid -> $next_id"

	done < <(dscl . list /Users UniqueID | grep _nixbld | sort -n -k2)
}

change_nixbld_uids(){
	local existing_gid name next_id user_n

	((next_id=NEW_NIX_FIRST_BUILD_UID))
	((user_n=1))
	name="$(nix_user_n "$user_n")"
	existing_gid="$(dsclattr "/Groups/nixbld" "PrimaryGroupID")"

	# we know that we have *some* nixbld users, but macOS may have
	# already clobbered the first few users if this system has been
	# upgraded

	echo ""
	echo "Step 2: re-create missing early _nixbld# users."

	until dscl . read "/Users/$name" &>/dev/null; do
		# iterate for a clean ID
		while id_unavailable "$next_id"; do
			((next_id++))
			if ((next_id >= 400)); then
				hit_id_cap
				exit 1
			fi
		done

		re_create_nixbld_user "$name" "$next_id" "$existing_gid"
		echo "      $name was missing; created with uid: $next_id"

		((user_n++))
		name="$(nix_user_n "$user_n")"
	done

	echo ""
	echo "Step 3: relocate remaining _nixbld# UIDs to $next_id+"

	# start at first _nixbld# not re-created above and increment
	# until _nixbld<n> doesn't exist
	while dscl . read "/Users/$name" &>/dev/null; do
		# iterate for a clean ID
		while id_unavailable "$next_id"; do
			((next_id++))
			if ((next_id >= 400)); then
				hit_id_cap
				exit 1
			fi
		done

		sudo dscl . -create "/Users/$name" UniqueID "$next_id"
		echo "      $name migrated to uid: $next_id"

		((user_n++))
		name="$(nix_user_n "$user_n")"
	done

	if ((user_n == 1)); then
		echo "Didn't find _nixbld1. Perhaps you have single-user Nix?"
		exit 1
	else
		echo "Migrated $((user_n - 1)) users. If you want to double-check, try:"
		echo "dscl . list /Users UniqueID | grep _nixbld | sort -n -k2"
	fi
}
needs_migration(){
	local name uid next_id user_n

	((next_id=NEW_NIX_FIRST_BUILD_UID))
	((user_n=1))

	while read -r name uid; do
		expected_name="$(nix_user_n "$user_n")"
		if [[ "$expected_name" != "$name" ]]; then
			return 0
		fi
		if [[ "$next_id" != "$uid" ]]; then
			return 0
		fi
		((next_id++))
		((user_n++))
	done < <(dscl . list /Users UniqueID | grep _nixbld | sort -n -k2)
	return 1
}


if any_nixbld; then
	if needs_migration; then
		echo "Attempting to migrate _nixbld users."
		temporarily_move_existing_nixbld_uids
		change_nixbld_uids
	else
		echo "_nixbld users already appear to be migrated."
	fi
else
	echo "Didn't find any _nixbld users. Perhaps you have single-user Nix?"
	exit 1
fi