create-user: passwordless sudo user (NOPASSWD, !tty_tickets, 7d cache) + retarget config; --create-user/--username flags
This commit is contained in:
parent
a9fff32255
commit
8a516f039c
155
modules/05-create-user.sh
Executable file
155
modules/05-create-user.sh
Executable file
@ -0,0 +1,155 @@
|
||||
#!/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.
|
||||
#
|
||||
# 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 )); then
|
||||
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"
|
||||
}
|
||||
20
setup.sh
20
setup.sh
@ -6,11 +6,18 @@
|
||||
# Flags:
|
||||
# --yes run all steps without prompting
|
||||
# --only NAME run only the step matching NAME
|
||||
# --skip NAME run all steps except NAME (repeatable in future)
|
||||
# --skip NAME run all steps except NAME
|
||||
# --create-user force creating a sudo user (otherwise Arch auto-creates, Debian asks)
|
||||
# --username NAME override the sudo user name (default: mk)
|
||||
# -h|--help usage
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Default config target = whoever is running this. The create-user step
|
||||
# (05-create-user) may override these to point at a freshly-created sudoer.
|
||||
export TARGET_USER="$USER"
|
||||
export TARGET_HOME="$HOME"
|
||||
|
||||
BOOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
@ -18,18 +25,25 @@ source "$BOOT_DIR/lib/detect.sh"
|
||||
source "$BOOT_DIR/lib/log.sh"
|
||||
source "$BOOT_DIR/lib/prompt.sh"
|
||||
|
||||
ALL=0; ONLY=""; SKIP=""
|
||||
# helper: resolve the target user's real home even when we're root operating
|
||||
# on a created user (whose home isn't $HOME).
|
||||
target_home() { getent passwd "${TARGET_USER:-$USER}" | cut -d: -f6; }
|
||||
|
||||
ALL=0; ONLY=""; SKIP=""; CREATE_USER=0; USERNAME_ARG=""
|
||||
while (( $# )); do
|
||||
case "$1" in
|
||||
--yes|-y) ALL=1; shift ;;
|
||||
--only) ONLY="${2:-}"; shift 2 ;;
|
||||
--skip) SKIP="${2:-}"; shift 2 ;;
|
||||
--create-user) CREATE_USER=1; shift ;;
|
||||
--username) USERNAME_ARG="${2:-}"; shift 2 ;;
|
||||
-h|--help)
|
||||
echo "usage: setup.sh [--yes] [--only NAME] [--skip NAME]"
|
||||
grep -E '^# --' "$0" | sed 's/^# //'
|
||||
exit 0 ;;
|
||||
*) echo "unknown arg: $1" >&2; shift ;;
|
||||
esac
|
||||
done
|
||||
export CREATE_USER USERNAME_ARG
|
||||
|
||||
# ---------------------------------------------------------------- collect steps
|
||||
NAMES=(); DESCS=(); RUNS=()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user