Skip to content

Commit 91027b0

Browse files
committed
add script to migrate macOS 15 Sequoia nixbld UIDs
While we don't have any easy way to forcibly notify everyone about the impending breakage (or forcibly migrate the users on their system), this script enables those who do hear about the problem to migrate their systems before they take the macOS update. It should also enable people who only discover it after the update when a build fails to ~fix their installs without a full reinstall.
1 parent caabdb0 commit 91027b0

File tree

2 files changed

+145
-18
lines changed

2 files changed

+145
-18
lines changed

scripts/bigsur-nixbld-user-migration.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
((NEW_NIX_FIRST_BUILD_UID=301))
44

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

@@ -15,7 +15,7 @@ change_nixbld_names_and_ids(){
1515
while read -r name uid; do
1616
echo " Checking $name (uid: $uid)"
1717
# iterate for a clean ID
18-
while id_available "$next_id"; do
18+
while id_unavailable "$next_id"; do
1919
((next_id++))
2020
if ((next_id >= 400)); then
2121
echo "We've hit UID 400 without placing all of your users :("

scripts/sequoia-nixbld-user-migration.sh

100644100755
Lines changed: 143 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,163 @@
11
#!/usr/bin/env bash
22

3+
set -x
4+
35
((NEW_NIX_FIRST_BUILD_UID=331))
6+
((TEMP_NIX_FIRST_BUILD_UID=31000))
7+
8+
nix_user_n() {
9+
printf "_nixbld%d" "$1"
10+
}
411

5-
id_available(){
12+
id_unavailable(){
613
dscl . list /Users UniqueID | grep -E '\b'"$1"'\b' >/dev/null
714
}
815

9-
change_nixbld_names_and_ids(){
10-
local name uid next_id
11-
((next_id=NEW_NIX_FIRST_BUILD_UID))
12-
echo "Attempting to migrate _nixbld users."
13-
echo "Each _nixbld# user should have its UID moved to $next_id+"
16+
any_nixbld(){
17+
dscl . list /Users UniqueID | grep -E '\b_nixbld' >/dev/null
18+
}
19+
20+
re_create_nixbld_user(){
21+
local name uid
22+
23+
name="$1"
24+
uid="$2"
25+
26+
sudo /usr/bin/dscl . -create "/Users/$name" "UniqueID" "$uid"
27+
sudo /usr/bin/dscl . -create "/Users/$name" "IsHidden" "1"
28+
sudo /usr/bin/dscl . -create "/Users/$name" "NFSHomeDirectory" "/var/empty"
29+
sudo /usr/bin/dscl . -create "/Users/$name" "RealName" "Nix build user $name"
30+
sudo /usr/bin/dscl . -create "/Users/$name" "UserShell" "/sbin/nologin"
31+
sudo /usr/bin/dscl . -create "/Users/$name" "PrimaryGroupID" "30001"
32+
}
33+
34+
hit_id_cap(){
35+
echo "We've hit UID 400 without placing all of your users :("
36+
echo "You should use the commands in this script as a starting"
37+
echo "point to review your UID-space and manually move the"
38+
echo "remaining users (or delete them, if you don't need them)."
39+
}
40+
41+
# evacuate the role-uid space to simplify final placement logic
42+
temporarily_move_existing_nixbld_uids(){
43+
local name uid next_id user_n
44+
45+
((next_id=TEMP_NIX_FIRST_BUILD_UID))
46+
47+
echo ""
48+
echo "Step 1: move existing _nixbld users out of the destination UID range."
49+
1450
while read -r name uid; do
15-
echo " Checking $name (uid: $uid)"
1651
# iterate for a clean ID
17-
while id_available "$next_id"; do
52+
while id_unavailable "$next_id"; do
1853
((next_id++))
19-
if ((next_id >= 400)); then
20-
echo "We've hit UID 400 without placing all of your users :("
54+
# We really want to get these all placed, but I guess there's
55+
# some risk we iterate forever--so we'll give up after 9k uids.
56+
if ((next_id >= 40000)); then
57+
echo "We've hit UID 40000 without temporarily placing all of your users :("
2158
echo "You should use the commands in this script as a starting"
2259
echo "point to review your UID-space and manually move the"
23-
echo "remaining users (or delete them, if you don't need them)."
60+
echo "remaining users to any open UID over 1000."
2461
exit 1
2562
fi
2663
done
64+
sudo dscl . -create "/Users/$name" UniqueID "$next_id"
65+
echo " Temporarily moved $name from uid $uid -> $next_id"
66+
67+
done < <(dscl . list /Users UniqueID | grep _nixbld | sort -n -k2)
68+
}
2769

28-
# first 2 are cleanup, it's OK if they aren't here
29-
sudo dscl . delete "/Users/$name" dsAttrTypeNative:_writers_passwd &>/dev/null || true
30-
sudo dscl . change "/Users/$name" NFSHomeDirectory "/private/var/empty 1" "/var/empty" &>/dev/null || true
31-
sudo dscl . change "/Users/$name" UniqueID "$uid" "$next_id"
70+
change_nixbld_uids(){
71+
local name next_id user_n
72+
73+
((next_id=NEW_NIX_FIRST_BUILD_UID))
74+
((user_n=1))
75+
name="$(nix_user_n "$user_n")"
76+
77+
# we know that we have *some* nixbld users, but macOS may have
78+
# already clobbered the first few users if this system has been
79+
# upgraded
80+
81+
echo ""
82+
echo "Step 2: re-create missing early _nixbld# users."
83+
84+
until dscl . read "/Users/$name" &>/dev/null; do
85+
# iterate for a clean ID
86+
while id_unavailable "$next_id"; do
87+
((next_id++))
88+
if ((next_id >= 400)); then
89+
hit_id_cap
90+
exit 1
91+
fi
92+
done
93+
94+
re_create_nixbld_user "$name" "$next_id"
95+
echo " $name was missing; created with uid: $next_id"
96+
97+
((user_n++))
98+
name="$(nix_user_n "$user_n")"
99+
done
100+
101+
echo ""
102+
echo "Step 3: relocate remaining _nixbld# UIDs to $next_id+"
103+
104+
# start at first _nixbld# not re-created above and increment
105+
# until _nixbld<n> doesn't exist
106+
while dscl . read "/Users/$name" &>/dev/null; do
107+
# iterate for a clean ID
108+
while id_unavailable "$next_id"; do
109+
((next_id++))
110+
if ((next_id >= 400)); then
111+
hit_id_cap
112+
exit 1
113+
fi
114+
done
115+
116+
sudo dscl . -create "/Users/$name" UniqueID "$next_id"
32117
echo " $name migrated to uid: $next_id"
118+
119+
((user_n++))
120+
name="$(nix_user_n "$user_n")"
121+
done
122+
123+
if ((user_n == 1)); then
124+
echo "Didn't find _nixbld1. Perhaps you have single-user Nix?"
125+
exit 1
126+
else
127+
echo "Migrated $((user_n - 1)) users. If you want to double-check, try:"
128+
echo "dscl . list /Users UniqueID | grep _nixbld | sort -n -k2"
129+
fi
130+
}
131+
needs_migration(){
132+
local name uid next_id user_n
133+
134+
((next_id=NEW_NIX_FIRST_BUILD_UID))
135+
((user_n=1))
136+
137+
while read -r name uid; do
138+
expected_name="$(nix_user_n "$user_n")"
139+
if [[ "$expected_name" != "$name" ]]; then
140+
return 0
141+
fi
142+
if [[ "$next_id" != "$uid" ]]; then
143+
return 0
144+
fi
145+
((next_id++))
146+
((user_n++))
33147
done < <(dscl . list /Users UniqueID | grep _nixbld | sort -n -k2)
148+
return 1
34149
}
35150

36-
change_nixbld_names_and_ids
151+
152+
if any_nixbld; then
153+
if needs_migration; then
154+
echo "Attempting to migrate _nixbld users."
155+
temporarily_move_existing_nixbld_uids
156+
change_nixbld_uids
157+
else
158+
echo "_nixbld users already appear to be migrated."
159+
fi
160+
else
161+
echo "Didn't find any _nixbld users. Perhaps you have single-user Nix?"
162+
exit 1
163+
fi

0 commit comments

Comments
 (0)