https://gitea.officer.dev/officerdev/platform.git, not the ssh form. The reachability check is now one test for either scheme: `git ls-remote` with both prompts disabled. That is the real question — not whether the host answers but whether this account can read the repository — and neither prompt fails cleanly on its own. Over https git asks for a username nobody is there to type; over ssh it asks for a password or stops on host-key verification. With GIT_TERMINAL_PROMPT=0 and BatchMode both off, an unreadable repository is an immediate non-zero rather than a hang. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
273 lines
9.9 KiB
Bash
Executable File
273 lines
9.9 KiB
Bash
Executable File
#!/bin/bash
|
|
set -e
|
|
|
|
# =============================================================================
|
|
# officer-setup — the platform, on a machine that is already provisioned
|
|
#
|
|
# The second half of the install. machine-setup/ brings a blank box up to a
|
|
# usable machine; this puts Officer on top of it.
|
|
#
|
|
# Run as root: sudo scripts/setup/officer-setup.sh
|
|
# =============================================================================
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROGRESS_FILE="$SCRIPT_DIR/officer-setup/.setup-progress"
|
|
|
|
ONLY_STEP=""
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--only)
|
|
ONLY_STEP="${2:-}"
|
|
shift 2
|
|
;;
|
|
--only=*)
|
|
ONLY_STEP="${1#*=}"
|
|
shift
|
|
;;
|
|
-l | --list)
|
|
grep -oP '^step "\K[^"]+' "${BASH_SOURCE[0]}"
|
|
exit 0
|
|
;;
|
|
-h | --help)
|
|
echo "usage: officer-setup.sh [--only <step>] [--list]"
|
|
exit 0
|
|
;;
|
|
*) echo "unknown option: $1" >&2 && exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
# shellcheck source=officer-setup/lib/base.sh
|
|
source "$SCRIPT_DIR/officer-setup/lib/base.sh"
|
|
# shellcheck source=officer-setup/lib/preflight.sh
|
|
source "$SCRIPT_DIR/officer-setup/lib/preflight.sh"
|
|
# shellcheck source=officer-setup/lib/repo.sh
|
|
source "$SCRIPT_DIR/officer-setup/lib/repo.sh"
|
|
|
|
trap 'echo ""; echo -e "${RED}╔══════════════════════════════════════════════════╗${NC}"; echo -e "${RED}║ OFFICER SETUP FAILED${NC}"; echo -e "${RED}║ Step: ${CURRENT_STEP:-unknown}${NC}"; echo -e "${RED}║ Line: $LINENO${NC}"; echo -e "${RED}║ Command: $BASH_COMMAND${NC}"; echo -e "${RED}╚══════════════════════════════════════════════════╝${NC}"' ERR
|
|
|
|
# =============================================================================
|
|
# 1. Pre-flight
|
|
# =============================================================================
|
|
|
|
echo ""
|
|
echo -e "${BOLD}╔══════════════════════════════════════════════════╗${NC}"
|
|
echo -e "${BOLD}║ Officer Setup ║${NC}"
|
|
echo -e "${BOLD}╚══════════════════════════════════════════════════╝${NC}"
|
|
|
|
if [[ "$EUID" -ne 0 ]]; then
|
|
fail "Please run as root: sudo ./officer-setup.sh"
|
|
fi
|
|
|
|
# ── what machine-setup already established ──
|
|
echo ""
|
|
if load_machine_answers; then
|
|
info "Read from machine-setup: ${MACHINE_ANSWERS}"
|
|
else
|
|
warn "machine-setup has not run on this machine"
|
|
echo " That is fine if you provisioned it another way — the questions it"
|
|
echo " would have answered are asked below instead."
|
|
fi
|
|
|
|
# ── the account ──
|
|
#
|
|
# A remembered answer can go stale: the account it names may have been renamed or
|
|
# removed since machine-setup ran. That is a reason to ask again, not a reason to
|
|
# stop — so the remembered value is checked before it is trusted, and a bad one
|
|
# is reported and replaced rather than ending the run.
|
|
if [[ -n "$USERNAME" ]] && ! owner_exists; then
|
|
warn "the remembered account '${USERNAME}' does not exist on this machine any more"
|
|
USERNAME=""
|
|
fi
|
|
|
|
while [[ -z "$USERNAME" ]] || ! owner_exists; do
|
|
echo ""
|
|
info "Which account owns this Officer install?"
|
|
echo " Its files, its node_modules and its pm2 process list all belong to"
|
|
echo " this account rather than to root."
|
|
echo ""
|
|
ask_required USERNAME "Username" "${SUDO_USER:-}"
|
|
owner_exists || warn "There is no account called '${USERNAME}' on this machine."
|
|
done
|
|
|
|
resolve_user_home
|
|
|
|
# ── where it goes ──
|
|
if [[ -z "$OFFICER_ROOT" ]]; then
|
|
echo ""
|
|
info "Where should Officer be installed?"
|
|
echo " One directory holding the app, its data, the item store and any"
|
|
echo " containers the app store provisions."
|
|
echo ""
|
|
ask_required OFFICER_ROOT "Path" "${USER_HOME}/officerdev"
|
|
fi
|
|
OFFICER_ROOT="${OFFICER_ROOT/#\~/$USER_HOME}"
|
|
[[ "$OFFICER_ROOT" == /* ]] || fail "That needs to be an absolute path — got '${OFFICER_ROOT}'"
|
|
OFFICER_ROOT="${OFFICER_ROOT%/}"
|
|
|
|
info "Account: ${USERNAME} (home ${USER_HOME})"
|
|
info "Officer: ${OFFICER_ROOT}"
|
|
[[ -n "$MACHINE_ROLE" ]] && info "Role: ${MACHINE_ROLE}"
|
|
|
|
# ── is the machine actually ready ──
|
|
#
|
|
# Checked and reported together. 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.
|
|
echo ""
|
|
info "What Officer needs from this machine"
|
|
|
|
mapfile -t MISSING < <(missing_tools)
|
|
mapfile -t MISSING_OPT < <(missing_optional_tools)
|
|
|
|
for t in "${REQUIRED_TOOLS[@]}"; do
|
|
if command -v "$t" &>/dev/null; then
|
|
printf ' %-6s %-10s %s\n' "$t" "ok" "$(tool_why "$t")"
|
|
else
|
|
printf ' %-6s %-10s %s\n' "$t" "MISSING" "$(tool_why "$t")"
|
|
fi
|
|
done
|
|
for t in "${OPTIONAL_TOOLS[@]}"; do
|
|
if command -v "$t" &>/dev/null; then
|
|
printf ' %-6s %-10s %s\n' "$t" "ok" "$(tool_why "$t")"
|
|
else
|
|
printf ' %-6s %-10s %s\n' "$t" "absent" "$(tool_why "$t") — optional"
|
|
fi
|
|
done
|
|
|
|
if ((${#MISSING[@]} > 0)); then
|
|
echo ""
|
|
fail "Missing: ${MISSING[*]}. Run scripts/setup/machine-setup/machine-setup.sh first, or install them yourself."
|
|
fi
|
|
|
|
if ((${#MISSING_OPT[@]} > 0)); then
|
|
echo ""
|
|
warn "No Docker. Postgres will have to be one you already run, and the app"
|
|
echo " store cannot provision anything until Docker is installed."
|
|
fi
|
|
|
|
if [[ -f "$PROGRESS_FILE" ]]; then
|
|
echo ""
|
|
info "Resuming — $(wc -l <"$PROGRESS_FILE") step(s) already done, and they will be skipped"
|
|
echo " To start over instead: sudo rm ${PROGRESS_FILE}"
|
|
else
|
|
echo ""
|
|
echo " This can be stopped at any point and run again later. Completed"
|
|
echo " steps are remembered and skipped."
|
|
fi
|
|
|
|
# =============================================================================
|
|
# 2. Repository
|
|
# =============================================================================
|
|
|
|
step "Repository"
|
|
if ! skip; then
|
|
PLATFORM_DIR="$(platform_dir)"
|
|
|
|
echo ""
|
|
info "Repository — where the platform's code lives"
|
|
echo " path: ${PLATFORM_DIR}"
|
|
|
|
if repo_exists; then
|
|
echo " remote: $(repo_remote)"
|
|
echo " branch: $(repo_branch)"
|
|
echo " working: $(repo_is_dirty && echo 'has uncommitted changes' || echo 'clean')"
|
|
|
|
# Reported, never silently corrected. Repointing somebody's remote is a
|
|
# decision about where their work goes, and this script is not entitled to
|
|
# make it quietly.
|
|
if [[ -n "$(repo_remote)" && "$(repo_remote)" != "$OFFICER_REPO" ]]; then
|
|
echo ""
|
|
warn "this checkout points somewhere other than ${OFFICER_REPO}"
|
|
echo " Left alone. To move it:"
|
|
echo " git -C ${PLATFORM_DIR} remote set-url origin ${OFFICER_REPO}"
|
|
fi
|
|
|
|
if repo_is_dirty; then
|
|
echo ""
|
|
echo " not pulling — there are uncommitted changes here, and a pull"
|
|
echo " would either fail or bury them"
|
|
SUMMARY+=("Repository: present at ${PLATFORM_DIR}, left alone (uncommitted changes)")
|
|
elif confirm "Pull the latest changes?"; then
|
|
if pull_repo; then
|
|
ok "up to date on $(repo_branch)"
|
|
SUMMARY+=("Repository: pulled, on $(repo_branch)")
|
|
else
|
|
# --ff-only, so this means the branch has diverged rather than that the
|
|
# network failed. Saying which matters.
|
|
warn "could not fast-forward — the local branch has diverged from the remote"
|
|
ERRORS+=("Repository: pull refused, branch diverged")
|
|
SUMMARY+=("Repository: present, pull refused (diverged)")
|
|
fi
|
|
else
|
|
SUMMARY+=("Repository: present at ${PLATFORM_DIR}")
|
|
fi
|
|
|
|
else
|
|
echo " nothing there yet"
|
|
echo ""
|
|
info "Clone from ${OFFICER_REPO}?"
|
|
echo " Cloned as ${USERNAME}, not as root — a repository owned by root is"
|
|
echo " one you cannot pull, commit in, or install into."
|
|
|
|
CLONE_URL="$OFFICER_REPO"
|
|
|
|
# Checked before cloning, for either scheme — see repo_reachable.
|
|
if ! repo_reachable "$CLONE_URL"; then
|
|
echo ""
|
|
warn "${USERNAME} cannot read ${CLONE_URL}"
|
|
echo " Either the host is not answering yet, or the repository is not"
|
|
echo " readable without credentials."
|
|
echo ""
|
|
echo " [1] try it anyway"
|
|
echo " [2] use a different URL"
|
|
echo " [3] stop here"
|
|
echo ""
|
|
REPO_PICK=""
|
|
while [[ -z "$REPO_PICK" ]]; do
|
|
if ! read -rp " Which one? (1/2/3) [1]: " REPO_CHOICE; then
|
|
echo ""
|
|
fail "No answer."
|
|
fi
|
|
case "${REPO_CHOICE:-1}" in
|
|
1) REPO_PICK=go ;;
|
|
2)
|
|
ask_required CLONE_URL "Repository URL" "$CLONE_URL"
|
|
REPO_PICK=go
|
|
;;
|
|
3) fail "Stopped. Nothing below can run without the repository." ;;
|
|
*) warn "Pick 1, 2 or 3." ;;
|
|
esac
|
|
done
|
|
fi
|
|
|
|
if confirm "Clone it now?"; then
|
|
if clone_repo "$CLONE_URL"; then
|
|
ok "cloned to ${PLATFORM_DIR} on $(repo_branch)"
|
|
SUMMARY+=("Repository: cloned from ${CLONE_URL}")
|
|
else
|
|
warn "the clone did not complete"
|
|
ERRORS+=("Repository: clone failed")
|
|
fail "Nothing below can run without the repository."
|
|
fi
|
|
else
|
|
fail "Nothing below can run without the repository."
|
|
fi
|
|
fi
|
|
step_ok
|
|
fi
|
|
|
|
# =============================================================================
|
|
# NOT BUILT YET
|
|
# =============================================================================
|
|
# 3 Dependencies bun install
|
|
# 4 Database Postgres in docker, or one you already run
|
|
# 5 Environment .env
|
|
# 6 Schema db:push
|
|
# 7 Build gen:index
|
|
# 8 Services pm2 startOrRestart · save · startup
|
|
# 9 Verify are the processes actually up
|
|
|
|
echo ""
|
|
echo -e "${BOLD} Pre-flight complete.${NC} The remaining sections are not built yet."
|
|
echo ""
|