Switches the canonical $LARRY_BASE_URL default from raw.githubusercontent.com
to the self-hosted Gitea mirror at git.bjnoela.com. GitHub stays in the
loop as $LARRY_BASE_URL_FALLBACK and is used automatically when the primary
fails (DNS, timeout, HTTP error, private repo).
What's new
- Origin defaults split into LARRY_BASE_URL (Gitea) +
LARRY_BASE_URL_FALLBACK (GitHub). Env vars still override either side.
- Every network call in self_update tries primary first, then fallback.
Emits "warn: gitea unreachable, falling back to github" on switch and
"warn: self-update skipped (both origins unreachable)" if both fail.
- New /origin slash-command family:
/origin — show current primary/fallback + which served last
/origin gitea — pin to Gitea (default state)
/origin github — swap so GitHub is primary, Gitea fallback
/origin auto — clear pin, revert to defaults
/origin <https://...> — pin to an arbitrary HTTPS base URL
Pin is persisted to $LARRY_HOME/.origin and re-read on next launch.
- Status line picks up a light origin badge when state is non-default
("github" pinned, "custom" pinned, or "gitea→github" on failover).
- install-larry.sh mirrors the same primary→fallback fetch logic so
first-contact installs still work even if Gitea is unreachable.
ACTION REQUIRED — Bryan, before this commit's auto-update path becomes
live you must set git.bjnoela.com/bryan/cloverleaf-larry repo visibility
to PUBLIC. Gitea defaults to private; until you toggle it, every client
will silently fall back to GitHub. Verify by running, from any box:
curl -fsSI https://git.bjnoela.com/bryan/cloverleaf-larry/raw/branch/main/VERSION
A 200 with the published VERSION means clients hit Gitea; a 404/403 means
they still ride the GitHub fallback.
Don't break
- v0.7.1 status-line position (between turns)
- v0.7.0 HL7 completion, mouse mode
- v0.6.9 status line state tracking, header capture
- v0.6.7 streaming, @file, slash completion, persistent history
- v0.6.6 CR-strip + slash TAB
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
229 lines
13 KiB
Bash
Executable File
229 lines
13 KiB
Bash
Executable File
#!/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 <BASE_URL>/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 (v0.7.2+): self-hosted Gitea at git.bjnoela.com is primary;
|
|
# bojj27/cloverleaf-larry on GitHub is the unauthenticated fallback used when
|
|
# Gitea is unreachable (DNS failure, repo set to private, server down, etc.).
|
|
# Override either via env if you fork or mirror elsewhere.
|
|
#
|
|
# IMPORTANT: the Gitea repo must be set to PUBLIC for unauthenticated raw-URL
|
|
# reads to succeed. Until Bryan toggles repo visibility, the installer (and
|
|
# auto-update) will silently fall back to GitHub.
|
|
LARRY_BASE_URL="${LARRY_BASE_URL:-https://git.bjnoela.com/bryan/cloverleaf-larry/raw/branch/main}"
|
|
LARRY_BASE_URL_FALLBACK="${LARRY_BASE_URL_FALLBACK:-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=""
|
|
|
|
# v0.7.2: install-larry.sh is the FIRST contact with the origin — it runs
|
|
# before larry.sh exists, so it has to do its own primary→fallback dance.
|
|
# Mirrors larry.sh's _fetch_with_fallback behavior; on every fetch, try
|
|
# primary first, then fallback if the primary curl exits non-zero or the
|
|
# resulting file is empty. Records the winning origin in $LAST_FETCH_ORIGIN
|
|
# so the post-install summary can tell Bryan which side served the install.
|
|
LAST_FETCH_ORIGIN=""
|
|
|
|
fetch() {
|
|
# $1 = remote relative path, $2 = local destination
|
|
if [ -n "$LARRY_BASE_URL" ]; then
|
|
say "fetching $1"
|
|
if curl -fsSL --max-time 30 "$LARRY_BASE_URL/$1" -o "$2" 2>/dev/null && [ -s "$2" ]; then
|
|
LAST_FETCH_ORIGIN="primary"
|
|
ok "$2"
|
|
return 0
|
|
fi
|
|
rm -f "$2"
|
|
if [ -n "${LARRY_BASE_URL_FALLBACK:-}" ] && [ "$LARRY_BASE_URL_FALLBACK" != "$LARRY_BASE_URL" ]; then
|
|
warn "primary unreachable for $1 — trying fallback"
|
|
if curl -fsSL --max-time 30 "$LARRY_BASE_URL_FALLBACK/$1" -o "$2" 2>/dev/null && [ -s "$2" ]; then
|
|
LAST_FETCH_ORIGIN="fallback"
|
|
ok "$2 (via fallback)"
|
|
return 0
|
|
fi
|
|
rm -f "$2"
|
|
fi
|
|
die "failed to fetch $1 from primary or fallback"
|
|
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/ssh-helper.sh "$LARRY_HOME/lib/ssh-helper.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-engine.sh "$LARRY_HOME/lib/nc-engine.sh"
|
|
fetch lib/nc-status.sh "$LARRY_HOME/lib/nc-status.sh"
|
|
fetch lib/nc-table.sh "$LARRY_HOME/lib/nc-table.sh"
|
|
fetch lib/nc-xlate.sh "$LARRY_HOME/lib/nc-xlate.sh"
|
|
fetch lib/nc-smat-diff.sh "$LARRY_HOME/lib/nc-smat-diff.sh"
|
|
fetch lib/nc-create-thread.sh "$LARRY_HOME/lib/nc-create-thread.sh"
|
|
fetch lib/nc-tclgen.sh "$LARRY_HOME/lib/nc-tclgen.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/hl7-schema.sh "$LARRY_HOME/lib/hl7-schema.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" <<EOF
|
|
#!/usr/bin/env bash
|
|
# Auto-generated by install-larry.sh
|
|
export LARRY_HOME="${LARRY_HOME}"
|
|
exec "${LARRY_HOME}/larry.sh" "\$@"
|
|
EOF
|
|
chmod +x "$LARRY_BIN_DIR/larry"
|
|
ok "shim: $LARRY_BIN_DIR/larry"
|
|
case ":$PATH:" in
|
|
*":$LARRY_BIN_DIR:"*) : ;;
|
|
*) warn "$LARRY_BIN_DIR is not on PATH — add 'export PATH=\"$LARRY_BIN_DIR:\$PATH\"' to your shell rc" ;;
|
|
esac
|
|
else
|
|
warn "cannot write to $LARRY_BIN_DIR — invoke larry directly as: $LARRY_HOME/larry.sh"
|
|
fi
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Done
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
echo ""
|
|
say "install complete (no system changes were made; everything lives under $LARRY_HOME)"
|
|
if [ "$LAST_FETCH_ORIGIN" = "fallback" ]; then
|
|
warn "files were served from the FALLBACK origin (primary unreachable)."
|
|
warn " primary : $LARRY_BASE_URL"
|
|
warn " fallback: $LARRY_BASE_URL_FALLBACK"
|
|
warn " if you expected the primary to work, check repo visibility (Gitea repos default to private)."
|
|
fi
|
|
echo ""
|
|
echo "Next steps:"
|
|
echo " 1) export ANTHROPIC_API_KEY=sk-ant-... (or larry will prompt on first run)"
|
|
echo " 2) larry (or $LARRY_HOME/larry.sh)"
|
|
echo " 3) larry /path/to/cloverleaf/site_root (to start with a working dir)"
|
|
echo ""
|
|
echo "Reverse SSH tunnel (optional, run in another shell or backgrounded):"
|
|
echo " $LARRY_HOME/larry-tunnel.sh --serveo # zero-config"
|
|
echo " $LARRY_HOME/larry-tunnel.sh --hop=user@bjnoela.com:22 # your hop"
|
|
echo ""
|