Files
argento/scripts/restore-users.sh
T

139 lines
5.3 KiB
Bash
Executable File

#!/bin/bash
# Recreate human users + groups with stable UIDs/GIDs, and restore the
# samba passdb from USB backup. Run during recovery BEFORE the data rsync
# so file ownership lands on the right UIDs.
#
# UIDs/GIDs MUST match what the original system used, or ZFS files end up
# owned by orphan numeric IDs. The script aborts loudly if it finds an
# existing user/group with a different UID/GID than expected — investigate
# and either rename the conflicting entry or update the constants below.
#
# Verify on a live system with:
# id timothykim # check uid + primary gid
# getent group sambagroup # check sambagroup gid
set -euo pipefail
[ "$EUID" -eq 0 ] || { echo "must run as root" >&2; exit 1; }
SAMBAGROUP_GID=1001
# Format: username:uid:gid:shell:home_flag:supp_groups
# - gid: numeric gid for the user's primary group (group name = username)
# - home_flag: "home" creates /home/<user>, "nohome" skips it (samba-only)
# - supp_groups: comma-separated supplementary groups, or "-" for none
USERS=(
"timothykim:1000:1000:/bin/bash:home:wheel,video,docker,sambagroup"
"yireh:1100:1100:/sbin/nologin:nohome:sambagroup"
"isaac:1101:1101:/sbin/nologin:nohome:sambagroup"
)
# Abort if an existing entity's numeric ID doesn't match what we expect
assert_match() {
local label=$1 expected=$2 actual=$3
if [ "$expected" != "$actual" ]; then
echo "[FATAL] $label mismatch: expected $expected, got $actual" >&2
echo " ZFS file ownership will break if you proceed." >&2
echo " Either rename the conflicting entry or update this script." >&2
exit 1
fi
}
echo "=== sambagroup ==="
if getent group sambagroup &>/dev/null; then
actual=$(getent group sambagroup | cut -d: -f3)
assert_match "sambagroup gid" "$SAMBAGROUP_GID" "$actual"
echo "[ok] sambagroup exists (gid $actual)"
else
groupadd -g "$SAMBAGROUP_GID" sambagroup
echo "[+] created sambagroup (gid $SAMBAGROUP_GID)"
fi
# Preflight: every supplementary group referenced must already exist.
# wheel/video are OS-default; docker is created by docker-ce install (RUNBOOK
# step 2); sambagroup was just handled above. If anything's missing, abort
# before touching users — half-applied group memberships are annoying to undo.
echo ""
echo "=== preflight: supplementary groups ==="
missing=()
for entry in "${USERS[@]}"; do
supp=$(echo "$entry" | cut -d: -f6)
[ "$supp" = "-" ] && continue
IFS=, read -ra groups <<< "$supp"
for g in "${groups[@]}"; do
if ! getent group "$g" &>/dev/null; then
missing+=("$g")
fi
done
done
if [ ${#missing[@]} -gt 0 ]; then
# de-dupe for a cleaner error message
uniq_missing=$(printf '%s\n' "${missing[@]}" | sort -u | tr '\n' ' ')
echo "[FATAL] required supplementary groups missing: $uniq_missing" >&2
echo " likely cause: a package that creates the group hasn't been" >&2
echo " installed yet (e.g., docker-ce creates the docker group)." >&2
echo " complete RUNBOOK step 2 before running this script." >&2
exit 1
fi
echo "[ok] all supplementary groups present"
for entry in "${USERS[@]}"; do
IFS=: read -r u uid gid shell home_flag supp <<< "$entry"
echo ""
echo "=== $u ==="
# Primary group (same name as user, pinned GID — pre-created so useradd
# doesn't auto-assign one via USERGROUPS_ENAB)
if getent group "$u" &>/dev/null; then
actual=$(getent group "$u" | cut -d: -f3)
assert_match "$u primary group gid" "$gid" "$actual"
echo "[ok] group $u exists (gid $actual)"
else
groupadd -g "$gid" "$u"
echo "[+] created group $u (gid $gid)"
fi
# User account
if id "$u" &>/dev/null; then
actual_uid=$(id -u "$u")
actual_gid=$(id -g "$u")
assert_match "$u uid" "$uid" "$actual_uid"
assert_match "$u primary gid" "$gid" "$actual_gid"
echo "[ok] $u exists (uid $actual_uid, gid $actual_gid)"
else
if [ "$home_flag" = "home" ]; then
useradd -u "$uid" -g "$gid" -s "$shell" -m "$u"
else
useradd -u "$uid" -g "$gid" -s "$shell" -M "$u"
fi
echo "[+] created $u (uid $uid, gid $gid)"
fi
# Supplementary groups (idempotent — usermod -aG won't error on duplicates)
if [ "$supp" != "-" ]; then
usermod -aG "$supp" "$u"
echo "[+] $u in supplementary groups: $supp"
fi
done
echo ""
echo "=== samba passdb ==="
PASSDB_SRC=/mnt/backup/argento/system/samba-private
if ! mountpoint -q /mnt/backup; then
echo "[!] /mnt/backup is not mounted — skipping samba passdb restore"
echo " mount the USB backup drive (RUNBOOK step 5) and re-run if you want"
echo " to restore preserved SMB passwords; otherwise set them manually:"
echo " for u in timothykim yireh isaac; do smbpasswd -a \"\$u\"; done"
elif [ -f "$PASSDB_SRC/passdb.tdb" ]; then
install -d -m 700 /var/lib/samba/private
cp "$PASSDB_SRC"/*.tdb /var/lib/samba/private/
chmod 600 /var/lib/samba/private/*.tdb
echo "[+] restored samba passdb from $PASSDB_SRC/"
else
echo "[!] no samba passdb backup found at $PASSDB_SRC/"
echo " run: for u in timothykim yireh isaac; do smbpasswd -a \"\$u\"; done"
fi
echo ""
echo "=== Done ==="
echo "Next: set timothykim's Linux login password if not already (passwd timothykim)"