Skip to content

Commit

Permalink
add script to migrate macOS 15 Sequoia nixbld UIDs
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
abathur committed Aug 2, 2024
1 parent caabdb0 commit 8b037a8
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 19 deletions.
4 changes: 2 additions & 2 deletions scripts/bigsur-nixbld-user-migration.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

((NEW_NIX_FIRST_BUILD_UID=301))

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

Expand All @@ -15,7 +15,7 @@ change_nixbld_names_and_ids(){
while read -r name uid; do
echo " Checking $name (uid: $uid)"
# iterate for a clean ID
while id_available "$next_id"; do
while id_unavailable "$next_id"; do
((next_id++))
if ((next_id >= 400)); then
echo "We've hit UID 400 without placing all of your users :("
Expand Down
137 changes: 120 additions & 17 deletions scripts/sequoia-nixbld-user-migration.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,36 +1,139 @@
#!/usr/bin/env bash

set -x

((NEW_NIX_FIRST_BUILD_UID=331))
((TEMP_NIX_FIRST_BUILD_UID=31000))

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

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

change_nixbld_names_and_ids(){
local name uid next_id
((next_id=NEW_NIX_FIRST_BUILD_UID))
echo "Attempting to migrate _nixbld users."
echo "Each _nixbld# user should have its UID moved to $next_id+"
any_nixbld(){
dscl . list /Users UniqueID | grep -E '\b_nixbld' >/dev/null
}

re_create_nixbld_user(){
local name uid

name="$1"
uid="$2"

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" "30001"
}

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
echo " Checking $name (uid: $uid)"
# iterate for a clean ID
while id_available "$next_id"; do
while id_unavailable "$next_id"; do
((next_id++))
if ((next_id >= 400)); then
echo "We've hit UID 400 without placing all of your users :("
# 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 (or delete them, if you don't need them)."
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"

# first 2 are cleanup, it's OK if they aren't here
sudo dscl . delete "/Users/$name" dsAttrTypeNative:_writers_passwd &>/dev/null || true
sudo dscl . change "/Users/$name" NFSHomeDirectory "/private/var/empty 1" "/var/empty" &>/dev/null || true
sudo dscl . change "/Users/$name" UniqueID "$uid" "$next_id"
echo " $name migrated to uid: $next_id"
done < <(dscl . list /Users UniqueID | grep _nixbld | sort -n -k2)
}

change_nixbld_names_and_ids
change_nixbld_uids(){
local name next_id user_n

((next_id=NEW_NIX_FIRST_BUILD_UID))
((user_n=1))
name="$(nix_user_n "$user_n")"

# 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"
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
}

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

0 comments on commit 8b037a8

Please sign in to comment.