#!/usr/bin/env bash # install-larry.sh — bootstrap Larry-Anywhere on a fresh remote shell. # No root, no package install, no sudo. Writes only into $LARRY_HOME. # # Usage: # curl -fsSL /install-larry.sh | bash # # Or with explicit base URL: # LARRY_BASE_URL=https://example.com/larry-anywhere bash install-larry.sh # # Env vars: # LARRY_HOME install location (default: $HOME/.larry) # LARRY_BASE_URL where to fetch files from (no trailing slash) # LARRY_BIN_DIR where to symlink the `larry` command (default: $HOME/bin) set -eu LARRY_HOME="${LARRY_HOME:-$HOME/.larry}" # Canonical hosting: bojj27/cloverleaf-larry on GitHub, branch main, raw view. # Override via env if you fork or mirror elsewhere. LARRY_BASE_URL="${LARRY_BASE_URL:-https://raw.githubusercontent.com/bojj27/cloverleaf-larry/main}" LARRY_BIN_DIR="${LARRY_BIN_DIR:-$HOME/bin}" C_RESET=$'\033[0m'; C_BOLD=$'\033[1m'; C_GREEN=$'\033[32m' C_YELLOW=$'\033[33m'; C_RED=$'\033[31m'; C_CYAN=$'\033[36m' say() { printf '%s%sinstall-larry>%s %s\n' "$C_CYAN" "$C_BOLD" "$C_RESET" "$*"; } ok() { printf ' %s✓%s %s\n' "$C_GREEN" "$C_RESET" "$*"; } warn() { printf ' %s!%s %s\n' "$C_YELLOW" "$C_RESET" "$*"; } die() { printf '%serror:%s %s\n' "$C_RED" "$C_RESET" "$*" >&2; exit 1; } # ───────────────────────────────────────────────────────────────────────────── # Detect platform # ───────────────────────────────────────────────────────────────────────────── UNAME_S="$(uname -s 2>/dev/null || echo unknown)" PLATFORM="" case "$UNAME_S" in Linux*) PLATFORM="linux" ;; Darwin*) PLATFORM="darwin" ;; CYGWIN*|MINGW*|MSYS*) PLATFORM="windows-cygwin" ;; # MobaXterm lives here *) PLATFORM="unknown" ;; esac ARCH="$(uname -m 2>/dev/null || echo unknown)" case "$ARCH" in x86_64|amd64) ARCH_NORM="amd64" ;; aarch64|arm64) ARCH_NORM="arm64" ;; i?86) ARCH_NORM="i386" ;; *) ARCH_NORM="$ARCH" ;; esac say "platform: $PLATFORM/$ARCH_NORM • LARRY_HOME=$LARRY_HOME" # ───────────────────────────────────────────────────────────────────────────── # Check required commands # ───────────────────────────────────────────────────────────────────────────── command -v bash >/dev/null 2>&1 || die "bash not found" command -v curl >/dev/null 2>&1 || die "curl not found" # ───────────────────────────────────────────────────────────────────────────── # Make dirs # ───────────────────────────────────────────────────────────────────────────── mkdir -p "$LARRY_HOME"/{agents,sessions,bin,lib} || die "cannot create $LARRY_HOME" chmod 700 "$LARRY_HOME" 2>/dev/null || true ok "created $LARRY_HOME" # ───────────────────────────────────────────────────────────────────────────── # Fetch the scripts. If LARRY_BASE_URL is not set, try to detect being run # from a local checkout (sibling files present) — copy locally instead. # ───────────────────────────────────────────────────────────────────────────── SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd)" || SCRIPT_DIR="" fetch() { # $1 = remote relative path, $2 = local destination if [ -n "$LARRY_BASE_URL" ]; then say "fetching $1" curl -fsSL --max-time 30 "$LARRY_BASE_URL/$1" -o "$2" \ && ok "$2" || die "failed to fetch $1" elif [ -n "$SCRIPT_DIR" ] && [ -f "$SCRIPT_DIR/$1" ]; then cp "$SCRIPT_DIR/$1" "$2" && ok "copied $1 (local)" else die "no LARRY_BASE_URL set and $1 not found in script dir" fi } fetch larry.sh "$LARRY_HOME/larry.sh" fetch larry-tunnel.sh "$LARRY_HOME/larry-tunnel.sh" fetch agents/larry.md "$LARRY_HOME/agents/larry.md" fetch agents/clover.md "$LARRY_HOME/agents/clover.md" fetch agents/cloverleaf-cheatsheet.md "$LARRY_HOME/agents/cloverleaf-cheatsheet.md" fetch agents/regress.md "$LARRY_HOME/agents/regress.md" fetch larry-rollback.sh "$LARRY_HOME/larry-rollback.sh" fetch larry-auth.sh "$LARRY_HOME/larry-auth.sh" fetch lib/oauth.sh "$LARRY_HOME/lib/oauth.sh" fetch lib/lessons.sh "$LARRY_HOME/lib/lessons.sh" fetch lib/hl7-sanitize.sh "$LARRY_HOME/lib/hl7-sanitize.sh" fetch lib/hl7-desanitize.sh "$LARRY_HOME/lib/hl7-desanitize.sh" fetch lib/each.sh "$LARRY_HOME/lib/each.sh" fetch lib/each-site.sh "$LARRY_HOME/lib/each-site.sh" fetch lib/len2nl.sh "$LARRY_HOME/lib/len2nl.sh" fetch lib/csv-to-table.sh "$LARRY_HOME/lib/csv-to-table.sh" fetch lib/table-to-csv.sh "$LARRY_HOME/lib/table-to-csv.sh" fetch lib/nc-parse.sh "$LARRY_HOME/lib/nc-parse.sh" fetch lib/nc-inbound.sh "$LARRY_HOME/lib/nc-inbound.sh" fetch lib/nc-make-jump.sh "$LARRY_HOME/lib/nc-make-jump.sh" fetch lib/hl7-field.sh "$LARRY_HOME/lib/hl7-field.sh" fetch lib/nc-msgs.sh "$LARRY_HOME/lib/nc-msgs.sh" fetch lib/nc-document.sh "$LARRY_HOME/lib/nc-document.sh" fetch lib/nc-diff-interface.sh "$LARRY_HOME/lib/nc-diff-interface.sh" fetch lib/nc-find.sh "$LARRY_HOME/lib/nc-find.sh" fetch lib/nc-insert-protocol.sh "$LARRY_HOME/lib/nc-insert-protocol.sh" fetch lib/hl7-diff.sh "$LARRY_HOME/lib/hl7-diff.sh" fetch lib/nc-regression.sh "$LARRY_HOME/lib/nc-regression.sh" fetch lib/journal.sh "$LARRY_HOME/lib/journal.sh" fetch VERSION "$LARRY_HOME/VERSION" fetch MANUAL.md "$LARRY_HOME/MANUAL.md" chmod +x "$LARRY_HOME/larry.sh" "$LARRY_HOME/larry-tunnel.sh" "$LARRY_HOME/larry-rollback.sh" "$LARRY_HOME/larry-auth.sh" "$LARRY_HOME/lib/"*.sh # ───────────────────────────────────────────────────────────────────────────── # jq fallback — download static binary into $LARRY_HOME/bin/ if missing # ───────────────────────────────────────────────────────────────────────────── if ! command -v jq >/dev/null 2>&1 && [ ! -x "$LARRY_HOME/bin/jq" ]; then say "jq not found — fetching static binary into $LARRY_HOME/bin/jq" JQ_BASE="https://github.com/jqlang/jq/releases/download/jq-1.7.1" JQ_URL="" case "$PLATFORM/$ARCH_NORM" in linux/amd64) JQ_URL="$JQ_BASE/jq-linux-amd64" ;; linux/arm64) JQ_URL="$JQ_BASE/jq-linux-arm64" ;; linux/i386) JQ_URL="$JQ_BASE/jq-linux-i386" ;; darwin/amd64) JQ_URL="$JQ_BASE/jq-macos-amd64" ;; darwin/arm64) JQ_URL="$JQ_BASE/jq-macos-arm64" ;; windows-cygwin/amd64) JQ_URL="$JQ_BASE/jq-windows-amd64.exe" ;; windows-cygwin/i386) JQ_URL="$JQ_BASE/jq-windows-i386.exe" ;; *) warn "no jq binary known for $PLATFORM/$ARCH_NORM — install jq manually" ;; esac if [ -n "$JQ_URL" ]; then local_jq="$LARRY_HOME/bin/jq" case "$PLATFORM" in windows-cygwin) local_jq="$LARRY_HOME/bin/jq.exe" ;; esac if curl -fsSL --max-time 60 "$JQ_URL" -o "$local_jq"; then chmod +x "$local_jq" ok "jq -> $local_jq" else warn "could not download jq from $JQ_URL" fi fi else ok "jq available" fi # ───────────────────────────────────────────────────────────────────────────── # Drop a `larry` shim onto PATH (best-effort) # ───────────────────────────────────────────────────────────────────────────── mkdir -p "$LARRY_BIN_DIR" 2>/dev/null || true if [ -d "$LARRY_BIN_DIR" ] && [ -w "$LARRY_BIN_DIR" ]; then cat > "$LARRY_BIN_DIR/larry" <