#!/bin/bash # ============================================================================= # officer-setup — shared foundation # ============================================================================= # # Sourced by officer-setup.sh before anything runs. DEFINITIONS ONLY, the same # rule machine-setup/lib holds to: nothing here installs, writes or restarts. # # ── Why this is a separate script from machine-setup ── # # They answer different questions. machine-setup asks what a MACHINE should be — # users, ssh, firewall, runtimes — and is worth running on a box that will never # see Officer. This one puts Officer on a machine that is already ready, and # assumes nothing about how it got that way. # # The split also means the failure modes stay apart: a broken firewall rule and a # failed database migration are not the same kind of problem and should not be # in the same run. [[ -n "${OFFICER_SETUP_BASE_LOADED:-}" ]] && return 0 OFFICER_SETUP_BASE_LOADED=1 SUMMARY=() ERRORS=() CURRENT_STEP="" SKIP_STEP=false USERNAME="${SETUP_USERNAME:-}" USER_HOME="" OFFICER_ROOT="${OFFICER_ROOT:-}" MACHINE_ROLE="${MACHINE_ROLE:-}" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' BOLD='\033[1m' NC='\033[0m' info() { echo -e "${CYAN}::${NC} $*"; } ok() { echo -e " ${GREEN}OK${NC}: $*"; } warn() { echo -e " ${YELLOW}WARN${NC}: $*"; } fail() { echo -e " ${RED}FAIL${NC}: $*" exit 1 } ONLY_STEP="${ONLY_STEP:-}" step() { CURRENT_STEP="$1" if [[ -n "$ONLY_STEP" ]]; then if [[ "${1,,}" == "${ONLY_STEP,,}" ]]; then SKIP_STEP=false echo "" echo -e "${BOLD}── $1 ──${NC}" else SKIP_STEP=true fi return fi if grep -qxF "$1" "$PROGRESS_FILE" 2>/dev/null; then echo -e " ${GREEN}SKIP${NC}: $1 (already done)" SKIP_STEP=true return fi SKIP_STEP=false echo "" echo -e "${BOLD}── $1 ──${NC}" } skip() { [[ "$SKIP_STEP" == true ]]; } step_ok() { [[ -n "$ONLY_STEP" ]] && return 0 echo "$CURRENT_STEP" >>"$PROGRESS_FILE" } page() { if [[ -t 1 ]] && command -v more &>/dev/null; then more; else cat; fi } confirm() { local message="${1:-Proceed?}" default="${2:-y}" help_fn="${3:-}" answer prompt [[ "${ASSUME_YES:-}" == "1" ]] && { [[ "$default" == "y" ]] && return 0 || return 1; } if [[ "$default" == "y" ]]; then prompt="[Y/n]"; else prompt="[y/N]"; fi [[ -n "$help_fn" ]] && prompt="${prompt%]}/?]" while true; do if ! read -rp " ${message} ${prompt}: " answer; then echo "" fail "No answer. Set ASSUME_YES=1 to run without prompts." fi [[ -z "$answer" ]] && answer="$default" case "$answer" in y | Y | yes | Yes) return 0 ;; n | N | no | No) return 1 ;; "?") if [[ -n "$help_fn" ]]; then echo "" "$help_fn" | page echo "" else warn "Answer y or n." fi ;; *) warn "Answer y or n${help_fn:+, or ? for what this is}." ;; esac done } ask_required() { local __var="$1" message="$2" default="$3" answer="" # Unattended takes the default where there IS one. Where there is not — the owning # account on a machine that machine-setup never ran on — it still asks, because # there is nothing to fall back to and a guess would install as the wrong user. if [[ "${UNATTENDED:-}" == "1" && -n "$default" ]]; then printf ' %s [%s] — unattended, taking the default\n' "$message" "$default" printf -v "$__var" '%s' "$default" return 0 fi 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" } user_group() { id -gn "${1:-$USERNAME}" 2>/dev/null || echo "${1:-$USERNAME}"; } # Run something as the account that owns the install. Officer's files, its # node_modules and its pm2 process list all belong to that account, not to root — # a repository cloned as root is one the owner cannot pull. as_owner() { (cd "${2:-/}" && sudo -H -u "$USERNAME" bash -c "$1"); }