#!/usr/bin/env bash # TRVChek installer -- macOS. No sudo required. set -euo pipefail VERSION="3.0.0" EXPECTED_SHA="4da431226efc1419c9fa61db2aac19f041c4a98611ef9defe445c92b610906c7" BASE_URL="https://install.trvchek.com" TARBALL_URL="$BASE_URL/releases/trvchek-$VERSION.tar.gz" TRV_HOME="$HOME/.trvchek" VERSIONS_DIR="$TRV_HOME/versions" TARGET_DIR="$VERSIONS_DIR/$VERSION" CURRENT_LINK="$TRV_HOME/current" BIN_DIR="$HOME/.local/bin" PATH_MARKER="# added by TRVChek installer" say() { printf '%s\n' "$*"; } fail() { printf 'TRVChek install failed: %s\n' "$*" >&2; exit 1; } TMP_DIR="" cleanup() { [ -n "$TMP_DIR" ] && rm -rf "$TMP_DIR" || true; } trap cleanup EXIT INT TERM say "TRVChek installer (v$VERSION)" # 1. OS check --------------------------------------------------------------- [ "$(uname -s)" = "Darwin" ] || fail "this installer supports macOS only (found $(uname -s))." # 2. Python check ----------------------------------------------------------- PY="" for candidate in python3 /usr/bin/python3 /opt/homebrew/bin/python3 /usr/local/bin/python3; do if command -v "$candidate" >/dev/null 2>&1; then if "$candidate" -c 'import sys; sys.exit(0 if sys.version_info >= (3, 9) else 1)' >/dev/null 2>&1; then PY="$(command -v "$candidate")" break fi fi done [ -n "$PY" ] || fail "Python 3.9 or newer is required. Install it, then re-run this installer." say "Using Python: $PY ($("$PY" -c 'import platform;print(platform.python_version())'))" command -v curl >/dev/null 2>&1 || fail "curl is required." command -v shasum >/dev/null 2>&1 || fail "shasum is required." command -v tar >/dev/null 2>&1 || fail "tar is required." # 3. Download ------------------------------------------------------------- TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/trvchek-install.XXXXXX")" ARCHIVE="$TMP_DIR/trvchek-$VERSION.tar.gz" say "Downloading $TARBALL_URL" curl -fsSL "$TARBALL_URL" -o "$ARCHIVE" || fail "could not download the release archive." # 4. Verify checksum ------------------------------------------------------ ACTUAL_SHA="$(shasum -a 256 "$ARCHIVE" | awk '{print $1}')" if [ "$ACTUAL_SHA" != "$EXPECTED_SHA" ]; then fail "checksum mismatch. Expected $EXPECTED_SHA, got $ACTUAL_SHA. Nothing was installed." fi say "Checksum verified." # 5. Unpack into temp ----------------------------------------------------- UNPACK="$TMP_DIR/unpack" mkdir -p "$UNPACK" tar -xzf "$ARCHIVE" -C "$UNPACK" || fail "could not unpack the release archive." PAYLOAD="$UNPACK/trvchek-$VERSION" [ -f "$PAYLOAD/trvchek-cli" ] || fail "release archive is incomplete (trvchek-cli missing)." [ -f "$PAYLOAD/trvchek/core/structural.py" ] || fail "release archive is incomplete (core missing)." # 6. Atomic install ------------------------------------------------------- # User state ($TRV_HOME/license.json etc.) is never touched. mkdir -p "$VERSIONS_DIR" STAGING="$VERSIONS_DIR/.staging-$VERSION.$$" rm -rf "$STAGING" cp -R "$PAYLOAD" "$STAGING" chmod 0755 "$STAGING/trvchek-cli" if [ -e "$TARGET_DIR" ]; then PREVIOUS="$VERSIONS_DIR/.previous-$VERSION.$$" mv "$TARGET_DIR" "$PREVIOUS" if mv "$STAGING" "$TARGET_DIR"; then rm -rf "$PREVIOUS" else mv "$PREVIOUS" "$TARGET_DIR" fail "could not replace the existing install; previous version left intact." fi else mv "$STAGING" "$TARGET_DIR" fi # Atomic symlink flip: create alongside, then rename over. TMP_LINK="$TRV_HOME/.current.$$" ln -sfn "$TARGET_DIR" "$TMP_LINK" mv -f "$TMP_LINK" "$CURRENT_LINK" say "Installed to $TARGET_DIR" # 7. Launchers ------------------------------------------------------------ mkdir -p "$BIN_DIR" cat > "$BIN_DIR/trvchek" < "$BIN_DIR/trvw" </dev/null; then say "PATH entry already present in $PROFILE" elif [ -n "$PROFILE" ] && printf '%s\n' "$PATH_LINE" >> "$PROFILE" 2>/dev/null; then say "Added $BIN_DIR to PATH in $PROFILE" say "Open a new terminal, or run: source $PROFILE" else say "" say "NOTE: could not update your shell profile automatically." say "Add this line to your shell profile yourself:" say " $PATH_LINE" fi # 9. Self-test ------------------------------------------------------------ if "$BIN_DIR/trvchek" --help >/dev/null 2>&1; then say "Self-test passed." else if "$PY" -c "import sys; sys.path.insert(0, '$TARGET_DIR'); import trvchek.core.structural" >/dev/null 2>&1; then say "Self-test passed (engine imports cleanly)." else fail "self-test failed. The engine did not load. Nothing else was changed." fi fi say "" say "TRVChek $VERSION installed." say "Next:" say " trvchek activate YOUR-LICENSE-KEY" say " cd your-project && trvw --auto-fix" if [ "$PATH_OK" = "0" ]; then say "" say "(If 'trvchek' is not found yet, start a new terminal session first.)" fi