#!/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/, "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)"