main/modules/05-create-user.sh

157 lines
6.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# 05-create-user — create a daily-driver sudo user when running as direct root.
#
# Why this exists: `makepkg` (yay / AUR) refuses to run as root. On a fresh VPS
# that lands you in as root with no SUDO_USER, we create a non-root sudoer,
# copy root's authorized_keys to it, configure passwordless sudo with
# !tty_tickets + a long timestamp_timeout, and retarget the config steps
# (zsh, nvim, nvm, docker group) at that user's $HOME.
#
# Behaviour:
# - Not root -> skip (you're already a normal user).
# - Root + SUDO_USER set -> skip (you sudoed in; that user is the target).
# - Root + no SUDO_USER + Arch -> auto-create (AUR needs it), confirm name.
# - Root + no SUDO_USER + Debian/Ubuntu -> ask y/N (optional, apt works as root).
# - --create-user flag -> force prompt regardless.
# - --username NAME -> override default name, no prompt.
# - --yes (no --username) -> default to 'mk' silently (no stall).
#
# Passwordless, no user password (NOPASSWD). Idempotent: re-runs re-sync
# authorized_keys + sudoers rather than failing.
step_name="create-user"
step_desc="create a sudo user (passwordless) + copy root's ssh keys + retarget config"
step_run="run_create_user"
run_create_user() {
log "create-user step"
# not root -> nothing to do, target stays as current user
if (( EUID != 0 )); then
echo " not root — target stays $USER ($HOME)"
export TARGET_USER="$USER" TARGET_HOME="$HOME"
return
fi
# root but came in via sudo -> use that invoking user, don't create a new one
if [[ -z "${SUDO_USER:-}" ]]; then _handle_direct_root; else _handle_sudoed_root; fi
}
# ── direct root (logged in as root; the case that actually needs a new user) ─
_handle_direct_root() {
local should_create=0
if (( CREATE_USER )) || [[ -n "${USERNAME_ARG:-}" ]]; then
[[ -n "${USERNAME_ARG:-}" ]] && log "--username given: forcing user creation"
should_create=1
elif [[ "$DISTRO" == "arch" ]]; then
# AUR/makepkg cannot run as root — we genuinely need a non-root sudoer.
echo " running as direct root on Arch: a non-root sudoer is needed for yay/AUR."
yn "Create a sudo user now? (recommended)" y && should_create=1
else
# Debian/Ubuntu: apt works as root, so this is convenience (key-first login).
yn "Create a daily-driver sudo user? (optional — apt works as root)" n && should_create=1
fi
if (( ! should_create )); then
echo " skipping user creation — config will target root ($HOME)"
warn "on Arch you won't be able to build AUR packages (yay/angie) as root"
export TARGET_USER="root" TARGET_HOME="$HOME"
return
fi
local user="${USERNAME_ARG:-}"
if [[ -z "$user" ]]; then
local default="mk"
read -rp " username [$default]: " user
[[ -z "$user" ]] && user="$default"
fi
_create_or_sync_user "$user"
}
# ── root via sudo: use the invoking user, don't create another ───────────────
_handle_sudoed_root() {
local user="$SUDO_USER"
local home; home="$(getent passwd "$user" | cut -d: -f6)"
log "running as root via sudo — using invoking user '$user' as target"
# ensure they have passwordless sudo + the ssh key + sudo group, then retarget
_create_or_sync_user "$user" skip_create
export TARGET_USER="$user" TARGET_HOME="$home"
}
# ── the meat: create if missing, sync sudoers, sync authorized_keys ──────────
_create_or_sync_user() { # username [skip_create]
local user="$1"
local skip_create="${2:-}"
if [[ "$skip_create" != "skip_create" ]]; then
if id "$user" >/dev/null 2>&1; then
ok "user '$user' already exists — syncing config only"
else
local shell_bin; shell_bin="$(command -v zsh 2>/dev/null || echo /bin/bash)"
local groups
case "$DISTRO" in
arch) groups="wheel" ;;
debian) groups="sudo,adm" ;;
esac
log "creating user '$user' (shell=$shell_bin, groups=$groups)"
useradd -m -s "$shell_bin" -G "$groups" "$user"
# passwordless account: clear the password field entirely
passwd -d "$user" >/dev/null 2>&1 || true
# unlock the account so SSH key login still works after passwd -d
usermod -p "" "$user" 2>/dev/null || true
ok "user '$user' created (no password set; ssh-key login only)"
fi
fi
local home; home="$(getent passwd "$user" | cut -d: -f6)"
_sync_authorized_keys "$user" "$home"
_install_sudoers "$user"
export TARGET_USER="$user" TARGET_HOME="$home"
ok "config target retargeted: user=$user home=$home"
}
# ── copy root's authorized_keys to the target user (idempotent) ──────────────
_sync_authorized_keys() { # user home
local user="$1" home="$2"
local src="/root/.ssh/authorized_keys"
if [[ ! -f "$src" ]]; then
warn "no /root/.ssh/authorized_keys — add your key manually:"
echo " install -d -m 700 -o $user -g $user $home/.ssh"
echo " # then: paste your pubkey into $home/.ssh/authorized_keys (chmod 600)"
return
fi
install -d -m 700 -o "$user" -g "$user" "$home/.ssh"
install -m 600 -o "$user" -g "$user" "$src" "$home/.ssh/authorized_keys"
local n; n=$(grep -cE '^[^#]' "$src" 2>/dev/null || echo 0)
ok "copied $n authorized key(s) to $home/.ssh/authorized_keys"
warn "sanity-check these are YOUR keys (fingerprints):"
ssh-keygen -lf "$src" 2>/dev/null | sed 's/^/ /' || true
}
# ── write /etc/sudoers.d/<user>: NOPASSWD, !tty_tickets, 7-day cache ─────────
_install_sudoers() { # user
local user="$1" file="/etc/sudoers.d/${user}"
log "configuring sudoers for '$user' (passwordless, !tty_tickets, 7d timeout)"
# write to a temp, validate with visudo -c, then move into place atomically
local tmp; tmp="$(mktemp)"
cat > "$tmp" <<EOF
# managed by bootstrap — passwordless sudo for $user
Defaults:$user !tty_tickets
Defaults:$user timestamp_timeout=10080
$user ALL=(ALL) NOPASSWD:ALL
EOF
# visudo must see proper perms (0440) to be happy
chmod 0440 "$tmp"
if visudo -cf "$tmp" >/dev/null 2>&1; then
install -m 0440 -o root -g root "$tmp" "$file"
ok "sudoers installed: $file"
else
err "visudo -c rejected the sudoers snippet — NOT installed, inspect aborted"
cat "$tmp" | sed 's/^/ /'
fi
rm -f "$tmp"
}