diff --git a/scripts/setup/machine-setup/lib/base.sh b/scripts/setup/machine-setup/lib/base.sh index 79c26773..ba76069f 100644 --- a/scripts/setup/machine-setup/lib/base.sh +++ b/scripts/setup/machine-setup/lib/base.sh @@ -285,9 +285,18 @@ as_user() { # places it with its mode in a single install(1) — so what lands in /etc is # already known good and already 0440. grant_passwordless_sudo() { - local user="$1" dest="/etc/sudoers.d/99-${user}-nopasswd" tmp + # Declared separately, deliberately. In `local a="$1" b="${a}"` bash expands + # $a before it has been assigned, so b comes out with the name missing — which + # here meant every account's rule landing in the same /etc/sudoers.d/99--nopasswd, + # each one silently overwriting the last, and has_passwordless_sudo never + # finding the file it was looking for. + local user="$1" + local dest="/etc/sudoers.d/99-${user}-nopasswd" + local tmp tmp="$(mktemp)" + [[ -n "$user" ]] || fail "grant_passwordless_sudo needs a username" + echo "${user} ALL=(ALL) NOPASSWD: ALL" >"$tmp" if ! visudo -c -f "$tmp" >/dev/null 2>&1; then diff --git a/scripts/setup/machine-setup/machine-setup.sh b/scripts/setup/machine-setup/machine-setup.sh index 89e2a362..36773366 100755 --- a/scripts/setup/machine-setup/machine-setup.sh +++ b/scripts/setup/machine-setup/machine-setup.sh @@ -96,7 +96,92 @@ info "Refreshing the package index..." pkg_refresh >/dev/null # ============================================================================= -# 2. Disk space +# 2. User account +# ============================================================================= + +# First of the acting sections, and before anything writes into a home. +# +# If the account does not exist yet, USER_HOME is a path that is not there — +# and the ballast's mkdir -p, running as root, would create it root-owned. adduser +# then finds the directory already present and does not populate or chown it. So +# the account is made before any step can put a file in its home. +step "User account" +if ! skip; then + echo "" + info "User account — the account you will actually log in and work as" + + if id "$USERNAME" &>/dev/null; then + echo " ${USERNAME} already exists" + USER_EXISTED=true + else + echo " ${USERNAME} does not exist yet and will be created" + echo " adduser will ask for a password and a few details" + USER_EXISTED=false + fi + + IN_SUDO=false + id -nG "$USERNAME" 2>/dev/null | tr ' ' '\n' | grep -qx sudo && IN_SUDO=true + echo " sudo group: $($IN_SUDO && echo 'already a member' || echo 'will be added')" + echo " passwordless: $(has_passwordless_sudo "$USERNAME" && echo 'already granted' || echo 'not granted')" + + if $USER_EXISTED && $IN_SUDO; then + SUMMARY+=("User: ${USERNAME} (already set up)") + elif confirm "Proceed?"; then + if ! $USER_EXISTED; then + adduser --gecos "" "$USERNAME" + SUMMARY+=("User: ${USERNAME} created") + + # Re-read it now the account is real. Until this point USER_HOME was the + # /home/ guess, since there was nothing to look up; adduser is free + # to have used something else, and every step after this writes there. + USER_HOME="$(getent passwd "$USERNAME" | cut -d: -f6)" + ok "home is ${USER_HOME}" + fi + $IN_SUDO || usermod -aG sudo "$USERNAME" + ok "${USERNAME} is in the sudo group" + $USER_EXISTED && SUMMARY+=("User: ${USERNAME} added to sudo") + else + warn "skipped by request" + SUMMARY+=("User: SKIPPED by request") + fi + + # ── passwordless sudo ── + # + # Asked separately because it is a security posture rather than part of + # creating an account, and because Officer has an actual requirement here: + # os-user-shell.ts runs `sudo -n` to provision a member's home, and a prompt it + # cannot answer is a failure it reports as a permissions error. + if has_passwordless_sudo "$USERNAME"; then + echo "" + echo " passwordless sudo is already granted to ${USERNAME}" + SUMMARY+=("Sudo: passwordless (already)") + else + echo "" + info "Passwordless sudo for ${USERNAME}?" + echo " Means sudo never asks for a password again. Convenient, and the" + echo " cost is real: anything that gets hold of this account, or of a key" + echo " that can log into it, is root without another step." + if is_role vps; then + echo " Worth weighing on a ${MACHINE_ROLE}, which faces the open internet." + fi + echo "" + echo " Officer needs it: it runs 'sudo -n' to provision a member's Linux" + echo " account, and a password prompt it cannot answer surfaces as a" + echo " permissions error rather than a question." + if confirm "Grant it?"; then + grant_passwordless_sudo "$USERNAME" + ok "passwordless sudo granted — remove /etc/sudoers.d/99-${USERNAME}-nopasswd to undo" + SUMMARY+=("Sudo: passwordless") + else + warn "skipped by request" + SUMMARY+=("Sudo: password required (Officer's account provisioning will not work)") + fi + fi + step_ok +fi + +# ============================================================================= +# 3. Disk space # ============================================================================= # # First of the sections that change anything, because everything after it sizes @@ -191,7 +276,7 @@ if ! skip; then fi # ============================================================================= -# 3. System update +# 4. System update # ============================================================================= # # Its own section because it is the only thing in the script that moves versions @@ -231,7 +316,7 @@ if ! skip; then fi # ============================================================================= -# 4. Core utils +# 5. Core utils # ============================================================================= # # What the distribution provides: the six this script would break without, and @@ -246,7 +331,7 @@ if ! skip; then fi # ============================================================================= -# 5. Command-line tools +# 6. Command-line tools # ============================================================================= # # A different thing from core utils, and kept apart from them: upstream binaries @@ -264,7 +349,7 @@ fi # ============================================================================= -# 6. Locale +# 7. Locale # ============================================================================= # # LOCALE in the environment overrides the default. @@ -302,7 +387,7 @@ if ! skip; then fi # ============================================================================= -# 7. Timezone +# 8. Timezone # ============================================================================= # # TIMEZONE in the environment answers the prompt ahead of time. @@ -368,7 +453,7 @@ if ! skip; then fi # ============================================================================= -# 8. Swap +# 9. Swap # ============================================================================= # # Disk the kernel can park cold pages on when RAM fills, so a spike costs @@ -422,7 +507,7 @@ if ! skip; then fi # ============================================================================= -# 9. Emergency disk ballast +# 10. Emergency disk ballast # ============================================================================= # # Always offered, whatever the role — the role only decides which way the @@ -546,7 +631,7 @@ elif ! skip; then fi # ============================================================================= -# 10. earlyoom +# 11. earlyoom # ============================================================================= step "earlyoom" @@ -581,7 +666,7 @@ if ! skip; then fi # ============================================================================= -# 11. inotify watch limit +# 12. inotify watch limit # ============================================================================= step "inotify watch limit" @@ -620,7 +705,7 @@ if ! skip; then fi # ============================================================================= -# 12. Sleep and suspend +# 13. Sleep and suspend # ============================================================================= step "Sleep and suspend" @@ -688,7 +773,7 @@ elif ! skip; then fi # ============================================================================= -# 13. Boot hang +# 14. Boot hang # ============================================================================= step "Boot hang" @@ -743,79 +828,6 @@ elif ! skip; then step_ok fi -# ============================================================================= -# 14. User account -# ============================================================================= - -step "User account" -if ! skip; then - echo "" - info "User account — the account you will actually log in and work as" - - if id "$USERNAME" &>/dev/null; then - echo " ${USERNAME} already exists" - USER_EXISTED=true - else - echo " ${USERNAME} does not exist yet and will be created" - echo " adduser will ask for a password and a few details" - USER_EXISTED=false - fi - - IN_SUDO=false - id -nG "$USERNAME" 2>/dev/null | tr ' ' '\n' | grep -qx sudo && IN_SUDO=true - echo " sudo group: $($IN_SUDO && echo 'already a member' || echo 'will be added')" - echo " passwordless: $(has_passwordless_sudo "$USERNAME" && echo 'already granted' || echo 'not granted')" - - if $USER_EXISTED && $IN_SUDO; then - SUMMARY+=("User: ${USERNAME} (already set up)") - elif confirm "Proceed?"; then - if ! $USER_EXISTED; then - adduser --gecos "" "$USERNAME" - SUMMARY+=("User: ${USERNAME} created") - fi - $IN_SUDO || usermod -aG sudo "$USERNAME" - ok "${USERNAME} is in the sudo group" - $USER_EXISTED && SUMMARY+=("User: ${USERNAME} added to sudo") - else - warn "skipped by request" - SUMMARY+=("User: SKIPPED by request") - fi - - # ── passwordless sudo ── - # - # Asked separately because it is a security posture rather than part of - # creating an account, and because Officer has an actual requirement here: - # os-user-shell.ts runs `sudo -n` to provision a member's home, and a prompt it - # cannot answer is a failure it reports as a permissions error. - if has_passwordless_sudo "$USERNAME"; then - echo "" - echo " passwordless sudo is already granted to ${USERNAME}" - SUMMARY+=("Sudo: passwordless (already)") - else - echo "" - info "Passwordless sudo for ${USERNAME}?" - echo " Means sudo never asks for a password again. Convenient, and the" - echo " cost is real: anything that gets hold of this account, or of a key" - echo " that can log into it, is root without another step." - if is_role vps; then - echo " Worth weighing on a ${MACHINE_ROLE}, which faces the open internet." - fi - echo "" - echo " Officer needs it: it runs 'sudo -n' to provision a member's Linux" - echo " account, and a password prompt it cannot answer surfaces as a" - echo " permissions error rather than a question." - if confirm "Grant it?"; then - grant_passwordless_sudo "$USERNAME" - ok "passwordless sudo granted — remove /etc/sudoers.d/99-${USERNAME}-nopasswd to undo" - SUMMARY+=("Sudo: passwordless") - else - warn "skipped by request" - SUMMARY+=("Sudo: password required (Officer's account provisioning will not work)") - fi - fi - step_ok -fi - # ============================================================================= # NOT PORTED YET # ============================================================================= @@ -830,7 +842,7 @@ fi # ============================================================================= -# 12. Summary +# 15. Summary # ============================================================================= echo ""