diff --git a/scripts/setup/machine-setup/lib/dev.sh b/scripts/setup/machine-setup/lib/dev.sh new file mode 100644 index 00000000..dfa1cc51 --- /dev/null +++ b/scripts/setup/machine-setup/lib/dev.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# ============================================================================= +# machine-setup — the development environment +# ============================================================================= +# +# Definitions only, like the other lib/ files. + +[[ -n "${MACHINE_SETUP_DEV_LOADED:-}" ]] && return 0 +MACHINE_SETUP_DEV_LOADED=1 + +# ----------------------------------------------------------------------------- +# git +# ----------------------------------------------------------------------------- +# +# Read and written as the account, not as root. `git config --global` writes to +# $HOME/.gitconfig, so running it under sudo without -H would write root's. +# +# ── Why this asks before touching an existing identity ── +# +# The original set all four values unconditionally on every run. Re-running it on +# a machine somebody already uses replaces the name and email they had with +# whatever is typed — and prompt_value accepts an empty answer, so pressing +# Enter twice wrote `user.name = ""`. An empty name is worse than none at all: +# unset makes git refuse to commit and say why, empty makes it commit with a +# blank author and never mention it. +# +# ── And why it is worth being careful about here in particular ── +# +# docs/agent-git-identity.md: every agent Officer runs commits AS THE OWNER, +# because it runs as the owner. So this is not only the human's identity — it is +# what `git log` will attribute every agent commit on this machine to. + +# Run from / rather than wherever the script was launched. +# +# `git config --global` reads and writes $HOME/.gitconfig and needs no repository +# — but git still stats the working directory on the way, looking for one. The +# script is typically launched from somewhere under the invoking user's home, +# which is 0750, so the target account cannot stat it and every call dies with +# +# fatal: failed to stat '': Permission denied +# +# Found because the writes failed silently: the section reported "written" while +# nothing had been. Both wrappers now run in a subshell from /, which every +# account can stat, and their exit status is checked by the caller. +git_get() { (cd / && sudo -H -u "$USERNAME" git config --global --get "$1" 2>/dev/null); } +git_set() { (cd / && sudo -H -u "$USERNAME" git config --global "$1" "$2"); } + +# Is there anything configured at all? +git_has_identity() { [[ -n "$(git_get user.name)" || -n "$(git_get user.email)" ]]; } + +# Ask for a value that must not be empty. The original's prompt accepted empty +# and wrote it; this re-asks. +ask_required() { + local __var="$1" message="$2" default="$3" answer="" + while [[ -z "$answer" ]]; do + if ! read -rp " ${message}${default:+ [$default]}: " answer; then + echo "" + fail "No answer." + fi + answer="${answer:-$default}" + [[ -z "$answer" ]] && warn "This one cannot be left blank." + done + printf -v "$__var" '%s' "$answer" +} diff --git a/scripts/setup/machine-setup/machine-setup.sh b/scripts/setup/machine-setup/machine-setup.sh index 08c7dbe8..f6a8282d 100755 --- a/scripts/setup/machine-setup/machine-setup.sh +++ b/scripts/setup/machine-setup/machine-setup.sh @@ -32,6 +32,8 @@ source "$SCRIPT_DIR/lib/files.sh" source "$SCRIPT_DIR/lib/ssh.sh" # shellcheck source=lib/network.sh source "$SCRIPT_DIR/lib/network.sh" +# shellcheck source=lib/dev.sh +source "$SCRIPT_DIR/lib/dev.sh" # Trap errors with context. Installed here rather than in lib/base.sh, because # that file is definitions only and a trap is a side effect on whoever sources it. @@ -1267,13 +1269,80 @@ EOF step_ok fi +# ============================================================================= +# 20. Git +# ============================================================================= + +step "Git" +if ! skip; then + echo "" + info "Git — the identity commits from this machine are made under" + + GIT_NAME_NOW="$(git_get user.name)" + GIT_EMAIL_NOW="$(git_get user.email)" + GIT_BRANCH_NOW="$(git_get init.defaultBranch)" + + GIT_DO=true + if git_has_identity; then + echo " name: ${GIT_NAME_NOW:-not set}" + echo " email: ${GIT_EMAIL_NOW:-not set}" + echo " default branch: ${GIT_BRANCH_NOW:-not set (git uses master)}" + echo "" + # Default no. There is already a working identity, and the common case for + # re-running this script is everything except this. + confirm "Change it?" n || GIT_DO=false + else + echo " nothing configured yet" + echo "" + echo " Worth knowing: agents Officer runs commit as this account, so this" + echo " is what git log will attribute their commits to as well." + fi + + if [[ "$GIT_DO" == false ]]; then + echo " left as it is" + SUMMARY+=("Git: unchanged (${GIT_NAME_NOW})") + else + echo "" + ask_required GIT_NAME "Name" "$GIT_NAME_NOW" + ask_required GIT_EMAIL "Email" "$GIT_EMAIL_NOW" + ask_required GIT_BRANCH "Default branch for new repositories" "${GIT_BRANCH_NOW:-master}" + + echo "" + echo " name: ${GIT_NAME}" + echo " email: ${GIT_EMAIL}" + echo " default branch: ${GIT_BRANCH}" + if confirm "Proceed?"; then + # Checked, not assumed. These run as another account and can fail for + # reasons that have nothing to do with the values — which is exactly what + # happened when they were run from a directory that account could not stat. + GIT_OK=true + git_set user.name "$GIT_NAME" || GIT_OK=false + git_set user.email "$GIT_EMAIL" || GIT_OK=false + git_set init.defaultBranch "$GIT_BRANCH" || GIT_OK=false + + if $GIT_OK && [[ "$(git_get user.name)" == "$GIT_NAME" ]]; then + ok "written to ${USER_HOME}/.gitconfig" + SUMMARY+=("Git: ${GIT_NAME} <${GIT_EMAIL}>, default branch ${GIT_BRANCH}") + else + warn "could not write the git config for ${USERNAME}" + ERRORS+=("Git: writing ${USER_HOME}/.gitconfig failed") + SUMMARY+=("Git: FAILED to write") + fi + else + warn "skipped by request" + SUMMARY+=("Git: SKIPPED by request") + fi + fi + step_ok +fi + # ============================================================================= # NOT PORTED YET # ============================================================================= # # Sections still to move across from scripts/setup-old/setup-ubuntu.sh, in order: # -# git config · docker · zsh + prompt (incl. .tmux.conf) · tailscale · neovim · js runtimes · +# docker · zsh + prompt (incl. .tmux.conf) · tailscale · neovim · js runtimes · # dev tools · ufw · zshrc # # Each arrives as its own commit. Delete this block when the list is empty.