#!/usr/bin/env bash # ============================================================================ # SHARP-ER - check that this machine can run the project # ---------------------------------------------------------------------------- # ./scripts/check-prereqs.sh check both setup paths, report # ./scripts/check-prereqs.sh manual only the tools PATH A needs # ./scripts/check-prereqs.sh docker only the tools PATH B needs # # Exits non-zero when a tool the chosen path REQUIRES is missing or too old, so # it can be used as a gate in CI or in an onboarding script. Read-only: it # starts nothing, installs nothing and writes nothing. # ============================================================================ set -uo pipefail MODE="${1:-all}" case "$MODE" in all|manual|docker) ;; -h|--help) printf 'usage: check-prereqs.sh [all|manual|docker]\n' exit 0 ;; *) printf 'unknown mode: %s (use all, manual or docker)\n' "$MODE" >&2 exit 2 ;; esac # Versions this repository is developed against. NODE_MIN="22.22.0" # React Router 8 requires >= 22.22.0 PNPM_MAJOR="9" # packageManager: pnpm@9.12.3 PG_MAJOR="16" if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then C_OK=$'\033[32m'; C_WARN=$'\033[33m'; C_ERR=$'\033[31m'; C_DIM=$'\033[2m'; C_OFF=$'\033[0m' else C_OK=""; C_WARN=""; C_ERR=""; C_DIM=""; C_OFF="" fi FAILED=0 pass() { printf '%s ok %s %-22s %s\n' "$C_OK" "$C_OFF" "$1" "${2:-}"; } warn() { printf '%s warn %s %-22s %s\n' "$C_WARN" "$C_OFF" "$1" "${2:-}"; } fail() { printf '%s fail %s %-22s %s\n' "$C_ERR" "$C_OFF" "$1" "${2:-}"; FAILED=1; } note() { printf '%s %s %-22s %s\n' "$C_DIM" "$C_OFF" "" "$1"; } # true when $1 >= $2, comparing dotted versions numerically. version_ge() { [ "$1" = "$2" ] && return 0 [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)" = "$2" ] } # Strip a leading v and anything after the numeric part: "v22.23.2" -> 22.23.2 clean_version() { printf '%s' "$1" | sed -E 's/^v//; s/[^0-9.].*$//'; } check_node() { if ! command -v node >/dev/null 2>&1; then fail "node" "not installed - need >= $NODE_MIN" note "nvm install \$(cat .nvmrc) # .nvmrc pins the version this repo uses" return fi local version; version="$(clean_version "$(node --version)")" if version_ge "$version" "$NODE_MIN"; then pass "node $version" ">= $NODE_MIN" else fail "node $version" "too old - React Router 8 requires >= $NODE_MIN" note "nvm install \$(cat .nvmrc) && nvm use" fi } check_pnpm() { if ! command -v pnpm >/dev/null 2>&1; then fail "pnpm" "not installed - need $PNPM_MAJOR.x" note "corepack enable && corepack prepare pnpm@9.12.3 --activate" return fi local version major; version="$(clean_version "$(pnpm --version)")"; major="${version%%.*}" if [ "$major" = "$PNPM_MAJOR" ]; then pass "pnpm $version" "major $PNPM_MAJOR" else warn "pnpm $version" "repo pins pnpm@9.12.3 - the lockfile may be rewritten" fi } check_postgres_client() { if ! command -v psql >/dev/null 2>&1; then fail "psql" "not installed - need the PostgreSQL $PG_MAJOR client" note "sudo apt install postgresql-$PG_MAJOR postgresql-client-$PG_MAJOR" return fi local version major version="$(clean_version "$(psql --version | awk '{print $3}')")" major="${version%%.*}" if [ "$major" = "$PG_MAJOR" ]; then pass "psql $version" "major $PG_MAJOR" else warn "psql $version" "developed against PostgreSQL $PG_MAJOR" fi } check_postgres_server() { command -v pg_isready >/dev/null 2>&1 || { note "pg_isready not found - skipped the server check"; return; } local host="${DATABASE_HOST:-127.0.0.1}" port="${DATABASE_PORT:-5432}" if pg_isready --host "$host" --port "$port" --timeout 3 >/dev/null 2>&1; then pass "postgres server" "answering on $host:$port" else warn "postgres server" "nothing on $host:$port - start it before ./scripts/setup-db.sh" fi } check_docker() { if ! command -v docker >/dev/null 2>&1; then fail "docker" "not installed" note "https://docs.docker.com/engine/install/" return fi pass "docker" "$(docker --version 2>/dev/null | sed 's/,.*//')" if docker compose version >/dev/null 2>&1; then pass "docker compose" "$(docker compose version --short 2>/dev/null)" else fail "docker compose" "v2 plugin missing (the old docker-compose binary is not supported)" fi if ! docker info >/dev/null 2>&1; then warn "docker daemon" "not reachable - start Docker, or add your user to the docker group" fi } check_env_files() { local root; root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" [ -f "$root/.env" ] && pass ".env" "present" || warn ".env" "missing - cp .env.example .env" if [ "$MODE" != "docker" ]; then [ -f "$root/apps/cms/.env" ] \ && pass "apps/cms/.env" "present" \ || warn "apps/cms/.env" "missing - cp apps/cms/.env.example apps/cms/.env" fi } printf '\nSHARP-ER prerequisites (%s)\n\n' "$MODE" if [ "$MODE" = "manual" ] || [ "$MODE" = "all" ]; then printf '%sPATH A - manual%s\n' "$C_DIM" "$C_OFF" check_node check_pnpm check_postgres_client check_postgres_server printf '\n' fi if [ "$MODE" = "docker" ] || [ "$MODE" = "all" ]; then printf '%sPATH B - docker%s\n' "$C_DIM" "$C_OFF" check_docker printf '\n' fi printf '%sEnvironment files%s\n' "$C_DIM" "$C_OFF" check_env_files printf '\n' if [ "$FAILED" -ne 0 ]; then printf '%sSomething required is missing - see the lines marked fail.%s\n\n' "$C_ERR" "$C_OFF" exit 1 fi printf '%sReady. Next: README.md, "PATH A" or "PATH B".%s\n\n' "$C_OK" "$C_OFF"