start officer-setup: pre-flight, inheriting what machine-setup already asked
The second half of the install, and a much smaller script than the original: of the old setup.sh's thirteen sections, seven are machine-setup's job now and two more were already removed. What is left is the repository, dependencies, the database, .env, the schema, the build and pm2. Pre-flight asks nothing on a normal run. machine-setup saves the account, the Officer path and the role beside itself, and this reads the same file — so machine-setup then officer-setup is two scripts and one set of answers. It prompts only where that file is absent, which is a supported case rather than an error: somebody may have provisioned the box their own way. It then checks the machine is actually ready — git, node, bun and pm2 required, docker optional — and reports all of them together with what each is for. Finding out about a missing bun three sections in, after a repository has been cloned and a database started, is a worse way to learn it. A missing required tool stops the run and names machine-setup. Found by running it: a remembered answer can go stale. My own earlier testing had left SETUP_USERNAME=gitfresh in that file, for a throwaway account I then deleted, and the run dead-ended on it. A remembered account that no longer exists is a reason to ask again, not a reason to stop — so it is checked before it is trusted, reported, and replaced. Docker being absent is a warning rather than a failure: Postgres can be one you already run, and the app store simply cannot provision until Docker is there. Sections 2 to 9 are listed and not built. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
#!/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=""
|
||||
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"); }
|
||||
@@ -0,0 +1,75 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# officer-setup — is this machine ready
|
||||
# =============================================================================
|
||||
#
|
||||
# Definitions only.
|
||||
#
|
||||
# ── Inherited, not re-asked ──
|
||||
#
|
||||
# machine-setup saves the account, the Officer path and the machine role beside
|
||||
# itself. This reads the same file, so a normal run — machine-setup, then this —
|
||||
# asks nothing at all. It only prompts on a machine where machine-setup never
|
||||
# ran, which is a supported case rather than an error: somebody may have
|
||||
# provisioned the box their own way.
|
||||
|
||||
[[ -n "${OFFICER_SETUP_PREFLIGHT_LOADED:-}" ]] && return 0
|
||||
OFFICER_SETUP_PREFLIGHT_LOADED=1
|
||||
|
||||
# Where machine-setup keeps what it was told. Beside this script, one directory
|
||||
# across.
|
||||
MACHINE_ANSWERS="${MACHINE_ANSWERS:-${SCRIPT_DIR}/machine-setup/.setup-answers}"
|
||||
|
||||
# Read as assignments rather than sourced: the file is read by a root run and
|
||||
# sourcing it would make it executable content.
|
||||
load_machine_answers() {
|
||||
[[ -r "$MACHINE_ANSWERS" ]] || return 1
|
||||
local key value
|
||||
while IFS='=' read -r key value; do
|
||||
[[ "$key" =~ ^[A-Z_]+$ ]] || continue
|
||||
[[ -n "$value" ]] || continue
|
||||
case "$key" in
|
||||
MACHINE_ROLE) if [[ -z "$MACHINE_ROLE" ]]; then MACHINE_ROLE="$value"; fi ;;
|
||||
SETUP_USERNAME) if [[ -z "$USERNAME" ]]; then USERNAME="$value"; fi ;;
|
||||
OFFICER_ROOT) if [[ -z "$OFFICER_ROOT" ]]; then OFFICER_ROOT="$value"; fi ;;
|
||||
esac
|
||||
done <"$MACHINE_ANSWERS"
|
||||
return 0
|
||||
}
|
||||
|
||||
# What Officer needs to already be here, and what installs it.
|
||||
#
|
||||
# Checked together and reported together: finding out about a missing bun three
|
||||
# sections in, after the repository has been cloned and a database started, is a
|
||||
# worse way to learn it than being told at the start.
|
||||
REQUIRED_TOOLS=(git node bun pm2)
|
||||
OPTIONAL_TOOLS=(docker)
|
||||
|
||||
missing_tools() {
|
||||
local t
|
||||
for t in "${REQUIRED_TOOLS[@]}"; do command -v "$t" &>/dev/null || echo "$t"; done
|
||||
}
|
||||
|
||||
missing_optional_tools() {
|
||||
local t
|
||||
for t in "${OPTIONAL_TOOLS[@]}"; do command -v "$t" &>/dev/null || echo "$t"; done
|
||||
}
|
||||
|
||||
tool_why() {
|
||||
case "$1" in
|
||||
git) echo "to clone and update the platform" ;;
|
||||
node) echo "pm2 runs on it, and the terminal sidecar builds node-pty against it" ;;
|
||||
bun) echo "the platform itself and nineteen of the twenty processes" ;;
|
||||
pm2) echo "supervises every process; the ecosystem files are written for it" ;;
|
||||
docker) echo "Postgres, and anything the app store provisions" ;;
|
||||
*) echo "" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# The account has to exist before anything is written to its home.
|
||||
owner_exists() { id "$USERNAME" &>/dev/null; }
|
||||
|
||||
resolve_user_home() {
|
||||
USER_HOME="$(getent passwd "$USERNAME" 2>/dev/null | cut -d: -f6)"
|
||||
[[ -n "$USER_HOME" ]] || USER_HOME="/home/${USERNAME}"
|
||||
}
|
||||
Reference in New Issue
Block a user